feat: use go tool

This commit is contained in:
2026-07-03 19:24:01 -04:00
parent 39548b4332
commit 4d0cab1fe0
1542 changed files with 252399 additions and 14 deletions
+156
View File
@@ -0,0 +1,156 @@
import type {CompileContext} from 'micromark-util-types'
export {directive} from './lib/syntax.js'
export {directiveHtml} from './lib/html.js'
/**
* Internal tuple representing an attribute.
*/
type AttributeTuple = [key: string, value: string]
/**
* Directive attribute.
*/
interface Attributes {
/**
* Key to value.
*/
[key: string]: string
}
/**
* Structure representing a directive.
*/
export interface Directive {
/**
* Private :)
*/
_fenceCount?: number | undefined
/**
* Object w/ HTML attributes.
*/
attributes?: Attributes | undefined
/**
* Compiled HTML content inside container directive.
*/
content?: string | undefined
/**
* Compiled HTML content that was in `[brackets]`.
*/
label?: string | undefined
/**
* Name of directive.
*/
name: string
/**
* Kind.
*/
type: 'containerDirective' | 'leafDirective' | 'textDirective'
}
/**
* Handle a directive.
*
* @param this
* Current context.
* @param directive
* Directive.
* @returns
* Signal whether the directive was handled.
*
* Yield `false` to let the fallback (a special handle for `'*'`) handle it.
*/
export type Handle = (
this: CompileContext,
directive: Directive
) => boolean | undefined
/**
* Configuration.
*
* > 👉 **Note**: the special field `'*'` can be used to specify a fallback
* > handle to handle all otherwise unhandled directives.
*/
export interface HtmlOptions {
[name: string]: Handle
}
/**
* Augment types.
*/
declare module 'micromark-util-types' {
/**
* Compile data.
*/
interface CompileData {
directiveAttributes?: Array<AttributeTuple>
directiveStack?: Array<Directive>
}
/**
* Token types.
*/
interface TokenTypeMap {
directiveContainer: 'directiveContainer'
directiveContainerAttributes: 'directiveContainerAttributes'
directiveContainerAttributesMarker: 'directiveContainerAttributesMarker'
directiveContainerAttribute: 'directiveContainerAttribute'
directiveContainerAttributeId: 'directiveContainerAttributeId'
directiveContainerAttributeIdValue: 'directiveContainerAttributeIdValue'
directiveContainerAttributeClass: 'directiveContainerAttributeClass'
directiveContainerAttributeClassValue: 'directiveContainerAttributeClassValue'
directiveContainerAttributeName: 'directiveContainerAttributeName'
directiveContainerAttributeInitializerMarker: 'directiveContainerAttributeInitializerMarker'
directiveContainerAttributeValueLiteral: 'directiveContainerAttributeValueLiteral'
directiveContainerAttributeValue: 'directiveContainerAttributeValue'
directiveContainerAttributeValueMarker: 'directiveContainerAttributeValueMarker'
directiveContainerAttributeValueData: 'directiveContainerAttributeValueData'
directiveContainerContent: 'directiveContainerContent'
directiveContainerFence: 'directiveContainerFence'
directiveContainerLabel: 'directiveContainerLabel'
directiveContainerLabelMarker: 'directiveContainerLabelMarker'
directiveContainerLabelString: 'directiveContainerLabelString'
directiveContainerName: 'directiveContainerName'
directiveContainerSequence: 'directiveContainerSequence'
directiveLeaf: 'directiveLeaf'
directiveLeafAttributes: 'directiveLeafAttributes'
directiveLeafAttributesMarker: 'directiveLeafAttributesMarker'
directiveLeafAttribute: 'directiveLeafAttribute'
directiveLeafAttributeId: 'directiveLeafAttributeId'
directiveLeafAttributeIdValue: 'directiveLeafAttributeIdValue'
directiveLeafAttributeClass: 'directiveLeafAttributeClass'
directiveLeafAttributeClassValue: 'directiveLeafAttributeClassValue'
directiveLeafAttributeName: 'directiveLeafAttributeName'
directiveLeafAttributeInitializerMarker: 'directiveLeafAttributeInitializerMarker'
directiveLeafAttributeValueLiteral: 'directiveLeafAttributeValueLiteral'
directiveLeafAttributeValue: 'directiveLeafAttributeValue'
directiveLeafAttributeValueMarker: 'directiveLeafAttributeValueMarker'
directiveLeafAttributeValueData: 'directiveLeafAttributeValueData'
directiveLeafLabel: 'directiveLeafLabel'
directiveLeafLabelMarker: 'directiveLeafLabelMarker'
directiveLeafLabelString: 'directiveLeafLabelString'
directiveLeafName: 'directiveLeafName'
directiveLeafSequence: 'directiveLeafSequence'
directiveText: 'directiveText'
directiveTextAttributes: 'directiveTextAttributes'
directiveTextAttributesMarker: 'directiveTextAttributesMarker'
directiveTextAttribute: 'directiveTextAttribute'
directiveTextAttributeId: 'directiveTextAttributeId'
directiveTextAttributeIdValue: 'directiveTextAttributeIdValue'
directiveTextAttributeClass: 'directiveTextAttributeClass'
directiveTextAttributeClassValue: 'directiveTextAttributeClassValue'
directiveTextAttributeName: 'directiveTextAttributeName'
directiveTextAttributeInitializerMarker: 'directiveTextAttributeInitializerMarker'
directiveTextAttributeValueLiteral: 'directiveTextAttributeValueLiteral'
directiveTextAttributeValue: 'directiveTextAttributeValue'
directiveTextAttributeValueMarker: 'directiveTextAttributeValueMarker'
directiveTextAttributeValueData: 'directiveTextAttributeValueData'
directiveTextLabel: 'directiveTextLabel'
directiveTextLabelMarker: 'directiveTextLabelMarker'
directiveTextLabelString: 'directiveTextLabelString'
directiveTextMarker: 'directiveTextMarker'
directiveTextName: 'directiveTextName'
}
}
+3
View File
@@ -0,0 +1,3 @@
// Note: more types exported from `index.d.ts`.
export {directive} from './lib/syntax.js'
export {directiveHtml} from './lib/html.js'
@@ -0,0 +1,4 @@
/** @type {Construct} */
export const directiveContainer: Construct;
import type { Construct } from 'micromark-util-types';
//# sourceMappingURL=directive-container.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"directive-container.d.ts","sourceRoot":"","sources":["directive-container.js"],"names":[],"mappings":"AAYA,wBAAwB;AACxB,iCADW,SAAS,CAInB;+BAfqE,sBAAsB"}
@@ -0,0 +1,323 @@
/**
* @import {Construct, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
import {ok as assert} from 'devlop'
import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding} from 'micromark-util-character'
import {codes, constants, types} from 'micromark-util-symbol'
import {factoryAttributes} from './factory-attributes.js'
import {factoryLabel} from './factory-label.js'
import {factoryName} from './factory-name.js'
/** @type {Construct} */
export const directiveContainer = {
tokenize: tokenizeDirectiveContainer,
concrete: true
}
const label = {tokenize: tokenizeLabel, partial: true}
const attributes = {tokenize: tokenizeAttributes, partial: true}
const nonLazyLine = {tokenize: tokenizeNonLazyLine, partial: true}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeDirectiveContainer(effects, ok, nok) {
const self = this
const tail = self.events[self.events.length - 1]
const initialSize =
tail && tail[1].type === types.linePrefix
? tail[2].sliceSerialize(tail[1], true).length
: 0
let sizeOpen = 0
/** @type {Token} */
let previous
return start
/** @type {State} */
function start(code) {
assert(code === codes.colon, 'expected `:`')
effects.enter('directiveContainer')
effects.enter('directiveContainerFence')
effects.enter('directiveContainerSequence')
return sequenceOpen(code)
}
/** @type {State} */
function sequenceOpen(code) {
if (code === codes.colon) {
effects.consume(code)
sizeOpen++
return sequenceOpen
}
if (sizeOpen < constants.codeFencedSequenceSizeMin) {
return nok(code)
}
effects.exit('directiveContainerSequence')
return factoryName.call(
self,
effects,
afterName,
nok,
'directiveContainerName'
)(code)
}
/** @type {State} */
function afterName(code) {
return code === codes.leftSquareBracket
? effects.attempt(label, afterLabel, afterLabel)(code)
: afterLabel(code)
}
/** @type {State} */
function afterLabel(code) {
return code === codes.leftCurlyBrace
? effects.attempt(attributes, afterAttributes, afterAttributes)(code)
: afterAttributes(code)
}
/** @type {State} */
function afterAttributes(code) {
return factorySpace(effects, openAfter, types.whitespace)(code)
}
/** @type {State} */
function openAfter(code) {
effects.exit('directiveContainerFence')
if (code === codes.eof) {
return after(code)
}
if (markdownLineEnding(code)) {
if (self.interrupt) {
return ok(code)
}
return effects.attempt(nonLazyLine, contentStart, after)(code)
}
return nok(code)
}
/** @type {State} */
function contentStart(code) {
if (code === codes.eof) {
return after(code)
}
if (markdownLineEnding(code)) {
return effects.check(
nonLazyLine,
emptyContentNonLazyLineAfter,
after
)(code)
}
effects.enter('directiveContainerContent')
return lineStart(code)
}
/** @type {State} */
function lineStart(code) {
return effects.attempt(
{tokenize: tokenizeClosingFence, partial: true},
afterContent,
initialSize
? factorySpace(effects, chunkStart, types.linePrefix, initialSize + 1)
: chunkStart
)(code)
}
/** @type {State} */
function chunkStart(code) {
if (code === codes.eof) {
return afterContent(code)
}
if (markdownLineEnding(code)) {
return effects.check(nonLazyLine, chunkNonLazyStart, afterContent)(code)
}
return chunkNonLazyStart(code)
}
/** @type {State} */
function contentContinue(code) {
if (code === codes.eof) {
const t = effects.exit(types.chunkDocument)
self.parser.lazy[t.start.line] = false
return afterContent(code)
}
if (markdownLineEnding(code)) {
return effects.check(nonLazyLine, nonLazyLineAfter, lineAfter)(code)
}
effects.consume(code)
return contentContinue
}
/** @type {State} */
function chunkNonLazyStart(code) {
const token = effects.enter(types.chunkDocument, {
contentType: constants.contentTypeDocument,
previous
})
if (previous) previous.next = token
previous = token
return contentContinue(code)
}
/** @type {State} */
function emptyContentNonLazyLineAfter(code) {
effects.enter('directiveContainerContent')
return lineStart(code)
}
/** @type {State} */
function nonLazyLineAfter(code) {
effects.consume(code)
const t = effects.exit(types.chunkDocument)
self.parser.lazy[t.start.line] = false
return lineStart
}
/** @type {State} */
function lineAfter(code) {
const t = effects.exit(types.chunkDocument)
self.parser.lazy[t.start.line] = false
return afterContent(code)
}
/** @type {State} */
function afterContent(code) {
effects.exit('directiveContainerContent')
return after(code)
}
/** @type {State} */
function after(code) {
effects.exit('directiveContainer')
return ok(code)
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeClosingFence(effects, ok, nok) {
let size = 0
assert(self.parser.constructs.disable.null, 'expected `disable.null`')
return factorySpace(
effects,
closingPrefixAfter,
types.linePrefix,
self.parser.constructs.disable.null.includes('codeIndented')
? undefined
: constants.tabSize
)
/** @type {State} */
function closingPrefixAfter(code) {
effects.enter('directiveContainerFence')
effects.enter('directiveContainerSequence')
return closingSequence(code)
}
/** @type {State} */
function closingSequence(code) {
if (code === codes.colon) {
effects.consume(code)
size++
return closingSequence
}
if (size < sizeOpen) return nok(code)
effects.exit('directiveContainerSequence')
return factorySpace(effects, closingSequenceEnd, types.whitespace)(code)
}
/** @type {State} */
function closingSequenceEnd(code) {
if (code === codes.eof || markdownLineEnding(code)) {
effects.exit('directiveContainerFence')
return ok(code)
}
return nok(code)
}
}
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeLabel(effects, ok, nok) {
// Always a `[`
return factoryLabel(
effects,
ok,
nok,
'directiveContainerLabel',
'directiveContainerLabelMarker',
'directiveContainerLabelString',
true
)
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeAttributes(effects, ok, nok) {
// Always a `{`
return factoryAttributes(
effects,
ok,
nok,
'directiveContainerAttributes',
'directiveContainerAttributesMarker',
'directiveContainerAttribute',
'directiveContainerAttributeId',
'directiveContainerAttributeClass',
'directiveContainerAttributeName',
'directiveContainerAttributeInitializerMarker',
'directiveContainerAttributeValueLiteral',
'directiveContainerAttributeValue',
'directiveContainerAttributeValueMarker',
'directiveContainerAttributeValueData',
true
)
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeNonLazyLine(effects, ok, nok) {
const self = this
return start
/** @type {State} */
function start(code) {
assert(markdownLineEnding(code), 'expected eol')
effects.enter(types.lineEnding)
effects.consume(code)
effects.exit(types.lineEnding)
return lineStart
}
/** @type {State} */
function lineStart(code) {
return self.parser.lazy[self.now().line] ? nok(code) : ok(code)
}
}
@@ -0,0 +1,4 @@
/** @type {Construct} */
export const directiveLeaf: Construct;
import type { Construct } from 'micromark-util-types';
//# sourceMappingURL=directive-leaf.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"directive-leaf.d.ts","sourceRoot":"","sources":["directive-leaf.js"],"names":[],"mappings":"AAYA,wBAAwB;AACxB,4BADW,SAAS,CAC0C;+BAZC,sBAAsB"}
+124
View File
@@ -0,0 +1,124 @@
/**
* @import {Construct, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
import {ok as assert} from 'devlop'
import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding} from 'micromark-util-character'
import {codes, types} from 'micromark-util-symbol'
import {factoryAttributes} from './factory-attributes.js'
import {factoryLabel} from './factory-label.js'
import {factoryName} from './factory-name.js'
/** @type {Construct} */
export const directiveLeaf = {tokenize: tokenizeDirectiveLeaf}
const label = {tokenize: tokenizeLabel, partial: true}
const attributes = {tokenize: tokenizeAttributes, partial: true}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeDirectiveLeaf(effects, ok, nok) {
const self = this
return start
/** @type {State} */
function start(code) {
assert(code === codes.colon, 'expected `:`')
effects.enter('directiveLeaf')
effects.enter('directiveLeafSequence')
effects.consume(code)
return inStart
}
/** @type {State} */
function inStart(code) {
if (code === codes.colon) {
effects.consume(code)
effects.exit('directiveLeafSequence')
return factoryName.call(
self,
effects,
afterName,
nok,
'directiveLeafName'
)
}
return nok(code)
}
/** @type {State} */
function afterName(code) {
return code === codes.leftSquareBracket
? effects.attempt(label, afterLabel, afterLabel)(code)
: afterLabel(code)
}
/** @type {State} */
function afterLabel(code) {
return code === codes.leftCurlyBrace
? effects.attempt(attributes, afterAttributes, afterAttributes)(code)
: afterAttributes(code)
}
/** @type {State} */
function afterAttributes(code) {
return factorySpace(effects, end, types.whitespace)(code)
}
/** @type {State} */
function end(code) {
if (code === codes.eof || markdownLineEnding(code)) {
effects.exit('directiveLeaf')
return ok(code)
}
return nok(code)
}
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeLabel(effects, ok, nok) {
// Always a `[`
return factoryLabel(
effects,
ok,
nok,
'directiveLeafLabel',
'directiveLeafLabelMarker',
'directiveLeafLabelString',
true
)
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeAttributes(effects, ok, nok) {
// Always a `{`
return factoryAttributes(
effects,
ok,
nok,
'directiveLeafAttributes',
'directiveLeafAttributesMarker',
'directiveLeafAttribute',
'directiveLeafAttributeId',
'directiveLeafAttributeClass',
'directiveLeafAttributeName',
'directiveLeafAttributeInitializerMarker',
'directiveLeafAttributeValueLiteral',
'directiveLeafAttributeValue',
'directiveLeafAttributeValueMarker',
'directiveLeafAttributeValueData',
true
)
}
@@ -0,0 +1,4 @@
/** @type {Construct} */
export const directiveText: Construct;
import type { Construct } from 'micromark-util-types';
//# sourceMappingURL=directive-text.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"directive-text.d.ts","sourceRoot":"","sources":["directive-text.js"],"names":[],"mappings":"AAUA,wBAAwB;AACxB,4BADW,SAAS,CAInB;+BAbwE,sBAAsB"}
+113
View File
@@ -0,0 +1,113 @@
/**
* @import {Construct, Previous, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
import {ok as assert} from 'devlop'
import {codes, types} from 'micromark-util-symbol'
import {factoryAttributes} from './factory-attributes.js'
import {factoryLabel} from './factory-label.js'
import {factoryName} from './factory-name.js'
/** @type {Construct} */
export const directiveText = {
tokenize: tokenizeDirectiveText,
previous
}
const label = {tokenize: tokenizeLabel, partial: true}
const attributes = {tokenize: tokenizeAttributes, partial: true}
/**
* @this {TokenizeContext}
* @type {Previous}
*/
function previous(code) {
// If there is a previous code, there will always be a tail.
return (
code !== codes.colon ||
this.events[this.events.length - 1][1].type === types.characterEscape
)
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeDirectiveText(effects, ok, nok) {
const self = this
return start
/** @type {State} */
function start(code) {
assert(code === codes.colon, 'expected `:`')
assert(previous.call(self, self.previous), 'expected correct previous')
effects.enter('directiveText')
effects.enter('directiveTextMarker')
effects.consume(code)
effects.exit('directiveTextMarker')
return factoryName.call(self, effects, afterName, nok, 'directiveTextName')
}
/** @type {State} */
function afterName(code) {
return code === codes.colon
? nok(code)
: code === codes.leftSquareBracket
? effects.attempt(label, afterLabel, afterLabel)(code)
: afterLabel(code)
}
/** @type {State} */
function afterLabel(code) {
return code === codes.leftCurlyBrace
? effects.attempt(attributes, afterAttributes, afterAttributes)(code)
: afterAttributes(code)
}
/** @type {State} */
function afterAttributes(code) {
effects.exit('directiveText')
return ok(code)
}
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeLabel(effects, ok, nok) {
// Always a `[`
return factoryLabel(
effects,
ok,
nok,
'directiveTextLabel',
'directiveTextLabelMarker',
'directiveTextLabelString'
)
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeAttributes(effects, ok, nok) {
// Always a `{`
return factoryAttributes(
effects,
ok,
nok,
'directiveTextAttributes',
'directiveTextAttributesMarker',
'directiveTextAttribute',
'directiveTextAttributeId',
'directiveTextAttributeClass',
'directiveTextAttributeName',
'directiveTextAttributeInitializerMarker',
'directiveTextAttributeValueLiteral',
'directiveTextAttributeValue',
'directiveTextAttributeValueMarker',
'directiveTextAttributeValueData'
)
}
@@ -0,0 +1,23 @@
/**
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} attributesType
* @param {TokenType} attributesMarkerType
* @param {TokenType} attributeType
* @param {TokenType} attributeIdType
* @param {TokenType} attributeClassType
* @param {TokenType} attributeNameType
* @param {TokenType} attributeInitializerType
* @param {TokenType} attributeValueLiteralType
* @param {TokenType} attributeValueType
* @param {TokenType} attributeValueMarker
* @param {TokenType} attributeValueData
* @param {boolean | undefined} [disallowEol=false]
*/
export function factoryAttributes(effects: Effects, ok: State, nok: State, attributesType: TokenType, attributesMarkerType: TokenType, attributeType: TokenType, attributeIdType: TokenType, attributeClassType: TokenType, attributeNameType: TokenType, attributeInitializerType: TokenType, attributeValueLiteralType: TokenType, attributeValueType: TokenType, attributeValueMarker: TokenType, attributeValueData: TokenType, disallowEol?: boolean | undefined): (code: Code) => State | undefined;
import type { Effects } from 'micromark-util-types';
import type { State } from 'micromark-util-types';
import type { TokenType } from 'micromark-util-types';
import type { Code } from 'micromark-util-types';
//# sourceMappingURL=factory-attributes.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"factory-attributes.d.ts","sourceRoot":"","sources":["factory-attributes.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;GAgBG;AACH,2CAhBW,OAAO,MACP,KAAK,OACL,KAAK,kBACL,SAAS,wBACT,SAAS,iBACT,SAAS,mBACT,SAAS,sBACT,SAAS,qBACT,SAAS,4BACT,SAAS,6BACT,SAAS,sBACT,SAAS,wBACT,SAAS,sBACT,SAAS,gBACT,OAAO,GAAG,SAAS,qCA4T7B;6BA1ViD,sBAAsB;2BAAtB,sBAAsB;+BAAtB,sBAAsB;0BAAtB,sBAAsB"}
@@ -0,0 +1,348 @@
/**
* @import {Code, Effects, State, TokenType} from 'micromark-util-types'
*/
import {ok as assert} from 'devlop'
import {factorySpace} from 'micromark-factory-space'
import {factoryWhitespace} from 'micromark-factory-whitespace'
import {
markdownLineEnding,
markdownLineEndingOrSpace,
markdownSpace,
unicodePunctuation,
unicodeWhitespace
} from 'micromark-util-character'
import {codes, types} from 'micromark-util-symbol'
/**
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} attributesType
* @param {TokenType} attributesMarkerType
* @param {TokenType} attributeType
* @param {TokenType} attributeIdType
* @param {TokenType} attributeClassType
* @param {TokenType} attributeNameType
* @param {TokenType} attributeInitializerType
* @param {TokenType} attributeValueLiteralType
* @param {TokenType} attributeValueType
* @param {TokenType} attributeValueMarker
* @param {TokenType} attributeValueData
* @param {boolean | undefined} [disallowEol=false]
*/
export function factoryAttributes(
effects,
ok,
nok,
attributesType,
attributesMarkerType,
attributeType,
attributeIdType,
attributeClassType,
attributeNameType,
attributeInitializerType,
attributeValueLiteralType,
attributeValueType,
attributeValueMarker,
attributeValueData,
disallowEol
) {
/** @type {TokenType} */
let type
/** @type {Code | undefined} */
let marker
return start
/** @type {State} */
function start(code) {
assert(code === codes.leftCurlyBrace, 'expected `{`')
effects.enter(attributesType)
effects.enter(attributesMarkerType)
effects.consume(code)
effects.exit(attributesMarkerType)
return between
}
/** @type {State} */
function between(code) {
if (code === codes.numberSign) {
type = attributeIdType
return shortcutStart(code)
}
if (code === codes.dot) {
type = attributeClassType
return shortcutStart(code)
}
if (disallowEol && markdownSpace(code)) {
return factorySpace(effects, between, types.whitespace)(code)
}
if (!disallowEol && markdownLineEndingOrSpace(code)) {
return factoryWhitespace(effects, between)(code)
}
if (
code === codes.eof ||
markdownLineEnding(code) ||
unicodeWhitespace(code) ||
(unicodePunctuation(code) &&
code !== codes.dash &&
code !== codes.underscore)
) {
return end(code)
}
effects.enter(attributeType)
effects.enter(attributeNameType)
effects.consume(code)
return name
}
/** @type {State} */
function shortcutStart(code) {
// Assume its registered.
const markerType = /** @type {TokenType} */ (type + 'Marker')
effects.enter(attributeType)
effects.enter(type)
effects.enter(markerType)
effects.consume(code)
effects.exit(markerType)
return shortcutStartAfter
}
/** @type {State} */
function shortcutStartAfter(code) {
if (
code === codes.eof ||
code === codes.quotationMark ||
code === codes.numberSign ||
code === codes.apostrophe ||
code === codes.dot ||
code === codes.lessThan ||
code === codes.equalsTo ||
code === codes.greaterThan ||
code === codes.graveAccent ||
code === codes.rightCurlyBrace ||
markdownLineEndingOrSpace(code)
) {
return nok(code)
}
// Assume its registered.
const valueType = /** @type {TokenType} */ (type + 'Value')
effects.enter(valueType)
effects.consume(code)
return shortcut
}
/** @type {State} */
function shortcut(code) {
if (
code === codes.eof ||
code === codes.quotationMark ||
code === codes.apostrophe ||
code === codes.lessThan ||
code === codes.equalsTo ||
code === codes.greaterThan ||
code === codes.graveAccent
) {
return nok(code)
}
if (
code === codes.numberSign ||
code === codes.dot ||
code === codes.rightCurlyBrace ||
markdownLineEndingOrSpace(code)
) {
// Assume its registered.
const valueType = /** @type {TokenType} */ (type + 'Value')
effects.exit(valueType)
effects.exit(type)
effects.exit(attributeType)
return between(code)
}
effects.consume(code)
return shortcut
}
/** @type {State} */
function name(code) {
if (
code === codes.eof ||
markdownLineEnding(code) ||
unicodeWhitespace(code) ||
(unicodePunctuation(code) &&
code !== codes.dash &&
code !== codes.dot &&
code !== codes.colon &&
code !== codes.underscore)
) {
effects.exit(attributeNameType)
if (disallowEol && markdownSpace(code)) {
return factorySpace(effects, nameAfter, types.whitespace)(code)
}
if (!disallowEol && markdownLineEndingOrSpace(code)) {
return factoryWhitespace(effects, nameAfter)(code)
}
return nameAfter(code)
}
effects.consume(code)
return name
}
/** @type {State} */
function nameAfter(code) {
if (code === codes.equalsTo) {
effects.enter(attributeInitializerType)
effects.consume(code)
effects.exit(attributeInitializerType)
return valueBefore
}
// Attribute w/o value.
effects.exit(attributeType)
return between(code)
}
/** @type {State} */
function valueBefore(code) {
if (
code === codes.eof ||
code === codes.lessThan ||
code === codes.equalsTo ||
code === codes.greaterThan ||
code === codes.graveAccent ||
code === codes.rightCurlyBrace ||
(disallowEol && markdownLineEnding(code))
) {
return nok(code)
}
if (code === codes.quotationMark || code === codes.apostrophe) {
effects.enter(attributeValueLiteralType)
effects.enter(attributeValueMarker)
effects.consume(code)
effects.exit(attributeValueMarker)
marker = code
return valueQuotedStart
}
if (disallowEol && markdownSpace(code)) {
return factorySpace(effects, valueBefore, types.whitespace)(code)
}
if (!disallowEol && markdownLineEndingOrSpace(code)) {
return factoryWhitespace(effects, valueBefore)(code)
}
effects.enter(attributeValueType)
effects.enter(attributeValueData)
effects.consume(code)
marker = undefined
return valueUnquoted
}
/** @type {State} */
function valueUnquoted(code) {
if (
code === codes.eof ||
code === codes.quotationMark ||
code === codes.apostrophe ||
code === codes.lessThan ||
code === codes.equalsTo ||
code === codes.greaterThan ||
code === codes.graveAccent
) {
return nok(code)
}
if (code === codes.rightCurlyBrace || markdownLineEndingOrSpace(code)) {
effects.exit(attributeValueData)
effects.exit(attributeValueType)
effects.exit(attributeType)
return between(code)
}
effects.consume(code)
return valueUnquoted
}
/** @type {State} */
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)
}
/** @type {State} */
function valueQuotedBetween(code) {
if (code === marker) {
effects.exit(attributeValueType)
return valueQuotedStart(code)
}
if (code === codes.eof) {
return nok(code)
}
// Note: blank lines cant exist in content.
if (markdownLineEnding(code)) {
return disallowEol
? nok(code)
: factoryWhitespace(effects, valueQuotedBetween)(code)
}
effects.enter(attributeValueData)
effects.consume(code)
return valueQuoted
}
/** @type {State} */
function valueQuoted(code) {
if (code === marker || code === codes.eof || markdownLineEnding(code)) {
effects.exit(attributeValueData)
return valueQuotedBetween(code)
}
effects.consume(code)
return valueQuoted
}
/** @type {State} */
function valueQuotedAfter(code) {
return code === codes.rightCurlyBrace || markdownLineEndingOrSpace(code)
? between(code)
: end(code)
}
/** @type {State} */
function end(code) {
if (code === codes.rightCurlyBrace) {
effects.enter(attributesMarkerType)
effects.consume(code)
effects.exit(attributesMarkerType)
effects.exit(attributesType)
return ok
}
return nok(code)
}
}
+15
View File
@@ -0,0 +1,15 @@
/**
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} type
* @param {TokenType} markerType
* @param {TokenType} stringType
* @param {boolean | undefined} [disallowEol=false]
*/
export function factoryLabel(effects: Effects, ok: State, nok: State, type: TokenType, markerType: TokenType, stringType: TokenType, disallowEol?: boolean | undefined): (code: Code) => State | undefined;
import type { Effects } from 'micromark-util-types';
import type { State } from 'micromark-util-types';
import type { TokenType } from 'micromark-util-types';
import type { Code } from 'micromark-util-types';
//# sourceMappingURL=factory-label.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"factory-label.d.ts","sourceRoot":"","sources":["factory-label.js"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,sCARW,OAAO,MACP,KAAK,OACL,KAAK,QACL,SAAS,cACT,SAAS,cACT,SAAS,gBACT,OAAO,GAAG,SAAS,qCAkH7B;6BArIwD,sBAAsB;2BAAtB,sBAAsB;+BAAtB,sBAAsB;0BAAtB,sBAAsB"}
+135
View File
@@ -0,0 +1,135 @@
/**
* @import {Code, Effects, State, Token, TokenType} from 'micromark-util-types'
*/
import {ok as assert} from 'devlop'
import {markdownLineEnding} from 'micromark-util-character'
import {codes, constants, types} from 'micromark-util-symbol'
// This is a fork of:
// <https://github.com/micromark/micromark/tree/main/packages/micromark-factory-label>
// to allow empty labels, balanced brackets (such as for nested directives),
// text instead of strings, and optionally disallows EOLs.
/**
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} type
* @param {TokenType} markerType
* @param {TokenType} stringType
* @param {boolean | undefined} [disallowEol=false]
*/
export function factoryLabel(
effects,
ok,
nok,
type,
markerType,
stringType,
disallowEol
) {
let size = 0
let balance = 0
/** @type {Token | undefined} */
let previous
return start
/** @type {State} */
function start(code) {
assert(code === codes.leftSquareBracket, 'expected `[`')
effects.enter(type)
effects.enter(markerType)
effects.consume(code)
effects.exit(markerType)
return afterStart
}
/** @type {State} */
function afterStart(code) {
if (code === codes.rightSquareBracket) {
effects.enter(markerType)
effects.consume(code)
effects.exit(markerType)
effects.exit(type)
return ok
}
effects.enter(stringType)
return lineStart(code)
}
/** @type {State} */
function lineStart(code) {
if (code === codes.rightSquareBracket && !balance) {
return atClosingBrace(code)
}
const token = effects.enter(types.chunkText, {
_contentTypeTextTrailing: true,
contentType: constants.contentTypeText,
previous
})
if (previous) previous.next = token
previous = token
return data(code)
}
/** @type {State} */
function data(code) {
if (code === codes.eof || size > constants.linkReferenceSizeMax) {
return nok(code)
}
if (
code === codes.leftSquareBracket &&
++balance > constants.linkResourceDestinationBalanceMax
) {
return nok(code)
}
if (code === codes.rightSquareBracket && !balance--) {
effects.exit(types.chunkText)
return atClosingBrace(code)
}
if (markdownLineEnding(code)) {
if (disallowEol) {
return nok(code)
}
effects.consume(code)
effects.exit(types.chunkText)
return lineStart
}
effects.consume(code)
return code === codes.backslash ? dataEscape : data
}
/** @type {State} */
function dataEscape(code) {
if (
code === codes.leftSquareBracket ||
code === codes.backslash ||
code === codes.rightSquareBracket
) {
effects.consume(code)
size++
return data
}
return data(code)
}
/** @type {State} */
function atClosingBrace(code) {
effects.exit(stringType)
effects.enter(markerType)
effects.consume(code)
effects.exit(markerType)
effects.exit(type)
return ok
}
}
+14
View File
@@ -0,0 +1,14 @@
/**
* @this {TokenizeContext}
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} type
*/
export function factoryName(this: TokenizeContext, effects: Effects, ok: State, nok: State, type: TokenType): (code: Code) => State | undefined;
import type { Effects } from 'micromark-util-types';
import type { State } from 'micromark-util-types';
import type { TokenType } from 'micromark-util-types';
import type { TokenizeContext } from 'micromark-util-types';
import type { Code } from 'micromark-util-types';
//# sourceMappingURL=factory-name.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"factory-name.d.ts","sourceRoot":"","sources":["factory-name.js"],"names":[],"mappings":"AAWA;;;;;;GAMG;AACH,4DALW,OAAO,MACP,KAAK,OACL,KAAK,QACL,SAAS,qCA0CnB;6BAzDkE,sBAAsB;2BAAtB,sBAAsB;+BAAtB,sBAAsB;qCAAtB,sBAAsB;0BAAtB,sBAAsB"}
+59
View File
@@ -0,0 +1,59 @@
/**
* @import {Code, Effects, State, TokenizeContext, TokenType} from 'micromark-util-types'
*/
import {
markdownLineEnding,
unicodePunctuation,
unicodeWhitespace
} from 'micromark-util-character'
import {codes} from 'micromark-util-symbol'
/**
* @this {TokenizeContext}
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} type
*/
export function factoryName(effects, ok, nok, type) {
const self = this
return start
/** @type {State} */
function start(code) {
if (
code === codes.eof ||
markdownLineEnding(code) ||
unicodePunctuation(code) ||
unicodeWhitespace(code)
) {
return nok(code)
}
effects.enter(type)
effects.consume(code)
return name
}
/** @type {State} */
function name(code) {
if (
code === codes.eof ||
markdownLineEnding(code) ||
unicodeWhitespace(code) ||
(unicodePunctuation(code) &&
code !== codes.dash &&
code !== codes.underscore)
) {
effects.exit(type)
return self.previous === codes.dash || self.previous === codes.underscore
? nok(code)
: ok(code)
}
effects.consume(code)
return name
}
}
+14
View File
@@ -0,0 +1,14 @@
/**
* Create an extension for `micromark` to support directives when serializing
* to HTML.
*
* @param {HtmlOptions | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions`, to
* support directives when serializing to HTML.
*/
export function directiveHtml(options?: HtmlOptions | null | undefined): HtmlExtension;
import type { HtmlOptions } from 'micromark-extension-directive';
import type { HtmlExtension } from 'micromark-util-types';
//# sourceMappingURL=html.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["html.js"],"names":[],"mappings":"AAUA;;;;;;;;;GASG;AACH,wCANW,WAAW,GAAG,IAAI,GAAG,SAAS,GAE5B,aAAa,CAwPzB;iCAvQwC,+BAA+B;mCACG,sBAAsB"}
+265
View File
@@ -0,0 +1,265 @@
/**
* @import {Directive, HtmlOptions} from 'micromark-extension-directive'
* @import {CompileContext, Handle as MicromarkHandle, HtmlExtension} from 'micromark-util-types'
*/
import {ok as assert} from 'devlop'
import {parseEntities} from 'parse-entities'
const own = {}.hasOwnProperty
/**
* Create an extension for `micromark` to support directives when serializing
* to HTML.
*
* @param {HtmlOptions | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions`, to
* support directives when serializing to HTML.
*/
export function directiveHtml(options) {
const options_ = options || {}
return {
enter: {
directiveContainer() {
enter.call(this, 'containerDirective')
},
directiveContainerAttributes: enterAttributes,
directiveContainerLabel: enterLabel,
directiveContainerContent() {
this.buffer()
},
directiveLeaf() {
enter.call(this, 'leafDirective')
},
directiveLeafAttributes: enterAttributes,
directiveLeafLabel: enterLabel,
directiveText() {
enter.call(this, 'textDirective')
},
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
}
}
/**
* @this {CompileContext}
* @param {Directive['type']} type
*/
function enter(type) {
let stack = this.getData('directiveStack')
if (!stack) this.setData('directiveStack', (stack = []))
stack.push({type, name: ''})
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitName(token) {
const stack = this.getData('directiveStack')
assert(stack, 'expected directive stack')
stack[stack.length - 1].name = this.sliceSerialize(token)
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function enterLabel() {
this.buffer()
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitLabel() {
const data = this.resume()
const stack = this.getData('directiveStack')
assert(stack, 'expected directive stack')
stack[stack.length - 1].label = data
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function enterAttributes() {
this.buffer()
this.setData('directiveAttributes', [])
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitAttributeIdValue(token) {
const attributes = this.getData('directiveAttributes')
assert(attributes, 'expected attributes')
attributes.push([
'id',
parseEntities(this.sliceSerialize(token), {
attribute: true
})
])
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitAttributeClassValue(token) {
const attributes = this.getData('directiveAttributes')
assert(attributes, 'expected attributes')
attributes.push([
'class',
parseEntities(this.sliceSerialize(token), {
attribute: true
})
])
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitAttributeName(token) {
// Attribute names in CommonMark are significantly limited, so character
// references cant exist.
const attributes = this.getData('directiveAttributes')
assert(attributes, 'expected attributes')
attributes.push([this.sliceSerialize(token), ''])
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitAttributeValue(token) {
const attributes = this.getData('directiveAttributes')
assert(attributes, 'expected attributes')
attributes[attributes.length - 1][1] = parseEntities(
this.sliceSerialize(token),
{attribute: true}
)
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitAttributes() {
const stack = this.getData('directiveStack')
assert(stack, 'expected directive stack')
const attributes = this.getData('directiveAttributes')
assert(attributes, 'expected attributes')
/** @type {Record<string, string>} */
const cleaned = {}
let index = -1
while (++index < attributes.length) {
const 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
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitContainerContent() {
const data = this.resume()
const stack = this.getData('directiveStack')
assert(stack, 'expected directive stack')
stack[stack.length - 1].content = data
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitContainerFence() {
const stack = this.getData('directiveStack')
assert(stack, 'expected directive stack')
const directive = stack[stack.length - 1]
if (!directive._fenceCount) directive._fenceCount = 0
directive._fenceCount++
if (directive._fenceCount === 1) this.setData('slurpOneLineEnding', true)
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exit() {
const stack = this.getData('directiveStack')
assert(stack, 'expected directive stack')
const directive = stack.pop()
assert(directive, 'expected directive')
/** @type {boolean | undefined} */
let found
/** @type {boolean | undefined} */
let result
assert(directive.name, 'expected `name`')
if (own.call(options_, directive.name)) {
result = options_[directive.name].call(this, directive)
found = result !== false
}
if (!found && own.call(options_, '*')) {
result = options_['*'].call(this, directive)
found = result !== false
}
if (!found && directive.type !== 'textDirective') {
this.setData('slurpOneLineEnding', true)
}
}
}
+10
View File
@@ -0,0 +1,10 @@
/**
* Create an extension for `micromark` to enable directive syntax.
*
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions`, to
* enable directive syntax.
*/
export function directive(): Extension;
import type { Extension } from 'micromark-util-types';
//# sourceMappingURL=syntax.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"syntax.d.ts","sourceRoot":"","sources":["syntax.js"],"names":[],"mappings":"AASA;;;;;;GAMG;AACH,6BAJa,SAAS,CASrB;+BApB2B,sBAAsB"}
+22
View File
@@ -0,0 +1,22 @@
/**
* @import {Extension} from 'micromark-util-types'
*/
import {codes} from 'micromark-util-symbol'
import {directiveContainer} from './directive-container.js'
import {directiveLeaf} from './directive-leaf.js'
import {directiveText} from './directive-text.js'
/**
* Create an extension for `micromark` to enable directive syntax.
*
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions`, to
* enable directive syntax.
*/
export function directive() {
return {
text: {[codes.colon]: directiveText},
flow: {[codes.colon]: [directiveContainer, directiveLeaf]}
}
}
+156
View File
@@ -0,0 +1,156 @@
import type {CompileContext} from 'micromark-util-types'
export {directive} from './lib/syntax.js'
export {directiveHtml} from './lib/html.js'
/**
* Internal tuple representing an attribute.
*/
type AttributeTuple = [key: string, value: string]
/**
* Directive attribute.
*/
interface Attributes {
/**
* Key to value.
*/
[key: string]: string
}
/**
* Structure representing a directive.
*/
export interface Directive {
/**
* Private :)
*/
_fenceCount?: number | undefined
/**
* Object w/ HTML attributes.
*/
attributes?: Attributes | undefined
/**
* Compiled HTML content inside container directive.
*/
content?: string | undefined
/**
* Compiled HTML content that was in `[brackets]`.
*/
label?: string | undefined
/**
* Name of directive.
*/
name: string
/**
* Kind.
*/
type: 'containerDirective' | 'leafDirective' | 'textDirective'
}
/**
* Handle a directive.
*
* @param this
* Current context.
* @param directive
* Directive.
* @returns
* Signal whether the directive was handled.
*
* Yield `false` to let the fallback (a special handle for `'*'`) handle it.
*/
export type Handle = (
this: CompileContext,
directive: Directive
) => boolean | undefined
/**
* Configuration.
*
* > 👉 **Note**: the special field `'*'` can be used to specify a fallback
* > handle to handle all otherwise unhandled directives.
*/
export interface HtmlOptions {
[name: string]: Handle
}
/**
* Augment types.
*/
declare module 'micromark-util-types' {
/**
* Compile data.
*/
interface CompileData {
directiveAttributes?: Array<AttributeTuple>
directiveStack?: Array<Directive>
}
/**
* Token types.
*/
interface TokenTypeMap {
directiveContainer: 'directiveContainer'
directiveContainerAttributes: 'directiveContainerAttributes'
directiveContainerAttributesMarker: 'directiveContainerAttributesMarker'
directiveContainerAttribute: 'directiveContainerAttribute'
directiveContainerAttributeId: 'directiveContainerAttributeId'
directiveContainerAttributeIdValue: 'directiveContainerAttributeIdValue'
directiveContainerAttributeClass: 'directiveContainerAttributeClass'
directiveContainerAttributeClassValue: 'directiveContainerAttributeClassValue'
directiveContainerAttributeName: 'directiveContainerAttributeName'
directiveContainerAttributeInitializerMarker: 'directiveContainerAttributeInitializerMarker'
directiveContainerAttributeValueLiteral: 'directiveContainerAttributeValueLiteral'
directiveContainerAttributeValue: 'directiveContainerAttributeValue'
directiveContainerAttributeValueMarker: 'directiveContainerAttributeValueMarker'
directiveContainerAttributeValueData: 'directiveContainerAttributeValueData'
directiveContainerContent: 'directiveContainerContent'
directiveContainerFence: 'directiveContainerFence'
directiveContainerLabel: 'directiveContainerLabel'
directiveContainerLabelMarker: 'directiveContainerLabelMarker'
directiveContainerLabelString: 'directiveContainerLabelString'
directiveContainerName: 'directiveContainerName'
directiveContainerSequence: 'directiveContainerSequence'
directiveLeaf: 'directiveLeaf'
directiveLeafAttributes: 'directiveLeafAttributes'
directiveLeafAttributesMarker: 'directiveLeafAttributesMarker'
directiveLeafAttribute: 'directiveLeafAttribute'
directiveLeafAttributeId: 'directiveLeafAttributeId'
directiveLeafAttributeIdValue: 'directiveLeafAttributeIdValue'
directiveLeafAttributeClass: 'directiveLeafAttributeClass'
directiveLeafAttributeClassValue: 'directiveLeafAttributeClassValue'
directiveLeafAttributeName: 'directiveLeafAttributeName'
directiveLeafAttributeInitializerMarker: 'directiveLeafAttributeInitializerMarker'
directiveLeafAttributeValueLiteral: 'directiveLeafAttributeValueLiteral'
directiveLeafAttributeValue: 'directiveLeafAttributeValue'
directiveLeafAttributeValueMarker: 'directiveLeafAttributeValueMarker'
directiveLeafAttributeValueData: 'directiveLeafAttributeValueData'
directiveLeafLabel: 'directiveLeafLabel'
directiveLeafLabelMarker: 'directiveLeafLabelMarker'
directiveLeafLabelString: 'directiveLeafLabelString'
directiveLeafName: 'directiveLeafName'
directiveLeafSequence: 'directiveLeafSequence'
directiveText: 'directiveText'
directiveTextAttributes: 'directiveTextAttributes'
directiveTextAttributesMarker: 'directiveTextAttributesMarker'
directiveTextAttribute: 'directiveTextAttribute'
directiveTextAttributeId: 'directiveTextAttributeId'
directiveTextAttributeIdValue: 'directiveTextAttributeIdValue'
directiveTextAttributeClass: 'directiveTextAttributeClass'
directiveTextAttributeClassValue: 'directiveTextAttributeClassValue'
directiveTextAttributeName: 'directiveTextAttributeName'
directiveTextAttributeInitializerMarker: 'directiveTextAttributeInitializerMarker'
directiveTextAttributeValueLiteral: 'directiveTextAttributeValueLiteral'
directiveTextAttributeValue: 'directiveTextAttributeValue'
directiveTextAttributeValueMarker: 'directiveTextAttributeValueMarker'
directiveTextAttributeValueData: 'directiveTextAttributeValueData'
directiveTextLabel: 'directiveTextLabel'
directiveTextLabelMarker: 'directiveTextLabelMarker'
directiveTextLabelString: 'directiveTextLabelString'
directiveTextMarker: 'directiveTextMarker'
directiveTextName: 'directiveTextName'
}
}
+3
View File
@@ -0,0 +1,3 @@
// Note: more types exported from `index.d.ts`.
export { directive } from './lib/syntax.js';
export { directiveHtml } from './lib/html.js';
@@ -0,0 +1,4 @@
/** @type {Construct} */
export const directiveContainer: Construct;
import type { Construct } from 'micromark-util-types';
//# sourceMappingURL=directive-container.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"directive-container.d.ts","sourceRoot":"","sources":["directive-container.js"],"names":[],"mappings":"AAYA,wBAAwB;AACxB,iCADW,SAAS,CAInB;+BAfqE,sBAAsB"}
+259
View File
@@ -0,0 +1,259 @@
/**
* @import {Construct, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
import { factoryAttributes } from './factory-attributes.js';
import { factoryLabel } from './factory-label.js';
import { factoryName } from './factory-name.js';
/** @type {Construct} */
export const directiveContainer = {
tokenize: tokenizeDirectiveContainer,
concrete: true
};
const label = {
tokenize: tokenizeLabel,
partial: true
};
const attributes = {
tokenize: tokenizeAttributes,
partial: true
};
const nonLazyLine = {
tokenize: tokenizeNonLazyLine,
partial: true
};
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeDirectiveContainer(effects, ok, nok) {
const self = this;
const tail = self.events[self.events.length - 1];
const initialSize = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
let sizeOpen = 0;
/** @type {Token} */
let previous;
return start;
/** @type {State} */
function start(code) {
effects.enter('directiveContainer');
effects.enter('directiveContainerFence');
effects.enter('directiveContainerSequence');
return sequenceOpen(code);
}
/** @type {State} */
function sequenceOpen(code) {
if (code === 58) {
effects.consume(code);
sizeOpen++;
return sequenceOpen;
}
if (sizeOpen < 3) {
return nok(code);
}
effects.exit('directiveContainerSequence');
return factoryName.call(self, effects, afterName, nok, 'directiveContainerName')(code);
}
/** @type {State} */
function afterName(code) {
return code === 91 ? effects.attempt(label, afterLabel, afterLabel)(code) : afterLabel(code);
}
/** @type {State} */
function afterLabel(code) {
return code === 123 ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) : afterAttributes(code);
}
/** @type {State} */
function afterAttributes(code) {
return factorySpace(effects, openAfter, "whitespace")(code);
}
/** @type {State} */
function openAfter(code) {
effects.exit('directiveContainerFence');
if (code === null) {
return after(code);
}
if (markdownLineEnding(code)) {
if (self.interrupt) {
return ok(code);
}
return effects.attempt(nonLazyLine, contentStart, after)(code);
}
return nok(code);
}
/** @type {State} */
function contentStart(code) {
if (code === null) {
return after(code);
}
if (markdownLineEnding(code)) {
return effects.check(nonLazyLine, emptyContentNonLazyLineAfter, after)(code);
}
effects.enter('directiveContainerContent');
return lineStart(code);
}
/** @type {State} */
function lineStart(code) {
return effects.attempt({
tokenize: tokenizeClosingFence,
partial: true
}, afterContent, initialSize ? factorySpace(effects, chunkStart, "linePrefix", initialSize + 1) : chunkStart)(code);
}
/** @type {State} */
function chunkStart(code) {
if (code === null) {
return afterContent(code);
}
if (markdownLineEnding(code)) {
return effects.check(nonLazyLine, chunkNonLazyStart, afterContent)(code);
}
return chunkNonLazyStart(code);
}
/** @type {State} */
function contentContinue(code) {
if (code === null) {
const t = effects.exit("chunkDocument");
self.parser.lazy[t.start.line] = false;
return afterContent(code);
}
if (markdownLineEnding(code)) {
return effects.check(nonLazyLine, nonLazyLineAfter, lineAfter)(code);
}
effects.consume(code);
return contentContinue;
}
/** @type {State} */
function chunkNonLazyStart(code) {
const token = effects.enter("chunkDocument", {
contentType: "document",
previous
});
if (previous) previous.next = token;
previous = token;
return contentContinue(code);
}
/** @type {State} */
function emptyContentNonLazyLineAfter(code) {
effects.enter('directiveContainerContent');
return lineStart(code);
}
/** @type {State} */
function nonLazyLineAfter(code) {
effects.consume(code);
const t = effects.exit("chunkDocument");
self.parser.lazy[t.start.line] = false;
return lineStart;
}
/** @type {State} */
function lineAfter(code) {
const t = effects.exit("chunkDocument");
self.parser.lazy[t.start.line] = false;
return afterContent(code);
}
/** @type {State} */
function afterContent(code) {
effects.exit('directiveContainerContent');
return after(code);
}
/** @type {State} */
function after(code) {
effects.exit('directiveContainer');
return ok(code);
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeClosingFence(effects, ok, nok) {
let size = 0;
return factorySpace(effects, closingPrefixAfter, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4);
/** @type {State} */
function closingPrefixAfter(code) {
effects.enter('directiveContainerFence');
effects.enter('directiveContainerSequence');
return closingSequence(code);
}
/** @type {State} */
function closingSequence(code) {
if (code === 58) {
effects.consume(code);
size++;
return closingSequence;
}
if (size < sizeOpen) return nok(code);
effects.exit('directiveContainerSequence');
return factorySpace(effects, closingSequenceEnd, "whitespace")(code);
}
/** @type {State} */
function closingSequenceEnd(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit('directiveContainerFence');
return ok(code);
}
return nok(code);
}
}
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeLabel(effects, ok, nok) {
// Always a `[`
return factoryLabel(effects, ok, nok, 'directiveContainerLabel', 'directiveContainerLabelMarker', 'directiveContainerLabelString', true);
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeAttributes(effects, ok, nok) {
// Always a `{`
return factoryAttributes(effects, ok, nok, 'directiveContainerAttributes', 'directiveContainerAttributesMarker', 'directiveContainerAttribute', 'directiveContainerAttributeId', 'directiveContainerAttributeClass', 'directiveContainerAttributeName', 'directiveContainerAttributeInitializerMarker', 'directiveContainerAttributeValueLiteral', 'directiveContainerAttributeValue', 'directiveContainerAttributeValueMarker', 'directiveContainerAttributeValueData', true);
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeNonLazyLine(effects, ok, nok) {
const self = this;
return start;
/** @type {State} */
function start(code) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return lineStart;
}
/** @type {State} */
function lineStart(code) {
return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
}
}
+4
View File
@@ -0,0 +1,4 @@
/** @type {Construct} */
export const directiveLeaf: Construct;
import type { Construct } from 'micromark-util-types';
//# sourceMappingURL=directive-leaf.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"directive-leaf.d.ts","sourceRoot":"","sources":["directive-leaf.js"],"names":[],"mappings":"AAYA,wBAAwB;AACxB,4BADW,SAAS,CAC0C;+BAZC,sBAAsB"}
+91
View File
@@ -0,0 +1,91 @@
/**
* @import {Construct, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding } from 'micromark-util-character';
import { factoryAttributes } from './factory-attributes.js';
import { factoryLabel } from './factory-label.js';
import { factoryName } from './factory-name.js';
/** @type {Construct} */
export const directiveLeaf = {
tokenize: tokenizeDirectiveLeaf
};
const label = {
tokenize: tokenizeLabel,
partial: true
};
const attributes = {
tokenize: tokenizeAttributes,
partial: true
};
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeDirectiveLeaf(effects, ok, nok) {
const self = this;
return start;
/** @type {State} */
function start(code) {
effects.enter('directiveLeaf');
effects.enter('directiveLeafSequence');
effects.consume(code);
return inStart;
}
/** @type {State} */
function inStart(code) {
if (code === 58) {
effects.consume(code);
effects.exit('directiveLeafSequence');
return factoryName.call(self, effects, afterName, nok, 'directiveLeafName');
}
return nok(code);
}
/** @type {State} */
function afterName(code) {
return code === 91 ? effects.attempt(label, afterLabel, afterLabel)(code) : afterLabel(code);
}
/** @type {State} */
function afterLabel(code) {
return code === 123 ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) : afterAttributes(code);
}
/** @type {State} */
function afterAttributes(code) {
return factorySpace(effects, end, "whitespace")(code);
}
/** @type {State} */
function end(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit('directiveLeaf');
return ok(code);
}
return nok(code);
}
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeLabel(effects, ok, nok) {
// Always a `[`
return factoryLabel(effects, ok, nok, 'directiveLeafLabel', 'directiveLeafLabelMarker', 'directiveLeafLabelString', true);
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeAttributes(effects, ok, nok) {
// Always a `{`
return factoryAttributes(effects, ok, nok, 'directiveLeafAttributes', 'directiveLeafAttributesMarker', 'directiveLeafAttribute', 'directiveLeafAttributeId', 'directiveLeafAttributeClass', 'directiveLeafAttributeName', 'directiveLeafAttributeInitializerMarker', 'directiveLeafAttributeValueLiteral', 'directiveLeafAttributeValue', 'directiveLeafAttributeValueMarker', 'directiveLeafAttributeValueData', true);
}
+4
View File
@@ -0,0 +1,4 @@
/** @type {Construct} */
export const directiveText: Construct;
import type { Construct } from 'micromark-util-types';
//# sourceMappingURL=directive-text.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"directive-text.d.ts","sourceRoot":"","sources":["directive-text.js"],"names":[],"mappings":"AAUA,wBAAwB;AACxB,4BADW,SAAS,CAInB;+BAbwE,sBAAsB"}
+82
View File
@@ -0,0 +1,82 @@
/**
* @import {Construct, Previous, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
import { factoryAttributes } from './factory-attributes.js';
import { factoryLabel } from './factory-label.js';
import { factoryName } from './factory-name.js';
/** @type {Construct} */
export const directiveText = {
tokenize: tokenizeDirectiveText,
previous
};
const label = {
tokenize: tokenizeLabel,
partial: true
};
const attributes = {
tokenize: tokenizeAttributes,
partial: true
};
/**
* @this {TokenizeContext}
* @type {Previous}
*/
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";
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeDirectiveText(effects, ok, nok) {
const self = this;
return start;
/** @type {State} */
function start(code) {
effects.enter('directiveText');
effects.enter('directiveTextMarker');
effects.consume(code);
effects.exit('directiveTextMarker');
return factoryName.call(self, effects, afterName, nok, 'directiveTextName');
}
/** @type {State} */
function afterName(code) {
return code === 58 ? nok(code) : code === 91 ? effects.attempt(label, afterLabel, afterLabel)(code) : afterLabel(code);
}
/** @type {State} */
function afterLabel(code) {
return code === 123 ? effects.attempt(attributes, afterAttributes, afterAttributes)(code) : afterAttributes(code);
}
/** @type {State} */
function afterAttributes(code) {
effects.exit('directiveText');
return ok(code);
}
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeLabel(effects, ok, nok) {
// Always a `[`
return factoryLabel(effects, ok, nok, 'directiveTextLabel', 'directiveTextLabelMarker', 'directiveTextLabelString');
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeAttributes(effects, ok, nok) {
// Always a `{`
return factoryAttributes(effects, ok, nok, 'directiveTextAttributes', 'directiveTextAttributesMarker', 'directiveTextAttribute', 'directiveTextAttributeId', 'directiveTextAttributeClass', 'directiveTextAttributeName', 'directiveTextAttributeInitializerMarker', 'directiveTextAttributeValueLiteral', 'directiveTextAttributeValue', 'directiveTextAttributeValueMarker', 'directiveTextAttributeValueData');
}
+23
View File
@@ -0,0 +1,23 @@
/**
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} attributesType
* @param {TokenType} attributesMarkerType
* @param {TokenType} attributeType
* @param {TokenType} attributeIdType
* @param {TokenType} attributeClassType
* @param {TokenType} attributeNameType
* @param {TokenType} attributeInitializerType
* @param {TokenType} attributeValueLiteralType
* @param {TokenType} attributeValueType
* @param {TokenType} attributeValueMarker
* @param {TokenType} attributeValueData
* @param {boolean | undefined} [disallowEol=false]
*/
export function factoryAttributes(effects: Effects, ok: State, nok: State, attributesType: TokenType, attributesMarkerType: TokenType, attributeType: TokenType, attributeIdType: TokenType, attributeClassType: TokenType, attributeNameType: TokenType, attributeInitializerType: TokenType, attributeValueLiteralType: TokenType, attributeValueType: TokenType, attributeValueMarker: TokenType, attributeValueData: TokenType, disallowEol?: boolean | undefined): (code: Code) => State | undefined;
import type { Effects } from 'micromark-util-types';
import type { State } from 'micromark-util-types';
import type { TokenType } from 'micromark-util-types';
import type { Code } from 'micromark-util-types';
//# sourceMappingURL=factory-attributes.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"factory-attributes.d.ts","sourceRoot":"","sources":["factory-attributes.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;GAgBG;AACH,2CAhBW,OAAO,MACP,KAAK,OACL,KAAK,kBACL,SAAS,wBACT,SAAS,iBACT,SAAS,mBACT,SAAS,sBACT,SAAS,qBACT,SAAS,4BACT,SAAS,6BACT,SAAS,sBACT,SAAS,wBACT,SAAS,sBACT,SAAS,gBACT,OAAO,GAAG,SAAS,qCA4T7B;6BA1ViD,sBAAsB;2BAAtB,sBAAsB;+BAAtB,sBAAsB;0BAAtB,sBAAsB"}
+238
View File
@@ -0,0 +1,238 @@
/**
* @import {Code, Effects, State, TokenType} from 'micromark-util-types'
*/
import { factorySpace } from 'micromark-factory-space';
import { factoryWhitespace } from 'micromark-factory-whitespace';
import { markdownLineEnding, markdownLineEndingOrSpace, markdownSpace, unicodePunctuation, unicodeWhitespace } from 'micromark-util-character';
/**
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} attributesType
* @param {TokenType} attributesMarkerType
* @param {TokenType} attributeType
* @param {TokenType} attributeIdType
* @param {TokenType} attributeClassType
* @param {TokenType} attributeNameType
* @param {TokenType} attributeInitializerType
* @param {TokenType} attributeValueLiteralType
* @param {TokenType} attributeValueType
* @param {TokenType} attributeValueMarker
* @param {TokenType} attributeValueData
* @param {boolean | undefined} [disallowEol=false]
*/
export function factoryAttributes(effects, ok, nok, attributesType, attributesMarkerType, attributeType, attributeIdType, attributeClassType, attributeNameType, attributeInitializerType, attributeValueLiteralType, attributeValueType, attributeValueMarker, attributeValueData, disallowEol) {
/** @type {TokenType} */
let type;
/** @type {Code | undefined} */
let marker;
return start;
/** @type {State} */
function start(code) {
effects.enter(attributesType);
effects.enter(attributesMarkerType);
effects.consume(code);
effects.exit(attributesMarkerType);
return between;
}
/** @type {State} */
function between(code) {
if (code === 35) {
type = attributeIdType;
return shortcutStart(code);
}
if (code === 46) {
type = attributeClassType;
return shortcutStart(code);
}
if (disallowEol && markdownSpace(code)) {
return factorySpace(effects, between, "whitespace")(code);
}
if (!disallowEol && markdownLineEndingOrSpace(code)) {
return factoryWhitespace(effects, between)(code);
}
if (code === null || markdownLineEnding(code) || unicodeWhitespace(code) || unicodePunctuation(code) && code !== 45 && code !== 95) {
return end(code);
}
effects.enter(attributeType);
effects.enter(attributeNameType);
effects.consume(code);
return name;
}
/** @type {State} */
function shortcutStart(code) {
// Assume its registered.
const markerType = /** @type {TokenType} */type + 'Marker';
effects.enter(attributeType);
effects.enter(type);
effects.enter(markerType);
effects.consume(code);
effects.exit(markerType);
return shortcutStartAfter;
}
/** @type {State} */
function shortcutStartAfter(code) {
if (code === null || code === 34 || code === 35 || code === 39 || code === 46 || code === 60 || code === 61 || code === 62 || code === 96 || code === 125 || markdownLineEndingOrSpace(code)) {
return nok(code);
}
// Assume its registered.
const valueType = /** @type {TokenType} */type + 'Value';
effects.enter(valueType);
effects.consume(code);
return shortcut;
}
/** @type {State} */
function shortcut(code) {
if (code === null || code === 34 || code === 39 || code === 60 || code === 61 || code === 62 || code === 96) {
return nok(code);
}
if (code === 35 || code === 46 || code === 125 || markdownLineEndingOrSpace(code)) {
// Assume its registered.
const valueType = /** @type {TokenType} */type + 'Value';
effects.exit(valueType);
effects.exit(type);
effects.exit(attributeType);
return between(code);
}
effects.consume(code);
return shortcut;
}
/** @type {State} */
function name(code) {
if (code === null || markdownLineEnding(code) || unicodeWhitespace(code) || unicodePunctuation(code) && code !== 45 && code !== 46 && code !== 58 && code !== 95) {
effects.exit(attributeNameType);
if (disallowEol && markdownSpace(code)) {
return factorySpace(effects, nameAfter, "whitespace")(code);
}
if (!disallowEol && markdownLineEndingOrSpace(code)) {
return factoryWhitespace(effects, nameAfter)(code);
}
return nameAfter(code);
}
effects.consume(code);
return name;
}
/** @type {State} */
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);
}
/** @type {State} */
function valueBefore(code) {
if (code === null || 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 factorySpace(effects, valueBefore, "whitespace")(code);
}
if (!disallowEol && markdownLineEndingOrSpace(code)) {
return factoryWhitespace(effects, valueBefore)(code);
}
effects.enter(attributeValueType);
effects.enter(attributeValueData);
effects.consume(code);
marker = undefined;
return valueUnquoted;
}
/** @type {State} */
function valueUnquoted(code) {
if (code === null || 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;
}
/** @type {State} */
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);
}
/** @type {State} */
function valueQuotedBetween(code) {
if (code === marker) {
effects.exit(attributeValueType);
return valueQuotedStart(code);
}
if (code === null) {
return nok(code);
}
// Note: blank lines cant exist in content.
if (markdownLineEnding(code)) {
return disallowEol ? nok(code) : factoryWhitespace(effects, valueQuotedBetween)(code);
}
effects.enter(attributeValueData);
effects.consume(code);
return valueQuoted;
}
/** @type {State} */
function valueQuoted(code) {
if (code === marker || code === null || markdownLineEnding(code)) {
effects.exit(attributeValueData);
return valueQuotedBetween(code);
}
effects.consume(code);
return valueQuoted;
}
/** @type {State} */
function valueQuotedAfter(code) {
return code === 125 || markdownLineEndingOrSpace(code) ? between(code) : end(code);
}
/** @type {State} */
function end(code) {
if (code === 125) {
effects.enter(attributesMarkerType);
effects.consume(code);
effects.exit(attributesMarkerType);
effects.exit(attributesType);
return ok;
}
return nok(code);
}
}
+15
View File
@@ -0,0 +1,15 @@
/**
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} type
* @param {TokenType} markerType
* @param {TokenType} stringType
* @param {boolean | undefined} [disallowEol=false]
*/
export function factoryLabel(effects: Effects, ok: State, nok: State, type: TokenType, markerType: TokenType, stringType: TokenType, disallowEol?: boolean | undefined): (code: Code) => State | undefined;
import type { Effects } from 'micromark-util-types';
import type { State } from 'micromark-util-types';
import type { TokenType } from 'micromark-util-types';
import type { Code } from 'micromark-util-types';
//# sourceMappingURL=factory-label.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"factory-label.d.ts","sourceRoot":"","sources":["factory-label.js"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,sCARW,OAAO,MACP,KAAK,OACL,KAAK,QACL,SAAS,cACT,SAAS,cACT,SAAS,gBACT,OAAO,GAAG,SAAS,qCAkH7B;6BArIwD,sBAAsB;2BAAtB,sBAAsB;+BAAtB,sBAAsB;0BAAtB,sBAAsB"}
+107
View File
@@ -0,0 +1,107 @@
/**
* @import {Code, Effects, State, Token, TokenType} from 'micromark-util-types'
*/
import { markdownLineEnding } from 'micromark-util-character';
// This is a fork of:
// <https://github.com/micromark/micromark/tree/main/packages/micromark-factory-label>
// to allow empty labels, balanced brackets (such as for nested directives),
// text instead of strings, and optionally disallows EOLs.
/**
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} type
* @param {TokenType} markerType
* @param {TokenType} stringType
* @param {boolean | undefined} [disallowEol=false]
*/
export function factoryLabel(effects, ok, nok, type, markerType, stringType, disallowEol) {
let size = 0;
let balance = 0;
/** @type {Token | undefined} */
let previous;
return start;
/** @type {State} */
function start(code) {
effects.enter(type);
effects.enter(markerType);
effects.consume(code);
effects.exit(markerType);
return afterStart;
}
/** @type {State} */
function afterStart(code) {
if (code === 93) {
effects.enter(markerType);
effects.consume(code);
effects.exit(markerType);
effects.exit(type);
return ok;
}
effects.enter(stringType);
return lineStart(code);
}
/** @type {State} */
function lineStart(code) {
if (code === 93 && !balance) {
return atClosingBrace(code);
}
const token = effects.enter("chunkText", {
_contentTypeTextTrailing: true,
contentType: "text",
previous
});
if (previous) previous.next = token;
previous = token;
return data(code);
}
/** @type {State} */
function data(code) {
if (code === null || size > 999) {
return nok(code);
}
if (code === 91 && ++balance > 32) {
return nok(code);
}
if (code === 93 && !balance--) {
effects.exit("chunkText");
return atClosingBrace(code);
}
if (markdownLineEnding(code)) {
if (disallowEol) {
return nok(code);
}
effects.consume(code);
effects.exit("chunkText");
return lineStart;
}
effects.consume(code);
return code === 92 ? dataEscape : data;
}
/** @type {State} */
function dataEscape(code) {
if (code === 91 || code === 92 || code === 93) {
effects.consume(code);
size++;
return data;
}
return data(code);
}
/** @type {State} */
function atClosingBrace(code) {
effects.exit(stringType);
effects.enter(markerType);
effects.consume(code);
effects.exit(markerType);
effects.exit(type);
return ok;
}
}
+14
View File
@@ -0,0 +1,14 @@
/**
* @this {TokenizeContext}
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} type
*/
export function factoryName(this: TokenizeContext, effects: Effects, ok: State, nok: State, type: TokenType): (code: Code) => State | undefined;
import type { Effects } from 'micromark-util-types';
import type { State } from 'micromark-util-types';
import type { TokenType } from 'micromark-util-types';
import type { TokenizeContext } from 'micromark-util-types';
import type { Code } from 'micromark-util-types';
//# sourceMappingURL=factory-name.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"factory-name.d.ts","sourceRoot":"","sources":["factory-name.js"],"names":[],"mappings":"AAWA;;;;;;GAMG;AACH,4DALW,OAAO,MACP,KAAK,OACL,KAAK,QACL,SAAS,qCA0CnB;6BAzDkE,sBAAsB;2BAAtB,sBAAsB;+BAAtB,sBAAsB;qCAAtB,sBAAsB;0BAAtB,sBAAsB"}
+36
View File
@@ -0,0 +1,36 @@
/**
* @import {Code, Effects, State, TokenizeContext, TokenType} from 'micromark-util-types'
*/
import { markdownLineEnding, unicodePunctuation, unicodeWhitespace } from 'micromark-util-character';
/**
* @this {TokenizeContext}
* @param {Effects} effects
* @param {State} ok
* @param {State} nok
* @param {TokenType} type
*/
export function factoryName(effects, ok, nok, type) {
const self = this;
return start;
/** @type {State} */
function start(code) {
if (code === null || markdownLineEnding(code) || unicodePunctuation(code) || unicodeWhitespace(code)) {
return nok(code);
}
effects.enter(type);
effects.consume(code);
return name;
}
/** @type {State} */
function name(code) {
if (code === null || markdownLineEnding(code) || unicodeWhitespace(code) || unicodePunctuation(code) && code !== 45 && code !== 95) {
effects.exit(type);
return self.previous === 45 || self.previous === 95 ? nok(code) : ok(code);
}
effects.consume(code);
return name;
}
}
+14
View File
@@ -0,0 +1,14 @@
/**
* Create an extension for `micromark` to support directives when serializing
* to HTML.
*
* @param {HtmlOptions | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions`, to
* support directives when serializing to HTML.
*/
export function directiveHtml(options?: HtmlOptions | null | undefined): HtmlExtension;
import type { HtmlOptions } from 'micromark-extension-directive';
import type { HtmlExtension } from 'micromark-util-types';
//# sourceMappingURL=html.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["html.js"],"names":[],"mappings":"AAUA;;;;;;;;;GASG;AACH,wCANW,WAAW,GAAG,IAAI,GAAG,SAAS,GAE5B,aAAa,CAwPzB;iCAvQwC,+BAA+B;mCACG,sBAAsB"}
+233
View File
@@ -0,0 +1,233 @@
/**
* @import {Directive, HtmlOptions} from 'micromark-extension-directive'
* @import {CompileContext, Handle as MicromarkHandle, HtmlExtension} from 'micromark-util-types'
*/
import { parseEntities } from 'parse-entities';
const own = {}.hasOwnProperty;
/**
* Create an extension for `micromark` to support directives when serializing
* to HTML.
*
* @param {HtmlOptions | null | undefined} [options={}]
* Configuration (default: `{}`).
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions`, to
* support directives when serializing to HTML.
*/
export function directiveHtml(options) {
const options_ = options || {};
return {
enter: {
directiveContainer() {
enter.call(this, 'containerDirective');
},
directiveContainerAttributes: enterAttributes,
directiveContainerLabel: enterLabel,
directiveContainerContent() {
this.buffer();
},
directiveLeaf() {
enter.call(this, 'leafDirective');
},
directiveLeafAttributes: enterAttributes,
directiveLeafLabel: enterLabel,
directiveText() {
enter.call(this, 'textDirective');
},
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
}
};
/**
* @this {CompileContext}
* @param {Directive['type']} type
*/
function enter(type) {
let stack = this.getData('directiveStack');
if (!stack) this.setData('directiveStack', stack = []);
stack.push({
type,
name: ''
});
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitName(token) {
const stack = this.getData('directiveStack');
stack[stack.length - 1].name = this.sliceSerialize(token);
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function enterLabel() {
this.buffer();
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitLabel() {
const data = this.resume();
const stack = this.getData('directiveStack');
stack[stack.length - 1].label = data;
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function enterAttributes() {
this.buffer();
this.setData('directiveAttributes', []);
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitAttributeIdValue(token) {
const attributes = this.getData('directiveAttributes');
attributes.push(['id', parseEntities(this.sliceSerialize(token), {
attribute: true
})]);
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitAttributeClassValue(token) {
const attributes = this.getData('directiveAttributes');
attributes.push(['class', parseEntities(this.sliceSerialize(token), {
attribute: true
})]);
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitAttributeName(token) {
// Attribute names in CommonMark are significantly limited, so character
// references cant exist.
const attributes = this.getData('directiveAttributes');
attributes.push([this.sliceSerialize(token), '']);
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitAttributeValue(token) {
const attributes = this.getData('directiveAttributes');
attributes[attributes.length - 1][1] = parseEntities(this.sliceSerialize(token), {
attribute: true
});
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitAttributes() {
const stack = this.getData('directiveStack');
const attributes = this.getData('directiveAttributes');
/** @type {Record<string, string>} */
const cleaned = {};
let index = -1;
while (++index < attributes.length) {
const 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;
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitContainerContent() {
const data = this.resume();
const stack = this.getData('directiveStack');
stack[stack.length - 1].content = data;
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exitContainerFence() {
const stack = this.getData('directiveStack');
const directive = stack[stack.length - 1];
if (!directive._fenceCount) directive._fenceCount = 0;
directive._fenceCount++;
if (directive._fenceCount === 1) this.setData('slurpOneLineEnding', true);
}
/**
* @this {CompileContext}
* @type {MicromarkHandle}
*/
function exit() {
const stack = this.getData('directiveStack');
const directive = stack.pop();
/** @type {boolean | undefined} */
let found;
/** @type {boolean | undefined} */
let result;
if (own.call(options_, directive.name)) {
result = options_[directive.name].call(this, directive);
found = result !== false;
}
if (!found && own.call(options_, '*')) {
result = options_['*'].call(this, directive);
found = result !== false;
}
if (!found && directive.type !== 'textDirective') {
this.setData('slurpOneLineEnding', true);
}
}
}
+10
View File
@@ -0,0 +1,10 @@
/**
* Create an extension for `micromark` to enable directive syntax.
*
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions`, to
* enable directive syntax.
*/
export function directive(): Extension;
import type { Extension } from 'micromark-util-types';
//# sourceMappingURL=syntax.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"syntax.d.ts","sourceRoot":"","sources":["syntax.js"],"names":[],"mappings":"AASA;;;;;;GAMG;AACH,6BAJa,SAAS,CASrB;+BApB2B,sBAAsB"}
+25
View File
@@ -0,0 +1,25 @@
/**
* @import {Extension} from 'micromark-util-types'
*/
import { directiveContainer } from './directive-container.js';
import { directiveLeaf } from './directive-leaf.js';
import { directiveText } from './directive-text.js';
/**
* Create an extension for `micromark` to enable directive syntax.
*
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions`, to
* enable directive syntax.
*/
export function directive() {
return {
text: {
[58]: directiveText
},
flow: {
[58]: [directiveContainer, directiveLeaf]
}
};
}
+22
View File
@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) Titus Wormer <tituswormer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+124
View File
@@ -0,0 +1,124 @@
{
"author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
"bugs": "https://github.com/micromark/micromark-extension-directive/issues",
"contributors": [
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
],
"dependencies": {
"devlop": "^1.0.0",
"micromark-factory-space": "^2.0.0",
"micromark-factory-whitespace": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0",
"parse-entities": "^4.0.0"
},
"description": "micromark extension to support generic directives (`:cite[smith04]`)",
"devDependencies": {
"@types/node": "^22.0.0",
"c8": "^10.0.0",
"html-void-elements": "^3.0.0",
"micromark": "^4.0.0",
"micromark-build": "^2.0.0",
"prettier": "^3.0.0",
"remark-cli": "^12.0.0",
"remark-preset-wooorm": "^11.0.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"xo": "^0.60.0"
},
"exports": {
"development": "./dev/index.js",
"default": "./index.js"
},
"files": [
"dev/",
"index.d.ts",
"index.js",
"lib/"
],
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
},
"keywords": [
"container",
"directive",
"extension",
"generic",
"markdown",
"micromark-extension",
"micromark",
"unified"
],
"license": "MIT",
"name": "micromark-extension-directive",
"prettier": {
"bracketSpacing": false,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false
},
"remarkConfig": {
"plugins": [
"remark-preset-wooorm"
]
},
"repository": "micromark/micromark-extension-directive",
"scripts": {
"build": "tsc --build --clean && tsc --build && type-coverage && micromark-build",
"format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
"test-api-prod": "node --conditions production test/index.js",
"test-api-dev": "node --conditions development test/index.js",
"test-api": "npm run test-api-dev && npm run test-api-prod",
"test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
"test": "npm run build && npm run format && npm run test-coverage"
},
"sideEffects": false,
"typeCoverage": {
"atLeast": 100,
"strict": true
},
"type": "module",
"version": "4.0.0",
"xo": {
"overrides": [
{
"files": [
"**/*.d.ts"
],
"rules": {
"@typescript-eslint/array-type": [
"error",
{
"default": "generic"
}
],
"@typescript-eslint/ban-types": [
"error",
{
"extendDefaults": true
}
],
"@typescript-eslint/consistent-indexed-object-style": [
"error",
"index-signature"
],
"@typescript-eslint/consistent-type-definitions": [
"error",
"interface"
]
}
}
],
"prettier": true,
"rules": {
"logical-assignment-operators": "off",
"max-params": "off",
"unicorn/no-this-assignment": "off",
"unicorn/prefer-at": "off"
}
}
}
+451
View File
@@ -0,0 +1,451 @@
# micromark-extension-directive
[![Build][build-badge]][build]
[![Coverage][coverage-badge]][coverage]
[![Downloads][downloads-badge]][downloads]
[![Size][size-badge]][size]
[![Sponsors][sponsors-badge]][collective]
[![Backers][backers-badge]][collective]
[![Chat][chat-badge]][chat]
[micromark][] extensions to support [directives][prop] (`:cite[smith04]` and
such).
## Contents
* [What is this?](#what-is-this)
* [When to use this](#when-to-use-this)
* [Install](#install)
* [Use](#use)
* [API](#api)
* [`directive()`](#directive)
* [`directiveHtml(options?)`](#directivehtmloptions)
* [`Directive`](#directive-1)
* [`Handle`](#handle)
* [`HtmlOptions`](#htmloptions)
* [Authoring](#authoring)
* [HTML](#html)
* [CSS](#css)
* [Syntax](#syntax)
* [Compatibility](#compatibility)
* [Security](#security)
* [Related](#related)
* [Contribute](#contribute)
* [License](#license)
## What is this?
This package contains two extensions that add support for directive syntax in
markdown to [`micromark`][micromark].
## When to use this
This project is useful when you want to solve the need for an infinite number
of potential extensions to markdown in a single markdown-esque way.
You can use these extensions when you are working with [`micromark`][micromark]
already.
When you need a syntax tree,
you can combine this package with
[`mdast-util-directive`][mdast-util-directive].
All these packages are used [`remark-directive`][remark-directive],
which focusses on making it easier to transform content by abstracting these
internals away.
## Install
This package is [ESM only][esm].
In Node.js (version 16+),
install with [npm][]:
[npm][]:
```sh
npm install micromark-extension-directive
```
In Deno with [`esm.sh`][esmsh]:
```js
import {directive, directiveHtml} from 'https://esm.sh/micromark-extension-directive@4'
```
In browsers with [`esm.sh`][esmsh]:
```html
<script type="module">
import {directive, directiveHtml} from 'https://esm.sh/micromark-extension-directive@4?bundle'
</script>
```
## Use
Say our document `example.md` contains:
```markdown
A lovely language know as :abbr[HTML]{title="HyperText Markup Language"}.
```
…and our module `example.js` looks as follows:
```js
/**
* @import {Handle} from 'micromark-extension-directive'
* @import {CompileContext} from 'micromark-util-types'
*/
import fs from 'node:fs/promises'
import {micromark} from 'micromark'
import {directive, directiveHtml} from 'micromark-extension-directive'
const output = micromark(await fs.readFile('example.md'), {
extensions: [directive()],
htmlExtensions: [directiveHtml({abbr})]
})
console.log(output)
/**
* @this {CompileContext}
* @type {Handle}
* @returns {false | undefined}
*/
function abbr(d) {
if (d.type !== 'textDirective') return false
this.tag('<abbr')
if (d.attributes && 'title' in d.attributes) {
this.tag(' title="' + this.encode(d.attributes.title) + '"')
}
this.tag('>')
this.raw(d.label || '')
this.tag('</abbr>')
}
```
…now running `node example.js` yields:
```html
<p>A lovely language know as <abbr title="HyperText Markup Language">HTML</abbr>.</p>
```
## API
This package exports the identifiers [`directive`][api-directive] and
[`directiveHtml`][api-directive-html].
There is no default export.
It exports the [TypeScript][] types
[`Directive`][api-directive-type],
[`Handle`][api-handle],
and [`HtmlOptions`][api-html-options].
The export map supports the [`development` condition][development].
Run `node --conditions development module.js` to get instrumented dev code.
Without this condition,
production code is loaded.
### `directive()`
Create an extension for `micromark` to enable directive syntax.
###### Returns
Extension for `micromark` that can be passed in `extensions`,
to enable directive syntax
([`Extension`][micromark-extension]).
### `directiveHtml(options?)`
Create an extension for `micromark` to support directives when serializing to
HTML.
> 👉 **Note**:
> this uses KaTeX to render math.
###### Parameters
* `options`
([`HtmlOptions`][api-html-options], default: `{}`)
— configuration
###### Returns
Extension for `micromark` that can be passed in `htmlExtensions`,
to support directives when serializing to HTML
([`HtmlExtension`][micromark-html-extension]).
### `Directive`
Structure representing a directive (TypeScript type).
###### Fields
* `type`
(`'containerDirective'`, `'leafDirective'`, or `'textDirective'`)
— kind
* `name`
(`string`)
— name of directive
* `label`
(`string` or `undefined`)
— compiled HTML content that was in `[brackets]`
* `attributes`
(`Record<string, string>` or `undefined`)
— object w/ HTML attributes
* `content`
(`string` or `undefined`)
— compiled HTML content inside container directive
### `Handle`
Handle a directive (TypeScript type).
###### Parameters
* `this` ([`CompileContext`][micromark-compile-context])
— current context
* `directive` ([`Directive`][api-directive-type])
— directive
###### Returns
Signal whether the directive was handled
(`boolean`, default: `true`).
Yield `false` to let the fallback (a special handle for `'*'`) handle it.
### `HtmlOptions`
Configuration (TypeScript type).
> 👉 **Note**:
> the special field `'*'` can be used to specify a fallback handle to handle
> all otherwise unhandled directives.
###### Type
```ts
type HtmlOptions = Record<string, Handle>
```
## Authoring
When authoring markdown with directives,
keep in mind that they dont work in most places.
On your own site it can be great!
## HTML
You can define how directives are turned into HTML.
If directives are not handled,
they do not emit anything.
## CSS
How to display directives is left as an exercise for the reader.
## Syntax
The syntax looks like this:
```markdown
Directives in text can form with a single colon,
such as :cite[smith04].
Their syntax is `:name[label]{attributes}`.
Leafs (block without content) can form by using two colons:
::youtube[Video of a cat in a box]{vid=01ab2cd3efg}
Their syntax is `::name[label]{attributes}` on its own line.
Containers (blocks with content) can form by using three colons:
:::spoiler
He dies.
:::
The `name` part is required.
The first character must be a letter,
other characters can be alphanumerical, `-`, and `_`.
`-` or `_` cannot end a name.
The `[label]` part is optional (`:x` and `:x[]` are equivalent)†.
When used,
it can include text constructs such as emphasis and so on: `x[a *b* c]`.
The `{attributes}` part is optional (`:x` and `:x{}` are equivalent)†.
When used,
it is handled like HTML attributes, such as that `{a}`, `{a=""}`, and `{a=''}`
but also `{a=b}`, `{a="b"}`, and `{a='b'}` are equivalent.
Shortcuts are available for `id=` (`{#readme}` for `{id=readme}`) and
`class` (`{.big}` for `{class=big}`).
When multiple ids are found,
the last is used; when multiple classes are found,
they are combined:
`{.red class=green .blue}` is equivalent to
`{.red .green .blue}` and `{class="red green blue"}`.
† there is one case where a name must be followed by an empty label or empty
attributes:
a *text* directive that only has a name,
cannot be followed by a colon.
So,
`:red:` doesnt work.
Use either `:red[]` or `:red{}` instead.
The reason for this is to allow GitHub emoji (gemoji) and directives to coexist.
Containers can be nested by using more colons outside:
::::spoiler
He dies.
:::spoiler
She is born.
:::
::::
The closing fence must include the same or more colons as the opening.
If no closing is found,
the container runs to the end of its parent container
(block quote, list item, document, or other container).
::::spoiler
These three are not enough to close
:::
So this line is also part of the container.
```
Note that while other implementations are sometimes loose in what they allow,
this implementation mimics CommonMark as closely as possible:
* whitespace is not allowed between colons and name (~~`: a`~~),
name and label (~~`:a []`~~),
name and attributes (~~`:a {}`~~),
or label and attributes (~~`:a[] {}`~~)
— because its not allowed in links either
(~~`[] ()`~~)
* no trailing colons allowed on the opening fence of a container
(~~`:::a:::`~~)
— because its not allowed in fenced code either
* the label and attributes in a leaf or container cannot include line endings
(~~`::a[b\nc]`~~)
— because its not allowed in fenced code either
## Compatibility
Projects maintained by the unified collective are compatible with maintained
versions of Node.js.
When we cut a new major release,
we drop support for unmaintained versions of Node.
This means we try to keep the current release line,
`micromark-extension-directive@4`,
compatible with Node.js 16.
This package works with `micromark` version `4` and later.
## Security
This package is safe assuming that you write safe handlers.
Any vulnerability in your code could open you to a
[cross-site scripting (XSS)][xss] attack.
## Related
* [`remark-directive`][remark-directive]
— remark plugin to support directives
* [`mdast-util-directive`][mdast-util-directive]
— mdast utility to support directives
## Contribute
See [`contributing.md` in `micromark/.github`][contributing] for ways to get
started.
See [`support.md`][support] for ways to get help.
This project has a [code of conduct][coc].
By interacting with this repository,
organization,
or community you agree to abide by its terms.
## License
[MIT][license] © [Titus Wormer][author]
<!-- Definitions -->
[api-directive]: #directive
[api-directive-html]: #directivehtmloptions
[api-directive-type]: #directive-1
[api-handle]: #handle
[api-html-options]: #htmloptions
[author]: https://wooorm.com
[backers-badge]: https://opencollective.com/unified/backers/badge.svg
[build]: https://github.com/micromark/micromark-extension-directive/actions
[build-badge]: https://github.com/micromark/micromark-extension-directive/workflows/main/badge.svg
[chat]: https://github.com/micromark/micromark/discussions
[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg
[coc]: https://github.com/micromark/.github/blob/main/code-of-conduct.md
[collective]: https://opencollective.com/unified
[contributing]: https://github.com/micromark/.github/blob/main/contributing.md
[coverage]: https://codecov.io/github/micromark/micromark-extension-directive
[coverage-badge]: https://img.shields.io/codecov/c/github/micromark/micromark-extension-directive.svg
[development]: https://nodejs.org/api/packages.html#packages_resolving_user_conditions
[downloads]: https://www.npmjs.com/package/micromark-extension-directive
[downloads-badge]: https://img.shields.io/npm/dm/micromark-extension-directive.svg
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
[esmsh]: https://esm.sh
[license]: license
[mdast-util-directive]: https://github.com/syntax-tree/mdast-util-directive
[micromark]: https://github.com/micromark/micromark
[micromark-compile-context]: https://github.com/micromark/micromark/blob/41e3c4c/packages/micromark-util-types/index.js#L457
[micromark-extension]: https://github.com/micromark/micromark#syntaxextension
[micromark-html-extension]: https://github.com/micromark/micromark#htmlextension
[npm]: https://docs.npmjs.com/cli/install
[prop]: https://talk.commonmark.org/t/generic-directives-plugins-syntax/444
[remark-directive]: https://github.com/remarkjs/remark-directive
[size]: https://bundlejs.com/?q=micromark-extension-directive
[size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=micromark-extension-directive
[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
[support]: https://github.com/micromark/.github/blob/main/support.md
[typescript]: https://www.typescriptlang.org
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting