@musnows/scriverse 1.1.3 → 1.1.4
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 +37 -3
- package/dist/app.js.map +1 -1
- package/dist/chapter-title-numbering.js +210 -0
- package/dist/chapter-title-numbering.js.map +1 -1
- package/dist/cli-contract.js +15 -6
- package/dist/cli-contract.js.map +1 -1
- package/dist/cli-core.js +37 -1
- package/dist/cli-core.js.map +1 -1
- package/dist/database.js +21 -2
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +374 -15
- package/dist/public/index.html +12 -2
- package/dist/public/styles.css +3 -2
- package/dist/store.js +190 -10
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +5 -1
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/store.js
CHANGED
|
@@ -16,7 +16,7 @@ import { DEFAULT_AI_STREAM_IDLE_TIMEOUT_SECONDS, normalizeAiStreamIdleTimeoutSec
|
|
|
16
16
|
import { normalizeCharacterAttributes, normalizeCharacterState } from "./public/character-profile.js";
|
|
17
17
|
import { normalizeRoleplayScenePin, roleplayUserTurnDisplayText, roleplayUserTurnTitleSource } from "./roleplay-turn.js";
|
|
18
18
|
import { chapterAnnotationLineHashes, createChapterLineIds, MAX_CHAPTER_LINE_IDS, parseChapterAnnotationLineIds, parseChapterAnnotationLineHashes, parseChapterLineIds, reconcileChapterLineIds, reanchorChapterAnnotations } from "./chapter-annotation-anchor.js";
|
|
19
|
-
import { renumberChapterTitle } from "./chapter-title-numbering.js";
|
|
19
|
+
import { chapterTitleFormats, isChapterTitlePreference, parseChapterTitleNumber, planAutoNumberedTitles, planVolumeTitleRenumber, renumberChapterTitle, resolveChapterTitleFormat } from "./chapter-title-numbering.js";
|
|
20
20
|
import { normalizeRoleplayMemoryContent, roleplayMemoryCandidateIsSafe } from "./roleplay-memory.js";
|
|
21
21
|
const WORK_LIST_BATCH_SIZE = 500;
|
|
22
22
|
const ENTITY_LIST_BATCH_SIZE = 400;
|
|
@@ -578,6 +578,7 @@ export class Store {
|
|
|
578
578
|
tags: entity.tags,
|
|
579
579
|
editorAutoIndentEnabled: entity.editorAutoIndentEnabled,
|
|
580
580
|
editorTypewriterModeEnabled: entity.editorTypewriterModeEnabled,
|
|
581
|
+
chapterTitleFormat: entity.chapterTitleFormat,
|
|
581
582
|
ownerUserId: entity.ownerUserId
|
|
582
583
|
};
|
|
583
584
|
if (type === "volume")
|
|
@@ -847,8 +848,8 @@ export class Store {
|
|
|
847
848
|
const ownerUserId = this.resolveWorkOwnerUserId(typeof snapshot.ownerUserId === "string" ? snapshot.ownerUserId : null, true);
|
|
848
849
|
const timestamp = now();
|
|
849
850
|
this.db.run(`INSERT INTO works (id, title, author, description, language, cover_url, tags_json,
|
|
850
|
-
editor_auto_indent_enabled, editor_typewriter_mode_enabled, version_no, created_at, updated_at, owner_user_id)
|
|
851
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`, entityId, String(snapshot.title ?? "未命名作品"), String(snapshot.author ?? ""), String(snapshot.description ?? ""), String(snapshot.language ?? "zh-CN"), snapshot.coverUrl ?? null, JSON.stringify(Array.isArray(snapshot.tags) ? snapshot.tags : []), snapshot.editorAutoIndentEnabled === true ? 1 : 0, snapshot.editorTypewriterModeEnabled === true ? 1 : 0, timestamp, timestamp, ownerUserId);
|
|
851
|
+
editor_auto_indent_enabled, editor_typewriter_mode_enabled, chapter_title_format, version_no, created_at, updated_at, owner_user_id)
|
|
852
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`, entityId, String(snapshot.title ?? "未命名作品"), String(snapshot.author ?? ""), String(snapshot.description ?? ""), String(snapshot.language ?? "zh-CN"), snapshot.coverUrl ?? null, JSON.stringify(Array.isArray(snapshot.tags) ? snapshot.tags : []), snapshot.editorAutoIndentEnabled === true ? 1 : 0, snapshot.editorTypewriterModeEnabled === true ? 1 : 0, isChapterTitlePreference(String(snapshot.chapterTitleFormat ?? "")) ? String(snapshot.chapterTitleFormat) : "off", timestamp, timestamp, ownerUserId);
|
|
852
853
|
if (ownerUserId !== SYSTEM_USER_ID) {
|
|
853
854
|
this.db.run("INSERT INTO work_memberships (work_id, user_id, role, invited_by_user_id, created_at) VALUES (?, ?, 'owner', ?, ?)", entityId, ownerUserId, ownerUserId, timestamp);
|
|
854
855
|
}
|
|
@@ -912,10 +913,12 @@ export class Store {
|
|
|
912
913
|
const workId = id("work");
|
|
913
914
|
const timestamp = now();
|
|
914
915
|
const resolvedOwnerUserId = this.resolveWorkOwnerUserId(ownerUserId);
|
|
916
|
+
const requestedTitleFormat = input.chapterTitleFormat ?? "";
|
|
917
|
+
const chapterTitleFormat = isChapterTitlePreference(requestedTitleFormat) ? requestedTitleFormat : "off";
|
|
915
918
|
this.db.transaction(() => {
|
|
916
919
|
this.db.run(`INSERT INTO works (id, title, author, description, language, cover_url, tags_json,
|
|
917
|
-
editor_auto_indent_enabled, editor_typewriter_mode_enabled, created_at, updated_at, owner_user_id)
|
|
918
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, workId, input.title, input.author ?? "", input.description ?? "", input.language ?? "zh-CN", input.coverUrl ?? null, JSON.stringify(input.tags ?? []), input.editorAutoIndentEnabled === true ? 1 : 0, input.editorTypewriterModeEnabled === true ? 1 : 0, timestamp, timestamp, resolvedOwnerUserId);
|
|
920
|
+
editor_auto_indent_enabled, editor_typewriter_mode_enabled, chapter_title_format, created_at, updated_at, owner_user_id)
|
|
921
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, workId, input.title, input.author ?? "", input.description ?? "", input.language ?? "zh-CN", input.coverUrl ?? null, JSON.stringify(input.tags ?? []), input.editorAutoIndentEnabled === true ? 1 : 0, input.editorTypewriterModeEnabled === true ? 1 : 0, chapterTitleFormat, timestamp, timestamp, resolvedOwnerUserId);
|
|
919
922
|
if (resolvedOwnerUserId !== SYSTEM_USER_ID) {
|
|
920
923
|
this.db.run("INSERT INTO work_memberships (work_id, user_id, role, invited_by_user_id, created_at) VALUES (?, ?, 'owner', ?, ?)", workId, resolvedOwnerUserId, resolvedOwnerUserId, timestamp);
|
|
921
924
|
}
|
|
@@ -1356,8 +1359,8 @@ export class Store {
|
|
|
1356
1359
|
this.assertExpectedVersion("work", workId, expectedVersionNo, "作品", Number(current.versionNo));
|
|
1357
1360
|
const timestamp = now();
|
|
1358
1361
|
this.db.run(`UPDATE works SET title = ?, author = ?, description = ?, language = ?, cover_url = ?, tags_json = ?,
|
|
1359
|
-
editor_auto_indent_enabled = ?, editor_typewriter_mode_enabled = ?, version_no = version_no + 1, updated_at = ?
|
|
1360
|
-
WHERE id = ?`, input.title ?? String(current.title), input.author ?? String(current.author), input.description ?? String(current.description), input.language ?? String(current.language), input.coverUrl === undefined ? current.coverUrl : input.coverUrl, JSON.stringify(input.tags ?? current.tags), input.editorAutoIndentEnabled === undefined ? (current.editorAutoIndentEnabled ? 1 : 0) : input.editorAutoIndentEnabled ? 1 : 0, input.editorTypewriterModeEnabled === undefined ? (current.editorTypewriterModeEnabled ? 1 : 0) : input.editorTypewriterModeEnabled ? 1 : 0, timestamp, workId);
|
|
1362
|
+
editor_auto_indent_enabled = ?, editor_typewriter_mode_enabled = ?, chapter_title_format = ?, version_no = version_no + 1, updated_at = ?
|
|
1363
|
+
WHERE id = ?`, input.title ?? String(current.title), input.author ?? String(current.author), input.description ?? String(current.description), input.language ?? String(current.language), input.coverUrl === undefined ? current.coverUrl : input.coverUrl, JSON.stringify(input.tags ?? current.tags), input.editorAutoIndentEnabled === undefined ? (current.editorAutoIndentEnabled ? 1 : 0) : input.editorAutoIndentEnabled ? 1 : 0, input.editorTypewriterModeEnabled === undefined ? (current.editorTypewriterModeEnabled ? 1 : 0) : input.editorTypewriterModeEnabled ? 1 : 0, input.chapterTitleFormat === undefined ? String(current.chapterTitleFormat ?? "off") : input.chapterTitleFormat, timestamp, workId);
|
|
1361
1364
|
this.recordEntityVersion("work", workId, source, sourceRef, changeNote || "更新作品信息", timestamp);
|
|
1362
1365
|
this.audit(workId, "work.updated", "work", workId, { fields: Object.keys(input), versionNo: Number(current.versionNo) + 1, source, sourceRef, changeNote });
|
|
1363
1366
|
});
|
|
@@ -2081,16 +2084,190 @@ export class Store {
|
|
|
2081
2084
|
}
|
|
2082
2085
|
createChapter(workId, input) {
|
|
2083
2086
|
return this.db.transaction(() => {
|
|
2087
|
+
if (input.insertAfterChapterId && input.insertBeforeChapterId) {
|
|
2088
|
+
throw new AppError(400, "CHAPTER_INSERT_ANCHOR_CONFLICT", "不能同时指定章节前后插入位置");
|
|
2089
|
+
}
|
|
2084
2090
|
this.getWork(workId);
|
|
2085
2091
|
const volume = this.getVolume(input.volumeId);
|
|
2086
2092
|
if (volume.workId !== workId)
|
|
2087
2093
|
throw new AppError(400, "VOLUME_WORK_MISMATCH", "卷不属于当前作品");
|
|
2088
|
-
const
|
|
2089
|
-
const
|
|
2090
|
-
this.
|
|
2094
|
+
const timestamp = now();
|
|
2095
|
+
const rows = this.listActiveChapterRows(workId, input.volumeId);
|
|
2096
|
+
let sortOrder = numberValue(this.db.get("SELECT COALESCE(MAX(sort_order), -1) AS value FROM chapters WHERE volume_id = ? AND deleted_at IS NULL", input.volumeId) ?? {}, "value") + 1;
|
|
2097
|
+
let insertIndex = rows.length;
|
|
2098
|
+
const anchorId = input.insertBeforeChapterId ?? input.insertAfterChapterId;
|
|
2099
|
+
if (anchorId) {
|
|
2100
|
+
const anchor = this.getChapter(anchorId);
|
|
2101
|
+
if (anchor.workId !== workId)
|
|
2102
|
+
throw new AppError(400, "CHAPTER_WORK_MISMATCH", "章节不属于当前作品");
|
|
2103
|
+
const anchorIndex = String(anchor.volumeId) === input.volumeId
|
|
2104
|
+
? rows.findIndex((row) => requiredString(row, "id") === anchorId)
|
|
2105
|
+
: -1;
|
|
2106
|
+
if (anchorIndex >= 0) {
|
|
2107
|
+
const before = Boolean(input.insertBeforeChapterId);
|
|
2108
|
+
sortOrder = Number(anchor.sortOrder) + (before ? 0 : 1);
|
|
2109
|
+
insertIndex = anchorIndex + (before ? 0 : 1);
|
|
2110
|
+
this.db.run("UPDATE chapters SET sort_order = sort_order + 1, updated_at = ? WHERE volume_id = ? AND deleted_at IS NULL AND sort_order >= ?", timestamp, input.volumeId, sortOrder);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
const pendingId = "__pending_chapter__";
|
|
2114
|
+
const ordered = [
|
|
2115
|
+
...rows.slice(0, insertIndex).map((row) => ({ id: requiredString(row, "id"), title: requiredString(row, "title") })),
|
|
2116
|
+
{ id: pendingId, title: input.title },
|
|
2117
|
+
...rows.slice(insertIndex).map((row) => ({ id: requiredString(row, "id"), title: requiredString(row, "title") }))
|
|
2118
|
+
];
|
|
2119
|
+
const formatId = input.numberTitle ? this.resolveWorkChapterTitleFormat(workId, ordered.map((chapter) => chapter.title)) : null;
|
|
2120
|
+
let title = input.title;
|
|
2121
|
+
let numberingUpdates = [];
|
|
2122
|
+
if (formatId) {
|
|
2123
|
+
try {
|
|
2124
|
+
numberingUpdates = planAutoNumberedTitles(ordered, pendingId, input.title, formatId);
|
|
2125
|
+
}
|
|
2126
|
+
catch (error) {
|
|
2127
|
+
this.rethrowChapterNumberError(error);
|
|
2128
|
+
}
|
|
2129
|
+
title = numberingUpdates.find((item) => item.id === pendingId)?.title ?? input.title;
|
|
2130
|
+
numberingUpdates = numberingUpdates.filter((item) => item.id !== pendingId);
|
|
2131
|
+
if (title.length > 300)
|
|
2132
|
+
throw new AppError(400, "CHAPTER_TITLE_TOO_LONG", "编号后的章节标题超过 300 个字符");
|
|
2133
|
+
}
|
|
2134
|
+
const chapterId = this.insertChapter(workId, input.volumeId, title, input.content ?? "", sortOrder, "manual", null, input.chapterType ?? "正文");
|
|
2135
|
+
if (numberingUpdates.length) {
|
|
2136
|
+
const shiftedRows = this.listActiveChapterRows(workId, input.volumeId);
|
|
2137
|
+
this.writeChapterTitleUpdates(workId, numberingUpdates, shiftedRows, "自动顺延章节序号", timestamp);
|
|
2138
|
+
}
|
|
2139
|
+
this.audit(workId, "chapter.created", "chapter", chapterId, formatId ? { chapterTitleFormat: formatId, numbered: numberingUpdates.length > 0 || title !== input.title } : {});
|
|
2140
|
+
return this.getChapter(chapterId);
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
describeChapterTitleFormat(workId) {
|
|
2144
|
+
const work = this.getWork(workId);
|
|
2145
|
+
const preference = isChapterTitlePreference(String(work.chapterTitleFormat ?? ""))
|
|
2146
|
+
? String(work.chapterTitleFormat)
|
|
2147
|
+
: "off";
|
|
2148
|
+
const detected = resolveChapterTitleFormat("auto", this.listActiveChapterRows(workId).map((row) => requiredString(row, "title")));
|
|
2149
|
+
return {
|
|
2150
|
+
preference,
|
|
2151
|
+
detected,
|
|
2152
|
+
resolved: resolveChapterTitleFormat(preference, this.listActiveChapterRows(workId).map((row) => requiredString(row, "title")))
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
applyChapterTitleNumber(chapterId, title, expectedVersionNo) {
|
|
2156
|
+
return this.db.transaction(() => {
|
|
2157
|
+
const current = this.getChapter(chapterId);
|
|
2158
|
+
this.assertExpectedRevision("chapter", chapterId, expectedVersionNo, "章节", Number(current.versionNo));
|
|
2159
|
+
const workId = String(current.workId);
|
|
2160
|
+
const volumeId = String(current.volumeId);
|
|
2161
|
+
const rows = this.listActiveChapterRows(workId, volumeId);
|
|
2162
|
+
const formatId = this.resolveWorkChapterTitleFormat(workId, rows.map((row) => requiredString(row, "title")));
|
|
2163
|
+
const savedNumber = parseChapterTitleNumber(String(current.title));
|
|
2164
|
+
if (!formatId || parseChapterTitleNumber(title) || savedNumber) {
|
|
2165
|
+
const nextTitle = formatId && savedNumber && !parseChapterTitleNumber(title)
|
|
2166
|
+
? renumberChapterTitle(title, savedNumber.number, chapterTitleFormats[savedNumber.formatId].template, chapterTitleFormats[savedNumber.formatId].style)
|
|
2167
|
+
: title;
|
|
2168
|
+
return this.saveChapter(chapterId, { title: nextTitle }, "manual", null, nextTitle === title ? "" : "补回章节序号", expectedVersionNo);
|
|
2169
|
+
}
|
|
2170
|
+
const ordered = rows.map((row) => ({
|
|
2171
|
+
id: requiredString(row, "id"),
|
|
2172
|
+
title: requiredString(row, "id") === chapterId ? title : requiredString(row, "title")
|
|
2173
|
+
}));
|
|
2174
|
+
let updates;
|
|
2175
|
+
try {
|
|
2176
|
+
updates = planAutoNumberedTitles(ordered, chapterId, title, formatId);
|
|
2177
|
+
}
|
|
2178
|
+
catch (error) {
|
|
2179
|
+
this.rethrowChapterNumberError(error);
|
|
2180
|
+
}
|
|
2181
|
+
const timestamp = now();
|
|
2182
|
+
this.writeChapterTitleUpdates(workId, updates, rows, "补写章节序号", timestamp);
|
|
2183
|
+
this.db.run("UPDATE works SET updated_at = ? WHERE id = ?", timestamp, workId);
|
|
2091
2184
|
return this.getChapter(chapterId);
|
|
2092
2185
|
});
|
|
2093
2186
|
}
|
|
2187
|
+
renumberVolumeChapterTitles(volumeId) {
|
|
2188
|
+
return this.db.transaction(() => {
|
|
2189
|
+
const volume = this.getVolume(volumeId);
|
|
2190
|
+
const workId = String(volume.workId);
|
|
2191
|
+
const rows = this.listActiveChapterRows(workId, volumeId);
|
|
2192
|
+
const formatId = this.resolveWorkChapterTitleFormat(workId, rows.map((row) => requiredString(row, "title")));
|
|
2193
|
+
if (!formatId)
|
|
2194
|
+
throw new AppError(400, "CHAPTER_TITLE_FORMAT_REQUIRED", "请先在目录的标题编号中选择格式,或使用自动识别");
|
|
2195
|
+
let plan;
|
|
2196
|
+
try {
|
|
2197
|
+
plan = planVolumeTitleRenumber(rows.map((row) => ({ id: requiredString(row, "id"), title: requiredString(row, "title") })), formatId);
|
|
2198
|
+
}
|
|
2199
|
+
catch (error) {
|
|
2200
|
+
this.rethrowChapterNumberError(error);
|
|
2201
|
+
}
|
|
2202
|
+
const timestamp = now();
|
|
2203
|
+
const updated = this.writeChapterTitleUpdates(workId, plan.updates, rows, "按分卷重排章节标题序号", timestamp);
|
|
2204
|
+
this.db.run("UPDATE works SET updated_at = ? WHERE id = ?", timestamp, workId);
|
|
2205
|
+
this.audit(workId, "chapter.volume-renumbered", "volume", volumeId, {
|
|
2206
|
+
format: formatId,
|
|
2207
|
+
startAt: plan.startAt,
|
|
2208
|
+
updated,
|
|
2209
|
+
chapterCount: rows.length
|
|
2210
|
+
});
|
|
2211
|
+
return { volumeId, updated, format: formatId, startAt: plan.startAt };
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
resolveWorkChapterTitleFormat(workId, titles) {
|
|
2215
|
+
const preference = String(this.getWork(workId).chapterTitleFormat ?? "off");
|
|
2216
|
+
return resolveChapterTitleFormat(preference, titles);
|
|
2217
|
+
}
|
|
2218
|
+
listActiveChapterRows(workId, volumeId) {
|
|
2219
|
+
return this.db.all(`SELECT chapter.* FROM chapters chapter
|
|
2220
|
+
JOIN volumes volume ON volume.id = chapter.volume_id
|
|
2221
|
+
WHERE chapter.work_id = ? AND chapter.deleted_at IS NULL AND volume.deleted_at IS NULL
|
|
2222
|
+
${volumeId ? "AND chapter.volume_id = ?" : ""}
|
|
2223
|
+
ORDER BY volume.sort_order, volume.created_at, volume.id,
|
|
2224
|
+
chapter.sort_order, chapter.created_at, chapter.id`, ...(volumeId ? [workId, volumeId] : [workId]));
|
|
2225
|
+
}
|
|
2226
|
+
rethrowChapterNumberError(error) {
|
|
2227
|
+
if (error instanceof RangeError)
|
|
2228
|
+
throw new AppError(400, "CHAPTER_NUMBER_RANGE", "章节序号超出 1 到 999999");
|
|
2229
|
+
throw error;
|
|
2230
|
+
}
|
|
2231
|
+
writeChapterTitleUpdates(workId, updates, rows, changeNote, timestamp) {
|
|
2232
|
+
const rowsById = new Map(rows.map((row) => [requiredString(row, "id"), row]));
|
|
2233
|
+
let updated = 0;
|
|
2234
|
+
for (const item of updates) {
|
|
2235
|
+
const row = rowsById.get(item.id);
|
|
2236
|
+
if (!row || requiredString(row, "title") === item.title)
|
|
2237
|
+
continue;
|
|
2238
|
+
if (item.title.length > 300) {
|
|
2239
|
+
throw new AppError(400, "CHAPTER_TITLE_TOO_LONG", `章节“${requiredString(row, "title").slice(0, 40)}”编号后的标题超过 300 个字符`);
|
|
2240
|
+
}
|
|
2241
|
+
const chapterId = item.id;
|
|
2242
|
+
const versionNo = numberValue(row, "version_no") + 1;
|
|
2243
|
+
this.db.run("UPDATE chapters SET title = ?, version_no = ?, analysis_status = 'expired', updated_at = ? WHERE id = ?", item.title, versionNo, timestamp, chapterId);
|
|
2244
|
+
this.syncChapterParagraphSearchVersion(chapterId, versionNo);
|
|
2245
|
+
this.insertChapterVersionRow({
|
|
2246
|
+
workId,
|
|
2247
|
+
chapterId,
|
|
2248
|
+
versionNo,
|
|
2249
|
+
title: item.title,
|
|
2250
|
+
content: requiredString(row, "content"),
|
|
2251
|
+
volumeId: requiredString(row, "volume_id"),
|
|
2252
|
+
sortOrder: numberValue(row, "sort_order"),
|
|
2253
|
+
chapterType: requiredString(row, "chapter_type"),
|
|
2254
|
+
source: "manual",
|
|
2255
|
+
sourceRef: null,
|
|
2256
|
+
changeNote,
|
|
2257
|
+
timestamp
|
|
2258
|
+
});
|
|
2259
|
+
this.invalidateChapter(workId, chapterId, versionNo);
|
|
2260
|
+
this.audit(workId, "chapter.saved", "chapter", chapterId, {
|
|
2261
|
+
previousTitle: requiredString(row, "title"),
|
|
2262
|
+
title: item.title,
|
|
2263
|
+
sequence: item.sequence,
|
|
2264
|
+
versionNo,
|
|
2265
|
+
renumbered: true
|
|
2266
|
+
});
|
|
2267
|
+
updated += 1;
|
|
2268
|
+
}
|
|
2269
|
+
return updated;
|
|
2270
|
+
}
|
|
2094
2271
|
getChapter(chapterId) {
|
|
2095
2272
|
const row = this.db.get(`SELECT chapter.* FROM chapters chapter
|
|
2096
2273
|
JOIN volumes volume ON volume.id = chapter.volume_id
|
|
@@ -3373,6 +3550,9 @@ export class Store {
|
|
|
3373
3550
|
offlineAccessEnabled: numberValue(row, "offline_access_enabled") === 1,
|
|
3374
3551
|
editorAutoIndentEnabled: numberValue(row, "editor_auto_indent_enabled") === 1,
|
|
3375
3552
|
editorTypewriterModeEnabled: numberValue(row, "editor_typewriter_mode_enabled") === 1,
|
|
3553
|
+
chapterTitleFormat: isChapterTitlePreference(optionalString(row, "chapter_title_format") ?? "")
|
|
3554
|
+
? optionalString(row, "chapter_title_format")
|
|
3555
|
+
: "off",
|
|
3376
3556
|
versionNo: numberValue(row, "version_no") || this.currentEntityVersionNo("work", workId),
|
|
3377
3557
|
ownerUserId,
|
|
3378
3558
|
accessRole,
|