Page Editor
A Notion-style editor for long text: Markdown shortcuts while you type, a / menu for blocks, a toolbar over selected text, code blocks with syntax colours and eight named colours.
c2-page-editor is a form control for writing long text with structure, without learning an editor. It has the blocks people reach for (headings, bulleted, numbered and to-do lists, quotes, code blocks, dividers and tables), the usual inline formats and links, and eight named text and background colours. It has nothing else: no images or columns. Every format can be reached three ways, so people use whichever they already know:
- Markdown as you type:
#gives a heading,-a bullet,[]a to-do,```a code block,**bold**bold. If the editor guessed wrong, Backspace straight after puts the characters back. - The
/menu: type/at the start of a line or after a space, then a few letters (/code,/red,/h2). The+button on an empty line opens the same menu. - The toolbar over selected text: turn into another block, bold, italic, underline, strike, code, link and colour.
The value is GitHub-flavoured Markdown, so it can be stored, searched and rendered anywhere.
For a comment, reply or email body, which needs a few inline formats and lists in a form field rather than a page, use c2-rich-text-editor: it shares this editor’s Markdown value.
Import
import '@c2n/components/page-editor'Usage
Writing
| Block or format | Type at the start of a line | Shortcut |
|---|---|---|
| Heading 1–3 | # , ## , ### | Ctrl/⌘ Alt 1–3 |
| Text | Ctrl/⌘ Alt 0 | |
| Bulleted list | - or * | Ctrl/⌘ Shift 8 |
| Numbered list | 1. | Ctrl/⌘ Shift 7 |
| To-do | [] or [x] | Ctrl/⌘ Shift 9 |
| Quote | > | |
| Code block | ``` or ```ts then Space or Enter | |
| Divider | --- | |
| Table | /table, or paste cells from a sheet | |
| Bold | **bold** | Ctrl/⌘ B |
| Italic | *italic* | Ctrl/⌘ I |
| Underline | Ctrl/⌘ U | |
| Strikethrough | ~~strike~~ | Ctrl/⌘ Shift S |
| Inline code | `code` | Ctrl/⌘ E |
| Link | paste a URL over text | Ctrl/⌘ K |
| Last colour | Ctrl/⌘ Shift H | |
| Clear formatting | Ctrl/⌘ \ | |
| Move to toolbar | Alt F10 |
A few rules keep long writing smooth. Enter on an empty list item steps out one level, then out of the list. Enter at the end of a heading starts a paragraph. Backspace at the start of a heading, list item or quote makes it a paragraph before it joins the line above. Tab and Shift Tab nest list items (three levels) and indent code; elsewhere Tab moves focus as usual. Shift Enter breaks the line inside a block. In a code block, Enter keeps the indentation, and Enter three times at the end (or Ctrl/⌘ Enter) leaves the block. Ctrl/⌘ Enter on a to-do ticks it.
Pasted rich text (from a web page, Google Docs or Word) keeps its headings, lists, quotes, code, links and emphasis; its own colours and fonts are dropped. Pasted plain text is read as Markdown. On touch screens a format bar sits on top of the keyboard while the page has focus.
Code blocks
Each code block has a language picker with search, a wrap toggle and a copy button, and shows line numbers. Syntax colours come from shiki, loaded the first time a block names a language and only for that language, so a page without code loads none of it. Every token colour is a CSS variable (--c2-page-editor__syntax-keyword--color and so on), mixed with the code’s text colour so it reads in light and dark themes.
Tables
A table is a c2-spreadsheet, loaded with the first table on a page: cells take numbers, text and formulas (=SUM(B1:B4)), and edit with the keys of Sheets and Excel. Typing = lists the functions, and inside one a hint names its arguments and shows the result. Right-click a cell, row or column to insert or delete rows and columns, make the selected cells bold, colour their text or background with the page’s colours or change their size (Ctrl/⌘+B toggles bold), add a total under them, show the formula bar, show the totals of the selection, or delete the table; New row and New column sit under it. ArrowDown at the end of the line above goes into its first row and ArrowUp on its first row comes back out, ArrowDown on its last row leaves it below, and Escape selects the whole table, which Enter goes back into and Backspace removes. Cells pasted from Sheets, Excel or a web page, or tab-separated lines, become a table; a single pasted cell stays text.
The header names the columns: double-click a column header, or right-click and pick Rename column, to type its name, and Tab goes on to the next. A new table starts there. The rows under the header are numbered from 1, so a formula reads them as in Sheets (=SUM(B1:B4)).
In the value a table is a pipe table: the header row holds the column names and each cell its raw input, so a formula stays a formula. The formula bar and the totals, which Markdown cannot say, are a comment line above it: <!-- table: formula-bar summary -->. A styled cell is written as styled page text is: <mark data-color="yellow"><span data-color="red" data-size="large">**Total**</span></mark>.
Colours
The eight colours (gray, brown, orange, yellow, green, blue, purple, red) are names, not values. In the value they are written as <span data-color="red"> and <mark data-color="yellow">; on screen each name is a CSS variable. Text colours are mixed with the page’s text colour and backgrounds are translucent, so the same page reads on a light or a dark surface, and a theme can change what “red” means everywhere at once. A pasted colour that is not one of the eight is dropped.
The value
# Launch plan
Ship the **new table** before <span data-color="red">Friday</span>, see `c2-table`.
- Faster sorting
- Stable order
1. First
- [x] Freeze the API
> Easy to use matters most.
---
| Task | Hours |
| --- | ---: |
| Docs | 3 |
| Total | =SUM(B1:B1) |
Read the [guide](https://example.com/guide).Formats Markdown has no syntax for are inline HTML, which Markdown renderers pass through: <u>, <span data-color>, <mark data-color>, and <br> for a line break or an empty line. text gives the page as plain text, and toHTML() as HTML with real nested lists. markdownToHTML(value) renders a stored value without an editor on the page.
import '@c2n/components/page-editor'
const editor = document.querySelector('c2-page-editor')!
editor.addEventListener('input', () => save(editor.value))
editor.value = '# Notes\
\
- [ ] Call the bank'input fires on every edit, after value is updated, and change when the editor loses focus with a changed value; writing the emitted value back, as v-model does, keeps the caret where it is. c2-page-editor is form-associated: it submits value under name, supports required, resets with its form and follows a disabled fieldset. blocks="paragraph bullet ordered" limits the blocks the menus, shortcuts and pasted content can use. setBlock(), toggleMark(), setColor(), setBackground(), setLink() and clearFormatting() drive it from your own controls, and format-change reports the block and formats at the selection.