[packages][m]: mv @flowershow/remark-wiki-link here

This commit is contained in:
olayway 2023-06-06 15:53:05 +02:00
parent c61a2db628
commit ee6812974d
14 changed files with 876 additions and 0 deletions

View File

@ -0,0 +1,4 @@
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime"]
}

View File

@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,4 @@
extension: [ts]
node-option:
- experimental-specifier-resolution=node
- loader=ts-node/esm

View File

@ -0,0 +1,121 @@
# remark-callouts
Remark plugin to add support for blockquote-based callouts/admonitions similar to the approach of [Obsidian](https://help.obsidian.md/How+to/Use+callouts) and [Microsoft Learn](https://learn.microsoft.com/en-us/contribute/markdown-reference#alerts-note-tip-important-caution-warning) style.
Using this plugin, markdown like this:
```md
> [!tip]
> hello callout
```
Would render as a callout like this:
<img width="645" alt="Tip callout block" src="https://user-images.githubusercontent.com/42637597/193016397-49a90b44-cf3d-4eeb-9ad6-c0c1e374ed27.png">
## Features supported
- [x] Supports blockquote style callouts
- [x] Supports nested blockquote callouts
- [x] Supports 13 types out of the box (with appropriate styling in default theme) - see list below
- [x] Supports aliases for types
- [x] Defaults to note callout for all other types eg. `> [!xyz]`
- [x] Supports dark and light mode styles
Future support:
- [ ] Support custom types and icons
- [ ] Support custom aliases
- [ ] Support Foldable callouts
- [ ] Support custom styles
## Geting Started
### Installation
```bash
npm install remark-callouts
```
### Usage
```js
import callouts from "remark-callouts";
await remark()
.use(remarkParse)
.use(callouts)
.use(remarkRehype)
.use(rehypeStringify).process(`\
> [!tip]
> hello callout
`);
```
HTML output
```js
<div>
<blockquote class="callout">
<div class="callout-title tip">
<span class="callout-icon">
<svg>...</svg>
</span>
<strong>Tip</strong>
</div>
<div class="callout-content">
<p>hello callout</p>
</div>
</blockquote>
</div>
```
Import the styles in your .css file
```css
@import "remark-callouts/styles.css";
```
or in your app.js
```js
import "remark-callouts/styles.css";
```
### Supported Callout Types
- note
- tip `aliases: hint, important`
- warning `alises: caution, attention`
- abstract `aliases: summary, tldr`
- info
- todo
- success `aliases: check, done`
- question `aliases: help, faq`
- failure `aliases: fail, missing`
- danger `alias: error`
- bug
- example
- quote `alias: cite`
# Change Log
## [2.0.0] - 2022-11-21
### Added
- Classname for icon.
### Changed
- Extract css styles which can be imported separately.
## [1.0.2] - 2022-11-03
### Fixed
- Case insensitive match for types.
## License
MIT

View File

@ -0,0 +1,41 @@
{
"name": "@flowershow/remark-callouts",
"version": "1.0.0",
"description": "remark plugin to add support for blockquote-based admonitions/callouts",
"repository": {
"type": "git",
"url": "git+https://github.com/flowershow/flowershow.git",
"directory": "packages/remark-callouts"
},
"keywords": [
"remark",
"remark-plugin",
"markdown",
"admonitions",
"callouts",
"obsidian"
],
"author": "Rufus Pollock",
"license": "MIT",
"bugs": {
"url": "https://github.com/flowershow/flowershow/issues"
},
"homepage": "https://github.com/flowershow/flowershow#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"mdast-util-from-markdown": "^1.2.0",
"svg-parser": "^2.0.4",
"unist-util-visit": "^4.1.0"
},
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./styles.css": "./styles.css"
}
}

View File

