diff --git a/.changeset/two-steaks-unite.md b/.changeset/two-steaks-unite.md new file mode 100644 index 0000000..97abb44 --- /dev/null +++ b/.changeset/two-steaks-unite.md @@ -0,0 +1,9 @@ +--- +"@matthiesenxyz/astro-gists": minor +--- + +Full Shiki theme support with options to have light/dark mode! Now creating our own expressive code engine from the core module instead of the expressive-code package + +## Breaking Changes: + +- User Config `theme` changed to `themes` will accept an array `['github-light','github-dark']` or a string `'github-dark'` \ No newline at end of file diff --git a/package/package.json b/package/package.json index c8a1be0..882a41e 100644 --- a/package/package.json +++ b/package/package.json @@ -47,10 +47,13 @@ "@octokit/types": "^12.6.0" }, "dependencies": { + "@expressive-code/core": "^0.33.5", + "@expressive-code/plugin-frames": "0.33.5", + "@expressive-code/plugin-shiki": "0.33.5", + "@expressive-code/plugin-text-markers": "0.33.5", "@expressive-code/plugin-line-numbers": "^0.33.5", "astro-integration-kit": "~0.7.1", "chalk": "5.3.0", - "expressive-code": "^0.33.5", "hast-util-to-html": "8.0.4", "p-retry": "6.2.0", "octokit": "^3.1.2", diff --git a/package/src/ExpressiveCode/engine.ts b/package/src/ExpressiveCode/engine.ts new file mode 100644 index 0000000..afa821f --- /dev/null +++ b/package/src/ExpressiveCode/engine.ts @@ -0,0 +1,90 @@ +import { ExpressiveCodeEngine, type ExpressiveCodeTheme, type ExpressiveCodeEngineConfig, type ExpressiveCodePlugin } from '@expressive-code/core' +import { type PluginFramesOptions, pluginFrames } from '@expressive-code/plugin-frames' +import type { PluginShikiOptions } from '@expressive-code/plugin-shiki' +import { loadShikiTheme, pluginShiki, type BundledShikiTheme } from '@expressive-code/plugin-shiki' +import { pluginTextMarkers } from '@expressive-code/plugin-text-markers' +import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers' + +export * from '@expressive-code/core' +export * from '@expressive-code/plugin-frames' +export * from '@expressive-code/plugin-shiki' +export * from '@expressive-code/plugin-text-markers' + +export interface ExpressiveCodeConfig extends ExpressiveCodeEngineConfig { + /** + * The Shiki plugin adds syntax highlighting to code blocks. + * + * This plugin is enabled by default. Set this to `false` to disable it. + * You can also configure the plugin by setting this to an options object. + */ + shiki?: PluginShikiOptions | boolean | undefined + /** + * The Text Markers plugin allows to highlight lines and inline ranges + * in code blocks in various styles (e.g. marked, inserted, deleted). + * + * This plugin is enabled by default. Set this to `false` to disable it. + */ + textMarkers?: boolean | undefined + /** + * The Frames plugin adds an editor or terminal frame around code blocks, + * including an optional title displayed as a tab or window caption. + * + * This plugin is enabled by default. Set this to `false` to disable it. + * You can also configure the plugin by setting this to an options object. + */ + frames?: PluginFramesOptions | boolean | undefined + themes?: ExpressiveCodeTheme[] | undefined; +} + + +export async function getTheme(themeName: BundledShikiTheme): Promise { + return await loadShikiTheme(themeName) +} + +const darkTheme = await getTheme('catppuccin-macchiato') +const lightTheme = await getTheme('catppuccin-latte') + +export class ExpressiveCode extends ExpressiveCodeEngine { + constructor({ shiki, textMarkers, frames, themes, ...baseConfig }: ExpressiveCodeConfig = {}) { + // Collect all default plugins with their configuration, + // but skip those that were disabled or already added to plugins + const pluginsToPrepend: ExpressiveCodePlugin[] = [] + const baseConfigPlugins = baseConfig.plugins?.flat() || [] + const notPresentInPlugins = (name: string) => baseConfigPlugins.every((plugin) => plugin.name !== name) + if (shiki !== false && notPresentInPlugins('Shiki')) { + pluginsToPrepend.push(pluginShiki(shiki !== true ? shiki : undefined)) + } + if (textMarkers !== false && notPresentInPlugins('TextMarkers')) { + if (typeof textMarkers === 'object' && (textMarkers as { styleOverrides: unknown }).styleOverrides) { + throw new Error( + `The Expressive Code config option "textMarkers" can no longer be an object, + but only undefined or a boolean. Please move any style settings into the + top-level "styleOverrides" object below the new "textMarkers" key.`.replace(/\s+/g, ' ') + ) + } + pluginsToPrepend.push(pluginTextMarkers()) + } + if (frames !== false && notPresentInPlugins('Frames')) { + if (typeof frames === 'object' && (frames as { styleOverrides: unknown }).styleOverrides) { + throw new Error( + `The config option "frames" no longer has its own "styleOverrides" object. + Please move any style settings into the top-level "styleOverrides" object + below the new "frames" key.`.replace(/\s+/g, ' ') + ) + } + pluginsToPrepend.push(pluginFrames(frames !== true ? frames : undefined)) + } + + pluginsToPrepend.push(pluginLineNumbers()) + + // Create a new plugins array with the default plugins prepended + const pluginsWithDefaults = [...pluginsToPrepend, ...(baseConfig.plugins || [])] + + if (!themes) { + themes = [darkTheme, lightTheme] + } + + // Call the base constructor with the new plugins array + super({ ...baseConfig, themes, plugins: pluginsWithDefaults }) + } +} \ No newline at end of file diff --git a/package/src/ExpressiveCode/index.ts b/package/src/ExpressiveCode/index.ts index 3db5bd9..8dc519a 100644 --- a/package/src/ExpressiveCode/index.ts +++ b/package/src/ExpressiveCode/index.ts @@ -1,17 +1,31 @@ -import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers' -import { ExpressiveCode, loadShikiTheme, type BundledShikiTheme } from 'expressive-code' -import config from "virtual:astro-gists/config"; + +import { ExpressiveCode, type BundledShikiTheme, type ExpressiveCodeTheme, loadShikiTheme } from './engine' +import Config from 'virtual:astro-gists/config' // Export defined components export { default as Code } from './components/Code.astro' -// Create a custom instance of ExpressiveCode -export const engine = new ExpressiveCode({ - themes: [ - config.theme ? - await loadShikiTheme(config.theme as BundledShikiTheme) : - await loadShikiTheme('catppuccin-macchiato'), await loadShikiTheme('catppuccin-latte') - ], - plugins: [pluginLineNumbers()], -}) + +export async function getTheme(themeName: BundledShikiTheme): Promise { + return await loadShikiTheme(themeName) +} + +// Get the themes from the configuration +const { themes: userThemes } = Config + +const themes: ExpressiveCodeTheme[] = [] + +// Load the themes from the configuration +if (userThemes) { + if (Array.isArray(userThemes)) { + for (const themeName of userThemes) { + themes.push(await getTheme(themeName)) + } + } else { + themes.push(await getTheme(userThemes)) + } +} + +// Create a custom instance of ExpressiveCode +export const engine = new ExpressiveCode({ themes: userThemes?.length ? themes : undefined }) diff --git a/package/src/UserConfigSchema.ts b/package/src/UserConfigSchema.ts index 113099e..68fa602 100644 --- a/package/src/UserConfigSchema.ts +++ b/package/src/UserConfigSchema.ts @@ -1,15 +1,17 @@ // Export the user config schema import { z } from "astro/zod"; -import type { BundledShikiTheme } from "expressive-code"; +import type { BundledShikiTheme } from "./ExpressiveCode/engine"; export const optionsSchema = z.object({ /** * Optional: Allows the user to change the default theme for the code blocks. - * @example ['github-dark'] + * @example ['github-light','github-dark'] + * @example 'github-light' + * + * @see All available themes are listed in the [Shiki documentation](https://shiki.matsu.io/docs/themes) * - * All available themes are listed in the [Shiki documentation](https://shiki.matsu.io/docs/themes). */ - theme: z.custom().optional(), + themes: z.custom().optional(), /** * Optional: Allows the user to enable verbose logging. */ diff --git a/package/virtual-config.d.ts b/package/virtual-config.d.ts index 162feb6..dc4a48e 100644 --- a/package/virtual-config.d.ts +++ b/package/virtual-config.d.ts @@ -1,4 +1,4 @@ declare module "virtual:astro-gists/config" { - const Config: import("./src/index").astroGistsUserConfig; + const Config: import("./src/UserConfigSchema").astroGistsUserConfig; export default Config; } \ No newline at end of file diff --git a/playground/package.json b/playground/package.json index 4888e99..78eda6d 100644 --- a/playground/package.json +++ b/playground/package.json @@ -11,8 +11,8 @@ "astro": "astro" }, "dependencies": { - "@matthiesenxyz/astro-gists": "*", - "astro": "^4.5.9" + "astro": "^4.5.9", + "@matthiesenxyz/astro-gists": "workspace:*" }, "devDependencies": { "@astrojs/mdx": "^2.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d18da2d..d7daa69 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,21 @@ importers: package: dependencies: + '@expressive-code/core': + specifier: ^0.33.5 + version: 0.33.5 + '@expressive-code/plugin-frames': + specifier: 0.33.5 + version: 0.33.5 '@expressive-code/plugin-line-numbers': specifier: ^0.33.5 version: 0.33.5 + '@expressive-code/plugin-shiki': + specifier: 0.33.5 + version: 0.33.5 + '@expressive-code/plugin-text-markers': + specifier: 0.33.5 + version: 0.33.5 astro: specifier: ^4.4.1 version: 4.4.11 @@ -29,9 +41,6 @@ importers: chalk: specifier: 5.3.0 version: 5.3.0 - expressive-code: - specifier: ^0.33.5 - version: 0.33.5 hast-util-to-html: specifier: 8.0.4 version: 8.0.4 @@ -52,7 +61,7 @@ importers: playground: dependencies: '@matthiesenxyz/astro-gists': - specifier: '*' + specifier: workspace:* version: link:../package astro: specifier: ^4.5.9 @@ -3048,15 +3057,6 @@ packages: requiresBuild: true optional: true - /expressive-code@0.33.5: - resolution: {integrity: sha512-UPg2jSvZEfXPiCa4MKtMoMQ5Hwiv7In5/LSCa/ukhjzZqPO48iVsCcEBgXWEUmEAQ02P0z00/xFfBmVnUKH+Zw==} - dependencies: - '@expressive-code/core': 0.33.5 - '@expressive-code/plugin-frames': 0.33.5 - '@expressive-code/plugin-shiki': 0.33.5 - '@expressive-code/plugin-text-markers': 0.33.5 - dev: false - /extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'}