Update Astro-Gist integration and related files
This commit is contained in:
parent
25c9c52454
commit
d6b8aac86c
|
@ -55,7 +55,8 @@ import { defineConfig } from "astro/config";
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
+ integrations: [astroGist({
|
+ integrations: [astroGist({
|
||||||
theme: 'github-dark' // OPTIONAL, if not set defaults to Astro's Houston, Only Available options are Shiki Bundled options
|
// This is the default options shown... dark is Astro's Houston-dark, and light is currently nightowl-light until a Houston-light is released.
|
||||||
|
theme: ['astroGists-dark', 'astroGists-light']
|
||||||
+ })]
|
+ })]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,35 +1,31 @@
|
||||||
---
|
---
|
||||||
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers'
|
|
||||||
import { ExpressiveCode, loadShikiTheme, type BundledShikiTheme, ExpressiveCodeTheme } from 'expressive-code'
|
|
||||||
import { toHtml } from 'hast-util-to-html'
|
import { toHtml } from 'hast-util-to-html'
|
||||||
import fs from 'node:fs'
|
import { getPageData } from './page-data';
|
||||||
import config from "virtual:astro-gists/config";
|
import { engine } from '../index'
|
||||||
import { getPageData } from './page-data'
|
|
||||||
|
|
||||||
const jsoncString = fs.readFileSync(new URL(`../themes/houston.jsonc`, import.meta.url), 'utf-8')
|
// Extract the code and other props from the Astro component
|
||||||
const houston = ExpressiveCodeTheme.fromJSONString(jsoncString)
|
const {
|
||||||
|
code,
|
||||||
const theme = config.theme
|
raw_url,
|
||||||
|
locale,
|
||||||
const { code, raw_url, locale,
|
lang = '',
|
||||||
lang = '', meta = '', ...props
|
meta = '',
|
||||||
|
...props
|
||||||
} = Astro.props
|
} = Astro.props
|
||||||
|
|
||||||
const pageData = getPageData(Astro.request)
|
// Get the current block group index from the page data
|
||||||
// Note: It's important to store the incremented index in a local variable immediately,
|
const pageData = getPageData(Astro.request)
|
||||||
// as the `pageData` object is shared between all components on the current page
|
// Note: It's important to store the incremented index in a local variable immediately,
|
||||||
// and can be changed by other Code components during the `await` calls below
|
// as the `pageData` object is shared between all components on the current page
|
||||||
const groupIndex = ++pageData.blockGroupIndex
|
// and can be changed by other Code components during the `await` calls below
|
||||||
|
const groupIndex = ++pageData.blockGroupIndex
|
||||||
const engine = new ExpressiveCode({
|
|
||||||
themes: [theme ? await loadShikiTheme(theme as BundledShikiTheme) : houston],
|
|
||||||
plugins: [pluginLineNumbers()],
|
|
||||||
})
|
|
||||||
|
|
||||||
|
// Get the base and theme styles, and the JS modules from the ExpressiveCode engine
|
||||||
const baseStyles = await engine.getBaseStyles();
|
const baseStyles = await engine.getBaseStyles();
|
||||||
const themeStyles = await engine.getThemeStyles();
|
const themeStyles = await engine.getThemeStyles();
|
||||||
const jsModules = await engine.getJsModules();
|
const jsModules = await engine.getJsModules();
|
||||||
|
|
||||||
|
// Render the code using the ExpressiveCode engine
|
||||||
const { renderedGroupAst, styles } = await engine.render({
|
const { renderedGroupAst, styles } = await engine.render({
|
||||||
language: lang, meta, locale, code,
|
language: lang, meta, locale, code,
|
||||||
parentDocument: {
|
parentDocument: {
|
||||||
|
@ -37,14 +33,17 @@ const { renderedGroupAst, styles } = await engine.render({
|
||||||
},
|
},
|
||||||
props, })
|
props, })
|
||||||
|
|
||||||
|
// Convert the rendered group AST to HTML
|
||||||
let htmlContent = toHtml(renderedGroupAst)
|
let htmlContent = toHtml(renderedGroupAst)
|
||||||
|
|
||||||
|
// Prepend the base and theme styles to the HTML content
|
||||||
const stylesToPrepend: string[] = []
|
const stylesToPrepend: string[] = []
|
||||||
stylesToPrepend.push(baseStyles, themeStyles, ...styles)
|
stylesToPrepend.push(baseStyles, themeStyles, ...styles)
|
||||||
if (stylesToPrepend.length) {
|
if (stylesToPrepend.length) {
|
||||||
htmlContent = `<style>${[...stylesToPrepend].join('').replace(/\.expressive-code/g, '.gist .expressive-code')}</style>${htmlContent}`
|
htmlContent = `<style>${[...stylesToPrepend].join('').replace(/\.expressive-code/g, '.gist .expressive-code')}</style>${htmlContent}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepend the JS modules to the HTML content
|
||||||
const scriptsToPrepend: string[] = []
|
const scriptsToPrepend: string[] = []
|
||||||
scriptsToPrepend.push(...jsModules)
|
scriptsToPrepend.push(...jsModules)
|
||||||
if (scriptsToPrepend.length) {
|
if (scriptsToPrepend.length) {
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers'
|
||||||
|
import { ExpressiveCode } from 'expressive-code'
|
||||||
|
import config from "virtual:astro-gists/config";
|
||||||
|
import { loadTheme } from "./theming";
|
||||||
|
|
||||||
|
export { default as Code } from './components/Code.astro'
|
||||||
|
|
||||||
|
export const engine = new ExpressiveCode({
|
||||||
|
themes: await loadTheme(config.theme),
|
||||||
|
plugins: [pluginLineNumbers()],
|
||||||
|
})
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,39 @@
|
||||||
|
import { ExpressiveCodeTheme, loadShikiTheme, type BundledShikiTheme } from 'expressive-code'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
|
||||||
|
function loadBundledThemeFromFile(theme: string) {
|
||||||
|
return fs.readFileSync(new URL(`./themes/${theme}.jsonc`, import.meta.url), 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
const houstonDark = loadBundledThemeFromFile('houston-dark');
|
||||||
|
const nightOwlLight = loadBundledThemeFromFile('night-owl-light');
|
||||||
|
|
||||||
|
export type BundledThemeName = 'astroGists-dark' | 'astroGists-light';
|
||||||
|
|
||||||
|
export type ShikiThemeOrBundledThemeName = BundledShikiTheme | BundledThemeName;
|
||||||
|
|
||||||
|
export async function loadTheme(
|
||||||
|
themes: ShikiThemeOrBundledThemeName[] | undefined
|
||||||
|
): Promise<ExpressiveCodeTheme[]> {
|
||||||
|
|
||||||
|
const shikiThemes = themes && !Array.isArray(themes) ? [themes] : themes
|
||||||
|
|
||||||
|
const themesToLoad = (!shikiThemes || !shikiThemes.length) ? ['astroGists-dark', 'astroGists-light'] : shikiThemes;
|
||||||
|
|
||||||
|
const loadedThemes = await Promise.all(
|
||||||
|
themesToLoad.map(async (theme) => {
|
||||||
|
if (theme === 'astroGists-dark' || theme === 'astroGists-light') {
|
||||||
|
const bundledTheme = theme === 'astroGists-dark' ? houstonDark : nightOwlLight;
|
||||||
|
return customizeBundledTheme(ExpressiveCodeTheme.fromJSONString(bundledTheme));
|
||||||
|
}
|
||||||
|
return await loadShikiTheme(theme as BundledShikiTheme);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return loadedThemes;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function customizeBundledTheme(theme: ExpressiveCodeTheme) {
|
||||||
|
return theme;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
import { getGistFile } from "../utils"
|
import { getGistFile } from "../lib"
|
||||||
import CodeBlob from "./CodeBlob.astro"
|
import { Code } from "../ExpressiveCode"
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
/** REQUIRED: Used to define the desired GitHub Gist ID */
|
/** REQUIRED: Used to define the desired GitHub Gist ID */
|
||||||
|
@ -26,7 +26,7 @@ const Gist = await getGistFile( gistId, filename);
|
||||||
|
|
||||||
---
|
---
|
||||||
{ Gist &&
|
{ Gist &&
|
||||||
<CodeBlob
|
<Code
|
||||||
wrap={WRAP} showLineNumbers={SLN}
|
wrap={WRAP} showLineNumbers={SLN}
|
||||||
title={Gist.filename} raw_url={Gist.raw_url}
|
title={Gist.filename} raw_url={Gist.raw_url}
|
||||||
code={ Gist.content ? Gist.content : "" }
|
code={ Gist.content ? Gist.content : "" }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
import { getGistGroup } from "../utils"
|
import { getGistGroup } from "../lib"
|
||||||
import CodeBlob from "./CodeBlob.astro";
|
import { Code } from "../ExpressiveCode";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
/** REQUIRED: Used to define the desired GitHub Gist ID */
|
/** REQUIRED: Used to define the desired GitHub Gist ID */
|
||||||
|
@ -29,7 +29,7 @@ const files = Gist.files;
|
||||||
{Object.keys(files).map((file) => {
|
{Object.keys(files).map((file) => {
|
||||||
const { content, filename, language, raw_url } = files[file];
|
const { content, filename, language, raw_url } = files[file];
|
||||||
return (
|
return (
|
||||||
<CodeBlob
|
<Code
|
||||||
title={filename} wrap={WRAP}
|
title={filename} wrap={WRAP}
|
||||||
code={content} raw_url={raw_url}
|
code={content} raw_url={raw_url}
|
||||||
lang={language.toLowerCase()}
|
lang={language.toLowerCase()}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
// Export Integration
|
||||||
import astroGist from "./integration";
|
import astroGist from "./integration";
|
||||||
|
|
||||||
export default astroGist;
|
export default astroGist;
|
||||||
|
|
||||||
|
// Export the user config schema
|
||||||
|
import { z } from "astro/zod";
|
||||||
|
import type { ShikiThemeOrBundledThemeName } from "./ExpressiveCode/theming"
|
||||||
|
|
||||||
|
export const optionsSchema = z.object({
|
||||||
|
/**
|
||||||
|
* Optional: Allows the user to change the default theme for the code blocks.
|
||||||
|
* @default ['astroGist-dark','astroGist-light']
|
||||||
|
*
|
||||||
|
* All available themes are listed in the [Shiki documentation](https://shiki.matsu.io/docs/themes).
|
||||||
|
*/
|
||||||
|
theme: z.custom<ShikiThemeOrBundledThemeName[]>().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type astroGistsUserConfig = z.infer<typeof optionsSchema>
|
|
@ -1,37 +1,10 @@
|
||||||
import { defineIntegration, createResolver } from "astro-integration-kit"
|
import { defineIntegration, createResolver } from "astro-integration-kit"
|
||||||
import { corePlugins } from "astro-integration-kit/plugins"
|
import { corePlugins } from "astro-integration-kit/plugins"
|
||||||
import { z } from "astro/zod";
|
import { isThereAToken, TOKEN_MISSING_ERROR } from "./lib"
|
||||||
import { loadEnv } from "vite";
|
import { optionsSchema } from "./index"
|
||||||
import { type BundledShikiTheme } from 'expressive-code'
|
|
||||||
|
|
||||||
// Load environment variables
|
/**
|
||||||
const { GITHUB_PERSONAL_TOKEN } = loadEnv("all", process.cwd(), "GITHUB_");
|
* Astro-Gist - An Astro.js integration for embedding GitHub Gists in your Astro.js project.
|
||||||
|
|
||||||
export const optionsSchema = z.object({
|
|
||||||
/**
|
|
||||||
* Optional: Allows the user to change the default theme for the code blocks.
|
|
||||||
*
|
|
||||||
* All available themes are listed in the [Shiki documentation](https://shiki.matsu.io/docs/themes).
|
|
||||||
*/
|
|
||||||
theme: z.custom<BundledShikiTheme>().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type UserConfig = z.infer<typeof optionsSchema>
|
|
||||||
|
|
||||||
/** Astro-Gist - An Astro.js integration for embedding GitHub Gists in your Astro.js project.
|
|
||||||
* @example
|
|
||||||
* import { defineConfig } from "astro/config";
|
|
||||||
* import astroGist from "@matthiesenxyz/astro-gists";
|
|
||||||
*
|
|
||||||
* export default defineConfig({
|
|
||||||
* integrations: [
|
|
||||||
* astroGist({
|
|
||||||
* // Optional: Change the default theme for the code blocks.
|
|
||||||
* // Default: `Astro Houston (Custom)` If you want to use the default theme, you can omit this option.
|
|
||||||
* theme: "github-dark"
|
|
||||||
* })
|
|
||||||
* ]
|
|
||||||
* });
|
|
||||||
*/
|
*/
|
||||||
export default defineIntegration({
|
export default defineIntegration({
|
||||||
name: "@matthiesenxyz/astro-gists",
|
name: "@matthiesenxyz/astro-gists",
|
||||||
|
@ -51,9 +24,7 @@ export default defineIntegration({
|
||||||
watchIntegration(resolve())
|
watchIntegration(resolve())
|
||||||
|
|
||||||
// Check for GITHUB_PERSONAL_TOKEN
|
// Check for GITHUB_PERSONAL_TOKEN
|
||||||
if (!GITHUB_PERSONAL_TOKEN) {
|
if (!isThereAToken) {configSetup.warn(TOKEN_MISSING_ERROR)}
|
||||||
configSetup.warn("GITHUB_PERSONAL_TOKEN not found. Please add it to your .env file. Without it, you will be limited to 60 requests per hour.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add virtual imports
|
// Add virtual imports
|
||||||
addVirtualImports({
|
addVirtualImports({
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { requestRetry } from './octokit';
|
||||||
|
|
||||||
|
// Get a Gist by ID
|
||||||
|
export const getGist = async (gistId: string) => {
|
||||||
|
/** @ts-ignore-error */
|
||||||
|
const { data: response } = await requestRetry('GET /gists/{gist_id}', { gist_id: gistId });
|
||||||
|
const data = response as {
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: we don't know the shape of the data returned from the API
|
||||||
|
files: any; data: unknown
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get a file from a Gist by ID and filename
|
||||||
|
export const getGistFile = async (
|
||||||
|
gistId: string,
|
||||||
|
filename: string
|
||||||
|
) => {
|
||||||
|
const gist = await getGist(gistId);
|
||||||
|
if (gist?.files) {
|
||||||
|
const file = gist.files[filename];
|
||||||
|
return file ? file : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGistGroup = async (gistId: string) => {
|
||||||
|
const gist = await getGist(gistId);
|
||||||
|
return gist;
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./gist-processor";
|
||||||
|
export * from "./octokit";
|
|
@ -5,11 +5,20 @@ import type { Route, RequestParameters, OctokitResponse } from "@octokit/types"
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
const { GITHUB_PERSONAL_TOKEN } = loadEnv("all", process.cwd(), "GITHUB_");
|
const { GITHUB_PERSONAL_TOKEN } = loadEnv("all", process.cwd(), "GITHUB_");
|
||||||
|
|
||||||
|
export const isThereAToken = () => {
|
||||||
|
if (!GITHUB_PERSONAL_TOKEN) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TOKEN_MISSING_ERROR = "GITHUB_PERSONAL_TOKEN not found. Please add it to your .env file. Without it, you will be limited to 60 requests per hour.";
|
||||||
|
|
||||||
// Create an Octokit instance
|
// Create an Octokit instance
|
||||||
export const octokit = new Octokit({ auth: GITHUB_PERSONAL_TOKEN });
|
const octokit = new Octokit({ auth: GITHUB_PERSONAL_TOKEN });
|
||||||
|
|
||||||
// Retry requests if rate limited
|
// Retry requests if rate limited
|
||||||
async function requestRetry(route: Route, parameters: RequestParameters) {
|
export async function requestRetry(route: Route, parameters: RequestParameters) {
|
||||||
try {
|
try {
|
||||||
const response: OctokitResponse<unknown, number> = await octokit.request(route, parameters);
|
const response: OctokitResponse<unknown, number> = await octokit.request(route, parameters);
|
||||||
return response;
|
return response;
|
||||||
|
@ -32,32 +41,3 @@ async function requestRetry(route: Route, parameters: RequestParameters) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a Gist by ID
|
|
||||||
export const getGist = async (gistId: string) => {
|
|
||||||
/** @ts-ignore-error */
|
|
||||||
const { data: response } = await requestRetry('GET /gists/{gist_id}', { gist_id: gistId });
|
|
||||||
const data = response as {
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: we don't know the shape of the data returned from the API
|
|
||||||
files: any; data: unknown
|
|
||||||
};
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get a file from a Gist by ID and filename
|
|
||||||
export const getGistFile = async (
|
|
||||||
gistId: string,
|
|
||||||
filename: string
|
|
||||||
) => {
|
|
||||||
const gist = await getGist(gistId);
|
|
||||||
if (gist?.files) {
|
|
||||||
const file = gist.files[filename];
|
|
||||||
return file ? file : null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getGistGroup = async (gistId: string) => {
|
|
||||||
const gist = await getGist(gistId);
|
|
||||||
return gist;
|
|
||||||
};
|
|
|
@ -1,4 +1,4 @@
|
||||||
declare module "virtual:astro-gists/config" {
|
declare module "virtual:astro-gists/config" {
|
||||||
const Config: import("./src/integration").UserConfig;
|
const Config: import("./src/index").astroGistsUserConfig;
|
||||||
export default Config;
|
export default Config;
|
||||||
}
|
}
|
Loading…
Reference in New Issue