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

Use global Link component instead of ProjectLink #900

Merged
merged 2 commits into from
Jul 9, 2023
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
2 changes: 2 additions & 0 deletions httpd-client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
CommitHeader,
Diff,
DiffAddedDeletedModifiedChangeset,
DiffCopiedMovedChangeset,
HunkLine,
} from "./lib/project/commit.js";
import type { Issue, IssueState } from "./lib/project/issue.js";
Expand Down Expand Up @@ -39,6 +40,7 @@ export type {
CommitHeader,
Diff,
DiffAddedDeletedModifiedChangeset,
DiffCopiedMovedChangeset,
DiffResponse,
HunkLine,
Issue,
Expand Down
8 changes: 0 additions & 8 deletions src/components/InlineMarkdown.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,11 @@
import dompurify from "dompurify";
import { marked } from "marked";

import { renderer } from "@app/lib/markdown";
import { twemoji } from "@app/lib/utils";

export let content: string;
export let fontSize: "tiny" | "small" | "medium" = "small";

marked.use({
renderer,
// TODO: Disables deprecated options, remove once removed from marked
mangle: false,
headerIds: false,
});

const render = (content: string): string =>
dompurify.sanitize(marked.parseInline(content));
</script>
Expand Down
81 changes: 48 additions & 33 deletions src/components/Markdown.svelte
Original file line number Diff line number Diff line change
@@ -1,51 +1,64 @@
<script lang="ts">
import dompurify from "dompurify";
import matter from "@radicle/gray-matter";
import { marked } from "marked";
import { afterUpdate } from "svelte";
import { marked } from "marked";
import { toDom } from "hast-util-to-dom";

import * as utils from "@app/lib/utils";
import { base, activeRouteStore } from "@app/lib/router";
import * as router from "@app/lib/router";
import { highlight } from "@app/lib/syntax";
import { isUrl, twemoji, scrollIntoView, canonicalize } from "@app/lib/utils";
import {
markdownExtensions as extensions,
renderer,
} from "@app/lib/markdown";
import { updateProjectRoute } from "@app/views/projects/router";
import { Renderer } from "@app/lib/markdown";

export let content: string;
export let hash: string | undefined = undefined;
// If present, resolve all relative links with respect to this URL
export let linkBaseUrl: string | undefined = undefined;
export let path: string = "/";
export let rawPath: string | undefined = undefined;

$: doc = matter(content);
$: frontMatter = Object.entries(doc.data).filter(
([, val]) => typeof val === "string" || typeof val === "number",
);
marked.use({
extensions,
renderer,
// TODO: Disables deprecated options, remove once removed from marked
mangle: false,
headerIds: false,
});

let container: HTMLElement;

const render = (content: string): string =>
dompurify.sanitize(marked.parse(content));
/**
* Do internal navigation on for clicks on anchor elements if possible
*/
function navigateInternalOnAnchor(event: MouseEvent) {
if (router.useDefaultNavigation(event)) {
return;
}

function navigateToMarkdownLink(event: any) {
if (event.target.matches(".file-link")) {
event.preventDefault();
if ($activeRouteStore.resource === "projects") {
void updateProjectRoute({
path: utils.canonicalize(event.target.getAttribute("href"), path),
});
}
let url: URL;
if (!(event.target instanceof HTMLAnchorElement)) {
return;
}
const href = event.target?.getAttribute("href");
if (href === null || href.startsWith("#")) {
return;
}

try {
url = new URL(href, window.location.href);
} catch {
return;
}

event.preventDefault();
void router.navigateToUrl("push", url);
}

function render(content: string): string {
return dompurify.sanitize(
marked.parse(content, {
renderer: new Renderer(linkBaseUrl),
// TODO: Disables deprecated options, remove once removed from marked
mangle: false,
headerIds: false,
}),
);
}

afterUpdate(async () => {
Expand All @@ -68,18 +81,13 @@
if (
imagePath &&
!isUrl(imagePath) &&
!imagePath.startsWith(`${base}twemoji`)
!imagePath.startsWith(`${router.base}twemoji`)
) {
i.setAttribute("src", `${rawPath}/${canonicalize(imagePath, path)}`);
}
}
}

const fileAnchorTags = document.querySelectorAll(".file-link");
fileAnchorTags.forEach(anchorTag => {
anchorTag.addEventListener("click", navigateToMarkdownLink);
});

// Replaces code blocks in the background with highlighted code.
const prefix = "language-";
const nodes = Array.from(document.body.querySelectorAll("pre code"));
Expand Down Expand Up @@ -337,6 +345,13 @@
</div>
{/if}

<div class="markdown" bind:this={container} use:twemoji={{ exclude: ["21a9"] }}>
<!-- The click handler only handles bubbling events from anchor tags -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="markdown"
bind:this={container}
use:twemoji={{ exclude: ["21a9"] }}
on:click={navigateInternalOnAnchor}>
{@html render(doc.content)}
</div>
31 changes: 0 additions & 31 deletions src/components/ProjectLink.svelte

This file was deleted.

62 changes: 38 additions & 24 deletions src/lib/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import dompurify from "dompurify";
import emojis from "@app/lib/emojis";
import katex from "katex";
import { marked } from "marked";
import { isUrl } from "@app/lib/utils";
import { marked, Renderer as BaseRenderer } from "marked";

dompurify.setConfig({
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -11,9 +10,6 @@ dompurify.setConfig({
FORBID_TAGS: ["textarea", "style"],
});

// TODO: Disables deprecated options, remove once removed from marked
marked.use({ mangle: false, headerIds: false });

const emojisMarkedExtension = {
name: "emoji",
level: "inline",
Expand Down Expand Up @@ -126,7 +122,30 @@ const anchorMarkedExtension = {
},
};

export const renderer = {
// TODO: Disables deprecated options, remove once removed from marked
marked.use({
extensions: [
anchorMarkedExtension,
emojisMarkedExtension,
footnoteMarkedExtension,
footnoteReferenceMarkedExtension,
katexMarkedExtension,
],
mangle: false,
headerIds: false,
});

export class Renderer extends BaseRenderer {
#baseUrl: string | undefined;

/**
* If `baseUrl` is provided, all hrefs attributes in anchor tags, except those
* starting with `#`, are resolved with respect to `baseUrl`
*/
constructor(baseUrl: string | undefined) {
super();
this.#baseUrl = baseUrl;
}
// Overwrites the rendering of heading tokens.
// Since there are possible non ASCII characters in headings,
// we escape them by replacing them with dashes and,
Expand All @@ -139,24 +158,19 @@ export const renderer = {
.replace(/^-|-$/g, "");

return `<h${level} id="${escapedText}">${text}</h${level}>`;
},
link(href: string, _title: string, text: string) {
// Adding the file-link class to relative file names,
// so we're able to navigate to the file in the editor.
if (!isUrl(href) && !href.startsWith("#")) {
return `<a href="${href}" class="file-link">${text}</a>`;
} else if (href.startsWith("#")) {
}

link(href: string, _title: string, text: string): string {
if (href.startsWith("#")) {
// By lowercasing we avoid casing mismatches, between headings and links.
return `<a href="${href.toLowerCase()}">${text}</a>`;
} else {
try {
href = new URL(href, this.#baseUrl).href;
} catch {
// Use original href value
}
return `<a href="${href}">${text}</a>`;
}
return `<a href="${href}">${text}</a>`;
},
};

export const markdownExtensions = [
anchorMarkedExtension,
emojisMarkedExtension,
footnoteMarkedExtension,
footnoteReferenceMarkedExtension,
katexMarkedExtension,
];
}
}
24 changes: 19 additions & 5 deletions src/lib/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,18 @@ export function useDefaultNavigation(event: MouseEvent) {
export const base = import.meta.env.VITE_HASH_ROUTING ? "./" : "/";

export async function loadFromLocation(): Promise<void> {
let { pathname, hash } = window.location;
await navigateToUrl("replace", new URL(window.location.href));
}

export async function navigateToUrl(
action: "push" | "replace",
url: URL,
): Promise<void> {
let { pathname, hash } = url;

if (url.origin !== window.origin) {
throw new Error("Cannot navigate to other origin");
}

if (import.meta.env.VITE_HASH_ROUTING) {
if (pathname === "/" && hash && !hash.startsWith("#/")) {
Expand All @@ -52,20 +63,23 @@ export async function loadFromLocation(): Promise<void> {
if (
currentUrl &&
currentUrl.pathname === pathname &&
currentUrl.search === window.location.search
currentUrl.search === url.search
) {
return;
}
}

const relativeUrl = pathname + window.location.search + (hash || "");
const url = new URL(relativeUrl, window.origin);
const relativeUrl = pathname + url.search + (hash || "");
url = new URL(relativeUrl, window.origin);
const route = pathToRoute(url);

if (route) {
await replace(route);
} else {
await replace({ resource: "notFound", params: { url: relativeUrl } });
await navigate(action, {
resource: "notFound",
params: { url: relativeUrl },
});
}
}

Expand Down
16 changes: 14 additions & 2 deletions src/views/projects/Blob.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import type { Blob } from "@httpd-client";
import type { BaseUrl, Blob } from "@httpd-client";

import { afterUpdate, onDestroy, onMount } from "svelte";
import { toHtml } from "hast-util-to-html";
Expand All @@ -11,6 +11,10 @@
import Readme from "@app/views/projects/Readme.svelte";
import SquareButton from "@app/components/SquareButton.svelte";

export let baseUrl: BaseUrl;
export let projectId: string;
export let peer: string | undefined;
export let revision: string | undefined;
export let path: string;
export let hash: string | undefined = undefined;
export let blob: Blob;
Expand Down Expand Up @@ -256,7 +260,15 @@
<span class="txt-tiny">Binary content</span>
</div>
{:else if showMarkdown && blob.content}
<Readme content={blob.content} {rawPath} {path} {hash} />
<Readme
{baseUrl}
{projectId}
{peer}
{revision}
content={blob.content}
{rawPath}
{path}
{hash} />
{:else if content}
<table class="code no-scrollbar">
{@html toHtml(content)}
Expand Down
Loading