feat: migrate to starlight

This commit is contained in:
DuroCodes
2024-05-06 17:15:30 -04:00
parent 767acedea7
commit bb190f2d81
15140 changed files with 2828326 additions and 35408 deletions

21
node_modules/@expressive-code/core/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Tibor Schiemann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

48
node_modules/@expressive-code/core/README.md generated vendored Normal file
View File

@@ -0,0 +1,48 @@
# @expressive-code/core
The core package of [Expressive Code](https://expressive-code.com/), an engine for presenting source code on the web.
## Documentation
[Read the Expressive Code docs](https://expressive-code.com/) to learn more about the features provided by Expressive Code.
## When should I use this?
Using this core package directly is **only recommended for advanced use cases**.
Unless you're a plugin or integration author, you should probably use a higher-level package like [`astro-expressive-code`](https://www.npmjs.com/package/astro-expressive-code) or [`rehype-expressive-code`](https://www.npmjs.com/package/rehype-expressive-code) instead of this one.
## Installation
```bash
npm install @expressive-code/core
```
## Usage example
```js
import { ExpressiveCodeEngine } from '@expressive-code/core'
import { toHtml } from '@expressive-code/core/hast'
const ec = new ExpressiveCodeEngine({
plugins: [
// Add your plugins here
],
})
const baseStyles = await ec.getBaseStyles()
const themeStyles = await ec.getThemeStyles()
const renderResult = await ec.render({
code: 'console.log("Hello world!")',
language: 'js',
})
// Output results to the console
console.dir({
baseStyles,
themeStyles,
blockStyles: renderResult.styles,
blockHtml: toHtml(renderResult.renderedGroupAst),
})
```

55
node_modules/@expressive-code/core/dist/hast.d.ts generated vendored Normal file
View File

@@ -0,0 +1,55 @@
import { Element } from 'hast';
export { Element, ElementContent, Node, Nodes, Parent, Parents, Properties, Root } from 'hast';
export { toHtml } from 'hast-util-to-html';
export { toText } from 'hast-util-to-text';
export { matches, select, selectAll } from 'hast-util-select';
export { visit } from 'unist-util-visit';
export { CONTINUE, EXIT, SKIP, visitParents } from 'unist-util-visit-parents';
export { h, s } from 'hastscript';
/**
* Sets a property on the given AST node.
*
* You can set the value to `null` to remove the property.
*/
declare function setProperty(node: Element, propertyName: string, value: string | string[] | null): void;
/**
* Retrieves an array of class names from the given AST node.
*/
declare function getClassNames(node: Element): string[];
/**
* Adds a class name to the given AST node.
*
* If the class name already exists on the node, it will not be added again.
*/
declare function addClassName(node: Element, className: string): void;
/**
* Removes a class name from the given AST node.
*
* If the class name does not exist on the node, nothing will be changed.
*/
declare function removeClassName(node: Element, className: string): void;
/**
* If the given node has a `style` attribute, parses it and returns a map of its styles.
*
* If the node has no `style` attribute, an empty map is returned.
*/
declare function getInlineStyles(node: Element): Map<string, string>;
/**
* Sets the `style` attribute on the given node to the given styles.
*
* Any existing styles will be overwritten.
*/
declare function setInlineStyles(node: Element, styles: Map<string, string>): void;
/**
* Sets a single inline style property on the given node.
*
* You can set the value to an empty string or `null` to remove the property.
*
* Use `valueFormat` to specify how the value should be serialized:
* - `'raw'`: The value is used as-is. This is the default.
* - `'string'`: The value is serialized as a CSS string value, escaping special characters.
*/
declare function setInlineStyle(node: Element, cssProperty: string, value: string | null, valueFormat?: 'raw' | 'string'): void;
export { addClassName, getClassNames, getInlineStyles, removeClassName, setInlineStyle, setInlineStyles, setProperty };

119
node_modules/@expressive-code/core/dist/hast.js generated vendored Normal file
View File

@@ -0,0 +1,119 @@
// src/hast.ts
import { toHtml } from "hast-util-to-html";
import { toText } from "hast-util-to-text";
import { matches, select, selectAll } from "hast-util-select";
import { visit } from "unist-util-visit";
import { visitParents, CONTINUE, EXIT, SKIP } from "unist-util-visit-parents";
import { h, s } from "hastscript";
import postcss, { Declaration } from "postcss";
// src/internal/escaping.ts
function serializeCssStringValue(value, quoteStyle = "single") {
const quote = quoteStyle === "single" ? "'" : '"';
const escapedValue = Array.from(value).map((char) => {
const code = char.charCodeAt(0);
switch (true) {
case code === 0:
return "\uFFFD";
case (code >= 1 && code <= 31 || code === 127):
return `\\${code.toString(16)} `;
case (char === quote || char === "\\"):
return `\\${char}`;
default:
return char;
}
}).join("");
return `${quote}${escapedValue}${quote}`;
}
// src/hast.ts
function setProperty(node, propertyName, value) {
const properties = node.properties || {};
node.properties = properties;
if (value !== null) {
properties[propertyName] = value;
} else {
delete properties[propertyName];
}
}
function getClassNames(node) {
const stringOrArr = node.properties?.className;
if (!stringOrArr || stringOrArr === true)
return [];
if (Array.isArray(stringOrArr))
return stringOrArr.map((className) => className.toString());
return stringOrArr.toString().split(" ");
}
function addClassName(node, className) {
const classNames = getClassNames(node);
if (classNames.indexOf(className) === -1)
classNames.push(className);
setProperty(node, "className", classNames);
}
function removeClassName(node, className) {
const classNames = getClassNames(node);
const index = classNames.indexOf(className);
if (index === -1)
return;
classNames.splice(index, 1);
setProperty(node, "className", classNames);
}
function getInlineStyles(node) {
const styles = /* @__PURE__ */ new Map();
const styleString = node.properties?.style?.toString().trim() || "";
if (!styleString)
return styles;
const postCssOptions = { from: void 0 };
try {
const root = postcss.parse(styleString, postCssOptions);
root.each((node2) => {
if (node2.type === "decl")
styles.set(node2.prop, node2.value);
});
} catch (error) {
}
return styles;
}
function setInlineStyles(node, styles) {
const styleString = [...styles].map(
([prop, value]) => new Declaration({
prop,
value,
raws: {
between: ":"
}
}).toString()
).join(";");
setProperty(node, "style", styleString);
}
function setInlineStyle(node, cssProperty, value, valueFormat = "raw") {
const styles = getInlineStyles(node);
if (value !== null) {
styles.set(cssProperty, valueFormat === "string" ? serializeCssStringValue(value) : value);
} else {
styles.delete(cssProperty);
}
setInlineStyles(node, styles);
}
export {
CONTINUE,
EXIT,
SKIP,
addClassName,
getClassNames,
getInlineStyles,
h,
matches,
removeClassName,
s,
select,
selectAll,
setInlineStyle,
setInlineStyles,
setProperty,
toHtml,
toText,
visit,
visitParents
};
//# sourceMappingURL=hast.js.map

1
node_modules/@expressive-code/core/dist/hast.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

1948
node_modules/@expressive-code/core/dist/index.d.ts generated vendored Normal file

File diff suppressed because one or more lines are too long

4777
node_modules/@expressive-code/core/dist/index.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

1
node_modules/@expressive-code/core/dist/index.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
export type Child = import('./lib/index.js').Child;
export type Properties = import('./lib/index.js').Properties;
export type Result = import('./lib/index.js').Result;
export { h, s } from "./lib/index.js";
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":"oBACa,OAAO,gBAAgB,EAAE,KAAK;yBAC9B,OAAO,gBAAgB,EAAE,UAAU;qBACnC,OAAO,gBAAgB,EAAE,MAAM"}

View File

@@ -0,0 +1,7 @@
/**
* @typedef {import('./lib/index.js').Child} Child
* @typedef {import('./lib/index.js').Properties} Properties
* @typedef {import('./lib/index.js').Result} Result
*/
export {h, s} from './lib/index.js'

View File

@@ -0,0 +1,141 @@
export * from "./jsx-automatic.js";
export const Fragment: null;
export const jsx: {
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: null, props: {
children?: import("./create-h.js").Child;
}, key?: string | undefined): import("hast").Root;
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: string, props: import("./create-automatic-runtime.js").JSXProps, key?: string | undefined): import("hast").Element;
};
export const jsxDEV: {
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: null, props: {
children?: import("./create-h.js").Child;
}, key?: string | undefined): import("hast").Root;
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: string, props: import("./create-automatic-runtime.js").JSXProps, key?: string | undefined): import("hast").Element;
};
export const jsxs: {
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: null, props: {
children?: import("./create-h.js").Child;
}, key?: string | undefined): import("hast").Root;
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: string, props: import("./create-automatic-runtime.js").JSXProps, key?: string | undefined): import("hast").Element;
};
//# sourceMappingURL=automatic-runtime-html.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"automatic-runtime-html.d.ts","sourceRoot":"","sources":["automatic-runtime-html.js"],"names":[],"mappings":""}

View File

@@ -0,0 +1,7 @@
import {createAutomaticRuntime} from './create-automatic-runtime.js'
import {h} from './index.js'
// Export `JSX` as a global for TypeScript.
export * from './jsx-automatic.js'
export const {Fragment, jsx, jsxDEV, jsxs} = createAutomaticRuntime(h)

View File

@@ -0,0 +1,141 @@
export * from "./jsx-automatic.js";
export const Fragment: null;
export const jsx: {
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: null, props: {
children?: import("./create-h.js").Child;
}, key?: string | undefined): import("hast").Root;
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: string, props: import("./create-automatic-runtime.js").JSXProps, key?: string | undefined): import("hast").Element;
};
export const jsxDEV: {
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: null, props: {
children?: import("./create-h.js").Child;
}, key?: string | undefined): import("hast").Root;
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: string, props: import("./create-automatic-runtime.js").JSXProps, key?: string | undefined): import("hast").Element;
};
export const jsxs: {
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: null, props: {
children?: import("./create-h.js").Child;
}, key?: string | undefined): import("hast").Root;
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: string, props: import("./create-automatic-runtime.js").JSXProps, key?: string | undefined): import("hast").Element;
};
//# sourceMappingURL=automatic-runtime-svg.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"automatic-runtime-svg.d.ts","sourceRoot":"","sources":["automatic-runtime-svg.js"],"names":[],"mappings":""}

View File

@@ -0,0 +1,7 @@
import {createAutomaticRuntime} from './create-automatic-runtime.js'
import {s} from './index.js'
// Export `JSX` as a global for TypeScript.
export * from './jsx-automatic.js'
export const {Fragment, jsx, jsxDEV, jsxs} = createAutomaticRuntime(s)

View File

@@ -0,0 +1,159 @@
/**
* Create an automatic runtime.
*
* @param {ReturnType<CreateH>} f
* `h` function.
* @returns
* Automatic JSX runtime.
*/
export function createAutomaticRuntime(f: ReturnType<CreateH>): {
Fragment: null;
jsx: {
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: null, props: {
children?: Child;
}, key?: string | undefined): Root;
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: string, props: JSXProps, key?: string | undefined): Element;
};
jsxDEV: {
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: null, props: {
children?: Child;
}, key?: string | undefined): Root;
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: string, props: JSXProps, key?: string | undefined): Element;
};
jsxs: {
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: null, props: {
children?: Child;
}, key?: string | undefined): Root;
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
(type: string, props: JSXProps, key?: string | undefined): Element;
};
};
export type Element = import('hast').Element;
export type Root = import('hast').Root;
export type Child = import('./create-h.js').Child;
export type Properties = import('./create-h.js').Properties;
export type PropertyValue = import('./create-h.js').PropertyValue;
export type Result = import('./create-h.js').Result;
export type Style = import('./create-h.js').Style;
export type CreateH = typeof import("./create-h.js").createH;
export type JSXProps = Record<string, Child | PropertyValue | Style>;
//# sourceMappingURL=create-automatic-runtime.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"create-automatic-runtime.d.ts","sourceRoot":"","sources":["create-automatic-runtime.js"],"names":[],"mappings":"AAiBA;;;;;;;GAOG;AACH,0CALW,WAAW,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;eAQjB,IAAI;uBACQ,KAAK;sCAEf,IAAI;;;;;;;;;;;;;;;;;;;;;eAGN,MAAM,SACN,QAAQ,6BAEN,OAAO;;;;;;;;;;;;;;;;;;;;;;;eATT,IAAI;uBACQ,KAAK;sCAEf,IAAI;;;;;;;;;;;;;;;;;;;;;eAGN,MAAM,SACN,QAAQ,6BAEN,OAAO;;;;;;;;;;;;;;;;;;;;;;;eATT,IAAI;uBACQ,KAAK;sCAEf,IAAI;;;;;;;;;;;;;;;;;;;;;eAGN,MAAM,SACN,QAAQ,6BAEN,OAAO;;EAmBrB;sBAvDY,OAAO,MAAM,EAAE,OAAO;mBACtB,OAAO,MAAM,EAAE,IAAI;oBAEnB,OAAO,eAAe,EAAE,KAAK;yBAC7B,OAAO,eAAe,EAAE,UAAU;4BAClC,OAAO,eAAe,EAAE,aAAa;qBACrC,OAAO,eAAe,EAAE,MAAM;oBAC9B,OAAO,eAAe,EAAE,KAAK;;uBAG7B,OAAO,MAAM,EAAE,KAAK,GAAG,aAAa,GAAG,KAAK,CAAC"}

View File

@@ -0,0 +1,57 @@
/**
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Root} Root
*
* @typedef {import('./create-h.js').Child} Child
* @typedef {import('./create-h.js').Properties} Properties
* @typedef {import('./create-h.js').PropertyValue} PropertyValue
* @typedef {import('./create-h.js').Result} Result
* @typedef {import('./create-h.js').Style} Style
* @typedef {import('./create-h.js').createH} CreateH
*
* @typedef {Record<string, Child | PropertyValue | Style>} JSXProps
*/
// Make VS code see references to above symbols.
''
/**
* Create an automatic runtime.
*
* @param {ReturnType<CreateH>} f
* `h` function.
* @returns
* Automatic JSX runtime.
*/
export function createAutomaticRuntime(f) {
/**
* @overload
* @param {null} type
* @param {{children?: Child}} props
* @param {string} [key]
* @returns {Root}
*
* @overload
* @param {string} type
* @param {JSXProps} props
* @param {string} [key]
* @returns {Element}
*
* @param {string | null} type
* Element name or `null` to get a root.
* @param {Properties & {children?: Child}} props
* Properties.
* @returns {Result}
* Result.
*/
function jsx(type, props) {
const {children, ...properties} = props
const result =
// @ts-ignore: `children` is fine: TS has a recursion problem which
// sometimes generates broken types.
type === null ? f(null, children) : f(type, properties, children)
return result
}
return {Fragment: null, jsx, jsxDEV: jsx, jsxs: jsx}
}

View File

