From feb31cf212c91d92f85ffaa456fefd5a3acd06a6 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Mon, 20 May 2024 21:11:40 -0400 Subject: [PATCH 01/25] Render note highlighting on document --- src/lib/api/notes.test.ts | 4 ++ src/lib/api/notes.ts | 4 ++ src/lib/components/documents/Note.svelte | 51 +++++++++++++++++++++ src/lib/components/documents/PDF.svelte | 12 ++++- src/lib/components/documents/PDFPage.svelte | 37 ++++++++++++++- 5 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 src/lib/components/documents/Note.svelte diff --git a/src/lib/api/notes.test.ts b/src/lib/api/notes.test.ts index 9648b9a5f..d3bfff9d1 100644 --- a/src/lib/api/notes.test.ts +++ b/src/lib/api/notes.test.ts @@ -33,6 +33,10 @@ describe("note helper methods", () => { ); }); + test("noteHashUrl", () => { + expect(notes.noteHashUrl(n)).toStrictEqual("#document/p3/a557"); + }); + 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..2e96a48fb 100644 --- a/src/lib/api/notes.ts +++ b/src/lib/api/notes.ts @@ -71,6 +71,10 @@ export function noteUrl(document: Document, note: Note): URL { ); } +export function noteHashUrl(note: Note): string { + return `#document/p${note.page_number + 1}/a${note.id}`; +} + /** 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/documents/Note.svelte b/src/lib/components/documents/Note.svelte new file mode 100644 index 000000000..faea3334f --- /dev/null +++ b/src/lib/components/documents/Note.svelte @@ -0,0 +1,51 @@ + + + + +{#if focused} +
+{:else} + + {note.title} + +{/if} + + diff --git a/src/lib/components/documents/PDF.svelte b/src/lib/components/documents/PDF.svelte index 809346a2c..37331b25b 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}
diff --git a/src/lib/components/documents/PDFPage.svelte b/src/lib/components/documents/PDFPage.svelte index e99a63bcb..98595e989 100644 --- a/src/lib/components/documents/PDFPage.svelte +++ b/src/lib/components/documents/PDFPage.svelte @@ -8,18 +8,23 @@ Selectable text can be rendered in one of two ways: - Passed in as a server-fetched JSON object --> @@ -17,3 +18,7 @@ Edit + + + Edit + diff --git a/src/lib/components/documents/Note.svelte b/src/lib/components/documents/Note.svelte index faea3334f..12ed83840 100644 --- a/src/lib/components/documents/Note.svelte +++ b/src/lib/components/documents/Note.svelte @@ -2,24 +2,58 @@ @component A single note, either overlaid on a document or on its own. It has two states, focused and normal. - - --> - {#if focused} -
+
+
+

{note.title}

+
+ + Edit + Delete + +
+
+ +
+ {note.content} +
+
+
{:else} + /* overlay mode */ a.note { + border-radius: 0.25rem; color: transparent; position: absolute; - border: 2px solid var(--note-public); - background-color: var(--note-public); - opacity: 0.33; + opacity: 0.5; pointer-events: all; + mix-blend-mode: multiply; + } + + a.note.public { + background-color: var(--note-public); } - .note.private { + a.note.private { + background-color: var(--note-private); border-color: var(--note-private); } - .note.org { + a.note.organization { + background-color: var(--note-org); border-color: var(--note-org); } + + /* focused mode */ + .focused { + display: flex; + width: 38.0625rem; + padding: var(--font-xs, 0.75rem) var(--font-md, 1rem); + flex-direction: column; + align-items: flex-start; + gap: var(--font-xs, 0.75rem); + } + + .focused header { + display: flex; + justify-content: space-between; + align-items: center; + align-self: stretch; + } + + .actions { + display: flex; + justify-content: center; + align-items: center; + gap: var(--font-md, 1rem); + } + + .excerpt { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.25rem; + align-self: stretch; + } + + .highlight { + height: 4.25rem; + align-self: stretch; + border-radius: 0.5rem; + border: 1px solid var(--gray-2, #d8dee2); + background: var(--gray-1, #f5f6f7); + } + + h4, + h4 a { + color: var(--gray-4, #5c717c); + text-decoration: none; + font-weight: var(--font-regular); + } + + h4 a:hover { + text-decoration: underline; + } diff --git a/src/lib/components/documents/PDFPage.svelte b/src/lib/components/documents/PDFPage.svelte index 98595e989..c2da03689 100644 --- a/src/lib/components/documents/PDFPage.svelte +++ b/src/lib/components/documents/PDFPage.svelte @@ -194,20 +194,22 @@ Selectable text can be rendered in one of two ways: {/if} -
- {#each notes as note} - - - - - - {/each} -
+ {#if notes} +
+ {#each notes as note} + + + + + + {/each} +
+ {/if} @@ -288,7 +290,6 @@ Selectable text can be rendered in one of two ways: position: absolute; pointer-events: all; left: -3rem; - transform: translateY(calc(-1.75rem / 3)); } /* pdfjs creates this */ 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..8114a61a0 --- /dev/null +++ b/src/lib/components/documents/stories/Note.stories.svelte @@ -0,0 +1,24 @@ + + + + + + + + + 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/routes/documents/[id]-[slug]/+layout.svelte b/src/routes/documents/[id]-[slug]/+layout.svelte index 82f0f3430..80abfb03b 100644 --- a/src/routes/documents/[id]-[slug]/+layout.svelte +++ b/src/routes/documents/[id]-[slug]/+layout.svelte @@ -21,9 +21,8 @@ import Projects from "./sidebar/Projects.svelte"; import Sections from "./sidebar/Sections.svelte"; - import { embedUrl } from "@/api/embed"; - import { pageImageUrl } from "@/api/viewer"; - import { canonicalUrl } from "@/lib/api/documents"; + import { embedUrl } from "$lib/api/embed"; + import { canonicalUrl, pageImageUrl } from "@/lib/api/documents"; export let data; @@ -48,13 +47,16 @@ {#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..966c19123 100644 --- a/src/routes/documents/[id]-[slug]/+page.svelte +++ b/src/routes/documents/[id]-[slug]/+page.svelte @@ -46,6 +46,7 @@ // pagination store, available via context const currentPage: Writable = writable(1); + let activeNote; setContext("currentPage", currentPage); From f1cbda66c115be136c58a4077fc5a2194dc425a7 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Tue, 21 May 2024 17:07:12 -0400 Subject: [PATCH 03/25] Everything but the excerpt --- src/lib/api/documents.ts | 2 +- .../fixtures/documents/document-expanded.json | 180 ++++++++++++++++-- src/lib/components/documents/Note.svelte | 116 ++++++++++- .../documents/stories/Note.stories.svelte | 6 +- 4 files changed, 279 insertions(+), 25 deletions(-) diff --git a/src/lib/api/documents.ts b/src/lib/api/documents.ts index 4b42cf080..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(",")); 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/components/documents/Note.svelte b/src/lib/components/documents/Note.svelte index 12ed83840..d4ef56aac 100644 --- a/src/lib/components/documents/Note.svelte +++ b/src/lib/components/documents/Note.svelte @@ -4,22 +4,56 @@ It has two states, focused and normal. --> {#if focused} @@ -27,12 +61,12 @@

{note.title}

- + {#if note.edit_access} Edit Delete - + {/if}
@@ -46,9 +80,32 @@
- {note.content} +

{@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} +
{:else} diff --git a/src/lib/components/documents/stories/Note.stories.svelte b/src/lib/components/documents/stories/Note.stories.svelte index 8114a61a0..fdd042091 100644 --- a/src/lib/components/documents/stories/Note.stories.svelte +++ b/src/lib/components/documents/stories/Note.stories.svelte @@ -15,10 +15,10 @@ }; - + - - + + From 5e2b8a0f6382d208016130dcf3e07b282ed0e399 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Wed, 22 May 2024 15:26:14 -0400 Subject: [PATCH 04/25] Positioning and opening/closing notes --- src/lib/components/documents/Note.svelte | 50 +++++++++++++++---- src/lib/components/documents/PDF.svelte | 1 + src/lib/components/documents/PDFPage.svelte | 45 ++++++++++++++++- .../documents/stories/Note.stories.svelte | 16 ++++++ src/routes/+layout.svelte | 1 + src/routes/documents/[id]-[slug]/+page.svelte | 42 ++++++++++------ 6 files changed, 131 insertions(+), 24 deletions(-) diff --git a/src/lib/components/documents/Note.svelte b/src/lib/components/documents/Note.svelte index d4ef56aac..1b3435346 100644 --- a/src/lib/components/documents/Note.svelte +++ b/src/lib/components/documents/Note.svelte @@ -4,10 +4,13 @@ It has two states, focused and normal. --> {#if focused} -
+

{note.title}

{#if note.edit_access} - Edit + {$_("dialog.edit")} Delete{$_("dialog.delete")} {/if}
@@ -109,7 +126,7 @@
{:else}
diff --git a/src/lib/components/documents/PDF.svelte b/src/lib/components/documents/PDF.svelte index 37331b25b..d6d1d652e 100644 --- a/src/lib/components/documents/PDF.svelte +++ b/src/lib/components/documents/PDF.svelte @@ -75,6 +75,7 @@ display: flex; flex-direction: column; margin: 0 auto; + padding-left: 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 c2da03689..738fbb573 100644 --- a/src/lib/components/documents/PDFPage.svelte +++ b/src/lib/components/documents/PDFPage.svelte @@ -10,7 +10,12 @@ Selectable text can be rendered in one of two ways: @@ -204,9 +216,14 @@ Selectable text can be rendered in one of two ways: style:top="{note.y1 * 100}%" > + {#if $activeNote} + + {/if} - + {/each}
{/if} @@ -284,6 +301,13 @@ Selectable text can be rendered in one of two ways: bottom: 0; width: 100%; pointer-events: none; + + /* make all child elements position relative to this container */ + /* transform: rotate(0deg); */ + } + + .notes :global(*) { + pointer-events: all; } .note { @@ -292,6 +316,25 @@ Selectable text can be rendered in one of two ways: 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 index fdd042091..8bc91fd82 100644 --- a/src/lib/components/documents/stories/Note.stories.svelte +++ b/src/lib/components/documents/stories/Note.stories.svelte @@ -8,6 +8,10 @@ const notes = document.notes as NoteType[]; + const html = `A chance for the Prime Minister and his Deputy to portray themselves as the "action men" of British politics. + Within a month-and-a-half, George Osborne, the country's new 38 year-old Chancellor of the Exchequor (i.e. the country's Finance Minister) + will present a budget to Parliament that calls for emergency actions to reduce Britain's forecast $280 billion deficit.`; + export const meta = { title: "Components / Documents / Note", component: Note, @@ -22,3 +26,15 @@ + + + + + + + + + + + + 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]/+page.svelte b/src/routes/documents/[id]-[slug]/+page.svelte index 966c19123..05a79b97d 100644 --- a/src/routes/documents/[id]-[slug]/+page.svelte +++ b/src/routes/documents/[id]-[slug]/+page.svelte @@ -1,5 +1,5 @@ + +
+ {#each notes as note} + + {/each} +
+ + diff --git a/src/routes/documents/[id]-[slug]/+page.svelte b/src/routes/documents/[id]-[slug]/+page.svelte index 05a79b97d..55f66813b 100644 --- a/src/routes/documents/[id]-[slug]/+page.svelte +++ b/src/routes/documents/[id]-[slug]/+page.svelte @@ -22,6 +22,7 @@ import Search from "$lib/components/forms/Search.svelte"; import Text from "$lib/components/documents/Text.svelte"; import ThumbnailGrid from "$lib/components/documents/ThumbnailGrid.svelte"; + import Notes from "$lib/components/documents/Notes.svelte"; // config and utils import { IMAGE_WIDTHS_MAP } from "@/config/config.js"; @@ -226,6 +227,10 @@ {/if} + {#if $mode === "notes"} + + {/if} +