Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions src/remark-if.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import type { Parent, Root } from "mdast";
import type { ContainerDirective } from "mdast-util-directive";
import type { Plugin } from "unified";
import type { Node } from "unist";
import { SKIP, visit } from "unist-util-visit";

interface RemarkIfOptions {
/**
* The current mode to use for filtering content.
* Content with a different mode will be removed.
*/
mode?: string;
}

interface DirectiveNode extends ContainerDirective {
attributes?: {
mode?: string;
};
}

/**
* A remark plugin that filters out content based on the mode attribute.
*
* Syntax: `::if{mode=opensource}...content...::`
*
* If the mode matches the configured mode, the content is kept (but the directive wrapper is removed).
* If the mode doesn't match, the entire directive and its content is removed.
*
* @example
* ```ts
* import { remarkIf } from './remark-if.js';
*
* export default {
* // cSpell:ignore opensource
* remarkPlugins: [[remarkIf, { mode: 'opensource' }]],
* };
* ```
*/
export const remarkIf: Plugin<[RemarkIfOptions?], Root> = (options = {}) => {
return (tree: Root) => {
visit(tree, (node: Node, index?: number, parent?: Parent) => {
// Handle both containerDirective (:::) and leafDirective (::) with name "if"
if (
(node.type !== "containerDirective" && node.type !== "leafDirective") ||
(node as DirectiveNode).name !== "if" ||
parent == null ||
index == null
) {
return;
}

const directive = node as DirectiveNode;

// Get the mode attribute from the directive
const modeAttribute = directive.attributes?.mode;

// Find the closing "::" marker (for leafDirective) or handle containerDirective
const isLeafDirective = node.type === "leafDirective";
let closingIndex = -1;

if (isLeafDirective) {
// For leaf directives, find the paragraph containing only "::"
for (let i = index + 1; i < parent.children.length; i++) {
const nextNode = parent.children[i];
if (nextNode.type !== "paragraph" || !("children" in nextNode)) {
continue;
}
const firstChild = nextNode.children?.[0];
if (
nextNode.children?.length === 1 &&
firstChild &&
firstChild.type === "text" &&
"value" in firstChild &&
typeof firstChild.value === "string" &&
firstChild.value.trim() === "::"
) {
closingIndex = i;
break;
}
}

// Fail if no closing "::" was found for leaf directive
if (closingIndex === -1) {
const position = directive.position
? ` at line ${directive.position.start.line}`
: "";
throw new Error(
`Unclosed ::if directive${position}. Leaf directives (::if) must be closed with a matching "::".`,
);
}
}

if (!modeAttribute) {
// If no mode attribute, keep the content but remove the directive wrapper
if (isLeafDirective && closingIndex > -1) {
// Remove the closing "::" and the directive, keep content in between
parent.children.splice(closingIndex, 1);
parent.children.splice(index, 1);
return [SKIP, index];
} else if (directive.children && directive.children.length > 0) {
parent.children.splice(index, 1, ...directive.children);
return [SKIP, index];
} else {
parent.children.splice(index, 1);
return [SKIP, index];
}
}

// If the mode matches, keep the content but remove the directive wrapper and closing "::"
if (modeAttribute === options.mode) {
if (isLeafDirective && closingIndex > -1) {
// Remove the closing "::" and the directive, keep content in between
parent.children.splice(closingIndex, 1);
parent.children.splice(index, 1);
return [SKIP, index];
} else if (directive.children && directive.children.length > 0) {
parent.children.splice(index, 1, ...directive.children);
return [SKIP, index];
} else {
parent.children.splice(index, 1);
return [SKIP, index];
}
}

// If the mode doesn't match, remove the directive and all content until the closing "::"
if (isLeafDirective && closingIndex > -1) {
// Remove from directive to closing "::" (inclusive)
parent.children.splice(index, closingIndex - index + 1);
} else {
// For container directives or when no closing found, just remove the directive
parent.children.splice(index, 1);
}
return [SKIP, index];
});
};
};
12 changes: 11 additions & 1 deletion vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -1270,7 +1270,7 @@
},
{
"source": "/docs/mcp-server/mcp-server{/}?",
"destination": "/mcp-server/introduction",
"destination": "/docs/mcp-server/introduction",
"permanent": true
},
{
Expand All @@ -1283,10 +1283,20 @@
"destination": "/docs/mcp-server/tools",
"permanent": true
},
{
"source": "/docs/handlers/mcp-server-custom-tools{/}?",
"destination": "/docs/mcp-server/custom-tools",
"permanent": true
},
{
"source": "/docs/handlers/mcp-server-resources{/}?",
"destination": "/docs/mcp-server/resources",
"permanent": true
},
{
"source": "/docs/handlers/mcp-server-prompts{/}?",
"destination": "/docs/mcp-server/prompts",
"permanent": true
}
],
"rewrites": [
Expand Down
7 changes: 7 additions & 0 deletions zudoku.build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { removeExtensions } from "zudoku/processors/removeExtensions";
import type { ZudokuBuildConfig } from "zudoku";
import type { PluggableList } from "unified";
import { remarkIf } from "./src/remark-if.js";

const buildConfig: ZudokuBuildConfig = {
remarkPlugins: (defaultPlugins: PluggableList) => [
...defaultPlugins,
[remarkIf, { mode: "zuplo" }],
],
processors: [
// Remove specific extensions
removeExtensions({
Expand Down
Loading