Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Load images in markdown documents
Alexis Sellier committed 4 years ago
commit 8e77f9f5aec5bb64cc7f25d807fbfb94d429b6d0
parent ec9e4fd16da52ca2ca447b8b4a71e7952ccdef76
4 files changed +72 -5
modified src/Markdown.svelte
@@ -3,14 +3,17 @@
  import { marked } from "marked";
  import fm from "front-matter";
  import type { FrontMatterResult } from "front-matter";
-
  import sanitizeHtml from 'sanitize-html';
+
  import type * as proj from "@app/project";
+
  import { getImageMime } from "@app/utils";
+
  import sanitizeHtml from "sanitize-html";

  export let content: string;
  export let doc: FrontMatterResult<Record<string, string>> = fm(content);
+
  export let getImage: (path: string) => Promise<proj.Blob>;

  let container: HTMLElement;

-
  const sanitize = (content: string): string => {
+
  const render = (content: string): string => {
    return sanitizeHtml(marked.parse(content), {
      allowedTags: sanitizeHtml.defaults.allowedTags.concat([
        "img",
@@ -35,6 +38,24 @@
        e.classList.add("no-underline");
      }
    }
+

+
    // Iterate over all images, and fetch their data from the API, then
+
    // replace the source with a Data-URL. We do this due to the absence
+
    // of a static file server.
+
    for (let i of container.querySelectorAll("img")) {
+
      const path = i.getAttribute("src");
+

+
      if (path) {
+
        getImage(path).then(blob => {
+
          if (blob.content) {
+
            const mime = getImageMime(path);
+
            if (mime) {
+
              i.setAttribute("src", `data:${mime};base64,${blob.content}`);
+
            }
+
          }
+
        });
+
      }
+
    }
  });
</script>

@@ -213,6 +234,6 @@
  {/if}

  <div class="markdown" bind:this={container}>
-
    {@html marked(sanitize(doc.body))}
+
    {@html render(doc.body)}
  </div>
{/if}
modified src/base/projects/Browser.svelte
@@ -48,6 +48,12 @@
    return state.blob;
  };

+
  // Get an image blob based on a relative path.
+
  const getImage = async (imagePath: string): Promise<proj.Blob> => {
+
    const finalPath = utils.canonicalize(imagePath, path);
+
    return project.getBlob(commit, finalPath, { highlight: false });
+
  };
+

  const onSelect = async ({ detail: newPath }: { detail: string }) => {
    // Ensure we don't spend any time in a "loading" state. This means
    // the loading spinner won't be shown, and instead the blob will be
@@ -159,7 +165,7 @@
          <Loading small center />
        {:then blob}
          {#if utils.isMarkdownPath(blob.path)}
-
            <Readme content={blob.content} />
+
            <Readme content={blob.content} {getImage} />
          {:else}
            <Blob line={browser.line} {blob} />
          {/if}
modified src/base/projects/Readme.svelte
@@ -1,6 +1,9 @@
<script lang="ts">
  import Markdown from '@app/Markdown.svelte';
+
  import type * as proj from '@app/project';
+

  export let content: string;
+
  export let getImage: (path: string) => Promise<proj.Blob>;
</script>

<style>
@@ -12,5 +15,5 @@
</style>

<article>
-
  <Markdown {content} />
+
  <Markdown {content} {getImage} />
</article>
modified src/utils.ts
@@ -174,6 +174,43 @@ export function clickOutside(node: HTMLElement, onEventFunction: () => void): an
  };
}

+
// Get the mime type of an image, given a file path.
+
// Returns `null` if unknown.
+
export function getImageMime(path: string): string | null {
+
  const mimes: Record<string, string> = {
+
    'apng': 'image/apng',
+
    'png': 'image/png',
+
    'svg': 'image/svg+xml',
+
    'gif': 'image/gif',
+
    'jpeg': 'image/jpeg',
+
    'jpg': 'image/jpeg',
+
    'webp': 'image/webp',
+
  };
+
  const ext = path.split(".").pop();
+

+
  if (ext) {
+
    if (mimes[ext]) {
+
      return mimes[ext];
+
    }
+
  }
+
  return null;
+
}
+

+
// TODO: Needs testing with absolute and relative paths.
+
export function canonicalize(path: string, base: string): string {
+
  const finalPath = base
+
    .split("/")
+
    .slice(0, -1) // Remove file name.
+
    .concat([path]) // Add image file path.
+
    .join("/");
+

+
  // URL is used to resolve relative paths, eg. `../../assets/image.png`.
+
  const url = new URL(finalPath, document.location.origin);
+
  const pathname = url.pathname.replace(/^\//, "");
+

+
  return pathname;
+
}
+

// Takes a URL, eg. "https://twitter.com/cloudhead", and return "cloudhead".
// Returns the original string if it was unable to extract the username.
export function parseUsername(input: string): string {