Examples / Markdown Previewer

Markdown Previewer

A live markdown editor with real-time HTML preview using marked.

advanced advanced ★ Featured
Loading preview…

A live markdown editor that renders HTML in real-time as you type. The left panel is a textarea for markdown input; the right panel shows the rendered HTML. Demonstrates external library integration and reactive computed values with side effects.

import { html, signal, computed, ref, raw } from "https://esm.sh/@elurjs/core@4.0.1";

const markdown = signal(`# Hello Elur

This is a **live markdown** previewer built with Elur signals.

## Features

- **Bold** and *italic* text
- [Links](https://elur.dev)
- \`Inline code\`

### Code block

\`\`\`
const count = signal(0);
\`\`\`

> Blockquotes work too!

1. Ordered lists
2. Are supported

- [x] Write markdown
- [ ] Ship to production
`);

const renderMarkdown = (md) => {
  let html = md
    .replace(/^### (.*$)/gm, '<h2>$1</h2>')
    .replace(/^## (.*$)/gm, '<h2>$1</h2>')
    .replace(/^# (.*$)/gm, '<h1>$1</h1>')
    .replace(/^> (.*$)/gm, '<blockquote>$1</blockquote>')
    .replace(/^- \[x\] (.*$)/gm, '<li>✓ $1</li>')
    .replace(/^- \[ \] (.*$)/gm, '<li>☐ $1</li>')
    .replace(/^\d+\. (.*$)/gm, '<li>$1</li>')
    .replace(/^- (.*$)/gm, '<li>$1</li>')
    .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
    .replace(/\*(.*?)\*/g, '<em>$1</em>')
    .replace(/`(.*?)`/g, '<code>$1</code>')
    .replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank">$1</a>')
    .replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
    .replace(/\n\n/g, '</p><p>')
    .replace(/\n/g, '<br/>');
  return '<p>' + html + '</p>';
};

const rendered = computed(() => renderMarkdown(markdown.value));

const wordCount = computed(() => {
  const text = markdown.value.trim();
  return text ? text.split(/\s+/).length : 0;
});

// NOTE: <textarea> is a raw-text element — a ${...} interpolation inside it
// renders the binding marker as literal text. Set its initial value
// imperatively via ref after mount instead.
const editorRef = ref();

const app = html`
  <div style="width:100%;max-width:400px">
    <div style="display:flex;gap:8px;margin-bottom:8px">
      <span class="filter-chip active">Editor</span>
      <span class="filter-chip">Preview</span>
      <span style="margin-left:auto;font-size:0.75rem;color:#8484a0;align-self:center">
        ${() => wordCount.value} words
      </span>
    </div>

    <div style="display:flex;gap:8px">
      <textarea
        ref=${editorRef}
        style="flex:1;min-height:200px;padding:12px;border:1px solid #2a2a3a;border-radius:8px;background:#16161f;color:#f0f0f5;font-family:monospace;font-size:0.8rem;resize:vertical"
        placeholder="Type markdown here..."
        @input=${(e) => markdown.value = e.target.value}
      ></textarea>

      <div
        class="md-preview"
        style="flex:1;min-height:200px;overflow-y:auto"
      >${() => raw(rendered.value)}</div>
    </div>
  </div>
`;

app.mount("#app");
editorRef.el.value = markdown.peek();