forked from DSpace/dspace-angular
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathindex.html
More file actions
150 lines (139 loc) · 6.87 KB
/
Copy pathindex.html
File metadata and controls
150 lines (139 loc) · 6.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<base href="/">
<title>DSpace</title>
<meta name="viewport" content="width=device-width,minimum-scale=1">
<meta http-equiv="cache-control" content="no-store">
<style id="__dspace-ssr-overlay-style">
/* Overlay used to mask the Angular 15 bootstrap re-render of the SSR DOM.
See src/index.html bootstrap script + AppComponent.removeSsrOverlayWhenStable.
The overlay element holds the SSR-rendered children moved out of <ds-app>,
so it keeps every original Angular view-encapsulation attribute (_ngcontent-scXXX),
inline style, and lifecycle context — i.e. it looks pixel-identical to what SSR sent. */
#__dspace_ssr_overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
width: 100%;
z-index: 10000;
background: #fff;
pointer-events: none;
}
/* Keep <ds-app> taking layout space (height of CSR result) but hidden, so the page
does not collapse the moment we drop the overlay. */
ds-app[data-dspace-ssr-hidden] { visibility: hidden; }
</style>
</head>
<body>
<!-- dependencies -->
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
<ds-app></ds-app>
<script>
/*
Anti-flicker overlay.
Why this exists: this codebase is on Angular 15 + ngUniversal. There is no provideClientHydration
available before Angular 16, so on every browser load Angular tears down the entire SSR DOM and
re-renders the component tree from scratch. The rebuild takes ~600-1500 ms on slow connections,
during which the user sees the SSR view -> blank/half-built CSR view -> final CSR view.
This script captures the SSR DOM as a non-interactive snapshot the moment it's parsed (before
any module/main script runs - those are type=module and therefore deferred). While Angular
rebuilds the real <ds-app> invisibly, the snapshot keeps the page looking stable. AppComponent
removes the overlay once ApplicationRef.isStable settles.
*/
(function () {
if (typeof window === 'undefined' || typeof document === 'undefined') return;
// Skip when Cypress is driving the page. The overlay duplicates SSR DOM (moved into the
// overlay) alongside the CSR DOM (rendered into <ds-app>) during the masking window — so
// any cy.get('#some-id').click() picks up two elements and fails. The overlay is a pure
// UX nicety, and Cypress E2E doesn't measure visual smoothness anyway; bail early.
if (typeof window.Cypress !== 'undefined') return;
try {
var app = document.querySelector('ds-app');
// If SSR was skipped for this route (excludePathPatterns), there are no children; nothing to mask.
if (!app || !app.firstElementChild) return;
if (document.getElementById('__dspace_ssr_overlay')) return;
// Critical: Angular's BrowserModule.withServerTransition removes ALL <style ng-transition="...">
// tags during bootstrap. Those tags hold the component-scoped CSS that styles the SSR DOM
// via attribute selectors like [_ngcontent-scXXX]. If we don't preserve copies, the overlay
// renders unstyled. Clone the SSR styles into <style data-dspace-ssr-keep> tags that Angular
// ignores, so the overlay keeps looking like the page the user already saw.
var ssrStyles = document.querySelectorAll('style[ng-transition]');
var keptStyles = [];
for (var i = 0; i < ssrStyles.length; i++) {
var copy = document.createElement('style');
copy.setAttribute('data-dspace-ssr-keep', '');
copy.textContent = ssrStyles[i].textContent;
document.head.appendChild(copy);
keptStyles.push(copy);
}
// Build overlay and MOVE (not clone) the SSR children into it. Moving keeps every live DOM
// detail (Angular's view-encapsulation attributes, computed inline styles, image-load state)
// so the overlay is pixel-identical to what the user already saw before Angular booted.
// Cloning via innerHTML loses parent-context-dependent rendering.
//
// Accessibility note: we deliberately do NOT set aria-hidden on the overlay. The overlay
// *is* the visible page during the masking window, so assistive technologies should read
// it. The original <ds-app> underneath gets visibility:hidden (via attribute + CSS rule),
// which removes both itself and its children from the accessibility tree.
var overlay = document.createElement('div');
overlay.id = '__dspace_ssr_overlay';
while (app.firstChild) {
overlay.appendChild(app.firstChild);
}
// Hide the now-empty <ds-app> so Angular can rebuild into it invisibly. We use an attribute
// (CSS in <head> targets it) rather than setting .style.visibility directly so Angular's
// template doesn't blow it away on first ChangeDetection.
app.setAttribute('data-dspace-ssr-hidden', '');
document.body.appendChild(overlay);
var removing = false;
window.__dspaceRemoveSsrOverlay = function () {
// Re-entrancy guard: null the pointer up-front so a racing isStable + 15s safety
// fallback cannot start two interleaving fade-out passes (which would re-remove
// the kept styles from underneath the first pass).
if (removing) return;
removing = true;
window.__dspaceRemoveSsrOverlay = null;
// Always unhide the real <ds-app> and drop the kept SSR styles first, even if the
// overlay node has gone missing (e.g. removed by an extension or another script).
// A bare early return here would otherwise leave the app permanently hidden.
app.removeAttribute('data-dspace-ssr-hidden');
var removeKeptStyles = function () {
for (var i = 0; i < keptStyles.length; i++) {
if (keptStyles[i].parentNode) keptStyles[i].parentNode.removeChild(keptStyles[i]);
}
keptStyles = [];
};
var el = document.getElementById('__dspace_ssr_overlay');
if (!el) {
removeKeptStyles();
return;
}
el.style.transition = 'opacity 150ms ease-out';
el.style.opacity = '0';
setTimeout(function () {
if (el && el.parentNode) el.parentNode.removeChild(el);
removeKeptStyles();
}, 200);
};
// Safety net: if the app never reaches isStable (e.g. permanent HTTP poll), remove anyway.
setTimeout(function () {
if (typeof window.__dspaceRemoveSsrOverlay === 'function') {
window.__dspaceRemoveSsrOverlay();
}
}, 15000);
} catch (e) {
// Don't let the overlay logic kill the page, but surface the failure so a silently-broken
// flicker fix is at least diagnosable in DevTools.
if (window.console && typeof console.warn === 'function') {
console.warn('[dspace-ssr-overlay] disabled due to error:', e);
}
}
})();
</script>
</body>
<!-- do not include client bundle, it is injected with Zone already loaded -->
</html>