Skip to content

Commit 8ec3533

Browse files
committed
[A] first push
1 parent 872d73c commit 8ec3533

File tree

8 files changed

+421
-17
lines changed

8 files changed

+421
-17
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
dist
3+
node_modules
4+
yarn.lock
5+
package-lock.json

BrowserRouter.svelte

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
import { setContext } from "svelte";
3+
import { historyStore } from "./historyRouter";
4+
5+
setContext("historyStore", historyStore);
6+
</script>
7+
8+
<slot />

LICENSE

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,46 @@
1-
MIT License
2-
31
Copyright (c) 2021 Alan Chen
42

5-
Permission is hereby granted, free of charge, to any person obtaining a copy
6-
of this software and associated documentation files (the "Software"), to deal
7-
in the Software without restriction, including without limitation the rights
8-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9-
copies of the Software, and to permit persons to whom the Software is
10-
furnished to do so, subject to the following conditions:
3+
"Anti 996" License Version 1.0 (Draft)
4+
5+
Permission is hereby granted to any individual or legal entity
6+
obtaining a copy of this licensed work (including the source code,
7+
documentation and/or related items, hereinafter collectively referred
8+
to as the "licensed work"), free of charge, to deal with the licensed
9+
work for any purpose, including without limitation, the rights to use,
10+
reproduce, modify, prepare derivative works of, distribute, publish
11+
and sublicense the licensed work, subject to the following conditions:
12+
13+
1. The individual or the legal entity must conspicuously display,
14+
without modification, this License and the notice on each redistributed
15+
or derivative copy of the Licensed Work.
16+
17+
2. The individual or the legal entity must strictly comply with all
18+
applicable laws, regulations, rules and standards of the jurisdiction
19+
relating to labor and employment where the individual is physically
20+
located or where the individual was born or naturalized; or where the
21+
legal entity is registered or is operating (whichever is stricter). In
22+
case that the jurisdiction has no such laws, regulations, rules and
23+
standards or its laws, regulations, rules and standards are
24+
unenforceable, the individual or the legal entity are required to
25+
comply with Core International Labor Standards.
1126

12-
The above copyright notice and this permission notice shall be included in all
13-
copies or substantial portions of the Software.
27+
3. The individual or the legal entity shall not induce, suggest or force
28+
its employee(s), whether full-time or part-time, or its independent
29+
contractor(s), in any methods, to agree in oral or written form, to
30+
directly or indirectly restrict, weaken or relinquish his or her
31+
rights or remedies under such laws, regulations, rules and standards
32+
relating to labor and employment as mentioned above, no matter whether
33+
such written or oral agreements are enforceable under the laws of the
34+
said jurisdiction, nor shall such individual or the legal entity
35+
limit, in any methods, the rights of its employee(s) or independent
36+
contractor(s) from reporting or complaining to the copyright holder or
37+
relevant authorities monitoring the compliance of the license about
38+
its violation(s) of the said license.
1439

15-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-
SOFTWARE.
40+
THE LICENSED WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
41+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
42+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
43+
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM,
44+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
45+
OTHERWISE, ARISING FROM, OUT OF OR IN ANY WAY CONNECTION WITH THE
46+
LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE LICENSED WORK.

Link.svelte

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script>
2+
import { push as pushState, replace as replaceState } from "./historyRouter";
3+
4+
export let replace = false;
5+
export let to = undefined;
6+
7+
const handler = () => {
8+
if (to) {
9+
if (replace) {
10+
replaceState(to);
11+
} else {
12+
pushState(to);
13+
}
14+
}
15+
};
16+
</script>
17+
18+
<a href="javascipt:void" on:click={handler}>
19+
<slot />
20+
</a>

