From 06a3da1d545a337df50b682bf92ec41c184b52d4 Mon Sep 17 00:00:00 2001 From: luxick Date: Fri, 10 Apr 2026 09:53:02 +0200 Subject: [PATCH] Add editor interface --- assets/editor.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ assets/page.html | 20 ++++++++++++++++++- assets/style.css | 48 ++++++++++++++++++++++++++++++++++++++++++++-- main.go | 3 ++- 4 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 assets/editor.js diff --git a/assets/editor.js b/assets/editor.js new file mode 100644 index 0000000..ac8f4c7 --- /dev/null +++ b/assets/editor.js @@ -0,0 +1,50 @@ +(function () { + var textarea = document.getElementById('editor'); + if (!textarea) return; + + function wrap(before, after, placeholder) { + var start = textarea.selectionStart; + var end = textarea.selectionEnd; + var selected = textarea.value.slice(start, end) || placeholder; + var replacement = before + selected + after; + textarea.value = textarea.value.slice(0, start) + replacement + textarea.value.slice(end); + if (selected === placeholder) { + textarea.selectionStart = start + before.length; + textarea.selectionEnd = start + before.length + placeholder.length; + } else { + textarea.selectionStart = start + replacement.length; + textarea.selectionEnd = start + replacement.length; + } + textarea.focus(); + } + + function linePrefix(prefix) { + var start = textarea.selectionStart; + var lineStart = textarea.value.lastIndexOf('\n', start - 1) + 1; + textarea.value = textarea.value.slice(0, lineStart) + prefix + textarea.value.slice(lineStart); + textarea.selectionStart = textarea.selectionEnd = start + prefix.length; + textarea.focus(); + } + + var actions = { + bold: function () { wrap('**', '**', 'bold text'); }, + italic: function () { wrap('*', '*', 'italic text'); }, + h1: function () { linePrefix('# '); }, + h2: function () { linePrefix('## '); }, + h3: function () { linePrefix('### '); }, + code: function () { wrap('`', '`', 'code'); }, + codeblock: function () { wrap('```\n', '\n```', 'code'); }, + quote: function () { linePrefix('> '); }, + link: function () { wrap('[', '](url)', 'link text'); }, + ul: function () { linePrefix('- '); }, + ol: function () { linePrefix('1. '); }, + hr: function () { wrap('\n\n---\n\n', '', ''); }, + }; + + document.querySelectorAll('[data-action]').forEach(function (btn) { + btn.addEventListener('click', function () { + var action = actions[btn.dataset.action]; + if (action) action(); + }); + }); +})(); diff --git a/assets/page.html b/assets/page.html index 8795c18..589ca29 100644 --- a/assets/page.html +++ b/assets/page.html @@ -18,12 +18,30 @@
{{if .EditMode}}
- +
+ + + + + + + + + + + + + + + +
+
CANCEL
+ {{else}} {{if .Content}}
{{.Content}}
{{end}} {{if .Entries}} diff --git a/assets/style.css b/assets/style.css index af1d509..5b7be5a 100644 --- a/assets/style.css +++ b/assets/style.css @@ -233,11 +233,52 @@ main { text-shadow: none; } +/* === Editor toolbar === */ +.editor-toolbar { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + border: 1px solid #0a0; + border-bottom: none; + padding: 0.4rem 0.6rem; + background: #001a00; +} + +.btn-tool { + background: none; + border: none; + color: #0f0; + font: inherit; + font-size: 0.85rem; + cursor: pointer; + padding: 0 0.15rem; + text-shadow: 0 0 4px #0a0; + white-space: nowrap; +} +.btn-tool::before { + content: "["; + color: #060; +} +.btn-tool::after { + content: "]"; + color: #060; +} +.btn-tool:hover { + color: #fff; + text-shadow: 0 0 6px #0f0; +} + +.toolbar-sep { + width: 1px; + background: #060; + margin: 0 0.2rem; + align-self: stretch; +} + /* === Edit form === */ .edit-form { display: flex; flex-direction: column; - gap: 1rem; } textarea { @@ -245,7 +286,10 @@ textarea { min-height: 60vh; background: #000; border: 1px solid #0a0; - color: white; + border-top: none; + color: #0f0; + caret-color: #0f0; + text-shadow: 0 0 4px #0a0; font: inherit; font-size: 0.9rem; line-height: 1.6; diff --git a/main.go b/main.go index 8362056..b5f8abd 100644 --- a/main.go +++ b/main.go @@ -86,7 +86,8 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } urlPath := path.Clean("/" + r.URL.Path) - fsPath := filepath.Join(h.root, filepath.FromSlash(urlPath)) + +fsPath := filepath.Join(h.root, filepath.FromSlash(urlPath)) // Security: ensure the resolved path stays within root. rel, err := filepath.Rel(h.root, fsPath)