A post of this blog is an envelope now, and a person can write a blog in a FIFO. But the body of an envelope had the size of the envelope in the editor. This post tells how the editor gets room for a long text.
The full view
Each envelope has a button "Open in full view". It opens the envelope in a modal dialog that fills the window. It is not the full screen of the browser: the window of the browser stays as it is. On a wide screen, the header fields are at the left, and the body is at the right with the height of the window. On a phone, the letter is in front of the envelope, as before, but it is high.
The body of an egress envelope is a text, so in the full view its long lines wrap. Then a line of the text can take more than one row, and the line numbers do not agree with the rows. So the line numbers go away there. The list above the body still names a line with an error.
One form
The dialog has the same form as the envelope in the page: a copy would be a second form with the same fields. Each envelope is in a <dialog>. When the dialog is closed, a rule of the style sheet gives it display: contents: the dialog has no box, and the envelope is in the page as before.
Before I changed the editor, I checked this in Chromium. A closed dialog with display: contents shows its fields in the page, and they take the focus. The tree of accessibility has no dialog for it, only the heading, the fields and the buttons. When the dialog opens as a modal dialog, it is in the top layer, it fills the window, and Escape closes it.
No JavaScript
The button has commandfor and command="show-modal", as the dialogs to add an envelope. The browser opens the dialog, so the full view works with no JavaScript. LiveView does not change the attribute open of the dialog, so a save keeps the full view open.
Live Markdown
In the full view, the body of an egress envelope with Content-Type: text/markdown shows the styles of Markdown now, while I write: a heading is large, **strong** is bold, _emphasis_ is italic, and code, links, the items of a list and the tables have their styles. The marks stay in the text, in a lighter color. I wanted the text to stay the text: the view hides no mark, so the place of the caret is the same in the text and on the screen, and a save sends what I see.
The view follows the rules of the converter of this site, not the rules of CommonMark. # Title and * item are not Markdown here, so they get no style: the view shows what the page will show. A variable of the template, such as $name, has a color too.
A view next to the textarea
The textarea stays the field of the form. The view is an element with contenteditable="plaintext-only" next to it. After each change, the view reads its text, writes its HTML again with a class on each part, puts the caret back at the same place, and puts the text into the textarea. Enter in an item of a list starts the next item, and Ctrl+B and Ctrl+I put the marks of strong and emphasis on the selected words. The browser cannot undo a change of HTML that a script wrote, so the view keeps its own history for Undo and Redo.
The functions that make the HTML and find the caret have no DOM, so node:test tests them. One test makes 20,000 random texts and checks that the text of the HTML is the same text, character for character.
Three things that I found
- Chrome sends
insertLineBreak, notinsertParagraph, for Enter in an element withplaintext-only. My first version waited forinsertParagraph, so a list did not continue. Now the view handles the key Enter itself. - The view is in a container with
phx-update="ignore". LiveView still sets thedata-*attributes of the server on such a container, and removes its otherdata-*attributes. The view kept its state indata-active, so the first answer of the server hid the view and took its focus. Now the state is a class. - A new value of
contentEditabletakes the focus away from the element. The view set it after each answer of the server. Now it sets it only when the field becomes read-only or editable.
The server also writes the textarea, and an answer that comes late has an older text of the view. The view keeps a short list of the texts that it sent: when one of them comes back, the view writes its own text again. A different text is a change on the server, and the view shows it.
The full view of a locked envelope, such as a post of this blog in the editor of mkfifo, did not fill the window. Its envelope has a <div> in place of a form, and the rules of the style sheet named the form. Now they name both.
Blocks
The tab "Blocks" next to "Text" shows the same body as blocks, as the notes of an ingress envelope have: a paragraph, a heading, a list, a numbered list, a quote, code, a table and an image.
The body stays Markdown. A change of a block writes the Markdown of that block into the body, and the tab "Text" shows the same text. Each block keeps its source, and a block that did not change writes its source again, character for character. So when I change one paragraph, the other blocks keep their text, with their spaces and their line breaks. A test makes blocks, writes their text, reads the blocks of that text again, and compares them.
I planned to write \$ for each $ in a block, so that a $ is a $ on the page. But the template keeps $5 and $USD as text already: only $name, ${name} and $( ) are variables. So a block keeps the text of the template as it is, and $name in a block is still a variable.
Blocks that look as the page
My first blocks were fields: each block had a label, a textarea or a grid of fields, and a select of its type. They worked, but they did not look as the page. Now each block is the element that the page shows, with the styles of a post: a paragraph is a paragraph, a heading is a heading, and a table is a table. I write in the block itself, as in the paragraphs and the headings of the notes. Bold words are bold, with no marks, and the block writes the marks into the Markdown.
A new module, markdown_inline.js, makes the HTML of the words of a block, and reads the Markdown again from the HTML after a change: strong, emphasis, code, links and images, with the rules of FIFO.Markdown. A variable of the template is a chip, and I cannot change a part of it, so a variable stays a variable. The tests of the module run in Node.js, on objects with the properties of DOM nodes.
The commands and the settings of a block are in one floating toolbar over the block with the focus, as in the notes: the type of the block, the buttons of bold, italic, code, a link and a variable, the settings of the type (the language of code, the rows, the columns and the alignment of a table, the address and the alt text of an image), and the buttons that move and delete the block. "+" in the gap after a block adds a paragraph. Enter in a paragraph starts a new paragraph with the words after the caret, and Backspace at the start of a paragraph joins it to the paragraph before it.
A body with no blocks showed one empty paragraph, and its "+" showed only on a pointer or with the focus, so it was not clear how to add a block. Now the tab shows the "+" button of the notes, with the text "Add a block", and the new paragraph takes its place. An empty paragraph goes away when the focus leaves the blocks, as an empty new block of a note.
The checks found these problems:
- Enter in a field of a form sends the form. The fields of my first blocks were in the form of the envelope, so Enter in a cell of a table or in the address of an image saved the envelope. The fields of the toolbar are in the same form. Now their attribute
formnames a form that is not on the page. Such a field has no form: Enter does not save the envelope, and LiveView does not send its changes. - A new quote had an empty paragraph with no height, and the browser could not put the caret in it, so the words that I typed went nowhere. Now an empty line of a quote or of a list has a line break, as the browser writes it.
- axe found
role="textbox"on the headings and the lists, where it is not permitted: it hid their roles. Now each element keeps its role, and a group around each block has its name, for example "Heading 2, block 3". npm testmust cover 100% of the files that its tests load, but its pattern*.jsmatched only the files of the Worker at the root, not the modules of the editor. Now it also includesassets/js/.
After a save
On a phone, the dialog of a new egress envelope looked broken after a save with an error: it had no backdrop, and the ingress envelope of the page was over it. When a save has an error, the error summary comes before the envelopes, and LiveView moved the envelopes after it. A move takes an element out of the document for a moment, and a modal dialog in it stops being modal: it stays open as a usual dialog. Now the summary has a place that is always in the page, so nothing moves. If a different change moves a dialog, an observer in the page makes the dialog modal again, with its scroll and its focus.
A save also changed the tab of the full view to "Text". While LiveView sends a form, it makes the fields of the form read-only for a moment. The view of the body took that for a locked envelope, and a locked envelope shows only the text. Now the view reads the value that LiveView keeps in data-phx-readonly.
Next
Buttons, a cover, and media with text are not blocks. Markdown has no syntax for them, so they wait for a syntax that the converter can read.