fix: router initialization

This commit is contained in:
Break27 2024-09-20 18:13:52 +08:00
parent c7bae4a2b5
commit 24a026c58e
6 changed files with 129 additions and 142 deletions

View File

@ -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:

View File

@ -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,19 +155,33 @@ 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.content.value = view?.render();
return Boolean(view);
}
this.router.on('error', () => {
this.content.fallback();
});
marked.use(zettelkasten());
},
async load() {
this.graph.transition(async () => { this.graph.transition(async () => {
const { Engine } = await import('./graph'); const { Engine } = await import('./graph');
let engine = Engine.fromIndex(this.index); let engine = Engine.fromIndex(this.index);
let node = engine.createInstance();
return engine.createInstance(url); return node;
}); });
this.navigation.transition(() => { this.navigation.transition(() => {
@ -162,21 +191,5 @@ export default () => ({
search.addAllAsync(searchable); search.addAllAsync(searchable);
return tree.render(false); return tree.render(false);
}); });
this.content.transition(() => {
return view?.render() ?? '';
});
return url;
} }
Routes.other = path => {
this.content.transition(() => {
let [,view] = this.index.createView(path);
return view.render();
});
}
marked.use(zettelkasten());
},
}) })

View File

@ -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();

View File

@ -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=&quot;\${url}&quot;]\`); active = $el.querySelector(\`[data-path=&quot;\${path}&quot;]\`);
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 -->
@ -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 = {}) {

View File

@ -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);
} }
} }

View File

@ -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