Add editor interface
This commit is contained in:
50
assets/editor.js
Normal file
50
assets/editor.js
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -18,12 +18,30 @@
|
||||
<main>
|
||||
{{if .EditMode}}
|
||||
<form class="edit-form" method="POST" action="{{.PostURL}}">
|
||||
<textarea name="content" autofocus>{{.RawContent}}</textarea>
|
||||
<div class="editor-toolbar">
|
||||
<button type="button" class="btn-tool" data-action="bold" title="Bold">**</button>
|
||||
<button type="button" class="btn-tool" data-action="italic" title="Italic">*</button>
|
||||
<span class="toolbar-sep"></span>
|
||||
<button type="button" class="btn-tool" data-action="h1" title="Heading 1">#</button>
|
||||
<button type="button" class="btn-tool" data-action="h2" title="Heading 2">##</button>
|
||||
<button type="button" class="btn-tool" data-action="h3" title="Heading 3">###</button>
|
||||
<span class="toolbar-sep"></span>
|
||||
<button type="button" class="btn-tool" data-action="code" title="Inline code">`</button>
|
||||
<button type="button" class="btn-tool" data-action="codeblock" title="Code block">```</button>
|
||||
<span class="toolbar-sep"></span>
|
||||
<button type="button" class="btn-tool" data-action="link" title="Link">[]</button>
|
||||
<button type="button" class="btn-tool" data-action="quote" title="Blockquote">></button>
|
||||
<button type="button" class="btn-tool" data-action="ul" title="Unordered list">-</button>
|
||||
<button type="button" class="btn-tool" data-action="ol" title="Ordered list">1.</button>
|
||||
<button type="button" class="btn-tool" data-action="hr" title="Horizontal rule">---</button>
|
||||
</div>
|
||||
<textarea name="content" id="editor" autofocus>{{.RawContent}}</textarea>
|
||||
<div class="form-actions">
|
||||
<a class="btn-cancel" href="{{.PostURL}}">CANCEL</a>
|
||||
<button class="btn-save" type="submit">SAVE</button>
|
||||
</div>
|
||||
</form>
|
||||
<script src="/_/editor.js"></script>
|
||||
{{else}} {{if .Content}}
|
||||
<div class="content">{{.Content}}</div>
|
||||
{{end}} {{if .Entries}}
|
||||
|
||||
@@ -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;
|
||||
|
||||
3
main.go
3
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)
|
||||
|
||||
Reference in New Issue
Block a user