Add entry attachment links with in-app navigation
This commit is contained in:
parent
3789492de3
commit
d559d9c18d
@ -9,12 +9,32 @@
|
|||||||
export let openDocumentName = "Daily Notes";
|
export let openDocumentName = "Daily Notes";
|
||||||
export let openDocumentContent = "";
|
export let openDocumentContent = "";
|
||||||
export let onDocumentContentChange: (content: string) => void = () => {};
|
export let onDocumentContentChange: (content: string) => void = () => {};
|
||||||
export let onOpenDocument: (doc: { id: string; label: string; initialContent: string }) => void = () => {};
|
export let onOpenDocument: (doc: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
initialContent: string;
|
||||||
|
linkedFrom?: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
initialContent: string;
|
||||||
|
section: "entries" | "fragments" | "todos" | "lists" | "calendar";
|
||||||
|
};
|
||||||
|
}) => void = () => {};
|
||||||
export let onDeleteDocument: (id: string) => void = () => {};
|
export let onDeleteDocument: (id: string) => void = () => {};
|
||||||
|
export let showLinkedBackButton = false;
|
||||||
|
export let onLinkedBack: () => void = () => {};
|
||||||
export let previewOnly = true;
|
export let previewOnly = true;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="editor-panel" aria-label="Editor area">
|
<main class="editor-panel" aria-label="Editor area">
|
||||||
|
{#if showLinkedBackButton}
|
||||||
|
<div class="editor-nav">
|
||||||
|
<button type="button" class="back-btn" on:click={onLinkedBack} aria-label="Back to source entry">
|
||||||
|
<span class="material-symbols-outlined" aria-hidden="true">arrow_back</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if !openDocumentId}
|
{#if !openDocumentId}
|
||||||
<div class="editor-empty">
|
<div class="editor-empty">
|
||||||
<span class="material-symbols-outlined empty-icon">edit_note</span>
|
<span class="material-symbols-outlined empty-icon">edit_note</span>
|
||||||
@ -50,6 +70,7 @@
|
|||||||
{openDocumentName}
|
{openDocumentName}
|
||||||
{openDocumentContent}
|
{openDocumentContent}
|
||||||
{onDocumentContentChange}
|
{onDocumentContentChange}
|
||||||
|
{onOpenDocument}
|
||||||
{previewOnly}
|
{previewOnly}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
@ -66,6 +87,30 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
background: color-mix(in srgb, var(--surface-1) 90%, var(--bg-editor) 10%);
|
||||||
|
color: var(--text-primary);
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
.editor-empty {
|
.editor-empty {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -1,15 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { listEntryTemplates, loadEntryTemplate, type EntryTemplateItemDto } from "$lib/backend/templates";
|
import { listEntryTemplates, loadEntryTemplate, type EntryTemplateItemDto } from "$lib/backend/templates";
|
||||||
import MarkdownToolbar from "$lib/components/editor/MarkdownToolbar.svelte";
|
import MarkdownToolbar from "$lib/components/editor/MarkdownToolbar.svelte";
|
||||||
|
import { entriesStore } from "$lib/stores/entries";
|
||||||
|
import { fragmentsStore } from "$lib/stores/fragments";
|
||||||
|
import { listsStore } from "$lib/stores/lists";
|
||||||
|
import { serializeTodoList, todoListsStore, todosStore } from "$lib/stores/todos";
|
||||||
import { renderMarkdown, extractEditorTitle } from "$lib/utils/markdown";
|
import { renderMarkdown, extractEditorTitle } from "$lib/utils/markdown";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
export let openDocumentId = "";
|
export let openDocumentId = "";
|
||||||
export let openDocumentName = "";
|
export let openDocumentName = "";
|
||||||
export let openDocumentContent = "";
|
export let openDocumentContent = "";
|
||||||
export let onDocumentContentChange: (content: string) => void = () => {};
|
export let onDocumentContentChange: (content: string) => void = () => {};
|
||||||
|
export let onOpenDocument: (doc: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
initialContent: string;
|
||||||
|
linkedFrom?: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
initialContent: string;
|
||||||
|
section: "entries" | "fragments" | "todos" | "lists" | "calendar";
|
||||||
|
};
|
||||||
|
}) => void = () => {};
|
||||||
|
|
||||||
type ListMode = "ul" | "ol" | null;
|
type ListMode = "ul" | "ol" | null;
|
||||||
|
type AttachmentOption = { id: string; label: string };
|
||||||
|
|
||||||
let markdownText = openDocumentContent;
|
let markdownText = openDocumentContent;
|
||||||
let lastOpenDocumentId = openDocumentId;
|
let lastOpenDocumentId = openDocumentId;
|
||||||
@ -20,6 +37,9 @@
|
|||||||
let templateError = "";
|
let templateError = "";
|
||||||
let templateRefreshRequested = false;
|
let templateRefreshRequested = false;
|
||||||
let listMode: ListMode = null;
|
let listMode: ListMode = null;
|
||||||
|
let fragmentAttachmentOptions: AttachmentOption[] = [];
|
||||||
|
let listAttachmentOptions: AttachmentOption[] = [];
|
||||||
|
let todoAttachmentOptions: AttachmentOption[] = [];
|
||||||
|
|
||||||
function updateDraft(value: string) {
|
function updateDraft(value: string) {
|
||||||
markdownText = value;
|
markdownText = value;
|
||||||
@ -150,6 +170,113 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeMarkdownLinkText(value: string): string {
|
||||||
|
return value.replace(/]/g, "\\]");
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendToAttachmentsSection(lineToAppend: string, attachmentId: string) {
|
||||||
|
const current = markdownText;
|
||||||
|
const normalized = current.replace(/\r\n/g, "\n");
|
||||||
|
if (normalized.includes(`(journal:${attachmentId})`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachmentsHeaderPattern = /^##\s+Attachments\s*$/im;
|
||||||
|
const headerMatch = attachmentsHeaderPattern.exec(normalized);
|
||||||
|
if (!headerMatch || headerMatch.index < 0) {
|
||||||
|
const spacer = normalized.trim().length > 0 ? "\n\n" : "";
|
||||||
|
updateDraft(`${normalized}${spacer}## Attachments\n${lineToAppend}\n`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerStart = headerMatch.index;
|
||||||
|
const headerEnd = headerStart + headerMatch[0].length;
|
||||||
|
const bodyStart = normalized.indexOf("\n", headerEnd);
|
||||||
|
const sectionBodyStart = bodyStart === -1 ? normalized.length : bodyStart + 1;
|
||||||
|
const nextHeaderMatch = /^##\s+/m.exec(normalized.slice(sectionBodyStart));
|
||||||
|
const sectionEnd = nextHeaderMatch ? sectionBodyStart + nextHeaderMatch.index : normalized.length;
|
||||||
|
|
||||||
|
const sectionBody = normalized.slice(sectionBodyStart, sectionEnd);
|
||||||
|
const bodyPrefix = sectionBody.length > 0 && !sectionBody.endsWith("\n") ? "\n" : "";
|
||||||
|
const insertion = `${bodyPrefix}${lineToAppend}\n`;
|
||||||
|
const next = `${normalized.slice(0, sectionEnd)}${insertion}${normalized.slice(sectionEnd)}`;
|
||||||
|
updateDraft(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachReference(kind: "Fragment" | "List" | "To-Do", option: AttachmentOption) {
|
||||||
|
const label = escapeMarkdownLinkText(option.label.trim() || `${kind} Item`);
|
||||||
|
const line = `- ${kind}: [${label}](journal:${option.id})`;
|
||||||
|
appendToAttachmentsSection(line, option.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveJournalLinkTarget(targetId: string): { id: string; label: string; initialContent: string } | null {
|
||||||
|
if (!targetId) return null;
|
||||||
|
|
||||||
|
if (targetId.startsWith("fragments/")) {
|
||||||
|
const fragment = get(fragmentsStore).find((item) => item.id === targetId);
|
||||||
|
if (!fragment) return null;
|
||||||
|
return { id: fragment.id, label: fragment.label, initialContent: fragment.initialContent };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetId.startsWith("lists/")) {
|
||||||
|
const list = get(listsStore).find((item) => item.id === targetId);
|
||||||
|
if (!list) return null;
|
||||||
|
return { id: list.id, label: list.label, initialContent: list.initialContent };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetId.startsWith("todos/")) {
|
||||||
|
const todoList = get(todoListsStore).find((item) => item.id === targetId);
|
||||||
|
if (!todoList) return null;
|
||||||
|
const todoItems = get(todosStore)[targetId] ?? [];
|
||||||
|
return {
|
||||||
|
id: todoList.id,
|
||||||
|
label: todoList.label,
|
||||||
|
initialContent: serializeTodoList(todoList.label, todoItems)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetId.startsWith("entries/")) {
|
||||||
|
const entry = get(entriesStore).find((item) => item.id === targetId);
|
||||||
|
if (!entry) return null;
|
||||||
|
return { id: entry.id, label: entry.label, initialContent: entry.initialContent };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePreviewClick(event: MouseEvent) {
|
||||||
|
const target = event.target as HTMLElement | null;
|
||||||
|
const anchor = target?.closest("a");
|
||||||
|
const href = anchor?.getAttribute("href");
|
||||||
|
if (!href || !href.startsWith("journal:")) return;
|
||||||
|
|
||||||
|
const targetId = href.slice("journal:".length).trim();
|
||||||
|
const doc = resolveJournalLinkTarget(targetId);
|
||||||
|
if (!doc) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
onOpenDocument({
|
||||||
|
...doc,
|
||||||
|
linkedFrom: {
|
||||||
|
id: openDocumentId,
|
||||||
|
label: openDocumentName,
|
||||||
|
initialContent: openDocumentContent,
|
||||||
|
section: "entries"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function interceptJournalLinks(node: HTMLElement) {
|
||||||
|
const onClick = (event: MouseEvent) => handlePreviewClick(event);
|
||||||
|
node.addEventListener("click", onClick);
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
node.removeEventListener("click", onClick);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function applyTemplateByPath(filePath: string) {
|
async function applyTemplateByPath(filePath: string) {
|
||||||
if (!filePath) return;
|
if (!filePath) return;
|
||||||
try {
|
try {
|
||||||
@ -245,6 +372,15 @@
|
|||||||
listMode = null;
|
listMode = null;
|
||||||
}
|
}
|
||||||
$: isEntryDocument = openDocumentId.startsWith("entries/file/") || openDocumentId.startsWith("entries/draft-");
|
$: isEntryDocument = openDocumentId.startsWith("entries/file/") || openDocumentId.startsWith("entries/draft-");
|
||||||
|
$: fragmentAttachmentOptions = $fragmentsStore
|
||||||
|
.filter((item) => item.id && item.label)
|
||||||
|
.map((item) => ({ id: item.id, label: item.label }));
|
||||||
|
$: listAttachmentOptions = $listsStore
|
||||||
|
.filter((item) => item.id && item.label)
|
||||||
|
.map((item) => ({ id: item.id, label: item.label }));
|
||||||
|
$: todoAttachmentOptions = $todoListsStore
|
||||||
|
.filter((item) => item.id && item.label)
|
||||||
|
.map((item) => ({ id: item.id, label: item.label }));
|
||||||
$: editorTitle = extractEditorTitle(markdownText, openDocumentName);
|
$: editorTitle = extractEditorTitle(markdownText, openDocumentName);
|
||||||
$: renderedHtml = renderMarkdown(markdownText);
|
$: renderedHtml = renderMarkdown(markdownText);
|
||||||
</script>
|
</script>
|
||||||
@ -260,8 +396,14 @@
|
|||||||
{templatesBusy}
|
{templatesBusy}
|
||||||
{templateOptions}
|
{templateOptions}
|
||||||
{listMode}
|
{listMode}
|
||||||
|
fragmentOptions={fragmentAttachmentOptions}
|
||||||
|
listOptions={listAttachmentOptions}
|
||||||
|
todoOptions={todoAttachmentOptions}
|
||||||
onApplyHeading={applyHeading}
|
onApplyHeading={applyHeading}
|
||||||
onApplyTemplate={(filePath) => void applyTemplateByPath(filePath)}
|
onApplyTemplate={(filePath) => void applyTemplateByPath(filePath)}
|
||||||
|
onAttachFragment={(option) => attachReference("Fragment", option)}
|
||||||
|
onAttachList={(option) => attachReference("List", option)}
|
||||||
|
onAttachTodo={(option) => attachReference("To-Do", option)}
|
||||||
onBold={() => applyWrap("**")}
|
onBold={() => applyWrap("**")}
|
||||||
onItalic={() => applyWrap("*")}
|
onItalic={() => applyWrap("*")}
|
||||||
onLink={insertLink}
|
onLink={insertLink}
|
||||||
@ -276,7 +418,7 @@
|
|||||||
|
|
||||||
<div class="editor-workspace">
|
<div class="editor-workspace">
|
||||||
{#if previewOnly}
|
{#if previewOnly}
|
||||||
<article class="markdown-preview" aria-label="Markdown preview">
|
<article class="markdown-preview" aria-label="Markdown preview" use:interceptJournalLinks>
|
||||||
{@html renderedHtml}
|
{@html renderedHtml}
|
||||||
</article>
|
</article>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@ -1,13 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { EntryTemplateItemDto } from "$lib/backend/templates";
|
import type { EntryTemplateItemDto } from "$lib/backend/templates";
|
||||||
|
type AttachmentOption = { id: string; label: string };
|
||||||
|
|
||||||
export let isEntryDocument = false;
|
export let isEntryDocument = false;
|
||||||
export let templatesBusy = false;
|
export let templatesBusy = false;
|
||||||
export let templateOptions: EntryTemplateItemDto[] = [];
|
export let templateOptions: EntryTemplateItemDto[] = [];
|
||||||
export let listMode: "ul" | "ol" | null = null;
|
export let listMode: "ul" | "ol" | null = null;
|
||||||
|
export let fragmentOptions: AttachmentOption[] = [];
|
||||||
|
export let listOptions: AttachmentOption[] = [];
|
||||||
|
export let todoOptions: AttachmentOption[] = [];
|
||||||
|
|
||||||
export let onApplyHeading: (level: number) => void = () => {};
|
export let onApplyHeading: (level: number) => void = () => {};
|
||||||
export let onApplyTemplate: (filePath: string) => void = () => {};
|
export let onApplyTemplate: (filePath: string) => void = () => {};
|
||||||
|
export let onAttachFragment: (option: AttachmentOption) => void = () => {};
|
||||||
|
export let onAttachList: (option: AttachmentOption) => void = () => {};
|
||||||
|
export let onAttachTodo: (option: AttachmentOption) => void = () => {};
|
||||||
export let onBold: () => void = () => {};
|
export let onBold: () => void = () => {};
|
||||||
export let onItalic: () => void = () => {};
|
export let onItalic: () => void = () => {};
|
||||||
export let onLink: () => void = () => {};
|
export let onLink: () => void = () => {};
|
||||||
@ -55,6 +62,63 @@
|
|||||||
<option value={template.filePath}>{template.fileName.replace(/\.template\.md$/i, "")}</option>
|
<option value={template.filePath}>{template.fileName.replace(/\.template\.md$/i, "")}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select
|
||||||
|
class="toolbar-select"
|
||||||
|
aria-label="Attach fragment"
|
||||||
|
disabled={fragmentOptions.length === 0}
|
||||||
|
on:change={(event) => {
|
||||||
|
const target = event.currentTarget as HTMLSelectElement;
|
||||||
|
const selected = fragmentOptions.find((option) => option.id === target.value);
|
||||||
|
if (selected) {
|
||||||
|
onAttachFragment(selected);
|
||||||
|
}
|
||||||
|
target.value = "";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="">Attach Fragment</option>
|
||||||
|
{#each fragmentOptions as option}
|
||||||
|
<option value={option.id}>{option.label}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select
|
||||||
|
class="toolbar-select"
|
||||||
|
aria-label="Attach list"
|
||||||
|
disabled={listOptions.length === 0}
|
||||||
|
on:change={(event) => {
|
||||||
|
const target = event.currentTarget as HTMLSelectElement;
|
||||||
|
const selected = listOptions.find((option) => option.id === target.value);
|
||||||
|
if (selected) {
|
||||||
|
onAttachList(selected);
|
||||||
|
}
|
||||||
|
target.value = "";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="">Attach List</option>
|
||||||
|
{#each listOptions as option}
|
||||||
|
<option value={option.id}>{option.label}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select
|
||||||
|
class="toolbar-select"
|
||||||
|
aria-label="Attach to-do list"
|
||||||
|
disabled={todoOptions.length === 0}
|
||||||
|
on:change={(event) => {
|
||||||
|
const target = event.currentTarget as HTMLSelectElement;
|
||||||
|
const selected = todoOptions.find((option) => option.id === target.value);
|
||||||
|
if (selected) {
|
||||||
|
onAttachTodo(selected);
|
||||||
|
}
|
||||||
|
target.value = "";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="">Attach To-Do</option>
|
||||||
|
{#each todoOptions as option}
|
||||||
|
<option value={option.id}>{option.label}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-divider" aria-hidden="true"></div>
|
<div class="toolbar-divider" aria-hidden="true"></div>
|
||||||
|
|||||||
@ -12,6 +12,10 @@ export function parseInline(input: string): string {
|
|||||||
value = value.replace(/`([^`]+)`/g, "<code>$1</code>");
|
value = value.replace(/`([^`]+)`/g, "<code>$1</code>");
|
||||||
value = value.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
value = value.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
||||||
value = value.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
value = value.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
||||||
|
value = value.replace(
|
||||||
|
/\[([^\]]+)\]\((journal:[^\s)]+)\)/g,
|
||||||
|
'<a href="$2">$1</a>'
|
||||||
|
);
|
||||||
value = value.replace(
|
value = value.replace(
|
||||||
/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
|
/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
|
||||||
'<a href="$2" target="_blank" rel="noreferrer">$1</a>'
|
'<a href="$2" target="_blank" rel="noreferrer">$1</a>'
|
||||||
|
|||||||
@ -19,6 +19,12 @@
|
|||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
initialContent: string;
|
initialContent: string;
|
||||||
|
linkedFrom?: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
initialContent: string;
|
||||||
|
section: StartupView;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialEntry = getDefaultEntry(get(entriesStore));
|
const initialEntry = getDefaultEntry(get(entriesStore));
|
||||||
@ -48,6 +54,7 @@
|
|||||||
let fragmentBootstrapInFlight = false;
|
let fragmentBootstrapInFlight = false;
|
||||||
let pendingDeleteItemId = "";
|
let pendingDeleteItemId = "";
|
||||||
let templateRefreshToken = 0;
|
let templateRefreshToken = 0;
|
||||||
|
let linkedBackTarget: OpenDocument["linkedFrom"] | null = null;
|
||||||
|
|
||||||
function resolveStartupSection(value: string): StartupView {
|
function resolveStartupSection(value: string): StartupView {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
@ -77,6 +84,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sectionFromDocumentId(id: string): StartupView | null {
|
||||||
|
if (id.startsWith("entries/")) return "entries";
|
||||||
|
if (id.startsWith("fragments/")) return "fragments";
|
||||||
|
if (id.startsWith("todos/")) return "todos";
|
||||||
|
if (id.startsWith("lists/")) return "lists";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function applyStartupSection(section: StartupView) {
|
function applyStartupSection(section: StartupView) {
|
||||||
selectedSection = section;
|
selectedSection = section;
|
||||||
editMode = false;
|
editMode = false;
|
||||||
@ -352,6 +367,13 @@
|
|||||||
const prevActiveId = activeDocumentId;
|
const prevActiveId = activeDocumentId;
|
||||||
await saveCurrentDocument();
|
await saveCurrentDocument();
|
||||||
editMode = false;
|
editMode = false;
|
||||||
|
linkedBackTarget = doc.linkedFrom ?? null;
|
||||||
|
const targetSection = sectionFromDocumentId(doc.id);
|
||||||
|
const effectiveSection = targetSection ?? selectedSection;
|
||||||
|
if (targetSection && targetSection !== selectedSection) {
|
||||||
|
selectedSection = targetSection;
|
||||||
|
panelOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
// If saveCurrentDocument promoted a draft to a file-backed entry and the
|
// If saveCurrentDocument promoted a draft to a file-backed entry and the
|
||||||
// caller passed the now-stale draft reference, the editor is already
|
// caller passed the now-stale draft reference, the editor is already
|
||||||
@ -361,7 +383,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let resolvedDoc = doc;
|
let resolvedDoc = doc;
|
||||||
if (selectedSection === "entries" && doc.id.startsWith("entries/file/") && !doc.initialContent) {
|
if (effectiveSection === "entries" && doc.id.startsWith("entries/file/") && !doc.initialContent) {
|
||||||
try {
|
try {
|
||||||
const loaded = await loadEntryByStoreId(doc.id);
|
const loaded = await loadEntryByStoreId(doc.id);
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
@ -370,7 +392,7 @@
|
|||||||
} catch {
|
} catch {
|
||||||
// entry content will use initialContent fallback
|
// entry content will use initialContent fallback
|
||||||
}
|
}
|
||||||
} else if (selectedSection === "entries" && doc.id.startsWith("entries/template-file/") && !doc.initialContent) {
|
} else if (effectiveSection === "entries" && doc.id.startsWith("entries/template-file/") && !doc.initialContent) {
|
||||||
try {
|
try {
|
||||||
const filePath = toTemplatePath(doc.id);
|
const filePath = toTemplatePath(doc.id);
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
@ -393,6 +415,18 @@
|
|||||||
activeDocumentLabel = resolvedDoc.label;
|
activeDocumentLabel = resolvedDoc.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleLinkedBack() {
|
||||||
|
if (!linkedBackTarget) return;
|
||||||
|
|
||||||
|
const target = linkedBackTarget;
|
||||||
|
linkedBackTarget = null;
|
||||||
|
await handleOpenDocument({
|
||||||
|
id: target.id,
|
||||||
|
label: target.label,
|
||||||
|
initialContent: target.initialContent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function handleDocumentContentChange(content: string) {
|
function handleDocumentContentChange(content: string) {
|
||||||
openDocuments = { ...openDocuments, [activeDocumentId]: content };
|
openDocuments = { ...openDocuments, [activeDocumentId]: content };
|
||||||
}
|
}
|
||||||
@ -488,6 +522,8 @@
|
|||||||
onDocumentContentChange={handleDocumentContentChange}
|
onDocumentContentChange={handleDocumentContentChange}
|
||||||
onOpenDocument={handleOpenDocument}
|
onOpenDocument={handleOpenDocument}
|
||||||
onDeleteDocument={handleDeleteDocument}
|
onDeleteDocument={handleDeleteDocument}
|
||||||
|
showLinkedBackButton={linkedBackTarget !== null}
|
||||||
|
onLinkedBack={handleLinkedBack}
|
||||||
previewOnly={!editMode}
|
previewOnly={!editMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user