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
JavaScript
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 };