2024-02-15 16:27:07 +03:00
import { POST } from '../modules/fetch.js' ;
2022-01-29 00:00:11 +03:00
const preventListener = ( e ) => e . preventDefault ( ) ;
2021-05-23 17:14:03 +03:00
/ * *
* 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 ( ) {
for ( const el of document . querySelectorAll ( ` .markup[data-can-edit=true] ` ) || [ ] ) {
const container = el . parentNode ;
const checkboxes = el . querySelectorAll ( ` .task-list-item input[type=checkbox] ` ) ;
for ( const checkbox of checkboxes ) {
2021-11-22 11:19:01 +03:00
if ( checkbox . hasAttribute ( 'data-editable' ) ) {
2021-06-14 23:42:58 +03:00
return ;
}
2021-11-22 11:19:01 +03:00
checkbox . setAttribute ( 'data-editable' , 'true' ) ;
2021-05-23 17:14:03 +03:00
checkbox . addEventListener ( 'input' , async ( ) => {
const checkboxCharacter = checkbox . checked ? 'x' : ' ' ;
2021-11-22 11:19:01 +03:00
const position = parseInt ( checkbox . getAttribute ( 'data-source-position' ) ) + 1 ;
2021-05-23 17:14:03 +03:00
const rawContent = container . querySelector ( '.raw-content' ) ;
const oldContent = rawContent . textContent ;
2021-06-14 23:42:58 +03:00
const encoder = new TextEncoder ( ) ;
const buffer = encoder . encode ( oldContent ) ;
2023-06-13 09:44:47 +03:00
// 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 } ` ) ;
}
2021-06-14 23:42:58 +03:00
buffer . set ( encoder . encode ( checkboxCharacter ) , position ) ;
const newContent = new TextDecoder ( ) . decode ( buffer ) ;
if ( newContent === oldContent ) {
return ;
}
2021-05-23 17:14:03 +03:00
// 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 ( '.edit-content-zone' ) ;
2021-11-22 11:19:01 +03:00
const updateUrl = editContentZone . getAttribute ( 'data-update-url' ) ;
const context = editContentZone . getAttribute ( 'data-context' ) ;
2021-05-23 17:14:03 +03:00
2024-02-15 16:27:07 +03:00
const requestBody = new FormData ( ) ;
requestBody . append ( 'ignore_attachments' , 'true' ) ;
requestBody . append ( 'content' , newContent ) ;
requestBody . append ( 'context' , context ) ;
await POST ( updateUrl , { data : requestBody } ) ;
2021-05-23 17:14:03 +03:00
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 ;
}
}
}