✨ feat(vscode): 代码阅读入口、文件树与预览编辑器
This commit is contained in:
1 parent
4d34b8c715
commit
2b823a32ca
24 files changed
+768
-72
No files matched your search
@@ -11,9 +11,6 @@ import type { SessionTabKind } from './handlers/terminals'
|
||||
import type { IssueOwnershipFacts } from './issueOwnership'
|
||||
import type { KanbanScope } from './kanbanScope'
|
||||
import type { ExtensionToWebview, WebviewToExtension } from './messages'
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import * as fs from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
import { env, TabInputTerminal, ThemeColor, Uri, ViewColumn, window, workspace } from 'vscode'
|
||||
import { deleteToken, getToken } from '../auth/secrets'
|
||||
import { importPluginIntoFigma } from '../figma/importPlugin'
|
||||
@@ -28,6 +25,7 @@ import { logger } from '../logging/logger'
|
||||
import { annotateMissingSessionIds } from '../sessions/sessionFiles'
|
||||
import { getEffectiveCommitProfilePath, getEffectivePrDiffSummaryProfilePath, getSettings } from '../settings/store'
|
||||
import { webhookCoordinator } from '../webhook/coordinator'
|
||||
import { makeNonce, webviewHtml, webviewUiRoot } from '../webview/html'
|
||||
import { loadYouTrackIssues } from '../youtrack/issueLoader'
|
||||
import * as handoffFlow from './handlers/handoffFlow'
|
||||
import * as issues from './handlers/issues'
|
||||
@@ -240,7 +238,7 @@ export class KanbanWebviewPanel {
|
||||
this.panel.webview.options = {
|
||||
enableScripts: true,
|
||||
localResourceRoots: [
|
||||
Uri.joinPath(this.context.extensionUri, 'dist', 'webview-ui'),
|
||||
webviewUiRoot(this.context.extensionUri),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -347,7 +345,7 @@ export class KanbanWebviewPanel {
|
||||
enableScripts: true,
|
||||
retainContextWhenHidden: true,
|
||||
localResourceRoots: [
|
||||
Uri.joinPath(context.extensionUri, 'dist', 'webview-ui'),
|
||||
webviewUiRoot(context.extensionUri),
|
||||
],
|
||||
},
|
||||
)
|
||||
@@ -1308,56 +1306,14 @@ export class KanbanWebviewPanel {
|
||||
}
|
||||
|
||||
private buildHtml(): string {
|
||||
const distRoot = Uri.joinPath(this.context.extensionUri, 'dist', 'webview-ui')
|
||||
const indexPath = path.join(distRoot.fsPath, 'index.html')
|
||||
let html = fs.readFileSync(indexPath, 'utf-8')
|
||||
|
||||
const nonce = makeNonce()
|
||||
const cspSource = this.panel.webview.cspSource
|
||||
|
||||
// 重写 src 和 href 的相对路径为 webview URI。vite 产物是固定名(无 hash),
|
||||
// webview 按 URL 缓存资源——必须按文件 mtime 加 ?v= 版本号,否则 reload 后
|
||||
// 仍会跑缓存里的旧 index.js / index.css。
|
||||
html = html.replace(/(src|href)="(\/[^"]+|[^"/][^"]*)"/g, (_m, attr, p) => {
|
||||
const cleaned = p.replace(/^\.?\//, '')
|
||||
const fileUri = Uri.joinPath(distRoot, cleaned)
|
||||
let version = ''
|
||||
try {
|
||||
version = `?v=${fs.statSync(fileUri.fsPath).mtimeMs}`
|
||||
}
|
||||
catch {
|
||||
// 资源文件不存在则不加版本号
|
||||
}
|
||||
const uri = this.panel.webview.asWebviewUri(fileUri)
|
||||
return `${attr}="${uri}${version}"`
|
||||
})
|
||||
|
||||
// 给所有 <script> 标签注入 nonce
|
||||
html = html.replace(/<script(\s[^>]*)?>/g, (_m, attrs) => {
|
||||
const a = attrs ?? ''
|
||||
return `<script nonce="${nonce}"${a}>`
|
||||
})
|
||||
|
||||
// 注入 CSP
|
||||
const csp = `default-src 'none'; img-src ${cspSource} data:; style-src ${cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}'; font-src ${cspSource};`
|
||||
html = html.replace(
|
||||
/<head>/,
|
||||
`<head>\n <meta http-equiv="Content-Security-Policy" content="${csp}">`,
|
||||
)
|
||||
|
||||
const html = webviewHtml(this.panel.webview, this.context.extensionUri, 'index.html', nonce)
|
||||
const snapshot = parseKanbanSnapshot(this.context.workspaceState.get(KANBAN_SNAPSHOT_KEY))
|
||||
if (snapshot) {
|
||||
html = html.replace(
|
||||
'</body>',
|
||||
` ${snapshotScriptTag(nonce, snapshot)}\n </body>`,
|
||||
)
|
||||
}
|
||||
|
||||
return html
|
||||
if (!snapshot)
|
||||
return html
|
||||
return html.replace(
|
||||
'</body>',
|
||||
` ${snapshotScriptTag(nonce, snapshot)}\n </body>`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// internal: handler 模块访问
|
||||
export function makeNonce(): string {
|
||||
return randomBytes(16).toString('base64').replace(/[+/=]/g, '')
|
||||
}
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
import { logger } from '../../logging/logger'
|
||||
import { annotateMissingSessionIds, defaultCodexSessionsDir } from '../../sessions/sessionFiles'
|
||||
import { getSettings } from '../../settings/store'
|
||||
import { makeNonce } from '../KanbanPanel'
|
||||
import { makeNonce } from '../../webview/html'
|
||||
import { cleanupFeatureBranch } from './branchCleanup'
|
||||
import {
|
||||
canAcceptHandoff,
|
||||
|
||||
@@ -36,9 +36,9 @@ import { mergeStateJsonComment } from '../../gitea/stateJson'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { getEffectivePrDiffSummaryProfilePath, getSettings } from '../../settings/store'
|
||||
import { webhookCoordinator } from '../../webhook/coordinator'
|
||||
import { makeNonce } from '../../webview/html'
|
||||
import { pickRandomIssueColor, themeColorIdToIconUri } from '../issueColor'
|
||||
import { evaluateLockedReason } from '../issueLock'
|
||||
import { makeNonce } from '../KanbanPanel'
|
||||
import { cleanupFeatureBranch } from './branchCleanup'
|
||||
import { clearSessionConfirmText, clearSessionDoneText, SESSION_KINDS } from './sessionKinds'
|
||||
import * as sessions from './sessions'
|
||||
|
||||
@@ -24,7 +24,7 @@ import { readStateJsonComment } from '../../gitea/stateJson'
|
||||
import { readFileSummaries, readSummarySession, writeFileSummary, writeSummarySession } from '../../sessions/prFileSummaryStore'
|
||||
import { readConfirmedSigs, writeConfirmedSigs } from '../../sessions/prReviewStore'
|
||||
import { getEffectivePrDiffSummaryProfilePath } from '../../settings/store'
|
||||
import { makeNonce } from '../KanbanPanel'
|
||||
import { makeNonce } from '../../webview/html'
|
||||
|
||||
/**
|
||||
* 解析当前工作区的 Gitea 仓库与 token。三者任一缺失返回 undefined,
|
||||
|
||||
@@ -22,9 +22,9 @@ import { logger } from '../../logging/logger'
|
||||
import { claudeSessionFileExists, codexSessionFileExists, defaultCodexSessionsDir } from '../../sessions/sessionFiles'
|
||||
import { getSettings } from '../../settings/store'
|
||||
import { webhookCoordinator } from '../../webhook/coordinator'
|
||||
import { makeNonce } from '../../webview/html'
|
||||
import { decideAutoReviewPath } from '../autoReviewDecision'
|
||||
import { pickBrainstormContinuePath } from '../brainstormContinue'
|
||||
import { makeNonce } from '../KanbanPanel'
|
||||
import { cleanupFeatureBranch } from './branchCleanup'
|
||||
import { planResumeCwd, resumeTerminalName } from './resumeTarget'
|
||||
import { submitToSessionTerminal } from './terminals'
|
||||
|
||||
@@ -15,8 +15,8 @@ import { logger } from '../../logging/logger'
|
||||
import { readProfiles, writeProfiles } from '../../profiles/store'
|
||||
import { getEffectiveCommitProfilePath, getEffectivePrDiffSummaryProfilePath, getSettings, saveSettings } from '../../settings/store'
|
||||
import { webhookCoordinator } from '../../webhook/coordinator'
|
||||
import { makeNonce } from '../../webview/html'
|
||||
import { youtrackHost } from '../../youtrack/issueLoader'
|
||||
import { makeNonce } from '../KanbanPanel'
|
||||
|
||||
export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
|
||||
host: string
|
||||
|
||||
@@ -10,7 +10,7 @@ import { findEnvFiles, lockEnvFiles, unlockEnvFiles } from '../../files/envLock'
|
||||
import { missingGitIdentityKeys, readGitIdentity } from '../../git/identity'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { getEffectiveCommitProfilePath } from '../../settings/store'
|
||||
import { makeNonce } from '../KanbanPanel'
|
||||
import { makeNonce } from '../../webview/html'
|
||||
import { dismiss, toast } from './handoffFlow'
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from '../../git/worktreeHooks'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { getSettings } from '../../settings/store'
|
||||
import { makeNonce } from '../KanbanPanel'
|
||||
import { makeNonce } from '../../webview/html'
|
||||
import { cleanupFeatureBranch } from './branchCleanup'
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,11 +14,11 @@ import { ThemeIcon, window, workspace } from 'vscode'
|
||||
import { getYouTrackToken } from '../../auth/secrets'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { getSettings } from '../../settings/store'
|
||||
import { makeNonce } from '../../webview/html'
|
||||
import { applyCommand, listIssues, listProjects, resolvedStateCommand } from '../../youtrack/api'
|
||||
import { readImportedIds, writeImportedIds } from '../../youtrack/importStore'
|
||||
import { youtrackHost } from '../../youtrack/issueLoader'
|
||||
import { mergeStateComment } from '../../youtrack/stateComment'
|
||||
import { makeNonce } from '../KanbanPanel'
|
||||
|
||||
function toast(panel: KanbanWebviewPanel, level: 'info' | 'success' | 'error', message: string): void {
|
||||
panel.postMessage({ type: 'toast/show', id: makeNonce(), level, message, dismissOnTimer: 6000 })
|
||||
|
||||
Reference in new issue
Block a user