@@ -0,0 +1,152 @@
/**
* @param {Schema} schema
* Schema to use.
* @param {string} defaultTagName
* Default tag name.
* @param {Array<string> | undefined} [caseSensitive]
* Case-sensitive tag names (default: `undefined`).
* @returns
* `h`.
*/
export function createH(schema: Schema, defaultTagName: string, caseSensitive?: Array<string> | undefined): {
/**
* Hyperscript compatible DSL for creating virtual hast trees.
*
* @overload
* @param {null | undefined} [selector]
* @param {...Child} children
* @returns {Root}
*
* @overload
* @param {string} selector
* @param {Properties} properties
* @param {...Child} children
* @returns {Element}
*
* @overload
* @param {string} selector
* @param {...Child} children
* @returns {Element}
*
* @param {string | null | undefined} [selector]
* Selector.
* @param {Child | Properties | null | undefined} [properties]
* Properties (or first child) (default: `undefined`).
* @param {...Child} children
* Children.
* @returns {Result}
* Result.
*/
(selector?: null | undefined, ...children: Child[]): Root;
/**
* Hyperscript compatible DSL for creating virtual hast trees.
*
* @overload
* @param {null | undefined} [selector]
* @param {...Child} children
* @returns {Root}
*
* @overload
* @param {string} selector
* @param {Properties} properties
* @param {...Child} children
* @returns {Element}
*
* @overload
* @param {string} selector
* @param {...Child} children
* @returns {Element}
*
* @param {string | null | undefined} [selector]
* Selector.
* @param {Child | Properties | null | undefined} [properties]
* Properties (or first child) (default: `undefined`).
* @param {...Child} children
* Children.
* @returns {Result}
* Result.
*/
(selector: string, properties: Properties, ...children: Child[]): Element;
/**
* Hyperscript compatible DSL for creating virtual hast trees.
*
* @overload
* @param {null | undefined} [selector]
* @param {...Child} children
* @returns {Root}
*
* @overload
* @param {string} selector
* @param {Properties} properties
* @param {...Child} children
* @returns {Element}
*
* @overload
* @param {string} selector
* @param {...Child} children
* @returns {Element}
*
* @param {string | null | undefined} [selector]
* Selector.
* @param {Child | Properties | null | undefined} [properties]
* Properties (or first child) (default: `undefined`).
* @param {...Child} children
* Children.
* @returns {Result}
* Result.
*/
(selector: string, ...children: Child[]): Element;
};
export type Element = import('hast').Element;
export type Nodes = import('hast').Nodes;
export type Root = import('hast').Root;
export type RootContent = import('hast').RootContent;
export type Info = import('property-information').Info;
export type Schema = import('property-information').Schema;
/**
* Result from a `h` (or `s`) call.
*/
export type Result = Element | Root;
/**
* Value for a CSS style field.
*/
export type StyleValue = number | string;
/**
* Supported value of a `style` prop.
*/
export type Style = Record<string, StyleValue>;
/**
* Primitive property value.
*/
export type PrimitiveValue = boolean | number | string | null | undefined;
/**
* List of property values for space- or comma separated values (such as `className`).
*/
export type ArrayValue = Array<number | string>;
/**
* Primitive value or list value.
*/
export type PropertyValue = (string | number)[] | PrimitiveValue;
/**
* Acceptable value for element properties.
*/
export type Properties = {
[property: string]: Style | PropertyValue;
};
/**
* Primitive children, either ignored (nullish), or turned into text nodes.
*/
export type PrimitiveChild = number | string | null | undefined;
/**
* List of children.
*/
export type ArrayChild = Array<(import("hast").Nodes | PrimitiveChild)[] | Nodes | PrimitiveChild>;
/**
* List of children (deep).
*/
export type ArrayChildNested = Array<Nodes | PrimitiveChild>;
/**
* Acceptable child value.
*/
export type Child = (import("hast").Nodes | PrimitiveChild | ArrayChildNested)[] | Nodes | PrimitiveChild;
//# sourceMappingURL=create-h.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"create-h.d.ts","sourceRoot":"","sources":["create-h.js"],"names":[],"mappings":"AA4CA;;;;;;;;;GASG;AACH,gCATW,MAAM,kBAEN,MAAM,kBAEN,MAAM,MAAM,CAAC,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAYvB,IAAI,GAAG,SAAS,eACb,KAAK,KACN,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAGN,MAAM,cACN,UAAU,eACP,KAAK,KACN,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAGT,MAAM,eACH,KAAK,KACN,OAAO;EA0DrB;sBAnIY,OAAO,MAAM,EAAE,OAAO;oBACtB,OAAO,MAAM,EAAE,KAAK;mBACpB,OAAO,MAAM,EAAE,IAAI;0BACnB,OAAO,MAAM,EAAE,WAAW;mBAE1B,OAAO,sBAAsB,EAAE,IAAI;qBACnC,OAAO,sBAAsB,EAAE,MAAM;;;;qBAIrC,OAAO,GAAG,IAAI;;;;yBAGd,MAAM,GAAG,MAAM;;;;oBAEf,OAAO,MAAM,EAAE,UAAU,CAAC;;;;6BAE1B,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS;;;;yBAE5C,MAAM,MAAM,GAAG,MAAM,CAAC;;;;4BAEtB,sBAAa,cAAc;;;;;;;;;;6BAK3B,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS;;;;yBAElC,MAAM,4CAAmB,KAAK,GAAG,cAAc,CAAC;;;;+BAEhD,MAAM,KAAK,GAAG,cAAc,CAAC;;;;oBAE7B,+DAAa,KAAK,GAAG,cAAc"}

View File

@@ -0,0 +1,357 @@
/**
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Nodes} Nodes
* @typedef {import('hast').Root} Root
* @typedef {import('hast').RootContent} RootContent
*
* @typedef {import('property-information').Info} Info
* @typedef {import('property-information').Schema} Schema
*/
/**
* @typedef {Element | Root} Result
* Result from a `h` (or `s`) call.
*
* @typedef {number | string} StyleValue
* Value for a CSS style field.
* @typedef {Record<string, StyleValue>} Style
* Supported value of a `style` prop.
* @typedef {boolean | number | string | null | undefined} PrimitiveValue
* Primitive property value.
* @typedef {Array<number | string>} ArrayValue
* List of property values for space- or comma separated values (such as `className`).
* @typedef {ArrayValue | PrimitiveValue} PropertyValue
* Primitive value or list value.
* @typedef {{[property: string]: PropertyValue | Style}} Properties
* Acceptable value for element properties.
*
* @typedef {number | string | null | undefined} PrimitiveChild
* Primitive children, either ignored (nullish), or turned into text nodes.
* @typedef {Array<ArrayChildNested | Nodes | PrimitiveChild>} ArrayChild
* List of children.
* @typedef {Array<Nodes | PrimitiveChild>} ArrayChildNested
* List of children (deep).
* @typedef {ArrayChild | Nodes | PrimitiveChild} Child
* Acceptable child value.
*/
import {parse as commas} from 'comma-separated-tokens'
import {parseSelector} from 'hast-util-parse-selector'
import {find, normalize} from 'property-information'
import {parse as spaces} from 'space-separated-tokens'
const own = {}.hasOwnProperty
/**
* @param {Schema} schema
* Schema to use.
* @param {string} defaultTagName
* Default tag name.
* @param {Array<string> | undefined} [caseSensitive]
* Case-sensitive tag names (default: `undefined`).
* @returns
* `h`.
*/
export function createH(schema, defaultTagName, caseSensitive) {
const adjust = caseSensitive && createAdjustMap(caseSensitive)
/**
* Hyperscript compatible DSL for creating virtual hast trees.
*
* @overload
* @param {null | undefined} [selector]
* @param {...Child} children
* @returns {Root}
*
* @overload
* @param {string} selector
* @param {Properties} properties
* @param {...Child} children
* @returns {Element}
*
* @overload
* @param {string} selector
* @param {...Child} children
* @returns {Element}
*
* @param {string | null | undefined} [selector]
* Selector.
* @param {Child | Properties | null | undefined} [properties]
* Properties (or first child) (default: `undefined`).
* @param {...Child} children
* Children.
* @returns {Result}
* Result.
*/
function h(selector, properties, ...children) {
let index = -1
/** @type {Result} */
let node
if (selector === undefined || selector === null) {
node = {type: 'root', children: []}
// Properties are not supported for roots.
const child = /** @type {Child} */ (properties)
children.unshift(child)
} else {
node = parseSelector(selector, defaultTagName)
// Normalize the name.
node.tagName = node.tagName.toLowerCase()
if (adjust && own.call(adjust, node.tagName)) {
node.tagName = adjust[node.tagName]
}
// Handle props.
if (isChild(properties)) {
children.unshift(properties)
} else {
/** @type {string} */
let key
for (key in properties) {
if (own.call(properties, key)) {
addProperty(schema, node.properties, key, properties[key])
}
}
}
}
// Handle children.
while (++index < children.length) {
addChild(node.children, children[index])
}
if (node.type === 'element' && node.tagName === 'template') {
node.content = {type: 'root', children: node.children}
node.children = []
}
return node
}
return h
}
/**
* Check if something is properties or a child.
*
* @param {Child | Properties} value
* Value to check.
* @returns {value is Child}
* Whether `value` is definitely a child.
*/
function isChild(value) {
// Never properties if not an object.
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
return true
}
// Never node without `type`; thats the main discriminator.
if (typeof value.type !== 'string') return false
// Slower check: never property value if object or array with
// non-number/strings.
const record = /** @type {Record<string, unknown>} */ (value)
const keys = Object.keys(value)
for (const key of keys) {
const value = record[key]
if (value && typeof value === 'object') {
if (!Array.isArray(value)) return true
const list = /** @type {Array<unknown>} */ (value)
for (const item of list) {
if (typeof item !== 'number' && typeof item !== 'string') {
return true
}
}
}
}
// Also see empty `children` as a node.
if ('children' in value && Array.isArray(value.children)) {
return true
}
// Default to properties, someone can always pass an empty object,
// put `data: {}` in a node,
// or wrap it in an array.
return false
}
/**
* @param {Schema} schema
* Schema.
* @param {Properties} properties
* Properties object.
* @param {string} key
* Property name.
* @param {PropertyValue | Style} value
* Property value.
* @returns {undefined}
* Nothing.
*/
function addProperty(schema, properties, key, value) {
const info = find(schema, key)
let index = -1
/** @type {PropertyValue} */
let result
// Ignore nullish and NaN values.
if (value === undefined || value === null) return
if (typeof value === 'number') {
// Ignore NaN.
if (Number.isNaN(value)) return
result = value
}
// Booleans.
else if (typeof value === 'boolean') {
result = value
}
// Handle list values.
else if (typeof value === 'string') {
if (info.spaceSeparated) {
result = spaces(value)
} else if (info.commaSeparated) {
result = commas(value)
} else if (info.commaOrSpaceSeparated) {
result = spaces(commas(value).join(' '))
} else {
result = parsePrimitive(info, info.property, value)
}
} else if (Array.isArray(value)) {
result = value.concat()
} else {
result = info.property === 'style' ? style(value) : String(value)
}
if (Array.isArray(result)) {
/** @type {Array<number | string>} */
const finalResult = []
while (++index < result.length) {
// Assume no booleans in array.
const value = /** @type {number | string} */ (
parsePrimitive(info, info.property, result[index])
)
finalResult[index] = value
}
result = finalResult
}
// Class names (which can be added both on the `selector` and here).
if (info.property === 'className' && Array.isArray(properties.className)) {
// Assume no booleans in `className`.
const value = /** @type {number | string} */ (result)
result = properties.className.concat(value)
}
properties[info.property] = result
}
/**
* @param {Array<RootContent>} nodes
* Children.
* @param {Child} value
* Child.
* @returns {undefined}
* Nothing.
*/
function addChild(nodes, value) {
let index = -1
if (value === undefined || value === null) {
// Empty.
} else if (typeof value === 'string' || typeof value === 'number') {
nodes.push({type: 'text', value: String(value)})
} else if (Array.isArray(value)) {
while (++index < value.length) {
addChild(nodes, value[index])
}
} else if (typeof value === 'object' && 'type' in value) {
if (value.type === 'root') {
addChild(nodes, value.children)
} else {
nodes.push(value)
}
} else {
throw new Error('Expected node, nodes, or string, got `' + value + '`')
}
}
/**
* Parse a single primitives.
*
* @param {Info} info
* Property information.
* @param {string} name
* Property name.
* @param {PrimitiveValue} value
* Property value.
* @returns {PrimitiveValue}
* Property value.
*/
function parsePrimitive(info, name, value) {
if (typeof value === 'string') {
if (info.number && value && !Number.isNaN(Number(value))) {
return Number(value)
}
if (
(info.boolean || info.overloadedBoolean) &&
(value === '' || normalize(value) === normalize(name))
) {
return true
}
}
return value
}
/**
* Serialize a `style` object as a string.
*
* @param {Style} value
* Style object.
* @returns {string}
* CSS string.
*/
function style(value) {
/** @type {Array<string>} */
const result = []
/** @type {string} */
let key
for (key in value) {
if (own.call(value, key)) {
result.push([key, value[key]].join(': '))
}
}
return result.join('; ')
}
/**
* Create a map to adjust casing.
*
* @param {Array<string>} values
* List of properly cased keys.
* @returns {Record<string, string>}
* Map of lowercase keys to uppercase keys.
*/
function createAdjustMap(values) {
/** @type {Record<string, string>} */
const result = {}
let index = -1
while (++index < values.length) {
result[values[index].toLowerCase()] = values[index]
}
return result
}

View File

@@ -0,0 +1,34 @@
/** @type {ReturnType<createH>} */
export const h: ReturnType<typeof createH>;
export namespace h {
namespace JSX {
type Element = import('./jsx-classic.js').Element;
type ElementChildrenAttribute = import('./jsx-classic.js').ElementChildrenAttribute;
type IntrinsicAttributes = import('./jsx-classic.js').IntrinsicAttributes;
type IntrinsicElements = import('./jsx-classic.js').IntrinsicElements;
}
}
/** @type {ReturnType<createH>} */
export const s: ReturnType<typeof createH>;
export namespace s {
namespace JSX {
type Element = import('./jsx-classic.js').Element;
type ElementChildrenAttribute = import('./jsx-classic.js').ElementChildrenAttribute;
type IntrinsicAttributes = import('./jsx-classic.js').IntrinsicAttributes;
type IntrinsicElements = import('./jsx-classic.js').IntrinsicElements;
}
}
/**
* Acceptable child value.
*/
export type Child = import('./create-h.js').Child;
/**
* Acceptable value for element properties.
*/
export type Properties = import('./create-h.js').Properties;
/**
* Result from a `h` (or `s`) call.
*/
export type Result = import('./create-h.js').Result;
import { createH } from './create-h.js';
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":"AA8BA,kCAAkC;AAClC,gBADW,0BAAmB,CACO;;;uBApBxB,OAAO,kBAAkB,EAAE,OAAO;wCAClC,OAAO,kBAAkB,EAAE,wBAAwB;mCACnD,OAAO,kBAAkB,EAAE,mBAAmB;iCAC9C,OAAO,kBAAkB,EAAE,iBAAiB;;;AAoBzD,kCAAkC;AAClC,gBADW,0BAAmB,CAC8B;;;uBAhB/C,OAAO,kBAAkB,EAAE,OAAO;wCAClC,OAAO,kBAAkB,EAAE,wBAAwB;mCACnD,OAAO,kBAAkB,EAAE,mBAAmB;iCAC9C,OAAO,kBAAkB,EAAE,iBAAiB;;;;;;oBArB5C,OAAO,eAAe,EAAE,KAAK;;;;yBAE7B,OAAO,eAAe,EAAE,UAAU;;;;qBAElC,OAAO,eAAe,EAAE,MAAM;wBAqBrB,eAAe"}

View File

@@ -0,0 +1,36 @@
/**
* @typedef {import('./create-h.js').Child} Child
* Acceptable child value.
* @typedef {import('./create-h.js').Properties} Properties
* Acceptable value for element properties.
* @typedef {import('./create-h.js').Result} Result
* Result from a `h` (or `s`) call.
*/
// Register the JSX namespace on `h`.
/**
* @typedef {import('./jsx-classic.js').Element} h.JSX.Element
* @typedef {import('./jsx-classic.js').ElementChildrenAttribute} h.JSX.ElementChildrenAttribute
* @typedef {import('./jsx-classic.js').IntrinsicAttributes} h.JSX.IntrinsicAttributes
* @typedef {import('./jsx-classic.js').IntrinsicElements} h.JSX.IntrinsicElements
*/
// Register the JSX namespace on `s`.
/**
* @typedef {import('./jsx-classic.js').Element} s.JSX.Element
* @typedef {import('./jsx-classic.js').ElementChildrenAttribute} s.JSX.ElementChildrenAttribute
* @typedef {import('./jsx-classic.js').IntrinsicAttributes} s.JSX.IntrinsicAttributes
* @typedef {import('./jsx-classic.js').IntrinsicElements} s.JSX.IntrinsicElements
*/
import {html, svg} from 'property-information'
import {createH} from './create-h.js'
import {svgCaseSensitiveTagNames} from './svg-case-sensitive-tag-names.js'
// Note: this explicit type is needed, otherwise TS creates broken types.
/** @type {ReturnType<createH>} */
export const h = createH(html, 'div')
// Note: this explicit type is needed, otherwise TS creates broken types.
/** @type {ReturnType<createH>} */
export const s = createH(svg, 'g', svgCaseSensitiveTagNames)

