gitea源码

tasklist.ts 3.6KB

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