From 0b47e4cb97ea9017d668caaa53ccc7eb4e0f5e38 Mon Sep 17 00:00:00 2001 From: Rokojori Date: Fri, 31 Jul 2026 15:43:04 +0200 Subject: [PATCH] feat: page editor unified toolbar, floating block menu, insertion triggers, drag-to-reorder, InputDialog Co-Authored-By: Claude Sonnet 4.6 --- .../confirm-dialog/confirm-dialog.css | 33 +- .../confirm-dialog/confirm-dialog.ts | 92 ++++ .../page-editor-panel/icons/block.svg | 86 ++++ .../page-editor-panel/icons/bold.svg | 78 +++ .../components/page-editor-panel/icons/h1.svg | 79 +++ .../components/page-editor-panel/icons/h2.svg | 80 ++++ .../components/page-editor-panel/icons/h3.svg | 79 +++ .../page-editor-panel/icons/italic.svg | 88 ++++ .../page-editor-panel/icons/link.svg | 78 +++ .../page-editor-panel/icons/mark.svg | 87 ++++ .../page-editor-panel/icons/under.svg | 92 ++++ .../page-editor-panel/page-editor-panel.css | 165 ++++--- .../page-editor-panel/page-editor-panel.ts | 448 +++++++++++++++--- .../history/2026/07-July/31-Friday/index.html | 123 ++++- .../07-July/31-Friday/page-editor-update.page | 71 +++ workspace/history/index.html | 2 +- 16 files changed, 1559 insertions(+), 122 deletions(-) create mode 100644 source/components/page-editor-panel/icons/block.svg create mode 100644 source/components/page-editor-panel/icons/bold.svg create mode 100644 source/components/page-editor-panel/icons/h1.svg create mode 100644 source/components/page-editor-panel/icons/h2.svg create mode 100644 source/components/page-editor-panel/icons/h3.svg create mode 100644 source/components/page-editor-panel/icons/italic.svg create mode 100644 source/components/page-editor-panel/icons/link.svg create mode 100644 source/components/page-editor-panel/icons/mark.svg create mode 100644 source/components/page-editor-panel/icons/under.svg create mode 100644 workspace/history/2026/07-July/31-Friday/page-editor-update.page diff --git a/source/components/confirm-dialog/confirm-dialog.css b/source/components/confirm-dialog/confirm-dialog.css index cae091b..94eff28 100644 --- a/source/components/confirm-dialog/confirm-dialog.css +++ b/source/components/confirm-dialog/confirm-dialog.css @@ -1,4 +1,5 @@ -confirm-dialog { +confirm-dialog, +input-dialog { display: flex; position: fixed; inset: 0; @@ -118,3 +119,33 @@ confirm-dialog { background: #e74c3c; border-color: #e74c3c; } + +/* ── Input dialog ────────────────────────────────────────────────────────── */ + +.cd-input-label { + display: block; + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; + color: #9ba4c7; + margin-bottom: 6px; +} + +.cd-input-field { + display: block; + width: 100%; + box-sizing: border-box; + background: #0f1117; + border: 1px solid #2a2d3a; + border-radius: 4px; + color: #e2e4ed; + font-size: 0.9rem; + font-family: inherit; + padding: 7px 10px; + outline: none; +} + +.cd-input-field:focus { + border-color: #7c8cff; +} diff --git a/source/components/confirm-dialog/confirm-dialog.ts b/source/components/confirm-dialog/confirm-dialog.ts index 44095e7..0cead42 100644 --- a/source/components/confirm-dialog/confirm-dialog.ts +++ b/source/components/confirm-dialog/confirm-dialog.ts @@ -82,3 +82,95 @@ export function showConfirmDialog(options: ConfirmDialogOptions): Promise void) | null = null; + + connectedCallback(): void { + this.style.display = 'none'; + this.innerHTML = ` +
+ + `; + + this.querySelector( '.cd-backdrop' )!.addEventListener( 'click', () => this._close( null ) ); + this.querySelector( '.cd-close' )!.addEventListener( 'click', () => this._close( null ) ); + document.addEventListener( 'keydown', ( e: KeyboardEvent ) => { + if ( e.key === 'Escape' && this.style.display !== 'none' ) this._close( null ); + if ( e.key === 'Enter' && this.style.display !== 'none' ) this._submit(); + } ); + } + + show( options: InputDialogOptions ): Promise { + this.querySelector( '.cd-icon' )!.textContent = options.icon ?? ''; + this.querySelector( '.cd-title' )!.textContent = options.title; + ( this.querySelector( '.cd-input-label' ) as HTMLElement ).textContent = options.label; + + const input = this.querySelector( '.cd-input-field' ) as HTMLInputElement; + input.value = options.defaultValue ?? ''; + + const footer = this.querySelector( '.cd-footer' )!; + footer.innerHTML = ''; + + const cancel = document.createElement( 'button' ); + cancel.className = 'cd-btn cd-btn-cancel'; + cancel.textContent = options.cancelLabel ?? 'Cancel'; + cancel.addEventListener( 'click', () => this._close( null ) ); + footer.appendChild( cancel ); + + const confirm = document.createElement( 'button' ); + confirm.className = 'cd-btn cd-btn-confirm'; + confirm.textContent = options.confirmLabel ?? 'OK'; + confirm.addEventListener( 'click', () => this._submit() ); + footer.appendChild( confirm ); + + this.style.display = ''; + input.focus(); + input.select(); + + return new Promise( resolve => { this.resolver = resolve; } ); + } + + private _submit(): void { + const value = ( this.querySelector( '.cd-input-field' ) as HTMLInputElement ).value.trim(); + this._close( value || null ); + } + + private _close( value: string | null ): void { + this.style.display = 'none'; + if ( this.resolver ) { this.resolver( value ); this.resolver = null; } + } +} + +customElements.define( 'input-dialog', InputDialog ); + +export function showInputDialog( options: InputDialogOptions ): Promise { + let dialog = document.querySelector( 'input-dialog' ) as InputDialog | null; + if ( !dialog ) { + dialog = document.createElement( 'input-dialog' ) as InputDialog; + document.body.appendChild( dialog ); + } + return dialog.show( options ); +} diff --git a/source/components/page-editor-panel/icons/block.svg b/source/components/page-editor-panel/icons/block.svg new file mode 100644 index 0000000..0e362b3 --- /dev/null +++ b/source/components/page-editor-panel/icons/block.svg @@ -0,0 +1,86 @@ + + + + diff --git a/source/components/page-editor-panel/icons/bold.svg b/source/components/page-editor-panel/icons/bold.svg new file mode 100644 index 0000000..48039f3 --- /dev/null +++ b/source/components/page-editor-panel/icons/bold.svg @@ -0,0 +1,78 @@ + + + + diff --git a/source/components/page-editor-panel/icons/h1.svg b/source/components/page-editor-panel/icons/h1.svg new file mode 100644 index 0000000..52cf29e --- /dev/null +++ b/source/components/page-editor-panel/icons/h1.svg @@ -0,0 +1,79 @@ + + + + diff --git a/source/components/page-editor-panel/icons/h2.svg b/source/components/page-editor-panel/icons/h2.svg new file mode 100644 index 0000000..3b358ff --- /dev/null +++ b/source/components/page-editor-panel/icons/h2.svg @@ -0,0 +1,80 @@ + + + + diff --git a/source/components/page-editor-panel/icons/h3.svg b/source/components/page-editor-panel/icons/h3.svg new file mode 100644 index 0000000..d583cc2 --- /dev/null +++ b/source/components/page-editor-panel/icons/h3.svg @@ -0,0 +1,79 @@ + + + + diff --git a/source/components/page-editor-panel/icons/italic.svg b/source/components/page-editor-panel/icons/italic.svg new file mode 100644 index 0000000..380f034 --- /dev/null +++ b/source/components/page-editor-panel/icons/italic.svg @@ -0,0 +1,88 @@ + + + +b diff --git a/source/components/page-editor-panel/icons/link.svg b/source/components/page-editor-panel/icons/link.svg new file mode 100644 index 0000000..76bbb25 --- /dev/null +++ b/source/components/page-editor-panel/icons/link.svg @@ -0,0 +1,78 @@ + + + + diff --git a/source/components/page-editor-panel/icons/mark.svg b/source/components/page-editor-panel/icons/mark.svg new file mode 100644 index 0000000..c3ca024 --- /dev/null +++ b/source/components/page-editor-panel/icons/mark.svg @@ -0,0 +1,87 @@ + + + + diff --git a/source/components/page-editor-panel/icons/under.svg b/source/components/page-editor-panel/icons/under.svg new file mode 100644 index 0000000..5e60913 --- /dev/null +++ b/source/components/page-editor-panel/icons/under.svg @@ -0,0 +1,92 @@ + + + +b diff --git a/source/components/page-editor-panel/page-editor-panel.css b/source/components/page-editor-panel/page-editor-panel.css index 7652935..2b5e1f4 100644 --- a/source/components/page-editor-panel/page-editor-panel.css +++ b/source/components/page-editor-panel/page-editor-panel.css @@ -3,29 +3,7 @@ page-editor-panel { flex-direction: column; height: 100%; background: #0f1117; -} - -.pep-mode-btn { - width: 28px; - height: 28px; - background: transparent; - border: 1px solid transparent; - border-radius: 4px; - color: #555; - cursor: pointer; - font-size: 0.9rem; - display: flex; - align-items: center; - justify-content: center; - padding: 0; - flex-shrink: 0; -} - -.pep-mode-btn:hover { color: #9ba4c7; background: #1a1d27; } -.pep-mode-btn.active { color: #7c8cff; border-color: #7c8cff; } - -.pep-toolbar-sep { - flex: 1; + position: relative; } /* ── Toolbar ─────────────────────────────────────────────────────────────── */ @@ -38,9 +16,10 @@ page-editor-panel { background: #13151f; border-bottom: 1px solid #2a2d3a; flex-shrink: 0; + overflow-x: auto; } -.pep-toolbar button { +.pep-toolbar button:not(.pep-fmt-btn) { padding: 3px 10px; background: transparent; border: 1px solid #2a2d3a; @@ -49,27 +28,102 @@ page-editor-panel { cursor: pointer; font-size: 0.8rem; font-family: inherit; + flex-shrink: 0; } -.pep-toolbar button:hover:not(:disabled) { background: #1a1d27; color: #e2e4ed; } -.pep-toolbar button:disabled { opacity: 0.3; cursor: default; } +.pep-toolbar button:not(.pep-fmt-btn):hover:not(:disabled) { background: #1a1d27; color: #e2e4ed; } +.pep-toolbar button:not(.pep-fmt-btn):disabled { opacity: 0.3; cursor: default; } .pep-save:not(:disabled) { border-color: #7c8cff; color: #7c8cff; } -.pep-save:not(:disabled):hover { background: #1e2235; } +.pep-save:not(:disabled):hover { background: #1e2235 !important; } .pep-pin.pinned { border-color: #f0a050; color: #f0a050; } -.pep-pin.pinned:hover { background: #1e1a10; } +.pep-pin.pinned:hover { background: #1e1a10 !important; } -/* ── Mode panels ─────────────────────────────────────────────────────────── */ - -.pep-mode-panel { - background: #13151f; - border-bottom: 1px solid #2a2d3a; - flex-shrink: 0; - padding: 6px 8px; +.pep-toolbar-sep { + flex: 1; + min-width: 4px; } -/* Blocks panel — horizontal scrollable block list */ +/* ── Icon + label buttons ────────────────────────────────────────────────── */ + +.pep-fmt-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + padding: 5px 8px; + background: transparent; + border: 1px solid transparent; + border-radius: 4px; + color: #9ba4c7; + cursor: pointer; + font-family: inherit; + flex-shrink: 0; +} + +.pep-fmt-btn:hover { background: #1a1d27; border-color: #2a2d3a; color: #e2e4ed; } + +.pep-btn-icon { + width: 24px; + height: 24px; + display: block; + opacity: 0.55; + flex-shrink: 0; +} + +.pep-fmt-btn:hover .pep-btn-icon { opacity: 0.9; } + +.pep-btn-label { + font-size: 10px; + font-weight: 600; + letter-spacing: 0.04em; + line-height: 1; + text-transform: uppercase; + white-space: nowrap; +} + +.pep-fmt-sep { + width: 1px; + height: 36px; + background: #2a2d3a; + align-self: center; + margin: 0 2px; + flex-shrink: 0; +} + +/* ── Floating block menu ─────────────────────────────────────────────────── */ + +.pep-block-menu { + position: absolute; + z-index: 100; + background: #13151f; + border: 1px solid #2a2d3a; + border-radius: 6px; + padding: 8px; + padding-top: 28px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); + min-width: 180px; +} + +.pep-block-menu-close { + position: absolute; + top: 5px; + right: 6px; + background: transparent; + border: none; + color: #555; + cursor: pointer; + font-size: 0.75rem; + padding: 2px 5px; + border-radius: 3px; + line-height: 1; +} + +.pep-block-menu-close:hover { color: #e2e4ed; background: #1a1d27; } + +/* ── Block list (used inside floating menu) ──────────────────────────────── */ .pep-block-list { display: flex; @@ -105,7 +159,6 @@ page-editor-panel { gap: 4px; } -/* CSS layout sketch helpers used inside .pep-block-preview */ .pbp-full { display: flex; flex: 1; } .pbp-two-col { display: flex; flex: 1; gap: 4px; } .pbp-area { flex: 1; background: #3a3d4a; border-radius: 2px; } @@ -127,34 +180,18 @@ page-editor-panel { white-space: nowrap; } -/* Areas panel — rich text formatting toolbar */ +/* ── Drag bar ────────────────────────────────────────────────────────────── */ -.pep-areas-panel { - display: flex; - flex-direction: row; - gap: 4px; -} - -.pep-fmt-btn { - padding: 2px 8px; - background: transparent; - border: 1px solid #2a2d3a; - border-radius: 4px; - color: #9ba4c7; - cursor: pointer; - font-size: 0.8rem; - font-family: inherit; - min-width: 28px; -} - -.pep-fmt-btn:hover { background: #1a1d27; color: #e2e4ed; } - -.pep-fmt-sep { - width: 1px; - height: 18px; - background: #2a2d3a; - align-self: center; - margin: 0 2px; +.pep-drag-bar { + position: absolute; + left: 8px; + right: 8px; + height: 3px; + background: #7c8cff; + border-radius: 2px; + pointer-events: none; + z-index: 50; + box-shadow: 0 0 8px rgba(124, 140, 255, 0.55); } /* ── Content area ────────────────────────────────────────────────────────── */ diff --git a/source/components/page-editor-panel/page-editor-panel.ts b/source/components/page-editor-panel/page-editor-panel.ts index b7b4ab0..0d8b9b3 100644 --- a/source/components/page-editor-panel/page-editor-panel.ts +++ b/source/components/page-editor-panel/page-editor-panel.ts @@ -1,12 +1,9 @@ import { Editor } from '../../editor/Editor.js'; import { EditorPanelDefinition, FileEditorPanelDefinition } from '../../editor/editor-panel.js'; import { ContextMenuDirectory, ContextMenuReadOnlyEntry } from '../context-menu/context-menu.js'; +import { showInputDialog } from '../confirm-dialog/confirm-dialog.js'; // ── Block registry ──────────────────────────────────────────────────────────── -// Add new block templates here. Each entry needs: -// name — display label shown below the preview -// preview — HTML markup for the CSS layout sketch (use .pbp-* classes); omit for text-only label -// html — snippet inserted into when the block is added interface PageBlockEntry { @@ -38,12 +35,7 @@ const PAGE_BLOCK_REGISTRY: PageBlockEntry[] = // ── Editor-injected styles ──────────────────────────────────────────────────── // Injected into the iframe after load via + + + + + + +

Page Editor Update

Restructuring the page editor
+
+ +

Toolbar Update

The mode toggle is removed. Block insertion and text formatting share one unified toolbar.
All buttons use an icon + label layout: a 24×24 SVG icon on top, a short uppercase label (max 6 characters, ~10 px font size) below.
The block insertion button (BLOCK) is the first button in the toolbar.
SVG icon placeholders are at source/components/page-editor-panel/icons/

+
+

Block Element Insertion UI

Instead of having a fixed bar on top, the block elements preview will be a floating menu that appears depending on its position. Usually it will be placed below the pointer position, but when it's too low to show the preview it will be placed above. The overlay will be visible until it closed by an (x) button on the top right (auto-close on outside interaction may be added later; for now it has no such behavior).

+
+

Block Element Insertion Triggers

The editor will add rectangle overlays before a block (as overlay roughly 10px touch/click space, but 5px visible rectangle), which will also open the Block Elements Preview. This will allow to insert a  block element before the block (where the overlay is placed) instead of only adding to the end of the document. 

+
+

Block Deletion

Blocks need a button on the top right so that they can be deleted. On landscape this button will only be visible, when the block is hovered. On portrait, tapping anywhere on a block (no contenteditable required) makes only that block's delete button visible; all other blocks' delete buttons are hidden. Tapping inside a different block or outside all blocks dismisses the active selection.

+
+

Toolbar Options

Add new options:
- Link: Allow a selected element to have a link, open a text prompt to set the target. When the selection is already a possible url, use it as suggestion.
- Mark: Add a new style for the custom element <marked-text></marked-text>, which is a bold, cyan colored highlight style, it should be different than a link.

Reorder the options:
[ BLOCK ] | [ H1 ] [ H2 ] [ H3 ] | [ LINK ] [ MARK ] | [ BOLD ] [ ITALIC ] [ UNDER ]
+
+ + + + \ No newline at end of file diff --git a/workspace/history/index.html b/workspace/history/index.html index 5453822..76d8538 100644 --- a/workspace/history/index.html +++ b/workspace/history/index.html @@ -21,7 +21,7 @@

Friday, 31 July 2026

-

File tree: context menu open-state preservation on refresh, "Open >" submenu for alternate editors, context menu label truncated to filename. Page editor toolbar: mode buttons moved into toolbar (sidebar removed). EditorConsole singleton + console-panel tab: centralised message log with es-info header display (5 s fade). openDocumentIn fix: editorTag derived from panelElement.tagName, not registry.

+

File tree: context menu open-state preservation, "Open >" submenu, label truncation. Page editor full redesign: unified icon+label toolbar, floating block menu, insertion trigger overlays, block deletion, drag-to-reorder blocks. InputDialog component. LINK/MARK formats with styled custom elements. EditorConsole + console-panel. openDocumentIn fix.