Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add hash routing
Sebastian Martinez committed 3 years ago
commit f38893407ab355b5ce107c1242b5863e7252685d
parent cd60ad7298e3b5f1fd2f319ed30607eca51987bc
15 files changed +87 -35
modified .eslintrc.json
@@ -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",
deleted _redirects
@@ -1 +0,0 @@
-
/*   /index.html   200
modified cypress/e2e/projectCommits.spec.ts
@@ -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",
      );
    });
modified cypress/e2e/projectHeader.spec.ts
@@ -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",
      );
    });
modified cypress/support/e2e.ts
@@ -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 };
modified package.json
@@ -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",
added public/_redirects
@@ -0,0 +1 @@
+
/*   /index.html   200
deleted scripts/build
@@ -1,5 +0,0 @@
-
#!/bin/sh
-
set -e
-

-
vite build
-
cp _redirects build/_redirects
modified src/base/projects/Blob.svelte
@@ -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 activeRoute: ProjectRoute;
  export let blob: Blob;
-
  export let line: string | null;
  export let getImage: (path: string) => Promise<Blob>;
-
  export let activeRoute: ProjectRoute;
+
  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}
modified src/base/projects/Browser.svelte
@@ -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 };
modified src/base/projects/View.svelte
@@ -51,6 +51,7 @@
      router.updateProjectRoute({
        revision,
        path,
+
        line: activeRoute.params.line,
        hash: activeRoute.params.hash,
        route: undefined,
      });
modified src/index.ts
@@ -4,4 +4,12 @@ const app = new App({
  target: document.body,
});

+
declare global {
+
  namespace NodeJS {
+
    interface ProcessEnv {
+
      hashRouting: number;
+
    }
+
  }
+
}
+

export default app;
modified src/router/definitions.ts
@@ -19,6 +19,7 @@ export interface ProjectsParams {
    | { resource: "patch"; params: { patch: string } }
    | { resource: "patches" };
  hash?: string;
+
  line?: string;
  path?: string;
  peer?: string;
  profile?: string;
modified src/router/index.ts
@@ -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") {
modified vite.config.ts
@@ -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",