@musnows/scriverse 0.8.0 → 0.8.1
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/ai.js +22 -2
- package/dist/ai.js.map +1 -1
- package/dist/app.js +39 -10
- package/dist/app.js.map +1 -1
- package/dist/public/app.js +161 -53
- package/dist/public/index.html +4 -4
- package/dist/public/stream-typewriter.d.ts +1 -0
- package/dist/public/stream-typewriter.js +12 -0
- package/dist/public/styles.css +19 -13
- package/dist/public/work-permissions.d.ts +1 -1
- package/dist/public/work-permissions.js +13 -1
- package/dist/store.js +50 -10
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +34 -7
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/work-permissions.js +13 -2
- package/dist/work-permissions.js.map +1 -1
- package/package.json +1 -1
package/dist/public/styles.css
CHANGED
|
@@ -371,10 +371,7 @@ input[type="checkbox"][hidden] { display: none; }
|
|
|
371
371
|
radial-gradient(circle at 50% -20%, var(--app-glow), transparent 45%),
|
|
372
372
|
var(--paper);
|
|
373
373
|
}
|
|
374
|
-
.app-shell:not(.shelf-mode):not(.ai-workspace-mode) { grid-template-columns: var(--left-panel-width) minmax(480px, 1fr); }
|
|
375
|
-
.app-shell:not(.shelf-mode):not(.ai-workspace-mode) .ai-panel { display: none; }
|
|
376
374
|
.app-shell.ai-workspace-mode > :is(.topbar, .left-panel, .main-panel) { visibility: hidden; pointer-events: none; }
|
|
377
|
-
|
|
378
375
|
.topbar {
|
|
379
376
|
grid-column: 1 / -1;
|
|
380
377
|
display: grid;
|
|
@@ -557,7 +554,7 @@ input[type="checkbox"][hidden] { display: none; }
|
|
|
557
554
|
.left-panel-body { flex: 1 1 auto; min-height: 0; padding: 18px 14px 16px; overflow-y: auto; }
|
|
558
555
|
.ai-panel { container-type: inline-size; border-left: 1px solid var(--line); display: flex; flex-direction: column; padding: 14px 16px 18px; min-width: 0; }
|
|
559
556
|
.panel-collapse-button { z-index: 8; flex: 0 0 auto; width: 24px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: var(--panel); color: var(--muted); font-size: 18px; line-height: 1; }
|
|
560
|
-
.ai-heading #ai-panel-toggle { flex-basis: 30px; width: 30px; height: 30px; }
|
|
557
|
+
.ai-heading #ai-panel-toggle { flex-basis: 30px; width: 30px; min-width: 30px; min-height: 30px; height: 30px; }
|
|
561
558
|
.panel-resize-handle { position: absolute; z-index: 7; top: 0; bottom: 0; width: 7px; cursor: col-resize; touch-action: none; }
|
|
562
559
|
.left-panel > .panel-resize-handle { right: 0; }.ai-panel > .panel-resize-handle { left: 0; }
|
|
563
560
|
.panel-resize-handle::after { content: ""; position: absolute; top: 0; bottom: 0; left: 3px; width: 1px; background: transparent; transition: background .14s ease; }
|
|
@@ -1368,7 +1365,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1368
1365
|
|
|
1369
1366
|
.hidden { display: none !important; }
|
|
1370
1367
|
.editor-view { container-name: editor-workspace; container-type: inline-size; display: grid; grid-template-rows: auto auto minmax(0, 1fr); height: 100%; }
|
|
1371
|
-
.editor-toolbar { position: relative; display: grid; grid-template-areas: "path path" "title actions"; grid-template-columns: minmax(0, 1fr) auto; align-items: flex-end; column-gap: 20px; row-gap: 6px; padding: 25px 7% 18px; border-bottom: 1px solid var(--line); }
|
|
1368
|
+
.editor-toolbar { position: relative; display: grid; grid-row: 1; grid-template-areas: "path path" "title actions"; grid-template-columns: minmax(0, 1fr) auto; align-items: flex-end; column-gap: 20px; row-gap: 6px; padding: 25px 7% 18px; border-bottom: 1px solid var(--line); }
|
|
1372
1369
|
#chapter-path { grid-area: path; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1373
1370
|
.chapter-title { grid-area: title; display: block; width: 100%; min-width: 0; border: 0; border-radius: 0; background: transparent; font-size: 27px; padding: 0; }
|
|
1374
1371
|
.chapter-title:focus { box-shadow: none; border-bottom: 1px solid var(--accent); }
|
|
@@ -1388,7 +1385,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1388
1385
|
.chapter-search-actions > span { flex: 1 1 120px; min-width: 0; color: var(--muted); font-size: 10px; line-height: 1.4; }
|
|
1389
1386
|
.chapter-search-actions button { min-height: 32px; padding: 6px 9px; font-size: 10px; }
|
|
1390
1387
|
.chapter-search-actions button:disabled { cursor: not-allowed; opacity: .45; }
|
|
1391
|
-
.chapter-foreshadow-reminder { --reminder-color: #80713d; display: grid; grid-template-areas: "summary actions" "details details"; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 9px 18px; padding: 10px 7%; border-bottom: 1px solid color-mix(in srgb, var(--reminder-color) 32%, var(--line)); background: color-mix(in srgb, var(--reminder-color) 7%, var(--surface)); }
|
|
1388
|
+
.chapter-foreshadow-reminder { --reminder-color: #80713d; display: grid; grid-row: 2; grid-template-areas: "summary actions" "details details"; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 9px 18px; padding: 10px 7%; border-bottom: 1px solid color-mix(in srgb, var(--reminder-color) 32%, var(--line)); background: color-mix(in srgb, var(--reminder-color) 7%, var(--surface)); }
|
|
1392
1389
|
.chapter-foreshadow-reminder[data-role="payoff"] { --reminder-color: #49745c; }
|
|
1393
1390
|
.chapter-foreshadow-reminder-summary { grid-area: summary; display: flex; min-width: 0; align-items: center; gap: 10px; }
|
|
1394
1391
|
.chapter-foreshadow-reminder-summary > div { display: grid; min-width: 0; gap: 2px; }
|
|
@@ -1411,11 +1408,16 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1411
1408
|
.chapter-foreshadow-reminder-actions { justify-content: flex-start; flex-wrap: wrap; }
|
|
1412
1409
|
.chapter-foreshadow-reminder-details { grid-template-columns: minmax(0, 1fr); }
|
|
1413
1410
|
}
|
|
1414
|
-
.editor-body { display: flex; min-height: 0; flex-direction: column; }
|
|
1411
|
+
.editor-body { display: flex; grid-row: 3; min-height: 0; flex-direction: column; }
|
|
1415
1412
|
.chapter-editor-frame { position: relative; display: grid; grid-template-columns: 38px minmax(0, 1fr); flex: 1 1 auto; width: 100%; min-height: 0; margin: 0; overflow: hidden; }
|
|
1416
1413
|
.chapter-line-numbers { position: relative; z-index: 2; grid-column: 1; grid-row: 1; min-height: 0; overflow: visible; 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; }
|
|
1417
1414
|
#chapter-line-numbers-inner { position: absolute; top: 32px; left: 0; right: 0; will-change: transform; }
|
|
1418
|
-
.chapter-line-number { position: absolute; left: 0; box-sizing: border-box; display: flex; width: 100%; align-items: flex-start; justify-content: center; padding: 0
|
|
1415
|
+
.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; }
|
|
1416
|
+
.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; }
|
|
1417
|
+
.chapter-line-number-select:focus-visible { z-index: 1; outline: 1px solid var(--accent); outline-offset: -1px; }
|
|
1418
|
+
.chapter-line-annotation-count { position: absolute; top: -2px; right: 0; z-index: 2; display: grid; min-width: 14px; height: 14px; place-items: center; padding: 0 3px; border: 1px solid var(--panel); border-radius: 999px; background: var(--accent); color: var(--paper); font-family: var(--font-latin), monospace; font-size: 9px; font-weight: 700; line-height: 1; cursor: pointer; box-shadow: 0 1px 3px rgba(49,42,32,.16); }
|
|
1419
|
+
.chapter-line-annotation-count:hover, .chapter-line-annotation-count:focus-visible { background: var(--accent-dark); }
|
|
1420
|
+
.chapter-line-annotation-count:focus-visible { outline: 1px solid var(--accent-dark); outline-offset: 1px; }
|
|
1419
1421
|
.chapter-line-number:hover { background: rgba(139,61,44,.08); color: var(--accent-dark); }
|
|
1420
1422
|
.chapter-line-number.is-line-selected { background: rgba(139,61,44,.16); color: var(--accent-dark); font-weight: 700; }
|
|
1421
1423
|
.chapter-line-number.is-line-selected::after { content: ""; position: absolute; top: 0; left: 100%; width: 100vw; height: 100%; background: rgba(139,61,44,.085); box-shadow: inset 3px 0 rgba(139,61,44,.28); pointer-events: none; }
|
|
@@ -2619,6 +2621,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2619
2621
|
.ai-error-settings-link:hover, .ai-error-settings-link:focus-visible { color: var(--accent-dark); }
|
|
2620
2622
|
.config-section.is-targeted { border-radius: 5px; box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 34%, transparent); }
|
|
2621
2623
|
.message-card-actions { position: absolute; right: 0; bottom: -31px; display: flex; align-items: center; gap: 12px; height: 24px; }
|
|
2624
|
+
.assistant-message .message-card-actions { right: auto; left: 0; }
|
|
2625
|
+
.user-message .message-card-actions { right: 0; left: auto; }
|
|
2622
2626
|
.message-card-actions button { display: inline-flex; align-items: center; gap: 4px; min-width: 0; padding: 3px 2px; border: 0; background: transparent; color: var(--muted); font-size: 9px; }
|
|
2623
2627
|
.message-card-actions button:hover, .message-card-actions button:focus-visible { color: var(--accent-dark); }
|
|
2624
2628
|
.message-action-icon { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
|
|
@@ -2725,7 +2729,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2725
2729
|
.ai-prompt-reference button { width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: inherit; font: 14px/1 var(--font-latin), monospace; }
|
|
2726
2730
|
.ai-prompt-reference button:hover, .ai-prompt-reference button:focus-visible { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
|
|
2727
2731
|
.prompt-composer-actions { position: absolute; right: 8px; bottom: 8px; display: flex; gap: 6px; align-items: center; }
|
|
2728
|
-
.ai-context-meter { --context-usage: 0; --context-meter-color: var(--green); position: relative; display: grid; flex: 0 0 32px; place-items: center; width: 32px; height: 32px; overflow: visible; border: 0; border-radius: 50%; background: conic-gradient(var(--context-meter-color) calc(var(--context-usage) * 1%), rgba(143,132,116,.2) 0); color: var(--ink); font-family: var(--font-latin); line-height: 1; cursor: pointer; }
|
|
2732
|
+
.ai-context-meter { --context-usage: 0; --context-meter-color: var(--green); position: relative; display: grid; flex: 0 0 32px; place-items: center; width: 32px; min-height: 32px; height: 32px; overflow: visible; border: 0; border-radius: 50%; background: conic-gradient(var(--context-meter-color) calc(var(--context-usage) * 1%), rgba(143,132,116,.2) 0); color: var(--ink); font-family: var(--font-latin); line-height: 1; cursor: pointer; }
|
|
2729
2733
|
.ai-context-meter::before { content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--paper); box-shadow: inset 0 0 0 1px rgba(143,132,116,.12); }
|
|
2730
2734
|
.ai-context-meter:hover, .ai-context-meter:focus-visible, .ai-context-meter[aria-expanded="true"] { outline: 0; filter: brightness(.96); }
|
|
2731
2735
|
.ai-context-meter:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
|
|
@@ -2752,7 +2756,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2752
2756
|
.ai-context-distribution-row[data-key="skills"] { --distribution-color: var(--muted); }
|
|
2753
2757
|
.ai-context-distribution-row[data-key="context"] { --distribution-color: var(--green); }
|
|
2754
2758
|
.ai-context-distribution-row[data-key="left"] { --distribution-color: color-mix(in srgb, var(--muted) 55%, transparent); }
|
|
2755
|
-
.ai-send-button { display: grid; flex: 0 0 32px; place-items: center; width: 32px; min-width: 32px; height: 32px; padding: 0; border: 0; border-radius: 4px; background: var(--accent); color: #fff; box-shadow: 0 4px 12px rgba(139,61,44,.2); }
|
|
2759
|
+
.ai-send-button { display: grid; flex: 0 0 32px; place-items: center; width: 32px; min-width: 32px; min-height: 32px; height: 32px; padding: 0; border: 0; border-radius: 4px; background: var(--accent); color: #fff; box-shadow: 0 4px 12px rgba(139,61,44,.2); }
|
|
2756
2760
|
.ai-send-button:hover, .ai-send-button:focus-visible { background: var(--accent-dark); outline: 0; }
|
|
2757
2761
|
.ai-send-button:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 24%, transparent), 0 4px 12px rgba(139,61,44,.2); }
|
|
2758
2762
|
.ai-send-button:disabled { cursor: wait; opacity: .62; }
|
|
@@ -3655,7 +3659,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
3655
3659
|
overflow-x: hidden;
|
|
3656
3660
|
overflow-y: hidden;
|
|
3657
3661
|
}
|
|
3662
|
+
.app-shell:not(.shelf-mode):not(.ai-workspace-mode) { grid-template-columns: minmax(0, 1fr); }
|
|
3658
3663
|
.app-shell.ai-hidden-mode:not(.shelf-mode) { grid-template-columns: minmax(0, 1fr); }
|
|
3664
|
+
.app-shell.ai-workspace-mode > :is(.topbar, .left-panel, .main-panel) { visibility: hidden; pointer-events: none; }
|
|
3659
3665
|
.app-shell.shelf-mode {
|
|
3660
3666
|
grid-template-rows: auto minmax(0, 1fr);
|
|
3661
3667
|
min-height: 100dvh;
|
|
@@ -3705,14 +3711,14 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
3705
3711
|
top: 0;
|
|
3706
3712
|
bottom: 0;
|
|
3707
3713
|
right: 0;
|
|
3708
|
-
width:
|
|
3714
|
+
width: 100%;
|
|
3709
3715
|
grid-column: auto;
|
|
3710
3716
|
grid-row: auto;
|
|
3711
3717
|
min-width: 0;
|
|
3712
3718
|
padding: 0;
|
|
3713
|
-
border-left:
|
|
3719
|
+
border-left: 0;
|
|
3714
3720
|
border-bottom: 1px solid var(--line);
|
|
3715
|
-
box-shadow:
|
|
3721
|
+
box-shadow: none;
|
|
3716
3722
|
overflow: hidden;
|
|
3717
3723
|
transform: translateX(0);
|
|
3718
3724
|
transition: transform .2s ease;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type WorkPermissionModule = "prose" | "drafts" | "settings" | "characters" | "races" | "organizations" | "timeline" | "relationships" | "outlines" | "reviews" | "ai-chat" | "ai-analysis" | "ai-settings";
|
|
1
|
+
export type WorkPermissionModule = "prose" | "comments" | "todos" | "drafts" | "settings" | "characters" | "races" | "organizations" | "timeline" | "relationships" | "outlines" | "reviews" | "ai-chat" | "ai-analysis" | "ai-settings";
|
|
2
2
|
export type WorkUiModule = "editor" | "drafts" | "settings" | "characters" | "races" | "organizations" | "timeline" | "comments" | "relationships" | "outlines" | "reviews" | "tasks" | "ai-settings";
|
|
3
3
|
export type WorkModuleAccess = "none" | "read" | "write";
|
|
4
4
|
export type WorkModulePermissions = Record<WorkPermissionModule, WorkModuleAccess>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export const WORK_PERMISSION_MODULES = Object.freeze([
|
|
2
2
|
{ id: "prose", uiModule: "editor", label: "正文" },
|
|
3
|
+
{ id: "comments", uiModule: "comments", label: "正文评论" },
|
|
4
|
+
{ id: "todos", uiModule: null, label: "正文待办" },
|
|
3
5
|
{ id: "drafts", uiModule: "drafts", label: "想法" },
|
|
4
6
|
{ id: "settings", uiModule: "settings", label: "设定库" },
|
|
5
7
|
{ id: "characters", uiModule: "characters", label: "角色" },
|
|
@@ -22,6 +24,8 @@ const validAccess = new Set(["none", "read", "write"]);
|
|
|
22
24
|
function migrateLegacyModulePermissions(value) {
|
|
23
25
|
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
24
26
|
const migrated = { ...value };
|
|
27
|
+
if (!validAccess.has(migrated.comments)) migrated.comments = validAccess.has(migrated.prose) ? migrated.prose : "none";
|
|
28
|
+
if (!validAccess.has(migrated.todos)) migrated.todos = validAccess.has(migrated.prose) ? migrated.prose : "none";
|
|
25
29
|
if (!validAccess.has(migrated.drafts)) {
|
|
26
30
|
const prose = validAccess.has(migrated.prose) ? migrated.prose : "none";
|
|
27
31
|
const settings = validAccess.has(migrated.settings) ? migrated.settings : "none";
|
|
@@ -83,10 +87,16 @@ export function canWritePermissionModule(work, moduleId) {
|
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
export function canReadUiModule(work, uiModule) {
|
|
90
|
+
if (uiModule === "comments") {
|
|
91
|
+
return canReadPermissionModule(work, "comments") || canReadPermissionModule(work, "todos");
|
|
92
|
+
}
|
|
86
93
|
return canReadPermissionModule(work, permissionModuleForUiModule(uiModule));
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
export function canWriteUiModule(work, uiModule) {
|
|
97
|
+
if (uiModule === "comments") {
|
|
98
|
+
return canWritePermissionModule(work, "comments") || canWritePermissionModule(work, "todos");
|
|
99
|
+
}
|
|
90
100
|
return canWritePermissionModule(work, permissionModuleForUiModule(uiModule));
|
|
91
101
|
}
|
|
92
102
|
|
|
@@ -96,10 +106,12 @@ export function firstReadableUiModule(work) {
|
|
|
96
106
|
|
|
97
107
|
export function permissionSummary(value) {
|
|
98
108
|
const permissions = normalizeModulePermissions(value, "custom");
|
|
99
|
-
const
|
|
109
|
+
const addable = WORK_PERMISSION_MODULES.filter((item) => ["comments", "todos"].includes(item.id) && permissions[item.id] === "write").map((item) => item.label);
|
|
110
|
+
const writable = WORK_PERMISSION_MODULES.filter((item) => !["comments", "todos"].includes(item.id) && permissions[item.id] === "write").map((item) => item.label);
|
|
100
111
|
const readable = WORK_PERMISSION_MODULES.filter((item) => permissions[item.id] === "read").map((item) => item.label);
|
|
101
112
|
const parts = [];
|
|
102
113
|
if (writable.length) parts.push(`可编辑:${writable.join("、")}`);
|
|
114
|
+
if (addable.length) parts.push(`可添加:${addable.join("、")}`);
|
|
103
115
|
if (readable.length) parts.push(`只读:${readable.join("、")}`);
|
|
104
116
|
if (!parts.length) parts.push("未授权任何模块");
|
|
105
117
|
return parts.join(";");
|
package/dist/store.js
CHANGED
|
@@ -2161,6 +2161,7 @@ export class Store {
|
|
|
2161
2161
|
note: requiredString(row, "note"),
|
|
2162
2162
|
status: requiredString(row, "status"),
|
|
2163
2163
|
versionNo: numberValue(row, "version_no"),
|
|
2164
|
+
createdByUserId: optionalString(row, "created_by_user_id"),
|
|
2164
2165
|
actor: optionalString(row, "actor_display_name") ?? optionalString(row, "actor_username") ?? "历史数据",
|
|
2165
2166
|
createdAt: requiredString(row, "created_at"),
|
|
2166
2167
|
updatedAt: requiredString(row, "updated_at")
|
|
@@ -2189,47 +2190,86 @@ export class Store {
|
|
|
2189
2190
|
throw notFound("章节批注");
|
|
2190
2191
|
return { ...this.mapChapterAnnotation(row), deletedAt: optionalString(row, "deleted_at") };
|
|
2191
2192
|
}
|
|
2192
|
-
listChapterAnnotations(chapterId) {
|
|
2193
|
+
listChapterAnnotations(chapterId, kinds, line) {
|
|
2193
2194
|
this.getChapter(chapterId);
|
|
2195
|
+
const kindFilter = kinds === undefined
|
|
2196
|
+
? { sql: "", params: [] }
|
|
2197
|
+
: kinds.length > 0
|
|
2198
|
+
? { sql: ` AND annotation.kind IN (${kinds.map(() => "?").join(",")})`, params: [...kinds] }
|
|
2199
|
+
: { sql: " AND 1 = 0", params: [] };
|
|
2200
|
+
const lineFilter = line === undefined
|
|
2201
|
+
? { sql: "", params: [] }
|
|
2202
|
+
: { sql: " AND annotation.start_line <= ? AND annotation.end_line >= ?", params: [line, line] };
|
|
2194
2203
|
return this.db.all(`SELECT annotation.*, user.display_name AS actor_display_name, user.username AS actor_username
|
|
2195
2204
|
FROM chapter_annotations annotation LEFT JOIN users user ON user.id = annotation.updated_by_user_id
|
|
2196
|
-
WHERE annotation.chapter_id = ? AND annotation.deleted_at IS NULL
|
|
2197
|
-
ORDER BY CASE annotation.status WHEN 'open' THEN 0 ELSE 1 END, annotation.start_line, annotation.created_at`, chapterId).map((row) => this.mapChapterAnnotation(row));
|
|
2205
|
+
WHERE annotation.chapter_id = ? AND annotation.deleted_at IS NULL${kindFilter.sql}${lineFilter.sql}
|
|
2206
|
+
ORDER BY CASE annotation.status WHEN 'open' THEN 0 ELSE 1 END, annotation.start_line, annotation.created_at`, chapterId, ...kindFilter.params, ...lineFilter.params).map((row) => this.mapChapterAnnotation(row));
|
|
2198
2207
|
}
|
|
2199
|
-
|
|
2208
|
+
listChapterAnnotationCounts(chapterId, kinds) {
|
|
2209
|
+
this.getChapter(chapterId);
|
|
2210
|
+
const kindFilter = kinds === undefined
|
|
2211
|
+
? { sql: "", params: [] }
|
|
2212
|
+
: kinds.length > 0
|
|
2213
|
+
? { sql: ` AND annotation.kind IN (${kinds.map(() => "?").join(",")})`, params: [...kinds] }
|
|
2214
|
+
: { sql: " AND 1 = 0", params: [] };
|
|
2215
|
+
return this.db.all(`WITH RECURSIVE annotation_lines(line, end_line) AS (
|
|
2216
|
+
SELECT annotation.start_line, annotation.end_line
|
|
2217
|
+
FROM chapter_annotations annotation
|
|
2218
|
+
WHERE annotation.chapter_id = ? AND annotation.deleted_at IS NULL${kindFilter.sql}
|
|
2219
|
+
UNION ALL
|
|
2220
|
+
SELECT line + 1, end_line
|
|
2221
|
+
FROM annotation_lines
|
|
2222
|
+
WHERE line < end_line
|
|
2223
|
+
)
|
|
2224
|
+
SELECT line, COUNT(*) AS count
|
|
2225
|
+
FROM annotation_lines
|
|
2226
|
+
GROUP BY line
|
|
2227
|
+
ORDER BY line`, chapterId, ...kindFilter.params).map((row) => ({ line: Number(row.line), count: Number(row.count) }));
|
|
2228
|
+
}
|
|
2229
|
+
listWorkChapterAnnotations(workId, kinds) {
|
|
2200
2230
|
this.getWork(workId);
|
|
2231
|
+
const kindFilter = kinds === undefined
|
|
2232
|
+
? { sql: "", params: [] }
|
|
2233
|
+
: kinds.length > 0
|
|
2234
|
+
? { sql: ` AND annotation.kind IN (${kinds.map(() => "?").join(",")})`, params: [...kinds] }
|
|
2235
|
+
: { sql: " AND 1 = 0", params: [] };
|
|
2201
2236
|
return this.db.all(`SELECT annotation.*, user.display_name AS actor_display_name, user.username AS actor_username,
|
|
2202
2237
|
chapter.title AS chapter_title, volume.title AS volume_title
|
|
2203
2238
|
FROM chapter_annotations annotation
|
|
2204
2239
|
JOIN chapters chapter ON chapter.id = annotation.chapter_id
|
|
2205
2240
|
JOIN volumes volume ON volume.id = chapter.volume_id
|
|
2206
2241
|
LEFT JOIN users user ON user.id = annotation.updated_by_user_id
|
|
2207
|
-
WHERE annotation.work_id = ? AND annotation.deleted_at IS NULL AND chapter.deleted_at IS NULL
|
|
2242
|
+
WHERE annotation.work_id = ? AND annotation.deleted_at IS NULL AND chapter.deleted_at IS NULL${kindFilter.sql}
|
|
2208
2243
|
ORDER BY CASE annotation.status WHEN 'open' THEN 0 ELSE 1 END,
|
|
2209
2244
|
volume.sort_order, volume.created_at, chapter.sort_order, chapter.created_at,
|
|
2210
|
-
annotation.start_line, annotation.created_at`, workId).map((row) => ({
|
|
2245
|
+
annotation.start_line, annotation.created_at`, workId, ...kindFilter.params).map((row) => ({
|
|
2211
2246
|
...this.mapChapterAnnotation(row),
|
|
2212
2247
|
volumeTitle: requiredString(row, "volume_title"),
|
|
2213
2248
|
chapterTitle: requiredString(row, "chapter_title")
|
|
2214
2249
|
}));
|
|
2215
2250
|
}
|
|
2216
|
-
listWorkChapterAnnotationsPage(workId, pagination) {
|
|
2251
|
+
listWorkChapterAnnotationsPage(workId, pagination, kinds) {
|
|
2217
2252
|
this.getWork(workId);
|
|
2218
2253
|
const page = paginationSql(pagination);
|
|
2254
|
+
const kindFilter = kinds === undefined
|
|
2255
|
+
? { sql: "", params: [] }
|
|
2256
|
+
: kinds.length > 0
|
|
2257
|
+
? { sql: ` AND annotation.kind IN (${kinds.map(() => "?").join(",")})`, params: [...kinds] }
|
|
2258
|
+
: { sql: " AND 1 = 0", params: [] };
|
|
2219
2259
|
const rows = this.db.all(`SELECT annotation.*, user.display_name AS actor_display_name, user.username AS actor_username,
|
|
2220
2260
|
chapter.title AS chapter_title, volume.title AS volume_title
|
|
2221
2261
|
FROM chapter_annotations annotation
|
|
2222
2262
|
JOIN chapters chapter ON chapter.id = annotation.chapter_id
|
|
2223
2263
|
JOIN volumes volume ON volume.id = chapter.volume_id
|
|
2224
2264
|
LEFT JOIN users user ON user.id = annotation.updated_by_user_id
|
|
2225
|
-
WHERE annotation.work_id = ? AND annotation.deleted_at IS NULL AND chapter.deleted_at IS NULL
|
|
2265
|
+
WHERE annotation.work_id = ? AND annotation.deleted_at IS NULL AND chapter.deleted_at IS NULL${kindFilter.sql}
|
|
2226
2266
|
ORDER BY CASE annotation.status WHEN 'open' THEN 0 ELSE 1 END,
|
|
2227
2267
|
volume.sort_order, volume.created_at, chapter.sort_order, chapter.created_at,
|
|
2228
|
-
annotation.start_line, annotation.created_at${page.sql}`, workId, ...page.params);
|
|
2268
|
+
annotation.start_line, annotation.created_at${page.sql}`, workId, ...kindFilter.params, ...page.params);
|
|
2229
2269
|
const total = numberValue(this.db.get(`SELECT COUNT(*) AS count
|
|
2230
2270
|
FROM chapter_annotations annotation
|
|
2231
2271
|
JOIN chapters chapter ON chapter.id = annotation.chapter_id
|
|
2232
|
-
WHERE annotation.work_id = ? AND annotation.deleted_at IS NULL AND chapter.deleted_at IS NULL`, workId) ?? {}, "count");
|
|
2272
|
+
WHERE annotation.work_id = ? AND annotation.deleted_at IS NULL AND chapter.deleted_at IS NULL${kindFilter.sql}`, workId, ...kindFilter.params) ?? {}, "count");
|
|
2233
2273
|
return paginated(rows.map((row) => ({
|
|
2234
2274
|
...this.mapChapterAnnotation(row),
|
|
2235
2275
|
volumeTitle: requiredString(row, "volume_title"),
|