diff --git a/package-lock.json b/package-lock.json index fadccd91..b190d0df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8067,6 +8067,11 @@ "resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-3.0.1.tgz", "integrity": "sha1-fzcwdHysyG4v4L+KF6cQ80eRUXo=" }, + "markdown-it-mark": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-2.0.0.tgz", + "integrity": "sha1-RqGqlHEFrtgYiXjgoBYXnkBPQsc=" + }, "markdown-it-pandoc-renderer": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/markdown-it-pandoc-renderer/-/markdown-it-pandoc-renderer-1.1.3.tgz", diff --git a/package.json b/package.json index b025326e..22eca173 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "markdown-it-deflist": "^2.0.2", "markdown-it-emoji": "^1.3.0", "markdown-it-footnote": "^3.0.1", + "markdown-it-mark": "^2.0.0", "markdown-it-pandoc-renderer": "1.1.3", "markdown-it-sub": "^1.0.0", "markdown-it-sup": "^1.0.0", diff --git a/src/components/menus/MainMenu.vue b/src/components/menus/MainMenu.vue index e44b3b63..fab1b4da 100644 --- a/src/components/menus/MainMenu.vue +++ b/src/components/menus/MainMenu.vue @@ -43,7 +43,7 @@ -
File history
+
History
Track and restore file revisions.
diff --git a/src/data/defaultFileProperties.yml b/src/data/defaultFileProperties.yml index 6b2abe7e..4047a108 100644 --- a/src/data/defaultFileProperties.yml +++ b/src/data/defaultFileProperties.yml @@ -28,9 +28,11 @@ extensions: #fence: true #footnote: true #linkify: true + #mark: true #sub: true #sup: true #table: true + #tasklist: true #typographer: true ## Emoji extension diff --git a/src/data/presets.js b/src/data/presets.js index b1ad84fc..c82c165d 100644 --- a/src/data/presets.js +++ b/src/data/presets.js @@ -7,9 +7,11 @@ const zero = { fence: false, footnote: false, linkify: false, + mark: false, sub: false, sup: false, table: false, + tasklist: false, typographer: false, }, emoji: { @@ -38,6 +40,7 @@ export default { fence: true, linkify: true, table: true, + tasklist: true, }, emoji: { enabled: true, @@ -52,9 +55,11 @@ export default { fence: true, footnote: true, linkify: true, + mark: true, sub: true, sup: true, table: true, + tasklist: true, typographer: true, }, emoji: { diff --git a/src/extensions/markdownExtension.js b/src/extensions/markdownExtension.js index 4fb2b9ed..d9aab82a 100644 --- a/src/extensions/markdownExtension.js +++ b/src/extensions/markdownExtension.js @@ -4,6 +4,8 @@ import markdownitDeflist from 'markdown-it-deflist'; import markdownitFootnote from 'markdown-it-footnote'; import markdownitSub from 'markdown-it-sub'; import markdownitSup from 'markdown-it-sup'; +import markdownitMark from 'markdown-it-mark'; +import markdownitTasklist from 'markdown-it-task-lists'; import extensionSvc from '../services/extensionSvc'; const coreBaseRules = [ @@ -88,12 +90,18 @@ extensionSvc.onInitConverter(0, (markdown, options) => { if (options.footnote) { markdown.use(markdownitFootnote); } + if (options.mark) { + markdown.use(markdownitMark); + } if (options.sub) { markdown.use(markdownitSub); } if (options.sup) { markdown.use(markdownitSup); } + if (options.tasklist) { + markdown.use(markdownitTasklist); + } markdown.core.ruler.before('replacements', 'anchors', (state) => { const anchorHash = {}; @@ -106,7 +114,7 @@ extensionSvc.onInitConverter(0, (markdown, options) => { } else if (token.type === 'heading_close') { headingOpenToken.headingContent = headingContent; - // Slugify according to http://pandoc.org/README.html#extension-auto_identifiers + // 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 diff --git a/src/libs/markdownItTasklist.js b/src/libs/markdownItTasklist.js new file mode 100644 index 00000000..4f49ac69 --- /dev/null +++ b/src/libs/markdownItTasklist.js @@ -0,0 +1,97 @@ +// Credit: https://github.com/revin/markdown-it-task-lists + +module.exports = (md) => { + md.core.ruler.after('inline', 'github-task-lists', (state) => { + const tokens = state.tokens; + for (let i = 2; i < tokens.length; i += 1) { + const token = tokens[i]; + if (token.type === 'inline' && + tokens[i - 1].type === 'paragraph_open' && + tokens[i - 2].type === 'list_item_open' && + startsWithTodoMarkdown(tokens[i]) + ) { + todoify(tokens[i], state.Token); + attrSet(tokens[i-2], 'class', 'task-list-item' + (!disableCheckboxes ? ' enabled' : '')); + attrSet(tokens[parentToken(tokens, i-2)], 'class', 'contains-task-list'); + } + } + }); +}; + +function attrSet(token, name, value) { + var index = token.attrIndex(name); + var attr = [name, value]; + + if (index < 0) { + token.attrPush(attr); + } else { + token.attrs[index] = attr; + } +} + +function parentToken(tokens, index) { + var targetLevel = tokens[index].level - 1; + for (var i = index - 1; i >= 0; i--) { + if (tokens[i].level === targetLevel) { + return i; + } + } + return -1; +} + +function todoify(token, TokenConstructor) { + token.children.unshift(makeCheckbox(token, TokenConstructor)); + token.children[1].content = token.children[1].content.slice(3); + token.content = token.content.slice(3); + + if (useLabelWrapper) { + if (useLabelAfter) { + token.children.pop(); + + // Use large random number as id property of the checkbox. + var id = 'task-item-' + Math.ceil(Math.random() * (10000 * 1000) - 1000); + token.children[0].content = token.children[0].content.slice(0, -1) + ' id="' + id + '">'; + token.children.push(afterLabel(token.content, id, TokenConstructor)); + } else { + token.children.unshift(beginLabel(TokenConstructor)); + token.children.push(endLabel(TokenConstructor)); + } + } +} + +function makeCheckbox(token, TokenConstructor) { + var checkbox = new TokenConstructor('html_inline', '', 0); + var disabledAttr = disableCheckboxes ? ' disabled="" ' : ''; + if (token.content.indexOf('[ ] ') === 0) { + checkbox.content = ''; + } else if (token.content.indexOf('[x] ') === 0 || token.content.indexOf('[X] ') === 0) { + checkbox.content = ''; + } + return checkbox; +} + +// these next two functions are kind of hacky; probably should really be a +// true block-level token with .tag=='label' +function beginLabel(TokenConstructor) { + var token = new TokenConstructor('html_inline', '', 0); + token.content = ''; + return token; +} + +function afterLabel(content, id, TokenConstructor) { + var token = new TokenConstructor('html_inline', '', 0); + token.content = ''; + token.attrs = [{for: id}]; + return token; +} + +function startsWithTodoMarkdown(token) { + // leading whitespace in a list item is already trimmed off by markdown-it + return token.content.indexOf('[ ] ') === 0 || token.content.indexOf('[x] ') === 0 || token.content.indexOf('[X] ') === 0; +}