Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
Built and signed on GitHub Actions
latest
nvlang/sveltexSvelte + Markdown + LaTeX
This package works with Node.js, Deno, BunIt is unknown whether this package works with Cloudflare Workers, Browsers




JSR Score
100%
Published
7 months ago (0.4.4)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529// File description: Types related to the `CodeHandler` class. /* eslint-disable tsdoc/syntax */ // Types import type { HighlightJsLanguage, HighlightJsThemeName, StarryNightLanguage, StarryNightThemeName, } from '../../data/code.ts'; import type { SimpleEscapeInstruction } from './Verbatim.ts'; import type { CssConfiguration } from './Css.ts'; import type { DeepRequiredDefined, RequiredDefinedNotNull, StringLiteralUnion, } from '../utils/utility-types.ts'; import type { Transformers } from './Handler.ts'; /** * Union type of supported code backends. */ export type CodeBackend = | 'highlight.js' | 'starry-night' | 'shiki' | 'escape' | 'none'; interface CommonCodeConfiguration { /** * Whether to add a class to the `<code>` tag with the name of the language * of the code it contains. If a string is provided, a class will be added * and the provided string will be used as the prefix for the class name. * * @defaultValue * ```ts * 'language-' * ``` * * @example * ```html * <pre><code class="language-javascript"> * ... * </code></pre> * ``` */ addLanguageClass?: boolean | string | undefined; /** * Transformers to apply to * - the inner content of the input which `CodeHandler` will receive for * processing, or to * - the inner content of the output produced by the `CodeHandler` (or by * whatever handler it forwards the content to). */ transformers?: Transformers<CodeProcessOptionsBase> | undefined; /** * Sveltex supports inline code highlighting, provided that the inline code * span contains a special string of some sort that acts as a language tag * and possibly meta string. However, the question is what part of the * inline code span should be considered to be this special string (if any). * * Unfortunately, as far as I'm aware, there is no widespread convention for * this, let alone a formal specification. As such, this option allows you * to specify a custom function that will be used to extract the special * string from the inline code span: * * @param inlineCode - The inner content of the code span (i.e., if the code * span is `` `example` ``, it would receive the string `'example'`). * @param validLanguageTag - A function that takes a string and returns * `true` if that string is a valid language tag for the current code * handler, or `false` otherwise. Aliases defined in `langAlias` are also * taken into account. * * @returns An object with the following properties: * * - `lang?: string`: The language tag, if any. * - `meta?: string`: The meta string, if any. * - `code: string`: The code that should be highlighted. * * @remarks * Since this function only receives the inner content of the code span, * syntaxes such as `` `code`{js} `` are not supported. * * @defaultValue * ```ts * (inlineCode, validLanguageTag) => { * let code = inlineCode; * let lang: string | undefined; * let meta: string | undefined; * if (code.startsWith('{')) { * const m = code.match(/^\{(.+?)\}\s(\s*\S[\w\W]*)$/); * const specialCandidate = m?.[1]; * const codeCandidate = m?.[2]; * if (specialCandidate && codeCandidate) { * const space = specialCandidate.match(/\s/)?.index; * const tag = specialCandidate.slice(0, space); * if (validLanguageTag(tag)) { * code = codeCandidate; * lang = tag; * if (space) meta = specialCandidate.slice(space + 1); * } * } * } else { * const m = code.match(/^([\w-]['\w-]*)\s(\s*\S[\w\W]*)$/); * const tag = m?.[1]; * const codeCandidate = m?.[2]; * if (tag && codeCandidate && validLanguageTag(tag)) { * code = codeCandidate; * lang = tag; * } * } * return { code, lang, meta }; * }; * ``` */ inlineMeta?: | (( inlineCode: string, validLanguageTag: (tag: string) => boolean, ) => | { lang?: string | undefined; meta?: string | undefined; code: string; } | null | undefined) | undefined | null; /** * Whether to append a newline to code blocks. Should have no visual impact * whatsoever, as `<pre><code>example</code></pre>` and * `<pre><code>example\n</code></pre>` render identically. Defaults to * `true` for the sake of CommonMark compliance. * * @defaultValue * ```ts * true * ``` */ appendNewline?: boolean | undefined; } /** * Type of the configuration object used to configure the code processor. * * @typeParam B - Syntax highlighting backend. * @returns Depending on `B`: * - `highlight.js`: The module's `HLJSOptions` type. * - `starry-night`: `{ customLanguages?: Grammar[]; options?: Options }`, where * `Options` and `Grammar` are types from the `@wooorm/starry-night` module. * - `escape`: {@link SimpleEscapeInstruction | `SimpleEscapeInstruction`}. * - `custom`: `Record<string, unknown>`. * - `none`: `Record<string, unknown>`. * * @remarks This is the type of the argument passed to the code handler's * `configure` function, together with {@link CommonCodeConfiguration | `GeneralCodeConfiguration`}. */ type SpecificCodeConfiguration<B extends CodeBackend> = B extends 'highlight.js' ? HighlightJsConfig : B extends 'starry-night' ? StarryNightConfig : B extends 'shiki' ? ShikiConfig : B extends 'escape' ? { escape?: SimpleEscapeInstruction | undefined; } : B extends 'none' ? object : never; interface HighlightJsConfig { /** * Configure the theme to use for syntax highlighting. */ theme?: CodeTheme<'highlight.js'> | undefined; /** * Options to pass to the `highlight` function from * `highlight.js`. */ 'highlight.js'?: | Partial< Omit< import('npm:highlight.js@^11.11.1').HLJSOptions, 'noHighlightRe' | 'languageDetectRe' > > | undefined; /** * Record of language aliases. * * @example * ```ts * { * 'example-alias': 'JavaScript', * } * ``` */ langAlias?: | Record<string, StringLiteralUnion<HighlightJsLanguage>> | undefined; } interface StarryNightConfig { /** * Configure the theme to use for syntax highlighting. */ theme?: CodeTheme<'starry-night'> | undefined; /** * Languages to register. If `'all'`, all languages are * registered. If `'common'`, some ≈35 common languages are * registered. * * If an array is provided, each entry will be treated as a * language or custom grammar to register. Furthermore, the * first entry of the array may be `'all'` or `'common'` to * extend the respective sets of languages. * * @defaultValue * ```ts * 'common' * ``` */ languages?: | (StarryNightLanguage | import('npm:@wooorm/starry-night@^3.7.0').Grammar)[] | [ 'common', ...( | StarryNightLanguage | import('npm:@wooorm/starry-night@^3.7.0').Grammar )[], ] | 'common' | ['all', ...import('npm:@wooorm/starry-night@^3.7.0').Grammar[]] | 'all' | undefined; /** * Default language. * * @defaultValue * ```ts * null * ``` */ lang?: StarryNightLanguage | null | undefined; /** * Record of language aliases. * * @example * ```ts * { * 'example-alias': 'JavaScript', * } * ``` */ langAlias?: | Record<string, StringLiteralUnion<StarryNightLanguage>> | undefined; } interface ShikiConfig { /** * Default options for Shiki's highlighter. */ shiki?: | (Omit< Partial< import('npm:shiki@^3.2.2').CodeToHastOptions< import('npm:shiki@^3.2.2').BundledLanguage, import('npm:shiki@^3.2.2').BundledTheme > >, 'structure' > & // For some reason, the `theme` and `themes` props disappear after // `Omit`ting them from the `CodeToHastOptions` type, so we have // to add them again. Partial< import('npm:shiki@^3.2.2').CodeOptionsThemes< import('npm:shiki@^3.2.2').BundledTheme > >) | undefined; /** * Customize how the meta string is parsed, if present. The result will be * merged into the `meta` prop that is passed to any plugins/transformers in * Shiki's pipeline. * * @defaultValue * ```ts * (metaString) => { * return Object.fromEntries( * metaString * .split(' ') * .reduce( * ( * prev: [string, boolean | string][], * curr: string, * ) => { * const [key, value] = curr.split('='); * const isNormalKey = * key && /^[A-Z0-9]+$/i.test(key); * if (isNormalKey) * prev = [...prev, [key, value ?? true]]; * return prev; * }, * [], * ), * ); * } * ``` * * @example * Consider the following code block: * * ````md * ```js key1=value1 key2 key3=false * console.log('Hello, world!'); * ``` * ```` * * The meta string is `'key1=value key2 key3=false'`, and the default * `parseMetaString` function will parse the meta string as follows: * * ```ts * { * key1: 'value1', * key2: true, * key3: 'false' * } * ``` */ parseMetaString?: | (( metaString: string, code: string, lang: string, ) => Record<string, unknown> | undefined | null) | undefined | null; /** * Record of language aliases. * * @example * ```ts * { * 'example-alias': 'javascript', * } * ``` */ langAlias?: | Record<string, StringLiteralUnion<import('npm:shiki@^3.2.2').BundledLanguage>> | undefined; } export type CodeBackendWithCss = 'highlight.js' | 'starry-night'; /** * Type of the input passed to the {@link CodeHandler | `CodeHandler`}'s * {@link CodeHandler.configure | `configure`} method. * * @typeParam B - Code backend. */ export type CodeConfiguration<B extends CodeBackend> = CommonCodeConfiguration & SpecificCodeConfiguration<B>; /** * Return type of the {@link CodeHandler | `CodeHandler`}'s `configuration` * getter. * * @typeParam B - Code backend. */ export type FullCodeConfiguration<B extends CodeBackend> = DeepRequiredDefined<CommonCodeConfiguration> & (B extends CodeBackendWithCss ? Omit<SpecificCodeConfiguration<B>, 'theme'> & { theme: FullCodeTheme<B>; } : // Shiki's configuration specifies that either `theme` or `themes` // be set, but not both. This is by design, and it's good for the // user (intellisense, type safety, etc.), but unfortunately, TS // is making it very difficult to work with implementation-wise. // In particular, I've found it often complaining about `theme` // or `themes` not being a property of the Shiki configuration, // presumably because, well, only one of them can be a property of // the configuration, and which one that is cannot be determined a // priori. Because of this, I'm modifying the configuration type // to make it weaker; instead of // `... & ({ theme: ... } | { themes: ... })`, I'm changing it to // `... & { theme?: ... } & { themes?: ... }`. This comes with the // caveat of requiring some added care to ensure that the behavior // that the stronger type dictated is still enforced, but has the // benefit of making TS complain less. B extends 'shiki' ? SpecificCodeConfiguration<B> & { shiki: Partial< import('npm:shiki@^3.2.2').CodeOptionsMultipleThemes< import('npm:shiki@^3.2.2').BundledTheme > > & Partial< import('npm:shiki@^3.2.2').CodeOptionsSingleTheme< import('npm:shiki@^3.2.2').BundledTheme > >; } : B extends 'escape' ? DeepRequiredDefined<SpecificCodeConfiguration<B>> : SpecificCodeConfiguration<B>); /** * Type of the {@link CodeHandler | `CodeHandler`}'s `process` function. * * @typeParam B - Code backend. */ export type CodeProcessFn = /** * @param code - The code to process. * @param inline - Whether to parse the code as inline or block. * @returns The processed code, or a promise resolving to it. */ (code: string, options: CodeProcessOptionsBase) => string | Promise<string>; /** * Type of the options object that may be passed to the * {@link CodeHandler | `CodeHandler`}'s `process` function. */ export interface CodeProcessOptionsBase { /** * The language of the code. * * @defaultValue Backend-specific, but generally speaking something like * `'plaintext'`. */ lang?: string | undefined; /** * Whether to parse the code as inline or block. * * @defaultValue `false` */ inline?: boolean | undefined; /** * Additional information to pass to the code handler. When set by * {@link CodeHandler.consumeDelims | `CodeHandler.consumeDelims`}, this is * the string following the language flag in a code block, or undefined if * no such string is present. How this property is used is * configuraton-specific. By default, it isn't used at all. */ metaString?: string | undefined; } export type FullCodeTheme< B extends CodeBackendWithCss, T extends 'cdn' | 'self-hosted' | 'none' = 'cdn' | 'self-hosted' | 'none', > = RequiredDefinedNotNull<CodeTheme<B, T>>; type CodeTheme< B extends CodeBackendWithCss, T extends 'cdn' | 'self-hosted' | 'none' = 'cdn' | 'self-hosted' | 'none', > = { /** * - `'cdn'`: Load the theme's stylesheet from a CDN with a `<link>` tag in * `<svelte:head>`. * - `'self-hosted'`: Fetch the theme's stylesheet from a CDN once, write it * to the local filesystem, and then import it with a `<link>` tag in * `<svelte:head>`. Cache invalidation goes by the filename, which in turn * might depend on the code backend's semver version string. This means * that manual modifications to the stylesheet will always be preserved, * since new versions of the stylesheet will have different filenames. It * furthermore enables the user to customize the stylesheet to their * liking and have those changes persist across updates by simply renaming * the stylesheet in accordance with the version of the code backend * currently in use. * - `'none'`: Do none of the above. */ type?: T | undefined; } & CssConfiguration<T> & (B extends 'starry-night' ? CodeThemeConfigStarryNight : CodeThemeConfigHighlightJs); interface CodeThemeConfigStarryNight { /** * Name of the theme to use. * * @defaultValue `'default'` */ name?: StarryNightThemeName | undefined; /** * - `'light'`: Fetch the light theme. * - `'dark'`: Fetch the dark theme. * - `'both'`: Fetch CSS file that uses * [`prefers-color-scheme`](https://developer.mozilla.org/docs/Web/CSS/%40media/prefers-color-scheme) * to dynamically pick the theme mode. * * @defaultValue `'both'` */ mode?: 'light' | 'dark' | 'both' | undefined; } interface CodeThemeConfigHighlightJs { /** * Name of the theme to use. * * @defaultValue `'default'` */ name?: HighlightJsThemeName | undefined; /** * Whether to fetch the minified version of the theme. * * @defaultValue `true` */ min?: boolean | undefined; }