Skip to content

Commit

Permalink
Refactor markdown-remark integration to optimize config
Browse files Browse the repository at this point in the history
  • Loading branch information
Adammatthiesen committed Jan 6, 2025
1 parent 19dddd1 commit bcc7337
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 97 deletions.
6 changes: 5 additions & 1 deletion packages/markdown-remark/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export * from './processor/index.js';
export { markdownRemark, default, type MarkdownRemarkOptions } from './integration/index.js';
export {
markdownRemark,
default,
type StudioCMSMarkdownRemarkOptions,
} from './integration/index.js';
25 changes: 14 additions & 11 deletions packages/markdown-remark/src/integration/index.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import type { AstroIntegration } from 'astro';
import { addVirtualImports, createResolver } from 'astro-integration-kit';
import { type MarkdownRemarkOptions, MarkdownRemarkOptionsSchema } from './schema.js';
import {
type StudioCMSMarkdownRemarkOptions,
StudioCMSMarkdownRemarkOptionsSchema,
} from './schema.js';
import { shared } from './shared.js';

export type { MarkdownRemarkOptions } from './schema.js';
export type { StudioCMSMarkdownRemarkOptions } from './schema.js';

/**
* Integrates the Markdown Remark processor into Astro available as `studiocms:markdown-remark`.
*
* @param {MarkdownRemarkOptions} opts Options for the Markdown Remark processor.
* @param {StudioCMSMarkdownRemarkOptions} opts Options for the Markdown Remark processor.
* @returns Astro integration.
*/
export function markdownRemark(opts?: MarkdownRemarkOptions): AstroIntegration {
export function markdownRemark(opts?: StudioCMSMarkdownRemarkOptions): AstroIntegration {
// Parse the options
const {
injectCSS,
markdown: { callouts, components, autolink },
} = MarkdownRemarkOptionsSchema.parse(opts);
const { injectCSS, components, markdownExtended } =
StudioCMSMarkdownRemarkOptionsSchema.parse(opts);

// Create a resolver for the current file
const { resolve } = createResolver(import.meta.url);

// Resolve the callout theme based on the user's configuration
const resolvedCalloutTheme = resolve(`../../assets/callout-themes/${callouts.theme}.css`);
const resolvedCalloutTheme = resolve(
`../../assets/callout-themes/${markdownExtended.callouts.theme}.css`
);

return {
name: '@studiocms/markdown-remark',
Expand All @@ -40,7 +43,7 @@ export function markdownRemark(opts?: MarkdownRemarkOptions): AstroIntegration {
// Styles for the Markdown Remark processor
'studiocms:markdown-remark/css': `
import '${resolve('../../assets/headings.css')}';
import '${resolvedCalloutTheme}';
${markdownExtended.callouts.enabled ? `import '${resolvedCalloutTheme}';` : ''}
`,
// User defined components for the Markdown processor
'studiocms:markdown-remark/user-components': `
Expand All @@ -67,7 +70,7 @@ export function markdownRemark(opts?: MarkdownRemarkOptions): AstroIntegration {

// Inject the Markdown configuration into the shared state
shared.markdownConfig = config.markdown;
shared.studiocms = { callouts, autolink };
shared.studiocms = markdownExtended;

// Inject types for the Markdown Remark processor
injectTypes({
Expand Down
11 changes: 9 additions & 2 deletions packages/markdown-remark/src/integration/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type MarkdownProcessorRenderOptions,
createMarkdownProcessor,
} from '../processor/index.js';
import { TransformToProcessor } from './schema.js';
import { shared } from './shared.js';
import type {
ComponentSlots,
Expand All @@ -24,9 +25,11 @@ import {

export type { Props, RenderResponse } from './types.js';

const studiocmsMarkdownExtended = TransformToProcessor.parse(shared.studiocms);

const processor = await createMarkdownProcessor({
...shared.markdownConfig,
studiocms: shared.studiocms,
...studiocmsMarkdownExtended,
});

const predefinedComponents = await importComponentsKeys(componentKeys);
Expand All @@ -51,7 +54,11 @@ export async function render(

const { code, metadata } = await processor.render(content, options);

const html = await transformHTML(code, componentsRendered, sanitizeOpts);
const html = await transformHTML(
code,
componentsRendered,
sanitizeOpts ?? shared.studiocms.sanitize
);

return {
html: new HTMLString(html),
Expand Down
121 changes: 75 additions & 46 deletions packages/markdown-remark/src/integration/schema.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,93 @@
import { z } from 'astro/zod';
import type { StudioCMSConfigOptions } from '../processor/types.js';

/**
* Options for the Markdown Callouts.
*/
const CalloutsSchema = z
export const StudioCMSSanitizeOptionsSchema = z
.object({
/**
* The theme to use for callouts.
*/
theme: z
.union([z.literal('github'), z.literal('obsidian'), z.literal('vitepress')])
.optional()
.default('obsidian'),
/** An Array of strings indicating elements that the sanitizer should not remove. All elements not in the array will be dropped. */
allowElements: z.array(z.string()).optional(),
/** An Array of strings indicating elements that the sanitizer should remove, but keeping their child elements. */
blockElements: z.array(z.string()).optional(),
/** An Array of strings indicating elements (including nested elements) that the sanitizer should remove. */
dropElements: z.array(z.string()).optional(),
/** An Object where each key is the attribute name and the value is an Array of allowed tag names. Matching attributes will not be removed. All attributes that are not in the array will be dropped. */
allowAttributes: z.record(z.array(z.string())).optional(),
/** An Object where each key is the attribute name and the value is an Array of dropped tag names. Matching attributes will be removed. */
dropAttributes: z.record(z.array(z.string())).optional(),
/** A Boolean value set to false (default) to remove components and their children. If set to true, components will be subject to built-in and custom configuration checks (and will be retained or dropped based on those checks). */
allowComponents: z.boolean().optional(),
/** A Boolean value set to false (default) to remove custom elements and their children. If set to true, custom elements will be subject to built-in and custom configuration checks (and will be retained or dropped based on those checks). */
allowCustomElements: z.boolean().optional(),
/** A Boolean value set to false (default) to remove HTML comments. Set to true in order to keep comments. */
allowComments: z.boolean().optional(),
})
.optional()
.default({});
.optional();

/**
* Extended options for the Astro Integration for Markdown Remark. Used to control how Markdown is processed.
*/
const MarkdownSchema = z
.object({
/**
* Configures the callouts theme.
*/
callouts: CalloutsSchema,
export const StudioCMSMarkdownExtendedSchema = z
.union([
z.literal(false),
z.object({
callouts: z
.union([
z.literal(false),
z.object({
theme: z
.union([z.literal('github'), z.literal('obsidian'), z.literal('vitepress')])
.optional()
.default('obsidian'),
}),
])
.optional()
.default({})
.transform((value) => {
if (value === false) {
return { theme: 'obsidian' as const, enabled: false };
}
return { ...value, enabled: true };
}),

/**
* Enables autolinking of headings.
*/
autolink: z.boolean().optional().default(true),
autoLinkHeadings: z.boolean().optional().default(true),

/**
* Configures the user defined components for the Markdown processor.
*/
components: z.record(z.string(), z.string()).optional().default({}),
})
sanitize: StudioCMSSanitizeOptionsSchema,
}),
])
.optional()
.default({});
.default({})
.transform((value) => {
if (value === false) {
return {
callouts: { enabled: false, theme: 'obsidian' as const },
autoLinkHeadings: false,
};
}
return value;
});

/**
* Options for the Markdown Remark processor.
*/
export const MarkdownRemarkOptionsSchema = z
export const StudioCMSMarkdownRemarkOptionsSchema = z
.object({
/**
* Inject CSS for the Markdown processor.
* Inject CSS for Rendering Markdown content.
*/
injectCSS: z.boolean().optional().default(true),

/**
* Extended options for the Astro Integration for Markdown Remark. Used to control how Markdown is processed.
*/
markdown: MarkdownSchema,
components: z.record(z.string(), z.string()).optional().default({}),

markdownExtended: StudioCMSMarkdownExtendedSchema,
})
.optional()
.default({});

/**
* Options for the Markdown Remark processor.
*/
export type MarkdownRemarkOptions = typeof MarkdownRemarkOptionsSchema._input;
export type CalloutConfig = typeof CalloutsSchema._output;
export const TransformToProcessor = StudioCMSMarkdownExtendedSchema.transform(
({ autoLinkHeadings, callouts }) => {
return {
studiocms: {
callouts: callouts.enabled ? { theme: callouts.theme } : false,
autolink: autoLinkHeadings,
},
} as { studiocms: StudioCMSConfigOptions };
}
);

export type StudioCMSMarkdownExtendedOptions = typeof StudioCMSMarkdownExtendedSchema._input;
export type StudioCMSMarkdownExtendedConfig = z.infer<typeof StudioCMSMarkdownExtendedSchema>;
export type StudioCMSMarkdownRemarkOptions = typeof StudioCMSMarkdownRemarkOptionsSchema._input;
export type StudioCMSMarkdownRemarkConfig = z.infer<typeof StudioCMSMarkdownRemarkOptionsSchema>;
7 changes: 2 additions & 5 deletions packages/markdown-remark/src/integration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { ComponentSlotValue } from 'astro/runtime/server/render/slot.js';
import type { SanitizeOptions } from 'ultrahtml/transformers/sanitize';
import type { HTMLString } from '../processor/HTMLString.js';
import type { MarkdownHeading } from '../processor/index.js';
import type { CalloutConfig } from './schema.js';
import type { StudioCMSMarkdownExtendedConfig } from './schema.js';

/**
* Represents the response from rendering a markdown document.
Expand Down Expand Up @@ -71,8 +71,5 @@ export interface ComponentSlots {
*/
export interface Shared {
markdownConfig: AstroConfig['markdown'];
studiocms: {
callouts: CalloutConfig;
autolink: boolean;
};
studiocms: StudioCMSMarkdownExtendedConfig;
}
33 changes: 16 additions & 17 deletions packages/markdown-remark/src/processor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { remarkCollectImages } from './remark-collect-images.js';
import type {
MarkdownProcessor,
MarkdownProcessorRenderResult,
StudioCMSCalloutOptions,
StudioCMSMarkdownOptions,
} from './types.js';

Expand Down Expand Up @@ -110,6 +111,21 @@ export async function createMarkdownProcessor(
studiocms = markdownConfigDefaults.studiocms,
} = opts ?? {};

let autolink = true;
let calloutsEnabled = true;
let calloutsConfig: StudioCMSCalloutOptions = { theme: 'obsidian' };

if (typeof studiocms === 'boolean') {
autolink = studiocms;
calloutsEnabled = studiocms;
} else if (typeof studiocms === 'object') {
autolink = studiocms.autolink ?? autolink;
calloutsEnabled = studiocms.callouts !== false;
if (typeof studiocms.callouts === 'object') {
calloutsConfig = studiocms.callouts;
}
}

const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins));
const loadedRehypePlugins = await Promise.all(loadPlugins(rehypePlugins));

Expand Down Expand Up @@ -159,23 +175,6 @@ export async function createMarkdownProcessor(
// Headings
parser.use(rehypeHeadingIds);

let autolink = true;
let calloutsEnabled = true;
let calloutsConfig: {
theme?: 'github' | 'obsidian' | 'vitepress';
} = { theme: 'obsidian' };

if (typeof studiocms === 'boolean') {
autolink = studiocms;
calloutsEnabled = studiocms;
} else if (typeof studiocms === 'object') {
autolink = studiocms.autolink ?? autolink;
calloutsEnabled = studiocms.callouts !== false;
if (typeof studiocms.callouts === 'object') {
calloutsConfig = studiocms.callouts;
}
}

// Autolink headings
if (autolink) {
parser.use(rehypeAutoLink, rehypeAutolinkOptions);
Expand Down
20 changes: 10 additions & 10 deletions packages/markdown-remark/src/processor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ export interface AstroMarkdownOptions {
smartypants?: boolean;
}

export interface StudioCMSCalloutOptions {
theme?: 'github' | 'obsidian' | 'vitepress';
}

export interface StudioCMSConfigOptions {
callouts?: StudioCMSCalloutOptions | false;
autolink?: boolean;
}

export interface StudioCMSMarkdownOptions extends AstroMarkdownOptions {
studiocms?:
| {
callouts?:
| {
theme?: 'github' | 'obsidian' | 'vitepress';
}
| false;
autolink?: boolean;
}
| false;
studiocms?: StudioCMSConfigOptions | false;
}

export interface MarkdownProcessor {
Expand Down
6 changes: 2 additions & 4 deletions packages/markdown-remark/tests/fixture/astro/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import { defineConfig } from 'astro/config';
export default defineConfig({
integrations: [
markdownRemark({
markdown: {
components: {
custom: './src/pages/custom-components/_comps/Custom.astro',
},
components: {
custom: './src/pages/custom-components/_comps/Custom.astro',
},
}),
],
Expand Down
2 changes: 1 addition & 1 deletion packages/markdown-remark/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"include": ["src", "v.d.ts"],
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
Expand Down
File renamed without changes.

0 comments on commit bcc7337

Please sign in to comment.