diff --git a/src/langs/json/en.json b/src/langs/json/en.json index 1611e768e..c07aa8438 100644 --- a/src/langs/json/en.json +++ b/src/langs/json/en.json @@ -822,7 +822,6 @@ "modifyDesc": "Rearrange, rotate, delete, insert, and split pages.", "info": "Edit Document Info", "infoDesc": "Modify document information like description and related URL.", - "data": "Edit Tags and Data", "dataDesc": "Add tags and key/value pairs to categorize your document.", "sections": "Edit Sections", "sectionsDesc": "Add sections to organize your document with a table of contents.", @@ -838,11 +837,28 @@ "title": "Add-Ons", "pinned": "Pinned add-ons will appear here" }, + "data": { + "title": "Data & Tags", + "empty": "Use tags or key/value data to organize documents", + "tags": "Tags", + "data": "Data" + }, "projects": { "title": "Projects", "pinned": "Pinned projects will appear here" + }, + "toc": { + "empty": "Sections organize your document with a table of contents", + "notes": "Notes", + "pageAbbrev": "p.", + "sections": "Sections", + "title": "Notes & Sections" } }, + "notes": { + "empty": "Add notes to highlight and comment on your document", + "cta": "Annotate this document" + }, "viewer": { "notFound": "Document not found", "notFoundDesc": "The document you requested either does not exist or you lack permission to access it", diff --git a/src/lib/api/documents.test.ts b/src/lib/api/documents.test.ts index 41cc944a4..ffa3a0ff8 100644 --- a/src/lib/api/documents.test.ts +++ b/src/lib/api/documents.test.ts @@ -291,6 +291,9 @@ describe("document helper methods", () => { // invalid hash returns page 1 expect(documents.pageFromHash("#nopage")).toStrictEqual(1); + + // match a note hash + expect(documents.pageFromHash("#document/p2/a2000002")).toStrictEqual(2); }); test("pageUrl", ({ document }) => { diff --git a/src/lib/api/documents.ts b/src/lib/api/documents.ts index bda07bf9b..268bf2681 100644 --- a/src/lib/api/documents.ts +++ b/src/lib/api/documents.ts @@ -70,7 +70,7 @@ export async function get( "projects", "revisions", "sections", - "notes", + "notes.user", ]; endpoint.searchParams.set("expand", expand.join(",")); @@ -293,12 +293,13 @@ export function pageHashUrl(page: number): string { } /** - * The opposite of pageHashUrl, extracting a page number from a URL hash + * The opposite of pageHashUrl, extracting a page number from a URL hash. + * Note that this will also match note hash URLs, which use the same prefix. * * @param hash URL hash */ export function pageFromHash(hash: string): number { - const re = /^#document\/p(\d+)$/; + const re = /^#document\/p(\d+)/; // match pages and notes const match = re.exec(hash); if (!match) return 1; diff --git a/src/lib/api/fixtures/documents/document-expanded.json b/src/lib/api/fixtures/documents/document-expanded.json index 9b51b69a0..758eb2dcd 100644 --- a/src/lib/api/fixtures/documents/document-expanded.json +++ b/src/lib/api/fixtures/documents/document-expanded.json @@ -74,7 +74,17 @@ "notes": [ { "id": 551, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 0, "access": "public", @@ -90,7 +100,17 @@ }, { "id": 549, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 0, "access": "public", @@ -106,7 +126,17 @@ }, { "id": 550, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 0, "access": "public", @@ -122,7 +152,17 @@ }, { "id": 552, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 0, "access": "public", @@ -138,7 +178,17 @@ }, { "id": 554, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 1, "access": "public", @@ -154,7 +204,17 @@ }, { "id": 553, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 1, "access": "public", @@ -170,7 +230,17 @@ }, { "id": 556, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 2, "access": "public", @@ -186,7 +256,17 @@ }, { "id": 555, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 2, "access": "public", @@ -202,7 +282,17 @@ }, { "id": 557, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 2, "access": "public", @@ -218,7 +308,17 @@ }, { "id": 558, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 2, "access": "public", @@ -234,7 +334,17 @@ }, { "id": 560, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 4, "access": "public", @@ -250,7 +360,17 @@ }, { "id": 559, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 4, "access": "public", @@ -266,7 +386,17 @@ }, { "id": 562, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 5, "access": "public", @@ -282,7 +412,17 @@ }, { "id": 561, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 5, "access": "public", @@ -298,7 +438,17 @@ }, { "id": 563, - "user": 126, + "user": { + "id": 126, + "avatar_url": "", + "name": "Chris Amico", + "organization": 14187, + "organizations": [14187], + "admin_organizations": [14187], + "username": "ChrisAmico_lSozDZNW", + "uuid": "caea06a2-ee1c-43e6-865a-a09078522bf1", + "verified_journalist": true + }, "organization": 60, "page_number": 6, "access": "public", diff --git a/src/lib/api/fixtures/documents/examples/agreement-between-conservatives-and-liberal-democrats-to-form-a-coalition-government.pdf b/src/lib/api/fixtures/documents/examples/agreement-between-conservatives-and-liberal-democrats-to-form-a-coalition-government.pdf new file mode 100644 index 000000000..60a92f531 Binary files /dev/null and b/src/lib/api/fixtures/documents/examples/agreement-between-conservatives-and-liberal-democrats-to-form-a-coalition-government.pdf differ diff --git a/src/lib/api/notes.test.ts b/src/lib/api/notes.test.ts index 9648b9a5f..705c9c040 100644 --- a/src/lib/api/notes.test.ts +++ b/src/lib/api/notes.test.ts @@ -33,6 +33,16 @@ describe("note helper methods", () => { ); }); + test("noteHashUrl", () => { + expect(notes.noteHashUrl(n)).toStrictEqual("#document/p3/a557"); + }); + + test("noteFromHash", () => { + const hash = notes.noteHashUrl(n); + + expect(notes.noteFromHash(hash)).toStrictEqual(n.id); + }); + test("width", () => { expect(notes.width(n)).toStrictEqual(n.x2 - n.x1); }); diff --git a/src/lib/api/notes.ts b/src/lib/api/notes.ts index 91d6eeac2..6a2f87ff0 100644 --- a/src/lib/api/notes.ts +++ b/src/lib/api/notes.ts @@ -71,6 +71,29 @@ export function noteUrl(document: Document, note: Note): URL { ); } +/** + * Generate the hash URL for a note, without the document URL + * @param note + * @returns hash + */ +export function noteHashUrl(note: Note): string { + return `#document/p${note.page_number + 1}/a${note.id}`; +} + +/** + * Opposite of noteHashUrl, returning a note ID. + * To get the page number, use pageFromHash + * @param hash + */ +export function noteFromHash(hash: string): number { + const re = /^#document\/p(\d+)\/a(\d+)$/; + const match = re.exec(hash); + + if (!match) return null; + + return +match[2] || null; +} + /** Width of a note, relative to the document */ export function width(note: Note): number { return note.x2 - note.x1; diff --git a/src/lib/components/common/Action.svelte b/src/lib/components/common/Action.svelte index 6dd7bb41d..6b0783790 100644 --- a/src/lib/components/common/Action.svelte +++ b/src/lib/components/common/Action.svelte @@ -26,8 +26,8 @@ border-radius: 0.5rem; background: transparent; - color: var(--primary, --blue-3, #4294f0); - fill: var(--primary, --blue-3, #4294f0); + color: var(--color, var(--primary, --blue-3, #4294f0)); + fill: var(--fill, var(--primary, --blue-3, #4294f0)); font-size: var(--font-xs, 0.75rem); font-weight: var(--font-bold, 700); diff --git a/src/lib/components/common/KV.svelte b/src/lib/components/common/KV.svelte index cb6678a92..08e204888 100644 --- a/src/lib/components/common/KV.svelte +++ b/src/lib/components/common/KV.svelte @@ -1,11 +1,12 @@ -
+
{#if !tag} {key} {/if} @@ -31,6 +32,10 @@ background: var(--blue-1, #eef3f9); } + .kv.inline { + display: inline-flex; + } + .key, .value { color: var(--gray-4, #5c717c); diff --git a/src/lib/components/common/stories/Action.stories.svelte b/src/lib/components/common/stories/Action.stories.svelte index f5e8919c3..107c0d2a8 100644 --- a/src/lib/components/common/stories/Action.stories.svelte +++ b/src/lib/components/common/stories/Action.stories.svelte @@ -7,6 +7,7 @@ title: "Components / Common / Action", component: Action, parameters: { layout: "centered" }, + tags: ["autodocs"], }; @@ -17,3 +18,7 @@ Edit + + + Edit + diff --git a/src/lib/components/documents/Note.svelte b/src/lib/components/documents/Note.svelte new file mode 100644 index 000000000..0dc3ff899 --- /dev/null +++ b/src/lib/components/documents/Note.svelte @@ -0,0 +1,345 @@ + + + +
+
+

{note.title}

+
+ {#if note.edit_access} + {$_("dialog.edit")} + {$_("dialog.delete")} + {/if} +
+
+
+ +
+
+

{@html clean(note.content)}

+
+
+ {#if note.edit_access} + + {:else} + + + {$_(`access.${access[note.access].value}.title`)} + + {/if} + + {#if user} +

+ {$_("annotation.by", { values: { name: user.name } })} +

+ {/if} +
+
+ + diff --git a/src/lib/components/documents/NoteLink.svelte b/src/lib/components/documents/NoteLink.svelte new file mode 100644 index 000000000..2427d6799 --- /dev/null +++ b/src/lib/components/documents/NoteLink.svelte @@ -0,0 +1,60 @@ + + + + + {note.title} + + + diff --git a/src/lib/components/documents/Notes.svelte b/src/lib/components/documents/Notes.svelte new file mode 100644 index 000000000..4684daf4c --- /dev/null +++ b/src/lib/components/documents/Notes.svelte @@ -0,0 +1,107 @@ + + +
+ {#if asset_url} + {#await pdf then pdf} + {#each notes as note} + + {:else} + +

{$_("notes.empty")}

+

+ {$_("notes.cta")} +

+
+ {/each} + {/await} + {:else} + {#each notes as note} + + {:else} + +

{$_("notes.empty")}

+

+ {$_("notes.cta")} +

+
+ {/each} + {/if} +
+ + diff --git a/src/lib/components/documents/PDF.svelte b/src/lib/components/documents/PDF.svelte index 809346a2c..badaab5e9 100644 --- a/src/lib/components/documents/PDF.svelte +++ b/src/lib/components/documents/PDF.svelte @@ -33,6 +33,15 @@ $: sizes = pageSizes(document.page_spec); + // index notes by page + $: notes = document.notes.reduce((m, note) => { + if (!m[note.page_number]) { + m[note.page_number] = []; + } + m[note.page_number].push(note); + return m; + }, {}); + let progress = { loaded: 0, total: 0, @@ -56,7 +65,8 @@
{#each sizes as [width, height], n} - + {@const page_number = n + 1} + {/each}
@@ -65,6 +75,7 @@ display: flex; flex-direction: column; margin: 0 auto; + padding: 0 3rem; gap: 3rem; width: 100%; overflow-x: auto; diff --git a/src/lib/components/documents/PDFPage.svelte b/src/lib/components/documents/PDFPage.svelte index e99a63bcb..e1f72ace4 100644 --- a/src/lib/components/documents/PDFPage.svelte +++ b/src/lib/components/documents/PDFPage.svelte @@ -8,18 +8,31 @@ Selectable text can be rendered in one of two ways: - Passed in as a server-fetched JSON object --> @@ -170,6 +192,7 @@ Selectable text can be rendered in one of two ways: style:--aspect={aspect} style:--scale-factor={numericScale.toFixed(2)} style:--width="{width}px" + style:--height="{height}px" data-loaded={loaded} > @@ -189,6 +212,37 @@ Selectable text can be rendered in one of two ways:
{/if} + {#if notes} + {#await pdf then pdf} +
+ {#each notes as note} + {@const is_active = note.id === $activeNote?.id} + openNote(e, note)} + > + + {#if is_active} + + {/if} + + {#if is_active} + + {:else} + + {/if} + {/each} +
+ {/await} + {/if}
@@ -201,6 +255,9 @@ Selectable text can be rendered in one of two ways: background-color: var(--white, white); box-shadow: var(--shadow); width: var(--width, "100%"); + + /* make this the container for everything below */ + contain: layout; } .page-container.scale-width { @@ -208,7 +265,9 @@ Selectable text can be rendered in one of two ways: } .page-container.scale-height { + aspect-ratio: 1 / var(--aspect); height: 90vh; + width: inherit; } .selectable-text { @@ -216,7 +275,6 @@ Selectable text can be rendered in one of two ways: top: 0; bottom: 0; width: 100%; - opacity: 0.5; } .word { @@ -258,6 +316,43 @@ Selectable text can be rendered in one of two ways: width: 100%; } + .notes { + position: absolute; + top: 0; + bottom: 0; + width: 100%; + pointer-events: none; + } + + .notes :global(*) { + pointer-events: all; + } + + .note { + position: absolute; + pointer-events: all; + left: -3rem; + } + + .note button { + border: none; + padding: 0; + background: none; + cursor: pointer; + + position: absolute; + margin: auto 0; + top: 0; + left: 0; + bottom: 0; + right: 0; + + display: flex; + padding-left: 0.5rem; + justify-content: left; + align-items: center; + } + /* pdfjs creates this */ :global(.hiddenCanvasElement) { display: none; diff --git a/src/lib/components/documents/stories/Note.stories.svelte b/src/lib/components/documents/stories/Note.stories.svelte new file mode 100644 index 000000000..d02b71767 --- /dev/null +++ b/src/lib/components/documents/stories/Note.stories.svelte @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + {#await load(url) then pdf} + + {/await} + diff --git a/src/lib/components/documents/stories/Notes.stories.svelte b/src/lib/components/documents/stories/Notes.stories.svelte new file mode 100644 index 000000000..efe8941b7 --- /dev/null +++ b/src/lib/components/documents/stories/Notes.stories.svelte @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/src/lib/components/documents/stories/PDF.stories.svelte b/src/lib/components/documents/stories/PDF.stories.svelte index ece3dc99a..934ecf9ac 100644 --- a/src/lib/components/documents/stories/PDF.stories.svelte +++ b/src/lib/components/documents/stories/PDF.stories.svelte @@ -1,5 +1,5 @@ + + +
+ +
+
+ +
diff --git a/src/lib/utils/search.ts b/src/lib/utils/search.ts index 952e5bc91..e168db84a 100644 --- a/src/lib/utils/search.ts +++ b/src/lib/utils/search.ts @@ -1,16 +1,16 @@ import type { User } from "@/api/types"; import type { Access } from "../api/types"; -import { APP_URL } from "@/config/config"; +import { APP_URL } from "@/config/config.js"; import { slugify } from "@/util/string.js"; export function searchUrl(query: string): URL { - const href = new URL("app", APP_URL); + const href = new URL("app/", APP_URL); href.searchParams.set("q", query); return href; } export function projectSearchUrl(project): string { - return searchUrl(`+project:${project.id} `).toString(); + return searchUrl(`+project:${project.id} `).href; } /** @@ -21,3 +21,22 @@ export function projectSearchUrl(project): string { export function userDocs(user: User, access: Access): string { return `+user:${slugify(user.name)}-${user.id} access:${access}`; } + +/** + * Tag search query + * @param t tag + * @returns formatted query + */ +export function tag(t: string): string { + return `+tag:"${t}"`; +} + +/** + * Data search query + * @param key + * @param value + * @returns formatted query + */ +export function kv(key: string, value: string): string { + return `+data_${key}:"${value}"`; +} diff --git a/src/lib/utils/tests/scroll.test.ts b/src/lib/utils/tests/scroll.test.ts index b59068618..b57ec15a0 100644 --- a/src/lib/utils/tests/scroll.test.ts +++ b/src/lib/utils/tests/scroll.test.ts @@ -22,9 +22,7 @@ describe("scroll helpers", () => { }); test("scrollToPage", () => { - const scrollIntoView = vi.fn().mockImplementation(function () { - console.log(this); - }); + const scrollIntoView = vi.fn(); const getElementById = vi .spyOn(document, "getElementById") .mockImplementation( diff --git a/src/lib/utils/tests/search.test.ts b/src/lib/utils/tests/search.test.ts index 4b06691e9..ab57b0525 100644 --- a/src/lib/utils/tests/search.test.ts +++ b/src/lib/utils/tests/search.test.ts @@ -1,14 +1,36 @@ -import { test, expect } from "vitest"; +import { test, expect, describe } from "vitest"; + +import { APP_URL } from "@/config/config.js"; import { slugify } from "@/util/string"; import { me } from "@/test/fixtures/accounts"; -import { userDocs } from "../search"; +import { userDocs, tag, kv, searchUrl } from "../search"; + +describe("search utilities", () => { + test("search URL", () => { + const href = new URL(`/app/`, APP_URL); + href.searchParams.set("q", "Nick Clegg"); + expect(searchUrl("Nick Clegg")).toStrictEqual(href); + }); + + test("userDocs", () => { + expect(userDocs(me, "public")).toStrictEqual( + `+user:${slugify(me.name)}-${me.id} access:public`, + ); + + expect(userDocs(me, "private")).toStrictEqual( + `+user:${slugify(me.name)}-${me.id} access:private`, + ); + }); + + test("tags", () => { + expect(tag("research")).toStrictEqual(`+tag:"research"`); -test("userDocs", () => { - expect(userDocs(me, "public")).toStrictEqual( - `+user:${slugify(me.name)}-${me.id} access:public`, - ); + expect(tag("PBS NewsHour")).toStrictEqual(`+tag:"PBS NewsHour"`); + }); - expect(userDocs(me, "private")).toStrictEqual( - `+user:${slugify(me.name)}-${me.id} access:private`, - ); + test("kv", () => { + expect(kv("parties", "David Cameron")).toStrictEqual( + `+data_parties:"David Cameron"`, + ); + }); }); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 02758e22c..4e56f1da3 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -17,6 +17,7 @@ // update context so other components can access and update setContext("me", me); setContext("org", org); + setContext("embed", data.embed); diff --git a/src/routes/documents/[id]-[slug]/+layout.svelte b/src/routes/documents/[id]-[slug]/+layout.svelte index 82f0f3430..0bef64b00 100644 --- a/src/routes/documents/[id]-[slug]/+layout.svelte +++ b/src/routes/documents/[id]-[slug]/+layout.svelte @@ -1,7 +1,7 @@ - + {#if document.noindex || document.admin_noindex} @@ -48,20 +52,25 @@ {#if document?.description?.trim().length > 0} {/if} - + - + + + diff --git a/src/routes/documents/[id]-[slug]/+page.svelte b/src/routes/documents/[id]-[slug]/+page.svelte index 53bf6361a..4e1187a80 100644 --- a/src/routes/documents/[id]-[slug]/+page.svelte +++ b/src/routes/documents/[id]-[slug]/+page.svelte @@ -1,5 +1,5 @@ - Data & Tags + {$_("sidebar.data.title")} {#if empty} -

Use tags or key/value data to organize documents

+

{$_("sidebar.data.empty")}

- {/if} + + + {#if tags} + + {#each tags as tag} + + {/each} + + {/if} + + {#if data.length} + + {#each data as [key, values]} + {#each values as value} + + {/each} + {/each} + + {/if} +
diff --git a/src/routes/documents/[id]-[slug]/sidebar/Notes.svelte b/src/routes/documents/[id]-[slug]/sidebar/Notes.svelte new file mode 100644 index 000000000..26bf931dc --- /dev/null +++ b/src/routes/documents/[id]-[slug]/sidebar/Notes.svelte @@ -0,0 +1,67 @@ + + + + + + {$_("sidebar.toc.notes")} + + +
    + {#each notes as note} +
  1. + + {note.title} + + {$_("sidebar.toc.pageAbbrev")} + {note.page_number + 1} + +
  2. + {:else} + + + {#if document.edit_access} +

    + {$_("notes.cta")} +

    + {:else} +

    {$_("notes.empty")}

    + {/if} +
    + {/each} +
+
+ + diff --git a/src/routes/documents/[id]-[slug]/sidebar/Sections.svelte b/src/routes/documents/[id]-[slug]/sidebar/Sections.svelte index 138c2908e..4ebf8e756 100644 --- a/src/routes/documents/[id]-[slug]/sidebar/Sections.svelte +++ b/src/routes/documents/[id]-[slug]/sidebar/Sections.svelte @@ -1,28 +1,56 @@ - Sections + {$_("sidebar.toc.sections")} - {#each sections as section} - - {section.title} - - {:else} +
    + {#each sections as section} +
  1. + + {section.title} + + + {$_("sidebar.toc.pageAbbrev")} + {section.page_number + 1} + +
  2. + {/each} +
+ + {#if empty} -

Sections organize your document with a table of contents

+

{$_("sidebar.toc.empty")}

- {/each} + {/if}
+ + diff --git a/src/routes/documents/[id]-[slug]/sidebar/stories/Data.stories.svelte b/src/routes/documents/[id]-[slug]/sidebar/stories/Data.stories.svelte new file mode 100644 index 000000000..13f5fba69 --- /dev/null +++ b/src/routes/documents/[id]-[slug]/sidebar/stories/Data.stories.svelte @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + +