Skip to content

Commit

Permalink
feat: implement wikilink and embed images
Browse files Browse the repository at this point in the history
  • Loading branch information
anakornk committed Dec 29, 2021
1 parent 3bd4d7f commit 5797746
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 34 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.log
index.js
node_modules
node_modules
*.d.ts
*.js
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Gatsby Remark Enhanced Wikilink

A Gatsby Remark plugin that adds support to Enhanced Wikilink Syntax (e.g., Obsidian Internal Links) for Gatsby ^4.0.0

- To support file linking and image embedding, use with `gatsby-remark-images`, `gatsby-remark-copy-linked-files` and `gatsby-source-filesystem`
- To be compatible with `gatsby-remark-autolink-headers`, the default implementation uses [Github Slugger](https://github.com/Flet/github-slugger) to slugify filenames and headings. This can be configured via setting a new `wikilinkToUrl` function. For more information, please see `Options` section below.

## Supported Syntax

- [x] Linking to MD files via `[[Internal Link#Heading | Alias]]`
- [x] Linking to other files via `[[../path/document.pdf]]`
- [x] Embed Images `![[../images/Hello.png]]`
- [ ] Embed Notes `![[Internal Notes]]`

## Installation

```bash
yarn add gatsby-remark-enhanced-wikilink
```

## Usage

Add the plugin to your Gatsby config:

```js
// gatsby-config.js
plugins: [
{
resolve: "gatsby-transformer-remark",
options: {
plugins: [
{
resolve: 'gatsby-remark-obsidian',
options: {
stripBrackets: true,
imageExtensions: ['png', 'jpg', 'jpeg'],
fileFileExtensions: ['png', 'jpg', 'jpeg', 'pdf']
// see other options below
},
},
]
}
},
],
```

## Options

```ts
type WikilinkArgs = {
fileName?: string;
heading?: string;
alias?: string;
};

type Options = {
stripBrackets?: boolean;
wikilinkToUrl?: (args: WikilinkArgs) => string;
wikilinkToLinkText?: (args: WikilinkArgs) => string;
imageExtensions?: Array<string>;
linkFileExtensions?: Array<string>;
};
```
36 changes: 27 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,42 @@
{
"name": "gatsby-remark-wikilink",
"name": "gatsby-remark-enhanced-wikilink",
"version": "1.0.0",
"description": "Add wikilinks to Gatsby Remark",
"description": "A Gatsby Remark plugin that adds support to Enhanced Wikilink Syntax",
"main": "index.js",
"types": "index.d.ts",
"author": "Anakorn Kyavatanakij <anakornk@gmail.com>",
"license": "MIT",
"keywords": [
"gatsby",
"remark",
"wikilink",
"obsidian"
],
"files": [
"index.js",
"index.d.ts"
],
"scripts": {
"build": "tsc",
"watch": "tsc --watch",
"prepack": "tsc"
},
"dependencies": {
"github-slugger": "^1.4.0",
"unist-util-visit": "^2.0.2"
},
"devDependencies": {
"@types/github-slugger": "^1.3.0",
"@types/mdast": "^3.0.10",
"@types/node": "^17.0.5",
"typescript": "^4.5.4"
},
"peerDependencies": {
"gatsby": "^4.0.0"
},
"scripts": {
"build": "tsc",
"watch": "tsc --watch"
},
"dependencies": {
"unist-util-visit": "^2.0.2",
"mdast-util-to-string": "^2.0.0"
"homepage": "https://github.com/anakornk/gatsby-remark-enhanced-wikilink#readme",
"repository": {
"type": "git",
"url": "https://github.com/anakornk/gatsby-remark-enhanced-wikilink.git"
}
}
177 changes: 159 additions & 18 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,162 @@
import visit from 'unist-util-visit';
import toString from 'mdast-util-to-string';

export = async ({ markdownAST }: any) => {
visit(markdownAST, 'heading', (node: any) => {
let { depth } = node;
// Skip if not an h1
if (depth !== 1) return;
// Grab the innerText of the heading node
let text = toString(node);
const html = `
<h1 style="color: rebeccapurple">
${text}
</h1>
`;
node.type = 'html';
node.children = undefined;
node.value = html;
import { Node } from 'unist';
import GithubSlugger from 'github-slugger';
import type { Text } from 'mdast';

type WikilinkArgs = {
fileName?: string;
heading?: string;
alias?: string;
};

type Options = {
stripBrackets?: boolean;
wikilinkToUrl?: (args: WikilinkArgs) => string;
wikilinkToLinkText?: (args: WikilinkArgs) => string;
imageExtensions?: Array<string>;
linkFileExtensions?: Array<string>;
};

const slugify = GithubSlugger.slug;

const defaultWikilinkToUrl: Options['wikilinkToUrl'] = ({
fileName,
heading,
}) => {
return `${slugify(fileName)}${heading && `#${slugify(heading)}`}` || '#';
};

const defaultWikilinkToLinkText: Options['wikilinkToLinkText'] = ({
fileName,
heading,
alias,
}) => {
return (
alias ||
(fileName && heading && `${fileName} > ${heading}`) ||
fileName ||
`> ${heading}`
);
};

const wrapWithBrackets = (text: string, isEmbed: boolean = false) => {
return isEmbed ? `![[${text}]]` : `[[${text}]]`;
};

const transformWikilink = (
{ markdownAST }: { markdownAST: Node },
{
stripBrackets = true,
wikilinkToUrl = defaultWikilinkToUrl,
wikilinkToLinkText = defaultWikilinkToLinkText,
imageExtensions = ['png', 'jpg', 'jpeg'],
linkFileExtensions = ['png', 'jpg', 'jpeg', 'pdf'],
}: Options
) => {
visit<Text>(markdownAST, 'text', (node, index, parent) => {
if (!parent) return;

const { value: text } = node;

const wikilinkRegExp = /(!?)\[\[(.+?)\]\]/g;

const result = [];
let start = 0;

let match = wikilinkRegExp.exec(text);

// loop each match and transform wikilinks
while (match) {
const position = match.index;
const fullMatchedString = match[0];
const isEmbed = match[1] === '!';
const wikilinkText = match[2];

if (start !== position) {
result.push({
type: 'text',
value: text.slice(start, position),
});
}

if (isEmbed) {
const imageRegExp = new RegExp(
`([^\\/]*)\\.(?:${imageExtensions.join('|')})$`,
'i'
);
const imageMatch = wikilinkText.match(imageRegExp);
if (imageMatch) {
result.push({
type: 'image',
url: wikilinkText,
alt: imageMatch[1] || wikilinkText,
});
} else {
// TODO: Support embed MD
result.push({
type: 'text',
value: wrapWithBrackets(wikilinkText, true),
});
}
} else {
const linkFileRegExp = new RegExp(
`\\.(?:${linkFileExtensions.join('|')})$`,
'i'
);
if (wikilinkText.match(linkFileRegExp)) {
result.push({
type: 'link',
url: wikilinkText,
children: [
{
type: 'text',
value: stripBrackets
? wikilinkText
: wrapWithBrackets(wikilinkText),
},
],
});
} else {
// get file name, heading and alias
const splitRegex = /([^#\|]*)(?:#?)([^\|]*)(?:\|?)(.*)/; // split wikilink text to fileName, heading and alias
let [_, fileName, heading, alias] = wikilinkText.match(
splitRegex
) || [,];
fileName = fileName?.replace(/\.md$/, '').trim(); // support filename with md ext
heading = heading?.trim();
alias = alias?.trim();

const url = wikilinkToUrl({ fileName, heading, alias });
const linkText = wikilinkToLinkText({ fileName, heading, alias });

result.push({
type: 'link',
url: url,
children: [
{
type: 'text',
value: stripBrackets ? linkText : wrapWithBrackets(linkText),
},
],
});
}
}

start = position + fullMatchedString.length;
match = wikilinkRegExp.exec(text);
}

// if there is at least one match
if (result.length > 0) {
// add the rest of the text that hasn't been matched
if (start < text.length) {
result.push({ type: 'text', value: text.slice(start) });
}
// add siblings
parent.children.splice(index, 1, ...result);
return index + result.length;
}
});
return markdownAST;
};

export = transformWikilink;
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"rootDir": "src",
"outDir": "./"
"outDir": "./",
"declaration": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules/**"]
Expand Down
13 changes: 9 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
# yarn lockfile v1


"@types/github-slugger@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@types/github-slugger/-/github-slugger-1.3.0.tgz#16ab393b30d8ae2a111ac748a015ac05a1fc5524"
integrity sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==

"@types/mdast@^3.0.10":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
Expand All @@ -19,10 +24,10 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==

mdast-util-to-string@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b"
integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==
github-slugger@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.4.0.tgz#206eb96cdb22ee56fdc53a28d5a302338463444e"
integrity sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==

typescript@^4.5.4:
version "4.5.4"
Expand Down

0 comments on commit 5797746

Please sign in to comment.