Install and status
Install the published editor with pnpm add @texuli/editor. It is pre-1.0, so a new minor version may break the API. The editor surface, toolbar, link dialog, controller, Markdown codec and clipboard import are on npm.
Clipboard HTML through @texuli/core/html, Paste as Markdown, the block-type menu, the emoji button and the export to chat dialects were released in 0.18.0. The editor ships no emoji data: the emoji picker comes from texuli/emoji, and the :shortcode: rule uses a lookup you pass.
<script lang="ts">
import { RichTextEditor } from '@texuli/editor/editor';
</script>
<RichTextEditor label="Notes" placeholder="Write the update…" />Markdown in and out
markdownToDocument reads Markdown into a valid editor document and documentToMarkdown writes it back, so the saved text reads back to the same document. Both live in @texuli/editor/markdown, outside the package barrel.
- Reading is untrusted by default: raw HTML stays literal text and every link passes the URL policy, so a refused link keeps only its text.
- The editor holds headings 1 to 4, paragraphs, bulleted, numbered and todo lists, quotes, fenced code, dividers and the strong, em, strike, code and link marks. Deeper headings become level 4, with a diagnostic.
- Input is capped and read in linear time; nothing here produces HTML.
import { documentToMarkdown, markdownToDocument } from '@texuli/editor/markdown';
const { doc, diagnostics } = markdownToDocument(agentText);
const saved = documentToMarkdown(doc);Safe paste
Clipboard HTML is read without a DOM by htmlToMarkdownTree from @texuli/core/html at the untrusted tier, then mapped onto the editor vocabulary. In development: released in 0.18.0.
- Scripts, styles, frames, SVG and other raw-text elements are dropped with their content, and every attribute but the few the editor maps is gone, event handlers included.
- A link the URL policy refuses, such as
javascript:, keeps its text and counts inlinksRemoved. - Images become their alt text, and table cells become paragraphs.
- Plain text stays plain. Paste as Markdown is a separate command: Mod+Alt+Shift+V in the editor, or
controller.pasteMarkdown(text), which loads the Markdown codec on first use.
import { EditorController } from '@texuli/editor/controller';
const controller = new EditorController();
const result = controller.paste(event.clipboardData);
// result.linksRemoved: links refused by the URL policyBlock-type menu
Pass blockMenu to RichTextEditor or EditorToolbar, or use BlockTypeMenu from @texuli/editor/block-menu. In development: released in 0.18.0.
- It follows the APG menu button pattern: the button names the current kind, and the menu holds item radios for paragraph, headings 1 to 4, bulleted, numbered and check lists, quote and code block.
- Enter, Space and Down Arrow open it on the first item, Up Arrow on the last; Escape closes it and returns focus.
- A choice is one undo step and is announced. A plain block leaves the lists and the quote around the caret.
<RichTextEditor value={doc} blockMenu label="Release notes" />Emoji
Pass emoji to RichTextEditor or EditorToolbar for an emoji button, or use EditorEmojiButton from @texuli/editor/emoji. In development: released in 0.18.0.
- The button opens
EmojiPickerfromtexuli/emojiin a popover: search, a keyboard grid and skin tones, with recents kept by the host. The picker and its catalog load when the popover opens. - A chosen emoji is one undo step, apart from typing, and focus returns to the text.
- A typed
:name:becomes its emoji when you pass ashortcodeslookup; one undo restores the shortcode. It never runs inside code. getEmojimatches the catalog's canonical shortcodes, such as:party_popper:. To accept common aliases such as:tada:too, build a map from the aliases inemojiCatalog.records, as the demo on the landing does.
import { getEmoji } from '@texuli/emojis/catalog';
import { EditorController } from '@texuli/editor/controller';
const controller = new EditorController({
shortcodes: (name) => getEmoji(`:${name}:`)?.unicode,
});Export to chat
documentToDialect in @texuli/editor/markdown writes the document for Slack, Discord, WhatsApp, Telegram (MarkdownV2 or HTML), Microsoft Teams, Google Chat, Mattermost or plain text. DialectExport from @texuli/editor/export shows the text, a Copy button and the losses. In development: released in 0.18.0.
- Every link is checked again by the URL policy, untrusted by default.
- Broadcast mentions are never written:
@everyone,@hereand@channelstay text. - What a format cannot carry is returned in
losses, grouped with a count. No chat format has checklists, so items keep their state as ☑ or ☐. - Agents get the same conversion through the
md.convertMCP operation.
import { documentToDialect } from '@texuli/editor/markdown';
const { value, losses } = documentToDialect(doc, 'slack');
