UNPKG

micromark-extension-cjk-friendly-gfm-strikethrough

Version:

micromark extension to make Markdown strikethrough (`~~`) in GFM more friendly with Chinese, Japanese, and Korean (CJK)

149 lines (147 loc) 5.29 kB
import { ok } from "devlop"; import { TwoPreviousCode, classifyCharacter, classifyPrecedingCharacter, isCjk, isCodeHighSurrogate, isCodeLowSurrogate, isIvs, isNonCjkPunctuation, isUnicodeWhitespace, tryGetGenuineNextCode, tryGetGenuinePreviousCode } from "micromark-extension-cjk-friendly-util"; import { splice } from "micromark-util-chunked"; import { resolveAll } from "micromark-util-resolve-all"; import { codes, constants, types } from "micromark-util-symbol"; //#region src/strikethrough.ts /** * Create an extension for `micromark` to enable GFM strikethrough syntax. * * @param {Options | null | undefined} [options={}] * Configuration. * @returns {Extension} * Extension for `micromark` that can be passed in `extensions`, to * enable GFM strikethrough syntax. */ function gfmStrikethroughCjkFriendly(options) { let single = (options || {}).singleTilde; const tokenizer = { name: "strikethrough", tokenize: tokenizeStrikethrough, resolveAll: resolveAllStrikethrough }; if (single === null || single === void 0) single = true; return { text: { [codes.tilde]: tokenizer }, insideSpan: { null: [tokenizer] }, attentionMarkers: { null: [codes.tilde] } }; /** * Take events and resolve strikethrough. * * @type {Resolver} */ function resolveAllStrikethrough(events, context) { let index = -1; while (++index < events.length) if (events[index][0] === "enter" && events[index][1].type === "strikethroughSequenceTemporary" && events[index][1]._close) { let open = index; while (open--) if (events[open][0] === "exit" && events[open][1].type === "strikethroughSequenceTemporary" && events[open][1]._open && events[index][1].end.offset - events[index][1].start.offset === events[open][1].end.offset - events[open][1].start.offset) { events[index][1].type = "strikethroughSequence"; events[open][1].type = "strikethroughSequence"; const strikethrough = { type: "strikethrough", start: Object.assign({}, events[open][1].start), end: Object.assign({}, events[index][1].end) }; const text = { type: "strikethroughText", start: Object.assign({}, events[open][1].end), end: Object.assign({}, events[index][1].start) }; /** @type {Array<Event>} */ const nextEvents = [ [ "enter", strikethrough, context ], [ "enter", events[open][1], context ], [ "exit", events[open][1], context ], [ "enter", text, context ] ]; const insideSpan = context.parser.constructs.insideSpan.null; if (insideSpan) splice(nextEvents, nextEvents.length, 0, resolveAll(insideSpan, events.slice(open + 1, index), context)); splice(nextEvents, nextEvents.length, 0, [ [ "exit", text, context ], [ "enter", events[index][1], context ], [ "exit", events[index][1], context ], [ "exit", strikethrough, context ] ]); splice(events, open - 1, index - open + 3, nextEvents); index = open + nextEvents.length - 2; break; } } index = -1; while (++index < events.length) if (events[index][1].type === "strikethroughSequenceTemporary") events[index][1].type = types.data; return events; } /** * @type {Tokenizer} */ function tokenizeStrikethrough(effects, ok$1, nok) { const { now, sliceSerialize, previous: tentativePrevious } = this; const previous = isCodeLowSurrogate(tentativePrevious) ? tryGetGenuinePreviousCode(tentativePrevious, now(), sliceSerialize) : tentativePrevious; const before = classifyCharacter(previous); const twoPrevious = new TwoPreviousCode(previous, now(), sliceSerialize); const beforePrimary = classifyPrecedingCharacter(before, twoPrevious.value.bind(twoPrevious), previous); const events = this.events; let size = 0; return start; function start(code) { ok(code === codes.tilde, "expected `~`"); if (previous === codes.tilde && events[events.length - 1][1].type !== types.characterEscape) return nok(code); effects.enter("strikethroughSequenceTemporary"); return more(code); } /** @type {State} */ function more(code) { const before$1 = classifyCharacter(previous); if (code === codes.tilde) { if (size > 1) return nok(code); effects.consume(code); size++; return more; } if (size < 2 && !single) return nok(code); const token = effects.exit("strikethroughSequenceTemporary"); const after = classifyCharacter(isCodeHighSurrogate(code) ? tryGetGenuineNextCode(code, now(), sliceSerialize) : code); const beforeSpaceOrNonCjkPunctuation = isNonCjkPunctuation(beforePrimary) || isUnicodeWhitespace(beforePrimary); const afterSpaceOrNonCjkPunctuation = isNonCjkPunctuation(after) || isUnicodeWhitespace(after); const beforeCjkOrIvs = isCjk(beforePrimary) || isIvs(before$1); token._open = !afterSpaceOrNonCjkPunctuation || after === constants.attentionSideAfter && (beforeSpaceOrNonCjkPunctuation || beforeCjkOrIvs); token._close = !beforeSpaceOrNonCjkPunctuation || before$1 === constants.attentionSideAfter && (afterSpaceOrNonCjkPunctuation || isCjk(after)); return ok$1(code); } } } //#endregion export { gfmStrikethroughCjkFriendly };