From e50bd93b0d7a4108f971fde910d2e6947cb575ed Mon Sep 17 00:00:00 2001 From: Allan Lasser Date: Thu, 24 Oct 2024 18:37:52 -0400 Subject: [PATCH 1/5] Update tsconfig/jsconfig 546 errors remaining --- jsconfig.json | 2 ++ tsconfig.json | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/jsconfig.json b/jsconfig.json index d1365282c..b50f8e19f 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -10,6 +10,8 @@ "sourceMap": true, "strict": false, "moduleResolution": "bundler", + "noUncheckedIndexedAccess": true, + "strictNullChecks": true, "types": ["@testing-library/jest-dom"] }, // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files diff --git a/tsconfig.json b/tsconfig.json index b1bc480b9..5d98af74a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,9 @@ "compilerOptions": { "resolveJsonModule": true, "allowJs": true, - "downlevelIteration": true + "downlevelIteration": true, + "noUncheckedIndexedAccess": true, + "strictNullChecks": true // "types": ["@testing-library/jest-dom"] }, "include": [ From b7eeffe998bdb193ad70e5e492f08928a21e1035 Mon Sep 17 00:00:00 2001 From: Allan Lasser Date: Mon, 28 Oct 2024 14:48:04 -0400 Subject: [PATCH 2/5] Fix type errors raised by stricter settings - routes - routes/embed - routes/(pages) - routes/(app) - lib - lib/api - lib/utils - lib/components --- src/addons/AddOnPin.svelte | 4 + src/addons/types.ts | 4 +- src/api/session.ts | 2 +- src/api/types/project.ts | 4 +- src/common/Button.svelte | 2 +- src/common/RelativeTime.svelte | 21 +- src/langs/json/en.json | 5 +- src/lib/api/addons.ts | 19 +- src/lib/api/collaborators.ts | 2 +- src/lib/api/documents.ts | 6 +- src/lib/api/notes.ts | 24 +- src/lib/api/sections.ts | 24 +- src/lib/api/tests/addons.test.ts | 12 +- src/lib/api/tests/collaborators.test.ts | 8 +- src/lib/api/tests/documents.test.ts | 10 +- src/lib/api/tests/notes.test.ts | 1 + src/lib/api/tests/sections.test.ts | 4 +- src/lib/api/types.d.ts | 4 +- src/lib/components/accounts/Avatar.svelte | 6 +- src/lib/components/accounts/Mailkey.svelte | 10 + src/lib/components/addons/AddOnMeta.svelte | 6 +- src/lib/components/addons/History.svelte | 10 +- .../components/addons/ScheduledEvent.svelte | 2 +- src/lib/components/common/Action.svelte | 4 +- src/lib/components/common/AddOns.svelte | 2 +- src/lib/components/common/Button.svelte | 12 +- src/lib/components/common/Empty.svelte | 2 +- src/lib/components/common/Flex.svelte | 2 +- src/lib/components/common/KV.svelte | 2 +- src/lib/components/common/MenuItem.svelte | 4 +- src/lib/components/common/PageToolbar.svelte | 2 +- src/lib/components/common/Paginator.svelte | 6 +- src/lib/components/common/Premium.svelte | 7 +- src/lib/components/common/TipOfDay.svelte | 4 +- src/lib/components/common/Toast.svelte | 28 +- src/lib/components/common/Tooltip.svelte | 18 +- .../common/stories/Highlight.stories.svelte | 2 +- src/lib/components/common/tests/Toast.test.ts | 14 +- .../documents/DocumentListItem.svelte | 10 +- src/lib/components/documents/Metadata.svelte | 7 +- .../documents/NoteHighlights.svelte | 5 +- .../documents/PageHighlights.svelte | 2 +- .../components/documents/ResultsList.svelte | 12 +- src/lib/components/documents/Share.svelte | 10 +- .../components/documents/sidebar/Notes.svelte | 2 +- .../documents/sidebar/Sections.svelte | 2 +- .../stories/NoteHighlights.stories.svelte | 4 +- .../stories/PageHighlights.stories.svelte | 4 +- .../stories/ResultsList.stories.svelte | 4 +- .../documents/tests/ResultsList.test.ts | 2 +- .../components/documents/tests/Share.test.ts | 8 +- src/lib/components/forms/AddOnDispatch.svelte | 4 +- src/lib/components/forms/BulkActions.svelte | 4 +- src/lib/components/forms/ConfirmDelete.svelte | 8 +- .../components/forms/DocumentUpload.svelte | 48 ++- src/lib/components/forms/Edit.svelte | 7 +- src/lib/components/forms/EditData.svelte | 4 +- src/lib/components/forms/EditDataMany.svelte | 4 +- src/lib/components/forms/EditNote.svelte | 4 +- .../components/forms/EditSectionRow.svelte | 12 +- src/lib/components/forms/EditSections.svelte | 12 +- src/lib/components/forms/Projects.svelte | 8 +- src/lib/components/forms/Reprocess.svelte | 8 +- src/lib/components/forms/Search.svelte | 5 +- src/lib/components/forms/UserFeedback.svelte | 6 +- .../forms/stories/EditNote.stories.svelte | 2 +- .../stories/RemoveCollaborator.stories.svelte | 2 +- .../stories/UpdateCollaborator.stories.svelte | 2 +- src/lib/components/inputs/AccessLevel.svelte | 2 +- src/lib/components/inputs/Checkbox.svelte | 2 +- src/lib/components/inputs/Choices.svelte | 2 +- src/lib/components/inputs/Dropzone.svelte | 3 +- src/lib/components/inputs/File.svelte | 8 +- src/lib/components/inputs/Number.svelte | 10 +- .../components/inputs/ProjectAccess.svelte | 2 +- src/lib/components/inputs/Selection.svelte | 6 +- src/lib/components/inputs/Switch.svelte | 10 +- src/lib/components/inputs/Text.svelte | 2 +- src/lib/components/inputs/TextArea.svelte | 2 +- .../components/layouts/AddOnBrowser.svelte | 26 +- src/lib/components/layouts/AddOnLayout.svelte | 14 +- .../components/layouts/DocumentBrowser.svelte | 20 +- .../components/layouts/DocumentLayout.svelte | 16 +- src/lib/components/layouts/Flatpage.svelte | 18 +- src/lib/components/layouts/Modal.svelte | 13 +- src/lib/components/layouts/Navigation.svelte | 36 +- src/lib/components/layouts/Sidebar.svelte | 6 +- src/lib/components/layouts/Toaster.svelte | 10 +- .../components/layouts/tests/Toaster.test.ts | 4 +- .../components/navigation/LanguageMenu.svelte | 2 +- src/lib/components/navigation/OrgMenu.svelte | 7 +- .../navigation/tests/Breadcrumbs.test.ts | 6 +- .../components/onboarding/GuidedTour.svelte | 13 +- src/lib/components/processing/AddOns.svelte | 37 +- .../components/processing/Documents.svelte | 60 +-- .../processing/ProcessContext.svelte | 2 +- .../processing/ProcessDrawer.svelte | 6 +- .../processing/stories/AddOns.stories.svelte | 2 - .../stories/Documents.stories.svelte | 2 - .../stories/ProcessDrawer.stories.svelte | 3 - .../components/projects/Collaborators.svelte | 9 +- src/lib/components/projects/ProjectPin.svelte | 8 +- .../components/projects/ProjectShare.svelte | 1 - .../stories/ProjectActions.stories.svelte | 1 - .../components/sidebar/SidebarGroup.svelte | 2 +- src/lib/components/sidebar/SidebarItem.svelte | 10 +- .../components/viewer/AnnotationLayer.svelte | 18 +- src/lib/components/viewer/Grid.svelte | 4 +- src/lib/components/viewer/Note.svelte | 25 +- src/lib/components/viewer/NoteTab.svelte | 2 +- src/lib/components/viewer/Notes.svelte | 2 +- src/lib/components/viewer/PDFPage.svelte | 5 +- src/lib/components/viewer/Page.svelte | 14 +- .../components/viewer/ReadingToolbar.svelte | 2 +- .../components/viewer/RedactionLayer.svelte | 19 +- src/lib/components/viewer/Text.svelte | 2 +- .../components/viewer/ViewerContext.svelte | 13 +- .../stories/AnnotationLayer.stories.svelte | 4 +- .../viewer/stories/PDFPage.stories.svelte | 6 +- .../viewer/stories/Viewer.stories.svelte | 4 +- src/lib/i18n/index.js | 4 +- src/lib/utils/api.ts | 7 +- src/lib/utils/embed.ts | 353 +++++++++--------- src/lib/utils/files.ts | 8 +- .../pageSize.js => lib/utils/pageSize.ts} | 2 +- src/lib/utils/permissions.ts | 6 +- src/lib/utils/search.ts | 6 +- src/lib/utils/storage.ts | 4 +- src/lib/utils/tests/api.test.ts | 6 +- .../utils/tests/pageSize.test.ts} | 2 +- src/lib/utils/tests/permissions.test.ts | 2 +- src/lib/utils/viewer.ts | 11 +- src/premium-credits/CreditMeter.svelte | 9 +- src/premium-credits/UpgradePrompt.svelte | 2 +- src/routes/(app)/+error.svelte | 4 +- src/routes/(app)/+layout.svelte | 4 +- src/routes/(app)/+layout.ts | 3 +- src/routes/(app)/+page.server.ts | 2 +- src/routes/(app)/add-ons/+error.svelte | 4 +- .../add-ons/[owner]/[repo]/+error.svelte | 4 +- .../add-ons/[owner]/[repo]/+page.server.ts | 14 +- .../[owner]/[repo]/[event]/+page.server.ts | 26 +- .../[owner]/[repo]/[event]/+page.svelte | 2 +- .../add-ons/[owner]/[repo]/[event]/+page.ts | 3 + src/routes/(app)/documents/+error.svelte | 4 +- src/routes/(app)/documents/+page.server.ts | 11 +- src/routes/(app)/documents/+page.svelte | 2 - src/routes/(app)/documents/+page.ts | 2 +- .../(app)/documents/[id]-[slug]/+error.svelte | 4 +- .../documents/[id]-[slug]/+page.server.ts | 31 +- .../(app)/documents/[id]-[slug]/+page.svelte | 3 +- .../(app)/documents/sidebar/Actions.svelte | 13 +- src/routes/(app)/projects/+error.svelte | 4 +- src/routes/(app)/projects/+page.server.ts | 4 + src/routes/(app)/projects/+page.svelte | 7 +- src/routes/(app)/projects/+page.ts | 8 +- .../(app)/projects/[id]-[slug]/+error.svelte | 4 +- .../projects/[id]-[slug]/+page.server.ts | 39 +- .../(app)/projects/[id]-[slug]/+page.ts | 11 +- src/routes/(app)/projects/[id]/+page.ts | 4 + src/routes/(app)/upload/+error.svelte | 4 +- src/routes/(app)/upload/+page.server.ts | 4 + src/routes/(app)/upload/+page.svelte | 2 +- src/routes/(pages)/+error.svelte | 4 +- src/routes/(pages)/[...path]/+page.ts | 5 +- src/routes/(pages)/home/+page.ts | 4 + src/routes/+error.svelte | 4 +- src/routes/+layout.svelte | 3 +- .../[id]/annotations/[note_id]/+page.svelte | 10 +- .../[id]/annotations/[note_id]/+page.ts | 5 + .../documents/[id]/pages/[page]/+page.svelte | 37 +- .../documents/[id]/pages/[page]/+page.ts | 8 +- .../[id]/pages/[page]/Annotation.svelte | 2 +- .../projects/[project_id]-[slug]/+page.ts | 9 +- src/test/components/UserContext.demo.svelte | 2 +- .../documents/examples/the-santa-anas.json | 1 - src/test/handlers/projects.ts | 6 +- vite.config.js | 2 +- 178 files changed, 1019 insertions(+), 746 deletions(-) rename src/{api/pageSize.js => lib/utils/pageSize.ts} (98%) rename src/{api/pageSize.test.js => lib/utils/tests/pageSize.test.ts} (92%) diff --git a/src/addons/AddOnPin.svelte b/src/addons/AddOnPin.svelte index aed8124e0..8e4125dc4 100644 --- a/src/addons/AddOnPin.svelte +++ b/src/addons/AddOnPin.svelte @@ -19,6 +19,10 @@ event.preventDefault(); const csrftoken = getCsrfToken(); + if (!csrftoken) { + console.error("No CSRF token found"); + return; + } const options: RequestInit = { credentials: "include", method: "PATCH", // this component can only update whether an addon is active or not diff --git a/src/addons/types.ts b/src/addons/types.ts index 86ed8cf9e..b7cbbc78a 100644 --- a/src/addons/types.ts +++ b/src/addons/types.ts @@ -60,8 +60,8 @@ interface AddOnParameters { // https://api.www.documentcloud.org/api/addons/ export interface AddOnListItem { id: number; - user: number; - organization: number; + user: null | number; + organization: null | number; access: "public" | "private"; name: string; repository: string; diff --git a/src/api/session.ts b/src/api/session.ts index 3ed07a6e6..97c4da24e 100644 --- a/src/api/session.ts +++ b/src/api/session.ts @@ -25,7 +25,7 @@ export function getCsrfToken() { ?.split(";") ?.map((c) => c.split("=")) // in case there's spaces in the cookie string, trim the key - ?.find(([k, v]) => k.trim() === CSRF_COOKIE_NAME) ?? []; + ?.find(([k, v]) => k?.trim() === CSRF_COOKIE_NAME) ?? []; return token; } diff --git a/src/api/types/project.ts b/src/api/types/project.ts index 08af727dd..41eedc08f 100644 --- a/src/api/types/project.ts +++ b/src/api/types/project.ts @@ -7,7 +7,7 @@ export interface Project { private: boolean; created_at: string; updated_at: string; - edit_access: boolean; - add_remove_access: boolean; + edit_access: null | boolean; + add_remove_access: null | boolean; pinned?: boolean; } diff --git a/src/common/Button.svelte b/src/common/Button.svelte index ae60e78e6..f61074197 100644 --- a/src/common/Button.svelte +++ b/src/common/Button.svelte @@ -23,7 +23,7 @@ export let type: "submit" | "reset" | "button" = "submit"; export let label = "Submit"; - export let disabledReason = null; + export let disabledReason = ""; diff --git a/src/common/RelativeTime.svelte b/src/common/RelativeTime.svelte index 182e1e68f..2a8feafdb 100644 --- a/src/common/RelativeTime.svelte +++ b/src/common/RelativeTime.svelte @@ -3,7 +3,7 @@ export let date: Date; - const relativeFormatter = new Intl.RelativeTimeFormat($locale, { + const relativeFormatter = new Intl.RelativeTimeFormat($locale ?? "en", { style: "long", }); @@ -24,23 +24,30 @@ function formatTimeAgo(date: Date): string { let duration = (date.getTime() - new Date().getTime()) / 1000; + let formatted: string = ""; for (let i = 0; i < DIVISIONS.length; i++) { - const division = DIVISIONS[i]; + const division = DIVISIONS[i]!; if (Math.abs(duration) < division.amount) { - return relativeFormatter.format(Math.round(duration), division.name); + formatted = relativeFormatter.format( + Math.round(duration), + division.name, + ); + break; } duration /= division.amount; } + + return formatted; } + + - - diff --git a/src/langs/json/en.json b/src/langs/json/en.json index 7d7b022d4..11b1f79a1 100644 --- a/src/langs/json/en.json +++ b/src/langs/json/en.json @@ -1038,7 +1038,8 @@ "memberMessage": "This Premium Add-On uses AI to perform advanced analysis. Contact your organization admin about upgrading your plan." }, "dispatch": "Dispatch", - "history": "History" + "history": "History", + "noHistory": "You have not run this add-on before" }, "addonBrowserDialog": { "title": "Browse Add-Ons", @@ -1100,6 +1101,7 @@ "mailkey": { "title": "Upload via email", "description": "

