summaryrefslogtreecommitdiff
path: root/packages/client/src/nirax.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/client/src/nirax.ts')
-rw-r--r--packages/client/src/nirax.ts210
1 files changed, 139 insertions, 71 deletions
diff --git a/packages/client/src/nirax.ts b/packages/client/src/nirax.ts
index d219787448..0ee39bf473 100644
--- a/packages/client/src/nirax.ts
+++ b/packages/client/src/nirax.ts
@@ -2,13 +2,18 @@
import { EventEmitter } from 'eventemitter3';
import { Ref, Component, ref, shallowRef, ShallowRef } from 'vue';
+import { pleaseLogin } from '@/scripts/please-login';
+import { safeURIDecode } from '@/scripts/safe-uri-decode';
type RouteDef = {
path: string;
component: Component;
query?: Record<string, string>;
+ loginRequired?: boolean;
name?: string;
+ hash?: string;
globalCacheKey?: string;
+ children?: RouteDef[];
};
type ParsedPath = (string | {
@@ -18,6 +23,8 @@ type ParsedPath = (string | {
optional?: boolean;
})[];
+export type Resolved = { route: RouteDef; props: Map<string, string>; child?: Resolved; };
+
function parsePath(path: string): ParsedPath {
const res = [] as ParsedPath;
@@ -35,7 +42,7 @@ function parsePath(path: string): ParsedPath {
wildcard,
optional,
});
- } else {
+ } else if (part.length !== 0) {
res.push(part);
}
}
@@ -47,8 +54,11 @@ export class Router extends EventEmitter<{
change: (ctx: {
beforePath: string;
path: string;
- route: RouteDef | null;
- props: Map<string, string> | null;
+ resolved: Resolved;
+ key: string;
+ }) => void;
+ replace: (ctx: {
+ path: string;
key: string;
}) => void;
push: (ctx: {
@@ -58,26 +68,33 @@ export class Router extends EventEmitter<{
props: Map<string, string> | null;
key: string;
}) => void;
+ same: () => void;
}> {
private routes: RouteDef[];
+ public current: Resolved;
+ public currentRef: ShallowRef<Resolved> = shallowRef();
+ public currentRoute: ShallowRef<RouteDef> = shallowRef();
private currentPath: string;
- private currentComponent: Component | null = null;
- private currentProps: Map<string, string> | null = null;
private currentKey = Date.now().toString();
- public currentRoute: ShallowRef<RouteDef | null> = shallowRef(null);
+ public navHook: ((path: string, flag?: any) => boolean) | null = null;
constructor(routes: Router['routes'], currentPath: Router['currentPath']) {
super();
this.routes = routes;
this.currentPath = currentPath;
- this.navigate(currentPath, null, true);
+ this.navigate(currentPath, null, false);
}
- public resolve(path: string): { route: RouteDef; props: Map<string, string>; } | null {
+ public resolve(path: string): Resolved | null {
let queryString: string | null = null;
+ let hash: string | null = null;
if (path[0] === '/') path = path.substring(1);
+ if (path.includes('#')) {
+ hash = path.substring(path.indexOf('#') + 1);
+ path = path.substring(0, path.indexOf('#'));
+ }
if (path.includes('?')) {
queryString = path.substring(path.indexOf('?') + 1);
path = path.substring(0, path.indexOf('?'));
@@ -85,68 +102,108 @@ export class Router extends EventEmitter<{
if (_DEV_) console.log('Routing: ', path, queryString);
- forEachRouteLoop:
- for (const route of this.routes) {
- let parts = path.split('/');
- const props = new Map<string, string>();
+ function check(routes: RouteDef[], _parts: string[]): Resolved | null {
+ forEachRouteLoop:
+ for (const route of routes) {
+ let parts = [ ..._parts ];
+ const props = new Map<string, string>();
- pathMatchLoop:
- for (const p of parsePath(route.path)) {
- if (typeof p === 'string') {
- if (p === parts[0]) {
- parts.shift();
- } else {
- continue forEachRouteLoop;
- }
- } else {
- if (parts[0] == null && !p.optional) {
- continue forEachRouteLoop;
- }
- if (p.wildcard) {
- if (parts.length !== 0) {
- props.set(p.name, parts.join('/'));
- parts = [];
+ pathMatchLoop:
+ for (const p of parsePath(route.path)) {
+ if (typeof p === 'string') {
+ if (p === parts[0]) {
+ parts.shift();
+ } else {
+ continue forEachRouteLoop;
}
- break pathMatchLoop;
} else {
- if (p.startsWith) {
- if (parts[0] == null || !parts[0].startsWith(p.startsWith)) continue forEachRouteLoop;
-
- props.set(p.name, parts[0].substring(p.startsWith.length));
- parts.shift();
+ if (parts[0] == null && !p.optional) {
+ continue forEachRouteLoop;
+ }
+ if (p.wildcard) {
+ if (parts.length !== 0) {
+ props.set(p.name, safeURIDecode(parts.join('/')));
+ parts = [];
+ }
+ break pathMatchLoop;
} else {
- props.set(p.name, parts[0]);
- parts.shift();
+ if (p.startsWith) {
+ if (parts[0] == null || !parts[0].startsWith(p.startsWith)) continue forEachRouteLoop;
+
+ props.set(p.name, safeURIDecode(parts[0].substring(p.startsWith.length)));
+ parts.shift();
+ } else {
+ if (parts[0]) {
+ props.set(p.name, safeURIDecode(parts[0]));
+ }
+ parts.shift();
+ }
}
}
}
- }
- if (parts.length !== 0) continue forEachRouteLoop;
-
- if (route.query != null && queryString != null) {
- const queryObject = [...new URLSearchParams(queryString).entries()]
- .reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {});
+ if (parts.length === 0) {
+ if (route.children) {
+ const child = check(route.children, []);
+ if (child) {
+ return {
+ route,
+ props,
+ child,
+ };
+ } else {
+ continue forEachRouteLoop;
+ }
+ }
- for (const q in route.query) {
- const as = route.query[q];
- if (queryObject[q]) {
- props.set(as, queryObject[q]);
+ if (route.hash != null && hash != null) {
+ props.set(route.hash, safeURIDecode(hash));
+ }
+
+ if (route.query != null && queryString != null) {
+ const queryObject = [...new URLSearchParams(queryString).entries()]
+ .reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {});
+
+ for (const q in route.query) {
+ const as = route.query[q];
+ if (queryObject[q]) {
+ props.set(as, safeURIDecode(queryObject[q]));
+ }
+ }
+ }
+
+ return {
+ route,
+ props,
+ };
+ } else {
+ if (route.children) {
+ const child = check(route.children, parts);
+ if (child) {
+ return {
+ route,
+ props,
+ child,
+ };
+ } else {
+ continue forEachRouteLoop;
+ }
+ } else {
+ continue forEachRouteLoop;
}
}
}
- return {
- route,
- props,
- };
+
+ return null;
}
- return null;
+ const _parts = path.split('/').filter(part => part.length !== 0);
+
+ return check(this.routes, _parts);
}
- private navigate(path: string, key: string | null | undefined, initial = false) {
+ private navigate(path: string, key: string | null | undefined, emitChange = true) {
const beforePath = this.currentPath;
- const beforeRoute = this.currentRoute.value;
this.currentPath = path;
const res = this.resolve(this.currentPath);
@@ -155,30 +212,27 @@ export class Router extends EventEmitter<{
throw new Error('no route found for: ' + path);
}
+ if (res.route.loginRequired) {
+ pleaseLogin('/');
+ }
+
const isSamePath = beforePath === path;
if (isSamePath && key == null) key = this.currentKey;
- this.currentComponent = res.route.component;
- this.currentProps = res.props;
+ this.current = res;
+ this.currentRef.value = res;
this.currentRoute.value = res.route;
- this.currentKey = this.currentRoute.value.globalCacheKey ?? key ?? Date.now().toString();
+ this.currentKey = res.route.globalCacheKey ?? key ?? path;
- if (!initial) {
+ if (emitChange) {
this.emit('change', {
beforePath,
path,
- route: this.currentRoute.value,
- props: this.currentProps,
+ resolved: res,
key: this.currentKey,
});
}
- }
- public getCurrentComponent() {
- return this.currentComponent;
- }
-
- public getCurrentProps() {
- return this.currentProps;
+ return res;
}
public getCurrentPath() {
@@ -189,19 +243,33 @@ export class Router extends EventEmitter<{
return this.currentKey;
}
- public push(path: string) {
+ public push(path: string, flag?: any) {
const beforePath = this.currentPath;
- this.navigate(path, null);
+ if (path === beforePath) {
+ this.emit('same');
+ return;
+ }
+ if (this.navHook) {
+ const cancel = this.navHook(path, flag);
+ if (cancel) return;
+ }
+ const res = this.navigate(path, null);
this.emit('push', {
beforePath,
path,
- route: this.currentRoute.value,
- props: this.currentProps,
+ route: res.route,
+ props: res.props,
key: this.currentKey,
});
}
- public change(path: string, key?: string | null) {
+ public replace(path: string, key?: string | null, emitEvent = true) {
this.navigate(path, key);
+ if (emitEvent) {
+ this.emit('replace', {
+ path,
+ key: this.currentKey,
+ });
+ }
}
}