Add hash routing

Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
Sebastian Martinez
2022-11-15 15:24:12 +01:00
parent cd60ad7298
commit f38893407a
14 changed files with 87 additions and 35 deletions
+1
View File
@@ -85,6 +85,7 @@
}
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-namespace": ["error", { "allowDeclarations": true }],
"@typescript-eslint/semi": ["error"],
"@typescript-eslint/member-delimiter-style": [
"error",
+2 -1
View File
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/// <reference types="cypress" />
import { MockProvider } from "@rsksmart/mock-web3-provider";
import { getPath } from "../support/e2e";
const groupedCommits = [
{
@@ -146,7 +147,7 @@ describe("project commits", () => {
it("display commit details", () => {
cy.location().should(location => {
expect(location.pathname).to.eq(
expect(getPath(location)).to.eq(
"/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/commits/cbf5df499ab4f4a908f1756fbe2c236a4530516a",
);
});
+6 -5
View File
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/// <reference types="cypress" />
import { MockProvider } from "@rsksmart/mock-web3-provider";
import { getPath } from "../support/e2e";
describe("project header", () => {
beforeEach(() => {
@@ -114,7 +115,7 @@ describe("project header", () => {
"@projectReadme",
]);
cy.location().should(location => {
expect(location.pathname).to.eq(
expect(getPath(location)).to.eq(
"/seeds/willow.radicle.garden/bright-forest-protocol/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/tree",
);
});
@@ -135,7 +136,7 @@ describe("project header", () => {
.click();
cy.wait(["@projectTreecbf5df4", "@projectReadme"]);
cy.location().should(location => {
expect(location.pathname).to.eq(
expect(getPath(location)).to.eq(
"/seeds/willow.radicle.garden/bright-forest-protocol/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/tree/master",
);
});
@@ -149,7 +150,7 @@ describe("project header", () => {
.click();
cy.wait(["@projectCommits"]);
cy.location().should(location => {
expect(location.pathname).to.eq(
expect(getPath(location)).to.eq(
"/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/history/master",
);
});
@@ -160,7 +161,7 @@ describe("project header", () => {
cy.get('[aria-label="Issue count"]').click();
cy.wait(["@projectIssues"]);
cy.location().should(location => {
expect(location.pathname).to.eq(
expect(getPath(location)).to.eq(
"/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/issues",
);
});
@@ -171,7 +172,7 @@ describe("project header", () => {
cy.get('[aria-label="Patch count"]').click();
cy.wait(["@projectPatches"]);
cy.location().should(location => {
expect(location.pathname).to.eq(
expect(getPath(location)).to.eq(
"/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/patches",
);
});
+6 -1
View File
@@ -5,4 +5,9 @@ declare global {
}
}
export {};
function getPath(location: Location): string {
const url = location.href.replace(window.origin, "");
return process.env.hashRouting ? url.substring(2) : url;
}
export { getPath };
+3 -1
View File
@@ -2,8 +2,10 @@
"version": "1.0.0",
"scripts": {
"start": "vite",
"start:ipfs": "HASH_ROUTING=1 vite --base ./",
"serve": "vite preview",
"build": "scripts/build",
"build": "vite build",
"build:ipfs": "HASH_ROUTING=1 vite build --base ./",
"postinstall": "scripts/copy-katex-assets && scripts/install-twemoji-assets",
"check": "scripts/check",
"format": "npx prettier '**/*.@(ts|js|svelte|json|css|html)' --ignore-path .gitignore --write",
View File
-5
View File
@@ -1,5 +0,0 @@
#!/bin/sh
set -e
vite build
cp _redirects build/_redirects
+18 -10
View File
@@ -2,18 +2,19 @@
import type { Blob } from "@app/project";
import type { ProjectRoute } from "@app/router/definitions";
import Readme from "@app/base/projects/Readme.svelte";
import HeaderToggleLabel from "@app/base/projects/HeaderToggleLabel.svelte";
import ProjectLink from "@app/router/ProjectLink.svelte";
import Readme from "@app/base/projects/Readme.svelte";
import { isMarkdownPath, scrollIntoView, twemoji } from "@app/utils";
import { onMount } from "svelte";
import { updateProjectRoute } from "@app/router";
export let blob: Blob;
export let line: string | null;
export let getImage: (path: string) => Promise<Blob>;
export let activeRoute: ProjectRoute;
export let blob: Blob;
export let getImage: (path: string) => Promise<Blob>;
export let line: string | undefined = undefined;
$: lineNumber = line ? parseInt(line.substring(1)) : null;
$: lineNumber = line ? parseInt(line.substring(1)) : undefined;
const lastCommit = blob.info.lastCommit;
const lines = blob.binary ? 0 : (blob.content.match(/\n/g) || []).length;
@@ -25,16 +26,23 @@
?.values()
.next().value;
$: if (line) {
scrollIntoView(line);
}
// Waiting onMount, due to the line numbers still loading.
onMount(() => {
if (line) {
scrollIntoView(line);
}
});
const isMarkdown = isMarkdownPath(blob.path);
// If we have a line number we should show the raw output.
let showMarkdown = line ? false : isMarkdown;
const toggleMarkdown = () => {
updateProjectRoute({ hash: undefined });
updateProjectRoute({ line: undefined });
showMarkdown = !showMarkdown;
};
$: if (line) {
scrollIntoView(line);
}
</script>
<style>
@@ -215,7 +223,7 @@
<div class="line-numbers">
{#each lineNumbers as lineNumber}
<ProjectLink
projectParams={{ hash: `L${lineNumber}` }}
projectParams={{ line: `L${lineNumber}` }}
id="L{lineNumber}">
<span class="line-number" class:highlighted={lineNumber === line} />
{lineNumber}
+1 -1
View File
@@ -28,7 +28,7 @@
export let activeRoute: ProjectRoute;
$: path = activeRoute.params.path || "/";
$: line = activeRoute.params.hash || null;
$: line = activeRoute.params.line;
// When the component is loaded the first time, the blob is yet to be loaded.
let state: State = { status: Status.Loading, path };
+1
View File
@@ -51,6 +51,7 @@
router.updateProjectRoute({
revision,
path,
line: activeRoute.params.line,
hash: activeRoute.params.hash,
route: undefined,
});
+8
View File
@@ -4,4 +4,12 @@ const app = new App({
target: document.body,
});
declare global {
namespace NodeJS {
interface ProcessEnv {
hashRouting: number;
}
}
}
export default app;
+1
View File
@@ -19,6 +19,7 @@ export interface ProjectsParams {
| { resource: "patch"; params: { patch: string } }
| { resource: "patches" };
hash?: string;
line?: string;
path?: string;
peer?: string;
profile?: string;
+33 -7
View File
@@ -16,6 +16,13 @@ export const activeRouteStore: Readable<Route> = derived(
},
);
// Gets triggered when clicking on an anchor hash tag e.g. <a href="#header"/>
// Allows the jump to a anchor hash
window.addEventListener("hashchange", e => {
const url = new URL(e.newURL);
updateProjectRoute({ hash: url.hash.substring(1) });
});
// Replaces history on any user interaction with forward and backwards buttons
// with the current window.history.state
window.addEventListener("popstate", e => {
@@ -30,6 +37,7 @@ export function createProjectRoute(
resource: "projects",
params: {
...activeRoute.params,
line: undefined,
hash: undefined,
...projectRouteParams,
},
@@ -71,7 +79,12 @@ export const push = (newRoute: Route): void => {
// Limit history to a maximum of 10 steps. We shouldn't be doing more than
// one subsequent pop() anyway.
historyStore.set([...history, newRoute].slice(-10));
window.history.pushState(newRoute, documentTitle, routeToPath(newRoute));
const path = process.env.hashRouting
? "#" + routeToPath(newRoute)
: routeToPath(newRoute);
window.history.replaceState(newRoute, documentTitle, path);
};
export const pop = (): void => {
@@ -85,7 +98,12 @@ export const pop = (): void => {
export function replace(newRoute: Route): void {
historyStore.set([newRoute]);
window.history.replaceState(newRoute, documentTitle, routeToPath(newRoute));
const path = process.env.hashRouting
? "#" + routeToPath(newRoute)
: routeToPath(newRoute);
window.history.replaceState(newRoute, documentTitle, path);
}
export const initialize = () => {
@@ -107,8 +125,9 @@ function pathToRoute(path: string): Route | null {
}
const url = new URL(path, window.origin);
// Pathname starts usually with a "/", we remove it to avoid bad interpretations
const segments = url.pathname.substring(1).split("/");
const segments = process.env.hashRouting
? url.hash.substring(2).split("#")[0].split("/") // Try to remove any additional hashes at the end of the URL.
: url.pathname.substring(1).split("/");
const resource = segments.shift();
switch (resource) {
@@ -274,7 +293,9 @@ export function routeToPath(route: Route) {
if (route.params.path && route.params.path !== "/") {
suffix += `/${route.params.path}`;
}
if (route.params.hash) {
if (route.params.line) {
suffix += `#${route.params.line}`;
} else if (route.params.hash) {
suffix += `#${route.params.hash}`;
}
if (route.params.search) {
@@ -285,7 +306,9 @@ export function routeToPath(route: Route) {
if (route.params.search) {
suffix += `${route.params.search}`;
}
if (route.params.hash) {
if (route.params.line) {
suffix += `#${route.params.line}`;
} else if (route.params.hash) {
suffix += `#${route.params.hash}`;
}
}
@@ -343,13 +366,16 @@ function resolveProjectRoute(
}
if (content === "tree") {
const line = url.href.match(/#L\d+$/)?.pop();
const hash = url.href.match(/#{1}[^#.]+$/)?.pop();
return {
view: { resource: "tree" },
urn,
peer,
path: undefined,
revision: undefined,
hash: url.hash.substring(1),
line: line?.substring(1),
hash: hash?.substring(1),
route: segments.join("/"),
};
} else if (content === "history") {
+7 -4
View File
@@ -1,8 +1,8 @@
///<reference types="vitest" />
import path from "path";
import type { UserConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import path from "path";
import pluginRewriteAll from "vite-plugin-rewrite-all";
import { svelte } from "@sveltejs/vite-plugin-svelte";
const config: UserConfig = {
optimizeDeps: {
@@ -38,8 +38,11 @@ const config: UserConfig = {
},
},
define: {
// eslint-disable-next-line @typescript-eslint/naming-convention
"process.env": { READABLE_STREAM: "disable" },
"process.env": {
// eslint-disable-next-line @typescript-eslint/naming-convention
READABLE_STREAM: "disable",
hashRouting: Boolean(process.env.HASH_ROUTING),
},
},
build: {
outDir: "build",