Reactive task lists
This commit is contained in:
parent
12c43b960b
commit
0a361a5ca0
53
src/extensions/libs/markdownItAnchor.js
Normal file
53
src/extensions/libs/markdownItAnchor.js
Normal file
@ -0,0 +1,53 @@
|
||||
export default (md) => {
|
||||
md.core.ruler.before('replacements', 'anchors', (state) => {
|
||||
const anchorHash = {};
|
||||
let headingOpenToken;
|
||||
let headingContent;
|
||||
state.tokens.forEach((token) => {
|
||||
if (token.type === 'heading_open') {
|
||||
headingContent = '';
|
||||
headingOpenToken = token;
|
||||
} else if (token.type === 'heading_close') {
|
||||
headingOpenToken.headingContent = headingContent;
|
||||
|
||||
// According to http://pandoc.org/README.html#extension-auto_identifiers
|
||||
let slug = headingContent
|
||||
.replace(/\s/g, '-') // Replace all spaces and newlines with hyphens
|
||||
.replace(/[\0-,/:-@[-^`{-~]/g, '') // Remove all punctuation, except underscores, hyphens, and periods
|
||||
.toLowerCase(); // Convert all alphabetic characters to lowercase
|
||||
|
||||
// Remove everything up to the first letter
|
||||
let i;
|
||||
for (i = 0; i < slug.length; i += 1) {
|
||||
const charCode = slug.charCodeAt(i);
|
||||
if ((charCode >= 0x61 && charCode <= 0x7A) || charCode > 0x7E) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing left after this, use `section`
|
||||
slug = slug.slice(i) || 'section';
|
||||
|
||||
let anchor = slug;
|
||||
let index = 1;
|
||||
while (Object.prototype.hasOwnProperty.call(anchorHash, anchor)) {
|
||||
anchor = `${slug}-${index}`;
|
||||
index += 1;
|
||||
}
|
||||
anchorHash[anchor] = true;
|
||||
headingOpenToken.headingAnchor = anchor;
|
||||
headingOpenToken.attrs = [
|
||||
['id', anchor],
|
||||
];
|
||||
headingOpenToken = undefined;
|
||||
} else if (headingOpenToken) {
|
||||
headingContent += token.children.reduce((result, child) => {
|
||||
if (child.type !== 'footnote_ref') {
|
||||
return result + child.content;
|
||||
}
|
||||
return result;
|
||||
}, '');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
@ -7,6 +7,7 @@ import markdownitImgsize from 'markdown-it-imsize';
|
||||
import markdownitSub from 'markdown-it-sub';
|
||||
import markdownitSup from 'markdown-it-sup';
|
||||
import markdownitTasklist from './libs/markdownItTasklist';
|
||||
import markdownitAnchor from './libs/markdownItAnchor';
|
||||
import extensionSvc from '../services/extensionSvc';
|
||||
|
||||
const coreBaseRules = [
|
||||
@ -106,58 +107,7 @@ extensionSvc.onInitConverter(0, (markdown, options) => {
|
||||
if (options.tasklist) {
|
||||
markdown.use(markdownitTasklist);
|
||||
}
|
||||
|
||||
markdown.core.ruler.before('replacements', 'anchors', (state) => {
|
||||
const anchorHash = {};
|
||||
let headingOpenToken;
|
||||
let headingContent;
|
||||
state.tokens.forEach((token) => {
|
||||
if (token.type === 'heading_open') {
|
||||
headingContent = '';
|
||||
headingOpenToken = token;
|
||||
} else if (token.type === 'heading_close') {
|
||||
headingOpenToken.headingContent = headingContent;
|
||||
|
||||
// According to http://pandoc.org/README.html#extension-auto_identifiers
|
||||
let slug = headingContent
|
||||
.replace(/\s/g, '-') // Replace all spaces and newlines with hyphens
|
||||
.replace(/[\0-,/:-@[-^`{-~]/g, '') // Remove all punctuation, except underscores, hyphens, and periods
|
||||
.toLowerCase(); // Convert all alphabetic characters to lowercase
|
||||
|
||||
// Remove everything up to the first letter
|
||||
let i;
|
||||
for (i = 0; i < slug.length; i += 1) {
|
||||
const charCode = slug.charCodeAt(i);
|
||||
if ((charCode >= 0x61 && charCode <= 0x7A) || charCode > 0x7E) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing left after this, use `section`
|
||||
slug = slug.slice(i) || 'section';
|
||||
|
||||
let anchor = slug;
|
||||
let index = 1;
|
||||
while (Object.prototype.hasOwnProperty.call(anchorHash, anchor)) {
|
||||
anchor = `${slug}-${index}`;
|
||||
index += 1;
|
||||
}
|
||||
anchorHash[anchor] = true;
|
||||
headingOpenToken.headingAnchor = anchor;
|
||||
headingOpenToken.attrs = [
|
||||
['id', anchor],
|
||||
];
|
||||
headingOpenToken = undefined;
|
||||
} else if (headingOpenToken) {
|
||||
headingContent += token.children.reduce((result, child) => {
|
||||
if (child.type !== 'footnote_ref') {
|
||||
return result + child.content;
|
||||
}
|
||||
return result;
|
||||
}, '');
|
||||
}
|
||||
});
|
||||
});
|
||||
markdown.use(markdownitAnchor);
|
||||
|
||||
// Wrap tables into a div for scrolling
|
||||
markdown.renderer.rules.table_open = (tokens, idx, opts) =>
|
||||
@ -188,22 +138,24 @@ extensionSvc.onInitConverter(0, (markdown, options) => {
|
||||
};
|
||||
});
|
||||
|
||||
extensionSvc.onSectionPreview((elt) => {
|
||||
extensionSvc.onSectionPreview((elt, options, isEditor) => {
|
||||
// Highlight with Prism
|
||||
elt.querySelectorAll('.prism').cl_each((prismElt) => {
|
||||
if (!prismElt.highlightedWithPrism) {
|
||||
if (!prismElt.$highlightedWithPrism) {
|
||||
Prism.highlightElement(prismElt);
|
||||
prismElt.highlightedWithPrism = true;
|
||||
prismElt.$highlightedWithPrism = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Transform task list spans into checkboxes
|
||||
// Transform task spans into checkboxes
|
||||
elt.querySelectorAll('span.task-list-item-checkbox').cl_each((spanElt) => {
|
||||
const checkboxElt = document.createElement('input');
|
||||
checkboxElt.type = 'checkbox';
|
||||
checkboxElt.className = 'task-list-item-checkbox';
|
||||
checkboxElt.checked = spanElt.classList.contains('checked');
|
||||
checkboxElt.disabled = 'disabled';
|
||||
if (!isEditor) {
|
||||
checkboxElt.disabled = 'disabled';
|
||||
}
|
||||
spanElt.parentNode.replaceChild(checkboxElt, spanElt);
|
||||
});
|
||||
});
|
||||
|
@ -167,7 +167,7 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
||||
} else {
|
||||
this.previewElt.appendChild(sectionPreviewElt);
|
||||
}
|
||||
extensionSvc.sectionPreview(sectionPreviewElt, this.options);
|
||||
extensionSvc.sectionPreview(sectionPreviewElt, this.options, true);
|
||||
loadingImages = [
|
||||
...loadingImages,
|
||||
...Array.prototype.slice.call(sectionPreviewElt.getElementsByTagName('img')),
|
||||
|
@ -29,9 +29,9 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
sectionPreview(elt, options) {
|
||||
sectionPreview(elt, options, isEditor) {
|
||||
sectionPreviewListeners.forEach((listener) => {
|
||||
listener(elt, options);
|
||||
listener(elt, options, isEditor);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import './shortcuts';
|
||||
import './keystrokes';
|
||||
import './scrollSync';
|
||||
import './taskChange';
|
||||
|
47
src/services/optional/taskChange.js
Normal file
47
src/services/optional/taskChange.js
Normal file
@ -0,0 +1,47 @@
|
||||
import editorSvc from '../editorSvc';
|
||||
import store from '../../store';
|
||||
|
||||
editorSvc.$on('inited', () => {
|
||||
const getPreviewOffset = (elt) => {
|
||||
let offset = 0;
|
||||
if (!elt || elt === editorSvc.previewElt) {
|
||||
return offset;
|
||||
}
|
||||
let previousSibling = elt.previousSibling;
|
||||
while (previousSibling) {
|
||||
offset += previousSibling.textContent.length;
|
||||
previousSibling = previousSibling.previousSibling;
|
||||
}
|
||||
return offset + getPreviewOffset(elt.parentNode);
|
||||
};
|
||||
|
||||
editorSvc.previewElt.addEventListener('click', (evt) => {
|
||||
if (evt.target.classList.contains('task-list-item-checkbox')) {
|
||||
evt.preventDefault();
|
||||
if (store.getters['content/isCurrentEditable']) {
|
||||
const editorContent = editorSvc.clEditor.getContent();
|
||||
// Use setTimeout to ensure evt.target.checked has the old value
|
||||
setTimeout(() => {
|
||||
// Make sure content has not changed
|
||||
if (editorContent === editorSvc.clEditor.getContent()) {
|
||||
const previewOffset = getPreviewOffset(evt.target);
|
||||
const endOffset = editorSvc.getEditorOffset(previewOffset + 1);
|
||||
if (endOffset != null) {
|
||||
const startOffset = editorContent.lastIndexOf('\n', endOffset) + 1;
|
||||
const line = editorContent.slice(startOffset, endOffset);
|
||||
const match = line.match(/^([ \t]*(?:[*+-]|\d+\.)[ \t]+\[)[ xX](\] .*)/);
|
||||
if (match) {
|
||||
let newContent = editorContent.slice(0, startOffset);
|
||||
newContent += match[1];
|
||||
newContent += evt.target.checked ? ' ' : 'x';
|
||||
newContent += match[2];
|
||||
newContent += editorContent.slice(endOffset);
|
||||
editorSvc.clEditor.setContent(newContent, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user