@qnote/q-ai-note 1.0.4 → 1.0.6
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/README.md +59 -0
- package/dist/cli-server.d.ts +3 -0
- package/dist/cli-server.d.ts.map +1 -0
- package/dist/cli-server.js +79 -0
- package/dist/cli-server.js.map +1 -0
- package/dist/cli.js +77 -15
- package/dist/cli.js.map +1 -1
- package/dist/server/api/chat.d.ts.map +1 -1
- package/dist/server/api/chat.js +6 -112
- package/dist/server/api/chat.js.map +1 -1
- package/dist/server/api/diary.d.ts.map +1 -1
- package/dist/server/api/diary.js +34 -0
- package/dist/server/api/diary.js.map +1 -1
- package/dist/server/db.d.ts.map +1 -1
- package/dist/server/db.js +8 -0
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.d.ts +7 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +65 -5
- package/dist/server/index.js.map +1 -1
- package/dist/web/app.js +822 -124
- package/dist/web/index.html +76 -23
- package/dist/web/styles.css +434 -13
- package/dist/web/vueRenderers.js +294 -56
- package/package.json +6 -3
package/dist/web/vueRenderers.js
CHANGED
|
@@ -14,7 +14,7 @@ function byId(targetId) {
|
|
|
14
14
|
export function mountSandboxGrid(targetId, options) {
|
|
15
15
|
const container = byId(targetId);
|
|
16
16
|
if (!container) return;
|
|
17
|
-
const { sandboxes = [], emptyText = '暂无数据', onOpen, onDelete } = options || {};
|
|
17
|
+
const { sandboxes = [], emptyText = '暂无数据', onOpen, onDelete, readonly = false } = options || {};
|
|
18
18
|
|
|
19
19
|
if (!sandboxes.length) {
|
|
20
20
|
container.innerHTML = `<div class="empty-state"><p>${esc(emptyText)}</p></div>`;
|
|
@@ -28,7 +28,7 @@ export function mountSandboxGrid(targetId, options) {
|
|
|
28
28
|
<div class="sandbox-meta">
|
|
29
29
|
<span>${esc(new Date(sandbox.updated_at).toLocaleDateString())}</span>
|
|
30
30
|
</div>
|
|
31
|
-
|
|
31
|
+
${readonly ? '' : `<button class="btn btn-secondary btn-sm sandbox-delete" data-delete-id="${esc(sandbox.id)}">删除</button>`}
|
|
32
32
|
</div>
|
|
33
33
|
`).join('');
|
|
34
34
|
|
|
@@ -52,7 +52,17 @@ export function mountHtmlList(targetId, htmlItems) {
|
|
|
52
52
|
export function mountDiaryTimeline(targetId, options) {
|
|
53
53
|
const container = byId(targetId);
|
|
54
54
|
if (!container) return;
|
|
55
|
-
const {
|
|
55
|
+
const {
|
|
56
|
+
diaries = [],
|
|
57
|
+
getSandboxName,
|
|
58
|
+
getWorkItemName,
|
|
59
|
+
onConfirm,
|
|
60
|
+
onIgnore,
|
|
61
|
+
onEdit,
|
|
62
|
+
onOpenWorkItem,
|
|
63
|
+
readonly = false,
|
|
64
|
+
renderContent,
|
|
65
|
+
} = options || {};
|
|
56
66
|
|
|
57
67
|
if (!diaries.length) {
|
|
58
68
|
container.innerHTML = '<div class="empty-state"><p>暂无日记</p></div>';
|
|
@@ -61,17 +71,31 @@ export function mountDiaryTimeline(targetId, options) {
|
|
|
61
71
|
|
|
62
72
|
container.innerHTML = diaries.map((diary) => `
|
|
63
73
|
<div class="diary-item ${diary.processed ? 'processed' : ''}">
|
|
64
|
-
<div class="diary-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
<div class="diary-head-row">
|
|
75
|
+
<div class="diary-meta">
|
|
76
|
+
${esc(new Date(diary.created_at).toLocaleString())}
|
|
77
|
+
${diary.sandbox_id ? ` · ${esc(getSandboxName?.(diary.sandbox_id) || diary.sandbox_id)}` : ''}
|
|
78
|
+
${diary.work_item_id ? `
|
|
79
|
+
· 节点:
|
|
80
|
+
<button
|
|
81
|
+
class="diary-open-node-link"
|
|
82
|
+
type="button"
|
|
83
|
+
data-open-work-item="${esc(diary.work_item_id)}"
|
|
84
|
+
data-open-sandbox="${esc(diary.sandbox_id || '')}"
|
|
85
|
+
>${esc(getWorkItemName?.(diary.sandbox_id, diary.work_item_id) || diary.work_item_id)}</button>
|
|
86
|
+
` : ''}
|
|
73
87
|
</div>
|
|
74
|
-
|
|
88
|
+
${readonly ? '' : `
|
|
89
|
+
<div class="diary-actions">
|
|
90
|
+
<button class="edit" data-edit-id="${esc(diary.id)}">编辑</button>
|
|
91
|
+
${diary.processed ? '' : `
|
|
92
|
+
<button class="confirm" data-confirm-id="${esc(diary.id)}">采纳</button>
|
|
93
|
+
<button class="ignore" data-ignore-id="${esc(diary.id)}">忽略</button>
|
|
94
|
+
`}
|
|
95
|
+
</div>
|
|
96
|
+
`}
|
|
97
|
+
</div>
|
|
98
|
+
<div class="diary-content">${typeof renderContent === 'function' ? renderContent(diary.content) : esc(diary.content)}</div>
|
|
75
99
|
</div>
|
|
76
100
|
`).join('');
|
|
77
101
|
|
|
@@ -81,15 +105,28 @@ export function mountDiaryTimeline(targetId, options) {
|
|
|
81
105
|
container.querySelectorAll('[data-ignore-id]').forEach((el) => {
|
|
82
106
|
el.addEventListener('click', () => onIgnore?.(el.getAttribute('data-ignore-id')));
|
|
83
107
|
});
|
|
108
|
+
container.querySelectorAll('[data-edit-id]').forEach((el) => {
|
|
109
|
+
el.addEventListener('click', () => onEdit?.(el.getAttribute('data-edit-id')));
|
|
110
|
+
});
|
|
111
|
+
container.querySelectorAll('[data-open-work-item]').forEach((el) => {
|
|
112
|
+
el.addEventListener('click', () => {
|
|
113
|
+
const workItemId = el.getAttribute('data-open-work-item');
|
|
114
|
+
const sandboxId = el.getAttribute('data-open-sandbox');
|
|
115
|
+
if (!workItemId || !sandboxId) return;
|
|
116
|
+
onOpenWorkItem?.(sandboxId, workItemId);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
84
119
|
}
|
|
85
120
|
|
|
86
121
|
function renderEntityBadges(summary) {
|
|
87
122
|
const issue = Number(summary?.issue || 0);
|
|
88
123
|
const knowledge = Number(summary?.knowledge || 0);
|
|
89
124
|
const capability = Number(summary?.capability || 0);
|
|
125
|
+
if (issue + knowledge + capability === 0) {
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
90
128
|
const issueOpen = Number(summary?.issue_open || 0);
|
|
91
129
|
const issueClosed = Number(summary?.issue_closed || 0);
|
|
92
|
-
const isBlindSpot = issue + knowledge + capability === 0;
|
|
93
130
|
const issueStateClass = issue === 0
|
|
94
131
|
? 'issue-none'
|
|
95
132
|
: issueOpen > 0
|
|
@@ -99,33 +136,70 @@ function renderEntityBadges(summary) {
|
|
|
99
136
|
: 'issue-none';
|
|
100
137
|
return `
|
|
101
138
|
<span
|
|
102
|
-
class="node-entity-mini-badges ${
|
|
139
|
+
class="node-entity-mini-badges ${issueStateClass}"
|
|
103
140
|
title="节点摘要:${issue}/${knowledge}/${capability}(Issue/Knowledge/Capability)"
|
|
104
141
|
>
|
|
105
|
-
<span class="node-entity-mini-badge
|
|
142
|
+
<span class="node-entity-mini-badge active">${issue}/${knowledge}/${capability}</span>
|
|
106
143
|
</span>
|
|
107
144
|
`;
|
|
108
145
|
}
|
|
109
146
|
|
|
110
|
-
function
|
|
147
|
+
function pickEntityPreviewRows(nodeId, entityRowsByNodeId, mode) {
|
|
148
|
+
if (!entityRowsByNodeId || mode === 'none') return [];
|
|
149
|
+
const rows = entityRowsByNodeId[nodeId] || [];
|
|
150
|
+
if (mode === 'all') return rows;
|
|
151
|
+
return rows.filter((row) => row.entity_type === mode);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function renderEntityPreviewBoxes(nodeId, entityRowsByNodeId, mode) {
|
|
155
|
+
const rows = pickEntityPreviewRows(nodeId, entityRowsByNodeId, mode);
|
|
156
|
+
if (!rows.length) return '';
|
|
157
|
+
const shownRows = rows.slice(0, 4);
|
|
158
|
+
return `
|
|
159
|
+
<div class="node-entity-preview-strip" data-node-id="${esc(nodeId)}">
|
|
160
|
+
${shownRows.map((row) => `
|
|
161
|
+
<button
|
|
162
|
+
class="entity-preview-box ${esc(row.entity_type)}"
|
|
163
|
+
type="button"
|
|
164
|
+
data-action="entity-preview"
|
|
165
|
+
data-node-id="${esc(nodeId)}"
|
|
166
|
+
data-entity-type="${esc(row.entity_type)}"
|
|
167
|
+
data-entity-id="${esc(row.id)}"
|
|
168
|
+
title="${esc(row.title || row.entity_type)}"
|
|
169
|
+
>
|
|
170
|
+
<span class="entity-preview-type">${esc(row.entity_type)}</span>
|
|
171
|
+
<span class="entity-preview-title">${esc(row.title || '-')}</span>
|
|
172
|
+
</button>
|
|
173
|
+
`).join('')}
|
|
174
|
+
${rows.length > shownRows.length ? `<span class="entity-preview-more">+${rows.length - shownRows.length}</span>` : ''}
|
|
175
|
+
</div>
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function renderTreeNode(node, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee = false, entityRowsByNodeId = {}, elementPreviewMode = 'none', readonly = false, selectedId = '') {
|
|
111
180
|
const children = byParent.get(node.id) || [];
|
|
112
181
|
const hasChildren = children.length > 0;
|
|
113
182
|
const isExpanded = expandedIdSet.has(node.id);
|
|
114
183
|
const isShort = String(node.name || '').trim().length <= 10;
|
|
115
184
|
const nodeSummary = entitySummaryByNodeId?.[node.id] || { issue: 0, knowledge: 0, capability: 0 };
|
|
185
|
+
const previewHtml = renderEntityPreviewBoxes(node.id, entityRowsByNodeId, elementPreviewMode);
|
|
186
|
+
const isSelected = String(selectedId || '') === String(node.id || '');
|
|
116
187
|
|
|
117
188
|
if (!hasChildren) {
|
|
118
189
|
return `
|
|
119
|
-
<div class="tree-leaf-node" data-id="${esc(node.id)}" data-select-id="${esc(node.id)}" tabindex="0">
|
|
120
|
-
<span class="node-status ${esc(node.status)}"></span>
|
|
190
|
+
<div class="tree-leaf-node ${isSelected ? 'is-selected' : ''}" data-id="${esc(node.id)}" data-select-id="${esc(node.id)}" tabindex="0">
|
|
121
191
|
<span class="node-name ${isShort ? 'short-name' : ''}" data-select-id="${esc(node.id)}">${esc(node.name)}</span>
|
|
122
192
|
${renderEntityBadges(nodeSummary)}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
193
|
+
${previewHtml}
|
|
194
|
+
${readonly ? '' : `
|
|
195
|
+
<div class="node-actions">
|
|
196
|
+
<button class="node-action-btn chat" data-action="quick-chat" data-id="${esc(node.id)}" title="快捷提问">💬</button>
|
|
197
|
+
<button class="node-action-btn" data-action="add-diary" data-id="${esc(node.id)}" title="记录日记">📝</button>
|
|
198
|
+
<button class="node-action-btn add-child" data-action="add-child" data-id="${esc(node.id)}" title="添加子任务">+</button>
|
|
199
|
+
<button class="node-action-btn" data-action="edit" data-id="${esc(node.id)}" title="编辑">✎</button>
|
|
200
|
+
<button class="node-action-btn delete" data-action="delete" data-id="${esc(node.id)}" title="删除">✕</button>
|
|
201
|
+
</div>
|
|
202
|
+
`}
|
|
129
203
|
</div>
|
|
130
204
|
`;
|
|
131
205
|
}
|
|
@@ -135,7 +209,7 @@ function renderTreeNode(node, byParent, expandedIdSet, entitySummaryByNodeId, sh
|
|
|
135
209
|
|
|
136
210
|
return `
|
|
137
211
|
<div class="tree-parent-card ${isExpanded ? 'expanded' : 'collapsed'}">
|
|
138
|
-
<div class="tree-parent-header" data-select-id="${esc(node.id)}" tabindex="0" aria-expanded="${isExpanded ? 'true' : 'false'}">
|
|
212
|
+
<div class="tree-parent-header ${isSelected ? 'is-selected' : ''}" data-select-id="${esc(node.id)}" tabindex="0" aria-expanded="${isExpanded ? 'true' : 'false'}">
|
|
139
213
|
<button
|
|
140
214
|
class="node-expand-btn ${isExpanded ? 'expanded' : ''}"
|
|
141
215
|
data-action="toggle"
|
|
@@ -146,37 +220,44 @@ function renderTreeNode(node, byParent, expandedIdSet, entitySummaryByNodeId, sh
|
|
|
146
220
|
>
|
|
147
221
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/></svg>
|
|
148
222
|
</button>
|
|
149
|
-
<span class="node-status ${esc(node.status)}"></span>
|
|
150
223
|
<span class="node-name">${esc(node.name)}</span>
|
|
151
224
|
${renderEntityBadges(nodeSummary)}
|
|
152
225
|
${showAssignee && node.assignee ? `<span class="node-meta">@${esc(node.assignee)}</span>` : ''}
|
|
153
|
-
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
226
|
+
${readonly ? '' : `
|
|
227
|
+
<div class="node-actions">
|
|
228
|
+
<button class="node-action-btn chat" data-action="quick-chat" data-id="${esc(node.id)}" title="快捷提问">💬</button>
|
|
229
|
+
<button class="node-action-btn" data-action="add-diary" data-id="${esc(node.id)}" title="记录日记">📝</button>
|
|
230
|
+
<button class="node-action-btn add-child" data-action="add-child" data-id="${esc(node.id)}" title="添加子任务">+</button>
|
|
231
|
+
<button class="node-action-btn" data-action="edit" data-id="${esc(node.id)}" title="编辑">✎</button>
|
|
232
|
+
<button class="node-action-btn delete" data-action="delete" data-id="${esc(node.id)}" title="删除">✕</button>
|
|
233
|
+
</div>
|
|
234
|
+
`}
|
|
159
235
|
</div>
|
|
160
236
|
${isExpanded ? `
|
|
237
|
+
${previewHtml ? `<div class="tree-node-preview-wrapper">${previewHtml}</div>` : ''}
|
|
161
238
|
<div class="tree-parent-children">
|
|
162
|
-
${branchChildren.map((child) => renderTreeNode(child, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee)).join('')}
|
|
239
|
+
${branchChildren.map((child) => renderTreeNode(child, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee, entityRowsByNodeId, elementPreviewMode, readonly, selectedId)).join('')}
|
|
163
240
|
${leafChildren.length ? `
|
|
164
241
|
<div class="tree-leaf-container">
|
|
165
242
|
<div class="tree-leaf-grid">
|
|
166
243
|
${leafChildren.map((child) => {
|
|
167
244
|
const shortName = String(child.name || '').trim().length <= 10;
|
|
168
245
|
const childSummary = entitySummaryByNodeId?.[child.id] || { issue: 0, knowledge: 0, capability: 0 };
|
|
246
|
+
const childPreviewHtml = renderEntityPreviewBoxes(child.id, entityRowsByNodeId, elementPreviewMode);
|
|
169
247
|
return `
|
|
170
|
-
<div class="tree-leaf-node" data-id="${esc(child.id)}" data-select-id="${esc(child.id)}" tabindex="0">
|
|
171
|
-
<span class="node-status ${esc(child.status)}"></span>
|
|
248
|
+
<div class="tree-leaf-node ${String(selectedId || '') === String(child.id || '') ? 'is-selected' : ''}" data-id="${esc(child.id)}" data-select-id="${esc(child.id)}" tabindex="0">
|
|
172
249
|
<span class="node-name ${shortName ? 'short-name' : ''}" data-select-id="${esc(child.id)}">${esc(child.name)}</span>
|
|
173
250
|
${renderEntityBadges(childSummary)}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
251
|
+
${childPreviewHtml}
|
|
252
|
+
${readonly ? '' : `
|
|
253
|
+
<div class="node-actions">
|
|
254
|
+
<button class="node-action-btn chat" data-action="quick-chat" data-id="${esc(child.id)}" title="快捷提问">💬</button>
|
|
255
|
+
<button class="node-action-btn" data-action="add-diary" data-id="${esc(child.id)}" title="记录日记">📝</button>
|
|
256
|
+
<button class="node-action-btn add-child" data-action="add-child" data-id="${esc(child.id)}" title="添加子任务">+</button>
|
|
257
|
+
<button class="node-action-btn" data-action="edit" data-id="${esc(child.id)}" title="编辑">✎</button>
|
|
258
|
+
<button class="node-action-btn delete" data-action="delete" data-id="${esc(child.id)}" title="删除">✕</button>
|
|
259
|
+
</div>
|
|
260
|
+
`}
|
|
180
261
|
</div>
|
|
181
262
|
`;
|
|
182
263
|
}).join('')}
|
|
@@ -232,18 +313,20 @@ function applyAdaptiveRootMixedGridLayout(container) {
|
|
|
232
313
|
});
|
|
233
314
|
}
|
|
234
315
|
|
|
235
|
-
function renderDenseLaneNode(node, byParent, expandedIdSet, entitySummaryByNodeId, depth = 0, showAssignee = false) {
|
|
316
|
+
function renderDenseLaneNode(node, byParent, expandedIdSet, entitySummaryByNodeId, depth = 0, showAssignee = false, entityRowsByNodeId = {}, elementPreviewMode = 'none', readonly = false, selectedId = '') {
|
|
236
317
|
const children = byParent.get(node.id) || [];
|
|
237
318
|
const hasChildren = children.length > 0;
|
|
238
319
|
const isExpanded = expandedIdSet.has(node.id);
|
|
239
320
|
const nodeSummary = entitySummaryByNodeId?.[node.id] || { issue: 0, knowledge: 0, capability: 0 };
|
|
240
321
|
const useStackSummary = depth >= 3;
|
|
322
|
+
const previewHtml = renderEntityPreviewBoxes(node.id, entityRowsByNodeId, elementPreviewMode);
|
|
241
323
|
const childrenHtml = hasChildren && isExpanded
|
|
242
|
-
? `<div class="lane-tree-children">${children.map((child) => renderDenseLaneNode(child, byParent, expandedIdSet, entitySummaryByNodeId, depth + 1, showAssignee)).join('')}</div>`
|
|
324
|
+
? `<div class="lane-tree-children">${children.map((child) => renderDenseLaneNode(child, byParent, expandedIdSet, entitySummaryByNodeId, depth + 1, showAssignee, entityRowsByNodeId, elementPreviewMode, readonly, selectedId)).join('')}</div>`
|
|
243
325
|
: '';
|
|
326
|
+
const isSelected = String(selectedId || '') === String(node.id || '');
|
|
244
327
|
return `
|
|
245
328
|
<div class="lane-tree-node" data-depth="${depth}">
|
|
246
|
-
<div class="lane-tree-node-row" data-select-id="${esc(node.id)}" data-node-id="${esc(node.id)}" tabindex="0">
|
|
329
|
+
<div class="lane-tree-node-row ${isSelected ? 'is-selected' : ''}" data-select-id="${esc(node.id)}" data-node-id="${esc(node.id)}" tabindex="0" draggable="${readonly ? 'false' : 'true'}">
|
|
247
330
|
${hasChildren ? `
|
|
248
331
|
<button
|
|
249
332
|
class="node-expand-btn dense-expand-btn ${isExpanded ? 'expanded' : ''}"
|
|
@@ -270,13 +353,16 @@ function renderDenseLaneNode(node, byParent, expandedIdSet, entitySummaryByNodeI
|
|
|
270
353
|
</div>
|
|
271
354
|
` : ''}
|
|
272
355
|
</div>
|
|
273
|
-
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
356
|
+
${readonly ? '' : `
|
|
357
|
+
<div class="node-actions">
|
|
358
|
+
<button class="node-action-btn chat" data-action="quick-chat" data-id="${esc(node.id)}" title="快捷提问">💬</button>
|
|
359
|
+
<button class="node-action-btn add-child" data-action="add-child" data-id="${esc(node.id)}" title="添加子任务">+</button>
|
|
360
|
+
<button class="node-action-btn" data-action="edit" data-id="${esc(node.id)}" title="编辑">✎</button>
|
|
361
|
+
<button class="node-action-btn delete" data-action="delete" data-id="${esc(node.id)}" title="删除">✕</button>
|
|
362
|
+
</div>
|
|
363
|
+
`}
|
|
279
364
|
</div>
|
|
365
|
+
${previewHtml ? `<div class="lane-node-preview-wrapper">${previewHtml}</div>` : ''}
|
|
280
366
|
${childrenHtml}
|
|
281
367
|
</div>
|
|
282
368
|
`;
|
|
@@ -316,16 +402,17 @@ function getDenseLaneWidthPx(root, byParent, showAssignee = false) {
|
|
|
316
402
|
return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, preferred));
|
|
317
403
|
}
|
|
318
404
|
|
|
319
|
-
function renderDenseTree(roots, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee = false) {
|
|
405
|
+
function renderDenseTree(roots, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee = false, entityRowsByNodeId = {}, elementPreviewMode = 'none', readonly = false, selectedId = '') {
|
|
320
406
|
return `
|
|
321
407
|
<div class="dense-tree dense-horizontal dense-lane-board">
|
|
322
408
|
${roots.map((root) => {
|
|
323
409
|
const laneWidth = getDenseLaneWidthPx(root, byParent, showAssignee);
|
|
324
410
|
const laneNameMax = Math.max(120, laneWidth - 86);
|
|
325
411
|
return `
|
|
326
|
-
<section class="dense-lane" style="--lane-width:${laneWidth}px;--lane-name-max:${laneNameMax}px;">
|
|
412
|
+
<section class="dense-lane" data-root-id="${esc(root.id)}" style="--lane-width:${laneWidth}px;--lane-name-max:${laneNameMax}px;">
|
|
413
|
+
${readonly ? '' : `<div class="dense-lane-drag-handle" data-lane-drag-id="${esc(root.id)}" draggable="true" title="拖拽调整泳道顺序">⋮⋮</div>`}
|
|
327
414
|
<div class="dense-lane-body">
|
|
328
|
-
${renderDenseLaneNode(root, byParent, expandedIdSet, entitySummaryByNodeId, 0, showAssignee)}
|
|
415
|
+
${renderDenseLaneNode(root, byParent, expandedIdSet, entitySummaryByNodeId, 0, showAssignee, entityRowsByNodeId, elementPreviewMode, readonly, selectedId)}
|
|
329
416
|
</div>
|
|
330
417
|
</section>
|
|
331
418
|
`;
|
|
@@ -342,13 +429,22 @@ export function mountWorkTree(targetId, options) {
|
|
|
342
429
|
expandedIds = [],
|
|
343
430
|
onToggleExpand,
|
|
344
431
|
onAddChild,
|
|
432
|
+
onAddDiary,
|
|
345
433
|
onEdit,
|
|
346
434
|
onDelete,
|
|
347
435
|
onQuickChat,
|
|
348
436
|
onSelect,
|
|
437
|
+
onSelectEntity,
|
|
438
|
+
onMoveNode,
|
|
439
|
+
onReorderSiblings,
|
|
440
|
+
onReorderLanes,
|
|
349
441
|
entitySummaryByNodeId = {},
|
|
442
|
+
entityRowsByNodeId = {},
|
|
443
|
+
elementPreviewMode = 'none',
|
|
350
444
|
renderMode = 'card',
|
|
351
445
|
showAssignee = false,
|
|
446
|
+
readonly = false,
|
|
447
|
+
selectedId = '',
|
|
352
448
|
} = options || {};
|
|
353
449
|
|
|
354
450
|
const byParent = new Map();
|
|
@@ -357,6 +453,33 @@ export function mountWorkTree(targetId, options) {
|
|
|
357
453
|
if (!byParent.has(key)) byParent.set(key, []);
|
|
358
454
|
byParent.get(key).push(item);
|
|
359
455
|
}
|
|
456
|
+
const laneOrderOf = (item) => {
|
|
457
|
+
const value = Number(item?.extra_data?.lane_order);
|
|
458
|
+
return Number.isFinite(value) ? value : Number.POSITIVE_INFINITY;
|
|
459
|
+
};
|
|
460
|
+
const keyOf = (item, keyName) => String(item?.extra_data?.[keyName] || '').trim();
|
|
461
|
+
byParent.forEach((rows, parentKey) => {
|
|
462
|
+
rows.sort((a, b) => {
|
|
463
|
+
if (parentKey === '__root__') {
|
|
464
|
+
const laneKeyA = keyOf(a, 'lane_order_key');
|
|
465
|
+
const laneKeyB = keyOf(b, 'lane_order_key');
|
|
466
|
+
if (laneKeyA && laneKeyB && laneKeyA !== laneKeyB) return laneKeyA.localeCompare(laneKeyB);
|
|
467
|
+
if (laneKeyA && !laneKeyB) return -1;
|
|
468
|
+
if (!laneKeyA && laneKeyB) return 1;
|
|
469
|
+
const laneDiff = laneOrderOf(a) - laneOrderOf(b);
|
|
470
|
+
if (laneDiff !== 0) return laneDiff;
|
|
471
|
+
} else {
|
|
472
|
+
const orderKeyA = keyOf(a, 'order_key');
|
|
473
|
+
const orderKeyB = keyOf(b, 'order_key');
|
|
474
|
+
if (orderKeyA && orderKeyB && orderKeyA !== orderKeyB) return orderKeyA.localeCompare(orderKeyB);
|
|
475
|
+
if (orderKeyA && !orderKeyB) return -1;
|
|
476
|
+
if (!orderKeyA && orderKeyB) return 1;
|
|
477
|
+
}
|
|
478
|
+
const timeA = String(a.created_at || '');
|
|
479
|
+
const timeB = String(b.created_at || '');
|
|
480
|
+
return timeA.localeCompare(timeB);
|
|
481
|
+
});
|
|
482
|
+
});
|
|
360
483
|
const roots = byParent.get('__root__') || [];
|
|
361
484
|
const expandedIdSet = new Set(expandedIds);
|
|
362
485
|
|
|
@@ -365,13 +488,13 @@ export function mountWorkTree(targetId, options) {
|
|
|
365
488
|
return;
|
|
366
489
|
}
|
|
367
490
|
if (renderMode === 'dense') {
|
|
368
|
-
container.innerHTML = renderDenseTree(roots, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee);
|
|
491
|
+
container.innerHTML = renderDenseTree(roots, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee, entityRowsByNodeId, elementPreviewMode, readonly, selectedId);
|
|
369
492
|
} else {
|
|
370
493
|
const htmlParts = roots.map((root) => {
|
|
371
494
|
const hasChildren = ((byParent.get(root.id) || []).length > 0);
|
|
372
495
|
return `
|
|
373
496
|
<div class="root-grid-item ${hasChildren ? 'branch' : 'leaf'}" data-root-item-type="${hasChildren ? 'branch' : 'leaf'}">
|
|
374
|
-
${renderTreeNode(root, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee)}
|
|
497
|
+
${renderTreeNode(root, byParent, expandedIdSet, entitySummaryByNodeId, showAssignee, entityRowsByNodeId, elementPreviewMode, readonly, selectedId)}
|
|
375
498
|
</div>
|
|
376
499
|
`;
|
|
377
500
|
});
|
|
@@ -382,10 +505,17 @@ export function mountWorkTree(targetId, options) {
|
|
|
382
505
|
el.addEventListener('click', (e) => {
|
|
383
506
|
e.stopPropagation();
|
|
384
507
|
const action = el.getAttribute('data-action');
|
|
508
|
+
if (action === 'entity-preview') {
|
|
509
|
+
const nodeId = el.getAttribute('data-node-id');
|
|
510
|
+
const entityType = el.getAttribute('data-entity-type') || 'all';
|
|
511
|
+
if (nodeId) onSelectEntity?.(nodeId, entityType);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
385
514
|
const id = el.getAttribute('data-id');
|
|
386
515
|
if (!id) return;
|
|
387
516
|
if (action === 'toggle') onToggleExpand?.(id);
|
|
388
517
|
if (action === 'add-child') onAddChild?.(id);
|
|
518
|
+
if (action === 'add-diary') onAddDiary?.(id);
|
|
389
519
|
if (action === 'edit') onEdit?.(id);
|
|
390
520
|
if (action === 'delete') onDelete?.(id);
|
|
391
521
|
if (action === 'quick-chat') onQuickChat?.(id, el);
|
|
@@ -402,5 +532,113 @@ export function mountWorkTree(targetId, options) {
|
|
|
402
532
|
if (renderMode !== 'dense') {
|
|
403
533
|
applyAdaptiveLeafGridLayout(container);
|
|
404
534
|
applyAdaptiveRootMixedGridLayout(container);
|
|
535
|
+
return;
|
|
405
536
|
}
|
|
537
|
+
|
|
538
|
+
let draggingNodeId = '';
|
|
539
|
+
let draggingLaneId = '';
|
|
540
|
+
const dragDropPositionByNodeId = new Map();
|
|
541
|
+
const rowEls = Array.from(container.querySelectorAll('.lane-tree-node-row[data-node-id]'));
|
|
542
|
+
rowEls.forEach((el) => {
|
|
543
|
+
const nodeId = el.getAttribute('data-node-id') || '';
|
|
544
|
+
el.addEventListener('dragstart', (event) => {
|
|
545
|
+
draggingNodeId = nodeId;
|
|
546
|
+
draggingLaneId = '';
|
|
547
|
+
el.classList.add('is-dragging');
|
|
548
|
+
event.stopPropagation();
|
|
549
|
+
event.dataTransfer?.setData('text/plain', nodeId);
|
|
550
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
|
|
551
|
+
});
|
|
552
|
+
el.addEventListener('dragend', () => {
|
|
553
|
+
draggingNodeId = '';
|
|
554
|
+
el.classList.remove('is-dragging');
|
|
555
|
+
rowEls.forEach((rowEl) => {
|
|
556
|
+
rowEl.classList.remove('drag-over-target');
|
|
557
|
+
rowEl.removeAttribute('data-drop-position');
|
|
558
|
+
});
|
|
559
|
+
dragDropPositionByNodeId.clear();
|
|
560
|
+
});
|
|
561
|
+
el.addEventListener('dragover', (event) => {
|
|
562
|
+
if (!draggingNodeId || draggingNodeId === nodeId) return;
|
|
563
|
+
event.preventDefault();
|
|
564
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
|
|
565
|
+
const rect = el.getBoundingClientRect();
|
|
566
|
+
const offsetY = event.clientY - rect.top;
|
|
567
|
+
const ratio = rect.height > 0 ? offsetY / rect.height : 0.5;
|
|
568
|
+
let dropPosition = 'child';
|
|
569
|
+
if (ratio < 0.25) dropPosition = 'before';
|
|
570
|
+
else if (ratio > 0.75) dropPosition = 'after';
|
|
571
|
+
dragDropPositionByNodeId.set(nodeId, dropPosition);
|
|
572
|
+
el.setAttribute('data-drop-position', dropPosition);
|
|
573
|
+
el.classList.add('drag-over-target');
|
|
574
|
+
});
|
|
575
|
+
el.addEventListener('dragleave', () => {
|
|
576
|
+
el.classList.remove('drag-over-target');
|
|
577
|
+
el.removeAttribute('data-drop-position');
|
|
578
|
+
dragDropPositionByNodeId.delete(nodeId);
|
|
579
|
+
});
|
|
580
|
+
el.addEventListener('drop', (event) => {
|
|
581
|
+
event.preventDefault();
|
|
582
|
+
el.classList.remove('drag-over-target');
|
|
583
|
+
const dropPosition = dragDropPositionByNodeId.get(nodeId) || 'child';
|
|
584
|
+
el.removeAttribute('data-drop-position');
|
|
585
|
+
dragDropPositionByNodeId.delete(nodeId);
|
|
586
|
+
if (!draggingNodeId || draggingNodeId === nodeId) return;
|
|
587
|
+
if (dropPosition === 'before' || dropPosition === 'after') {
|
|
588
|
+
onReorderSiblings?.(draggingNodeId, nodeId, dropPosition);
|
|
589
|
+
} else {
|
|
590
|
+
onMoveNode?.(draggingNodeId, nodeId);
|
|
591
|
+
}
|
|
592
|
+
draggingNodeId = '';
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
const board = container.querySelector('.dense-lane-board');
|
|
597
|
+
board?.addEventListener('dragover', (event) => {
|
|
598
|
+
if (!draggingNodeId) return;
|
|
599
|
+
event.preventDefault();
|
|
600
|
+
});
|
|
601
|
+
board?.addEventListener('drop', (event) => {
|
|
602
|
+
if (!draggingNodeId) return;
|
|
603
|
+
event.preventDefault();
|
|
604
|
+
onMoveNode?.(draggingNodeId, null);
|
|
605
|
+
draggingNodeId = '';
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
const laneEls = Array.from(container.querySelectorAll('.dense-lane[data-root-id]'));
|
|
609
|
+
const laneHandleEls = Array.from(container.querySelectorAll('.dense-lane-drag-handle[data-lane-drag-id]'));
|
|
610
|
+
laneHandleEls.forEach((handle) => {
|
|
611
|
+
const rootId = handle.getAttribute('data-lane-drag-id') || '';
|
|
612
|
+
handle.addEventListener('dragstart', (event) => {
|
|
613
|
+
draggingLaneId = rootId;
|
|
614
|
+
draggingNodeId = '';
|
|
615
|
+
const lane = handle.closest('.dense-lane');
|
|
616
|
+
lane?.classList.add('is-lane-dragging');
|
|
617
|
+
event.dataTransfer?.setData('text/plain', rootId);
|
|
618
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
|
|
619
|
+
});
|
|
620
|
+
handle.addEventListener('dragend', () => {
|
|
621
|
+
const lane = handle.closest('.dense-lane');
|
|
622
|
+
lane?.classList.remove('is-lane-dragging');
|
|
623
|
+
draggingLaneId = '';
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
laneEls.forEach((el) => {
|
|
627
|
+
const rootId = el.getAttribute('data-root-id') || '';
|
|
628
|
+
el.addEventListener('dragover', (event) => {
|
|
629
|
+
if (!draggingLaneId || draggingLaneId === rootId) return;
|
|
630
|
+
event.preventDefault();
|
|
631
|
+
el.classList.add('lane-drag-over-target');
|
|
632
|
+
});
|
|
633
|
+
el.addEventListener('dragleave', () => {
|
|
634
|
+
el.classList.remove('lane-drag-over-target');
|
|
635
|
+
});
|
|
636
|
+
el.addEventListener('drop', (event) => {
|
|
637
|
+
event.preventDefault();
|
|
638
|
+
el.classList.remove('lane-drag-over-target');
|
|
639
|
+
if (!draggingLaneId || draggingLaneId === rootId) return;
|
|
640
|
+
onReorderLanes?.(draggingLaneId, rootId);
|
|
641
|
+
draggingLaneId = '';
|
|
642
|
+
});
|
|
643
|
+
});
|
|
406
644
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qnote/q-ai-note",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI-assisted personal work sandbox and diary system",
|
|
6
6
|
"main": "dist/server/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"q-ai-note": "dist/cli.js"
|
|
8
|
+
"q-ai-note": "dist/cli.js",
|
|
9
|
+
"q-ai-note-server": "dist/cli.js",
|
|
10
|
+
"q-ai-note-my-server": "dist/cli.js"
|
|
9
11
|
},
|
|
10
12
|
"files": [
|
|
11
13
|
"dist/**/*",
|
|
@@ -16,7 +18,8 @@
|
|
|
16
18
|
"build": "tsc && node scripts/copy-web-assets.mjs",
|
|
17
19
|
"start": "PORT=3000 node dist/server/index.js",
|
|
18
20
|
"test": "vitest run",
|
|
19
|
-
"test:e2e": "playwright test",
|
|
21
|
+
"test:e2e": "env -u CI playwright test --grep-invert @ai --workers=1",
|
|
22
|
+
"test:e2e:full": "playwright test",
|
|
20
23
|
"test:watch": "vitest",
|
|
21
24
|
"paths:check": "tsx scripts/print-runtime-paths.ts",
|
|
22
25
|
"prepack": "npm run build"
|