Skip to content

Commit

Permalink
[refactor] Core Logic
Browse files Browse the repository at this point in the history
  • Loading branch information
TechQuery committed Oct 22, 2019
1 parent 5f7918e commit c8e6b32
Show file tree
Hide file tree
Showing 15 changed files with 269 additions and 200 deletions.
18 changes: 17 additions & 1 deletion ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ import { HTMLRouter } from 'cell-router/source';

import { history } from '../model';

function Test({ path }) {
return <span>{path}</span>;
}

function Example({ path }) {
return <span>{path}</span>;
}

@observer
@component({
tagName: 'page-router',
Expand All @@ -77,7 +85,15 @@ export default class PageRouter extends HTMLRouter {
<a href="example">Example</a>
</li>
</ul>
<div>{history.path}</div>
<div>
{matchRoutes(
[
{ paths: ['test'], component: Test },
{ paths: ['example'], component: Example }
],
history.path
)}
</div>
</main>
);
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cell-router",
"version": "2.0.0-beta.6",
"version": "2.0.0-rc.0",
"license": "LGPL-3.0",
"description": "Web Component Router based on WebCell & MobX",
"keywords": [
Expand Down Expand Up @@ -39,7 +39,7 @@
"koapache": "^1.0.6",
"lint-staged": "^9.4.2",
"microbundle": "^0.11.0",
"mobx": "^5.14.0",
"mobx": "^5.14.2",
"mobx-web-cell": "^0.2.3",
"parcel-bundler": "^1.12.4",
"prettier": "^1.18.2",
Expand Down
74 changes: 56 additions & 18 deletions source/HTMLRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,69 @@ import { mixin, delegate } from 'web-cell';

import { History } from './History';

const NonRoute = /^((\w+:)?\/\/|#|javascript:)/;
type LinkElement = HTMLAnchorElement | HTMLAreaElement;

export abstract class HTMLRouter extends mixin() {
protected abstract history: History;

constructor() {
super();
static isRoute(link: LinkElement) {
const path = link.getAttribute('href');

this.addEventListener(
'click',
delegate('a[href], area[href]', this.handleLink)
return (
(link.target || '_self') === '_self' &&
/^https?:$/.test(link.protocol) &&
path !== link.href &&
path[0] !== '#'
);
}

handleLink = (
event: MouseEvent,
link: HTMLAnchorElement | HTMLAreaElement
) => {
if ((link.target || '_self') !== '_self') return;
get parentRouter(): HTMLRouter | undefined {
var node = this;
// @ts-ignore
while ((node = node.parentNode || node.host))
if (node instanceof HTMLRouter) return node;
}

event.preventDefault(), event.stopPropagation();
protected abstract history: History;

const path = link.getAttribute('href');
push = delegate('a[href]', (event: MouseEvent) => {
const link = event.target as LinkElement;

if (HTMLRouter.isRoute(link)) {
event.preventDefault(), event.stopPropagation();

this.history.push(
link.getAttribute('href'),
link.title || link.textContent
);
} else if (/^#.+/.test(link.getAttribute('href'))) {
const anchor = this.querySelector(link.hash);

if (anchor) anchor.scrollIntoView({ behavior: 'smooth' });
}
});

if (path && !NonRoute.test(path))
this.history.push(path, link.title || link.textContent.trim());
};
back = () => this.history.back();

connectedCallback() {
super.connectedCallback();

const { hash, href } = window.location;

if (!this.parentRouter) {
this.history.base = hash ? href.slice(0, -hash.length) : href;

const { base, path, title, ...data } = history.state || {};

this.history.replace(hash.slice(1), title, data);
} else {
this.history.base = href;
}

this.addEventListener('click', this.push);
window.addEventListener('popstate', this.back);
}

disconnectedCallback() {
this.removeEventListener('click', this.push);
window.removeEventListener('popstate', this.back);
}
}
77 changes: 39 additions & 38 deletions source/History.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { observable } from 'mobx';
import { parseURL } from './utility';
import { observable, action } from 'mobx';

const { location, history } = window;

Expand All @@ -9,58 +8,60 @@ export enum HistoryMode {
}

export class History {
protected mode: HistoryMode;
protected root = location.pathname;
protected baseURL: string;

@observable
path: string;

constructor(mode: HistoryMode = HistoryMode.hash) {
this.mode = mode;
this.path =
mode === HistoryMode.path ? '' : location.hash.split('#')[1];
set base(value: string) {
const { origin, pathname, hash } = new URL(value, location.href);

var { title } = history.state || '';

if (title) document.title = title;
else title = document.title;
this.baseURL = origin + pathname + hash;
}

history.replaceState(
{ mode, root: this.root, path: this.path, title },
title
);
get base() {
return this.baseURL;
}

window.addEventListener('popstate', ({ state }) => {
if (!state) return;
@observable
path = '';

const { mode, root, path, title } = state;
mode: HistoryMode;

if (mode !== this.mode || root !== this.root) return;
constructor(mode = HistoryMode.hash) {
this.mode = mode;
}

if (title) document.title = title;
@action
push(path: string, title = document.title, data?: any) {
const { base, mode } = this;

this.path = path;
});
}
history.pushState(
{ ...data, base, path, title },
(document.title = title),
base + mode + path
);

mount(path: string = location.pathname + location.hash) {
(this.root = path), (this.path = '');
this.path = path;
}

push(path: string, title: string, data?: any) {
if (title) document.title = title;
else title = document.title;
@action
replace(path: string, title = document.title, data?: any) {
const { base, mode } = this;

history.pushState(
{ ...data, mode: this.mode, root: this.root, path, title },
title,
(this.root + this.mode + path).replace(/\/{2,}/g, '/')
history.replaceState(
{ ...data, base, path, title },
(document.title = title),
base + mode + path
);

this.path = path;
}

get parsedPath() {
return parseURL(this.path);
@action
back() {
const { base, path, title } = history.state || {};

if (base === this.base) {
if (typeof path === 'string') this.path = path;
if (title) document.title = title;
}
}
}
29 changes: 0 additions & 29 deletions source/utility.ts

This file was deleted.

43 changes: 43 additions & 0 deletions source/utility.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { createCell } from 'web-cell';

export function parsePathData(URI: string) {
const [path, data] = URI.split('?'),
params = {};
// @ts-ignore
for (let [key, value] of Array.from(new URLSearchParams(data).entries())) {
const item = params[key];

try {
value = JSON.parse(value);
} catch (error) {
/**/
}

if (!(item != null)) {
params[key] = value;
continue;
}

if (!(item instanceof Array)) params[key] = [item];

params[key].push(value);
}

return { path, params };
}

interface Route {
paths: (string | RegExp)[];
component: Function;
}

export function matchRoutes(list: Route[], path: string) {
for (const { paths, component: Component } of list)
for (const item of paths)
if (
typeof item === 'string'
? path.startsWith(item)
: item.exec(path)
)
return <Component {...parsePathData(path)} />;
}
7 changes: 5 additions & 2 deletions test/DOM-polyfill.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { JSDOM } from 'jsdom';

const { window } = new JSDOM('', { url: 'http://localhost/' });
const { window } = new JSDOM('', {
url: 'http://localhost/',
pretendToBeVisual: true
});

for (const key of ['window', 'URL']) {
for (const key of ['window', 'document', 'URL']) {
// @ts-ignore
global[key] = window[key];
}
Loading

0 comments on commit c8e6b32

Please sign in to comment.