import { isWordCharacter } from './commonmark/emphasis-matching.ts' export type Flavour = 'lossless' | 'plain' type AlertMarker = { folded: boolean; length: number; panelType: string } export const foldedAlertMarker = '[!NOTE]-' export const highlightDelimiter = '==' const alertWords: Readonly> = { error: 'CAUTION', info: 'NOTE', note: 'IMPORTANT', success: 'TIP', tip: 'TIP', warning: 'WARNING', } const panelTypesByWord: Readonly> = { attention: 'warning', bug: 'error', caution: 'error', check: 'success', danger: 'error', done: 'success', error: 'error', fail: 'error', failure: 'error', hint: 'tip', important: 'note', missing: 'error', success: 'success', tip: 'tip', warning: 'warning', } const unspacedScript = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Khmer}\p{Script=Lao}\p{Script=Myanmar}\p{Script=Thai}]$/u export function alertMarker(panelType: unknown): string { const word = typeof panelType === 'string' && Object.hasOwn(alertWords, panelType) ? alertWords[panelType] : undefined return `[!${word ?? 'NOTE'}]` } export function readAlertMarker(text: string): AlertMarker | undefined { const marker = /^\[!([\w-]+)\]([+-]?)/.exec(text) if (marker === null) return undefined const word = (marker[1] ?? '').toLowerCase() const panelType = Object.hasOwn(panelTypesByWord, word) ? panelTypesByWord[word] : undefined return { folded: marker[2] !== '', length: marker[0].length, panelType: panelType ?? 'info' } } // A marker leads text that ends at it or goes on past whitespace or a hard break. export function leadingMarker(text: string, read: (text: string) => T | undefined): T | undefined { const marker = read(text) if (marker === undefined) return undefined const rest = text.slice(marker.length, marker.length + 2) return rest === '' || /^(?:[ \t\n]|\\\n)/.test(rest) ? marker : undefined } // A delimiter is bounded outside by the code point beyond it, and flanks by the character inside it. export function highlightFlanking(source: string, index: number): { closes: boolean; opens: boolean } { const end = index + highlightDelimiter.length const before = Array.from(source.slice(Math.max(0, index - 2), index)).at(-1) ?? '' const after = Array.from(source.slice(end, end + 2))[0] ?? '' return { closes: flanks(before) && bounds(after), opens: flanks(after) && bounds(before) } } function bounds(character: string): boolean { return !isWordCharacter(character) || unspacedScript.test(character) } function flanks(character: string): boolean { return character !== '' && !/\s/.test(character) } export function taskMarker(state: unknown): string { return state === 'DONE' ? '[x]' : '[ ]' } export function readTaskMarker(text: string): { length: number; state: 'DONE' | 'TODO' } | undefined { const marker = /^\[([ xX])\]/.exec(text) if (marker === null) return undefined return { length: marker[0].length, state: marker[1] === ' ' ? 'TODO' : 'DONE' } }