Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
87f2415
Migrate project components to Svelte 5
eyeseast Jan 24, 2026
6f3d0f9
Convert sidebar components. SidebarGroup touches a lot of components.
eyeseast Jan 24, 2026
d3bd669
Fix stories
eyeseast Jan 24, 2026
8200b3d
State not stores
eyeseast Jan 27, 2026
7cc7ae1
Add KV methods to hit the API directly
eyeseast Jan 28, 2026
5ffce26
Ensure links in DocumentListItem open in new tabs when embedded
eyeseast Jan 28, 2026
a1c897d
Merge pull request #1280 from MuckRock/1279-project-embed-links
eyeseast Jan 28, 2026
fbed239
Migrate project components to Svelte 5
eyeseast Jan 24, 2026
2801285
Convert sidebar components. SidebarGroup touches a lot of components.
eyeseast Jan 24, 2026
55a9d4e
Fix stories
eyeseast Jan 24, 2026
215d334
State not stores
eyeseast Jan 27, 2026
8eaeacd
Merge branch '1245-projects' of github.com:MuckRock/documentcloud-fro…
eyeseast Jan 29, 2026
40272bf
fix project stories
eyeseast Jan 29, 2026
2889a57
Use page store instead of state until Storybook is sorted
eyeseast Jan 29, 2026
4f08d5b
More page store stuff
eyeseast Jan 29, 2026
cb92861
Add KV methods to hit the API directly
eyeseast Jan 28, 2026
daaa508
Merge branch '1275-kv-refactor' of github.com:MuckRock/documentcloud-…
eyeseast Jan 29, 2026
c248649
Refactor KeyValue.svelte and EditData.svelte to hit the API directly.
eyeseast Jan 30, 2026
e350a8c
Adding and removing
eyeseast Jan 31, 2026
08abde9
Editing values
eyeseast Jan 31, 2026
cd2003e
Editing keys
eyeseast Jan 31, 2026
f1e03e7
Error handling
eyeseast Feb 2, 2026
26706ed
fix mock
eyeseast Feb 2, 2026
b0f8a63
Add tooltips to KV buttons and migrate Tooltip component
eyeseast Feb 2, 2026
fc74baf
Add aria-label where title was
eyeseast Feb 2, 2026
f728bbd
Track edited in EditData.svelte
eyeseast Feb 2, 2026
9d81268
Track edited in EditDataMany.svelte
eyeseast Feb 2, 2026
e6fbff3
It's not a form anymore so remove form handling
eyeseast Feb 2, 2026
f284459
Use EditDataMany everywhere. Confirm close with unsaved changes. Empt…
eyeseast Feb 3, 2026
8814ba2
One form to edit data
eyeseast Feb 3, 2026
e5dc83c
Only set edited = true if something actually changed
eyeseast Feb 3, 2026
fade352
Merge pull request #1276 from MuckRock/1275-kv-refactor
eyeseast Feb 5, 2026
8416fc5
Migrate project components to Svelte 5
eyeseast Jan 24, 2026
fe7619e
Convert sidebar components. SidebarGroup touches a lot of components.
eyeseast Jan 24, 2026
a5c36d0
Fix stories
eyeseast Jan 24, 2026
ac5073c
State not stores
eyeseast Jan 27, 2026
0f38333
Fix stories
eyeseast Jan 24, 2026
99cb82b
State not stores
eyeseast Jan 27, 2026
074243f
fix project stories
eyeseast Jan 29, 2026
a57e94e
Use page store instead of state until Storybook is sorted
eyeseast Jan 29, 2026
4fafddd
More page store stuff
eyeseast Jan 29, 2026
1f33d89
Merge branch '1245-projects' of github.com:MuckRock/documentcloud-fro…
eyeseast Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ const preview: Preview = {
},
},
},
state: {
page: {
data: {
breadcrumbs: [],
},
},
},
},
options: {
storySort: {
Expand Down
5 changes: 4 additions & 1 deletion src/langs/json/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,10 @@
"save": "Save",
"cancel": "Cancel",
"addNew": "Add New Item:",
"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."
"many": "Add tags and data to {n, plural, one {the selected document} other {all # selected documents}}. Only common entries are shown here. Removing items will remove them from all selected documents.",
"total_edited": "Unsaved changes: {n}",
"empty": "No tags or data have been set. Add new items below.",
"confirm": "You have {n} unsaved changes. Close and discard these updates?"
},
"bulk": {
"title": "Actions",
Expand Down
1 change: 0 additions & 1 deletion src/lib/api/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Lots of duplicated code here that should get consolidated at some point.
*/
import type {
APIError,
APIResponse,
Data,
DataUpdate,
Expand Down
137 changes: 137 additions & 0 deletions src/lib/api/kv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/* Methods to operate on a single document's KV data */

import type {
APIResponse,
Data,
Document,
Maybe,
ValidationError,
} from "./types";

import { APP_URL, BASE_API_URL, CSRF_HEADER_NAME } from "@/config/config.js";
import { getApiResponse } from "$lib/utils";

interface DataPayload {
values?: string[];
remove?: string[];
}

/**
* Fetch the value of a single key
* GET /api/documents/<document_id>/data/<key>/
*/
export async function get(
document: Document,
key: string,
fetch = globalThis.fetch,
): Promise<APIResponse<string[]>> {
const endpoint = new URL(
`documents/${document.id}/data/${key}/`,
BASE_API_URL,
);

const response = await fetch(endpoint, { credentials: "include" }).catch(
console.warn,
);

return getApiResponse<string[]>(response);
}

/**
* Set values for a key, overwriting any existing keys
* PUT /api/documents/<document_id>/data/<key>/
*/
export async function set(
document: Document,
key: string,
values: string[],
csrf_token: string,
fetch = globalThis.fetch,
): Promise<APIResponse<Data, ValidationError>> {
const endpoint = new URL(
`documents/${document.id}/data/${key}/`,
BASE_API_URL,
);

const body: DataPayload = { values };

const response = await fetch(endpoint, {
credentials: "include",
method: "PUT",
headers: {
"Content-type": "application/json",
[CSRF_HEADER_NAME]: csrf_token,
Referer: APP_URL,
},
body: JSON.stringify(body),
}).catch(console.warn);

return getApiResponse<Data, ValidationError>(response);
}

/**
* Update or remove values for a key
* PATCH /api/documents/<document_id>/data/<key>/
*/
export async function update(
document: Document,
key: string,
values: Maybe<string[]> = undefined,
remove: Maybe<string[]> = undefined,
csrf_token: string,
fetch = globalThis.fetch,
): Promise<APIResponse<Data, ValidationError>> {
const endpoint = new URL(
`documents/${document.id}/data/${key}/`,
BASE_API_URL,
);

const body: DataPayload = {};

if (values && values.length) {
body.values = values;
}

if (remove && remove.length > 0) {
body.remove = remove;
}

const response = await fetch(endpoint, {
credentials: "include",
method: "PATCH",
headers: {
"Content-type": "application/json",
[CSRF_HEADER_NAME]: csrf_token,
Referer: APP_URL,
},
body: JSON.stringify(body),
}).catch(console.warn);

return getApiResponse<Data, ValidationError>(response);
}

// utils, colocated here to reduce the number of files we need

/**
* Generate a common set of unique keys for a group of documents
*/
export function keys(documents: Document[]): Set<string> {
return new Set(documents.flatMap((d) => Object.keys(d.data)));
}

/**
* Generate a Data object with only keys and values common to all documents
*/
export function common(documents: Document[]): Data {
return documents.reduce((m: Data, d: Document, index: number) => {
// use the first document as our baseline
if (index === 0) return { ...d.data };

for (const [k, v] of Object.entries(m)) {
const shared = new Set(d.data[k]).intersection(new Set(v));
m[k] = [...shared];
}

return m;
}, {} as Data);
}
Loading