From f8b09e18bdce78da8b1c34cbfbb0d0706e7ddfa3 Mon Sep 17 00:00:00 2001 From: luxick Date: Tue, 16 Jun 2026 10:25:24 +0200 Subject: [PATCH] Remove obsolete functions --- assets/page/anchors.js | 115 ++---------------------- assets/page/main.html | 1 - assets/page/tasks.js | 21 ----- assets/style.css | 12 +-- main.go | 8 -- tasks.go | 199 ----------------------------------------- 6 files changed, 13 insertions(+), 343 deletions(-) diff --git a/assets/page/anchors.js b/assets/page/anchors.js index 43709da..9cd7721 100644 --- a/assets/page/anchors.js +++ b/assets/page/anchors.js @@ -2,113 +2,16 @@ var content = document.querySelector('.content'); if (!content) return; - var allHeadings = content.querySelectorAll('h1, h2, h3, h4, h5, h6'); - if (!allHeadings.length) return; + var headings = content.querySelectorAll('h2, h3, h4'); + if (!headings.length) return; - function copyAnchor(id, item, label, menu) { - var url = window.location.origin + window.location.pathname + '#' + id; - function flash() { - item.textContent = 'Copied!'; - setTimeout(function () { - item.textContent = label; - menu.classList.remove('is-open'); - }, 1200); - } - function fallback() { - var ta = document.createElement('textarea'); - ta.value = url; - ta.setAttribute('readonly', ''); - ta.style.position = 'fixed'; - ta.style.opacity = '0'; - document.body.appendChild(ta); - ta.select(); - var ok = false; - try { ok = document.execCommand('copy'); } catch (e) { ok = false; } - document.body.removeChild(ta); - if (ok) flash(); - else openModal({ - title: 'Copy anchor link', - body: 'Could not copy automatically. URL:\n' + url, - confirm: { label: 'OK', onConfirm: function () { closeModal(); } } - }); - } - if (navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(url).then(flash, fallback); - } else { - fallback(); - } - } - - function addTask(sectionIndex, headingId) { - var input = document.createElement('input'); - input.type = 'text'; - input.className = 'input'; - input.placeholder = 'Task description'; - var ctrl = openModal({ - title: 'Add task', - body: input, - confirm: { - label: 'ADD', - initiallyDisabled: true, - onConfirm: function () { - var text = input.value.trim(); - if (!text) return; - var action = window.location.pathname + '?addtask=' + sectionIndex; - var target = window.location.pathname + '#' + headingId; - closeModal(); - postReplace(action, 'text=' + encodeURIComponent(text), target); - } - } - }); - input.addEventListener('input', function () { - ctrl.setConfirmDisabled(input.value.trim() === ''); - }); - } - - allHeadings.forEach(function (h, i) { + headings.forEach(function (h) { if (!h.id) return; - var tag = h.tagName.toLowerCase(); - if (tag !== 'h2' && tag !== 'h3' && tag !== 'h4') return; - - var sectionIndex = i + 1; - - var wrap = document.createElement('span'); - wrap.className = 'dropdown heading-anchor'; - - var trigger = document.createElement('button'); - trigger.type = 'button'; - trigger.className = 'dropdown-toggle'; - trigger.setAttribute('aria-haspopup', 'menu'); - trigger.setAttribute('aria-label', 'Section actions'); - trigger.textContent = '#'; - - var menu = document.createElement('div'); - menu.className = 'dropdown-menu'; - - var copyLabel = 'Copy anchor link'; - var copyBtn = document.createElement('button'); - copyBtn.type = 'button'; - copyBtn.className = 'btn btn-tool btn-block'; - copyBtn.dataset.action = 'copy-anchor'; - copyBtn.textContent = copyLabel; - copyBtn.addEventListener('click', function (e) { - e.stopPropagation(); - copyAnchor(h.id, copyBtn, copyLabel, menu); - }); - - var addBtn = document.createElement('button'); - addBtn.type = 'button'; - addBtn.className = 'btn btn-tool btn-block'; - addBtn.dataset.action = 'add-task'; - addBtn.textContent = 'Add task'; - addBtn.addEventListener('click', function () { addTask(sectionIndex, h.id); }); - - menu.appendChild(copyBtn); - menu.appendChild(addBtn); - wrap.appendChild(trigger); - wrap.appendChild(menu); - h.insertBefore(wrap, h.firstChild); - - wireDropdown(trigger); + var a = document.createElement('a'); + a.href = '#' + h.id; + a.className = 'heading-anchor'; + a.setAttribute('aria-label', 'Link to this section'); + a.textContent = '#'; + h.insertBefore(a, h.firstChild); }); }()); diff --git a/assets/page/main.html b/assets/page/main.html index a93fc3e..3890b30 100644 --- a/assets/page/main.html +++ b/assets/page/main.html @@ -57,7 +57,6 @@ {{if not .IsRoot}} {{end}} - {{if not .IsRoot}} {{end}} diff --git a/assets/page/tasks.js b/assets/page/tasks.js index 5fca60f..180ce36 100644 --- a/assets/page/tasks.js +++ b/assets/page/tasks.js @@ -21,25 +21,4 @@ }); }); }); - - var hasChecked = !!document.querySelector('input.task-checkbox:checked'); - if (hasChecked) { - var btn = document.querySelector('[data-action="clean-tasks"]'); - if (btn) btn.hidden = false; - } })(); - -function cleanUpTasks() { - openModal({ - title: 'Clean up tasks', - body: 'Remove all completed tasks from this page?', - confirm: { - label: 'CLEAN UP', - danger: true, - onConfirm: function () { - closeModal(); - postReplace(window.location.pathname + '?cleantasks=1', null, window.location.pathname); - } - } - }); -} diff --git a/assets/style.css b/assets/style.css index 9402728..3976f44 100644 --- a/assets/style.css +++ b/assets/style.css @@ -267,17 +267,13 @@ main > h2 { text-decoration: line-through; } -.dropdown.heading-anchor { margin-right: 0.4em; font-weight: normal; } -.heading-anchor .dropdown-toggle { - background: none; - border: none; - padding: 0; - cursor: pointer; +a.heading-anchor { color: var(--text-muted); - font: inherit; + margin-right: 0.4em; font-weight: normal; + text-decoration: none; } -.heading-anchor .dropdown-toggle:hover { color: var(--primary-hover); } +a.heading-anchor:hover { color: var(--primary-hover); } /* === Data tables === Shared style for the file listing, search-suggestion dropdown, and diff --git a/main.go b/main.go index b1ef564..3c196b5 100644 --- a/main.go +++ b/main.go @@ -371,14 +371,6 @@ func (h *handler) handlePost(w http.ResponseWriter, r *http.Request, urlPath, fs h.handleAppend(w, r, urlPath, fsPath) return } - if query.Has("cleantasks") { - h.handleCleanTasks(w, r, urlPath, fsPath) - return - } - if query.Has("addtask") { - h.handleAddTask(w, r, urlPath, fsPath) - return - } if query.Has("settings") { h.handleSettings(w, r, urlPath, fsPath) return diff --git a/tasks.go b/tasks.go index e11bcb0..3cf5e15 100644 --- a/tasks.go +++ b/tasks.go @@ -7,7 +7,6 @@ import ( "path/filepath" "regexp" "strconv" - "strings" ) // taskCheckboxRe matches the tag goldmark's GFM extension emits for a @@ -111,201 +110,3 @@ func flipTaskLine(raw []byte, n int, checked bool) ([]byte, bool) { lines[target] = taskLineRe.ReplaceAll(lines[target], replacement) return bytes.Join(lines, []byte("\n")), true } - -// handleCleanTasks rewrites index.md with every completed task line — and its -// continuation lines — removed. Triggered by POST /{path}?cleantasks=1. -func (h *handler) handleCleanTasks(w http.ResponseWriter, r *http.Request, urlPath, fsPath string) { - indexPath := filepath.Join(fsPath, "index.md") - raw, err := os.ReadFile(indexPath) - if err != nil { - http.Error(w, "read failed: "+err.Error(), http.StatusInternalServerError) - return - } - updated := stripCompletedTasks(raw) - if !bytes.Equal(updated, raw) { - if err := writeFileAtomic(indexPath, updated, 0644); err != nil { - http.Error(w, "write failed: "+err.Error(), http.StatusInternalServerError) - return - } - } - http.Redirect(w, r, urlPath, http.StatusSeeOther) -} - -// handleAddTask appends a single `- [ ] text` task to the last task list in -// the selected section, or creates a new list at the end of the section if -// none exists. Triggered by POST /{path}?addtask= with form -// field `text`. -func (h *handler) handleAddTask(w http.ResponseWriter, r *http.Request, urlPath, fsPath string) { - sectionIndex, err := strconv.Atoi(r.URL.Query().Get("addtask")) - if err != nil || sectionIndex < 1 { - http.Error(w, "bad section index", http.StatusBadRequest) - return - } - if err := r.ParseForm(); err != nil { - http.Error(w, "bad request", http.StatusBadRequest) - return - } - text := r.FormValue("text") - if strings.ContainsAny(text, "\r\n") { - http.Error(w, "text must be single-line", http.StatusBadRequest) - return - } - text = strings.TrimSpace(text) - if text == "" { - http.Error(w, "empty text", http.StatusBadRequest) - return - } - - indexPath := filepath.Join(fsPath, "index.md") - raw, err := os.ReadFile(indexPath) - if err != nil { - http.Error(w, "read failed: "+err.Error(), http.StatusInternalServerError) - return - } - sections := splitSections(raw) - if sectionIndex >= len(sections) { - http.Error(w, "section out of range", http.StatusBadRequest) - return - } - - sections[sectionIndex] = appendToLastTaskList(sections[sectionIndex], text) - updated := joinSections(sections) - if err := writeFileAtomic(indexPath, updated, 0644); err != nil { - http.Error(w, "write failed: "+err.Error(), http.StatusInternalServerError) - return - } - - target := urlPath - ids := headingIDs(updated) - if sectionIndex-1 < len(ids) { - target = urlPath + "#" + ids[sectionIndex-1] - } - http.Redirect(w, r, target, http.StatusSeeOther) -} - -// splitLines returns raw split on '\n', dropping the trailing empty element -// produced when raw ends in '\n', and reports whether that newline was there. -// reassemble undoes the split with the matching trailing-newline state. -func splitLines(raw []byte) (lines [][]byte, trailingNewline bool) { - trailingNewline = len(raw) > 0 && raw[len(raw)-1] == '\n' - lines = bytes.Split(raw, []byte("\n")) - if trailingNewline && len(lines) > 0 && len(lines[len(lines)-1]) == 0 { - lines = lines[:len(lines)-1] - } - return lines, trailingNewline -} - -func reassemble(lines [][]byte, trailingNewline bool) []byte { - out := bytes.Join(lines, []byte("\n")) - if trailingNewline && len(out) > 0 { - out = append(out, '\n') - } - return out -} - -// isFence reports whether line opens or closes a fenced code block. -func isFence(line []byte) bool { - t := bytes.TrimLeft(line, " \t") - return bytes.HasPrefix(t, []byte("```")) || bytes.HasPrefix(t, []byte("~~~")) -} - -// indentWidth counts leading-whitespace columns, tabs and spaces equally. -// Adequate for the user's own wiki text, where mixed tab/space indents are rare. -func indentWidth(line []byte) int { - n := 0 - for n < len(line) && (line[n] == ' ' || line[n] == '\t') { - n++ - } - return n -} - -// stripCompletedTasks removes every `[x]`/`[X]` task line and its continuation -// lines (blank, or indented strictly more than the bullet) from raw. Lines -// inside fenced code blocks are ignored, matching flipTaskLine's contract. -func stripCompletedTasks(raw []byte) []byte { - lines, trailing := splitLines(raw) - out := make([][]byte, 0, len(lines)) - inFence := false - for i := 0; i < len(lines); i++ { - line := lines[i] - if isFence(line) { - inFence = !inFence - out = append(out, line) - continue - } - if !inFence { - if m := taskLineRe.FindSubmatch(line); m != nil && (m[2][0] == 'x' || m[2][0] == 'X') { - bulletIndent := indentWidth(line) - j := i + 1 - for j < len(lines) { - next := lines[j] - if len(bytes.TrimSpace(next)) > 0 && indentWidth(next) <= bulletIndent { - break - } - j++ - } - i = j - 1 - continue - } - } - out = append(out, line) - } - return reassemble(out, trailing) -} - -// appendToLastTaskList inserts `- [ ] text` after the last task list item in -// sectionBytes. If no task list exists in the section, it appends a new list -// at the end, separated by a blank line. Bullet character and indent are -// inherited from the existing last item when present. -func appendToLastTaskList(sectionBytes []byte, text string) []byte { - lines, trailing := splitLines(sectionBytes) - - // Forward scan: track fence state and remember the last non-fenced task line. - lastTask, lastPrefix, lastIndent := -1, "", 0 - inFence := false - for i, line := range lines { - if isFence(line) { - inFence = !inFence - continue - } - if inFence { - continue - } - if m := taskLineRe.FindSubmatch(line); m != nil { - lastTask = i - lastPrefix = string(m[1]) - lastIndent = indentWidth(line) - } - } - - if lastTask >= 0 { - // Walk forward over continuation lines (blank or more-indented), then - // back over trailing blanks so the new task slots in after the last - // real content line of the item. - end := lastTask + 1 - for end < len(lines) { - next := lines[end] - if len(bytes.TrimSpace(next)) > 0 && indentWidth(next) <= lastIndent { - break - } - end++ - } - for end > lastTask+1 && len(bytes.TrimSpace(lines[end-1])) == 0 { - end-- - } - newLine := []byte(lastPrefix + "[ ] " + text) - out := append(append(append([][]byte{}, lines[:end]...), newLine), lines[end:]...) - return reassemble(out, trailing) - } - - // No task list — append one at section end, blank-line-separated from any - // preceding content. Trim trailing blanks first to control spacing exactly. - for len(lines) > 0 && len(bytes.TrimSpace(lines[len(lines)-1])) == 0 { - lines = lines[:len(lines)-1] - } - if len(lines) > 0 { - lines = append(lines, nil) - } - lines = append(lines, []byte("- [ ] "+text)) - return reassemble(lines, trailing) -}