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 markdownitSub from 'markdown-it-sub';
|
||||||
import markdownitSup from 'markdown-it-sup';
|
import markdownitSup from 'markdown-it-sup';
|
||||||
import markdownitTasklist from './libs/markdownItTasklist';
|
import markdownitTasklist from './libs/markdownItTasklist';
|
||||||
|
import markdownitAnchor from './libs/markdownItAnchor';
|
||||||
import extensionSvc from '../services/extensionSvc';
|
import extensionSvc from '../services/extensionSvc';
|
||||||
|
|
||||||
const coreBaseRules = [
|
const coreBaseRules = [
|
||||||
@ -106,58 +107,7 @@ extensionSvc.onInitConverter(0, (markdown, options) => {
|
|||||||
if (options.tasklist) {
|
if (options.tasklist) {
|
||||||
markdown.use(markdownitTasklist);
|
markdown.use(markdownitTasklist);
|
||||||
}
|
}
|
||||||
|
markdown.use(markdownitAnchor);
|
||||||
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;
|
|
||||||
}, '');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wrap tables into a div for scrolling
|
// Wrap tables into a div for scrolling
|
||||||
markdown.renderer.rules.table_open = (tokens, idx, opts) =>
|
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
|
// Highlight with Prism
|
||||||
elt.querySelectorAll('.prism').cl_each((prismElt) => {
|
elt.querySelectorAll('.prism').cl_each((prismElt) => {
|
||||||
if (!prismElt.highlightedWithPrism) {
|
if (!prismElt.$highlightedWithPrism) {
|
||||||
Prism.highlightElement(prismElt);
|
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) => {
|
elt.querySelectorAll('span.task-list-item-checkbox').cl_each((spanElt) => {
|
||||||
const checkboxElt = document.createElement('input');
|
const checkboxElt = document.createElement('input');
|
||||||
checkboxElt.type = 'checkbox';
|
checkboxElt.type = 'checkbox';
|
||||||
checkboxElt.className = 'task-list-item-checkbox';
|
checkboxElt.className = 'task-list-item-checkbox';
|
||||||
checkboxElt.checked = spanElt.classList.contains('checked');
|
checkboxElt.checked = spanElt.classList.contains('checked');
|
||||||
|
if (!isEditor) {
|
||||||
checkboxElt.disabled = 'disabled';
|
checkboxElt.disabled = 'disabled';
|
||||||
|
}
|
||||||
spanElt.parentNode.replaceChild(checkboxElt, spanElt);
|
spanElt.parentNode.replaceChild(checkboxElt, spanElt);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -167,7 +167,7 @@ const editorSvc = Object.assign(new Vue(), editorSvcDiscussions, editorSvcUtils,
|
|||||||
} else {
|
} else {
|
||||||
this.previewElt.appendChild(sectionPreviewElt);
|
this.previewElt.appendChild(sectionPreviewElt);
|
||||||
}
|
}
|
||||||
extensionSvc.sectionPreview(sectionPreviewElt, this.options);
|
extensionSvc.sectionPreview(sectionPreviewElt, this.options, true);
|
||||||
loadingImages = [
|
loadingImages = [
|
||||||
...loadingImages,
|
...loadingImages,
|
||||||
...Array.prototype.slice.call(sectionPreviewElt.getElementsByTagName('img')),
|
...Array.prototype.slice.call(sectionPreviewElt.getElementsByTagName('img')),
|
||||||
|
@ -29,9 +29,9 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
sectionPreview(elt, options) {
|
sectionPreview(elt, options, isEditor) {
|
||||||
sectionPreviewListeners.forEach((listener) => {
|
sectionPreviewListeners.forEach((listener) => {
|
||||||
listener(elt, options);
|
listener(elt, options, isEditor);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
import './shortcuts';
|
import './shortcuts';
|
||||||
import './keystrokes';
|
import './keystrokes';
|
||||||
import './scrollSync';
|
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