loading up the forgejo repo on tangled to test page performance
at forgejo 90 lines 3.8 kB view raw
1import {POST} from '../modules/fetch.js'; 2import {showErrorToast} from '../modules/toast.js'; 3 4const preventListener = (e) => e.preventDefault(); 5 6/** 7 * Attaches `input` handlers to markdown rendered tasklist checkboxes in comments. 8 * 9 * When a checkbox value changes, the corresponding [ ] or [x] in the markdown string 10 * is set accordingly and sent to the server. On success it updates the raw-content on 11 * error it resets the checkbox to its original value. 12 */ 13export function initMarkupTasklist() { 14 for (const el of document.querySelectorAll(`.markup[data-can-edit=true]`) || []) { 15 const container = el.parentNode; 16 const checkboxes = el.querySelectorAll(`.task-list-item input[type=checkbox]`); 17 18 for (const checkbox of checkboxes) { 19 if (checkbox.hasAttribute('data-editable')) { 20 return; 21 } 22 23 checkbox.setAttribute('data-editable', 'true'); 24 checkbox.addEventListener('input', async () => { 25 const checkboxCharacter = checkbox.checked ? 'x' : ' '; 26 const position = parseInt(checkbox.getAttribute('data-source-position')) + 1; 27 28 const rawContent = container.querySelector('.raw-content'); 29 const oldContent = rawContent.textContent; 30 31 const encoder = new TextEncoder(); 32 const buffer = encoder.encode(oldContent); 33 // Indexes may fall off the ends and return undefined. 34 if (buffer[position - 1] !== '['.codePointAt(0) || 35 buffer[position] !== ' '.codePointAt(0) && buffer[position] !== 'x'.codePointAt(0) && buffer[position] !== 'X'.codePointAt(0) || 36 buffer[position + 1] !== ']'.codePointAt(0)) { 37 // Position is probably wrong. Revert and don't allow change. 38 checkbox.checked = !checkbox.checked; 39 throw new Error(`Expected position to be space, x or X and surrounded by brackets, but it's not: position=${position}`); 40 } 41 buffer.set(encoder.encode(checkboxCharacter), position); 42 const newContent = new TextDecoder().decode(buffer); 43 44 if (newContent === oldContent) { 45 return; 46 } 47 48 // Prevent further inputs until the request is done. This does not use the 49 // `disabled` attribute because it causes the border to flash on click. 50 for (const checkbox of checkboxes) { 51 checkbox.addEventListener('click', preventListener); 52 } 53 54 try { 55 const editContentZone = container.querySelector('.edit-content-zone'); 56 const updateUrl = editContentZone.getAttribute('data-update-url'); 57 const context = editContentZone.getAttribute('data-context'); 58 const contentVersion = editContentZone.getAttribute('data-content-version'); 59 60 const requestBody = new FormData(); 61 requestBody.append('ignore_attachments', 'true'); 62 requestBody.append('content', newContent); 63 requestBody.append('context', context); 64 requestBody.append('content_version', contentVersion); 65 const response = await POST(updateUrl, {data: requestBody}); 66 const data = await response.json(); 67 if (response.status === 400) { 68 showErrorToast(data.errorMessage); 69 return; 70 } 71 editContentZone.setAttribute('data-content-version', data.contentVersion); 72 rawContent.textContent = newContent; 73 } catch (err) { 74 checkbox.checked = !checkbox.checked; 75 console.error(err); 76 } 77 78 // Enable input on checkboxes again 79 for (const checkbox of checkboxes) { 80 checkbox.removeEventListener('click', preventListener); 81 } 82 }); 83 } 84 85 // Enable the checkboxes as they are initially disabled by the markdown renderer 86 for (const checkbox of checkboxes) { 87 checkbox.disabled = false; 88 } 89 } 90}