Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Convert emojis to twemojis
Sebastian Martinez committed 3 years ago
commit cd60ad7298e3b5f1fd2f319ed30607eca51987bc
parent ae9d9b4024474f3932952310a51d36962ae8a021
34 files changed +247 -58
modified .gitignore
@@ -8,6 +8,9 @@ KaTeX_**.ttf
KaTeX_**.woff
KaTeX_**.woff2

+
# Twemoji Assets
+
public/twemoji/*.svg
+

# Integration Tests
cypress/screenshots

modified package-lock.json
@@ -27,6 +27,7 @@
        "siwe": "^2.0.5",
        "svelte": "^3.52.0",
        "svelte-preprocess": "^4.10.7",
+
        "twemoji": "^14.0.2",
        "util": "^0.12.5"
      },
      "devDependencies": {
@@ -6772,6 +6773,62 @@
      "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==",
      "dev": true
    },
+
    "node_modules/twemoji": {
+
      "version": "14.0.2",
+
      "resolved": "https://registry.npmjs.org/twemoji/-/twemoji-14.0.2.tgz",
+
      "integrity": "sha512-BzOoXIe1QVdmsUmZ54xbEH+8AgtOKUiG53zO5vVP2iUu6h5u9lN15NcuS6te4OY96qx0H7JK9vjjl9WQbkTRuA==",
+
      "dependencies": {
+
        "fs-extra": "^8.0.1",
+
        "jsonfile": "^5.0.0",
+
        "twemoji-parser": "14.0.0",
+
        "universalify": "^0.1.2"
+
      }
+
    },
+
    "node_modules/twemoji-parser": {
+
      "version": "14.0.0",
+
      "resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-14.0.0.tgz",
+
      "integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA=="
+
    },
+
    "node_modules/twemoji/node_modules/fs-extra": {
+
      "version": "8.1.0",
+
      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+
      "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+
      "dependencies": {
+
        "graceful-fs": "^4.2.0",
+
        "jsonfile": "^4.0.0",
+
        "universalify": "^0.1.0"
+
      },
+
      "engines": {
+
        "node": ">=6 <7 || >=8"
+
      }
+
    },
+
    "node_modules/twemoji/node_modules/fs-extra/node_modules/jsonfile": {
+
      "version": "4.0.0",
+
      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+
      "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+
      "optionalDependencies": {
+
        "graceful-fs": "^4.1.6"
+
      }
+
    },
+
    "node_modules/twemoji/node_modules/jsonfile": {
+
      "version": "5.0.0",
+
      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz",
+
      "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==",
+
      "dependencies": {
+
        "universalify": "^0.1.2"
+
      },
+
      "optionalDependencies": {
+
        "graceful-fs": "^4.1.6"
+
      }
+
    },
+
    "node_modules/twemoji/node_modules/universalify": {
+
      "version": "0.1.2",
+
      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+
      "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+
      "engines": {
+
        "node": ">= 4.0.0"
+
      }
+
    },
    "node_modules/type-check": {
      "version": "0.4.0",
      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -12013,6 +12070,58 @@
      "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==",
      "dev": true
    },
+
    "twemoji": {
+
      "version": "14.0.2",
+
      "resolved": "https://registry.npmjs.org/twemoji/-/twemoji-14.0.2.tgz",
+
      "integrity": "sha512-BzOoXIe1QVdmsUmZ54xbEH+8AgtOKUiG53zO5vVP2iUu6h5u9lN15NcuS6te4OY96qx0H7JK9vjjl9WQbkTRuA==",
+
      "requires": {
+
        "fs-extra": "^8.0.1",
+
        "jsonfile": "^5.0.0",
+
        "twemoji-parser": "14.0.0",
+
        "universalify": "^0.1.2"
+
      },
+
      "dependencies": {
+
        "fs-extra": {
+
          "version": "8.1.0",
+
          "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+
          "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+
          "requires": {
+
            "graceful-fs": "^4.2.0",
+
            "jsonfile": "^4.0.0",
+
            "universalify": "^0.1.0"
+
          },
+
          "dependencies": {
+
            "jsonfile": {
+
              "version": "4.0.0",
+
              "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+
              "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+
              "requires": {
+
                "graceful-fs": "^4.1.6"
+
              }
+
            }
+
          }
+
        },
+
        "jsonfile": {
+
          "version": "5.0.0",
+
          "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz",
+
          "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==",
+
          "requires": {
+
            "graceful-fs": "^4.1.6",
+
            "universalify": "^0.1.2"
+
          }
+
        },
+
        "universalify": {
+
          "version": "0.1.2",
+
          "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+
          "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+
        }
+
      }
+
    },
+
    "twemoji-parser": {
+
      "version": "14.0.0",
+
      "resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-14.0.0.tgz",
+
      "integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA=="
+
    },
    "type-check": {
      "version": "0.4.0",
      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
modified package.json
@@ -4,7 +4,7 @@
    "start": "vite",
    "serve": "vite preview",
    "build": "scripts/build",
-
    "postinstall": "scripts/copy-katex-assets",
+
    "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",
    "test:unit": "TZ='UTC' vitest run",
@@ -55,6 +55,7 @@
    "siwe": "^2.0.5",
    "svelte": "^3.52.0",
    "svelte-preprocess": "^4.10.7",
+
    "twemoji": "^14.0.2",
    "util": "^0.12.5"
  }
}
added public/twemoji/.gitkeep
modified public/typography.css
@@ -103,3 +103,9 @@ p {
  color: var(--color-foreground);
  border-bottom-color: var(--color-foreground);
}
+
.txt-emoji {
+
  height: 1em;
+
  width: 1em;
+
  margin: 0 0.05em 0 0.1em;
+
  vertical-align: -0.1em;
+
}
added scripts/install-twemoji-assets
@@ -0,0 +1,9 @@
+
#!/bin/bash
+
set -Eeou pipefail
+

+
version="$(node -e 'console.log(require("twemoji/package.json").version)')"
+

+
echo "Installing Twemoji SVG assets v${version}"
+

+
curl -sSL "https://github.com/twitter/twemoji/archive/refs/tags/v${version}.tar.gz" \
+
  | tar -x -z -C public/twemoji/ --strip-components=3 "twemoji-${version}/assets/svg"
modified src/App.svelte
@@ -2,7 +2,7 @@
  import { Connection, state, session } from "@app/session";
  import { getWallet } from "@app/wallet";
  import { initialize, activeRouteStore } from "@app/router";
-
  import { unreachable } from "@app/utils";
+
  import { twemoji, unreachable } from "@app/utils";

  import ColorPalette from "@app/ColorPalette.svelte";
  import Faucet from "@app/base/faucet/Routes.svelte";
@@ -118,7 +118,7 @@
    <div class="wrapper">
      <Modal error subtle>
        <span slot="title">
-
          <div class="emoji">๐Ÿ‘ป</div>
+
          <div class="emoji" use:twemoji>๐Ÿ‘ป</div>
          <div>Error connecting to network</div>
        </span>

modified src/Dropdown.svelte
@@ -1,6 +1,7 @@
<script lang="ts" strictEvents>
  import { createEventDispatcher } from "svelte";
  import Badge from "@app/Badge.svelte";
+
  import { twemoji } from "@app/utils";

  export let items: {
    key: string;
@@ -54,6 +55,7 @@
      <div
        class="dropdown-item"
        class:selected={value === selected}
+
        use:twemoji
        on:click={() => onSelect(value)}
        {title}>
        {@html key}
modified src/ErrorModal.svelte
@@ -1,9 +1,10 @@
<script lang="ts" strictEvents>
  import type { Err } from "@app/error";

-
  import { createEventDispatcher } from "svelte";
-
  import Modal from "@app/Modal.svelte";
  import Button from "@app/Button.svelte";
+
  import Modal from "@app/Modal.svelte";
+
  import { createEventDispatcher } from "svelte";
+
  import { twemoji } from "@app/utils";

  const dispatch = createEventDispatcher<{ close: never }>();

@@ -20,7 +21,7 @@
</script>

<Modal on:close error {floating} {subtle}>
-
  <span slot="title">
+
  <span slot="title" use:twemoji>
    {#if emoji}
      <div>{emoji}</div>
    {/if}
modified src/Markdown.svelte
@@ -5,8 +5,10 @@
  import type * as proj from "@app/project";
  import {
    markdownExtensions as extensions,
+
    renderer,
    getImageMime,
    isUrl,
+
    twemoji,
    scrollIntoView,
  } from "@app/utils";
  import dompurify from "dompurify";
@@ -17,12 +19,12 @@
  export let doc = matter(content);

  const frontMatter = Object.entries(doc.data);
-
  marked.use({ extensions });
+
  marked.use({ extensions, renderer });

  let container: HTMLElement;

  const render = (content: string): string =>
-
    dompurify.sanitize(marked.parse(content));
+
    dompurify.sanitize(marked.parse(content, { headerIds: false }));

  onMount(() => {
    // Don't underline <a> tags that contain images.
@@ -43,7 +45,7 @@
      const path = i.getAttribute("src");

      // Make sure the source isn't a URL before trying to fetch it from the repo
-
      if (path && !isUrl(path)) {
+
      if (path && !isUrl(path) && !path.startsWith("/twemoji")) {
        getImage(path).then(blob => {
          if (blob.content) {
            const mime = getImageMime(path);
@@ -267,6 +269,6 @@
  </div>
{/if}

-
<div class="markdown" bind:this={container}>
+
<div class="markdown" bind:this={container} use:twemoji>
  {@html render(doc.content)}
</div>
modified src/NotFound.svelte
@@ -1,5 +1,6 @@
<script lang="ts">
  import * as router from "@app/router";
+
  import { twemoji } from "@app/utils";

  import Button from "@app/Button.svelte";
  import Modal from "@app/Modal.svelte";
@@ -9,7 +10,7 @@
</script>

<Modal subtle>
-
  <span slot="title">๐Ÿœ๏ธ</span>
+
  <span slot="title" use:twemoji>๐Ÿœ๏ธ</span>
  <span slot="body">
    <p class="txt-medium txt-highlight">
      <span class="txt-bold">{title}</span>
modified src/Placeholder.svelte
@@ -1,5 +1,7 @@
<script lang="ts">
-
  export let icon: string;
+
  import { twemoji } from "@app/utils";
+

+
  export let emoji: string;
</script>

<style>
@@ -17,14 +19,14 @@
    padding: 1rem 0;
    font-weight: var(--font-weight-bold);
  }
-
  .placeholder .icon {
+
  .placeholder .emoji {
    margin-bottom: 1rem;
  }
</style>

<div class="placeholder">
  <header>
-
    <div class="icon txt-large">{icon}</div>
+
    <div class="emoji txt-large" use:twemoji>{emoji}</div>
    <slot name="title" />
  </header>
  <slot name="body" />
modified src/RadicleUrn.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
-
  import { parseRadicleId, toClipboard } from "@app/utils";
+
  import { parseRadicleId, toClipboard, twemoji } from "@app/utils";
  import Button from "@app/Button.svelte";

  export let urn: string;
@@ -35,7 +35,7 @@

<div class="layout-desktop">
  <div class="urn">
-
    <span class="icon">๐ŸŒฑ</span>
+
    <span class="icon" use:twemoji>๐ŸŒฑ</span>
    <span class="txt-faded">rad:git:</span>
    <span>{parseRadicleId(urn)}</span>
  </div>
modified src/SeedAddress.spec.ts
@@ -15,7 +15,12 @@ describe("SeedAddress", () => {
        port: 8776,
      },
    });
-
    cy.get("span.seed-icon").should("have.text", "๐ŸŒฑ");
+
    cy.get("span.seed-icon img").should("have.attr", "alt", "๐ŸŒฑ");
+
    cy.get("span.seed-icon img").should(
+
      "have.attr",
+
      "src",
+
      "/twemoji/1f331.svg",
+
    );
    cy.contains("seed.cloudhead.io").should("be.visible");
  });

@@ -31,7 +36,12 @@ describe("SeedAddress", () => {
        full: true,
      },
    });
-
    cy.get("span.seed-icon").should("have.text", "๐ŸŒฑ");
+
    cy.get("span.seed-icon img").should("have.attr", "alt", "๐ŸŒฑ");
+
    cy.get("span.seed-icon img").should(
+
      "have.attr",
+
      "src",
+
      "/twemoji/1f331.svg",
+
    );
    cy.get("body")
      .contains("hydkkkโ€ฆcoygh1@seed.cloudhead.io")
      .should("be.visible");
modified src/SeedAddress.svelte
@@ -1,5 +1,10 @@
<script lang="ts">
-
  import { formatSeedAddress, formatSeedId, formatSeedHost } from "@app/utils";
+
  import {
+
    formatSeedAddress,
+
    formatSeedId,
+
    formatSeedHost,
+
    twemoji,
+
  } from "@app/utils";
  import type { Seed } from "@app/base/seeds/Seed";
  import Clipboard from "@app/Clipboard.svelte";
  import Link from "@app/router/Link.svelte";
@@ -37,7 +42,7 @@

<div class="wrapper">
  <div class="seed-address">
-
    <span class="seed-icon">{seed.emoji}</span>
+
    <span class="seed-icon" use:twemoji>{seed.emoji}</span>
    {#if full}
      <span>
        <Link
modified src/base/faucet/Withdraw.svelte
@@ -3,6 +3,7 @@
  import type { Wallet } from "@app/wallet";

  import { onMount } from "svelte";
+
  import { twemoji } from "@app/utils";

  import * as router from "@app/router";
  import Button from "@app/Button.svelte";
@@ -64,7 +65,7 @@
    on:close={back} />
{:else}
  <Modal>
-
    <span slot="title">
+
    <span slot="title" use:twemoji>
      {#if state.status === Status.Success}
        <div>๐ŸŽ‰</div>
      {:else}
modified src/base/home/Index.svelte
@@ -8,7 +8,7 @@
  import Widget from "@app/base/projects/Widget.svelte";
  import config from "@app/config.json";
  import { Project } from "@app/project";
-
  import { setOpenGraphMetaTag } from "@app/utils";
+
  import { setOpenGraphMetaTag, twemoji } from "@app/utils";

  setOpenGraphMetaTag([
    { prop: "og:title", content: "Radicle Interface" },
@@ -92,7 +92,7 @@

<main>
  <div class="blurb">
-
    <p>
+
    <p use:twemoji>
      Radicle ๐ŸŒฑ enables developers ๐Ÿง™ to securely collaborate ๐Ÿ” on software
      over a peer-to-peer network ๐ŸŒ built on Git.
    </p>
modified src/base/projects/Blob.svelte
@@ -5,7 +5,7 @@
  import Readme from "@app/base/projects/Readme.svelte";
  import HeaderToggleLabel from "@app/base/projects/HeaderToggleLabel.svelte";
  import ProjectLink from "@app/router/ProjectLink.svelte";
-
  import { isMarkdownPath, scrollIntoView } from "@app/utils";
+
  import { isMarkdownPath, scrollIntoView, twemoji } from "@app/utils";
  import { updateProjectRoute } from "@app/router";

  export let blob: Blob;
@@ -201,7 +201,7 @@
  <div class="container">
    {#if blob.binary}
      <div class="binary">
-
        <div>๐Ÿ‘€</div>
+
        <div use:twemoji>๐Ÿ‘€</div>
        <span class="txt-tiny">Binary content</span>
      </div>
    {:else if showMarkdown}
modified src/base/projects/Browser.svelte
@@ -208,7 +208,7 @@
        {:then blob}
          <Blob {line} {blob} {getImage} {activeRoute} />
        {:catch}
-
          <Placeholder icon="๐Ÿ‚">
+
          <Placeholder emoji="๐Ÿ‚">
            <span slot="title">
              {#if path !== "/"}
                <div class="txt-monospace">{path}</div>
@@ -226,7 +226,7 @@
      </div>
    {:else}
      <div class="placeholder">
-
        <Placeholder icon="๐Ÿ‘€">
+
        <Placeholder emoji="๐Ÿ‘€">
          <span slot="title">Nothing to show</span>
          <span slot="body">We couldn't find any files at this revision.</span>
        </Placeholder>
modified src/base/projects/Issue/IssueTeaser.svelte
@@ -1,9 +1,11 @@
<script lang="ts">
-
  import { onMount } from "svelte";
-
  import { formatObjectId } from "@app/cobs";
  import type { Issue } from "@app/issue";
  import type { Wallet } from "@app/wallet";
+

  import { Profile, ProfileType } from "@app/profile";
+
  import { formatObjectId } from "@app/cobs";
+
  import { onMount } from "svelte";
+
  import { twemoji } from "@app/utils";

  import Authorship from "@app/Authorship.svelte";

@@ -122,7 +124,7 @@
  {#if commentCount > 0}
    <div class="column-right">
      <div class="comment-count">
-
        <span class="txt-tiny emoji">๐Ÿ’ฌ</span>
+
        <span class="txt-tiny emoji" use:twemoji>๐Ÿ’ฌ</span>
        <span>{commentCount}</span>
      </div>
    </div>
modified src/base/projects/Issues.svelte
@@ -90,7 +90,7 @@
      {/each}
    </div>
  {:else}
-
    <Placeholder icon="๐Ÿฃ">
+
    <Placeholder emoji="๐Ÿฃ">
      <div slot="title">{capitalize(state)} issues</div>
      <div slot="body">No issues matched the current filter</div>
    </Placeholder>
modified src/base/projects/Patch.svelte
@@ -142,7 +142,7 @@
        stats={revision.changeset.stats}
        on:browse={e => onBrowse(e, revision.oid)} />
    {:else if activeTab === PatchTab.Diff}
-
      <Placeholder icon="๐Ÿณ">
+
      <Placeholder emoji="๐Ÿณ">
        <span slot="title">No changeset found</span>
        <span slot="body">
          We couldn't find a changeset related to this patch or revision
modified src/base/projects/Patch/PatchTeaser.svelte
@@ -1,9 +1,11 @@
<script lang="ts">
-
  import { onMount } from "svelte";
-
  import { formatObjectId } from "@app/cobs";
  import type { Patch } from "@app/patch";
  import type { Wallet } from "@app/wallet";
+

  import { Profile, ProfileType } from "@app/profile";
+
  import { formatObjectId } from "@app/cobs";
+
  import { onMount } from "svelte";
+
  import { twemoji } from "@app/utils";

  import Authorship from "@app/Authorship.svelte";

@@ -122,7 +124,7 @@
  {#if commentCount > 0}
    <div class="column-right">
      <div class="comment-count">
-
        <span class="txt-tiny emoji">๐Ÿ’ฌ</span>
+
        <span class="txt-tiny emoji" use:twemoji>๐Ÿ’ฌ</span>
        <span>{commentCount}</span>
      </div>
    </div>
modified src/base/projects/Patches.svelte
@@ -87,7 +87,7 @@
      {/each}
    </div>
  {:else}
-
    <Placeholder icon="๐Ÿ–">
+
    <Placeholder emoji="๐Ÿ–">
      <div slot="title">{capitalize(state)} patches</div>
      <div slot="body">No patches matched the current filter</div>
    </Placeholder>
modified src/base/projects/View.svelte
@@ -176,7 +176,7 @@
    {:catch e}
      <div class="message">
        {#if peer}
-
          <Placeholder icon="๐Ÿ‚">
+
          <Placeholder emoji="๐Ÿ‚">
            <span slot="title">
              <span class="txt-monospace">{formatSeedId(peer)}</span>
            </span>
@@ -188,7 +188,7 @@
            </span>
          </Placeholder>
        {:else}
-
          <Placeholder icon="๐Ÿ‚">
+
          <Placeholder emoji="๐Ÿ‚">
            <span slot="body">
              <span style="display: block">Couldn't load source tree.</span>
              <span>{e.message}</span>
modified src/base/registrations/New.svelte
@@ -9,7 +9,7 @@
  import Loading from "@app/Loading.svelte";
  import Message from "@app/Message.svelte";
  import Modal from "@app/Modal.svelte";
-
  import { formatAddress } from "@app/utils";
+
  import { formatAddress, twemoji } from "@app/utils";
  import { registrar } from "./registrar";
  import { session } from "@app/session";

@@ -80,7 +80,7 @@

<Modal narrow>
  <span slot="title">
-
    <div>๐ŸŒ</div>
+
    <div use:twemoji>๐ŸŒ</div>
    <span>Register a name</span>
  </span>

modified src/base/registrations/Submit.svelte
@@ -10,7 +10,12 @@
  import ErrorModal from "@app/ErrorModal.svelte";
  import Loading from "@app/Loading.svelte";
  import Modal from "@app/Modal.svelte";
-
  import { registerName, State, state } from "./registrar";
+
  import {
+
    registerName,
+
    State,
+
    state,
+
  } from "@app/base/registrations/registrar";
+
  import { twemoji } from "@app/utils";

  export let wallet: Wallet;
  export let name: string;
@@ -75,7 +80,7 @@
      })} />
{:else}
  <Modal>
-
    <span slot="title">
+
    <span slot="title" use:twemoji>
      {#if $state.connection === State.Registered}
        <div>๐ŸŽ‰</div>
      {:else}
modified src/base/registrations/Update.svelte
@@ -1,14 +1,16 @@
<script lang="ts" strictEvents>
-
  import { onMount, createEventDispatcher } from "svelte";
-
  import { setRecords } from "./resolver";
  import type { EnsRecord } from "./resolver";
  import type { Registration } from "./registrar";
  import type { Wallet } from "@app/wallet";
+
  import type { State } from "@app/utils";
+

+
  import Button from "@app/Button.svelte";
  import Loading from "@app/Loading.svelte";
  import Modal from "@app/Modal.svelte";
-
  import type { State } from "@app/utils";
  import { Status } from "@app/utils";
-
  import Button from "@app/Button.svelte";
+
  import { onMount, createEventDispatcher } from "svelte";
+
  import { setRecords } from "./resolver";
+
  import { twemoji } from "@app/utils";

  export let domain: string;
  export let wallet: Wallet;
@@ -52,7 +54,7 @@

<Modal floating error={state.status === Status.Failed}>
  <span slot="title">
-
    <div>๐Ÿงพ</div>
+
    <div use:twemoji>๐Ÿงพ</div>
    <div>Update registration</div>
  </span>
  <span slot="subtitle">
modified src/base/registrations/View.svelte
@@ -18,7 +18,7 @@
  import { assert } from "@app/error";
  import { defaultHttpApiPort } from "@app/base/seeds/Seed";
  import { getRegistration, getOwner } from "./registrar";
-
  import { isAddressEqual, isReverseRecordSet } from "@app/utils";
+
  import { isAddressEqual, isReverseRecordSet, twemoji } from "@app/utils";
  import { session } from "@app/session";

  enum Status {
@@ -239,7 +239,7 @@
{:else if state.status === Status.NotFound}
  <Modal subtle>
    <span slot="title" class="txt-highlight">
-
      <div>๐Ÿ„</div>
+
      <div use:twemoji>๐Ÿ„</div>
      {domain}
    </span>

modified src/base/seeds/View.svelte
@@ -2,7 +2,7 @@
  import type { Wallet } from "@app/wallet";
  import type { Stats } from "@app/base/seeds/Seed";
  import type { ProjectInfo } from "@app/project";
-
  import { formatSeedId, formatSeedHost } from "@app/utils";
+
  import { formatSeedId, formatSeedHost, twemoji } from "@app/utils";
  import { Seed } from "@app/base/seeds/Seed";
  import Loading from "@app/Loading.svelte";
  import SeedAddress from "@app/SeedAddress.svelte";
@@ -122,7 +122,7 @@
      <span class="title txt-title">
        <span class="txt-bold">
          {hostName}
-
          <span class="layout-desktop-inline">{seed.emoji}</span>
+
          <span class="layout-desktop-inline" use:twemoji>{seed.emoji}</span>
        </span>
      </span>
      <!-- User Session -->
modified src/components/Modal/ConnectWallet.svelte
@@ -4,9 +4,10 @@
  import { createEventDispatcher } from "svelte";
  import { qrcode } from "pure-svg-code";

+
  import Button from "@app/Button.svelte";
  import Modal from "@app/Modal.svelte";
  import { state } from "@app/session";
-
  import Button from "@app/Button.svelte";
+
  import { twemoji } from "@app/utils";

  export let uri: string;
  export let wallet: Wallet;
@@ -57,7 +58,7 @@
<div class="wrapper">
  <Modal floating center>
    <div slot="title">
-
      <div>๐Ÿ‘›</div>
+
      <div use:twemoji>๐Ÿ‘›</div>
      <div>Connect your wallet</div>
    </div>

modified src/components/Modal/SearchResults.svelte
@@ -1,6 +1,6 @@
<script lang="ts" strictEvents>
  import Modal from "@app/Modal.svelte";
-
  import { formatRadicleUrn, getSeedEmoji } from "@app/utils";
+
  import { formatRadicleUrn, getSeedEmoji, twemoji } from "@app/utils";
  import type { Wallet } from "@app/wallet";
  import Address from "@app/Address.svelte";
  import Button from "@app/Button.svelte";
@@ -34,7 +34,7 @@
<svelte:window on:click={() => dispatch("close")} />

<Modal center floating>
-
  <span slot="title">๏ธ๐Ÿ”</span>
+
  <span slot="title" use:twemoji>๏ธ๐Ÿ”</span>
  <span slot="subtitle">
    <p class="txt-highlight txt-medium">
      <span class="txt-bold">
modified src/ens/SetName.svelte
@@ -10,7 +10,7 @@
  import Loading from "@app/Loading.svelte";
  import Modal from "@app/Modal.svelte";
  import TextInput from "@app/TextInput.svelte";
-
  import { formatAddress, isAddressEqual } from "@app/utils";
+
  import { formatAddress, isAddressEqual, twemoji } from "@app/utils";

  const dispatch = createEventDispatcher<{ close: never }>();

@@ -76,7 +76,7 @@

{#if state === State.Success}
  <Modal floating>
-
    <div slot="title">โœ…</div>
+
    <div slot="title" use:twemoji>โœ…</div>

    <div slot="subtitle">
      The ENS name for {entity.address} was set to
@@ -123,7 +123,7 @@
{:else}
  <Modal floating>
    <div slot="title">
-
      <div>๐Ÿงฃ</div>
+
      <div use:twemoji>๐Ÿงฃ</div>
      <span>Associate profile</span>
    </div>

modified src/utils.ts
@@ -1,4 +1,5 @@
import { ethers } from "ethers";
+
import twemojiModule from "twemoji";
import md5 from "md5";
import { BigNumber } from "ethers";
import katex from "katex";
@@ -520,4 +521,28 @@ const katexMarkedExtension = {
    }),
};

+
// Overwrites the rendering of heading tokens.
+
// Since there are possible non ASCII characters in headings,
+
// we escape them by replacing them with dashes and,
+
// trim eventual dashes on each side of the string.
+
export const renderer = {
+
  heading(text: string, level: 1 | 2 | 3 | 4 | 5 | 6) {
+
    const escapedText = text
+
      .toLowerCase()
+
      .replace(/[^\w]+/g, "-")
+
      .replace(/^-|-$/g, "");
+

+
    return `<h${level} id="${escapedText}">${text}</h${level}>`;
+
  },
+
};
+

export const markdownExtensions = [emojisMarkedExtension, katexMarkedExtension];
+

+
export function twemoji(node: HTMLElement) {
+
  twemojiModule.parse(node, {
+
    base: "/",
+
    folder: "twemoji",
+
    ext: ".svg",
+
    className: `txt-emoji`,
+
  });
+
}