nexusframework 0.3.0-beta.72 → 0.3.0-beta.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE.md +55 -55
  2. package/README.md +12 -12
  3. package/about/index.json +5 -5
  4. package/about/index.nhp +4 -4
  5. package/icon.png +0 -0
  6. package/index.d.ts +12 -15
  7. package/index.js +94 -81
  8. package/index.js.map +1 -1
  9. package/index.ts +101 -89
  10. package/indexOfSkeleton.nhp +133 -133
  11. package/legacySkeleton.nhp +22 -22
  12. package/loader/overlay.css +1 -1
  13. package/loader/overlay.html +25 -25
  14. package/loader/overlay.scss +152 -152
  15. package/package.json +102 -89
  16. package/scripts/es5/compat.js +101 -0
  17. package/scripts/es5/compat.js.map +1 -0
  18. package/scripts/es5/compat.min.js +2 -0
  19. package/scripts/es5/compat.min.js.map +1 -0
  20. package/scripts/es5/loader.js +0 -0
  21. package/scripts/es5/loader.js.map +0 -0
  22. package/scripts/es5/loader.min.js +1 -1
  23. package/scripts/es5/loader.min.js.map +0 -0
  24. package/scripts/es5/nexusframeworkclient.js +1092 -1103
  25. package/scripts/es5/nexusframeworkclient.js.map +1 -1
  26. package/scripts/es5/nexusframeworkclient.min.js +1 -1
  27. package/scripts/es5/nexusframeworkclient.min.js.map +1 -1
  28. package/scripts/es6/compat.js +65 -0
  29. package/scripts/es6/compat.js.map +1 -0
  30. package/scripts/es6/compat.min.js +2 -0
  31. package/scripts/es6/compat.min.js.map +1 -0
  32. package/scripts/es6/loader.js +0 -0
  33. package/scripts/es6/loader.js.map +0 -0
  34. package/scripts/es6/loader.min.js +1 -1
  35. package/scripts/es6/loader.min.js.map +0 -0
  36. package/scripts/es6/nexusframeworkclient.js +1026 -1049
  37. package/scripts/es6/nexusframeworkclient.js.map +1 -1
  38. package/scripts/es6/nexusframeworkclient.min.js +1 -1
  39. package/scripts/es6/nexusframeworkclient.min.js.map +1 -1
  40. package/scripts/index.d.ts +259 -251
  41. package/scripts/src/compat.ts +62 -0
  42. package/scripts/src/loader.ts +385 -385
  43. package/scripts/src/nexusframeworkclient.ts +1163 -1185
  44. package/scripts/tsconfig.json +11 -11
  45. package/src/cli.d.ts +1 -0
  46. package/src/cli.js +102 -102
  47. package/src/cli.js.map +1 -1
  48. package/src/cli.ts +101 -101
  49. package/src/compileScripts.d.ts +2 -2
  50. package/src/compileScripts.js +145 -145
  51. package/src/compileScripts.js.map +1 -1
  52. package/src/compileScripts.ts +153 -153
  53. package/src/nexusframework.d.ts +177 -168
  54. package/src/nexusframework.js +3578 -3422
  55. package/src/nexusframework.js.map +1 -1
  56. package/src/nexusframework.ts +3631 -3473
  57. package/templates/basic/package.json +3 -3
  58. package/templates/basic/www/skeleton.nhp +1 -1
  59. package/templates/bootstrap/package.json +2 -2
  60. package/templates/bootstrap/www/skeleton.nhp +1 -1
  61. package/templates/bootstrap-material-design/package.json +2 -2
  62. package/templates/bootstrap-material-design/www/skeleton.nhp +1 -1
  63. package/tsconfig.json +25 -22
  64. package/types.d.ts +612 -564