You can upload documents to your account by sending them to to a special email address as attachments.

For security reasons, this email is only shown to you once. Please copy it to a secure location.

This address will accept attachments from any email account. Documents are uploaded as private. Generating a new upload address will disable any previously created addresses.

We’d love your feedback and to hear about creative use cases at info@documentcloud.org.

", + "missing_csrf": "Missing CSRF token", "create": { "button": "Create new email address", "success": "Upload email address succesfully created:
{mailkey}@uploads.documentcloud.org", @@ -1134,6 +1136,7 @@ "processing": { "addons": "Add-Ons", "documents": "Documents", + "unknown": "Unknown", "totalCount": "{n} active {n, plural, one {process} other {processes}}" }, "feedback": { diff --git a/src/lib/api/addons.ts b/src/lib/api/addons.ts index e0f458301..4eaaaed98 100644 --- a/src/lib/api/addons.ts +++ b/src/lib/api/addons.ts @@ -69,10 +69,11 @@ export async function getAddon( const repository = [owner, repo].join("/"); const { data: addons, error } = await getAddons({ repository }, fetch); // there should only be one result, if the addon exists - if (error || addons.results.length < 1) { + + if (error || !addons || addons.results.length < 1) { return null; } - return addons.results[0]; + return addons.results[0] ?? null; } export async function getEvent( @@ -298,17 +299,15 @@ export function buildPayload( const payload: AddOnPayload = { addon: addon.id, parameters }; const selection = formData.get("selection"); + const documents = formData.get("documents"); + const query = formData.get("query"); - if (selection === "selected") { - payload.documents = formData - .get("documents") - .toString() - .split(",") - .map(Number); + if (selection === "selected" && documents) { + payload.documents = documents.toString().split(",").map(Number); } - if (selection === "query") { - payload.query = formData.get("query").toString(); + if (selection === "query" && query) { + payload.query = query.toString(); } if (formData.has("event")) { diff --git a/src/lib/api/collaborators.ts b/src/lib/api/collaborators.ts index a4849714c..b4302f8b1 100644 --- a/src/lib/api/collaborators.ts +++ b/src/lib/api/collaborators.ts @@ -49,7 +49,7 @@ export async function add( export async function update( project_id: number, user_id: number, - access: ProjectAccess, + access: string, csrf_token: string, fetch = globalThis.fetch, ): Promise> { diff --git a/src/lib/api/documents.ts b/src/lib/api/documents.ts index 94272c3a8..6f02eae92 100644 --- a/src/lib/api/documents.ts +++ b/src/lib/api/documents.ts @@ -251,7 +251,7 @@ export async function process( }[], csrf_token: string, fetch = globalThis.fetch, -): Promise> { +): Promise> { const endpoint = new URL("documents/process/", BASE_API_URL); const resp = await fetch(endpoint, { @@ -560,9 +560,9 @@ export function pageFromHash(hash: string): Nullable { const re = /^#document\/p(\d+)/; // match pages and notes const match = re.exec(hash); - if (!match) return null; + if (!match || !match[1]) return null; - return +match[1] || null; + return +match[1]; } /** diff --git a/src/lib/api/notes.ts b/src/lib/api/notes.ts index 9635f37d5..0c9062b08 100644 --- a/src/lib/api/notes.ts +++ b/src/lib/api/notes.ts @@ -4,6 +4,8 @@ import type { Document, Note, NoteResults, + Nullable, + Page, ValidationError, } from "./types"; @@ -22,18 +24,19 @@ import { getApiResponse, isErrorCode } from "../utils"; * @example https://api.www.documentcloud.org/api/documents/2622/notes/ * @deprecated */ -export async function list(doc_id: number, fetch = globalThis.fetch) { +export async function list( + doc_id: number, + fetch = globalThis.fetch, +): Promise>> { const endpoint = new URL(`documents/${doc_id}/notes/`, BASE_API_URL); endpoint.searchParams.set("expand", DEFAULT_EXPAND); - const resp = await fetch(endpoint, { credentials: "include" }); - - if (isErrorCode(resp.status)) { - throw new Error(resp.statusText); - } + const resp = await fetch(endpoint, { credentials: "include" }).catch( + console.error, + ); - return resp.json(); + return getApiResponse>(resp); } /** @@ -176,13 +179,12 @@ export function noteHashUrl(note: Note): string { * To get the page number, use pageFromHash * @param hash */ -export function noteFromHash(hash: string): number { +export function noteFromHash(hash: string): Nullable { const re = /^#document\/p(\d+)\/a(\d+)$/; const match = re.exec(hash); - if (!match) return null; - - return +match[2] || null; + if (!match || !match[2]) return null; + return +match[2]; } /** Width of a note, relative to the document */ diff --git a/src/lib/api/sections.ts b/src/lib/api/sections.ts index 25a593713..fddcc8946 100644 --- a/src/lib/api/sections.ts +++ b/src/lib/api/sections.ts @@ -74,11 +74,17 @@ export async function get( export async function create( doc_id: string | number, section: { page_number: number; title: string }, - csrf_token: string, + csrf_token: string | undefined, fetch = globalThis.fetch, ): Promise> { const endpoint = new URL(`documents/${doc_id}/sections/`, BASE_API_URL); + if (!csrf_token) { + return Promise.reject({ + error: { status: 403, message: "CSRF token required" }, + }); + } + const resp = await fetch(endpoint, { credentials: "include", body: JSON.stringify(section), @@ -100,7 +106,7 @@ export async function update( doc_id: string | number, section_id: string | number, section: { page_number?: number; title?: string }, - csrf_token: string, + csrf_token: string | undefined, fetch = globalThis.fetch, ): Promise> { const endpoint = new URL( @@ -108,6 +114,12 @@ export async function update( BASE_API_URL, ); + if (!csrf_token) { + return Promise.reject({ + error: { status: 403, message: "CSRF token required" }, + }); + } + const resp = await fetch(endpoint, { credentials: "include", body: JSON.stringify(section), @@ -128,7 +140,7 @@ export async function update( export async function remove( doc_id: string | number, section_id: string | number, - csrf_token: string, + csrf_token: string | undefined, fetch = globalThis.fetch, ): Promise> { const endpoint = new URL( @@ -136,6 +148,12 @@ export async function remove( BASE_API_URL, ); + if (!csrf_token) { + return Promise.reject({ + error: { status: 403, message: "CSRF token required" }, + }); + } + const resp = await fetch(endpoint, { credentials: "include", method: "DELETE", diff --git a/src/lib/api/tests/addons.test.ts b/src/lib/api/tests/addons.test.ts index 578e9bda6..ab3ccad8c 100644 --- a/src/lib/api/tests/addons.test.ts +++ b/src/lib/api/tests/addons.test.ts @@ -103,7 +103,7 @@ describe("getAddon", async () => { describe("addon payloads", () => { test("buildPayload single dispatch", () => { - const scraper = addonsList.results.find((a) => a.name === "Scraper"); + const scraper = addonsList.results.find((a) => a.name === "Scraper")!; const parameters = { site: "https://www.documentcloud.org", project: "test", @@ -118,7 +118,7 @@ describe("addon payloads", () => { }); test("buildPayload scheduled event", () => { - const scraper = addonsList.results.find((a) => a.name === "Scraper"); + const scraper = addonsList.results.find((a) => a.name === "Scraper")!; const parameters = { site: "https://www.documentcloud.org", project: "test", @@ -140,7 +140,7 @@ describe("addon payloads", () => { test("buildPayload array param", () => { const siteSnapshot = addonsList.results.find( (a) => a.name === "Site Snapshot", - ); + )!; const parameters = { sites: ["https://www.muckrock.com", "https://www.documentcloud.org"], project_id: 1, @@ -159,7 +159,7 @@ describe("addon payloads", () => { }); test("buildPayload remove blank values", () => { - const scraper = addonsList.results.find((a) => a.name === "Scraper"); + const scraper = addonsList.results.find((a) => a.name === "Scraper")!; const parameters = { site: "https://www.documentcloud.org", @@ -187,7 +187,7 @@ describe("addon payloads", () => { test("buildPayload with documents", () => { const translate = addonsList.results.find( (a) => a.name === "Translate Documents", - ); + )!; const parameters = { access_level: "public", project_id: 1, @@ -219,7 +219,7 @@ describe("addon payloads", () => { test("buildPayload with query", () => { const translate = addonsList.results.find( (a) => a.name === "Translate Documents", - ); + )!; const parameters = { access_level: "public", project_id: 1, diff --git a/src/lib/api/tests/collaborators.test.ts b/src/lib/api/tests/collaborators.test.ts index f4622c78f..dfebcf48a 100644 --- a/src/lib/api/tests/collaborators.test.ts +++ b/src/lib/api/tests/collaborators.test.ts @@ -74,7 +74,7 @@ describe("manage project users", () => { const { data } = await collaborators.add( project.id, - { email: me.email, access: "admin" }, + { email: me.email!, access: "admin" }, "token", mockFetch, ); @@ -105,7 +105,7 @@ describe("manage project users", () => { const { user, access } = JSON.parse(options.body); const updated: ProjectUser = users.results.find( (u) => u.user.id === 1020, - ); + )!; return { ok: true, @@ -119,7 +119,7 @@ describe("manage project users", () => { }; }); - const me = users.results.find((u) => u.user.id === 1020); + const me = users.results.find((u) => u.user.id === 1020)!; const { data: updated } = await collaborators.update( project.id, @@ -154,7 +154,7 @@ describe("manage project users", () => { }; }); - const me = users.results.find((u) => u.user.id === 1020); + const me = users.results.find((u) => u.user.id === 1020)!; const { data } = await collaborators.remove( project.id, me.user.id, diff --git a/src/lib/api/tests/documents.test.ts b/src/lib/api/tests/documents.test.ts index 6f7d937cf..c54dc1edb 100644 --- a/src/lib/api/tests/documents.test.ts +++ b/src/lib/api/tests/documents.test.ts @@ -32,7 +32,7 @@ const test = base.extend({ "@/test/fixtures/documents/search-highlight.json" ); - await use(results as DocumentResults); + await use(results as unknown as DocumentResults); }, document: async ({}, use: Use) => { @@ -300,13 +300,13 @@ describe("document uploads and processing", () => { ); const resp = await documents.upload( - new URL(doc.presigned_url), + new URL(doc.presigned_url!), file, mockFetch, ); expect(resp.ok).toBeTruthy(); - expect(mockFetch).toHaveBeenCalledWith(new URL(doc.presigned_url), { + expect(mockFetch).toHaveBeenCalledWith(new URL(doc.presigned_url!), { body: file, headers: { "Content-Type": file.type, @@ -508,7 +508,7 @@ describe("document write methods", () => { mockFetch, ); - expect(updated.title).toStrictEqual("Updated title"); + expect(updated?.title).toStrictEqual("Updated title"); }); test("documents.edit_many", async ({ documents: docs }) => { @@ -570,7 +570,7 @@ describe("document write methods", () => { mockFetch, ); - expect(data["_tag"]).toEqual(["one", "two"]); + expect(data?.["_tag"]).toEqual(["one", "two"]); expect(mockFetch).toBeCalledWith( new URL(`documents/${document.id}/data/_tag/`, BASE_API_URL), { diff --git a/src/lib/api/tests/notes.test.ts b/src/lib/api/tests/notes.test.ts index 37bba9d83..3bbaf8a94 100644 --- a/src/lib/api/tests/notes.test.ts +++ b/src/lib/api/tests/notes.test.ts @@ -184,6 +184,7 @@ describe("note helper methods", () => { }); test("isPageLevel", ({ note }) => { + // @ts-ignore const copy: Note = { ...note, x1: null, x2: null, y1: null, y2: null }; expect(notes.isPageLevel(copy)).toBeTruthy(); diff --git a/src/lib/api/tests/sections.test.ts b/src/lib/api/tests/sections.test.ts index 5d4d3718b..5a69eaf5e 100644 --- a/src/lib/api/tests/sections.test.ts +++ b/src/lib/api/tests/sections.test.ts @@ -75,7 +75,7 @@ describe("sections: writing", () => { }); test("sections.update", async ({ document, sectionList }) => { - const section = sectionList.results[0]; + const section = sectionList.results[0]!; const mockFetch = vi.fn().mockImplementation(async (endpoint, options) => { const updated = { ...section, ...JSON.parse(options.body) }; @@ -118,7 +118,7 @@ describe("sections: writing", () => { }); test("sections.remove", async ({ document, sectionList }) => { - const section = sectionList.results[0]; + const section = sectionList.results[0]!; const mockFetch = vi.fn().mockImplementation(async (endpoint, options) => { return { ok: true, diff --git a/src/lib/api/types.d.ts b/src/lib/api/types.d.ts index 9294d6a47..84e8ae827 100644 --- a/src/lib/api/types.d.ts +++ b/src/lib/api/types.d.ts @@ -90,8 +90,8 @@ interface AddOnParameters { // API endpoint https://api.www.documentcloud.org/api/addons/ export interface AddOnListItem { id: number; - user: number; - organization: number; + user: null | number; + organization: null | number; access: "public" | "private"; name: string; repository: string; diff --git a/src/lib/components/accounts/Avatar.svelte b/src/lib/components/accounts/Avatar.svelte index d7ec46879..f4e2b38e6 100644 --- a/src/lib/components/accounts/Avatar.svelte +++ b/src/lib/components/accounts/Avatar.svelte @@ -1,9 +1,9 @@
diff --git a/src/lib/components/accounts/Mailkey.svelte b/src/lib/components/accounts/Mailkey.svelte index 5a3e50857..f9d2b1fff 100644 --- a/src/lib/components/accounts/Mailkey.svelte +++ b/src/lib/components/accounts/Mailkey.svelte @@ -20,6 +20,11 @@ async function create() { reset(); const csrf_token = getCsrfToken(); + if (!csrf_token) { + error = true; + message = $_("mailkey.missing_csrf"); + return; + } const mailkey = await createMailkey(csrf_token, fetch); if (mailkey) { message = $_("mailkey.create.success", { @@ -34,6 +39,11 @@ async function destroy() { reset(); const csrf_token = getCsrfToken(); + if (!csrf_token) { + error = true; + message = $_("mailkey.missing_csrf"); + return; + } if (await destroyMailkey(csrf_token, fetch)) { message = $_("mailkey.destroy.success"); } else { diff --git a/src/lib/components/addons/AddOnMeta.svelte b/src/lib/components/addons/AddOnMeta.svelte index b7f538947..3f87d1064 100644 --- a/src/lib/components/addons/AddOnMeta.svelte +++ b/src/lib/components/addons/AddOnMeta.svelte @@ -2,7 +2,7 @@ import type { AddOnListItem } from "@/addons/types"; import { _ } from "svelte-i18n"; - import { MarkGithub16, Share16 } from "svelte-octicons"; + import { MarkGithub16 } from "svelte-octicons"; import Button from "$lib/components/common/Button.svelte"; import Flex from "@/lib/components/common/Flex.svelte"; @@ -34,10 +34,6 @@

{github_org}

-