Skip to content

Formatting toolbar above the text of the editor - #106

Merged
codeling merged 8 commits into
claude/keyboard-shortcutsfrom
claude/formatting-toolbar
Nov 4, 2026
Merged

codeling merged 8 commits into
claude/keyboard-shortcutsfrom
claude/formatting-toolbar

Conversation

@codeling

@codeling codeling commented Nov 4, 2026

Copy link
Copy Markdown
Owner

Stack (5 of 5, top): this PR targets #104 (keyboard shortcuts). Below that are #103 (live preview), #102 (code highlighting) and #101 (page history). It contains only the toolbar commits.

To merge all of them as one, merge from the top down, each into its base: this PR, #104, #103, #102. Then merge #101 into main.

What it does

A toolbar (role="toolbar") directly above the text in the editor, for new pages, editing and restoring old versions. The markup is hidden and wiki.js shows it, so pages without JavaScript are unchanged.

Button Effect
B / I bold / italic around the selection. Same code as Ctrl+B / Ctrl+I, including removing it again (via a shared FORMATS map)
Heading menu with levels 1–4. Sets the # prefix on the selected lines, replaces another level, removes it when the same level is chosen again. The current level is checked
Link / [[ ]] Markdown link (same as Ctrl+K) / wiki link (pressed again, it removes it)
Bulleted, numbered list, quote add or remove the prefix on the selected lines. Numbered lists get 1., 2., … A quote puts > on blank lines inside it, so it stays one block
Code backticks within one line. Over several lines or on an empty line, a fenced block with the cursor right after ``` so the language can be typed for the highlighting from #102. A selected fenced block loses its fences
Rule --- as a block of its own
Table dialog for columns (1–20) and rows (1–100), default 3×3
Upload, ? the existing upload dialog and formatting help, moved here from the page toolbar

Table dialog

  • It inserts an aligned PHP Markdown Extra pipe table with "Column N" headers and selects the first header.
  • There is no "header row" option, because Markdown Extra tables always need one; the dialog says so.
  • Selected lines separated by tabs or commas, for example pasted from a spreadsheet, become a table directly, with | escaped.

Behaviour

  • Undo, draft and preview: every change goes through insertTyped(). That makes it one Ctrl+Z step, and the draft autosave, the leave-page warning and the live preview (Live preview in the editor (#95) #103) all see it.
  • Focus: clicking a button keeps the focus and the selection in the text.
  • Keyboard:
    • The toolbar is one Tab stop. Arrows, Home and End move between the buttons, and Esc returns to the text.
    • Alt+F10 in the text jumps to the toolbar; the "?" list of shortcuts names it.
    • The heading menu works with arrows, Enter and Esc.
  • Moved controls: the old bold/italic/code icon group and the upload link are gone from the page toolbar; both only worked with JavaScript anyway. They are now the last buttons of this toolbar, with the same ids. The help button has aria-expanded.
  • Layout: on narrow screens the bar wraps. When the preview replaces the text (≤700px), the bar is hidden too. No strikethrough button, because PHP Markdown Extra doesn't support ~~.
  • Fix outside the toolbar (its own commit, with a test): "Esc then Shift+Tab" didn't leave the text, because pressing Shift alone reset the Esc state. That blocked getting back to the toolbar with the keyboard.
  • Docs: new SVG icons (currentColor, <use>), CSS for both color schemes, German translations, a toolbar line and a table example in the formatting help, README and tests/README.

Tests

  • Integration: FormatBarTest checks the markup. PageFrameTest is updated for the moved help button.
  • Browser: toolbar.spec.js, 22 tests:
    • every button, and removing bold, italic and headings again
    • lists, quote, code inline, as a block and unwrapped, rule, links
    • the table dialog at 3×3, 1×1 and 20×100; out-of-range values keep it open; Cancel and Esc insert nothing
    • converting tab- and comma-separated lines
    • one Ctrl+Z undoes each change; draft and preview update
    • focus and selection, keyboard navigation, 320px and 360px widths, the preview layouts
    • dark-scheme contrast, no CSP violations, no toolbar without JavaScript
  • Changed tests:
    • shortcuts.spec.js has the Alt+F10 row.
    • editor-indent.spec.js tests Esc then Shift+Tab.
    • xss.spec.js: its interact() clicks every element, which now includes the formatting buttons. The test now checks the exact text before the clicks; afterwards it checks that no dialog appeared and that the payload is still in the text.
  • Results in the development container, on the whole stack:
    • vendor/bin/phpunit: 2249 tests. The one failure, EditorUploadTest::testUploadControlsAreNotShownInTheEditorItself, also fails on main there, because the container has no imagick.
    • composer analyse: clean.
    • Playwright: 226 passed, 9 skipped (crop and real-server tests).

Notes

  • Tooltips name Ctrl, like the existing "Save (Ctrl+S)"; they don't switch to Cmd on a Mac.
  • The drawer's own close link still uses the CSP-hashed inline handler. Moving it to addEventListener would let the hash go in a follow-up.
  • Comma-separated detection is deliberately simple: two or more lines with the same number of commas and no quotes.

Generated by Claude Code

Esc in the text makes the next Tab move the focus instead of indenting,
so that the text can be left with the keyboard. With Shift+Tab, the
keydown of Shift itself came first and reset this, so the text could
only be left forwards. Keys which are only modifiers no longer reset it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFJFMQhkaze5WhCnZxGD64
Heading, bulleted and numbered list, quote, horizontal rule, table and
help, in the style of the other icons: 16 x 16, drawn with currentColor
in the element with the id "i" which iconSvg() uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFJFMQhkaze5WhCnZxGD64
A toolbar (role="toolbar") directly above the text, in the editor of
new and existing pages and when restoring a version: bold, italic, a
menu of headings 1 to 4, links and links to pages ([[ ]]), bulleted and
numbered lists, quotes, code, horizontal rules and tables, then upload
and the formatting help. There is no strikethrough, which PHP Markdown
does not have.

Every change goes through applyChange() and insertTyped(), like Ctrl+B,
Ctrl+I and Ctrl+K, whose functions the buttons share (FORMATS): it is
one step for Ctrl+Z, an input event for the draft, the warning about
leaving and the preview, and the selection moves with the text. Line
formats (headings, lists, quotes) set, replace or remove the marker of
the touched lines; code is inline within a line and a fenced block for
several lines or an empty line, with the cursor after ``` for the
language; rules and tables are blocks of their own, with blank lines
added as needed. The table dialog asks for 1 to 20 columns and 1 to 100
rows below the header row (default 3 x 3), inserts an aligned pipe table
of PHP Markdown Extra and selects the name of the first column; selected
lines separated by tabs or commas become a table instead.

