diff --git a/packages/remark-callouts/.babelrc b/packages/remark-callouts/.babelrc
new file mode 100644
index 00000000..2fdda153
--- /dev/null
+++ b/packages/remark-callouts/.babelrc
@@ -0,0 +1,4 @@
+{
+ "presets": ["@babel/preset-env"],
+ "plugins": ["@babel/plugin-transform-runtime"]
+}
diff --git a/packages/remark-callouts/.eslintrc.json b/packages/remark-callouts/.eslintrc.json
new file mode 100644
index 00000000..9d9c0db5
--- /dev/null
+++ b/packages/remark-callouts/.eslintrc.json
@@ -0,0 +1,18 @@
+{
+ "extends": ["../../.eslintrc.json"],
+ "ignorePatterns": ["!**/*"],
+ "overrides": [
+ {
+ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+ "rules": {}
+ },
+ {
+ "files": ["*.ts", "*.tsx"],
+ "rules": {}
+ },
+ {
+ "files": ["*.js", "*.jsx"],
+ "rules": {}
+ }
+ ]
+}
diff --git a/packages/remark-callouts/.mocharc.yaml b/packages/remark-callouts/.mocharc.yaml
new file mode 100644
index 00000000..dbc2c67b
--- /dev/null
+++ b/packages/remark-callouts/.mocharc.yaml
@@ -0,0 +1,4 @@
+extension: [ts]
+node-option:
+ - experimental-specifier-resolution=node
+ - loader=ts-node/esm
diff --git a/packages/remark-callouts/README.md b/packages/remark-callouts/README.md
new file mode 100644
index 00000000..7f670cad
--- /dev/null
+++ b/packages/remark-callouts/README.md
@@ -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:
+
+
+
+## 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
+
+
+
+
+
+
+ Tip
+
+
+
+
+```
+
+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
diff --git a/packages/remark-callouts/package.json b/packages/remark-callouts/package.json
new file mode 100644
index 00000000..7056813d
--- /dev/null
+++ b/packages/remark-callouts/package.json
@@ -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"
+ }
+}
diff --git a/packages/remark-callouts/project.json b/packages/remark-callouts/project.json
new file mode 100644
index 00000000..02ef1f8d
--- /dev/null
+++ b/packages/remark-callouts/project.json
@@ -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": []
+}
diff --git a/packages/remark-callouts/src/index.ts b/packages/remark-callouts/src/index.ts
new file mode 100644
index 00000000..28c2aea5
--- /dev/null
+++ b/packages/remark-callouts/src/index.ts
@@ -0,0 +1,2 @@
+export * from "./lib/remark-callouts";
+export { default } from "./lib/remark-callouts";
diff --git a/packages/remark-callouts/src/lib/calloutTypes.ts b/packages/remark-callouts/src/lib/calloutTypes.ts
new file mode 100644
index 00000000..a2dc81c4
--- /dev/null
+++ b/packages/remark-callouts/src/lib/calloutTypes.ts
@@ -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: '',
+ },
+ tip: {
+ keyword: "tip",
+ color: "#00bfa6",
+ svg: '',
+ },
+ warning: {
+ keyword: "warning",
+ color: "#ff9100",
+ svg: '',
+ },
+ abstract: {
+ keyword: "abstract",
+ color: "#00aeff",
+ svg: '',
+ },
+ info: {
+ keyword: "info",
+ color: "#00b8d4",
+ svg: '',
+ },
+ todo: {
+ keyword: "todo",
+ color: "#00b8d4",
+ svg: '',
+ },
+ success: {
+ keyword: "success",
+ color: "#00c853",
+ svg: '',
+ },
+ question: {
+ keyword: "question",
+ color: "#63dd17",
+ svg: '',
+ },
+ failure: {
+ keyword: "failure",
+ color: "#ff5252",
+ svg: '',
+ },
+ danger: {
+ keyword: "danger",
+ color: "#ff1745",
+ svg: '',
+ },
+ bug: {
+ keyword: "bug",
+ color: "#f50057",
+ svg: '',
+ },
+ example: {
+ keyword: "example",
+ color: "#7c4dff",
+ svg: '',
+ },
+ quote: {
+ keyword: "quote",
+ color: "#9e9e9e",
+ svg: '',
+ },
+};
diff --git a/packages/remark-callouts/src/lib/remark-callouts.ts b/packages/remark-callouts/src/lib/remark-callouts.ts
new file mode 100644
index 00000000..4924638f
--- /dev/null
+++ b/packages/remark-callouts/src/lib/remark-callouts.ts
@@ -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) {
+ 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) => {
+ // Filter required elems
+ if (node.type !== "blockquote") return;
+
+ /** add breaks to text without needing spaces or escapes (turns enters into
)
+ * 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(
+ `^\\[!(?(.*?))\\][\t\f ]?(?.*?)$`,
+ "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;
diff --git a/packages/remark-callouts/styles.css b/packages/remark-callouts/styles.css
new file mode 100644
index 00000000..d9609226
--- /dev/null
+++ b/packages/remark-callouts/styles.css
@@ -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;
+}
diff --git a/packages/remark-callouts/test/remark-callouts.spec.ts b/packages/remark-callouts/test/remark-callouts.spec.ts
new file mode 100644
index 00000000..518c5b85
--- /dev/null
+++ b/packages/remark-callouts/test/remark-callouts.spec.ts
@@ -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) {
+ 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");
+ });
+});
diff --git a/packages/remark-callouts/tsconfig.json b/packages/remark-callouts/tsconfig.json
new file mode 100644
index 00000000..62ebbd94
--- /dev/null
+++ b/packages/remark-callouts/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ },
+ {
+ "path": "./tsconfig.spec.json"
+ }
+ ]
+}
diff --git a/packages/remark-callouts/tsconfig.lib.json b/packages/remark-callouts/tsconfig.lib.json
new file mode 100644
index 00000000..18857e49
--- /dev/null
+++ b/packages/remark-callouts/tsconfig.lib.json
@@ -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"]
+}
diff --git a/packages/remark-callouts/tsconfig.spec.json b/packages/remark-callouts/tsconfig.spec.json
new file mode 100644
index 00000000..094684db
--- /dev/null
+++ b/packages/remark-callouts/tsconfig.spec.json
@@ -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"
+ ]
+}