Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sveltekit] Text view #532

Merged
merged 17 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
26 changes: 22 additions & 4 deletions public/fonts.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
font-style: normal;
font-weight: 400;
src: url("/fonts/source-sans-pro-v21-latin-regular.eot"); /* IE9 Compat Modes */
src: local(""),
src:
local(""),
url("/fonts/source-sans-pro-v21-latin-regular.eot?#iefix")
format("embedded-opentype"),
/* IE6-IE8 */ url("/fonts/source-sans-pro-v21-latin-regular.woff2")
Expand All @@ -24,7 +25,8 @@
font-style: italic;
font-weight: 400;
src: url("/fonts/source-sans-pro-v21-latin-italic.eot"); /* IE9 Compat Modes */
src: local(""),
src:
local(""),
url("/fonts/source-sans-pro-v21-latin-italic.eot?#iefix")
format("embedded-opentype"),
/* IE6-IE8 */ url("/fonts/source-sans-pro-v21-latin-italic.woff2")
Expand All @@ -44,7 +46,8 @@
font-style: normal;
font-weight: 600;
src: url("/fonts/source-sans-pro-v21-latin-600.eot"); /* IE9 Compat Modes */
src: local(""),
src:
local(""),
url("/fonts/source-sans-pro-v21-latin-600.eot?#iefix")
format("embedded-opentype"),
/* IE6-IE8 */ url("/fonts/source-sans-pro-v21-latin-600.woff2")
Expand All @@ -64,7 +67,8 @@
font-style: normal;
font-weight: 700;
src: url("/fonts/source-sans-pro-v21-latin-700.eot"); /* IE9 Compat Modes */
src: local(""),
src:
local(""),
url("/fonts/source-sans-pro-v21-latin-700.eot?#iefix")
format("embedded-opentype"),
/* IE6-IE8 */ url("/fonts/source-sans-pro-v21-latin-700.woff2")
Expand All @@ -77,3 +81,17 @@
url("/fonts/source-sans-pro-v21-latin-700.svg#SourceSansPro")
format("svg"); /* Legacy iOS */
}

/* Source Code Pro */
@font-face {
font-family: "Source Code Pro";
font-style: normal;
src: url("/fonts/SourceCodePro-VariableFont_wght.ttf") format("truetype");
}

