forked from Alamantus/FeatherWiki
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nanochoo.js
161 lines (136 loc) · 4.22 KB
/
nanochoo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
* Modified from nanochoo, a fork of choo at about half the size, specifically for use with Feather Wiki
* https://github.com/nanaian/nanochoo
*
* The `nanochoo` fork of `choo@6` removed the `navigate` events and `nanohref` package
* that Feather Wiki needs to prevent links from changing pages, so that has been
* added back in along with a `go` event that combines the removed `navigate`,
* `pushState`, and `replaceState` events into one. I also removed the `toString`
* method entirely because Feather Wiki is only focused on browser use.
*
* `nanochoo`'s primary changes to `choo@6` are recorded in its README on GitHub.
*
* @licence MIT
*/
var nanobus = require('nanobus') // Handles the emitter
var nanohref = require('nanohref') // Prevents browser navigation within wiki
var nanomorph = require('nanomorph') // Efficiently diffs DOM elements for render
var nanoraf = require('nanoraf') // Prevents too many renders
function documentReady (f) {
if (document.readyState === 'complete' || document.readyState === 'interactive') f()
else document.addEventListener('DOMContentLoaded', f)
}
function getParams () {
const p = {};
const s = new URLSearchParams(window.location.search);
s.forEach((v, k) => {
v = s.getAll(k);
p[k] = v.length > 1 ? v : v[0];
});
return p;
};
var HISTORY = {};
export default function Choo () {
if (!(this instanceof Choo)) return new Choo()
var self = this
// define events used by choo
this._events = {
ONLOAD: 'DOMContentLoaded',
TITLE: 'DOMTitleChange',
RENDER: 'render',
GO: 'go',
}
// properties for internal use only
this._loaded = false
this._stores = []
this._tree = null
this._viewHandler = null
// properties that are part of the API
this.emitter = nanobus('choo.emit')
this.emit = this.emitter.emit.bind(this.emitter)
this.state = {
events: this._events,
title: document.title,
query: getParams(),
}
this.emitter.prependListener(this._events.TITLE, function (title) {
self.state.title = document.title = title
})
}
Choo.prototype.view = function (handler) {
this._viewHandler = handler
}
Choo.prototype.use = function (cb) {
var self = this
this._stores.push(function () {
cb(self.state, self.emitter, self)
})
}
Choo.prototype.start = function () {
var self = this
const hashScroll = () => {
const el = document.getElementById(location.hash.substring(1));
if (!el) return false;
el?.scrollIntoView();
return true;
}
this.emitter.prependListener(this._events.GO, function (to = null, action = 'push') {
if (to) {
history[action + 'State'](HISTORY, self.state.title, to)
}
self.state.query = getParams()
if (self._loaded) {
self.emitter.emit(self._events.RENDER, function () {
// Scroll to top of page if no location hash is set
hashScroll() || window.scroll(0, 0);
})
}
})
window.onpopstate = function () {
self.emitter.emit(self._events.GO)
}
nanohref(function (location) {
var href = location.href
var currHref = window.location.href
if (href === currHref) return
self.emitter.emit(self._events.GO, href)
})
this._stores.forEach(function (initStore) {
initStore()
})
function render () {
return self._viewHandler(self.state, function (eventName, data) {
self.emitter.emit.apply(self.emitter, arguments)
})
}
this._tree = render()
this._rq = [] // render queue
this._rd = null // render debounce
this.emitter.prependListener(this._events.RENDER, cb => {
if (typeof cb === 'function') self._rq.push(cb)
if (self._rd !== null) clearTimeout(self._rd)
self._rd = setTimeout(nanoraf(() => {
var newTree = render()
nanomorph(self._tree, newTree)
while(self._rq.length > 0) (self._rq.shift())()
}), 9)
})
documentReady(function () {
self.emitter.emit(self._events.ONLOAD)
self._loaded = true
hashScroll();
})
return this._tree
}
Choo.prototype.mount = function mount (selector) {
var self = this
documentReady(function () {
var newTree = self.start()
if (typeof selector === 'string') {
self._tree = document.querySelector(selector)
} else {
self._tree = selector
}
nanomorph(self._tree, newTree)
})
}