1
1
mirror of https://github.com/go-gitea/gitea synced 2025-03-10 20:54:30 +00:00
gitea/web_src/js/markup/tasklist.ts
Kerwin Bryant f0f1737d4d
Refactor markup and pdf-viewer to use new init framework (#33772)
1. Add some "render-content" classes to "markup" elements when the
content is rendered
2. Use correct "markup" wrapper for "preview" (but not set that class on
the tab)
3. Remove incorrect "markup" class from LFS file view, because there is
no markup content
    * "edit-diff" is also removed because it does nothing
5. Use "initPdfViewer" for PDF viewer
6. Remove incorrect "content" class from milestone markup
7. Init all ".markup" elements by new init framework

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-03 11:49:15 -08:00

91 lines
3.6 KiB
TypeScript

import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
const preventListener = (e: Event) => e.preventDefault();
/**
* Attaches `input` handlers to markdown rendered tasklist checkboxes in comments.
*
* When a checkbox value changes, the corresponding [ ] or [x] in the markdown string
* is set accordingly and sent to the server. On success, it updates the raw-content on
* error it resets the checkbox to its original value.
*/
export function initMarkupTasklist(elMarkup: HTMLElement): void {
if (!elMarkup.matches('[data-can-edit=true]')) return;
const container = elMarkup.parentNode;
const checkboxes = elMarkup.querySelectorAll<HTMLInputElement>(`.task-list-item input[type=checkbox]`);
for (const checkbox of checkboxes) {
if (checkbox.hasAttribute('data-editable')) {
return;
}
checkbox.setAttribute('data-editable', 'true');
checkbox.addEventListener('input', async () => {
const checkboxCharacter = checkbox.checked ? 'x' : ' ';
const position = parseInt(checkbox.getAttribute('data-source-position')) + 1;
const rawContent = container.querySelector('.raw-content');
const oldContent = rawContent.textContent;
const encoder = new TextEncoder();
const buffer = encoder.encode(oldContent);
// Indexes may fall off the ends and return undefined.
if (buffer[position - 1] !== '['.codePointAt(0) ||
buffer[position] !== ' '.codePointAt(0) && buffer[position] !== 'x'.codePointAt(0) ||
buffer[position + 1] !== ']'.codePointAt(0)) {
// Position is probably wrong. Revert and don't allow change.
checkbox.checked = !checkbox.checked;
throw new Error(`Expected position to be space or x and surrounded by brackets, but it's not: position=${position}`);
}
buffer.set(encoder.encode(checkboxCharacter), position);
const newContent = new TextDecoder().decode(buffer);
if (newContent === oldContent) {
return;
}
// Prevent further inputs until the request is done. This does not use the
// `disabled` attribute because it causes the border to flash on click.
for (const checkbox of checkboxes) {
checkbox.addEventListener('click', preventListener);
}
try {
const editContentZone = container.querySelector<HTMLDivElement>('.edit-content-zone');
const updateUrl = editContentZone.getAttribute('data-update-url');
const context = editContentZone.getAttribute('data-context');
const contentVersion = editContentZone.getAttribute('data-content-version');
const requestBody = new FormData();
requestBody.append('ignore_attachments', 'true');
requestBody.append('content', newContent);
requestBody.append('context', context);
requestBody.append('content_version', contentVersion);
const response = await POST(updateUrl, {data: requestBody});
const data = await response.json();
if (response.status === 400) {
showErrorToast(data.errorMessage);
return;
}
editContentZone.setAttribute('data-content-version', data.contentVersion);
rawContent.textContent = newContent;
} catch (err) {
checkbox.checked = !checkbox.checked;
console.error(err);
}
// Enable input on checkboxes again
for (const checkbox of checkboxes) {
checkbox.removeEventListener('click', preventListener);
}
});
// Enable the checkboxes as they are initially disabled by the markdown renderer
for (const checkbox of checkboxes) {
checkbox.disabled = false;
}
}
}