@musnows/scriverse 1.1.0 → 1.1.2

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.
@@ -1452,9 +1452,12 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1452
1452
  .chapter-add-button { flex: none; }
1453
1453
  .volume-chapters { display: block; }
1454
1454
  .volume-node.is-collapsed .volume-chapters { display: none; }
1455
- .chapter-node { display: grid; grid-template-columns: minmax(0, 1fr) max-content; gap: 8px; width: calc(100% + 6px); padding: 9px 8px 9px 24px; border: 0; background: transparent; border-radius: 4px; text-align: left; font-size: 12px; }
1455
+ .chapter-node { display: grid; grid-template-columns: minmax(0, 1fr) max-content; gap: 8px; width: calc(100% + 6px); padding: 9px 8px 9px 24px; border: 0; background: transparent; border-radius: 4px; text-align: left; font-size: 12px; position: relative; cursor: pointer; user-select: none; }
1456
1456
  .chapter-node:hover, .chapter-node.active { background: var(--row-active); }
1457
- .chapter-node[draggable="true"] { cursor: grab; }
1457
+ .chapter-drag-handle { position: absolute; inset: 0 auto 0 0; display: grid; place-items: center; width: 24px; color: var(--muted); cursor: grab; opacity: .5; }
1458
+ .chapter-drag-handle svg { width: 12px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; pointer-events: none; }
1459
+ .chapter-node:hover .chapter-drag-handle, .chapter-node:focus-visible .chapter-drag-handle { opacity: 1; }
1460
+ .chapter-node.is-dragging .chapter-drag-handle { cursor: grabbing; }
1458
1461
  .chapter-node.is-dragging { opacity: .42; cursor: grabbing; }
1459
1462
  .chapter-node.is-drag-over { box-shadow: inset 0 2px 0 var(--accent); }
1460
1463
  .chapter-node.is-drag-over.drop-after { box-shadow: inset 0 -2px 0 var(--accent); }
@@ -1580,7 +1583,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1580
1583
  .editor-body { display: flex; grid-row: 3; min-height: 0; flex-direction: column; }
1581
1584
  .chapter-editor-frame { position: relative; display: flex; flex-direction: column; flex: 1 1 auto; width: 100%; min-height: 0; margin: 0; overflow: hidden; }
1582
1585
  .chapter-editor-scroll { position: relative; display: grid; grid-template-columns: 38px minmax(0, 1fr); flex: 1 1 auto; width: 100%; min-height: 0; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; }
1583
- .chapter-line-numbers { position: relative; z-index: 2; grid-column: 1; grid-row: 1; min-height: 0; overflow: hidden; border-right: 1px solid var(--line); background: linear-gradient(to right, transparent, rgba(143,132,116,.04)); color: var(--muted); font-family: var(--font-latin), monospace; font-size: clamp(11px, calc(var(--editor-font-size) * .72), 14px); line-height: 1.2; user-select: none; }
1586
+ .chapter-line-numbers { position: relative; z-index: 2; grid-column: 1; grid-row: 1; min-height: 0; overflow: clip; overflow-clip-margin: 12px; border-right: 1px solid var(--line); background: linear-gradient(to right, transparent, rgba(143,132,116,.04)); color: var(--muted); font-family: var(--font-latin), monospace; font-size: clamp(11px, calc(var(--editor-font-size) * .72), 14px); line-height: 1.2; user-select: none; }
1584
1587
  #chapter-line-numbers-inner { position: absolute; top: 32px; left: 0; right: 0; }
1585
1588
  .chapter-line-number { position: absolute; left: 0; box-sizing: border-box; display: flex; width: 100%; align-items: flex-start; justify-content: center; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; font-variant-numeric: tabular-nums; text-align: center; }
1586
1589
  .chapter-line-number-select { position: relative; display: flex; width: 100%; height: 100%; align-items: flex-start; justify-content: center; padding-inline: 3px; border: 0; background: transparent; color: inherit; font: inherit; font-variant-numeric: tabular-nums; cursor: pointer; }
