fix editor layout

This commit is contained in:
evilchili 2026-01-30 14:59:00 -08:00
parent 2d07a5c5f6
commit d28b31ed14
9 changed files with 321 additions and 39 deletions

View File

@ -13,14 +13,17 @@
<input name='title' id="data_form__title" type='text' value="{{ page.title }}">
<textarea name="body" id="data_form__body">{{ page.body }}</textarea>
</form>
<div id='masthead'>
{% block nav %}
{% include "nav.html" %}
{% include "breadcrumbs.html" %}
{% endblock %}
{% if user.can_write(page) %}
{% include "toolbar.html" %}
{% endif %}
</div>
<div class='main-aligned'>
<div class='content'>
<main>
<main id='main'>
{% for message in g.messages %}
<dialog class="alert">
{{ message }}
@ -29,7 +32,6 @@
{% block content %}{% endblock %}
</main>
</div>
</div>
<footer>
{% block footer %}
fnord

View File

@ -1,4 +1,4 @@
<nav>
<nav id='pagenav'>
<ul class="container content-aligned">
<li><a href='{{ root.uri }}'>Home</a></li>
{% for subpage in root.members %}

View File

@ -1,6 +1,9 @@
{% extends "base.html" %}
{% block styles %}
{% if user.can_write(page) %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='froghat-editor.css' ) }}">
{% endif %}
{% endblock %}
{% block content %}

View File