The buttons keep the focus (and the selection) in the text when clicked.
The toolbar is one stop of Tab with a roving tabindex: arrow keys, Home
and End move between the buttons, Esc goes back to the text, Alt+F10 in
the text goes to the toolbar (also in the list of shortcuts and in the
formatting help, which shows a table too).

The upload button and the toggle of the formatting help move from the
toolbar of the page into the new one: both only work with the script,
like the new toolbar, which is hidden without it. The help toggle is a
"?" icon at the end instead of the bold/italic/code icon group, which
looked like formatting buttons. Where the preview takes the place of the
text (narrow screens) the toolbar is hidden with the text; otherwise it
wraps. Event handlers are added by wiki.js, the texts come from the
templates (translated, German included), the colors from the variables
of both schemes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFJFMQhkaze5WhCnZxGD64
…ose event

The test of the large table (20 columns, 100 rows) read the text right after
the click on Insert. The table is inserted in the "close" event of the
dialog, which the browser dispatches as a task of its own after the form is
submitted, so on a slower browser (CI) the text could still be the old one.
The test now waits until the table is there, like the other cases, which use
assertions that retry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFJFMQhkaze5WhCnZxGD64
main now takes the icons from the subtree of codeling/webicons, where
iconSvg() looks for them; the icons of the formatting toolbar are now part
of it (codeling/webicons#1, taken with the subtree update in the page
history branch), redrawn in the style of the set, so the copies in
w2-icons/ go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFJFMQhkaze5WhCnZxGD64
…em once

After the table dialog closes, the selection and the focus of the text are
restored in its "close" event, a task of its own after the dialog is
hidden; on the slower browser of CI the test read the cursor before that
("|text" instead of "te|xt"). All checks of the selection (marked()) and
of the focus (focused()) now use expect.poll, which passes at once when the
value is already right, so none of them depends on how soon a browser runs
such a task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFJFMQhkaze5WhCnZxGD64
@codeling
codeling added this pull request to stack #110 November 4, 2026 16:06
@codeling
codeling merged commit bab9923 into main Nov 4, 2026
21 checks passed
@codeling
codeling deleted the claude/formatting-toolbar branch November 4, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants