From a12e3a6f88760229ed4bc42b5e3c86560236472e Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Thu, 20 May 2021 12:24:45 +0430 Subject: [PATCH 01/17] feat: mdv syntax --- src/core/parser/markdown/directive/index.ts | 32 ++ .../micromark-directive/factory-attributes.js | 300 ++++++++++++++++++ .../micromark-directive/factory-label.js | 122 +++++++ .../micromark-directive/factory-name.js | 37 +++ .../directive/micromark-directive/html.js | 193 +++++++++++ .../directive/micromark-directive/index.js | 2 + .../directive/micromark-directive/syntax.js | 14 + .../tokenize-directive-container.js | 258 +++++++++++++++ .../tokenize-directive-leaf.js | 97 ++++++ .../tokenize-directive-text.js | 92 ++++++ .../remark-directive/from-markdown.js | 147 +++++++++ .../directive/remark-directive/to-markdown.js | 169 ++++++++++ .../remark-plugin.ts} | 38 ++- src/core/parser/markdown/index.ts | 8 +- .../parser/markdown/plugin/remark-prose.ts | 53 ---- src/types/core.ts | 1 - 16 files changed, 1483 insertions(+), 80 deletions(-) create mode 100644 src/core/parser/markdown/directive/index.ts create mode 100644 src/core/parser/markdown/directive/micromark-directive/factory-attributes.js create mode 100644 src/core/parser/markdown/directive/micromark-directive/factory-label.js create mode 100644 src/core/parser/markdown/directive/micromark-directive/factory-name.js create mode 100644 src/core/parser/markdown/directive/micromark-directive/html.js create mode 100644 src/core/parser/markdown/directive/micromark-directive/index.js create mode 100644 src/core/parser/markdown/directive/micromark-directive/syntax.js create mode 100644 src/core/parser/markdown/directive/micromark-directive/tokenize-directive-container.js create mode 100644 src/core/parser/markdown/directive/micromark-directive/tokenize-directive-leaf.js create mode 100644 src/core/parser/markdown/directive/micromark-directive/tokenize-directive-text.js create mode 100644 src/core/parser/markdown/directive/remark-directive/from-markdown.js create mode 100644 src/core/parser/markdown/directive/remark-directive/to-markdown.js rename src/core/parser/markdown/{plugin/directive.ts => directive/remark-plugin.ts} (56%) delete mode 100644 src/core/parser/markdown/plugin/remark-prose.ts diff --git a/src/core/parser/markdown/directive/index.ts b/src/core/parser/markdown/directive/index.ts new file mode 100644 index 000000000..0595daae8 --- /dev/null +++ b/src/core/parser/markdown/directive/index.ts @@ -0,0 +1,32 @@ +// https://github.com/remarkjs/remark-directive/blob/main/index.js +import syntax from './micromark-directive/syntax' +import fromMarkdown from './remark-directive/from-markdown' +import toMarkdown from './remark-directive/to-markdown' + +let warningIssued + +module.exports = directive + +function directive() { + const data = this.data() + + /* istanbul ignore next - old remark. */ + if ( + !warningIssued && + ((this.Parser && this.Parser.prototype && this.Parser.prototype.blockTokenizers) || + (this.Compiler && this.Compiler.prototype && this.Compiler.prototype.visitors)) + ) { + warningIssued = true + console.warn('[remark-directive] Warning: please upgrade to remark 13 to use this plugin') + } + + add('micromarkExtensions', syntax()) + add('fromMarkdownExtensions', fromMarkdown) + add('toMarkdownExtensions', toMarkdown) + + function add(field, value) { + /* istanbul ignore if - other extensions. */ + if (data[field]) data[field].push(value) + else data[field] = [value] + } +} diff --git a/src/core/parser/markdown/directive/micromark-directive/factory-attributes.js b/src/core/parser/markdown/directive/micromark-directive/factory-attributes.js new file mode 100644 index 000000000..983a9f00f --- /dev/null +++ b/src/core/parser/markdown/directive/micromark-directive/factory-attributes.js @@ -0,0 +1,300 @@ +'use strict' + +module.exports = createAttributes + +var asciiAlpha = require('micromark/dist/character/ascii-alpha') +var asciiAlphanumeric = require('micromark/dist/character/ascii-alphanumeric') +var markdownLineEnding = require('micromark/dist/character/markdown-line-ending') +var markdownLineEndingOrSpace = require('micromark/dist/character/markdown-line-ending-or-space') +var markdownSpace = require('micromark/dist/character/markdown-space') +var createWhitespace = require('micromark/dist/tokenize/factory-whitespace') +var createSpace = require('micromark/dist/tokenize/factory-space') + +/* eslint-disable-next-line max-params */ +function createAttributes( + effects, + ok, + nok, + attributesType, + attributesMarkerType, + attributeType, + attributeIdType, + attributeClassType, + attributeNameType, + attributeInitializerType, + attributeValueLiteralType, + attributeValueType, + attributeValueMarker, + attributeValueData, + disallowEol +) { + var type + var marker + + return start + + function start(code) { + // Always a `{` + effects.enter(attributesType) + effects.enter(attributesMarkerType) + effects.consume(code) + effects.exit(attributesMarkerType) + return between + } + + function between(code) { + if (code === 35 /* `#` */) { + type = attributeIdType + return shortcutStart(code) + } + + if (code === 46 /* `.` */) { + type = attributeClassType + return shortcutStart(code) + } + + if (code === 58 /* `:` */ || code === 95 /* `_` */ || asciiAlpha(code)) { + effects.enter(attributeType) + effects.enter(attributeNameType) + effects.consume(code) + return name + } + + if (disallowEol && markdownSpace(code)) { + return createSpace(effects, between, 'whitespace')(code) + } + + if (!disallowEol && markdownLineEndingOrSpace(code)) { + return createWhitespace(effects, between)(code) + } + + return end(code) + } + + function shortcutStart(code) { + effects.enter(attributeType) + effects.enter(type) + effects.enter(type + 'Marker') + effects.consume(code) + effects.exit(type + 'Marker') + return shortcutStartAfter + } + + function shortcutStartAfter(code) { + if ( + code === null /* EOF */ || + code === 34 /* `"` */ || + code === 35 /* `#` */ || + code === 39 /* `'` */ || + code === 46 /* `.` */ || + code === 60 /* `<` */ || + code === 61 /* `=` */ || + code === 62 /* `>` */ || + code === 96 /* `` ` `` */ || + code === 125 /* `}` */ || + markdownLineEndingOrSpace(code) + ) { + return nok(code) + } + + effects.enter(type + 'Value') + effects.consume(code) + return shortcut + } + + function shortcut(code) { + if ( + code === null /* EOF */ || + code === 34 /* `"` */ || + code === 39 /* `'` */ || + code === 60 /* `<` */ || + code === 61 /* `=` */ || + code === 62 /* `>` */ || + code === 96 /* `` ` `` */ + ) { + return nok(code) + } + + if ( + code === 35 /* `#` */ || + code === 46 /* `.` */ || + code === 125 /* `}` */ || + markdownLineEndingOrSpace(code) + ) { + effects.exit(type + 'Value') + effects.exit(type) + effects.exit(attributeType) + return between(code) + } + + effects.consume(code) + return shortcut + } + + function name(code) { + if ( + code === 45 /* `-` */ || + code === 46 /* `.` */ || + code === 58 /* `:` */ || + code === 95 /* `_` */ || + asciiAlphanumeric(code) + ) { + effects.consume(code) + return name + } + + effects.exit(attributeNameType) + + if (disallowEol && markdownSpace(code)) { + return createSpace(effects, nameAfter, 'whitespace')(code) + } + + if (!disallowEol && markdownLineEndingOrSpace(code)) { + return createWhitespace(effects, nameAfter)(code) + } + + return nameAfter(code) + } + + function nameAfter(code) { + if (code === 61 /* `=` */) { + effects.enter(attributeInitializerType) + effects.consume(code) + effects.exit(attributeInitializerType) + return valueBefore + } + + // Attribute w/o value. + effects.exit(attributeType) + return between(code) + } + + function valueBefore(code) { + if ( + code === null /* EOF */ || + code === 60 /* `<` */ || + code === 61 /* `=` */ || + code === 62 /* `>` */ || + code === 96 /* `` ` `` */ || + code === 125 /* `}` */ || + (disallowEol && markdownLineEnding(code)) + ) { + return nok(code) + } + + if (code === 34 /* `"` */ || code === 39 /* `'` */) { + effects.enter(attributeValueLiteralType) + effects.enter(attributeValueMarker) + effects.consume(code) + effects.exit(attributeValueMarker) + marker = code + return valueQuotedStart + } + + if (disallowEol && markdownSpace(code)) { + return createSpace(effects, valueBefore, 'whitespace')(code) + } + + if (!disallowEol && markdownLineEndingOrSpace(code)) { + return createWhitespace(effects, valueBefore)(code) + } + + effects.enter(attributeValueType) + effects.enter(attributeValueData) + effects.consume(code) + marker = undefined + return valueUnquoted + } + + function valueUnquoted(code) { + if ( + code === null /* EOF */ || + code === 34 /* `"` */ || + code === 39 /* `'` */ || + code === 60 /* `<` */ || + code === 61 /* `=` */ || + code === 62 /* `>` */ || + code === 96 /* `` ` `` */ + ) { + return nok(code) + } + + if (code === 125 /* `}` */ || markdownLineEndingOrSpace(code)) { + effects.exit(attributeValueData) + effects.exit(attributeValueType) + effects.exit(attributeType) + return between(code) + } + + effects.consume(code) + return valueUnquoted + } + + function valueQuotedStart(code) { + if (code === marker) { + effects.enter(attributeValueMarker) + effects.consume(code) + effects.exit(attributeValueMarker) + effects.exit(attributeValueLiteralType) + effects.exit(attributeType) + return valueQuotedAfter + } + + effects.enter(attributeValueType) + return valueQuotedBetween(code) + } + + function valueQuotedBetween(code) { + if (code === marker) { + effects.exit(attributeValueType) + return valueQuotedStart(code) + } + + if (code === null /* EOF */) { + return nok(code) + } + + // Note: blank lines can’t exist in content. + if (markdownLineEnding(code)) { + return disallowEol + ? nok(code) + : createWhitespace(effects, valueQuotedBetween)(code) + } + + effects.enter(attributeValueData) + effects.consume(code) + return valueQuoted + } + + function valueQuoted(code) { + if ( + code === marker || + code === null /* EOF */ || + markdownLineEnding(code) + ) { + effects.exit(attributeValueData) + return valueQuotedBetween(code) + } + + effects.consume(code) + return valueQuoted + } + + function valueQuotedAfter(code) { + return code === 125 /* `}` */ || markdownLineEndingOrSpace(code) + ? between(code) + : end(code) + } + + function end(code) { + if (code === 125 /* `}` */) { + effects.enter(attributesMarkerType) + effects.consume(code) + effects.exit(attributesMarkerType) + effects.exit(attributesType) + return ok + } + + return nok(code) + } +} diff --git a/src/core/parser/markdown/directive/micromark-directive/factory-label.js b/src/core/parser/markdown/directive/micromark-directive/factory-label.js new file mode 100644 index 000000000..458fa5cb5 --- /dev/null +++ b/src/core/parser/markdown/directive/micromark-directive/factory-label.js @@ -0,0 +1,122 @@ +module.exports = createLabel + +var markdownLineEnding = require('micromark/dist/character/markdown-line-ending') + +// This is a fork of: +// +// to allow empty labels, balanced brackets (such as for nested directives), +// text instead of strings, and optionally disallows EOLs. + +// eslint-disable-next-line max-params +function createLabel( + effects, + ok, + nok, + type, + markerType, + stringType, + disallowEol +) { + var size = 0 + var balance = 0 + + return start + + function start(code) { + /* istanbul ignore if - always `[` */ + if (code !== 91 /* `[` */) throw new Error('expected `[`') + effects.enter(type) + effects.enter(markerType) + effects.consume(code) + effects.exit(markerType) + return afterStart + } + + function afterStart(code) { + if (code === 93 /* `]` */) { + effects.enter(markerType) + effects.consume(code) + effects.exit(markerType) + effects.exit(type) + return ok + } + + effects.enter(stringType) + return atBreak(code) + } + + function atBreak(code) { + if ( + code === null /* EOF */ || + /* */ + size > 999 + ) { + return nok(code) + } + + if (code === 93 /* `]` */ && !balance--) { + return atClosingBrace(code) + } + + if (markdownLineEnding(code)) { + if (disallowEol) { + return nok(code) + } + + effects.enter('lineEnding') + effects.consume(code) + effects.exit('lineEnding') + return atBreak + } + + effects.enter('chunkText', {contentType: 'text'}) + return label(code) + } + + function label(code) { + if ( + code === null /* EOF */ || + markdownLineEnding(code) || + /* */ + size > 999 + ) { + effects.exit('chunkText') + return atBreak(code) + } + + if (code === 91 /* `[` */ && ++balance > 3) { + return nok(code) + } + + if (code === 93 /* `]` */ && !balance--) { + effects.exit('chunkText') + return atClosingBrace(code) + } + + effects.consume(code) + return code === 92 /* `\` */ ? labelEscape : label + } + + function atClosingBrace(code) { + effects.exit(stringType) + effects.enter(markerType) + effects.consume(code) + effects.exit(markerType) + effects.exit(type) + return ok + } + + function labelEscape(code) { + if ( + code === 91 /* `[` */ || + code === 92 /* `\` */ || + code === 93 /* `]` */ + ) { + effects.consume(code) + size++ + return label + } + + return label(code) + } +} diff --git a/src/core/parser/markdown/directive/micromark-directive/factory-name.js b/src/core/parser/markdown/directive/micromark-directive/factory-name.js new file mode 100644 index 000000000..3c581be37 --- /dev/null +++ b/src/core/parser/markdown/directive/micromark-directive/factory-name.js @@ -0,0 +1,37 @@ +'use strict' + +module.exports = createName + +var asciiAlpha = require('micromark/dist/character/ascii-alpha') +var asciiAlphanumeric = require('micromark/dist/character/ascii-alphanumeric') + +function createName(effects, ok, nok, nameType) { + var self = this + + return start + + function start(code) { + if (asciiAlpha(code)) { + effects.enter(nameType) + effects.consume(code) + return name + } + + return nok(code) + } + + function name(code) { + if ( + code === 45 /* `-` */ || + code === 95 /* `_` */ || + asciiAlphanumeric(code) + ) { + effects.consume(code) + return name + } + + effects.exit(nameType) + // To do next major: disallow `-` at end of name too, for consistency. + return self.previous === 95 /* `_` */ ? nok(code) : ok(code) + } +} diff --git a/src/core/parser/markdown/directive/micromark-directive/html.js b/src/core/parser/markdown/directive/micromark-directive/html.js new file mode 100644 index 000000000..ec5eff516 --- /dev/null +++ b/src/core/parser/markdown/directive/micromark-directive/html.js @@ -0,0 +1,193 @@ +'use strict' + +module.exports = createDirectiveHtmlExtension + +var decode = require('parse-entities/decode-entity') +var own = {}.hasOwnProperty + +function createDirectiveHtmlExtension(options) { + var extensions = options || {} + + return { + enter: { + directiveContainer: enterContainer, + directiveContainerAttributes: enterAttributes, + directiveContainerContent: enterContainerContent, + directiveContainerLabel: enterLabel, + + directiveLeaf: enterLeaf, + directiveLeafAttributes: enterAttributes, + directiveLeafLabel: enterLabel, + + directiveText: enterText, + directiveTextAttributes: enterAttributes, + directiveTextLabel: enterLabel + }, + exit: { + directiveContainer: exit, + directiveContainerAttributeClassValue: exitAttributeClassValue, + directiveContainerAttributeIdValue: exitAttributeIdValue, + directiveContainerAttributeName: exitAttributeName, + directiveContainerAttributeValue: exitAttributeValue, + directiveContainerAttributes: exitAttributes, + directiveContainerContent: exitContainerContent, + directiveContainerFence: exitContainerFence, + directiveContainerLabel: exitLabel, + directiveContainerName: exitName, + + directiveLeaf: exit, + directiveLeafAttributeClassValue: exitAttributeClassValue, + directiveLeafAttributeIdValue: exitAttributeIdValue, + directiveLeafAttributeName: exitAttributeName, + directiveLeafAttributeValue: exitAttributeValue, + directiveLeafAttributes: exitAttributes, + directiveLeafLabel: exitLabel, + directiveLeafName: exitName, + + directiveText: exit, + directiveTextAttributeClassValue: exitAttributeClassValue, + directiveTextAttributeIdValue: exitAttributeIdValue, + directiveTextAttributeName: exitAttributeName, + directiveTextAttributeValue: exitAttributeValue, + directiveTextAttributes: exitAttributes, + directiveTextLabel: exitLabel, + directiveTextName: exitName + } + } + + function enterContainer() { + return enter.call(this, 'containerDirective') + } + + function enterLeaf() { + return enter.call(this, 'leafDirective') + } + + function enterText() { + return enter.call(this, 'textDirective') + } + + function enter(type) { + var stack = this.getData('directiveStack') + if (!stack) this.setData('directiveStack', (stack = [])) + stack.push({type: type}) + } + + function exitName(token) { + var stack = this.getData('directiveStack') + stack[stack.length - 1].name = this.sliceSerialize(token) + } + + function enterLabel() { + this.buffer() + } + + function exitLabel() { + var data = this.resume() + var stack = this.getData('directiveStack') + stack[stack.length - 1].label = data + } + + function enterAttributes() { + this.buffer() + this.setData('directiveAttributes', []) + } + + function exitAttributeIdValue(token) { + this.getData('directiveAttributes').push([ + 'id', + decodeLight(this.sliceSerialize(token)) + ]) + } + + function exitAttributeClassValue(token) { + this.getData('directiveAttributes').push([ + 'class', + decodeLight(this.sliceSerialize(token)) + ]) + } + + function exitAttributeName(token) { + // Attribute names in CommonMark are significantly limited, so character + // references can’t exist. + this.getData('directiveAttributes').push([this.sliceSerialize(token), '']) + } + + function exitAttributeValue(token) { + var attributes = this.getData('directiveAttributes') + attributes[attributes.length - 1][1] = decodeLight( + this.sliceSerialize(token) + ) + } + + function exitAttributes() { + var stack = this.getData('directiveStack') + var attributes = this.getData('directiveAttributes') + var cleaned = {} + var index = -1 + var attribute + + while (++index < attributes.length) { + attribute = attributes[index] + + if (attribute[0] === 'class' && cleaned.class) { + cleaned.class += ' ' + attribute[1] + } else { + cleaned[attribute[0]] = attribute[1] + } + } + + this.resume() + this.setData('directiveAttributes') + stack[stack.length - 1].attributes = cleaned + } + + function enterContainerContent() { + this.buffer() + } + + function exitContainerContent() { + var data = this.resume() + var stack = this.getData('directiveStack') + stack[stack.length - 1].content = data + } + + function exitContainerFence() { + var stack = this.getData('directiveStack') + var directive = stack[stack.length - 1] + if (!directive.fenceCount) directive.fenceCount = 0 + directive.fenceCount++ + if (directive.fenceCount === 1) this.setData('slurpOneLineEnding', true) + } + + function exit() { + var directive = this.getData('directiveStack').pop() + var found + var result + + if (own.call(extensions, directive.name)) { + result = extensions[directive.name].call(this, directive) + found = result !== false + } + + if (!found && own.call(extensions, '*')) { + result = extensions['*'].call(this, directive) + found = result !== false + } + + if (!found && directive.type !== 'textDirective') { + this.setData('slurpOneLineEnding', true) + } + } +} + +function decodeLight(value) { + return value.replace( + /&(#(\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi, + decodeIfPossible + ) +} + +function decodeIfPossible($0, $1) { + return decode($1) || $0 +} diff --git a/src/core/parser/markdown/directive/micromark-directive/index.js b/src/core/parser/markdown/directive/micromark-directive/index.js new file mode 100644 index 000000000..9d5ed6a79 --- /dev/null +++ b/src/core/parser/markdown/directive/micromark-directive/index.js @@ -0,0 +1,2 @@ +// https://github.com/micromark/micromark-extension-directive/blob/main/lib/index.js +module.exports = require('./syntax') diff --git a/src/core/parser/markdown/directive/micromark-directive/syntax.js b/src/core/parser/markdown/directive/micromark-directive/syntax.js new file mode 100644 index 000000000..8fe416434 --- /dev/null +++ b/src/core/parser/markdown/directive/micromark-directive/syntax.js @@ -0,0 +1,14 @@ +'use strict' + +module.exports = directive + +var directiveText = require('./tokenize-directive-text') +var directiveLeaf = require('./tokenize-directive-leaf') +var directiveContainer = require('./tokenize-directive-container') + +function directive() { + return { + text: {58: directiveText}, + flow: {58: [directiveContainer, directiveLeaf]} + } +} diff --git a/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-container.js b/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-container.js new file mode 100644 index 000000000..f9c98767d --- /dev/null +++ b/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-container.js @@ -0,0 +1,258 @@ +'use strict' + +exports.tokenize = tokenizeDirectiveContainer +exports.concrete = true + +const markdownLineEnding = require('micromark/dist/character/markdown-line-ending') +const createSpace = require('micromark/dist/tokenize/factory-space') +const prefixSize = require('micromark/dist/util/prefix-size') +const createAttributes = require('./factory-attributes') +const createLabel = require('./factory-label') +const createName = require('./factory-name') + +const label = { tokenize: tokenizeLabel, partial: true } +const attributes = { tokenize: tokenizeAttributes, partial: true } + +function tokenizeDirectiveContainer(effects, ok, nok) { + const self = this + const initialPrefix = prefixSize(this.events, 'linePrefix') + let sizeOpen = 0 + let previous + + return start + + function start(code) { + /* istanbul ignore if - handled by mm */ + if (code !== 58 /* `:` */) throw new Error('expected `:`') + effects.enter('directiveContainer') + effects.enter('directiveContainerFence') + effects.enter('directiveContainerSequence') + return sequenceOpen(code) + } + + function tokenizeClosingSection(effects, ok, nok) { + let size = 0 + + return createSpace(effects, closingPrefixAfter, 'linePrefix', 4) + + function closingPrefixAfter(code) { + effects.enter('directiveContainerSectionSequence') + return closingSequence(code) + } + + function closingSequence(code) { + if (code === 45 /* `-` */) { + effects.consume(code) + size++ + return closingSequence + } + + if (size < 3) return nok(code) + effects.exit('directiveContainerSectionSequence') + return createSpace(effects, ok, 'whitespace')(code) + } + } + function sectionOpen(code) { + effects.exit('directiveContainerSection') + effects.enter('directiveContainerSection') + + if (markdownLineEnding(code)) { + return createSpace(effects, lineStart, 'whitespace')(code) + } + + effects.enter('directiveContainerSectionTitle') + return sectionTitle + } + + function sectionTitle(code) { + if (markdownLineEnding(code)) { + effects.exit('directiveContainerSectionTitle') + return createSpace(effects, lineStart, 'linePrefix', 4)(code) + } + effects.consume(code) + return sectionTitle + } + + function sequenceOpen(code) { + if (code === 58 /* `:` */) { + effects.consume(code) + sizeOpen++ + return sequenceOpen + } + + if (sizeOpen < 3) { + return nok(code) + } + + effects.exit('directiveContainerSequence') + return createName.call(self, effects, afterName, nok, 'directiveContainerName')(code) + } + + function afterName(code) { + return code === 91 /* `[` */ ? effects.attempt(label, afterLabel, afterLabel)(code) : afterLabel(code) + } + + function afterLabel(code) { + return code === 123 /* `{` */ + ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) + : afterAttributes(code) + } + + function afterAttributes(code) { + return createSpace(effects, openAfter, 'whitespace')(code) + } + + function openAfter(code) { + effects.exit('directiveContainerFence') + + if (code === null) { + effects.exit('directiveContainer') + return ok(code) + } + + if (markdownLineEnding(code)) { + effects.enter('lineEnding') + effects.consume(code) + effects.exit('lineEnding') + return self.interrupt ? ok : contentStart + } + + return nok(code) + } + + function contentStart(code) { + if (code === null) { + effects.exit('directiveContainer') + return ok(code) + } + + effects.enter('directiveContainerContent') + effects.enter('directiveContainerSection') + return lineStart(code) + } + + function lineStart(code) { + if (code === null) { + return after(code) + } + + if (code === 45 /* `-` */) { + return effects.attempt( + { tokenize: tokenizeClosingSection, partial: true }, + sectionOpen, + initialPrefix ? createSpace(effects, chunkStart, 'linePrefix', initialPrefix + 1) : chunkStart + )(code) + } + + return effects.attempt( + { tokenize: tokenizeClosingFence, partial: true }, + after, + initialPrefix ? createSpace(effects, chunkStart, 'linePrefix', initialPrefix + 1) : chunkStart + )(code) + } + + function chunkStart(code) { + if (code === null) { + return after(code) + } + + const token = effects.enter('chunkDocument', { + contentType: 'document', + previous + }) + if (previous) previous.next = token + previous = token + return contentContinue(code) + } + + function contentContinue(code) { + if (code === null) { + effects.exit('chunkDocument') + return after(code) + } + + if (markdownLineEnding(code)) { + effects.consume(code) + effects.exit('chunkDocument') + return lineStart + } + + effects.consume(code) + return contentContinue + } + + function after(code) { + effects.exit('directiveContainerSection') + effects.exit('directiveContainerContent') + effects.exit('directiveContainer') + return ok(code) + } + + function tokenizeClosingFence(effects, ok, nok) { + let size = 0 + + return createSpace(effects, closingPrefixAfter, 'linePrefix', 4) + + function closingPrefixAfter(code) { + effects.enter('directiveContainerFence') + effects.enter('directiveContainerSequence') + return closingSequence(code) + } + + function closingSequence(code) { + if (code === 58 /* `:` */) { + effects.consume(code) + size++ + return closingSequence + } + + // it is important to match sequence + if (size !== sizeOpen) return nok(code) + effects.exit('directiveContainerSequence') + return createSpace(effects, closingSequenceEnd, 'whitespace')(code) + } + + function closingSequenceEnd(code) { + if (code === null || markdownLineEnding(code)) { + effects.exit('directiveContainerFence') + return ok(code) + } + + return nok(code) + } + } +} + +function tokenizeLabel(effects, ok, nok) { + // Always a `[` + return createLabel( + effects, + ok, + nok, + 'directiveContainerLabel', + 'directiveContainerLabelMarker', + 'directiveContainerLabelString', + true + ) +} + +function tokenizeAttributes(effects, ok, nok) { + // Always a `{` + return createAttributes( + effects, + ok, + nok, + 'directiveContainerAttributes', + 'directiveContainerAttributesMarker', + 'directiveContainerAttribute', + 'directiveContainerAttributeId', + 'directiveContainerAttributeClass', + 'directiveContainerAttributeName', + 'directiveContainerAttributeInitializerMarker', + 'directiveContainerAttributeValueLiteral', + 'directiveContainerAttributeValue', + 'directiveContainerAttributeValueMarker', + 'directiveContainerAttributeValueData', + true + ) +} diff --git a/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-leaf.js b/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-leaf.js new file mode 100644 index 000000000..fb964eb03 --- /dev/null +++ b/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-leaf.js @@ -0,0 +1,97 @@ +'use strict' + +exports.tokenize = tokenizeDirectiveLeaf + +var markdownLineEnding = require('micromark/dist/character/markdown-line-ending') +var createSpace = require('micromark/dist/tokenize/factory-space') +var createAttributes = require('./factory-attributes') +var createLabel = require('./factory-label') +var createName = require('./factory-name') + +var label = {tokenize: tokenizeLabel, partial: true} +var attributes = {tokenize: tokenizeAttributes, partial: true} + +function tokenizeDirectiveLeaf(effects, ok, nok) { + var self = this + + return start + + function start(code) { + /* istanbul ignore if - handled by mm */ + if (code !== 58 /* `:` */) throw new Error('expected `:`') + + effects.enter('directiveLeaf') + effects.enter('directiveLeafSequence') + effects.consume(code) + return inStart + } + + function inStart(code) { + if (code === 58 /* `:` */) { + effects.consume(code) + effects.exit('directiveLeafSequence') + return createName.call(self, effects, afterName, nok, 'directiveLeafName') + } + + return nok(code) + } + + function afterName(code) { + return code === 91 /* `[` */ + ? effects.attempt(label, afterLabel, afterLabel)(code) + : afterLabel(code) + } + + function afterLabel(code) { + return code === 123 /* `{` */ + ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) + : afterAttributes(code) + } + + function afterAttributes(code) { + return createSpace(effects, end, 'whitespace')(code) + } + + function end(code) { + if (code === null || markdownLineEnding(code)) { + effects.exit('directiveLeaf') + return ok(code) + } + + return nok(code) + } +} + +function tokenizeLabel(effects, ok, nok) { + // Always a `[` + return createLabel( + effects, + ok, + nok, + 'directiveLeafLabel', + 'directiveLeafLabelMarker', + 'directiveLeafLabelString', + true + ) +} + +function tokenizeAttributes(effects, ok, nok) { + // Always a `{` + return createAttributes( + effects, + ok, + nok, + 'directiveLeafAttributes', + 'directiveLeafAttributesMarker', + 'directiveLeafAttribute', + 'directiveLeafAttributeId', + 'directiveLeafAttributeClass', + 'directiveLeafAttributeName', + 'directiveLeafAttributeInitializerMarker', + 'directiveLeafAttributeValueLiteral', + 'directiveLeafAttributeValue', + 'directiveLeafAttributeValueMarker', + 'directiveLeafAttributeValueData', + true + ) +} diff --git a/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-text.js b/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-text.js new file mode 100644 index 000000000..19caf994a --- /dev/null +++ b/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-text.js @@ -0,0 +1,92 @@ +'use strict' + +exports.tokenize = tokenizeDirectiveText +exports.previous = previous + +var createAttributes = require('./factory-attributes') +var createLabel = require('./factory-label') +var createName = require('./factory-name') + +var label = {tokenize: tokenizeLabel, partial: true} +var attributes = {tokenize: tokenizeAttributes, partial: true} + +function previous(code) { + // If there is a previous code, there will always be a tail. + return ( + code !== 58 /* `:` */ || + this.events[this.events.length - 1][1].type === 'characterEscape' + ) +} + +function tokenizeDirectiveText(effects, ok, nok) { + var self = this + + return start + + function start(code) { + /* istanbul ignore if - handled by mm */ + if (code !== 58 /* `:` */) throw new Error('expected `:`') + + /* istanbul ignore if - handled by mm */ + if (!previous.call(self, self.previous)) { + throw new Error('expected correct previous') + } + + effects.enter('directiveText') + effects.enter('directiveTextMarker') + effects.consume(code) + effects.exit('directiveTextMarker') + return createName.call(self, effects, afterName, nok, 'directiveTextName') + } + + function afterName(code) { + return code === 58 /* `:` */ + ? nok(code) + : code === 91 /* `[` */ + ? effects.attempt(label, afterLabel, afterLabel)(code) + : afterLabel(code) + } + + function afterLabel(code) { + return code === 123 /* `{` */ + ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) + : afterAttributes(code) + } + + function afterAttributes(code) { + effects.exit('directiveText') + return ok(code) + } +} + +function tokenizeLabel(effects, ok, nok) { + // Always a `[` + return createLabel( + effects, + ok, + nok, + 'directiveTextLabel', + 'directiveTextLabelMarker', + 'directiveTextLabelString' + ) +} + +function tokenizeAttributes(effects, ok, nok) { + // Always a `{` + return createAttributes( + effects, + ok, + nok, + 'directiveTextAttributes', + 'directiveTextAttributesMarker', + 'directiveTextAttribute', + 'directiveTextAttributeId', + 'directiveTextAttributeClass', + 'directiveTextAttributeName', + 'directiveTextAttributeInitializerMarker', + 'directiveTextAttributeValueLiteral', + 'directiveTextAttributeValue', + 'directiveTextAttributeValueMarker', + 'directiveTextAttributeValueData' + ) +} diff --git a/src/core/parser/markdown/directive/remark-directive/from-markdown.js b/src/core/parser/markdown/directive/remark-directive/from-markdown.js new file mode 100644 index 000000000..023c18ad0 --- /dev/null +++ b/src/core/parser/markdown/directive/remark-directive/from-markdown.js @@ -0,0 +1,147 @@ +const decode = require('parse-entities/decode-entity') + +exports.canContainEols = ['textDirective'] +exports.enter = { + directiveContainerSection(token) { + enter.call(this, 'directiveContainerSection', token) + }, + directiveContainer: enterContainer, + directiveContainerAttributes: enterAttributes, + directiveContainerLabel: enterContainerLabel, + + directiveLeaf: enterLeaf, + directiveLeafAttributes: enterAttributes, + + directiveText: enterText, + directiveTextAttributes: enterAttributes +} +exports.exit = { + directiveContainerSectionTitle(token) { + this.stack[this.stack.length - 1].name = this.sliceSerialize(token) + }, + directiveContainerSection(token) { + const section = this.stack[this.stack.length - 1] + section.raw = this.sliceSerialize(token) + this.exit(token) + }, + directiveContainer(token) { + const container = this.stack[this.stack.length - 1] + if (container.children.length > 1) { + const dataSection = container.children.shift() + container.rawData = dataSection.raw + } + if (container.children.length === 1) { + container.children = container.children[0].children + } + // TODO: handle multiple slots + this.exit(token) + }, + directiveContainerAttributeClassValue: exitAttributeClassValue, + directiveContainerAttributeIdValue: exitAttributeIdValue, + directiveContainerAttributeName: exitAttributeName, + directiveContainerAttributeValue: exitAttributeValue, + directiveContainerAttributes: exitAttributes, + directiveContainerLabel: exitContainerLabel, + directiveContainerName: exitName, + + directiveLeaf: exit, + directiveLeafAttributeClassValue: exitAttributeClassValue, + directiveLeafAttributeIdValue: exitAttributeIdValue, + directiveLeafAttributeName: exitAttributeName, + directiveLeafAttributeValue: exitAttributeValue, + directiveLeafAttributes: exitAttributes, + directiveLeafName: exitName, + + directiveText: exit, + directiveTextAttributeClassValue: exitAttributeClassValue, + directiveTextAttributeIdValue: exitAttributeIdValue, + directiveTextAttributeName: exitAttributeName, + directiveTextAttributeValue: exitAttributeValue, + directiveTextAttributes: exitAttributes, + directiveTextName: exitName +} + +function enterContainer(token) { + enter.call(this, 'containerDirective', token) +} + +function enterLeaf(token) { + enter.call(this, 'leafDirective', token) +} + +function enterText(token) { + enter.call(this, 'textDirective', token) +} + +function enter(type, token) { + this.enter({ type, name: '', attributes: {}, children: [] }, token) +} + +function exitName(token) { + this.stack[this.stack.length - 1].name = this.sliceSerialize(token) +} + +function enterContainerLabel(token) { + this.enter({ type: 'paragraph', data: { directiveLabel: true }, children: [] }, token) +} + +function exitContainerLabel(token) { + this.exit(token) +} + +function enterAttributes() { + this.setData('directiveAttributes', []) + this.buffer() // Capture EOLs +} + +function exitAttributeIdValue(token) { + this.getData('directiveAttributes').push(['id', decodeLight(this.sliceSerialize(token))]) +} + +function exitAttributeClassValue(token) { + this.getData('directiveAttributes').push(['class', decodeLight(this.sliceSerialize(token))]) +} + +function exitAttributeValue(token) { + const attributes = this.getData('directiveAttributes') + attributes[attributes.length - 1][1] = decodeLight(this.sliceSerialize(token)) +} + +function exitAttributeName(token) { + // Attribute names in CommonMark are significantly limited, so character + // references can’t exist. + this.getData('directiveAttributes').push([this.sliceSerialize(token), '']) +} + +function exitAttributes() { + const attributes = this.getData('directiveAttributes') + const cleaned = {} + let index = -1 + let attribute + + while (++index < attributes.length) { + attribute = attributes[index] + + if (attribute[0] === 'class' && cleaned.class) { + cleaned.class += ' ' + attribute[1] + } else { + cleaned[attribute[0]] = attribute[1] + } + } + + this.setData('directiveAttributes') + this.resume() // Drop EOLs + this.stack[this.stack.length - 1].attributes = cleaned +} + +function exit(token) { + this.exit(token) +} + +function decodeLight(value) { + return value.replace(/&(#(\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi, decodeIfPossible) +} + +function decodeIfPossible($0, $1) { + return decode($1) || $0 +} diff --git a/src/core/parser/markdown/directive/remark-directive/to-markdown.js b/src/core/parser/markdown/directive/remark-directive/to-markdown.js new file mode 100644 index 000000000..1724f421e --- /dev/null +++ b/src/core/parser/markdown/directive/remark-directive/to-markdown.js @@ -0,0 +1,169 @@ +// TODO: convert container sections to markdown +exports.unsafe = [ + { + character: '\r', + inConstruct: ['leafDirectiveLabel', 'containerDirectiveLabel'] + }, + { + character: '\n', + inConstruct: ['leafDirectiveLabel', 'containerDirectiveLabel'] + }, + { + before: '[^:]', + character: ':', + after: '[A-Za-z]', + inConstruct: ['phrasing'] + }, + { atBreak: true, character: ':', after: ':' } +] + +exports.handlers = { + containerDirective: handleDirective, + leafDirective: handleDirective, + textDirective: handleDirective +} + +handleDirective.peek = peekDirective + +const repeatString = require('repeat-string') +const encode = require('stringify-entities/light') +const visit = require('unist-util-visit-parents') +const flow = require('mdast-util-to-markdown/lib/util/container-flow') +const phrasing = require('mdast-util-to-markdown/lib/util/container-phrasing') +const checkQuote = require('mdast-util-to-markdown/lib/util/check-quote') + +const own = {}.hasOwnProperty + +const shortcut = /^[^\t\n\r "#'.<=>`}]+$/ + +function handleDirective(node, _, context) { + const prefix = fence(node) + const exit = context.enter(node.type) + let value = prefix + (node.name || '') + label(node, context) + attributes(node, context) + let subvalue + + if (node.type === 'containerDirective') { + subvalue = content(node, context) + if (subvalue) value += '\n' + subvalue + value += '\n' + prefix + } + + exit() + return value +} + +function peekDirective() { + return ':' +} + +function label(node, context) { + let label = node + let exit + let subexit + let value + + if (node.type === 'containerDirective') { + if (!inlineDirectiveLabel(node)) return '' + label = node.children[0] + } + + exit = context.enter('label') + subexit = context.enter(node.type + 'Label') + value = phrasing(label, context, { before: '[', after: ']' }) + subexit() + exit() + return value ? '[' + value + ']' : '' +} + +function attributes(node, context) { + const quote = checkQuote(context) + const subset = node.type === 'textDirective' ? [quote] : [quote, '\n', '\r'] + const attrs = node.attributes || {} + const values = [] + let id + let classesFull + let classes + let value + let key + let index + + for (key in attrs) { + if (own.call(attrs, key) && attrs[key] != null) { + value = String(attrs[key]) + + if (key === 'id') { + id = shortcut.test(value) ? '#' + value : quoted('id', value) + } else if (key === 'class') { + value = value.split(/[\t\n\r ]+/g) + classesFull = [] + classes = [] + index = -1 + + while (++index < value.length) { + ;(shortcut.test(value[index]) ? classes : classesFull).push(value[index]) + } + + classesFull = classesFull.length ? quoted('class', classesFull.join(' ')) : '' + classes = classes.length ? '.' + classes.join('.') : '' + } else { + values.push(quoted(key, value)) + } + } + } + + if (classesFull) { + values.unshift(classesFull) + } + + if (classes) { + values.unshift(classes) + } + + if (id) { + values.unshift(id) + } + + return values.length ? '{' + values.join(' ') + '}' : '' + + function quoted(key, value) { + return key + (value ? '=' + quote + encode(value, { subset }) + quote : '') + } +} + +function content(node, context) { + const content = inlineDirectiveLabel(node) ? Object.assign({}, node, { children: node.children.slice(1) }) : node + + return flow(content, context) +} + +function inlineDirectiveLabel(node) { + return node.children && node.children[0] && node.children[0].data && node.children[0].data.directiveLabel +} + +function fence(node) { + let size = 0 + + if (node.type === 'containerDirective') { + visit(node, 'containerDirective', onvisit) + size += 3 + } else if (node.type === 'leafDirective') { + size = 2 + } else { + size = 1 + } + + return repeatString(':', size) + + function onvisit(node, parents) { + let index = parents.length + let nesting = 0 + + while (index--) { + if (parents[index].type === 'containerDirective') { + nesting++ + } + } + + if (nesting > size) size = nesting + } +} diff --git a/src/core/parser/markdown/plugin/directive.ts b/src/core/parser/markdown/directive/remark-plugin.ts similarity index 56% rename from src/core/parser/markdown/plugin/directive.ts rename to src/core/parser/markdown/directive/remark-plugin.ts index 1a94d312b..178c8e783 100644 --- a/src/core/parser/markdown/plugin/directive.ts +++ b/src/core/parser/markdown/directive/remark-plugin.ts @@ -1,21 +1,23 @@ import visit from 'unist-util-visit' import h from 'hastscript' -import { useMarkdownParser } from '../' +import { useMarkdownParser } from '..' const toFrontMatter = (yamlString: string) => `--- ${yamlString} ---` -export default function htmlDirectives({ directives, dataComponents }) { +export default function htmlDirectives({ directives }) { const parser = useMarkdownParser() - function toData(raw) { - const lines = raw.split('\n') - const markdown = lines.slice(1, lines.length - 1).join('\n') + function processNode(node) { + if (!node.rawData) { + return { data: {} } + } - const { data } = parser.parseFrontMatter(toFrontMatter(markdown)) + const yaml = node.rawData + const { data } = parser.parseFrontMatter(toFrontMatter(yaml)) - return data + return { data } } function bindData(data, pageData) { @@ -31,7 +33,7 @@ export default function htmlDirectives({ directives, dataComponents }) { return Object.fromEntries(enteries) } - return async (tree, { data: pageData, contents }) => { + return async (tree, { data: pageData }) => { const jobs = [] visit(tree, ['textDirective', 'leafDirective', 'containerDirective'], visitor) @@ -40,20 +42,16 @@ export default function htmlDirectives({ directives, dataComponents }) { const data = node.data || (node.data = {}) const hast = h(node.name, node.attributes) - if (dataComponents.includes(node.name) || typeof node.attributes.yml !== 'undefined') { - const { start, end } = node.position - hast.properties = bindData( - { - ...hast.properties, - ...toData(contents.substr(start.offset, end.offset - start.offset)) - }, - pageData - ) - } + const { data: nodeData } = processNode(node) data.hName = hast.tagName - data.hProperties = hast.properties - + data.hProperties = bindData( + { + ...hast.properties, + ...nodeData + }, + pageData + ) if (directive) { jobs.push(directive(node, pageData)) } diff --git a/src/core/parser/markdown/index.ts b/src/core/parser/markdown/index.ts index 706267032..c1a1b56d3 100644 --- a/src/core/parser/markdown/index.ts +++ b/src/core/parser/markdown/index.ts @@ -15,10 +15,9 @@ const DEFAULTS: MarkdownParserOptions = { directives: { props: propsDirective }, - dataComponents: ['video-player', 'block-hero', 'block-features'], remarkPlugins: [ + resolve(__dirname, './directive'), 'remark-emoji', - 'remark-directive', 'remark-squeeze-paragraphs', 'remark-slug', ['remark-autolink-headings', { behavior: 'wrap' }], @@ -72,10 +71,7 @@ async function parse(file, options) { export function useMarkdownParser(options: Partial = {}) { options = defu(options, DEFAULTS) - options.remarkPlugins.unshift([ - resolve(__dirname, './plugin/directive'), - { directives: options.directives, dataComponents: options.dataComponents } - ]) + options.remarkPlugins.unshift([resolve(__dirname, './directive/remark-plugin'), { directives: options.directives }]) processOptions(options) return { diff --git a/src/core/parser/markdown/plugin/remark-prose.ts b/src/core/parser/markdown/plugin/remark-prose.ts deleted file mode 100644 index 08892fb25..000000000 --- a/src/core/parser/markdown/plugin/remark-prose.ts +++ /dev/null @@ -1,53 +0,0 @@ -const TAG_REGEX = /^\s*<\/?([A-Za-z0-9-_]+) ?[^>]*>/ -const PROSE_ELEMENTS = [ - // HTML tags - 'div', - 'p', - 'ul', - - // Global tags - 'props', - 'Props' -] - -const isJsNode = (node, customProsElements = []) => { - let match - if (node.type === 'containerDirective') { - return !PROSE_ELEMENTS.includes(node.name) && !customProsElements.includes(node.name) - } - if (node.type === 'html') { - match = node.value.match(TAG_REGEX) - } - if (!match && node.children && node.children[0] && node.children[0].type === 'html') { - match = node.children[0].value.match(TAG_REGEX) - } - return ( - match && - !PROSE_ELEMENTS.includes(match[1]) && // ensure tag is not a valid prose tag - !customProsElements.includes(match[1]) - ) -} - -export default ({ prosElements = [], proseClass = 'prose' }) => { - return tree => { - let insideProse = false - tree.children = tree.children.flatMap((node, i) => { - if (insideProse && isJsNode(node, prosElements)) { - insideProse = false - return [{ type: 'html', value: '' }, node] - } - if (!insideProse && !isJsNode(node, prosElements)) { - insideProse = true - return [ - { type: 'html', value: `
` }, - node, - ...(i === tree.children.length - 1 ? [{ type: 'html', value: '
' }] : []) - ] - } - if (i === tree.children.length - 1 && insideProse) { - return [node, { type: 'html', value: '' }] - } - return [node] - }) - } -} diff --git a/src/types/core.ts b/src/types/core.ts index 88608e8ac..4c00d7018 100644 --- a/src/types/core.ts +++ b/src/types/core.ts @@ -141,7 +141,6 @@ export interface MarkdownParserOptions { searchDepth: number } directives: any - dataComponents: string[] remarkPlugins: (string | [string, any])[] rehypePlugins: (string | [string, any])[] } From a1bda1e618b2146b1948f81669489c3251c74248 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Thu, 20 May 2021 16:01:46 +0430 Subject: [PATCH 02/17] fix: nested blocks --- docs/pages/1.get-started/1.installation.md | 8 +-- docs/pages/2.usage/1.content.md | 4 +- docs/pages/index.md | 3 +- docs/pages/syntax.md | 19 ++++++ docs/pages/templates/pre-launch.md | 3 +- .../tokenize-directive-container.js | 61 +++++++++++++++---- 6 files changed, 80 insertions(+), 18 deletions(-) create mode 100644 docs/pages/syntax.md diff --git a/docs/pages/1.get-started/1.installation.md b/docs/pages/1.get-started/1.installation.md index b6e1dfef0..f887cd9dc 100644 --- a/docs/pages/1.get-started/1.installation.md +++ b/docs/pages/1.get-started/1.installation.md @@ -39,17 +39,17 @@ Create and deploy using Vercel **See it in action**: -:::video-player{yml loop playsinline controls} -poster: https://res.cloudinary.com/nuxt/video/upload/v1612886404/docus/docus-vercel_wwaryz.jpg +:::video-player{loop playsinline controls} sources: - - src: https://res.cloudinary.com/nuxt/video/upload/q_auto/v1612886404/docus/docus-vercel_wwaryz.webm type: video/webm - src: https://res.cloudinary.com/nuxt/video/upload/q_auto/v1612886404/docus/docus-vercel_wwaryz.mp4 type: video/mp4 - src: https//res.cloudinary.com/nuxt/video/upload/q_auto/v1612886404/docus/docus-vercel_wwaryz.ogv type: video/ogg - ::: +poster: https://res.cloudinary.com/nuxt/video/upload/v1612886404/docus/docus-vercel_wwaryz.jpg +--- +::: ## Directory Structure diff --git a/docs/pages/2.usage/1.content.md b/docs/pages/2.usage/1.content.md index f9a72d6de..d4d00e7df 100644 --- a/docs/pages/2.usage/1.content.md +++ b/docs/pages/2.usage/1.content.md @@ -19,7 +19,9 @@ category: 'Getting started' Introducing my awesome Nuxt module! ``` -:::alert{type="info"} +:::alert +type: info +--- Checkout Nuxt Content documentation on [writing markdown content](https://content.nuxtjs.org/writing#markdown). ::: diff --git a/docs/pages/index.md b/docs/pages/index.md index accbe992c..cc516ee0b 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -6,7 +6,7 @@ description: >- navigation: false --- -:::block-hero{yml} +:::block-hero title: title description: description cta: @@ -16,6 +16,7 @@ secondary: - Open on GitHub - https://github.com/nuxtlabs/docus snippet: npx degit nuxtlabs/docus-starter#main docs +--- ::: ::::card-grid{title="What's included?"} diff --git a/docs/pages/syntax.md b/docs/pages/syntax.md new file mode 100644 index 000000000..a752e5ebe --- /dev/null +++ b/docs/pages/syntax.md @@ -0,0 +1,19 @@ +:::alert +type: info +--- +### Features +- Yaml data +- Markdown support +::: + +:::alert +type: info +--- +Nested Blocks with data + +::::alert +type: success +--- +Yaay +:::: +::: \ No newline at end of file diff --git a/docs/pages/templates/pre-launch.md b/docs/pages/templates/pre-launch.md index 832199365..f803dfc86 100644 --- a/docs/pages/templates/pre-launch.md +++ b/docs/pages/templates/pre-launch.md @@ -4,11 +4,12 @@ title: Pre-launch template --- -:::pre-launch-hero{yml} +:::pre-launch-hero title: Awesome startup description: Pretty long awesome startup description cta: description: Request an invite placeholder: Your email label: Get Invite +--- ::: diff --git a/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-container.js b/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-container.js index f9c98767d..293325de4 100644 --- a/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-container.js +++ b/src/core/parser/markdown/directive/micromark-directive/tokenize-directive-container.js @@ -18,6 +18,7 @@ function tokenizeDirectiveContainer(effects, ok, nok) { const initialPrefix = prefixSize(this.events, 'linePrefix') let sizeOpen = 0 let previous + const containerSequenceSize = [] return start @@ -135,20 +136,22 @@ function tokenizeDirectiveContainer(effects, ok, nok) { if (code === null) { return after(code) } + const chunkStartFn = initialPrefix ? createSpace(effects, chunkStart, 'linePrefix', initialPrefix + 1) : chunkStart - if (code === 45 /* `-` */) { - return effects.attempt( - { tokenize: tokenizeClosingSection, partial: true }, - sectionOpen, - initialPrefix ? createSpace(effects, chunkStart, 'linePrefix', initialPrefix + 1) : chunkStart - )(code) + if (!containerSequenceSize.length && code === 45 /* `-` */) { + return effects.attempt({ tokenize: tokenizeClosingSection, partial: true }, sectionOpen, chunkStartFn)(code) } - return effects.attempt( - { tokenize: tokenizeClosingFence, partial: true }, - after, - initialPrefix ? createSpace(effects, chunkStart, 'linePrefix', initialPrefix + 1) : chunkStart - )(code) + const attempt = effects.attempt({ tokenize: tokenizeClosingFence, partial: true }, after, chunkStartFn) + + /** + * disbale spliting inner sections + */ + if (code === 58 /* `:` */) { + return effects.check({ tokenize: detectContainer, partial: true }, chunkStartFn, attempt)(code) + } + + return attempt } function chunkStart(code) { @@ -206,6 +209,9 @@ function tokenizeDirectiveContainer(effects, ok, nok) { return closingSequence } + if (containerSequenceSize.length && size === containerSequenceSize[containerSequenceSize.length - 1]) { + containerSequenceSize.pop() + } // it is important to match sequence if (size !== sizeOpen) return nok(code) effects.exit('directiveContainerSequence') @@ -221,6 +227,39 @@ function tokenizeDirectiveContainer(effects, ok, nok) { return nok(code) } } + function detectContainer(effects, ok, nok) { + let size = 0 + + return openingPrefixAfter + + function openingPrefixAfter(code) { + return openingSequence(code) + } + + function openingSequence(code) { + if (code === 58 /* `:` */) { + effects.consume(code) + size++ + return openingSequence + } + + // it is important to match sequence + if (size < 3) return nok(code) + + return openingSequenceEnd + } + + function openingSequenceEnd(code) { + if (code === null || markdownLineEnding(code)) { + return nok(code) + } + + // memorize cotainer sequence + containerSequenceSize.push(size) + + return ok(code) + } + } } function tokenizeLabel(effects, ok, nok) { From dd11d5f957e189bfa3fb82034a36b1e1e7ee696e Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Thu, 20 May 2021 16:17:04 +0430 Subject: [PATCH 03/17] chore: update syntax page --- docs/pages/syntax.md | 45 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/docs/pages/syntax.md b/docs/pages/syntax.md index a752e5ebe..5392eca40 100644 --- a/docs/pages/syntax.md +++ b/docs/pages/syntax.md @@ -1,19 +1,56 @@ -:::alert +:::code-group + +::::code-block{preview label="Preview"} +:::::alert type: info --- ### Features - Yaml data - Markdown support +::::: +:::: + +```md [Code] +:::::alert +type: info +--- +### Features +- Yaml data +- Markdown support +::::: +``` ::: -:::alert +--- + +:::code-group + +::::code-block{preview label="Preview"} +::::::alert type: info --- Nested Blocks with data -::::alert +:::::::alert type: success --- Yaay +::::::: +:::::: :::: -::: \ No newline at end of file + +```md [Code] +::::::alert +type: info +--- +Nested Blocks with data + +:::::::alert +type: success +--- +Yaay +::::::: +:::::: +``` + +::: From 8da20a700188b1482deb302694712fe17fbc35d5 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Thu, 20 May 2021 20:15:47 +0430 Subject: [PATCH 04/17] feat: support named slots --- docs/pages/index.md | 8 ++++++-- src/core/parser/markdown/compiler.ts | 8 ++++++++ .../remark-directive/from-markdown.js | 19 +++++++++++++++---- src/core/runtime/utils.ts | 5 ++++- .../components/organisms/BlockHero.vue | 6 ++++-- 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/docs/pages/index.md b/docs/pages/index.md index cc516ee0b..130c5fd90 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -7,7 +7,6 @@ navigation: false --- :::block-hero -title: title description: description cta: - Get started @@ -16,7 +15,12 @@ secondary: - Open on GitHub - https://github.com/nuxtlabs/docus snippet: npx degit nuxtlabs/docus-starter#main docs ---- + +---title +Documentation Generator based on Nuxt and Windi. + +---description +Write pages in markdown, use Vue components, add style with Windi CSS and enjoy the power of Nuxt with a blazing fast developer experience. ::: ::::card-grid{title="What's included?"} diff --git a/src/core/parser/markdown/compiler.ts b/src/core/parser/markdown/compiler.ts index 23102ca82..ecf4e54d4 100644 --- a/src/core/parser/markdown/compiler.ts +++ b/src/core/parser/markdown/compiler.ts @@ -13,6 +13,14 @@ function parseAsJSON(node: Node, parent: DocusMarkdownNode[]) { if (node.type === 'element') { const childs = [] + /** + * rename directive slots tags name + */ + if (node.tagName === 'directive-slot') { + node.tagName = 'template' + node.content = { ...node } + } + /** * Replace a tag with nuxt-link if relative */ diff --git a/src/core/parser/markdown/directive/remark-directive/from-markdown.js b/src/core/parser/markdown/directive/remark-directive/from-markdown.js index 023c18ad0..ed57c9b50 100644 --- a/src/core/parser/markdown/directive/remark-directive/from-markdown.js +++ b/src/core/parser/markdown/directive/remark-directive/from-markdown.js @@ -30,10 +30,21 @@ exports.exit = { const dataSection = container.children.shift() container.rawData = dataSection.raw } - if (container.children.length === 1) { - container.children = container.children[0].children - } - // TODO: handle multiple slots + + container.children = container.children.flatMap(child => { + if (child.name === 'default' || !child.name) { + return child.children + } + child.data = { + hName: 'directive-slot', + hProperties: { + ...child.attributes, + [`v-slot:${child.name}`]: '' + } + } + return child + }) + this.exit(token) }, directiveContainerAttributeClassValue: exitAttributeClassValue, diff --git a/src/core/runtime/utils.ts b/src/core/runtime/utils.ts index b1741b9ec..d728303fc 100644 --- a/src/core/runtime/utils.ts +++ b/src/core/runtime/utils.ts @@ -18,10 +18,13 @@ export const Markdown = { functional: true, render: (_h, ctx) => { let node = ctx.props.node + if (typeof node === 'function') { + node = node() + } if (typeof node === 'string') { return [node] } - if (ctx.props.unwrap) { + if (node && ctx.props.unwrap) { const tags = ctx.props.unwrap.split(/[,\s]/) node = flatUnwrap(node, tags) } diff --git a/src/defaultTheme/components/organisms/BlockHero.vue b/src/defaultTheme/components/organisms/BlockHero.vue index 70ee0efbe..472535541 100644 --- a/src/defaultTheme/components/organisms/BlockHero.vue +++ b/src/defaultTheme/components/organisms/BlockHero.vue @@ -17,7 +17,7 @@ sm:mb-8 " > - {{ title }} +

- {{ description }} +

@@ -70,8 +70,10 @@ From 7258cefd8303b9601f4342d7589147f6e52a3dea Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Wed, 2 Jun 2021 20:32:04 +0430 Subject: [PATCH 17/17] fix: support class props --- docs/content/2.usage/3.assets.md | 4 ++-- docs/content/templates/pricing.md | 2 +- src/core/parser/markdown/directive/remark-plugin.ts | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/content/2.usage/3.assets.md b/docs/content/2.usage/3.assets.md index 46926a5e3..d295e0f4f 100644 --- a/docs/content/2.usage/3.assets.md +++ b/docs/content/2.usage/3.assets.md @@ -29,8 +29,8 @@ In order to display an image for a specific mode, you can use `dark-img` and `li ::code-group ::code-block{label="Preview" active preview} -Logo light -Logo dark + :img{src="/logo-light.svg" class="light-img" alt="Logo light" style="margin:0;" width="219" height="40"} + :img{src="/logo-dark.svg" class="dark-img" alt="Logo dark" style="margin:0;" width="219" height="40"}

Switch between light and dark mode: 

:: diff --git a/docs/content/templates/pricing.md b/docs/content/templates/pricing.md index 623cf2638..9dbc79bf1 100644 --- a/docs/content/templates/pricing.md +++ b/docs/content/templates/pricing.md @@ -55,6 +55,6 @@ meta: preSelectedBadge: Popular checkoutText: 'Total:' checkoutButtonText: Checkout - +--- :: \ No newline at end of file diff --git a/src/core/parser/markdown/directive/remark-plugin.ts b/src/core/parser/markdown/directive/remark-plugin.ts index 1e8b6e38b..f6a9ea1c7 100644 --- a/src/core/parser/markdown/directive/remark-plugin.ts +++ b/src/core/parser/markdown/directive/remark-plugin.ts @@ -40,14 +40,14 @@ export default function htmlDirectives({ directives }) { function visitor(node) { const directive = directives[node.name] const data = node.data || (node.data = {}) - const hast = h(node.name, node.attributes) + // parse data slots and retrive data const nodeData = getNodeData(node) - data.hName = hast.tagName + data.hName = node.name data.hProperties = bindData( { - ...hast.properties, + ...node.attributes, ...nodeData }, pageData