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