@ -0,0 +1,64 @@
{
"name": "remark-callouts",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/remark-callouts/src",
"projectType": "library",
"targets": {
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/remark-callouts/**/*.ts"]
}
},
"test": {
"executor": "nx:run-commands",
"options": {
"command": "TS_NODE_PROJECT='packages/remark-callouts/tsconfig.spec.json' mocha --config packages/remark-callouts/.mocharc.yaml packages/remark-callouts/test/remark-callouts.spec.ts"
}
},
"build": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"entryFile": "packages/remark-callouts/src/index.ts",
"outputPath": "packages/remark-callouts/dist",
"compiler": "babel",
"tsConfig": "packages/remark-callouts/tsconfig.lib.json",
"project": "packages/remark-callouts/package.json",
"format": ["esm", "cjs"],
"extractCss": true,
"generateExportsField": true,
"assets": [
{
"glob": "packages/remark-callouts/README.md",
"input": ".",
"output": "."
},
{
"glob": "packages/remark-callouts/styles.css",
"input": ".",
"output": "."
}
]
}
},
"publish:dry": {
"executor": "nx:run-commands",
"options": {
"commands": ["npm publish --dry-run"],
"parallel": false,
"cwd": "packages/remark-callouts/dist"
}
},
"publish": {
"executor": "nx:run-commands",
"options": {
"commands": ["npm publish"],
"parallel": false,
"cwd": "packages/remark-callouts/dist"
}
}
},
"tags": []
}

View File

@ -0,0 +1,2 @@
export * from "./lib/remark-callouts";
export { default } from "./lib/remark-callouts";

View File

@ -0,0 +1,83 @@
export const calloutTypes = {
// aliases
summary: "abstract",
tldr: "abstract",
hint: "tip",
important: "tip",
check: "success",
done: "success",
help: "question",
faq: "question",
caution: "warning",
attention: "warning",
fail: "failure",
missing: "failure",
error: "danger",
cite: "quote",
// base types
note: {
keyword: "note",
color: "#448aff",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-pencil"><line x1="18" y1="2" x2="22" y2="6"></line><path d="M7.5 20.5 19 9l-4-4L3.5 16.5 2 22z"></path></svg>',
},
tip: {
keyword: "tip",
color: "#00bfa6",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-flame"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"></path></svg>',
},
warning: {
keyword: "warning",
color: "#ff9100",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-alert-triangle"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>',
},
abstract: {
keyword: "abstract",
color: "#00aeff",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-clipboard-list"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><path d="M15 2H9a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1z"></path><path d="M12 11h4"></path><path d="M12 16h4"></path><path d="M8 11h.01"></path><path d="M8 16h.01"></path></svg>',
},
info: {
keyword: "info",
color: "#00b8d4",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-info"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>',
},
todo: {
keyword: "todo",
color: "#00b8d4",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-check-circle-2"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path><path d="m9 12 2 2 4-4"></path></svg>',
},
success: {
keyword: "success",
color: "#00c853",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-check"><polyline points="20 6 9 17 4 12"></polyline></svg>',
},
question: {
keyword: "question",
color: "#63dd17",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>',
},
failure: {
keyword: "failure",
color: "#ff5252",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>',
},
danger: {
keyword: "danger",
color: "#ff1745",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-zap"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>',
},
bug: {
keyword: "bug",
color: "#f50057",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-bug"><rect x="8" y="6" width="8" height="14" rx="4"></rect><path d="m19 7-3 2"></path><path d="m5 7 3 2"></path><path d="m19 19-3-2"></path><path d="m5 19 3-2"></path><path d="M20 13h-4"></path><path d="M4 13h4"></path><path d="m10 4 1 2"></path><path d="m14 4-1 2"></path></svg>',
},
example: {
keyword: "example",
color: "#7c4dff",
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-list"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>',
},
quote: {
keyword: "quote",
color: "#9e9e9e",
svg: '<svg viewBox="0 0 100 100" class="quote-glyph" width="16" height="16"><path fill="currentColor" stroke="currentColor" d="M16.7,13.3c-3.7,0-6.7,3-6.7,6.7v26.7c0,3.7,3,6.7,6.7,6.7h13.5c0.1,6-0.5,18.7-6.3,28.2c0,0,0,0,0,0 c-0.9,1.4-0.7,3.1,0.5,4.2c1.2,1.1,3,1.2,4.3,0.2c0,0,14.7-11.2,14.7-32.7V20c0-3.7-3-6.7-6.7-6.7L16.7,13.3z M63.3,13.3 c-3.7,0-6.7,3-6.7,6.7v26.7c0,3.7,3,6.7,6.7,6.7h13.5c0.1,6-0.5,18.7-6.3,28.2h0c-0.9,1.4-0.7,3.1,0.5,4.2c1.2,1.1,3,1.2,4.3,0.2 c0,0,14.7-11.2,14.7-32.7V20c0-3.7-3-6.7-6.7-6.7L63.3,13.3z"></path></svg>',
},
};