package/dist/store.js CHANGED
@@ -2571,25 +2571,32 @@ export class Store {
2571
2571
  this.assertExpectedRevision("chapter", chapterId, expectedVersionNo, "章节", Number(lockedChapter.versionNo));
2572
2572
  const sourceVolumeId = String(lockedChapter.volumeId);
2573
2573
  const targetVolumeId = input.volumeId;
2574
- const sourceChapterIds = this.db.all("SELECT id FROM chapters WHERE volume_id = ? AND deleted_at IS NULL ORDER BY sort_order, created_at, id", sourceVolumeId).map((row) => requiredString(row, "id")).filter((idValue) => idValue !== chapterId);
2574
+ const sourceChapters = this.db.all("SELECT id, sort_order FROM chapters WHERE volume_id = ? AND deleted_at IS NULL ORDER BY sort_order, created_at, id", sourceVolumeId);
2575
+ const sourceChapterIds = sourceChapters.map((row) => requiredString(row, "id")).filter((idValue) => idValue !== chapterId);
2576
+ const targetChapters = sourceVolumeId === targetVolumeId
2577
+ ? sourceChapters
2578
+ : this.db.all("SELECT id, sort_order FROM chapters WHERE volume_id = ? AND deleted_at IS NULL ORDER BY sort_order, created_at, id", targetVolumeId);
2579
+ const previousSortOrders = new Map([...sourceChapters, ...targetChapters].map((row) => [requiredString(row, "id"), numberValue(row, "sort_order")]));
2575
2580
  const targetChapterIds = sourceVolumeId === targetVolumeId
2576
2581
  ? sourceChapterIds
2577
- : this.db.all("SELECT id FROM chapters WHERE volume_id = ? AND deleted_at IS NULL ORDER BY sort_order, created_at, id", targetVolumeId).map((row) => requiredString(row, "id")).filter((idValue) => idValue !== chapterId);
2582
+ : targetChapters.map((row) => requiredString(row, "id")).filter((idValue) => idValue !== chapterId);
2578
2583
  const targetIndex = Math.min(input.sortOrder, targetChapterIds.length);
2579
2584
  targetChapterIds.splice(targetIndex, 0, chapterId);
2580
2585
  const timestamp = now();
2581
- sourceChapterIds.forEach((idValue, sortOrder) => {
2586
+ const updateSortOrder = (idValue, sortOrder) => {
2587
+ if (idValue === chapterId || previousSortOrders.get(idValue) === sortOrder)
2588
+ return;
2582
2589
  this.db.run("UPDATE chapters SET sort_order = ?, updated_at = ? WHERE id = ?", sortOrder, timestamp, idValue);
2583
- });
2584
- targetChapterIds.forEach((idValue, sortOrder) => {
2585
- this.db.run("UPDATE chapters SET volume_id = ?, sort_order = ?, updated_at = ? WHERE id = ?", targetVolumeId, sortOrder, timestamp, idValue);
2586
- });
2590
+ };
2591
+ if (sourceVolumeId !== targetVolumeId)
2592
+ sourceChapterIds.forEach(updateSortOrder);
2593
+ targetChapterIds.forEach(updateSortOrder);
2587
2594
  const versionNo = Number(lockedChapter.versionNo) + 1;
2588
2595
  this.db.run(`UPDATE analysis_tasks SET status = 'expired', updated_at = ?
2589
2596
  WHERE work_id = ? AND status IN ('pending', 'running', 'completed', 'partial', 'review')
2590
2597
  AND json_extract(scope_json, '$.type') = 'volume'
2591
2598
  AND json_extract(scope_json, '$.volumeId') IN (?, ?)`, timestamp, String(lockedChapter.workId), sourceVolumeId, targetVolumeId);
2592
- this.db.run("UPDATE chapters SET version_no = ?, analysis_status = 'expired', updated_at = ? WHERE id = ?", versionNo, timestamp, chapterId);
2599
+ this.db.run("UPDATE chapters SET volume_id = ?, sort_order = ?, version_no = ?, analysis_status = 'expired', updated_at = ? WHERE id = ?", targetVolumeId, targetIndex, versionNo, timestamp, chapterId);
2593
2600
  this.syncChapterParagraphSearchVersion(chapterId, versionNo);
2594
2601
  this.insertChapterVersionRow({
2595
2602
  workId: String(lockedChapter.workId),