Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feat/backlinks'
Browse files Browse the repository at this point in the history
  • Loading branch information
kkoscielniak committed Aug 4, 2023
2 parents d21cb3b + 0934090 commit 3e1514f
Show file tree
Hide file tree
Showing 16 changed files with 1,200 additions and 25 deletions.
9 changes: 9 additions & 0 deletions .vitepress/config.ts
Expand Up @@ -3,6 +3,8 @@ import mdWikilinks from "markdown-it-wikilinks";
import mdCheckbox from "markdown-it-checkbox";
import mdInclude from "markdown-it-include";
import { sidebar } from "./plugins/sidebar";
import { backlinksMarkdownIt } from "./theme/plugins/backlinks";
import { getBacklinksCollection } from "./theme/plugins/backlinks/backlinksCollection";

export default defineConfig({
base: "/",
Expand Down Expand Up @@ -35,6 +37,7 @@ export default defineConfig({
});

md.use(wikilinks).use(mdCheckbox).use(mdInclude, "partials");
// .use(backlinksMarkdownIt, { vault: "/" });
},
},
head: [
Expand Down Expand Up @@ -73,4 +76,10 @@ export default defineConfig({
],
["meta", { name: "msapplication-TileColor", content: "#3a0839" }],
],
transformPageData: async (pageData) => {
let backlinks = await getBacklinksCollection(pageData);
return {
backlinks,
};
},
});
26 changes: 26 additions & 0 deletions .vitepress/theme/Layout.ts
@@ -0,0 +1,26 @@
import { defineComponent, h, reactive, watch } from "vue";
import { useRoute } from "vitepress";
import DefaultTheme from "vitepress/theme";
import BacklinkReferences from "./plugins/backlinks/components/Backlinks.vue";
import Title from "./components/Title.vue";

export const Layout = defineComponent({
name: "Layout",

setup() {
const route = useRoute();
const state = reactive({ key: route.path });

watch(
() => route.path,
(path) => (state.key = path),
);

return () =>
h(DefaultTheme.Layout, null, {
"doc-before": () => h(Title, { key: `${state.key}` }),
"aside-outline-after": () =>
h(BacklinkReferences, { key: `${state.key}` }),
});
},
});
19 changes: 0 additions & 19 deletions .vitepress/theme/LayoutWithTitle.vue

This file was deleted.

37 changes: 37 additions & 0 deletions .vitepress/theme/backlinks.data.ts
@@ -0,0 +1,37 @@
import MarkdownIt from "markdown-it";
import { backlinksMarkdownIt as mdBacklinks } from "./plugins/backlinks/backlinksMarkdownIt";
import { collection } from "./plugins/backlinks";
import fs from "fs";
import matter from "gray-matter";

const md = new MarkdownIt();
md.use(mdBacklinks, { vault: "/" });

export default {
watch: "**/*.md", // TODO: Fix adding `private/` with fast-glob
async load(files?: string[]) {
if (!files) {
return {
data: [],
};
}

for await (const file of files) {
if (!file.endsWith(".md")) {
continue;
}

const src = fs.readFileSync(file, "utf-8");
const { data: frontmatter } = matter(src);

await md.render(src, {
relativePath: file,
frontmatter,
});
}

return {
data: Object.fromEntries(collection),
};
},
};
10 changes: 10 additions & 0 deletions .vitepress/theme/components/Title.vue
@@ -0,0 +1,10 @@
<template>
<div class="vp-doc">
<h1 v-if="!$frontmatter.customHeader">
{{ $frontmatter.title }}
<a class="header-anchor" href="#"></a>
</h1>
</div>
</template>

<script setup />
4 changes: 2 additions & 2 deletions .vitepress/theme/index.ts
@@ -1,10 +1,10 @@
// https://vitepress.dev/guide/custom-theme
import Theme from "vitepress/theme";
import Title from "./LayoutWithTitle.vue";
import "./style.css";
import { Layout } from "./Layout";

export default {
Layout: Title,
extends: Theme,
Layout,
enhanceApp({ app, router, siteData }) {},
};
22 changes: 22 additions & 0 deletions .vitepress/theme/plugins/backlinks/backlinksCollection.ts
@@ -0,0 +1,22 @@
export interface Backlink {
title: string;
path: string;
}

export const collection: Map<string, Backlink[]> = new Map();

import type { PageData } from "vitepress";

// NOTE: collection here is lazy collected during the dev runtime.
// it won't generate the backlink references information until you visit the
// page.
export const getBacklinksCollection = async (pageData: PageData) => {
const relativePath = pageData.relativePath.replace(".md", "");
// console.log(relativePath);
// console.log(collection);

if (!collection.has(relativePath)) {
return [];
}
return collection.get(relativePath);
};
51 changes: 51 additions & 0 deletions .vitepress/theme/plugins/backlinks/backlinksMarkdownIt.ts
@@ -0,0 +1,51 @@
import type { PluginWithParams } from "markdown-it";
import { collection } from "./backlinksCollection.js";

export const regexp = /\[{2}\s*(.+?)\s*\]{2}/gi;

