feat: page editor unified toolbar, floating block menu, insertion triggers, drag-to-reorder, InputDialog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
@ -1,4 +1,5 @@
|
||||||
confirm-dialog {
|
confirm-dialog,
|
||||||
|
input-dialog {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
|
@ -118,3 +119,33 @@ confirm-dialog {
|
||||||
background: #e74c3c;
|
background: #e74c3c;
|
||||||
border-color: #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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,3 +82,95 @@ export function showConfirmDialog(options: ConfirmDialogOptions): Promise<boolea
|
||||||
}
|
}
|
||||||
return dialog.show(options);
|
return dialog.show(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Input dialog ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface InputDialogOptions {
|
||||||
|
icon?: string;
|
||||||
|
title: string;
|
||||||
|
label: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
confirmLabel?: string;
|
||||||
|
cancelLabel?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class InputDialog extends HTMLElement {
|
||||||
|
private resolver: (( value: string | null ) => void) | null = null;
|
||||||
|
|
||||||
|
connectedCallback(): void {
|
||||||
|
this.style.display = 'none';
|
||||||
|
this.innerHTML = `
|
||||||
|
<div class="cd-backdrop"></div>
|
||||||
|
<div class="cd-box" role="dialog" aria-modal="true">
|
||||||
|
<div class="cd-titlebar">
|
||||||
|
<span class="cd-icon"></span>
|
||||||
|
<span class="cd-title"></span>
|
||||||
|
<button class="cd-close" title="Close">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="cd-body">
|
||||||
|
<label class="cd-input-label"></label>
|
||||||
|
<input class="cd-input-field" type="text" autocomplete="off" spellcheck="false">
|
||||||
|
</div>
|
||||||
|
<div class="cd-footer"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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<string | null> {
|
||||||
|
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<string | null>( 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<string | null> {
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="block.svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#333333"
|
||||||
|
bordercolor="#404040"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#333333"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="2.1322"
|
||||||
|
inkscape:cx="-14.538974"
|
||||||
|
inkscape:cy="15.007973"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs2"><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
id="linearGradient24138"><stop
|
||||||
|
style="stop-color:#005a7d;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop24134" /><stop
|
||||||
|
style="stop-color:#001558;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop24136" /></linearGradient><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath7940"><rect
|
||||||
|
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="rect7942"
|
||||||
|
width="1440"
|
||||||
|
height="810"
|
||||||
|
x="0"
|
||||||
|
y="0" /></clipPath><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
xlink:href="#linearGradient24138"
|
||||||
|
id="linearGradient24140"
|
||||||
|
x1="46.913242"
|
||||||
|
y1="37.308647"
|
||||||
|
x2="67.499985"
|
||||||
|
y2="103.96677"
|
||||||
|
gradientUnits="userSpaceOnUse" /></defs><g
|
||||||
|
inkscape:label="Content"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"><rect
|
||||||
|
style="fill:url(#linearGradient24140);fill-opacity:1;stroke:#2cdfff;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect23381"
|
||||||
|
width="109.43874"
|
||||||
|
height="103.13769"
|
||||||
|
x="9.2857094"
|
||||||
|
y="10.612239"
|
||||||
|
ry="7.9806666" /><rect
|
||||||
|
style="fill:#33fff5;fill-opacity:1;stroke:none;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect24142"
|
||||||
|
width="85.123344"
|
||||||
|
height="41.740921"
|
||||||
|
x="20.635963"
|
||||||
|
y="24.387957"
|
||||||
|
ry="7.9806666" /><rect
|
||||||
|
style="fill:#33fff5;fill-opacity:1;stroke:none;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect24144"
|
||||||
|
width="84.654343"
|
||||||
|
height="21.339458"
|
||||||
|
x="20.635963"
|
||||||
|
y="76.446861"
|
||||||
|
ry="7.9806666" /></g></svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="bold.svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#333333"
|
||||||
|
bordercolor="#404040"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#333333"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="3.0153862"
|
||||||
|
inkscape:cx="30.178556"
|
||||||
|
inkscape:cy="128.6734"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs2"><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
id="linearGradient24138"><stop
|
||||||
|
style="stop-color:#54003b;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop24134" /><stop
|
||||||
|
style="stop-color:#4a0012;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop24136" /></linearGradient><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath7940"><rect
|
||||||
|
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="rect7942"
|
||||||
|
width="1440"
|
||||||
|
height="810"
|
||||||
|
x="0"
|
||||||
|
y="0" /></clipPath><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
xlink:href="#linearGradient24138"
|
||||||
|
id="linearGradient24140"
|
||||||
|
x1="46.913242"
|
||||||
|
y1="37.308647"
|
||||||
|
x2="67.499985"
|
||||||
|
y2="103.96677"
|
||||||
|
gradientUnits="userSpaceOnUse" /></defs><g
|
||||||
|
inkscape:label="Content"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"><rect
|
||||||
|
style="fill:url(#linearGradient24140);fill-opacity:1;stroke:#b02cff;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect23381"
|
||||||
|
width="109.43874"
|
||||||
|
height="103.13769"
|
||||||
|
x="9.2857094"
|
||||||
|
y="10.612239"
|
||||||
|
ry="7.9806666" /><g
|
||||||
|
aria-label="b"
|
||||||
|
id="text33457"
|
||||||
|
style="font-weight:bold;font-size:64.4787px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Bold';text-align:center;text-anchor:middle;fill:#dfb2fb;stroke-width:80.5983;stroke-linecap:round;stroke-linejoin:round;paint-order:fill markers stroke"><path
|
||||||
|
d="m 79.378169,61.162939 q 1.418532,3.610807 1.418532,8.253273 0,3.99768 -0.967181,7.221615 -1.418531,4.706945 -4.900381,7.479529 -3.417371,2.708105 -7.737444,2.708105 -5.093817,0 -7.479529,-3.546328 -0.128957,-0.193437 -0.257915,-0.128958 -0.128957,0.06448 -0.128957,0.257915 v 1.934361 q 0,0.451351 -0.257915,0.709266 -0.257915,0.257914 -0.709265,0.257914 H 48.170479 q -0.451351,0 -0.709265,-0.257914 -0.257915,-0.257915 -0.257915,-0.709266 V 42.141723 q 0,-0.451351 0.257915,-0.709266 0.257914,-0.257915 0.709265,-0.257915 h 10.187635 q 0.45135,0 0.709265,0.257915 0.257915,0.257915 0.257915,0.709266 v 12.960218 q 0,0.193436 0.128957,0.257915 0.128958,0 0.257915,-0.128957 2.256755,-2.966021 6.9637,-2.966021 4.577987,0 7.866401,2.51467 3.288414,2.45019 4.835902,6.383391 z M 67.127217,74.316593 q 0.96718,-1.869882 0.96718,-4.706945 0,-3.030498 -0.96718,-4.835902 -1.096138,-2.127797 -3.417371,-2.127797 -2.192276,0 -3.352893,1.99884 -1.031659,1.869882 -1.031659,4.964859 0,2.837063 0.967181,4.642467 1.160616,2.192275 3.417371,2.192275 2.256754,0 3.417371,-2.127797 z"
|
||||||
|
style="font-weight:900;-inkscape-font-specification:'Barlow Heavy'"
|
||||||
|
id="path39896" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="h1.svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#333333"
|
||||||
|
bordercolor="#404040"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#333333"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="4.2644001"
|
||||||
|
inkscape:cx="62.142387"
|
||||||
|
inkscape:cy="73.163867"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs2"><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
id="linearGradient24138"><stop
|
||||||
|
style="stop-color:#00167d;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop24134" /><stop
|
||||||
|
style="stop-color:#440058;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop24136" /></linearGradient><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath7940"><rect
|
||||||
|
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="rect7942"
|
||||||
|
width="1440"
|
||||||
|
height="810"
|
||||||
|
x="0"
|
||||||
|
y="0" /></clipPath><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
xlink:href="#linearGradient24138"
|
||||||
|
id="linearGradient24140"
|
||||||
|
x1="46.913242"
|
||||||
|
y1="37.308647"
|
||||||
|
x2="67.499985"
|
||||||
|
y2="103.96677"
|
||||||
|
gradientUnits="userSpaceOnUse" /></defs><g
|
||||||
|
inkscape:label="Content"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"><rect
|
||||||
|
style="fill:url(#linearGradient24140);fill-opacity:1;stroke:#2c8fff;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect23381"
|
||||||
|
width="109.43874"
|
||||||
|
height="103.13769"
|
||||||
|
x="9.2857094"
|
||||||
|
y="10.612239"
|
||||||
|
ry="7.9806666" /><g
|
||||||
|
aria-label="H1"
|
||||||
|
id="text26419"
|
||||||
|
style="font-weight:bold;font-size:68.5853px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Bold';text-align:center;text-anchor:middle;fill:#9ecfff;stroke-width:28.5773;stroke-linecap:round;stroke-linejoin:round;paint-order:fill markers stroke"><path
|
||||||
|
d="m 59.404804,38.136024 q 0,-0.342926 0.205756,-0.548682 Q 59.884901,37.313 60.227827,37.313 h 8.02448 q 0.342926,0 0.548682,0.274342 0.274341,0.205756 0.274341,0.548682 v 46.363661 q 0,0.342926 -0.274341,0.617267 -0.205756,0.205756 -0.548682,0.205756 h -8.02448 q -0.342926,0 -0.617267,-0.205756 -0.205756,-0.274341 -0.205756,-0.617267 V 65.570143 q 0,-0.342927 -0.342927,-0.342927 h -15.15735 q -0.342927,0 -0.342927,0.342927 v 18.929542 q 0,0.342926 -0.274341,0.617267 -0.205756,0.205756 -0.548682,0.205756 h -8.02448 q -0.342927,0 -0.617268,-0.205756 -0.205756,-0.274341 -0.205756,-0.617267 V 38.136024 q 0,-0.342926 0.205756,-0.548682 Q 34.37117,37.313 34.714097,37.313 h 8.02448 q 0.342926,0 0.548682,0.274342 0.274341,0.205756 0.274341,0.548682 v 18.449445 q 0,0.342926 0.342927,0.342926 h 15.15735 q 0.342927,0 0.342927,-0.342926 z"
|
||||||
|
id="path26585" /><path
|
||||||
|
d="M 83.066748,37.518756 Q 83.409674,37.313 84.095527,37.313 h 8.298821 q 0.342926,0 0.548682,0.274342 0.274341,0.205756 0.274341,0.548682 v 46.363661 q 0,0.342926 -0.274341,0.617267 -0.205756,0.205756 -0.548682,0.205756 h -8.02448 q -0.342926,0 -0.617268,-0.205756 -0.205755,-0.274341 -0.205755,-0.617267 V 46.914942 q 0,-0.137171 -0.137171,-0.274341 -0.137171,-0.137171 -0.274341,-0.06858 l -6.789945,1.851803 -0.274341,0.06858 q -0.617267,0 -0.617267,-0.754438 L 75.248024,41.83963 q 0,-0.685853 0.617267,-0.960194 z"
|
||||||
|
id="path26587" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="h2.svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#333333"
|
||||||
|
bordercolor="#404040"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#333333"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="4.2644001"
|
||||||
|
inkscape:cx="62.142387"
|
||||||
|
inkscape:cy="73.163867"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs2"><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
id="linearGradient24138"><stop
|
||||||
|
style="stop-color:#00167d;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop24134" /><stop
|
||||||
|
style="stop-color:#440058;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop24136" /></linearGradient><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath7940"><rect
|
||||||
|
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="rect7942"
|
||||||
|
width="1440"
|
||||||
|
height="810"
|
||||||
|
x="0"
|
||||||
|
y="0" /></clipPath><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
xlink:href="#linearGradient24138"
|
||||||
|
id="linearGradient24140"
|
||||||
|
x1="46.913242"
|
||||||
|
y1="37.308647"
|
||||||
|
x2="67.499985"
|
||||||
|
y2="103.96677"
|
||||||
|
gradientUnits="userSpaceOnUse" /></defs><g
|
||||||
|
inkscape:label="Content"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"><rect
|
||||||
|
style="fill:url(#linearGradient24140);fill-opacity:1;stroke:#2c8fff;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect23381"
|
||||||
|
width="109.43874"
|
||||||
|
height="103.13769"
|
||||||
|
x="9.2857094"
|
||||||
|
y="10.612239"
|
||||||
|
ry="7.9806666" /><g
|
||||||
|
aria-label="H2"
|
||||||
|
id="text26419"
|
||||||
|
style="font-weight:bold;font-size:68.5853px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Bold';text-align:center;text-anchor:middle;fill:#9ecfff;stroke-width:28.5773;stroke-linecap:round;stroke-linejoin:round;paint-order:fill markers stroke"
|
||||||
|
transform="matrix(0.81979767,0,0,0.81979767,11.545317,11.000184)"><path
|
||||||
|
d="m 52.34054,38.136024 q 0,-0.342926 0.205755,-0.548682 Q 52.820637,37.313 53.163563,37.313 h 8.02448 q 0.342926,0 0.548682,0.274342 0.274341,0.205756 0.274341,0.548682 v 46.363661 q 0,0.342926 -0.274341,0.617267 -0.205756,0.205756 -0.548682,0.205756 h -8.02448 q -0.342926,0 -0.617268,-0.205756 Q 52.34054,84.842611 52.34054,84.499685 V 65.570143 q 0,-0.342927 -0.342927,-0.342927 h -15.15735 q -0.342927,0 -0.342927,0.342927 v 18.929542 q 0,0.342926 -0.274341,0.617267 -0.205756,0.205756 -0.548683,0.205756 h -8.024479 q -0.342927,0 -0.617268,-0.205756 -0.205756,-0.274341 -0.205756,-0.617267 V 38.136024 q 0,-0.342926 0.205756,-0.548682 Q 27.306906,37.313 27.649833,37.313 h 8.024479 q 0.342927,0 0.548683,0.274342 0.274341,0.205756 0.274341,0.548682 v 18.449445 q 0,0.342926 0.342927,0.342926 h 15.15735 q 0.342927,0 0.342927,-0.342926 z"
|
||||||
|
id="path26621" /><path
|
||||||
|
d="m 81.695063,76.612375 q -0.137171,0.137171 -0.06858,0.274342 0.06858,0.13717 0.274341,0.13717 h 18.586616 q 0.34293,0 0.54869,0.274341 0.27434,0.205756 0.27434,0.548683 v 6.652774 q 0,0.342926 -0.27434,0.617267 -0.20576,0.205756 -0.54869,0.205756 H 69.624051 q -0.342927,0 -0.617268,-0.205756 -0.205756,-0.274341 -0.205756,-0.617267 v -6.309848 q 0,-0.617267 0.411512,-1.028779 3.36068,-3.292094 6.85853,-7.132871 3.49785,-3.909362 4.389459,-4.869556 1.920388,-2.263315 3.909362,-4.320874 6.241262,-6.927115 6.241262,-10.287794 0,-2.400486 -1.714633,-3.909362 -1.714632,-1.577462 -4.458044,-1.577462 -2.743412,0 -4.458044,1.577462 -1.714633,1.508876 -1.714633,4.046532 v 1.714633 q 0,0.342926 -0.274341,0.617267 -0.205756,0.205756 -0.548682,0.205756 H 69.34971 q -0.342927,0 -0.617268,-0.205756 -0.205756,-0.274341 -0.205756,-0.617267 v -3.223509 q 0.205756,-3.703606 2.3319,-6.515603 2.126144,-2.880583 5.623994,-4.389459 3.566436,-1.508877 7.955895,-1.508877 4.869556,0 8.435991,1.851803 3.635021,1.783218 5.555409,4.869556 1.988975,3.086339 1.988975,6.85853 0,2.880582 -1.440292,5.898335 -1.440291,3.017753 -4.320874,6.515604 -2.126144,2.674826 -4.595215,5.281067 -2.46907,2.606242 -7.338627,7.544383 z"
|
||||||
|
id="path26623" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 5.0 KiB |
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="h3.svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#333333"
|
||||||
|
bordercolor="#404040"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#333333"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="4.2644001"
|
||||||
|
inkscape:cx="62.142387"
|
||||||
|
inkscape:cy="73.163867"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs2"><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
id="linearGradient24138"><stop
|
||||||
|
style="stop-color:#00167d;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop24134" /><stop
|
||||||
|
style="stop-color:#440058;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop24136" /></linearGradient><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath7940"><rect
|
||||||
|
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="rect7942"
|
||||||
|
width="1440"
|
||||||
|
height="810"
|
||||||
|
x="0"
|
||||||
|
y="0" /></clipPath><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
xlink:href="#linearGradient24138"
|
||||||
|
id="linearGradient24140"
|
||||||
|
x1="46.913242"
|
||||||
|
y1="37.308647"
|
||||||
|
x2="67.499985"
|
||||||
|
y2="103.96677"
|
||||||
|
gradientUnits="userSpaceOnUse" /></defs><g
|
||||||
|
inkscape:label="Content"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"><rect
|
||||||
|
style="fill:url(#linearGradient24140);fill-opacity:1;stroke:#2c8fff;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect23381"
|
||||||
|
width="109.43874"
|
||||||
|
height="103.13769"
|
||||||
|
x="9.2857094"
|
||||||
|
y="10.612239"
|
||||||
|
ry="7.9806666" /><g
|
||||||
|
aria-label="H3"
|
||||||
|
id="text26419-9"
|
||||||
|
style="font-weight:bold;font-size:47.1453px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Bold';text-align:center;text-anchor:middle;fill:#9ecfff;stroke-width:19.644;stroke-linecap:round;stroke-linejoin:round;paint-order:fill markers stroke"><path
|
||||||
|
d="m 56.317395,45.38275 q 0,-0.235726 0.141436,-0.377162 0.188581,-0.188581 0.424308,-0.188581 h 5.516 q 0.235726,0 0.377162,0.188581 0.188582,0.141436 0.188582,0.377162 v 31.870224 q 0,0.235727 -0.188582,0.424308 -0.141436,0.141436 -0.377162,0.141436 h -5.516 q -0.235727,0 -0.424308,-0.141436 -0.141436,-0.188581 -0.141436,-0.424308 V 64.240871 q 0,-0.235726 -0.235727,-0.235726 H 45.662557 q -0.235727,0 -0.235727,0.235726 v 13.012103 q 0,0.235727 -0.188581,0.424308 -0.141436,0.141436 -0.377162,0.141436 h -5.516001 q -0.235726,0 -0.424307,-0.141436 -0.141436,-0.188581 -0.141436,-0.424308 V 45.38275 q 0,-0.235726 0.141436,-0.377162 0.188581,-0.188581 0.424307,-0.188581 h 5.516001 q 0.235726,0 0.377162,0.188581 0.188581,0.141436 0.188581,0.377162 v 12.682086 q 0,0.235727 0.235727,0.235727 h 10.419111 q 0.235727,0 0.235727,-0.235727 z"
|
||||||
|
id="path27445" /><path
|
||||||
|
d="m 87.99905,62.072187 q 0.942906,2.168684 0.942906,4.997402 0,2.545846 -0.848616,4.71453 -1.131487,3.0173 -3.818769,4.714531 -2.640137,1.69723 -6.270325,1.69723 -3.583043,0 -6.317471,-1.791521 -2.687282,-1.791522 -3.865914,-4.855966 -0.660035,-1.838667 -0.754325,-3.960206 0,-0.565743 0.565744,-0.565743 h 5.563145 q 0.565744,0 0.565744,0.565743 0.188581,1.555795 0.518598,2.31012 0.424308,1.225778 1.367214,1.932958 0.990051,0.660034 2.31012,0.660034 2.640137,0 3.630188,-2.31012 0.660034,-1.414359 0.660034,-3.300171 0,-2.215829 -0.707179,-3.630188 -1.084342,-2.215829 -3.630189,-2.215829 -0.518598,0 -1.084342,0.330017 -0.565743,0.282871 -1.367213,0.848615 -0.188582,0.141436 -0.377163,0.141436 -0.282872,0 -0.424308,-0.282872 l -2.781572,-3.91306 q -0.09429,-0.141436 -0.09429,-0.330017 0,-0.282872 0.188581,-0.471453 L 79.46575,50.80446 q 0.09429,-0.09429 0.04715,-0.188581 0,-0.09429 -0.141436,-0.09429 H 68.292314 q -0.235727,0 -0.424308,-0.141436 Q 67.72657,50.191571 67.72657,49.955845 V 45.38275 q 0,-0.235726 0.141436,-0.377162 0.188581,-0.188581 0.424308,-0.188581 h 19.659591 q 0.235726,0 0.377162,0.188581 0.188581,0.141436 0.188581,0.377162 v 5.185984 q 0,0.377162 -0.330017,0.707179 l -6.176034,5.657436 q -0.09429,0.09429 -0.09429,0.188581 0.04715,0.09429 0.235726,0.09429 4.101642,0.80147 5.846018,4.855966 z"
|
||||||
|
id="path27447" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 5.0 KiB |
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="italic.svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#333333"
|
||||||
|
bordercolor="#404040"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#333333"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="2.1322"
|
||||||
|
inkscape:cx="118.65678"
|
||||||
|
inkscape:cy="60.50089"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs2"><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
id="linearGradient24138"><stop
|
||||||
|
style="stop-color:#54003b;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop24134" /><stop
|
||||||
|
style="stop-color:#4a0012;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop24136" /></linearGradient><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath7940"><rect
|
||||||
|
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="rect7942"
|
||||||
|
width="1440"
|
||||||
|
height="810"
|
||||||
|
x="0"
|
||||||
|
y="0" /></clipPath><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
xlink:href="#linearGradient24138"
|
||||||
|
id="linearGradient24140"
|
||||||
|
x1="46.913242"
|
||||||
|
y1="37.308647"
|
||||||
|
x2="67.499985"
|
||||||
|
y2="103.96677"
|
||||||
|
gradientUnits="userSpaceOnUse" /></defs><g
|
||||||
|
inkscape:label="Content"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"><rect
|
||||||
|
style="fill:url(#linearGradient24140);fill-opacity:1;stroke:#b02cff;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect23381"
|
||||||
|
width="109.43874"
|
||||||
|
height="103.13769"
|
||||||
|
x="9.2857094"
|
||||||
|
y="10.612239"
|
||||||
|
ry="7.9806666" /><text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:64.4787px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Bold';text-align:center;text-anchor:middle;fill:#dfb2fb;fill-opacity:1;stroke:none;stroke-width:80.5983;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
x="-118.08614"
|
||||||
|
y="86.162491"
|
||||||
|
id="text33457"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan33455"
|
||||||
|
x="-118.08614"
|
||||||
|
y="86.162491"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy';fill:#dfb2fb;fill-opacity:1;stroke:none;stroke-width:80.5983">b</tspan></text><g
|
||||||
|
aria-label="i"
|
||||||
|
transform="skewX(-8.1733529)"
|
||||||
|
id="text34207"
|
||||||
|
style="font-style:italic;font-size:64.4787px;font-family:Barlow;-inkscape-font-specification:'Barlow Italic';text-align:center;text-anchor:middle;fill:#dfb2fb;stroke-width:80.5983;stroke-linecap:round;stroke-linejoin:round;paint-order:fill markers stroke"><path
|
||||||
|
d="m 74.707408,48.363918 q -1.354052,0 -2.256754,-0.838223 -0.838223,-0.902702 -0.838223,-2.256754 0,-1.740925 1.160616,-2.901542 1.225096,-1.225095 2.837063,-1.225095 1.354053,0 2.192276,0.838223 0.902702,0.838223 0.902702,2.256755 0,1.740924 -1.160617,2.96602 -1.160616,1.160616 -2.837063,1.160616 z m -6.44787,38.493783 q -0.580308,0 -0.580308,-0.644787 l 3.868722,-31.336647 q 0.128957,-0.644787 0.709266,-0.644787 h 3.159456 q 0.322394,0 0.451351,0.193436 0.193436,0.193436 0.128957,0.451351 L 72.12826,86.212914 q -0.128957,0.644787 -0.709265,0.644787 z"
|
||||||
|
id="path34224" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="link.svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#333333"
|
||||||
|
bordercolor="#404040"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#333333"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="3.0153862"
|
||||||
|
inkscape:cx="57.040786"
|
||||||
|
inkscape:cy="67.321393"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs2"><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
id="linearGradient24138"><stop
|
||||||
|
style="stop-color:#330054;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop24134" /><stop
|
||||||
|
style="stop-color:#4a0028;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop24136" /></linearGradient><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath7940"><rect
|
||||||
|
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="rect7942"
|
||||||
|
width="1440"
|
||||||
|
height="810"
|
||||||
|
x="0"
|
||||||
|
y="0" /></clipPath><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
xlink:href="#linearGradient24138"
|
||||||
|
id="linearGradient24140"
|
||||||
|
x1="46.913242"
|
||||||
|
y1="37.308647"
|
||||||
|
x2="67.499985"
|
||||||
|
y2="103.96677"
|
||||||
|
gradientUnits="userSpaceOnUse" /></defs><g
|
||||||
|
inkscape:label="Content"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"><rect
|
||||||
|
style="fill:url(#linearGradient24140);fill-opacity:1;stroke:#472cff;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect23381"
|
||||||
|
width="109.43874"
|
||||||
|
height="103.13769"
|
||||||
|
x="9.2857094"
|
||||||
|
y="10.612239"
|
||||||
|
ry="7.9806666" /><path
|
||||||
|
style="color:#000000;fill:#8596e4;stroke:none;stroke-width:0.638116;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none;paint-order:fill markers stroke;fill-opacity:1"
|
||||||
|
d="m 104.18831,35.263677 c -2.58798,-3.508492 -6.813172,-5.052014 -10.849804,-5.009012 -4.036633,0.043 -8.134603,1.495177 -11.757688,4.167682 l -9.707829,7.160809 c -3.623084,2.672505 -6.220453,6.15903 -7.453359,10.003011 -1.232907,3.843982 -1.005692,8.336544 1.582287,11.845037 2.587977,3.508491 6.813174,5.052014 10.849807,5.009012 4.036632,-0.043 8.134603,-1.495178 11.757688,-4.167682 l 9.707828,-7.160809 c 3.62308,-2.672506 6.22045,-6.159031 7.45336,-10.003012 1.23291,-3.843981 1.00569,-8.336544 -1.58229,-11.845036 z m -7.702872,5.68189 c 0.474132,0.642774 0.694767,1.60471 0.170382,3.239648 -0.524385,1.634938 -1.88839,3.650943 -4.020471,5.223635 l -9.707827,7.160809 c -2.13208,1.572692 -4.460937,2.28066 -6.177814,2.29895 -1.716878,0.01829 -2.570786,-0.476521 -3.044918,-1.119295 -0.474131,-0.642775 -0.694766,-1.60471 -0.170381,-3.239648 0.524385,-1.634938 1.888391,-3.650944 4.020471,-5.223636 l 9.707828,-7.160809 c 2.13208,-1.572692 4.460936,-2.280661 6.177813,-2.29895 1.716877,-0.01829 2.570786,0.476521 3.044917,1.119296 z"
|
||||||
|
id="rect29762" /><path
|
||||||
|
style="color:#000000;fill:#8596e4;stroke:none;stroke-width:0.638116;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none;paint-order:fill markers stroke;fill-opacity:1"
|
||||||
|
d="m 63.997148,64.909986 c -2.587979,-3.508492 -6.813175,-5.052014 -10.849807,-5.009012 -4.036633,0.043 -8.134607,1.495179 -11.757691,4.167683 l -9.707828,7.160811 c -3.62308,2.672503 -6.22045,6.159029 -7.45336,10.003006 -1.23291,3.843987 -1.00569,8.336547 1.58229,11.845037 2.58798,3.5085 6.813171,5.05202 10.849809,5.00901 4.036631,-0.043 8.134601,-1.49517 11.757683,-4.16768 l 9.70783,-7.16081 c 3.623083,-2.6725 6.220453,-6.159027 7.453361,-10.003011 1.232906,-3.843979 1.00569,-8.336542 -1.582287,-11.845034 z m -7.702875,5.681891 c 0.474131,0.642774 0.694767,1.604709 0.170383,3.239646 -0.524389,1.634941 -1.888394,3.650949 -4.020472,5.223637 l -9.70783,7.160811 c -2.132078,1.57269 -4.460933,2.28066 -6.177809,2.29895 -1.716882,0.0183 -2.57079,-0.47653 -3.044916,-1.1193 -0.474133,-0.64277 -0.694774,-1.60471 -0.170383,-3.23964 0.524384,-1.634947 1.888388,-3.650952 4.020467,-5.22364 l 9.707829,-7.16081 c 2.132079,-1.57269 4.460934,-2.280659 6.177815,-2.298952 1.716876,-0.01829 2.570785,0.476523 3.044916,1.119298 z"
|
||||||
|
id="path29877" /></g></svg>
|
||||||
|
After Width: | Height: | Size: 5.0 KiB |
|
|
@ -0,0 +1,87 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="mark.svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#333333"
|
||||||
|
bordercolor="#404040"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#333333"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="2.1322"
|
||||||
|
inkscape:cx="118.65678"
|
||||||
|
inkscape:cy="60.50089"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs2"><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
id="linearGradient24138"><stop
|
||||||
|
style="stop-color:#330054;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop24134" /><stop
|
||||||
|
style="stop-color:#4a0028;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop24136" /></linearGradient><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath7940"><rect
|
||||||
|
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="rect7942"
|
||||||
|
width="1440"
|
||||||
|
height="810"
|
||||||
|
x="0"
|
||||||
|
y="0" /></clipPath><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
xlink:href="#linearGradient24138"
|
||||||
|
id="linearGradient24140"
|
||||||
|
x1="46.913242"
|
||||||
|
y1="37.308647"
|
||||||
|
x2="67.499985"
|
||||||
|
y2="103.96677"
|
||||||
|
gradientUnits="userSpaceOnUse" /></defs><g
|
||||||
|
inkscape:label="Content"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"><rect
|
||||||
|
style="fill:url(#linearGradient24140);fill-opacity:1;stroke:#472cff;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect23381"
|
||||||
|
width="109.43874"
|
||||||
|
height="103.13769"
|
||||||
|
x="9.2857094"
|
||||||
|
y="10.612239"
|
||||||
|
ry="7.9806666" /><path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#8596e4;fill-opacity:1;stroke:none;stroke-width:15;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="path30220"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
sodipodi:sides="5"
|
||||||
|
sodipodi:cx="64.999969"
|
||||||
|
sodipodi:cy="78.265266"
|
||||||
|
sodipodi:r1="15.714126"
|
||||||
|
sodipodi:r2="24.152317"
|
||||||
|
sodipodi:arg1="-1.0385123"
|
||||||
|
sodipodi:arg2="-0.41019376"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 72.974931,64.725187 14.173766,3.908449 -6.806949,13.032155 0.66278,14.687829 -14.497779,-2.446635 -13.764146,5.169129 -2.153171,-14.544258 -9.16949,-11.493132 13.167046,-6.542211 8.09709,-12.272275 z"
|
||||||
|
transform="matrix(1.2900866,0,0,1.2900866,-18.899186,-37.393152)" /></g></svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="under.svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#333333"
|
||||||
|
bordercolor="#404040"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#333333"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="3.0153862"
|
||||||
|
inkscape:cx="139.61727"
|
||||||
|
inkscape:cy="126.68361"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g36437" /><defs
|
||||||
|
id="defs2"><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
id="linearGradient24138"><stop
|
||||||
|
style="stop-color:#54003b;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop24134" /><stop
|
||||||
|
style="stop-color:#4a0012;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop24136" /></linearGradient><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath7940"><rect
|
||||||
|
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
id="rect7942"
|
||||||
|
width="1440"
|
||||||
|
height="810"
|
||||||
|
x="0"
|
||||||
|
y="0" /></clipPath><linearGradient
|
||||||
|
inkscape:collect="never"
|
||||||
|
xlink:href="#linearGradient24138"
|
||||||
|
id="linearGradient24140"
|
||||||
|
x1="46.913242"
|
||||||
|
y1="37.308647"
|
||||||
|
x2="67.499985"
|
||||||
|
y2="103.96677"
|
||||||
|
gradientUnits="userSpaceOnUse" /></defs><g
|
||||||
|
inkscape:label="Content"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="fill:#ffffff;fill-opacity:1"><rect
|
||||||
|
style="fill:url(#linearGradient24140);fill-opacity:1;stroke:#b02cff;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
id="rect23381"
|
||||||
|
width="109.43874"
|
||||||
|
height="103.13769"
|
||||||
|
x="9.2857094"
|
||||||
|
y="10.612239"
|
||||||
|
ry="7.9806666" /><text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:64.4787px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Bold';text-align:center;text-anchor:middle;fill:#dfb2fb;fill-opacity:1;stroke:none;stroke-width:80.5983;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
x="-118.08614"
|
||||||
|
y="86.162491"
|
||||||
|
id="text33457"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan33455"
|
||||||
|
x="-118.08614"
|
||||||
|
y="86.162491"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy';fill:#dfb2fb;fill-opacity:1;stroke:none;stroke-width:80.5983">b</tspan></text><g
|
||||||
|
id="g36437"
|
||||||
|
transform="translate(0,1.3069252)"><g
|
||||||
|
aria-label="u"
|
||||||
|
id="text34987"
|
||||||
|
style="font-size:64.4787px;font-family:Barlow;-inkscape-font-specification:Barlow;text-align:center;text-anchor:middle;fill:#dfb2fb;stroke-width:80.5983;stroke-linecap:round;stroke-linejoin:round;paint-order:fill markers stroke"><path
|
||||||
|
d="m 71.834164,41.987087 q 0,-0.644787 0.644787,-0.644787 h 3.288413 q 0.644787,0 0.644787,0.644787 v 31.336647 q 0,0.644787 -0.644787,0.644787 h -3.288413 q -0.644787,0 -0.644787,-0.644787 V 70.55115 q 0,-0.128957 -0.128958,-0.193436 -0.128957,-0.06448 -0.193436,0.06448 -2.708105,3.997679 -8.575667,3.997679 -3.094977,0 -5.738604,-1.225095 -2.579148,-1.289574 -4.126637,-3.675286 -1.48301,-2.385712 -1.48301,-5.738604 v -21.7938 q 0,-0.644787 0.644787,-0.644787 h 3.288414 q 0.644787,0 0.644787,0.644787 v 20.439747 q 0,3.675286 2.063318,5.867562 2.063319,2.127797 5.609647,2.127797 3.675286,0 5.803083,-2.192276 2.192276,-2.192276 2.192276,-5.803083 z"
|
||||||
|
id="path36439" /></g><path
|
||||||
|
style="color:#000000;fill:#dfb2fb;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none;paint-order:fill markers stroke"
|
||||||
|
d="m 51.84375,81.042969 a 1.5,1.5 0 0 0 -1.5,1.5 1.5,1.5 0 0 0 1.5,1.5 h 24.3125 a 1.5,1.5 0 0 0 1.5,-1.5 1.5,1.5 0 0 0 -1.5,-1.5 z"
|
||||||
|
id="path35052" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.7 KiB |
|
|
@ -3,29 +3,7 @@ page-editor-panel {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #0f1117;
|
background: #0f1117;
|
||||||
}
|
position: relative;
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Toolbar ─────────────────────────────────────────────────────────────── */
|
/* ── Toolbar ─────────────────────────────────────────────────────────────── */
|
||||||
|
|
@ -38,9 +16,10 @@ page-editor-panel {
|
||||||
background: #13151f;
|
background: #13151f;
|
||||||
border-bottom: 1px solid #2a2d3a;
|
border-bottom: 1px solid #2a2d3a;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pep-toolbar button {
|
.pep-toolbar button:not(.pep-fmt-btn) {
|
||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid #2a2d3a;
|
border: 1px solid #2a2d3a;
|
||||||
|
|
@ -49,27 +28,102 @@ page-editor-panel {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pep-toolbar button:hover:not(:disabled) { background: #1a1d27; color: #e2e4ed; }
|
.pep-toolbar button:not(.pep-fmt-btn):hover:not(:disabled) { background: #1a1d27; color: #e2e4ed; }
|
||||||
.pep-toolbar button:disabled { opacity: 0.3; cursor: default; }
|
.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) { 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 { border-color: #f0a050; color: #f0a050; }
|
||||||
.pep-pin.pinned:hover { background: #1e1a10; }
|
.pep-pin.pinned:hover { background: #1e1a10 !important; }
|
||||||
|
|
||||||
/* ── Mode panels ─────────────────────────────────────────────────────────── */
|
.pep-toolbar-sep {
|
||||||
|
flex: 1;
|
||||||
.pep-mode-panel {
|
min-width: 4px;
|
||||||
background: #13151f;
|
|
||||||
border-bottom: 1px solid #2a2d3a;
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding: 6px 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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 {
|
.pep-block-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -105,7 +159,6 @@ page-editor-panel {
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CSS layout sketch helpers used inside .pep-block-preview */
|
|
||||||
.pbp-full { display: flex; flex: 1; }
|
.pbp-full { display: flex; flex: 1; }
|
||||||
.pbp-two-col { display: flex; flex: 1; gap: 4px; }
|
.pbp-two-col { display: flex; flex: 1; gap: 4px; }
|
||||||
.pbp-area { flex: 1; background: #3a3d4a; border-radius: 2px; }
|
.pbp-area { flex: 1; background: #3a3d4a; border-radius: 2px; }
|
||||||
|
|
@ -127,34 +180,18 @@ page-editor-panel {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Areas panel — rich text formatting toolbar */
|
/* ── Drag bar ────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.pep-areas-panel {
|
.pep-drag-bar {
|
||||||
display: flex;
|
position: absolute;
|
||||||
flex-direction: row;
|
left: 8px;
|
||||||
gap: 4px;
|
right: 8px;
|
||||||
}
|
height: 3px;
|
||||||
|
background: #7c8cff;
|
||||||
.pep-fmt-btn {
|
border-radius: 2px;
|
||||||
padding: 2px 8px;
|
pointer-events: none;
|
||||||
background: transparent;
|
z-index: 50;
|
||||||
border: 1px solid #2a2d3a;
|
box-shadow: 0 0 8px rgba(124, 140, 255, 0.55);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Content area ────────────────────────────────────────────────────────── */
|
/* ── Content area ────────────────────────────────────────────────────────── */
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import { Editor } from '../../editor/Editor.js';
|
import { Editor } from '../../editor/Editor.js';
|
||||||
import { EditorPanelDefinition, FileEditorPanelDefinition } from '../../editor/editor-panel.js';
|
import { EditorPanelDefinition, FileEditorPanelDefinition } from '../../editor/editor-panel.js';
|
||||||
import { ContextMenuDirectory, ContextMenuReadOnlyEntry } from '../context-menu/context-menu.js';
|
import { ContextMenuDirectory, ContextMenuReadOnlyEntry } from '../context-menu/context-menu.js';
|
||||||
|
import { showInputDialog } from '../confirm-dialog/confirm-dialog.js';
|
||||||
|
|
||||||
// ── Block registry ────────────────────────────────────────────────────────────
|
// ── 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 <page-root> when the block is added
|
|
||||||
|
|
||||||
interface PageBlockEntry
|
interface PageBlockEntry
|
||||||
{
|
{
|
||||||
|
|
@ -38,12 +35,7 @@ const PAGE_BLOCK_REGISTRY: PageBlockEntry[] =
|
||||||
|
|
||||||
// ── Editor-injected styles ────────────────────────────────────────────────────
|
// ── Editor-injected styles ────────────────────────────────────────────────────
|
||||||
// Injected into the iframe <head> after load via <style id="pep-editor-injected">.
|
// Injected into the iframe <head> after load via <style id="pep-editor-injected">.
|
||||||
// Never written to disk — stripped from captured HTML before saving by temporarily
|
// Stripped from captured HTML before saving via document.cloneNode — never on disk.
|
||||||
// removing the element, capturing outerHTML, then re-appending.
|
|
||||||
// To update editor-side layout styles, edit PEP_EDITOR_STYLES.
|
|
||||||
//
|
|
||||||
// Sandbox: the iframe uses sandbox="allow-same-origin" to block script execution.
|
|
||||||
// To adjust sandboxing behaviour, change the sandbox attribute on .pep-frame.
|
|
||||||
|
|
||||||
const PEP_EDITOR_STYLES = `
|
const PEP_EDITOR_STYLES = `
|
||||||
page-header, page-footer {
|
page-header, page-footer {
|
||||||
|
|
@ -60,6 +52,7 @@ page-block {
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
page-block.pep-block-full { flex-direction: row; }
|
page-block.pep-block-full { flex-direction: row; }
|
||||||
page-block.pep-block-two-col { flex-direction: row; }
|
page-block.pep-block-two-col { flex-direction: row; }
|
||||||
|
|
@ -75,12 +68,55 @@ page-area {
|
||||||
@media (orientation: portrait) {
|
@media (orientation: portrait) {
|
||||||
page-block.pep-block-two-col { flex-direction: column; }
|
page-block.pep-block-two-col { flex-direction: column; }
|
||||||
}
|
}
|
||||||
|
.pep-insert-trigger {
|
||||||
|
display: block;
|
||||||
|
height: 10px;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
.pep-insert-trigger:active { cursor: grabbing; }
|
||||||
|
.pep-insert-trigger::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 5px;
|
||||||
|
margin-top: 2.5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: transparent;
|
||||||
|
transition: background 0.1s;
|
||||||
|
}
|
||||||
|
.pep-insert-trigger:hover::before {
|
||||||
|
background: rgba(124, 140, 255, 0.3);
|
||||||
|
}
|
||||||
|
.pep-drop-target::before {
|
||||||
|
background: rgba(124, 140, 255, 0.75) !important;
|
||||||
|
}
|
||||||
|
.pep-delete-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
background: #1a1d27;
|
||||||
|
border: 1px solid #3a3d4a;
|
||||||
|
color: #9ba4c7;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
padding: 2px 6px;
|
||||||
|
display: none;
|
||||||
|
z-index: 10;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.pep-delete-btn:hover { background: #2a1d27; border-color: #ff6060; color: #ff8080; }
|
||||||
|
@media (hover: hover) {
|
||||||
|
page-block:hover .pep-delete-btn { display: block; }
|
||||||
|
}
|
||||||
|
page-block.pep-block-active .pep-delete-btn { display: block; }
|
||||||
|
marked-text {
|
||||||
|
font-weight: 700;
|
||||||
|
color: hsl(190, 80%, 90%);
|
||||||
|
}
|
||||||
|
a { color: hsl(200, 80%, 70%); }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// ── Default theme CSS ─────────────────────────────────────────────────────────
|
// ── Default theme CSS ─────────────────────────────────────────────────────────
|
||||||
// Scoped under [data-theme="default-roject"] so rules never leak outside page-root.
|
|
||||||
// Embedded as a <style> tag in the page <head> for new files.
|
|
||||||
// Future: replace with a <link> to styles.rokojori.com when themes are hosted there.
|
|
||||||
|
|
||||||
const DEFAULT_THEME_CSS = `@import url('https://styles.rokojori.com/get-font?family=Barlow&weights=100,400,700,900');
|
const DEFAULT_THEME_CSS = `@import url('https://styles.rokojori.com/get-font?family=Barlow&weights=100,400,700,900');
|
||||||
|
|
||||||
|
|
@ -123,11 +159,18 @@ const DEFAULT_THEME_CSS = `@import url('https://styles.rokojori.com/get-font?fam
|
||||||
[data-theme="default-roject"] strong {
|
[data-theme="default-roject"] strong {
|
||||||
color: #e2e4ed;
|
color: #e2e4ed;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="default-roject"] marked-text {
|
||||||
|
font-weight: 700;
|
||||||
|
color: hsl(190, 80%, 90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="default-roject"] a {
|
||||||
|
color: hsl(200, 80%, 70%);
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
// ── Standard template for new / empty .page files ────────────────────────────
|
// ── Standard template for new / empty .page files ────────────────────────────
|
||||||
// Injected automatically when a .page file is opened with empty (or whitespace-only)
|
|
||||||
// content. The document is marked dirty immediately — user must save to persist.
|
|
||||||
|
|
||||||
const PAGE_TEMPLATE = `<!DOCTYPE html>
|
const PAGE_TEMPLATE = `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
@ -153,11 +196,7 @@ ${ DEFAULT_THEME_CSS }
|
||||||
|
|
||||||
// ── Validation ────────────────────────────────────────────────────────────────
|
// ── Validation ────────────────────────────────────────────────────────────────
|
||||||
// Placeholder — always returns true.
|
// Placeholder — always returns true.
|
||||||
// TODO: implement real validation:
|
// TODO: implement real validation when format is stable.
|
||||||
// - Exactly one <page-header>, one <page-root>, one <page-footer> as direct
|
|
||||||
// children of <body>; no other elements at that level.
|
|
||||||
// - <page-root> may be empty or contain any number of <page-block> children.
|
|
||||||
// When validation fails, the file should fall back to code-panel.
|
|
||||||
|
|
||||||
function validatePageFormat( _doc: Document ): boolean
|
function validatePageFormat( _doc: Document ): boolean
|
||||||
{
|
{
|
||||||
|
|
@ -165,10 +204,6 @@ function validatePageFormat( _doc: Document ): boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Rich-text helper ──────────────────────────────────────────────────────────
|
// ── Rich-text helper ──────────────────────────────────────────────────────────
|
||||||
// Wraps the given Range in a new element created in `doc`.
|
|
||||||
// Uses Range.extractContents() which handles both fully-contained nodes
|
|
||||||
// (wrapped outside) and boundary intersections (text nodes split automatically
|
|
||||||
// by the Range API, wrapped inside the outer element).
|
|
||||||
// Note: adjacent identical elements are not merged after wrapping (future work).
|
// Note: adjacent identical elements are not merged after wrapping (future work).
|
||||||
|
|
||||||
function wrapSelection( doc: Document, range: Range, tagName: string, attributes?: Record<string, string> ): HTMLElement
|
function wrapSelection( doc: Document, range: Range, tagName: string, attributes?: Record<string, string> ): HTMLElement
|
||||||
|
|
@ -188,6 +223,32 @@ function wrapSelection( doc: Document, range: Range, tagName: string, attributes
|
||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Toolbar HTML ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function iconBtn( tag: string, icon: string, label: string, title: string, extraClass = '' ): string
|
||||||
|
{
|
||||||
|
const dataTag = tag ? `data-tag="${ tag }"` : '';
|
||||||
|
return `<button class="pep-fmt-btn ${ extraClass }" ${ dataTag } title="${ title }">
|
||||||
|
<img class="pep-btn-icon" src="/components/page-editor-panel/icons/${ icon }.svg" width="24" height="24" alt="">
|
||||||
|
<span class="pep-btn-label">${ label }</span>
|
||||||
|
</button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TOOLBAR_BUTTONS = `
|
||||||
|
${ iconBtn( '', 'block', 'BLOCK', 'Insert Block', 'pep-btn-block' ) }
|
||||||
|
<div class="pep-fmt-sep"></div>
|
||||||
|
${ iconBtn( 'h1', 'h1', 'H1', 'Heading 1' ) }
|
||||||
|
${ iconBtn( 'h2', 'h2', 'H2', 'Heading 2' ) }
|
||||||
|
${ iconBtn( 'h3', 'h3', 'H3', 'Heading 3' ) }
|
||||||
|
<div class="pep-fmt-sep"></div>
|
||||||
|
${ iconBtn( 'link', 'link', 'LINK', 'Link' ) }
|
||||||
|
${ iconBtn( 'marked-text', 'mark', 'MARK', 'Mark' ) }
|
||||||
|
<div class="pep-fmt-sep"></div>
|
||||||
|
${ iconBtn( 'b', 'bold', 'BOLD', 'Bold' ) }
|
||||||
|
${ iconBtn( 'i', 'italic', 'ITALIC', 'Italic' ) }
|
||||||
|
${ iconBtn( 'u', 'under', 'UNDER', 'Underline' ) }
|
||||||
|
`;
|
||||||
|
|
||||||
// ── Component ─────────────────────────────────────────────────────────────────
|
// ── Component ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
class PageEditorPanel extends HTMLElement
|
class PageEditorPanel extends HTMLElement
|
||||||
|
|
@ -202,7 +263,13 @@ class PageEditorPanel extends HTMLElement
|
||||||
_mutationObserver: MutationObserver | null = null;
|
_mutationObserver: MutationObserver | null = null;
|
||||||
_initialized = false;
|
_initialized = false;
|
||||||
_needsRestore = false;
|
_needsRestore = false;
|
||||||
_mode: 'blocks' | 'areas' = 'blocks';
|
_blockInsertTarget: Element | null = null;
|
||||||
|
_pageRootClickHandler: (( e: Event ) => void) | null = null;
|
||||||
|
_bodyClickHandler: (() => void) | null = null;
|
||||||
|
_draggedBlock: Element | null = null;
|
||||||
|
_dropTrigger: Element | null = null;
|
||||||
|
_dragBar: HTMLElement | null = null;
|
||||||
|
_iframeTriggers: Element[] = [];
|
||||||
|
|
||||||
connectedCallback(): void
|
connectedCallback(): void
|
||||||
{
|
{
|
||||||
|
|
@ -217,10 +284,10 @@ class PageEditorPanel extends HTMLElement
|
||||||
<button class="pep-redo" title="Redo (Ctrl+Y)" disabled>↪</button>
|
<button class="pep-redo" title="Redo (Ctrl+Y)" disabled>↪</button>
|
||||||
<button class="pep-save" title="Save (Ctrl+S)" disabled>Save</button>
|
<button class="pep-save" title="Save (Ctrl+S)" disabled>Save</button>
|
||||||
<div class="pep-toolbar-sep"></div>
|
<div class="pep-toolbar-sep"></div>
|
||||||
<button class="pep-mode-btn pep-mode-blocks active" title="Blocks">⊞</button>
|
${ TOOLBAR_BUTTONS }
|
||||||
<button class="pep-mode-btn pep-mode-areas" title="Areas">T</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pep-mode-panel pep-blocks-panel">
|
<div class="pep-block-menu" style="display:none">
|
||||||
|
<button class="pep-block-menu-close" title="Close">✕</button>
|
||||||
<div class="pep-block-list">
|
<div class="pep-block-list">
|
||||||
${ PAGE_BLOCK_REGISTRY.map( b => `
|
${ PAGE_BLOCK_REGISTRY.map( b => `
|
||||||
<div class="pep-block-item" data-block="${ b.name }">
|
<div class="pep-block-item" data-block="${ b.name }">
|
||||||
|
|
@ -230,15 +297,6 @@ class PageEditorPanel extends HTMLElement
|
||||||
` ).join( '' ) }
|
` ).join( '' ) }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pep-mode-panel pep-areas-panel" style="display:none">
|
|
||||||
<button class="pep-fmt-btn" data-tag="b" title="Bold"><b>B</b></button>
|
|
||||||
<button class="pep-fmt-btn" data-tag="i" title="Italic"><i>I</i></button>
|
|
||||||
<button class="pep-fmt-btn" data-tag="u" title="Underline"><u>U</u></button>
|
|
||||||
<div class="pep-fmt-sep"></div>
|
|
||||||
<button class="pep-fmt-btn" data-tag="h1" title="Heading 1">H1</button>
|
|
||||||
<button class="pep-fmt-btn" data-tag="h2" title="Heading 2">H2</button>
|
|
||||||
<button class="pep-fmt-btn" data-tag="h3" title="Heading 3">H3</button>
|
|
||||||
</div>
|
|
||||||
<div class="pep-empty">Open a .page file from the file tree</div>
|
<div class="pep-empty">Open a .page file from the file tree</div>
|
||||||
<iframe class="pep-frame" sandbox="allow-same-origin" style="display:none"></iframe>
|
<iframe class="pep-frame" sandbox="allow-same-origin" style="display:none"></iframe>
|
||||||
`;
|
`;
|
||||||
|
|
@ -249,8 +307,17 @@ class PageEditorPanel extends HTMLElement
|
||||||
this.querySelector( '.pep-save' )!.addEventListener( 'click', () => this._save() );
|
this.querySelector( '.pep-save' )!.addEventListener( 'click', () => this._save() );
|
||||||
this.querySelector( '.pep-undo' )!.addEventListener( 'click', () => this._undo() );
|
this.querySelector( '.pep-undo' )!.addEventListener( 'click', () => this._undo() );
|
||||||
this.querySelector( '.pep-redo' )!.addEventListener( 'click', () => this._redo() );
|
this.querySelector( '.pep-redo' )!.addEventListener( 'click', () => this._redo() );
|
||||||
this.querySelector( '.pep-mode-blocks' )!.addEventListener( 'click', () => this._setMode( 'blocks' ) );
|
|
||||||
this.querySelector( '.pep-mode-areas' )!.addEventListener( 'click', () => this._setMode( 'areas' ) );
|
this.querySelector( '.pep-btn-block' )!.addEventListener( 'click', ( e ) =>
|
||||||
|
{
|
||||||
|
e.stopPropagation();
|
||||||
|
this._blockInsertTarget = null;
|
||||||
|
const rect = ( e.currentTarget as HTMLElement ).getBoundingClientRect();
|
||||||
|
this._openBlockMenu( rect );
|
||||||
|
} );
|
||||||
|
|
||||||
|
this.querySelector( '.pep-block-menu-close' )!.addEventListener( 'click', () => this._closeBlockMenu() );
|
||||||
|
this.querySelector( '.pep-block-menu' )!.addEventListener( 'click', ( e ) => e.stopPropagation() );
|
||||||
|
|
||||||
this.querySelectorAll( '.pep-block-item' ).forEach( item =>
|
this.querySelectorAll( '.pep-block-item' ).forEach( item =>
|
||||||
{
|
{
|
||||||
|
|
@ -259,14 +326,23 @@ class PageEditorPanel extends HTMLElement
|
||||||
const blockName = ( item as HTMLElement ).dataset.block!;
|
const blockName = ( item as HTMLElement ).dataset.block!;
|
||||||
const entry = PAGE_BLOCK_REGISTRY.find( b => b.name === blockName );
|
const entry = PAGE_BLOCK_REGISTRY.find( b => b.name === blockName );
|
||||||
if ( entry ) this._insertBlock( entry.html );
|
if ( entry ) this._insertBlock( entry.html );
|
||||||
|
this._closeBlockMenu();
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
this.querySelectorAll( '.pep-fmt-btn' ).forEach( btn =>
|
this.querySelectorAll( '.pep-fmt-btn' ).forEach( btn =>
|
||||||
|
{
|
||||||
|
// Prevent focus leaving the iframe (which would clear the selection) on click
|
||||||
|
btn.addEventListener( 'mousedown', ( e ) => e.preventDefault() );
|
||||||
|
} );
|
||||||
|
|
||||||
|
this.querySelectorAll( '.pep-fmt-btn[data-tag]' ).forEach( btn =>
|
||||||
{
|
{
|
||||||
btn.addEventListener( 'click', () => this._applyFormat( ( btn as HTMLElement ).dataset.tag! ) );
|
btn.addEventListener( 'click', () => this._applyFormat( ( btn as HTMLElement ).dataset.tag! ) );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
document.addEventListener( 'click', () => this._closeBlockMenu() );
|
||||||
|
|
||||||
Editor.get().onDocumentOpened.addListener( ( e ) =>
|
Editor.get().onDocumentOpened.addListener( ( e ) =>
|
||||||
{
|
{
|
||||||
if ( 'page-editor-panel' !== e.editorTag ) return;
|
if ( 'page-editor-panel' !== e.editorTag ) return;
|
||||||
|
|
@ -288,13 +364,50 @@ class PageEditorPanel extends HTMLElement
|
||||||
if ( this._undoStack.length > 0 ) this._needsRestore = true;
|
if ( this._undoStack.length > 0 ) this._needsRestore = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_setMode( mode: 'blocks' | 'areas' ): void
|
_openBlockMenu( anchorRect: DOMRect ): void
|
||||||
{
|
{
|
||||||
this._mode = mode;
|
const menu = this.querySelector( '.pep-block-menu' ) as HTMLElement;
|
||||||
this.querySelector( '.pep-mode-blocks' )!.classList.toggle( 'active', mode === 'blocks' );
|
const componentRect = this.getBoundingClientRect();
|
||||||
this.querySelector( '.pep-mode-areas' )!.classList.toggle( 'active', mode === 'areas' );
|
|
||||||
( this.querySelector( '.pep-blocks-panel' ) as HTMLElement ).style.display = mode === 'blocks' ? '' : 'none';
|
// Show offscreen first to measure
|
||||||
( this.querySelector( '.pep-areas-panel' ) as HTMLElement ).style.display = mode === 'areas' ? '' : 'none';
|
menu.style.top = '-9999px';
|
||||||
|
menu.style.left = '-9999px';
|
||||||
|
menu.style.display = '';
|
||||||
|
|
||||||
|
const menuHeight = menu.offsetHeight;
|
||||||
|
const menuWidth = menu.offsetWidth;
|
||||||
|
|
||||||
|
let top = anchorRect.bottom - componentRect.top + 4;
|
||||||
|
let left = anchorRect.left - componentRect.left;
|
||||||
|
|
||||||
|
if ( top + menuHeight > componentRect.height - 8 )
|
||||||
|
{
|
||||||
|
top = anchorRect.top - componentRect.top - menuHeight - 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
left = Math.max( 8, Math.min( left, componentRect.width - menuWidth - 8 ) );
|
||||||
|
top = Math.max( 8, top );
|
||||||
|
|
||||||
|
menu.style.top = top + 'px';
|
||||||
|
menu.style.left = left + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
_openBlockMenuNearIframeElement( el: Element ): void
|
||||||
|
{
|
||||||
|
const iframeRect = this._iframe!.getBoundingClientRect();
|
||||||
|
const elRect = el.getBoundingClientRect();
|
||||||
|
const anchorRect = new DOMRect(
|
||||||
|
iframeRect.left + elRect.left,
|
||||||
|
iframeRect.top + elRect.top,
|
||||||
|
elRect.width,
|
||||||
|
elRect.height
|
||||||
|
);
|
||||||
|
this._openBlockMenu( anchorRect );
|
||||||
|
}
|
||||||
|
|
||||||
|
_closeBlockMenu(): void
|
||||||
|
{
|
||||||
|
( this.querySelector( '.pep-block-menu' ) as HTMLElement ).style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateTabLabel( path: string ): void
|
_updateTabLabel( path: string ): void
|
||||||
|
|
@ -307,10 +420,9 @@ class PageEditorPanel extends HTMLElement
|
||||||
{
|
{
|
||||||
this.currentPath = path;
|
this.currentPath = path;
|
||||||
this._updateTabLabel( path );
|
this._updateTabLabel( path );
|
||||||
this.querySelector( '.pep-empty' )!.setAttribute( 'style', 'display:none' );
|
( this.querySelector( '.pep-empty' ) as HTMLElement ).style.display = 'none';
|
||||||
this._iframe!.style.display = '';
|
this._iframe!.style.display = '';
|
||||||
|
|
||||||
// Auto-template: empty files get the standard structure injected and marked dirty
|
|
||||||
if ( ! content.trim() )
|
if ( ! content.trim() )
|
||||||
{
|
{
|
||||||
this._undoStack = [ PAGE_TEMPLATE ];
|
this._undoStack = [ PAGE_TEMPLATE ];
|
||||||
|
|
@ -360,11 +472,86 @@ class PageEditorPanel extends HTMLElement
|
||||||
if ( pageRoot )
|
if ( pageRoot )
|
||||||
{
|
{
|
||||||
this._mutationObserver = new MutationObserver( () => this._onContentChanged() );
|
this._mutationObserver = new MutationObserver( () => this._onContentChanged() );
|
||||||
|
this._refreshIframeOverlays( doc, pageRoot );
|
||||||
this._mutationObserver.observe( pageRoot, { subtree: true, childList: true, characterData: true, attributes: true } );
|
this._mutationObserver.observe( pageRoot, { subtree: true, childList: true, characterData: true, attributes: true } );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_refreshIframeOverlays( doc: Document, pageRoot: Element ): void
|
||||||
|
{
|
||||||
|
this._mutationObserver?.disconnect();
|
||||||
|
|
||||||
|
doc.querySelectorAll( '.pep-insert-trigger, .pep-delete-btn' ).forEach( el => el.remove() );
|
||||||
|
|
||||||
|
const blocks = Array.from( pageRoot.querySelectorAll( ':scope > page-block' ) );
|
||||||
|
|
||||||
|
for ( const block of blocks )
|
||||||
|
{
|
||||||
|
const trigger = doc.createElement( 'div' );
|
||||||
|
trigger.className = 'pep-insert-trigger';
|
||||||
|
pageRoot.insertBefore( trigger, block );
|
||||||
|
trigger.addEventListener( 'mousedown', ( e ) =>
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
this._startDragTracking( e as MouseEvent, block, trigger, doc, pageRoot );
|
||||||
|
} );
|
||||||
|
|
||||||
|
const delBtn = doc.createElement( 'button' );
|
||||||
|
delBtn.className = 'pep-delete-btn';
|
||||||
|
delBtn.textContent = '✕';
|
||||||
|
delBtn.title = 'Delete block';
|
||||||
|
block.appendChild( delBtn );
|
||||||
|
delBtn.addEventListener( 'click', ( e ) =>
|
||||||
|
{
|
||||||
|
e.stopPropagation();
|
||||||
|
this._deleteBlock( block as HTMLElement, doc, pageRoot );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTrigger = doc.createElement( 'div' );
|
||||||
|
endTrigger.className = 'pep-insert-trigger';
|
||||||
|
pageRoot.appendChild( endTrigger );
|
||||||
|
endTrigger.addEventListener( 'mousedown', ( e ) =>
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
this._startDragTracking( e as MouseEvent, null, endTrigger, doc, pageRoot );
|
||||||
|
} );
|
||||||
|
|
||||||
|
this._iframeTriggers = Array.from( pageRoot.querySelectorAll( '.pep-insert-trigger' ) );
|
||||||
|
|
||||||
|
// Portrait block-tap: event delegation on pageRoot
|
||||||
|
if ( this._pageRootClickHandler )
|
||||||
|
{
|
||||||
|
pageRoot.removeEventListener( 'click', this._pageRootClickHandler );
|
||||||
|
}
|
||||||
|
this._pageRootClickHandler = ( e: Event ) =>
|
||||||
|
{
|
||||||
|
const block = ( e.target as Element ).closest( 'page-block' );
|
||||||
|
if ( ! block ) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
doc.querySelectorAll( 'page-block.pep-block-active' ).forEach( b => b.classList.remove( 'pep-block-active' ) );
|
||||||
|
block.classList.add( 'pep-block-active' );
|
||||||
|
};
|
||||||
|
pageRoot.addEventListener( 'click', this._pageRootClickHandler );
|
||||||
|
|
||||||
|
// Tap outside all blocks dismisses active selection
|
||||||
|
if ( this._bodyClickHandler )
|
||||||
|
{
|
||||||
|
doc.body.removeEventListener( 'click', this._bodyClickHandler );
|
||||||
|
}
|
||||||
|
this._bodyClickHandler = () =>
|
||||||
|
{
|
||||||
|
doc.querySelectorAll( 'page-block.pep-block-active' ).forEach( b => b.classList.remove( 'pep-block-active' ) );
|
||||||
|
};
|
||||||
|
doc.body.addEventListener( 'click', this._bodyClickHandler );
|
||||||
|
|
||||||
|
if ( this._mutationObserver )
|
||||||
|
{
|
||||||
|
this._mutationObserver.observe( pageRoot, { subtree: true, childList: true, characterData: true, attributes: true } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_injectEditorStyles( doc: Document ): void
|
_injectEditorStyles( doc: Document ): void
|
||||||
{
|
{
|
||||||
const existing = doc.getElementById( 'pep-editor-injected' );
|
const existing = doc.getElementById( 'pep-editor-injected' );
|
||||||
|
|
@ -378,11 +565,11 @@ class PageEditorPanel extends HTMLElement
|
||||||
_captureHtml(): string
|
_captureHtml(): string
|
||||||
{
|
{
|
||||||
const doc = this._iframe!.contentDocument!;
|
const doc = this._iframe!.contentDocument!;
|
||||||
const injected = doc.getElementById( 'pep-editor-injected' );
|
const clone = doc.cloneNode( true ) as Document;
|
||||||
if ( injected ) injected.remove();
|
clone.getElementById( 'pep-editor-injected' )?.remove();
|
||||||
const html = '<!DOCTYPE html>\n' + doc.documentElement.outerHTML;
|
clone.querySelectorAll( '.pep-insert-trigger, .pep-delete-btn' ).forEach( el => el.remove() );
|
||||||
if ( injected ) doc.head.appendChild( injected );
|
clone.querySelectorAll( '.pep-block-active' ).forEach( el => el.classList.remove( 'pep-block-active' ) );
|
||||||
return html;
|
return '<!DOCTYPE html>\n' + clone.documentElement.outerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onContentChanged(): void
|
_onContentChanged(): void
|
||||||
|
|
@ -414,7 +601,15 @@ class PageEditorPanel extends HTMLElement
|
||||||
while ( temp.firstChild )
|
while ( temp.firstChild )
|
||||||
{
|
{
|
||||||
const child = temp.firstChild;
|
const child = temp.firstChild;
|
||||||
|
|
||||||
|
if ( this._blockInsertTarget )
|
||||||
|
{
|
||||||
|
pageRoot.insertBefore( child, this._blockInsertTarget );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
pageRoot.appendChild( child );
|
pageRoot.appendChild( child );
|
||||||
|
}
|
||||||
|
|
||||||
if ( child.nodeType === Node.ELEMENT_NODE )
|
if ( child.nodeType === Node.ELEMENT_NODE )
|
||||||
{
|
{
|
||||||
|
|
@ -425,9 +620,140 @@ class PageEditorPanel extends HTMLElement
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._onContentChanged();
|
||||||
|
this._refreshIframeOverlays( doc, pageRoot );
|
||||||
}
|
}
|
||||||
|
|
||||||
_applyFormat( tagName: string, attributes?: Record<string, string> ): void
|
_deleteBlock( block: HTMLElement, doc: Document, pageRoot: Element ): void
|
||||||
|
{
|
||||||
|
block.remove();
|
||||||
|
this._onContentChanged();
|
||||||
|
this._refreshIframeOverlays( doc, pageRoot );
|
||||||
|
}
|
||||||
|
|
||||||
|
_startDragTracking( e: MouseEvent, draggedBlock: Element | null, trigger: Element, doc: Document, pageRoot: Element ): void
|
||||||
|
{
|
||||||
|
const startX = e.clientX;
|
||||||
|
const startY = e.clientY;
|
||||||
|
let dragging = false;
|
||||||
|
|
||||||
|
const onMove = ( me: MouseEvent ) =>
|
||||||
|
{
|
||||||
|
if ( ! dragging )
|
||||||
|
{
|
||||||
|
if ( Math.hypot( me.clientX - startX, me.clientY - startY ) <= 4 ) return;
|
||||||
|
if ( draggedBlock === null ) return; // end trigger — click only
|
||||||
|
dragging = true;
|
||||||
|
this._draggedBlock = draggedBlock;
|
||||||
|
this._createDragBar();
|
||||||
|
}
|
||||||
|
this._updateDragVisual( me );
|
||||||
|
};
|
||||||
|
|
||||||
|
const onUp = () =>
|
||||||
|
{
|
||||||
|
doc.removeEventListener( 'mousemove', onMove );
|
||||||
|
doc.removeEventListener( 'mouseup', onUp );
|
||||||
|
document.removeEventListener( 'mouseup', onUp );
|
||||||
|
|
||||||
|
if ( dragging )
|
||||||
|
{
|
||||||
|
this._endDrag( doc, pageRoot );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._blockInsertTarget = draggedBlock;
|
||||||
|
this._openBlockMenuNearIframeElement( trigger );
|
||||||
|
}
|
||||||
|
dragging = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
doc.addEventListener( 'mousemove', onMove );
|
||||||
|
doc.addEventListener( 'mouseup', onUp );
|
||||||
|
document.addEventListener( 'mouseup', onUp );
|
||||||
|
}
|
||||||
|
|
||||||
|
_createDragBar(): void
|
||||||
|
{
|
||||||
|
if ( this._dragBar ) this._dragBar.remove();
|
||||||
|
const bar = document.createElement( 'div' );
|
||||||
|
bar.className = 'pep-drag-bar';
|
||||||
|
this.appendChild( bar );
|
||||||
|
this._dragBar = bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateDragVisual( me: MouseEvent ): void
|
||||||
|
{
|
||||||
|
let nearest: Element | null = null;
|
||||||
|
let minDist = Infinity;
|
||||||
|
|
||||||
|
for ( const t of this._iframeTriggers )
|
||||||
|
{
|
||||||
|
const rect = t.getBoundingClientRect();
|
||||||
|
const dist = Math.abs( me.clientY - ( rect.top + rect.height / 2 ) );
|
||||||
|
if ( dist < minDist ) { minDist = dist; nearest = t; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this._dropTrigger !== nearest )
|
||||||
|
{
|
||||||
|
this._dropTrigger?.classList.remove( 'pep-drop-target' );
|
||||||
|
this._dropTrigger = nearest;
|
||||||
|
nearest?.classList.add( 'pep-drop-target' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this._dragBar && nearest )
|
||||||
|
{
|
||||||
|
const iframeRect = this._iframe!.getBoundingClientRect();
|
||||||
|
const componentRect = this.getBoundingClientRect();
|
||||||
|
const triggerRect = nearest.getBoundingClientRect();
|
||||||
|
const barTop = ( iframeRect.top - componentRect.top ) + triggerRect.top + triggerRect.height / 2 - 1.5;
|
||||||
|
this._dragBar.style.top = barTop + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_endDrag( doc: Document, pageRoot: Element ): void
|
||||||
|
{
|
||||||
|
const draggedBlock = this._draggedBlock;
|
||||||
|
const dropTrigger = this._dropTrigger;
|
||||||
|
this._clearDragVisual();
|
||||||
|
|
||||||
|
if ( draggedBlock && dropTrigger )
|
||||||
|
{
|
||||||
|
this._reorderBlock( draggedBlock, dropTrigger, pageRoot, doc );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearDragVisual(): void
|
||||||
|
{
|
||||||
|
this._dragBar?.remove();
|
||||||
|
this._dragBar = null;
|
||||||
|
this._draggedBlock = null;
|
||||||
|
this._dropTrigger?.classList.remove( 'pep-drop-target' );
|
||||||
|
this._dropTrigger = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_reorderBlock( draggedBlock: Element, dropTrigger: Element, pageRoot: Element, doc: Document ): void
|
||||||
|
{
|
||||||
|
const insertBefore = dropTrigger.nextElementSibling;
|
||||||
|
|
||||||
|
if ( insertBefore === draggedBlock ) return; // dropping immediately before itself
|
||||||
|
if ( draggedBlock.nextElementSibling === dropTrigger ) return; // dropping immediately after itself
|
||||||
|
|
||||||
|
if ( insertBefore )
|
||||||
|
{
|
||||||
|
pageRoot.insertBefore( draggedBlock, insertBefore );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pageRoot.appendChild( draggedBlock );
|
||||||
|
}
|
||||||
|
|
||||||
|
this._onContentChanged();
|
||||||
|
this._refreshIframeOverlays( doc, pageRoot );
|
||||||
|
}
|
||||||
|
|
||||||
|
async _applyFormat( tagName: string ): Promise<void>
|
||||||
{
|
{
|
||||||
const iframeWin = this._iframe?.contentWindow;
|
const iframeWin = this._iframe?.contentWindow;
|
||||||
if ( ! iframeWin ) return;
|
if ( ! iframeWin ) return;
|
||||||
|
|
@ -435,7 +761,19 @@ class PageEditorPanel extends HTMLElement
|
||||||
if ( ! sel || sel.rangeCount === 0 ) return;
|
if ( ! sel || sel.rangeCount === 0 ) return;
|
||||||
const range = sel.getRangeAt( 0 );
|
const range = sel.getRangeAt( 0 );
|
||||||
if ( range.collapsed ) return;
|
if ( range.collapsed ) return;
|
||||||
wrapSelection( this._iframe!.contentDocument!, range, tagName, attributes );
|
const doc = this._iframe!.contentDocument!;
|
||||||
|
|
||||||
|
if ( tagName === 'link' )
|
||||||
|
{
|
||||||
|
const selectedText = sel.toString().trim();
|
||||||
|
const defaultUrl = /^https?:\/\//.test( selectedText ) ? selectedText : '';
|
||||||
|
const url = await showInputDialog( { icon: '🔗', title: 'Create Link', label: 'URL', defaultValue: defaultUrl } );
|
||||||
|
if ( ! url ) return;
|
||||||
|
wrapSelection( doc, range, 'a', { href: url } );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapSelection( doc, range, tagName );
|
||||||
}
|
}
|
||||||
|
|
||||||
_undo(): void
|
_undo(): void
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
<header>
|
<header>
|
||||||
<p class="date">Friday, 31 July 2026</p>
|
<p class="date">Friday, 31 July 2026</p>
|
||||||
<h1>Session History</h1>
|
<h1>Session History</h1>
|
||||||
<p class="subtitle">File tree UX improvements, page editor toolbar, EditorConsole message system, console-panel tab.</p>
|
<p class="subtitle">File tree UX improvements, page editor full redesign (unified toolbar, floating block menu, insertion triggers, drag-to-reorder), InputDialog component, EditorConsole, console-panel tab.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|
@ -113,6 +113,96 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>Page editor: unified toolbar with icon + label buttons</h3>
|
||||||
|
<p>
|
||||||
|
The two-mode system (Blocks / Areas toggle) was removed. All formatting and block
|
||||||
|
actions now live in a single toolbar row:
|
||||||
|
<code>[ BLOCK ] | [ H1 ] [ H2 ] [ H3 ] | [ LINK ] [ MARK ] | [ BOLD ] [ ITALIC ] [ UNDER ]</code>.
|
||||||
|
Each button shows a 24×24 SVG icon on top and a short uppercase label (max 6 characters,
|
||||||
|
~10 px) below. SVG placeholders are in
|
||||||
|
<code>source/components/page-editor-panel/icons/</code>.
|
||||||
|
<code>mousedown → preventDefault</code> on all format buttons preserves the iframe
|
||||||
|
contenteditable selection when clicking the toolbar.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>Page editor: floating block menu</h3>
|
||||||
|
<p>
|
||||||
|
Clicking the BLOCK button opens a floating overlay (<code>.pep-block-menu</code>)
|
||||||
|
positioned absolutely within the component. Default placement: below the anchor;
|
||||||
|
flips above when too close to the bottom edge. An ✕ button closes the menu;
|
||||||
|
clicking outside also closes it. The menu is shown offscreen first to measure its
|
||||||
|
height before final positioning.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>Page editor: block insertion triggers and drag-to-reorder</h3>
|
||||||
|
<p>
|
||||||
|
Thin <code>.pep-insert-trigger</code> overlays (10 px click area, 5 px visible via
|
||||||
|
<code>::before</code>) are injected before every <code>page-block</code> and after
|
||||||
|
the last one. Interaction is split on <code>mousedown</code>:
|
||||||
|
</p>
|
||||||
|
<ul style="margin-top:0.5rem;line-height:1.9">
|
||||||
|
<li><strong>Click (no movement)</strong> — opens the block menu to insert before
|
||||||
|
that block (or append for the end trigger).</li>
|
||||||
|
<li><strong>Drag (> 4 px movement)</strong> — a snapping bar
|
||||||
|
(<code>.pep-drag-bar</code>) appears in the outer component and follows the cursor,
|
||||||
|
snapping to the nearest trigger's Y position. The nearest trigger gets
|
||||||
|
<code>.pep-drop-target</code> (brighter highlight). On <code>mouseup</code> the
|
||||||
|
dragged block is moved to the drop position. No-op guards prevent moving a block
|
||||||
|
before or immediately after itself.</li>
|
||||||
|
</ul>
|
||||||
|
<p style="margin-top:0.75rem">
|
||||||
|
The end trigger is click-only — no block follows it, so drag is a no-op there.
|
||||||
|
A fallback <code>mouseup</code> on the outer <code>document</code> handles release
|
||||||
|
outside the iframe.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>Page editor: block deletion</h3>
|
||||||
|
<p>
|
||||||
|
A <code>.pep-delete-btn</code> (✕) is injected top-right inside every
|
||||||
|
<code>page-block</code>. On landscape (<code>@media (hover: hover)</code>) it appears
|
||||||
|
on block hover via CSS. On portrait, tapping anywhere on a block (no contenteditable
|
||||||
|
required) adds <code>.pep-block-active</code> via event delegation on
|
||||||
|
<code>page-root</code>; all other blocks lose it. Tapping in another block or outside
|
||||||
|
all blocks (body listener) dismisses the active state. The delete button removes the
|
||||||
|
block, records an undo entry, then rebuilds overlays.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>Page editor: LINK and MARK formats, styled custom elements</h3>
|
||||||
|
<p>
|
||||||
|
Two new toolbar actions: <strong>LINK</strong> wraps the selection in
|
||||||
|
<code><a href="..."></code> after prompting for a URL (selected text is used
|
||||||
|
as the default if it starts with <code>https?://</code>).
|
||||||
|
<strong>MARK</strong> wraps in <code><marked-text></code>.
|
||||||
|
</p>
|
||||||
|
<p style="margin-top:0.75rem">
|
||||||
|
CSS for both is injected into the iframe and saved in the page file:
|
||||||
|
<code>marked-text</code> — bold, <code>hsl(190, 80%, 90%)</code>;
|
||||||
|
<code>a</code> — <code>hsl(200, 80%, 70%)</code>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>InputDialog component</h3>
|
||||||
|
<p>
|
||||||
|
New <code>InputDialog</code> class added to <code>confirm-dialog.ts</code>, following
|
||||||
|
the same visual pattern as <code>ConfirmDialog</code> (backdrop, titlebar with icon /
|
||||||
|
title / ✕, body, footer). Body contains a label and a full-width text input.
|
||||||
|
Enter submits, Escape cancels. Returns <code>Promise<string | null></code>.
|
||||||
|
Exported as <code>showInputDialog(options)</code>. The
|
||||||
|
<code>confirm-dialog, input-dialog</code> CSS selector gives both dialogs
|
||||||
|
<code>position: fixed; inset: 0</code> centering.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>Bug fix: <code>openDocumentIn</code> ignored the target panel type</h3>
|
<h3>Bug fix: <code>openDocumentIn</code> ignored the target panel type</h3>
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -157,6 +247,37 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<p>
|
||||||
|
<strong><code>document.cloneNode(true)</code> for HTML capture.</strong>
|
||||||
|
Previously, <code>_captureHtml()</code> temporarily removed injected overlay elements
|
||||||
|
from the live DOM, serialised, then re-added them — causing flickering and complexity.
|
||||||
|
Cloning the document first and stripping overlays from the clone leaves the live DOM
|
||||||
|
untouched entirely.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<p>
|
||||||
|
<strong>Manual <code>_onContentChanged()</code> before <code>_refreshIframeOverlays()</code>
|
||||||
|
in insert and delete.</strong>
|
||||||
|
Calling <code>MutationObserver.disconnect()</code> inside <code>_refreshIframeOverlays</code>
|
||||||
|
discards any pending (not yet delivered) mutation callbacks — meaning a block insert or
|
||||||
|
delete would not be recorded in the undo stack. The fix is to call
|
||||||
|
<code>_onContentChanged()</code> synchronously first, then refresh overlays.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<p>
|
||||||
|
<strong>Event delegation on <code>page-root</code> for portrait block-tap.</strong>
|
||||||
|
Attaching per-block click listeners in <code>_refreshIframeOverlays</code> would stack
|
||||||
|
duplicate listeners across repeated refresh calls (blocks are not replaced, only
|
||||||
|
triggers and delete buttons are). A single delegated listener on <code>page-root</code>
|
||||||
|
— removed and re-added each refresh — avoids the accumulation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"><head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>New Page</title>
|
||||||
|
<style>
|
||||||
|
@import url('https://styles.rokojori.com/get-font?family=Barlow&weights=100,400,700,900');
|
||||||
|
|
||||||
|
[data-theme="default-roject"] {
|
||||||
|
background: #0f1117;
|
||||||
|
color: #c8cce0;
|
||||||
|
font-family: 'Barlow', sans-serif;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="default-roject"] h1 {
|
||||||
|
color: #7c8cff;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: italic;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 1.5rem 0 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="default-roject"] h2 {
|
||||||
|
color: #7c8cff;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 1.25rem 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="default-roject"] h3 {
|
||||||
|
color: #9ba4c7;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 1rem 0 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="default-roject"] p {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="default-roject"] b,
|
||||||
|
[data-theme="default-roject"] strong {
|
||||||
|
color: #e2e4ed;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body data-theme="default-roject">
|
||||||
|
<page-header></page-header>
|
||||||
|
|
||||||
|
<page-root data-theme="default-roject">
|
||||||
|
<page-block class="pep-block-full">
|
||||||
|
<page-area contenteditable="true" style="outline: none;"><h1>Page Editor Update</h1><div>Restructuring the page editor</div></page-area>
|
||||||
|
</page-block>
|
||||||
|
<page-block class="pep-block-full">
|
||||||
|
<page-area contenteditable="true" style="outline: none;"><h2>Toolbar Update</h2><div>The mode toggle is removed. <marked-text>Block</marked-text> insertion and text formatting share one unified toolbar.</div><div>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.</div><div>The block insertion button (BLOCK) is the first button in the toolbar.</div><div>SVG icon placeholders are at source/components/page-editor-panel/icons/</div><div>A link: <a href="https://rokojori.com">https://rokojori.com</a></div><div><br></div></page-area>
|
||||||
|
</page-block><page-block class="pep-block-full">
|
||||||
|
<page-area contenteditable="true" style="outline: none;"><h2>Block Element Insertion UI</h2><div>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).</div><div><br></div></page-area>
|
||||||
|
</page-block><page-block class="pep-block-full">
|
||||||
|
<page-area contenteditable="true" style="outline: none;"><h2>Block Element Insertion Triggers</h2><div>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. </div><div><br></div></page-area>
|
||||||
|
</page-block><page-block class="pep-block-full">
|
||||||
|
<page-area contenteditable="true" style="outline: none;"><h2>Block Deletion</h2><div>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.</div><div><br></div></page-area>
|
||||||
|
</page-block><page-block class="pep-block-full">
|
||||||
|
<page-area contenteditable="true" style="outline: none;"><h2>Toolbar Options</h2><div><b>Add new options:</b></div><div>- 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.</div><div>- 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.</div><div><br></div><div><b>Reorder the options:</b></div><div>[ BLOCK ] | [ H1 ] [ H2 ] [ H3 ] | [ LINK ] [ MARK ] | [ BOLD ] [ ITALIC ] [ UNDER ]</div></page-area>
|
||||||
|
</page-block></page-root>
|
||||||
|
|
||||||
|
<page-footer></page-footer>
|
||||||
|
|
||||||
|
</body></html>
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3><a href="2026/07-July/31-Friday/index.html">Friday, 31 July 2026</a></h3>
|
<h3><a href="2026/07-July/31-Friday/index.html">Friday, 31 July 2026</a></h3>
|
||||||
<p>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.</p>
|
<p>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.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|
|
||||||