Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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