diff --git a/.gitignore b/.gitignore index c95090079..c0f6cdf0e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ tests/fixtures/development.json .vscode vite.config.js.timestamp-* vite.config.ts.timestamp-* +*.log # sveltekit /build diff --git a/package-lock.json b/package-lock.json index 481f4b194..4112cb48f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6653,14 +6653,6 @@ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" }, - "node_modules/@sveltejs/kit/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@sveltejs/kit/node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -8398,13 +8390,13 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -8412,7 +8404,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -9379,10 +9371,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true, + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -10246,13 +10237,14 @@ } }, "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "hasInstallScript": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" }, "engines": { @@ -10664,6 +10656,25 @@ "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==" }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esniff/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, "node_modules/espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -10963,17 +10974,17 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -11423,9 +11434,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -12424,9 +12435,9 @@ } }, "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", "dev": true }, "node_modules/ipaddr.js": { @@ -17678,9 +17689,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "dependencies": { "bytes": "3.1.2", diff --git a/src/config/config.js b/src/config/config.js index 73ff826d5..473668561 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -63,6 +63,7 @@ export const IMAGE_WIDTHS = IMAGE_WIDTHS_ENTRIES.map(([name, width]) => [ export const IMAGE_WIDTHS_MAP = new Map(IMAGE_WIDTHS_ENTRIES); +export const DEFAULT_PER_PAGE = 25; export const MAX_PER_PAGE = 100; export const PDF_SIZE_LIMIT = 525336576; diff --git a/src/lib/api/documents.test.ts b/src/lib/api/documents.test.ts index aa5c357f8..150beaea1 100644 --- a/src/lib/api/documents.test.ts +++ b/src/lib/api/documents.test.ts @@ -2,7 +2,7 @@ import { describe, test as base, expect } from "vitest"; import { APP_URL, IMAGE_WIDTHS_ENTRIES } from "@/config/config.js"; import * as documents from "./documents"; -import type { Document } from "./types"; +import type { Document, sizes } from "./types"; type Use = (value: T) => Promise; @@ -56,7 +56,9 @@ describe("document helper methods", () => { test("pageImageUrl", ({ document }) => { const page = 1; IMAGE_WIDTHS_ENTRIES.forEach(([size, width]) => { - expect(documents.pageImageUrl(document, page, size)).toStrictEqual( + expect( + documents.pageImageUrl(document, page, size as sizes), + ).toStrictEqual( new URL( `documents/${document.id}/pages/${document.slug}-p${page}-${size}.gif`, document.asset_url, @@ -92,5 +94,27 @@ describe("document helper methods", () => { ); }); - test.todo("userOrgString"); + test("userOrgString", async ({ document }) => { + // user + org expanded + expect(documents.userOrgString(document)).toStrictEqual( + "Chris Amico (NewsHour)", + ); + + // user and org not expanded + const d2 = (await import("./fixtures/documents/document.json")) as Document; + expect(documents.userOrgString(d2)).toStrictEqual(""); + + // user, but no org + const d3 = { ...document, organization: 1 }; + expect(documents.userOrgString(d3)).toStrictEqual("Chris Amico"); + }); + + test("pdfUrl", ({ document }) => { + expect(documents.pdfUrl(document)).toStrictEqual( + new URL( + `documents/${document.id}/${document.slug}.pdf`, + document.asset_url, + ), + ); + }); }); diff --git a/src/lib/api/documents.ts b/src/lib/api/documents.ts index d3cd9b6a7..de2d04d0a 100644 --- a/src/lib/api/documents.ts +++ b/src/lib/api/documents.ts @@ -1,23 +1,38 @@ /** API helpers related to documents. * Lots of duplicated code here that should get consolidated at some point. */ +import type { Document, DocumentResults, SearchOptions, sizes } from "./types"; + import { error } from "@sveltejs/kit"; -import { APP_URL, BASE_API_URL } from "@/config/config.js"; + import { DEFAULT_EXPAND } from "@/api/common.js"; import { isOrg } from "@/api/types/orgAndUser"; +import { APP_URL, BASE_API_URL } from "@/config/config.js"; import { isErrorCode } from "../utils"; -/** Search documents */ +/** + * Search documents + * https://www.documentcloud.org/help/search/ + * + * */ export async function search( query = "", - highlight = false, + options: SearchOptions = { + hl: Boolean(query), + per_page: 25, + cursor: "", + version: "2.0", + }, fetch = globalThis.fetch, -) { +): Promise { const endpoint = new URL("documents/search/", BASE_API_URL); endpoint.searchParams.set("expand", DEFAULT_EXPAND); endpoint.searchParams.set("q", query); - endpoint.searchParams.set("hl", String(highlight)); + + for (const [k, v] of Object.entries(options)) { + endpoint.searchParams.set(k, String(v)); + } const resp = await fetch(endpoint, { credentials: "include" }); @@ -37,7 +52,7 @@ export async function get( fetch: typeof globalThis.fetch = globalThis.fetch, ): Promise { const endpoint = new URL(`documents/${id}.json`, BASE_API_URL); - const expand = ["user", "organization", "projects", "revisions"]; + const expand = ["user", "organization", "projects", "revisions", "sections"]; endpoint.searchParams.set("expand", expand.join(",")); const resp = await fetch(endpoint, { credentials: "include" }); @@ -150,7 +165,7 @@ export async function cancel(id: number) {} * @param {import('./types').Document} document * @returns {URL} */ -export function canonicalUrl(document) { +export function canonicalUrl(document: Document): URL { const path = `/documents/${document.id}-${document.slug}/`; return new URL(path, APP_URL); } @@ -164,7 +179,7 @@ export function canonicalUrl(document) { * @param {number} page * @returns {URL} */ -export function canonicalPageUrl(document, page) { +export function canonicalPageUrl(document: Document, page: number): URL { return new URL(`/documents/${document.id}/pages/${page}/`, APP_URL); } @@ -172,10 +187,8 @@ export function canonicalPageUrl(document, page) { * Generate the hash for a path, without the host or path * * @export - * @param {number} page - * @returns {URL} */ -export function pageHashUrl(page) { +export function pageHashUrl(page: number): string { return `#document/p${page}`; } @@ -187,7 +200,7 @@ export function pageHashUrl(page) { * @param {number} page * @returns {URL} */ -export function pageUrl(document, page) { +export function pageUrl(document: Document, page: number): URL { return new URL(pageHashUrl(page), canonicalUrl(document)); } @@ -200,7 +213,11 @@ export function pageUrl(document, page) { * @param {import('./types').sizes} size * @returns {URL} */ -export function pageImageUrl(document, page, size) { +export function pageImageUrl( + document: Document, + page: number, + size: sizes, +): URL { return new URL( `documents/${document.id}/pages/${document.slug}-p${page}-${size}.gif`, document.asset_url, @@ -211,11 +228,8 @@ export function pageImageUrl(document, page, size) { * Asset URL for page text * * @export - * @param {import('./types').Document} document - * @param {number} page - * @returns {URL} */ -export function textUrl(document, page) { +export function textUrl(document: Document, page: number): URL { return new URL( `documents/${document.id}/pages/${document.slug}-p${page}.txt`, document.asset_url, @@ -226,10 +240,8 @@ export function textUrl(document, page) { * Asset URL for JSON text * * @export - * @param {import('./types').Document} document - * @returns {URL} */ -export function jsonUrl(document) { +export function jsonUrl(document: Document): URL { return new URL( `documents/${document.id}/${document.slug}.txt.json`, document.asset_url, @@ -240,25 +252,33 @@ export function jsonUrl(document) { * Asset URL for text positions * * @export - * @param {import('./types').Document} document - * @param {number} page - * @returns {URL} */ -export function selectableTextUrl(document, page) { +export function selectableTextUrl(document: Document, page: number): URL { return new URL( `documents/${document.id}/pages/${document.slug}-p${page}.position.json`, document.asset_url, ); } +/** + * Generate URL for the PDF version of a document. + * This will always be a PDF, regardless of the original file type. + * + * @export + */ +export function pdfUrl(document: Document): URL { + return new URL( + `documents/${document.id}/${document.slug}.pdf`, + document.asset_url, + ); +} + /** * Generate a user (organization) string * * @export - * @param {import('./types').Document} document - * @returns {string} */ -export function userOrgString(document) { +export function userOrgString(document: Document): string { // we have an org and user if (isOrg(document.organization) && typeof document.user === "object") { return `${document.user.name} (${document.organization.name})`; @@ -266,7 +286,7 @@ export function userOrgString(document) { // just a user if (typeof document.user === "object") { - return document.user; + return document.user.name; } // nothing, so return nothing diff --git a/src/lib/api/embed.test.js b/src/lib/api/embed.test.ts similarity index 55% rename from src/lib/api/embed.test.js rename to src/lib/api/embed.test.ts index 46606c6ee..dbee13064 100644 --- a/src/lib/api/embed.test.js +++ b/src/lib/api/embed.test.ts @@ -1,28 +1,39 @@ +import type { Document, Note, OEmbed } from "./types"; import { test, describe, expect } from "vitest"; import { APP_URL, BASE_API_URL } from "@/config/config.js"; -import { embedUrl, getEmbed } from "./embed.js"; +import { embedUrl, getEmbed } from "./embed"; import * as documents from "./documents"; import * as notes from "./notes.js"; import document from "./fixtures/documents/document.json"; import note from "./fixtures/notes/note.json"; +import oembed from "./fixtures/oembed.json"; describe("embed tests", () => { test("embedUrl", () => { // document - const docUrl = documents.canonicalUrl(document); + const docUrl = documents.canonicalUrl(document as Document); expect(embedUrl(docUrl)).toStrictEqual( new URL(`oembed/?url=${docUrl.toString()}`, BASE_API_URL), ); // note - const noteUrl = notes.noteUrl(document, note); + const noteUrl = notes.noteUrl(document as Document, note as Note); expect(embedUrl(noteUrl)).toStrictEqual( new URL(`oembed/?url=${noteUrl.toString()}`, BASE_API_URL), ); }); test.todo("getEmbed"); + /* need to sort out mocking first + test("getEmbed", async () => { + const url = + "https://www.documentcloud.org/documents/282753-lefler-thesis.html"; + const result = await getEmbed(url); + + expect(result).toEqual(oembed); + }); + */ }); diff --git a/src/lib/api/embed.js b/src/lib/api/embed.ts similarity index 52% rename from src/lib/api/embed.js rename to src/lib/api/embed.ts index 892e2eaeb..7e863fec6 100644 --- a/src/lib/api/embed.js +++ b/src/lib/api/embed.ts @@ -1,15 +1,14 @@ // api utilities for embeds +import type { OEmbed } from "./types"; import { BASE_API_URL } from "@/config/config.js"; /** * Generate an oembed URL for a given DocumentCloud URL * * @export - * @param {URL | string} url - * @returns {URL} */ -export function embedUrl(url) { +export function embedUrl(url: URL | string): URL { return new URL(`oembed/?url=${url.toString()}`, BASE_API_URL); } @@ -17,7 +16,9 @@ export function embedUrl(url) { * Fetch embed code from the OEmbed API endpoint * * @export - * @param {URL | string} url - * @returns {import('./types').OEmbed} */ -export async function getEmbed(url) {} +export async function getEmbed(url: URL | string): Promise { + const endpoint = embedUrl(url); + + return fetch(endpoint, { credentials: "include" }).then((r) => r.json()); +} diff --git a/src/lib/api/fixtures/mock.js b/src/lib/api/fixtures/mock.js index 3a20deee7..56c4d61ab 100644 --- a/src/lib/api/fixtures/mock.js +++ b/src/lib/api/fixtures/mock.js @@ -4,6 +4,7 @@ import meFixture from "../fixtures/users/me.json"; import projectFixture from "../fixtures/projects/project.json"; import projDocsPage1 from "../fixtures/projects/project-documents-expanded.json"; import projDocsPage2 from "../fixtures/projects/project-documents-2.json"; +import oembedFixture from "../fixtures/oembed.json"; import { BASE_API_URL } from "@/config/config.js"; @@ -34,6 +35,7 @@ const urls = { info: u("projects/"), documents: u("projects/*/documents/"), }, + oembed: u("oembed/"), }; export const me = { @@ -48,3 +50,5 @@ export const projects = { pageHandler(projDocsPage1, projDocsPage2), ), }; + +export const oembed = rest.get(urls.oembed, dataHandler(oembedFixture)); diff --git a/src/lib/api/fixtures/oembed.json b/src/lib/api/fixtures/oembed.json new file mode 100644 index 000000000..fa4d02d5b --- /dev/null +++ b/src/lib/api/fixtures/oembed.json @@ -0,0 +1,11 @@ +{ + "version": "1.0", + "provider_name": "DocumentCloud", + "provider_url": "https://www.documentcloud.org", + "cache_age": 300, + "title": "Lefler Thesis", + "width": 700, + "height": 905, + "html": "\n", + "type": "rich" +} diff --git a/src/lib/api/projects.test.js b/src/lib/api/projects.test.js index c4bfc8ec1..9e44e0a84 100644 --- a/src/lib/api/projects.test.js +++ b/src/lib/api/projects.test.js @@ -1,6 +1,6 @@ import { describe, test, expect } from "vitest"; -import * as projects from "./projects.js"; +import * as projects from "./projects"; describe("project tests", () => { test.todo("projects.get"); diff --git a/src/lib/api/sections.test.js b/src/lib/api/sections.test.ts similarity index 100% rename from src/lib/api/sections.test.js rename to src/lib/api/sections.test.ts diff --git a/src/lib/api/sections.js b/src/lib/api/sections.ts similarity index 100% rename from src/lib/api/sections.js rename to src/lib/api/sections.ts diff --git a/src/lib/api/types.d.ts b/src/lib/api/types.d.ts index 7d911f6db..226ab6be3 100644 --- a/src/lib/api/types.d.ts +++ b/src/lib/api/types.d.ts @@ -109,7 +109,6 @@ export interface Document { note_highlights?: Record; } -// export type DocumentResults = Page; export interface DocumentResults extends Page {} export interface Note { @@ -139,6 +138,14 @@ export interface Section { export type SectionResults = Page
; +export interface SearchOptions { + hl?: boolean; + per_page?: number; + cursor?: string; + expand?: string; + version?: number | string; +} + export interface OEmbed { version: "1.0"; provider_name: "DocumentCloud"; diff --git a/src/lib/components/Search.svelte b/src/lib/components/Search.svelte index b60959d78..a6e59aa50 100644 --- a/src/lib/components/Search.svelte +++ b/src/lib/components/Search.svelte @@ -1,6 +1,5 @@ -
+ + {/if} + diff --git a/src/lib/components/documents/stories/ResultsList.stories.svelte b/src/lib/components/documents/stories/ResultsList.stories.svelte index 886307168..9e31ad2a5 100644 --- a/src/lib/components/documents/stories/ResultsList.stories.svelte +++ b/src/lib/components/documents/stories/ResultsList.stories.svelte @@ -1,11 +1,13 @@ -
+
-
+
+
+ + +
diff --git a/src/routes/(embed)/documents/[id]/annotations/[note_id]/+page.svelte b/src/routes/(embed)/documents/[id]/annotations/[note_id]/+page.svelte index 0dd4a98ca..c289bd4f8 100644 --- a/src/routes/(embed)/documents/[id]/annotations/[note_id]/+page.svelte +++ b/src/routes/(embed)/documents/[id]/annotations/[note_id]/+page.svelte @@ -6,7 +6,7 @@ import { informSize } from "@/embed/iframeSizer.js"; import { pageImageUrl } from "@/lib/api/documents"; import * as notes from "$lib/api/notes.js"; - import { embedUrl } from "$lib/api/embed.js"; + import { embedUrl } from "$lib/api/embed"; import { canonicalNoteUrl, noteUrl } from "$lib/api/notes.js"; import { pageSizesFromSpec } from "@/api/pageSize.js"; import { IMAGE_WIDTHS_MAP } from "@/config/config.js"; diff --git a/src/routes/(embed)/documents/[id]/pages/[page]/+page.svelte b/src/routes/(embed)/documents/[id]/pages/[page]/+page.svelte index 71940346e..74e95e28e 100644 --- a/src/routes/(embed)/documents/[id]/pages/[page]/+page.svelte +++ b/src/routes/(embed)/documents/[id]/pages/[page]/+page.svelte @@ -14,7 +14,7 @@ textUrl, userOrgString, } from "@/lib/api/documents"; - import { embedUrl } from "$lib/api/embed.js"; + import { embedUrl } from "$lib/api/embed"; export let data; diff --git a/src/routes/app/+page.svelte b/src/routes/app/+page.svelte index 08702a3fd..8ea406a44 100644 --- a/src/routes/app/+page.svelte +++ b/src/routes/app/+page.svelte @@ -1,39 +1,27 @@ @@ -44,45 +32,48 @@ {#await searchResults} Loading… {:then results} - + {/await} - + + diff --git a/src/routes/app/+page.ts b/src/routes/app/+page.ts index 2798376c6..29764d20c 100644 --- a/src/routes/app/+page.ts +++ b/src/routes/app/+page.ts @@ -1,11 +1,32 @@ -import { search } from "$lib/api/documents.js"; +import type { SearchOptions } from "$lib/api/types"; + +import { DEFAULT_PER_PAGE } from "@/config/config.js"; +import { search } from "$lib/api/documents"; export async function load({ url, fetch }) { const query = url.searchParams.get("q") || ""; - const searchResults = search(query, true, fetch); + const per_page = +url.searchParams.get("per_page") || DEFAULT_PER_PAGE; + const cursor = url.searchParams.get("cursor") || ""; + + const options: SearchOptions = { + hl: Boolean(query), + version: "2.0", + }; + + if (per_page) { + options.per_page = per_page; + } + + if (cursor) { + options.cursor = cursor; + } + + const searchResults = search(query, options, fetch); return { - searchResults, query, + per_page, + cursor, + searchResults, }; } diff --git a/src/routes/documents/[id]-[slug]/+layout.svelte b/src/routes/documents/[id]-[slug]/+layout.svelte index 3019b78af..cdc784e3f 100644 --- a/src/routes/documents/[id]-[slug]/+layout.svelte +++ b/src/routes/documents/[id]-[slug]/+layout.svelte @@ -1,38 +1,73 @@ - + - {#if data.document.noindex || data.document.admin_noindex} + {#if document.noindex || document.admin_noindex} {/if} - - - - {data.document.title} - DocumentCloud + + + + {document.title} - DocumentCloud - {#if data.document?.description?.trim().length > 0} - + {#if document?.description?.trim().length > 0} + {/if} - + -

{data.document.title}

+ + + + + + + + + + + + + + + - + + + + + diff --git a/src/routes/documents/[id]-[slug]/+layout.js b/src/routes/documents/[id]-[slug]/+layout.ts similarity index 69% rename from src/routes/documents/[id]-[slug]/+layout.js rename to src/routes/documents/[id]-[slug]/+layout.ts index ee4225464..f404e07fb 100644 --- a/src/routes/documents/[id]-[slug]/+layout.js +++ b/src/routes/documents/[id]-[slug]/+layout.ts @@ -6,15 +6,19 @@ import { redirect } from "@sveltejs/kit"; import * as documents from "@/lib/api/documents"; +import { getPinnedAddons } from "$lib/api/addons.js"; /** @type {import('./$types').PageLoad} */ export async function load({ fetch, params }) { - const document = await documents.get(params.id, fetch); + const document = await documents.get(+params.id, fetch); if (document.slug !== params.slug) { const canonical = new URL(document.canonical_url); redirect(302, canonical.pathname); } - return { document }; + // stream this + const pinnedAddons = getPinnedAddons(fetch); + + return { document, pinnedAddons }; } diff --git a/src/routes/documents/[id]-[slug]/+page.js b/src/routes/documents/[id]-[slug]/+page.ts similarity index 54% rename from src/routes/documents/[id]-[slug]/+page.js rename to src/routes/documents/[id]-[slug]/+page.ts index f8fe39cec..5d274b196 100644 --- a/src/routes/documents/[id]-[slug]/+page.js +++ b/src/routes/documents/[id]-[slug]/+page.ts @@ -1,12 +1,10 @@ /** Load notes and sections for viewing a single document */ -import * as notes from "$lib/api/notes.js"; -import * as sections from "$lib/api/sections.js"; +import * as notes from "$lib/api/notes"; /** @type {import('./$types').PageLoad} */ export async function load({ params, fetch }) { // stream these, because we can wait on them return { - notes: notes.list(params.id, fetch), - sections: sections.list(params.id, fetch), + notes: notes.list(+params.id, fetch), }; } diff --git a/src/routes/documents/[id]-[slug]/annotate/+page.svelte b/src/routes/documents/[id]-[slug]/annotate/+page.svelte new file mode 100644 index 000000000..e69de29bb diff --git a/src/routes/documents/[id]-[slug]/redact/+page.svelte b/src/routes/documents/[id]-[slug]/redact/+page.svelte new file mode 100644 index 000000000..e69de29bb diff --git a/src/routes/documents/[id]-[slug]/sidebar/Actions.svelte b/src/routes/documents/[id]-[slug]/sidebar/Actions.svelte new file mode 100644 index 000000000..7adbc68d2 --- /dev/null +++ b/src/routes/documents/[id]-[slug]/sidebar/Actions.svelte @@ -0,0 +1,93 @@ + + + + + + {document.access} access + + + Edit + + + + + + Revision History + + + + + Download PDF + + + + + + + Share … + + + + + Add a note … + + + + + Redact … + + + + + Modify Pages … + + + + diff --git a/src/routes/documents/[id]-[slug]/sidebar/Data.svelte b/src/routes/documents/[id]-[slug]/sidebar/Data.svelte new file mode 100644 index 000000000..7dc90800d --- /dev/null +++ b/src/routes/documents/[id]-[slug]/sidebar/Data.svelte @@ -0,0 +1,30 @@ + + + + + + Data & Tags + + + {#if empty} + +

Use tags or key/value data to organize documents

+
+ + {/if} +
diff --git a/src/routes/documents/[id]-[slug]/sidebar/DocumentMetadata.svelte b/src/routes/documents/[id]-[slug]/sidebar/DocumentMetadata.svelte new file mode 100644 index 000000000..326e79294 --- /dev/null +++ b/src/routes/documents/[id]-[slug]/sidebar/DocumentMetadata.svelte @@ -0,0 +1,63 @@ + + + + + +

{document.title}

+
+ + + {#if document.description} +

+ {document.description} +

+ {/if} + + Edit + +
+ + + + + + + + + + +
diff --git a/src/routes/documents/[id]-[slug]/sidebar/Projects.svelte b/src/routes/documents/[id]-[slug]/sidebar/Projects.svelte new file mode 100644 index 000000000..6c2d7ae07 --- /dev/null +++ b/src/routes/documents/[id]-[slug]/sidebar/Projects.svelte @@ -0,0 +1,26 @@ + + + + + + Projects + + + {#each projects as project} + + {project.title} + + {:else} + Add this document to projects + {/each} + diff --git a/src/routes/documents/[id]-[slug]/sidebar/Sections.svelte b/src/routes/documents/[id]-[slug]/sidebar/Sections.svelte new file mode 100644 index 000000000..138c2908e --- /dev/null +++ b/src/routes/documents/[id]-[slug]/sidebar/Sections.svelte @@ -0,0 +1,28 @@ + + + + + + Sections + + + {#each sections as section} + + {section.title} + + {:else} + +

Sections organize your document with a table of contents

+
+ {/each} +
diff --git a/src/structure/project.js b/src/structure/project.js index aaa7ee00e..19b78a276 100644 --- a/src/structure/project.js +++ b/src/structure/project.js @@ -31,6 +31,9 @@ export class Project extends Svue { description(project) { return project.description; }, + private(project) { + return project.private; + }, editAccess(project) { if (project.edit_access == null) return false; return project.edit_access; diff --git a/src/style/kit.css b/src/style/kit.css index 35b5d7985..a626e2a8b 100644 --- a/src/style/kit.css +++ b/src/style/kit.css @@ -123,4 +123,41 @@ p { a { color: var(--primary); -} \ No newline at end of file +} + +dl, +dt, +dd { + font-size: var(--font-m); + margin: 0; +} + +dt { + color: var(--gray-5, #233944); + font-size: var(--font-xs, 0.75rem); + font-weight: var(--font-semibold, 600); +} + +dd { + color: var(--gray-5, #233944); + font-size: var(--font-s, 0.875rem); + font-weight: var(--font-regular, 400); +} + +/* +Utility classes +*/ + +/* https://css-tricks.com/inclusively-hidden/ + * Hiding class, making content visible only to screen readers but not visually + * "sr" meaning "screen-reader" + */ +.sr-only:not(:focus):not(:active) { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} diff --git a/vite.config.js b/vite.config.js index 054217e14..7eab898a8 100644 --- a/vite.config.js +++ b/vite.config.js @@ -14,6 +14,9 @@ export default defineConfig({ define: { "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), + "process.env.DEPLOY_PRIME_URL": JSON.stringify( + process.env.DEPLOY_PRIME_URL, + ), }, envPrefix: "DC_",