Integrate flowershow packages (#923)
* [packages][m]: mv @flowershow/core package here * [packages/core][xs]: rename to @portaljs/core * [package.json][xs]: setup npm workspaces * [packages/core][xs]:replace deprecated rollup executor * [core/package.json][s]: fix mermaid versions * [core/tsconfig][xs]: rm extends * [core/jest.config][xs]: rm coverageDirectory * [core/package.json][xs]: install core-js * [packages.json][s]:use same version for all nrwl packages * [core/.eslintrc][xs]: adjust ignorePatterns * [core/project.json][xs]: rm publish targets * [packages][m]: mv @flowershow/remark-wiki-link here * [packages][m]: mv @flowershow/remark-wiki-link here * [packages][m]: mv @flowershow/remark-embed here * [remark-callouts/project.json][xs]: adjst test pattern * [package.json][s]: install missing deps * [remark-callouts][xs]: adjst fields in package.json * [remark-callouts][s]: rm pubish targets and adjst build executor * [remark-embed/jest.config][xs]: rm unknown option coverageDirectory * [remark-embed][xs]: rm publish targets * [remark-embed][s]: rename to @portaljs/remark-embed * [remark-wiki-link/eslintrc][xs]:adjst ignorePatterns * [package.json][xs]: install missing deps * [remark-wiki-link/test][xs]:specify format - also temporarily force any type on htmlExtension * [remark-wiki-link/README][xs]: replace @flowershow with @portaljs * [remark-wiki-link][xs]:rm old changelog * [remark-wiki-link][xs]: adjst package.json * [remark-wiki-link/project.json][xs]: rm publish targets * [core][s]: rm old changelog * [core/README][xs]:correct scope name * [remark-callouts/README][xs]: add @portaljs to pckg name * [remark-embed/README][xs]: add @portaljs to pckg name * [package-lock.json][xs]: refresh after rebasing on main
This commit is contained in:
4
packages/remark-wiki-link/.babelrc
Normal file
4
packages/remark-wiki-link/.babelrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env"],
|
||||
"plugins": ["@babel/plugin-transform-runtime"]
|
||||
}
|
||||
7
packages/remark-wiki-link/.eslintrc.json
Normal file
7
packages/remark-wiki-link/.eslintrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*", "dist/**/*"],
|
||||
"env": {
|
||||
"jest": true
|
||||
}
|
||||
}
|
||||
2
packages/remark-wiki-link/.npmignore
Normal file
2
packages/remark-wiki-link/.npmignore
Normal file
@@ -0,0 +1,2 @@
|
||||
src
|
||||
test
|
||||
132
packages/remark-wiki-link/README.md
Normal file
132
packages/remark-wiki-link/README.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# remark-wiki-link
|
||||
|
||||
Parse and render wiki-style links in markdown especially Obsidian style links.
|
||||
|
||||
## What is this ?
|
||||
|
||||
Using obsidian, when we type in wiki link syntax for eg. `[[wiki_link]]` it would parse them as anchors.
|
||||
|
||||
## Features supported
|
||||
|
||||
- [x] Support `[[Internal link]]`
|
||||
- [x] Support `[[Internal link|With custom text]]`
|
||||
- [x] Support `[[Internal link#heading]]`
|
||||
- [x] Support `[[Internal link#heading|With custom text]]`
|
||||
- [x] Support `![[Document.pdf]]`
|
||||
- [x] Support `![[Image.png]]`
|
||||
|
||||
* Supported image formats are jpg, jpeg, png, apng, webp, gif, svg, bmp, ico
|
||||
* Unsupported image formats will display a raw wiki link string, e.g. `[[Image.xyz]]`.
|
||||
|
||||
Future support:
|
||||
|
||||
- [ ] Support `![[Audio.mp3]]`
|
||||
- [ ] Support `![[Video.mp4]]`
|
||||
- [ ] Support `![[Embed note]]`
|
||||
- [ ] Support `![[Embed note#heading]]`
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @portaljs/remark-wiki-link
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
import unified from "unified";
|
||||
import markdown from "remark-parse";
|
||||
import wikiLinkPlugin from "@portaljs/remark-wiki-link";
|
||||
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
```
|
||||
|
||||
## Configuration options
|
||||
|
||||
### `pathFormat`
|
||||
|
||||
Type: `"raw" | "obisidan-absolute" | "obsidian-short"`
|
||||
Default: `"raw"`
|
||||
|
||||
- `"raw"`: use this option for regular relative or absolute paths (or Obsidian relative paths), e.g. `[[../some/folder/file]]` or `[[[/some/folder/file]]]`,
|
||||
- `"obsidian-absolute"`: use this option for Obsidian absolute paths, i.e. paths with no leading `/`, e.g. `[[some/folder/file]]`
|
||||
- `"obsidian-short"`: use this option for Obsidian shortened paths, e.g. `[[file]]` to resolve them to absolute paths. Note that apart from setting this value, you will also need to pass a list of paths to files in your content folder, and pass it as `permalinks` option. You can generate this list yourself or use our util function `getPermalinks`. See below for more info.
|
||||
|
||||
> [!note]
|
||||
> Wiki link format in Obsidian can be configured in Settings -> Files & Links -> New link format.
|
||||
|
||||
### `aliasDivider`
|
||||
|
||||
Type: single character string
|
||||
Default: `"|"`
|
||||
|
||||
Alias divider character used in your wiki links. E.g. `[[/some/folder/file|Alias]]`
|
||||
|
||||
### `permalinks`
|
||||
|
||||
Type: `Array<string>`
|
||||
Default: `[]`
|
||||
|
||||
A list of permalinks you want to match your wiki link paths with. Wiki links with matched permalinks will have `node.data.exists` property set to `true`. Wiki links with no matching permalinks will also have additional class `new` set.
|
||||
|
||||
### `wikiLinkResolver`
|
||||
|
||||
Type: `(name: string) => Array<string>`
|
||||
Default: `(name: string) => name.replace(/\/index$/, "")` (simplified; see source code for full version)
|
||||
|
||||
A function that will take the wiki link target page (e.g. `"/some/folder/file"` in `[[/some/folder/file#Some Heading|Some Alias]]` wiki link) and return an array of pages to which the wiki link **can** be resolved (one of them will be used, depending on wheather `pemalinks` are passed, and if match is found).
|
||||
|
||||
If `permalinks` are passed, the resulting array will be matched against these permalinks to find the match. The matching pemalink will be used as node's `href` (or `src` for images).
|
||||
|
||||
If no matching permalink is found, the first item from the array returned by this function will be used as a node's `href` (or `src` for images). So, if you want to write a custom wiki link -> url
|
||||
|
||||
### `newClassName`
|
||||
|
||||
Type: `string`
|
||||
Default: `"new"`
|
||||
|
||||
Class name added to nodes created for wiki links for which no matching permalink (passed in `permalinks` option) was found.
|
||||
|
||||
### `wikiLinkClassName`
|
||||
|
||||
Type: `string`
|
||||
Default: `"internal"`
|
||||
|
||||
Class name added to all wiki link nodes.
|
||||
|
||||
### `hrefTemplate`
|
||||
|
||||
Type: `(permalink: string) => string`
|
||||
Default: `(permalink: string) => permalink`
|
||||
|
||||
A function that will be used to convert a matched permalink of the wiki link to `href` (or `src` for images).
|
||||
|
||||
### `markdownFolder` ❌ (deprecated as of version 1.1.0)
|
||||
|
||||
A string that points to the content folder, that will be used to resolve Obsidian shortened wiki link path format.
|
||||
|
||||
Instead of using this option, use e.g. `getPermalinks` util function exported from this package to generate a list of permalinks from your content folder, and pass it explicitly as `permalinks` option.
|
||||
|
||||
## Generating list of permalinks from content folder with `getPermalinks`
|
||||
|
||||
If you're using shortened path format for your Obsidian wiki links, in order to resolve them correctly to paths they point to, you need to set `option.pathFormat: "obsidian-short"` but also provide the plugin with a list of permalinks that point to files in your content folder as `option.permalinks`. You can use your own script to generate this list or use our util function `getPermalinks` like so:
|
||||
|
||||
```javascript {4,6,11-12}
|
||||
import unified from "unified";
|
||||
import markdown from "remark-parse";
|
||||
import wikiLinkPlugin from "@portaljs/remark-wiki-link";
|
||||
import { getPermalinks } from "@portaljs/remark-wiki-link";
|
||||
|
||||
const permalinks = await getPermalinks("path-to-your-content-folder");
|
||||
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin, {
|
||||
pathFormat: "obsidian-short",
|
||||
permalinks,
|
||||
});
|
||||
```
|
||||
|
||||
## Running tests
|
||||
|
||||
```bash
|
||||
pnpm nx test remark-wiki-link
|
||||
```
|
||||
14
packages/remark-wiki-link/jest.config.ts
Normal file
14
packages/remark-wiki-link/jest.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { JestConfigWithTsJest } from "ts-jest";
|
||||
|
||||
const jestConfig: JestConfigWithTsJest = {
|
||||
displayName: "remark-wiki-link",
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
transform: {
|
||||
"^.+\\.[tj]s?$": "ts-jest",
|
||||
},
|
||||
moduleFileExtensions: ["ts", "js"],
|
||||
transformIgnorePatterns: ["<rootDir>/node_modules/(?!remark-parse)"],
|
||||
};
|
||||
|
||||
export default jestConfig;
|
||||
43
packages/remark-wiki-link/package.json
Normal file
43
packages/remark-wiki-link/package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@portaljs/remark-wiki-link",
|
||||
"version": "1.0.0",
|
||||
"description": "Parse and render wiki-style links in markdown especially Obsidian style links.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/datopian/portaljs.git",
|
||||
"directory": "packages/remark-wiki-link"
|
||||
},
|
||||
"keywords": [
|
||||
"remark",
|
||||
"remark-plugin",
|
||||
"markdown",
|
||||
"gfm",
|
||||
"obsidian"
|
||||
],
|
||||
"author": "Rufus Pollock",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/datopian/portaljs/issues"
|
||||
},
|
||||
"homepage": "https://github.com/datopian/portaljs#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"mdast-util-to-markdown": "^1.5.0",
|
||||
"mdast-util-wiki-link": "^0.0.2",
|
||||
"micromark-util-symbol": "^1.0.1"
|
||||
},
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"micromark": "^3.1.0",
|
||||
"remark-gfm": "^3.0.1"
|
||||
}
|
||||
}
|
||||
45
packages/remark-wiki-link/project.json
Normal file
45
packages/remark-wiki-link/project.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "remark-wiki-link",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/remark-wiki-link/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["packages/remark-wiki-link/**/*.{ts,js}"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "packages/remark-wiki-link/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"executor": "@nrwl/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"entryFile": "packages/remark-wiki-link/src/index.ts",
|
||||
"outputPath": "packages/remark-wiki-link/dist",
|
||||
"compiler": "babel",
|
||||
"tsConfig": "packages/remark-wiki-link/tsconfig.lib.json",
|
||||
"project": "packages/remark-wiki-link/package.json",
|
||||
"format": ["esm", "cjs"],
|
||||
"external": ["mdast-util-wiki-link"],
|
||||
"generateExportsField": true,
|
||||
"assets": [
|
||||
{
|
||||
"glob": "packages/remark-wiki-link/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
3
packages/remark-wiki-link/src/index.ts
Normal file
3
packages/remark-wiki-link/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./lib/remarkWikiLink";
|
||||
export { default } from "./lib/remarkWikiLink";
|
||||
export { getPermalinks } from "./utils";
|
||||
172
packages/remark-wiki-link/src/lib/fromMarkdown.ts
Normal file
172
packages/remark-wiki-link/src/lib/fromMarkdown.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { isSupportedFileFormat } from "./isSupportedFileFormat";
|
||||
|
||||
const defaultWikiLinkResolver = (target: string) => {
|
||||
// for [[#heading]] links
|
||||
if (!target) {
|
||||
return [];
|
||||
}
|
||||
let permalink = target.replace(/\/index$/, "");
|
||||
// TODO what to do with [[index]] link?
|
||||
if (permalink.length === 0) {
|
||||
permalink = "/";
|
||||
}
|
||||
return [permalink];
|
||||
};
|
||||
|
||||
export interface FromMarkdownOptions {
|
||||
pathFormat?:
|
||||
| "raw" // default; use for regular relative or absolute paths
|
||||
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
|
||||
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
|
||||
permalinks?: string[]; // list of permalinks to match possible permalinks of a wiki link against
|
||||
wikiLinkResolver?: (name: string) => string[]; // function to resolve wiki links to an array of possible permalinks
|
||||
newClassName?: string; // class name to add to links that don't have a matching permalink
|
||||
wikiLinkClassName?: string; // class name to add to all wiki links
|
||||
hrefTemplate?: (permalink: string) => string; // function to generate the href attribute of a link
|
||||
}
|
||||
|
||||
// mdas-util-from-markdown extension
|
||||
// https://github.com/syntax-tree/mdast-util-from-markdown#extension
|
||||
function fromMarkdown(opts: FromMarkdownOptions = {}) {
|
||||
const pathFormat = opts.pathFormat || "raw";
|
||||
const permalinks = opts.permalinks || [];
|
||||
const wikiLinkResolver = opts.wikiLinkResolver || defaultWikiLinkResolver;
|
||||
const newClassName = opts.newClassName || "new";
|
||||
const wikiLinkClassName = opts.wikiLinkClassName || "internal";
|
||||
const defaultHrefTemplate = (permalink: string) => permalink;
|
||||
|
||||
const hrefTemplate = opts.hrefTemplate || defaultHrefTemplate;
|
||||
|
||||
function top(stack) {
|
||||
return stack[stack.length - 1];
|
||||
}
|
||||
|
||||
function enterWikiLink(token) {
|
||||
this.enter(
|
||||
{
|
||||
type: "wikiLink",
|
||||
data: {
|
||||
isEmbed: token.isType === "embed",
|
||||
target: null, // the target of the link, e.g. "Foo Bar#Heading" in "[[Foo Bar#Heading]]"
|
||||
alias: null, // the alias of the link, e.g. "Foo" in "[[Foo Bar|Foo]]"
|
||||
permalink: null, // TODO shouldn't this be named just "link"?
|
||||
exists: null, // TODO is this even needed here?
|
||||
// fields for mdast-util-to-hast (used e.g. by remark-rehype)
|
||||
hName: null,
|
||||
hProperties: null,
|
||||
hChildren: null,
|
||||
},
|
||||
},
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
function exitWikiLinkTarget(token) {
|
||||
const target = this.sliceSerialize(token);
|
||||
const current = top(this.stack);
|
||||
current.data.target = target;
|
||||
}
|
||||
|
||||
function exitWikiLinkAlias(token) {
|
||||
const alias = this.sliceSerialize(token);
|
||||
const current = top(this.stack);
|
||||
current.data.alias = alias;
|
||||
}
|
||||
|
||||
function exitWikiLink(token) {
|
||||
const wikiLink = this.exit(token);
|
||||
const {
|
||||
data: { isEmbed, target, alias },
|
||||
} = wikiLink;
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const wikiLinkWithHeadingPattern = /([\w\s\/\.-]*)(#.*)?/;
|
||||
const [, path, heading = ""] = target.match(wikiLinkWithHeadingPattern);
|
||||
|
||||
const possibleWikiLinkPermalinks = wikiLinkResolver(path);
|
||||
|
||||
const matchingPermalink = permalinks.find((e) => {
|
||||
return possibleWikiLinkPermalinks.find((p) => {
|
||||
if (pathFormat === "obsidian-short") {
|
||||
if (e === p || e.endsWith(p)) {
|
||||
return true;
|
||||
}
|
||||
} else if (pathFormat === "obsidian-absolute") {
|
||||
if (e === "/" + p) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (e === p) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// TODO this is ugly
|
||||
const link =
|
||||
matchingPermalink ||
|
||||
(pathFormat === "obsidian-absolute"
|
||||
? "/" + possibleWikiLinkPermalinks[0]
|
||||
: possibleWikiLinkPermalinks[0]) ||
|
||||
"";
|
||||
|
||||
wikiLink.data.exists = !!matchingPermalink;
|
||||
wikiLink.data.permalink = link;
|
||||
|
||||
// remove leading # if the target is a heading on the same page
|
||||
const displayName = alias || target.replace(/^#/, "");
|
||||
const headingId = heading.replace(/\s+/, "-").toLowerCase();
|
||||
let classNames = wikiLinkClassName;
|
||||
if (!matchingPermalink) {
|
||||
classNames += " " + newClassName;
|
||||
}
|
||||
|
||||
if (isEmbed) {
|
||||
const [isSupportedFormat, format] = isSupportedFileFormat(target);
|
||||
if (!isSupportedFormat) {
|
||||
wikiLink.data.hName = "p";
|
||||
wikiLink.data.hChildren = [
|
||||
{
|
||||
type: "text",
|
||||
value: `![[${target}]]`,
|
||||
},
|
||||
];
|
||||
} else if (format === "pdf") {
|
||||
wikiLink.data.hName = "iframe";
|
||||
wikiLink.data.hProperties = {
|
||||
className: classNames,
|
||||
width: "100%",
|
||||
src: `${hrefTemplate(link)}#toolbar=0`,
|
||||
};
|
||||
} else {
|
||||
wikiLink.data.hName = "img";
|
||||
wikiLink.data.hProperties = {
|
||||
className: classNames,
|
||||
src: hrefTemplate(link),
|
||||
alt: displayName,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
wikiLink.data.hName = "a";
|
||||
wikiLink.data.hProperties = {
|
||||
className: classNames,
|
||||
href: hrefTemplate(link) + headingId,
|
||||
};
|
||||
wikiLink.data.hChildren = [{ type: "text", value: displayName }];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
enter: {
|
||||
wikiLink: enterWikiLink,
|
||||
},
|
||||
exit: {
|
||||
wikiLinkTarget: exitWikiLinkTarget,
|
||||
wikiLinkAlias: exitWikiLinkAlias,
|
||||
wikiLink: exitWikiLink,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { fromMarkdown };
|
||||
146
packages/remark-wiki-link/src/lib/html.ts
Normal file
146
packages/remark-wiki-link/src/lib/html.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { isSupportedFileFormat } from "./isSupportedFileFormat";
|
||||
|
||||
const defaultWikiLinkResolver = (target: string) => {
|
||||
// for [[#heading]] links
|
||||
if (!target) {
|
||||
return [];
|
||||
}
|
||||
let permalink = target.replace(/\/index$/, "");
|
||||
// TODO what to do with [[index]] link?
|
||||
if (permalink.length === 0) {
|
||||
permalink = "/";
|
||||
}
|
||||
return [permalink];
|
||||
};
|
||||
|
||||
export interface HtmlOptions {
|
||||
pathFormat?:
|
||||
| "raw" // default; use for regular relative or absolute paths
|
||||
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
|
||||
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
|
||||
permalinks?: string[]; // list of permalinks to match possible permalinks of a wiki link against
|
||||
wikiLinkResolver?: (name: string) => string[]; // function to resolve wiki links to an array of possible permalinks
|
||||
newClassName?: string; // class name to add to links that don't have a matching permalink
|
||||
wikiLinkClassName?: string; // class name to add to all wiki links
|
||||
hrefTemplate?: (permalink: string) => string; // function to generate the href attribute of a link
|
||||
}
|
||||
|
||||
// Micromark HtmlExtension
|
||||
// https://github.com/micromark/micromark#htmlextension
|
||||
function html(opts: HtmlOptions = {}) {
|
||||
const pathFormat = opts.pathFormat || "raw";
|
||||
const permalinks = opts.permalinks || [];
|
||||
const wikiLinkResolver = opts.wikiLinkResolver || defaultWikiLinkResolver;
|
||||
const newClassName = opts.newClassName || "new";
|
||||
const wikiLinkClassName = opts.wikiLinkClassName || "internal";
|
||||
const defaultHrefTemplate = (permalink: string) => permalink;
|
||||
const hrefTemplate = opts.hrefTemplate || defaultHrefTemplate;
|
||||
|
||||
function top(stack) {
|
||||
return stack[stack.length - 1];
|
||||
}
|
||||
|
||||
function enterWikiLink() {
|
||||
let stack = this.getData("wikiLinkStack");
|
||||
if (!stack) this.setData("wikiLinkStack", (stack = []));
|
||||
|
||||
stack.push({});
|
||||
}
|
||||
|
||||
function exitWikiLinkTarget(token) {
|
||||
const target = this.sliceSerialize(token);
|
||||
const current = top(this.getData("wikiLinkStack"));
|
||||
current.target = target;
|
||||
}
|
||||
|
||||
function exitWikiLinkAlias(token) {
|
||||
const alias = this.sliceSerialize(token);
|
||||
const current = top(this.getData("wikiLinkStack"));
|
||||
current.alias = alias;
|
||||
}
|
||||
|
||||
function exitWikiLink(token) {
|
||||
const wikiLink = this.getData("wikiLinkStack").pop();
|
||||
const { target, alias } = wikiLink;
|
||||
const isEmbed = token.isType === "embed";
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const wikiLinkWithHeadingPattern = /([\w\s\/\.-]*)(#.*)?/;
|
||||
const [, path, heading = ""] = target.match(wikiLinkWithHeadingPattern);
|
||||
|
||||
const possibleWikiLinkPermalinks = wikiLinkResolver(path);
|
||||
|
||||
const matchingPermalink = permalinks.find((e) => {
|
||||
return possibleWikiLinkPermalinks.find((p) => {
|
||||
if (pathFormat === "obsidian-short") {
|
||||
if (e === p || e.endsWith(p)) {
|
||||
return true;
|
||||
}
|
||||
} else if (pathFormat === "obsidian-absolute") {
|
||||
if (e === "/" + p) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (e === p) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// TODO this is ugly
|
||||
const link =
|
||||
matchingPermalink ||
|
||||
(pathFormat === "obsidian-absolute"
|
||||
? "/" + possibleWikiLinkPermalinks[0]
|
||||
: possibleWikiLinkPermalinks[0]) ||
|
||||
"";
|
||||
|
||||
// remove leading # if the target is a heading on the same page
|
||||
const displayName = alias || target.replace(/^#/, "");
|
||||
// replace spaces with dashes and lowercase headings
|
||||
const headingId = heading.replace(/\s+/, "-").toLowerCase();
|
||||
let classNames = wikiLinkClassName;
|
||||
if (!matchingPermalink) {
|
||||
classNames += " " + newClassName;
|
||||
}
|
||||
|
||||
if (isEmbed) {
|
||||
const [isSupportedFormat, format] = isSupportedFileFormat(target);
|
||||
if (!isSupportedFormat) {
|
||||
this.raw(`![[${target}]]`);
|
||||
} else if (format === "pdf") {
|
||||
this.tag(
|
||||
`<iframe width="100%" src="${hrefTemplate(
|
||||
link
|
||||
)}#toolbar=0" class="${classNames}" />`
|
||||
);
|
||||
} else {
|
||||
this.tag(
|
||||
`<img src="${hrefTemplate(
|
||||
link
|
||||
)}" alt="${displayName}" class="${classNames}" />`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.tag(
|
||||
`<a href="${hrefTemplate(link + headingId)}" class="${classNames}">`
|
||||
);
|
||||
this.raw(displayName);
|
||||
this.tag("</a>");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
enter: {
|
||||
wikiLink: enterWikiLink,
|
||||
},
|
||||
exit: {
|
||||
wikiLinkTarget: exitWikiLinkTarget,
|
||||
wikiLinkAlias: exitWikiLinkAlias,
|
||||
wikiLink: exitWikiLink,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { html };
|
||||
28
packages/remark-wiki-link/src/lib/isSupportedFileFormat.ts
Normal file
28
packages/remark-wiki-link/src/lib/isSupportedFileFormat.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// TODO why only these?
|
||||
export const supportedFileFormats = [
|
||||
"webp",
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"gif",
|
||||
"bmp",
|
||||
"svg",
|
||||
"apng",
|
||||
"png",
|
||||
"avif",
|
||||
"ico",
|
||||
"pdf",
|
||||
];
|
||||
|
||||
export const isSupportedFileFormat = (filePath: string): [boolean, string] => {
|
||||
const fileExtensionPattern = /\.([0-9a-z]{1,4})$/i;
|
||||
const match = filePath.match(fileExtensionPattern);
|
||||
|
||||
if (!match) {
|
||||
return [false, null];
|
||||
}
|
||||
|
||||
const [, extension] = match;
|
||||
const isSupported = supportedFileFormats.includes(extension);
|
||||
|
||||
return [isSupported, extension];
|
||||
};
|
||||
42
packages/remark-wiki-link/src/lib/remarkWikiLink.ts
Normal file
42
packages/remark-wiki-link/src/lib/remarkWikiLink.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { toMarkdown } from "mdast-util-wiki-link";
|
||||
import { syntax, SyntaxOptions } from "./syntax";
|
||||
import { fromMarkdown, FromMarkdownOptions } from "./fromMarkdown";
|
||||
|
||||
let warningIssued = false;
|
||||
|
||||
type RemarkWikiLinkOptions = FromMarkdownOptions & SyntaxOptions;
|
||||
|
||||
function remarkWikiLink(opts: RemarkWikiLinkOptions = {}) {
|
||||
const data = this.data(); // this is a reference to the processor
|
||||
|
||||
function add(field, value) {
|
||||
if (data[field]) data[field].push(value);
|
||||
else data[field] = [value];
|
||||
}
|
||||
|
||||
if (
|
||||
!warningIssued &&
|
||||
((this.Parser &&
|
||||
this.Parser.prototype &&
|
||||
this.Parser.prototype.blockTokenizers) ||
|
||||
(this.Compiler &&
|
||||
this.Compiler.prototype &&
|
||||
this.Compiler.prototype.visitors))
|
||||
) {
|
||||
warningIssued = true;
|
||||
console.warn(
|
||||
"[remark-wiki-link] Warning: please upgrade to remark 13 to use this plugin"
|
||||
);
|
||||
}
|
||||
|
||||
// add extensions to packages used by remark-parse
|
||||
// micromark extensions
|
||||
add("micromarkExtensions", syntax(opts));
|
||||
// mdast-util-from-markdown extensions
|
||||
add("fromMarkdownExtensions", fromMarkdown(opts));
|
||||
// mdast-util-to-markdown extensions
|
||||
add("toMarkdownExtensions", toMarkdown(opts));
|
||||
}
|
||||
|
||||
export default remarkWikiLink;
|
||||
export { remarkWikiLink };
|
||||
168
packages/remark-wiki-link/src/lib/syntax.ts
Normal file
168
packages/remark-wiki-link/src/lib/syntax.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
// Adjusted copy of https://github.com/landakram/micromark-extension-wiki-link/blob/master/src/index.js
|
||||
import { codes } from "micromark-util-symbol/codes.js";
|
||||
|
||||
export interface SyntaxOptions {
|
||||
aliasDivider?: string;
|
||||
}
|
||||
|
||||
function isEndOfLineOrFile(code: number) {
|
||||
return (
|
||||
code === codes.carriageReturnLineFeed ||
|
||||
code === codes.carriageReturn ||
|
||||
code === codes.lineFeed ||
|
||||
code === codes.eof
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Token types:
|
||||
* - `wikiLink`:
|
||||
* - `wikiLinkMarker`: The opening and closing brackets
|
||||
* - `wikiLinkData`: The data between the brackets
|
||||
* - `wikiLinkTarget`: The target of the link (the part before the alias divider)
|
||||
* - `wikiLinkAliasMarker`: The alias divider
|
||||
* - `wikiLinkAlias`: The alias of the link (the part after the alias divider)
|
||||
* */
|
||||
|
||||
function wikiLink(opts: SyntaxOptions = {}) {
|
||||
const aliasDivider = opts.aliasDivider || "|";
|
||||
|
||||
const aliasMarker = aliasDivider.charCodeAt(0);
|
||||
const startMarker = codes.leftSquareBracket;
|
||||
const embedStartMarker = codes.exclamationMark;
|
||||
const endMarker = codes.rightSquareBracket;
|
||||
|
||||
function tokenize(effects, ok, nok) {
|
||||
let data = false;
|
||||
let alias = false;
|
||||
|
||||
let startMarkerCount = 0;
|
||||
let endMarkerCount = 0;
|
||||
|
||||
return start;
|
||||
|
||||
// recognize the start of a wiki link
|
||||
function start(code: number) {
|
||||
if (code === startMarker) {
|
||||
effects.enter("wikiLink");
|
||||
effects.enter("wikiLinkMarker");
|
||||
|
||||
return consumeStart(code);
|
||||
} else if (code === embedStartMarker) {
|
||||
effects.enter("wikiLink", { isType: "embed" });
|
||||
effects.enter("wikiLinkMarker", { isType: "embed" });
|
||||
|
||||
return consumeStart(code);
|
||||
} else {
|
||||
return nok(code);
|
||||
}
|
||||
}
|
||||
function consumeStart(code: number) {
|
||||
// when coursor is at the first character after the start marker `[[`
|
||||
if (startMarkerCount === 2) {
|
||||
effects.exit("wikiLinkMarker");
|
||||
return consumeData(code);
|
||||
}
|
||||
|
||||
if (code === startMarker || code === embedStartMarker) {
|
||||
if (code === startMarker) {
|
||||
startMarkerCount++;
|
||||
}
|
||||
effects.consume(code);
|
||||
return consumeStart;
|
||||
} else {
|
||||
return nok(code);
|
||||
}
|
||||
}
|
||||
|
||||
function consumeData(code: number) {
|
||||
if (isEndOfLineOrFile(code)) {
|
||||
return nok(code);
|
||||
}
|
||||
|
||||
effects.enter("wikiLinkData");
|
||||
effects.enter("wikiLinkTarget");
|
||||
return consumeTarget(code);
|
||||
}
|
||||
|
||||
function consumeTarget(code: number) {
|
||||
if (code === aliasMarker) {
|
||||
if (!data) return nok(code);
|
||||
effects.exit("wikiLinkTarget");
|
||||
effects.enter("wikiLinkAliasMarker");
|
||||
return consumeAliasMarker(code);
|
||||
}
|
||||
|
||||
if (code === endMarker) {
|
||||
if (!data) return nok(code);
|
||||
effects.exit("wikiLinkTarget");
|
||||
effects.exit("wikiLinkData");
|
||||
effects.enter("wikiLinkMarker");
|
||||
return consumeEnd(code);
|
||||
}
|
||||
|
||||
if (isEndOfLineOrFile(code)) {
|
||||
return nok(code);
|
||||
}
|
||||
|
||||
data = true;
|
||||
effects.consume(code);
|
||||
|
||||
return consumeTarget;
|
||||
}
|
||||
|
||||
function consumeAliasMarker(code) {
|
||||
effects.consume(code);
|
||||
effects.exit("wikiLinkAliasMarker");
|
||||
effects.enter("wikiLinkAlias");
|
||||
return consumeAlias(code);
|
||||
}
|
||||
|
||||
function consumeAlias(code: number) {
|
||||
if (code === endMarker) {
|
||||
if (!alias) return nok(code);
|
||||
effects.exit("wikiLinkAlias");
|
||||
effects.exit("wikiLinkData");
|
||||
effects.enter("wikiLinkMarker");
|
||||
return consumeEnd(code);
|
||||
}
|
||||
|
||||
if (isEndOfLineOrFile(code)) {
|
||||
return nok(code);
|
||||
}
|
||||
|
||||
alias = true;
|
||||
effects.consume(code);
|
||||
|
||||
return consumeAlias;
|
||||
}
|
||||
|
||||
function consumeEnd(code: number) {
|
||||
if (endMarkerCount === 2) {
|
||||
effects.exit("wikiLinkMarker");
|
||||
effects.exit("wikiLink");
|
||||
return ok(code);
|
||||
}
|
||||
|
||||
if (code !== endMarker) {
|
||||
return nok(code);
|
||||
}
|
||||
|
||||
effects.consume(code);
|
||||
endMarkerCount++;
|
||||
|
||||
return consumeEnd;
|
||||
}
|
||||
}
|
||||
|
||||
const wikiLinkConstruct = { tokenize };
|
||||
|
||||
return {
|
||||
text: {
|
||||
[codes.leftSquareBracket]: wikiLinkConstruct,
|
||||
[codes.exclamationMark]: wikiLinkConstruct,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { wikiLink as syntax };
|
||||
43
packages/remark-wiki-link/src/utils/getPermalinks.ts
Normal file
43
packages/remark-wiki-link/src/utils/getPermalinks.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
// recursively get all files in a directory
|
||||
const recursiveGetFiles = (dir: string) => {
|
||||
const dirents = fs.readdirSync(dir, { withFileTypes: true });
|
||||
const files = dirents
|
||||
.filter((dirent) => dirent.isFile())
|
||||
.map((dirent) => path.join(dir, dirent.name));
|
||||
const dirs = dirents
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => path.join(dir, dirent.name));
|
||||
for (const d of dirs) {
|
||||
files.push(...recursiveGetFiles(d));
|
||||
}
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
export const getPermalinks = (
|
||||
markdownFolder: string,
|
||||
ignorePatterns: Array<RegExp> = [],
|
||||
func: (str: any, ...args: any[]) => string = defaultPathToPermalinkFunc
|
||||
) => {
|
||||
const files = recursiveGetFiles(markdownFolder);
|
||||
const filesFiltered = files.filter((file) => {
|
||||
return !ignorePatterns.some((pattern) => file.match(pattern));
|
||||
});
|
||||
|
||||
return filesFiltered.map((file) => func(file, markdownFolder));
|
||||
};
|
||||
|
||||
const defaultPathToPermalinkFunc = (
|
||||
filePath: string,
|
||||
markdownFolder: string
|
||||
) => {
|
||||
const permalink = filePath
|
||||
.replace(markdownFolder, "") // make the permalink relative to the markdown folder
|
||||
.replace(/\.(mdx|md)/, "")
|
||||
.replace(/\\/g, "/") // replace windows backslash with forward slash
|
||||
.replace(/\/index$/, ""); // remove index from the end of the permalink
|
||||
return permalink.length > 0 ? permalink : "/"; // for home page
|
||||
};
|
||||
1
packages/remark-wiki-link/src/utils/index.ts
Normal file
1
packages/remark-wiki-link/src/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { getPermalinks } from "./getPermalinks";
|
||||
2
packages/remark-wiki-link/test/fixtures/content/abc.md
vendored
Normal file
2
packages/remark-wiki-link/test/fixtures/content/abc.md
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Document Title
|
||||
|
||||
BIN
packages/remark-wiki-link/test/fixtures/content/assets/Pasted Image 123.png
vendored
Normal file
BIN
packages/remark-wiki-link/test/fixtures/content/assets/Pasted Image 123.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
2
packages/remark-wiki-link/test/fixtures/content/blog/Second Post.md
vendored
Normal file
2
packages/remark-wiki-link/test/fixtures/content/blog/Second Post.md
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Document Title
|
||||
|
||||
2
packages/remark-wiki-link/test/fixtures/content/blog/first-post.md
vendored
Normal file
2
packages/remark-wiki-link/test/fixtures/content/blog/first-post.md
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Document Title
|
||||
|
||||
2
packages/remark-wiki-link/test/fixtures/content/blog/index.md
vendored
Normal file
2
packages/remark-wiki-link/test/fixtures/content/blog/index.md
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Document Title
|
||||
|
||||
2
packages/remark-wiki-link/test/fixtures/content/blog/third-post.md
vendored
Normal file
2
packages/remark-wiki-link/test/fixtures/content/blog/third-post.md
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Document Title
|
||||
|
||||
2
packages/remark-wiki-link/test/fixtures/content/blog/tutorials/first-tutorial.md
vendored
Normal file
2
packages/remark-wiki-link/test/fixtures/content/blog/tutorials/first-tutorial.md
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Document Title
|
||||
|
||||
2
packages/remark-wiki-link/test/fixtures/content/index.md
vendored
Normal file
2
packages/remark-wiki-link/test/fixtures/content/index.md
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Document Title
|
||||
|
||||
62
packages/remark-wiki-link/test/getPermalinks.spec.ts
Normal file
62
packages/remark-wiki-link/test/getPermalinks.spec.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import * as path from "path";
|
||||
// import * as url from "url";
|
||||
import { getPermalinks } from "../src/utils";
|
||||
|
||||
// const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
||||
// const markdownFolder = path.join(__dirname, "/fixtures/content");
|
||||
const markdownFolder = path.join(
|
||||
".",
|
||||
"/packages/remark-wiki-link/test/fixtures/content"
|
||||
);
|
||||
|
||||
describe("getPermalinks", () => {
|
||||
test("should return an array of permalinks", () => {
|
||||
const expectedPermalinks = [
|
||||
"/", // /index.md
|
||||
"/abc",
|
||||
"/blog/first-post",
|
||||
"/blog/Second Post",
|
||||
"/blog/third-post",
|
||||
"/blog", // /blog/index.md
|
||||
"/blog/tutorials/first-tutorial",
|
||||
"/assets/Pasted Image 123.png",
|
||||
];
|
||||
|
||||
const permalinks = getPermalinks(markdownFolder, [/\.DS_Store/]);
|
||||
expect(permalinks).toHaveLength(expectedPermalinks.length);
|
||||
permalinks.forEach((permalink) => {
|
||||
expect(expectedPermalinks).toContain(permalink);
|
||||
});
|
||||
});
|
||||
|
||||
test("should return an array of permalinks with custom path -> permalink converter function", () => {
|
||||
const expectedPermalinks = [
|
||||
"/", // /index.md
|
||||
"/abc",
|
||||
"/blog/first-post",
|
||||
"/blog/second-post",
|
||||
"/blog/third-post",
|
||||
"/blog", // /blog/index.md
|
||||
"/blog/tutorials/first-tutorial",
|
||||
"/assets/pasted-image-123.png",
|
||||
];
|
||||
|
||||
const func = (filePath: string, markdownFolder: string) => {
|
||||
const permalink = filePath
|
||||
.replace(markdownFolder, "") // make the permalink relative to the markdown folder
|
||||
.replace(/\.(mdx|md)/, "")
|
||||
.replace(/\\/g, "/") // replace windows backslash with forward slash
|
||||
.replace(/\/index$/, "") // remove index from the end of the permalink
|
||||
.replace(/ /g, "-") // replace spaces with hyphens
|
||||
.toLowerCase(); // convert to lowercase
|
||||
|
||||
return permalink.length > 0 ? permalink : "/"; // for home page
|
||||
};
|
||||
|
||||
const permalinks = getPermalinks(markdownFolder, [/\.DS_Store/], func);
|
||||
expect(permalinks).toHaveLength(expectedPermalinks.length);
|
||||
permalinks.forEach((permalink) => {
|
||||
expect(expectedPermalinks).toContain(permalink);
|
||||
});
|
||||
});
|
||||
});
|
||||
23
packages/remark-wiki-link/test/isSupportedFileFormat.spec.ts
Normal file
23
packages/remark-wiki-link/test/isSupportedFileFormat.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
isSupportedFileFormat,
|
||||
supportedFileFormats,
|
||||
} from "../src/lib/isSupportedFileFormat";
|
||||
|
||||
describe("isSupportedFileFormat", () => {
|
||||
test("should return [false, null] for a path with no file extension", () => {
|
||||
const filePath = "/content/some/markdown/page";
|
||||
expect(isSupportedFileFormat(filePath)).toStrictEqual([false, null]);
|
||||
});
|
||||
|
||||
test("should return [true, <extension>] for a path with supported file extension", () => {
|
||||
supportedFileFormats.forEach((fileFormat) => {
|
||||
const filePath = `image.${fileFormat}`;
|
||||
expect(isSupportedFileFormat(filePath)).toStrictEqual([true, fileFormat]);
|
||||
});
|
||||
});
|
||||
|
||||
test("should return [false, <extension>] for a path with unsupported file extension", () => {
|
||||
const filePath = "image.xyz";
|
||||
expect(isSupportedFileFormat(filePath)).toStrictEqual([false, "xyz"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,312 @@
|
||||
import { syntax } from "../src/lib/syntax";
|
||||
import { html } from "../src/lib/html";
|
||||
import { micromark } from "micromark";
|
||||
|
||||
describe("micromark-extension-wiki-link", () => {
|
||||
describe("parses a wikilink", () => {
|
||||
test("with 'raw' file format (default) that has no matching permalink", () => {
|
||||
const serialized = micromark("[[Wiki Link]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
// note: class="internal new"
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="Wiki Link" class="internal new">Wiki Link</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("with 'raw' file format (default) that has a matching permalink", () => {
|
||||
const serialized = micromark("[[Wiki Link]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html({ permalinks: ["Wiki Link"] }) as any], // TODO type fix
|
||||
});
|
||||
// note: class="internal"
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="Wiki Link" class="internal">Wiki Link</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("with shortened Obsidian-style path that has no matching permalink", () => {
|
||||
const serialized = micromark("[[Wiki Link]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [
|
||||
html({
|
||||
pathFormat: "obsidian-short",
|
||||
}) as any // TODO type fix
|
||||
],
|
||||
});
|
||||
// note: class="internal new"
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="Wiki Link" class="internal new">Wiki Link</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("with shortened Obsidian-style path that has a matching permalink", () => {
|
||||
const serialized = micromark("[[Wiki Link]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [
|
||||
html({
|
||||
permalinks: ["/some/folder/Wiki Link"],
|
||||
pathFormat: "obsidian-short",
|
||||
}) as any // TODO type fix
|
||||
],
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="/some/folder/Wiki Link" class="internal">Wiki Link</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
// Obsidian absolute path doesn't have a leading slash
|
||||
test("with 'obsidian-absolute' path format that has no matching permalink", () => {
|
||||
const serialized = micromark("[[some/folder/Wiki Link]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html({ pathFormat: "obsidian-absolute" }) as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="/some/folder/Wiki Link" class="internal new">some/folder/Wiki Link</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
// Obsidian absolute path doesn't have a leading slash
|
||||
test("with 'obsidian-absolute' path format that has a matching permalink", () => {
|
||||
const serialized = micromark("[[some/folder/Wiki Link]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [
|
||||
html({
|
||||
permalinks: ["/some/folder/Wiki Link"],
|
||||
pathFormat: "obsidian-absolute",
|
||||
}) as any // TODO type fix
|
||||
],
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="/some/folder/Wiki Link" class="internal">some/folder/Wiki Link</a></p>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("aliases and headings", () => {
|
||||
test("parses a wiki link with heading", () => {
|
||||
const serialized = micromark("[[Wiki Link#Some Heading]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
// note: lowercased and hyphenated heading
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="Wiki Link#some-heading" class="internal new">Wiki Link#Some Heading</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("parses a wiki link with heading and alias", () => {
|
||||
const serialized = micromark("[[Wiki Link#Some Heading|Alias]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
// note: lowercased and hyphenated heading
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="Wiki Link#some-heading" class="internal new">Alias</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("parses a wiki link to a heading on the same page", () => {
|
||||
const serialized = micromark("[[#Some Heading]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="#some-heading" class="internal new">Some Heading</a></p>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("image embeds", () => {
|
||||
test("parses an image embed of supported file format", () => {
|
||||
const serialized = micromark("![[My Image.jpg]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><img src="My Image.jpg" alt="My Image.jpg" class="internal new" /></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("parses an image embed of unsupported file format", () => {
|
||||
const serialized = micromark("![[My Image.xyz]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe("<p>![[My Image.xyz]]</p>");
|
||||
});
|
||||
|
||||
test("parses and image ambed with a matching permalink", () => {
|
||||
const serialized = micromark("![[My Image.jpg]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html({ permalinks: ["My Image.jpg"] }) as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><img src="My Image.jpg" alt="My Image.jpg" class="internal" /></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("parses an image embed with a matching permalink and Obsidian-style shortedned path", () => {
|
||||
const serialized = micromark("![[My Image.jpg]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [
|
||||
html({
|
||||
permalinks: ["/assets/My Image.jpg"],
|
||||
pathFormat: "obsidian-short",
|
||||
}) as any // TODO type fix
|
||||
],
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><img src="/assets/My Image.jpg" alt="My Image.jpg" class="internal" /></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("parses an image embed with an alt text", () => {
|
||||
const serialized = micromark("![[My Image.jpg|My Image Alt]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><img src="My Image.jpg" alt="My Image Alt" class="internal new" /></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("parses a pdf embed", () => {
|
||||
const serialized = micromark("![[My Document.pdf]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><iframe width="100%" src="My Document.pdf#toolbar=0" class="internal new" /></p>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("invalid wiki links", () => {
|
||||
test("doesn't parse a wiki link with two missing closing brackets", () => {
|
||||
const serialized = micromark("[[Wiki Link", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe("<p>[[Wiki Link</p>");
|
||||
});
|
||||
|
||||
test("doesn't parse a wiki link with one missing closing bracket", () => {
|
||||
const serialized = micromark("[[Wiki Link]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe("<p>[[Wiki Link]</p>");
|
||||
});
|
||||
|
||||
test("doesn't parse a wiki link with a missing opening bracket", () => {
|
||||
const serialized = micromark("[Wiki Link]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe("<p>[Wiki Link]]</p>");
|
||||
});
|
||||
|
||||
test("doesn't parse a wiki link in single brackets", () => {
|
||||
const serialized = micromark("[Wiki Link]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe("<p>[Wiki Link]</p>");
|
||||
});
|
||||
});
|
||||
|
||||
describe("other options", () => {
|
||||
test("parses a wiki link with a custom class", () => {
|
||||
const serialized = micromark("[[Wiki Link]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [
|
||||
html({
|
||||
newClassName: "test-new",
|
||||
wikiLinkClassName: "test-wiki-link",
|
||||
}) as any // TODO type fix
|
||||
],
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="Wiki Link" class="test-wiki-link test-new">Wiki Link</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("parses a wiki link with a custom divider", () => {
|
||||
const serialized = micromark("[[Wiki Link:Alias Name]]", "ascii", {
|
||||
extensions: [syntax({ aliasDivider: ":" })],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="Wiki Link" class="internal new">Alias Name</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("parses a wiki link with a custom page resolver", () => {
|
||||
const serialized = micromark("[[Wiki Link]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [
|
||||
html({
|
||||
wikiLinkResolver: (page) => [
|
||||
page.replace(/\s+/, "-").toLowerCase(),
|
||||
],
|
||||
}) as any // TODO type fix
|
||||
],
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="wiki-link" class="internal new">Wiki Link</a></p>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("parses wiki links to index files", () => {
|
||||
const serialized = micromark("[[/some/folder/index]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="/some/folder" class="internal new">/some/folder/index</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
describe("other", () => {
|
||||
test("parses a wiki link to some index page in a folder with no matching permalink", () => {
|
||||
const serialized = micromark("[[/some/folder/index]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="/some/folder" class="internal new">/some/folder/index</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("parses a wiki link to some index page in a folder with a matching permalink", () => {
|
||||
const serialized = micromark("[[/some/folder/index]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html({ permalinks: ["/some/folder"] }) as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="/some/folder" class="internal">/some/folder/index</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("parses a wiki link to home index page with no matching permalink", () => {
|
||||
const serialized = micromark("[[/index]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html() as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe(
|
||||
'<p><a href="/" class="internal new">/index</a></p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("parses a wiki link to home index page with a matching permalink", () => {
|
||||
const serialized = micromark("[[/index]]", "ascii", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html({ permalinks: ["/"] }) as any], // TODO type fix
|
||||
});
|
||||
expect(serialized).toBe('<p><a href="/" class="internal">/index</a></p>');
|
||||
});
|
||||
});
|
||||
});
|
||||
538
packages/remark-wiki-link/test/remarkWikiLink.spec.ts
Normal file
538
packages/remark-wiki-link/test/remarkWikiLink.spec.ts
Normal file
@@ -0,0 +1,538 @@
|
||||
import markdown from "remark-parse";
|
||||
import { unified } from "unified";
|
||||
import { select } from "unist-util-select";
|
||||
import { visit } from "unist-util-visit";
|
||||
import { Node } from "unist";
|
||||
|
||||
import wikiLinkPlugin from "../src/lib/remarkWikiLink";
|
||||
|
||||
describe("remark-wiki-link", () => {
|
||||
describe("parses a wikilink", () => {
|
||||
test("with 'raw' file format (default) that has no matching permalink", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("[[Wiki Link]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(false);
|
||||
expect(node.data?.permalink).toEqual("Wiki Link");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual(
|
||||
"internal new"
|
||||
);
|
||||
expect((node.data?.hProperties as any).href).toEqual("Wiki Link");
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual("Wiki Link");
|
||||
});
|
||||
});
|
||||
|
||||
test("with 'raw' file format (default) that has a matching permalink", () => {
|
||||
const processor = unified()
|
||||
.use(markdown)
|
||||
.use(wikiLinkPlugin, {
|
||||
permalinks: ["Wiki Link"],
|
||||
});
|
||||
|
||||
let ast = processor.parse("[[Wiki Link]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(true);
|
||||
expect(node.data?.permalink).toEqual("Wiki Link");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual("internal");
|
||||
expect((node.data?.hProperties as any).href).toEqual("Wiki Link");
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual("Wiki Link");
|
||||
});
|
||||
});
|
||||
|
||||
test("with shortened Obsidian-style path that has no matching permalink", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin, {
|
||||
pathFormat: "obsidian-short",
|
||||
});
|
||||
|
||||
let ast = processor.parse("[[Wiki Link]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(false);
|
||||
expect(node.data?.permalink).toEqual("Wiki Link");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual(
|
||||
"internal new"
|
||||
);
|
||||
expect((node.data?.hProperties as any).href).toEqual("Wiki Link");
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual("Wiki Link");
|
||||
});
|
||||
});
|
||||
|
||||
test("with shortened Obsidian-style path that has a matching permalink", () => {
|
||||
const processor = unified()
|
||||
.use(markdown)
|
||||
.use(wikiLinkPlugin, {
|
||||
permalinks: ["/some/folder/Wiki Link"],
|
||||
pathFormat: "obsidian-short",
|
||||
});
|
||||
|
||||
let ast = processor.parse("[[Wiki Link]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(true);
|
||||
expect(node.data?.permalink).toEqual("/some/folder/Wiki Link");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual("internal");
|
||||
expect((node.data?.hProperties as any).href).toEqual(
|
||||
"/some/folder/Wiki Link"
|
||||
);
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual("Wiki Link");
|
||||
});
|
||||
});
|
||||
|
||||
// Obsidian absolute path doesn't have a leading slash
|
||||
test("with 'obsidian-absolute' path format that has no matching permalink", () => {
|
||||
const processor = unified()
|
||||
.use(markdown)
|
||||
.use(wikiLinkPlugin, { pathFormat: "obsidian-absolute" });
|
||||
|
||||
let ast = processor.parse("[[some/folder/Wiki Link]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(false);
|
||||
expect(node.data?.permalink).toEqual("/some/folder/Wiki Link");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual(
|
||||
"internal new"
|
||||
);
|
||||
expect((node.data?.hProperties as any).href).toEqual(
|
||||
"/some/folder/Wiki Link"
|
||||
);
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual(
|
||||
"some/folder/Wiki Link"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Obsidian absolute path doesn't have a leading slash
|
||||
test("with 'obsidian-absolute' path format that has a matching permalink", () => {
|
||||
const processor = unified()
|
||||
.use(markdown)
|
||||
.use(wikiLinkPlugin, {
|
||||
permalinks: ["/some/folder/Wiki Link"],
|
||||
pathFormat: "obsidian-absolute",
|
||||
});
|
||||
|
||||
let ast = processor.parse("[[some/folder/Wiki Link]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(true);
|
||||
expect(node.data?.permalink).toEqual("/some/folder/Wiki Link");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual("internal");
|
||||
expect((node.data?.hProperties as any).href).toEqual(
|
||||
"/some/folder/Wiki Link"
|
||||
);
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual(
|
||||
"some/folder/Wiki Link"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("aliases and headings", () => {
|
||||
test("parses a wiki link with heading", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("[[Wiki Link#Some Heading]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(false);
|
||||
expect(node.data?.permalink).toEqual("Wiki Link");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual(
|
||||
"internal new"
|
||||
);
|
||||
expect((node.data?.hProperties as any).href).toEqual(
|
||||
"Wiki Link#some-heading"
|
||||
);
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual(
|
||||
"Wiki Link#Some Heading"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("parses a wiki link with heading and alias", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("[[Wiki Link#Some Heading|Alias]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(false);
|
||||
expect(node.data?.permalink).toEqual("Wiki Link");
|
||||
expect(node.data?.alias).toEqual("Alias");
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual(
|
||||
"internal new"
|
||||
);
|
||||
expect((node.data?.hProperties as any).href).toEqual(
|
||||
"Wiki Link#some-heading"
|
||||
);
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual("Alias");
|
||||
});
|
||||
});
|
||||
|
||||
test("parses a wiki link to a heading on the same page", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("[[#Some Heading]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(false);
|
||||
expect(node.data?.permalink).toEqual(""); // TODO should this be null?
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual(
|
||||
"internal new"
|
||||
);
|
||||
expect((node.data?.hProperties as any).href).toEqual("#some-heading");
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual("Some Heading");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("image embeds", () => {
|
||||
test("parses an image embed of supported file format", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("![[My Image.png]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.isEmbed).toEqual(true);
|
||||
expect(node.data?.target).toEqual("My Image.png");
|
||||
expect(node.data?.permalink).toEqual("My Image.png");
|
||||
expect(node.data?.hName).toEqual("img");
|
||||
expect((node.data?.hProperties as any).src).toEqual("My Image.png");
|
||||
expect((node.data?.hProperties as any).alt).toEqual("My Image.png");
|
||||
});
|
||||
});
|
||||
|
||||
test("parses an image embed of unsupported file format", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("![[My Image.xyz]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.isEmbed).toEqual(true);
|
||||
expect(node.data?.target).toEqual("My Image.xyz");
|
||||
expect(node.data?.permalink).toEqual("My Image.xyz");
|
||||
expect(node.data?.hName).toEqual("p");
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual(
|
||||
"![[My Image.xyz]]"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("parses an image embed with a matching permalink", () => {
|
||||
const processor = unified()
|
||||
.use(markdown)
|
||||
.use(wikiLinkPlugin, {
|
||||
permalinks: ["Pasted Image 123.png"],
|
||||
});
|
||||
|
||||
let ast = processor.parse("![[Pasted Image 123.png]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.isEmbed).toEqual(true);
|
||||
expect(node.data?.target).toEqual("Pasted Image 123.png");
|
||||
expect(node.data?.exists).toEqual(true);
|
||||
expect(node.data?.permalink).toEqual("Pasted Image 123.png");
|
||||
expect(node.data?.hName).toEqual("img");
|
||||
expect((node.data?.hProperties as any).src).toEqual(
|
||||
"Pasted Image 123.png"
|
||||
);
|
||||
expect((node.data?.hProperties as any).alt).toEqual(
|
||||
"Pasted Image 123.png"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("parses an image embed with a matching permalink and Obsidian-style shortedned path", () => {
|
||||
const processor = unified()
|
||||
.use(markdown)
|
||||
.use(wikiLinkPlugin, {
|
||||
pathFormat: "obsidian-short",
|
||||
permalinks: ["/assets/Pasted Image 123.png"],
|
||||
});
|
||||
|
||||
let ast = processor.parse("![[Pasted Image 123.png]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.isEmbed).toEqual(true);
|
||||
expect(node.data?.target).toEqual("Pasted Image 123.png");
|
||||
expect(node.data?.exists).toEqual(true);
|
||||
expect(node.data?.permalink).toEqual("/assets/Pasted Image 123.png");
|
||||
expect(node.data?.hName).toEqual("img");
|
||||
expect((node.data?.hProperties as any).src).toEqual(
|
||||
"/assets/Pasted Image 123.png"
|
||||
);
|
||||
expect((node.data?.hProperties as any).alt).toEqual(
|
||||
"Pasted Image 123.png"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("parses an image embed with an alt text", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("![[My Image.png|Alt Text]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.isEmbed).toEqual(true);
|
||||
expect(node.data?.target).toEqual("My Image.png");
|
||||
expect(node.data?.permalink).toEqual("My Image.png");
|
||||
expect(node.data?.hName).toEqual("img");
|
||||
expect((node.data?.hProperties as any).src).toEqual("My Image.png");
|
||||
expect((node.data?.hProperties as any).alt).toEqual("Alt Text");
|
||||
});
|
||||
});
|
||||
|
||||
test("parses a pdf embed", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("![[My Document.pdf]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.isEmbed).toEqual(true);
|
||||
expect(node.data?.target).toEqual("My Document.pdf");
|
||||
expect(node.data?.permalink).toEqual("My Document.pdf");
|
||||
expect(node.data?.hName).toEqual("iframe");
|
||||
expect((node.data?.hProperties as any).src).toEqual(
|
||||
"My Document.pdf#toolbar=0"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("invalid wiki links", () => {
|
||||
test("doesn't parse a wiki link with two missing closing brackets", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("[[Wiki Link");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).toEqual(null);
|
||||
});
|
||||
|
||||
test("doesn't parse a wiki link with one missing closing bracket", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("[[Wiki Link]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).toEqual(null);
|
||||
});
|
||||
|
||||
test("doesn't parse a wiki link with a missing opening bracket", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("Wiki Link]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).toEqual(null);
|
||||
});
|
||||
|
||||
test("doesn't parse a wiki link in single brackets", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("[Wiki Link]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
test("supports different config options", () => {
|
||||
const processor = unified()
|
||||
.use(markdown)
|
||||
.use(wikiLinkPlugin, {
|
||||
aliasDivider: ":",
|
||||
pathFormat: "obsidian-short",
|
||||
permalinks: ["/some/folder/123/real-page"],
|
||||
wikiLinkResolver: (pageName: string) => [
|
||||
`123/${pageName.replace(/ /g, "-").toLowerCase()}`,
|
||||
],
|
||||
wikiLinkClassName: "my-wiki-link-class",
|
||||
hrefTemplate: (permalink: string) => `https://my-site.com${permalink}`,
|
||||
});
|
||||
|
||||
let ast = processor.parse("[[Real Page#Some Heading:Page Alias]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(true);
|
||||
expect(node.data?.permalink).toEqual("/some/folder/123/real-page");
|
||||
expect(node.data?.alias).toEqual("Page Alias");
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual(
|
||||
"my-wiki-link-class"
|
||||
);
|
||||
expect((node.data?.hProperties as any).href).toEqual(
|
||||
"https://my-site.com/some/folder/123/real-page#some-heading"
|
||||
);
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual("Page Alias");
|
||||
});
|
||||
});
|
||||
|
||||
test("parses wiki links to index files", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("[[/some/folder/index]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
expect(select("wikiLink", ast)).not.toEqual(null);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(false);
|
||||
expect(node.data?.permalink).toEqual("/some/folder");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual("internal new");
|
||||
expect((node.data?.hProperties as any).href).toEqual("/some/folder");
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual(
|
||||
"/some/folder/index"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("other", () => {
|
||||
test("parses a wiki link to some index page in a folder with no matching permalink", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("[[/some/folder/index]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(false);
|
||||
expect(node.data?.permalink).toEqual("/some/folder");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual(
|
||||
"internal new"
|
||||
);
|
||||
expect((node.data?.hProperties as any).href).toEqual("/some/folder");
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual(
|
||||
"/some/folder/index"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("parses a wiki link to some index page in a folder with a matching permalink", () => {
|
||||
const processor = unified()
|
||||
.use(markdown)
|
||||
.use(wikiLinkPlugin, { permalinks: ["/some/folder"] });
|
||||
|
||||
let ast = processor.parse("[[/some/folder/index]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(true);
|
||||
expect(node.data?.permalink).toEqual("/some/folder");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual("internal");
|
||||
expect((node.data?.hProperties as any).href).toEqual("/some/folder");
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual(
|
||||
"/some/folder/index"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("parses a wiki link to home index page with no matching permalink", () => {
|
||||
const processor = unified().use(markdown).use(wikiLinkPlugin);
|
||||
|
||||
let ast = processor.parse("[[/index]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(false);
|
||||
expect(node.data?.permalink).toEqual("/");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual(
|
||||
"internal new"
|
||||
);
|
||||
expect((node.data?.hProperties as any).href).toEqual("/");
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual("/index");
|
||||
});
|
||||
});
|
||||
|
||||
test("parses a wiki link to home index page with a matching permalink", () => {
|
||||
const processor = unified()
|
||||
.use(markdown)
|
||||
.use(wikiLinkPlugin, { permalinks: ["/"] });
|
||||
|
||||
let ast = processor.parse("[[/index]]");
|
||||
ast = processor.runSync(ast);
|
||||
|
||||
visit(ast, "wikiLink", (node: Node) => {
|
||||
expect(node.data?.exists).toEqual(true);
|
||||
expect(node.data?.permalink).toEqual("/");
|
||||
expect(node.data?.alias).toEqual(null);
|
||||
expect(node.data?.hName).toEqual("a");
|
||||
expect((node.data?.hProperties as any).className).toEqual("internal");
|
||||
expect((node.data?.hProperties as any).href).toEqual("/");
|
||||
expect((node.data?.hChildren as any)[0].value).toEqual("/index");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
20
packages/remark-wiki-link/tsconfig.json
Normal file
20
packages/remark-wiki-link/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"target": "es2020",
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
packages/remark-wiki-link/tsconfig.lib.json
Normal file
10
packages/remark-wiki-link/tsconfig.lib.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node"],
|
||||
"outDir": "dist",
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"exclude": ["**/*.spec.ts"],
|
||||
"include": ["**/*.ts", "**/*.js"]
|
||||
}
|
||||
7
packages/remark-wiki-link/tsconfig.spec.json
Normal file
7
packages/remark-wiki-link/tsconfig.spec.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "jest"]
|
||||
},
|
||||
"include": ["**/*.spec.ts", "**/*.d.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user