Open factoidforrest opened 2 years ago
You're probably done with this by now, but for future googlers:
Remark recommends you use remark-directive instead.
Recommendation: https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins Remark Directive: https://github.com/remarkjs/remark-directive#use
@eestein any tips on how to reproduce the callouts in this plugin?
@fmonper1 you have to create your own plugin and the CSS classes. I'm gonna share the one I created:
const acceptableCalloutTypes = {
'note': {cssClass: '', iconClass: 'comment-alt-lines'},
'tip': {cssClass: 'is-success', iconClass: 'lightbulb'},
'info': {cssClass: 'is-info', iconClass: 'info-circle'},
'warning': {cssClass: 'is-warning', iconClass: 'exclamation-triangle'},
'danger': {cssClass: 'is-danger', iconClass: 'siren-on'}
};
/**
* Plugin to generate callout blocks.
*/
function calloutsPlugin() {
return (tree) => {
visit(tree, (node) => {
if (node.type === 'textDirective' || node.type === 'leafDirective' || node.type === 'containerDirective') {
if (!Object.keys(acceptableCalloutTypes).includes(node.name)) {
return;
}
const boxInfo = acceptableCalloutTypes[node.name];
// Adding CSS classes according to the type.
const data = node.data || (node.data = {});
const tagName = node.type === 'textDirective' ? 'span' : 'div';
data.hName = tagName;
data.hProperties = h(tagName, {class: `message ${boxInfo.cssClass}`}).properties;
// Creating the icon.
const icon = h('i');
const iconData = icon.data || (icon.data = {});
iconData.hName = 'i';
iconData.hProperties = h('i', {class: `far fa-${boxInfo.iconClass} md-callout-icon`}).properties;
// Creating the icon's column.
const iconWrapper = h('div');
const iconWrapperData = iconWrapper.data || (iconWrapper.data = {});
iconWrapperData.hName = 'div';
iconWrapperData.hProperties = h('div', {class: 'column is-narrow'}).properties;
iconWrapper.children = [icon];
// Creating the content's column.
const contentColWrapper = h('div');
const contentColWrapperData = contentColWrapper.data || (contentColWrapper.data = {});
contentColWrapperData.hName = 'div';
contentColWrapperData.hProperties = h('div', {class: 'column'}).properties;
contentColWrapper.children = [...node.children]; // Adding markdown's content block.
// Creating the column's wrapper.
const columnsWrapper = h('div');
const columnsWrapperData = columnsWrapper.data || (columnsWrapper.data = {});
columnsWrapperData.hName = 'div';
columnsWrapperData.hProperties = h('div', {class: 'columns'}).properties;
columnsWrapper.children = [iconWrapper, contentColWrapper];
// Creating the wrapper for the callout's content.
const contentWrapper = h('div');
const wrapperData = contentWrapper.data || (contentWrapper.data = {});
wrapperData.hName = 'div';
wrapperData.hProperties = h('div', {class: 'message-body'}).properties;
contentWrapper.children = [columnsWrapper];
node.children = [contentWrapper];
}
});
};
}
And the usage in my markdown files remains the same:
:::info
Message
:::
Remember that for the styling to work you must add your CSS and follow my code's structure, if you're copying/pasting.
This is the CSS FW I used: https://bulma.io/documentation/components/message/#message-body-only
@eestein, thank you so much for sharing your plugin! It's working wonderfully for me and you saved me a ton of time. In the spirit of continuing to help any others that come across this, I'll also share a few tweaks I made.
:::info{title="Some Title"}
).data
metadata assignment stuff really hard to follow)/** @typedef {import('remark-directive')} */
/** @typedef {import('unified').Plugin<[Settings], import('mdast').Root>} Plugin */
import { h, s } from 'hastscript';
import { visit } from 'unist-util-visit';
/**
* @typedef {{ title?: string, size?: number }} Attributes
* @typedef {Object} Settings
*/
const callouts = {
note: { color: 'brandTan', icon: 'h-clipboard-list', title: 'Note' },
tip: { color: 'success', icon: 'h-clipboard-check', title: 'Tip' },
info: { color: 'primary', icon: 'i-info', title: 'Info' },
warning: { color: 'warning', icon: 'i-alert-triangle', title: 'Warning' },
danger: { color: 'danger', icon: 'i-alert-octagon', title: 'Danger' },
};
const iconSizeMap = /** @type {Record<number, string>} */ ({
4: 'large',
5: 'medium',
6: 'small',
});
const spacingMap = /** @type {Record<number, string>} */ ({
4: '300',
5: '200',
6: '100',
});
/**
* Recursively walk a `hast` tree and decorate each node with the metadata
* required for `remark-directive`
*
* @param {JSX.Element} node
*/
const decorateHast = node => {
Object.assign(node.data ?? (node.data = {}), {
hName: node.tagName,
hProperties: node.properties,
});
if (node.children && Array.isArray(node.children)) {
node.children.forEach(decorateHast);
}
};
/**
* Check if directive `name` is a supported callout
*
* @param {string} name
* @returns {name is keyof typeof callouts}
*/
const isSupportedCallout = name => Object.keys(callouts).includes(name);
/**
* Remark plugin to support block-quote style callouts with the same syntax
* introduced in `remark-admonition` which is apparently no longer supported in
* the latest version of Remark.
*
* @see {@link https://github.com/elviswolcott/remark-admonitions/issues/49#issuecomment-1162400177}
* @see {@link https://github.com/remarkjs/remark-directive#examples}
*
* @type {Plugin}
*/
const plugin = () => tree => {
visit(tree, node => {
if (
!(
node.type === 'textDirective' ||
node.type === 'leafDirective' ||
node.type === 'containerDirective'
)
) {
return;
}
if (!isSupportedCallout(node.name)) return;
// Grab attributes from the directive and apply defaults
const { color, icon, title: defaultTitle } = callouts[node.name];
const { title = defaultTitle, size = '6' } = node.attributes ?? {};
// Next, build up all of the elements that are going to make up the
// callout DOM structure in `hast`. These are separated out as nesting all
// of the `hastscript` `h` and `s` calls would get a little unweildy
// compared to something like JSX.
// Icon -----------------
const iconSize = iconSizeMap[size] ?? 'small';
const svg = s(
'svg',
{
xmlns: 'http://www.w3.org/2000/svg',
class: `icon icon-${iconSize} text-${color}-700 m-0`,
},
[s('use', { 'xlink:href': `/icons/all.svg#${icon}` })],
);
// Heading --------------
const heading = h(
`h${size}`,
{ class: `m-0 fw-bodySemiBold text-${color}-700` },
[{ type: 'text', value: title }],
);
// Title --------------
const spacing = spacingMap[size] ?? '100';
const titleContainer =
// Wrapping just the title container in `.hover-bootstrap`
// to apply the Bootstrap theme for the icon and heading
h('span', { class: 'hover-bootstrap' }, [
h(
'span',
{
class: `d-inline-flex align-items-center gap-${spacing} mb-${spacing}`,
},
[svg, heading],
),
]);
// Body --------------
const body = h('div', { class: 'callout-body' }, [
titleContainer,
// Actual Markdown content for the callout
h('div', { class: 'column' }, [...node.children]),
]);
// Mutate the actual node we're visiting to attach the `hast`
// tree we've built up with all of the elements we're inserting
node.tagName = node.type === 'textDirective' ? 'span' : 'blockquote';
node.properties = { class: `message`, 'data-color-scheme': color };
node.children = [body];
// Finally we need to walk this whole `hast` tree we've built and augment
// each node with the metadata that `remark-directive` requires
decorateHast(node);
});
};
export default plugin;
:::note
The icons must always come _after_ the `<input>` element
:::
:::note{title="Anchor must accept a ref"}
As with [`trigger`](#basic-usage), if you pass a custom component inside
`anchor` ensure that it uses `forwardRef` so the popover is triggered
successfully.
:::
:::info{title="Core Concepts" size="5"}
By the end of this walkthrough, you'll have a solid understanding of:
- Contributing a bug fix
- Adding changelog information associated with your fix
- Getting your fix released using continuous integration
:::
I am getting
Cannot read properties of undefined (reading 'blockTokenizers')
In the DOM when I use this plugin with react-markdown.Given that this project seems more or less fully abandoned, is there a replacement that does something similar? Are we waiting for someone to fork this. I tried to figure out how to fix this plugin but given the very sparse documentation on how to write unified plugins, I got very confused.