@ -0,0 +1,139 @@
:root {
--toolbar-background: transparent;
--toolbar-border-radius: var(--content-border-radius);
--toolbar-enabled-background: var(--content-background);
--toolbar-height: 32px !important;
--toolbar-spacing: 5px !important;
--toolbar-button-size: 24px;
--toolbar-icon-size: 16px;
--toolbar-button-enabled-background: rgba(128, 192, 128);
--toolbar-button-active-background: rgba(192, 255, 192);
--toolbar-button-enabled-border: 1px solid #000;
/* Icons by Flaticon: https://www.flaticon.com/uicons */
--toolbar-icon-bold: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path d="M17.954,10.663A6.986,6.986,0,0,0,12,0H5A2,2,0,0,0,3,2V22a2,2,0,0,0,2,2H15a6.994,6.994,0,0,0,2.954-13.337ZM7,4h5a3,3,0,0,1,0,6H7Zm8,16H7V14h8a3,3,0,0,1,0,6Z"/></svg>');
--toolbar-icon-italic: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path d="M20,0H7A1,1,0,0,0,7,2h5.354L9.627,22H4a1,1,0,0,0,0,2H17a1,1,0,0,0,0-2H11.646L14.373,2H20a1,1,0,0,0,0-2Z"/></svg>');
--toolbar-icon-underline: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path d="M12,20a8.009,8.009,0,0,0,8-8V1a1,1,0,0,0-2,0V12A6,6,0,0,1,6,12V1A1,1,0,0,0,4,1V12A8.009,8.009,0,0,0,12,20Z"/><path d="M23,22H1a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/></svg>');
--toolbar-icon-bullet_list: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path d="M7,6H23a1,1,0,0,0,0-2H7A1,1,0,0,0,7,6Z"/><path d="M23,11H7a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M23,18H7a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/><circle cx="2" cy="5" r="2"/><circle cx="2" cy="12" r="2"/><circle cx="2" cy="19" r="2"/></svg>');
--toolbar-icon-center: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path d="M1,6H23a1,1,0,0,0,0-2H1A1,1,0,0,0,1,6Z"/><path d="M5,9a1,1,0,0,0,0,2H19a1,1,0,0,0,0-2Z"/><path d="M19,19H5a1,1,0,0,0,0,2H19a1,1,0,0,0,0-2Z"/><path d="M23,14H1a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/></svg>');
--toolbar-icon-toggle: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path d="m22.81,9.08c.807-.642,1.25-1.642,1.183-2.676-.067-1.05-.646-2.001-1.547-2.546-.772-.466-1.946-.892-3.574-1.297-.411-1.476-1.767-2.562-3.372-2.562-1.493,0-2.77.94-3.272,2.259C3.12,6.628.56,12.888.025,17.398c-.195,1.646.332,3.311,1.448,4.567,1.149,1.293,2.799,2.035,4.527,2.035h9c.552,0,1-.448,1-1s-.448-1-1-1h-2v-3c0-1.428.193-3.121.398-4.513l5.464,8.587c.369.579,1,.925,1.687.925h1.451c.552,0,1-.448,1-1s-.448-1-1-1h-1.451s-6.342-9.966-6.342-9.966c.116-.058.245-.095.383-.108,3.276-.294,6.349-1.358,8.22-2.846Zm-7.31-7.08c.827,0,1.5.673,1.5,1.5s-.673,1.5-1.5,1.5-1.5-.673-1.5-1.5.673-1.5,1.5-1.5Zm-3.783,10.367c-.143.797-.297,1.747-.426,2.745-1.189-1.297-2.897-2.112-4.791-2.112h-.5c-.552,0-1,.448-1,1s.448,1,1,1h.5c2.481,0,4.5,2.019,4.5,4.5v2.5h-5c-1.157,0-2.263-.497-3.032-1.363-.747-.841-1.087-1.908-.957-3.003.647-5.452,4.145-9.959,10.144-13.101.442,1.427,1.774,2.467,3.344,2.467,1.541,0,2.853-1.001,3.319-2.387,1.191.315,2.061.636,2.594.958.346.209.56.56.585.962.025.386-.133.744-.433.982-1.857,1.477-4.913,2.218-7.154,2.419-1.351.122-2.459,1.123-2.694,2.433Z" /></svg>');
--toolbar-icon-wysiwyg: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path d="m18 9.064a3.049 3.049 0 0 0 -.9-2.164 3.139 3.139 0 0 0 -4.334 0l-11.866 11.869a3.064 3.064 0 0 0 4.33 4.331l11.87-11.869a3.047 3.047 0 0 0 .9-2.167zm-14.184 12.624a1.087 1.087 0 0 1 -1.5 0 1.062 1.062 0 0 1 0-1.5l7.769-7.77 1.505 1.505zm11.872-11.872-2.688 2.689-1.5-1.505 2.689-2.688a1.063 1.063 0 1 1 1.5 1.5zm-10.825-6.961 1.55-.442.442-1.55a1.191 1.191 0 0 1 2.29 0l.442 1.55 1.55.442a1.191 1.191 0 0 1 0 2.29l-1.55.442-.442 1.55a1.191 1.191 0 0 1 -2.29 0l-.442-1.55-1.55-.442a1.191 1.191 0 0 1 0-2.29zm18.274 14.29-1.55.442-.442 1.55a1.191 1.191 0 0 1 -2.29 0l-.442-1.55-1.55-.442a1.191 1.191 0 0 1 0-2.29l1.55-.442.442-1.55a1.191 1.191 0 0 1 2.29 0l.442 1.55 1.55.442a1.191 1.191 0 0 1 0 2.29zm-5.382-14.645 1.356-.387.389-1.358a1.042 1.042 0 0 1 2 0l.387 1.356 1.356.387a1.042 1.042 0 0 1 0 2l-1.356.387-.387 1.359a1.042 1.042 0 0 1 -2 0l-.387-1.355-1.358-.389a1.042 1.042 0 0 1 0-2z"/></svg>');
--toolbar-icon-save: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill-rule="evenodd" clip-rule="evenodd" d="M18.1716 1C18.702 1 19.2107 1.21071 19.5858 1.58579L22.4142 4.41421C22.7893 4.78929 23 5.29799 23 5.82843V20C23 21.6569 21.6569 23 20 23H4C2.34315 23 1 21.6569 1 20V4C1 2.34315 2.34315 1 4 1H18.1716ZM4 3C3.44772 3 3 3.44772 3 4V20C3 20.5523 3.44772 21 4 21L5 21L5 15C5 13.3431 6.34315 12 8 12L16 12C17.6569 12 19 13.3431 19 15V21H20C20.5523 21 21 20.5523 21 20V6.82843C21 6.29799 20.7893 5.78929 20.4142 5.41421L18.5858 3.58579C18.2107 3.21071 17.702 3 17.1716 3H17V5C17 6.65685 15.6569 8 14 8H10C8.34315 8 7 6.65685 7 5V3H4ZM17 21V15C17 14.4477 16.5523 14 16 14L8 14C7.44772 14 7 14.4477 7 15L7 21L17 21ZM9 3H15V5C15 5.55228 14.5523 6 14 6H10C9.44772 6 9 5.55228 9 5V3Z" /></svg>');
}
#froghat[contenteditable] {
outline: 0px solid transparent;
}
#froghat {
box-sizing: border-box;
height: -webkit-fill-available;
}
#froghat.edit {
font-family: monospace;
white-space: break-spaces;
}
#froghat.wysiwyg {
}
main.editing {
border: 1px solid green;
border-top: 1px solid transparent;
border-radius: 0px;
}
#toolbar {
background: var(--toolbar-background);
border-radius: var(--toolbar-border-radius);
height: var(--toolbar-height);
border: 1px solid transparent;
margin: 0;
margin-bottom: 0px;
padding: var(--toolbar-spacing);
ul {
display: flex;
margin: 0px;
padding: 0px;
border-radius: 5px;
li {
padding: 0px;
padding-right: 5px;
margin: 0px;
text-align: center;
line-height: var(--toolbar-button-size);
width: var(--toolbar-button-size);
height: var(--toolbar-button-size);
a {
padding: var(--toolbar-spacing);
opacity: 0.3;
display: block;
width: var(--toolbar-icon-size);
height: var(--toolbar-icon-size);
background-repeat: no-repeat;
background-attachment: local;
background-position: center;
border-radius: 5px;
border: 1px solid transparent;
}
#wysiwyg { background-image: var(--toolbar-icon-wysiwyg); }
#bold { background-image: var(--toolbar-icon-bold); }
#italic { background-image: var(--toolbar-icon-italic); }
#underline { background-image: var(--toolbar-icon-underline); }
#bullet_list { background-image: var(--toolbar-icon-bullet_list); }
#center { background-image: var(--toolbar-icon-center); }
#toggle {
background-color: var(--toolbar-button-enabled-background);
background-image: var(--toolbar-icon-toggle);
-moz-transform: scaleX(-1);
-o-transform: scaleX(-1);
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
filter: FlipH;
-ms-filter: "FlipH";
opacity: 1.0;
cursor: pointer;
}
#toggle:hover {
border: var(--toolbar-button-enabled-border);
background-color: var(--toolbar-button-active-background);
}
#save { background-image: var(--toolbar-icon-save); }
}
li:last-child {
display: block;
margin-left: auto;
}
}
}
#toolbar.enabled {
background: var(--toolbar-enabled-background);
border-radius: 0px;
border: 1px solid green;
border-bottom: 1px solid transparent;
ul {
li {
a {
opacity: 1.0;
cursor: pointer;
}
a.on {
border: var(--toolbar-button-enabled-border);
background-color: var(--toolbar-button-enabled-background);
}
a:hover {
border: var(--toolbar-button-enabled-border);
background-color: var(--toolbar-button-active-background);
}
}
}
}

