Skip to content

Commit

Permalink
Note structure
Browse files Browse the repository at this point in the history
  • Loading branch information
eyeseast committed May 21, 2024
1 parent feb31cf commit 6603695
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 37 deletions.
3 changes: 3 additions & 0 deletions src/lib/api/documents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down
5 changes: 3 additions & 2 deletions src/lib/api/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions src/lib/api/notes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ describe("note helper methods", () => {
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);
});
Expand Down
19 changes: 19 additions & 0 deletions src/lib/api/notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +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;
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/common/Action.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions src/lib/components/common/stories/Action.stories.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
title: "Components / Common / Action",
component: Action,
parameters: { layout: "centered" },
tags: ["autodocs"],
};
</script>

Expand All @@ -17,3 +18,7 @@
<Story name="Text and Icon">
<Action icon={Pencil16}>Edit</Action>
</Story>

<Story name="Custom colors">
<Action --fill="red" --color="green" icon={Pencil16}>Edit</Action>
</Story>
112 changes: 102 additions & 10 deletions src/lib/components/documents/Note.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,58 @@
@component
A single note, either overlaid on a document or on its own.
It has two states, focused and normal.
-->

<script lang="ts">
import type { Note } from "$lib/api/types";
import { _ } from "svelte-i18n";
import { Pencil16, Trash16 } from "svelte-octicons";
import Action from "../common/Action.svelte";
import SignedIn from "../common/SignedIn.svelte";
import { noteHashUrl, width, height } from "$lib/api/notes";
import { pageHashUrl } from "$lib/api/documents";
export let note: Note;
export let focused = false;
$: id = noteHashUrl(note).replace("#", "");
$: href = noteHashUrl(note);
$: page_number = note.page_number + 1; // note pages are 0-indexed
</script>

{#if focused}
<div class="note"></div>
<div {id} class="note focused {note.access}">
<header>
<h3>{note.title}</h3>
<div class="actions">
<SignedIn>
<Action icon={Pencil16}>Edit</Action>
<Action --color="var(--red)" --fill="var(--red)" icon={Trash16}
>Delete</Action
>
</SignedIn>
</div>
</header>
<div class="excerpt">
<h4 {id}>
<a href={pageHashUrl(page_number)}>
{$_("documents.pageAbbrev")}
{page_number}
</a>
</h4>

<div class="highlight"></div>
</div>
<div class="content">
{note.content}
</div>
<footer></footer>
</div>
{:else}
<a
href={noteHashUrl(note)}
{id}
{href}
class="note {note.access}"
title={note.title}
style:top="{note.y1 * 100}%"
Expand All @@ -32,20 +66,78 @@
{/if}

<style>
/* 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;
}
</style>
31 changes: 16 additions & 15 deletions src/lib/components/documents/PDFPage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -194,20 +194,22 @@ Selectable text can be rendered in one of two ways:
<!-- pdfjs.renderTextLayer will fill this in -->
</div>
{/if}
<div class="notes">
{#each notes as note}
<a
class="note"
href={noteHashUrl(note)}
title={note.title}
style:top="{note.y1 * 100}%"
>
<NoteTab access={note.access} />
</a>

<Note {note} />
{/each}
</div>
{#if notes}
<div class="notes">
{#each notes as note}
<a
class="note"
href={noteHashUrl(note)}
title={note.title}
style:top="{note.y1 * 100}%"
>
<NoteTab access={note.access} />
</a>

<Note {note} />
{/each}
</div>
{/if}
</div>
</Page>

Expand Down Expand Up @@ -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 */
Expand Down
24 changes: 24 additions & 0 deletions src/lib/components/documents/stories/Note.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script lang="ts" context="module">
import type { Note as NoteType } from "$lib/api/types";
import { Story } from "@storybook/addon-svelte-csf";
import Note from "../Note.svelte";
import document from "$lib/api/fixtures/documents/document-expanded.json";
const notes = document.notes as NoteType[];
export const meta = {
title: "Components / Documents / Note",
component: Note,
parameters: { layout: "centered" },
};
</script>

<Story name="small">
<Note focused note={notes[0]} />
</Story>

<Story name="bigger">
<Note focused note={notes[1]} />
</Story>
4 changes: 1 addition & 3 deletions src/lib/utils/tests/scroll.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 7 additions & 5 deletions src/routes/documents/[id]-[slug]/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -48,13 +47,16 @@
<link
rel="alternate"
type="application/json+oembed"
href={embedUrl(document.canonical_url)}
href={embedUrl(document.canonical_url).href}
title={document.title}
/>
{#if document?.description?.trim().length > 0}
<meta property="og:description" content={document.description} />
{/if}
<meta property="og:image" content={pageImageUrl(document, 0, 700)} />
<meta
property="og:image"
content={pageImageUrl(document, 0, "normal").href}
/>
</svelte:head>

<MainLayout>
Expand Down
1 change: 1 addition & 0 deletions src/routes/documents/[id]-[slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
// pagination store, available via context
const currentPage: Writable<number> = writable(1);
let activeNote;
setContext("currentPage", currentPage);
Expand Down

0 comments on commit 6603695

Please sign in to comment.