mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-20 05:04:50 +02:00
Make task list checkboxes clickable in the preview tab (#37010)
When a checkbox is toggled in the markup preview tab, the change is now synced back to the editor textarea. Extracted a `toggleTasklistCheckbox` helper to deduplicate the byte-offset toggle logic. --------- Co-authored-by: Claude (Opus 4.6) <noreply@anthropic.com>
This commit is contained in:
parent
da51d5af1a
commit
50a1dc9486
@ -10,6 +10,7 @@ import {
|
|||||||
} from './EditorUpload.ts';
|
} from './EditorUpload.ts';
|
||||||
import {handleGlobalEnterQuickSubmit} from './QuickSubmit.ts';
|
import {handleGlobalEnterQuickSubmit} from './QuickSubmit.ts';
|
||||||
import {renderPreviewPanelContent} from '../repo-editor.ts';
|
import {renderPreviewPanelContent} from '../repo-editor.ts';
|
||||||
|
import {toggleTasklistCheckbox} from '../../markup/tasklist.ts';
|
||||||
import {easyMDEToolbarActions} from './EasyMDEToolbarActions.ts';
|
import {easyMDEToolbarActions} from './EasyMDEToolbarActions.ts';
|
||||||
import {initTextExpander} from './TextExpander.ts';
|
import {initTextExpander} from './TextExpander.ts';
|
||||||
import {showErrorToast} from '../../modules/toast.ts';
|
import {showErrorToast} from '../../modules/toast.ts';
|
||||||
@ -236,6 +237,20 @@ export class ComboMarkdownEditor {
|
|||||||
const response = await POST(this.previewUrl, {data: formData});
|
const response = await POST(this.previewUrl, {data: formData});
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
renderPreviewPanelContent(panelPreviewer, data);
|
renderPreviewPanelContent(panelPreviewer, data);
|
||||||
|
// enable task list checkboxes in preview and sync state back to the editor
|
||||||
|
for (const checkbox of panelPreviewer.querySelectorAll<HTMLInputElement>('.task-list-item input[type=checkbox]')) {
|
||||||
|
checkbox.disabled = false;
|
||||||
|
checkbox.addEventListener('input', () => {
|
||||||
|
const position = parseInt(checkbox.getAttribute('data-source-position')!) + 1;
|
||||||
|
const newContent = toggleTasklistCheckbox(this.value(), position, checkbox.checked);
|
||||||
|
if (newContent === null) {
|
||||||
|
checkbox.checked = !checkbox.checked;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.value(newContent);
|
||||||
|
triggerEditorContentChanged(this.container);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
web_src/js/markup/tasklist.test.ts
Normal file
9
web_src/js/markup/tasklist.test.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import {toggleTasklistCheckbox} from './tasklist.ts';
|
||||||
|
|
||||||
|
test('toggleTasklistCheckbox', () => {
|
||||||
|
expect(toggleTasklistCheckbox('- [ ] task', 3, true)).toEqual('- [x] task');
|
||||||
|
expect(toggleTasklistCheckbox('- [x] task', 3, false)).toEqual('- [ ] task');
|
||||||
|
expect(toggleTasklistCheckbox('- [ ] task', 0, true)).toBeNull();
|
||||||
|
expect(toggleTasklistCheckbox('- [ ] task', 99, true)).toBeNull();
|
||||||
|
expect(toggleTasklistCheckbox('😀 - [ ] task', 8, true)).toEqual('😀 - [x] task');
|
||||||
|
});
|
||||||
@ -3,6 +3,23 @@ import {showErrorToast} from '../modules/toast.ts';
|
|||||||
|
|
||||||
const preventListener = (e: Event) => e.preventDefault();
|
const preventListener = (e: Event) => e.preventDefault();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle a task list checkbox in markdown content.
|
||||||
|
* `position` is the byte offset of the space or `x` character inside `[ ]`.
|
||||||
|
* Returns the updated content, or null if the position is invalid.
|
||||||
|
*/
|
||||||
|
export function toggleTasklistCheckbox(content: string, position: number, checked: boolean): string | null {
|
||||||
|
const buffer = new TextEncoder().encode(content);
|
||||||
|
// Indexes may fall off the ends and return undefined.
|
||||||
|
if (buffer[position - 1] !== '['.charCodeAt(0) ||
|
||||||
|
buffer[position] !== ' '.charCodeAt(0) && buffer[position] !== 'x'.charCodeAt(0) ||
|
||||||
|
buffer[position + 1] !== ']'.charCodeAt(0)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
buffer[position] = checked ? 'x'.charCodeAt(0) : ' '.charCodeAt(0);
|
||||||
|
return new TextDecoder().decode(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches `input` handlers to markdown rendered tasklist checkboxes in comments.
|
* Attaches `input` handlers to markdown rendered tasklist checkboxes in comments.
|
||||||
*
|
*
|
||||||
@ -23,24 +40,17 @@ export function initMarkupTasklist(elMarkup: HTMLElement): void {
|
|||||||
|
|
||||||
checkbox.setAttribute('data-editable', 'true');
|
checkbox.setAttribute('data-editable', 'true');
|
||||||
checkbox.addEventListener('input', async () => {
|
checkbox.addEventListener('input', async () => {
|
||||||
const checkboxCharacter = checkbox.checked ? 'x' : ' ';
|
|
||||||
const position = parseInt(checkbox.getAttribute('data-source-position')!) + 1;
|
const position = parseInt(checkbox.getAttribute('data-source-position')!) + 1;
|
||||||
|
|
||||||
const rawContent = container.querySelector('.raw-content')!;
|
const rawContent = container.querySelector('.raw-content')!;
|
||||||
const oldContent = rawContent.textContent;
|
const oldContent = rawContent.textContent;
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
const newContent = toggleTasklistCheckbox(oldContent, position, checkbox.checked);
|
||||||
const buffer = encoder.encode(oldContent);
|
if (newContent === null) {
|
||||||
// Indexes may fall off the ends and return undefined.
|
// Position is probably wrong. Revert and don't allow change.
|
||||||
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;
|
checkbox.checked = !checkbox.checked;
|
||||||
throw new Error(`Expected position to be space or x and surrounded by brackets, but it's not: position=${position}`);
|
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) {
|
if (newContent === oldContent) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user