Skip to content

Commit cffdf82

Browse files
committed
refactor: improve page request mechanism
1 parent a8e80a4 commit cffdf82

File tree

2 files changed

+183
-159
lines changed

2 files changed

+183
-159
lines changed

views/assets/js/loader.js

Lines changed: 168 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -22,148 +22,178 @@
2222
};
2323
if (localStorage.getItem('{{hu-lts}}-loader-key') !== navigator.userAgent)
2424
return displayErrorPage();
25-
const loadPage = () => {
26-
removeEventListener('load', loadPage);
27-
fetch(location.pathname + '.ico', { mode: 'same-origin' })
28-
.then((response) => {
29-
response.blob().then((blob) => {
30-
new Response(
31-
blob.stream().pipeThrough(new DecompressionStream('gzip'))
32-
)
33-
.text()
34-
.then((text) => {
35-
((currentDoc, newDoc) => {
36-
const deferScripts = [],
37-
syncScripts = [];
38-
let reachedEnd = false,
39-
waitForHead = false,
40-
headScripts = 0;
41-
const bodyLoader = () => {
42-
headScripts--;
43-
if (waitForHead && headScripts <= 0) {
44-
waitForHead = false;
45-
currentDoc.body.replaceWith(recursiveClone(newDoc.body));
46-
if (reachedEnd) loadNextScript(false)();
47-
}
48-
return waitForHead;
49-
};
50-
const loadNextScript = (isDefer, currentScript) => () => {
51-
if (
52-
!isDefer &&
53-
currentScript &&
54-
'head' === currentScript.parentElement.tagName.toLowerCase()
55-
)
56-
bodyLoader();
57-
let nextScript = [...currentDoc.scripts].find(
58-
(script) =>
59-
script.getAttribute('itemprop') === 'script-insert' &&
60-
script.defer === isDefer
61-
);
62-
if (nextScript) {
63-
const replacement = isDefer
64-
? deferScripts.shift()
65-
: syncScripts.shift();
66-
nextScript.replaceWith(replacement);
67-
if (replacement.childNodes.length > 0)
68-
loadNextScript(isDefer, replacement)();
69-
} else if (!isDefer && !waitForHead) loadNextScript(true)();
70-
else reachedEnd = true;
71-
};
72-
const recursiveClone = (node) => {
73-
if (node.nodeType !== Node.ELEMENT_NODE) return node;
74-
const nodeName = node.tagName.toLowerCase();
75-
if (['svg', 'xml'].includes(nodeName))
76-
return node.cloneNode(1);
77-
let elementCopy = currentDoc.createElement(nodeName);
78-
let j = 0,
79-
nodeList = [...node.attributes];
80-
for (; j < nodeList.length; j++)
81-
elementCopy.setAttribute(
82-
nodeList[j].nodeName,
83-
nodeList[j].nodeValue || ''
84-
);
85-
nodeList = [...node.childNodes];
86-
for (j = 0; j < nodeList.length; j++)
87-
elementCopy.appendChild(recursiveClone(nodeList[j]));
88-
if ('script' === nodeName && !node.async) {
89-
const isDefer =
90-
node.defer || 'module' === node.type.toLowerCase();
91-
let replacement = currentDoc.createElement('script');
92-
if (isDefer) replacement.setAttribute('defer', '');
93-
replacement.setAttribute('itemprop', 'script-insert');
94-
if (node.childNodes.length <= 0) {
95-
elementCopy.addEventListener(
96-
'load',
97-
loadNextScript(isDefer, elementCopy)
98-
);
99-
elementCopy.addEventListener(
100-
'error',
101-
loadNextScript(isDefer, elementCopy)
102-
);
25+
const loadPage =
26+
(destination = location, pushState = true) =>
27+
() => {
28+
removeEventListener('load', loadPage);
29+
fetch(destination.pathname + '.ico', { mode: 'same-origin' })
30+
.then((response) => {
31+
if (destination !== location && pushState) {
32+
if (response.status === 200) history.pushState({}, '', destination);
33+
else return location.assign(new URL(destination, location));
34+
}
35+
response.blob().then((blob) => {
36+
new Response(
37+
blob.stream().pipeThrough(new DecompressionStream('gzip'))
38+
)
39+
.text()
40+
.then((text) => {
41+
((currentDoc, newDoc) => {
42+
const deferScripts = [],
43+
syncScripts = [];
44+
let reachedEnd = false,
45+
waitForHead = false,
46+
headScripts = 0;
47+
const bodyLoader = () => {
48+
headScripts--;
49+
if (waitForHead && headScripts <= 0) {
50+
waitForHead = false;
51+
currentDoc.body.replaceWith(recursiveClone(newDoc.body));
52+
if (reachedEnd) loadNextScript(false)();
10353
}
104-
if (isDefer) deferScripts.push(elementCopy);
105-
else {
106-
syncScripts.push(elementCopy);
107-
if ('head' === node.parentElement.tagName.toLowerCase())
108-
headScripts++;
54+
return waitForHead;
55+
};
56+
const loadNextScript = (isDefer, currentScript) => () => {
57+
if (
58+
!isDefer &&
59+
currentScript &&
60+
'head' ===
61+
currentScript.parentElement.tagName.toLowerCase()
62+
)
63+
bodyLoader();
64+
let nextScript = [...currentDoc.scripts].find(
65+
(script) =>
66+
script.getAttribute('itemprop') === 'script-insert' &&
67+
script.defer === isDefer
68+
);
69+
if (nextScript) {
70+
const replacement = isDefer
71+
? deferScripts.shift()
72+
: syncScripts.shift();
73+
nextScript.replaceWith(replacement);
74+
if (replacement.childNodes.length > 0)
75+
loadNextScript(isDefer, replacement)();
76+
} else if (!isDefer && !waitForHead) loadNextScript(true)();
77+
else reachedEnd = true;
78+
};
79+
const recursiveClone = (node) => {
80+
if (node.nodeType !== Node.ELEMENT_NODE) return node;
81+
const nodeName = node.tagName.toLowerCase();
82+
if (['svg', 'xml'].includes(nodeName))
83+
return node.cloneNode(1);
84+
let elementCopy = currentDoc.createElement(nodeName);
85+
let j = 0,
86+
nodeList = [...node.attributes];
87+
for (; j < nodeList.length; j++) {
88+
let attrName = nodeList[j].nodeName;
89+
let attrValue = nodeList[j].nodeValue;
90+
elementCopy.setAttribute(attrName, attrValue || '');
91+
if (attrName.toLowerCase() === 'href')
92+
try {
93+
new URL(attrValue);
94+
} catch (e) {
95+
if (attrValue[0] === '.' || attrValue[0] === '/')
96+
elementCopy.addEventListener('click', (event) => {
97+
event.preventDefault();
98+
if (attrValue === '{{route}}{{/}}')
99+
attrValue = '{{route}}{{/index}}';
100+
loadPage(new URL(attrValue, location))();
101+
});
102+
}
109103
}
110-
return replacement;
111-
} else if (['style', 'link'].includes(nodeName)) {
112-
if ('link' === nodeName && !/^stylesheet$/i.test(node.rel))
113-
return elementCopy;
114-
else if (node.childNodes.length <= 0) {
115-
elementCopy.addEventListener('load', bodyLoader);
116-
elementCopy.addEventListener('error', bodyLoader);
117-
if ('head' === node.parentElement.tagName.toLowerCase())
118-
headScripts++;
104+
nodeList = [...node.childNodes];
105+
for (j = 0; j < nodeList.length; j++)
106+
elementCopy.appendChild(recursiveClone(nodeList[j]));
107+
if ('script' === nodeName && !node.async) {
108+
const isDefer =
109+
node.defer || 'module' === node.type.toLowerCase();
110+
let replacement = currentDoc.createElement('script');
111+
if (isDefer) replacement.setAttribute('defer', '');
112+
replacement.setAttribute('itemprop', 'script-insert');
113+
if (node.childNodes.length <= 0) {
114+
elementCopy.addEventListener(
115+
'load',
116+
loadNextScript(isDefer, elementCopy)
117+
);
118+
elementCopy.addEventListener(
119+
'error',
120+
loadNextScript(isDefer, elementCopy)
121+
);
122+
}
123+
if (isDefer) deferScripts.push(elementCopy);
124+
else {
125+
syncScripts.push(elementCopy);
126+
if ('head' === node.parentElement.tagName.toLowerCase())
127+
headScripts++;
128+
}
129+
return replacement;
130+
} else if (['style', 'link'].includes(nodeName)) {
131+
if (
132+
'link' === nodeName &&
133+
!/^stylesheet$/i.test(node.rel)
134+
)
135+
return elementCopy;
136+
else if (node.childNodes.length <= 0) {
137+
elementCopy.addEventListener('load', bodyLoader);
138+
elementCopy.addEventListener('error', bodyLoader);
139+
if ('head' === node.parentElement.tagName.toLowerCase())
140+
headScripts++;
141+
}
119142
}
120-
}
121-
return elementCopy;
122-
};
123-
let currentType = currentDoc.doctype,
124-
newType = newDoc.doctype,
125-
currentDocNode = currentDoc.documentElement,
126-
newDocNode = newDoc.documentElement;
127-
if (currentType)
128-
if (newType) currentType.replaceWith(newType);
129-
else currentType.remove();
130-
else if (newType) currentDoc.prepend(newType);
131-
if (currentDocNode)
132-
if (newDocNode) {
133-
if (
134-
currentDocNode.tagName === newDocNode.tagName &&
135-
currentDoc.head &&
136-
newDoc.head &&
137-
currentDoc.body &&
138-
newDoc.body
139-
) {
140-
[...currentDocNode.attributes].forEach((attribute) => {
141-
currentDocNode.removeAttribute(attribute.nodeName);
142-
});
143-
[...newDocNode.attributes].forEach((attribute) => {
144-
currentDocNode.setAttribute(
145-
attribute.nodeName,
146-
attribute.nodeValue || ''
143+
return elementCopy;
144+
};
145+
let currentType = currentDoc.doctype,
146+
newType = newDoc.doctype,
147+
currentDocNode = currentDoc.documentElement,
148+
newDocNode = newDoc.documentElement;
149+
if (currentType)
150+
if (newType) currentType.replaceWith(newType);
151+
else currentType.remove();
152+
else if (newType) currentDoc.prepend(newType);
153+
if (currentDocNode)
154+
if (newDocNode) {
155+
if (
156+
currentDocNode.tagName === newDocNode.tagName &&
157+
currentDoc.head &&
158+
newDoc.head &&
159+
currentDoc.body &&
160+
newDoc.body
161+
) {
162+
[...currentDocNode.attributes].forEach((attribute) => {
163+
currentDocNode.removeAttribute(attribute.nodeName);
164+
});
165+
[...newDocNode.attributes].forEach((attribute) => {
166+
currentDocNode.setAttribute(
167+
attribute.nodeName,
168+
attribute.nodeValue || ''
169+
);
170+
});
171+
waitForHead = true;
172+
currentDoc.head.replaceWith(
173+
recursiveClone(newDoc.head)
147174
);
148-
});
149-
waitForHead = true;
150-
currentDoc.head.replaceWith(recursiveClone(newDoc.head));
151-
} else
152-
currentDocNode.replaceWith(recursiveClone(newDocNode));
153-
} else currentDocNode.remove();
154-
else if (newDocNode)
155-
currentDocNode.appendChild(recursiveClone(newDocNode));
175+
} else
176+
currentDocNode.replaceWith(recursiveClone(newDocNode));
177+
} else currentDocNode.remove();
178+
else if (newDocNode)
179+
currentDocNode.appendChild(recursiveClone(newDocNode));
156180

157-
loadNextScript(false)();
158-
})(document, new DOMParser().parseFromString(text, 'text/html'));
159-
});
181+
loadNextScript(false)();
182+
})(
183+
document,
184+
new DOMParser().parseFromString(text, 'text/html')
185+
);
186+
});
187+
});
188+
})
189+
.catch((error) => {
190+
console.log(error);
191+
displayErrorPage();
160192
});
161-
})
162-
.catch((error) => {
163-
console.log(error);
164-
displayErrorPage();
165-
});
166-
};
167-
if (document.readyState === 'complete') loadPage();
168-
else addEventListener('load', loadPage);
193+
};
194+
if (document.readyState === 'complete') loadPage()();
195+
else addEventListener('load', loadPage());
196+
addEventListener('popstate', () => {
197+
loadPage(location, false)();
198+
});
169199
})();

views/pages/nav/directory.html

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -56,48 +56,42 @@ <h2 class="box-description">
5656
{{mask}}{{ Select a directory below: }}
5757
</h2>
5858
<div class="box-button-container">
59-
<a id="emtooltip" href="{{route}}{{/emulators}}" class="box-button"
59+
<a
60+
id="emtooltip"
61+
href="{{route}}{{/emulators}}"
62+
class="box-button tippy-button"
63+
data-tippy-content="{{mask}}{{Emulators [Webretro v6.5]: Fully ported RetroArch with over 20 emulators featured!}}"
6064
>{{mask}}{{Emulators}}</a
6165
>
6266
<a
6367
id="eltooltip"
6468
href="{{route}}{{/retro-games}}"
65-
class="box-button"
69+
class="box-button tippy-button"
70+
data-tippy-content="{{mask}}{{EmuLibrary [Collection]: Popular collection of games sourced from other websites. You can set this via the settings menu.}}"
6671
>{{mask}}{{EmuLibrary}}</a
6772
>
68-
<a id="wgtooltip" href="{{route}}{{/web-games}}" class="box-button"
73+
<a
74+
id="wgtooltip"
75+
href="{{route}}{{/web-games}}"
76+
class="box-button tippy-button"
77+
data-tippy-content="{{mask}}{{Web Games [Collection]: Collection of popular web games, including WebGL and HTML5 games.}}"
6978
>{{mask}}{{Web Games}}</a
7079
>
7180
<a
7281
id="fgtooltip"
7382
href="{{route}}{{/flash-games}}"
74-
class="box-button"
83+
class="box-button tippy-button"
84+
data-tippy-content="{{mask}}{{Flash Games [Collection]: Collection of Flash games emulated via Ruffle.}}"
7585
>{{mask}}{{Flash Games}}</a
7686
>
7787
<script src="https://unpkg.com/@popperjs/core@2"></script>
7888
<script src="https://unpkg.com/tippy.js@6"></script>
7989
<script>
80-
const tooltipContent = {
81-
emtooltip:
82-
'{{mask}}{{Emulators [Webretro v6.5]: Fully ported RetroArch with over 20 emulators featured!}}',
83-
eltooltip:
84-
'{{mask}}{{EmuLibrary [Collection]: Popular collection of games sourced from other websites. You can set this via the settings menu.}}',
85-
wgtooltip:
86-
'{{mask}}{{Web Games [Collection]: Collection of popular web games, including WebGL and HTML5 games.}}',
87-
fgtooltip:
88-
'{{mask}}{{Flash Games [Collection]: Collection of Flash games emulated via Ruffle.}}',
89-
};
90-
91-
tippy.setDefaultProps({
90+
tippy('.tippy-button', {
9291
delay: 50,
9392
animateFill: true,
9493
placement: 'bottom',
9594
});
96-
97-
tippy('#emtooltip', { content: tooltipContent.emtooltip });
98-
tippy('#eltooltip', { content: tooltipContent.eltooltip });
99-
tippy('#wgtooltip', { content: tooltipContent.wgtooltip });
100-
tippy('#fgtooltip', { content: tooltipContent.fgtooltip });
10195
</script>
10296
</div>
10397
</div>

0 commit comments

Comments
 (0)