diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..b38d80584 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +Significant changes to DocumentCloud's frontend will be noted in this file. We also announce new features in MuckRock's regular [Release Notes](https://www.muckrock.com/news/archives/?tags=115608) column. + +DocumentCloud is a running application used by thousands of journalists around the world, so updates here are organized by date, not by version number. The goal of this file is to keep a record of features added, updated and removed. + +## 2024-12-02 DocumentCloud on SvelteKit released + +This is the latest iteration of DocumentCloud, bringing the frontend to SvelteKit and updating the UI. + +Our goals: + +- Clean up the user interface to provide a consistent experience that scales across devices. +- Server-render pages, making them faster to load and easier to share. +- Simplify the application’s logic to make it easier to reason about its structure, reactivity and state, and make it easier to support new features. +- Modernize our technologies and development practices to speed up development, ensuring high-quality, bug free code from the start. diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 2108c89ca..1ed6a4643 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -33,10 +33,13 @@ export const handleError = Sentry.handleErrorWithSentry(); /** @type {import('@sveltejs/kit').Handle} */ async function language({ event, resolve }) { const lang = - event.request.headers.get("accept-language")?.split(",")[0] ?? "en-US"; + event.request.headers.get("accept-language")?.split(",")[0] ?? "en"; - if (lang) { - locale.set(lang); + // use en.json for en-US and such + const [language, ...tags] = lang.split("-"); + + if (language) { + locale.set(language); } return resolve(event, { diff --git a/src/langs/json/en.json b/src/langs/json/en.json index 0ec1235fb..ff6681be9 100644 --- a/src/langs/json/en.json +++ b/src/langs/json/en.json @@ -11,7 +11,10 @@ "title": "Title" }, "search": { - "matchingResults": "{n, plural, one {# matching result} other {# matching results}}" + "matchingResults": "{n, plural, one {# matching result} other {# matching results}}", + "reset": "Clear Search", + "help": "Use filters like user:, project: or organization: to refine searches. Use sort: to order results.", + "more": "Learn more" }, "homeTemplate": { "signedIn": "Signed in as {name}", @@ -203,7 +206,9 @@ "pageAbbrev": "p.", "pageCount": "{n, plural, one {# page} other {# pages}}", "select": "Select", - "noteCount": "{n, plural, one {# note} other {# notes}}" + "noteCount": "{n, plural, one {# note} other {# notes}}", + "more": "Load more", + "retry": "Please try again." }, "error": { "report": "Report a problem" @@ -247,7 +252,7 @@ "view": "Viewer" }, "delete": { - "action": "Delete project", + "action": "Delete Project", "confirm": "Confirm delete", "really": "Are you sure you want to delete this project ({project})?" }, @@ -258,7 +263,7 @@ "collaborators": { "title": "Collaborators", "empty": "No collaborators", - "add": "Invite users to this project" + "add": "Invite" }, "fields": { "title": "Title", @@ -345,15 +350,18 @@ "shareEmbed": "Share & Embed", "sharedWith": "Shared with", "revisions": "Revision History", - "edit": "Edit Metadata", + "edit": "Edit Document Metadata", "upload": "Upload Documents", "uploadToProject": "Upload to Project", "download": "Download File", "reprocess": "Reprocess", + "delete": "Delete", + "shareEmbedProject": "Share & Embed Project", + "editProject": "Edit Project Metadata", + "deleteProject": "Delete Project", "created": "Created on", "updated": "Last updated on", "language": "Language", - "delete": "Delete", "ocr_engine": "OCR Engine", "addons": { "title": "Add-Ons", @@ -382,7 +390,7 @@ "cta": "Annotate this document" }, "edit": { - "title": "Edit document metadata", + "title": "Edit Document Metadata", "fields": { "title": "Title", "description": "Description", @@ -417,10 +425,10 @@ } }, "redact": { + "title": "Redact Document", "confirm": "Save", "confirmTitle": "Save Redactions", "really": "Are you sure you wish to redact the current document? If you continue, the document viewer will be inaccessible temporarily while the document reprocesses with the redactions in place. This change is irreversible.", - "title": "Redact Document", "instructions": "Click and drag to draw a black rectangle over each portion of the document you’d like to redact. Associated text will be removed when you save your redactions.", "undo": "Undo", "cancel": "Discard", @@ -432,13 +440,13 @@ "page": "Page", "title": "Title", "delete": "Delete", - "new": "Add a new section", + "new": "Add New Section", "clear": "Clear", "update": "Update section", "overwrite": "There is already a section starting on page {n}." }, "delete": { - "title": "Confirm delete", + "title": "Confirm Delete", "really": "Proceeding will permanently delete the {n, plural, one {selected document} other {# selected documents}}.", "continue": "Do you wish to continue?", "confirm": "Delete", @@ -447,7 +455,7 @@ "none": "No documents selected. Close this form and select at least one document first." }, "data": { - "title": "Edit tags and data", + "title": "Edit Tags & Data", "key": "Key", "newkey": "New item", "value": "Value", @@ -457,15 +465,15 @@ "save": "Save", "cancel": "Cancel", "addNew": "Add new item:", - "many": "Saving will add the following data and tags to {n, plural, one {the selected document} other {all # selected documents}}. No tags or data will be removed." + "many": "Saving will add the following tags and data to {n, plural, one {the selected document} other {all # selected documents}}. No tags or data will be removed." }, "bulk": { - "title": "Edit", + "title": "Actions", "actions": { - "share": "Share", - "edit": "Edit metadata", - "data": "Edit tags & data", - "project": "Move to project", + "share": "Share & Embed", + "edit": "Edit Metadata", + "data": "Edit Tags & Data", + "project": "Move to Project", "reprocess": "Reprocess", "delete": "Delete" } diff --git a/src/lib/api/types.d.ts b/src/lib/api/types.d.ts index 7fb28597b..bef902f0f 100644 --- a/src/lib/api/types.d.ts +++ b/src/lib/api/types.d.ts @@ -1,9 +1,7 @@ /** * API response types - * - * This is a separate module from what's in src/api to prevent conflicts. - * Both modules can be merged later. - * */ + */ + import type { DefinedError } from "ajv"; export type Access = "public" | "private" | "organization"; // https://www.documentcloud.org/help/api#access-levels diff --git a/src/lib/components/accounts/tests/__snapshots__/UserMenu.test.ts.snap b/src/lib/components/accounts/tests/__snapshots__/UserMenu.test.ts.snap index d6f245ae9..e3e6ba138 100644 --- a/src/lib/components/accounts/tests/__snapshots__/UserMenu.test.ts.snap +++ b/src/lib/components/accounts/tests/__snapshots__/UserMenu.test.ts.snap @@ -9,7 +9,7 @@ exports[`UserMenu 1`] = ` tabindex="0" > Account settings @@ -93,7 +93,7 @@ exports[`UserMenu 1`] = ` Upload via email @@ -121,7 +121,7 @@ exports[`UserMenu 1`] = ` @@ -139,7 +139,7 @@ exports[`UserMenu 1`] = ` Sign out diff --git a/src/lib/components/addons/History.svelte b/src/lib/components/addons/History.svelte index d295a0987..f9f7d492a 100644 --- a/src/lib/components/addons/History.svelte +++ b/src/lib/components/addons/History.svelte @@ -3,7 +3,7 @@ import type { Run } from "$lib/api/types"; import { _ } from "svelte-i18n"; - import { History16, History24, Hourglass24 } from "svelte-octicons"; + import { Alert24, History16, History24, Hourglass24 } from "svelte-octicons"; import HistoryEvent from "./HistoryEvent.svelte"; import Paginator from "$lib/components/common/Paginator.svelte"; @@ -11,33 +11,38 @@ import SidebarItem from "../sidebar/SidebarItem.svelte"; import Empty from "../common/Empty.svelte"; + import { getApiResponse } from "$lib/utils/api"; + export let runs: Run[]; export let previous: Maybe> = undefined; export let next: Maybe> = undefined; export let loading = false; + let error: string = ""; + $: empty = runs.length === 0; // load the next set of results async function load(url: URL) { loading = true; - // todo: better error handling - const res = await fetch(url, { credentials: "include" }).catch( + const resp = await fetch(url, { credentials: "include" }).catch( console.error, ); - if (!res) return console.error("API error"); - if (!res.ok) { - console.error(res.statusText); - loading = false; + + const { data: results, error: err } = await getApiResponse>(resp); + + if (err) { + error = err.message; } - const results: Page = await res.json(); + if (results) { + runs = results.results; + next = results.next; + previous = results.previous; + } - runs = results.results; - next = results.next; - previous = results.previous; loading = false; } @@ -50,6 +55,10 @@ {#if loading} Loading past runs… + {:else if error} + + {error} + {:else} {#each runs as run} @@ -62,10 +71,10 @@ { + on:next={() => { if (next) load(new URL(next)); }} - on:previous={(e) => { + on:previous={() => { if (previous) load(new URL(previous)); }} /> diff --git a/src/lib/components/addons/Scheduled.svelte b/src/lib/components/addons/Scheduled.svelte index 10428c2bd..4f197a9a1 100644 --- a/src/lib/components/addons/Scheduled.svelte +++ b/src/lib/components/addons/Scheduled.svelte @@ -2,7 +2,7 @@ import type { Maybe, Nullable, Page, Event } from "$lib/api/types"; import { _ } from "svelte-i18n"; - import { Clock16, Hourglass24 } from "svelte-octicons"; + import { Alert24, Clock16, Hourglass24 } from "svelte-octicons"; import ScheduledEvent from "./ScheduledEvent.svelte"; import SidebarGroup from "../sidebar/SidebarGroup.svelte"; @@ -10,31 +10,37 @@ import Paginator from "$lib/components/common/Paginator.svelte"; import Empty from "../common/Empty.svelte"; + import { getApiResponse } from "$lib/utils/api"; + export let events: Event[]; export let previous: Maybe> = undefined; export let next: Maybe> = undefined; export let loading = false; + let error: string = ""; + // load the next set of results async function load(url: URL) { loading = true; - // todo: better error handling - const res = await fetch(url, { credentials: "include" }).catch( + const resp = await fetch(url, { credentials: "include" }).catch( console.error, ); - if (!res) return console.error("API error"); - if (!res.ok) { - console.error(res.statusText); - loading = false; + + const { data: results, error: err } = + await getApiResponse>(resp); + + if (err) { + error = err.message; } - const results: Page = await res.json(); + if (results) { + events = results.results; + next = results.next; + previous = results.previous; + } - events = results.results; - next = results.next; - previous = results.previous; loading = false; } @@ -47,6 +53,10 @@ {#if loading} {$_("common.loading")} + {:else if error} + + {error} + {:else} {#each events as event} @@ -61,12 +71,12 @@ { + on:next={() => { if (next) { load(new URL(next)); } }} - on:previous={(e) => { + on:previous={() => { if (previous) { load(new URL(previous)); } diff --git a/src/lib/components/addons/tests/__snapshots__/AddOnsNavigation.test.ts.snap b/src/lib/components/addons/tests/__snapshots__/AddOnsNavigation.test.ts.snap index 77eee7d47..e297a7bd7 100644 --- a/src/lib/components/addons/tests/__snapshots__/AddOnsNavigation.test.ts.snap +++ b/src/lib/components/addons/tests/__snapshots__/AddOnsNavigation.test.ts.snap @@ -24,7 +24,7 @@ exports[`AddOnsNavigation 1`] = ` style="display: contents; --hover-background: var(--blue-2);" > @@ -43,7 +43,7 @@ exports[`AddOnsNavigation 1`] = ` All @@ -56,7 +56,7 @@ exports[`AddOnsNavigation 1`] = ` style="display: contents; --hover-background: var(--orange-2);" > @@ -80,7 +80,7 @@ exports[`AddOnsNavigation 1`] = ` Pinned @@ -93,7 +93,7 @@ exports[`AddOnsNavigation 1`] = ` style="display: contents; --hover-background: var(--yellow-2);" > @@ -112,7 +112,7 @@ exports[`AddOnsNavigation 1`] = ` Featured @@ -125,7 +125,7 @@ exports[`AddOnsNavigation 1`] = ` style="display: contents; --hover-background: var(--green-2);" > @@ -155,7 +155,7 @@ exports[`AddOnsNavigation 1`] = ` Premium @@ -172,14 +172,14 @@ exports[`AddOnsNavigation 1`] = ` style="display: contents; --color: var(--gray-4);" > Collections @@ -189,13 +189,13 @@ exports[`AddOnsNavigation 1`] = ` AI @@ -204,13 +204,13 @@ exports[`AddOnsNavigation 1`] = ` Analyze @@ -219,13 +219,13 @@ exports[`AddOnsNavigation 1`] = ` Bulk @@ -234,13 +234,13 @@ exports[`AddOnsNavigation 1`] = ` Export @@ -249,13 +249,13 @@ exports[`AddOnsNavigation 1`] = ` Extract @@ -264,13 +264,13 @@ exports[`AddOnsNavigation 1`] = ` File @@ -279,13 +279,13 @@ exports[`AddOnsNavigation 1`] = ` Monitor diff --git a/src/lib/components/documents/Access.svelte b/src/lib/components/common/Access.svelte similarity index 81% rename from src/lib/components/documents/Access.svelte rename to src/lib/components/common/Access.svelte index a07c21644..bd8f56e13 100644 --- a/src/lib/components/documents/Access.svelte +++ b/src/lib/components/common/Access.svelte @@ -22,7 +22,7 @@ }, ]; - export function getLevel(access: Access): Level | undefined { + export function getLevel(access: Access): Maybe { return levels.find((level) => level.value === access); } @@ -30,16 +30,18 @@ -
- - {$_(level.title)} -
+{#if level} +
+ + {$_(level.title)} +
+{/if} diff --git a/src/lib/components/documents/Share.svelte b/src/lib/components/documents/Share.svelte index a2409e607..86f5959e4 100644 --- a/src/lib/components/documents/Share.svelte +++ b/src/lib/components/documents/Share.svelte @@ -363,6 +363,7 @@ flex-direction: column; flex: 1 1 12rem; grid-row: 2/3; + min-width: 0; } .right { flex: 2 1 24rem; diff --git a/src/lib/components/documents/stories/ResultsList.stories.svelte b/src/lib/components/documents/stories/ResultsList.stories.svelte index c11f64b9a..8150abf5f 100644 --- a/src/lib/components/documents/stories/ResultsList.stories.svelte +++ b/src/lib/components/documents/stories/ResultsList.stories.svelte @@ -7,6 +7,7 @@ import Unverified from "../../accounts/Unverified.svelte"; import { me } from "@/test/fixtures/accounts"; + import { documents as mock } from "@/test/handlers/documents"; // typescript complains without the type assertion import searchResults from "@/test/fixtures/documents/search-highlight.json"; @@ -24,7 +25,6 @@ export const meta = { title: "Components / Documents / Results list", component: ResultsList, - tags: ["autodocs"], }; const user = { ...me, verified_journalist: false }; @@ -57,3 +57,7 @@ + + + + diff --git a/src/lib/components/documents/tests/__snapshots__/DocumentListItem.test.ts.snap b/src/lib/components/documents/tests/__snapshots__/DocumentListItem.test.ts.snap index 7efac65dc..b99bbaaac 100644 --- a/src/lib/components/documents/tests/__snapshots__/DocumentListItem.test.ts.snap +++ b/src/lib/components/documents/tests/__snapshots__/DocumentListItem.test.ts.snap @@ -3,15 +3,15 @@ exports[`DocumentListItem > renders 1`] = `
Page 1, Quarterly Reports created pursuant to the letter from the National Archives and Records Administration dated June 1, 2018 renders 1`] = `

Quarterly Reports created pursuant to the letter from the National Archives and Records Administration dated June 1, 2018

renders 1`] = ` Public
+

20 pages - @@ -69,7 +70,7 @@ exports[`DocumentListItem > renders 1`] = `

diff --git a/src/lib/components/forms/Search.svelte b/src/lib/components/forms/Search.svelte index d38672ff9..efecc6869 100644 --- a/src/lib/components/forms/Search.svelte +++ b/src/lib/components/forms/Search.svelte @@ -11,6 +11,7 @@ export let query: string = ""; export let placeholder: string = $_("common.search"); export let action: Maybe = undefined; + export let id = "query"; let input: HTMLInputElement; let form: HTMLFormElement; @@ -48,50 +49,46 @@ on:reset={clear} bind:this={form} > -
+ diff --git a/src/lib/components/layouts/AddOnLayout.svelte b/src/lib/components/layouts/AddOnLayout.svelte index 541bbafca..bb82772f3 100644 --- a/src/lib/components/layouts/AddOnLayout.svelte +++ b/src/lib/components/layouts/AddOnLayout.svelte @@ -12,7 +12,13 @@ import { _ } from "svelte-i18n"; import { setContext } from "svelte"; - import { Clock16, History16, Hourglass24, Play16 } from "svelte-octicons"; + import { + Clock16, + History16, + Hourglass24, + Play16, + SidebarExpand16, + } from "svelte-octicons"; import AddOnDispatch, { values } from "../forms/AddOnDispatch.svelte"; import AddOnMeta from "../addons/AddOnMeta.svelte"; @@ -33,6 +39,13 @@ import { schedules } from "../addons/ScheduledEvent.svelte"; import { getProcessLoader } from "../processing/ProcessContext.svelte"; + import SidebarLayout from "./SidebarLayout.svelte"; + import Documents from "../sidebar/Documents.svelte"; + import Projects from "../sidebar/Projects.svelte"; + import AddOns from "../sidebar/AddOns.svelte"; + import Flex from "../common/Flex.svelte"; + import { sidebars } from "./Sidebar.svelte"; + import Button from "../common/Button.svelte"; export let addon: AddOnListItem; export let event: Event | null = null; @@ -87,141 +100,179 @@ } -
-
-
-
- (currentTab = "dispatch")} - > - - {$_("addonDispatchDialog.dispatch")} - - - (currentTab = "history")} - > - - {$_("addonDispatchDialog.history")} - + + + + + + +
+
+
+ {#if !addon.parameters.documents && $sidebars["navigation"] === false} +
+ +
+ {/if} + +
+
+ (currentTab = "dispatch")} + > + + {$_("addonDispatchDialog.dispatch")} + - {#if canSchedule} (currentTab = "scheduled")} + active={currentTab === "history"} + on:click={() => (currentTab = "history")} > - - {$_("addonDispatchDialog.scheduled")} + + {$_("addonDispatchDialog.history")} - {/if} -
-
- {#if currentTab === "scheduled"} - {#await scheduled} - {$_("common.loading")} - {:then scheduled} - {#if scheduled} - - {/if} - {/await} - {:else if currentTab === "history"} -
- {#await history} + + {#if canSchedule} + (currentTab = "scheduled")} + > + + {$_("addonDispatchDialog.scheduled")} + + {/if} +
+
+ {#if currentTab === "scheduled"} + {#await scheduled} {$_("common.loading")} - {:then history} - {#if history} - - {:else} - {$_("addonDispatchDialog.noHistory")} {/if} {/await} -
- {:else} - - - {#await search then results} - + {#await history} + {$_("common.loading")} + {:then history} + {#if history} + + {:else} + {$_("addonDispatchDialog.noHistory")} + {/if} + {/await} +
+ {:else} + + + {#await search then results} + + {/await} + + + {/if} + +
+ {#if addon.parameters.documents} +
+ + + {#if $sidebars["navigation"] === false} +
+ +
+ {/if} + + + +
+ + {#await search} + {$_("common.loading")} + {:then search} + {/await} - - {/if} - - - {#if addon.parameters.documents} -
- - - - - - {#await search} - {$_("common.loading")} - {:then search} - - {/await} - - - + + - - {#if $visible && $total} - {$_("inputs.resultsCount", { - values: { n: $visible.size, total: $total }, - })} - {/if} - - - -
- {/if} -
+ + {#if $visible && $total} + {$_("inputs.resultsCount", { + values: { n: $visible.size, total: $total }, + })} + {/if} + + + +
+ {/if} +
+ diff --git a/src/lib/components/layouts/DocumentBrowser.svelte b/src/lib/components/layouts/DocumentBrowser.svelte index f04d5d359..a1d0102a0 100644 --- a/src/lib/components/layouts/DocumentBrowser.svelte +++ b/src/lib/components/layouts/DocumentBrowser.svelte @@ -36,7 +36,7 @@ // Form components import Dropzone from "$lib/components/inputs/Dropzone.svelte"; - import BulkActions from "$lib/components/documents/BulkActions.svelte"; + import DocumentActions from "$lib/components/sidebar/DocumentActions.svelte"; import Search from "$lib/components/forms/Search.svelte"; import { filesToUpload, @@ -72,10 +72,10 @@ export let query: string = ""; export let project: Nullable = null; export let uiText: UITextProps = { - loading: $_("common.loading"), - error: $_("common.error"), - empty: $_("common.empty"), - search: $_("common.search"), + loading: "common.loading", + error: "common.error", + empty: "common.empty", + search: "common.search", }; let width: number; @@ -154,7 +154,14 @@
- + + + {@html $_("search.help")} + + {$_("search.more")} + + +
@@ -173,10 +180,10 @@ {/if} {#await searchResults} - {uiText.loading} + {$_(uiText.loading)} {:then documentsResults} {#if !query && !documentsResults.results?.length} - {uiText.empty} + {$_(uiText.empty)} {:else} {/if} {:catch} - {uiText.error} + {$_(uiText.error)} {/await} {#if !embed} @@ -226,7 +233,7 @@ - close()} /> + close()} /> diff --git a/src/lib/components/layouts/DocumentLayout.svelte b/src/lib/components/layouts/DocumentLayout.svelte index 1c02925bb..e2a26123f 100644 --- a/src/lib/components/layouts/DocumentLayout.svelte +++ b/src/lib/components/layouts/DocumentLayout.svelte @@ -3,16 +3,11 @@ Assumes it's a child of a ViewerContext --> - + + +
@@ -48,8 +51,12 @@
+ +

Document Actions

+ +

Project Actions

- +
@@ -72,4 +79,13 @@ border: 1px solid var(--gray-2); box-shadow: inset var(--shadow-2); } + h4 { + margin: 0; + font-size: var(--font-xs); + font-weight: 600; + color: var(--gray-4); + text-transform: uppercase; + letter-spacing: 1px; + padding: 0.5rem; + } diff --git a/src/lib/components/layouts/Sidebar.svelte b/src/lib/components/layouts/Sidebar.svelte index 2d9a3b5fa..ccdd40718 100644 --- a/src/lib/components/layouts/Sidebar.svelte +++ b/src/lib/components/layouts/Sidebar.svelte @@ -130,7 +130,7 @@ display: flex; flex-direction: column; position: relative; - gap: 1rem; + gap: 0.5rem; height: 100%; z-index: 0; } diff --git a/src/lib/components/layouts/stories/AppLayout.stories.svelte b/src/lib/components/layouts/stories/AppLayout.stories.svelte index 090437329..a60b768df 100644 --- a/src/lib/components/layouts/stories/AppLayout.stories.svelte +++ b/src/lib/components/layouts/stories/AppLayout.stories.svelte @@ -6,12 +6,12 @@ import DocumentBrowser from "../DocumentBrowser.svelte"; import SidebarLayout from "../SidebarLayout.svelte"; - import Documents from "@/routes/(app)/documents/sidebar/Documents.svelte"; - import Projects from "@/routes/(app)/documents/sidebar/Projects.svelte"; + import Documents from "@/lib/components/sidebar/Documents.svelte"; + import Projects from "@/lib/components/sidebar/Projects.svelte"; import Button from "../../common/Button.svelte"; import { PlusCircle16 } from "svelte-octicons"; - import BulkActions from "$lib/components/documents/BulkActions.svelte"; - import AddOns from "$lib/components/common/AddOns.svelte"; + import BulkActions from "@/lib/components/sidebar/DocumentActions.svelte"; + import AddOns from "@/lib/components/sidebar/AddOns.svelte"; import { documentsList } from "@/test/fixtures/documents"; import { addons } from "@/test/handlers/addons"; @@ -30,6 +30,9 @@ stores: { page: { url: "/", + data: { + pinnedAddons: Promise.resolve({ data: activeAddons }), + }, }, }, }, @@ -47,6 +50,7 @@ + @@ -56,7 +60,6 @@ {$_("sidebar.upload")} -
diff --git a/src/lib/components/layouts/stories/SidebarLayout.stories.svelte b/src/lib/components/layouts/stories/SidebarLayout.stories.svelte index cfbe4faf5..6eb1b5282 100644 --- a/src/lib/components/layouts/stories/SidebarLayout.stories.svelte +++ b/src/lib/components/layouts/stories/SidebarLayout.stories.svelte @@ -2,16 +2,14 @@ import { Story, Template } from "@storybook/addon-svelte-csf"; import { _ } from "svelte-i18n"; - import { PlusCircle16 } from "svelte-octicons"; import SidebarLayout from "../SidebarLayout.svelte"; import DocumentBrowser from "../DocumentBrowser.svelte"; - import Button from "$lib/components/common/Button.svelte"; - - import BulkActions from "$lib/components/documents/BulkActions.svelte"; - import AddOns from "@/lib/components/common/AddOns.svelte"; - import Documents from "@/routes/(app)/documents/sidebar/Documents.svelte"; - import Projects from "@/routes/(app)/documents/sidebar/Projects.svelte"; + import UploadButton from "$lib/components/sidebar/UploadButton.svelte"; + import DocumentActions from "$lib/components/sidebar/DocumentActions.svelte"; + import AddOnsNavigation from "$lib/components/sidebar/AddOns.svelte"; + import DocumentsNavigation from "$lib/components/sidebar/Documents.svelte"; + import ProjectsNavigation from "$lib/components/sidebar/Projects.svelte"; import { documentsList } from "@/test/fixtures/documents"; import { activeAddons } from "@/test/fixtures/addons"; @@ -29,6 +27,9 @@ stores: { page: { url: "/", + data: { + pinnedAddons: Promise.resolve({ data: activeAddons }), + }, }, }, }, @@ -44,18 +45,16 @@
- - + + + - - - + +
diff --git a/src/lib/components/navigation/OrgMenu.svelte b/src/lib/components/navigation/OrgMenu.svelte index 9c8b775bc..baf05a2c3 100644 --- a/src/lib/components/navigation/OrgMenu.svelte +++ b/src/lib/components/navigation/OrgMenu.svelte @@ -226,6 +226,7 @@ font-family: var(--font-sans, "Source Sans Pro"); font-weight: var(--font-semibold, 600); line-height: 1rem; + margin-bottom: 0; } .arrow { diff --git a/src/lib/components/projects/Collaborators.svelte b/src/lib/components/projects/Collaborators.svelte index 63f5d97fe..734024c88 100644 --- a/src/lib/components/projects/Collaborators.svelte +++ b/src/lib/components/projects/Collaborators.svelte @@ -26,6 +26,7 @@ import { getUserName } from "$lib/api/accounts"; import { getCurrentUser } from "@/lib/utils/permissions"; + import Tooltip from "../common/Tooltip.svelte"; export let project: Project; export let users: ProjectUser[]; @@ -69,77 +70,112 @@ getUserName(a.user).localeCompare(getUserName(b.user)), ); } - - - - - {$_("projects.collaborators.title")} - + function group(users: ProjectUser[]) { + const groups: Record = { + admin: [], + edit: [], + view: [], + }; + + users.forEach((user) => { + groups[user.access].push(user); + }); + + return groups; + } + - {#if users.length > 0 && project.add_remove_access} - (show = "invite")}> - - {$_("projects.collaborators.add")} +{#if users.length > 0 || project.add_remove_access} + + + + {$_("projects.collaborators.title")} - {/if} - - {#each sort(users) as user} - {#if isProjectUser || project.edit_access} - - - {getUserName(user.user)} - - {$_(accessLabels[user.access])} - {#if project.add_remove_access && !isMe(user, $me)} - - - {/if} - - - {:else} - - - {getUserName(user.user)} - - {/if} - {:else} - - {$_("projects.collaborators.empty")} + + {#if project.add_remove_access} - (show = "invite")}> + + {/if} + + + {#each Object.entries(group(sort(users))) as [key, members]} + {#if members.length} +
+

{$_(accessLabels[key])}

+
{/if} -
- {/each} -
+ {#each members as user} + {#if isProjectUser || project.edit_access} + + + {getUserName(user.user)} + + {#if project.add_remove_access && !isMe(user, $me)} + + + + + + + {/if} + + + {:else} + + + {getUserName(user.user)} + + {/if} + {/each} + {:else} + + {$_("projects.collaborators.empty")} + {#if project.add_remove_access} + (show = "invite")}> + {$_("projects.collaborators.add")} + + {/if} + + {/each} +
+{/if} -{#if show} +{#if show && project.add_remove_access}

{actions[show]}

@@ -160,14 +196,13 @@ {/if} diff --git a/src/lib/components/projects/ProjectActions.svelte b/src/lib/components/projects/ProjectActions.svelte deleted file mode 100644 index b92545f61..000000000 --- a/src/lib/components/projects/ProjectActions.svelte +++ /dev/null @@ -1,127 +0,0 @@ - - - - -
- {#if isSignedIn($me) && canUploadFiles($me) && project.edit_access} - - {/if} - - {#if project.edit_access || project.add_remove_access} - -
- {#if project.edit_access} - - {/if} - - {#if project.edit_access} - - {/if} -
- {/if} - - -
- - - -
-
- -{#if show} - - -

- {actions[show]} -

- {#if show === "edit"} - - {/if} - - {#if show === "share"} - - {/if} - - {#if show === "delete"} - - {/if} -
-
-{/if} - - diff --git a/src/lib/components/projects/ProjectHeader.svelte b/src/lib/components/projects/ProjectHeader.svelte index 4a1d3391b..b96016d36 100644 --- a/src/lib/components/projects/ProjectHeader.svelte +++ b/src/lib/components/projects/ProjectHeader.svelte @@ -5,6 +5,7 @@ import Flex from "$lib/components/common/Flex.svelte"; import { remToPx } from "$lib/utils/layout"; import ProjectPin from "./ProjectPin.svelte"; + import Access, { getLevel } from "../common/Access.svelte"; export let project: Project; export let show = { @@ -29,9 +30,9 @@ {#if show.access}
{#if project.private} - {$_("projects.access.private")} + {:else} - {$_("projects.access.public")} + {/if}
{/if} diff --git a/src/lib/components/projects/ProjectPin.svelte b/src/lib/components/projects/ProjectPin.svelte index eaead0c1b..bf4558268 100644 --- a/src/lib/components/projects/ProjectPin.svelte +++ b/src/lib/components/projects/ProjectPin.svelte @@ -1,11 +1,7 @@ - + diff --git a/src/lib/components/common/AddOns.svelte b/src/lib/components/sidebar/AddOns.svelte similarity index 55% rename from src/lib/components/common/AddOns.svelte rename to src/lib/components/sidebar/AddOns.svelte index 4363eecae..a9c8e8ba6 100644 --- a/src/lib/components/common/AddOns.svelte +++ b/src/lib/components/sidebar/AddOns.svelte @@ -1,29 +1,30 @@ -{#each Object.entries(actions) as [action, [label, icon]]} - { - show(action); - afterClick?.(); - }} - > - - {label} - -{/each} + + {#each Object.entries(actions) as [action, [label, icon, mode]]} + + {/each} + {#if visible} diff --git a/src/routes/(app)/documents/sidebar/Documents.svelte b/src/lib/components/sidebar/Documents.svelte similarity index 67% rename from src/routes/(app)/documents/sidebar/Documents.svelte rename to src/lib/components/sidebar/Documents.svelte index fb74e5771..efafb2fdf 100644 --- a/src/routes/(app)/documents/sidebar/Documents.svelte +++ b/src/lib/components/sidebar/Documents.svelte @@ -5,15 +5,15 @@ import { getContext } from "svelte"; import { _ } from "svelte-i18n"; import { + Search16, File16, Globe16, - Infinity16, Lock16, Organization16, + Person16, } from "svelte-octicons"; import { page } from "$app/stores"; - import Flex from "$lib/components/common/Flex.svelte"; import SidebarItem from "$lib/components/sidebar/SidebarItem.svelte"; import SignedIn from "$lib/components/common/SignedIn.svelte"; @@ -21,6 +21,8 @@ import { slugify } from "$lib/utils/slugify"; import { userDocs } from "$lib/utils/search"; import { getCurrentUser } from "@/lib/utils/permissions"; + import SidebarGroup from "@/lib/components/sidebar/SidebarGroup.svelte"; + import Button from "@/lib/components/common/Button.svelte"; const me = getCurrentUser(); const org: Writable = getContext("org"); @@ -43,47 +45,61 @@ } - - - - {$_("documents.allDocuments", { - values: { access: "" }, - })} + + + + Documents - + - - + + {$_("documents.yourDocuments")} - - + {$_("documents.accessDocuments", { - values: { access: "Public " }, + values: { access: "Private " }, })} - + {$_("documents.accessDocuments", { - values: { access: "Private " }, + values: { access: "Public " }, })} {#if $org && !$org.individual} - - + + {$_("documents.nameDocuments", { values: { name: $org.name, access: "" }, })} {/if} - + diff --git a/src/lib/components/sidebar/ProjectActions.svelte b/src/lib/components/sidebar/ProjectActions.svelte new file mode 100644 index 000000000..1fba9980a --- /dev/null +++ b/src/lib/components/sidebar/ProjectActions.svelte @@ -0,0 +1,78 @@ + + + + + + + + {#if project.edit_access || project.add_remove_access} + + {#if project.edit_access} + + {/if} + + {#if project.edit_access} + + {/if} + {/if} + + +{#if show} + + +

+ {actions[show]} +

+ {#if show === "edit"} + + {/if} + + {#if show === "share"} + + {/if} + + {#if show === "delete"} + + {/if} +
+
+{/if} diff --git a/src/lib/components/sidebar/Projects.svelte b/src/lib/components/sidebar/Projects.svelte new file mode 100644 index 000000000..b71ab68a9 --- /dev/null +++ b/src/lib/components/sidebar/Projects.svelte @@ -0,0 +1,82 @@ + + + + + + {$_("sidebar.projects.title")} + + + + + {$_("projects.yours")} + + + + {$_("projects.shared")} + + {#await pinned} + {$_("common.loading")} + {:then projects} + {#each sort(projects) as project} + + + {project.title} + + {:else} + + + {$_("sidebar.projects.pinned")} + + {/each} + {/await} + + diff --git a/src/lib/components/sidebar/SidebarGroup.svelte b/src/lib/components/sidebar/SidebarGroup.svelte index 0a31904d1..51e4543e4 100644 --- a/src/lib/components/sidebar/SidebarGroup.svelte +++ b/src/lib/components/sidebar/SidebarGroup.svelte @@ -48,7 +48,12 @@
{#if $$slots.title}{/if} - + {#if $$slots.action}{/if} {/if} {#if !collapsed} @@ -63,6 +68,7 @@ display: flex; flex-direction: column; gap: 0.25rem; + padding-bottom: 1rem; } header { flex: 1 0 0; diff --git a/src/lib/components/sidebar/SidebarItem.svelte b/src/lib/components/sidebar/SidebarItem.svelte index 49462e194..79b6e20b6 100644 --- a/src/lib/components/sidebar/SidebarItem.svelte +++ b/src/lib/components/sidebar/SidebarItem.svelte @@ -87,7 +87,7 @@ .container.hover:hover, .container.hover:focus { cursor: pointer; - background: var(--hover-background, var(--gray-1, #d8dee2)); + background: var(--hover-background, var(--blue-1, #d8dee2)); color: var(--hover-color, var(--color, inherit)); fill: var(--hover-fill, var(--fill, inherit)); } diff --git a/src/lib/components/sidebar/UploadButton.svelte b/src/lib/components/sidebar/UploadButton.svelte new file mode 100644 index 000000000..2aede1fb1 --- /dev/null +++ b/src/lib/components/sidebar/UploadButton.svelte @@ -0,0 +1,40 @@ + + +{#if isSignedIn($me) && canUploadFiles($me)} + {#if project} + + {:else} + + {/if} +{/if} diff --git a/src/lib/components/documents/Actions.svelte b/src/lib/components/sidebar/ViewerActions.svelte similarity index 97% rename from src/lib/components/documents/Actions.svelte rename to src/lib/components/sidebar/ViewerActions.svelte index 6751eb7f0..2afc6d3b4 100644 --- a/src/lib/components/documents/Actions.svelte +++ b/src/lib/components/sidebar/ViewerActions.svelte @@ -22,8 +22,8 @@ import PremiumBadge from "$lib/components/premium-credits/PremiumBadge.svelte"; import UpgradePrompt from "$lib/components/premium-credits/UpgradePrompt.svelte"; - import Revisions from "./Revisions.svelte"; - import Share from "./Share.svelte"; + import Revisions from "$lib/components/documents/Revisions.svelte"; + import Share from "$lib/components/documents/Share.svelte"; import { getUpgradeUrl } from "$lib/api/accounts"; import { pdfUrl } from "$lib/api/documents"; @@ -44,6 +44,16 @@
+
+ + +
{#if document.edit_access}
- {/if} -
- - -
- {#if document.edit_access}
- {/if} - - - {/if} + + diff --git a/src/routes/(app)/documents/+page.ts b/src/routes/(app)/documents/+page.ts index 6e14acba5..f1ea847b5 100644 --- a/src/routes/(app)/documents/+page.ts +++ b/src/routes/(app)/documents/+page.ts @@ -1,8 +1,7 @@ -import type { DocumentResults, SearchOptions } from "$lib/api/types"; +import type { SearchOptions } from "$lib/api/types"; import { DEFAULT_PER_PAGE } from "@/config/config.js"; import { search } from "$lib/api/documents"; -import { getPinnedAddons } from "$lib/api/addons.js"; export async function load({ url, fetch, data }) { const query = url.searchParams.get("q") || ""; @@ -24,14 +23,11 @@ export async function load({ url, fetch, data }) { const searchResults = search(query, options, fetch); - const pinnedAddons = getPinnedAddons(fetch); - return { ...data, query, per_page, cursor, searchResults, - pinnedAddons, }; } diff --git a/src/routes/(app)/documents/[id]-[slug]/+page.svelte b/src/routes/(app)/documents/[id]-[slug]/+page.svelte index 60a4d8d5b..2d91a71ac 100644 --- a/src/routes/(app)/documents/[id]-[slug]/+page.svelte +++ b/src/routes/(app)/documents/[id]-[slug]/+page.svelte @@ -25,9 +25,7 @@ $: canonical_url = documents.canonicalUrl(document).href; $: action = data.action; - $: addons = data.pinnedAddons; $: hasDescription = Boolean(document.description?.trim().length); - $: query = data.query || ""; @@ -67,6 +65,6 @@ - + diff --git a/src/routes/(app)/documents/[id]-[slug]/+page.ts b/src/routes/(app)/documents/[id]-[slug]/+page.ts index 3f86b3942..f652c087b 100644 --- a/src/routes/(app)/documents/[id]-[slug]/+page.ts +++ b/src/routes/(app)/documents/[id]-[slug]/+page.ts @@ -8,7 +8,6 @@ import type { ReadMode } from "@/lib/api/types"; import { redirect } from "@sveltejs/kit"; import * as documents from "$lib/api/documents"; -import { getPinnedAddons } from "$lib/api/addons"; import { breadcrumbTrail } from "$lib/utils/index"; import loadDocument from "$lib/load/document"; @@ -39,9 +38,6 @@ export async function load({ fetch, params, parent, depends, url }) { { href: canonical.pathname, title: document.title }, ]); - // stream this - const pinnedAddons = getPinnedAddons(fetch); - const query = getQuery(url); return { @@ -49,7 +45,6 @@ export async function load({ fetch, params, parent, depends, url }) { mode, asset_url, action, - pinnedAddons, breadcrumbs, query, }; diff --git a/src/routes/(app)/documents/sidebar/Projects.svelte b/src/routes/(app)/documents/sidebar/Projects.svelte deleted file mode 100644 index ad438ed22..000000000 --- a/src/routes/(app)/documents/sidebar/Projects.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - - - - {$_("sidebar.projects.title")} - - - {$_("common.explore")} - - - {#await pinned} - {$_("common.loading")} - {:then projects} - {#each sort(projects) as project} - - - {project.title} - - {:else} - {$_("sidebar.projects.pinned")} - {/each} - {/await} - - diff --git a/src/routes/(app)/projects/+page.svelte b/src/routes/(app)/projects/+page.svelte index 12ba9c2d9..e3288b75f 100644 --- a/src/routes/(app)/projects/+page.svelte +++ b/src/routes/(app)/projects/+page.svelte @@ -2,30 +2,27 @@ import type { Nullable } from "$lib/api/types"; import { _ } from "svelte-i18n"; - import { - FileDirectory24, - People16, - Person16, - Globe16, - } from "svelte-octicons"; + import { FileDirectory24, SidebarExpand16 } from "svelte-octicons"; import { goto } from "$app/navigation"; import { page } from "$app/stores"; import Button from "$lib/components/common/Button.svelte"; - import ContentLayout from "$lib/components/layouts/ContentLayout.svelte"; import Empty from "$lib/components/common/Empty.svelte"; import Flex from "$lib/components/common/Flex.svelte"; import PageToolbar from "$lib/components/common/PageToolbar.svelte"; import Paginator from "$lib/components/common/Paginator.svelte"; - import ProjectListItem from "$lib/components/projects/ProjectListItem.svelte"; + import EditProject from "$lib/components/forms/EditProject.svelte"; import Search from "$lib/components/forms/Search.svelte"; - import SidebarItem from "$lib/components/sidebar/SidebarItem.svelte"; - import SidebarLayout from "@/lib/components/layouts/SidebarLayout.svelte"; - - import EditProject from "@/lib/components/forms/EditProject.svelte"; + import ContentLayout from "$lib/components/layouts/ContentLayout.svelte"; import Modal from "$lib/components/layouts/Modal.svelte"; import Portal from "$lib/components/layouts/Portal.svelte"; + import SidebarLayout from "$lib/components/layouts/SidebarLayout.svelte"; + import ProjectListItem from "$lib/components/projects/ProjectListItem.svelte"; + import Documents from "$lib/components/sidebar/Documents.svelte"; + import Projects from "$lib/components/sidebar/Projects.svelte"; + import AddOns from "$lib/components/sidebar/AddOns.svelte"; + import { sidebars } from "$lib/components/layouts/Sidebar.svelte"; import { getCurrentUser } from "$lib/utils/permissions"; @@ -57,33 +54,48 @@ - - {#if $me} - - - {$_("projects.yours")} - - - - {$_("projects.shared")} - - {/if} - - - {$_("projects.public")} - - + + + - - - + + + {#if $sidebars["navigation"] === false} +
+ +
+ {/if} + + + + {#if $sidebars["action"] === false} +
+ +
+ {/if} +
+
{#each projects as project} @@ -119,3 +131,18 @@ {/if} + + diff --git a/src/routes/(app)/projects/[id]-[slug]/+page.svelte b/src/routes/(app)/projects/[id]-[slug]/+page.svelte index c33e1ef9f..608f7df07 100644 --- a/src/routes/(app)/projects/[id]-[slug]/+page.svelte +++ b/src/routes/(app)/projects/[id]-[slug]/+page.svelte @@ -1,6 +1,8 @@ @@ -36,4 +39,4 @@ {/if} - + diff --git a/src/routes/(app)/projects/[id]-[slug]/+page.ts b/src/routes/(app)/projects/[id]-[slug]/+page.ts index 84d8a1381..578166830 100644 --- a/src/routes/(app)/projects/[id]-[slug]/+page.ts +++ b/src/routes/(app)/projects/[id]-[slug]/+page.ts @@ -12,7 +12,6 @@ import * as projects from "$lib/api/projects"; import * as collaborators from "$lib/api/collaborators"; import { search } from "$lib/api/documents"; import { breadcrumbTrail } from "$lib/utils/navigation"; -import { getPinnedAddons } from "$lib/api/addons"; export async function load({ params, url, parent, data, fetch }) { const id = parseInt(params.id, 10); @@ -52,9 +51,6 @@ export async function load({ params, url, parent, data, fetch }) { fetch, ); - // stream this - const pinnedAddons = getPinnedAddons(fetch); - return { ...(data ?? {}), // include csrf_token breadcrumbs, @@ -62,6 +58,5 @@ export async function load({ params, url, parent, data, fetch }) { query, project: project.data, users, - pinnedAddons, }; } diff --git a/src/routes/(app)/upload/+page.svelte b/src/routes/(app)/upload/+page.svelte index 04a73abe2..91795dde0 100644 --- a/src/routes/(app)/upload/+page.svelte +++ b/src/routes/(app)/upload/+page.svelte @@ -1,14 +1,11 @@ diff --git a/src/test/handlers/documents.ts b/src/test/handlers/documents.ts index 21f59b624..4b411b20f 100644 --- a/src/test/handlers/documents.ts +++ b/src/test/handlers/documents.ts @@ -13,6 +13,10 @@ export const documents = { new URL("documents/pending/", BASE_API_URL).href, (req, res, ctx) => res(ctx.json(pending)), ), + error: rest.get( + new URL("documents/search/", "https://api.www.documentcloud.org/api/").href, + (req, res, ctx) => res(ctx.status(500, "Something went wrong")), + ), }; export const revisionControl = {