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,132 @@
|
|
|
1
|
+
// WebSocket bridge that provides acquireVsCodeApi() for standalone mode.
|
|
2
|
+
// Must be imported BEFORE App.tsx so the global is available at module load time.
|
|
3
|
+
|
|
4
|
+
const ws = new WebSocket(`ws://${window.location.host}/ws`)
|
|
5
|
+
const pendingMessages: string[] = []
|
|
6
|
+
let connected = false
|
|
7
|
+
|
|
8
|
+
ws.addEventListener('open', () => {
|
|
9
|
+
connected = true
|
|
10
|
+
for (const msg of pendingMessages) {
|
|
11
|
+
ws.send(msg)
|
|
12
|
+
}
|
|
13
|
+
pendingMessages.length = 0
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
ws.addEventListener('message', (event) => {
|
|
17
|
+
try {
|
|
18
|
+
const data = JSON.parse(event.data)
|
|
19
|
+
window.postMessage(data, '*')
|
|
20
|
+
} catch {
|
|
21
|
+
// ignore malformed messages
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
ws.addEventListener('close', () => {
|
|
26
|
+
connected = false
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// State persistence via sessionStorage
|
|
30
|
+
let savedState: unknown = null
|
|
31
|
+
try {
|
|
32
|
+
const stored = sessionStorage.getItem('kanban-standalone-state')
|
|
33
|
+
if (stored) savedState = JSON.parse(stored)
|
|
34
|
+
} catch {
|
|
35
|
+
// ignore
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- Standalone attachment handling ---
|
|
39
|
+
function handleAddAttachment(featureId: string) {
|
|
40
|
+
const input = document.createElement('input')
|
|
41
|
+
input.type = 'file'
|
|
42
|
+
input.multiple = true
|
|
43
|
+
input.style.display = 'none'
|
|
44
|
+
document.body.appendChild(input)
|
|
45
|
+
|
|
46
|
+
const cleanup = () => {
|
|
47
|
+
input.remove()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
input.onchange = async () => {
|
|
51
|
+
if (!input.files || input.files.length === 0) {
|
|
52
|
+
cleanup()
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
const files: { name: string; data: string }[] = []
|
|
56
|
+
for (const file of Array.from(input.files)) {
|
|
57
|
+
const buf = await file.arrayBuffer()
|
|
58
|
+
const bytes = new Uint8Array(buf)
|
|
59
|
+
// Encode in chunks to avoid stack overflow on large files
|
|
60
|
+
let binary = ''
|
|
61
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
62
|
+
binary += String.fromCharCode(bytes[i])
|
|
63
|
+
}
|
|
64
|
+
files.push({ name: file.name, data: btoa(binary) })
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
await fetch('/api/upload-attachment', {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: { 'Content-Type': 'application/json' },
|
|
70
|
+
body: JSON.stringify({ featureId, files })
|
|
71
|
+
})
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error('Failed to upload attachment:', err)
|
|
74
|
+
}
|
|
75
|
+
cleanup()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
input.click()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function handleOpenAttachment(featureId: string, attachment: string) {
|
|
82
|
+
const url = `/api/attachment?featureId=${encodeURIComponent(featureId)}&filename=${encodeURIComponent(attachment)}`
|
|
83
|
+
window.open(url, '_blank')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Provide the same API shape as acquireVsCodeApi()
|
|
87
|
+
;(window as unknown as Record<string, unknown>).acquireVsCodeApi = () => ({
|
|
88
|
+
postMessage(message: unknown) {
|
|
89
|
+
const msg = message as Record<string, unknown>
|
|
90
|
+
// Intercept attachment messages — handle browser-side in standalone
|
|
91
|
+
if (msg.type === 'addAttachment') {
|
|
92
|
+
handleAddAttachment(msg.featureId as string)
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
if (msg.type === 'openAttachment') {
|
|
96
|
+
handleOpenAttachment(msg.featureId as string, msg.attachment as string)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const json = JSON.stringify(message)
|
|
101
|
+
if (connected) {
|
|
102
|
+
ws.send(json)
|
|
103
|
+
} else {
|
|
104
|
+
pendingMessages.push(json)
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
getState() {
|
|
108
|
+
return savedState
|
|
109
|
+
},
|
|
110
|
+
setState(state: unknown) {
|
|
111
|
+
savedState = state
|
|
112
|
+
try {
|
|
113
|
+
sessionStorage.setItem('kanban-standalone-state', JSON.stringify(state))
|
|
114
|
+
} catch {
|
|
115
|
+
// ignore
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Set dark mode class based on system preference (replaces VSCode's vscode-dark class)
|
|
121
|
+
const darkMq = window.matchMedia('(prefers-color-scheme: dark)')
|
|
122
|
+
function applyTheme(dark: boolean) {
|
|
123
|
+
document.body.classList.toggle('vscode-dark', dark)
|
|
124
|
+
document.body.classList.toggle('vscode-light', !dark)
|
|
125
|
+
}
|
|
126
|
+
darkMq.addEventListener('change', (e) => applyTheme(e.matches))
|
|
127
|
+
// Apply immediately (body may not exist yet, so also apply on DOMContentLoaded)
|
|
128
|
+
if (document.body) {
|
|
129
|
+
applyTheme(darkMq.matches)
|
|
130
|
+
} else {
|
|
131
|
+
document.addEventListener('DOMContentLoaded', () => applyTheme(darkMq.matches))
|
|
132
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Kanban Board</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="./standalone-main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { create } from 'zustand'
|
|
2
|
+
import type { Feature, FeatureStatus, KanbanColumn, Priority, CardDisplaySettings } from '../../shared/types'
|
|
3
|
+
|
|
4
|
+
export type DueDateFilter = 'all' | 'overdue' | 'today' | 'this-week' | 'no-date'
|
|
5
|
+
export type LayoutMode = 'horizontal' | 'vertical'
|
|
6
|
+
|
|
7
|
+
interface KanbanState {
|
|
8
|
+
features: Feature[]
|
|
9
|
+
columns: KanbanColumn[]
|
|
10
|
+
isDarkMode: boolean
|
|
11
|
+
searchQuery: string
|
|
12
|
+
priorityFilter: Priority | 'all'
|
|
13
|
+
assigneeFilter: string | 'all'
|
|
14
|
+
labelFilter: string | 'all'
|
|
15
|
+
dueDateFilter: DueDateFilter
|
|
16
|
+
layout: LayoutMode
|
|
17
|
+
cardSettings: CardDisplaySettings
|
|
18
|
+
settingsOpen: boolean
|
|
19
|
+
|
|
20
|
+
setFeatures: (features: Feature[]) => void
|
|
21
|
+
setColumns: (columns: KanbanColumn[]) => void
|
|
22
|
+
setIsDarkMode: (dark: boolean) => void
|
|
23
|
+
setCardSettings: (settings: CardDisplaySettings) => void
|
|
24
|
+
setSettingsOpen: (open: boolean) => void
|
|
25
|
+
setSearchQuery: (query: string) => void
|
|
26
|
+
setPriorityFilter: (priority: Priority | 'all') => void
|
|
27
|
+
setAssigneeFilter: (assignee: string | 'all') => void
|
|
28
|
+
setLabelFilter: (label: string | 'all') => void
|
|
29
|
+
setDueDateFilter: (filter: DueDateFilter) => void
|
|
30
|
+
setLayout: (layout: LayoutMode) => void
|
|
31
|
+
toggleLayout: () => void
|
|
32
|
+
clearAllFilters: () => void
|
|
33
|
+
|
|
34
|
+
addFeature: (feature: Feature) => void
|
|
35
|
+
updateFeature: (id: string, updates: Partial<Feature>) => void
|
|
36
|
+
removeFeature: (id: string) => void
|
|
37
|
+
getFeaturesByStatus: (status: FeatureStatus) => Feature[]
|
|
38
|
+
getFilteredFeaturesByStatus: (status: FeatureStatus) => Feature[]
|
|
39
|
+
getUniqueAssignees: () => string[]
|
|
40
|
+
getUniqueLabels: () => string[]
|
|
41
|
+
hasActiveFilters: () => boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const getInitialDarkMode = (): boolean => {
|
|
45
|
+
// Check for VSCode theme
|
|
46
|
+
if (typeof document !== 'undefined') {
|
|
47
|
+
return document.body.classList.contains('vscode-dark') ||
|
|
48
|
+
document.body.classList.contains('vscode-high-contrast')
|
|
49
|
+
}
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const isToday = (date: Date): boolean => {
|
|
54
|
+
const today = new Date()
|
|
55
|
+
return (
|
|
56
|
+
date.getFullYear() === today.getFullYear() &&
|
|
57
|
+
date.getMonth() === today.getMonth() &&
|
|
58
|
+
date.getDate() === today.getDate()
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const isThisWeek = (date: Date): boolean => {
|
|
63
|
+
const today = new Date()
|
|
64
|
+
const startOfWeek = new Date(today)
|
|
65
|
+
startOfWeek.setDate(today.getDate() - today.getDay())
|
|
66
|
+
startOfWeek.setHours(0, 0, 0, 0)
|
|
67
|
+
|
|
68
|
+
const endOfWeek = new Date(startOfWeek)
|
|
69
|
+
endOfWeek.setDate(startOfWeek.getDate() + 7)
|
|
70
|
+
|
|
71
|
+
return date >= startOfWeek && date < endOfWeek
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const isOverdue = (date: Date): boolean => {
|
|
75
|
+
const today = new Date()
|
|
76
|
+
today.setHours(0, 0, 0, 0)
|
|
77
|
+
return date < today
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const useStore = create<KanbanState>((set, get) => ({
|
|
81
|
+
features: [],
|
|
82
|
+
columns: [],
|
|
83
|
+
isDarkMode: getInitialDarkMode(),
|
|
84
|
+
searchQuery: '',
|
|
85
|
+
priorityFilter: 'all',
|
|
86
|
+
assigneeFilter: 'all',
|
|
87
|
+
labelFilter: 'all',
|
|
88
|
+
dueDateFilter: 'all',
|
|
89
|
+
layout: 'horizontal',
|
|
90
|
+
cardSettings: {
|
|
91
|
+
showPriorityBadges: true,
|
|
92
|
+
showAssignee: true,
|
|
93
|
+
showDueDate: true,
|
|
94
|
+
showLabels: true,
|
|
95
|
+
showBuildWithAI: true,
|
|
96
|
+
showFileName: false,
|
|
97
|
+
compactMode: false,
|
|
98
|
+
markdownEditorMode: false,
|
|
99
|
+
defaultPriority: 'medium',
|
|
100
|
+
defaultStatus: 'backlog'
|
|
101
|
+
},
|
|
102
|
+
settingsOpen: false,
|
|
103
|
+
|
|
104
|
+
setFeatures: (features) => set({ features }),
|
|
105
|
+
setColumns: (columns) => set({ columns }),
|
|
106
|
+
setIsDarkMode: (dark) => set({ isDarkMode: dark }),
|
|
107
|
+
setCardSettings: (settings) => set({ cardSettings: settings }),
|
|
108
|
+
setSettingsOpen: (open) => set({ settingsOpen: open }),
|
|
109
|
+
setSearchQuery: (query) => set({ searchQuery: query }),
|
|
110
|
+
setPriorityFilter: (priority) => set({ priorityFilter: priority }),
|
|
111
|
+
setAssigneeFilter: (assignee) => set({ assigneeFilter: assignee }),
|
|
112
|
+
setLabelFilter: (label) => set({ labelFilter: label }),
|
|
113
|
+
setDueDateFilter: (filter) => set({ dueDateFilter: filter }),
|
|
114
|
+
setLayout: (layout) => set({ layout }),
|
|
115
|
+
toggleLayout: () => set((state) => ({ layout: state.layout === 'horizontal' ? 'vertical' : 'horizontal' })),
|
|
116
|
+
|
|
117
|
+
clearAllFilters: () =>
|
|
118
|
+
set({
|
|
119
|
+
searchQuery: '',
|
|
120
|
+
priorityFilter: 'all',
|
|
121
|
+
assigneeFilter: 'all',
|
|
122
|
+
labelFilter: 'all',
|
|
123
|
+
dueDateFilter: 'all'
|
|
124
|
+
}),
|
|
125
|
+
|
|
126
|
+
addFeature: (feature) =>
|
|
127
|
+
set((state) => ({
|
|
128
|
+
features: [...state.features, feature]
|
|
129
|
+
})),
|
|
130
|
+
|
|
131
|
+
updateFeature: (id, updates) =>
|
|
132
|
+
set((state) => ({
|
|
133
|
+
features: state.features.map((f) => (f.id === id ? { ...f, ...updates } : f))
|
|
134
|
+
})),
|
|
135
|
+
|
|
136
|
+
removeFeature: (id) =>
|
|
137
|
+
set((state) => ({
|
|
138
|
+
features: state.features.filter((f) => f.id !== id)
|
|
139
|
+
})),
|
|
140
|
+
|
|
141
|
+
getFeaturesByStatus: (status) => {
|
|
142
|
+
const { features } = get()
|
|
143
|
+
return features
|
|
144
|
+
.filter((f) => f.status === status)
|
|
145
|
+
.sort((a, b) => (a.order < b.order ? -1 : a.order > b.order ? 1 : 0))
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
getFilteredFeaturesByStatus: (status) => {
|
|
149
|
+
const {
|
|
150
|
+
features,
|
|
151
|
+
searchQuery,
|
|
152
|
+
priorityFilter,
|
|
153
|
+
assigneeFilter,
|
|
154
|
+
labelFilter,
|
|
155
|
+
dueDateFilter
|
|
156
|
+
} = get()
|
|
157
|
+
|
|
158
|
+
return features
|
|
159
|
+
.filter((f) => {
|
|
160
|
+
if (f.status !== status) return false
|
|
161
|
+
|
|
162
|
+
// Priority filter
|
|
163
|
+
if (priorityFilter !== 'all' && f.priority !== priorityFilter) return false
|
|
164
|
+
|
|
165
|
+
// Assignee filter
|
|
166
|
+
if (assigneeFilter !== 'all') {
|
|
167
|
+
if (assigneeFilter === 'unassigned') {
|
|
168
|
+
if (f.assignee) return false
|
|
169
|
+
} else if (f.assignee !== assigneeFilter) {
|
|
170
|
+
return false
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Label filter
|
|
175
|
+
if (labelFilter !== 'all' && !f.labels.includes(labelFilter)) return false
|
|
176
|
+
|
|
177
|
+
// Due date filter
|
|
178
|
+
if (dueDateFilter !== 'all') {
|
|
179
|
+
if (dueDateFilter === 'no-date') {
|
|
180
|
+
if (f.dueDate) return false
|
|
181
|
+
} else if (!f.dueDate) {
|
|
182
|
+
return false
|
|
183
|
+
} else {
|
|
184
|
+
const dueDate = new Date(f.dueDate)
|
|
185
|
+
if (dueDateFilter === 'overdue' && !isOverdue(dueDate)) return false
|
|
186
|
+
if (dueDateFilter === 'today' && !isToday(dueDate)) return false
|
|
187
|
+
if (dueDateFilter === 'this-week' && !isThisWeek(dueDate)) return false
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Search query
|
|
192
|
+
if (searchQuery) {
|
|
193
|
+
const query = searchQuery.toLowerCase()
|
|
194
|
+
return (
|
|
195
|
+
f.content.toLowerCase().includes(query) ||
|
|
196
|
+
f.id.toLowerCase().includes(query) ||
|
|
197
|
+
(f.assignee && f.assignee.toLowerCase().includes(query)) ||
|
|
198
|
+
f.labels.some((l) => l.toLowerCase().includes(query))
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return true
|
|
203
|
+
})
|
|
204
|
+
.sort((a, b) => (a.order < b.order ? -1 : a.order > b.order ? 1 : 0))
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
getUniqueAssignees: () => {
|
|
208
|
+
const { features } = get()
|
|
209
|
+
const assignees = new Set<string>()
|
|
210
|
+
features.forEach((f) => {
|
|
211
|
+
if (f.assignee) assignees.add(f.assignee)
|
|
212
|
+
})
|
|
213
|
+
return Array.from(assignees).sort()
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
getUniqueLabels: () => {
|
|
217
|
+
const { features } = get()
|
|
218
|
+
const labels = new Set<string>()
|
|
219
|
+
features.forEach((f) => {
|
|
220
|
+
f.labels.forEach((l) => labels.add(l))
|
|
221
|
+
})
|
|
222
|
+
return Array.from(labels).sort()
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
hasActiveFilters: () => {
|
|
226
|
+
const {
|
|
227
|
+
searchQuery,
|
|
228
|
+
priorityFilter,
|
|
229
|
+
assigneeFilter,
|
|
230
|
+
labelFilter,
|
|
231
|
+
dueDateFilter
|
|
232
|
+
} = get()
|
|
233
|
+
return (
|
|
234
|
+
searchQuery !== '' ||
|
|
235
|
+
priorityFilter !== 'all' ||
|
|
236
|
+
assigneeFilter !== 'all' ||
|
|
237
|
+
labelFilter !== 'all' ||
|
|
238
|
+
dueDateFilter !== 'all'
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
}))
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import typography from '@tailwindcss/typography'
|
|
2
|
+
|
|
3
|
+
/** @type {import('tailwindcss').Config} */
|
|
4
|
+
export default {
|
|
5
|
+
content: [
|
|
6
|
+
"./src/webview/**/*.{js,ts,jsx,tsx}",
|
|
7
|
+
],
|
|
8
|
+
darkMode: 'class',
|
|
9
|
+
theme: {
|
|
10
|
+
extend: {
|
|
11
|
+
typography: () => ({
|
|
12
|
+
DEFAULT: {
|
|
13
|
+
css: {
|
|
14
|
+
'--tw-prose-body': 'var(--vscode-foreground)',
|
|
15
|
+
'--tw-prose-headings': 'var(--vscode-foreground)',
|
|
16
|
+
'--tw-prose-lead': 'var(--vscode-foreground)',
|
|
17
|
+
'--tw-prose-links': 'var(--vscode-textLink-foreground)',
|
|
18
|
+
'--tw-prose-bold': 'var(--vscode-foreground)',
|
|
19
|
+
'--tw-prose-counters': 'var(--vscode-descriptionForeground)',
|
|
20
|
+
'--tw-prose-bullets': 'var(--vscode-descriptionForeground)',
|
|
21
|
+
'--tw-prose-hr': 'var(--vscode-panel-border)',
|
|
22
|
+
'--tw-prose-quotes': 'var(--vscode-foreground)',
|
|
23
|
+
'--tw-prose-quote-borders': 'var(--vscode-textBlockQuote-border)',
|
|
24
|
+
'--tw-prose-captions': 'var(--vscode-descriptionForeground)',
|
|
25
|
+
'--tw-prose-code': 'var(--vscode-textPreformat-foreground)',
|
|
26
|
+
'--tw-prose-pre-code': 'var(--vscode-editor-foreground)',
|
|
27
|
+
'--tw-prose-pre-bg': 'var(--vscode-textBlockQuote-background)',
|
|
28
|
+
'--tw-prose-th-borders': 'var(--vscode-panel-border)',
|
|
29
|
+
'--tw-prose-td-borders': 'var(--vscode-panel-border)',
|
|
30
|
+
// Invert colors for dark mode handled by CSS variables
|
|
31
|
+
'--tw-prose-invert-body': 'var(--vscode-foreground)',
|
|
32
|
+
'--tw-prose-invert-headings': 'var(--vscode-foreground)',
|
|
33
|
+
'--tw-prose-invert-lead': 'var(--vscode-foreground)',
|
|
34
|
+
'--tw-prose-invert-links': 'var(--vscode-textLink-foreground)',
|
|
35
|
+
'--tw-prose-invert-bold': 'var(--vscode-foreground)',
|
|
36
|
+
'--tw-prose-invert-counters': 'var(--vscode-descriptionForeground)',
|
|
37
|
+
'--tw-prose-invert-bullets': 'var(--vscode-descriptionForeground)',
|
|
38
|
+
'--tw-prose-invert-hr': 'var(--vscode-panel-border)',
|
|
39
|
+
'--tw-prose-invert-quotes': 'var(--vscode-foreground)',
|
|
40
|
+
'--tw-prose-invert-quote-borders': 'var(--vscode-textBlockQuote-border)',
|
|
41
|
+
'--tw-prose-invert-captions': 'var(--vscode-descriptionForeground)',
|
|
42
|
+
'--tw-prose-invert-code': 'var(--vscode-textPreformat-foreground)',
|
|
43
|
+
'--tw-prose-invert-pre-code': 'var(--vscode-editor-foreground)',
|
|
44
|
+
'--tw-prose-invert-pre-bg': 'var(--vscode-textBlockQuote-background)',
|
|
45
|
+
'--tw-prose-invert-th-borders': 'var(--vscode-panel-border)',
|
|
46
|
+
'--tw-prose-invert-td-borders': 'var(--vscode-panel-border)',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
plugins: [typography],
|
|
53
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
15
|
+
"baseUrl": ".",
|
|
16
|
+
"paths": {
|
|
17
|
+
"@/*": ["src/*"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist"]
|
|
22
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
import { resolve } from 'path'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react()],
|
|
7
|
+
root: resolve(__dirname, 'src/webview'),
|
|
8
|
+
build: {
|
|
9
|
+
outDir: resolve(__dirname, 'dist/webview'),
|
|
10
|
+
emptyOutDir: true,
|
|
11
|
+
rollupOptions: {
|
|
12
|
+
input: {
|
|
13
|
+
index: resolve(__dirname, 'src/webview/main.tsx')
|
|
14
|
+
},
|
|
15
|
+
output: {
|
|
16
|
+
entryFileNames: '[name].js',
|
|
17
|
+
chunkFileNames: '[name]-[hash].js',
|
|
18
|
+
assetFileNames: '[name].[ext]',
|
|
19
|
+
// Split vendor deps into separate chunks for parallel loading
|
|
20
|
+
manualChunks: {
|
|
21
|
+
'react-vendor': ['react', 'react-dom'],
|
|
22
|
+
'icons': ['lucide-react']
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
cssCodeSplit: false,
|
|
27
|
+
sourcemap: true,
|
|
28
|
+
// Optimize chunk size
|
|
29
|
+
chunkSizeWarningLimit: 300
|
|
30
|
+
},
|
|
31
|
+
resolve: {
|
|
32
|
+
alias: {
|
|
33
|
+
'@': resolve(__dirname, 'src')
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
|
|
3
|
+
import react from '@vitejs/plugin-react'
|
|
4
|
+
import { defineConfig, type Plugin } from 'vite'
|
|
5
|
+
|
|
6
|
+
// In dev mode, serve standalone.html as the default page
|
|
7
|
+
function standaloneHtmlPlugin(): Plugin {
|
|
8
|
+
return {
|
|
9
|
+
name: 'standalone-html',
|
|
10
|
+
configureServer(server) {
|
|
11
|
+
server.middlewares.use((req, _res, next) => {
|
|
12
|
+
if (req.url === '/' || req.url === '/index.html') {
|
|
13
|
+
req.url = '/standalone.html'
|
|
14
|
+
}
|
|
15
|
+
next()
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
plugins: [react(), standaloneHtmlPlugin()],
|
|
23
|
+
root: resolve(__dirname, 'src/webview'),
|
|
24
|
+
server: {
|
|
25
|
+
port: 3000,
|
|
26
|
+
proxy: {
|
|
27
|
+
'/ws': {
|
|
28
|
+
target: 'ws://localhost:3001',
|
|
29
|
+
ws: true
|
|
30
|
+
},
|
|
31
|
+
'/api': {
|
|
32
|
+
target: 'http://localhost:3001'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
build: {
|
|
37
|
+
outDir: resolve(__dirname, 'dist/standalone-webview'),
|
|
38
|
+
emptyOutDir: true,
|
|
39
|
+
rollupOptions: {
|
|
40
|
+
input: {
|
|
41
|
+
index: resolve(__dirname, 'src/webview/standalone-main.tsx')
|
|
42
|
+
},
|
|
43
|
+
output: {
|
|
44
|
+
entryFileNames: '[name].js',
|
|
45
|
+
chunkFileNames: '[name]-[hash].js',
|
|
46
|
+
assetFileNames: '[name].[ext]',
|
|
47
|
+
manualChunks: {
|
|
48
|
+
'react-vendor': ['react', 'react-dom'],
|
|
49
|
+
'icons': ['lucide-react']
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
cssCodeSplit: false,
|
|
54
|
+
sourcemap: true,
|
|
55
|
+
chunkSizeWarningLimit: 300
|
|
56
|
+
},
|
|
57
|
+
resolve: {
|
|
58
|
+
alias: {
|
|
59
|
+
'@': resolve(__dirname, 'src')
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
})
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
import { resolve } from 'path'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
root: resolve(__dirname),
|
|
7
|
+
include: ['src/**/*.{test,spec}.{ts,tsx}'],
|
|
8
|
+
testTimeout: 30000
|
|
9
|
+
},
|
|
10
|
+
resolve: {
|
|
11
|
+
alias: {
|
|
12
|
+
'@': resolve(__dirname, 'src')
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
})
|