AkurAI Build
Menu

Public repository / 275d4fa11a39ecc30e2caf604d5a0d499910e8db

AkurAI-Mail / src/lib/webmail/RichTextEditor.svelte

Latest change 22202d1bde69be4ace8a299dffd91f8ff24378df - Recover deployed webmail frontend (frontend parity with EC2 a6afffc) by Ólafur Búi Ólafsson

<script lang="ts">
  import { Bold, Italic, Link2, List, ListOrdered, Quote } from "lucide-svelte";

  let { html = $bindable(""), text = $bindable(""), placeholder = "", resetKey = 0 }: { html?: string; text?: string; placeholder?: string; resetKey?: number } = $props();
  let editor: HTMLDivElement;
  let loadedKey = -1;

  // Load `html` into the editor only when the parent bumps resetKey (new compose, reply, forward),
  // so typing does not reset the caret.
  $effect(() => {
    if (editor && resetKey !== loadedKey) {
      loadedKey = resetKey;
      editor.innerHTML = html;
      text = editor.innerText;
    }
  });

  function sync() {
    html = editor.innerHTML;
    text = editor.innerText;
  }

  function exec(command: string, value?: string) {
    editor.focus();
    document.execCommand(command, false, value);
    sync();
  }

  function insertLink() {
    const url = window.prompt("Link URL");
    if (url) exec("createLink", url);
  }
</script>

<div class="rte-toolbar" role="toolbar" aria-label="Formatting">
  <button type="button" aria-label="Bold" title="Bold" onclick={() => exec("bold")}><Bold size={16} /></button>
  <button type="button" aria-label="Italic" title="Italic" onclick={() => exec("italic")}><Italic size={16} /></button>
  <button type="button" aria-label="Bulleted list" title="Bulleted list" onclick={() => exec("insertUnorderedList")}><List size={16} /></button>
  <button type="button" aria-label="Numbered list" title="Numbered list" onclick={() => exec("insertOrderedList")}><ListOrdered size={16} /></button>
  <button type="button" aria-label="Link" title="Link" onclick={insertLink}><Link2 size={16} /></button>
  <button type="button" aria-label="Quote" title="Quote" onclick={() => exec("formatBlock", "blockquote")}><Quote size={16} /></button>
</div>
<div class="rte-body" bind:this={editor} contenteditable="true" role="textbox" aria-multiline="true" aria-label="Message" data-placeholder={placeholder} oninput={sync}></div>

<style>
  .rte-toolbar {
    border-bottom: 1px solid var(--line-soft);
    gap: 2px;
    padding: 4px 0 8px;
    display: flex;
  }
  .rte-toolbar button {
    block-size: 30px;
    inline-size: 30px;
    color: var(--ink);
    background: 0 0;
    border: 1px solid #0000;
    border-radius: 6px;
    place-items: center;
    padding: 0;
    display: grid;
  }
  .rte-toolbar button:hover {
    background: var(--surface-soft);
    border-color: var(--line);
  }
  .rte-body {
    outline: none;
    flex: 1;
    min-height: 190px;
    padding: 8px 0;
    overflow: auto;
  }
  .rte-body:empty:before {
    content: attr(data-placeholder);
    color: var(--muted);
    opacity: 0.82;
  }
  .rte-body :global(blockquote) {
    border-left: 3px solid var(--line);
    color: var(--muted);
    margin: 6px 0;
    padding-left: 10px;
  }
  .rte-body :global(ul),
  .rte-body :global(ol) {
    padding-left: 22px;
  }
  .rte-body :global(a) {
    color: var(--accent);
  }
</style>