const linkMatcher = (cap: RegExpExecArray) => {
const backlink = cap[1].split("|");
const path = backlink[0];
const title = backlink[backlink.length - 1];

return { path, title };
};

export const backlinksMarkdownIt: PluginWithParams = (md, { vault }): void => {
md.core.ruler.before("normalize", "backlinks", (state) => {
// console.log(state);
let relativePath = state.env.relativePath?.replace(".md", "");
let selfTitle = state.env.frontmatter?.title;

state.src = ((src) => {
let cap: RegExpExecArray | null;
while ((cap = regexp.exec(src))) {
const { title, path } = linkMatcher(cap);

if (selfTitle != undefined && relativePath != undefined) {
let backlinks = collection.get(path) ?? [];

// TODO: use set to exclude duplicate backlinks
let found = false;
for (const backlink of backlinks) {
if (backlink.path == relativePath) {
found = true;
break;
}
}
if (!found) {
backlinks.push({
title: selfTitle,
path: relativePath,
});
collection.set(path, backlinks);
}
}

// console.log(collection);
}

return src;
})(state.src);
});
};
56 changes: 56 additions & 0 deletions .vitepress/theme/plugins/backlinks/components/Backlinks.vue
@@ -0,0 +1,56 @@
<template>
<div v-if="backlinks.length" class="backlinks">
<div class="content">
<div class="backlinks-title">{{ backlinks.length }} linked note{{ backlinks.length > 1 ? "s" : "" }}</div>
<ul>
<li v-for="backlink in backlinks">
<a class="backlink" :href="`/` + backlink.path">{{ backlink.title }}</a>
</li>
</ul>
</div>
</div>
</template>

<script setup lang="ts">
import { useRoute } from "vitepress";
import { data as backlinksCollection } from "../../../backlinks.data";
const route = useRoute();
const backlinks = backlinksCollection.data[route.path.replace(".html", "").slice(1)] || [];
</script>

<style scoped lang="scss">
.backlinks {
.content {
position: relative;
border-left: 1px solid var(--vp-c-divider);
padding-left: 16px;
font-size: 13px;
font-weight: 500;
}
.backlinks-title {
letter-spacing: 0.4px;
line-height: 28px;
font-size: 13px;
font-weight: 600;
}
.backlink {
display: block;
line-height: 28px;
color: var(--vp-c-text-2);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.5s;
font-weight: 500;
&:hover {
color: var(--vp-c-text-1);
transition: color 0.25s;
}
}
}
</style>
2 changes: 2 additions & 0 deletions .vitepress/theme/plugins/backlinks/index.ts
@@ -0,0 +1,2 @@
export * from "./backlinksCollection";
export * from "./backlinksMarkdownIt";
7 changes: 7 additions & 0 deletions .vitepress/theme/style.css
Expand Up @@ -108,3 +108,10 @@
{
content: none !important;
}

/**
* Component: Aside
* -------------------------------------------------------------------------- */
.VPDocAside {
gap: 20px;
}
12 changes: 12 additions & 0 deletions backlinks.ts
@@ -0,0 +1,12 @@
import glob from "fast-glob";
import MarkdownIt from "markdown-it";
import { backlinksMarkdownIt as mdBacklinks } from "./.vitepress/theme/plugins/backlinks/backlinksMarkdownIt.js";
import { collection } from "./.vitepress/theme/plugins/backlinks/backlinksCollection.js";

const md = new MarkdownIt();

const files = glob.md.use(mdBacklinks, { vault: "/" });

// const x = md.parse("Hello [[world]]!", {});
// console.log(x);
console.log(collection);
12 changes: 11 additions & 1 deletion package.json
Expand Up @@ -4,18 +4,28 @@
"build": "vitepress build",
"preview": "vitepress preview",
"format": "prettier --write .",
"fix-links": "node ./scripts/fix-md-links.js $(git diff --name-only --cached)"
"fix-links": "node ./scripts/fix-md-links.js $(git diff --name-only --cached)",
"dev:backlinks": "nodemon --exec ts-node --esm ./backlinks.ts"
},
"devDependencies": {
"markdown-it": "^13.0.1",
"markdown-it-checkbox": "^1.1.0",
"markdown-it-wikilinks": "^1.3.0",
"nodemon": "^3.0.1",
"prettier": "^3.0.0",
"sass": "^1.64.0",
"ts-node": "^10.9.1",
"typescript": "^5.1.6",
"vitepress": "^1.0.0-beta.5",
"vitepress-sidebar": "^1.8.1",
"vue": "^3.3.4"
},
"type": "module",
"dependencies": {
"@types/markdown-it": "^12.2.3",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"fast-glob": "^3.3.1",
"gray-matter": "^4.0.3",
"markdown-it-include": "^2.0.0"
}
}
1 change: 1 addition & 0 deletions todo.md
@@ -0,0 +1 @@
https://www.youtube.com/watch?v=YcM-nR7lE40 - 1h eee heee
13 changes: 13 additions & 0 deletions tsconfig.json
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"outDir": "./dist",
"declaration": true,
"target": "es6",
"module": "Node16",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"lib": ["ES2020", "DOM"]
}
// "include": ["src/**/*"]
}

0 comments on commit 3e1514f

Please sign in to comment.