Extensions
ruby
Furigana · a reading printed above its base text.
`<ruby>漢字<rt>かんじ</rt></ruby>`. Japanese needs it wherever a reader may not know a kanji, and Chinese typesetting uses the same element for pinyin, so this is not a niche of a niche · it is how CJK text is annotated at all.
The base is content rather than an attribute, so `getText`, search and a word count all see 漢字 as the text it is. It is not editable in place, and that is an engine limit rather than a choice: a selection snaps to the nearest textblock, an inline node is not one, so no caret can be put inside a ruby. `setRuby` and `unsetRuby` replace the node instead, which is what a furigana control does anyway.
The reading is an attribute: one short string that is never formatted, and modelling it as a second child would let a paste drop a paragraph into an annotation.
That split only works because a parse rule can say where the content is. `<ruby>` holds the base *and* the `rt`, so without `contentElement` the reading is parsed as part of the word it annotates and 漢字 comes back as 漢字かんじ. This shipped for an afternoon as an atom with the base as an attribute, which round-tripped correctly and could not be edited.
1 · Install
One package for every extension · there is no @matrajs/ruby to install.
npm i @matrajs/core2 · Add it to the editor
Import it and put it in the extensions array. That array is the whole feature list:
what is not in it is not in your bundle and not in the document's schema.
import { createEditor, document, paragraph, text, ruby } from '@matrajs/core'
const editor = createEditor({
extensions: [document, paragraph, text, ruby],
element: document.querySelector('#editor'),
})3 · Use it
Run a command, and give focus back · a click on a button takes it away.
editor.commands.setRuby('かんじ')
editor.focus()Wired to a button, with the pressed state kept in step:
const button = document.querySelector('#setRuby')
button.addEventListener('click', () => {
editor.commands.setRuby('かんじ')
editor.focus()
})
const read = () => button.setAttribute('aria-pressed', String(editor.isActive('ruby')))
editor.on('selectionChange', read)
editor.on('change', read)
read()
Both events, not one. selectionChange covers the caret moving into
it;
change covers the command that changed it without moving anything. A
toolbar wired to only the first goes stale the moment someone uses the keyboard.
Commands
Every one returns true if it changed the document and false
if it could not · a command that does not apply where the caret is says so rather than
throwing.
| Command | Call |
|---|---|
setRuby |
editor.commands.setRuby(reading: string)
|
unsetRuby |
editor.commands.unsetRuby()
|
In the document
| Attribute | Default |
|---|---|
reading | "" |
-
Reads
rubywhen HTML is pasted or loaded. -
Writes
rubyingetHTML()andrenderToHTML().