@@ -1,1185 +1,1163 @@
1
- /// <reference path="../index.d.ts" />
2
- Object.defineProperties(window, {
3
- NexusFrameworkTransport: {
4
- configurable: true,
5
- set: function (instance) {
6
- Object.defineProperty(window, "NexusFrameworkTransport", {
7
- value: instance
8
- });
9
- },
10
- get: function () {
11
- var impl: NexusFrameworkTransport;
12
- if ("XMLHttpRequest" in window) {
13
- class NexusFrameworkXMLHttpRequestResponse implements NexusFrameworkTransportResponse {
14
- hadData: boolean;
15
- private _url: string;
16
- private parsedJson: any;
17
- private request: XMLHttpRequest;
18
- private processedHeaders: {[index: string]: string[]};
19
- constructor(request: XMLHttpRequest, url: string, hadData?: boolean) {
20
- this._url = url;
21
- this.request = request;
22
- this.hadData = hadData;
23
- }
24
- get url() {
25
- return this.request.responseURL || this._url;
26
- }
27
- get code() {
28
- return this.request.status;
29
- }
30
- get contentLength() {
31
- return parseInt(this.request.getResponseHeader("content-length")) || this.request.responseText.length;
32
- }
33
- get contentFromJSON() {
34
- if (!this.parsedJson)
35
- this.parsedJson = JSON.parse(this.request.responseText);
36
- return this.parsedJson;
37
- }
38
- get contentAsString() {
39
- return this.request.responseText;
40
- }
41
- get headers(): {[index: string]: string[]} {
42
- if (!this.processedHeaders) {
43
- const headers: {[index: string]: string[]} = this.processedHeaders = {};
44
- this.request.getAllResponseHeaders().split(/\r?\n/g).forEach(function (header) {
45
- const index = header.indexOf(":");
46
- var key: string, val: string;
47
- if (index > 0) {
48
- key = header.substring(0, index).trim().toLowerCase();
49
- val = header.substring(index + 1).trim();
50
- } else
51
- key = header.trim().toLowerCase();
52
- var list = headers[key];
53
- if (!list)
54
- list = headers[key] = [];
55
- if (val)
56
- list.push(val);
57
- });
58
- }
59
- return this.processedHeaders;
60
- }
61
- }
62
- const execute = function (method: string, url: string, data: any, cb: (res: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void) {
63
- const request = new XMLHttpRequest();
64
- request.open(method, url, true);
65
- if (extraHeaders)
66
- Object.keys(extraHeaders).forEach(function (key) {
67
- request.setRequestHeader(key, extraHeaders[key]);
68
- });
69
- if (progcb)
70
- request.onprogress = function (ev) {
71
- if (ev.lengthComputable && ev.total)
72
- progcb(ev.loaded, ev.total);
73
- }
74
- request.onreadystatechange = function (e) {
75
- if (request.readyState === XMLHttpRequest.DONE) {
76
- cb(new NexusFrameworkXMLHttpRequestResponse(request, url, !!data));
77
- }
78
- }
79
- const type = data && data.type;
80
- if (type)
81
- switch(type) {
82
- case "":
83
- case "text/urlencoded":
84
- var dat = "";
85
- for (var entry of data.data as any) {
86
- if (dat)
87
- dat += "&";
88
- dat += encodeURIComponent(entry[0]);
89
- dat += "=";
90
- dat += encodeURIComponent(entry[1]);
91
- }
92
- request.setRequestHeader("Content-Type", "text/urlencoded");
93
- request.send(dat);
94
- break;
95
- case "multipart/form-data":
96
- request.send(data.data);
97
- break;
98
- default:
99
- request.send(data);
100
- }
101
- else
102
- request.send(data);
103
- }
104
- impl = {
105
- get: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void) {
106
- execute("GET", url, undefined, cb, extraHeaders, progcb);
107
- },
108
- head: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void) {
109
- execute("HEAD", url, undefined, cb, extraHeaders, progcb);
110
- },
111
- post: function (url: string, data: any, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void) {
112
- execute("POST", url, data, cb, extraHeaders, progcb);
113
- },
114
- put: function (url: string, data: any, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void) {
115
- execute("PUT", url, data, cb, extraHeaders, progcb);
116
- },
117
- execute,
118
- del: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void) {
119
- execute("DELETE", url, undefined, cb, extraHeaders, progcb);
120
- }
121
- };
122
- } else {
123
- impl = {
124
- get(url: string, cb: (res: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}): void {
125
- cb({
126
- url,
127
- code: 0,
128
- get contentFromJSON() {
129
- throw new Error("No response to parse.");
130
- },
131
- contentAsString: "",
132
- contentLength: 0,
133
- headers: {}
134
- });
135
- },
136
- head(url: string, cb: (res: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}): void {
137
- cb({
138
- url,
139
- code: 0,
140
- get contentFromJSON() {
141
- throw new Error("No response to parse.");
142
- },
143
- contentAsString: "",
144
- contentLength: 0,
145
- headers: {}
146
- });
147
- },
148
- put(url: string, data: any, cb: (res: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}): void {
149
- cb({
150
- url,
151
- code: 0,
152
- get contentFromJSON() {
153
- throw new Error("No response to parse.");
154
- },
155
- contentAsString: "",
156
- contentLength: 0,
157
- headers: {}
158
- });
159
- },
160
- post(url: string, data: any, cb: (res: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}): void {
161
- cb({
162
- url,
163
- code: 0,
164
- get contentFromJSON() {
165
- throw new Error("No response to parse.");
166
- },
167
- contentAsString: "",
168
- contentLength: 0,
169
- headers: {}
170
- });
171
- },
172
- execute(method: string, url: string, data: any, cb: (res: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}): void {
173
- cb({
174
- url,
175
- code: 0,
176
- get contentFromJSON() {
177
- throw new Error("No response to parse.");
178
- },
179
- contentAsString: "",
180
- contentLength: 0,
181
- headers: {}
182
- });
183
- },
184
- del(url: string, cb: (res: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}): void {
185
- cb({
186
- url,
187
- code: 0,
188
- get contentFromJSON() {
189
- throw new Error("No response to parse.");
190
- },
191
- contentAsString: "",
192
- contentLength: 0,
193
- headers: {}
194
- });
195
- }
196
- };
197
- }
198
- Object.defineProperty(window, "NexusFrameworkTransport", {
199
- value: impl
200
- });
201
- return impl;
202
- }
203
- },
204
- NexusFrameworkImpl: {
205
- configurable: true,
206
- set: function (instance) {
207
- Object.defineProperty(window, "NexusFrameworkImpl", {
208
- value: instance
209
- });
210
- },
211
- get: function () {
212
- const loader = window.NexusFrameworkLoader;
213
- const showError = loader.showError;
214
- const debug = /*function(...args: any[]){};*/console.log.bind(console);
215
- const GA_ANALYTICS: NexusFrameworkAnalyticsAdapter = {
216
- reportError: function (err: Error, fatal?: boolean) {
217
- if (window.ga)
218
- try {
219
- window.ga('send', 'exception', {
220
- 'exDescription': (err.stack || "" + err).replace(/\n/g, "\n\t"),
221
- 'exFatal': fatal
222
- });
223
- } catch (e) {
224
- console.warn(e);
225
- }
226
- },
227
- reportEvent: function (category: string, action: string, label?: string, value?: number) {
228
- if (window.ga)
229
- try {
230
- window.ga('send', 'event', category, action, label, value);
231
- } catch (e) {
232
- console.warn(e);
233
- }
234
- },
235
- reportPage: function (path?: string) {
236
- if (window.ga)
237
- try {
238
- if (!path)
239
- path = location.pathname;
240
- window.ga('set', 'page', path);
241
- window.ga('send', 'pageview');
242
- } catch (e) {
243
- console.warn(e);
244
- }
245
- }
246
- };
247
- const r = document.createElement("a");
248
- const protocol = location.href.match(/^\w+:/)[0];
249
- const resolveUrl = function (url: string) {
250
- r.setAttribute("href", url);
251
- var href = r.href;
252
- if (/^\/\//.test(href))
253
- href = protocol + href;
254
- return href;
255
- }
256
- interface PageSystemImpl {
257
- requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void;
258
- }
259
- const convertResponse = function (res: NexusFrameworkPageSystemResponse, url = location.href) {
260
- var storage: any;
261
- return (typeof res.data === "string" || res.data instanceof String) ? {
262
- url,
263
- code: res.code,
264
- contentAsString: res.data,
265
- get contentLength() {
266
- return parseInt(res.headers['content-length'] && res.headers['content-length'][0]) || res.data.length
267
- },
268
- get contentFromJSON() {
269
- if (!storage)
270
- storage = JSON.parse(res.data);
271
- return storage;
272
- },
273
- headers: res.headers
274
- } : {
275
- url,
276
- code: res.code,
277
- get contentAsString() {
278
- if (!storage)
279
- storage = JSON.stringify(res.data);
280
- return storage;
281
- },
282
- contentFromJSON: res.data,
283
- headers: res.headers,
284
- get contentLength() {
285
- var length = parseInt(res.headers['content-length'] && res.headers['content-length'][0]);
286
- if (length)
287
- return length;
288
- if (!storage)
289
- storage = JSON.stringify(res.data);
290
- return storage.length;
291
- }
292
- };
293
- }
294
- const loaderContainerRegex = /(^|\s)loader\-(progress|error)\-container(\s|$)/;
295
- class NexusFrameworkBase implements NexusFrameworkClient {
296
- public readonly url: string;
297
- public readonly io: SocketIOClient.Socket;
298
- private activerid = 0;
299
- private _analytics: NexusFrameworkAnalyticsAdapter;
300
- private errorreporter: (err: Error, fatal?: boolean) => void;
301
- private components: {[index: string]: NexusFrameworkComponentFactory} = {};
302
- private pagesyshandler: (res: NexusFrameworkTransportResponse, pageReady: (err?: Error) => void) => void;
303
- private pagesysprerequest: (path: string) => boolean;
304
- private pagesysimpl: PageSystemImpl;
305
- private currentUserID: string = undefined;
306
- private progressVisible = false;
307
- private animationTiming = 500;
308
- public constructor(url = "/", io?: SocketIOClient.Socket) {
309
- url = resolveUrl(url);
310
- if (!/\/$/.test(url))
311
- url += "/";
312
- Object.defineProperties(this, {
313
- io: {
314
- value: io
315
- },
316
- url: {
317
- value: url
318
- }
319
- });
320
- this._analytics = GA_ANALYTICS;
321
- }
322
-
323
- resolveUrl(url: string) {
324
- if (/^\w+\:/.test(url))
325
- return url;
326
- return resolveUrl(this.url + url);
327
- }
328
-
329
- disableAll(root: HTMLElement = document.body) {
330
- const focusable = root.querySelectorAll("a, input, select, textarea, iframe, button, *[focusable], *[tabindex]");
331
- for (var i = 0; i < focusable.length; i++) {
332
- const child = focusable[i];
333
- const tabindex = child.getAttribute("tabindex");
334
- if (tabindex == "-1")
335
- return;
336
- child.setAttribute("data-tabindex", tabindex ? tabindex : "");
337
- child.setAttribute("data-tabindex", "-1");
338
- }
339
- }
340
- enableAll(root: HTMLElement = document.body) {
341
- const focusable = root.querySelectorAll("*[data-tabindex]");
342
- for (var i = 0; i < focusable.length; i++) {
343
- const child = focusable[i];
344
- child.setAttribute("tabindex", child.getAttribute("data-tabindex"));
345
- child.removeAttribute("data-tabindex");
346
- }
347
- }
348
-
349
- get analytics() {
350
- return this._analytics;
351
- }
352
- set analytics(value) {
353
- this._analytics = value ? value : GA_ANALYTICS;
354
- }
355
-
356
- reportError(err: Error, fatal?: boolean) {
357
- console[fatal ? "error" : "warn"](err.stack);
358
- if (this.errorreporter)
359
- this.errorreporter(err, fatal);
360
- }
361
- installErrorReporter(errorreporter: (err: Error, fatal?: boolean) => void) {
362
- this.errorreporter = errorreporter;
363
- }
364
-
365
- registerComponent(selector: string, impl: NexusFrameworkComponentFactory) {
366
- if (impl) {
367
- this.components[selector] = impl;
368
- this.createComponents(document.head);
369
- this.createComponents(document.body);
370
- } else {
371
- delete this.components[selector];
372
- var destroy = (root: HTMLElement) => {
373
- const elements = root.querySelectorAll(selector);
374
- if (!elements.length)
375
- return;
376
-
377
- for (var i = 0; i < elements.length; i++) {
378
- const element = elements[i];
379
- var components = element['__nf_cmapping__'];
380
- if (!components)
381
- components = element['__nf_cmapping__'] = {};
382
- const component: NexusFrameworkComponent = components[selector];
383
- if (component) {
384
- try {
385
- component.destroy();
386
- } catch (e) {
387
- this.reportError(e);
388
- }
389
- }
390
- }
391
- }
392
- destroy(document.head);
393
- destroy(document.body);
394
- }
395
- }
396
- unregisterComponent(selector: string, impl: NexusFrameworkComponentFactory) {
397
- }
398
- createComponents(root: Element): void {
399
- Object.keys(this.components).forEach((selector) => {
400
- const elements = root.querySelectorAll(selector);
401
- if (!elements.length)
402
- return;
403
-
404
- for (var i = 0; i < elements.length; i++) {
405
- const element = elements[i];
406
- var components = element['__nf_cmapping__'];
407
- if (!components)
408
- components = element['__nf_cmapping__'] = {};
409
- if (components[selector])
410
- continue;
411
- const componentFactory = this.components[selector];
412
- try {
413
- (components[selector] = new componentFactory()).create(element as HTMLElement);
414
- } catch (e) {
415
- this.reportError(e);
416
- }
417
- }
418
- });
419
- }
420
- destroyComponents(root: HTMLElement): void {
421
- Object.keys(this.components).forEach((selector) => {
422
- const elements = root.querySelectorAll(selector);
423
- if (!elements.length)
424
- return;
425
-
426
- for (var i = 0; i < elements.length; i++) {
427
- const element = elements[i];
428
- var components = element['__nf_cmapping__'];
429
- if (!components)
430
- components = element['__nf_cmapping__'] = {};
431
- const component: NexusFrameworkComponent = components[selector];
432
- if (component) {
433
- try {
434
- component.destroy();
435
- } catch (e) {
436
- this.reportError(e);
437
- }
438
- }
439
- }
440
- });
441
- }
442
- restoreComponents(root: HTMLElement, state: Object): void {
443
- Object.keys(this.components).forEach((selector) => {
444
- const states: any[] = state[selector];
445
- if (!states)
446
- return;
447
-
448
- const elements = root.querySelectorAll(selector);
449
- if (!elements.length)
450
- return;
451
-
452
- for (var i = 0; i < elements.length; i++) {
453
- const element = elements[i];
454
- if (states.length) {
455
- const _state = states.shift();
456
- if (_state) {
457
- var components = element['__nf_cmapping__'];
458
- if (!components)
459
- components = element['__nf_cmapping__'] = {};
460
- var component: NexusFrameworkComponent = components[selector];
461
- if (!component) {
462
- const componentFactory = this.components[selector];
463
- try {
464
- (component = components[selector] = new componentFactory()).create(element as HTMLElement);
465
- } catch (e) {
466
- this.reportError(e);
467
- console.warn(e);
468
- continue;
469
- }
470
- }
471
- try {
472
- component.restore(_state);
473
- } catch (e) {
474
- this.reportError(e);
475
- console.warn(e);
476
- }
477
- }
478
- } else
479
- break;
480
- }
481
- });
482
- }
483
- saveComponents(root: HTMLElement): Object {
484
- var state = {};
485
- Object.keys(this.components).forEach((selector) => {
486
- const states = state[selector] = [];
487
- const elements = root.querySelectorAll(selector);
488
- if (!elements.length)
489
- return;
490
-
491
- for (var i = 0; i < elements.length; i++) {
492
- const element = elements[i];
493
- var components = element['__nf_cmapping__'];
494
- if (!components)
495
- components = element['__nf_cmapping__'] = {};
496
- var component: NexusFrameworkComponent = components[selector];
497
- if (!component) {
498
- const componentFactory = this.components[selector];
499
- try {
500
- (component = components[selector] = new componentFactory).create(element as HTMLElement);
501
- } catch (e) {
502
- states.push(undefined);
503
- this.reportError(e);
504
- console.warn(e);
505
- continue;
506
- }
507
- }
508
- try {
509
- states.push(component.save());
510
- } catch (e) {
511
- states.push(undefined);
512
- this.reportError(e);
513
- console.warn(e);
514
- }
515
- }
516
- });
517
- return state;
518
- }
519
-
520
- initPageSystem(opts?: NexusFrameworkPageSystemOptions) {
521
- if (!loader)
522
- return console.error("The NexusFramework Loader is required for the dynamic Page System to work correctly.");
523
- if (!history.pushState || !history.replaceState)
524
- return console.warn("This browser is missing an essential feature required for the dynamic Page System.")
525
- if (this.pagesyshandler)
526
- return console.warn("Page System already initialized, ignoring additional requests to initialize.")
527
-
528
- interface PageState {
529
- data: {
530
- title?: string;
531
- scroll?: number[];
532
- user?: string | number;
533
- response?: NexusFrameworkTransportResponse;
534
- error?: Error;
535
- body?: any;
536
- };
537
- url: string;
538
- updated: number;
539
- size: number;
540
- }
541
-
542
- opts = opts || {};
543
- const self = this;
544
- var pageStatesSize = 0;
545
- const pageStates: PageState[] = [];
546
- this.animationTiming = opts.animationTiming || 500;
547
- const pageStateCacheSize = opts.pageHistoryCacheSize || 25000000;
548
- const wrapCB = (cb: (res: NexusFrameworkTransportResponse) => void) => {
549
- return (res: NexusFrameworkTransportResponse) => {
550
- const user = res.headers['x-logged-user'];
551
- if (user)
552
- self.currentUserID = user[0];
553
- else
554
- self.currentUserID = undefined;
555
- debug("Current UID", self.currentUserID);
556
- cb(res);
557
- if (res.code === 200)
558
- setTimeout(() => {
559
- self._analytics.reportPage();
560
- });
561
- }
562
- }
563
- loader.showError = function(error: Error) {
564
- const match = location.href.match(beforeHash);
565
- const baseurl = match && match[1] || location.href;
566
- const length = error.stack.length;
567
- const tooBig = pageStateCacheSize <= length;
568
- try {
569
- const cPageStates = pageStates.length;
570
- for(var i=0; i<cPageStates; i++) {
571
- const state = pageStates[i];
572
- if (state.url === baseurl) {
573
- if (tooBig) {
574
- pageStates.splice(i, 1);
575
- pageStatesSize -= state.size;
576
- } else {
577
- state.data = {error};
578
- state.updated = +new Date;
579
- pageStatesSize += length - state.size;
580
- }
581
- throw true;
582
- }
583
- }
584
- if (tooBig)
585
- throw true;
586
- pageStates.push({
587
- url: baseurl,
588
- data: {error},
589
- updated: +new Date,
590
- size: length
591
- });
592
- pageStatesSize += length;
593
- } catch(e) {
594
- if (e !== true)
595
- throw e;
596
- }
597
- return showError(error);
598
- }
599
- const transportPageSystem = {
600
- requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void {
601
- if (self.pagesysprerequest && !self.pagesysprerequest(path)) {
602
- self.defaultRequestPage(path, post);
603
- return;
604
- }
605
- if (!opts.noprogress) {
606
- self.disableAll();
607
- loader.showProgress();
608
- }
609
- const url = self.resolveUrl(":pagesys/" + path);
610
- const _cb = opts.noprogress ? cb : function (res: NexusFrameworkTransportResponse) {
611
- if (opts.noprogress)
612
- cb(res);
613
- else
614
- loader.showProgress(() => {
615
- cb(res);
616
- self.enableAll();
617
- });
618
- };
619
- if (post)
620
- window.NexusFrameworkTransport.post(url, post, wrapCB(_cb));
621
- else
622
- window.NexusFrameworkTransport.get(url, wrapCB(_cb));
623
- }
624
- };
625
-
626
- if (!opts.noio && !opts.nopagesysio && this.io) {
627
- const io = this.io;
628
- this.pagesysimpl = {
629
- requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void {
630
- if (io.connected) {
631
- if (self.pagesysprerequest && !self.pagesysprerequest(path)) {
632
- self.defaultRequestPage(path, post);
633
- return;
634
- }
635
- if (!opts.noprogress) {
636
- self.disableAll();
637
- loader.showProgress();
638
- }
639
- const headers: any = {
640
- "referrer": location.href
641
- };
642
- var val = document.cookie;
643
- if (val)
644
- headers['cookie'] = val;
645
- io.emit("page", post ? "POST" : "GET", path, post, headers, function (res: NexusFrameworkPageSystemResponse) {
646
- if (rid != self.activerid)
647
- return;
648
-
649
- const cookies = res.headers['set-cookie'];
650
- if (cookies) {
651
- cookies.forEach(function (cookie) {
652
- document.cookie = cookie;
653
- });
654
- }
655
-
656
- const _cb = opts.noprogress ? cb : function (res) {
657
- if (!opts.noprogress)
658
- cb(res);
659
- else
660
- loader.showProgress(() => {
661
- cb(res);
662
- self.enableAll();
663
- });
664
- };
665
- wrapCB(_cb)(convertResponse(res, self.resolveUrl(path)));
666
- });
667
- } else
668
- transportPageSystem.requestPage(path, cb, post, rid);
669
- }
670
- }
671
- } else
672
- this.pagesysimpl = transportPageSystem;
673
- var base: HTMLBaseElement | NodeListOf<HTMLBaseElement> = document.getElementsByTagName("base");
674
- base = base && base[0];
675
- this.pagesysprerequest = opts.prerequest;
676
- this.pagesyshandler = opts.handler || ((res: NexusFrameworkTransportResponse, pageReady: () => void) => {
677
- try {
678
- const contentType = res.headers['content-type'][0];
679
- if (!/\/html(;.+)?$/.test(contentType.toLowerCase())) {
680
- throw new Error("Content type is not html");
681
- }
682
- } catch (e) {}
683
- const content = res.contentAsString;
684
- const bodyindex = content.indexOf("<body");
685
- if (bodyindex == -1)
686
- throw new Error("Could not find start of body tag");
687
- const endbodyindex = content.indexOf("</body>") || content.indexOf("</ body>");
688
- if (endbodyindex == -1)
689
- throw new Error("Could not find end of body tag");
690
- const title = content.match(/<title>([^<]+)<\/\s*title>/);
691
- document.title = title && title[1] || "Title Not Set";
692
- const mockhtml = document.createElement("html");
693
- mockhtml.innerHTML = content.substring(bodyindex, endbodyindex) + "</body>";
694
- var loaderScript: any;
695
- var childs = mockhtml.children;
696
- const toAdd: Element[] = [];
697
- for (var i = 0; i < childs.length; i++) {
698
- const child = childs[i];
699
- switch (child.nodeName.toUpperCase()) {
700
- case "HEAD":
701
- break;
702
- case "BODY":
703
- i = -1;
704
- childs = child.children;
705
- break;
706
- case "SCRIPT":
707
- const match = child.innerHTML.match(/^NexusFrameworkLoader\.load\((.+)\);?$/);
708
- if (match)
709
- loaderScript = JSON.parse(match[1]);
710
- break;
711
- default:
712
- if (loaderContainerRegex.test(child.className))
713
- break;
714
- toAdd.push(child);
715
- }
716
- }
717
- if (!toAdd.length)
718
- throw new Error("Nothing found in response to add to page");
719
- if (!loaderScript)
720
- throw new Error("NexusFrameworkLoader script not found...");
721
- childs = document.body.children;
722
- for (var i = childs.length - 1; i >= 0; i--) {
723
- const child = childs[i];
724
- switch (child.nodeName.toUpperCase()) {
725
- case "LINK":
726
- case "SCRIPT":
727
- break;
728
- default:
729
- if (loaderContainerRegex.test(child.className))
730
- break;
731
- document.body.removeChild(child);
732
- }
733
- }
734
- const fragment = document.createDocumentFragment();
735
- toAdd.forEach((el) => {
736
- fragment.appendChild(el);
737
- this.createComponents(el);
738
- });
739
- document.body.appendChild(fragment);
740
- loader.load(loaderScript, function() {
741
- self.progressVisible = true;
742
- loader.resetProgress();
743
- pageReady();
744
- });
745
- return true;
746
- });
747
- var forwardPopState: any[];
748
- const beforeHash = /^([^#]+)(#.*)?$/;
749
- var chash = location.href.match(beforeHash)[1];
750
- const startsWith = new RegExp("^" + this.url.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + "(.*)$", "i");
751
- class AnchorElementComponent implements NexusFrameworkComponent {
752
- private readonly handler = (e: Event) => {
753
- if (this.element.hasAttribute("data-nopagesys") || this.element.hasAttribute("data-nodynamic"))
754
- return;
755
- const href = this.element.getAttribute("href");
756
- if (!href || !href.length)
757
- return;
758
- var url = this.element.href;
759
- const bhash = url.match(beforeHash);
760
- if (bhash[2] && chash === bhash[1])
761
- return;
762
- if (startsWith.test(url))
763
- try {
764
- const match = url.match(/^(.+)#.*$/);
765
- if (match)
766
- url = match[1];
767
- self.requestPage(url.substring(self.url.length));
768
- try {
769
- e.stopPropagation();
770
- } catch (e) {}
771
- try {
772
- e.preventDefault();
773
- } catch (e) {}
774
- } catch (e) {
775
- console.warn(e);
776
- }
777
- else
778
- debug("Navigating", url);
779
- };
780
- private element: HTMLAnchorElement;
781
- create(element: HTMLAnchorElement): void {
782
- element.addEventListener("click", this.handler);
783
- this.element = element;
784
- }
785
- destroy(): void {
786
- this.element.removeEventListener("click", this.handler);
787
- }
788
- restore(data: any): void {}
789
- save() {}
790
- }
791
- class FormElementComponent implements NexusFrameworkComponent {
792
- private readonly handler = (e: Event) => {
793
- if (this.element.hasAttribute("data-nopagesys") || this.element.hasAttribute("data-nodynamic"))
794
- return;
795
- const method = this.element.getAttribute("method") || "get";
796
- const enctype = this.element.getAttribute("enctype") || "text/urlencoded";
797
- const action = this.element.getAttribute("action") || "";
798
- var url = resolveUrl(action);
799
- if (startsWith.test(url)) {
800
- try {
801
- const match = url.match(/^(.+)#.*$/);
802
- if (match)
803
- url = match[1];
804
- const formData = new FormData(this.element);
805
- loader.showProgress(undefined, "Submitting Form", "Your form data is being processed");
806
- if (method.trim().toLowerCase() === "post")
807
- self.requestPage(url.substring(self.url.length), {type:enctype,data:formData});
808
- else {
809
- var first = true;
810
- var reqUrl = url.substring(self.url.length) + "?";
811
- for (var entry of formData as any) {
812
- if (first)
813
- first = false;
814
- else
815
- reqUrl += "&";
816
- reqUrl += encodeURIComponent(entry[0]);
817
- reqUrl += "=";
818
- reqUrl += encodeURIComponent(entry[1]);
819
- }
820
- self.requestPage(reqUrl);
821
- }
822
- try {
823
- e.stopPropagation();
824
- } catch (e) {}
825
- try {
826
- e.preventDefault();
827
- } catch (e) {}
828
- Array.prototype.forEach.call(this.element.querySelectorAll("a, input, select, textarea, iframe, button, *[name]"), function(el) {
829
- el.setAttribute("disabled", "disabled");
830
- el.className += " disabled";
831
- });
832
- Array.prototype.forEach.call(this.element.querySelectorAll("button:not([type]), button[type=submit]"), function(button) {
833
- button.innerHTML = "Processing Request";
834
- });
835
- Array.prototype.forEach.call(this.element.querySelectorAll("input[type=submit]"), function(input) {
836
- input.value = "Processing Request";
837
- });
838
- return;
839
- } catch (e) {
840
- debug(e);
841
- }
842
- }
843
- debug("Submitting", url);
844
- };
845
- private element: HTMLFormElement;
846
- create(element: HTMLFormElement): void {
847
- element.addEventListener("submit", this.handler);
848
- this.element = element;
849
- }
850
- destroy(): void {
851
- this.element.removeEventListener("submit", this.handler);
852
- }
853
- restore(data: any): void {}
854
- save() {}
855
- }
856
- const requestPage = this.requestPage = (path: string, post?: any, replace = false) => {
857
- const match = path.match(/\.([^\/]+)([\?#].*)?$/);
858
- if (match && match[0] !== "htm" && match[0] !== "html") {
859
- this.defaultRequestPage(path, post);
860
- debug("Ignoring navigation", path, match);
861
- } else {
862
- debug("requestPage", path);
863
- const rid = ++self.activerid;
864
- const baseurl = this.resolveUrl(path);
865
- const fullurl = baseurl + (match && match[2] || "");
866
- if (replace)
867
- history.replaceState(!!post, "Loading...", fullurl);
868
- else {
869
- history.pushState(!!post, "Loading...", fullurl);
870
- chash = fullurl.match(beforeHash)[1];
871
- window.scrollTo(0, 0);
872
- }
873
- loader.resetError();
874
- loader.resetProgress();
875
- this.pagesysimpl.requestPage(path, (res) => {
876
- try {
877
- if (rid !== self.activerid)
878
- return;
879
-
880
- if (!res.code) {
881
- loader.showProgress(undefined, "Connection Issue", "Check your network and try again");
882
- document.title = "Connection Issue";
883
- history.replaceState(res.hadData, "Connection Issue", fullurl);
884
- setTimeout(function() {
885
- if (rid !== self.activerid)
886
- return;
887
- requestPage(path, post, true);
888
- }, 900000);
889
- return;
890
- } else if (res.code === 502 || res.code === 503) {
891
- loader.showProgress(undefined, "Scheduled Maintenance", "Sorry but this website is undergoing scheduled maintenance");
892
- document.title = "Scheduled Maintenance";
893
- history.replaceState(res.hadData, "Scheduled Maintenance", fullurl);
894
- setTimeout(function() {
895
- if (rid !== self.activerid)
896
- return;
897
- requestPage(path, post, true);
898
- }, 900000);
899
- return;
900
- }
901
-
902
- const location = res.headers['x-location'] || res.headers['location'];
903
- if (location) {
904
- const url = resolveUrl(location[0]);
905
- if (startsWith.test(url)) {
906
- (this.requestPage as any)(url.substring(this.url.length), undefined, true);
907
- return;
908
- }
909
-
910
- console.warn("Requested redirect to external url:", url);
911
- window.location.href = url;
912
- return;
913
- }
914
-
915
- const contentDisposition = res.headers['content-disposition'];
916
- if (contentDisposition && contentDisposition[0].toLowerCase() == "attachment")
917
- throw new Error("Attachment disposition");
918
- this.pagesyshandler(res, function() {
919
- if (rid !== self.activerid)
920
- return;
921
- try {
922
- const title = document.title;
923
- const contentType = res.headers['content-type'];
924
-
925
- const length = res.contentLength;
926
- const tooBig = pageStateCacheSize <= length;
927
- const stateData = tooBig ? undefined : {
928
- title: title,
929
- user: self.currentUserID,
930
- scroll: [window.scrollX, window.scrollY],
931
- body: self.saveComponents(document.body),
932
- basehref: base ? base['href'] : undefined,
933
- response: res
934
- };
935
- var i: number;
936
- const cPageStates = pageStates.length;
937
- try {
938
- for(i=0; i<cPageStates; i++) {
939
- const state = pageStates[i];
940
- if (state.url === baseurl) {
941
- if (tooBig) {
942
- pageStates.splice(i, 1);
943
- pageStatesSize -= state.size;
944
- } else {
945
- state.data = stateData;
946
- state.updated = +new Date;
947
- pageStatesSize += length - state.size;
948
- }
949
- throw true;
950
- }
951
- }
952
- if (!tooBig) {
953
- pageStates.push({
954
- url: baseurl,
955
- data: stateData,
956
- updated: +new Date,
957
- size: length
958
- });
959
- pageStatesSize += length;
960
- throw true;
961
- } else
962
- debug("Response too big to store", baseurl, length);
963
- } catch(e) {
964
- if (e === true) {
965
- const over = pageStatesSize - pageStateCacheSize;
966
- if (over > 0) {
967
- pageStates.sort(function(a, b) {
968
- return a.updated - b.updated;
969
- });
970
- var found = 0;
971
- for(i=0; i<cPageStates; i++) {
972
- found += pageStates[i].size;
973
- if (found >= over)
974
- break;
975
- }
976
- i ++;
977
- pageStates.splice(0, i);
978
- debug("Trimmed", i, "items...");
979
- }
980
- } else
981
- throw e;
982
- }
983
-
984
- history.replaceState(res.hadData, document.title, fullurl);
985
- self.emit("page", baseurl, path);
986
- } catch(e) {
987
- debug(e);
988
- if (replace)
989
- window.location.reload(true);
990
- else
991
- try {
992
- chash = undefined;
993
- forwardPopState = [path, post];
994
- debug("Going backwards");
995
- history.go(-1);
996
- } catch (e) {}
997
- }
998
- });
999
- } catch (e) {
1000
- debug(e);
1001
- if (replace)
1002
- window.location.reload(true);
1003
- else
1004
- try {
1005
- chash = undefined;
1006
- forwardPopState = [path, post];
1007
- debug("Going backwards");
1008
- history.go(-1);
1009
- } catch (e) {}
1010
- }
1011
- }, post, rid);
1012
- }
1013
- };
1014
- this.registerComponent("a", AnchorElementComponent);
1015
- this.registerComponent("form", FormElementComponent);
1016
- window.addEventListener('popstate', (e) => {
1017
- try {
1018
- var state: PageState;
1019
- const bhash = location.href.match(beforeHash);
1020
- const cStates = pageStates.length;
1021
- const baseurl = bhash[1];
1022
- for(var i=0; i<cStates; i++) {
1023
- const _state = pageStates[i];
1024
- if (_state.url === baseurl) {
1025
- state = _state;
1026
- break;
1027
- }
1028
- }
1029
- const hasState = !!state;
1030
- const rid = ++ self.activerid;
1031
-
1032
- const error = hasState && state.data.error;
1033
- if (error) {
1034
- showError(error);
1035
- return;
1036
- }
1037
-
1038
- if (forwardPopState) {
1039
- debug("forwardPopState", forwardPopState);
1040
- const forward = forwardPopState;
1041
- setTimeout(() => {
1042
- if (rid === self.activerid)
1043
- self.defaultRequestPage(forward[0], forward[1]);
1044
- });
1045
- forwardPopState = undefined;
1046
- return;
1047
- }
1048
-
1049
- if (chash === baseurl) {
1050
- debug("Only hash has changed...", baseurl);
1051
- return;
1052
- }
1053
- console.log(baseurl, chash, e);
1054
- console.trace();
1055
- chash = bhash[1];
1056
-
1057
- if (e.state)
1058
- alert("You submitted data to this page, which cannot be re-sent. Because of this, what you're viewing may not be the same now.");
1059
-
1060
- if (!hasState)
1061
- throw new Error("No state for: " + baseurl + ", reloading...");
1062
- if (state.data.user != self.currentUserID)
1063
- throw new Error("User has changed since state was created, reloading...");
1064
- document.title = state.data.title;
1065
- loader.resetProgress();
1066
- loader.resetError();
1067
- self.pagesyshandler(state.data.response, function(err?: Error) {
1068
- if (rid !== self.activerid)
1069
- return;
1070
- try {
1071
- if (err)
1072
- throw err;
1073
- if (state.data.body)
1074
- self.restoreComponents(document.body, state.data.body);
1075
- if (state.data.scroll)
1076
- window.scrollTo.apply(window, state.data.scroll);
1077
- debug("Used stored page state!", state);
1078
- } catch(err) {
1079
- debug(err);
1080
- var url = location.href;
1081
- if (startsWith.test(url)) {
1082
- try {
1083
- if (/reloading\.\.\.$/.test(err.message)) {
1084
- (self.requestPage as any)(url.substring(self.url.length), undefined, true);
1085
- return;
1086
- }
1087
- console.error("Unknown error occured, actually navigating to page...");
1088
- } catch (e) {
1089
- console.error(e);
1090
- }
1091
- }
1092
- location.reload(true);
1093
- }
1094
- });
1095
- } catch (err) {
1096
- debug(err);
1097
- var url = location.href;
1098
- if (startsWith.test(url)) {
1099
- try {
1100
- if (/reloading\.\.\.$/.test(err.message)) {
1101
- (self.requestPage as any)(url.substring(self.url.length), undefined, true);
1102
- return;
1103
- }
1104
- console.error("Unknown error occured, actually navigating to page...");
1105
- } catch (e) {
1106
- console.error(e);
1107
- }
1108
- }
1109
- location.reload(true);
1110
- }
1111
- });
1112
- return true;
1113
- }
1114
- defaultRequestPage(path: string, post?: any) {
1115
- if (post)
1116
- throw new Error("Posting is not supported without an initialized page system, yet");
1117
- else
1118
- location.href = this.resolveUrl(path);
1119
- }
1120
- requestPage = this.defaultRequestPage;
1121
- private _listeners: {[index: string]: Function[]} = {};
1122
- on(event: string, cb: (...args: any[]) => void) {
1123
- var listeners = this._listeners[event];
1124
- if (listeners)
1125
- listeners.push(cb);
1126
- else
1127
- this._listeners[event] = listeners = [cb];
1128
-
1129
- }
1130
- off(event: string, cb: (...args: any[]) => void) {
1131
- var index: number;
1132
- var listeners = this._listeners[event];
1133
- if (listeners && (index = listeners.indexOf(cb)) > -1)
1134
- listeners.splice(index, 1);
1135
- }
1136
- emit(event: string, ...args: any[]) {
1137
- const self = this;
1138
- const listeners = this._listeners[event];
1139
- if (listeners)
1140
- listeners.forEach(function (cb) {
1141
- cb.apply(self, args);
1142
- })
1143
- }
1144
- }
1145
- var impl;
1146
- if (window.io)
1147
- impl = class NexusFrameworkWithIO extends NexusFrameworkBase {
1148
- public constructor(url?: string) {
1149
- super(url, window.io({ // TODO: Parse the root URL and give a path and hostname properly
1150
- path: "/:io"
1151
- }));
1152
- const io = this.io;
1153
- io.on("connect", function () {
1154
- io.emit("init", loader.requestedResources());
1155
- });
1156
- }
1157
- }
1158
- else
1159
- impl = class NexusFrameworkNoIO extends NexusFrameworkBase {
1160
- public constructor(url?: string) {
1161
- super(url);
1162
- }
1163
- }
1164
- Object.defineProperty(window, "NexusFrameworkImpl", {
1165
- value: impl
1166
- });
1167
- return impl;
1168
- }
1169
- },
1170
- NexusFrameworkClient: {
1171
- configurable: true,
1172
- set: function (instance) {
1173
- Object.defineProperty(window, "NexusFrameworkClient", {
1174
- value: instance
1175
- });
1176
- },
1177
- get: function () {
1178
- const instance = new window.NexusFrameworkImpl();
1179
- Object.defineProperty(window, "NexusFrameworkClient", {
1180
- value: instance
1181
- });
1182
- return instance;
1183
- }
1184
- }
1185
- });
1
+ /// <reference path="../index.d.ts" />
2
+ (function(window) {
3
+ const BSON = new window['bson'];
4
+ Object.defineProperties(window, {
5
+ NexusFrameworkTransport: {
6
+ configurable: true,
7
+ set: function (instance) {
8
+ Object.defineProperty(window, "NexusFrameworkTransport", {
9
+ value: instance
10
+ });
11
+ },
12
+ get: function () {
13
+ var impl: NexusFrameworkTransport;
14
+ if ("XMLHttpRequest" in window) {
15
+ class NexusFrameworkXMLHttpRequestResponse implements NexusFrameworkTransportResponse {
16
+ hadData: boolean;
17
+ abResponse: boolean;
18
+ private _url: string;
19
+ private parsedJson: any;
20
+ private parsedBson: any;
21
+ private request: XMLHttpRequest;
22
+ private processedHeaders: {[index: string]: string[]};
23
+ constructor(request: XMLHttpRequest, url: string, hadData?: boolean, abResponse?: boolean) {
24
+ this._url = url;
25
+ this.request = request;
26
+ this.hadData = hadData;
27
+ this.abResponse = abResponse;
28
+ }
29
+ get url() {
30
+ return this.request.responseURL || this._url;
31
+ }
32
+ get code() {
33
+ return this.request.status;
34
+ }
35
+ get contentLength() {
36
+ return parseInt(this.request.getResponseHeader("content-length")) || this.request.responseText.length;
37
+ }
38
+ get contentFromJSON() {
39
+ if (!this.parsedJson)
40
+ this.parsedJson = JSON.parse(this.request.responseText);
41
+ return this.parsedJson;
42
+ }
43
+ get contentFromBSON() {
44
+ if (!this.parsedBson) {
45
+ var response = this.request.response;
46
+ if (typeof response === "string")
47
+ response = Buffer.from(response, "utf8");
48
+ else
49
+ response = new Buffer(response);
50
+ this.parsedBson = BSON.deserialize(response, {promoteValues:true});
51
+ }
52
+ return this.parsedBson;
53
+ }
54
+ get contentAsString() {
55
+ return this.request.responseText;
56
+ }
57
+ get contentAsArrayBuffer() {
58
+ return this.request.response;
59
+ }
60
+ get headers(): {[index: string]: string[]} {
61
+ if (!this.processedHeaders) {
62
+ const headers: {[index: string]: string[]} = this.processedHeaders = {};
63
+ this.request.getAllResponseHeaders().split(/\r?\n/g).forEach(function (header) {
64
+ const index = header.indexOf(":");
65
+ var key: string, val: string;
66
+ if (index > 0) {
67
+ key = header.substring(0, index).trim().toLowerCase();
68
+ val = header.substring(index + 1).trim();
69
+ } else
70
+ key = header.trim().toLowerCase();
71
+ var list = headers[key];
72
+ if (!list)
73
+ list = headers[key] = [];
74
+ if (val)
75
+ list.push(val);
76
+ });
77
+ }
78
+ return this.processedHeaders;
79
+ }
80
+ }
81
+ const execute = function (method: string, url: string, data: any, cb: (res: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
82
+ const request = new XMLHttpRequest();
83
+ var responseIsArrayBuffer;
84
+ try {
85
+ if (!wantsBinary)
86
+ throw false;
87
+ request.responseType = "arraybuffer";
88
+ if (request.responseType !== "arraybuffer") {
89
+ console.error("Could not request arraybuffer");
90
+ throw false;
91
+ }
92
+ responseIsArrayBuffer = true;
93
+ } catch(e) {
94
+ responseIsArrayBuffer = false;
95
+ }
96
+ request.open(method, url, true);
97
+ if (extraHeaders)
98
+ Object.keys(extraHeaders).forEach(function (key) {
99
+ request.setRequestHeader(key, extraHeaders[key]);
100
+ });
101
+ if (progcb)
102
+ request.onprogress = function (ev) {
103
+ if (ev.lengthComputable && ev.total)
104
+ progcb(ev.loaded, ev.total);
105
+ }
106
+ request.onreadystatechange = function (e) {
107
+ if (request.readyState === XMLHttpRequest.DONE)
108
+ cb(new NexusFrameworkXMLHttpRequestResponse(request, url, !!data, responseIsArrayBuffer));
109
+ }
110
+ const type = data && data.type;
111
+ if (type)
112
+ switch(type) {
113
+ case "":
114
+ case "text/urlencoded":
115
+ var dat = "";
116
+ for (var entry of data.data as any) {
117
+ if (dat)
118
+ dat += "&";
119
+ dat += encodeURIComponent(entry[0]);
120
+ dat += "=";
121
+ dat += encodeURIComponent(entry[1]);
122
+ }
123
+ request.setRequestHeader("Content-Type", "text/urlencoded");
124
+ request.send(dat);
125
+ break;
126
+ case "text/json":
127
+ request.setRequestHeader("Content-Type", "text/json");
128
+ request.send(JSON.stringify(data.data));
129
+ break;
130
+ case "application/bson":
131
+ request.setRequestHeader("Content-Type", "application/bson");
132
+ request.send(BSON.serialize(data.data));
133
+ break;
134
+ case "multipart/form-data":
135
+ request.send(data.data);
136
+ break;
137
+ default:
138
+ request.send(data);
139
+ }
140
+ else
141
+ request.send(data);
142
+ }
143
+ impl = {
144
+ get: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
145
+ execute("GET", url, undefined, cb, extraHeaders, progcb, wantsBinary);
146
+ },
147
+ head: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
148
+ execute("HEAD", url, undefined, cb, extraHeaders, progcb, wantsBinary);
149
+ },
150
+ post: function (url: string, data: any, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
151
+ execute("POST", url, data, cb, extraHeaders, progcb, wantsBinary);
152
+ },
153
+ put: function (url: string, data: any, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
154
+ execute("PUT", url, data, cb, extraHeaders, progcb, wantsBinary);
155
+ },
156
+ execute,
157
+ del: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
158
+ execute("DELETE", url, undefined, cb, extraHeaders, progcb, wantsBinary);
159
+ }
160
+ };
161
+ } else
162
+ throw new Error("No transport implementations");
163
+ Object.defineProperty(window, "NexusFrameworkTransport", {
164
+ value: impl
165
+ });
166
+ return impl;
167
+ }
168
+ },
169
+ NexusFrameworkImpl: {
170
+ configurable: true,
171
+ set: function (instance) {
172
+ Object.defineProperty(window, "NexusFrameworkImpl", {
173
+ value: instance
174
+ });
175
+ },
176
+ get: function () {
177
+ const loader = window.NexusFrameworkLoader;
178
+ const showError = loader.showError;
179
+ const debug = /*function(...args: any[]){};*/console.log.bind(console);
180
+ const GA_ANALYTICS: NexusFrameworkAnalyticsAdapter = {
181
+ reportError: function (err: Error, fatal?: boolean) {
182
+ if (window.ga)
183
+ try {
184
+ window.ga('send', 'exception', {
185
+ 'exDescription': (err.stack || "" + err).replace(/\n/g, "\n\t"),
186
+ 'exFatal': fatal
187
+ });
188
+ } catch (e) {
189
+ console.warn(e);
190
+ }
191
+ },
192
+ reportEvent: function (category: string, action: string, label?: string, value?: number) {
193
+ if (window.ga)
194
+ try {
195
+ window.ga('send', 'event', category, action, label, value);
196
+ } catch (e) {
197
+ console.warn(e);
198
+ }
199
+ },
200
+ reportPage: function (path?: string) {
201
+ if (window.ga)
202
+ try {
203
+ if (!path)
204
+ path = location.pathname;
205
+ window.ga('set', 'page', path);
206
+ window.ga('send', 'pageview');
207
+ } catch (e) {
208
+ console.warn(e);
209
+ }
210
+ }
211
+ };
212
+ const r = document.createElement("a");
213
+ const protocol = location.href.match(/^\w+:/)[0];
214
+ const resolveUrl = function (url: string) {
215
+ r.setAttribute("href", url);
216
+ var href = r.href;
217
+ if (/^\/\//.test(href))
218
+ href = protocol + href;
219
+ return href;
220
+ }
221
+ interface PageSystemImpl {
222
+ requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void;
223
+ }
224
+ const convertResponse = function (res: NexusFrameworkPageSystemResponse, url = location.href): NexusFrameworkTransportResponse{
225
+ var storage: any;
226
+ return (typeof res.data === "string") ? {
227
+ url,
228
+ code: res.code,
229
+ contentAsString: res.data,
230
+ get contentLength() {
231
+ return parseInt(res.headers['content-length'] && res.headers['content-length'][0]) || res.data.length
232
+ },
233
+ get contentAsArrayBuffer(): ArrayBuffer{
234
+ throw new Error("Not implemented");
235
+ },
236
+ get contentFromBSON() {
237
+ if (!storage)
238
+ storage = JSON.parse(res.data);
239
+ return storage;
240
+ },
241
+ get contentFromJSON() {
242
+ if (!storage)
243
+ storage = JSON.parse(res.data);
244
+ return storage;
245
+ },
246
+ headers: res.headers
247
+ } : {
248
+ url,
249
+ code: res.code,
250
+ get contentAsString() {
251
+ if (!storage)
252
+ storage = JSON.stringify(res.data);
253
+ return storage;
254
+ },
255
+ get contentAsArrayBuffer(): ArrayBuffer{
256
+ throw new Error("Not implemented");
257
+ },
258
+ contentFromJSON: res.data,
259
+ contentFromBSON: res.data,
260
+ headers: res.headers,
261
+ get contentLength() {
262
+ var length = parseInt(res.headers['content-length'] && res.headers['content-length'][0]);
263
+ if (length)
264
+ return length;
265
+ if (!storage)
266
+ storage = JSON.stringify(res.data);
267
+ return storage.length;
268
+ }
269
+ };
270
+ }
271
+ const loaderContainerRegex = /(^|\s)loader\-(progress|error)\-container(\s|$)/;
272
+ class NexusFrameworkBase implements NexusFrameworkClient {
273
+ public readonly url: string;
274
+ public readonly io: SocketIOClient.Socket;
275
+ private activerid = 0;
276
+ private _analytics: NexusFrameworkAnalyticsAdapter;
277
+ private errorreporter: (err: Error, fatal?: boolean) => void;
278
+ private components: {[index: string]: NexusFrameworkComponentFactory} = {};
279
+ private pagesyshandler: (res: NexusFrameworkTransportResponse, pageReady: (err?: Error) => void) => void;
280
+ private pagesysprerequest: (path: string) => boolean;
281
+ private pagesysimpl: PageSystemImpl;
282
+ private currentUserID: string = undefined;
283
+ private progressVisible = false;
284
+ private animationTiming = 500;
285
+ public constructor(url = "/", io?: SocketIOClient.Socket) {
286
+ url = resolveUrl(url);
287
+ if (!/\/$/.test(url))
288
+ url += "/";
289
+ Object.defineProperties(this, {
290
+ io: {
291
+ value: io
292
+ },
293
+ url: {
294
+ value: url
295
+ }
296
+ });
297
+ this._analytics = GA_ANALYTICS;
298
+ }
299
+
300
+ resolveUrl(url: string) {
301
+ if (/^\w+\:/.test(url))
302
+ return url;
303
+ return resolveUrl(this.url + url);
304
+ }
305
+
306
+ disableAll(root: HTMLElement = document.body) {
307
+ const focusable = root.querySelectorAll("a, input, select, textarea, iframe, button, *[focusable], *[tabindex]");
308
+ for (var i = 0; i < focusable.length; i++) {
309
+ const child = focusable[i];
310
+ const tabindex = child.getAttribute("tabindex");
311
+ if (tabindex == "-1")
312
+ return;
313
+ child.setAttribute("data-tabindex", tabindex ? tabindex : "");
314
+ child.setAttribute("data-tabindex", "-1");
315
+ }
316
+ }
317
+ enableAll(root: HTMLElement = document.body) {
318
+ const focusable = root.querySelectorAll("*[data-tabindex]");
319
+ for (var i = 0; i < focusable.length; i++) {
320
+ const child = focusable[i];
321
+ child.setAttribute("tabindex", child.getAttribute("data-tabindex"));
322
+ child.removeAttribute("data-tabindex");
323
+ }
324
+ }
325
+
326
+ get analytics() {
327
+ return this._analytics;
328
+ }
329
+ set analytics(value) {
330
+ this._analytics = value ? value : GA_ANALYTICS;
331
+ }
332
+
333
+ reportError(err: Error, fatal?: boolean) {
334
+ console[fatal ? "error" : "warn"](err.stack);
335
+ if (this.errorreporter)
336
+ this.errorreporter(err, fatal);
337
+ }
338
+ installErrorReporter(errorreporter: (err: Error, fatal?: boolean) => void) {
339
+ this.errorreporter = errorreporter;
340
+ }
341
+
342
+ registerComponent(selector: string, impl: NexusFrameworkComponentFactory) {
343
+ if (impl) {
344
+ this.components[selector] = impl;
345
+ this.createComponents(document.head);
346
+ this.createComponents(document.body);
347
+ } else {
348
+ delete this.components[selector];
349
+ var destroy = (root: HTMLElement) => {
350
+ const elements = root.querySelectorAll(selector);
351
+ if (!elements.length)
352
+ return;
353
+
354
+ for (var i = 0; i < elements.length; i++) {
355
+ const element = elements[i];
356
+ var components = element['__nf_cmapping__'];
357
+ if (!components)
358
+ components = element['__nf_cmapping__'] = {};
359
+ const component: NexusFrameworkComponent = components[selector];
360
+ if (component) {
361
+ try {
362
+ component.destroy();
363
+ } catch (e) {
364
+ this.reportError(e);
365
+ }
366
+ }
367
+ }
368
+ }
369
+ destroy(document.head);
370
+ destroy(document.body);
371
+ }
372
+ }
373
+ unregisterComponent(selector: string, impl: NexusFrameworkComponentFactory) {
374
+ }
375
+ createComponents(root: Element): void {
376
+ Object.keys(this.components).forEach((selector) => {
377
+ const elements = root.querySelectorAll(selector);
378
+ if (!elements.length)
379
+ return;
380
+
381
+ for (var i = 0; i < elements.length; i++) {
382
+ const element = elements[i];
383
+ var components = element['__nf_cmapping__'];
384
+ if (!components)
385
+ components = element['__nf_cmapping__'] = {};
386
+ if (components[selector])
387
+ continue;
388
+ const componentFactory = this.components[selector];
389
+ try {
390
+ (components[selector] = new componentFactory()).create(element as HTMLElement);
391
+ } catch (e) {
392
+ this.reportError(e);
393
+ }
394
+ }
395
+ });
396
+ }
397
+ destroyComponents(root: HTMLElement): void {
398
+ Object.keys(this.components).forEach((selector) => {
399
+ const elements = root.querySelectorAll(selector);
400
+ if (!elements.length)
401
+ return;
402
+
403
+ for (var i = 0; i < elements.length; i++) {
404
+ const element = elements[i];
405
+ var components = element['__nf_cmapping__'];
406
+ if (!components)
407
+ components = element['__nf_cmapping__'] = {};
408
+ const component: NexusFrameworkComponent = components[selector];
409
+ if (component) {
410
+ try {
411
+ component.destroy();
412
+ } catch (e) {
413
+ this.reportError(e);
414
+ }
415
+ }
416
+ }
417
+ });
418
+ }
419
+ restoreComponents(root: HTMLElement, state: Object): void {
420
+ Object.keys(this.components).forEach((selector) => {
421
+ const states: any[] = state[selector];
422
+ if (!states)
423
+ return;
424
+
425
+ const elements = root.querySelectorAll(selector);
426
+ if (!elements.length)
427
+ return;
428
+
429
+ for (var i = 0; i < elements.length; i++) {
430
+ const element = elements[i];
431
+ if (states.length) {
432
+ const _state = states.shift();
433
+ if (_state) {
434
+ var components = element['__nf_cmapping__'];
435
+ if (!components)
436
+ components = element['__nf_cmapping__'] = {};
437
+ var component: NexusFrameworkComponent = components[selector];
438
+ if (!component) {
439
+ const componentFactory = this.components[selector];
440
+ try {
441
+ (component = components[selector] = new componentFactory()).create(element as HTMLElement);
442
+ } catch (e) {
443
+ this.reportError(e);
444
+ console.warn(e);
445
+ continue;
446
+ }
447
+ }
448
+ try {
449
+ component.restore(_state);
450
+ } catch (e) {
451
+ this.reportError(e);
452
+ console.warn(e);
453
+ }
454
+ }
455
+ } else
456
+ break;
457
+ }
458
+ });
459
+ }
460
+ saveComponents(root: HTMLElement): Object {
461
+ var state = {};
462
+ Object.keys(this.components).forEach((selector) => {
463
+ const states = state[selector] = [];
464
+ const elements = root.querySelectorAll(selector);
465
+ if (!elements.length)
466
+ return;
467
+
468
+ for (var i = 0; i < elements.length; i++) {
469
+ const element = elements[i];
470
+ var components = element['__nf_cmapping__'];
471
+ if (!components)
472
+ components = element['__nf_cmapping__'] = {};
473
+ var component: NexusFrameworkComponent = components[selector];
474
+ if (!component) {
475
+ const componentFactory = this.components[selector];
476
+ try {
477
+ (component = components[selector] = new componentFactory).create(element as HTMLElement);
478
+ } catch (e) {
479
+ states.push(undefined);
480
+ this.reportError(e);
481
+ console.warn(e);
482
+ continue;
483
+ }
484
+ }
485
+ try {
486
+ states.push(component.save());
487
+ } catch (e) {
488
+ states.push(undefined);
489
+ this.reportError(e);
490
+ console.warn(e);
491
+ }
492
+ }
493
+ });
494
+ return state;
495
+ }
496
+
497
+ initPageSystem(opts?: NexusFrameworkPageSystemOptions) {
498
+ if (!loader)
499
+ return console.error("The NexusFramework Loader is required for the dynamic Page System to work correctly.");
500
+ if (!history.pushState || !history.replaceState)
501
+ return console.warn("This browser is missing an essential feature required for the dynamic Page System.")
502
+ if (this.pagesyshandler)
503
+ return console.warn("Page System already initialized, ignoring additional requests to initialize.")
504
+
505
+ interface PageState {
506
+ data: {
507
+ title?: string;
508
+ scroll?: number[];
509
+ user?: string | number;
510
+ response?: NexusFrameworkTransportResponse;
511
+ error?: Error;
512
+ body?: any;
513
+ };
514
+ url: string;
515
+ updated: number;
516
+ size: number;
517
+ }
518
+
519
+ opts = opts || {};
520
+ const self = this;
521
+ var pageStatesSize = 0;
522
+ const pageStates: PageState[] = [];
523
+ this.animationTiming = opts.animationTiming || 500;
524
+ const pageStateCacheSize = opts.pageHistoryCacheSize || 25000000;
525
+ const wrapCB = (cb: (res: NexusFrameworkTransportResponse) => void) => {
526
+ return (res: NexusFrameworkTransportResponse) => {
527
+ const user = res.headers['x-logged-user'];
528
+ if (user)
529
+ self.currentUserID = user[0];
530
+ else
531
+ self.currentUserID = undefined;
532
+ debug("Current UID", self.currentUserID);
533
+ cb(res);
534
+ if (res.code === 200)
535
+ setTimeout(() => {
536
+ self._analytics.reportPage();
537
+ });
538
+ }
539
+ }
540
+ loader.showError = function(error: Error) {
541
+ const match = location.href.match(beforeHash);
542
+ const baseurl = match && match[1] || location.href;
543
+ const length = error.stack.length;
544
+ const tooBig = pageStateCacheSize <= length;
545
+ try {
546
+ const cPageStates = pageStates.length;
547
+ for(var i=0; i<cPageStates; i++) {
548
+ const state = pageStates[i];
549
+ if (state.url === baseurl) {
550
+ if (tooBig) {
551
+ pageStates.splice(i, 1);
552
+ pageStatesSize -= state.size;
553
+ } else {
554
+ state.data = {error};
555
+ state.updated = +new Date;
556
+ pageStatesSize += length - state.size;
557
+ }
558
+ throw true;
559
+ }
560
+ }
561
+ if (tooBig)
562
+ throw true;
563
+ pageStates.push({
564
+ url: baseurl,
565
+ data: {error},
566
+ updated: +new Date,
567
+ size: length
568
+ });
569
+ pageStatesSize += length;
570
+ } catch(e) {
571
+ if (e !== true)
572
+ throw e;
573
+ }
574
+ return showError(error);
575
+ }
576
+ const transportPageSystem = {
577
+ requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void {
578
+ if (self.pagesysprerequest && !self.pagesysprerequest(path)) {
579
+ self.defaultRequestPage(path, post);
580
+ return;
581
+ }
582
+ if (!opts.noprogress) {
583
+ self.disableAll();
584
+ loader.showProgress();
585
+ }
586
+ const url = self.resolveUrl(":pagesys/" + path);
587
+ const _cb = opts.noprogress ? cb : function (res: NexusFrameworkTransportResponse) {
588
+ if (opts.noprogress)
589
+ cb(res);
590
+ else
591
+ loader.showProgress(() => {
592
+ cb(res);
593
+ self.enableAll();
594
+ });
595
+ };
596
+ if (post)
597
+ window.NexusFrameworkTransport.post(url, post, wrapCB(_cb), undefined, undefined, true);
598
+ else
599
+ window.NexusFrameworkTransport.get(url, wrapCB(_cb), undefined, undefined, true);
600
+ }
601
+ };
602
+
603
+ if (!opts.noio && this.io) {
604
+ const io = this.io;
605
+ this.pagesysimpl = {
606
+ requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void {
607
+ if (io.connected) {
608
+ if (self.pagesysprerequest && !self.pagesysprerequest(path)) {
609
+ self.defaultRequestPage(path, post);
610
+ return;
611
+ }
612
+ if (!opts.noprogress) {
613
+ self.disableAll();
614
+ loader.showProgress();
615
+ }
616
+ const headers: any = {
617
+ "referrer": location.href
618
+ };
619
+ var val = document.cookie;
620
+ if (val)
621
+ headers['cookie'] = val;
622
+ io.emit("page", post ? "POST" : "GET", path, post, headers, function (res: NexusFrameworkPageSystemResponse) {
623
+ if (rid != self.activerid)
624
+ return;
625
+
626
+ const cookies = res.headers['set-cookie'];
627
+ if (cookies) {
628
+ cookies.forEach(function (cookie) {
629
+ document.cookie = cookie;
630
+ });
631
+ }
632
+
633
+ const _cb = opts.noprogress ? cb : function (res) {
634
+ if (!opts.noprogress)
635
+ cb(res);
636
+ else
637
+ loader.showProgress(() => {
638
+ cb(res);
639
+ self.enableAll();
640
+ });
641
+ };
642
+ wrapCB(_cb)(convertResponse(res, self.resolveUrl(path)));
643
+ });
644
+ } else
645
+ transportPageSystem.requestPage(path, cb, post, rid);
646
+ }
647
+ }
648
+ } else
649
+ this.pagesysimpl = transportPageSystem;
650
+ var base: HTMLBaseElement | NodeListOf<HTMLBaseElement> = document.getElementsByTagName("base");
651
+ base = base && base[0];
652
+ this.pagesysprerequest = opts.prerequest;
653
+ this.pagesyshandler = opts.handler || ((res: NexusFrameworkTransportResponse, pageReady: () => void) => {
654
+ try {
655
+ const contentType = res.headers['content-type'][0];
656
+ if (!/\/html(;.+)?$/.test(contentType.toLowerCase())) {
657
+ throw new Error("Content type is not html");
658
+ }
659
+ } catch (e) {}
660
+ const content = res.contentAsString;
661
+ const bodyindex = content.indexOf("<body");
662
+ if (bodyindex == -1)
663
+ throw new Error("Could not find start of body tag");
664
+ const endbodyindex = content.indexOf("</body>") || content.indexOf("</ body>");
665
+ if (endbodyindex == -1)
666
+ throw new Error("Could not find end of body tag");
667
+ const title = content.match(/<title>([^<]+)<\/\s*title>/);
668
+ document.title = title && title[1] || "Title Not Set";
669
+ const mockhtml = document.createElement("html");
670
+ mockhtml.innerHTML = content.substring(bodyindex, endbodyindex) + "</body>";
671
+ var loaderScript: any;
672
+ var childs = mockhtml.children;
673
+ const toAdd: Element[] = [];
674
+ for (var i = 0; i < childs.length; i++) {
675
+ const child = childs[i];
676
+ switch (child.nodeName.toUpperCase()) {
677
+ case "HEAD":
678
+ break;
679
+ case "BODY":
680
+ i = -1;
681
+ childs = child.children;
682
+ break;
683
+ case "SCRIPT":
684
+ const match = child.innerHTML.match(/^NexusFrameworkLoader\.load\((.+)\);?$/);
685
+ if (match)
686
+ loaderScript = JSON.parse(match[1]);
687
+ break;
688
+ default:
689
+ if (loaderContainerRegex.test(child.className))
690
+ break;
691
+ toAdd.push(child);
692
+ }
693
+ }
694
+ if (!toAdd.length)
695
+ throw new Error("Nothing found in response to add to page");
696
+ if (!loaderScript)
697
+ throw new Error("NexusFrameworkLoader script not found...");
698
+ childs = document.body.children;
699
+ for (var i = childs.length - 1; i >= 0; i--) {
700
+ const child = childs[i];
701
+ switch (child.nodeName.toUpperCase()) {
702
+ case "LINK":
703
+ case "SCRIPT":
704
+ break;
705
+ default:
706
+ if (loaderContainerRegex.test(child.className))
707
+ break;
708
+ document.body.removeChild(child);
709
+ }
710
+ }
711
+ const fragment = document.createDocumentFragment();
712
+ toAdd.forEach((el) => {
713
+ fragment.appendChild(el);
714
+ this.createComponents(el);
715
+ });
716
+ document.body.appendChild(fragment);
717
+ loader.load(loaderScript, function() {
718
+ self.progressVisible = true;
719
+ loader.resetProgress();
720
+ pageReady();
721
+ });
722
+ return true;
723
+ });
724
+ var forwardPopState: any[];
725
+ const beforeHash = /^([^#]+)(#.*)?$/;
726
+ var chash = location.href.match(beforeHash)[1];
727
+ const startsWith = new RegExp("^" + this.url.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + "(.*)$", "i");
728
+ class AnchorElementComponent implements NexusFrameworkComponent {
729
+ private readonly handler = (e: Event) => {
730
+ if (this.element.hasAttribute("data-nopagesys") || this.element.hasAttribute("data-nodynamic"))
731
+ return;
732
+ const href = this.element.getAttribute("href");
733
+ if (!href || !href.length)
734
+ return;
735
+ var url = this.element.href;
736
+ const bhash = url.match(beforeHash);
737
+ if (bhash[2] && chash === bhash[1])
738
+ return;
739
+ if (startsWith.test(url))
740
+ try {
741
+ const match = url.match(/^(.+)#.*$/);
742
+ if (match)
743
+ url = match[1];
744
+ self.requestPage(url.substring(self.url.length));
745
+ try {
746
+ e.stopPropagation();
747
+ } catch (e) {}
748
+ try {
749
+ e.preventDefault();
750
+ } catch (e) {}
751
+ } catch (e) {
752
+ console.warn(e);
753
+ }
754
+ else
755
+ debug("Navigating", url);
756
+ };
757
+ private element: HTMLAnchorElement;
758
+ create(element: HTMLAnchorElement): void {
759
+ element.addEventListener("click", this.handler);
760
+ this.element = element;
761
+ }
762
+ destroy(): void {
763
+ this.element.removeEventListener("click", this.handler);
764
+ }
765
+ restore(data: any): void {}
766
+ save() {}
767
+ }
768
+ class FormElementComponent implements NexusFrameworkComponent {
769
+ private readonly handler = (e: Event) => {
770
+ if (this.element.hasAttribute("data-nopagesys") || this.element.hasAttribute("data-nodynamic"))
771
+ return;
772
+ const method = this.element.getAttribute("method") || "get";
773
+ const enctype = this.element.getAttribute("enctype") || "text/urlencoded";
774
+ const action = this.element.getAttribute("action") || "";
775
+ var url = resolveUrl(action);
776
+ if (startsWith.test(url)) {
777
+ try {
778
+ const match = url.match(/^(.+)#.*$/);
779
+ if (match)
780
+ url = match[1];
781
+ const formData = new FormData(this.element);
782
+ loader.showProgress(undefined, "Submitting Form", "Your form data is being processed");
783
+ if (method.trim().toLowerCase() === "post")
784
+ self.requestPage(url.substring(self.url.length), {type:enctype,data:formData});
785
+ else {
786
+ var first = true;
787
+ var reqUrl = url.substring(self.url.length) + "?";
788
+ for (var entry of formData as any) {
789
+ if (first)
790
+ first = false;
791
+ else
792
+ reqUrl += "&";
793
+ reqUrl += encodeURIComponent(entry[0]);
794
+ reqUrl += "=";
795
+ reqUrl += encodeURIComponent(entry[1]);
796
+ }
797
+ self.requestPage(reqUrl);
798
+ }
799
+ try {
800
+ e.stopPropagation();
801
+ } catch (e) {}
802
+ try {
803
+ e.preventDefault();
804
+ } catch (e) {}
805
+ Array.prototype.forEach.call(this.element.querySelectorAll("a, input, select, textarea, iframe, button, *[name]"), function(el) {
806
+ el.setAttribute("disabled", "disabled");
807
+ el.className += " disabled";
808
+ });
809
+ Array.prototype.forEach.call(this.element.querySelectorAll("button:not([type]), button[type=submit]"), function(button) {
810
+ button.innerHTML = "Processing Request";
811
+ });
812
+ Array.prototype.forEach.call(this.element.querySelectorAll("input[type=submit]"), function(input) {
813
+ input.value = "Processing Request";
814
+ });
815
+ return;
816
+ } catch (e) {
817
+ debug(e);
818
+ }
819
+ }
820
+ debug("Submitting", url);
821
+ };
822
+ private element: HTMLFormElement;
823
+ create(element: HTMLFormElement): void {
824
+ element.addEventListener("submit", this.handler);
825
+ this.element = element;
826
+ }
827
+ destroy(): void {
828
+ this.element.removeEventListener("submit", this.handler);
829
+ }
830
+ restore(data: any): void {}
831
+ save() {}
832
+ }
833
+ const requestPage = this.requestPage = (path: string, post?: any, replace = false) => {
834
+ const match = path.match(/\.([^\/]+)([\?#].*)?$/);
835
+ if (match && match[0] !== "htm" && match[0] !== "html") {
836
+ this.defaultRequestPage(path, post);
837
+ debug("Ignoring navigation", path, match);
838
+ } else {
839
+ debug("requestPage", path);
840
+ const rid = ++self.activerid;
841
+ const baseurl = this.resolveUrl(path);
842
+ const fullurl = baseurl + (match && match[2] || "");
843
+ if (replace)
844
+ history.replaceState(!!post, "Loading...", fullurl);
845
+ else {
846
+ history.pushState(!!post, "Loading...", fullurl);
847
+ chash = fullurl.match(beforeHash)[1];
848
+ window.scrollTo(0, 0);
849
+ }
850
+ loader.resetError();
851
+ loader.resetProgress();
852
+ this.pagesysimpl.requestPage(path, (res) => {
853
+ try {
854
+ if (rid !== self.activerid)
855
+ return;
856
+
857
+ if (!res.code) {
858
+ loader.showProgress(undefined, "Connection Issue", "Check your network and try again");
859
+ document.title = "Connection Issue";
860
+ history.replaceState(res.hadData, "Connection Issue", fullurl);
861
+ setTimeout(function() {
862
+ if (rid !== self.activerid)
863
+ return;
864
+ requestPage(path, post, true);
865
+ }, 900000);
866
+ return;
867
+ } else if (res.code === 502 || res.code === 503) {
868
+ loader.showProgress(undefined, "Scheduled Maintenance", "Sorry but this website is undergoing scheduled maintenance");
869
+ document.title = "Scheduled Maintenance";
870
+ history.replaceState(res.hadData, "Scheduled Maintenance", fullurl);
871
+ setTimeout(function() {
872
+ if (rid !== self.activerid)
873
+ return;
874
+ requestPage(path, post, true);
875
+ }, 900000);
876
+ return;
877
+ }
878
+
879
+ const location = res.headers['x-location'] || res.headers['location'];
880
+ if (location) {
881
+ const url = resolveUrl(location[0]);
882
+ if (startsWith.test(url)) {
883
+ (this.requestPage as any)(url.substring(this.url.length), undefined, true);
884
+ return;
885
+ }
886
+
887
+ console.warn("Requested redirect to external url:", url);
888
+ window.location.href = url;
889
+ return;
890
+ }
891
+
892
+ const contentDisposition = res.headers['content-disposition'];
893
+ if (contentDisposition && contentDisposition[0].toLowerCase() == "attachment")
894
+ throw new Error("Attachment disposition");
895
+ this.pagesyshandler(res, function() {
896
+ if (rid !== self.activerid)
897
+ return;
898
+ try {
899
+ const title = document.title;
900
+
901
+ const length = res.contentLength;
902
+ const tooBig = pageStateCacheSize <= length;
903
+ const stateData = tooBig ? undefined : {
904
+ title: title,
905
+ user: self.currentUserID,
906
+ scroll: [window.scrollX, window.scrollY],
907
+ body: self.saveComponents(document.body),
908
+ basehref: base ? base['href'] : undefined,
909
+ response: res
910
+ };
911
+ var i: number;
912
+ const cPageStates = pageStates.length;
913
+ try {
914
+ for(i=0; i<cPageStates; i++) {
915
+ const state = pageStates[i];
916
+ if (state.url === baseurl) {
917
+ if (tooBig) {
918
+ pageStates.splice(i, 1);
919
+ pageStatesSize -= state.size;
920
+ } else {
921
+ state.data = stateData;
922
+ state.updated = +new Date;
923
+ pageStatesSize += length - state.size;
924
+ }
925
+ throw true;
926
+ }
927
+ }
928
+ if (!tooBig) {
929
+ pageStates.push({
930
+ url: baseurl,
931
+ data: stateData,
932
+ updated: +new Date,
933
+ size: length
934
+ });
935
+ pageStatesSize += length;
936
+ throw true;
937
+ } else
938
+ debug("Response too big to store", baseurl, length);
939
+ } catch(e) {
940
+ if (e === true) {
941
+ const over = pageStatesSize - pageStateCacheSize;
942
+ if (over > 0) {
943
+ pageStates.sort(function(a, b) {
944
+ return a.updated - b.updated;
945
+ });
946
+ var found = 0;
947
+ for(i=0; i<cPageStates; i++) {
948
+ found += pageStates[i].size;
949
+ if (found >= over)
950
+ break;
951
+ }
952
+ i ++;
953
+ pageStates.splice(0, i);
954
+ debug("Trimmed", i, "items...");
955
+ }
956
+ } else
957
+ throw e;
958
+ }
959
+
960
+ history.replaceState(res.hadData, document.title, fullurl);
961
+ self.emit("page", baseurl, path);
962
+ } catch(e) {
963
+ debug(e);
964
+ if (replace)
965
+ window.location.reload(true);
966
+ else
967
+ try {
968
+ chash = undefined;
969
+ forwardPopState = [path, post];
970
+ debug("Going backwards");
971
+ history.go(-1);
972
+ } catch (e) {}
973
+ }
974
+ });
975
+ } catch (e) {
976
+ debug(e);
977
+ if (replace)
978
+ window.location.reload(true);
979
+ else
980
+ try {
981
+ chash = undefined;
982
+ forwardPopState = [path, post];
983
+ debug("Going backwards");
984
+ history.go(-1);
985
+ } catch (e) {}
986
+ }
987
+ }, post, rid);
988
+ }
989
+ };
990
+ this.registerComponent("a", AnchorElementComponent);
991
+ this.registerComponent("form", FormElementComponent);
992
+ window.addEventListener('popstate', (e) => {
993
+ try {
994
+ var state: PageState;
995
+ const bhash = location.href.match(beforeHash);
996
+ const cStates = pageStates.length;
997
+ const baseurl = bhash[1];
998
+ for(var i=0; i<cStates; i++) {
999
+ const _state = pageStates[i];
1000
+ if (_state.url === baseurl) {
1001
+ state = _state;
1002
+ break;
1003
+ }
1004
+ }
1005
+ const hasState = !!state;
1006
+ const rid = ++ self.activerid;
1007
+
1008
+ const error = hasState && state.data.error;
1009
+ if (error) {
1010
+ showError(error);
1011
+ return;
1012
+ }
1013
+
1014
+ if (forwardPopState) {
1015
+ debug("forwardPopState", forwardPopState);
1016
+ const forward = forwardPopState;
1017
+ setTimeout(() => {
1018
+ if (rid === self.activerid)
1019
+ self.defaultRequestPage(forward[0], forward[1]);
1020
+ });
1021
+ forwardPopState = undefined;
1022
+ return;
1023
+ }
1024
+
1025
+ if (chash === baseurl) {
1026
+ debug("Only hash has changed...", baseurl);
1027
+ return;
1028
+ }
1029
+ chash = bhash[1];
1030
+
1031
+ if (e.state)
1032
+ alert("You submitted data to this page, which cannot be re-sent. Because of this, what you're viewing may not be the same now.");
1033
+
1034
+ if (!hasState)
1035
+ throw new Error("No state for: " + baseurl + ", reloading...");
1036
+ if (state.data.user != self.currentUserID)
1037
+ throw new Error("User has changed since state was created, reloading...");
1038
+ document.title = state.data.title;
1039
+ loader.resetProgress();
1040
+ loader.resetError();
1041
+ self.pagesyshandler(state.data.response, function(err?: Error) {
1042
+ if (rid !== self.activerid)
1043
+ return;
1044
+ try {
1045
+ if (err)
1046
+ throw err;
1047
+ if (state.data.body)
1048
+ self.restoreComponents(document.body, state.data.body);
1049
+ if (state.data.scroll)
1050
+ window.scrollTo.apply(window, state.data.scroll);
1051
+ debug("Used stored page state!", state);
1052
+ } catch(err) {
1053
+ debug(err);
1054
+ var url = location.href;
1055
+ if (startsWith.test(url)) {
1056
+ try {
1057
+ if (/reloading\.\.\.$/.test(err.message)) {
1058
+ (self.requestPage as any)(url.substring(self.url.length), undefined, true);
1059
+ return;
1060
+ }
1061
+ console.error("Unknown error occured, actually navigating to page...");
1062
+ } catch (e) {
1063
+ console.error(e);
1064
+ }
1065
+ }
1066
+ location.reload(true);
1067
+ }
1068
+ });
1069
+ } catch (err) {
1070
+ debug(err);
1071
+ var url = location.href;
1072
+ if (startsWith.test(url)) {
1073
+ try {
1074
+ if (/reloading\.\.\.$/.test(err.message)) {
1075
+ (self.requestPage as any)(url.substring(self.url.length), undefined, true);
1076
+ return;
1077
+ }
1078
+ console.error("Unknown error occured, actually navigating to page...");
1079
+ } catch (e) {
1080
+ console.error(e);
1081
+ }
1082
+ }
1083
+ location.reload(true);
1084
+ }
1085
+ });
1086
+ return true;
1087
+ }
1088
+ get currentUser() {
1089
+ return this.currentUserID;
1090
+ }
1091
+ defaultRequestPage(path: string, post?: any) {
1092
+ if (post)
1093
+ throw new Error("Posting is not supported without an initialized page system, yet");
1094
+ else
1095
+ location.href = this.resolveUrl(path);
1096
+ }
1097
+ requestPage = this.defaultRequestPage;
1098
+ private _listeners: {[index: string]: Function[]} = {};
1099
+ on(event: string, cb: (...args: any[]) => void) {
1100
+ var listeners = this._listeners[event];
1101
+ if (listeners)
1102
+ listeners.push(cb);
1103
+ else
1104
+ this._listeners[event] = listeners = [cb];
1105
+
1106
+ }
1107
+ off(event: string, cb: (...args: any[]) => void) {
1108
+ var index: number;
1109
+ var listeners = this._listeners[event];
1110
+ if (listeners && (index = listeners.indexOf(cb)) > -1)
1111
+ listeners.splice(index, 1);
1112
+ }
1113
+ emit(event: string, ...args: any[]) {
1114
+ const self = this;
1115
+ const listeners = this._listeners[event];
1116
+ if (listeners)
1117
+ listeners.forEach(function (cb) {
1118
+ cb.apply(self, args);
1119
+ })
1120
+ }
1121
+ }
1122
+ var impl;
1123
+ if (window.io)
1124
+ impl = class NexusFrameworkWithIO extends NexusFrameworkBase {
1125
+ public constructor(url?: string) {
1126
+ super(url, window.io({ // TODO: Parse the root URL and give a path and hostname properly
1127
+ path: "/:io"
1128
+ }));
1129
+ const io = this.io;
1130
+ io.on("connect", function () {
1131
+ io.emit("init", loader.requestedResources());
1132
+ });
1133
+ }
1134
+ }
1135
+ else
1136
+ impl = class NexusFrameworkNoIO extends NexusFrameworkBase {
1137
+ public constructor(url?: string) {
1138
+ super(url);
1139
+ }
1140
+ }
1141
+ Object.defineProperty(window, "NexusFrameworkImpl", {
1142
+ value: impl
1143
+ });
1144
+ return impl;
1145
+ }
1146
+ },
1147
+ NexusFrameworkClient: {
1148
+ configurable: true,
1149
+ set: function (instance) {
1150
+ Object.defineProperty(window, "NexusFrameworkClient", {
1151
+ value: instance
1152
+ });
1153
+ },
1154
+ get: function () {
1155
+ const instance = new window.NexusFrameworkImpl();
1156
+ Object.defineProperty(window, "NexusFrameworkClient", {
1157
+ value: instance
1158
+ });
1159
+ return instance;
1160
+ }
1161
+ }
1162
+ });
1163
+ })(window);