Skip to content

About

A Web version of MaxPad, a WYSIWYG markdown editor.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

MarkdownEditor.Web

A WYSIWYG Markdown editor for web pages, as a plain JavaScript component: no framework, no build step and no dependencies. You edit formatted text; the document stays Markdown. It is the web counterpart of MaxPad in MarkdownTools: the same layers, the same rules for what each key does, and a browser version of the VirtualTextView control it is built on.

  • Pluggable. new MarkdownEditor(element, { markdown }) from a script, or a <markdown-editor> element in plain HTML. The element is a form field: a form submits its Markdown, like a <textarea>.
  • WYSIWYG, with the Markdown kept. The syntax is hidden; with the caret in a construct (bold text, a link, a heading's line), its syntax shows, ready to edit, and hides again when the caret leaves. An unedited document round-trips byte for byte, line endings included.
  • CommonMark, plus the extensions MaxPad uses: pipe tables, task lists, YAML front matter, ==underline==, ~~strikethrough~~, ~subscript~ and ^superscript^.
  • Virtualized. Only the paragraphs near the viewport are laid out, so long documents scroll as fast as short ones.

Quick start

Serve the folder (ES modules are not loaded from file:// pages), then:

<div id="editor" style="height: 480px"></div>
<script type="module">
  import { MarkdownEditor } from './src/index.js';

  const editor = new MarkdownEditor('#editor', {
    markdown: '# Hello\n\nSome **bold** text.',
  });
  editor.addEventListener('change', () => save(editor.value));
</script>

Or, with no script of your own:

<script type="module" src="./src/index.js"></script>

<form>
  <markdown-editor name="body" style="height: 320px">
    <script type="text/markdown">
      ## What's new

      - **Faster** loading
      - [ ] Write the *documentation*
    </script>
  </markdown-editor>
</form>

The editor fills the element it is put in, so give that element a height.

The user manual says everything the editor does, with examples: getting started, writing in it (every key, every kind of Markdown, the raw view), and building it into a page (options, methods, events, the toolbar, themes and stylesheets, the element, recipes).

To run the demos and the tests on Windows, with nothing installed but PowerShell:

powershell -ExecutionPolicy Bypass -File tools/serve.ps1

Then open:

The editor: MarkdownEditor

new MarkdownEditor(target, options) puts an editor into target (an element, or a selector of one). The options, all optional:

Option Meaning
markdown The document to start with.
theme 'light' (the default), 'dark', or a theme object (below).
toolbar true (the default), false, or a list of DEFAULT_TOOLBAR's items and of your own buttons (below).
readOnly The document can be read, selected and copied, not changed.
revealSyntax Show the syntax at the caret (true by default).
revealUnfocused Show it while the editor has no focus too (false by default).
showFormattingMarks Show ¶ at the end of each paragraph and ↵ at a line break.
imageBase The URL relative picture addresses are loaded from.
openLinks Ctrl+click opens a link in a new tab (true by default).
prompt async (question, value) => answer: asks for a link's address.
rawMode Start in the raw Markdown view (below).
showParagraphNumbers Each paragraph's number in a gutter left of the text.
showParagraphTypes Each paragraph's type (Heading 1, Bullet, Code...) in the gutter.
showOutline Collapsible headings: triangles beside them fold what they head (off).
screenReader 'auto' (on with a mouse, off on a touch screen), 'on' or 'off': the text at the caret given to a screen reader.

What a page reads and calls:

Member What it is
value The Markdown, in the document's own line endings; set it to load another.
version Counts every change: compare it with the one saved to know if it is dirty.
selection, select(a, c) The selection as offsets in the Markdown.
undo(), redo() Unlimited, of the Markdown; a word typed is one step.
toggleInline(f) 'strong', 'em', 'underline', 'strike', 'code', 'sub', 'sup'.
isInline(f) Whether the selection is in that format.
setBlockType(t) 'paragraph', 'h1'...'h6', 'quote', 'bullet', 'number',
'task' or 'code'; again on the same type makes paragraphs again.
blockType The type of the paragraph at the caret.
insertTable() Also insertRule(), insertCodeBlock(), insertLink(url),
insertImage(url, alt).
execute(name) Any toolbar command by name ('h2', 'bullet', 'link'...).
toolbarItems The toolbar's items: set it to change which buttons show, and their order.
refreshToolbar() Shows your own buttons' state again (pressed, enabled).
rawMode, toggleRawMode() The raw Markdown view on or off (below).
showParagraphNumbers The gutter's paragraph numbers on or off (the toolbar's # button).
showParagraphTypes The gutter's paragraph types on or off (the toolbar's button beside #).
showOutline Collapsible headings on or off (yours to offer: the default toolbar has no button).
collapseAll(), expandAll() Fold every heading, or open them all.
paragraphNumber The caret's paragraph's number, as the gutter shows it: for a status bar.
theme, zoom Change the look, or the scale, at any time.
zoomIn(), zoomOut() 10% larger or smaller (the toolbar's + and -); resetZoom(): 100%.
readOnly, focus() As their names say.
focusToolbar() The focus to the toolbar (Alt+F10), its button last used.
destroy() Takes the editor out of the page.

Events (addEventListener): change after each change of the Markdown, selectionchange, modechange (the raw view on or off), toolbarclick (one of your buttons; detail.name), zoomchange, linkclick (detail.href; call preventDefault() to handle it yourself), and clipboardunavailable (Shift+Delete or Ctrl+Shift+V could not use the clipboard: nothing was cut or pasted).

Your own toolbar buttons go in the toolbar list (or toolbarItems) beside the editor's names, as objects: name, title, icon (an SVG: a whole <svg>, or what goes in a 24 x 24 one drawn in strokes, as the editor's icons are), or label (text), and onClick(editor, event); pressed(editor) makes it a switch shown pressed or not, and enabled(editor) says when it can be clicked. They look like the editor's own buttons, and the caret stays in the editor.

const lock = {
  name: 'readonly', title: 'Read-only',
  icon: '<rect x="4" y="11" width="16" height="10" rx="2"/>' +
    '<path d="M8 11V7a4 4 0 0 1 8 0v4"/>',
  pressed: (editor) => editor.readOnly,
  onClick: (editor) => (editor.readOnly = !editor.readOnly),
};
new MarkdownEditor('#editor', {
  toolbar: ['undo', 'redo', '|', 'strong', 'em', '|', lock],
});

<markdown-editor toolbar="undo redo | strong em"> picks the editor's items by name.

The raw Markdown view (rawMode, the toolbar's last button, Ctrl+Shift+R) shows the Markdown itself in the editor, as plain text, and lets you edit it there, as MaxPad's View > Raw Markdown: the caret stays where it was, looking changes nothing, and what you change there becomes the document when you leave the view, one undo bringing back the document as it was before. While it is on, value is the text it shows, and undo and redo go an edit at a time.

A theme object overrides any of LIGHT_THEME's values: fontFamily, fontSize, codeFontFamily, color, background, headingColor, linkColor, syntaxColor, codeBackground, quoteBarColor, documentWidth, centered, listIndent and the rest (see src/markdown/styleProvider.js). Give base: 'dark' to start from the dark theme.

Restyling the content with your own stylesheet. The text is styled by classes of the editor's default stylesheet, which sits in the CSS cascade layer mde: a rule of your own stylesheet wins over it whatever its specificity, so you restyle only what you want. Each paragraph is mde-content-para and its kind's (mde-content-p, mde-content-h1 to -h6, mde-content-bullet, mde-content-code-block, mde-content-table-cell...); runs are mde-content-code, mde-content-link, mde-content-strike... The theme's values are custom properties on .mde-content (--mde-heading-color, --mde-link-color, --mde-font-size...), and the dark theme is .mde-content.mde-dark. Give sizes in em to have them zoom with the text (section 10 of documents/implementation.md lists every class).

.mde-content-h1 { font-size: 2.4em; color: #8b0000; }
.mde-content { --mde-link-color: #2e7d32; }
.mde-content-code { border: 1px solid #ccc; }
.mde-content-page { margin: 0; }  /* the document at the left */
.mde-content { --mde-document-width: 40em; }

The document is centered in the editor and at most 720 px wide (at 100%): the page, .mde-content-page, has margin: 0 auto and its max-width from --mde-document-width.

Keys

Keys What they do
Enter A new paragraph; in a list, a new item; on an empty item, out of the list
Shift+Enter A line break inside the paragraph (a backslash hard break)
Backspace, Delete At a block's edge: join, take off a list's marker or a quote level
Tab, Shift+Tab At an item's start: in or out a level; at a heading's: its level
Ctrl+B, Ctrl+I, Ctrl+U Bold, italic, underline
Ctrl+`, Ctrl+Shift+X Inline code, strikethrough
Ctrl+K A link
Ctrl+1 to Ctrl+6, Ctrl+0 Heading 1 to 6, paragraph (Ctrl+Alt+1 to 6 and 0 too)
Ctrl+=, Ctrl+- One heading level deeper (a paragraph becomes Heading 1), shallower
Ctrl+Shift+7, 8, 9 Numbered list, bullet list, task list
Ctrl+Alt+Q, C, L Quote, code block, horizontal line
Ctrl+Z, Ctrl+Y Undo, redo
Ctrl+Shift+R The raw Markdown view on or off
Alt+F10 To the toolbar: Left and Right in it, Escape back
Shift+Alt+F1 Screen reader mode on or off
Escape, then Tab Out of the editor (Tab alone indents)
Ctrl and the mouse wheel Zoom (about 2% a notch)

The keys are MaxPad's. A browser gives the page Ctrl+1 to Ctrl+6 before it switches tabs with them, and switches none when the page takes them; Ctrl+Alt+1 to 6 are there as well for a browser that keeps them. MaxPad's Ctrl+Shift+T, Q, C, [ and ] are the browser's own, so the lists are on Google Docs' keys and the quote, the code block and the line on Ctrl+Alt.

How it is built

documents/implementation.md describes how it works, module by module. In short, the layers are MaxPad's, one module each:

Here In MarkdownTools What it does
markdown/parser.js, inlines.js MaxPad.Dom Markdown to a tree with source offsets
markdown/projection.js MaxPad.Projection The tree as the view's paragraphs
markdown/commands.js, formatting.js, blocks.js MaxPad.Editing The keys as edits
markdown/controller.js, history.js MarkdownViewController The loop, undo
markdown/styleProvider.js MarkdownStyleProvider The default look
view/VirtualTextView.js OptimaxTools.RichText The rich text view
MarkdownEditor.js, element.js MaxPad (the window) The component

The source is the authority. The view shows a projection of the Markdown: each paragraph of the view is the source of its block's lines, with the syntax in it as hidden runs, so a position in the view is a position in the source plus an offset. Every key the user presses reaches the controller as an edit command; it works out the new Markdown (each rule is a pure function of the source and the selection, in commands.js), reads it again, and gives the view just the paragraphs that changed. The view's own editing never runs for Markdown.

Line endings. A line ending is normalized to the document's own on load: a file whose line endings are a mixture is saved with one kind.

The view is a component of its own, as VirtualTextView is: a list of paragraphs of runs, styles from a style provider, its own caret, selection, keyboard, mouse, IME and clipboard handling, and its own editing for a host that wants it (TinyPad). Before it changes anything it raises a cancelable editcommand event; it tells an editRecorder of each edit it makes, as a TextEdit that applyEdit replays and inverse() undoes; replaceParagraphs and restyleParagraph change what it shows without an undo step. It draws text with the browser's own layout, one element per paragraph near the viewport, and its own caret and selection over it; a hidden text area takes the keyboard, the input method and the clipboard.

Not done yet

  • find and replace, spelling.
  • Code highlighting by language, pasting pictures.
  • Table commands beyond typing in cells, Tab and Enter (rows and columns added or deleted, alignment).

About

A Web version of MaxPad, a WYSIWYG markdown editor.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages