feat(post-form): EasyMDE editor + /post-scoped ESM bundle (U3)
Add a page-scoped js/src/post-form.js bundle (built ESM + code-splitting so U4's heic-to becomes a deferred chunk) loaded only by post-form.html.twig via a new base head_assets block. EasyMDE replaces the bare textarea with a minimal toolbar (bold/italic/list/link/preview), FA-free via CSS glyphs, and syncs codemirror.save() on change + capture-phase submit so the inline required-field validator and payload see the live value. Refs R7, R15, KTD2, KTD3.
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* /post form bundle CSS. U3 scope: make the EasyMDE editor usable without the
|
||||
* FontAwesome dependency (toolbar glyphs supplied here). Field Notes theming,
|
||||
* mobile layout, and the "More options" disclosure land in U5.
|
||||
*/
|
||||
|
||||
/* EasyMDE toolbar glyphs — replace the FontAwesome icons EasyMDE expects. */
|
||||
.editor-toolbar .mde-btn::before {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-family: inherit;
|
||||
}
|
||||
.editor-toolbar .mde-bold::before { content: 'B'; font-weight: 700; }
|
||||
.editor-toolbar .mde-italic::before { content: 'I'; font-style: italic; }
|
||||
.editor-toolbar .mde-ul::before { content: '\2022\2002\2014'; } /* • — */
|
||||
.editor-toolbar .mde-link::before { content: '\1F517'; } /* 🔗 */
|
||||
.editor-toolbar .mde-preview::before { content: '\1F441'; } /* 👁 */
|
||||
|
||||
.EasyMDEContainer .CodeMirror {
|
||||
min-height: 180px;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* /post journal form bundle — page-scoped (loaded only by post-form.html.twig).
|
||||
* Kept out of the global main.js so EasyMDE (+ the U4 HEIC converter, lazy-loaded)
|
||||
* never ship on any other page. Built as ESM with code-splitting so the heic-to
|
||||
* dynamic import becomes a separately-fetched chunk (see package.json / KTD2).
|
||||
*
|
||||
* Every init function presence-guards its own DOM, mirroring main.js's
|
||||
* initTripStats pattern, so this is a no-op if an element is absent.
|
||||
*/
|
||||
import EasyMDE from 'easymde';
|
||||
import 'easymde/dist/easymde.min.css';
|
||||
import './post-form.css';
|
||||
|
||||
/* ── Markdown editor (EasyMDE) ───────────────────────────── */
|
||||
function initEditor() {
|
||||
var textarea = document.querySelector('textarea[name="data[content]"]');
|
||||
if (!textarea) return null;
|
||||
|
||||
var editor = new EasyMDE({
|
||||
element: textarea,
|
||||
spellChecker: false,
|
||||
autoDownloadFontAwesome: false, // toolbar glyphs come from post-form.css, no FA CDN
|
||||
status: false,
|
||||
placeholder: 'What happened today?',
|
||||
minHeight: '180px',
|
||||
// Minimal toolbar; custom classNames drive the CSS glyphs in post-form.css.
|
||||
toolbar: [
|
||||
{ name: 'bold', action: EasyMDE.toggleBold, className: 'mde-btn mde-bold', title: 'Bold' },
|
||||
{ name: 'italic', action: EasyMDE.toggleItalic, className: 'mde-btn mde-italic', title: 'Italic' },
|
||||
{ name: 'unordered-list', action: EasyMDE.toggleUnorderedList, className: 'mde-btn mde-ul', title: 'Bulleted list' },
|
||||
{ name: 'link', action: EasyMDE.drawLink, className: 'mde-btn mde-link', title: 'Insert link' },
|
||||
'|',
|
||||
{ name: 'preview', action: EasyMDE.togglePreview, className: 'mde-btn mde-preview', title: 'Toggle preview' },
|
||||
],
|
||||
});
|
||||
|
||||
// KTD3 / R15: keep the underlying <textarea> in sync with the editor so the
|
||||
// form's custom required-field validator (which reads [name="data[content]"])
|
||||
// and the submitted payload both see the live value.
|
||||
editor.codemirror.on('change', function () { editor.codemirror.save(); });
|
||||
|
||||
var form = textarea.closest('form');
|
||||
if (form) {
|
||||
// Capture phase fires before the inline bubble-phase validator, so the
|
||||
// textarea already holds the current value when validation reads it.
|
||||
form.addEventListener('submit', function () { editor.codemirror.save(); }, true);
|
||||
}
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
/* ── Boot ────────────────────────────────────────────────── */
|
||||
function boot() {
|
||||
// Exposed for later units (U6 draft restore) and the Playwright specs;
|
||||
// null when this bundle loads on a page without the content field.
|
||||
window.postFormEditor = initEditor();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', boot);
|
||||
} else {
|
||||
boot();
|
||||
}
|
||||
Generated
+77
@@ -8,6 +8,7 @@
|
||||
"@fontsource-variable/dm-sans": "latest",
|
||||
"@fontsource/dm-serif-display": "latest",
|
||||
"@mapbox/togeojson": "^0.16.2",
|
||||
"easymde": "^2",
|
||||
"maplibre-gl": "^4",
|
||||
"photoswipe": "^5",
|
||||
"scrollama": "^3"
|
||||
@@ -523,6 +524,21 @@
|
||||
"integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@types/codemirror": {
|
||||
"version": "5.60.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.17.tgz",
|
||||
"integrity": "sha512-AZq2FIsUHVMlp7VSe2hTfl5w4pcUkoFkM3zVsRKsn1ca8CXRDYvnin04+HP2REkwsxemuHqvDofdlhUWNpbwfw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/tern": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
@@ -555,6 +571,12 @@
|
||||
"@types/pbf": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/marked": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.3.2.tgz",
|
||||
"integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/pbf": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz",
|
||||
@@ -570,6 +592,15 @@
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tern": {
|
||||
"version": "0.23.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
||||
"integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.13",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
|
||||
@@ -585,6 +616,21 @@
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "5.65.21",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.21.tgz",
|
||||
"integrity": "sha512-6teYk0bA0nR3QP0ihGMoxuKzpl5W80FpnHpBJpgy66NK3cZv5b/d/HY8PnRvfSsCG1MTfr92u2WUl+wT0E40mQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/codemirror-spell-checker": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz",
|
||||
"integrity": "sha512-2Tl6n0v+GJRsC9K3MLCdLaMOmvWL0uukajNJseorZJsslaxZyZMgENocPU8R0DyoTAiKsyqiemSOZo7kjGV0LQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"typo-js": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||
@@ -606,6 +652,19 @@
|
||||
"integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/easymde": {
|
||||
"version": "2.21.0",
|
||||
"resolved": "https://registry.npmjs.org/easymde/-/easymde-2.21.0.tgz",
|
||||
"integrity": "sha512-5uE7I/DEN8gvGRwxaqAv7h1PMEK2ykNXVX5zL0dK3nCYROGja3AMbdQz8eCEELnfvCfy7tRkTmLuvyJG8uSWjQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/codemirror": "^5.60.10",
|
||||
"@types/marked": "^4.0.7",
|
||||
"codemirror": "^5.65.15",
|
||||
"codemirror-spell-checker": "1.1.2",
|
||||
"marked": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
@@ -796,6 +855,18 @@
|
||||
"url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
|
||||
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
@@ -936,6 +1007,12 @@
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typo-js": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.3.2.tgz",
|
||||
"integrity": "sha512-Z1YkJ7IIYNrFeOxAlHUercY4Q2I+PhYD/3VkWpJGy/Oqudy3bFpNcQxnv6Oa9fTSXCHPGz1eDoX1bZYm2Z891A==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "node scripts/gen-weather-icons.js && esbuild js/src/main.js --bundle --minify --format=iife --outfile=js/main.js --loader:.woff2=file --loader:.woff=file --asset-names=../fonts/[name] && esbuild js/src/map.js --bundle --minify --format=iife --outfile=js/map.js && mkdir -p css-compiled fonts && { mv js/main.css css-compiled/main.css 2>/dev/null || true; } && { mv js/map.css css-compiled/map.css 2>/dev/null || true; }"
|
||||
"build": "node scripts/gen-weather-icons.js && esbuild js/src/main.js --bundle --minify --format=iife --outfile=js/main.js --loader:.woff2=file --loader:.woff=file --asset-names=../fonts/[name] && esbuild js/src/map.js --bundle --minify --format=iife --outfile=js/map.js && esbuild js/src/post-form.js --bundle --minify --format=esm --splitting --outdir=js/post && mkdir -p css-compiled fonts && { mv js/main.css css-compiled/main.css 2>/dev/null || true; } && { mv js/map.css css-compiled/map.css 2>/dev/null || true; } && { mv js/post/post-form.css css-compiled/post-form.css 2>/dev/null || true; }"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/dm-sans": "latest",
|
||||
"@fontsource/dm-serif-display": "latest",
|
||||
"@mapbox/togeojson": "^0.16.2",
|
||||
"easymde": "^2",
|
||||
"maplibre-gl": "^4",
|
||||
"photoswipe": "^5",
|
||||
"scrollama": "^3"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
{% do assets.addJs('theme://js/main.js', {group: 'bottom'}) %}
|
||||
{% do assets.addJs('theme://js/nav.js', {group: 'bottom'}) %}
|
||||
{% block map_assets %}{% endblock %}
|
||||
{% block head_assets %}{% endblock %}
|
||||
{{ assets.css()|raw }}
|
||||
{{ assets.js()|raw }}
|
||||
</head>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{% extends 'default.html.twig' %}
|
||||
|
||||
{% block head_assets %}
|
||||
{% do assets.addCss('theme://css-compiled/post-form.css') %}
|
||||
<script type="module" src="{{ url('theme://js/post/post-form.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="post-form-wrap">
|
||||
<h1>New Entry</h1>
|
||||
|
||||
Reference in New Issue
Block a user