Route.svelte

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<script>
2+
import { getContext } from "svelte";
3+
import { parseDynamicRoute } from "./url.js";
4+
export let component = undefined;
5+
export let path = undefined;
6+
7+
const historyStore = getContext("historyStore");
8+
let isFitCurrentPath = false;
9+
/**
10+
* current router view route info.
11+
*/
12+
let route = undefined;
13+
$: {
14+
const paramPareseResult = parseDynamicRoute($historyStore.currentRoute.path, path);
15+
if (paramPareseResult !== false) {
16+
isFitCurrentPath = true;
17+
route = {
18+
...$historyStore.currentRoute,
19+
params: paramPareseResult
20+
}
21+
} else {
22+
isFitCurrentPath = false;
23+
}
24+
}
25+
</script>
26+
{#if isFitCurrentPath}
27+
<svelte:component this={component} {route} />
28+
{:else}
29+
<!-- else content here -->
30+
{/if}

historyRouter.js

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import { writable } from "svelte/store";
2+
import { parse, stringify } from "./url";
3+
4+
/**
5+
* stack, get path and routes by store subscribe calling.
6+
*/
7+
export let historyStack;
8+
9+
let beforeHookHandler = () => true;
10+
11+
/**
12+
* store, pass reactive data to Route component.
13+
*/
14+
export const historyStore = writable({
15+
currentRoute: parse("/"),
16+
routes: [parse("/")]
17+
})
18+
19+
historyStore.subscribe((val) => {
20+
historyStack = val;
21+
});
22+
23+
/**
24+
* router hook before changing
25+
*
26+
* @param {function} hook
27+
*/
28+
export const onBeforeEnter = (hook) => {
29+
beforeHookHandler = hook;
30+
}
31+
32+
/**
33+
* handle route jumping logic by calling hook, if step into next route or jump to redirect route, change promise to resolve. else if intercept router, change promise to reject.
34+
*
35+
* @param {object} to
36+
* @param {object} from
37+
* @param {string} action
38+
*/
39+
const callHook = (to, from, action) => {
40+
return new Promise((resolve, reject) => {
41+
let nextStatus = undefined;
42+
const next = (res = true) => nextStatus = res;
43+
beforeHookHandler(to, from, next, action);
44+
if (nextStatus === true) {
45+
resolve();
46+
} else if (nextStatus === false) {
47+
reject();
48+
} else if (
49+
typeof nextStatus === "string" ||
50+
typeof nextStatus === "object"
51+
) {
52+
resolve(nextStatus);
53+
}
54+
});
55+
}
56+
57+
/**
58+
* push forward a history route
59+
*
60+
* @param {string | object} route
61+
*/
62+
export const push = async (route) => {
63+
try {
64+
const url = stringify(route);
65+
const routeObj = parse(route);
66+
if (historyStack.currentRoute.url !== url) {
67+
const status = await callHook(routeObj, historyStack.currentRoute, "push");
68+
if (status !== undefined) {
69+
push(status);
70+
} else {
71+
window.history.pushState(null, "", url);
72+
historyStore.update((store) => {
73+
store.currentRoute = routeObj;
74+
store.routes.push(routeObj);
75+
return store;
76+
});
77+
}
78+
}
79+
} catch (error) {
80+
if (error) {
81+
console.log("[router push error:]", error);
82+
}
83+
}
84+
}
85+
86+
/**
87+
* replace current history route
88+
*
89+
* @param {string | object} route
90+
*/
91+
export const replace = async (route) => {
92+
try {
93+
const url = stringify(route);
94+
const routeObj = parse(route);
95+
if (historyStack.currentRoute.url !== url) {
96+
const status = await callHook(routeObj, historyStack.currentRoute, "replace");
97+
if (status !== undefined) {
98+
push(status);
99+
} else {
100+
window.history.replaceState(null, "", url);
101+
historyStore.update((store) => {
102+
store.currentRoute = routeObj;
103+
store.routes[0] = routeObj;
104+
return store;
105+
});
106+
}
107+
}
108+
} catch (error) {
109+
if (error) {
110+
console.log("[router replace error:]", error);
111+
}
112+
}
113+
}
114+
115+
/**
116+
* jump route
117+
*
118+
* @param {number} step
119+
*/
120+
export const go = async (step) => {
121+
try {
122+
const curretIndex = historyStack.routes.findIndex(item => item.url === historyStack.currentRoute.url);
123+
const stepRouteObj = historyStack.routes[curretIndex + step];
124+
if (step !== 0) {
125+
const status = await callHook(stepRouteObj, historyStack.currentRoute, "go");
126+
if (status !== undefined) {
127+
push(status);
128+
} else {
129+
window.history.go(step);
130+
historyStore.update((store) => {
131+
store.currentRoute = stepRouteObj;
132+
return store;
133+
});
134+
}
135+
}
136+
} catch (error) {
137+
if (error) {
138+
console.log("[router go error:]", error);
139+
}
140+
}
141+
}
142+
143+
/**
144+
* step forward a route
145+
*/
146+
export const forward = async () => {
147+
try {
148+
const curretIndex = historyStack.routes.findIndex(item => item.url === historyStack.currentRoute.url);
149+
const forwardRouteObj = historyStack.routes[curretIndex + 1];
150+
const status = await callHook(forwardRouteObj, historyStack.currentRoute, "forward");
151+
if (status !== undefined) {
152+
push(status);
153+
} else {
154+
window.history.forward();
155+
historyStore.update((store) => {
156+
store.currentRoute = forwardRouteObj;
157+
return store;
158+
});
159+
}
160+
} catch (error) {
161+
if (error) {
162+
console.log("[router forward error:]", error);
163+
}
164+
}
165+
}
166+
167+
/**
168+
* step back a route
169+
*/
170+
export const back = async () => {
171+
try {
172+
const curretIndex = historyStack.routes.findIndex(item => item.url === historyStack.currentRoute.url);
173+
const backRouteObj = historyStack.routes[curretIndex - 1];
174+
const status = await callHook(backRouteObj, historyStack.currentRoute, "back");
175+
if (status !== undefined) {
176+
push(status);
177+
} else {
178+
window.history.back();
179+
historyStore.update((store) => {
180+
store.currentRoute = backRouteObj;
181+
return store;
182+
});
183+
}
184+
} catch (error) {
185+
if (error) {
186+
console.log("[router go error:]", error);
187+
}
188+
}
189+
}
190+
191+
window.onpopstate = async (e) => {
192+
try {
193+
const url = window.location.href.split(window.location.origin)[1];
194+
const routeObj = parse(url);
195+
const status = await callHook(routeObj, historyStack.currentRoute, "popstate");
196+
if (status !== undefined) {
197+
push(status);
198+
} else {
199+
historyStore.update((store) => {
200+
store.currentRoute = routeObj;
201+
return store;
202+
});
203+
}
204+
} catch (error) {
205+
if (error) {
206+
console.log("[router change error:]", error);
207+
} else {
208+
/**
209+
* avoid browser history stepping back or forward
210+
*/
211+
const url = window.location.href.split(window.location.origin)[1];
212+
const routeObj = parse(url);
213+
const curretIndex = historyStack.routes.findIndex(item => item.url === historyStack.currentRoute.url);
214+
const routeIndex = historyStack.routes.findIndex(item => item.url === routeObj.url);
215+
const isBackForward = routeIndex < curretIndex;
216+
const isNextForward = routeIndex > curretIndex;
217+
if (isBackForward) {
218+
window.history.forward();
219+
} else if (isNextForward) {
220+
window.history.back();
221+
}
222+
}
223+
}
224+
}

index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import BrowserRouterComponent from "./BrowserRouter.svelte";
2+
import RouteComponent from "./Route.svelte";
3+
import LinkComponent from "./Link.svelte";
4+
import * as router from "./historyRouter";
5+
6+
export const BrowserRouter = BrowserRouterComponent;
7+
export const Route = RouteComponent;
8+
export const Link = LinkComponent;
9+
export const historyRouter = router;

0 commit comments

Comments
 (0)