diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl index 19132622..b7f219b8 100644 --- a/browser/components/markdown.styl +++ b/browser/components/markdown.styl @@ -373,6 +373,36 @@ for name, val in admonition_types color: val[color] content: val[icon] +dl + margin 2rem 0 + padding 0 + display flex + width 100% + flex-wrap wrap + align-items flex-start + border-bottom 1px solid borderColor + background-color tableHeadBgColor + +dt + border-top 1px solid borderColor + font-weight bold + text-align right + overflow hidden + flex-basis 20% + padding 0.4rem 0.9rem + box-sizing border-box + +dd + border-top 1px solid borderColor + flex-basis 80% + padding 0.4rem 0.9rem + min-height 2.5rem + background-color $ui-noteDetail-backgroundColor + box-sizing border-box + +dd + dd + margin-left 20% + pre.fence flex-wrap wrap @@ -436,6 +466,14 @@ body[data-theme="dark"] kbd background-color themeDarkBorder color themeDarkText + dl + border-color themeDarkBorder + background-color themeDarkTableHead + dt + border-color themeDarkBorder + dd + border-color themeDarkBorder + background-color themeDarkPreview themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%) @@ -463,6 +501,14 @@ body[data-theme="solarized-dark"] border-color themeSolarizedDarkTableBorder &:last-child border-right solid 1px themeSolarizedDarkTableBorder + dl + border-color themeDarkBorder + background-color themeSolarizedDarkTableHead + dt + border-color themeDarkBorder + dd + border-color themeDarkBorder + background-color $ui-solarized-dark-noteDetail-backgroundColor themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%) @@ -492,6 +538,14 @@ body[data-theme="monokai"] border-right solid 1px themeMonokaiTableBorder kbd background-color themeDarkBackground + dl + border-color themeDarkBorder + background-color themeMonokaiTableHead + dt + border-color themeDarkBorder + dd + border-color themeDarkBorder + background-color $ui-monokai-noteDetail-backgroundColor themeDraculaTableOdd = $ui-dracula-noteDetail-backgroundColor themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%) @@ -520,4 +574,12 @@ body[data-theme="dracula"] &:last-child border-right solid 1px themeDraculaTableBorder kbd - background-color themeDarkBackground \ No newline at end of file + background-color themeDarkBackground + dl + border-color themeDarkBorder + background-color themeDraculaTableHead + dt + border-color themeDarkBorder + dd + border-color themeDarkBorder + background-color $ui-dracula-noteDetail-backgroundColor diff --git a/browser/lib/markdown-it-deflist.js b/browser/lib/markdown-it-deflist.js new file mode 100644 index 00000000..db14c636 --- /dev/null +++ b/browser/lib/markdown-it-deflist.js @@ -0,0 +1,232 @@ +'use strict' + +module.exports = function definitionListPlugin (md) { + var isSpace = md.utils.isSpace + + // Search `[:~][\n ]`, returns next pos after marker on success + // or -1 on fail. + function skipMarker (state, line) { + let start = state.bMarks[line] + state.tShift[line] + const max = state.eMarks[line] + + if (start >= max) { return -1 } + + // Check bullet + const marker = state.src.charCodeAt(start++) + if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1 } + + const pos = state.skipSpaces(start) + + // require space after ":" + if (start === pos) { return -1 } + + return start + } + + function markTightParagraphs (state, idx) { + const level = state.level + 2 + + let i + let l + for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { + if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { + state.tokens[i + 2].hidden = true + state.tokens[i].hidden = true + i += 2 + } + } + } + + function deflist (state, startLine, endLine, silent) { + var ch, + contentStart, + ddLine, + dtLine, + itemLines, + listLines, + listTokIdx, + max, + newEndLine, + nextLine, + offset, + oldDDIndent, + oldIndent, + oldLineMax, + oldParentType, + oldSCount, + oldTShift, + oldTight, + pos, + prevEmptyEnd, + tight, + token + + if (silent) { + // quirk: validation mode validates a dd block only, not a whole deflist + if (state.ddIndent < 0) { return false } + return skipMarker(state, startLine) >= 0 + } + + nextLine = startLine + 1 + if (nextLine >= endLine) { return false } + + if (state.isEmpty(nextLine)) { + nextLine++ + if (nextLine >= endLine) { return false } + } + + if (state.sCount[nextLine] < state.blkIndent) { return false } + contentStart = skipMarker(state, nextLine) + if (contentStart < 0) { return false } + + // Start list + listTokIdx = state.tokens.length + tight = true + + token = state.push('dl_open', 'dl', 1) + token.map = listLines = [ startLine, 0 ] + + // + // Iterate list items + // + + dtLine = startLine + ddLine = nextLine + + // One definition list can contain multiple DTs, + // and one DT can be followed by multiple DDs. + // + // Thus, there is two loops here, and label is + // needed to break out of the second one + // + /* eslint no-labels:0,block-scoped-var:0 */ + OUTER: + for (;;) { + prevEmptyEnd = false + + token = state.push('dt_open', 'dt', 1) + token.map = [ dtLine, dtLine ] + + token = state.push('inline', '', 0) + token.map = [ dtLine, dtLine ] + token.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim() + token.children = [] + + token = state.push('dt_close', 'dt', -1) + + for (;;) { + token = state.push('dd_open', 'dd', 1) + token.map = itemLines = [ ddLine, 0 ] + + pos = contentStart + max = state.eMarks[ddLine] + offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine]) + + while (pos < max) { + ch = state.src.charCodeAt(pos) + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - offset % 4 + } else { + offset++ + } + } else { + break + } + + pos++ + } + + contentStart = pos + + oldTight = state.tight + oldDDIndent = state.ddIndent + oldIndent = state.blkIndent + oldTShift = state.tShift[ddLine] + oldSCount = state.sCount[ddLine] + oldParentType = state.parentType + state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2 + state.tShift[ddLine] = contentStart - state.bMarks[ddLine] + state.sCount[ddLine] = offset + state.tight = true + state.parentType = 'deflist' + + newEndLine = ddLine + while (++newEndLine < endLine && (state.sCount[newEndLine] >= state.sCount[ddLine] || state.isEmpty(newEndLine))) { + } + + oldLineMax = state.lineMax + state.lineMax = newEndLine + + state.md.block.tokenize(state, ddLine, newEndLine, true) + + state.lineMax = oldLineMax + + // If any of list item is tight, mark list as tight + if (!state.tight || prevEmptyEnd) { + tight = false + } + // Item become loose if finish with empty line, + // but we should filter last element, because it means list finish + prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1) + + state.tShift[ddLine] = oldTShift + state.sCount[ddLine] = oldSCount + state.tight = oldTight + state.parentType = oldParentType + state.blkIndent = oldIndent + state.ddIndent = oldDDIndent + + token = state.push('dd_close', 'dd', -1) + + itemLines[1] = nextLine = state.line + + if (nextLine >= endLine) { break OUTER } + + if (state.sCount[nextLine] < state.blkIndent) { break OUTER } + contentStart = skipMarker(state, nextLine) + if (contentStart < 0) { break } + + ddLine = nextLine + + // go to the next loop iteration: + // insert DD tag and repeat checking + } + + if (nextLine >= endLine) { break } + dtLine = nextLine + + if (state.isEmpty(dtLine)) { break } + if (state.sCount[dtLine] < state.blkIndent) { break } + + ddLine = dtLine + 1 + if (ddLine >= endLine) { break } + if (state.isEmpty(ddLine)) { ddLine++ } + if (ddLine >= endLine) { break } + + if (state.sCount[ddLine] < state.blkIndent) { break } + contentStart = skipMarker(state, ddLine) + if (contentStart < 0) { break } + + // go to the next loop iteration: + // insert DT and DD tags and repeat checking + } + + // Finilize list + token = state.push('dl_close', 'dl', -1) + + listLines[1] = nextLine + + state.line = nextLine + + // mark paragraphs tight if needed + if (tight) { + markTightParagraphs(state, listTokIdx) + } + + return true + } + + md.block.ruler.before('paragraph', 'deflist', deflist, { alt: [ 'paragraph', 'reference' ] }) +} diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index be04fb08..13ef758a 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -127,8 +127,11 @@ class Markdown { } }) this.md.use(require('markdown-it-kbd')) - this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']}) + this.md.use(require('markdown-it-abbr')) + this.md.use(require('markdown-it-sub')) + this.md.use(require('markdown-it-sup')) + this.md.use(require('./markdown-it-deflist')) this.md.use(require('./markdown-it-frontmatter')) this.md.use(require('./markdown-it-fence'), { @@ -268,9 +271,12 @@ class Markdown { this.md.renderer.render = (tokens, options, env) => { tokens.forEach((token) => { switch (token.type) { - case 'heading_open': - case 'paragraph_open': case 'blockquote_open': + case 'dd_open': + case 'dt_open': + case 'heading_open': + case 'list_item_open': + case 'paragraph_open': case 'table_open': token.attrPush(['data-line', token.map[0]]) } diff --git a/package.json b/package.json index 8c1c6dc8..47b6d2bd 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "lodash": "^4.11.1", "lodash-move": "^1.1.1", "markdown-it": "^6.0.1", + "markdown-it-abbr": "^1.0.4", "markdown-it-admonition": "^1.0.4", "markdown-it-emoji": "^1.1.1", "markdown-it-footnote": "^3.0.0", @@ -82,6 +83,8 @@ "markdown-it-named-headers": "^0.0.4", "markdown-it-plantuml": "^1.1.0", "markdown-it-smartarrows": "^1.0.1", + "markdown-it-sub": "^1.0.0", + "markdown-it-sup": "^1.0.0", "markdown-toc": "^1.2.0", "mdurl": "^1.0.1", "mermaid": "^8.0.0-rc.8", diff --git a/tests/fixtures/markdowns.js b/tests/fixtures/markdowns.js index 312e6c18..0ee80909 100644 --- a/tests/fixtures/markdowns.js +++ b/tests/fixtures/markdowns.js @@ -50,6 +50,58 @@ const smartQuotes = 'This is a "QUOTE".' const breaks = 'This is the first line.\nThis is the second line.' +const abbrevations = ` +## abbr + +The HTML specification +is maintained by the W3C. + +*[HTML]: Hyper Text Markup Language +*[W3C]: World Wide Web Consortium +` + +const subTexts = ` +## sub + +H~2~0 +` + +const supTexts = ` +## sup + +29^th^ +` + +const deflists = ` +## definition list + +### list 1 + +Term 1 + ~ Definition 1 + +Term 2 + ~ Definition 2a + ~ Definition 2b + +Term 3 +~ + + +### list 2 + +Term 1 + +: Definition 1 + +Term 2 with *inline markup* + +: Definition 2 + + { some code, part of Definition 2 } + + Third paragraph of definition 2. +` const shortcuts = 'Ctrl\n\n[[Ctrl]]' export default { @@ -59,5 +111,9 @@ export default { checkboxes, smartQuotes, breaks, + abbrevations, + subTexts, + supTexts, + deflists, shortcuts } diff --git a/tests/lib/markdown-test.js b/tests/lib/markdown-test.js index 0e330a65..46ae5941 100644 --- a/tests/lib/markdown-test.js +++ b/tests/lib/markdown-test.js @@ -44,6 +44,26 @@ test('Markdown.render() should render line breaks correctly', t => { t.snapshot(renderedNonBreaks) }) +test('Markdown.render() should renders abbrevations correctly', t => { + const rendered = md.render(markdownFixtures.abbrevations) + t.snapshot(rendered) +}) + +test('Markdown.render() should renders sub correctly', t => { + const rendered = md.render(markdownFixtures.subTexts) + t.snapshot(rendered) +}) + +test('Markdown.render() should renders sup correctly', t => { + const rendered = md.render(markdownFixtures.supTexts) + t.snapshot(rendered) +}) + +test('Markdown.render() should renders definition lists correctly', t => { + const rendered = md.render(markdownFixtures.deflists) + t.snapshot(rendered) +}) + test('Markdown.render() should render shortcuts correctly', t => { const rendered = md.render(markdownFixtures.shortcuts) t.snapshot(rendered) diff --git a/tests/lib/snapshots/markdown-test.js.md b/tests/lib/snapshots/markdown-test.js.md index 6fe17835..eefb232c 100644 --- a/tests/lib/snapshots/markdown-test.js.md +++ b/tests/lib/snapshots/markdown-test.js.md @@ -33,13 +33,22 @@ Generated by [AVA](https://ava.li). `c=pmsqrta2+b2c = pmsqrt{a^2 + b^2}␊ ` +## Markdown.render() should renders abbrevations correctly + +> Snapshot 1 + + `

abbr

␊ +

The HTML specification
␊ + is maintained by the W3C.

␊ + ` + ## Markdown.render() should renders checkboxes > Snapshot 1 `␊ ` @@ -54,6 +63,37 @@ Generated by [AVA](https://ava.li). ␊ ` +## Markdown.render() should renders definition lists correctly + +> Snapshot 1 + + `

definition list

␊ +

list 1

␊ +
␊ +
Term 1
␊ +
Definition 1
␊ +
Term 2
␊ +
Definition 2a
␊ +
Definition 2b
␊ +
␊ +

Term 3
␊ + ~

␊ +

list 2

␊ +
␊ +
Term 1
␊ +
␊ +

Definition 1

␊ +
␊ +
Term 2 with inline markup
␊ +
␊ +

Definition 2

␊ +
  { some code, part of Definition 2 }␊
+    
␊ +

Third paragraph of definition 2.

␊ +
␊ +
␊ + ` + ## Markdown.render() should renders markdown correctly > Snapshot 1 @@ -63,31 +103,47 @@ Generated by [AVA](https://ava.li).

Docs 📝


Article Archive 📚


Community 🍻

␊ ` +## Markdown.render() should renders sub correctly + +> Snapshot 1 + + `

sub

␊ +

H20

␊ + ` + +## Markdown.render() should renders sup correctly + +> Snapshot 1 + + `

sup

␊ +

29th

␊ + ` + ## Markdown.render() should text with quotes correctly > Snapshot 1 diff --git a/tests/lib/snapshots/markdown-test.js.snap b/tests/lib/snapshots/markdown-test.js.snap index c1cf5903..b60aae57 100644 Binary files a/tests/lib/snapshots/markdown-test.js.snap and b/tests/lib/snapshots/markdown-test.js.snap differ diff --git a/yarn.lock b/yarn.lock index b05daf78..35c7b9db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5848,6 +5848,10 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +markdown-it-abbr@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/markdown-it-abbr/-/markdown-it-abbr-1.0.4.tgz#d66b5364521cbb3dd8aa59dadfba2fb6865c8fd8" + markdown-it-admonition@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/markdown-it-admonition/-/markdown-it-admonition-1.0.4.tgz#d7bbc7eb1fe6168fc8cc304de7a9d8c993acb2f5" @@ -5888,6 +5892,14 @@ markdown-it-smartarrows@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/markdown-it-smartarrows/-/markdown-it-smartarrows-1.0.1.tgz#b570e9c0ff9812e0db6ace19afa5ba12b64bb9a7" +markdown-it-sub@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz#375fd6026eae7ddcb012497f6411195ea1e3afe8" + +markdown-it-sup@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz#cb9c9ff91a5255ac08f3fd3d63286e15df0a1fc3" + markdown-it@^5.0.3: version "5.1.0" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-5.1.0.tgz#25286b8465bac496f3f1b77eed544643e9bd718d"