@musnows/scriverse 0.3.9 → 0.3.10
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/app.js +163 -30
- package/dist/app.js.map +1 -1
- package/dist/public/app.js +213 -98
- package/dist/public/index.html +17 -4
- package/dist/public/styles.css +31 -22
- package/dist/public/work-permissions.d.ts +14 -0
- package/dist/public/work-permissions.js +71 -0
- package/dist/store.js +19 -7
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +200 -52
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/work-permissions.js +128 -0
- package/dist/work-permissions.js.map +1 -0
- package/package.json +1 -1
package/dist/public/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<script src="/theme-init.js?v=20260720-route-skeleton"></script>
|
|
10
10
|
<link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest">
|
|
12
|
-
<link rel="stylesheet" href="/styles.css?v=20260722-
|
|
12
|
+
<link rel="stylesheet" href="/styles.css?v=20260722-module-permissions-cr">
|
|
13
13
|
</head>
|
|
14
14
|
<body class="auth-pending">
|
|
15
15
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -529,9 +529,22 @@
|
|
|
529
529
|
</dialog>
|
|
530
530
|
|
|
531
531
|
<dialog id="members-dialog" class="dialog wide-dialog" aria-labelledby="members-dialog-title">
|
|
532
|
-
<div class="dialog-header"><div><span id="members-dialog-eyebrow" class="eyebrow">作品权限</span><h2 id="members-dialog-title"
|
|
532
|
+
<div class="dialog-header"><div><span id="members-dialog-eyebrow" class="eyebrow">作品权限</span><h2 id="members-dialog-title">成员模块权限</h2></div><button id="members-dialog-close" class="dialog-close" aria-label="关闭" type="button">×</button></div>
|
|
533
533
|
<div class="access-dialog-body">
|
|
534
|
-
<form id="member-
|
|
534
|
+
<form id="member-permission-form" class="member-permission-form">
|
|
535
|
+
<label class="member-person-field">选择成员<select id="member-user-select" required></select><small>先选择已注册用户,再逐项配置模块权限。</small></label>
|
|
536
|
+
<fieldset id="member-permission-fieldset" disabled>
|
|
537
|
+
<legend>模块读写权限</legend>
|
|
538
|
+
<div class="member-permission-presets" aria-label="权限快捷设置">
|
|
539
|
+
<button type="button" data-permission-preset="none">全部关闭</button>
|
|
540
|
+
<button type="button" data-permission-preset="read">全部只读</button>
|
|
541
|
+
<button type="button" data-permission-preset="write">全部可编辑</button>
|
|
542
|
+
</div>
|
|
543
|
+
<div id="member-permission-grid" class="member-permission-grid"></div>
|
|
544
|
+
<small class="member-permission-help">“可编辑”自动包含读取权限;未授权模块不会显示,也不能通过 API 读取。</small>
|
|
545
|
+
<button id="member-permission-submit" class="primary-button" type="submit">保存成员权限</button>
|
|
546
|
+
</fieldset>
|
|
547
|
+
</form>
|
|
535
548
|
<div id="members-list" class="access-list"></div>
|
|
536
549
|
</div>
|
|
537
550
|
</dialog>
|
|
@@ -598,6 +611,6 @@
|
|
|
598
611
|
</dialog>
|
|
599
612
|
|
|
600
613
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
601
|
-
<script type="module" src="/app.js?v=20260722-
|
|
614
|
+
<script type="module" src="/app.js?v=20260722-module-permissions-cr"></script>
|
|
602
615
|
</body>
|
|
603
616
|
</html>
|
package/dist/public/styles.css
CHANGED
|
@@ -391,8 +391,8 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
|
|
|
391
391
|
.settings-hub-card strong { align-self: end; font-size: 16px; }
|
|
392
392
|
.settings-hub-card small { align-self: start; color: var(--muted); font-size: 10px; line-height: 1.5; }
|
|
393
393
|
.settings-work-note { max-width: 1000px; margin: 18px auto 0; color: var(--muted); font-size: 11px; }
|
|
394
|
-
.access-dialog-body { display: grid; gap: 16px; padding: 18px 24px 22px; }
|
|
395
|
-
.access-list { display: grid; gap: 8px; max-height: min(
|
|
394
|
+
.access-dialog-body { display: grid; gap: 16px; max-height: calc(88vh - 82px); overflow-y: auto; padding: 18px 24px 22px; }
|
|
395
|
+
.access-list { display: grid; gap: 8px; max-height: min(34vh, 340px); overflow: auto; padding: 0; }
|
|
396
396
|
.access-row {
|
|
397
397
|
display: grid;
|
|
398
398
|
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
@@ -450,9 +450,10 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
|
|
|
450
450
|
cursor: not-allowed;
|
|
451
451
|
opacity: .46;
|
|
452
452
|
}
|
|
453
|
-
.member-
|
|
454
|
-
.member-
|
|
455
|
-
.member-
|
|
453
|
+
.member-permission-form { display: grid; gap: 12px; margin: 0; padding: 14px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface-soft); }
|
|
454
|
+
.member-person-field { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
|
|
455
|
+
.member-person-field small, .member-permission-help { color: var(--muted); font-size: 10px; line-height: 1.5; }
|
|
456
|
+
.member-person-field select, .member-permission-row select {
|
|
456
457
|
width: 100%;
|
|
457
458
|
min-height: 38px;
|
|
458
459
|
padding: 9px 10px;
|
|
@@ -462,11 +463,20 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
|
|
|
462
463
|
color: var(--ink);
|
|
463
464
|
font-size: 13px;
|
|
464
465
|
}
|
|
465
|
-
.member-
|
|
466
|
-
.
|
|
467
|
-
.
|
|
468
|
-
.
|
|
469
|
-
.
|
|
466
|
+
.member-permission-form fieldset { display: grid; gap: 12px; min-width: 0; margin: 0; padding: 14px; border: 1px solid var(--line); border-radius: 6px; }
|
|
467
|
+
.member-permission-form fieldset:disabled { opacity: .58; }
|
|
468
|
+
.member-permission-form legend { padding: 0 6px; color: var(--ink); font-size: 12px; font-weight: 650; }
|
|
469
|
+
.member-permission-presets { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
470
|
+
.member-permission-presets button { padding: 6px 9px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--muted); font-size: 10px; }
|
|
471
|
+
.member-permission-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 12px; }
|
|
472
|
+
.member-permission-row { display: grid; grid-template-columns: minmax(90px, 1fr) minmax(112px, .7fr); align-items: center; gap: 8px; color: var(--ink); font-size: 11px; }
|
|
473
|
+
.member-permission-row select { min-height: 34px; padding-block: 6px; font-size: 11px; }
|
|
474
|
+
.member-permission-form .primary-button { justify-self: end; min-height: 38px; white-space: nowrap; }
|
|
475
|
+
.permission-hidden { display: none !important; }
|
|
476
|
+
.app-shell.ai-hidden-mode:not(.shelf-mode) { grid-template-columns: var(--left-panel-width) minmax(480px, 1fr); }
|
|
477
|
+
.app-shell.prose-hidden-mode:not(.shelf-mode) .left-actions,
|
|
478
|
+
.app-shell.prose-hidden-mode:not(.shelf-mode) .panel-heading,
|
|
479
|
+
.app-shell.prose-hidden-mode:not(.shelf-mode) #novel-tree { display: none !important; }
|
|
470
480
|
.app-shell.prose-read-only-mode:not(.shelf-mode) .left-primary-actions .file-button,
|
|
471
481
|
.app-shell.prose-read-only-mode:not(.shelf-mode) #new-volume-button,
|
|
472
482
|
.app-shell.prose-read-only-mode:not(.shelf-mode) #new-chapter-button,
|
|
@@ -475,16 +485,11 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
|
|
|
475
485
|
.app-shell.prose-read-only-mode:not(.shelf-mode) .left-primary-actions { justify-content: flex-end; }
|
|
476
486
|
.app-shell.prose-read-only-mode:not(.shelf-mode) #chapter-title[readonly],
|
|
477
487
|
.app-shell.prose-read-only-mode:not(.shelf-mode) #chapter-content[readonly] { cursor: default; }
|
|
478
|
-
.app-shell.settings-only-mode:not(.shelf-mode) [data-work-settings],
|
|
479
|
-
.app-shell.settings-only-mode:not(.shelf-mode) [data-module="tasks"],
|
|
480
|
-
.app-shell.settings-only-mode:not(.shelf-mode) [data-module="ai-settings"] { display: none !important; }
|
|
481
488
|
.app-shell.view-only-mode:not(.shelf-mode) .left-primary-actions .file-button,
|
|
482
489
|
.app-shell.view-only-mode:not(.shelf-mode) #new-volume-button,
|
|
483
490
|
.app-shell.view-only-mode:not(.shelf-mode) #new-chapter-button,
|
|
484
491
|
.app-shell.view-only-mode:not(.shelf-mode) #module-create-button,
|
|
485
492
|
.app-shell.view-only-mode:not(.shelf-mode) [data-work-settings],
|
|
486
|
-
.app-shell.view-only-mode:not(.shelf-mode) [data-module="tasks"],
|
|
487
|
-
.app-shell.view-only-mode:not(.shelf-mode) [data-module="ai-settings"],
|
|
488
493
|
.app-shell.view-only-mode:not(.shelf-mode) #tidy-blank-lines-button,
|
|
489
494
|
.app-shell.view-only-mode:not(.shelf-mode) #save-button { display: none !important; }
|
|
490
495
|
.app-shell.view-only-mode:not(.shelf-mode) .left-primary-actions { justify-content: flex-end; }
|
|
@@ -508,6 +513,8 @@ body.work-viewer-mode [data-edit-relationship],
|
|
|
508
513
|
body.work-viewer-mode [data-character-relationship-edit],
|
|
509
514
|
body.work-viewer-mode [data-character-relationship-create],
|
|
510
515
|
body.work-viewer-mode [data-review-id],
|
|
516
|
+
body.work-viewer-mode [data-merge-review],
|
|
517
|
+
body.work-viewer-mode [data-keep-characters-separate],
|
|
511
518
|
body.work-viewer-mode .task-auto-run-panel,
|
|
512
519
|
body.work-viewer-mode [data-run-task],
|
|
513
520
|
body.work-viewer-mode [data-cancel-task],
|
|
@@ -519,11 +526,6 @@ body.work-viewer-mode [data-item-list-remove],
|
|
|
519
526
|
body.work-viewer-mode [data-structured-list-add],
|
|
520
527
|
body.work-viewer-mode [data-structured-list-remove],
|
|
521
528
|
body.work-viewer-mode .character-editor-actions { display: none !important; }
|
|
522
|
-
body.work-settings-editor-mode [data-review-id],
|
|
523
|
-
body.work-settings-editor-mode .task-auto-run-panel,
|
|
524
|
-
body.work-settings-editor-mode [data-run-task],
|
|
525
|
-
body.work-settings-editor-mode [data-cancel-task],
|
|
526
|
-
body.work-settings-editor-mode [data-restore-version] { display: none !important; }
|
|
527
529
|
.search-form { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: 10px; margin: 0; }
|
|
528
530
|
.search-form label { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
|
|
529
531
|
.search-form input {
|
|
@@ -589,10 +591,12 @@ body.work-settings-editor-mode [data-restore-version] { display: none !important
|
|
|
589
591
|
-webkit-box-orient: vertical;
|
|
590
592
|
}
|
|
591
593
|
.work-access-field { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface-soft); }
|
|
592
|
-
.work-access-field > div { display: grid; gap:
|
|
594
|
+
.work-access-field > div { display: grid; gap: 6px; min-width: 0; }
|
|
593
595
|
.work-access-field strong { color: var(--ink); font-size: 12px; }
|
|
594
596
|
.work-access-field small { color: var(--muted); font-size: 10px; line-height: 1.5; }
|
|
595
597
|
.work-access-field .ghost-button { flex: 0 0 auto; white-space: nowrap; }
|
|
598
|
+
.work-access-options { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
599
|
+
.work-access-options span { padding: 3px 7px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); color: var(--ink); font-size: 10px; line-height: 1.35; }
|
|
596
600
|
.account-dialog { width: min(640px, 92vw); overflow: hidden; }
|
|
597
601
|
.account-settings-body { display: grid; gap: 16px; max-height: calc(88vh - 86px); overflow-y: auto; padding: 20px 24px 24px; }
|
|
598
602
|
.account-settings-section { padding: 18px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface-soft); }
|
|
@@ -1590,7 +1594,12 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1590
1594
|
.relationship-table .relationship-actions button + button { margin-left: 5px; }
|
|
1591
1595
|
|
|
1592
1596
|
@media (max-width: 540px) {
|
|
1593
|
-
.member-
|
|
1597
|
+
.member-permission-grid { grid-template-columns: minmax(0, 1fr); }
|
|
1598
|
+
.member-permission-row { grid-template-columns: minmax(0, 1fr); }
|
|
1599
|
+
.member-permission-form .primary-button { justify-self: stretch; }
|
|
1600
|
+
.access-row { grid-template-columns: minmax(0, 1fr); align-items: stretch; }
|
|
1601
|
+
.access-row select, .access-row button { width: 100%; }
|
|
1602
|
+
.work-access-field { align-items: stretch; flex-direction: column; }
|
|
1594
1603
|
.onboarding-popover { width: calc(100vw - 24px); }
|
|
1595
1604
|
.onboarding-popover-header { padding-inline: 15px; }
|
|
1596
1605
|
.onboarding-popover-body { padding: 16px 17px 18px; }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type WorkPermissionModule = "prose" | "settings" | "characters" | "races" | "organizations" | "timeline" | "relationships" | "outlines" | "reviews" | "ai" | "ai-settings";
|
|
2
|
+
export type WorkUiModule = "editor" | "settings" | "characters" | "races" | "organizations" | "timeline" | "relationships" | "outlines" | "reviews" | "tasks" | "ai-settings";
|
|
3
|
+
export type WorkModuleAccess = "none" | "read" | "write";
|
|
4
|
+
export type WorkModulePermissions = Record<WorkPermissionModule, WorkModuleAccess>;
|
|
5
|
+
export type WorkPermissionAware = { accessRole?: string | null; modulePermissions?: WorkModulePermissions };
|
|
6
|
+
|
|
7
|
+
export const WORK_PERMISSION_MODULES: readonly Readonly<{ id: WorkPermissionModule; uiModule: WorkUiModule; label: string }>[];
|
|
8
|
+
export function emptyModulePermissions(): WorkModulePermissions;
|
|
9
|
+
export function normalizeModulePermissions(value: unknown, accessRole?: string): WorkModulePermissions;
|
|
10
|
+
export function permissionModuleForUiModule(uiModule: string): WorkPermissionModule;
|
|
11
|
+
export function canReadUiModule(work: WorkPermissionAware | null | undefined, uiModule: string): boolean;
|
|
12
|
+
export function canWriteUiModule(work: WorkPermissionAware | null | undefined, uiModule: string): boolean;
|
|
13
|
+
export function firstReadableUiModule(work: WorkPermissionAware | null | undefined): WorkUiModule | null;
|
|
14
|
+
export function permissionSummary(value: unknown): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export const WORK_PERMISSION_MODULES = Object.freeze([
|
|
2
|
+
{ id: "prose", uiModule: "editor", label: "正文" },
|
|
3
|
+
{ id: "settings", uiModule: "settings", label: "设定库" },
|
|
4
|
+
{ id: "characters", uiModule: "characters", label: "角色" },
|
|
5
|
+
{ id: "races", uiModule: "races", label: "种族" },
|
|
6
|
+
{ id: "organizations", uiModule: "organizations", label: "组织" },
|
|
7
|
+
{ id: "timeline", uiModule: "timeline", label: "时间轴" },
|
|
8
|
+
{ id: "relationships", uiModule: "relationships", label: "关系" },
|
|
9
|
+
{ id: "outlines", uiModule: "outlines", label: "大纲与伏笔" },
|
|
10
|
+
{ id: "reviews", uiModule: "reviews", label: "审核" },
|
|
11
|
+
{ id: "ai", uiModule: "tasks", label: "AI 对话与分析" },
|
|
12
|
+
{ id: "ai-settings", uiModule: "ai-settings", label: "AI 设置" }
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
const permissionModuleByUiModule = new Map(WORK_PERMISSION_MODULES.map((item) => [item.uiModule, item.id]));
|
|
16
|
+
const validAccess = new Set(["none", "read", "write"]);
|
|
17
|
+
|
|
18
|
+
export function emptyModulePermissions() {
|
|
19
|
+
return Object.fromEntries(WORK_PERMISSION_MODULES.map((item) => [item.id, "none"]));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function normalizeModulePermissions(value, accessRole = "viewer") {
|
|
23
|
+
if (value && typeof value === "object" && !Array.isArray(value)
|
|
24
|
+
&& WORK_PERMISSION_MODULES.every((item) => validAccess.has(value[item.id]))) {
|
|
25
|
+
return Object.fromEntries(WORK_PERMISSION_MODULES.map((item) => [item.id, value[item.id]]));
|
|
26
|
+
}
|
|
27
|
+
const fallback = emptyModulePermissions();
|
|
28
|
+
for (const item of WORK_PERMISSION_MODULES) {
|
|
29
|
+
fallback[item.id] = accessRole === "editor" || accessRole === "admin" || accessRole === "owner"
|
|
30
|
+
? "write"
|
|
31
|
+
: accessRole === "viewer" || accessRole === "settings-editor" ? "read" : "none";
|
|
32
|
+
}
|
|
33
|
+
if (accessRole === "settings-editor") {
|
|
34
|
+
fallback.prose = "read";
|
|
35
|
+
fallback.reviews = "read";
|
|
36
|
+
fallback.ai = "read";
|
|
37
|
+
fallback["ai-settings"] = "read";
|
|
38
|
+
}
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function permissionModuleForUiModule(uiModule) {
|
|
43
|
+
return permissionModuleByUiModule.get(String(uiModule)) ?? "prose";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function canReadUiModule(work, uiModule) {
|
|
47
|
+
if (!work) return false;
|
|
48
|
+
const permissions = normalizeModulePermissions(work.modulePermissions, work.accessRole);
|
|
49
|
+
return permissions[permissionModuleForUiModule(uiModule)] !== "none";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function canWriteUiModule(work, uiModule) {
|
|
53
|
+
if (!work) return false;
|
|
54
|
+
const permissions = normalizeModulePermissions(work.modulePermissions, work.accessRole);
|
|
55
|
+
return permissions[permissionModuleForUiModule(uiModule)] === "write";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function firstReadableUiModule(work) {
|
|
59
|
+
return WORK_PERMISSION_MODULES.find((item) => canReadUiModule(work, item.uiModule))?.uiModule ?? null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function permissionSummary(value) {
|
|
63
|
+
const permissions = normalizeModulePermissions(value, "custom");
|
|
64
|
+
const writable = WORK_PERMISSION_MODULES.filter((item) => permissions[item.id] === "write").map((item) => item.label);
|
|
65
|
+
const readable = WORK_PERMISSION_MODULES.filter((item) => permissions[item.id] === "read").map((item) => item.label);
|
|
66
|
+
const parts = [];
|
|
67
|
+
if (writable.length) parts.push(`可编辑:${writable.join("、")}`);
|
|
68
|
+
if (readable.length) parts.push(`只读:${readable.join("、")}`);
|
|
69
|
+
if (!parts.length) parts.push("未授权任何模块");
|
|
70
|
+
return parts.join(";");
|
|
71
|
+
}
|
package/dist/store.js
CHANGED
|
@@ -4,6 +4,7 @@ import { AppError, notFound } from "./errors.js";
|
|
|
4
4
|
import { accountReference, logger } from "./logger.js";
|
|
5
5
|
import { paginated, paginationSql } from "./pagination.js";
|
|
6
6
|
import { currentRequestActor } from "./request-context.js";
|
|
7
|
+
import { classifyWorkModulePermissions, emptyWorkModulePermissions, fullWorkModulePermissions, storedWorkModulePermissions } from "./work-permissions.js";
|
|
7
8
|
import { countWords, documentShortSearchTerms, id, json, normalizeDocumentSearchText, normalizeParagraphSpacing, now, splitDocumentParagraphs } from "./utils.js";
|
|
8
9
|
export const versionedEntityTypes = [
|
|
9
10
|
"work",
|
|
@@ -603,6 +604,9 @@ export class Store {
|
|
|
603
604
|
}
|
|
604
605
|
getWorkDirectory(workId) {
|
|
605
606
|
const work = this.getWork(workId);
|
|
607
|
+
const permissions = work.modulePermissions;
|
|
608
|
+
if (permissions.prose === "none")
|
|
609
|
+
return { ...work, volumes: [] };
|
|
606
610
|
const volumeRows = this.db.all("SELECT * FROM volumes WHERE work_id = ? ORDER BY sort_order, created_at", workId);
|
|
607
611
|
const chapterRows = this.db.all(`SELECT id, work_id, volume_id, title, chapter_type, sort_order, word_count, version_no,
|
|
608
612
|
analysis_status, excluded_from_analysis, created_at, updated_at
|
|
@@ -623,6 +627,9 @@ export class Store {
|
|
|
623
627
|
}
|
|
624
628
|
getWorkDirectoryPage(workId, pagination) {
|
|
625
629
|
const work = this.getWork(workId);
|
|
630
|
+
const permissions = work.modulePermissions;
|
|
631
|
+
if (permissions.prose === "none")
|
|
632
|
+
return { ...work, volumes: [], directoryPage: paginated([], pagination) };
|
|
626
633
|
const volumeRows = this.db.all("SELECT * FROM volumes WHERE work_id = ? ORDER BY sort_order, created_at", workId);
|
|
627
634
|
const page = paginationSql(pagination);
|
|
628
635
|
const chapterRows = this.db.all(`SELECT id, work_id, volume_id, title, chapter_type, sort_order, word_count, version_no,
|
|
@@ -1193,15 +1200,19 @@ export class Store {
|
|
|
1193
1200
|
const membership = actor
|
|
1194
1201
|
? this.db.get("SELECT role, permissions_json FROM work_memberships WHERE work_id = ? AND user_id = ?", requiredString(row, "id"), actor.userId)
|
|
1195
1202
|
: undefined;
|
|
1196
|
-
const membershipPermissions = json(optionalString(membership ?? {}, "permissions_json"), {});
|
|
1197
1203
|
const membershipRole = String(membership?.role ?? "");
|
|
1204
|
+
const ownerAccess = ownerUserId === actor?.userId;
|
|
1205
|
+
const adminAccess = actor?.role === "admin" && actor.authentication !== "api-key";
|
|
1206
|
+
const modulePermissions = !actor || ownerAccess || adminAccess
|
|
1207
|
+
? fullWorkModulePermissions()
|
|
1208
|
+
: membershipRole
|
|
1209
|
+
? storedWorkModulePermissions(membershipRole, optionalString(membership ?? {}, "permissions_json"))
|
|
1210
|
+
: emptyWorkModulePermissions();
|
|
1198
1211
|
const accessRole = ownerUserId === actor?.userId
|
|
1199
1212
|
? "owner"
|
|
1200
|
-
:
|
|
1213
|
+
: adminAccess
|
|
1201
1214
|
? "admin"
|
|
1202
|
-
: membershipRole
|
|
1203
|
-
? "settings-editor"
|
|
1204
|
-
: ["editor", "viewer"].includes(membershipRole) ? membershipRole : null;
|
|
1215
|
+
: membershipRole ? classifyWorkModulePermissions(modulePermissions) : null;
|
|
1205
1216
|
const count = this.db.get("SELECT COUNT(*) AS chapter_count, COALESCE(SUM(word_count), 0) AS word_count FROM chapters WHERE work_id = ?", requiredString(row, "id"));
|
|
1206
1217
|
const cover = this.db.get("SELECT updated_at FROM work_covers WHERE work_id = ?", requiredString(row, "id"));
|
|
1207
1218
|
return {
|
|
@@ -1217,8 +1228,9 @@ export class Store {
|
|
|
1217
1228
|
versionNo: numberValue(row, "version_no") || this.currentEntityVersionNo("work", requiredString(row, "id")),
|
|
1218
1229
|
ownerUserId,
|
|
1219
1230
|
accessRole,
|
|
1220
|
-
|
|
1221
|
-
|
|
1231
|
+
modulePermissions,
|
|
1232
|
+
chapterCount: modulePermissions.prose === "none" ? 0 : numberValue(count ?? {}, "chapter_count"),
|
|
1233
|
+
wordCount: modulePermissions.prose === "none" ? 0 : numberValue(count ?? {}, "word_count"),
|
|
1222
1234
|
createdAt: requiredString(row, "created_at"),
|
|
1223
1235
|
updatedAt: requiredString(row, "updated_at")
|
|
1224
1236
|
};
|