View File

@@ -0,0 +1,43 @@
import type {Child, Properties, Result} from './create-h.js'
export namespace JSX {
/**
* Define the return value of JSX syntax.
*/
type Element = Result
/**
* Key of this interface defines as what prop children are passed.
*/
interface ElementChildrenAttribute {
/**
* Only the key matters, not the value.
*/
children?: never
}
/**
* Disallow the use of functional components.
*/
type IntrinsicAttributes = never
/**
* Define the prop types for known elements.
*
* For `hastscript` this defines any string may be used in combination with
* `hast` `Properties`.
*
* This **must** be an interface.
*/
interface IntrinsicElements {
[name: string]:
| Properties
| {
/**
* The prop that matches `ElementChildrenAttribute` key defines the
* type of JSX children, defines the children type.
*/
children?: Child
}
}
}

View File

@@ -0,0 +1,2 @@
// Empty (only used for TypeScript).
export {}

View File

@@ -0,0 +1,47 @@
import type {Child, Properties, Result} from './create-h.js'
/**
* This unique symbol is declared to specify the key on which JSX children are
* passed, without conflicting with the `Attributes` type.
*/
declare const children: unique symbol
/**
* Define the return value of JSX syntax.
*/
export type Element = Result
/**
* Key of this interface defines as what prop children are passed.
*/
export interface ElementChildrenAttribute {
/**
* Only the key matters, not the value.
*/
[children]?: never
}
/**
* Disallow the use of functional components.
*/
export type IntrinsicAttributes = never
/**
* Define the prop types for known elements.
*
* For `hastscript` this defines any string may be used in combination with
* `hast` `Properties`.
*
* This **must** be an interface.
*/
export interface IntrinsicElements {
[name: string]:
| Properties
| {
/**
* The prop that matches `ElementChildrenAttribute` key defines the
* type of JSX children, defines the children type.
*/
[children]?: Child
}
}

View File

@@ -0,0 +1,2 @@
// Empty (only used for TypeScript).
export {}

View File

@@ -0,0 +1,2 @@
export const svgCaseSensitiveTagNames: string[];
//# sourceMappingURL=svg-case-sensitive-tag-names.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"svg-case-sensitive-tag-names.d.ts","sourceRoot":"","sources":["svg-case-sensitive-tag-names.js"],"names":[],"mappings":"AAAA,gDAwCC"}

View File

@@ -0,0 +1,41 @@
export const svgCaseSensitiveTagNames = [
'altGlyph',
'altGlyphDef',
'altGlyphItem',
'animateColor',
'animateMotion',
'animateTransform',
'clipPath',
'feBlend',
'feColorMatrix',
'feComponentTransfer',
'feComposite',
'feConvolveMatrix',
'feDiffuseLighting',
'feDisplacementMap',
'feDistantLight',
'feDropShadow',
'feFlood',
'feFuncA',
'feFuncB',
'feFuncG',
'feFuncR',
'feGaussianBlur',
'feImage',
'feMerge',
'feMergeNode',
'feMorphology',
'feOffset',
'fePointLight',
'feSpecularLighting',
'feSpotLight',
'feTile',
'feTurbulence',
'foreignObject',
'glyphRef',
'linearGradient',
'radialGradient',
'solidColor',
'textArea',
'textPath'
]

View File

@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,118 @@
{
"name": "hastscript",
"version": "9.0.0",
"description": "hast utility to create trees",
"license": "MIT",
"keywords": [
"unist",
"hast",
"hast-util",
"util",
"utility",
"html",
"rehype",
"vdom",
"virtual",
"dom",
"hyperscript",
"dsl"
],
"repository": "syntax-tree/hastscript",
"bugs": "https://github.com/syntax-tree/hastscript/issues",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
},
"author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
"contributors": [
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
],
"sideEffects": false,
"type": "module",
"main": "index.js",
"types": "index.d.ts",
"exports": {
".": "./index.js",
"./jsx-runtime": "./lib/automatic-runtime-html.js",
"./jsx-dev-runtime": "./lib/automatic-runtime-html.js",
"./svg/jsx-runtime": "./lib/automatic-runtime-svg.js",
"./svg/jsx-dev-runtime": "./lib/automatic-runtime-svg.js"
},
"files": [
"lib/",
"index.d.ts.map",
"index.d.ts",
"index.js"
],
"dependencies": {
"@types/hast": "^3.0.0",
"comma-separated-tokens": "^2.0.0",
"hast-util-parse-selector": "^4.0.0",
"property-information": "^6.0.0",
"space-separated-tokens": "^2.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"acorn-jsx": "^5.0.0",
"c8": "^9.0.0",
"esast-util-from-js": "^2.0.0",
"estree-util-build-jsx": "^3.0.0",
"estree-util-to-js": "^2.0.0",
"prettier": "^3.0.0",
"remark-cli": "^11.0.0",
"remark-preset-wooorm": "^9.0.0",
"svg-tag-names": "^3.0.0",
"tsd": "^0.30.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"unist-builder": "^4.0.0",
"xo": "^0.56.0"
},
"scripts": {
"prepack": "npm run build && npm run format",
"build": "tsc --build --clean && tsc --build && tsd && type-coverage",
"generate": "node script/generate-jsx.js && node script/build.js",
"format": "remark . -qfo && prettier . -w --log-level warn && xo --fix",
"test-api": "node --conditions development test/index.js",
"test-coverage": "c8 --100 --reporter lcov npm run test-api",
"test": "npm run generate && npm run build && npm run format && npm run test-coverage"
},
"prettier": {
"bracketSpacing": false,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false
},
"remarkConfig": {
"plugins": [
"remark-preset-wooorm"
]
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"ignoreCatch": true,
"#": "needed `any`s :'(",
"ignoreFiles": [
"test/jsx-build-jsx-automatic-development.js"
],
"strict": true
},
"xo": {
"overrides": [
{
"files": "**/*.ts",
"rules": {
"@typescript-eslint/consistent-indexed-object-style": "off",
"@typescript-eslint/consistent-type-definitions": "off"
}
}
],
"prettier": true,
"rules": {
"n/file-extension-in-import": "off"
}
}
}

View File