View File

@ -0,0 +1,285 @@
import { visit } from "unist-util-visit";
import { fromMarkdown } from "mdast-util-from-markdown";
import type { Plugin } from "unified";
import type { Node, Data, Parent } from "unist";
import type { Blockquote, Heading, Text, BlockContent } from "mdast";
import { parse } from "svg-parser";
import { calloutTypes } from "./calloutTypes";
// escape regex special characters
function escapeRegExp(s: string) {
return s.replace(new RegExp(`[-[\\]{}()*+?.\\\\^$|/]`, "g"), "\\$&");
}
// match breaks
const find = /[\t ]*(?:\r?\n|\r)/g;
export const callouts: Plugin = function (providedConfig?: Partial<Config>) {
const config: Config = { ...defaultConfig, ...providedConfig };
const defaultKeywords: string = Object.keys(config.types)
.map(escapeRegExp)
.join("|");
return function (tree) {
visit(tree, (node: Node, index, parent: Parent<Node>) => {
// Filter required elems
if (node.type !== "blockquote") return;
/** add breaks to text without needing spaces or escapes (turns enters into <br>)
* code taken directly from remark-breaks,
* see https://github.com/remarkjs/remark-breaks for more info on what this does.
*/
visit(node, "text", (node: Text, index: number, parent: Parent) => {
const result = [];
let start = 0;
find.lastIndex = 0;
let match = find.exec(node.value);
while (match) {
const position = match.index;
if (start !== position) {
result.push({
type: "text",
value: node.value.slice(start, position),
});
}
result.push({ type: "break" });
start = position + match[0].length;
match = find.exec(node.value);
}
if (result.length > 0 && parent && typeof index === "number") {
if (start < node.value.length) {
result.push({ type: "text", value: node.value.slice(start) });
}
parent.children.splice(index, 1, ...result);
return index + result.length;
}
});
/** add classnames to headings within blockquotes,
* mainly to identify when using other plugins that
* might interfere. for eg, rehype-auto-link-headings.
*/
visit(node, "heading", (node) => {
const heading = node as Heading;
heading.data = {
hProperties: {
className: "blockquote-heading",
},
};
});
// wrap blockquote in a div
const wrapper = {
...node,
type: "element",
tagName: "div",
data: {
hProperties: {},
},
children: [node],
};
parent.children.splice(Number(index), 1, wrapper);
const blockquote = wrapper.children[0] as Blockquote;
blockquote.data = {
hProperties: {
className: "blockquote",
},
};
// check for callout syntax starts here
if (
blockquote.children.length <= 0 ||
blockquote.children[0].type !== "paragraph"
)
return;
const paragraph = blockquote.children[0];
if (
paragraph.children.length <= 0 ||
paragraph.children[0].type !== "text"
)
return;
const [t, ...rest] = paragraph.children;
const regex = new RegExp(
`^\\[!(?<keyword>(.*?))\\][\t\f ]?(?<title>.*?)$`,
"gi"
);
const m = regex.exec(t.value);
// if no callout syntax, forget about it.
if (!m) return;
const [key, title] = [m.groups?.keyword, m.groups?.title];
// if there's nothing inside the brackets, is it really a callout ?
if (!key) return;
const keyword = key.toLowerCase();
const isOneOfKeywords: boolean = new RegExp(defaultKeywords).test(
keyword
);
if (title) {
const mdast = fromMarkdown(title.trim()).children[0];
if (mdast.type === "heading") {
mdast.data = {
...mdast.data,
hProperties: {
className: "blockquote-heading",
},
};
}
blockquote.children.unshift(mdast as BlockContent);
} else {
t.value =
typeof keyword.charAt(0) === "string"
? keyword.charAt(0).toUpperCase() + keyword.slice(1)
: keyword;
}
const entry: { [index: string]: string } = {};
if (isOneOfKeywords) {
if (typeof config?.types[keyword] === "string") {
const e = String(config?.types[keyword]);
Object.assign(entry, config?.types[e]);
} else {
Object.assign(entry, config?.types[keyword]);
}
} else {
Object.assign(entry, config?.types["note"]);
}
let parsedSvg;
if (entry && entry.svg) {
parsedSvg = parse(entry.svg);
}
// create icon and title node wrapped in div
const titleNode: object = {
type: "element",
children: [
{
type: "element",
tagName: "span",
data: {
hName: "span",
hProperties: {
style: `color:${entry?.color}`,
className: "callout-icon",
},
hChildren: parsedSvg?.children ? parsedSvg.children : [],
},
},
{
type: "element",
children: title ? [blockquote.children[0]] : [t],
data: {
hName: "strong",
},
},
],
data: {
...blockquote.children[0]?.data,
hProperties: {
className: `${formatClassNameMap(config.classNameMaps.title)(
keyword
)} ${isOneOfKeywords ? keyword : "note"}`,
style: `background-color: ${entry?.color}1a;`,
},
},
};
// remove the callout paragraph from the content body
if (title) {
blockquote.children.shift();
}
if (rest.length > 0) {
rest.shift();
paragraph.children = rest;
} else {
blockquote.children.shift();
}
// wrap blockquote content in div
const contentNode: object = {
type: "element",
children: blockquote.children,
data: {
hProperties: {
className: "callout-content",
style:
parent.type !== "root"
? `border-right:1px solid ${entry?.color}33;
border-bottom:1px solid ${entry?.color}33;`
: "",
},
},
};
if (blockquote.children.length > 0)
blockquote.children = [contentNode] as BlockContent[];
blockquote.children.unshift(titleNode as BlockContent);
// Add classes for the callout block
blockquote.data = config.dataMaps.block({
...blockquote.data,
hProperties: {
className: formatClassNameMap(config.classNameMaps.block)(
keyword.toLowerCase()
),
style: `border-left-color:${entry?.color};`,
},
});
});
};
};
export interface Config {
classNameMaps: {
block: ClassNameMap;
title: ClassNameMap;
};
dataMaps: {
block: (data: Data) => Data;
title: (data: Data) => Data;
};
types: { [index: string]: string | object };
}
export const defaultConfig: Config = {
classNameMaps: {
block: "callout",
title: "callout-title",
},
dataMaps: {
block: (data) => data,
title: (data) => data,
},
types: { ...calloutTypes },
};
type ClassNames = string | string[];
type ClassNameMap = ClassNames | ((title: string) => ClassNames);
function formatClassNameMap(gen: ClassNameMap) {
return (title: string) => {
const classNames = typeof gen == "function" ? gen(title) : gen;
return typeof classNames == "object" ? classNames.join(" ") : classNames;
};
}
export default callouts;

