@musnows/scriverse 0.3.8 → 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 +270 -85
- package/dist/app.js.map +1 -1
- package/dist/cli-contract.js +4 -4
- package/dist/cli-contract.js.map +1 -1
- package/dist/cli-core.js +37 -6
- package/dist/cli-core.js.map +1 -1
- package/dist/database.js +41 -0
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +455 -122
- package/dist/public/index.html +40 -4
- package/dist/public/styles.css +55 -12
- package/dist/public/work-permissions.d.ts +14 -0
- package/dist/public/work-permissions.js +71 -0
- package/dist/store.js +432 -112
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +214 -32
- 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">
|
|
@@ -316,6 +316,29 @@
|
|
|
316
316
|
</form>
|
|
317
317
|
</dialog>
|
|
318
318
|
|
|
319
|
+
<dialog id="import-mode-dialog" class="dialog import-mode-dialog" aria-labelledby="import-mode-dialog-title" aria-describedby="import-mode-dialog-description" data-testid="import-mode-dialog">
|
|
320
|
+
<form method="dialog">
|
|
321
|
+
<div class="dialog-header">
|
|
322
|
+
<div><span class="eyebrow">已有项目</span><h2 id="import-mode-dialog-title">选择正文导入方式</h2></div>
|
|
323
|
+
<button class="dialog-close" value="cancel" aria-label="取消导入" type="submit">×</button>
|
|
324
|
+
</div>
|
|
325
|
+
<div class="import-mode-body">
|
|
326
|
+
<p id="import-mode-dialog-description">即将把所选文件导入当前作品。请选择追加还是覆盖正文。</p>
|
|
327
|
+
<p id="import-mode-file-summary" class="import-mode-file-summary"></p>
|
|
328
|
+
<p id="import-mode-unsaved-warning" class="import-mode-unsaved-warning hidden" role="alert">当前章节有未保存修改,继续导入会丢失这些本地修改。</p>
|
|
329
|
+
<div class="import-mode-options" aria-label="导入方式说明">
|
|
330
|
+
<section><strong>追加正文</strong><span>保留现有卷章,把新文件解析出的卷章添加到目录末尾。</span></section>
|
|
331
|
+
<section><strong>覆盖正文</strong><span>删除现有正文目录后写入新文件;导入前会保留可恢复的版本快照。</span></section>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
<div class="dialog-actions">
|
|
335
|
+
<button class="ghost-button" value="cancel" type="submit">取消</button>
|
|
336
|
+
<button id="import-mode-append" class="ghost-button" value="append" type="submit">追加正文</button>
|
|
337
|
+
<button id="import-mode-overwrite" class="primary-button" value="overwrite" type="submit">覆盖正文</button>
|
|
338
|
+
</div>
|
|
339
|
+
</form>
|
|
340
|
+
</dialog>
|
|
341
|
+
|
|
319
342
|
<section id="entity-editor-view" class="entity-editor-view hidden" aria-label="全屏资料编辑">
|
|
320
343
|
<form id="setting-editor-form" class="entity-editor-page hidden" aria-labelledby="setting-editor-title">
|
|
321
344
|
<header class="entity-editor-header">
|
|
@@ -506,9 +529,22 @@
|
|
|
506
529
|
</dialog>
|
|
507
530
|
|
|
508
531
|
<dialog id="members-dialog" class="dialog wide-dialog" aria-labelledby="members-dialog-title">
|
|
509
|
-
<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>
|
|
510
533
|
<div class="access-dialog-body">
|
|
511
|
-
<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>
|
|
512
548
|
<div id="members-list" class="access-list"></div>
|
|
513
549
|
</div>
|
|
514
550
|
</dialog>
|
|
@@ -575,6 +611,6 @@
|
|
|
575
611
|
</dialog>
|
|
576
612
|
|
|
577
613
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
578
|
-
<script type="module" src="/app.js?v=20260722-
|
|
614
|
+
<script type="module" src="/app.js?v=20260722-module-permissions-cr"></script>
|
|
579
615
|
</body>
|
|
580
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,16 +463,33 @@ 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
|
-
.
|
|
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; }
|
|
480
|
+
.app-shell.prose-read-only-mode:not(.shelf-mode) .left-primary-actions .file-button,
|
|
481
|
+
.app-shell.prose-read-only-mode:not(.shelf-mode) #new-volume-button,
|
|
482
|
+
.app-shell.prose-read-only-mode:not(.shelf-mode) #new-chapter-button,
|
|
483
|
+
.app-shell.prose-read-only-mode:not(.shelf-mode) #tidy-blank-lines-button,
|
|
484
|
+
.app-shell.prose-read-only-mode:not(.shelf-mode) #save-button { display: none !important; }
|
|
485
|
+
.app-shell.prose-read-only-mode:not(.shelf-mode) .left-primary-actions { justify-content: flex-end; }
|
|
486
|
+
.app-shell.prose-read-only-mode:not(.shelf-mode) #chapter-title[readonly],
|
|
487
|
+
.app-shell.prose-read-only-mode:not(.shelf-mode) #chapter-content[readonly] { cursor: default; }
|
|
468
488
|
.app-shell.view-only-mode:not(.shelf-mode) .left-primary-actions .file-button,
|
|
469
489
|
.app-shell.view-only-mode:not(.shelf-mode) #new-volume-button,
|
|
470
490
|
.app-shell.view-only-mode:not(.shelf-mode) #new-chapter-button,
|
|
471
491
|
.app-shell.view-only-mode:not(.shelf-mode) #module-create-button,
|
|
472
492
|
.app-shell.view-only-mode:not(.shelf-mode) [data-work-settings],
|
|
473
|
-
.app-shell.view-only-mode:not(.shelf-mode) [data-module="tasks"],
|
|
474
|
-
.app-shell.view-only-mode:not(.shelf-mode) [data-module="ai-settings"],
|
|
475
493
|
.app-shell.view-only-mode:not(.shelf-mode) #tidy-blank-lines-button,
|
|
476
494
|
.app-shell.view-only-mode:not(.shelf-mode) #save-button { display: none !important; }
|
|
477
495
|
.app-shell.view-only-mode:not(.shelf-mode) .left-primary-actions { justify-content: flex-end; }
|
|
@@ -495,6 +513,8 @@ body.work-viewer-mode [data-edit-relationship],
|
|
|
495
513
|
body.work-viewer-mode [data-character-relationship-edit],
|
|
496
514
|
body.work-viewer-mode [data-character-relationship-create],
|
|
497
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],
|
|
498
518
|
body.work-viewer-mode .task-auto-run-panel,
|
|
499
519
|
body.work-viewer-mode [data-run-task],
|
|
500
520
|
body.work-viewer-mode [data-cancel-task],
|
|
@@ -571,10 +591,12 @@ body.work-viewer-mode .character-editor-actions { display: none !important; }
|
|
|
571
591
|
-webkit-box-orient: vertical;
|
|
572
592
|
}
|
|
573
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); }
|
|
574
|
-
.work-access-field > div { display: grid; gap:
|
|
594
|
+
.work-access-field > div { display: grid; gap: 6px; min-width: 0; }
|
|
575
595
|
.work-access-field strong { color: var(--ink); font-size: 12px; }
|
|
576
596
|
.work-access-field small { color: var(--muted); font-size: 10px; line-height: 1.5; }
|
|
577
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; }
|
|
578
600
|
.account-dialog { width: min(640px, 92vw); overflow: hidden; }
|
|
579
601
|
.account-settings-body { display: grid; gap: 16px; max-height: calc(88vh - 86px); overflow-y: auto; padding: 20px 24px 24px; }
|
|
580
602
|
.account-settings-section { padding: 18px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface-soft); }
|
|
@@ -803,6 +825,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
803
825
|
.character-duplicate-actions { flex-wrap: wrap; }
|
|
804
826
|
.card-actions { display: flex; gap: 6px; margin-top: 15px; }
|
|
805
827
|
.card-actions button { border: 1px solid var(--line); background: transparent; border-radius: 3px; font-size: 10px; padding: 5px 8px; }
|
|
828
|
+
.card-actions .danger-button { border-color: color-mix(in srgb, var(--accent) 52%, var(--line)); color: var(--accent-dark); }
|
|
829
|
+
.card-actions .danger-button:hover, .card-actions .danger-button:focus-visible { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 9%, transparent); }
|
|
806
830
|
.card-actions .primary-button { border-color: var(--accent); background: var(--accent); color: #fff; padding: 8px 13px; font-size: 12px; }
|
|
807
831
|
.card-actions .primary-button:hover, .card-actions .primary-button:focus-visible { background: var(--accent-dark); }
|
|
808
832
|
.provider-card-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); }
|
|
@@ -1344,6 +1368,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1344
1368
|
.dialog-fields label { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
|
|
1345
1369
|
.dialog-fields input, .dialog-fields textarea, .dialog-fields select { width: 100%; padding: 9px 10px; font-size: 13px; }
|
|
1346
1370
|
.dialog-fields textarea { resize: vertical; min-height: 90px; line-height: 1.6; }
|
|
1371
|
+
.merge-dialog-note { margin: 0; padding: 10px 12px; border-left: 2px solid var(--accent); background: var(--surface-soft); color: var(--muted); font-size: 11px; line-height: 1.6; }
|
|
1347
1372
|
.form-field { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
|
|
1348
1373
|
.analysis-type-description { margin: 0; color: var(--muted); font-size: 10px; line-height: 1.55; }
|
|
1349
1374
|
.item-list-rows { display: grid; gap: 7px; }
|
|
@@ -1384,6 +1409,19 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1384
1409
|
.checkbox-field input { width: auto; }
|
|
1385
1410
|
.dialog-actions { display: flex; justify-content: flex-end; gap: 8px; padding: 14px 24px 20px; }
|
|
1386
1411
|
.dialog-actions .reset-button { margin-right: auto; }
|
|
1412
|
+
.import-mode-dialog { width: min(560px, calc(100vw - 32px)); }
|
|
1413
|
+
.import-mode-body { display: grid; gap: 14px; padding: 20px 24px 6px; }
|
|
1414
|
+
.import-mode-body p { margin: 0; color: var(--muted); font-size: 11px; line-height: 1.65; }
|
|
1415
|
+
.import-mode-file-summary { overflow-wrap: anywhere; color: var(--ink) !important; font-weight: 600; }
|
|
1416
|
+
.import-mode-unsaved-warning { padding: 9px 11px; border: 1px solid color-mix(in srgb, var(--accent) 46%, var(--line)); border-radius: 4px; background: color-mix(in srgb, var(--accent) 8%, var(--surface)); color: var(--accent-dark) !important; }
|
|
1417
|
+
.import-mode-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
|
|
1418
|
+
.import-mode-options section { display: grid; gap: 5px; min-width: 0; padding: 12px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface-soft); }
|
|
1419
|
+
.import-mode-options strong { color: var(--ink); font-size: 12px; }
|
|
1420
|
+
.import-mode-options span { color: var(--muted); font-size: 10px; line-height: 1.55; }
|
|
1421
|
+
@media (max-width: 560px) {
|
|
1422
|
+
.import-mode-options { grid-template-columns: 1fr; }
|
|
1423
|
+
.import-mode-dialog .dialog-actions { flex-wrap: wrap; }
|
|
1424
|
+
}
|
|
1387
1425
|
.entity-editor-open { overflow: hidden; }
|
|
1388
1426
|
.entity-editor-view { position: fixed; z-index: 700; inset: 0; overflow: hidden; background: var(--panel); color: var(--ink); }
|
|
1389
1427
|
.entity-editor-view.hidden, .entity-editor-page.hidden { display: none; }
|
|
@@ -1556,7 +1594,12 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1556
1594
|
.relationship-table .relationship-actions button + button { margin-left: 5px; }
|
|
1557
1595
|
|
|
1558
1596
|
@media (max-width: 540px) {
|
|
1559
|
-
.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; }
|
|
1560
1603
|
.onboarding-popover { width: calc(100vw - 24px); }
|
|
1561
1604
|
.onboarding-popover-header { padding-inline: 15px; }
|
|
1562
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
|
+
}
|