@@ -0,0 +1,472 @@
# hastscript
[![Build][build-badge]][build]
[![Coverage][coverage-badge]][coverage]
[![Downloads][downloads-badge]][downloads]
[![Size][size-badge]][size]
[![Sponsors][sponsors-badge]][collective]
[![Backers][backers-badge]][collective]
[![Chat][chat-badge]][chat]
[hast][] utility to create trees with ease.
## Contents
* [What is this?](#what-is-this)
* [When should I use this?](#when-should-i-use-this)
* [Install](#install)
* [Use](#use)
* [API](#api)
* [`h(selector?[, properties][, …children])`](#hselector-properties-children)
* [`s(selector?[, properties][, …children])`](#sselector-properties-children)
* [`Child`](#child)
* [`Properties`](#properties-1)
* [`Result`](#result)
* [Syntax tree](#syntax-tree)
* [JSX](#jsx)
* [Types](#types)
* [Compatibility](#compatibility)
* [Security](#security)
* [Related](#related)
* [Contribute](#contribute)
* [License](#license)
## What is this?
This package is a hyperscript interface (like `createElement` from React and
`h` from Vue and such) to help with creating hast trees.
## When should I use this?
You can use this utility in your project when you generate hast syntax trees
with code.
It helps because it replaces most of the repetition otherwise needed in a syntax
tree with function calls.
It also helps as it improves the attributes you pass by turning them into the
form that is required by hast.
You can instead use [`unist-builder`][u] when creating any unist nodes and
[`xastscript`][x] when creating xast (XML) nodes.
## Install
This package is [ESM only][esm].
In Node.js (version 16+), install with [npm][]:
```sh
npm install hastscript
```
In Deno with [`esm.sh`][esmsh]:
```js
import {h} from 'https://esm.sh/hastscript@9'
```
In browsers with [`esm.sh`][esmsh]:
```html
<script type="module">
import {h} from 'https://esm.sh/hastscript@9?bundle'
</script>
```
## Use
```js
import {h, s} from 'hastscript'
console.log(
h('.foo#some-id', [
h('span', 'some text'),
h('input', {type: 'text', value: 'foo'}),
h('a.alpha', {class: 'bravo charlie', download: 'download'}, [
'delta',
'echo'
])
])
)
console.log(
s('svg', {xmlns: 'http://www.w3.org/2000/svg', viewbox: '0 0 500 500'}, [
s('title', 'SVG `<circle>` element'),
s('circle', {cx: 120, cy: 120, r: 100})
])
)
```
Yields:
```js
{
type: 'element',
tagName: 'div',
properties: {className: ['foo'], id: 'some-id'},
children: [
{
type: 'element',
tagName: 'span',
properties: {},
children: [{type: 'text', value: 'some text'}]
},
{
type: 'element',
tagName: 'input',
properties: {type: 'text', value: 'foo'},
children: []
},
{
type: 'element',
tagName: 'a',
properties: {className: ['alpha', 'bravo', 'charlie'], download: true},
children: [{type: 'text', value: 'delta'}, {type: 'text', value: 'echo'}]
}
]
}
{
type: 'element',
tagName: 'svg',
properties: {xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 500 500'},
children: [
{
type: 'element',
tagName: 'title',
properties: {},
children: [{type: 'text', value: 'SVG `<circle>` element'}]
},
{
type: 'element',
tagName: 'circle',
properties: {cx: 120, cy: 120, r: 100},
children: []
}
]
}
```
## API
This package exports the identifiers [`h`][api-h] and [`s`][api-s].
There is no default export.
The export map supports the automatic JSX runtime.
You can pass `hastscript` or `hastscript/svg` to your build tool (TypeScript,
Babel, SWC) with an `importSource` option or similar.
### `h(selector?[, properties][, …children])`
Create virtual **[hast][]** trees for HTML.
##### Signatures
* `h(): root`
* `h(null[, …children]): root`
* `h(selector[, properties][, …children]): element`
##### Parameters
###### `selector`
Simple CSS selector (`string`, optional).
Can contain a tag name (`foo`), IDs (`#bar`), and classes (`.baz`).
If the selector is a string but there is no tag name in it, `h` defaults to
build a `div` element, and `s` to a `g` element.
`selector` is parsed by [`hast-util-parse-selector`][parse-selector].
When string, builds an [`Element`][element].
When nullish, builds a [`Root`][root] instead.
###### `properties`
Properties of the element ([`Properties`][api-properties], optional).
###### `children`
Children of the node ([`Child`][api-child] or `Array<Child>`, optional).
##### Returns
Created tree ([`Result`][api-result]).
[`Element`][element] when a `selector` is passed, otherwise [`Root`][root].
### `s(selector?[, properties][, …children])`
Create virtual **[hast][]** trees for SVG.
Signatures, parameters, and return value are the same as `h` above.
Importantly, the `selector` and `properties` parameters are interpreted as
SVG.
### `Child`
(Lists of) children (TypeScript type).
When strings or numbers are encountered, they are turned into [`Text`][text]
nodes.
[`Root`][root] nodes are treated as “fragments”, meaning that their children
are used instead.
###### Type
```ts
type Child =
| Array<Node | number | string | null | undefined>
| Node
| number
| string
| null
| undefined
```
### `Properties`
Map of properties (TypeScript type).
Keys should match either the HTML attribute name, or the DOM property name, but
are case-insensitive.
###### Type
```ts
type Properties = Record<
string,
| boolean
| number
| string
| null
| undefined
// For comma- and space-separated values such as `className`:
| Array<number | string>
// Accepts value for `style` prop as object.
| Record<string, number | string>
>
```
### `Result`
Result from a `h` (or `s`) call (TypeScript type).
###### Type
```ts
type Result = Element | Root
```
## Syntax tree
The syntax tree is [hast][].
## JSX
This package can be used with JSX.
You should use the automatic JSX runtime set to `hastscript` or
`hastscript/svg`.
> 👉 **Note**: while `h` supports dots (`.`) for classes or number signs (`#`)
> for IDs in `selector`, those are not supported in JSX.
> 🪦 **Legacy**: you can also use the classic JSX runtime, but this is not
> recommended.
> To do so, import `h` (or `s`) yourself and define it as the pragma (plus
> set the fragment to `null`).
The Use example above can then be written like so, using inline pragmas, so
that SVG can be used too:
`example-html.jsx`:
```jsx
/** @jsxImportSource hastscript */
console.log(
<div class="foo" id="some-id">
<span>some text</span>
<input type="text" value="foo" />
<a class="alpha bravo charlie" download>
deltaecho
</a>
</div>
)
```
`example-svg.jsx`:
```jsx
/** @jsxImportSource hastscript/svg */
console.log(
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 500 500">
<title>SVG `&lt;circle&gt;` element</title>
<circle cx={120} cy={120} r={100} />
</svg>
)
```
## Types
This package is fully typed with [TypeScript][].
It exports the additional types [`Child`][api-child],
[`Properties`][api-properties], and
[`Result`][api-result].
## Compatibility
Projects maintained by the unified collective are compatible with maintained
versions of Node.js.
When we cut a new major release, we drop support for unmaintained versions of
Node.
This means we try to keep the current release line, `hastscript@^9`,
compatible with Node.js 16.
## Security
Use of `hastscript` can open you up to a [cross-site scripting (XSS)][xss]
when you pass user-provided input to it because values are injected into the
syntax tree.
The following example shows how an image is injected that fails loading and
therefore runs code in a browser.
```js
const tree = h()
// Somehow someone injected these properties instead of an expected `src` and
// `alt`:
const otherProps = {src: 'x', onError: 'alert(1)'}
tree.children.push(h('img', {src: 'default.png', ...otherProps}))
```
Yields:
```html
<img src="x" onerror="alert(1)">
```
The following example shows how code can run in a browser because someone stored
an object in a database instead of the expected string.
```js
const tree = h()
// Somehow this isnt the expected `'wooorm'`.
const username = {
type: 'element',
tagName: 'script',
children: [{type: 'text', value: 'alert(2)'}]
}
tree.children.push(h('span.handle', username))
```
Yields:
```html
<span class="handle"><script>alert(2)</script></span>
```
Either do not use user-provided input in `hastscript` or use
[`hast-util-santize`][hast-util-sanitize].
## Related
* [`unist-builder`](https://github.com/syntax-tree/unist-builder)
— create unist trees
* [`xastscript`](https://github.com/syntax-tree/xastscript)
— create xast trees
* [`hast-to-hyperscript`](https://github.com/syntax-tree/hast-to-hyperscript)
— turn hast into React, Preact, Vue, etc
* [`hast-util-to-html`](https://github.com/syntax-tree/hast-util-to-html)
— turn hast into HTML
* [`hast-util-to-dom`](https://github.com/syntax-tree/hast-util-to-dom)
— turn hast into DOM trees
* [`estree-util-build-jsx`](https://github.com/syntax-tree/estree-util-build-jsx)
— compile JSX away
## Contribute
See [`contributing.md`][contributing] in [`syntax-tree/.github`][health] for
ways to get started.
See [`support.md`][support] for ways to get help.
This project has a [code of conduct][coc].
By interacting with this repository, organization, or community you agree to
abide by its terms.
## License
[MIT][license] © [Titus Wormer][author]
<!-- Definitions -->
[build-badge]: https://github.com/syntax-tree/hastscript/workflows/main/badge.svg
[build]: https://github.com/syntax-tree/hastscript/actions
[coverage-badge]: https://img.shields.io/codecov/c/github/syntax-tree/hastscript.svg
[coverage]: https://codecov.io/github/syntax-tree/hastscript
[downloads-badge]: https://img.shields.io/npm/dm/hastscript.svg
[downloads]: https://www.npmjs.com/package/hastscript
[size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=hastscript
[size]: https://bundlejs.com/?q=hastscript
[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
[backers-badge]: https://opencollective.com/unified/backers/badge.svg
[collective]: https://opencollective.com/unified
[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg
[chat]: https://github.com/syntax-tree/unist/discussions
[npm]: https://docs.npmjs.com/cli/install
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
[esmsh]: https://esm.sh
[typescript]: https://www.typescriptlang.org
[license]: license
[author]: https://wooorm.com
[health]: https://github.com/syntax-tree/.github
[contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md
[support]: https://github.com/syntax-tree/.github/blob/main/support.md
[coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md
[hast]: https://github.com/syntax-tree/hast
[element]: https://github.com/syntax-tree/hast#element
[root]: https://github.com/syntax-tree/xast#root
[text]: https://github.com/syntax-tree/hast#text
[u]: https://github.com/syntax-tree/unist-builder
[x]: https://github.com/syntax-tree/xastscript
[parse-selector]: https://github.com/syntax-tree/hast-util-parse-selector
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
[hast-util-sanitize]: https://github.com/syntax-tree/hast-util-sanitize
[api-h]: #hselector-properties-children
[api-s]: #sselector-properties-children
[api-child]: #child
[api-properties]: #properties-1
[api-result]: #result

66
node_modules/@expressive-code/core/package.json generated vendored Normal file
View File

@@ -0,0 +1,66 @@
{
"name": "@expressive-code/core",
"version": "0.35.3",
"description": "A text marking & annotation engine for presenting source code on the web.",
"keywords": [],
"author": "Tibor Schiemann",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/expressive-code/expressive-code.git",
"directory": "packages/@expressive-code/core"
},
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./hast": {
"types": "./dist/hast.d.ts",
"default": "./dist/hast.js"
}
},
"types": "./dist/index.d.ts",
"typesVersions": {
"*": {
"hast": [
"dist/hast.d.ts"
]
}
},
"files": [
"dist"
],
"dependencies": {
"@ctrl/tinycolor": "^4.0.4",
"hast-util-select": "^6.0.2",
"hast-util-to-html": "^9.0.1",
"hast-util-to-text": "^4.0.1",
"hastscript": "^9.0.0",
"postcss": "^8.4.38",
"postcss-nested": "^6.0.1",
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.1"
},
"devDependencies": {
"@types/culori": "^2.1.0",
"@types/hast": "^3.0.4",
"culori": "^4.0.1",
"djb2a": "^2.0.0",
"hast-util-sanitize": "^5.0.1",
"shiki": "^1.1.7",
"strip-json-comments": "^5.0.1"
},
"scripts": {
"build": "pnpm build-js-modules && tsup ./src/index.ts ./src/hast.ts --format esm --no-splitting --dts --sourcemap --clean",
"build-js-modules": "tsm --require=../../../scripts/lib/filter-warnings.cjs ../../../scripts/build-js-module.ts ./src/internal/tabindex-js-module.ts",
"coverage": "vitest run --coverage",
"test": "vitest run --reporter verbose",
"test-short": "vitest run --reporter basic",
"test-watch": "vitest --reporter verbose",
"watch": "pnpm build --watch src"
}
}

21
node_modules/@expressive-code/plugin-frames/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Tibor Schiemann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

13
node_modules/@expressive-code/plugin-frames/README.md generated vendored Normal file
View File

@@ -0,0 +1,13 @@
# @expressive-code/plugin-frames
A default plugin of Expressive Code, an engine for presenting source code on the web.
It renders a window frame around every code block. Depending on the code's language, this frame can look like a code editor (similar to VS Code), or like a terminal window.
## Documentation
[Read this plugin's documentation](https://expressive-code.com/key-features/frames/) on the Expressive Code website to learn more about its features.
## Installation (not required)
No installation is required. This package is **installed by default** by our higher-level packages.

View File

@@ -0,0 +1,264 @@
import { PluginTexts, ExpressiveCodePlugin } from '@expressive-code/core';
declare const frameTypes: readonly ["code", "terminal", "none", "auto"];
type FrameType = (typeof frameTypes)[number];
declare const LanguageGroups: {
code: string[];
terminal: string[];
data: string[];
styles: string[];
textContent: string[];
};
declare const LanguagesWithFencedFrontmatter: string[];
interface FramesStyleSettings {
/**
* The color to use for the shadow of the frame.
* @default
* ({ theme, resolveSetting }) => theme.colors['widget.shadow'] || multiplyAlpha(resolveSetting('borderColor'), 0.75)
*/
shadowColor: string;
/**
* The CSS value for the box shadow of the frame.
* @default
* ({ resolveSetting }) => `0.1rem 0.1rem 0.2rem ${resolveSetting('frames.shadowColor')}`
*/
frameBoxShadowCssValue: string;
/**
* The CSS `background` value for the active editor tab.
* @default
* ({ theme }) => theme.colors['tab.activeBackground']
*/
editorActiveTabBackground: string;
/**
* The foreground color of the active editor tab.
* @default
* ({ theme }) => theme.colors['tab.activeForeground']
*/
editorActiveTabForeground: string;
/**
* The border color of the active editor tab.
* @default 'transparent'
*/
editorActiveTabBorderColor: string;
/**
* The height of the indicator lines highlighting the active editor tab.
* These are colorful lines that appear at the top and/or bottom of the active tab.
*
* The individual line colors can be set in {@link editorActiveTabIndicatorTopColor} and
* {@link editorActiveTabIndicatorBottomColor}.
*
* @default
* ({ resolveSetting }) => resolveSetting('borderWidth')
*/
editorActiveTabIndicatorHeight: string;
/**
* The color of the indicator line displayed at the top border of the active editor tab.
* @default
* ({ theme }) => theme.colors['tab.activeBorderTop']
*/
editorActiveTabIndicatorTopColor: string;
/**
* The color of the indicator line displayed at the bottom border of the active editor tab.
* @default
* ({ theme }) => theme.colors['tab.activeBorder']
*/
editorActiveTabIndicatorBottomColor: string;
/**
* The inline margin (= left margin in horizontal writing mode) to apply inside the tab bar
* before the first editor tab.
* @default '0'
*/
editorTabsMarginInlineStart: string;
/**
* The block margin (= top margin in horizontal writing mode) to apply inside the tab bar
* before the editor tabs.
* @default '0'
*/
editorTabsMarginBlockStart: string;
/**
* The border radius to apply to the outer corners of editor tabs.
* @default
* ({ resolveSetting }) => resolveSetting('borderRadius')
*/
editorTabBorderRadius: string;
/**
* The CSS `background` value of the editor tab bar.
* @default
* ({ theme }) => theme.colors['editorGroupHeader.tabsBackground']
*/
editorTabBarBackground: string;
/**
* The border color of the editor tab bar.
* @default
* ({ resolveSetting }) => resolveSetting('borderColor')
*/
editorTabBarBorderColor: string;
/**
* The color of the bottom border of the editor tab bar. This is an additional border
* that can be used to display a line between the editor tab bar and the code contents.
* @default
* ({ theme }) => theme.colors['editorGroupHeader.tabsBorder'] || 'transparent'
*/
editorTabBarBorderBottomColor: string;
/**
* The background color of the code editor.
* This color is used for the "code" frame type.
* @default
* ({ resolveSetting }) => resolveSetting('codeBackground')
*/
editorBackground: string;
/**
* The color of the three dots in the terminal title bar.
* @default
* ({ resolveSetting }) => resolveSetting('frames.terminalTitlebarForeground')
*/
terminalTitlebarDotsForeground: string;
/**
* The opacity of the three dots in the terminal title bar.
* @default '0.15'
*/
terminalTitlebarDotsOpacity: string;
/**
* The background color of the terminal title bar.
* @default
* ({ theme }) => theme.colors['titleBar.activeBackground'] || theme.colors['editorGroupHeader.tabsBackground']
*/
terminalTitlebarBackground: string;
/**
* The foreground color of the terminal title bar.
* @default
* ({ theme }) => theme.colors['titleBar.activeForeground']
*/
terminalTitlebarForeground: string;
/**
* The color of the border between the terminal title bar and the terminal contents.
* @default
* ({ theme, resolveSetting }) =>
* theme.colors['titleBar.border'] ||
* onBackground(resolveSetting('borderColor'), theme.type === 'dark' ? '#000000bf' : '#ffffffbf')
*/
terminalTitlebarBorderBottomColor: string;
/**
* The background color of the terminal window.
* This color is used for the "terminal" frame type.
* @default
* ({ theme }) => theme.colors['terminal.background']
*/
terminalBackground: string;
/**
* The background color of the copy button.
* This color is modified by the state-dependent opacity values specified in
* {@link inlineButtonBackgroundIdleOpacity}, {@link inlineButtonBackgroundHoverOrFocusOpacity}
* and {@link inlineButtonBackgroundActiveOpacity}.
* @default
* ({ resolveSetting }) => resolveSetting('frames.inlineButtonForeground')
*/
inlineButtonBackground: string;
/**
* The opacity of the copy button background when idle.
* @default '0'
*/
inlineButtonBackgroundIdleOpacity: string;
/**
* The opacity of the copy button background when hovered or focused.
* @default '0.2'
*/
inlineButtonBackgroundHoverOrFocusOpacity: string;
/**
* The opacity of the copy button background when pressed.
* @default '0.3'
*/
inlineButtonBackgroundActiveOpacity: string;
/**
* The foreground color of the copy button.
* @default
* ({ resolveSetting }) => resolveSetting('codeForeground')
*/
inlineButtonForeground: string;
/**
* The border color of the copy button.
* @default
* ({ resolveSetting }) => resolveSetting('frames.inlineButtonForeground')
*/
inlineButtonBorder: string;
/**
* The opacity of the copy button border.
* @default '0.4'
*/
inlineButtonBorderOpacity: string;
/**
* The background color of the tooltip shown after successfully copying the code.
* @default
* ({ theme }) => setLuminance(theme.colors['terminal.ansiGreen'] || '#0dbc79', 0.18)
*/
tooltipSuccessBackground: string;
/**
* The foreground color of the tooltip shown after successfully copying the code.
* @default 'white'
*/
tooltipSuccessForeground: string;
}
declare module '@expressive-code/core' {
interface StyleSettings {
frames: FramesStyleSettings;
}
}
interface PluginFramesOptions {
/**
* If `true`, and no title was found in the code block's meta string,
* the plugin will try to find and extract a comment line containing the code block file name
* from the first 4 lines of the code.
*
* @default true
*/
extractFileNameFromCode?: boolean | undefined;
/**
* If `true`, a "Copy to clipboard" button will be shown for each code block.
*
* @default true
*/
showCopyToClipboardButton?: boolean | undefined;
/**
* If `true`, the "Copy to clipboard" button of terminal window frames
* will remove comment lines starting with `#` from the copied text.
*
* This is useful to reduce the copied text to the actual commands users need to run,
* instead of also copying explanatory comments or instructions.
*
* @default true
*/
removeCommentsWhenCopyingTerminalFrames?: boolean | undefined;
}
interface PluginFramesProps {
/**
* The code block's title. For terminal frames, this is displayed as the terminal window title,
* and for code frames, it's displayed as the file name in an open file tab.
*
* If no title is given, the plugin will try to automatically extract a title from a
* [file name comment](https://expressive-code.com/key-features/frames/#file-name-comments)
* inside your code, unless disabled by the `extractFileNameFromCode` option.
*/
title: string;
/**
* Allows you to override the automatic frame type detection for a code block.
*
* The supported values are `code`, `terminal`, `none` and `auto`.
*
* @default `auto`
*/
frame: FrameType;
}
declare module '@expressive-code/core' {
interface ExpressiveCodeBlockProps extends PluginFramesProps {
}
}
declare const pluginFramesTexts: PluginTexts<{
terminalWindowFallbackTitle: string;
copyButtonTooltip: string;
copyButtonCopied: string;
}>;
declare function pluginFrames(options?: PluginFramesOptions): ExpressiveCodePlugin;
export { FramesStyleSettings, LanguageGroups, LanguagesWithFencedFrontmatter, PluginFramesOptions, PluginFramesProps, pluginFrames, pluginFramesTexts };

View File

@@ -0,0 +1,588 @@
// src/index.ts
import { PluginTexts } from "@expressive-code/core";
import { h } from "@expressive-code/core/hast";
// src/styles.ts
import { PluginStyleSettings, multiplyAlpha, onBackground, setLuminance } from "@expressive-code/core";
var framesStyleSettings = new PluginStyleSettings({
defaultValues: {
frames: {
shadowColor: ({ theme, resolveSetting }) => theme.colors["widget.shadow"] || multiplyAlpha(resolveSetting("borderColor"), 0.75),
frameBoxShadowCssValue: ({ resolveSetting }) => `0.1rem 0.1rem 0.2rem ${resolveSetting("frames.shadowColor")}`,
editorActiveTabBackground: ({ theme }) => theme.colors["tab.activeBackground"],
editorActiveTabForeground: ({ theme }) => theme.colors["tab.activeForeground"],
editorActiveTabBorderColor: "transparent",
editorActiveTabIndicatorHeight: ({ resolveSetting }) => resolveSetting("borderWidth"),
editorActiveTabIndicatorTopColor: ({ theme }) => theme.colors["tab.activeBorderTop"],
editorActiveTabIndicatorBottomColor: ({ theme }) => theme.colors["tab.activeBorder"],
editorTabsMarginInlineStart: "0",
editorTabsMarginBlockStart: "0",
editorTabBorderRadius: ({ resolveSetting }) => resolveSetting("borderRadius"),
editorTabBarBackground: ({ theme }) => theme.colors["editorGroupHeader.tabsBackground"],
editorTabBarBorderColor: ({ resolveSetting }) => resolveSetting("borderColor"),
editorTabBarBorderBottomColor: ({ theme }) => theme.colors["editorGroupHeader.tabsBorder"] || "transparent",
editorBackground: ({ resolveSetting }) => resolveSetting("codeBackground"),
terminalTitlebarDotsForeground: ({ resolveSetting }) => resolveSetting("frames.terminalTitlebarForeground"),
terminalTitlebarDotsOpacity: "0.15",
terminalTitlebarBackground: ({ theme }) => theme.colors["titleBar.activeBackground"] || theme.colors["editorGroupHeader.tabsBackground"],
terminalTitlebarForeground: ({ theme }) => theme.colors["titleBar.activeForeground"],
terminalTitlebarBorderBottomColor: ({ theme, resolveSetting }) => theme.colors["titleBar.border"] || onBackground(resolveSetting("borderColor"), theme.type === "dark" ? "#000000bf" : "#ffffffbf"),
terminalBackground: ({ theme }) => theme.colors["terminal.background"],
inlineButtonBackground: ({ resolveSetting }) => resolveSetting("frames.inlineButtonForeground"),
inlineButtonBackgroundIdleOpacity: "0",
inlineButtonBackgroundHoverOrFocusOpacity: "0.2",
inlineButtonBackgroundActiveOpacity: "0.3",
inlineButtonForeground: ({ resolveSetting }) => resolveSetting("codeForeground"),
inlineButtonBorder: ({ resolveSetting }) => resolveSetting("frames.inlineButtonForeground"),
inlineButtonBorderOpacity: "0.4",
tooltipSuccessBackground: ({ theme }) => setLuminance(theme.colors["terminal.ansiGreen"] || "#0dbc79", 0.18),
tooltipSuccessForeground: "white"
}
}
});
function getFramesBaseStyles({ cssVar }, options) {
const dotsSvg = [
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 16' preserveAspectRatio='xMidYMid meet'>`,
`<circle cx='8' cy='8' r='8'/>`,
`<circle cx='30' cy='8' r='8'/>`,
`<circle cx='52' cy='8' r='8'/>`,
`</svg>`
].join("");
const escapedDotsSvg = dotsSvg.replace(/</g, "%3C").replace(/>/g, "%3E");
const terminalTitlebarDots = `url("data:image/svg+xml,${escapedDotsSvg}")`;
const copySvg = [
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.75'>`,
`<path d='M3 19a2 2 0 0 1-1-2V2a2 2 0 0 1 1-1h13a2 2 0 0 1 2 1'/>`,
`<rect x='6' y='5' width='16' height='18' rx='1.5' ry='1.5'/>`,
`</svg>`
].join("");
const escapedCopySvg = copySvg.replace(/</g, "%3C").replace(/>/g, "%3E");
const copyToClipboard = `url("data:image/svg+xml,${escapedCopySvg}")`;
const tabBarBackground = [
`linear-gradient(to top, ${cssVar("frames.editorTabBarBorderBottomColor")} ${cssVar("borderWidth")}, transparent ${cssVar("borderWidth")})`,
`linear-gradient(${cssVar("frames.editorTabBarBackground")}, ${cssVar("frames.editorTabBarBackground")})`
].join(",");
const frameStyles = `.frame {
all: unset;
position: relative;
display: block;
--header-border-radius: calc(${cssVar("borderRadius")} + ${cssVar("borderWidth")});
--tab-border-radius: calc(${cssVar("frames.editorTabBorderRadius")} + ${cssVar("borderWidth")});
--button-spacing: 0.4rem;
--code-background: ${cssVar("frames.editorBackground")};
border-radius: var(--header-border-radius);
box-shadow: ${cssVar("frames.frameBoxShadowCssValue")};
.header {
display: none;
z-index: 1;
position: relative;
border-radius: var(--header-border-radius) var(--header-border-radius) 0 0;
}
/* Styles to apply if we have a title bar or tab bar */
&.has-title,
&.is-terminal {
& pre, & code {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
/* Prevent empty window titles from collapsing in height */
.title:empty:before {
content: '\\a0';
}
/* Editor tab bar */
&.has-title:not(.is-terminal) {
--button-spacing: calc(1.9rem + 2 * (${cssVar("uiPaddingBlock")} + ${cssVar("frames.editorActiveTabIndicatorHeight")}));
/* Active editor tab */
& .title {
position: relative;
color: ${cssVar("frames.editorActiveTabForeground")};
background: ${cssVar("frames.editorActiveTabBackground")};
background-clip: padding-box;
margin-block-start: ${cssVar("frames.editorTabsMarginBlockStart")};
padding: calc(${cssVar("uiPaddingBlock")} + ${cssVar("frames.editorActiveTabIndicatorHeight")}) ${cssVar("uiPaddingInline")};
border: ${cssVar("borderWidth")} solid ${cssVar("frames.editorActiveTabBorderColor")};
border-radius: var(--tab-border-radius) var(--tab-border-radius) 0 0;
border-bottom: none;
overflow: hidden;
&::after {
content: '';
position: absolute;
pointer-events: none;
inset: 0;
border-top: ${cssVar("frames.editorActiveTabIndicatorHeight")} solid ${cssVar("frames.editorActiveTabIndicatorTopColor")};
border-bottom: ${cssVar("frames.editorActiveTabIndicatorHeight")} solid ${cssVar("frames.editorActiveTabIndicatorBottomColor")};
}
}
/* Tab bar background */
& .header {
display: flex;
background: ${tabBarBackground};
background-repeat: no-repeat;
padding-inline-start: ${cssVar("frames.editorTabsMarginInlineStart")};
&::before {
content: '';
position: absolute;
pointer-events: none;
inset: 0;
border: ${cssVar("borderWidth")} solid ${cssVar("frames.editorTabBarBorderColor")};
border-radius: inherit;
border-bottom: none;
}
}
}
/* Terminal window */
&.is-terminal {
--button-spacing: calc(1.9rem + ${cssVar("borderWidth")} + 2 * ${cssVar("uiPaddingBlock")});
--code-background: ${cssVar("frames.terminalBackground")};
/* Terminal title bar */
& .header {
display: flex;
align-items: center;
justify-content: center;
padding-block: ${cssVar("uiPaddingBlock")};
padding-block-end: calc(${cssVar("uiPaddingBlock")} + ${cssVar("borderWidth")});
position: relative;
font-weight: 500;
letter-spacing: 0.025ch;
color: ${cssVar("frames.terminalTitlebarForeground")};
background: ${cssVar("frames.terminalTitlebarBackground")};
border: ${cssVar("borderWidth")} solid ${cssVar("borderColor")};
border-bottom: none;
/* Display three dots at the left side of the header */
&::before {
content: '';
position: absolute;
pointer-events: none;
left: ${cssVar("uiPaddingInline")};
width: 2.1rem;
height: ${2.1 / 60 * 16}rem;
line-height: 0;
background-color: ${cssVar("frames.terminalTitlebarDotsForeground")};
opacity: ${cssVar("frames.terminalTitlebarDotsOpacity")};
-webkit-mask-image: ${terminalTitlebarDots};
-webkit-mask-repeat: no-repeat;
mask-image: ${terminalTitlebarDots};
mask-repeat: no-repeat;
}
/* Display a border below the header */
&::after {
content: '';
position: absolute;
pointer-events: none;
inset: 0;
border-bottom: ${cssVar("borderWidth")} solid ${cssVar("frames.terminalTitlebarBorderBottomColor")};
}
}
}
/* Code */
& pre {
background: var(--code-background);
}
}`;
const copyButtonStyles = `.copy {
display: flex;
gap: 0.25rem;
flex-direction: row;
position: absolute;
inset-block-start: calc(${cssVar("borderWidth")} + var(--button-spacing));
inset-inline-end: calc(${cssVar("borderWidth")} + ${cssVar("uiPaddingInline")} / 2);
/* RTL support: Code is always LTR, so the inline copy button
must match this to avoid overlapping the start of lines */
direction: ltr;
unicode-bidi: isolate;
button {
position: relative;
align-self: flex-end;
margin: 0;
padding: 0;
border: none;
border-radius: 0.2rem;
z-index: 1;
cursor: pointer;
transition-property: opacity, background, border-color;
transition-duration: 0.2s;
transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* Mobile-first styles: Make the button visible and tappable */
width: 2.5rem;
height: 2.5rem;
background: var(--code-background);
opacity: 0.75;
div {
position: absolute;
inset: 0;
border-radius: inherit;
background: ${cssVar("frames.inlineButtonBackground")};
opacity: ${cssVar("frames.inlineButtonBackgroundIdleOpacity")};
transition-property: inherit;
transition-duration: inherit;
transition-timing-function: inherit;
}
&::before {
content: '';
position: absolute;
pointer-events: none;
inset: 0;
border-radius: inherit;
border: ${cssVar("borderWidth")} solid ${cssVar("frames.inlineButtonBorder")};
opacity: ${cssVar("frames.inlineButtonBorderOpacity")};
}
&::after {
content: '';
position: absolute;
pointer-events: none;
inset: 0;
background-color: ${cssVar("frames.inlineButtonForeground")};
-webkit-mask-image: ${copyToClipboard};
-webkit-mask-repeat: no-repeat;
mask-image: ${copyToClipboard};
mask-repeat: no-repeat;
margin: 0.475rem;
line-height: 0;
}
/*
On hover or focus, make the button fully opaque
and set hover/focus background opacity
*/
&:hover, &:focus:focus-visible {
opacity: 1;
div {
opacity: ${cssVar("frames.inlineButtonBackgroundHoverOrFocusOpacity")};
}
}
/* On press, set active background opacity */
&:active {
opacity: 1;
div {
opacity: ${cssVar("frames.inlineButtonBackgroundActiveOpacity")};
}
}
}
.feedback {
--tooltip-arrow-size: 0.35rem;
--tooltip-bg: ${cssVar("frames.tooltipSuccessBackground")};
color: ${cssVar("frames.tooltipSuccessForeground")};
pointer-events: none;
user-select: none;
-webkit-user-select: none;
position: relative;
align-self: center;
background-color: var(--tooltip-bg);
z-index: 99;
padding: 0.125rem 0.75rem;
border-radius: 0.2rem;
margin-inline-end: var(--tooltip-arrow-size);
opacity: 0;
transition-property: opacity, transform;
transition-duration: 0.2s;
transition-timing-function: ease-in-out;
transform: translate3d(0, 0.25rem, 0);
&::after {
content: '';
position: absolute;
pointer-events: none;
top: calc(50% - var(--tooltip-arrow-size));
inset-inline-end: calc(-2 * (var(--tooltip-arrow-size) - 0.5px));
border: var(--tooltip-arrow-size) solid transparent;
border-inline-start-color: var(--tooltip-bg);
}
&.show {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
}
@media (hover: hover) {
/* If a mouse is available, hide the button by default and make it smaller */
.copy button {
opacity: 0;
width: 2rem;
height: 2rem;
}
/* Reveal the non-hovered button in the following cases:
- when the frame is hovered
- when a sibling inside the frame is focused
- when the copy button shows a visible feedback message
*/
.frame:hover .copy button:not(:hover),
.frame:focus-within :focus-visible ~ .copy button:not(:hover),
.frame .copy .feedback.show ~ button:not(:hover) {
opacity: 0.75;
}
}`;
const styles = [
// Always add base frame styles
frameStyles,
// Add copy button styles if enabled
options.showCopyToClipboardButton ? copyButtonStyles : ""
];
return styles.join("\n");
}
// src/utils.ts
var frameTypes = ["code", "terminal", "none", "auto"];
function frameTypeFromString(input) {
if (input === "")
input = "none";
if (input === "editor")
input = "code";
if (input === "shell")
input = "terminal";
const frameType = input;
return frameTypes.includes(frameType) ? frameType : void 0;
}
var LanguageGroups = {
code: ["astro", "cjs", "htm", "html", "js", "jsx", "mjs", "svelte", "ts", "tsx", "typescript", "vb", "vue", "vue-html"],
terminal: ["ansi", "bash", "bat", "batch", "cmd", "console", "nu", "nushell", "powershell", "ps", "ps1", "psd1", "psm1", "sh", "shell", "shellscript", "shellsession", "zsh"],
data: ["csv", "env", "ini", "json", "toml", "xml", "yaml", "yml"],
styles: ["css", "less", "sass", "scss", "styl", "stylus", "xsl"],
textContent: ["markdown", "md", "mdx"]
};
var LanguagesWithFencedFrontmatter = ["astro", "markdown", "md", "mdx", "toml", "yaml", "yml"];
function isTerminalLanguage(language) {
return LanguageGroups.terminal.includes(language);
}
var getFileNameCommentRegExpString = () => [
// Start of line
`^`,
// Optional whitespace
`\\s*`,
// Mandatory comment start: `//`, `#` (but not `#!`), `<!--` or `/*`
`(?://|#(?!!)|<!--|/\\*)`,
// Optional whitespace
`\\s*`,
// Optional prefix before the file name:
// - This is intended to match strings like `File name:` or `Example :`,
// but not Windows drive letters like `C:`,
// or URL protocols like `https:`
// - We therefore expect the prefix to begin with any sequence of characters
// not starting with a letter + colon (to rule out Windows drive letters)
// - The prefix must then be followed by:
// - a Japanese colon (`\\uff1a`), or
// - a regular colon (`:`) not followed by `//` (to rule out URL protocols)
`(?:((?![a-z]:).*?)(?:\\uff1a|:(?!//)))?`,
// Optional whitespace
`\\s*`,
// Capture the file name
`(`,
// Optional Windows drive letter
`(?:[a-z]:)?`,
// Optional sequence of characters allowed in file paths
`[\\w./~%[\\]+\\\\-]*`,
// Optional dot and supported file extension
`(?:\\.(?:${Object.values(LanguageGroups).flat().sort().join("|")}))?`,
// End of file name capture
`)`,
// Optional whitespace
`\\s*`,
// Optional HTML or JS/CSS comment end (`-->` or `*/`)
`(?:-->|\\*/)?`,
// Optional whitespace
`\\s*`,
// End of line
`$`
].join("");
var fileNameCommentRegExp;
function getFileNameFromComment(line, lang) {
if (fileNameCommentRegExp === void 0) {
fileNameCommentRegExp = new RegExp(getFileNameCommentRegExpString(), "i");
}
const matches = fileNameCommentRegExp.exec(line);
const textBeforeFileName = matches?.[1] ?? "";
const possibleFileName = matches?.[2];
if (!possibleFileName)
return;
if (!possibleFileName.match(/[^.:/\\~]/))
return;
if (possibleFileName.match(/^\.{2,}(?!\/|\\)/))
return;
const languageGroup = Object.values(LanguageGroups).find((group) => group.includes(lang));
const fileNameWithoutPath = possibleFileName.replace(/^.*[/\\]/, "");
const fileExt = fileNameWithoutPath.match(/\.([^.]+)$/)?.[1];
const hasTypicalFileNameBeginning = possibleFileName.match(/^(\/|\\|\.[/\\]|~|[a-z]:).+/i);
const hasFileNameStartingWithDot = fileNameWithoutPath.startsWith(".");
const looksLikeSeparatedPath = (
// Contains path separators
possibleFileName.match(/[/\\]/) && // Also contains other characters (except path separators, numbers and dots)
possibleFileName.match(/[^/\\0-9.]/) && // Does not contain spaces
!possibleFileName.match(/\s/) && // Is all lowercase
possibleFileName === possibleFileName.toLowerCase()
);
const hasTypicalFileNamePattern = hasTypicalFileNameBeginning || hasFileNameStartingWithDot || looksLikeSeparatedPath;
if (hasTypicalFileNamePattern && (!textBeforeFileName.length || languageGroup === LanguageGroups.terminal)) {
return possibleFileName;
}
if (!fileExt || languageGroup && !languageGroup.includes(fileExt))
return;
return possibleFileName;
}
function extractFileNameFromCodeBlock(codeBlock) {
let extractedFileName = void 0;
let lineIdx = codeBlock.getLines(0, 4).findIndex((line) => {
extractedFileName = getFileNameFromComment(line.text, codeBlock.language);
return !!extractedFileName;
});
if (!extractedFileName)
return;
codeBlock.deleteLine(lineIdx);
if (LanguagesWithFencedFrontmatter.includes(codeBlock.language)) {
const openingFence = lineIdx > 0 ? codeBlock.getLine(lineIdx - 1)?.text.trim() : void 0;
const closingFence = codeBlock.getLine(lineIdx)?.text.trim();
const isFrontmatterEmptyNow = openingFence === closingFence && ["---", "+++"].includes(openingFence ?? "");
if (isFrontmatterEmptyNow) {
lineIdx--;
codeBlock.deleteLine(lineIdx);
codeBlock.deleteLine(lineIdx);
}
}
if (codeBlock.getLine(lineIdx)?.text.trim().length === 0) {
codeBlock.deleteLine(lineIdx);
}
return extractedFileName;
}
// src/copy-js-module.min.ts
var copy_js_module_min_default = 'try{(()=>{function i(o){let e=document.createElement("pre");Object.assign(e.style,{opacity:"0",pointerEvents:"none",position:"absolute",overflow:"hidden",left:"0",top:"0",width:"20px",height:"20px",webkitUserSelect:"auto",userSelect:"all"}),e.ariaHidden="true",e.textContent=o,document.body.appendChild(e);let a=document.createRange();a.selectNode(e);let n=getSelection();if(!n)return!1;n.removeAllRanges(),n.addRange(a);let r=!1;try{r=document.execCommand("copy")}finally{n.removeAllRanges(),document.body.removeChild(e)}return r}async function l(o){let e=o.currentTarget,a=e.dataset,n=!1,r=a.code.replace(/\\u007f/g,`\n`);try{await navigator.clipboard.writeText(r),n=!0}catch{n=i(r)}if(!n||e.parentNode?.querySelector(".feedback"))return;let t=document.createElement("div");t.classList.add("feedback"),t.append(a.copied),e.before(t),t.offsetWidth,requestAnimationFrame(()=>t?.classList.add("show"));let c=()=>!t||t.classList.remove("show"),d=()=>{!t||parseFloat(getComputedStyle(t).opacity)>0||(t.remove(),t=void 0)};setTimeout(c,1500),setTimeout(d,2500),e.addEventListener("blur",c),t.addEventListener("transitioncancel",d),t.addEventListener("transitionend",d)}function s(o){o.querySelectorAll?.("[SELECTOR]").forEach(e=>e.addEventListener("click",l))}s(document);var u=new MutationObserver(o=>o.forEach(e=>e.addedNodes.forEach(a=>{s(a)})));u.observe(document.body,{childList:!0,subtree:!0});document.addEventListener("astro:page-load",()=>{s(document)});})();}catch(e){console.error("[EC] copy-js-module failed:",e)}';
// src/index.ts
var pluginFramesTexts = new PluginTexts({
terminalWindowFallbackTitle: "Terminal window",
copyButtonTooltip: "Copy to clipboard",
copyButtonCopied: "Copied!"
});
pluginFramesTexts.addLocale("de", {
terminalWindowFallbackTitle: "Terminal-Fenster",
copyButtonTooltip: "In die Zwischenablage kopieren",
copyButtonCopied: "Kopiert!"
});
function pluginFrames(options = {}) {
options = {
extractFileNameFromCode: true,
showCopyToClipboardButton: true,
removeCommentsWhenCopyingTerminalFrames: true,
...options
};
return {
name: "Frames",
styleSettings: framesStyleSettings,
baseStyles: (context) => getFramesBaseStyles(context, options),
jsModules: options.showCopyToClipboardButton ? [copy_js_module_min_default.replace(/\[SELECTOR\]/g, ".expressive-code .copy button")] : void 0,
hooks: {
preprocessMetadata: ({ codeBlock }) => {
const { metaOptions, props } = codeBlock;
props.title = metaOptions.getString("title") ?? props.title;
const frame = metaOptions.getString("frame");
if (frame !== void 0) {
const frameType = frameTypeFromString(frame);
if (frameType === void 0)
throw new Error(
`Invalid frame type \`${frame}\` found in code block meta string.
Valid frame types are: ${frameTypes.join(", ")}.`.replace(/\s+/g, " ")
);
props.frame = frameType;
}
},
preprocessCode: ({ codeBlock }) => {
const { props, language } = codeBlock;
if (props.title === void 0 && props.frame !== "none" && options.extractFileNameFromCode) {
props.title = extractFileNameFromCodeBlock(codeBlock);
}
if ((props.frame ?? "auto") === "auto" && isTerminalLanguage(language)) {
const titleIsFileName = props.title && getFileNameFromComment(`// ${props.title}`, language);
if (titleIsFileName || codeBlock.getLines(0, 4).some((line) => line.text.match(/^\s*#!/))) {
props.frame = "code";
}
}
},
postprocessRenderedBlock: ({ codeBlock, renderData, locale }) => {
const texts = pluginFramesTexts.get(locale);
const { title: titleText, frame = "auto" } = codeBlock.props;
const isTerminal = frame === "terminal" || frame === "auto" && isTerminalLanguage(codeBlock.language);
const visibleTitle = frame !== "none" && titleText || isTerminal ? [h("span", { className: "title" }, titleText || "")] : [];
const screenReaderTitle = !titleText && isTerminal ? [h("span", { className: "sr-only" }, texts.terminalWindowFallbackTitle)] : [];
const extraElements = [];
if (options.showCopyToClipboardButton) {
let codeToCopy = codeBlock.code;
if (options.removeCommentsWhenCopyingTerminalFrames && isTerminal) {
codeToCopy = codeToCopy.replace(/(?<=^|\n)\s*#.*($|\n+)/g, "").trim();
}
codeToCopy = codeToCopy.replace(/\n/g, "\x7F");
extraElements.push(
h("div", { className: "copy" }, [
h(
"button",
{
title: texts.copyButtonTooltip,
"data-copied": texts.copyButtonCopied,
"data-code": codeToCopy
},
[h("div")]
)
])
);
}
renderData.blockAst = h(
"figure",
{
className: [
"frame",
// If the code block is a terminal, add the `is-terminal` class
...isTerminal ? ["is-terminal"] : [],
// If the code block has a title, add the `has-title` class
...frame !== "none" && titleText ? ["has-title"] : []
]
},
[
h("figcaption", { className: "header" }, [...visibleTitle, ...screenReaderTitle]),
// Render the original code block
renderData.blockAst,
// Add any extra elements (e.g. copy button)
...extraElements
]
);
}
}
};
}
export {
LanguageGroups,
LanguagesWithFencedFrontmatter,
pluginFrames,
pluginFramesTexts
};
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,40 @@
{
"name": "@expressive-code/plugin-frames",
"version": "0.35.3",
"description": "Frames plugin for Expressive Code. Wraps code blocks in a styled editor or terminal frame with support for titles, multiple tabs and more.",
"keywords": [],
"author": "Tibor Schiemann",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/expressive-code/expressive-code.git",
"directory": "packages/@expressive-code/plugin-frames"
},
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"exports": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"dependencies": {
"@expressive-code/core": "^0.35.3"
},
"devDependencies": {
"@expressive-code/plugin-shiki": "^0.35.3",
"@internal/test-utils": "^0.2.28"
},
"scripts": {
"build": "pnpm build-js-modules && tsup ./src/index.ts --format esm --dts --sourcemap --clean",
"build-js-modules": "tsm --require=../../../scripts/lib/filter-warnings.cjs ../../../scripts/build-js-module.ts ./src/copy-js-module.ts",
"coverage": "vitest run --coverage",
"test": "vitest run --reporter verbose",
"test-short": "vitest run --reporter basic",
"test-watch": "vitest --reporter verbose",
"watch": "pnpm build --watch src"
}
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Tibor Schiemann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,53 @@
import { ExpressiveCodePlugin } from '@expressive-code/core';
interface LineNumbersStyleSettings {
/**
* Allows overriding the foreground color to use for line numbers.
*
* By default, line numbers inherit the gutter foreground color defined by the
* `gutterForeground` core style setting.
*
* @default 'inherit'
*/
foreground: string;
/**
* Allows overriding the foreground color to use for highlighted line numbers.
*
* By default, highlighted line numbers inherit the gutter highlighted foreground color
* defined by the `gutterHighlightForeground` core style setting.
*
* @default 'inherit'
*/
highlightForeground: string;
}
declare module '@expressive-code/core' {
interface StyleSettings {
lineNumbers: LineNumbersStyleSettings;
}
}
interface PluginLineNumbersProps {
/**
* Whether to show line numbers on the current code block.
*
* The default value of this prop can be changed using the `defaultProps` option
* in your Expressive Code configuration. You can also change the default value by language
* using `defaultProps.overridesByLang`.
*
* @default true
*/
showLineNumbers: boolean;
/**
* The line number to start counting from.
*
* @default 1
*/
startLineNumber: number;
}
declare module '@expressive-code/core' {
interface ExpressiveCodeBlockProps extends PluginLineNumbersProps {
}
}
declare function pluginLineNumbers(): ExpressiveCodePlugin;
export { LineNumbersStyleSettings, PluginLineNumbersProps, pluginLineNumbers };

View File

@@ -0,0 +1,65 @@
// src/index.ts
import { setInlineStyle, h } from "@expressive-code/core/hast";
// src/styles.ts
import { PluginStyleSettings } from "@expressive-code/core";
var lineNumbersStyleSettings = new PluginStyleSettings({
defaultValues: {
lineNumbers: {
foreground: "inherit",
highlightForeground: "inherit"
}
}
});
function getLineNumbersBaseStyles({ cssVar }) {
const result = `
.gutter .ln {
display: inline-flex;
justify-content: flex-end;
align-items: flex-start;
box-sizing: content-box;
min-width: var(--lnWidth, 2ch);
padding-inline: 2ch;
color: ${cssVar("lineNumbers.foreground")};
.highlight & {
color: ${cssVar("lineNumbers.highlightForeground")};
}
}
`;
return result;
}
// src/index.ts
function pluginLineNumbers() {
return {
name: "Line numbers",
styleSettings: lineNumbersStyleSettings,
baseStyles: (context) => getLineNumbersBaseStyles(context),
hooks: {
preprocessMetadata: ({ codeBlock: { metaOptions, props }, addGutterElement }) => {
props.showLineNumbers = metaOptions.getBoolean("showLineNumbers") ?? props.showLineNumbers;
props.startLineNumber = metaOptions.getInteger("startLineNumber") ?? props.startLineNumber;
if (props.showLineNumbers !== false) {
addGutterElement({
renderPhase: "earlier",
renderLine: ({ codeBlock, lineIndex }) => {
return h("div.ln", `${lineIndex + (codeBlock.props.startLineNumber ?? 1)}`);
},
renderPlaceholder: () => h("div.ln")
});
}
},
postprocessRenderedBlock: ({ codeBlock, renderData }) => {
const { startLineNumber = 1 } = codeBlock.props;
const endLineNumber = startLineNumber + codeBlock.getLines().length - 1;
const lnWidth = Math.max(startLineNumber.toString().length, endLineNumber.toString().length);
if (lnWidth > 2)
setInlineStyle(renderData.blockAst, "--lnWidth", `${lnWidth}ch`);
}
}
};
}
export {
pluginLineNumbers
};
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,42 @@
{
"name": "@expressive-code/plugin-line-numbers",
"version": "0.35.3",
"description": "Line numbers plugin for Expressive Code, a text marking & annotation engine for presenting source code on the web.",
"keywords": [],
"author": "Tibor Schiemann",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/expressive-code/expressive-code.git",
"directory": "packages/@expressive-code/plugin-line-numbers"
},
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"exports": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"dependencies": {
"@expressive-code/core": "^0.35.3"
},
"devDependencies": {
"@expressive-code/plugin-collapsible-sections": "^0.35.3",
"@expressive-code/plugin-frames": "^0.35.3",
"@expressive-code/plugin-shiki": "^0.35.3",
"@expressive-code/plugin-text-markers": "^0.35.3",
"@internal/test-utils": "^0.2.28"
},
"scripts": {
"build": "tsup ./src/index.ts --format esm --dts --sourcemap --clean",
"coverage": "vitest run --coverage",
"test": "vitest run --reporter verbose",
"test-short": "vitest run --reporter basic",
"test-watch": "vitest --reporter verbose",
"watch": "pnpm build --watch src"
}
}

21
node_modules/@expressive-code/plugin-shiki/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Tibor Schiemann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

13
node_modules/@expressive-code/plugin-shiki/README.md generated vendored Normal file
View File

@@ -0,0 +1,13 @@
# @expressive-code/plugin-shiki
A default plugin of Expressive Code, an engine for presenting source code on the web.
It performs syntax highlighting of your code blocks using Shiki, which uses the same engine as VS Code to provide accurate syntax highlighting for over 100 languages.
## Documentation
[Read this plugin's documentation](https://expressive-code.com/key-features/syntax-highlighting/) on the Expressive Code website to learn more about its features.
## Installation (not required)
No installation is required. This package is **installed by default** by our higher-level packages.

View File

@@ -0,0 +1,33 @@
import { ExpressiveCodeTheme, ExpressiveCodePlugin } from '@expressive-code/core';
import { MaybeGetter, MaybeArray, LanguageRegistration as LanguageRegistration$1, bundledThemes } from 'shiki';
type Optional<T, K extends keyof T> = Omit<T, K> & Pick<Partial<T>, K>;
type IRawRepository = Optional<LanguageRegistration$1['repository'], '$self' | '$base'>;
interface LanguageRegistration extends Omit<LanguageRegistration$1, 'repository'> {
repository?: IRawRepository | undefined;
}
type LanguageInput = MaybeGetter<MaybeArray<LanguageRegistration>>;
interface PluginShikiOptions {
/**
* A list of additional languages that should be available for syntax highlighting.
*
* You can pass any of the language input types supported by Shiki, e.g.:
* - `import('./some-exported-grammar.mjs')`
* - `async () => JSON.parse(await fs.readFile('some-json-grammar.json', 'utf-8'))`
*
* See the [Shiki documentation](https://shiki.style/guide/load-lang) for more information.
*/
langs?: LanguageInput[] | undefined;
}
/**
* A list of all themes bundled with Shiki.
*/
type BundledShikiTheme = Exclude<keyof typeof bundledThemes, 'css-variables'>;
/**
* Loads a theme bundled with Shiki for use with Expressive Code.
*/
declare function loadShikiTheme(bundledThemeName: BundledShikiTheme): Promise<ExpressiveCodeTheme>;
declare function pluginShiki(options?: PluginShikiOptions): ExpressiveCodePlugin;
export { BundledShikiTheme, PluginShikiOptions, loadShikiTheme, pluginShiki };

View File

@@ -0,0 +1,192 @@
// src/index.ts
import { ExpressiveCodeTheme as ExpressiveCodeTheme2, InlineStyleAnnotation } from "@expressive-code/core";
// src/highlighter.ts
import { getHighlighter, isSpecialLang, bundledLanguages } from "shiki";
import { getStableObjectHash } from "@expressive-code/core";
var highlighterPromiseByConfig = /* @__PURE__ */ new Map();
var promisesByHighlighter = /* @__PURE__ */ new WeakMap();
var themeCacheKeys = /* @__PURE__ */ new WeakMap();
async function getCachedHighlighter(config = {}) {
const configCacheKey = getStableObjectHash(config);
let highlighterPromise = highlighterPromiseByConfig.get(configCacheKey);
if (highlighterPromise === void 0) {
const langs = [];
if (config.langs?.length) {
langs.push(...config.langs);
}
langs.push(...Object.keys(bundledLanguages));
highlighterPromise = getHighlighter({
themes: [],
langs
});
highlighterPromiseByConfig.set(configCacheKey, highlighterPromise);
}
return highlighterPromise;
}
async function ensureThemeIsLoaded(highlighter, theme) {
const existingCacheKey = themeCacheKeys.get(theme);
const cacheKey = existingCacheKey ?? `${theme.name}-${getStableObjectHash({ bg: theme.bg, fg: theme.fg, settings: theme.settings })}`;
if (!existingCacheKey)
themeCacheKeys.set(theme, cacheKey);
if (!highlighter.getLoadedThemes().includes(cacheKey)) {
await memoizeHighlighterTask(highlighter, `loadTheme:${cacheKey}`, () => {
const themeUsingCacheKey = { ...theme, name: cacheKey, settings: theme.settings ?? [] };
return highlighter.loadTheme(themeUsingCacheKey);
});
}
return cacheKey;
}
async function ensureLanguageIsLoaded(highlighter, language) {
const loadedLanguages = new Set(highlighter.getLoadedLanguages());
const isLoaded = loadedLanguages.has(language);
const isSpecial = isSpecialLang(language);
const isBundled = Object.keys(bundledLanguages).includes(language);
const isAvailable = isLoaded || isSpecial || isBundled;
if (!isAvailable)
return "txt";
if (isLoaded || isSpecial)
return language;
const loadedLanguage = await memoizeHighlighterTask(highlighter, `loadLanguage:${language}`, async () => {
await highlighter.loadLanguage(language);
return language;
});
return loadedLanguage;
}
function memoizeHighlighterTask(highlighter, taskId, taskFn) {
let promises = promisesByHighlighter.get(highlighter);
if (!promises) {
promises = /* @__PURE__ */ new Map();
promisesByHighlighter.set(highlighter, promises);
}
let promise = promises.get(taskId);
if (promise === void 0) {
promise = taskFn();
promises.set(taskId, promise);
}
return promise;
}
// src/index.ts
import { bundledThemes } from "shiki";
async function loadShikiTheme(bundledThemeName) {
const shikiTheme = (await bundledThemes[bundledThemeName]()).default;
return new ExpressiveCodeTheme2(shikiTheme);
}
function pluginShiki(options = {}) {
const { langs } = options;
return {
name: "Shiki",
hooks: {
performSyntaxAnalysis: async ({ codeBlock, styleVariants, config: { logger } }) => {
const codeLines = codeBlock.getLines();
let code = codeBlock.code;
if (isTerminalLanguage(codeBlock.language)) {
code = code.replace(/<([^>]*[^>\s])>/g, "X$1X");
}
let highlighter;
try {
highlighter = await getCachedHighlighter({ langs });
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
throw new Error(`Failed to load syntax highlighter. Please ensure that the configured langs are supported by Shiki.
Received error message: "${error.message}"`, {
cause: error
});
}
const loadedLanguageName = await ensureLanguageIsLoaded(highlighter, codeBlock.language);
if (loadedLanguageName !== codeBlock.language) {
logger.warn(
`Found unknown code block language "${codeBlock.language}" in ${codeBlock.parentDocument?.sourceFilePath ? `document "${codeBlock.parentDocument?.sourceFilePath}"` : "markdown/MDX document"}. Using "${loadedLanguageName}" instead. You can add custom languages using the "langs" config option.`
);
}
for (let styleVariantIndex = 0; styleVariantIndex < styleVariants.length; styleVariantIndex++) {
const theme = styleVariants[styleVariantIndex].theme;
const loadedThemeName = await ensureThemeIsLoaded(highlighter, theme);
let tokenLines;
try {
tokenLines = highlighter.codeToTokensBase(code, {
// @ts-expect-error: We took care that the language is loaded
lang: loadedLanguageName,
// @ts-expect-error: We took care that the theme is loaded
theme: loadedThemeName,
includeExplanation: false
});
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
throw new Error(`Shiki failed to highlight code block with language "${codeBlock.language}" and theme "${theme.name}".
Received error message: "${error.message}"`, {
cause: error
});
}
tokenLines.forEach((line, lineIndex) => {
if (codeBlock.language === "ansi" && styleVariantIndex === 0)
removeAnsiSequencesFromCodeLine(codeLines[lineIndex], line);
let charIndex = 0;
line.forEach((token) => {
const tokenLength = token.content.length;
const tokenEndIndex = charIndex + tokenLength;
const fontStyle = token.fontStyle || 0 /* None */;
codeLines[lineIndex]?.addAnnotation(
new InlineStyleAnnotation({
styleVariantIndex,
color: token.color || theme.fg,
italic: (fontStyle & 1 /* Italic */) === 1 /* Italic */,
bold: (fontStyle & 2 /* Bold */) === 2 /* Bold */,
underline: (fontStyle & 4 /* Underline */) === 4 /* Underline */,
inlineRange: {
columnStart: charIndex,
columnEnd: tokenEndIndex
},
renderPhase: "earliest"
})
);
charIndex = tokenEndIndex;
});
});
}
}
}
};
}
function isTerminalLanguage(language) {
return ["shellscript", "shell", "bash", "sh", "zsh", "nu", "nushell"].includes(language);
}
function removeAnsiSequencesFromCodeLine(codeLine, lineTokens) {
const newLine = lineTokens.map((token) => token.content).join("");
const rangesToRemove = getRemovedRanges(codeLine.text, newLine);
for (let index = rangesToRemove.length - 1; index >= 0; index--) {
const [start, end] = rangesToRemove[index];
codeLine.editText(start, end, "");
}
}
function getRemovedRanges(original, edited) {
const ranges = [];
let from = -1;
let orgIdx = 0;
let edtIdx = 0;
while (orgIdx < original.length && edtIdx < edited.length) {
if (original[orgIdx] !== edited[edtIdx]) {
if (from === -1)
from = orgIdx;
orgIdx++;
} else {
if (from > -1) {
ranges.push([from, orgIdx]);
from = -1;
}
orgIdx++;
edtIdx++;
}
}
if (edtIdx < edited.length)
throw new Error(`Edited string contains characters not present in original (${JSON.stringify({ original, edited })})`);
if (orgIdx < original.length)
ranges.push([orgIdx, original.length]);
return ranges;
}
export {
loadShikiTheme,
pluginShiki
};
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,39 @@
{
"name": "@expressive-code/plugin-shiki",
"version": "0.35.3",
"description": "Shiki syntax highlighting plugin for Expressive Code, a text marking & annotation engine for presenting source code on the web.",
"keywords": [],
"author": "Tibor Schiemann",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/expressive-code/expressive-code.git",
"directory": "packages/@expressive-code/plugin-shiki"
},
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"exports": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"dependencies": {
"@expressive-code/core": "^0.35.3",
"shiki": "^1.1.7"
},
"devDependencies": {
"@internal/test-utils": "^0.2.28"
},
"scripts": {
"build": "tsup ./src/index.ts --format esm --dts --sourcemap --clean",
"coverage": "vitest run --coverage",
"test": "vitest run --reporter verbose",
"test-short": "vitest run --reporter basic",
"test-watch": "vitest --reporter verbose",
"watch": "pnpm build --watch src"
}
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Tibor Schiemann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,15 @@
# @expressive-code/plugin-text-markers
A default plugin of Expressive Code, an engine for presenting source code on the web.
It allows you to mark entire lines & line ranges, as well as individual text inside your lines using a selection of marker styles (marked, inserted, deleted).
It also ensures accessible color contrast of the marked code, automatically tweaking the text colors if necessary while keeping syntax highlighting intact.
## Documentation
[Read this plugin's documentation](https://expressive-code.com/key-features/text-markers/) on the Expressive Code website to learn more about its features.
## Installation (not required)
No installation is required. This package is **installed by default** by our higher-level packages.

View File

@@ -0,0 +1,230 @@
import { ExpressiveCodePlugin } from '@expressive-code/core';
interface TextMarkersStyleSettings {
/**
* The margin between the code block border and the line marker accent bar
* displayed on the left side of a full-line text marker.
* @default '0rem'
*/
lineMarkerAccentMargin: string;
/**
* The width of the line marker accent bar. This is the vertical border-like bar
* displayed on the left side of a full-line text marker.
* @default '0.15rem'
*/
lineMarkerAccentWidth: string;
/**
* The inline padding (= left & right padding in horizontal writing mode)
* around line marker labels.
*
* @note If your line marker labels overlap with the code content,
* consider increasing the root style setting `codePaddingInline`.
*
* @default '0.2rem'
*/
lineMarkerLabelPaddingInline: string;
/**
* The text color of the optional labels that can be displayed on the left side
* of a full-line text marker.
* @default 'white'
*/
lineMarkerLabelColor: string;
/**
* The margin between the code block border and the diff indicator (e.g. `+` or `-`)
* displayed on the left side of a full-line text marker.
* @default '0.3rem'
*/
lineDiffIndicatorMarginLeft: string;
/**
* The width of the border around inline text markers, rendered in a way
* that does not cause marked code to shift.
* @default '1.5px'
*/
inlineMarkerBorderWidth: string;
/**
* The border radius of inline text markers.
* @default '0.2rem'
*/
inlineMarkerBorderRadius: string;
/**
* The inline padding of inline text markers. Keep this low to prevent marked code
* from shifting too much compared to the original text.
* @default '0.15rem'
*/
inlineMarkerPadding: string;
/**
* The LCH hue to be used for marked text (text marker type `mark`).
* @default '284' (a blue hue)
*/
markHue: string;
/**
* The LCH hue to be used for inserted text (text marker type `ins`).
* @default '136' (a green hue)
*/
insHue: string;
/**
* The LCH hue to be used for deleted text (text marker type `del`).
* @default '33' (a red hue)
*/
delHue: string;
/**
* The LCH chroma to be used for all text marker types.
*
* The chroma value defines the saturation of the color. Higher values lead to
* more saturated colors, lower values lead to less saturated colors.
*
* @default '40'
*/
defaultChroma: string;
/**
* The LCH luminance to be used for all text marker types.
* @default
* ['32%', '75%'] // 32% for dark themes, 75% for light themes
*/
defaultLuminance: string;
/**
* The opacity of the background color of all text marker types.
* @default '50%'
*/
backgroundOpacity: string;
/**
* The LCH luminance to be used for the border color of all text marker types.
* @default '48%'
*/
borderLuminance: string;
/**
* The opacity of the border color of all text marker types.
* @default '81.6%'
*/
borderOpacity: string;
/**
* The LCH luminance to be used for the diff indicator (e.g. `+` or `-`).
* @default
* ['67%', '40%'] // 67% for dark themes, 40% for light themes
*/
indicatorLuminance: string;
/**
* The opacity of the diff indicator (e.g. `+` or `-`).
* @default '81.6%'
*/
indicatorOpacity: string;
/**
* The content to be displayed inside the diff indicator of inserted lines.
*
* Note that this is used as the `content` value in a CSS pseudo-element,
* so you need to wrap any text in additional quotes.
*
* @default "'+'"
*/
insDiffIndicatorContent: string;
/**
* The content to be displayed inside the diff indicator of deleted lines.
*
* Note that this is used as the `content` value in a CSS pseudo-element,
* so you need to wrap any text in additional quotes.
*
* @default "'-'"
*/
delDiffIndicatorContent: string;
/**
* The background color of marked text (text marker type `mark`).
* @default
* lch(<defaultLuminance> <defaultChroma> <markHue> / <backgroundOpacity>)
*/
markBackground: string;
/**
* The border color of marked text (text marker type `mark`).
* @default
* lch(<borderLuminance> <defaultChroma> <markHue> / <borderOpacity>)
*/
markBorderColor: string;
/**
* The background color of inserted text (text marker type `ins`).
* @default
* lch(<defaultLuminance> <defaultChroma> <insHue> / <backgroundOpacity>)
*/
insBackground: string;
/**
* The border color of inserted text (text marker type `ins`).
* @default
* lch(<borderLuminance> <defaultChroma> <insHue> / <borderOpacity>)
*/
insBorderColor: string;
/**
* The color of the diff indicator (e.g. `+` or `-`) of inserted lines.
* @default
* lch(<indicatorLuminance> <defaultChroma> <insHue> / <indicatorOpacity>)
*/
insDiffIndicatorColor: string;
/**
* The background color of deleted text (text marker type `del`).
* @default
* lch(<defaultLuminance> <defaultChroma> <delHue> / <backgroundOpacity>)
*/
delBackground: string;
/**
* The border color of deleted text (text marker type `del`).
* @default
* lch(<borderLuminance> <defaultChroma> <delHue> / <borderOpacity>)
*/
delBorderColor: string;
/**
* The color of the diff indicator (e.g. `+` or `-`) of deleted lines.
* @default
* lch(<indicatorLuminance> <defaultChroma> <delHue> / <indicatorOpacity>)
*/
delDiffIndicatorColor: string;
}
declare module '@expressive-code/core' {
interface StyleSettings {
textMarkers: TextMarkersStyleSettings;
}
}
type MarkerLineOrRange = number | {
range: string;
label?: string | undefined;
};
/**
* A single text marker definition that can be used in the `mark`, `ins`, and `del` props
* to define text and line markers.
*/
type MarkerDefinition = string | RegExp | MarkerLineOrRange;
interface PluginTextMarkersProps {
/**
* Defines the code block's [text & line markers](https://expressive-code.com/key-features/text-markers/)
* of the default neutral type.
*
* You can either pass a single marker definition or an array of them.
*/
mark: MarkerDefinition | MarkerDefinition[];
/**
* Defines the code block's [text & line markers](https://expressive-code.com/key-features/text-markers/)
* of the "inserted" type.
*
* You can either pass a single marker definition or an array of them.
*/
ins: MarkerDefinition | MarkerDefinition[];
/**
* Defines the code block's [text & line markers](https://expressive-code.com/key-features/text-markers/)
* of the "deleted" type.
*
* You can either pass a single marker definition or an array of them.
*/
del: MarkerDefinition | MarkerDefinition[];
/**
* Allows you to enable processing of diff syntax for non-diff languages.
*
* If set to `true`, you can prefix lines with `+` or `-`, no matter what the language of
* the code block is. The prefixes will be removed and the lines will be highlighted as
* inserted or deleted lines.
*/
useDiffSyntax: boolean;
}
declare module '@expressive-code/core' {
interface ExpressiveCodeBlockProps extends PluginTextMarkersProps {
}
}
declare function pluginTextMarkers(): ExpressiveCodePlugin;
export { MarkerDefinition, MarkerLineOrRange, PluginTextMarkersProps, TextMarkersStyleSettings, pluginTextMarkers };

View File

@@ -0,0 +1,590 @@
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// ../../../node_modules/.pnpm/parse-numeric-range@1.3.0/node_modules/parse-numeric-range/index.js
var require_parse_numeric_range = __commonJS({
"../../../node_modules/.pnpm/parse-numeric-range@1.3.0/node_modules/parse-numeric-range/index.js"(exports, module) {
"use strict";
function parsePart(string) {
let res = [];
let m;
for (let str of string.split(",").map((str2) => str2.trim())) {
if (/^-?\d+$/.test(str)) {
res.push(parseInt(str, 10));
} else if (m = str.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/)) {
let [_, lhs, sep, rhs] = m;
if (lhs && rhs) {
lhs = parseInt(lhs);
rhs = parseInt(rhs);
const incr = lhs < rhs ? 1 : -1;
if (sep === "-" || sep === ".." || sep === "\u2025")
rhs += incr;
for (let i = lhs; i !== rhs; i += incr)
res.push(i);
}
}
}
return res;
}
exports.default = parsePart;
module.exports = parsePart;
}
});
// src/index.ts
var import_parse_numeric_range = __toESM(require_parse_numeric_range(), 1);
import {
AnnotationRenderPhaseOrder,
InlineStyleAnnotation,
ensureColorContrastOnBackground,
getStaticBackgroundColor,
isInlineStyleAnnotation,
onBackground
} from "@expressive-code/core";
// src/marker-types.ts
var MarkerTypeOrder = ["mark", "del", "ins"];
function markerTypeFromString(input) {
if (input === "add")
input = "ins";
if (input === "rem")
input = "del";
const markerType = input;
return MarkerTypeOrder.includes(markerType) ? markerType : void 0;
}
// src/styles.ts
import { PluginStyleSettings, codeLineClass, toHexColor } from "@expressive-code/core";
var textMarkersStyleSettings = new PluginStyleSettings({
defaultValues: {
textMarkers: {
lineMarkerAccentMargin: "0rem",
lineMarkerAccentWidth: "0.15rem",
lineMarkerLabelPaddingInline: "0.2rem",
lineMarkerLabelColor: "white",
lineDiffIndicatorMarginLeft: "0.3rem",
inlineMarkerBorderWidth: "1.5px",
inlineMarkerBorderRadius: "0.2rem",
inlineMarkerPadding: "0.15rem",
// Define base colors for all markers in the LCH color space,
// which leads to consistent perceived brightness independent of hue
markHue: "284",
insHue: "136",
delHue: "33",
defaultChroma: "40",
defaultLuminance: ["32%", "75%"],
backgroundOpacity: "50%",
borderLuminance: "48%",
borderOpacity: "81.6%",
indicatorLuminance: ["67%", "40%"],
indicatorOpacity: "81.6%",
// You can use these to override the diff indicator content
insDiffIndicatorContent: "'+'",
delDiffIndicatorContent: "'-'",
// The settings below will be calculated based on the settings above
markBackground: (context) => resolveBg(context, "textMarkers.markHue"),
markBorderColor: (context) => resolveBorder(context, "textMarkers.markHue"),
insBackground: (context) => resolveBg(context, "textMarkers.insHue"),
insBorderColor: (context) => resolveBorder(context, "textMarkers.insHue"),
insDiffIndicatorColor: (context) => resolveIndicator(context, "textMarkers.insHue"),
delBackground: (context) => resolveBg(context, "textMarkers.delHue"),
delBorderColor: (context) => resolveBorder(context, "textMarkers.delHue"),
delDiffIndicatorColor: (context) => resolveIndicator(context, "textMarkers.delHue")
}
},
cssVarExclusions: [
// Exclude all settings from CSS variable output that will not be used directly in styles,
// but instead be used to calculate other settings
"textMarkers.markHue",
"textMarkers.insHue",
"textMarkers.delHue",
"textMarkers.defaultChroma",
"textMarkers.defaultLuminance",
"textMarkers.backgroundOpacity",
"textMarkers.borderLuminance",
"textMarkers.borderOpacity",
"textMarkers.indicatorLuminance",
"textMarkers.indicatorOpacity"
]
});
function getTextMarkersBaseStyles({ cssVar }) {
const result = `
.${codeLineClass} {
/* Support line-level mark/ins/del */
&.mark {
--tmLineBgCol: ${cssVar("textMarkers.markBackground")};
& .code {
--ecLineBrdCol: ${cssVar("textMarkers.markBorderColor")};
}
}
&.ins {
--tmLineBgCol: ${cssVar("textMarkers.insBackground")};
--tmLabel: ${cssVar("textMarkers.insDiffIndicatorContent")};
& .code {
--ecLineBrdCol: ${cssVar("textMarkers.insBorderColor")};
&::before {
color: ${cssVar("textMarkers.insDiffIndicatorColor")};
}
}
}
&.del {
--tmLineBgCol: ${cssVar("textMarkers.delBackground")};
--tmLabel: ${cssVar("textMarkers.delDiffIndicatorContent")};
& .code {
--ecLineBrdCol: ${cssVar("textMarkers.delBorderColor")};
&::before {
color: ${cssVar("textMarkers.delDiffIndicatorColor")};
}
}
}
&.mark,
&.ins,
&.del {
background: var(--tmLineBgCol);
& .code {
--ecGtrBrdWd: ${cssVar("textMarkers.lineMarkerAccentWidth")};
}
& .code::before {
display: block;
position: absolute;
left: 0;
box-sizing: border-box;
content: var(--tmLabel, ' ');
padding-inline-start: ${cssVar("textMarkers.lineDiffIndicatorMarginLeft")};
text-align: center;
/* Prevent long labels from wrapping to avoid overlapping the code */
white-space: pre;
}
&.tm-label {
& .code::before {
background: var(--ecLineBrdCol);
padding: 0 calc(${cssVar("textMarkers.lineMarkerLabelPaddingInline")} + ${cssVar("textMarkers.lineMarkerAccentWidth")}) 0 ${cssVar("textMarkers.lineMarkerLabelPaddingInline")};
color: ${cssVar("textMarkers.lineMarkerLabelColor")};
}
}
}
/* Support inline mark/ins/del */
& mark {
--tmInlineBgCol: ${cssVar("textMarkers.markBackground")};
--tmInlineBrdCol: ${cssVar("textMarkers.markBorderColor")};
}
& ins {
--tmInlineBgCol: ${cssVar("textMarkers.insBackground")};
--tmInlineBrdCol: ${cssVar("textMarkers.insBorderColor")};
}
& del {
--tmInlineBgCol: ${cssVar("textMarkers.delBackground")};
--tmInlineBrdCol: ${cssVar("textMarkers.delBorderColor")};
}
& mark,
& ins,
& del {
all: unset;
display: inline-block;
position: relative;
--tmBrdL: ${cssVar("textMarkers.inlineMarkerBorderWidth")};
--tmBrdR: ${cssVar("textMarkers.inlineMarkerBorderWidth")};
--tmRadL: ${cssVar("textMarkers.inlineMarkerBorderRadius")};
--tmRadR: ${cssVar("textMarkers.inlineMarkerBorderRadius")};
margin-inline: 0.025rem;
padding-inline: ${cssVar("textMarkers.inlineMarkerPadding")};
border-radius: var(--tmRadL) var(--tmRadR) var(--tmRadR) var(--tmRadL);
background: var(--tmInlineBgCol);
background-clip: padding-box;
&.open-start {
margin-inline-start: 0;
padding-inline-start: 0;
--tmBrdL: 0px;
--tmRadL: 0;
}
&.open-end {
margin-inline-end: 0;
padding-inline-end: 0;
--tmBrdR: 0px;
--tmRadR: 0;
}
&::before {
content: '';
position: absolute;
pointer-events: none;
display: inline-block;
inset: 0;
border-radius: var(--tmRadL) var(--tmRadR) var(--tmRadR) var(--tmRadL);
border: ${cssVar("textMarkers.inlineMarkerBorderWidth")} solid var(--tmInlineBrdCol);
border-inline-width: var(--tmBrdL) var(--tmBrdR);
}
}
}
`;
return result;
}
var markerBgColorPaths = {
mark: "textMarkers.markBackground",
ins: "textMarkers.insBackground",
del: "textMarkers.delBackground"
};
function resolveBg({ resolveSetting: r }, hue) {
return toHexColor(`lch(${r("textMarkers.defaultLuminance")} ${r("textMarkers.defaultChroma")} ${r(hue)} / ${r("textMarkers.backgroundOpacity")})`);
}
function resolveBorder({ resolveSetting: r }, hue) {
return toHexColor(`lch(${r("textMarkers.borderLuminance")} ${r("textMarkers.defaultChroma")} ${r(hue)} / ${r("textMarkers.borderOpacity")})`);
}
function resolveIndicator({ resolveSetting: r }, hue) {
return toHexColor(`lch(${r("textMarkers.indicatorLuminance")} ${r("textMarkers.defaultChroma")} ${r(hue)} / ${r("textMarkers.indicatorOpacity")})`);
}
// src/utils.ts
function getGroupIndicesFromRegExpMatch(match) {
let groupIndices = match.indices;
if (groupIndices?.length)
return groupIndices;
const fullMatchIndex = match.index;
groupIndices = match.map((groupValue) => {
const groupIndex = groupValue ? match[0].indexOf(groupValue) : -1;
if (groupIndex === -1)
return null;
const groupStart = fullMatchIndex + groupIndex;
const groupEnd = groupStart + groupValue.length;
return [groupStart, groupEnd];
});
return groupIndices;
}
function toDefinitionsArray(value) {
if (value === void 0)
return [];
return Array.isArray(value) ? value : [value];
}
// src/inline-markers.ts
function getInlineSearchTermMatches(lineText, codeBlock) {
const markerMatches = [];
MarkerTypeOrder.forEach((markerType) => {
toDefinitionsArray(codeBlock.props[markerType]).forEach((definition) => {
if (typeof definition === "string") {
let idx = lineText.indexOf(definition, 0);
while (idx > -1) {
markerMatches.push({
markerType,
start: idx,
end: idx + definition.length
});
idx = lineText.indexOf(definition, idx + definition.length);
}
}
if (definition instanceof RegExp) {
const matches = lineText.matchAll(definition);
for (const match of matches) {
const rawGroupIndices = getGroupIndicesFromRegExpMatch(match);
let groupIndices = rawGroupIndices.flatMap((range) => range ? [range] : []);
if (!groupIndices.length) {
const fullMatchIndex = match.index;
groupIndices = [[fullMatchIndex, fullMatchIndex + match[0].length]];
}
if (groupIndices.length > 1) {
groupIndices.shift();
}
groupIndices.forEach((range) => {
markerMatches.push({
markerType,
start: range[0],
end: range[1]
});
});
}
}
});
});
return markerMatches;
}
function flattenInlineMarkerRanges(markerRanges) {
const flattenedRanges = [];
const addRange = (newRange) => {
for (let idx = flattenedRanges.length - 1; idx >= 0; idx--) {
const curRange = flattenedRanges[idx];
if (newRange.end <= curRange.start || newRange.start >= curRange.end)
continue;
if (newRange.start <= curRange.start && newRange.end >= curRange.end) {
flattenedRanges.splice(idx, 1);
continue;
}
if (newRange.markerType === curRange.markerType) {
flattenedRanges.splice(idx, 1);
newRange = {
...newRange,
start: Math.min(newRange.start, curRange.start),
end: Math.max(newRange.end, curRange.end)
};
continue;
}
if (newRange.start > curRange.start && newRange.end < curRange.end) {
flattenedRanges.splice(idx, 1, { ...curRange, end: newRange.start }, { ...curRange, start: newRange.end });
continue;
}
if (newRange.start > curRange.start) {
curRange.end = newRange.start;
}
if (newRange.end < curRange.end) {
curRange.start = newRange.end;
}
}
flattenedRanges.push(newRange);
flattenedRanges.sort((a, b) => a.start - b.start);
};
MarkerTypeOrder.forEach((markerType) => {
markerRanges.filter((range) => range.markerType === markerType).forEach(addRange);
});
return flattenedRanges;
}
// src/annotations.ts
import { ExpressiveCodeAnnotation } from "@expressive-code/core";
import { addClassName, h, setInlineStyle } from "@expressive-code/core/hast";
var TextMarkerAnnotation = class extends ExpressiveCodeAnnotation {
markerType;
backgroundColor;
label;
constructor({ markerType, backgroundColor, label, ...baseOptions }) {
super(baseOptions);
this.markerType = markerType;
this.backgroundColor = backgroundColor;
this.label = label;
}
render(options) {
if (!this.inlineRange)
return this.renderFullLineMarker(options);
return this.renderInlineMarker(options);
}
renderFullLineMarker({ nodesToTransform }) {
return nodesToTransform.map((node) => {
if (node.type === "element") {
addClassName(node, "highlight");
addClassName(node, this.markerType);
if (this.label) {
addClassName(node, "tm-label");
setInlineStyle(node, "--tmLabel", this.label, "string");
}
}
return node;
});
}
renderInlineMarker({ nodesToTransform }) {
return nodesToTransform.map((node, idx) => {
const transformedNode = h(this.markerType, node);
if (nodesToTransform.length > 0 && idx > 0) {
addClassName(transformedNode, "open-start");
}
if (nodesToTransform.length > 0 && idx < nodesToTransform.length - 1) {
addClassName(transformedNode, "open-end");
}
return transformedNode;
});
}
};
// src/index.ts
function pluginTextMarkers() {
return {
name: "TextMarkers",
styleSettings: textMarkersStyleSettings,
baseStyles: (context) => getTextMarkersBaseStyles(context),
hooks: {
preprocessLanguage: ({ codeBlock }) => {
const lang = codeBlock.metaOptions.getString("lang");
if (lang && codeBlock.language === "diff") {
codeBlock.language = lang;
codeBlock.props.useDiffSyntax = true;
}
},
preprocessMetadata: ({ codeBlock, cssVar }) => {
const addDefinition = (target, definition) => {
const definitions = toDefinitionsArray(codeBlock.props[target]);
definitions.push(definition);
codeBlock.props[target] = definitions;
};
codeBlock.metaOptions.list([...MarkerTypeOrder, "", "add", "rem"]).forEach((option) => {
const { kind, key, value } = option;
const markerType = markerTypeFromString(key || "mark");
if (!markerType)
return;
if (kind === "string" || kind === "regexp")
addDefinition(markerType, value);
if (kind === "range") {
let label = void 0;
const range = value.replace(/^\s*?(["'])([^\1]+?)\1:\s*?/, (_match, _quote, labelValue) => {
label = labelValue;
return "";
});
addDefinition(markerType, { range, label });
}
});
codeBlock.props.useDiffSyntax = codeBlock.metaOptions.getBoolean("useDiffSyntax") ?? codeBlock.props.useDiffSyntax;
MarkerTypeOrder.forEach((markerType) => {
toDefinitionsArray(codeBlock.props[markerType]).forEach((definition) => {
if (typeof definition === "string" || definition instanceof RegExp)
return;
const objDefinition = typeof definition === "number" ? { range: `${definition}` } : definition;
const { range = "", label } = objDefinition;
const lineNumbers = (0, import_parse_numeric_range.default)(range);
lineNumbers.forEach((lineNumber, idx) => {
const lineIndex = lineNumber - 1;
codeBlock.getLine(lineIndex)?.addAnnotation(
new TextMarkerAnnotation({
markerType,
backgroundColor: cssVar(markerBgColorPaths[markerType]),
// Add a label to the first line of each consecutive range
label: idx === 0 || lineNumber - lineNumbers[idx - 1] !== 1 ? label : void 0
})
);
});
});
});
},
preprocessCode: ({ codeBlock, cssVar }) => {
if (codeBlock.language === "diff" || codeBlock.props.useDiffSyntax) {
const lines = codeBlock.getLines();
const couldBeRealDiffFile = lines.slice(0, 4).some((line) => line.text.match(/^([*+-]{3}\s|@@\s|[0-9,]+[acd][0-9,]+\s*$)/));
if (!couldBeRealDiffFile) {
let minIndentation = Infinity;
const parsedLines = lines.map((line) => {
const [, indentation, marker, content] = line.text.match(/^(([+-](?![+-]))?\s*)(.*)$/) || [];
const markerType = marker === "+" ? "ins" : marker === "-" ? "del" : void 0;
if (content.trim().length > 0 && indentation.length < minIndentation)
minIndentation = indentation.length;
return {
line,
markerType
};
});
parsedLines.forEach(({ line, markerType }) => {
const colsToRemove = minIndentation || (markerType ? 1 : 0);
if (colsToRemove > 0)
line.editText(0, colsToRemove, "");
if (markerType) {
line.addAnnotation(
new TextMarkerAnnotation({
markerType,
backgroundColor: cssVar(markerBgColorPaths[markerType])
})
);
}
});
}
}
},
annotateCode: ({ codeBlock, cssVar }) => {
codeBlock.getLines().forEach((line) => {
const markerRanges = getInlineSearchTermMatches(line.text, codeBlock);
if (!markerRanges.length)
return;
const flattenedRanges = flattenInlineMarkerRanges(markerRanges);
flattenedRanges.forEach(({ markerType, start, end }) => {
line.addAnnotation(
new TextMarkerAnnotation({
markerType,
backgroundColor: cssVar(markerBgColorPaths[markerType]),
inlineRange: {
columnStart: start,
columnEnd: end
}
})
);
});
});
},
postprocessAnnotations: ({ codeBlock, styleVariants, config }) => {
if (config.minSyntaxHighlightingColorContrast <= 0)
return;
codeBlock.getLines().forEach((line) => {
const annotations = line.getAnnotations();
const markers = [];
let fullLineMarker = void 0;
for (const annotation of annotations) {
if (!(annotation instanceof TextMarkerAnnotation))
continue;
if (annotation.inlineRange) {
markers.push(annotation);
continue;
}
if (fullLineMarker) {
if (MarkerTypeOrder.indexOf(annotation.markerType) < MarkerTypeOrder.indexOf(fullLineMarker.markerType))
continue;
if (AnnotationRenderPhaseOrder.indexOf(annotation.renderPhase || "normal") < AnnotationRenderPhaseOrder.indexOf(fullLineMarker.renderPhase || "normal"))
continue;
}
fullLineMarker = annotation;
}
if (fullLineMarker)
markers.unshift(fullLineMarker);
styleVariants.forEach((styleVariant, styleVariantIndex) => {
const fullLineMarkerBgColor = fullLineMarker && styleVariant.resolvedStyleSettings.get(markerBgColorPaths[fullLineMarker.markerType]) || "transparent";
const lineBgColor = onBackground(fullLineMarkerBgColor, getStaticBackgroundColor(styleVariant));
const textColors = annotations.filter(
(annotation) => isInlineStyleAnnotation(annotation) && annotation.color && // Only consider annotations that apply to the current style variant
(annotation.styleVariantIndex === void 0 || annotation.styleVariantIndex === styleVariantIndex)
);
textColors.forEach((textColor) => {
const textFgColor = textColor.color;
const textStart = textColor.inlineRange?.columnStart;
const textEnd = textColor.inlineRange?.columnEnd;
if (textFgColor === void 0 || textStart === void 0 || textEnd === void 0)
return;
markers.forEach((marker) => {
const markerStart = marker.inlineRange?.columnStart ?? 0;
const markerEnd = marker.inlineRange?.columnEnd ?? line.text.length;
if (markerStart > textEnd || markerEnd < textStart)
return;
const inlineMarkerBgColor = marker.inlineRange && styleVariant.resolvedStyleSettings.get(markerBgColorPaths[marker.markerType]) || "transparent";
const combinedBgColor = onBackground(inlineMarkerBgColor, lineBgColor);
const readableTextColor = ensureColorContrastOnBackground(textFgColor, combinedBgColor, config.minSyntaxHighlightingColorContrast);
if (readableTextColor.toLowerCase() === textFgColor.toLowerCase())
return;
line.addAnnotation(
new InlineStyleAnnotation({
styleVariantIndex,
inlineRange: {
columnStart: Math.max(textStart, markerStart),
columnEnd: Math.min(textEnd, markerEnd)
},
color: readableTextColor,
renderPhase: "earlier"
})
);
});
});
});
});
}
}
};
}
export {
pluginTextMarkers
};
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,40 @@
{
"name": "@expressive-code/plugin-text-markers",
"version": "0.35.3",
"description": "Text marker plugin for Expressive Code, a text marking & annotation engine for presenting source code on the web.",
"keywords": [],
"author": "Tibor Schiemann",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/expressive-code/expressive-code.git",
"directory": "packages/@expressive-code/plugin-text-markers"
},
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"exports": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"dependencies": {
"@expressive-code/core": "^0.35.3"
},
"devDependencies": {
"@expressive-code/plugin-shiki": "^0.35.3",
"parse-numeric-range": "^1.3.0",
"@internal/test-utils": "^0.2.28"
},
"scripts": {
"build": "tsup ./src/index.ts --format esm --dts --sourcemap --clean",
"coverage": "vitest run --coverage",
"test": "vitest run --reporter verbose",
"test-short": "vitest run --reporter basic",
"test-watch": "vitest --reporter verbose",
"watch": "pnpm build --watch src"
}
}