fix: router initialization
This commit is contained in:
parent
c7bae4a2b5
commit
24a026c58e
@ -4,13 +4,12 @@ language: en
|
|||||||
|
|
||||||
vault:
|
vault:
|
||||||
root: public
|
root: public
|
||||||
cleanup: true
|
cleanup: false
|
||||||
|
|
||||||
view:
|
|
||||||
index:
|
|
||||||
notfound:
|
|
||||||
|
|
||||||
metadata:
|
metadata:
|
||||||
hidden:
|
hidden:
|
||||||
- example
|
- example
|
||||||
- foo/bar.md
|
- foo/bar.md
|
||||||
|
routes:
|
||||||
|
index:
|
||||||
|
error:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Engine } from './search'
|
import { Engine } from './search'
|
||||||
import { Index, State } from './object'
|
import { Index, State } from './object'
|
||||||
import router, { Routes } from './router'
|
import router from './router'
|
||||||
import zettelkasten from './zettelkasten'
|
import zettelkasten from './zettelkasten'
|
||||||
|
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
@ -60,6 +60,21 @@ export default () => ({
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fallback() {
|
||||||
|
this.value = `
|
||||||
|
<div class="flex size-full justify-center self-center">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-24 text-neutral-200">
|
||||||
|
<path fill-rule="evenodd" d="M4.5 2A1.5 1.5 0 0 0 3 3.5v13A1.5 1.5 0 0 0 4.5 18h11a1.5 1.5 0 0 0 1.5-1.5V7.621a1.5 1.5 0 0 0-.44-1.06l-4.12-4.122A1.5 1.5 0 0 0 11.378 2H4.5Zm2.25 8.5a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5Zm0 3a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5Z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<div class="font-semibold">
|
||||||
|
NOT FOUND
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
graph: new class extends State {
|
graph: new class extends State {
|
||||||
@ -80,8 +95,8 @@ export default () => ({
|
|||||||
>
|
>
|
||||||
<div class="relative rounded-md border size-full p-1 bg-white"
|
<div class="relative rounded-md border size-full p-1 bg-white"
|
||||||
x-data="{ ready: false }"
|
x-data="{ ready: false }"
|
||||||
x-init="if (graph.value?.nodeType) { ready = true; $el.appendChild(graph.value) }"
|
x-init="if (graph.value?.nodeType) { ready = true; $el.appendChild(graph.value) } else ready = false;"
|
||||||
@click="if (expand && $el.children.length === 1) { destroy(); toggleGlobal(false) }"
|
@click="if (expand && !ready) { destroy(); toggleGlobal(false) }"
|
||||||
>
|
>
|
||||||
<template x-if="ready">
|
<template x-if="ready">
|
||||||
<div class="flex items-center gap-x-1 mr-1 mt-1 absolute top-0 right-0 z-20 text-neutral-600">
|
<div class="flex items-center gap-x-1 mr-1 mt-1 absolute top-0 right-0 z-20 text-neutral-600">
|
||||||
@ -103,7 +118,7 @@ export default () => ({
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="!ready">
|
<template x-if="!ready">
|
||||||
<div x-html="graph.init()" class="size-full">
|
<div x-html="graph.value" class="size-full">
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -140,43 +155,41 @@ export default () => ({
|
|||||||
async init() {
|
async init() {
|
||||||
this.content.transition(async () => {
|
this.content.transition(async () => {
|
||||||
this.index = await Index.fromPack();
|
this.index = await Index.fromPack();
|
||||||
this.router.start();
|
|
||||||
|
|
||||||
console.log(this.index);
|
this.content.value = ''; // clear init state
|
||||||
|
this.load.apply(this);
|
||||||
|
|
||||||
|
this.router.start(this.index.metadata.routes);
|
||||||
});
|
});
|
||||||
|
|
||||||
Routes.index = () => {
|
this.router.route = (path) => {
|
||||||
let [url, view] = this.index.createView();
|
let view = this.index.getObject(path);
|
||||||
|
|
||||||
this.graph.transition(async () => {
|
this.content.value = view?.render();
|
||||||
const { Engine } = await import('./graph');
|
return Boolean(view);
|
||||||
let engine = Engine.fromIndex(this.index);
|
|
||||||
|
|
||||||
return engine.createInstance(url);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.navigation.transition(() => {
|
|
||||||
let [tree, searchable] = this.index.createTree();
|
|
||||||
let search = this.search.engine.getInstance();
|
|
||||||
|
|
||||||
search.addAllAsync(searchable);
|
|
||||||
return tree.render(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.content.transition(() => {
|
|
||||||
return view?.render() ?? '';
|
|
||||||
});
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Routes.other = path => {
|
this.router.on('error', () => {
|
||||||
this.content.transition(() => {
|
this.content.fallback();
|
||||||
let [,view] = this.index.createView(path);
|
});
|
||||||
return view.render();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
marked.use(zettelkasten());
|
marked.use(zettelkasten());
|
||||||
},
|
},
|
||||||
|
async load() {
|
||||||
|
this.graph.transition(async () => {
|
||||||
|
const { Engine } = await import('./graph');
|
||||||
|
let engine = Engine.fromIndex(this.index);
|
||||||
|
let node = engine.createInstance();
|
||||||
|
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.navigation.transition(() => {
|
||||||
|
let [tree, searchable] = this.index.createTree();
|
||||||
|
let search = this.search.engine.getInstance();
|
||||||
|
|
||||||
|
search.addAllAsync(searchable);
|
||||||
|
return tree.render(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export class Engine {
|
|||||||
return new Engine(graph);
|
return new Engine(graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
createInstance(url) {
|
createInstance() {
|
||||||
let settings = { allowInvalidContainer: true };
|
let settings = { allowInvalidContainer: true };
|
||||||
let container = document.createElement('div');
|
let container = document.createElement('div');
|
||||||
|
|
||||||
@ -59,9 +59,8 @@ export class Engine {
|
|||||||
this.setupInteraction();
|
this.setupInteraction();
|
||||||
this.setupGraphStyle();
|
this.setupGraphStyle();
|
||||||
|
|
||||||
let camera = this.instance.getCamera();
|
let path = router.last('retrieve')?.params?.path;
|
||||||
router.emit('retrieve', { path: url });
|
setTimeout(() => router.emit('retrieve', { path }), 500);
|
||||||
camera.animate({ ratio: 1 }, { easing: "linear", duration: 200 });
|
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
@ -201,6 +200,12 @@ export class Engine {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
panover(path) {
|
||||||
|
let camera = this.instance.getCamera();
|
||||||
|
router.emit('graphview update', { path });
|
||||||
|
camera.animate({ ratio: 0.5 }, { easing: "linear", duration: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.layout.kill();
|
this.layout.kill();
|
||||||
this.instance.kill();
|
this.instance.kill();
|
||||||
|
|||||||
@ -53,9 +53,8 @@ class BaseNode extends BaseObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Index extends BaseObject {
|
export class Index extends BaseObject {
|
||||||
constructor(view, metadata, object, links = {}) {
|
constructor(metadata, object, links = {}) {
|
||||||
super();
|
super();
|
||||||
this.view = view;
|
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
this.object = object;
|
this.object = object;
|
||||||
this.links = links;
|
this.links = links;
|
||||||
@ -64,13 +63,7 @@ export class Index extends BaseObject {
|
|||||||
static Metadata(x) {
|
static Metadata(x) {
|
||||||
return {
|
return {
|
||||||
hidden: x?.hidden,
|
hidden: x?.hidden,
|
||||||
}
|
routes: x?.routes,
|
||||||
}
|
|
||||||
|
|
||||||
static View(x) {
|
|
||||||
return {
|
|
||||||
index: x?.index,
|
|
||||||
notfound: x?.notfound
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,27 +144,6 @@ export class Index extends BaseObject {
|
|||||||
|
|
||||||
return [root, searchable];
|
return [root, searchable];
|
||||||
}
|
}
|
||||||
|
|
||||||
createView(path = '') {
|
|
||||||
let entry = this.getObject(path);
|
|
||||||
let view = this.view;
|
|
||||||
|
|
||||||
if (! path) {
|
|
||||||
path = view.index ?? '';
|
|
||||||
entry = this.getObject(path);
|
|
||||||
|
|
||||||
if (!entry) return [path, Document.Blank()];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! entry) {
|
|
||||||
path = view.notfound ?? '';
|
|
||||||
entry = this.getObject(path);
|
|
||||||
|
|
||||||
if (!entry) return [path, Document.NotFound()];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [path, entry];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Folder extends BaseNode {
|
export class Folder extends BaseNode {
|
||||||
@ -242,10 +214,12 @@ export class Folder extends BaseNode {
|
|||||||
|
|
||||||
return `
|
return `
|
||||||
<div x-data="{ active: null, navigate: false }"
|
<div x-data="{ active: null, navigate: false }"
|
||||||
x-init="router.on('ready', ({ url }) => {
|
x-init="router.on('ready', () => {
|
||||||
let isFromNav = navigate;
|
let isFromNav = navigate;
|
||||||
|
let path = window.location.pathname;
|
||||||
|
|
||||||
active?.setAttribute('data-active', false);
|
active?.setAttribute('data-active', false);
|
||||||
active = $el.querySelector(\`[data-path="\${url}"]\`);
|
active = $el.querySelector(\`[data-path="\${path}"]\`);
|
||||||
active?.setAttribute('data-active', true);
|
active?.setAttribute('data-active', true);
|
||||||
|
|
||||||
if (isFromNav) return;
|
if (isFromNav) return;
|
||||||
@ -278,29 +252,6 @@ export class Document extends BaseNode {
|
|||||||
this.links = [];
|
this.links = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
static NotFound() {
|
|
||||||
return {
|
|
||||||
render: () => `
|
|
||||||
<div class="flex size-full justify-center self-center">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-24 text-neutral-200">
|
|
||||||
<path fill-rule="evenodd" d="M4.5 2A1.5 1.5 0 0 0 3 3.5v13A1.5 1.5 0 0 0 4.5 18h11a1.5 1.5 0 0 0 1.5-1.5V7.621a1.5 1.5 0 0 0-.44-1.06l-4.12-4.122A1.5 1.5 0 0 0 11.378 2H4.5Zm2.25 8.5a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5Zm0 3a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5Z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
<div class="font-semibold">
|
|
||||||
NOT FOUND
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Blank() {
|
|
||||||
return {
|
|
||||||
render: () => ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fullname() {
|
fullname() {
|
||||||
return this.name + '.md';
|
return this.name + '.md';
|
||||||
}
|
}
|
||||||
@ -312,13 +263,7 @@ export class Document extends BaseNode {
|
|||||||
let title = this.metadata.title ?? this.name;
|
let title = this.metadata.title ?? this.name;
|
||||||
document.title = `${title} - ${appName}`;
|
document.title = `${title} - ${appName}`;
|
||||||
|
|
||||||
return `
|
let sidebar = () => `
|
||||||
<div class="flex flex-col w-full md:mx-12 m-6 md:mt-8">
|
|
||||||
<div class="font-bold text-4xl mb-6">${title}</div>
|
|
||||||
<div x-ref="page" class="prose prose-neutral md:max-w-prose max-w-none w-full">
|
|
||||||
${marked(this.content)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="lg:block hidden 2xl:min-w-80 max-w-72 w-full max-h-screen">
|
<div class="lg:block hidden 2xl:min-w-80 max-w-72 w-full max-h-screen">
|
||||||
<div class="flex flex-col fixed gap-y-8 pt-6 2xl:w-80 w-72 select-none">
|
<div class="flex flex-col fixed gap-y-8 pt-6 2xl:w-80 w-72 select-none">
|
||||||
<!-- Interactive Graph -->
|
<!-- Interactive Graph -->
|
||||||
@ -329,16 +274,16 @@ export class Document extends BaseNode {
|
|||||||
$nextTick(() => headings = $refs.page.querySelectorAll(':not([data-type]) > :is(h1, h2, h3, h4, h5, h6)'))"
|
$nextTick(() => headings = $refs.page.querySelectorAll(':not([data-type]) > :is(h1, h2, h3, h4, h5, h6)'))"
|
||||||
x-show="headings.length > 0"
|
x-show="headings.length > 0"
|
||||||
class="mr-8"
|
class="mr-8"
|
||||||
>
|
>
|
||||||
<div class="uppercase text-sm font-semibold mb-3">On this page</div>
|
<div class="uppercase text-sm font-semibold mb-3">On this page</div>
|
||||||
<div class="overflow-y-auto overscroll-contain 2xl:max-h-96 max-h-64">
|
<div class="overflow-y-auto overscroll-contain 2xl:max-h-96 max-h-64">
|
||||||
<template x-for="heading in headings">
|
<template x-for="heading in headings">
|
||||||
<div :data-level="heading.tagName"
|
<div :data-level="heading.tagName"
|
||||||
x-data="{ id: '#' + heading.innerText, scroll: () => heading.scrollIntoView() }"
|
x-data="{ id: '#' + heading.innerText, scroll: () => heading.scrollIntoView() }"
|
||||||
x-init="if (hash === id) scroll()"
|
x-init="if (hash === id) scroll()"
|
||||||
class="data-[level=H1]:pl-0 data-[level=H1]:border-0 data-[level=H1]:ml-0
|
class="data-[level=H1]:pl-0 data-[level=H1]:border-0 data-[level=H1]:ml-0
|
||||||
data-[level=H2]:ml-1 data-[level=H3]:ml-5 data-[level=H4]:ml-9 data-[level=H5]:ml-[3.25rem] ml-[4.25rem]
|
data-[level=H2]:ml-1 data-[level=H3]:ml-5 data-[level=H4]:ml-9 data-[level=H5]:ml-[3.25rem] ml-[4.25rem]
|
||||||
pl-3 border-l"
|
pl-3 border-l"
|
||||||
>
|
>
|
||||||
<a x-text="heading.innerText"
|
<a x-text="heading.innerText"
|
||||||
class="text-neutral-500 hover:text-neutral-800"
|
class="text-neutral-500 hover:text-neutral-800"
|
||||||
@ -352,6 +297,18 @@ export class Document extends BaseNode {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="flex flex-col w-full md:mx-12 m-6 md:mt-8">
|
||||||
|
<div class="font-bold text-4xl mb-6">${title}</div>
|
||||||
|
<div x-ref="page" class="prose prose-neutral max-w-none w-full
|
||||||
|
${this.metadata['no_sidebar'] ? '' : 'md:max-w-prose'}"
|
||||||
|
>
|
||||||
|
${marked(this.content)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${this.metadata['no_sidebar'] ? '' : sidebar()}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline(attributes = {}) {
|
inline(attributes = {}) {
|
||||||
|
|||||||
@ -1,24 +1,34 @@
|
|||||||
export const Routes = {
|
const Routes = {
|
||||||
index() {},
|
index: null,
|
||||||
other() {}
|
error: null
|
||||||
}
|
}
|
||||||
|
|
||||||
const Events = [
|
const Events = {
|
||||||
/* eventName: [callbacks], */
|
entries: { /* eventName: [callbacks], */ },
|
||||||
]
|
history: [ /* { eventName, params, }, */ ]
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
react() {
|
react() {
|
||||||
let url = window.location.pathname;
|
let path = decodeURI(window.location.pathname);
|
||||||
if (url === '/') {
|
if (path === '/') {
|
||||||
url = encodeURI(Routes.index());
|
if (! Routes.index) return;
|
||||||
history.replaceState({}, '', url);
|
history.replaceState({}, '', Routes.index);
|
||||||
} else {
|
return this.react();
|
||||||
Routes.other(decodeURI(url));
|
|
||||||
}
|
}
|
||||||
this.emit('ready', { url });
|
|
||||||
|
if (! this.route(path)) {
|
||||||
|
if (! Routes.error) this.emit('error');
|
||||||
|
return this.goto(Routes.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('ready');
|
||||||
},
|
},
|
||||||
start() {
|
route(path) {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
start(routes) {
|
||||||
|
Object.assign(Routes, routes);
|
||||||
window.addEventListener("popstate", () => this.react());
|
window.addEventListener("popstate", () => this.react());
|
||||||
this.react();
|
this.react();
|
||||||
},
|
},
|
||||||
@ -32,10 +42,16 @@ export default {
|
|||||||
this.react();
|
this.react();
|
||||||
},
|
},
|
||||||
emit(event, params) {
|
emit(event, params) {
|
||||||
Events[event]?.forEach(fn => fn(params));
|
Events.history.unshift({ event, params });
|
||||||
|
Events.history.length = Math.min(Events.history.length, 50);
|
||||||
|
|
||||||
|
Events.entries[event]?.forEach(fn => fn(params));
|
||||||
|
},
|
||||||
|
last(event) {
|
||||||
|
return Events.history.find(e => e.event === event);
|
||||||
},
|
},
|
||||||
on(event, callback) {
|
on(event, callback) {
|
||||||
if (! Events[event]) Events[event] = [];
|
if (! Events.entries[event]) Events.entries[event] = [];
|
||||||
Events[event]?.push(callback);
|
Events.entries[event]?.push(callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ function walkSourceDir([root, parent], callback) {
|
|||||||
let filenames = fs.readdirSync(base);
|
let filenames = fs.readdirSync(base);
|
||||||
|
|
||||||
for (let name of filenames) {
|
for (let name of filenames) {
|
||||||
// omit filenames starting with '.' / '_'
|
// omit filenames starting with '.' or '_'
|
||||||
if (name.startsWith(".") || name.startsWith('_')) continue;
|
if (name.startsWith(".") || name.startsWith('_')) continue;
|
||||||
|
|
||||||
let path = [base, name].join('/');
|
let path = [base, name].join('/');
|
||||||
@ -63,7 +63,9 @@ function bisect(filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildMdDocs(index, ctx) /* always return falsy values */ {
|
function buildMdDocs(index, ctx) /* always return falsy values */ {
|
||||||
let { attributes, body } = fm(ctx.content);
|
let content = fs.readFileSync(ctx.path, 'utf8');
|
||||||
|
|
||||||
|
let { attributes, body } = fm(content);
|
||||||
let pathname = [ctx.parent, ctx.short].filter(Boolean).join('/');
|
let pathname = [ctx.parent, ctx.short].filter(Boolean).join('/');
|
||||||
|
|
||||||
let text = transform.wikilink(body, href => {
|
let text = transform.wikilink(body, href => {
|
||||||
@ -116,10 +118,7 @@ function buildMdDocs(index, ctx) /* always return falsy values */ {
|
|||||||
|
|
||||||
function buildObject(index, ctx) /* always return falsy values */ {
|
function buildObject(index, ctx) /* always return falsy values */ {
|
||||||
if (ctx.extension === 'md') {
|
if (ctx.extension === 'md') {
|
||||||
let content = fs.readFileSync(ctx.path, 'utf8');
|
return buildMdDocs(index, ctx);
|
||||||
let context = { content, ...ctx };
|
|
||||||
|
|
||||||
return buildMdDocs(index, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = fs.readFileSync(ctx.path);
|
let content = fs.readFileSync(ctx.path);
|
||||||
@ -140,11 +139,9 @@ function buildObject(index, ctx) /* always return falsy values */ {
|
|||||||
fs.writeFileSync(path, content);
|
fs.writeFileSync(path, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildIndex(config, vault) {
|
function buildIndex(config) {
|
||||||
let metadata = Index.Metadata(config["metadata"]);
|
let metadata = Index.Metadata(config["metadata"]);
|
||||||
let view = Index.View(config["view"]);
|
let index = new Index(metadata, {});
|
||||||
|
|
||||||
let index = new Index(view, metadata, {});
|
|
||||||
|
|
||||||
if (! fs.existsSync(PATH.INDEX)) {
|
if (! fs.existsSync(PATH.INDEX)) {
|
||||||
// create index if not found
|
// create index if not found
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user