These are the known limits of Kotoba 0.1.

Browsers

The browser tests run in Chromium, Firefox and WebKit (the engine of Safari), on Linux, with the Playwright versions of these browsers. They do not run in Safari on macOS or iOS, in a mobile browser, or with a screen reader. They paste with a paste event that carries the clipboard data, as the browser sends it, not through the system clipboard.

Prompt queries

A prompt query ends at white space unless the prompt has spaces: true; with it, a query ends at two spaces in a row. A query has at most 200 characters (max_length, 64 by default), and a local prompt at most 500 items. See the Prompts guide.

Kotoba.Content keeps the rendered HTML and text in the row, with a cache version. Kotoba.Components.kotoba_content/1 renders the document again only when the cache version of Kotoba changes. It does not see a change of your node registry (config :kotoba, nodes:) or of config :kotoba, allowed_link_schemes:. After such a change, stored rows keep their old HTML until you render them again:

import Ecto.Query

Repo.transaction(fn ->
  from(p in Post, where: not is_nil(p.body))
  |> Repo.stream()
  |> Enum.each(fn post ->
    post
    |> Ecto.Changeset.change()
    |> Ecto.Changeset.force_change(:body, Kotoba.Content.rerender(post.body))
    |> Repo.update!()
  end)
end, timeout: :infinity)

Kotoba.Content compares two values by their documents only, so Ecto.Changeset.change/2 sees no change in a new cache. Ecto.Changeset.force_change/3 writes it.

A node that is only in the nodes attribute of the component, and not in the config, is cached as an unknown node. See the Custom nodes guide.

Direct uploads to a cloud store are not supported

LiveView's external: uploads are not supported. Each file comes to the LiveView, Kotoba checks its bytes, and the storage adapter then sends it to the store. See the Uploads guide.

Two short selection races of Lexical

Lexical reads a selection change from the browser a moment late. The toolbar and the link form read the selection of the page, so they act on the text that the person sees. Two cases are still open. Both need keys less than one frame apart, which a person does not type:

  • Shift+Tab within about 2 ms of a click or of Shift+Arrow can put the focus back in the editor. The content does not change.
  • Backspace or Delete at once after an arrow key can use the old selection. This is the behaviour of every Lexical editor.

Bundle size

The editor bundle is about 579 KB, 188 KB with gzip. It has Lexical, its plugins (tables, about 70 KB of it), the Prism grammars of the 28 code languages of Kotoba.CodeLanguages (about 45 KB) and suggestions (about 17 KB). The bundle is part of your app.js, so every page that loads app.js pays for its size.

Galleries are reordered with the keyboard and the toolbar

An image of a gallery moves with Alt+Left and Alt+Right or the Move image buttons, not with the mouse: there is no drag and drop. A gallery is valid only under the root (not in a list item or a table cell), and has no caption of its own or layout options: kotoba.css shows it as a grid of cropped images, which your CSS can change (.kotoba-gallery).

PDF and video previews are the browser's

A PDF shows in the browser's own viewer: most phones have none, and show the download link. A video plays only in a format the browser has: MP4 with H.264 plays everywhere, WebM with VP8 or VP9 everywhere but some Safari versions. Kotoba does not convert videos or render preview images; the :preview option of Kotoba.Live.consume_uploads/4 lets your app do it (see the Uploads guide). QuickTime and Matroska files are stored as files.

Tables have no merged cells, colours or widths

A table cell cannot span rows or columns in the editor: a pasted table with merged cells becomes a table of plain cells, and the text of a merged cell goes in its first cell. The editor drops the background colour of a pasted cell, and it has no column resizing. A document made elsewhere can have colSpan, rowSpan, backgroundColor and width keys: the server keeps them, and its HTML renders colspan and rowspan (no colour or width), but the editor splits the merged cells and drops the colours when the document loads in it. A table in a table is not allowed; a table pasted into a cell fills the cells from there.

Markdown has no merged cells and no blocks in a cell, so the Markdown of a table is a GitHub Flavored Markdown table with each cell on one line: a line break or a second block in a cell becomes <br>. When the first row is not a row of header cells, the Markdown table has an empty header row.

Code languages are the bundle's

The editor highlights the 28 languages of Kotoba.CodeLanguages and their aliases, and an app cannot add a grammar. A block in another language keeps its language and is plain text in the editor. Prism knows the names in lower case: a block with Elixir shows as Elixir in the picker but is not highlighted until a language is picked. The HTML of a code block has class="language-<name>" with the stored name, and no highlighting.

window.Prism

The editor has its own copy of Prism. It saves the page's window.Prism before it loads its copy and puts it back after, so a page's Prism keeps working. A page cannot use the editor's Prism through window.Prism, and a Prism plugin on the page does not change the editor's highlighting.

The hidden input after a patch uses phx:update

After a LiveView patch, the hook writes the current document into the hidden input again. It uses LiveView's phx:update event, which is not in the documented hook API. The form events do not depend on it (they read the form through formdata). See the Forms guide.

The installer reads app.js with a scanner

mix kotoba.install does not parse JavaScript. For some forms of app.js it cannot make the edit, and it prints the lines to add by hand. See the Quickstart.

Lexical is pinned

The bundle has Lexical 0.51, and the document envelope records "lexical" => "0.51". A later Kotoba version moves to a new Lexical version with its own changes to the documents, if they are necessary.