[packages][m]: mv @flowershow/remark-wiki-link here
This commit is contained in:
parent
ee6812974d
commit
e32332ac84
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"],
|
||||
"env": {
|
||||
"jest": true
|
||||
},
|
||||
"ignorePatterns": ["dist/**/*"]
|
||||
}
|
||||
2
packages/remark-wiki-link/.npmignore
Normal file
2
packages/remark-wiki-link/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
src
|
||||
test
|
||||
32
packages/remark-wiki-link/CHANGELOG.md
Normal file
32
packages/remark-wiki-link/CHANGELOG.md
Normal file
@ -0,0 +1,32 @@
|
||||
# @flowershow/remark-wiki-link
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 5ef0262: Don't hyphentate or lowercase src/hrefs by default. Add ignore patterns option to `getPermalinks` util function and allow passing custom paths -> permalinks converter function.
|
||||
|
||||
## 1.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 135a238: Small regex fix in pageResolver.
|
||||
|
||||
## 1.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 71110e2: Fix: wiki links to index pages.
|
||||
- ffa9766: Fix wiki links to headings on the same page.
|
||||
|
||||
## 1.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 22fb5f0: Code refactoring and test converage extension. Also, disabling markdownFolder option in favour of exported getPermalinks function that should be used by the user to generate permalinks list and explicitly pass it to the plugin.
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ae2bf0d: Dynamic import `getFiles` function, only when `markdownFolder` is passed as an option.
|
||||
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 @flowershow/remark-wiki-link
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
import unified from "unified";
|
||||
import markdown from "remark-parse";
|
||||
import wikiLinkPlugin from "@flowershow/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 "@flowershow/remark-wiki-link";
|
||||
import { getPermalinks } from "@flowershow/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": "@flowershow/remark-wiki-link",
|
||||
"version": "1.2.0",
|
||||
"description": "Parse and render wiki-style links in markdown especially Obsidian style links.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/flowershow/flowershow.git",
|
||||
"directory": "packages/remark-wiki-link"
|
||||
},
|
||||
"keywords": [
|
||||
"remark",
|
||||
"remark-plugin",
|
||||
"markdown",
|
||||
"gfm",
|
||||
"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-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"
|
||||
}
|
||||
}
|
||||
63
packages/remark-wiki-link/project.json
Normal file
63
packages/remark-wiki-link/project.json
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"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/web: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": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"publish:dry": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"commands": ["npm publish --dry-run"],
|
||||
"parallel": false,
|
||||
"cwd": "packages/remark-wiki-link/dist"
|
||||
},
|
||||
"dependsOn": ["build"]
|
||||
},
|
||||
"publish": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"commands": ["npm publish"],
|
||||
"parallel": false,
|
||||
"cwd": "packages/remark-wiki-link/dist"
|
||||
},
|
||||
"dependsOn": ["build"]
|
||||
}
|
||||
},
|
||||
"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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
// 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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html({ permalinks: ["Wiki Link"] })],
|
||||
});
|
||||
// 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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [
|
||||
html({
|
||||
pathFormat: "obsidian-short",
|
||||
}),
|
||||
],
|
||||
});
|
||||
// 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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [
|
||||
html({
|
||||
permalinks: ["/some/folder/Wiki Link"],
|
||||
pathFormat: "obsidian-short",
|
||||
}),
|
||||
],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html({ pathFormat: "obsidian-absolute" })],
|
||||
});
|
||||
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",
|
||||
}),
|
||||
],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
// 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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
// 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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
expect(serialized).toBe("<p>![[My Image.xyz]]</p>");
|
||||
});
|
||||
|
||||
test("parses and image ambed with a matching permalink", () => {
|
||||
const serialized = micromark("![[My Image.jpg]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html({ permalinks: ["My Image.jpg"] })],
|
||||
});
|
||||
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",
|
||||
}),
|
||||
],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
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", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
expect(serialized).toBe("<p>[[Wiki Link</p>");
|
||||
});
|
||||
|
||||
test("doesn't parse a wiki link with one missing closing bracket", () => {
|
||||
const serialized = micromark("[[Wiki Link]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
expect(serialized).toBe("<p>[[Wiki Link]</p>");
|
||||
});
|
||||
|
||||
test("doesn't parse a wiki link with a missing opening bracket", () => {
|
||||
const serialized = micromark("[Wiki Link]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
expect(serialized).toBe("<p>[Wiki Link]]</p>");
|
||||
});
|
||||
|
||||
test("doesn't parse a wiki link in single brackets", () => {
|
||||
const serialized = micromark("[Wiki Link]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
expect(serialized).toBe("<p>[Wiki Link]</p>");
|
||||
});
|
||||
});
|
||||
|
||||
describe("other options", () => {
|
||||
test("parses a wiki link with a custom class", () => {
|
||||
const serialized = micromark("[[Wiki Link]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [
|
||||
html({
|
||||
newClassName: "test-new",
|
||||
wikiLinkClassName: "test-wiki-link",
|
||||
}),
|
||||
],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax({ aliasDivider: ":" })],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [
|
||||
html({
|
||||
wikiLinkResolver: (page) => [
|
||||
page.replace(/\s+/, "-").toLowerCase(),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html({ permalinks: ["/some/folder"] })],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html()],
|
||||
});
|
||||
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]]", {
|
||||
extensions: [syntax()],
|
||||
htmlExtensions: [html({ permalinks: ["/"] })],
|
||||
});
|
||||
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"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user