View File

@ -1,3 +1,96 @@
class ToolbarButton {
constructor(settings) {
this.id = settings.id;
this.element = settings.element || document.getElementById(this.id);
this.toolbar = settings.toolbar;
this.onclick = settings.onclick;
this.element.addEventListener('click', (e) => {
if (this.element.enabled) {
this.onclick({clickEvent: e, button: this });
}
});
if (settings.enabled) {
this.enable();
}
}
enable() {
this.element.enabled = true;
}
disable() {
this.element.enabled = false;
}
on() {
this.element.classList.add('on');
}
off() {
this.element.classList.remove('on');
}
toggle() {
this.element.classList.toggle('on');
}
}
class FroghatToolbar {
constructor(settings) {
this.editor = settings.editor;
this.element = settings.toolbar || document.getElementById('toolbar');
this.buttons = {
'bold': new ToolbarButton({ toolbar: this, id: 'bold', onclick: this.#click_bold }),
'italic': new ToolbarButton({ toolbar: this, id: 'italic', onclick: this.#click_italic }),
'underline': new ToolbarButton({ toolbar: this, id: 'underline', onclick: this.#click_underline }),
'bullet_list': new ToolbarButton({ toolbar: this, id: 'bullet_list', onclick: this.#click_bullet_list }),
'center': new ToolbarButton({ toolbar: this, id: 'center', onclick: this.#click_center }),
'save': new ToolbarButton({ toolbar: this, id: 'save', onclick: this.#click_save }),
'wysiwyg': new ToolbarButton({ toolbar: this, id: 'wysiwyg', onclick: this.#click_wysiwyg, enabled: true }),
'toggle': new ToolbarButton({ toolbar: this, id: 'toggle', onclick: this.#click_toggle, enabled: true })
}
}
#click_bold({clickEvent, button}) {
}
#click_italic({clickEvent, button}) {
}
#click_underline({clickEvent, button}) {
}
#click_bullet_list({clickEvent, button}) {
}
#click_center({clickEvent, button}) {
}
#click_save({clickEvent, button}) {
}
#click_wysiwyg({clickEvent, button}) {
button.toolbar.editor.toggleWysiwyg();
}
#click_toggle({clickEvent, button}) {
button.toolbar.editor.toggleView();
}
enable() {
button.toolbar.element.classList.add("enabled");
button.toolbar.buttons.forEach(button => { button.enable() });
}
disable() {
button.toolbar.element.classList.remove("enabled");
button.toolbar.buttons.forEach(button => { button.disable() });
button.toolbar.buttons.toggle.enable();
}
}
class FroghatEditor extends Froghat {
run() {
@ -7,6 +100,8 @@ class FroghatEditor extends Froghat {
WYSIWYG: 'wysiwyg'
}
this.toolbar = new FroghatToolbar({editor: this});
this.turndown = new TurndownService({
headingStyle: 'atx',
codeBlockStyle: 'fenced',
@ -32,6 +127,25 @@ class FroghatEditor extends Froghat {
});
};
toggleWysiwyg() {
if (this.getState() === this.states.EDIT) {
this.wysiwyg();
} else {
this.edit();
}
}
toggleView() {
this.toolbar.element.classList.toggle("enabled");
if (this.getState() === this.states.VIEW) {
this.wysiwyg();
this.element.focus();
} else {
this.view();
}
}
htmlToMarkdown(html) {
return this.turndown.turndown(html || this.element.innerHTML);
}
@ -60,7 +174,6 @@ class FroghatEditor extends Froghat {
if (this.getState() === this.states.WYSIWYG) {
return;
}
this.changed = false;
this.element.contentEditable = true;
this.element.innerHTML = this.getHTML();
Array.from(this.element.querySelectorAll('.macro')).forEach(el => {
@ -70,6 +183,8 @@ class FroghatEditor extends Froghat {
}
});
this.setState(this.states.WYSIWYG);
this.toolbar.buttons.wysiwyg.on();
document.getElementById("main").classList.add("editing");
}
edit() {
@ -79,10 +194,11 @@ class FroghatEditor extends Froghat {
if (this.state === this.states.EDIT) {
return;
}
this.changed = false;
this.element.contentEditable = true;
this.element.innerHTML = encodeHtmlEntities(this.getMarkdown());
this.setState(this.states.EDIT);
this.toolbar.buttons.wysiwyg.off();
document.getElementById("main").classList.add("editing");
}
insertAtCursor(node) {

View File

@ -10,7 +10,7 @@ body {
font-family: var(--default-font-family);
font-size: var(--default-font-size);
background: var(--body-background);
height: inherit;
height: auto;
width: 100%;
margin: 0px;
box-sizing: border-box;
@ -125,12 +125,21 @@ nav ul.container {
margin: auto;
}
#masthead {
background: var(--body-background);
padding-bottom: var(--masthead-spacing);
display: block;
height: fit-content;
position:sticky;
top: 0px;
z-index: 100;
}
nav {
display: block;
margin: 0px;
padding: 0px;
height: var(--nav-height);
background: var(--blue);
}
nav > ul > li {
@ -144,13 +153,21 @@ nav > ul > li:first-child {
}
nav > ul > li > a {
color: var(--nav-color);
display: inline-block;
}
nav > ul > li:hover > a {
#pagenav > ul > li > a {
color: var(--nav-color);
}
#pagenav > ul > li:hover > a {
color: var(--nav-hover-color);
}
#pagenav {
background: var(--blue);
}
.dropdown {position: static;}
#breadcrumbs {
@ -180,21 +197,17 @@ nav > ul > li:hover > a {
padding-right: var(--content-padding);
}
.content {
main {
position: relative;
display: table;
width: calc(100% - (2 * var(--content-padding)));
display: block;
width: calc(100% - (2 * var(--content-padding)) - 2px);
min-height: var(--main-height);
height: fit-content;
padding: var(--content-padding);
background: var(--content-background);
border: var(--content-border);
border-radius: var(--content-border-radius);
}
main {
display: table-row;
height: var(--main-height);
}
footer {
display: block;
position: relative;
@ -350,14 +363,4 @@ div.macro {
#froghat.view {
}
#froghat.edit {
font-family: monospace;
white-space: pre;
}
#froghat.wysiwyg {
}
#froghat.wysiwyg .md {
opacity: 0.5;
}

View File

@ -143,6 +143,7 @@ class Froghat {
this.element.innerHTML = this.getHTML();
this.setState(this.states.VIEW);
this.element.contentEditable = false;
document.getElementById("main").classList.remove("editing");
}
}

View File

@ -18,23 +18,24 @@
--breadcrumbs-height: 50px;
--footer-height: 50px;
--footer-spacing: var(--breadcrumbs-height);
--toolbar-height: 0px;
--toolbar-spacing: 0px;
--masthead-spacing: 0px;
--max-width: 1024px;
--min-width: calc(710px + (2 * var(--content-padding)));
--main-height: calc(100vh - var(--nav-height) - var(--footer-height) - var(--breadcrumbs-height) - calc(2 * var(--content-padding)) - var(--footer-spacing));
--main-height: calc(100vh - var(--nav-height) - var(--masthead-spacing) - var(--toolbar-height) - (3 * var(--toolbar-spacing)) - var(--footer-height) - var(--breadcrumbs-height) - calc(2 * var(--content-padding)) - var(--footer-spacing));
--content-border-radius: calc(0.5 * var(--content-padding));
--content-border: 1px solid transparent;
/* colors */
--blue: #0ca0d6;
--white: #FFF;
--body-background: #EEE;
--nav-color: #000055;
--nav-hover-color: #FFF;
--content-background: #FFF;
--footer-background-color: #DDD;
}

View File

@ -0,0 +1,17 @@
<div class='main-aligned'>
<nav id='toolbar'>
<ul>
<li><a class='button' id='bold'></a></li>
<li><a class='button' id='italic'></a></li>
<li><a class='button' id='underline'></a></li>
<li>&nbsp;</li>
<li><a class='button' id='bullet_list'></a></li>
<li>&nbsp;</li>
<li><a class='button' id='center'></a></li>
<li>&nbsp;</li>
<li><a class='button' id='wysiwyg' alt='toggle WYSIWYG mode'></a></li>
<li><a class='button' id='save' alt='save'></a></li>
<li><a class='button' id='toggle' alt='toggle edit mode'></a></li>
</ul>
</nav>
</div>