kanban-lite 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +9 -0
- package/.github/workflows/ci.yml +59 -0
- package/.github/workflows/release.yml +75 -0
- package/.prettierignore +6 -0
- package/.prettierrc.yaml +4 -0
- package/.vscode/extensions.json +3 -0
- package/.vscode/launch.json +17 -0
- package/.vscode/settings.json +21 -0
- package/.vscode/tasks.json +22 -0
- package/.vscodeignore +11 -0
- package/CHANGELOG.md +184 -0
- package/CLAUDE.md +58 -0
- package/CONTRIBUTING.md +114 -0
- package/LICENSE +22 -0
- package/README.md +482 -0
- package/SKILL.md +237 -0
- package/dist/cli.js +8716 -0
- package/dist/extension.js +8463 -0
- package/dist/mcp-server.js +1327 -0
- package/dist/standalone-webview/icons-Dx9MGYqN.js +180 -0
- package/dist/standalone-webview/icons-Dx9MGYqN.js.map +1 -0
- package/dist/standalone-webview/index.js +85 -0
- package/dist/standalone-webview/index.js.map +1 -0
- package/dist/standalone-webview/react-vendor-DkYdDBET.js +25 -0
- package/dist/standalone-webview/react-vendor-DkYdDBET.js.map +1 -0
- package/dist/standalone-webview/style.css +1 -0
- package/dist/standalone.js +7513 -0
- package/dist/webview/icons-Dx9MGYqN.js +180 -0
- package/dist/webview/icons-Dx9MGYqN.js.map +1 -0
- package/dist/webview/index.js +85 -0
- package/dist/webview/index.js.map +1 -0
- package/dist/webview/react-vendor-DkYdDBET.js +25 -0
- package/dist/webview/react-vendor-DkYdDBET.js.map +1 -0
- package/dist/webview/style.css +1 -0
- package/docs/images/board-overview.png +0 -0
- package/docs/images/editor-view.png +0 -0
- package/docs/plans/2026-02-20-kanban-json-config-design.md +74 -0
- package/docs/plans/2026-02-20-kanban-json-config.md +690 -0
- package/eslint.config.mjs +31 -0
- package/package.json +161 -0
- package/postcss.config.js +6 -0
- package/resources/icon-light.png +0 -0
- package/resources/icon-light.svg +105 -0
- package/resources/icon.png +0 -0
- package/resources/icon.svg +105 -0
- package/resources/kanban-dark.svg +21 -0
- package/resources/kanban-light.svg +21 -0
- package/resources/kanban.svg +21 -0
- package/src/cli/index.ts +846 -0
- package/src/extension/FeatureHeaderProvider.ts +370 -0
- package/src/extension/KanbanPanel.ts +973 -0
- package/src/extension/SidebarViewProvider.ts +507 -0
- package/src/extension/featureFileUtils.ts +82 -0
- package/src/extension/index.ts +234 -0
- package/src/mcp-server/index.ts +632 -0
- package/src/sdk/KanbanSDK.ts +349 -0
- package/src/sdk/__tests__/KanbanSDK.test.ts +468 -0
- package/src/sdk/__tests__/parser.test.ts +170 -0
- package/src/sdk/fileUtils.ts +76 -0
- package/src/sdk/index.ts +6 -0
- package/src/sdk/parser.ts +70 -0
- package/src/sdk/types.ts +15 -0
- package/src/shared/config.ts +113 -0
- package/src/shared/editorTypes.ts +14 -0
- package/src/shared/types.ts +120 -0
- package/src/standalone/__tests__/server.integration.test.ts +1916 -0
- package/src/standalone/__tests__/webhooks.test.ts +357 -0
- package/src/standalone/fileUtils.ts +70 -0
- package/src/standalone/index.ts +71 -0
- package/src/standalone/server.ts +1046 -0
- package/src/standalone/webhooks.ts +135 -0
- package/src/webview/App.tsx +469 -0
- package/src/webview/assets/main.css +329 -0
- package/src/webview/assets/standalone-theme.css +130 -0
- package/src/webview/components/ColumnDialog.tsx +119 -0
- package/src/webview/components/CreateFeatureDialog.tsx +524 -0
- package/src/webview/components/DatePicker.tsx +185 -0
- package/src/webview/components/FeatureCard.tsx +186 -0
- package/src/webview/components/FeatureEditor.tsx +623 -0
- package/src/webview/components/KanbanBoard.tsx +144 -0
- package/src/webview/components/KanbanColumn.tsx +159 -0
- package/src/webview/components/MarkdownEditor.tsx +291 -0
- package/src/webview/components/PrioritySelect.tsx +39 -0
- package/src/webview/components/QuickAddInput.tsx +72 -0
- package/src/webview/components/SettingsPanel.tsx +284 -0
- package/src/webview/components/Toolbar.tsx +175 -0
- package/src/webview/components/UndoToast.tsx +70 -0
- package/src/webview/index.html +12 -0
- package/src/webview/lib/utils.ts +6 -0
- package/src/webview/main.tsx +11 -0
- package/src/webview/standalone-main.tsx +13 -0
- package/src/webview/standalone-shim.ts +132 -0
- package/src/webview/standalone.html +12 -0
- package/src/webview/store/index.ts +241 -0
- package/tailwind.config.js +53 -0
- package/tsconfig.json +22 -0
- package/vite.config.ts +36 -0
- package/vite.standalone.config.ts +62 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import * as vscode from 'vscode'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import type { FeatureFrontmatter, EditorExtensionMessage, EditorWebviewMessage } from '../shared/editorTypes'
|
|
4
|
+
import type { FeatureStatus, Priority } from '../shared/types'
|
|
5
|
+
import { readConfig, CONFIG_FILENAME } from '../shared/config'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Provides a webview panel that shows feature metadata (frontmatter) as a header.
|
|
9
|
+
* The actual markdown editing is done by VSCode's native text editor.
|
|
10
|
+
*/
|
|
11
|
+
export class FeatureHeaderProvider implements vscode.WebviewViewProvider {
|
|
12
|
+
public static readonly viewType = 'kanban-lite.featureHeader'
|
|
13
|
+
|
|
14
|
+
private _view?: vscode.WebviewView
|
|
15
|
+
private _currentDocument?: vscode.TextDocument
|
|
16
|
+
private _disposables: vscode.Disposable[] = []
|
|
17
|
+
|
|
18
|
+
constructor(private readonly _extensionUri: vscode.Uri) {}
|
|
19
|
+
|
|
20
|
+
public static register(context: vscode.ExtensionContext): vscode.Disposable {
|
|
21
|
+
const provider = new FeatureHeaderProvider(context.extensionUri)
|
|
22
|
+
|
|
23
|
+
const disposables: vscode.Disposable[] = []
|
|
24
|
+
|
|
25
|
+
// Register the webview view provider
|
|
26
|
+
disposables.push(
|
|
27
|
+
vscode.window.registerWebviewViewProvider(
|
|
28
|
+
FeatureHeaderProvider.viewType,
|
|
29
|
+
provider,
|
|
30
|
+
{
|
|
31
|
+
webviewOptions: {
|
|
32
|
+
retainContextWhenHidden: true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
// Listen for active editor changes
|
|
39
|
+
disposables.push(
|
|
40
|
+
vscode.window.onDidChangeActiveTextEditor(editor => {
|
|
41
|
+
provider._onActiveEditorChanged(editor)
|
|
42
|
+
})
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
// Listen for document changes
|
|
46
|
+
disposables.push(
|
|
47
|
+
vscode.workspace.onDidChangeTextDocument(e => {
|
|
48
|
+
provider._onDocumentChanged(e)
|
|
49
|
+
})
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
// Listen for .kanban.json changes
|
|
53
|
+
const workspaceFolders = vscode.workspace.workspaceFolders
|
|
54
|
+
if (workspaceFolders) {
|
|
55
|
+
const root = workspaceFolders[0].uri.fsPath
|
|
56
|
+
const configPattern = new vscode.RelativePattern(root, CONFIG_FILENAME)
|
|
57
|
+
const configWatcher = vscode.workspace.createFileSystemWatcher(configPattern)
|
|
58
|
+
|
|
59
|
+
const handleConfigChange = () => {
|
|
60
|
+
provider._onActiveEditorChanged(vscode.window.activeTextEditor)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
configWatcher.onDidChange(handleConfigChange)
|
|
64
|
+
configWatcher.onDidCreate(handleConfigChange)
|
|
65
|
+
configWatcher.onDidDelete(handleConfigChange)
|
|
66
|
+
disposables.push(configWatcher)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return vscode.Disposable.from(...disposables)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public resolveWebviewView(
|
|
73
|
+
webviewView: vscode.WebviewView,
|
|
74
|
+
_context: vscode.WebviewViewResolveContext,
|
|
75
|
+
_token: vscode.CancellationToken
|
|
76
|
+
): void {
|
|
77
|
+
this._view = webviewView
|
|
78
|
+
|
|
79
|
+
webviewView.webview.options = {
|
|
80
|
+
enableScripts: true,
|
|
81
|
+
localResourceRoots: [
|
|
82
|
+
vscode.Uri.joinPath(this._extensionUri, 'dist'),
|
|
83
|
+
vscode.Uri.joinPath(this._extensionUri, 'dist', 'webview')
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview)
|
|
88
|
+
|
|
89
|
+
// Handle messages from the webview
|
|
90
|
+
webviewView.webview.onDidReceiveMessage(async (message: EditorWebviewMessage) => {
|
|
91
|
+
switch (message.type) {
|
|
92
|
+
case 'ready':
|
|
93
|
+
this._updateViewForCurrentEditor()
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
case 'frontmatterUpdate':
|
|
97
|
+
await this._updateFrontmatter(message.frontmatter)
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
case 'requestSave':
|
|
101
|
+
if (this._currentDocument) {
|
|
102
|
+
await this._currentDocument.save()
|
|
103
|
+
}
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
case 'startWithAI': {
|
|
107
|
+
if (!this._currentDocument) return
|
|
108
|
+
await this._currentDocument.save()
|
|
109
|
+
|
|
110
|
+
const fullText = this._currentDocument.getText()
|
|
111
|
+
const { frontmatter: fm, content: docContent } = this._parseDocument(fullText)
|
|
112
|
+
|
|
113
|
+
// Parse title from the first # heading in content
|
|
114
|
+
const titleMatch = docContent.match(/^#\s+(.+)$/m)
|
|
115
|
+
const title = titleMatch ? titleMatch[1].trim() : 'Untitled'
|
|
116
|
+
|
|
117
|
+
const labels = fm.labels.length > 0 ? ` [${fm.labels.join(', ')}]` : ''
|
|
118
|
+
const description = docContent.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim()
|
|
119
|
+
const shortDesc = description.length > 200 ? description.substring(0, 200) + '...' : description
|
|
120
|
+
|
|
121
|
+
const prompt = `Implement this feature: "${title}" (${fm.priority} priority)${labels}. ${shortDesc} See full details in: ${this._currentDocument.uri.fsPath}`
|
|
122
|
+
|
|
123
|
+
const agent = message.agent || 'claude'
|
|
124
|
+
const permissionMode = message.permissionMode || 'default'
|
|
125
|
+
|
|
126
|
+
let command: string
|
|
127
|
+
const escapedPrompt = prompt.replace(/"/g, '\\"')
|
|
128
|
+
|
|
129
|
+
switch (agent) {
|
|
130
|
+
case 'claude': {
|
|
131
|
+
const permissionFlag = permissionMode !== 'default' ? ` --permission-mode ${permissionMode}` : ''
|
|
132
|
+
command = `claude${permissionFlag} "${escapedPrompt}"`
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
case 'codex': {
|
|
136
|
+
const approvalMap: Record<string, string> = {
|
|
137
|
+
'default': 'suggest',
|
|
138
|
+
'plan': 'suggest',
|
|
139
|
+
'acceptEdits': 'auto-edit',
|
|
140
|
+
'bypassPermissions': 'full-auto'
|
|
141
|
+
}
|
|
142
|
+
const approvalMode = approvalMap[permissionMode] || 'suggest'
|
|
143
|
+
command = `codex --approval-mode ${approvalMode} "${escapedPrompt}"`
|
|
144
|
+
break
|
|
145
|
+
}
|
|
146
|
+
case 'opencode': {
|
|
147
|
+
command = `opencode "${escapedPrompt}"`
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
default:
|
|
151
|
+
command = `claude "${escapedPrompt}"`
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const agentNames: Record<string, string> = {
|
|
155
|
+
'claude': 'Claude Code',
|
|
156
|
+
'codex': 'Codex',
|
|
157
|
+
'opencode': 'OpenCode'
|
|
158
|
+
}
|
|
159
|
+
const terminal = vscode.window.createTerminal({
|
|
160
|
+
name: agentNames[agent] || 'AI Agent',
|
|
161
|
+
cwd: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
|
|
162
|
+
})
|
|
163
|
+
terminal.show()
|
|
164
|
+
terminal.sendText(command)
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// Update view when it becomes visible
|
|
171
|
+
webviewView.onDidChangeVisibility(() => {
|
|
172
|
+
if (webviewView.visible) {
|
|
173
|
+
this._updateViewForCurrentEditor()
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Check current editor
|
|
178
|
+
this._updateViewForCurrentEditor()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private _onActiveEditorChanged(editor: vscode.TextEditor | undefined): void {
|
|
182
|
+
if (!editor) {
|
|
183
|
+
this._currentDocument = undefined
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Only track .md files in the features directory (including status subfolders)
|
|
188
|
+
const uri = editor.document.uri
|
|
189
|
+
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
|
|
190
|
+
if (!workspaceRoot) return
|
|
191
|
+
const kanbanConfig = readConfig(workspaceRoot)
|
|
192
|
+
const fullFeaturesDir = path.join(workspaceRoot, kanbanConfig.featuresDirectory)
|
|
193
|
+
if (uri.fsPath.endsWith('.md') && uri.fsPath.startsWith(fullFeaturesDir + path.sep)) {
|
|
194
|
+
this._currentDocument = editor.document
|
|
195
|
+
this._updateViewForCurrentEditor()
|
|
196
|
+
} else {
|
|
197
|
+
this._currentDocument = undefined
|
|
198
|
+
this._hideView()
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private _onDocumentChanged(e: vscode.TextDocumentChangeEvent): void {
|
|
203
|
+
if (this._currentDocument && e.document.uri.toString() === this._currentDocument.uri.toString()) {
|
|
204
|
+
this._updateViewForCurrentEditor()
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private _updateViewForCurrentEditor(): void {
|
|
209
|
+
if (!this._view || !this._currentDocument) return
|
|
210
|
+
|
|
211
|
+
const { frontmatter } = this._parseDocument(this._currentDocument.getText())
|
|
212
|
+
const fileName = this._currentDocument.uri.path.split('/').pop()?.replace(/\.md$/, '') || 'Untitled'
|
|
213
|
+
|
|
214
|
+
const message: EditorExtensionMessage = {
|
|
215
|
+
type: 'init',
|
|
216
|
+
content: '', // Not used anymore
|
|
217
|
+
frontmatter,
|
|
218
|
+
fileName
|
|
219
|
+
}
|
|
220
|
+
this._view.webview.postMessage(message)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private _hideView(): void {
|
|
224
|
+
// Send empty state to hide content
|
|
225
|
+
if (this._view) {
|
|
226
|
+
this._view.webview.postMessage({
|
|
227
|
+
type: 'init',
|
|
228
|
+
content: '',
|
|
229
|
+
frontmatter: null,
|
|
230
|
+
fileName: ''
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private async _updateFrontmatter(frontmatter: FeatureFrontmatter): Promise<void> {
|
|
236
|
+
if (!this._currentDocument) return
|
|
237
|
+
|
|
238
|
+
const { content } = this._parseDocument(this._currentDocument.getText())
|
|
239
|
+
const newText = this._serializeDocument(frontmatter, content)
|
|
240
|
+
|
|
241
|
+
const edit = new vscode.WorkspaceEdit()
|
|
242
|
+
edit.replace(
|
|
243
|
+
this._currentDocument.uri,
|
|
244
|
+
new vscode.Range(0, 0, this._currentDocument.lineCount, 0),
|
|
245
|
+
newText
|
|
246
|
+
)
|
|
247
|
+
await vscode.workspace.applyEdit(edit)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private _parseDocument(text: string): { frontmatter: FeatureFrontmatter; content: string } {
|
|
251
|
+
text = text.replace(/\r\n/g, '\n')
|
|
252
|
+
const frontmatterMatch = text.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/)
|
|
253
|
+
|
|
254
|
+
if (!frontmatterMatch) {
|
|
255
|
+
return {
|
|
256
|
+
frontmatter: this._getDefaultFrontmatter(),
|
|
257
|
+
content: text
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const frontmatterText = frontmatterMatch[1]
|
|
262
|
+
const content = frontmatterMatch[2] || ''
|
|
263
|
+
|
|
264
|
+
const getValue = (key: string): string => {
|
|
265
|
+
const match = frontmatterText.match(new RegExp(`^${key}:\\s*(.*)$`, 'm'))
|
|
266
|
+
if (!match) return ''
|
|
267
|
+
const value = match[1].trim().replace(/^["']|["']$/g, '')
|
|
268
|
+
return value === 'null' ? '' : value
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const getArrayValue = (key: string): string[] => {
|
|
272
|
+
const match = frontmatterText.match(new RegExp(`^${key}:\\s*\\[([^\\]]*)\\]`, 'm'))
|
|
273
|
+
if (!match) return []
|
|
274
|
+
return match[1].split(',').map(s => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const frontmatter: FeatureFrontmatter = {
|
|
278
|
+
id: getValue('id') || 'unknown',
|
|
279
|
+
status: (getValue('status') as FeatureStatus) || 'backlog',
|
|
280
|
+
priority: (getValue('priority') as Priority) || 'medium',
|
|
281
|
+
assignee: getValue('assignee') || null,
|
|
282
|
+
dueDate: getValue('dueDate') || null,
|
|
283
|
+
created: getValue('created') || new Date().toISOString(),
|
|
284
|
+
modified: getValue('modified') || new Date().toISOString(),
|
|
285
|
+
completedAt: getValue('completedAt') || null,
|
|
286
|
+
labels: getArrayValue('labels'),
|
|
287
|
+
attachments: getArrayValue('attachments'),
|
|
288
|
+
order: getValue('order') || 'a0'
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return { frontmatter, content: content.trim() }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private _getDefaultFrontmatter(): FeatureFrontmatter {
|
|
295
|
+
const now = new Date().toISOString()
|
|
296
|
+
return {
|
|
297
|
+
id: 'unknown',
|
|
298
|
+
status: 'backlog',
|
|
299
|
+
priority: 'medium',
|
|
300
|
+
assignee: null,
|
|
301
|
+
dueDate: null,
|
|
302
|
+
created: now,
|
|
303
|
+
modified: now,
|
|
304
|
+
completedAt: null,
|
|
305
|
+
labels: [],
|
|
306
|
+
attachments: [],
|
|
307
|
+
order: 'a0'
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private _serializeDocument(frontmatter: FeatureFrontmatter, content: string): string {
|
|
312
|
+
const updatedFrontmatter = {
|
|
313
|
+
...frontmatter,
|
|
314
|
+
modified: new Date().toISOString()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const frontmatterLines = [
|
|
318
|
+
'---',
|
|
319
|
+
`id: "${updatedFrontmatter.id}"`,
|
|
320
|
+
`status: "${updatedFrontmatter.status}"`,
|
|
321
|
+
`priority: "${updatedFrontmatter.priority}"`,
|
|
322
|
+
`assignee: ${updatedFrontmatter.assignee ? `"${updatedFrontmatter.assignee}"` : 'null'}`,
|
|
323
|
+
`dueDate: ${updatedFrontmatter.dueDate ? `"${updatedFrontmatter.dueDate}"` : 'null'}`,
|
|
324
|
+
`created: "${updatedFrontmatter.created}"`,
|
|
325
|
+
`modified: "${updatedFrontmatter.modified}"`,
|
|
326
|
+
`completedAt: ${updatedFrontmatter.completedAt ? `"${updatedFrontmatter.completedAt}"` : 'null'}`,
|
|
327
|
+
`labels: [${frontmatter.labels.map((l: string) => `"${l}"`).join(', ')}]`,
|
|
328
|
+
`order: "${frontmatter.order}"`,
|
|
329
|
+
'---',
|
|
330
|
+
''
|
|
331
|
+
].join('\n')
|
|
332
|
+
|
|
333
|
+
return frontmatterLines + content
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private _getNonce(): string {
|
|
337
|
+
let text = ''
|
|
338
|
+
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
339
|
+
for (let i = 0; i < 32; i++) {
|
|
340
|
+
text += possible.charAt(Math.floor(Math.random() * possible.length))
|
|
341
|
+
}
|
|
342
|
+
return text
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private _getHtmlForWebview(webview: vscode.Webview): string {
|
|
346
|
+
const scriptUri = webview.asWebviewUri(
|
|
347
|
+
vscode.Uri.joinPath(this._extensionUri, 'dist', 'webview', 'editor.js')
|
|
348
|
+
)
|
|
349
|
+
const styleUri = webview.asWebviewUri(
|
|
350
|
+
vscode.Uri.joinPath(this._extensionUri, 'dist', 'webview', 'style.css')
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
const nonce = this._getNonce()
|
|
354
|
+
|
|
355
|
+
return `<!DOCTYPE html>
|
|
356
|
+
<html lang="en">
|
|
357
|
+
<head>
|
|
358
|
+
<meta charset="UTF-8">
|
|
359
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
360
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}';">
|
|
361
|
+
<link href="${styleUri}" rel="stylesheet">
|
|
362
|
+
<title>Feature Header</title>
|
|
363
|
+
</head>
|
|
364
|
+
<body>
|
|
365
|
+
<div id="root"></div>
|
|
366
|
+
<script type="module" nonce="${nonce}" src="${scriptUri}"></script>
|
|
367
|
+
</body>
|
|
368
|
+
</html>`
|
|
369
|
+
}
|
|
370
|
+
}
|