diff --git a/web_src/js/index.js b/web_src/js/index.js index 0d60c21ccacc1..d32397855fae4 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -15,6 +15,7 @@ import initHeatmap from './features/heatmap.js'; import initProject from './features/projects.js'; import initServiceWorker from './features/serviceworker.js'; import initMarkdownAnchors from './markdown/anchors.js'; +import initMarkdownCheckboxes from './markdown/checkboxes.js'; import renderMarkdownContent from './markdown/content.js'; import attachTribute from './features/tribute.js'; import createColorPicker from './features/colorpicker.js'; @@ -2644,6 +2645,8 @@ $(document).ready(async () => { renderMarkdownContent(), initGithook(), ]); + + initMarkdownCheckboxes(); }); function changeHash(hash) { diff --git a/web_src/js/markdown/checkboxes.js b/web_src/js/markdown/checkboxes.js new file mode 100644 index 0000000000000..9c4d3f150c76a --- /dev/null +++ b/web_src/js/markdown/checkboxes.js @@ -0,0 +1,70 @@ +const checkboxMarkdownPattern = /\[[ x]]/g; + +/** + * Attaches `change` handlers to markdown rendered 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 default function initMarkdownCheckboxes() { + $('.comment .segment').each((_, segment) => { + const $segment = $(segment); + const $checkboxes = $segment.find('.render-content.markdown input:checkbox'); + + const onChange = async (ev, checkboxIndex) => { + const $checkbox = $(ev.target); + const checkboxMarkdown = $checkbox.is(':checked') ? '[x]' : '[ ]'; + + const $rawContent = $segment.find('.raw-content'); + const oldContent = $rawContent.text(); + const newContent = oldContent.replace(checkboxMarkdownPattern, replaceNthMatchWith(checkboxIndex, checkboxMarkdown)); + + if (newContent !== oldContent) { + disableAll($checkboxes); + + try { + const url = $segment.find('.edit-content-zone').data('update-url'); + const context = $segment.find('.edit-content-zone').data('context'); + + await submit(newContent, url, context); + $rawContent.text(newContent); + } catch (e) { + $checkbox.prop('checked', !$checkbox.is(':checked')); + + console.error(e); + } finally { + enableAll($checkboxes); + } + } + }; + + enableAll($checkboxes); + $checkboxes.each((checkboxIndex, checkboxElement) => { + $(checkboxElement).on('change', (ev) => onChange(ev, checkboxIndex)); + }); + }); +} + +function enableAll ($checkboxes) { $checkboxes.removeAttr('disabled') } +function disableAll ($checkboxes) { $checkboxes.attr('disabled', 'disabled') } + +function submit (content, url, context) { + const csrf = window.config.csrf; + + return $.post(url, { + _csrf: csrf, + context, + content, + }); +} + +function replaceNthMatchWith(n, replaceWith) { + let matchIndex = 0; + + return (match) => { + if (n === matchIndex++) { + return replaceWith; + } + + return match; + }; +}