diff options
| author | grm <grm@eyesin.space> | 2026-09-12 12:26:17 +0300 |
|---|---|---|
| committer | grm <grm@eyesin.space> | 2026-09-12 12:26:17 +0300 |
| commit | 2b450b66fee4906e746f6e04a6d07a72ff2faf64 (patch) | |
| tree | ba2dbb3d9b3580113510dbf24d4ffe660cf95b26 /internal/web/templates/partials/editor.html | |
| parent | 336199c65d910bbd45a9c5dcbc3beae214d75b98 (diff) | |
| download | blogspace-2b450b66fee4906e746f6e04a6d07a72ff2faf64.tar.gz blogspace-2b450b66fee4906e746f6e04a6d07a72ff2faf64.tar.bz2 blogspace-2b450b66fee4906e746f6e04a6d07a72ff2faf64.zip | |
Insert images straight from the Markdown editor
A shared editor partial adds an "Insert image" file picker plus paste and
drag-and-drop on the textarea; a few lines of inline JS upload the file to
the existing images endpoint (which now answers JSON when asked) and put
the Markdown at the caret. Without JS the same file rides along with the
form and is appended to the text on save, so nothing depends on scripting.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web/templates/partials/editor.html')
| -rw-r--r-- | internal/web/templates/partials/editor.html | 44 |
1 files changed, 44 insertions, 0 deletions
diff --git a/internal/web/templates/partials/editor.html b/internal/web/templates/partials/editor.html new file mode 100644 index 0000000..8e0acaf --- /dev/null +++ b/internal/web/templates/partials/editor.html @@ -0,0 +1,44 @@ +{{define "editor"}}<textarea name="{{.name}}" id="ed-{{.name}}" rows="{{.rows}}"{{if .tall}} class="editor"{{end}}>{{.value}}</textarea> +<div class="editor-tools"> + <label class="upload">Insert image <input type="file" name="inline_image" id="ed-{{.name}}-file" accept="image/*"></label> + <span class="upload-status" id="ed-{{.name}}-status"></span> + <span class="muted small">…or paste / drop an image into the text.</span> + <noscript><span class="muted small">The image is added at the end of the text when you save.</span></noscript> +</div> +{{template "mdhelp"}} +<script> +(function () { + var ta = document.getElementById("ed-{{.name}}"), inp = document.getElementById("ed-{{.name}}-file"), st = document.getElementById("ed-{{.name}}-status"); + if (!window.fetch || !window.FormData || !ta || !inp) return; // old browser: the file is uploaded on save instead + function insert(text) { + var s = ta.selectionStart, e = ta.selectionEnd, pre = ta.value.slice(0, s), post = ta.value.slice(e); + var chunk = (pre && !/\n$/.test(pre) ? "\n" : "") + text + "\n"; + ta.value = pre + chunk + post; + ta.selectionStart = ta.selectionEnd = (pre + chunk).length; + ta.focus(); + } + function upload(file) { + st.textContent = "Uploading " + file.name + "…"; + var fd = new FormData(); + fd.append("_csrf", {{.csrf}}); + fd.append("file", file); + fetch({{.upload}}, { method: "POST", body: fd, credentials: "same-origin", headers: { Accept: "application/json" } }) + .then(function (r) { return r.json().then(function (j) { if (!r.ok || j.error) throw new Error(j.error || "upload failed"); return j; }); }) + .then(function (j) { insert(j.markdown); st.textContent = "Inserted " + j.filename + "."; inp.value = ""; }) + .catch(function (err) { st.textContent = "Could not upload: " + err.message; }); + } + inp.addEventListener("change", function () { if (inp.files && inp.files[0]) upload(inp.files[0]); }); + ta.addEventListener("paste", function (ev) { + var items = ev.clipboardData && ev.clipboardData.items; + if (!items) return; + for (var i = 0; i < items.length; i++) { + if (items[i].kind === "file" && /^image\//.test(items[i].type)) { ev.preventDefault(); upload(items[i].getAsFile()); return; } + } + }); + ta.addEventListener("dragover", function (ev) { ev.preventDefault(); }); + ta.addEventListener("drop", function (ev) { + var f = ev.dataTransfer && ev.dataTransfer.files && ev.dataTransfer.files[0]; + if (f && /^image\//.test(f.type)) { ev.preventDefault(); upload(f); } + }); +})(); +</script>{{end}} |
