Monorepo for Tangled tangled.org

appview: Support selecting multiple lines in blob

Signed-off-by: Naomi Roberts <mia@naomieow.xyz>

Changed files
+96 -7
appview
pages
templates
+90
appview/pages/templates/fragments/multiline-select.html
··· 1 + {{ define "fragments/multiline-select" }} 2 + <script> 3 + function highlight(scroll = false) { 4 + document.querySelectorAll(".hl").forEach(el => { 5 + el.classList.remove("hl"); 6 + }); 7 + 8 + const hash = window.location.hash; 9 + if (!hash || !hash.startsWith("#L")) { 10 + return; 11 + } 12 + 13 + const rangeStr = hash.substring(2); 14 + const parts = rangeStr.split("-"); 15 + let startLine, endLine; 16 + 17 + if (parts.length === 2) { 18 + startLine = parseInt(parts[0], 10); 19 + endLine = parseInt(parts[1], 10); 20 + } else { 21 + startLine = parseInt(parts[0], 10); 22 + endLine = startLine; 23 + } 24 + 25 + if (isNaN(startLine) || isNaN(endLine)) { 26 + console.log("nan"); 27 + console.log(startLine); 28 + console.log(endLine); 29 + return; 30 + } 31 + 32 + let target = null; 33 + 34 + for (let i = startLine; i<= endLine; i++) { 35 + const idEl = document.getElementById(`L${i}`); 36 + if (idEl) { 37 + const el = idEl.closest(".line"); 38 + if (el) { 39 + el.classList.add("hl"); 40 + target = el; 41 + } 42 + } 43 + } 44 + 45 + if (scroll && target) { 46 + target.scrollIntoView({ 47 + behavior: "smooth", 48 + block: "center", 49 + }); 50 + } 51 + } 52 + 53 + document.addEventListener("DOMContentLoaded", () => { 54 + console.log("DOMContentLoaded"); 55 + highlight(true); 56 + }); 57 + window.addEventListener("hashchange", () => { 58 + console.log("hashchange"); 59 + highlight(); 60 + }); 61 + window.addEventListener("popstate", () => { 62 + console.log("popstate"); 63 + highlight(); 64 + }); 65 + 66 + const lineNumbers = document.querySelectorAll('a[href^="#L"'); 67 + let startLine = null; 68 + 69 + lineNumbers.forEach(el => { 70 + el.addEventListener("click", (event) => { 71 + event.preventDefault(); 72 + const currentLine = parseInt(el.href.split("#L")[1]); 73 + 74 + if (event.shiftKey && startLine !== null) { 75 + const endLine = currentLine; 76 + const min = Math.min(startLine, endLine); 77 + const max = Math.max(startLine, endLine); 78 + const newHash = `#L${min}-${max}`; 79 + history.pushState(null, '', newHash); 80 + } else { 81 + const newHash = `#L${currentLine}`; 82 + history.pushState(null, '', newHash); 83 + startLine = currentLine; 84 + } 85 + 86 + highlight(); 87 + }); 88 + }); 89 + </script> 90 + {{ end }}
+1
appview/pages/templates/repo/blob.html
··· 78 78 {{ end }} 79 79 </div> 80 80 {{ end }} 81 + {{ template "fragments/multiline-select" }} 81 82 {{ end }}
+3 -2
appview/pages/templates/strings/string.html
··· 23 23 hx-boost="true" 24 24 href="/strings/{{ .String.Did }}/{{ .String.Rkey }}/edit"> 25 25 {{ i "pencil" "size-4" }} 26 - <span class="hidden md:inline">edit</span> 26 + <span class="hidden md:inline">edit</span> 27 27 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 28 28 </a> 29 29 <button ··· 34 34 hx-confirm="Are you sure you want to delete the string `{{ .String.Filename }}`?" 35 35 > 36 36 {{ i "trash-2" "size-4" }} 37 - <span class="hidden md:inline">delete</span> 37 + <span class="hidden md:inline">delete</span> 38 38 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 39 39 </button> 40 40 </div> ··· 80 80 <div id="blob-contents" class="whitespace-pre peer-target:bg-yellow-200 dark:peer-target:bg-yellow-900">{{ .String.Contents | escapeHtml }}</div> 81 81 {{ end }} 82 82 </div> 83 + {{ template "fragments/multiline-select" }} 83 84 </section> 84 85 {{ end }}
+2 -5
input.css
··· 228 228 } 229 229 /* LineHighlight */ 230 230 .chroma .hl { 231 - background-color: #bcc0cc; 231 + @apply bg-amber-400/30 dark:bg-amber-500/20; 232 232 } 233 + 233 234 /* LineNumbersTable */ 234 235 .chroma .lnt { 235 236 white-space: pre; ··· 864 865 text-decoration: underline; 865 866 } 866 867 } 867 - 868 - .chroma .line:has(.ln:target) { 869 - @apply bg-amber-400/30 dark:bg-amber-500/20; 870 - }