|
| 1 | +import { encode } from 'html-entities' |
| 2 | +import { TokenizationError, type TagToken, type TopLevelToken } from 'liquidjs' |
| 3 | + |
| 4 | +import { codeLanguages } from '@/code-tabs/lib/languages' |
| 5 | + |
| 6 | +interface LiquidTemplate { |
| 7 | + [key: string]: unknown |
| 8 | +} |
| 9 | + |
| 10 | +interface LiquidStream { |
| 11 | + on(event: string, callback: (template?: LiquidTemplate) => void): LiquidStream |
| 12 | + stop(): void |
| 13 | + start(): void |
| 14 | +} |
| 15 | + |
| 16 | +interface LiquidEngine { |
| 17 | + parser: { |
| 18 | + parseStream(tokens: TopLevelToken[]): LiquidStream |
| 19 | + } |
| 20 | + renderer: { |
| 21 | + renderTemplates(templates: LiquidTemplate[], scope: Record<string, unknown>): string |
| 22 | + } |
| 23 | + parseAndRender(template: string, context: Record<string, string>): string |
| 24 | +} |
| 25 | + |
| 26 | +interface LiquidBlockTag { |
| 27 | + type: 'block' |
| 28 | + templates: LiquidTemplate[] |
| 29 | + liquid: LiquidEngine | null |
| 30 | + parse(tagToken: TagToken, remainTokens: TopLevelToken[]): void |
| 31 | + render(scope: Record<string, unknown>): Generator<unknown, unknown, unknown> |
| 32 | +} |
| 33 | + |
| 34 | +const codeTabsTemplate = '<div class="ghd-codetabs">{{ output }}</div>\n' |
| 35 | +const codeTabTemplate = |
| 36 | + '<div class="ghd-codetab" data-lang="{{ languageKey }}" data-label="{{ label }}"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">{{ label }}</div>{{ output }}</div>\n' |
| 37 | +const codeTabSyntax = /^(?<key>[a-z0-9-]+)(?:\s+"(?<label>[^"]+)")?$/ |
| 38 | +const codeTabSyntaxHelp = |
| 39 | + 'Syntax Error in tag \'codetab\' - Valid syntax: codetab <language-key> ["Label"]' |
| 40 | + |
| 41 | +export const tags = ['codetabs', 'codetab'] |
| 42 | + |
| 43 | +function parseBlockTemplates( |
| 44 | + context: LiquidBlockTag, |
| 45 | + tagToken: TagToken, |
| 46 | + remainTokens: TopLevelToken[], |
| 47 | + endTagName: string, |
| 48 | +): void { |
| 49 | + context.templates = [] |
| 50 | + |
| 51 | + const stream = context.liquid!.parser.parseStream(remainTokens) |
| 52 | + stream |
| 53 | + .on(`tag:${endTagName}`, () => stream.stop()) |
| 54 | + .on('template', (template?: LiquidTemplate) => { |
| 55 | + if (template) context.templates.push(template) |
| 56 | + }) |
| 57 | + .on('end', () => { |
| 58 | + throw new Error(`tag ${tagToken.getText()} not closed`) |
| 59 | + }) |
| 60 | + stream.start() |
| 61 | +} |
| 62 | + |
| 63 | +export const CodeTabs: LiquidBlockTag = { |
| 64 | + type: 'block', |
| 65 | + templates: [], |
| 66 | + liquid: null, |
| 67 | + |
| 68 | + parse(tagToken: TagToken, remainTokens: TopLevelToken[]): void { |
| 69 | + if (tagToken.args.trim()) { |
| 70 | + throw new TokenizationError( |
| 71 | + "Syntax Error in tag 'codetabs' - This tag does not accept arguments", |
| 72 | + tagToken, |
| 73 | + ) |
| 74 | + } |
| 75 | + |
| 76 | + parseBlockTemplates(this, tagToken, remainTokens, 'endcodetabs') |
| 77 | + }, |
| 78 | + |
| 79 | + *render(scope: Record<string, unknown>): Generator<unknown, unknown, unknown> { |
| 80 | + const output = yield this.liquid!.renderer.renderTemplates(this.templates, scope) |
| 81 | + return yield this.liquid!.parseAndRender(codeTabsTemplate, { output }) |
| 82 | + }, |
| 83 | +} |
| 84 | + |
| 85 | +interface CodeTabTag extends LiquidBlockTag { |
| 86 | + languageKey: string |
| 87 | + label: string |
| 88 | +} |
| 89 | + |
| 90 | +export const CodeTab: CodeTabTag = { |
| 91 | + type: 'block', |
| 92 | + templates: [], |
| 93 | + liquid: null, |
| 94 | + languageKey: '', |
| 95 | + label: '', |
| 96 | + |
| 97 | + parse(tagToken: TagToken, remainTokens: TopLevelToken[]): void { |
| 98 | + const args = tagToken.args.trim() |
| 99 | + const match = args.match(codeTabSyntax) |
| 100 | + |
| 101 | + if (!match?.groups) { |
| 102 | + throw new TokenizationError(codeTabSyntaxHelp, tagToken) |
| 103 | + } |
| 104 | + |
| 105 | + const { key, label } = match.groups |
| 106 | + if (!Object.prototype.hasOwnProperty.call(codeLanguages, key)) { |
| 107 | + throw new TokenizationError( |
| 108 | + `Unknown codetab language '${key}'. Valid values: ${Object.keys(codeLanguages).join(', ')}`, |
| 109 | + tagToken, |
| 110 | + ) |
| 111 | + } |
| 112 | + |
| 113 | + this.languageKey = encode(key) |
| 114 | + this.label = encode(label || codeLanguages[key]) |
| 115 | + |
| 116 | + parseBlockTemplates(this, tagToken, remainTokens, 'endcodetab') |
| 117 | + }, |
| 118 | + |
| 119 | + *render(scope: Record<string, unknown>): Generator<unknown, unknown, unknown> { |
| 120 | + const output = yield this.liquid!.renderer.renderTemplates(this.templates, scope) |
| 121 | + return yield this.liquid!.parseAndRender(codeTabTemplate, { |
| 122 | + languageKey: this.languageKey, |
| 123 | + label: this.label, |
| 124 | + output, |
| 125 | + }) |
| 126 | + }, |
| 127 | +} |
0 commit comments