Skip to content

Commit

Permalink
Merge pull request #985 from MuckRock/933-embed-params
Browse files Browse the repository at this point in the history
  • Loading branch information
eyeseast authored Dec 16, 2024
2 parents 387dc42 + 3411694 commit ac8fef7
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 20 deletions.
8 changes: 7 additions & 1 deletion src/lib/components/common/Tab.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
<!-- @component
A tab component for navigating within a page.
Note that links will preserve querystrings where possible.
-->
<script lang="ts">
import { qs } from "$lib/utils/navigation";
export let active = false;
export let disabled = false;
export let href: string = "";
</script>

<div class="tab" role="tab" class:active class:disabled>
{#if href}
<a {href} class:disabled>
<a {href} class:disabled use:qs>
<slot />
</a>
{:else}
Expand Down
24 changes: 13 additions & 11 deletions src/lib/components/viewer/ReadingToolbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,6 @@ Assumes it's a child of a ViewerContext
const mode = getCurrentMode();
const embed = isEmbedded();
$: document = $documentStore;
$: canWrite = !embed && document.edit_access;
$: BREAKPOINTS = {
READ_MENU: width > remToPx(52),
WRITE_MENU: width < remToPx(37),
SEARCH_MENU: width < remToPx(24),
};
const readModeTabs: Map<ReadMode, string> = new Map([
["document", $_("mode.document")],
["text", $_("mode.text")],
Expand Down Expand Up @@ -83,6 +75,18 @@ Assumes it's a child of a ViewerContext
redacting: EyeClosed16,
search: Search16,
};
$: document = $documentStore;
$: canWrite = !embed && document.edit_access;
$: BREAKPOINTS = {
READ_MENU: width > remToPx(52),
WRITE_MENU: width < remToPx(37),
SEARCH_MENU: width < remToPx(24),
};
$: current = Array.from(readModeDropdownItems ?? []).find(
([value]) => value === $mode,
)?.[1];
</script>

<PageToolbar bind:width>
Expand All @@ -103,9 +107,7 @@ Assumes it's a child of a ViewerContext
<Dropdown position="bottom-start">
<SidebarItem slot="anchor">
<svelte:component this={icons[$mode]} slot="start" />
{Array.from(readModeDropdownItems ?? []).find(
([value]) => value === $mode,
)?.[1]}
{current}
<ChevronDown12 slot="end" />
</SidebarItem>
<Menu slot="default" let:close>
Expand Down
18 changes: 11 additions & 7 deletions src/lib/components/viewer/Search.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,32 @@ Assumes it's a child of a ViewerContext
-->

<script lang="ts">
import type { APIResponse, Highlights } from "@/lib/api/types";
import type { APIResponse, Highlights } from "$lib/api/types";
import { page } from "$app/stores";
import { getContext } from "svelte";
import { _ } from "svelte-i18n";
import { Hourglass24, Search24 } from "svelte-octicons";
import { page } from "$app/stores";
import Empty from "../common/Empty.svelte";
import Error from "../common/Error.svelte";
import Highlight from "../common/Highlight.svelte";
import { getDocument } from "./ViewerContext.svelte";
import { getQuery, highlight, pageNumber } from "$lib/utils/search";
import { getViewerHref } from "$lib/utils/viewer";
import { qs } from "$lib/utils/navigation";
import { searchWithin } from "$lib/api/documents";
const document = getDocument();
const embed: boolean = getContext("embed");
let search: Promise<[number, string[]][]>;
$: query = getQuery($page.url, "q");
$: search = searchWithin($document.id, query).then(formatResults);
// Format page numbers, highlight search results, and remove invalid pages
function formatResults(results: APIResponse<Highlights>) {
if (results.error) throw new TypeError(results.error.message);
Expand All @@ -35,9 +42,6 @@ Assumes it's a child of a ViewerContext
.filter(([page]) => !isNaN(page));
}
$: query = getQuery($page.url, "q");
$: search = searchWithin($document.id, query).then(formatResults);
function countResults(results: [number, string[]][]) {
return results.reduce((acc, [, segments]) => acc + segments.length, 0);
}
Expand Down Expand Up @@ -69,7 +73,7 @@ Assumes it's a child of a ViewerContext
query,
})}

<a {href} class="card">
<a use:qs {href} class="card">
<Highlight
title="{$_('documents.pageAbbrev')} {pageNumber}"
segments={resultsList}
Expand Down
22 changes: 22 additions & 0 deletions src/lib/utils/navigation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Action } from "svelte/action";

type Breadcrumb = { href?: string; title: string };
type Parent = () => Promise<{ breadcrumbs?: Array<Breadcrumb> }>;

Expand All @@ -11,3 +13,23 @@ export async function breadcrumbTrail(
const { breadcrumbs: trail = [] } = await parent();
return trail.concat(crumbs);
}

/**
* Make a link preserve existing query params and merge in new ones
*
* @type {Action}
* @param node
*/
export function qs(node: HTMLAnchorElement) {
if (typeof window === "undefined") return;

const href = new URL(node.href);
const params = new URLSearchParams(window.location.search);

for (const [k, v] of href.searchParams) {
params.set(k, v);
}

href.search = params.toString();
node.href = href.toString();
}
41 changes: 40 additions & 1 deletion src/lib/utils/tests/navigation.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
/**
* @vitest-environment jsdom
* @vitest-environment-options { "url": "https://www.dev.documentcloud.org/documents/20000065-creating-adaptable-skills-a-nonlinear-pedagogy-approach-to-mental-imagery/?mode=search&embed=1&q=pedagogy&title=0" }
*/

import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
import { breadcrumbTrail } from "../navigation";
import { breadcrumbTrail, qs } from "../navigation";

describe("breadcrumbTrail", () => {
it("returns an empty array as a base case", async () => {
const parent = vi.fn().mockResolvedValue({});
expect(await breadcrumbTrail(parent)).toEqual([]);
});

it("returns the parent's breadcrumb trail, if it exists", async () => {
const parentTrail = [{ href: "/first", title: "First Level" }];
const parent = vi.fn().mockResolvedValue({ breadcrumbs: parentTrail });
expect(await breadcrumbTrail(parent)).toEqual(parentTrail);
});

it("concats the provided trail onto the parent's trail", async () => {
const parentTrail = [{ href: "/first", title: "First Level" }];
const childTrail = [{ href: "/second", title: "Second Level" }];
Expand All @@ -21,3 +28,35 @@ describe("breadcrumbTrail", () => {
]);
});
});

describe("querystring links", () => {
afterEach(() => {
vi.resetAllMocks();
});

it("adds new query params to existing URL params", () => {
const links = [
"https://www.dev.documentcloud.org/documents/20000065-creating-adaptable-skills-a-nonlinear-pedagogy-approach-to-mental-imagery/?mode=document#document/p1",
"https://www.dev.documentcloud.org/documents/20000065-creating-adaptable-skills-a-nonlinear-pedagogy-approach-to-mental-imagery/?mode=notes",
"https://www.dev.documentcloud.org/documents/20000065-creating-adaptable-skills-a-nonlinear-pedagogy-approach-to-mental-imagery/?mode=document",
];

const fixed = [
"?mode=document&embed=1&q=pedagogy&title=0",
"?mode=notes&embed=1&q=pedagogy&title=0",
"?mode=document&embed=1&q=pedagogy&title=0",
];

links.forEach((href, i) => {
// create the link
let a = document.createElement("a");
a.href = href;
a.textContent = "test";

// fix the link
qs(a);

expect(new URL(a.href).search).toEqual(fixed[i]);
});
});
});

0 comments on commit ac8fef7

Please sign in to comment.