@musnows/scriverse 0.6.5 → 0.6.7

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.
@@ -16,7 +16,7 @@ import { formatAiToolCallResult } from "/ai-tool-call.js?v=20260801-ai-tool-resu
16
16
  import { copyAiRawMarkdown } from "/ai-message-actions.js?v=20260713-copy-raw-markdown";
17
17
  import { THEME_STORAGE_KEY, nextTheme, normalizeTheme, themeToggleLabel } from "/theme.js?v=20260713-dark-mode";
18
18
  import { buildCharacterDetails, buildCharacterState, characterStateEntries, normalizeCharacterDetails, normalizeCharacterSections } from "/character-profile.js?v=20260713-character-editor";
19
- import { characterVersionSourceLabel, describeCharacterVersionChanges } from "/character-version.js?v=20260725-unified-permissions";
19
+ import { characterVersionSourceLabel, describeCharacterVersionChanges } from "/character-version.js?v=20260801-entity-lifecycle-v1";
20
20
  import { VERSIONED_ENTITY_LABELS, entityVersionSnapshotSummary, entityVersionSourceLabel } from "/entity-version.js?v=20260731-drafts-to-ideas-v1";
21
21
  import {
22
22
  chapterVersionSourceLabel,
@@ -48,6 +48,12 @@ import { isGlobalSearchShortcut } from "/keyboard-shortcuts.js?v=20260723-global
48
48
  import { resolveGlobalSearchTarget, splitGlobalSearchHighlight } from "/global-search.js?v=20260728-hybrid-search-v1";
49
49
  import { filterCharacters, paginateCharacters } from "/character-filters.js?v=20260725-character-filters";
50
50
  import { filterRelationships } from "/relationship-filters.js?v=20260726-relationship-filters";
51
+ import {
52
+ prepareTimelineEvents,
53
+ resolveTimelineActiveTrackId,
54
+ normalizeTimelineSortDirection,
55
+ timelineTrackColorIndex
56
+ } from "/timeline-view.js?v=20260801-timeline-sort-actions-v1";
51
57
  import { backgroundTaskActivityCount, backgroundTaskPollDelay, collectBackgroundTaskTransitions } from "/background-task-center.js?v=20260726-background-task-center-v1";
52
58
  import { createModuleRequestCache } from "/module-request-cache.js?v=20260730-module-request-cache-v1";
53
59
  import { systemStatusPresentation } from "/system-status.js?v=20260801-system-health-v1";
@@ -177,6 +183,8 @@ const chapterBatchSelectedIds = new Set();
177
183
  let chapterMovePending = false;
178
184
 
179
185
  let timelineMultiSelectEnabled = false;
186
+ let timelineActiveTrackId = null;
187
+ let timelineSortDirection = "asc";
180
188
  let taskProgressRefreshTimer = null;
181
189
  let taskAutoRunEditing = false;
182
190
  let taskAutoRunEditingWorkId = null;
@@ -2268,7 +2276,7 @@ function clearChapterLineSelection() {
2268
2276
  }
2269
2277
 
2270
2278
  const typographyStorageKey = "ai-novel-typography-v1";
2271
- const typographyDefaults = Object.freeze({ cjkFont: "system", latinFont: "system", fontSize: 17, uiFontSize: 16, aiFontSize: 14, density: "balanced" });
2279
+ const typographyDefaults = Object.freeze({ cjkFont: "system", latinFont: "system", fontSize: 17, uiFontSize: 14, aiFontSize: 14, density: "balanced" });
2272
2280
  const cjkFontStacks = {
2273
2281
  system: '"PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", "Heiti SC"',
2274
2282
  pingfang: '"PingFang SC", "Heiti SC", "Microsoft YaHei", "Noto Sans CJK SC"',
@@ -4121,6 +4129,9 @@ function resetWorkScopedUiCaches() {
4121
4129
  Object.keys(moduleListPages).forEach((key) => { moduleListPages[key] = 1; });
4122
4130
  relationshipFilters.fromCharacterIds = [];
4123
4131
  relationshipFilters.toCharacterIds = [];
4132
+ timelineActiveTrackId = null;
4133
+ timelineSortDirection = "asc";
4134
+ timelineMultiSelectEnabled = false;
4124
4135
  taskListPage = 1;
4125
4136
  state.collapsedVolumeIds.clear();
4126
4137
  loadedVolumeChapterIds.clear();
@@ -5360,6 +5371,10 @@ function renderSettingRows(records) {
5360
5371
  }).join("")}</div>`;
5361
5372
  }
5362
5373
 
5374
+ function entityLifecycleBadge(active, label) {
5375
+ return active ? `<span class="entity-lifecycle-badge">${esc(label)}</span>` : "";
5376
+ }
5377
+
5363
5378
  async function renderSettings(page = moduleListPages.settings) {
5364
5379
  const records = await moduleApiAllPages("settings", `/api/works/${state.work.id}/settings`);
5365
5380
  state.settings = records;
@@ -5405,7 +5420,7 @@ async function renderCharacters(page = characterListPage) {
5405
5420
  const details = normalizeCharacterDetails(item.attributes?.details);
5406
5421
  return `
5407
5422
  <article class="record-card character-card preview-record-card has-card-edit" data-open-character="${esc(item.id)}" role="button" tabindex="0" aria-label="查看角色 ${esc(item.name)}">${recordCardEditButton("edit-character", item.id, `角色“${item.name}”`)}
5408
- <div class="character-card-heading"><h3>${esc(item.name)}</h3>${characterLockBadge(item)}</div>
5423
+ <div class="character-card-heading"><h3>${esc(item.name)}</h3>${entityLifecycleBadge(item.isDead, "已死亡")}${characterLockBadge(item)}</div>
5409
5424
  ${item.attributes?.identity ? `<p class="character-identity">${esc(item.attributes.identity)}</p>` : ""}
5410
5425
  ${item.aliases.length ? `<div class="character-aliases"><b>别名</b>${item.aliases.map((alias) => `<span class="pill">${esc(alias)}</span>`).join("")}</div>` : ""}
5411
5426
  ${item.code ? `<div class="character-code"><b>编号</b><span class="pill">${esc(item.code)}</span></div>` : ""}
@@ -5427,7 +5442,7 @@ async function renderCharacters(page = characterListPage) {
5427
5442
  const line = meta ? `${meta} · ${preview}` : preview;
5428
5443
  return `
5429
5444
  <article class="record-card module-row character-row character-card preview-record-card" data-open-character="${esc(item.id)}" role="button" tabindex="0" aria-label="查看角色 ${esc(item.name)}">
5430
- <div class="character-card-heading"><h3>${esc(item.name)}</h3>${characterLockBadge(item)}</div>
5445
+ <div class="character-card-heading"><h3>${esc(item.name)}</h3>${entityLifecycleBadge(item.isDead, "已死亡")}${characterLockBadge(item)}</div>
5431
5446
  <p class="module-row-preview" title="${esc(line)}">${esc(line)}</p>
5432
5447
  <div class="card-actions">${characterActions(item)}</div>
5433
5448
  </article>`;
@@ -5505,7 +5520,7 @@ function renderRaceCollection(total, descendantsLoading = false) {
5505
5520
  ? raceActions(item)
5506
5521
  : `<div class="card-actions">${raceActions(item)}</div>`;
5507
5522
  const renderRaceNode = (item) => `<details class="race-tree-node"${state.collapsedRaceIds.has(item.id) ? "" : " open"} data-race-node="${esc(item.id)}">
5508
- <summary><span>${esc(item.name)}</span><small>${directChildCount(item)} 个直接子种族</small></summary>
5523
+ <summary><span>${esc(item.name)}${entityLifecycleBadge(item.isExtinct, "已灭绝")}</span><small>${directChildCount(item)} 个直接子种族</small></summary>
5509
5524
  <div class="race-tree-branch">
5510
5525
  <article class="record-card race-card preview-record-card${canEditRaces ? " has-card-edit" : ""}" data-open-race="${esc(item.id)}" role="button" tabindex="0" aria-label="查看种族 ${esc(item.name)}"><small>${item.memberIds.length} 位直接角色 · ${item.settingsCount ?? item.settings?.length ?? 0} 条自身设定</small>
5511
5526
  <div class="race-path" aria-label="种族路径">${esc(racePathLabel(item))}</div>
@@ -5523,7 +5538,7 @@ function renderRaceCollection(total, descendantsLoading = false) {
5523
5538
  return `
5524
5539
  <article class="record-card module-row race-card preview-record-card" data-open-race="${esc(item.id)}" role="button" tabindex="0" aria-label="查看种族 ${esc(item.name)}">
5525
5540
  <small>${esc(meta)}</small>
5526
- <h3>${esc(item.name)}<span class="module-row-path">${esc(racePathLabel(item))}</span></h3>
5541
+ <h3>${esc(item.name)}${entityLifecycleBadge(item.isExtinct, "已灭绝")}<span class="module-row-path">${esc(racePathLabel(item))}</span></h3>
5527
5542
  <p class="module-row-preview" title="${esc(preview)}">${esc(preview)}${item.members.length ? ` · ${esc(item.members.map((member) => member.name).join("、"))}` : ""}</p>
5528
5543
  <div class="card-actions">${raceActions(item)}</div>
5529
5544
  </article>`;
@@ -5610,7 +5625,7 @@ async function renderOrganizations(page = moduleListPages.organizations) {
5610
5625
  : `<div class="card-actions">${organizationActions(item)}</div>`;
5611
5626
  const organizationCards = () => `<div class="card-grid organization-grid">${pageResult.items.map((item) => `
5612
5627
  <article class="record-card organization-card preview-record-card${canEditOrganizations ? " has-card-edit" : ""}" data-open-organization="${esc(item.id)}" role="button" tabindex="0" aria-label="查看组织 ${esc(item.name)}"><small>${item.memberIds.length} 位成员 · ${(item.settingsCount ?? item.settings?.length ?? 0) ? "已填写组织设定" : "暂无组织设定"}</small>
5613
- <h3>${esc(item.name)}</h3><p>${esc(item.description || "尚未填写组织简介")}</p>
5628
+ <h3>${esc(item.name)}${entityLifecycleBadge(item.isDissolved, "已解散")}</h3><p>${esc(item.description || "尚未填写组织简介")}</p>
5614
5629
  <div class="organization-settings">${item.settingsCount ? `<span class="pill">${item.settingsCount} 条组织设定,打开查看详情</span>` : '<span class="pill">暂无组织设定</span>'}</div>
5615
5630
  <p class="organization-members">成员:${item.members.length ? item.members.map((member) => esc(member.name)).join("、") : "暂无绑定角色"}</p>
5616
5631
  ${organizationCardActions(item)}
@@ -5621,7 +5636,7 @@ async function renderOrganizations(page = moduleListPages.organizations) {
5621
5636
  return `
5622
5637
  <article class="record-card module-row organization-card" data-open-organization="${esc(item.id)}" role="button" tabindex="0" aria-label="查看组织 ${esc(item.name)}">
5623
5638
  <small>${item.memberIds.length} 位成员 · ${(item.settingsCount ?? item.settings?.length ?? 0) ? "已填写组织设定" : "暂无组织设定"}</small>
5624
- <h3>${esc(item.name)}</h3>
5639
+ <h3>${esc(item.name)}${entityLifecycleBadge(item.isDissolved, "已解散")}</h3>
5625
5640
  <p class="module-row-preview" title="${esc(`${preview} · 成员:${members}`)}">${esc(preview)} · ${esc(members)}</p>
5626
5641
  <div class="card-actions">${organizationActions(item)}</div>
5627
5642
  </article>`;
@@ -5664,24 +5679,63 @@ async function renderTimeline(page = moduleListPages.timeline) {
5664
5679
  moduleApiAllPages("timeline", `/api/works/${state.work.id}/timeline`),
5665
5680
  moduleApiAllPages("timeline", `/api/works/${state.work.id}/timeline-tracks`)
5666
5681
  ]);
5667
- mountModuleCount(events.length);
5668
- const pageResult = paginateModuleItems(events, page, "timeline");
5682
+ state.timelineTracks = tracks;
5683
+ const orderedTracks = [...tracks].sort((left, right) => {
5684
+ const orderDelta = Number(left.sortOrder ?? 0) - Number(right.sortOrder ?? 0);
5685
+ if (orderDelta !== 0) return orderDelta;
5686
+ return String(left.id).localeCompare(String(right.id));
5687
+ });
5688
+ const tabTracks = [
5689
+ ...orderedTracks,
5690
+ { id: "", name: "未分组", description: "尚未归入独立时间轴的时间节点。" }
5691
+ ];
5692
+ timelineActiveTrackId = resolveTimelineActiveTrackId(timelineActiveTrackId, tracks);
5693
+ timelineSortDirection = normalizeTimelineSortDirection(timelineSortDirection);
5694
+ const activeTrack = tabTracks.find((track) => String(track.id ?? "") === String(timelineActiveTrackId)) ?? tabTracks[tabTracks.length - 1];
5695
+ const filteredEvents = prepareTimelineEvents(events, { trackIds: [timelineActiveTrackId] }, { direction: timelineSortDirection });
5696
+ mountModuleCount(filteredEvents.length);
5697
+ const pageResult = paginateModuleItems(filteredEvents, page, "timeline");
5669
5698
  moduleListPages.timeline = pageResult.page;
5670
5699
  timelineMultiSelectEnabled = false;
5671
5700
  $("#timeline-tools")?.remove();
5701
+ $("#module-header-actions").querySelector('[data-module-header-action="timeline-filter-toggle"]')?.remove();
5672
5702
  $("#module-header-actions").insertAdjacentHTML("beforeend", `<div id="timeline-tools" class="timeline-tools" data-module-header-action="timeline-tools" role="group" aria-label="时间轴操作"><button id="create-timeline-track" class="ghost-button" type="button">新建独立时间轴</button><button id="timeline-multi-select-toggle" class="ghost-button" type="button" aria-pressed="false">多选</button>${events.length > 1 ? '<button id="merge-events" class="ghost-button" type="button" hidden>合并所选事件</button>' : ""}</div>`);
5673
- state.timelineTracks = tracks;
5674
- const lanes = [...tracks, { id: "", name: "未分组时间轴", description: "尚未归入独立大事件的时间节点。", sortOrder: Number.MAX_SAFE_INTEGER }];
5675
- const eventCard = (item) => `<article class="timeline-kanban-card"><div class="timeline-card-meta"><input type="checkbox" data-event-select="${esc(item.id)}" aria-label="选择 ${esc(item.name)}" hidden><small>${esc(item.timeLabel)} · ${esc(timelineStatusLabel(item.status))}</small></div><h4>${esc(item.name)}</h4><p>${esc(item.description || "暂无说明")}</p>${item.location ? `<span>地点:${esc(item.location)}</span>` : ""}<div class="card-actions"><button data-edit-event="${esc(item.id)}">编辑与排序</button><button data-split-event="${esc(item.id)}">拆分</button><button data-entity-history="timeline-event" data-entity-id="${esc(item.id)}" data-entity-title="${esc(item.name)}">版本历史</button></div></article>`;
5676
- $("#module-content").innerHTML = `<div class="timeline-kanban" data-testid="timeline-kanban">${lanes.map((track) => {
5677
- const laneEvents = pageResult.items.filter((item) => (item.trackId ?? "") === track.id);
5678
- return `<section class="timeline-lane" data-track-id="${esc(track.id)}"><header><div><small>${laneEvents.length} 个节点</small><h3>${esc(track.name)}</h3></div>${track.id ? `<div class="timeline-track-actions"><button class="timeline-track-menu" data-edit-timeline-track="${esc(track.id)}" type="button">编辑</button><button class="timeline-track-menu" data-entity-history="timeline-track" data-entity-id="${esc(track.id)}" data-entity-title="${esc(track.name)}" type="button">历史</button></div>` : ""}</header><p class="timeline-track-description">${esc(track.description || "暂无说明")}</p><div class="timeline-lane-events">${laneEvents.map(eventCard).join("") || '<div class="timeline-lane-empty">还没有时间节点</div>'}</div><button class="timeline-add-event" data-add-event-track="${esc(track.id)}" type="button">添加事件</button></section>`;
5679
- }).join("")}</div>${renderModulePagination(pageResult, "timeline", "时间线事件")}`;
5703
+ const tabsMarkup = `<div class="timeline-track-tabs" role="tablist" aria-label="时间轴轨道">${tabTracks.map((track) => {
5704
+ const trackKey = String(track.id ?? "");
5705
+ const selected = trackKey === String(timelineActiveTrackId);
5706
+ const colorIndex = timelineTrackColorIndex(track.id, tracks);
5707
+ return `<button type="button" class="timeline-track-tab" role="tab" id="timeline-track-tab-${esc(trackKey || "ungrouped")}" data-timeline-track-tab="${esc(trackKey)}" data-track-color-index="${colorIndex}" aria-selected="${selected}" aria-controls="timeline-track-panel" tabindex="${selected ? "0" : "-1"}"><span class="timeline-track-swatch" aria-hidden="true"></span><span>${esc(track.name)}</span></button>`;
5708
+ }).join("")}</div>`;
5709
+ const trackManageActions = activeTrack.id
5710
+ ? `<button class="timeline-track-menu" data-edit-timeline-track="${esc(activeTrack.id)}" type="button">编辑</button><button class="timeline-track-menu" data-entity-history="timeline-track" data-entity-id="${esc(activeTrack.id)}" data-entity-title="${esc(activeTrack.name)}" type="button">历史</button>`
5711
+ : "";
5712
+ const panelMarkup = `<section id="timeline-track-panel" class="timeline-track-panel" data-track-color-index="${timelineTrackColorIndex(activeTrack.id, tracks)}" role="tabpanel" aria-labelledby="timeline-track-tab-${esc(String(activeTrack.id || "ungrouped"))}"><div class="timeline-track-panel-copy"><strong>${esc(activeTrack.name)}</strong><p>${esc(activeTrack.description || "暂无说明")}</p></div><div class="timeline-track-panel-actions"><div class="timeline-track-actions" role="group" aria-label="当前轨道操作">${trackManageActions}<button class="timeline-track-menu" data-add-event-track="${esc(String(activeTrack.id ?? ""))}" type="button">添加事件</button><button class="timeline-track-menu" data-timeline-sort="asc" type="button" aria-pressed="${timelineSortDirection === "asc"}">正序</button><button class="timeline-track-menu" data-timeline-sort="desc" type="button" aria-pressed="${timelineSortDirection === "desc"}">倒序</button></div></div></section>`;
5713
+ const eventItem = (item) => {
5714
+ const trackKey = item.trackId ?? "";
5715
+ const colorIndex = timelineTrackColorIndex(trackKey, tracks);
5716
+ return `<article class="timeline-item" data-track-color-index="${colorIndex}" data-event-id="${esc(item.id)}"><div class="timeline-card-meta"><input class="timeline-select" type="checkbox" data-event-select="${esc(item.id)}" aria-label="选择 ${esc(item.name)}" hidden><small>${esc(item.timeLabel)} · ${esc(timelineStatusLabel(item.status))}</small></div><h3>${esc(item.name)}</h3><p>${esc(item.description || "暂无说明")}</p>${item.location ? `<span class="timeline-item-location">地点:${esc(item.location)}</span>` : ""}<div class="card-actions"><button data-edit-event="${esc(item.id)}" type="button">编辑与排序</button><button data-split-event="${esc(item.id)}" type="button">拆分</button><button data-entity-history="timeline-event" data-entity-id="${esc(item.id)}" data-entity-title="${esc(item.name)}" type="button">版本历史</button></div></article>`;
5717
+ };
5718
+ const axisMarkup = pageResult.items.length
5719
+ ? `<div class="timeline-axis" data-testid="timeline-axis"><div class="timeline-list">${pageResult.items.map(eventItem).join("")}</div></div>`
5720
+ : `<div class="timeline-axis" data-testid="timeline-axis"><div class="empty-state"><b>当前轨道还没有时间节点</b>可以添加事件,或切换到其他轨道查看。</div></div>`;
5721
+ $("#module-content").innerHTML = `${tabsMarkup}${panelMarkup}${axisMarkup}${renderModulePagination(pageResult, "timeline", "时间线事件")}`;
5680
5722
  bindModulePagination("timeline", renderTimeline);
5681
5723
  $("#create-timeline-track").addEventListener("click", () => openTimelineTrackDialog());
5682
5724
  $("#timeline-multi-select-toggle").addEventListener("click", () => setTimelineMultiSelectMode(!timelineMultiSelectEnabled));
5683
5725
  $("#module-content").querySelectorAll("[data-event-select]").forEach((input) => input.addEventListener("change", updateTimelineMultiSelectControls));
5684
5726
  setTimelineMultiSelectMode(false);
5727
+ $("#module-content").querySelectorAll("[data-timeline-track-tab]").forEach((button) => button.addEventListener("click", async () => {
5728
+ const nextTrackId = button.dataset.timelineTrackTab ?? "";
5729
+ if (String(nextTrackId) === String(timelineActiveTrackId)) return;
5730
+ timelineActiveTrackId = nextTrackId;
5731
+ await renderTimeline(1);
5732
+ }));
5733
+ $("#module-content").querySelectorAll("[data-timeline-sort]").forEach((button) => button.addEventListener("click", async () => {
5734
+ const nextDirection = normalizeTimelineSortDirection(button.dataset.timelineSort);
5735
+ if (nextDirection === timelineSortDirection) return;
5736
+ timelineSortDirection = nextDirection;
5737
+ await renderTimeline(1);
5738
+ }));
5685
5739
  $("#module-content").querySelectorAll("[data-edit-timeline-track]").forEach((button) => button.addEventListener("click", () => openTimelineTrackDialog(tracks.find((track) => track.id === button.dataset.editTimelineTrack))));
5686
5740
  $("#module-content").querySelectorAll("[data-add-event-track]").forEach((button) => button.addEventListener("click", () => openTimelineDialog(null, button.dataset.addEventTrack || null)));
5687
5741
  $("#module-content").querySelectorAll("[data-edit-event]").forEach((button) => button.addEventListener("click", () => openTimelineDialog(events.find((item) => item.id === button.dataset.editEvent))));
@@ -8941,6 +8995,7 @@ function renderCharacterEditorFields(item) {
8941
8995
  $("#character-editor-fields").innerHTML = [
8942
8996
  characterEditorSection("basic", "基础资料", "用于检索、去重和建立人物在作品中的基本归属。",
8943
8997
  field("name", "标准名", "text", item?.name) +
8998
+ field("isDead", "标记为已死亡", "checkbox", item?.isDead ?? false) +
8944
8999
  field("aliases", "别名", "item-list", item?.aliases ?? []) +
8945
9000
  (!canReadModule("races")
8946
9001
  ? '<div class="character-editor-empty-field"><b>种族</b><span>当前账户没有种族模块读取权限,原有绑定不会被修改。</span></div>'
@@ -8993,6 +9048,7 @@ function collectCharacterBody(form) {
8993
9048
  delete profile.sections;
8994
9049
  const body = {
8995
9050
  name: String(form.get("name") ?? "").trim(),
9051
+ isDead: form.has("isDead"),
8996
9052
  code: String(form.get("code") ?? "").trim(),
8997
9053
  aliases: form.getAll("aliases").map((value) => String(value).trim()).filter(Boolean),
8998
9054
  attributes: {
@@ -9201,7 +9257,10 @@ function renderKnowledgeEditorFields(kind, item, memberOptions, parentOptions) {
9201
9257
  ? [["basic", "基础资料", "名称、层级与简介"], ["settings", "共同设定", "Markdown 设定章节"], ["members", "种族成员", "角色归属"]]
9202
9258
  : [["basic", "基础资料", "名称与简介"], ["settings", "组织设定", "Markdown 设定章节"], ["members", "组织成员", "角色归属"]];
9203
9259
  $("#knowledge-editor-nav").innerHTML = tabs.map(([key, tabTitle, description], index) => `<button type="button" role="tab" data-knowledge-editor-tab="${key}" aria-selected="${index === 0}" tabindex="${index === 0 ? "0" : "-1"}">${tabTitle}<small>${description}</small></button>`).join("");
9204
- const basicFields = field("name", `${label}名称`, "text", item?.name, []) + (isRace ? field("parentRaceId", "父种族", "select", item?.parentRaceId ?? "", parentOptions) : "") + field("description", `${label}简介`, "textarea", item?.description, []);
9260
+ const basicFields = field("name", `${label}名称`, "text", item?.name, [])
9261
+ + field(isRace ? "isExtinct" : "isDissolved", isRace ? "标记为已灭绝" : "标记为已解散", "checkbox", isRace ? item?.isExtinct ?? false : item?.isDissolved ?? false)
9262
+ + (isRace ? field("parentRaceId", "父种族", "select", item?.parentRaceId ?? "", parentOptions) : "")
9263
+ + field("description", `${label}简介`, "textarea", item?.description, []);
9205
9264
  const memberField = memberOptions.length
9206
9265
  ? field("memberIds", isRace ? "属于该种族的角色(可多选)" : "组织成员(可多选)", "chips", item?.memberIds ?? [], memberOptions)
9207
9266
  : `<div class="character-editor-empty-field"><strong>${isRace ? "种族成员" : "组织成员"}</strong><span>当前还没有可绑定的角色。</span></div>`;
@@ -9307,8 +9366,8 @@ async function openKnowledgeEditor(kind, item, { readOnly = false } = {}) {
9307
9366
  if (untitled >= 0) throw new Error(`请填写第 ${untitled + 1} 条 Markdown 设定的标题`);
9308
9367
  const settingsMarkdown = settingsSections.map((section) => section.contentMarkdown).join("\n\n");
9309
9368
  const body = isRace
9310
- ? { name, parentRaceId: data.get("parentRaceId") || null, description: data.get("description"), settingsMarkdown, settingsSections }
9311
- : { name, description: data.get("description"), settingsMarkdown, settingsSections };
9369
+ ? { name, isExtinct: data.has("isExtinct"), parentRaceId: data.get("parentRaceId") || null, description: data.get("description"), settingsMarkdown, settingsSections }
9370
+ : { name, isDissolved: data.has("isDissolved"), description: data.get("description"), settingsMarkdown, settingsSections };
9312
9371
  if (canReadModule("characters")) body.memberIds = data.getAll("memberIds").map(String);
9313
9372
  const wasEditing = Boolean(knowledgeEditorItem);
9314
9373
  if (!(await confirmConcurrentSave())) return;
@@ -9337,7 +9396,7 @@ async function openOrganizationDialog(item, options) {
9337
9396
  }
9338
9397
 
9339
9398
  function openTimelineTrackDialog(item) {
9340
- openDialog(item ? "编辑独立时间轴" : "新建独立时间轴", field("name", "时间轴名称", "text", item?.name) + field("description", "时间轴简介", "textarea", item?.description) + field("sortOrder", "看板排序", "number", item?.sortOrder ?? state.timelineTracks.length), async (form) => {
9399
+ openDialog(item ? "编辑独立时间轴" : "新建独立时间轴", field("name", "时间轴名称", "text", item?.name) + field("description", "时间轴简介", "textarea", item?.description) + field("sortOrder", "轨道排序", "number", item?.sortOrder ?? state.timelineTracks.length), async (form) => {
9341
9400
  await api(item ? `/api/timeline-tracks/${item.id}` : `/api/works/${state.work.id}/timeline-tracks`, { method: item ? "PATCH" : "POST", body: { name: form.get("name"), description: form.get("description"), sortOrder: Number(form.get("sortOrder")) } });
9342
9401
  await renderTimeline();
9343
9402
  }, item ? "大事件分组" : "多线叙事");
@@ -1,5 +1,6 @@
1
1
  const fieldLabels = Object.freeze({
2
2
  name: "标准名",
3
+ isDead: "死亡标识",
3
4
  code: "编号",
4
5
  aliases: "别名",
5
6
  raceId: "种族",
@@ -10,7 +10,7 @@
10
10
  <link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
11
11
  <link rel="manifest" href="/site.webmanifest">
12
12
  <link rel="stylesheet" href="/vendor/vditor/dist/index.css?v=3.11.2">
13
- <link rel="stylesheet" href="/styles.css?v=20260801-ai-context-mention-v1">
13
+ <link rel="stylesheet" href="/styles.css?v=20260801-ui-font-scale-shelf-v3">
14
14
  </head>
15
15
  <body class="auth-pending">
16
16
  <section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
@@ -664,9 +664,9 @@
664
664
  </label>
665
665
  <label>界面字号
666
666
  <select id="appearance-ui-font-size" name="uiFontSize">
667
- <option value="14">14 px(小)</option>
667
+ <option value="14">14 px(标准)</option>
668
668
  <option value="15">15 px</option>
669
- <option value="16">16 px(标准)</option>
669
+ <option value="16">16 px</option>
670
670
  <option value="17">17 px</option>
671
671
  <option value="18">18 px(大)</option>
672
672
  </select>
@@ -704,7 +704,7 @@
704
704
  <strong>字体预览 Typography 0123</strong>
705
705
  <span>中文默认使用黑体,English letters always use monospace.</span>
706
706
  <span>第二段用于预览正文的行距与段落密度。</span>
707
- <span id="font-size-preview">界面 16 px · Agent 对话 14 px</span>
707
+ <span id="font-size-preview">界面 14 px · Agent 对话 14 px</span>
708
708
  </div>
709
709
  </div>
710
710
  <div class="dialog-actions">
@@ -951,6 +951,6 @@
951
951
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
952
952
  <script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
953
953
  <script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
954
- <script type="module" src="/app.js?v=20260801-global-setting-context-v1"></script>
954
+ <script type="module" src="/app.js?v=20260801-timeline-lifecycle-v1"></script>
955
955
  </body>
956
956
  </html>
@@ -38,6 +38,14 @@
38
38
  --relationship-edge-selected: rgba(38,69,118,.9);
39
39
  --relationship-label-ink: rgba(40,52,73,.9);
40
40
  --relationship-label-stroke: #eef2f7;
41
+ --timeline-track-palette-0: #8b3d2c;
42
+ --timeline-track-palette-1: #356f94;
43
+ --timeline-track-palette-2: #5e765c;
44
+ --timeline-track-palette-3: #9a6b2f;
45
+ --timeline-track-palette-4: #6b5b95;
46
+ --timeline-track-palette-5: #2f7a78;
47
+ --timeline-track-palette-6: #8a4f63;
48
+ --timeline-track-palette-7: #746f65;
41
49
  --relationship-node-ink: rgba(36,51,74,.9);
42
50
  --relationship-node-label: rgba(48,62,86,.88);
43
51
  --relationship-node-label-active: rgba(24,44,71,.98);
@@ -55,7 +63,7 @@
55
63
  --relationship-detail-muted: #67758c;
56
64
  --font-latin: "SFMono-Regular", "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono";
57
65
  --font-cjk: "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", "Heiti SC";
58
- --ui-font-size: 16px;
66
+ --ui-font-size: 14px;
59
67
  --ui-font-scale: 1;
60
68
  --ai-font-size: 14px;
61
69
  --ai-font-scale: 1;
@@ -272,6 +280,14 @@
272
280
  --relationship-edge-selected: rgba(220,220,235,.88);
273
281
  --relationship-label-ink: rgba(230,230,240,.88);
274
282
  --relationship-label-stroke: #12121a;
283
+ --timeline-track-palette-0: #bd6550;
284
+ --timeline-track-palette-1: #7fb8d8;
285
+ --timeline-track-palette-2: #89a985;
286
+ --timeline-track-palette-3: #d4a35c;
287
+ --timeline-track-palette-4: #a794c8;
288
+ --timeline-track-palette-5: #6cb5b2;
289
+ --timeline-track-palette-6: #c4879a;
290
+ --timeline-track-palette-7: #aaa398;
275
291
  --relationship-node-ink: rgba(245,245,250,.88);
276
292
  --relationship-node-label: rgba(235,235,245,.78);
277
293
  --relationship-node-label-active: rgba(255,255,255,.95);
@@ -1295,6 +1311,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1295
1311
  .record-card.has-card-edit .character-card-heading { padding-right: 34px; }
1296
1312
  .record-card.has-card-edit .character-card-heading h3 { padding-right: 0; }
1297
1313
  .character-lock-badge { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 3px; min-height: 20px; padding: 2px 6px; border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--line)); border-radius: 10px; background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent-dark); font-size: 9px; font-variant-numeric: tabular-nums; line-height: 1; }
1314
+ .entity-lifecycle-badge { display: inline-flex; align-items: center; min-height: 20px; margin-left: 7px; padding: 2px 7px; border: 1px solid color-mix(in srgb, var(--accent) 58%, var(--line)); border-radius: 10px; background: color-mix(in srgb, var(--accent) 13%, var(--surface)); color: var(--accent-dark); font-size: 9px; font-weight: 650; line-height: 1; vertical-align: middle; white-space: nowrap; }
1298
1315
  .character-lock-badge svg { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.8; }
1299
1316
  .character-filter-toolbar { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)) auto; gap: 12px; align-items: start; width: 100%; min-width: 0; box-sizing: border-box; margin-bottom: 18px; padding: 12px 14px; border: 1px solid var(--line); background: var(--surface-soft); }
1300
1317
  .character-filter-dropdown { position: relative; min-width: 0; }
@@ -1732,30 +1749,65 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1732
1749
  .default-model-select { min-width: 260px; padding: 7px 9px; font-size: 11px; }
1733
1750
  .empty-state { padding: 70px 30px; text-align: center; color: var(--muted); border: 1px dashed var(--line); }
1734
1751
  .empty-state b { display: block; color: var(--ink); margin-bottom: 8px; }
1752
+ .timeline-axis { display: grid; gap: 14px; }
1735
1753
  .timeline-list { position: relative; margin-left: 15px; border-left: 1px solid var(--line); }
1736
- .timeline-item { position: relative; padding: 0 0 28px 25px; }
1737
- .timeline-item::before { content: ""; position: absolute; left: -5px; top: 5px; width: 9px; height: 9px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 5px var(--paper); }
1738
- .timeline-item h3 { margin: 4px 0 7px; }
1754
+ .timeline-item { position: relative; padding: 0 0 28px 25px; --timeline-track-color: var(--accent); }
1755
+ .timeline-item::before { content: ""; position: absolute; left: -5px; top: 5px; width: 9px; height: 9px; border-radius: 50%; background: var(--timeline-track-color); box-shadow: 0 0 0 5px var(--paper); }
1756
+ .timeline-item h3 { margin: 4px 0 7px; font-size: 15px; }
1739
1757
  .timeline-item p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.55; }
1758
+ .timeline-item-location { display: block; margin-top: 7px; color: var(--muted); font-size: 10px; }
1759
+ .timeline-item .card-actions { margin-top: 10px; }
1740
1760
  .table-list { width: 100%; border-collapse: collapse; font-size: 12px; }
1741
1761
  .table-list th, .table-list td { padding: 12px 10px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
1742
1762
  .table-list th { color: var(--muted); font-weight: 400; font-size: 10px; text-transform: uppercase; letter-spacing: .1em; }
1743
1763
  .danger-text { color: var(--accent); }
1744
1764
  .timeline-tools { display: flex; justify-content: flex-end; gap: 8px; }
1745
1765
  .timeline-select { position: absolute; left: -33px; top: 0; }
1746
- .timeline-kanban { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(290px, 340px); gap: 14px; align-items: start; overflow-x: auto; padding: 3px 2px 18px; scroll-snap-type: x proximity; }
1747
- .timeline-lane { display: flex; max-height: calc(100vh - 250px); min-height: 420px; flex-direction: column; overflow: hidden; border: 1px solid var(--line); border-radius: 7px; background: color-mix(in srgb, var(--paper-deep) 42%, transparent); scroll-snap-align: start; }
1748
- .timeline-lane > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; padding: 14px 14px 10px; border-bottom: 1px solid var(--line); }
1749
- .timeline-lane > header h3 { margin: 3px 0 0; font-size: 16px; }.timeline-lane > header small { color: var(--accent); font-size: 9px; }
1750
- .timeline-track-menu { border: 1px solid var(--line); border-radius: 3px; padding: 5px 7px; background: transparent; font-size: 9px; }
1751
- .timeline-track-actions { display: flex; gap: 5px; }
1752
- .timeline-track-description { min-height: 40px; margin: 0; padding: 9px 14px; color: var(--muted); font-size: 10px; line-height: 1.45; }
1753
- .timeline-lane-events { display: grid; gap: 9px; align-content: start; flex: 1; overflow-y: auto; padding: 8px; }
1754
- .timeline-kanban-card { padding: 12px; border: 1px solid var(--line); border-radius: 5px; background: var(--panel); box-shadow: 0 5px 16px rgba(49,42,32,.06); }
1755
- .timeline-kanban-card h4 { margin: 8px 0 6px; font-size: 14px; }.timeline-kanban-card p { margin: 0; color: var(--muted); font-size: 11px; line-height: 1.5; }.timeline-kanban-card > span { display: block; margin-top: 7px; color: var(--muted); font-size: 9px; }
1756
- .timeline-card-meta { display: flex; gap: 7px; align-items: center; }.timeline-card-meta small { color: var(--accent); font-size: 9px; }
1757
- .timeline-lane-empty { padding: 34px 12px; border: 1px dashed var(--line); color: var(--muted); text-align: center; font-size: 10px; }
1758
- .timeline-add-event { margin: 8px; padding: 8px; border: 1px dashed var(--line); border-radius: 4px; background: transparent; color: var(--muted); font-size: 10px; }
1766
+ .timeline-card-meta { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; }
1767
+ .timeline-card-meta small { color: var(--timeline-track-color, var(--accent)); font-size: 9px; }
1768
+ .timeline-track-badge { display: inline-flex; align-items: center; padding: 2px 7px; border: 1px solid color-mix(in srgb, var(--timeline-track-color, var(--accent)) 45%, var(--line)); border-radius: 999px; color: var(--timeline-track-color, var(--accent)); font-size: 9px; line-height: 1.3; background: color-mix(in srgb, var(--timeline-track-color, var(--accent)) 10%, transparent); }
1769
+ .timeline-track-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
1770
+ .timeline-track-tab { display: inline-flex; align-items: center; gap: 7px; min-height: 34px; padding: 0 12px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--muted); font-size: 11px; }
1771
+ .timeline-track-tab:hover, .timeline-track-tab:focus-visible { color: var(--ink); background: var(--paper-deep); }
1772
+ .timeline-track-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
1773
+ .timeline-track-tab[aria-selected="true"] { color: var(--ink); border-color: color-mix(in srgb, var(--timeline-track-color, var(--accent)) 55%, var(--line)); background: color-mix(in srgb, var(--timeline-track-color, var(--accent)) 14%, var(--surface)); box-shadow: inset 0 -2px 0 var(--timeline-track-color, var(--accent)); font-weight: 600; }
1774
+ .timeline-track-tab .timeline-track-swatch { margin-top: 0; }
1775
+ .timeline-track-panel { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-start; justify-content: space-between; margin-bottom: 16px; padding: 12px 14px; border: 1px solid var(--line); border-radius: 5px; background: var(--panel); }
1776
+ .timeline-track-panel-copy { display: grid; gap: 4px; min-width: min(280px, 100%); flex: 1; }
1777
+ .timeline-track-panel-copy strong { font-size: 13px; }
1778
+ .timeline-track-panel-copy p { margin: 0; color: var(--muted); font-size: 11px; line-height: 1.45; }
1779
+ .timeline-track-panel-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1780
+ .timeline-track-swatch { width: 10px; height: 10px; margin-top: 3px; flex: 0 0 auto; border-radius: 50%; background: var(--timeline-track-color, var(--accent)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--timeline-track-color, var(--accent)) 16%, transparent); }
1781
+ .timeline-track-menu { border: 1px solid var(--line); border-radius: 3px; padding: 5px 7px; background: transparent; color: var(--muted); font-size: 9px; line-height: 1.2; }
1782
+ .timeline-track-menu:hover, .timeline-track-menu:focus-visible { color: var(--ink); background: var(--paper-deep); }
1783
+ .timeline-track-menu:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
1784
+ .timeline-track-menu[aria-pressed="true"] { color: var(--ink); border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); background: color-mix(in srgb, var(--accent) 14%, transparent); }
1785
+ .timeline-track-actions { display: flex; flex-wrap: wrap; gap: 5px; flex: 0 0 auto; align-items: center; }
1786
+ .timeline-add-event { padding: 5px 7px; border: 1px solid var(--line); border-radius: 3px; background: transparent; color: var(--muted); font-size: 9px; line-height: 1.2; }
1787
+ .timeline-item[data-track-color-index="0"],
1788
+ .timeline-track-tab[data-track-color-index="0"],
1789
+ .timeline-track-panel[data-track-color-index="0"] { --timeline-track-color: var(--timeline-track-palette-0); }
1790
+ .timeline-item[data-track-color-index="1"],
1791
+ .timeline-track-tab[data-track-color-index="1"],
1792
+ .timeline-track-panel[data-track-color-index="1"] { --timeline-track-color: var(--timeline-track-palette-1); }
1793
+ .timeline-item[data-track-color-index="2"],
1794
+ .timeline-track-tab[data-track-color-index="2"],
1795
+ .timeline-track-panel[data-track-color-index="2"] { --timeline-track-color: var(--timeline-track-palette-2); }
1796
+ .timeline-item[data-track-color-index="3"],
1797
+ .timeline-track-tab[data-track-color-index="3"],
1798
+ .timeline-track-panel[data-track-color-index="3"] { --timeline-track-color: var(--timeline-track-palette-3); }
1799
+ .timeline-item[data-track-color-index="4"],
1800
+ .timeline-track-tab[data-track-color-index="4"],
1801
+ .timeline-track-panel[data-track-color-index="4"] { --timeline-track-color: var(--timeline-track-palette-4); }
1802
+ .timeline-item[data-track-color-index="5"],
1803
+ .timeline-track-tab[data-track-color-index="5"],
1804
+ .timeline-track-panel[data-track-color-index="5"] { --timeline-track-color: var(--timeline-track-palette-5); }
1805
+ .timeline-item[data-track-color-index="6"],
1806
+ .timeline-track-tab[data-track-color-index="6"],
1807
+ .timeline-track-panel[data-track-color-index="6"] { --timeline-track-color: var(--timeline-track-palette-6); }
1808
+ .timeline-item[data-track-color-index="7"],
1809
+ .timeline-track-tab[data-track-color-index="7"],
1810
+ .timeline-track-panel[data-track-color-index="7"] { --timeline-track-color: var(--timeline-track-palette-7); }
1759
1811
  .relationship-map-card { margin-bottom: 28px; overflow: hidden; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-soft); }
1760
1812
  .relationship-map-toolbar { display: flex; align-items: center; gap: 18px; padding: 13px 16px; border-bottom: 1px solid var(--line); }
1761
1813
  .relationship-map-toolbar > div:first-child { display: grid; gap: 3px; }
@@ -2536,9 +2588,11 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2536
2588
  .character-aliases b { margin-right: 2px; color: var(--muted); font-size: 9px; font-weight: 500; }
2537
2589
  .character-aliases .pill { margin: 0; border: 1px solid var(--line); background: var(--surface); }
2538
2590
  .character-code { display: flex; align-items: center; gap: 7px; margin-top: 10px; color: var(--muted); font-size: 10px; }
2591
+ .character-code b { font-weight: 500; }
2592
+ .character-code .pill { display: inline-flex; align-items: center; justify-content: center; margin: 0; min-height: 18px; padding: 2px 8px; line-height: 1; font-family: var(--font-latin), var(--font-cjk), monospace; }
2539
2593
  .character-species { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: center; gap: 6px; margin-top: 8px; color: var(--muted); font-size: 10px; }
2540
2594
  .character-species b { font-weight: 500; }
2541
- .character-species .pill { justify-self: start; min-width: 0; max-width: 100%; margin: 0; overflow-wrap: anywhere; }
2595
+ .character-species .pill { display: inline-flex; align-items: center; justify-content: center; justify-self: start; min-width: 0; max-width: 100%; margin: 0; min-height: 18px; padding: 2px 8px; line-height: 1.25; overflow-wrap: anywhere; }
2542
2596
  .character-detail-list { display: grid; gap: 5px; margin: 12px 0; }
2543
2597
  .character-detail-list div { display: grid; grid-template-columns: minmax(70px, .42fr) minmax(0, 1fr); gap: 8px; font-size: 10px; }
2544
2598
  .character-detail-list dt { color: var(--muted); }
@@ -3243,15 +3297,15 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3243
3297
  .dialog-submit-progress > span { width: 100%; transform: none; }
3244
3298
  }
3245
3299
 
3246
- /* 显示设置中的界面字号覆盖常用控件和辅助文字;正文与 Agent 对话分别独立调节。 */
3300
+ /* 显示设置中的界面字号按原有视觉层级缩放;正文与 Agent 对话分别独立调节。 */
3247
3301
  body { font-size: var(--ui-font-size); }
3248
- :is(.module-nav button, .ghost-button, .primary-button, .module-header p, .eyebrow,
3249
- .panel-heading, .field-label, .dialog-header-meta, .dialog-fields label,
3250
- .dialog-fields .form-field-note, .appearance-grid label, .config-section-header p,
3251
- .config-section .config-save-button, .font-preview span, .settings-hub-card small,
3252
- .settings-hub-card strong, .shelf-header p, .topbar, .book-card small) {
3253
- font-size: calc(1em * var(--ui-font-scale));
3254
- }
3302
+ :is(.module-nav button, .ghost-button.ghost-button, .primary-button.primary-button) { font-size: calc(12px * var(--ui-font-scale)); }
3303
+ .module-header p { font-size: calc(13px * var(--ui-font-scale)); }
3304
+ .eyebrow, .field-label, .dialog-header-meta, .settings-hub-card small, .book-card small, .book-info > span { font-size: calc(10px * var(--ui-font-scale)); }
3305
+ .panel-heading, .dialog-fields label, .dialog-fields .form-field-note, .appearance-grid label,
3306
+ .config-section-header p, .config-section .config-save-button { font-size: calc(11px * var(--ui-font-scale)); }
3307
+ .settings-hub-card strong, .book-info strong { font-size: calc(16px * var(--ui-font-scale)); }
3308
+ .shelf-header p, .topbar { font-size: var(--ui-font-size); }
3255
3309
  .appearance-grid select, .dialog-fields input, .dialog-fields textarea, .dialog-fields select { font-size: calc(13px * var(--ui-font-scale)); }
3256
3310
 
3257
3311
  /* Agent 面板原有的细小字号统一以可读的对话字号为基准缩放。 */
@@ -0,0 +1,50 @@
1
+ export type TimelineSortDirection = "asc" | "desc";
2
+
3
+ export type TimelineViewEvent = {
4
+ id?: string | null;
5
+ trackId?: string | null;
6
+ timeSort?: number | null;
7
+ updatedAt?: string | null;
8
+ };
9
+
10
+ export type TimelineViewTrack = {
11
+ id?: string | null;
12
+ name?: string | null;
13
+ sortOrder?: number | null;
14
+ };
15
+
16
+ export declare const TIMELINE_TRACK_PALETTE_SIZE: number;
17
+ export declare const TIMELINE_UNGROUPED_COLOR_INDEX: number;
18
+
19
+ export declare function normalizeTimelineSortDirection(direction?: unknown): TimelineSortDirection;
20
+
21
+ export declare function sortTimelineEvents<T extends TimelineViewEvent>(
22
+ events?: T[],
23
+ options?: { direction?: TimelineSortDirection }
24
+ ): T[];
25
+
26
+ export declare function filterTimelineEvents<T extends TimelineViewEvent>(
27
+ events?: T[],
28
+ filters?: { trackIds?: Array<string | null | undefined> }
29
+ ): T[];
30
+
31
+ export declare function timelineTrackColorIndex(
32
+ trackId: string | null | undefined,
33
+ tracks?: TimelineViewTrack[]
34
+ ): number;
35
+
36
+ export declare function prepareTimelineEvents<T extends TimelineViewEvent>(
37
+ events?: T[],
38
+ filters?: { trackIds?: Array<string | null | undefined> },
39
+ options?: { direction?: TimelineSortDirection }
40
+ ): T[];
41
+
42
+ export declare function resolveTimelineActiveTrackId(
43
+ activeTrackId: string | null | undefined,
44
+ tracks?: TimelineViewTrack[]
45
+ ): string;
46
+
47
+ export declare function timelineTrackDisplayName(
48
+ trackId: string | null | undefined,
49
+ tracks?: TimelineViewTrack[]
50
+ ): string;