Extending from another plugin
You can add a new source or a new display from a separate MyST plugin. Your plugin just needs to find the placeholder nodes and fill them in. (To add a built-in source/display to this repo instead, see Contributing.)
How to define your own collector or display function¶
The {listing} directive emits a listingPlaceholder node carrying the user’s options (source, display, path, sort, limit, ...).
Your plugin should ship a document-stage transform that selects those nodes and either:
collects — sets
node.itemsto a list of items (a collector), ordisplays — replaces a node whose
:display:you own with your rendered AST (a display).
An item is a plain object; see Items for the fields the built-ins understand.
Staging and ordering¶
Transforms in MyST can run in one of two stages: document first, and project after.
Here are some things to keep in mind to make sure your plugin works properly:
Run at the
documentstage.myst-listingresolves title links during this stage, so items collected later won’t link correctly.Run before
myst-listing’s render. Cross-plugin order follows load order inmyst.yml, so list your plugin before themyst-listingplugin.A node whose
:source:or:display:myst-listingdoesn’t recognize is left untouched through the document stage, giving your transform a chance to claim it. Anything still unclaimed by the project stage is reported as an unknown source.
Add a collector¶
Set node.items for the source you own; leave the rest alone:
const collectStars = {
name: "listing-collect-stars",
stage: "document",
plugin: (_opts, utils) => async (tree) => {
for (const node of utils.selectAll("listingPlaceholder", tree)) {
if (node.source !== "stars") continue; // only the source we own
node.items = await fetchStars(node.path); // your async logic here
}
},
};
export default { name: "Listing stars", transforms: [collectStars] };Load both plugins in myst.yml (yours first), and :source: stars now works.
Add a display¶
Replace a node whose :display: you own with your own AST.
By this point a collector has already filled node.items:
const renderBadges = {
name: "listing-display-badges",
stage: "document",
plugin: (_opts, utils) => (tree) => {
for (const node of utils.selectAll("listingPlaceholder", tree)) {
if (node.display !== "badges" || node.items === undefined) continue;
// Replace the placeholder in place with your node.
const out = { type: "div", children: node.items.map(toBadge) };
for (const key of Object.keys(node)) if (key !== "type") delete node[key];
Object.assign(node, out);
}
},
};
export default { name: "Listing badges", transforms: [renderBadges] };