@font-face {
font-family: "Source Code Pro";
font-style: italic;
src: url("/fonts/SourceCodePro-Italic-VariableFont_wght.ttf")
eyeseast marked this conversation as resolved.
Show resolved Hide resolved
format("truetype");
}
Binary file not shown.
Binary file added public/fonts/SourceCodePro-VariableFont_wght.ttf
Binary file not shown.
6 changes: 3 additions & 3 deletions src/common/Dropdown2.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
position: relative;
}
.dropdownContainer.open {
z-index: var(--menuActive);
z-index: var(--menuActive, 16);
}
.title {
display: block;
Expand All @@ -143,7 +143,7 @@
border: 1px solid rgba(0, 0, 0, 0.1);
}
.title:hover {
background: var(--light-primary);
background: var(--light-primary, #eff7ff);
}
.title.open {
background: var(--primary);
Expand Down Expand Up @@ -187,7 +187,7 @@
}

.overlay {
z-index: var(--menuShim);
z-index: var(--menuShim, 14);
position: fixed;
top: 0;
left: 0;
Expand Down
2 changes: 1 addition & 1 deletion src/common/Paginator.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
bind:value={inputValue}
on:change={handleChange}
on:keyup={handleKeyup}
style={`width: ${inputWidth}ch`}
style={`min-width: ${inputWidth}ch`}
/>
{:else}
<span class="pageNumber">{page}</span>
Expand Down
8 changes: 4 additions & 4 deletions src/lib/api/addons.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe("getAddons", () => {
});
it("calls the addons API endpoint", async () => {
await addons.getAddons({}, mockFetch);
const expectedEndpoint = new URL(`addons`, BASE_API_URL);
const expectedEndpoint = new URL("addons/", BASE_API_URL);
expect(mockFetch).toHaveBeenCalledWith(
expectedEndpoint,
expect.any(Object),
Expand All @@ -28,7 +28,7 @@ describe("getAddons", () => {
{ active: true, featured: true, query: "foobar" },
mockFetch,
);
const expectedEndpoint = new URL(`addons`, BASE_API_URL);
const expectedEndpoint = new URL("addons/", BASE_API_URL);
expectedEndpoint.searchParams.set("active", "true");
expectedEndpoint.searchParams.set("featured", "true");
expectedEndpoint.searchParams.set("query", "foobar");
Expand Down Expand Up @@ -57,7 +57,7 @@ test("getPinnedAddons", async () => {
.mockResolvedValue({ ok: true, json: async () => {} });
await addons.getPinnedAddons(mockFetch);
expect(mockFetch).toHaveBeenCalledWith(
new URL(`addons?active=true`, BASE_API_URL),
new URL(`addons/?active=true`, BASE_API_URL),
expect.any(Object),
);
});
Expand All @@ -76,7 +76,7 @@ describe("getAddon", async () => {
it("calls the addons list endpoint with a respository query argument", async () => {
await addons.getAddon("MuckRock", "addon-repo", mockFetch);
expect(mockFetch).toHaveBeenCalledWith(
new URL(`addons?repository=MuckRock%2Faddon-repo`, BASE_API_URL),
new URL(`addons/?repository=MuckRock%2Faddon-repo`, BASE_API_URL),
expect.any(Object),
);
});
Expand Down
83 changes: 81 additions & 2 deletions src/lib/api/documents.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import type { Document, DocumentUpload, Pending, Sizes } from "./types";
import type {
Document,
DocumentText,
DocumentUpload,
Pending,
Sizes,
} from "./types";

import { vi, test as base, describe, expect, afterEach } from "vitest";
import { APP_URL, DC_BASE, IMAGE_WIDTHS_ENTRIES } from "@/config/config.js";
import {
APP_URL,
BASE_API_URL,
DC_BASE,
IMAGE_WIDTHS_ENTRIES,
} from "@/config/config.js";

import * as documents from "./documents";

Expand Down Expand Up @@ -31,11 +42,79 @@ const test = base.extend({

await use(pending);
},

text: async ({}, use: Use<DocumentText>) => {
const { default: text } = await import(
"./fixtures/documents/document.txt.json"
);

await use(text);
},
});

describe("document fetching", () => {
test.todo("documents.get");
test.todo("documents.search");

test("documents.text public", async ({ document, text }) => {
const mockFetch = vi.fn().mockImplementation(async () => ({
ok: true,
async json() {
return text;
},
}));

const endpoint = documents.jsonUrl(document);

documents.text(document, mockFetch).then((t) => {
expect(t).toMatchObject(text);
});

expect(mockFetch).toHaveBeenCalledWith(endpoint);
});

test("documents.text private", async ({ document, text }) => {
const { asset_url } = document;
const privateText = new URL("private.txt.json", asset_url).toString();
const privateDoc = {
...document,
access: "private",
asset_url: BASE_API_URL,
} as Document;

// to get private assets, we need to hit the API first for credentials
// then fetch the actual asset from cloud storage
const mockFetch = vi.fn().mockImplementation(async (endpoint, options) => {
console.log(endpoint.toString(), options);
// call 2
if (endpoint === privateText) {
return {
ok: true,
async json() {
return text;
},
};
}

// call 1
return {
status: 200,
headers: new Headers([["Location", privateText]]),
async json() {
return {
location: privateText,
};
},
};
});

documents.text(privateDoc, mockFetch).then((t) => {
expect(t).toMatchObject(text);

// we need two fetches for private assets
expect(mockFetch).toHaveBeenCalledTimes(2);
});
});
});

describe("document uploads and processing", () => {
Expand Down
58 changes: 58 additions & 0 deletions src/lib/api/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import type {
Document,
DocumentText,
DocumentUpload,
DocumentResults,
Pending,
Expand All @@ -16,6 +17,8 @@ import { isOrg } from "@/api/types/orgAndUser";
import { APP_URL, BASE_API_URL, CSRF_HEADER_NAME } from "@/config/config.js";
import { isErrorCode } from "../utils";

export const MODES = new Set(["document", "text", "thumbnails", "notes"]);

/**
* Search documents
* https://www.documentcloud.org/help/search/
Expand Down Expand Up @@ -86,6 +89,61 @@ export async function get(
return resp.json();
}

/**
* Get text for a document. It may be a private asset, which requires a two-step fetch.
*
* @param document The document to get text for
* @param fetch A fetch function
* @returns
*/
export async function text(
document: Document,
fetch = globalThis.fetch,
): Promise<DocumentText> {
// for errors
const empty = { updated: 0, pages: [] };

// for public documents, we can just fetch the asset
if (document.access === "public") {
const url = jsonUrl(document);
const resp = await fetch(url).catch(console.error);

if (!resp || isErrorCode(resp.status)) {
return empty;
}

return resp.json();
}

// for private and organization docs, we need to hit the API first
// with credentials, and then fetch the returned location
let resp: Response | void = await fetch(jsonUrl(document), {
credentials: "include",
redirect: "error",
headers: {
Accept: "application/json",
},
}).catch(console.error);

if (!resp) {
return empty;
}

const { location } = await resp.json();

if (!location) {
return empty;
}

resp = await fetch(location).catch(console.error);

if (!resp || isErrorCode(resp.status)) {
return empty;
}

return resp.json();
eyeseast marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Create new documents in a batch (or a batch of one).
*
Expand Down
26 changes: 26 additions & 0 deletions src/lib/api/fixtures/documents/document.txt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"updated": 1708614455938,
"pages": [
{
"page": 0,
"contents": "\u201cThe Santa Anas\u201d\nJoan Didion\nThere is something uneasy in the Los Angeles air this afternoon, some unnatural \nstillness, some tension. What it means is that tonight a Santa Ana will begin to \nblow, a hot wind from the northeast whining down through the Cajon and San \nGorgonio Passes, blowing up sand storms out along Route 66, drying the hills and \nthe nerves to flash point. For a few days now we will see smoke back in the \ncanyons, and hear sirens in the night. \nI have neither heard nor read that a Santa Ana is due, but I know it, and almost \neveryone I have seen today knows it too. We know it because we feel it. The baby \nfrets. The maid sulks. I rekindle a waning argument with the telephone company, \nthen cut my losses and lie down, given over to whatever it is in the air. To live \nwith the Santa Ana is to accept, consciously or unconsciously, a deeply \nmechanistic view of human behavior.\nI recall being told, when I first moved to Los Angeles and was living on an \nisolated beach, that the Indians would throw themselves into the sea when the bad \nwind blew. I could see why. The Pacific turned ominously glossy during a Santa \nAna period, and one woke in the night troubled not only by the peacocks \nscreaming in the olive trees but by the eerie absence of surf. The heat was surreal. \nThe sky had a yellow cast, the kind of light sometimes called \"earthquake \nweather\". My only neighbor would not come out of her house for days, and there \nwere no lights at night, and her husband roamed the place with a machete. One \nday he would tell me that he had heard a trespasser, the next a rattlesnake. \n\"On nights like that,\" Raymond Chandler once wrote about the Santa Ana, \"every \nbooze party ends in a fight. Meek little wives feel the edge of the carving knife \nand study their husbands' necks. Anything can happen.\" That was the kind of \nwind it was. I did not know then that there was any basis for the effect it had on \nall of us, but it turns out to be another of those cases in which science bears out \nfolk wisdom. \nThe Santa Ana, which is named for one of the canyons it rushers through, is foehn \nwind, like the foehn of Austria and Switzerland and the hamsin of Israel. There \nare a number of persistent malevolent winds, perhaps the best know of which are \nthe mistral of France and the Mediterranean sirocco, but a foehn wind has distinct \ncharacteristics: it occurs on the leeward slope of a mountain range and, although \nthe air begins as a cold mass, it is warmed as it comes down the mountain and \nappears finally as a hot dry wind. Whenever and wherever foehn blows, doctors \nhear about headaches and nausea and allergies, about \"nervousness,\" about \n\"depression.\" \nIn Los Angeles some teachers do not attempt to conduct formal classes during a \nSanta Ana, because the children become unmanageable. In Switzerland the \u0000",
"ocr": null,
"lang": "eng",
"updated": 1708614455938
},
{
"page": 1,
"contents": "suicide rate goes up during the foehn, and in the courts of some Swiss cantons the \nwind is considered a mitigating circumstance for crime. Surgeons are said to \nwatch the wind, because blood does not clot normally during a foehn. \nA few years ago an Israeli physicist discovered that not only during such winds, \nbut for the ten or twelve hours which precede them, the air carries an unusually \nhigh ratio of positive to negative ions. No one seems to know exactly why that \nshould be; some talk about friction and others suggest solar disturbances. In any \ncase the positive ions are there, and what an excess of positive ions does, in the \nsimplest terms, is make people unhappy. One cannot get much more mechanistic \nthan that. \nEasterners commonly complain that there is no \"weather\" at all in Southern \nCalifornia, that the days and the seasons slip by relentlessly, numbingly bland. \nThat is quite misleading. In fact the climate is characterized by infrequent but \nviolent extremes: two periods of torrential subtropical rains which continue for \nweeks and wash out the hills and send subdivisions sliding toward the sea; about \ntwenty scattered days a year of the Santa Ana, which, with its incendiary dryness, \ninvariably means fire. At the first prediction of a Santa Ana, the Forest Service \nflies men and equipment from northern California into the southern forests, and \nthe Los Angeles Fire Department cancels its ordinary non-firefighting routines. \nThe Santa Ana caused Malibu to burn as it did in 1956, and Bel Air in 1961, and \nSanta Barbara in 1964. In the winter of 1966-67 eleven men were killed fighting a \nSanta Ana fire that spread through the San Gabriel Mountains.\nJust to watch the front-page news out of Los Angeles during a Santa Ana is to get \nvery close to what it is about the place. The longest single Santa Ana period in \nrecent years was in 1957, and it lasted not the usual three or four days but \nfourteen days, from November 21 until December 4. On the first day 25,000 acres \nof the San Gabriel Mountains were burning, with gusts reaching 100 miles an \nhour. In town, the wind reached Force 12, or hurricane force, on the Beaufort \nScale; oil derricks were toppled and people ordered off the downtown streets to \navoid injury from flying objects. On November 22 the fire in the San Gabriels \nwas out of control. On November 24 six people were killed in automobile \naccidents, and by the end of the week the Los Angeles Times was keeping a box \nscore of traffic deaths. On November 26 a prominent Pasadena attorney, \ndepressed about money, shot and killed his wife, their two sons and himself. On \nNovember 27 a South Gate divorc\u00e9e, twenty-two, was murdered and thrown from \na moving car. On November 30 the San Gabriel fire was still out of control, and \nthe wind in town was blowing eighty miles an hour. On the first day of December \nfour people died violently, and on the third the wind began to break. \nIt is hard for people who have not lived in Los Angeles to realize how radically \nthe Santa Ana figures in the local imagination. The city burning is Los Angeles's \ndeepest image of itself. Nathaniel West perceived that, in The Day of the Locust, \nand at the time of the 1965 Watts riots what struck the imagination most indelibly \u0000",
"ocr": null,
"lang": "eng",
"updated": 1708614455938
},
{
"page": 2,
"contents": "were the fires. For days one could drive the Harbor Freeway and see the city on \nfire, just as we had always known it would be in the end. \nLos Angeles weather is the weather of catastrophe, of apocalypse, and, just as the \nreliably long and bitter winters of New England determine the way life is lived \nthere, so the violence and the unpredictability of the Santa Ana affect the entire \nquality of life in Los Angeles, accentuate its impermanence, its unreliability. The \nwinds shows us how close to the edge we are. \nin Slouching Towards Bethlehem, 1969, London: Andre Deutch.\u0000",
"ocr": null,
"lang": "eng",
"updated": 1708614455938
}
]
}
18 changes: 17 additions & 1 deletion src/lib/api/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ export type Access = "public" | "private" | "organization"; // https://www.docum

export type Data = Record<string, string[]>;

export type Highlights = Record<string, string[]>;

export type Status = "success" | "readable" | "pending" | "error" | "nofile"; // https://www.documentcloud.org/help/api#statuses

export type Sizes = "thumbnail" | "small" | "normal" | "large" | "xlarge";

export type Highlights = Record<string, string[]>;
export type ViewerMode = "document" | "text" | "thumbnails" | "notes";

export interface NoteHighlight {
title: string[];
Expand Down Expand Up @@ -207,4 +209,18 @@ export interface Pending {
pages: number;
}

// See JSON Text https://www.documentcloud.org/help/api/#static-assets
export interface TextPage {
page: number;
contents: string;
ocr: string | null;
lang: string;
updated: number; // timestamp
}

export interface DocumentText {
updated: number; // timestamp
pages: TextPage[];
}

export type ProjectMembershipList = Page<ProjectMembershipItem>;
Loading
Loading