@qnote/q-ai-note 1.0.8 → 1.0.9

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/dist/web/app.js CHANGED
@@ -487,16 +487,50 @@ function renderWorkTree() {
487
487
  });
488
488
  await loadSandbox(state.currentSandbox.id);
489
489
  },
490
- onReorderLanes: treeReadonly ? undefined : async (dragRootId, targetRootId) => {
490
+ onReorderLanes: treeReadonly ? undefined : async (dragRootId, targetRootId, position = 'before') => {
491
491
  if (!state.currentSandbox) return;
492
- if (!dragRootId || !targetRootId || dragRootId === targetRootId) return;
492
+ if (!dragRootId) return;
493
+ if (targetRootId && dragRootId === targetRootId) return;
493
494
  const roots = (state.currentSandbox.items || []).filter((item) => !item.parent_id);
494
495
  const ordered = [...roots].sort((a, b) => compareSiblingOrder(a, b, 'lane_order_key'));
496
+ const missingLaneKeyRoots = ordered.filter((item) => !getNodeOrderKey(item, 'lane_order_key'));
497
+ if (missingLaneKeyRoots.length > 0) {
498
+ let leftKey = '';
499
+ for (const root of ordered) {
500
+ const currentKey = getNodeOrderKey(root, 'lane_order_key');
501
+ if (currentKey) {
502
+ leftKey = currentKey;
503
+ continue;
504
+ }
505
+ const generatedKey = rankBetween(leftKey, '');
506
+ await apiRequest(`${API_BASE}/items/${root.id}`, {
507
+ method: 'PUT',
508
+ body: JSON.stringify({
509
+ extra_data: {
510
+ ...(root.extra_data || {}),
511
+ lane_order_key: generatedKey,
512
+ },
513
+ }),
514
+ });
515
+ root.extra_data = {
516
+ ...(root.extra_data || {}),
517
+ lane_order_key: generatedKey,
518
+ };
519
+ leftKey = generatedKey;
520
+ }
521
+ }
495
522
  const fromIdx = ordered.findIndex((item) => item.id === dragRootId);
496
- const toIdx = ordered.findIndex((item) => item.id === targetRootId);
497
- if (fromIdx < 0 || toIdx < 0) return;
523
+ if (fromIdx < 0) return;
498
524
  const [moved] = ordered.splice(fromIdx, 1);
499
- ordered.splice(toIdx, 0, moved);
525
+ let insertIndex = ordered.length;
526
+ if (targetRootId) {
527
+ const targetIdx = ordered.findIndex((item) => item.id === targetRootId);
528
+ if (targetIdx < 0) return;
529
+ insertIndex = position === 'after' ? targetIdx + 1 : targetIdx;
530
+ } else if (position !== 'end') {
531
+ insertIndex = ordered.length;
532
+ }
533
+ ordered.splice(insertIndex, 0, moved);
500
534
  const movedIndex = ordered.findIndex((item) => item.id === dragRootId);
501
535
  const left = ordered[movedIndex - 1] || null;
502
536
  const right = ordered[movedIndex + 1] || null;
@@ -623,7 +657,7 @@ function getNodeOrderKey(item, keyName = 'order_key') {
623
657
  function compareSiblingOrder(a, b, keyName = 'order_key') {
624
658
  const keyA = getNodeOrderKey(a, keyName);
625
659
  const keyB = getNodeOrderKey(b, keyName);
626
- if (keyA && keyB && keyA !== keyB) return keyA.localeCompare(keyB);
660
+ if (keyA && keyB && keyA !== keyB) return keyA < keyB ? -1 : 1;
627
661
  if (keyA && !keyB) return -1;
628
662
  if (!keyA && keyB) return 1;
629
663
  const numericA = Number(a?.extra_data?.lane_order);
@@ -633,7 +667,10 @@ function compareSiblingOrder(a, b, keyName = 'order_key') {
633
667
  if (keyName === 'lane_order_key' && validA && validB && numericA !== numericB) return numericA - numericB;
634
668
  if (keyName === 'lane_order_key' && validA && !validB) return -1;
635
669
  if (keyName === 'lane_order_key' && !validA && validB) return 1;
636
- return String(a?.created_at || '').localeCompare(String(b?.created_at || ''));
670
+ const createdA = String(a?.created_at || '');
671
+ const createdB = String(b?.created_at || '');
672
+ if (createdA === createdB) return 0;
673
+ return createdA < createdB ? -1 : 1;
637
674
  }
638
675
 
639
676
  function populateParentSelect(items, preferredParentId = null) {
@@ -1468,7 +1505,14 @@ async function loadSandboxChats(sandboxId) {
1468
1505
  const chats = await apiRequest(`${API_BASE}/chats/sandbox/${sandboxId}`);
1469
1506
  state.chats = chats;
1470
1507
 
1471
- mountHtmlList('sandbox-chat-messages', chats.map((chat) => renderChatEntry(chat, { safeText, renderAIActionMessage })));
1508
+ mountHtmlList(
1509
+ 'sandbox-chat-messages',
1510
+ chats.map((chat) => renderChatEntry(chat, {
1511
+ safeText,
1512
+ renderAIActionMessage,
1513
+ renderContent: (content) => renderMarkdownSnippet(content),
1514
+ })),
1515
+ );
1472
1516
 
1473
1517
  messages.scrollTop = messages.scrollHeight;
1474
1518
  }
@@ -1500,7 +1544,7 @@ function renderAIActionMessage(action) {
1500
1544
  };
1501
1545
 
1502
1546
  if (actionType === 'response' || actionType === 'clarify') {
1503
- return `<div class="chat-message assistant">${escapeHtml(action.response || action.observation || '')}</div>`;
1547
+ return `<div class="chat-message assistant">${renderMarkdownSnippet(action.response || action.observation || '')}</div>`;
1504
1548
  }
1505
1549
  else if (actionType === 'confirm' && action.confirm_items) {
1506
1550
  // Skip confirm, go directly to done
@@ -1527,7 +1571,7 @@ function renderAIActionMessage(action) {
1527
1571
  </div>`;
1528
1572
  }
1529
1573
 
1530
- return `<div class="chat-message assistant">${escapeHtml(action.response || '')}</div>`;
1574
+ return `<div class="chat-message assistant">${renderMarkdownSnippet(action.response || '')}</div>`;
1531
1575
  }
1532
1576
 
1533
1577
  window.undoOperation = async function(operationId, btn) {
@@ -2531,7 +2575,7 @@ async function initApp() {
2531
2575
  const actionType = action.action;
2532
2576
 
2533
2577
  if (actionType === 'response' || actionType === 'clarify') {
2534
- messages.insertAdjacentHTML('beforeend', `<div class="chat-message assistant">${safeText(action.response || action.observation || '')}</div>`);
2578
+ messages.insertAdjacentHTML('beforeend', `<div class="chat-message assistant">${renderMarkdownSnippet(action.response || action.observation || '')}</div>`);
2535
2579
  messages.scrollTop = messages.scrollHeight;
2536
2580
 
2537
2581
  if (actionType === 'clarify') {
@@ -2623,7 +2667,7 @@ async function initApp() {
2623
2667
  state.pendingAction = null;
2624
2668
  }
2625
2669
  else if (actionType === 'stop') {
2626
- messages.insertAdjacentHTML('beforeend', `<div class="chat-message assistant">${safeText(action.observation || '已取消操作')}</div>`);
2670
+ messages.insertAdjacentHTML('beforeend', `<div class="chat-message assistant">${renderMarkdownSnippet(action.observation || '已取消操作')}</div>`);
2627
2671
  messages.scrollTop = messages.scrollHeight;
2628
2672
  state.pendingAction = null;
2629
2673
  }
@@ -14,7 +14,7 @@ export function tryParseAssistantAction(content) {
14
14
  }
15
15
 
16
16
  export function renderChatEntry(chat, helpers) {
17
- const { safeText, renderAIActionMessage } = helpers;
17
+ const { safeText, renderAIActionMessage, renderContent } = helpers;
18
18
 
19
19
  if (!('role' in chat)) {
20
20
  return `<div class="chat-message diary">[日记] ${safeText(chat.content)}</div>`;
@@ -25,6 +25,10 @@ export function renderChatEntry(chat, helpers) {
25
25
  if (parsedAction) {
26
26
  return renderAIActionMessage(parsedAction);
27
27
  }
28
+ const contentHtml = typeof renderContent === 'function'
29
+ ? renderContent(chat.content)
30
+ : safeText(chat.content);
31
+ return `<div class="chat-message assistant">${contentHtml}</div>`;
28
32
  }
29
33
 
30
34
  return `<div class="chat-message ${chat.role}">${safeText(chat.content)}</div>`;
@@ -647,6 +647,30 @@ h2 {
647
647
  flex-wrap: nowrap;
648
648
  }
649
649
 
650
+ .dense-lane-drop-slot {
651
+ width: 14px;
652
+ min-width: 14px;
653
+ align-self: stretch;
654
+ border-radius: 6px;
655
+ min-height: 180px;
656
+ transition: background-color 0.15s ease, box-shadow 0.15s ease;
657
+ }
658
+
659
+ .dense-lane-drop-slot.end {
660
+ width: 24px;
661
+ min-width: 24px;
662
+ }
663
+
664
+ .dense-lane-drop-slot.lane-slot-drag-over {
665
+ background: rgba(59, 130, 246, 0.28);
666
+ box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.45);
667
+ }
668
+
669
+ .dense-lane-board.is-lane-dragging-active .dense-lane-drop-slot {
670
+ background: rgba(148, 163, 184, 0.14);
671
+ box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.22);
672
+ }
673
+
650
674
  .dense-lane {
651
675
  min-width: var(--lane-width, 220px);
652
676
  width: var(--lane-width, 220px);
@@ -411,6 +411,7 @@ function renderDenseTree(roots, byParent, expandedIdSet, entitySummaryByNodeId,
411
411
  const laneWidth = getDenseLaneWidthPx(root, byParent, showAssignee);
412
412
  const laneNameMax = Math.max(120, laneWidth - 86);
413
413
  return `
414
+ <div class="dense-lane-drop-slot" data-lane-slot-root-id="${esc(root.id)}" data-lane-slot-position="before" title="拖拽到此处:插入到当前泳道之前"></div>
414
415
  <section class="dense-lane" data-root-id="${esc(root.id)}" style="--lane-width:${laneWidth}px;--lane-name-max:${laneNameMax}px;">
415
416
  ${readonly ? '' : `<div class="dense-lane-drag-handle" data-lane-drag-id="${esc(root.id)}" draggable="true" title="拖拽调整泳道顺序">⋮⋮</div>`}
416
417
  <div class="dense-lane-body">
@@ -419,6 +420,7 @@ function renderDenseTree(roots, byParent, expandedIdSet, entitySummaryByNodeId,
419
420
  </section>
420
421
  `;
421
422
  }).join('')}
423
+ <div class="dense-lane-drop-slot end" data-lane-slot-end="1" title="拖拽到此处:移动到最后"></div>
422
424
  </div>
423
425
  `;
424
426
  }
@@ -608,12 +610,16 @@ export function mountWorkTree(targetId, options) {
608
610
  });
609
611
 
610
612
  const laneEls = Array.from(container.querySelectorAll('.dense-lane[data-root-id]'));
613
+ const laneSlotEls = Array.from(container.querySelectorAll('.dense-lane-drop-slot[data-lane-slot-root-id]'));
614
+ const laneEndSlotEl = container.querySelector('.dense-lane-drop-slot[data-lane-slot-end="1"]');
615
+ const laneDropPositionByRootId = new Map();
611
616
  const laneHandleEls = Array.from(container.querySelectorAll('.dense-lane-drag-handle[data-lane-drag-id]'));
612
617
  laneHandleEls.forEach((handle) => {
613
618
  const rootId = handle.getAttribute('data-lane-drag-id') || '';
614
619
  handle.addEventListener('dragstart', (event) => {
615
620
  draggingLaneId = rootId;
616
621
  draggingNodeId = '';
622
+ board?.classList.add('is-lane-dragging-active');
617
623
  const lane = handle.closest('.dense-lane');
618
624
  lane?.classList.add('is-lane-dragging');
619
625
  event.dataTransfer?.setData('text/plain', rootId);
@@ -622,6 +628,14 @@ export function mountWorkTree(targetId, options) {
622
628
  handle.addEventListener('dragend', () => {
623
629
  const lane = handle.closest('.dense-lane');
624
630
  lane?.classList.remove('is-lane-dragging');
631
+ board?.classList.remove('is-lane-dragging-active');
632
+ laneEls.forEach((laneEl) => {
633
+ laneEl.classList.remove('lane-drag-over-target');
634
+ laneEl.removeAttribute('data-lane-drop-position');
635
+ });
636
+ laneSlotEls.forEach((slotEl) => slotEl.classList.remove('lane-slot-drag-over'));
637
+ laneEndSlotEl?.classList.remove('lane-slot-drag-over');
638
+ laneDropPositionByRootId.clear();
625
639
  draggingLaneId = '';
626
640
  });
627
641
  });
@@ -630,17 +644,101 @@ export function mountWorkTree(targetId, options) {
630
644
  el.addEventListener('dragover', (event) => {
631
645
  if (!draggingLaneId || draggingLaneId === rootId) return;
632
646
  event.preventDefault();
647
+ const rect = el.getBoundingClientRect();
648
+ const offsetX = event.clientX - rect.left;
649
+ const ratio = rect.width > 0 ? offsetX / rect.width : 0.5;
650
+ const laneDropPosition = ratio >= 0.5 ? 'after' : 'before';
651
+ laneDropPositionByRootId.set(rootId, laneDropPosition);
652
+ el.setAttribute('data-lane-drop-position', laneDropPosition);
633
653
  el.classList.add('lane-drag-over-target');
634
654
  });
635
655
  el.addEventListener('dragleave', () => {
636
656
  el.classList.remove('lane-drag-over-target');
657
+ el.removeAttribute('data-lane-drop-position');
658
+ laneDropPositionByRootId.delete(rootId);
637
659
  });
638
660
  el.addEventListener('drop', (event) => {
639
661
  event.preventDefault();
640
662
  el.classList.remove('lane-drag-over-target');
663
+ let laneDropPosition = laneDropPositionByRootId.get(rootId);
664
+ if (!laneDropPosition) {
665
+ const rect = el.getBoundingClientRect();
666
+ const offsetX = event.clientX - rect.left;
667
+ const ratio = rect.width > 0 ? offsetX / rect.width : 0.5;
668
+ laneDropPosition = ratio >= 0.5 ? 'after' : 'before';
669
+ }
670
+ el.removeAttribute('data-lane-drop-position');
671
+ laneDropPositionByRootId.delete(rootId);
641
672
  if (!draggingLaneId || draggingLaneId === rootId) return;
642
- onReorderLanes?.(draggingLaneId, rootId);
673
+ if (laneDropPosition === 'before') {
674
+ event.stopPropagation();
675
+ onReorderLanes?.(draggingLaneId, rootId, 'before');
676
+ board?.classList.remove('is-lane-dragging-active');
677
+ draggingLaneId = '';
678
+ } else {
679
+ const orderedRootIds = laneEls
680
+ .map((laneEl) => laneEl.getAttribute('data-root-id') || '')
681
+ .filter((id) => id && id !== draggingLaneId);
682
+ const isLastTarget = orderedRootIds.length > 0 && orderedRootIds[orderedRootIds.length - 1] === rootId;
683
+ if (!isLastTarget) {
684
+ event.stopPropagation();
685
+ onReorderLanes?.(draggingLaneId, rootId, 'after');
686
+ board?.classList.remove('is-lane-dragging-active');
687
+ draggingLaneId = '';
688
+ }
689
+ }
690
+ });
691
+ });
692
+ laneSlotEls.forEach((slotEl) => {
693
+ const targetRootId = slotEl.getAttribute('data-lane-slot-root-id') || '';
694
+ slotEl.addEventListener('dragover', (event) => {
695
+ if (!draggingLaneId || draggingLaneId === targetRootId) return;
696
+ event.preventDefault();
697
+ slotEl.classList.add('lane-slot-drag-over');
698
+ });
699
+ slotEl.addEventListener('dragleave', () => {
700
+ slotEl.classList.remove('lane-slot-drag-over');
701
+ });
702
+ slotEl.addEventListener('drop', (event) => {
703
+ event.preventDefault();
704
+ event.stopPropagation();
705
+ slotEl.classList.remove('lane-slot-drag-over');
706
+ if (!draggingLaneId || draggingLaneId === targetRootId) return;
707
+ onReorderLanes?.(draggingLaneId, targetRootId, 'before');
708
+ board?.classList.remove('is-lane-dragging-active');
643
709
  draggingLaneId = '';
644
710
  });
645
711
  });
712
+ laneEndSlotEl?.addEventListener('dragover', (event) => {
713
+ if (!draggingLaneId) return;
714
+ event.preventDefault();
715
+ laneEndSlotEl.classList.add('lane-slot-drag-over');
716
+ });
717
+ laneEndSlotEl?.addEventListener('dragleave', () => {
718
+ laneEndSlotEl.classList.remove('lane-slot-drag-over');
719
+ });
720
+ laneEndSlotEl?.addEventListener('drop', (event) => {
721
+ if (!draggingLaneId) return;
722
+ event.preventDefault();
723
+ event.stopPropagation();
724
+ laneEndSlotEl.classList.remove('lane-slot-drag-over');
725
+ onReorderLanes?.(draggingLaneId, '', 'end');
726
+ board?.classList.remove('is-lane-dragging-active');
727
+ draggingLaneId = '';
728
+ });
729
+ board?.addEventListener('dragover', (event) => {
730
+ if (!draggingLaneId) return;
731
+ event.preventDefault();
732
+ });
733
+ board?.addEventListener('drop', (event) => {
734
+ if (!draggingLaneId) return;
735
+ event.preventDefault();
736
+ laneEls.forEach((laneEl) => {
737
+ laneEl.classList.remove('lane-drag-over-target');
738
+ laneEl.removeAttribute('data-lane-drop-position');
739
+ });
740
+ laneDropPositionByRootId.clear();
741
+ onReorderLanes?.(draggingLaneId, '', 'end');
742
+ draggingLaneId = '';
743
+ });
646
744
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qnote/q-ai-note",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "type": "module",
5
5
  "description": "AI-assisted personal work sandbox and diary system",
6
6
  "main": "dist/server/index.js",