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/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"
}
}