View File

@ -0,0 +1,64 @@
:root {
--callout-bg-color: #f2f3f5;
}
:root.dark {
--callout-bg-color: #161616;
}
.blockquote,
.callout {
background: #f2f3f5;
background: var(--callout-bg-color);
font-style: normal;
border-radius: 2px;
}
.callout {
padding: 0 !important;
}
.callout-title {
display: flex;
align-items: center;
padding: 10px;
gap: 10px;
}
.callout-title > strong {
font-weight: 700;
}
.blockquote,
.callout-content {
padding: 10px 20px;
}
.blockquote-heading {
margin: 5px 0 !important;
padding: 0 !important;
}
.blockquote > p,
.callout-content > p {
font-weight: normal;
margin: 5px 0;
}
.callout-title p {
margin: 0;
}
.callout-title > strong {
line-height: 1.5;
}
.callout p:before,
p:after {
display: none;
}
.blockquote > p:before,
p:after {
display: none;
}

View File

@ -0,0 +1,143 @@
import { expect } from "chai";
import { parseDocument } from "htmlparser2";
import { selectOne } from "css-select";
import { remark } from "remark";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import { callouts, Config } from "../src";
async function mdToHtml(md: string, options?: Partial<Config>) {
return String(
await remark()
.use(remarkParse)
.use(callouts, options)
.use(remarkRehype)
.use(rehypeStringify)
.process(md)
);
}
describe("remark callouts", function () {
it("parses a blockquote without a callout", async function () {
const html = await mdToHtml(`\
> no callout
`);
const doc = parseDocument(html);
const blockquote = selectOne("div > blockquote.blockquote > p", doc);
expect(blockquote).to.have.nested.property("firstChild.data", "no callout");
});
it("parses a blockquote callout with title and content", async function () {
const html = await mdToHtml(`\
> [!tip]
> example content here
`);
const doc = parseDocument(html);
const calloutTitle = selectOne(
"div > blockquote.callout > div.callout-title.tip > strong",
doc
);
const calloutContent = selectOne(
"div > blockquote.callout > div.callout-content > p",
doc
);
expect(calloutTitle).to.have.nested.property("firstChild.data", "Tip");
expect(calloutContent).to.have.nested.property(
"firstChild.data",
"example content here"
);
});
it("parses a blockquote callout with case insensitive keyword", async function () {
const html = await mdToHtml(`\
> [!INFO]
`);
const doc = parseDocument(html);
const calloutTitle = selectOne(
"div > blockquote.callout > div.callout-title.info > strong",
doc
);
expect(calloutTitle).to.have.nested.property("firstChild.data", "Info");
});
it("parses a blockquote callout with an icon", async function () {
const html = await mdToHtml(`\
> [!tip]
> example content here
`);
const doc = parseDocument(html);
const calloutIcon = selectOne(
"div > blockquote.callout > div.callout-title.tip > span.callout-icon > svg",
doc
);
expect(calloutIcon).to.exist;
});
it("parses a blockquote callout with a custom title", async function () {
const html = await mdToHtml(`\
> [!tip] Custom Title
> content
`);
const doc = parseDocument(html);
const calloutTitle = selectOne(
"div > blockquote.callout > div.callout-title.tip > strong > p",
doc
);
expect(calloutTitle).to.have.nested.property(
"firstChild.data",
"Custom Title"
);
});
it("parses a blockquote callout with unknown type to use note", async function () {
const html = await mdToHtml(`\
> [!xyz]
> content
`);
const doc = parseDocument(html);
const calloutTitle = selectOne(
"div > blockquote.callout > div.callout-title.note > strong",
doc
);
expect(calloutTitle).to.have.nested.property("firstChild.data", "Xyz");
});
it("parses a blockquote callout with unknown type and custom title", async function () {
const html = await mdToHtml(`\
> [!xyz] Some title
> content
`);
const doc = parseDocument(html);
const calloutTitle = selectOne(
"div > blockquote.callout > div.callout-title.note > strong > p",
doc
);
expect(calloutTitle).to.have.nested.property(
"firstChild.data",
"Some title"
);
});
it("parses a nested blockquote with callout", async function () {
const html = await mdToHtml(`\
> [!note]
> content
> > [!info]
> > nested callout
`);
const doc = parseDocument(html);
const nestedCallout = selectOne(
"div > blockquote.callout > div.callout-content > div > blockquote.callout > div.callout-title > strong",
doc
);
expect(nestedCallout).to.have.nested.property("firstChild.data", "Info");
});
});

View File

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"target": "es2020",
"module": "es2020",
"types": ["node"],
"moduleResolution": "node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"exclude": ["**/*.spec.ts", "**/*.test.ts"],
"include": ["src/**/*.ts"]
}

View File

@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"module": "es2020",
"moduleResolution": "node",
"types": ["mocha", "node"]
},
"include": [
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx",
"**/*.test.js",
"**/*.spec.js",
"**/*.test.jsx",
"**/*.spec.jsx",
"**/*.d.ts"
]
}