@teipublisher/pb-components 2.26.1-next.3 → 3.0.0-next-4.2
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.
- package/.github/workflows/docker-cypress.yml +53 -0
- package/.github/workflows/node.js.yml +70 -21
- package/.releaserc.json +7 -2
- package/CHANGELOG.md +363 -11
- package/Dockerfile +78 -70
- package/README.md +112 -4
- package/css/components.css +5 -5
- package/css/gridjs/mermaid.min.css +1 -1
- package/css/leaflet/Control.Geocoder.css +1 -126
- package/css/leaflet/images/layers.png +0 -0
- package/css/tify/tify.css +6 -5
- package/css/tom-select/tom-select.bootstrap4.min.css +1 -1
- package/css/tom-select/tom-select.bootstrap5.min.css +1 -1
- package/css/tom-select/tom-select.default.min.css +1 -1
- package/css/tom-select/tom-select.default.min.css.map +1 -0
- package/css/tom-select/tom-select.min.css +1 -1
- package/cypress.config.js +84 -0
- package/dist/api.html +1 -1
- package/dist/css/design-system.css +607 -0
- package/dist/demo/bundle-test.html +4 -3
- package/dist/demo/components.css +46 -1
- package/dist/demo/design-system.html +710 -0
- package/dist/demo/dts-client.html +2 -2
- package/dist/demo/pb-autocomplete.html +23 -11
- package/dist/demo/pb-autocomplete2.html +66 -55
- package/dist/demo/pb-autocomplete3.html +17 -8
- package/dist/demo/pb-blacklab-highlight.html +28 -11
- package/dist/demo/pb-blacklab-results.html +3 -2
- package/dist/demo/pb-browse-docs.html +24 -24
- package/dist/demo/pb-browse-docs2.html +3 -3
- package/dist/demo/pb-clipboard.html +32 -28
- package/dist/demo/pb-code-editor.html +6 -6
- package/dist/demo/pb-code-highlight.html +63 -63
- package/dist/demo/pb-codepen.html +1 -1
- package/dist/demo/pb-collapse.html +1 -1
- package/dist/demo/pb-collapse2.html +2 -2
- package/dist/demo/pb-combo-box.html +135 -130
- package/dist/demo/pb-custom-form.html +64 -55
- package/dist/demo/pb-dialog.html +12 -6
- package/dist/demo/pb-document.html +1 -1
- package/dist/demo/pb-download.html +68 -59
- package/dist/demo/pb-drawer.html +67 -46
- package/dist/demo/pb-drawer2.html +65 -58
- package/dist/demo/pb-edit-app.html +2 -2
- package/dist/demo/pb-edit-xml.html +1 -1
- package/dist/demo/pb-facsimile-2.html +26 -11
- package/dist/demo/pb-facsimile-3.html +25 -10
- package/dist/demo/pb-facsimile-dedup-test-2.html +48 -0
- package/dist/demo/pb-facsimile-dedup-test.html +48 -0
- package/dist/demo/pb-facsimile.html +4 -4
- package/dist/demo/pb-formula.html +1 -1
- package/dist/demo/pb-grid.html +22 -8
- package/dist/demo/pb-highlight.html +2 -2
- package/dist/demo/pb-i18n-simple.html +1 -0
- package/dist/demo/pb-i18n.html +15 -5
- package/dist/demo/pb-image-strip-standalone.html +2 -2
- package/dist/demo/pb-image-strip-view.html +2 -2
- package/dist/demo/pb-leaflet-map.html +3 -3
- package/dist/demo/pb-leaflet-map2.html +2 -2
- package/dist/demo/pb-leaflet-map3.html +3 -3
- package/dist/demo/pb-link.html +1 -1
- package/dist/demo/pb-load.html +2 -6
- package/dist/demo/pb-login.html +1 -3
- package/dist/demo/pb-manage-odds.html +9 -4
- package/dist/demo/pb-markdown.html +1 -1
- package/dist/demo/pb-media-query.html +2 -2
- package/dist/demo/pb-mei.html +2 -2
- package/dist/demo/pb-mei2.html +2 -2
- package/dist/demo/pb-message.html +2 -3
- package/dist/demo/pb-odd-editor.html +54 -52
- package/dist/demo/pb-page-header.html +27 -0
- package/dist/demo/pb-popover.html +1 -1
- package/dist/demo/pb-print-preview.html +2 -2
- package/dist/demo/pb-progress.html +4 -4
- package/dist/demo/pb-repeat.html +32 -36
- package/dist/demo/pb-search.html +16 -5
- package/dist/demo/pb-search2.html +4 -4
- package/dist/demo/pb-search3.html +3 -3
- package/dist/demo/pb-search4.html +3 -3
- package/dist/demo/pb-select-feature.html +4 -4
- package/dist/demo/pb-select-feature2.html +4 -4
- package/dist/demo/pb-select-feature3.html +2 -2
- package/dist/demo/pb-select-i18n.html +58 -53
- package/dist/demo/pb-select-odd.html +1 -1
- package/dist/demo/pb-select.html +190 -75
- package/dist/demo/pb-select2.html +91 -37
- package/dist/demo/pb-select3.html +109 -41
- package/dist/demo/pb-svg.html +1 -1
- package/dist/demo/pb-table-grid.html +26 -15
- package/dist/demo/pb-tabs.html +15 -7
- package/dist/demo/pb-tify.html +7 -7
- package/dist/demo/pb-timeline.html +1 -1
- package/dist/demo/pb-timeline2.html +1 -1
- package/dist/demo/pb-toggle-feature.html +26 -23
- package/dist/demo/pb-toggle-feature2.html +4 -4
- package/dist/demo/pb-toggle-feature3.html +2 -2
- package/dist/demo/pb-toggle-feature4.html +56 -54
- package/dist/demo/pb-version.html +2 -2
- package/dist/demo/pb-view.html +78 -40
- package/dist/demo/pb-view2.html +69 -46
- package/dist/demo/pb-view3.html +53 -48
- package/dist/demo/pb-view4.html +70 -49
- package/dist/demo/pb-zoom.html +2 -2
- package/dist/{es-global-bridge-d8ce175d.js → es-global-bridge-D8ZcUcx_.js} +0 -4
- package/dist/focus-mixin-VCsFap6b.js +768 -0
- package/dist/images/icons.svg +217 -0
- package/dist/jinn-codemirror-DETLdm08.js +1 -0
- package/dist/lib/openseadragon.min.js +80 -0
- package/dist/lib/openseadragon.min.js.map +1 -0
- package/dist/pb-code-editor.js +25 -20
- package/dist/pb-component-docs.js +414 -3225
- package/dist/pb-components-bundle.js +3046 -4402
- package/dist/pb-dialog-tklYGWfc.js +121 -0
- package/dist/pb-edit-app.js +208 -107
- package/dist/pb-elements.json +716 -249
- package/dist/pb-facsimile.js +46 -0
- package/dist/pb-i18n-C0NDma4h.js +1 -0
- package/dist/pb-leaflet-map.js +23 -23
- package/dist/pb-mei.js +152 -134
- package/dist/pb-mixin-DHoWQheB.js +1 -0
- package/dist/pb-odd-editor.js +1671 -1231
- package/dist/pb-tify.js +1 -27
- package/dist/unsafe-html-D5VGo9Oq.js +1 -0
- package/dist/urls-BEONu_g4.js +1 -0
- package/eslint.config.mjs +92 -0
- package/gh-pages.js +5 -3
- package/i18n/common/en.json +6 -0
- package/i18n/common/pl.json +2 -2
- package/images/icons.svg +217 -0
- package/index.html +0 -5
- package/lib/leaflet-src.js.map +1 -0
- package/lib/leaflet.markercluster-src.js.map +1 -0
- package/lib/openseadragon.min.js +6 -6
- package/package.json +56 -81
- package/pb-elements.json +716 -249
- package/rollup.config.mjs +312 -0
- package/src/assets/components.css +5 -5
- package/src/assets/design-system.css +607 -0
- package/src/authority/airtable.js +20 -21
- package/src/authority/anton.js +129 -129
- package/src/authority/custom.js +70 -27
- package/src/authority/geonames.js +38 -32
- package/src/authority/gnd.js +50 -42
- package/src/authority/kbga.js +136 -134
- package/src/authority/metagrid.js +44 -46
- package/src/authority/reconciliation.js +66 -68
- package/src/authority/registry.js +4 -4
- package/src/docs/demo-utils.js +91 -0
- package/src/docs/pb-component-docs.js +287 -147
- package/src/docs/pb-component-view.js +380 -273
- package/src/docs/pb-components-list.js +115 -51
- package/src/docs/pb-demo-snippet.js +199 -174
- package/src/dts-client.js +306 -303
- package/src/dts-select-endpoint.js +125 -85
- package/src/parse-date-service.js +184 -135
- package/src/pb-ajax.js +175 -173
- package/src/pb-authority-lookup.js +198 -158
- package/src/pb-autocomplete.js +731 -313
- package/src/pb-blacklab-highlight.js +266 -260
- package/src/pb-blacklab-results.js +230 -225
- package/src/pb-browse-docs.js +601 -484
- package/src/pb-browse.js +68 -65
- package/src/pb-clipboard.js +97 -76
- package/src/pb-code-editor.js +111 -103
- package/src/pb-code-highlight.js +234 -204
- package/src/pb-codepen.js +81 -73
- package/src/pb-collapse.js +265 -152
- package/src/pb-combo-box.js +191 -191
- package/src/pb-components-bundle.js +1 -7
- package/src/pb-components.js +2 -6
- package/src/pb-custom-form.js +230 -141
- package/src/pb-dialog.js +99 -63
- package/src/pb-document.js +118 -91
- package/src/pb-download.js +214 -198
- package/src/pb-drawer.js +146 -149
- package/src/pb-edit-app.js +471 -240
- package/src/pb-edit-xml.js +101 -98
- package/src/pb-events.js +126 -107
- package/src/pb-facs-link.js +130 -101
- package/src/pb-facsimile.js +494 -410
- package/src/pb-fetch.js +389 -0
- package/src/pb-formula.js +152 -154
- package/src/pb-geolocation.js +130 -132
- package/src/pb-grid-action.js +59 -56
- package/src/pb-grid.js +388 -228
- package/src/pb-highlight.js +142 -142
- package/src/pb-hotkeys.js +40 -42
- package/src/pb-i18n.js +115 -127
- package/src/pb-icon-button.js +108 -0
- package/src/pb-icon.js +283 -0
- package/src/pb-image-strip.js +85 -79
- package/src/pb-lang.js +142 -57
- package/src/pb-leaflet-map.js +551 -483
- package/src/pb-link.js +132 -126
- package/src/pb-load.js +495 -428
- package/src/pb-login.js +303 -248
- package/src/pb-manage-odds.js +384 -338
- package/src/pb-map-icon.js +90 -90
- package/src/pb-map-layer.js +86 -86
- package/src/pb-markdown.js +107 -110
- package/src/pb-media-query.js +75 -73
- package/src/pb-mei.js +523 -303
- package/src/pb-message.js +144 -98
- package/src/pb-mixin.js +268 -265
- package/src/pb-navigation.js +83 -96
- package/src/pb-observable.js +39 -39
- package/src/pb-odd-editor.js +1209 -948
- package/src/pb-odd-elementspec-editor.js +375 -310
- package/src/pb-odd-model-editor.js +1189 -941
- package/src/pb-odd-parameter-editor.js +269 -170
- package/src/pb-odd-rendition-editor.js +184 -131
- package/src/pb-page.js +451 -422
- package/src/pb-paginate.js +260 -178
- package/src/pb-panel.js +217 -183
- package/src/pb-popover-themes.js +16 -9
- package/src/pb-popover.js +297 -288
- package/src/pb-print-preview.js +128 -128
- package/src/pb-progress.js +52 -52
- package/src/pb-repeat.js +141 -108
- package/src/pb-restricted.js +85 -78
- package/src/pb-search.js +258 -230
- package/src/pb-select-feature.js +210 -126
- package/src/pb-select-odd.js +184 -118
- package/src/pb-select-template.js +113 -78
- package/src/pb-select.js +330 -229
- package/src/pb-split-list.js +181 -176
- package/src/pb-svg.js +81 -80
- package/src/pb-table-column.js +55 -55
- package/src/pb-table-grid.js +334 -205
- package/src/pb-tabs.js +238 -61
- package/src/pb-tify.js +3331 -126
- package/src/pb-timeline.js +394 -255
- package/src/pb-toggle-feature.js +196 -188
- package/src/pb-upload.js +201 -176
- package/src/pb-version.js +22 -34
- package/src/pb-view-annotate.js +138 -102
- package/src/pb-view.js +1722 -1272
- package/src/pb-zoom.js +144 -46
- package/src/search-result-service.js +256 -223
- package/src/seed-element.js +14 -22
- package/src/settings.js +4 -4
- package/src/theming.js +98 -91
- package/src/urls.js +403 -289
- package/src/utils.js +53 -51
- package/vite.config.js +86 -0
- package/.github/workflows/main.yml +0 -24
- package/.github/workflows/release.js.yml +0 -34
- package/css/pb-styles.css +0 -51
- package/dist/iron-form-3b8dcaa7.js +0 -210
- package/dist/jinn-codemirror-da0e2d1f.js +0 -1
- package/dist/paper-checkbox-515a5284.js +0 -1597
- package/dist/paper-icon-button-b1d31571.js +0 -398
- package/dist/paper-listbox-a3b7175c.js +0 -1265
- package/dist/pb-i18n-0611135a.js +0 -1
- package/dist/pb-mixin-b1caa22e.js +0 -158
- package/dist/polymer-hack.js +0 -1
- package/dist/vaadin-element-mixin-fe4a4883.js +0 -527
- package/lib/Control.Geocoder.min.js +0 -2
- package/lib/Control.Geocoder.min.js.map +0 -1
- package/src/assets/pb-styles.css +0 -51
- package/src/pb-light-dom.js +0 -41
- package/src/polymer-hack.js +0 -6
package/src/pb-view.js
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
import { LitElement, html, css } from 'lit
|
|
2
|
-
import
|
|
3
|
-
import { pbMixin, waitOnce } from
|
|
4
|
-
import { registry } from
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import '@polymer/iron-ajax';
|
|
9
|
-
import '@polymer/paper-dialog';
|
|
10
|
-
import '@polymer/paper-dialog-scrollable';
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { animate } from 'animejs';
|
|
3
|
+
import { pbMixin, waitOnce } from './pb-mixin.js';
|
|
4
|
+
import { registry } from './urls.js';
|
|
5
|
+
import { typesetMath } from './pb-formula.js';
|
|
6
|
+
import { loadStylesheets, themableMixin } from './theming.js';
|
|
7
|
+
import './pb-fetch.js';
|
|
11
8
|
|
|
12
9
|
/**
|
|
13
10
|
* This is the main component for viewing text which has been transformed via an ODD.
|
|
14
11
|
* The document to be viewed is determined by the `pb-document` element the property
|
|
15
12
|
* `src` points to. If not overwritten, `pb-view` will use the settings defined by
|
|
16
13
|
* the connected document, like view type, ODD etc.
|
|
17
|
-
*
|
|
14
|
+
*
|
|
18
15
|
* `pb-view` can display an entire document or just a fragment of it
|
|
19
16
|
* as defined by the properties `xpath`, `xmlId` or `nodeId`. The most common use case
|
|
20
17
|
* is to set `xpath` to point to a specific part of a document.
|
|
21
|
-
*
|
|
18
|
+
*
|
|
22
19
|
* Navigating to the next or previous fragment would usually be triggered by a separate
|
|
23
20
|
* `pb-navigation` element, which sends a `pb-navigate` event to the `pb-view`. However,
|
|
24
21
|
* `pb-view` also implements automatic loading of next/previous fragments if the user
|
|
@@ -50,1368 +47,1821 @@ import '@polymer/paper-dialog-scrollable';
|
|
|
50
47
|
* @cssprop --pb-view-scroll-margin-top - Applied to any element with an id
|
|
51
48
|
* @csspart content - The root div around the displayed content
|
|
52
49
|
* @csspart footnotes - div containing the footnotes
|
|
53
|
-
|
|
50
|
+
|
|
54
51
|
* @fires pb-start-update - Fired before the element updates its content
|
|
55
52
|
* @fires pb-update - Fired when the component received content from the server
|
|
56
53
|
* @fires pb-end-update - Fired after the element has finished updating its content
|
|
57
54
|
* @fires pb-navigate - When received, navigate forward or backward in the document
|
|
58
|
-
* @fires pb-zoom - When received, zoom in or out by changing font size of the content
|
|
59
55
|
* @fires pb-refresh - When received, refresh the content based on the parameters passed in the event
|
|
60
56
|
* @fires pb-toggle - When received, toggle content properties
|
|
61
57
|
*/
|
|
62
58
|
export class PbView extends themableMixin(pbMixin(LitElement)) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
59
|
+
static get properties() {
|
|
60
|
+
return {
|
|
61
|
+
/**
|
|
62
|
+
* The id of a `pb-document` element this view should display.
|
|
63
|
+
* Settings like `odd` or `view` will be taken from the `pb-document`
|
|
64
|
+
* unless overwritten by properties in this component.
|
|
65
|
+
*
|
|
66
|
+
* This property is **required** and **must** point to an existing `pb-document` with
|
|
67
|
+
* the given id.
|
|
68
|
+
*
|
|
69
|
+
* Setting the property after initialization will clear the properties xmlId, nodeId and odd.
|
|
70
|
+
*/
|
|
71
|
+
src: {
|
|
72
|
+
type: String,
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* The ODD to use for rendering the document. Overwrites an ODD defined on
|
|
76
|
+
* `pb-document`. The odd should be specified by its name without path
|
|
77
|
+
* or the `.odd` suffix.
|
|
78
|
+
*/
|
|
79
|
+
odd: {
|
|
80
|
+
type: String,
|
|
81
|
+
},
|
|
82
|
+
/**
|
|
83
|
+
* The view type to use for paginating the document. Either `page`, `div` or `single`.
|
|
84
|
+
* Overwrites the same property specified on `pb-document`. Values have the following meaning:
|
|
85
|
+
*
|
|
86
|
+
* Value | Displayed content
|
|
87
|
+
* ------|------------------
|
|
88
|
+
* `page` | content is displayed page by page as determined by tei:pb
|
|
89
|
+
* `div` | content is displayed by divisions
|
|
90
|
+
* `single` | do not paginate but display entire content at once
|
|
91
|
+
*/
|
|
92
|
+
view: {
|
|
93
|
+
type: String,
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* Controls the pagination-by-div algorithm: if a page would have less than
|
|
97
|
+
* `fill` elements, it tries to fill
|
|
98
|
+
* up the page by pulling following divs in. When set to 0, it will never
|
|
99
|
+
* attempt to fill up the page. For the annotation editor this should
|
|
100
|
+
* always be 0.
|
|
101
|
+
*/
|
|
102
|
+
fill: {
|
|
103
|
+
type: Number,
|
|
104
|
+
},
|
|
105
|
+
/**
|
|
106
|
+
* An eXist nodeId. If specified, selects the root of the fragment of the document
|
|
107
|
+
* which should be displayed. Normally this property is set automatically by pagination.
|
|
108
|
+
*/
|
|
109
|
+
nodeId: {
|
|
110
|
+
type: String,
|
|
111
|
+
attribute: 'node-id',
|
|
112
|
+
},
|
|
113
|
+
/**
|
|
114
|
+
* An xml:id to be displayed. If specified, this determines the root of the fragment to be
|
|
115
|
+
* displayed. Use to directly navigate to a specific section.
|
|
116
|
+
*/
|
|
117
|
+
xmlId: {
|
|
118
|
+
type: Array,
|
|
119
|
+
attribute: 'xml-id',
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* An optional XPath expression: the root of the fragment to be processed is determined
|
|
123
|
+
* by evaluating the given XPath expression. The XPath expression should be absolute.
|
|
124
|
+
* The namespace of the document is declared as default namespace, so no prefixes should
|
|
125
|
+
* be used.
|
|
126
|
+
*
|
|
127
|
+
* If the `map` property is used, it may change scope for the displayed fragment.
|
|
128
|
+
*/
|
|
129
|
+
xpath: {
|
|
130
|
+
type: String,
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* If defined denotes the local name of an XQuery function in `modules/map.xql`, which will be called
|
|
134
|
+
* with the current root node and should return the node of a mapped fragment. This is helpful if one
|
|
135
|
+
* wants, for example, to show a translation fragment aligned with the part of the transcription currently
|
|
136
|
+
* shown. In this case, the properties of the `pb-view` would still point to the transcription, but the function
|
|
137
|
+
* identified by map would return the corresponding fragment from the translation to be processed.
|
|
138
|
+
*
|
|
139
|
+
* Navigation in the document is still determined by the current root as defined through the `root`, `xpath`
|
|
140
|
+
* and `xmlId` properties.
|
|
141
|
+
*/
|
|
142
|
+
map: {
|
|
143
|
+
type: String,
|
|
144
|
+
},
|
|
145
|
+
/**
|
|
146
|
+
* If set to true, the component will not load automatically. Instead it will wait until it receives a `pb-update`
|
|
147
|
+
* event. Use this to make one `pb-view` component dependent on another one. Default is 'false'.
|
|
148
|
+
*/
|
|
149
|
+
onUpdate: {
|
|
150
|
+
type: Boolean,
|
|
151
|
+
attribute: 'on-update',
|
|
152
|
+
},
|
|
153
|
+
/**
|
|
154
|
+
* Message to display if no content was returned by the server.
|
|
155
|
+
* Set to empty string to show nothing.
|
|
156
|
+
*/
|
|
157
|
+
notFound: {
|
|
158
|
+
type: String,
|
|
159
|
+
attribute: 'not-found',
|
|
160
|
+
},
|
|
161
|
+
/**
|
|
162
|
+
* The relative URL to the script on the server which will be called for loading content.
|
|
163
|
+
*/
|
|
164
|
+
url: {
|
|
165
|
+
type: String,
|
|
166
|
+
},
|
|
167
|
+
/**
|
|
168
|
+
* If set, rewrite URLs to load pages as static HTML files,
|
|
169
|
+
* so no TEI Publisher instance is required. Use this in combination with
|
|
170
|
+
* [tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).
|
|
171
|
+
* The value should point to the HTTP root path under which the static version
|
|
172
|
+
* will be hosted. This is used to resolve CSS stylesheets.
|
|
173
|
+
*/
|
|
174
|
+
static: {
|
|
175
|
+
type: String,
|
|
176
|
+
},
|
|
177
|
+
/**
|
|
178
|
+
* The server returns footnotes separately. Set this property
|
|
179
|
+
* if you wish to append them to the main text.
|
|
180
|
+
*/
|
|
181
|
+
appendFootnotes: {
|
|
182
|
+
type: Boolean,
|
|
183
|
+
attribute: 'append-footnotes',
|
|
184
|
+
},
|
|
185
|
+
/**
|
|
186
|
+
* Should matches be highlighted if a search has been executed?
|
|
187
|
+
*/
|
|
188
|
+
suppressHighlight: {
|
|
189
|
+
type: Boolean,
|
|
190
|
+
attribute: 'suppress-highlight',
|
|
191
|
+
},
|
|
192
|
+
/**
|
|
193
|
+
* CSS selector to find column breaks in the content returned
|
|
194
|
+
* from the server. If this property is set and column breaks
|
|
195
|
+
* are found, the component will display two columns side by side.
|
|
196
|
+
*/
|
|
197
|
+
columnSeparator: {
|
|
198
|
+
type: String,
|
|
199
|
+
attribute: 'column-separator',
|
|
200
|
+
},
|
|
201
|
+
/**
|
|
202
|
+
* The reading direction, i.e. 'ltr' or 'rtl'.
|
|
203
|
+
*
|
|
204
|
+
* @type {"ltr"|"rtl"}
|
|
205
|
+
*/
|
|
206
|
+
direction: {
|
|
207
|
+
type: String,
|
|
208
|
+
},
|
|
209
|
+
/**
|
|
210
|
+
* If set, points to an external stylesheet which should be applied to
|
|
211
|
+
* the text *after* the ODD-generated styles.
|
|
212
|
+
*/
|
|
213
|
+
loadCss: {
|
|
214
|
+
type: String,
|
|
215
|
+
attribute: 'load-css',
|
|
216
|
+
},
|
|
217
|
+
/**
|
|
218
|
+
* If set, relative links (img, a) will be made absolute.
|
|
219
|
+
*/
|
|
220
|
+
fixLinks: {
|
|
221
|
+
type: Boolean,
|
|
222
|
+
attribute: 'fix-links',
|
|
223
|
+
},
|
|
224
|
+
/**
|
|
225
|
+
* If set, a refresh will be triggered if a `pb-i18n-update` event is received,
|
|
226
|
+
* e.g. due to the user selecting a different interface language.
|
|
227
|
+
*
|
|
228
|
+
* Also requires `requireLanguage` to be set on the surrounding `pb-page`.
|
|
229
|
+
* See there for more information.
|
|
230
|
+
*/
|
|
231
|
+
useLanguage: {
|
|
232
|
+
type: Boolean,
|
|
233
|
+
attribute: 'use-language',
|
|
234
|
+
},
|
|
235
|
+
/**
|
|
236
|
+
* wether to animate the view when new page is loaded. Defaults to 'false' meaning that no
|
|
237
|
+
* animation takes place. If 'true' will apply a translateX transistion in forward/backward direction.
|
|
238
|
+
*/
|
|
239
|
+
animation: {
|
|
240
|
+
type: Boolean,
|
|
241
|
+
},
|
|
242
|
+
/**
|
|
243
|
+
* Experimental: if enabled, the view will incrementally load new document fragments if the user tries to scroll
|
|
244
|
+
* beyond the start or end of the visible text. The feature inserts a small blank section at the top
|
|
245
|
+
* and bottom. If this section becomes visible, a load operation will be triggered.
|
|
246
|
+
*
|
|
247
|
+
* Note: only browsers implementing the `IntersectionObserver` API are supported. Also the feature
|
|
248
|
+
* does not work in two-column mode or with animations.
|
|
249
|
+
*/
|
|
250
|
+
infiniteScroll: {
|
|
251
|
+
type: Boolean,
|
|
252
|
+
attribute: 'infinite-scroll',
|
|
253
|
+
},
|
|
254
|
+
/**
|
|
255
|
+
* Maximum number of fragments to keep in memory if `infinite-scroll`
|
|
256
|
+
* is enabled. If the user is scrolling beyond the maximum, fragements
|
|
257
|
+
* will be removed from the DOM before or after the current reading position.
|
|
258
|
+
* Default is 10. Set to zero to allow loading the entire document.
|
|
259
|
+
*/
|
|
260
|
+
infiniteScrollMax: {
|
|
261
|
+
type: Number,
|
|
262
|
+
attribute: 'infinite-scroll-max',
|
|
263
|
+
},
|
|
264
|
+
/**
|
|
265
|
+
* A selector pointing to other components this component depends on.
|
|
266
|
+
* When method `wait` is called, it will wait until all referenced
|
|
267
|
+
* components signal with a `pb-ready` event that they are ready and listening
|
|
268
|
+
* to events.
|
|
269
|
+
*
|
|
270
|
+
* `pb-view` by default sets this property to select `pb-toggle-feature` and `pb-select-feature`
|
|
271
|
+
* elements.
|
|
272
|
+
*/
|
|
273
|
+
waitFor: {
|
|
274
|
+
type: String,
|
|
275
|
+
attribute: 'wait-for',
|
|
276
|
+
},
|
|
277
|
+
/**
|
|
278
|
+
* By default, navigating to next/previous page will update browser parameters,
|
|
279
|
+
* so reloading the page will load the correct position within the document.
|
|
280
|
+
*
|
|
281
|
+
* Set this property to disable location tracking for the component altogether.
|
|
282
|
+
*/
|
|
283
|
+
disableHistory: {
|
|
284
|
+
type: Boolean,
|
|
285
|
+
attribute: 'disable-history',
|
|
286
|
+
},
|
|
287
|
+
/**
|
|
288
|
+
* If set to true, pb-view will only read from the registry and never write to it.
|
|
289
|
+
* This is useful when pb-view is used as a simple receiver of navigation updates
|
|
290
|
+
* from other components (e.g., pb-tify). When enabled, pb-view will:
|
|
291
|
+
* - Still subscribe to registry changes
|
|
292
|
+
* - Still read from registry to determine what to display
|
|
293
|
+
* - Never call registry.replace or registry.commit after initial connection
|
|
294
|
+
*
|
|
295
|
+
* This prevents pb-view from resetting the registry with stale state during navigation,
|
|
296
|
+
* which can cause bounce-back issues when used with components like pb-tify.
|
|
297
|
+
*
|
|
298
|
+
* Default is false (pb-view can write to registry as before).
|
|
299
|
+
*/
|
|
300
|
+
readOnlyRegistry: {
|
|
301
|
+
type: Boolean,
|
|
302
|
+
attribute: 'read-only-registry',
|
|
303
|
+
},
|
|
304
|
+
/**
|
|
305
|
+
* If set to the name of an event, the content of the pb-view will not be replaced
|
|
306
|
+
* immediately upon updates. Instead, an event is emitted, which contains the new content
|
|
307
|
+
* in property `root`. An event handler intercepting the event can thus modify the content.
|
|
308
|
+
* Once it is done, it should pass the modified content to the callback function provided
|
|
309
|
+
* in the event detail under the name `render`. See the demo for an example.
|
|
310
|
+
*/
|
|
311
|
+
beforeUpdate: {
|
|
312
|
+
type: String,
|
|
313
|
+
attribute: 'before-update-event',
|
|
314
|
+
},
|
|
315
|
+
/**
|
|
316
|
+
* If set, do not scroll the view to target node (e.g. given in URL hash)
|
|
317
|
+
* after content was loaded.
|
|
318
|
+
*/
|
|
319
|
+
noScroll: {
|
|
320
|
+
type: Boolean,
|
|
321
|
+
attribute: 'no-scroll',
|
|
322
|
+
},
|
|
323
|
+
_features: {
|
|
324
|
+
type: Object,
|
|
325
|
+
},
|
|
326
|
+
_content: {
|
|
327
|
+
type: Node,
|
|
328
|
+
attribute: false,
|
|
329
|
+
},
|
|
330
|
+
_column1: {
|
|
331
|
+
type: Node,
|
|
332
|
+
attribute: false,
|
|
333
|
+
},
|
|
334
|
+
_column2: {
|
|
335
|
+
type: Node,
|
|
336
|
+
attribute: false,
|
|
337
|
+
},
|
|
338
|
+
_footnotes: {
|
|
339
|
+
type: Node,
|
|
340
|
+
attribute: false,
|
|
341
|
+
},
|
|
342
|
+
_style: {
|
|
343
|
+
type: Node,
|
|
344
|
+
attribute: false,
|
|
345
|
+
},
|
|
346
|
+
_additionalParams: {
|
|
347
|
+
type: Object,
|
|
348
|
+
},
|
|
349
|
+
...super.properties,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
constructor() {
|
|
354
|
+
super();
|
|
355
|
+
this.src = null;
|
|
356
|
+
this.url = null;
|
|
357
|
+
this.readOnlyRegistry = false; // Default: pb-view can write to registry
|
|
358
|
+
this._registryInitialized = false; // Track if registry has been initialized
|
|
359
|
+
this.onUpdate = false;
|
|
360
|
+
this.appendFootnotes = false;
|
|
361
|
+
this.notFound = null;
|
|
362
|
+
this.animation = false;
|
|
363
|
+
this.direction = 'ltr';
|
|
364
|
+
this.suppressHighlight = false;
|
|
365
|
+
this.highlight = false;
|
|
366
|
+
this.infiniteScrollMax = 10;
|
|
367
|
+
this.disableHistory = false;
|
|
368
|
+
this.beforeUpdate = null;
|
|
369
|
+
this.noScroll = false;
|
|
370
|
+
this._features = {};
|
|
371
|
+
this._additionalParams = {};
|
|
372
|
+
this._selector = {};
|
|
373
|
+
this._chunks = [];
|
|
374
|
+
this._scrollTarget = null;
|
|
375
|
+
this._loading = false;
|
|
376
|
+
this._lastRequestKey = null;
|
|
377
|
+
this.static = null;
|
|
378
|
+
// Debouncing for refresh calls (Option 6: Hybrid approach)
|
|
379
|
+
this._refreshDebounceTimer = null;
|
|
380
|
+
this._pendingRefreshEvent = null;
|
|
381
|
+
this._hasLoadedOnce = false; // Track if view has loaded at least once
|
|
382
|
+
this._lastLoadedId = null; // Track the last id that was actually loaded (for detecting navigation)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
386
|
+
super.attributeChangedCallback(name, oldVal, newVal);
|
|
387
|
+
switch (name) {
|
|
388
|
+
case 'src':
|
|
389
|
+
this._updateSource(newVal, oldVal);
|
|
390
|
+
break;
|
|
329
391
|
}
|
|
392
|
+
}
|
|
330
393
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
this.src = null;
|
|
334
|
-
this.url = null;
|
|
335
|
-
this.onUpdate = false;
|
|
336
|
-
this.appendFootnotes = false;
|
|
337
|
-
this.notFound = null;
|
|
338
|
-
this.animation = false;
|
|
339
|
-
this.direction = 'ltr';
|
|
340
|
-
this.suppressHighlight = false;
|
|
341
|
-
this.highlight = false;
|
|
342
|
-
this.infiniteScrollMax = 10;
|
|
343
|
-
this.disableHistory = false;
|
|
344
|
-
this.beforeUpdate = null;
|
|
345
|
-
this.noScroll = false;
|
|
346
|
-
this._features = {};
|
|
347
|
-
this._additionalParams = {};
|
|
348
|
-
this._selector = {};
|
|
349
|
-
this._chunks = [];
|
|
350
|
-
this._scrollTarget = null;
|
|
351
|
-
this.static = null;
|
|
352
|
-
}
|
|
394
|
+
connectedCallback() {
|
|
395
|
+
super.connectedCallback();
|
|
353
396
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
397
|
+
if (this.loadCss) {
|
|
398
|
+
waitOnce('pb-page-ready', () => {
|
|
399
|
+
loadStylesheets([this.toAbsoluteURL(this.loadCss)]).then(theme => {
|
|
400
|
+
if (theme) {
|
|
401
|
+
this.shadowRoot.adoptedStyleSheets = [...this.shadowRoot.adoptedStyleSheets, theme];
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
});
|
|
361
405
|
}
|
|
362
406
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
.then((theme) => {
|
|
370
|
-
this.shadowRoot.adoptedStyleSheets = [...this.shadowRoot.adoptedStyleSheets, theme];
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (this.infiniteScroll) {
|
|
376
|
-
this.columnSeparator = null;
|
|
377
|
-
this.animation = false;
|
|
378
|
-
this._content = document.createElement('div');
|
|
379
|
-
this._content.className = 'infinite-content';
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (!this.disableHistory) {
|
|
383
|
-
if (registry.state.id && !this.xmlId) {
|
|
384
|
-
this.xmlId = registry.state.id;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (registry.state.action && registry.state.action === 'search') {
|
|
388
|
-
this.highlight = true;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (this.view === 'single') {
|
|
392
|
-
this.nodeId = null;
|
|
393
|
-
} else if (registry.state.root && !this.nodeId) {
|
|
394
|
-
this.nodeId = registry.state.root;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const newState = {
|
|
398
|
-
id: this.xmlId,
|
|
399
|
-
view: this.getView(),
|
|
400
|
-
odd: this.getOdd(),
|
|
401
|
-
path: this.getDocument().path
|
|
402
|
-
};
|
|
403
|
-
if (this.view !== 'single') {
|
|
404
|
-
newState.root = this.nodeId;
|
|
405
|
-
}
|
|
406
|
-
console.log('id: %s; state: %o', this.id, newState);
|
|
407
|
-
registry.replace(this, newState);
|
|
408
|
-
|
|
409
|
-
registry.subscribe(this, (state) => {
|
|
410
|
-
this._setState(state);
|
|
411
|
-
this._refresh();
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
if (!this.waitFor) {
|
|
415
|
-
this.waitFor = 'pb-toggle-feature,pb-select-feature,pb-navigation';
|
|
416
|
-
}
|
|
407
|
+
if (this.infiniteScroll) {
|
|
408
|
+
this.columnSeparator = null;
|
|
409
|
+
this.animation = false;
|
|
410
|
+
this._content = document.createElement('div');
|
|
411
|
+
this._content.className = 'infinite-content';
|
|
412
|
+
}
|
|
417
413
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
this.
|
|
425
|
-
|
|
426
|
-
|
|
414
|
+
if (!this.disableHistory) {
|
|
415
|
+
if (registry.state.id && !this.xmlId) {
|
|
416
|
+
this.xmlId = registry.state.id;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (registry.state.action && registry.state.action === 'search') {
|
|
420
|
+
this.highlight = true;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (this.view === 'single') {
|
|
424
|
+
this.nodeId = null;
|
|
425
|
+
} else if (registry.state.root && !this.nodeId) {
|
|
426
|
+
this.nodeId = registry.state.root;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Only call registry.replace during initial connection, not during navigation
|
|
430
|
+
// If readOnlyRegistry is true, never call registry.replace (even during initial connection)
|
|
431
|
+
// This prevents pb-view from resetting registry with stale state during navigation
|
|
432
|
+
// Check both property (LitElement) and attribute (XML/XHTML compatibility)
|
|
433
|
+
const isReadOnly = this.readOnlyRegistry || this.hasAttribute('read-only-registry');
|
|
434
|
+
if (!this._registryInitialized && !isReadOnly) {
|
|
435
|
+
const _doc = this.getDocument ? this.getDocument() : null;
|
|
436
|
+
const newState = {
|
|
437
|
+
id: this.xmlId,
|
|
438
|
+
view: this.getView(),
|
|
439
|
+
odd: this.getOdd(),
|
|
440
|
+
path: _doc ? _doc.path : undefined,
|
|
441
|
+
};
|
|
442
|
+
if (this.view !== 'single') {
|
|
443
|
+
newState.root = this.nodeId;
|
|
444
|
+
}
|
|
445
|
+
if (this.fill) {
|
|
446
|
+
newState.fill = this.fill;
|
|
447
|
+
}
|
|
448
|
+
console.warn('[pb-view] connectedCallback: Calling registry.replace (read-only-registry not set)', {
|
|
449
|
+
readOnlyRegistry: this.readOnlyRegistry,
|
|
450
|
+
hasAttribute: this.hasAttribute('read-only-registry'),
|
|
451
|
+
isReadOnly,
|
|
452
|
+
_registryInitialized: this._registryInitialized,
|
|
453
|
+
newState
|
|
427
454
|
});
|
|
428
|
-
|
|
429
|
-
|
|
455
|
+
registry.replace(this, newState);
|
|
456
|
+
this._registryInitialized = true;
|
|
457
|
+
} else if (isReadOnly) {
|
|
458
|
+
console.log('[pb-view] connectedCallback: Skipping registry.replace (read-only-registry is set)', {
|
|
459
|
+
readOnlyRegistry: this.readOnlyRegistry,
|
|
460
|
+
hasAttribute: this.hasAttribute('read-only-registry'),
|
|
461
|
+
isReadOnly,
|
|
462
|
+
_registryInitialized: this._registryInitialized
|
|
430
463
|
});
|
|
431
|
-
|
|
432
|
-
const needsRefresh = this._features.language && this._features.language !== ev.detail.language;
|
|
433
|
-
this._features.language = ev.detail.language;
|
|
434
|
-
if (this.useLanguage && needsRefresh) {
|
|
435
|
-
this._setState(registry.getState(this));
|
|
436
|
-
this._refresh();
|
|
437
|
-
}
|
|
438
|
-
}, []);
|
|
439
|
-
|
|
440
|
-
this.signalReady();
|
|
464
|
+
}
|
|
441
465
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
466
|
+
registry.subscribe(this, state => {
|
|
467
|
+
this._setState(state);
|
|
468
|
+
// Use debounced refresh to batch rapid registry changes
|
|
469
|
+
this._refresh(null); // Pass null as event, registry state will be used
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
if (!this.waitFor) {
|
|
473
|
+
this.waitFor = 'pb-toggle-feature,pb-select-feature,pb-navigation';
|
|
447
474
|
}
|
|
448
475
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
476
|
+
this.subscribeTo('pb-navigate', ev => {
|
|
477
|
+
if (ev.detail.source && ev.detail.source === this) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
this.navigate(ev.detail.direction);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
this.subscribeTo('pb-toggle', ev => {
|
|
484
|
+
this.toggleFeature(ev);
|
|
485
|
+
});
|
|
486
|
+
this.subscribeTo(
|
|
487
|
+
'pb-i18n-update',
|
|
488
|
+
ev => {
|
|
489
|
+
const needsRefresh =
|
|
490
|
+
this._features.language && this._features.language !== ev.detail.language;
|
|
491
|
+
this._features.language = ev.detail.language;
|
|
492
|
+
if (this.useLanguage && needsRefresh) {
|
|
493
|
+
this._setState(registry.getState(this));
|
|
494
|
+
this._refresh();
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
[],
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
this.signalReady();
|
|
501
|
+
|
|
502
|
+
if (this.onUpdate) {
|
|
503
|
+
this.subscribeTo('pb-update', ev => {
|
|
504
|
+
this._refresh(ev);
|
|
505
|
+
});
|
|
454
506
|
}
|
|
507
|
+
}
|
|
455
508
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
509
|
+
disconnectedCallback() {
|
|
510
|
+
super.disconnectedCallback();
|
|
511
|
+
if (this._scrollObserver) {
|
|
512
|
+
this._scrollObserver.disconnect();
|
|
513
|
+
}
|
|
514
|
+
// Reset registry initialization flag when component is disconnected
|
|
515
|
+
// BUT: If read-only-registry is set, don't reset - we never want to call registry.replace
|
|
516
|
+
// This prevents pb-view from resetting registry when pb-grid rebuilds DOM
|
|
517
|
+
const isReadOnly = this.readOnlyRegistry || this.hasAttribute('read-only-registry');
|
|
518
|
+
if (!isReadOnly) {
|
|
519
|
+
this._registryInitialized = false;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
firstUpdated() {
|
|
524
|
+
super.firstUpdated();
|
|
525
|
+
this.enableScrollbar(true);
|
|
526
|
+
if (this.infiniteScroll) {
|
|
527
|
+
this._topObserver = this.shadowRoot.getElementById('top-observer');
|
|
528
|
+
this._bottomObserver = this.shadowRoot.getElementById('bottom-observer');
|
|
529
|
+
this._bottomObserver.style.display = 'none';
|
|
530
|
+
this._topObserver.style.display = 'none';
|
|
531
|
+
this._scrollObserver = new IntersectionObserver(entries => {
|
|
532
|
+
if (!this._content) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
entries.forEach(entry => {
|
|
536
|
+
if (entry.isIntersecting) {
|
|
537
|
+
if (entry.target.id === 'bottom-observer') {
|
|
538
|
+
const lastChild = this._content.lastElementChild;
|
|
539
|
+
if (lastChild) {
|
|
540
|
+
const next = lastChild.getAttribute('data-next');
|
|
541
|
+
if (next && !this._content.querySelector(`[data-root="${next}"]`)) {
|
|
542
|
+
this._checkChunks('forward');
|
|
543
|
+
this._load(next, 'forward');
|
|
467
544
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
this._checkChunks('forward');
|
|
477
|
-
this._load(next, 'forward');
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
} else {
|
|
481
|
-
const firstChild = this._content.firstElementChild;
|
|
482
|
-
if (firstChild) {
|
|
483
|
-
const previous = firstChild.getAttribute('data-previous');
|
|
484
|
-
if (previous && !this._content.querySelector(`[data-root="${previous}"]`)) {
|
|
485
|
-
this._checkChunks('backward');
|
|
486
|
-
this._load(previous, 'backward');
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
if (!this.onUpdate) {
|
|
495
|
-
waitOnce('pb-page-ready', (data) => {
|
|
496
|
-
if (data && data.language) {
|
|
497
|
-
this._features.language = data.language;
|
|
545
|
+
}
|
|
546
|
+
} else {
|
|
547
|
+
const firstChild = this._content.firstElementChild;
|
|
548
|
+
if (firstChild) {
|
|
549
|
+
const previous = firstChild.getAttribute('data-previous');
|
|
550
|
+
if (previous && !this._content.querySelector(`[data-root="${previous}"]`)) {
|
|
551
|
+
this._checkChunks('backward');
|
|
552
|
+
this._load(previous, 'backward');
|
|
498
553
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
});
|
|
505
|
-
});
|
|
506
|
-
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
});
|
|
507
559
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
560
|
+
if (!this.onUpdate) {
|
|
561
|
+
waitOnce('pb-page-ready', data => {
|
|
562
|
+
if (data && data.language) {
|
|
563
|
+
this._features.language = data.language;
|
|
564
|
+
}
|
|
565
|
+
this.wait(() => {
|
|
566
|
+
if (!this.disableHistory) {
|
|
567
|
+
this._setState(registry.state);
|
|
568
|
+
}
|
|
569
|
+
this._refresh();
|
|
570
|
+
});
|
|
571
|
+
});
|
|
516
572
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
573
|
+
// Subscribe to pb-refresh events
|
|
574
|
+
// Application code should specify subscribe="transcription" (or other channel) via HTML attribute
|
|
575
|
+
// If no subscribe attribute is set, uses defaultChannel (following pb-mixin pattern)
|
|
576
|
+
// This makes components generic and reusable - applications configure the channel
|
|
577
|
+
this.subscribeTo('pb-refresh', (ev) => {
|
|
578
|
+
this._refresh(ev);
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Returns the ODD used to render content.
|
|
584
|
+
*
|
|
585
|
+
* @returns the ODD being used
|
|
586
|
+
*/
|
|
587
|
+
getOdd() {
|
|
588
|
+
try {
|
|
589
|
+
return this.odd || this.getDocument().odd || 'teipublisher';
|
|
590
|
+
} catch {
|
|
591
|
+
return this.odd || 'teipublisher';
|
|
520
592
|
}
|
|
593
|
+
}
|
|
521
594
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
595
|
+
getView() {
|
|
596
|
+
try {
|
|
597
|
+
return this.view || this.getDocument().view || 'single';
|
|
598
|
+
} catch {
|
|
599
|
+
return this.view || 'single';
|
|
528
600
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Trigger an update of this element's content
|
|
605
|
+
*/
|
|
606
|
+
forceUpdate() {
|
|
607
|
+
this._load(this.nodeId);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
animate() {
|
|
611
|
+
// animate new element if 'animation' property is 'true'
|
|
612
|
+
if (this.animation) {
|
|
613
|
+
if (this.lastDirection === 'forward') {
|
|
614
|
+
animate(this.shadowRoot.getElementById('view'), {
|
|
615
|
+
opacity: [0, 1],
|
|
616
|
+
translateX: [1000, 0],
|
|
617
|
+
duration: 300,
|
|
618
|
+
ease: 'linear',
|
|
619
|
+
});
|
|
620
|
+
} else {
|
|
621
|
+
animate(this.shadowRoot.getElementById('view'), {
|
|
622
|
+
opacity: [0, 1],
|
|
623
|
+
translateX: [-1000, 0],
|
|
624
|
+
duration: 300,
|
|
625
|
+
ease: 'linear',
|
|
626
|
+
});
|
|
627
|
+
}
|
|
551
628
|
}
|
|
629
|
+
}
|
|
552
630
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
631
|
+
enableScrollbar(enable) {
|
|
632
|
+
if (enable) {
|
|
633
|
+
this.classList.add('noscroll');
|
|
634
|
+
} else {
|
|
635
|
+
this.classList.remove('noscroll');
|
|
559
636
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
if (ev.detail.path) {
|
|
573
|
-
const doc = this.getDocument();
|
|
574
|
-
doc.path = ev.detail.path;
|
|
575
|
-
}
|
|
576
|
-
if (ev.detail.id) {
|
|
577
|
-
this.xmlId = ev.detail.id;
|
|
578
|
-
} else if (ev.detail.id == null) {
|
|
579
|
-
this.xmlId = null;
|
|
580
|
-
}
|
|
581
|
-
this.odd = ev.detail.odd || this.odd;
|
|
582
|
-
if (ev.detail.columnSeparator !== undefined) {
|
|
583
|
-
this.columnSeparator = ev.detail.columnSeparator;
|
|
584
|
-
}
|
|
585
|
-
this.view = ev.detail.view || this.getView();
|
|
586
|
-
if (ev.detail.xpath) {
|
|
587
|
-
this.xpath = ev.detail.xpath;
|
|
588
|
-
this.nodeId = null;
|
|
589
|
-
}
|
|
590
|
-
// clear nodeId if set to null
|
|
591
|
-
if (ev.detail.root === null) {
|
|
592
|
-
this.nodeId = null;
|
|
593
|
-
} else {
|
|
594
|
-
this.nodeId = (ev.detail.position !== undefined ? ev.detail.position : ev.detail.root) || this.nodeId;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// check if the URL template needs any other parameters
|
|
598
|
-
// and set them on this._additionalParams
|
|
599
|
-
registry.pathParams.forEach((key) => {
|
|
600
|
-
this._additionalParams[key] = ev.detail[key];
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
if (!this.noScroll) {
|
|
604
|
-
this._scrollTarget = ev.detail.hash;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
this._updateStyles();
|
|
608
|
-
if (this.infiniteScroll) {
|
|
609
|
-
this._clear();
|
|
610
|
-
}
|
|
611
|
-
this._load(this.nodeId);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
_refresh(ev) {
|
|
640
|
+
// Store the pending event for debouncing
|
|
641
|
+
this._pendingRefreshEvent = ev;
|
|
642
|
+
|
|
643
|
+
// Clear existing debounce timer
|
|
644
|
+
if (this._refreshDebounceTimer) {
|
|
645
|
+
clearTimeout(this._refreshDebounceTimer);
|
|
612
646
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
647
|
+
|
|
648
|
+
// Debounce: wait 150ms to batch rapid changes
|
|
649
|
+
this._refreshDebounceTimer = setTimeout(() => {
|
|
650
|
+
this._doRefresh(this._pendingRefreshEvent);
|
|
651
|
+
this._pendingRefreshEvent = null;
|
|
652
|
+
this._refreshDebounceTimer = null;
|
|
653
|
+
}, 150);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
_doRefresh(ev) {
|
|
657
|
+
// Merge registry state with event details, prioritizing registry state
|
|
658
|
+
const registryState = registry.getState(this);
|
|
659
|
+
const eventDetail = ev && ev.detail ? ev.detail : {};
|
|
660
|
+
|
|
661
|
+
// Priority: registry state > event detail > current values
|
|
662
|
+
const mergedState = {
|
|
663
|
+
...eventDetail,
|
|
664
|
+
...registryState, // Registry state overrides event detail
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
// Check if this is a metadata panel BEFORE processing any state changes
|
|
668
|
+
// Check pb-param elements for mode parameter
|
|
669
|
+
let modeParam = null;
|
|
670
|
+
const modeParamEl = this.querySelector && this.querySelector('pb-param[name="mode"]');
|
|
671
|
+
if (modeParamEl) {
|
|
672
|
+
modeParam = modeParamEl.getAttribute('value');
|
|
673
|
+
}
|
|
674
|
+
// Also check _additionalParams (might be set from registry or events)
|
|
675
|
+
if (!modeParam && this._additionalParams) {
|
|
676
|
+
modeParam = this._additionalParams.mode || this._additionalParams['user.mode'];
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const isMetadataPanel = modeParam === 'metadata-panel' ||
|
|
680
|
+
(this.xpath && this.view === 'single' && !this.nodeId);
|
|
681
|
+
|
|
682
|
+
// Skip refresh if this view has xpath (e.g., metadata panel) - it shouldn't react to page navigation
|
|
683
|
+
// BUT allow initial load (when _hasLoadedOnce is false)
|
|
684
|
+
if (this.xpath && !mergedState.xpath && this._hasLoadedOnce) {
|
|
685
|
+
// This view is bound to a specific xpath (like metadata), don't change it after initial load
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Skip refresh for metadata panel - it shouldn't react to page navigation
|
|
690
|
+
// BUT allow initial load (when _hasLoadedOnce is false)
|
|
691
|
+
if (isMetadataPanel && this._hasLoadedOnce) {
|
|
692
|
+
// Metadata panel shouldn't react to page navigation events (root/id changes)
|
|
693
|
+
// Only allow updates if explicitly changing xpath or view mode
|
|
694
|
+
// Filter out canvas IDs before checking
|
|
695
|
+
const hasCanvasId = mergedState.id && /\.jpg$|_\d{2,3}\.jpg/.test(String(mergedState.id));
|
|
696
|
+
if (mergedState.root || (mergedState.id && !hasCanvasId) || mergedState.position) {
|
|
697
|
+
// This is a page navigation event - skip it for metadata panel
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// For metadata panel, filter out canvas IDs from merged state
|
|
703
|
+
// Canvas IDs (like "A-N-38_004.jpg") should not affect metadata panel content
|
|
704
|
+
// Create a filtered copy if needed
|
|
705
|
+
let filteredState = mergedState;
|
|
706
|
+
if (isMetadataPanel && mergedState.id) {
|
|
707
|
+
const looksLikeCanvasId = /\.jpg$|_\d{2,3}\.jpg/.test(String(mergedState.id));
|
|
708
|
+
if (looksLikeCanvasId) {
|
|
709
|
+
// Remove canvas ID from merged state for metadata panel
|
|
710
|
+
filteredState = { ...mergedState };
|
|
711
|
+
delete filteredState.id;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Use filteredState for the rest of the function (filteredState === mergedState if no filtering was needed)
|
|
716
|
+
const stateToUse = filteredState;
|
|
717
|
+
|
|
718
|
+
// Handle hash-only changes (scroll without reload)
|
|
719
|
+
if (stateToUse.hash &&
|
|
720
|
+
!this.noScroll &&
|
|
721
|
+
!(stateToUse.id || stateToUse.path || stateToUse.odd || stateToUse.view || stateToUse.position)) {
|
|
722
|
+
this._scrollTarget = stateToUse.hash;
|
|
723
|
+
const target = this.shadowRoot.getElementById(this._scrollTarget);
|
|
724
|
+
if (target) {
|
|
725
|
+
setTimeout(() => target.scrollIntoView({ block: 'nearest' }));
|
|
726
|
+
}
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Store old values BEFORE updating - critical for change detection
|
|
731
|
+
// IMPORTANT: For _refresh(null) case where _setState was called first,
|
|
732
|
+
// _setState may have already updated this.xmlId, so oldXmlId might be the new value.
|
|
733
|
+
// We'll compare against _lastLoadedId (what was actually loaded) to detect changes.
|
|
734
|
+
const oldXmlId = this.xmlId;
|
|
735
|
+
const oldNodeId = this.nodeId;
|
|
736
|
+
const oldPath = this.getDocument ? (this.getDocument()?.path || null) : null;
|
|
737
|
+
// For _refresh(null) case, also check if _setState set an ID in _features or _additionalParams
|
|
738
|
+
// that's different from what was loaded
|
|
739
|
+
// CRITICAL: Check these BEFORE we process the ID, because processing might update them
|
|
740
|
+
const additionalParamsIdBefore = this._additionalParams && this._additionalParams.id ? this._additionalParams.id : null;
|
|
741
|
+
const featuresIdBefore = this._features && this._features.id ? this._features.id : null;
|
|
742
|
+
const lastLoadedIdForComparison = this._lastLoadedId; // What was actually loaded via API
|
|
743
|
+
|
|
744
|
+
// For _refresh(null) case: if _setState set an ID that's different from what was loaded,
|
|
745
|
+
// we should reload even if oldXmlId === newId (because _setState already updated this.xmlId)
|
|
746
|
+
const setIdChanged = (additionalParamsIdBefore && additionalParamsIdBefore !== lastLoadedIdForComparison && lastLoadedIdForComparison !== null) ||
|
|
747
|
+
(featuresIdBefore && featuresIdBefore !== lastLoadedIdForComparison && lastLoadedIdForComparison !== null);
|
|
748
|
+
|
|
749
|
+
// Apply merged state
|
|
750
|
+
// CRITICAL: Event detail takes precedence over registry state for path/odd changes
|
|
751
|
+
// This is important because events explicitly request path/odd changes, while registry might have old values
|
|
752
|
+
const eventPath = ev && ev.detail && ev.detail.path ? ev.detail.path : null;
|
|
753
|
+
const eventOdd = ev && ev.detail && ev.detail.odd ? ev.detail.odd : null;
|
|
754
|
+
// Use event path/odd if present, otherwise use stateToUse (registry state)
|
|
755
|
+
const pathToApply = eventPath || stateToUse.path;
|
|
756
|
+
const oddToApply = eventOdd || stateToUse.odd;
|
|
757
|
+
|
|
758
|
+
if (pathToApply) {
|
|
759
|
+
const doc = this.getDocument();
|
|
760
|
+
if (doc) {
|
|
761
|
+
doc.path = pathToApply;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Store for later use in pathChanged/oddChanged checks
|
|
766
|
+
const newPath = pathToApply;
|
|
767
|
+
const newOdd = oddToApply;
|
|
768
|
+
|
|
769
|
+
// Handle id parameter - distinguish between XML IDs and canvas IDs
|
|
770
|
+
// Canvas IDs (from pb-tify) have patterns like "A-N-38_004.jpg" or end with ".jpg"
|
|
771
|
+
// XML IDs are actual xml:id attributes in the TEI document
|
|
772
|
+
// CRITICAL: Use ev.detail.id directly to avoid issues with filtered/overridden state
|
|
773
|
+
// Also check _additionalParams and _features for ID (set via _setState when ev is null)
|
|
774
|
+
const eventId = ev && ev.detail && ev.detail.id ? ev.detail.id : null;
|
|
775
|
+
const additionalParamsId = this._additionalParams && this._additionalParams.id ? this._additionalParams.id : null;
|
|
776
|
+
const featuresId = this._features && this._features.id ? this._features.id : null;
|
|
777
|
+
// For _refresh(null) case, also check if _setState was called with id
|
|
778
|
+
// _setState stores id in _features if not in pathParams, or _additionalParams if in pathParams
|
|
779
|
+
const idToProcess = eventId || (stateToUse.id !== undefined ? stateToUse.id : null) || additionalParamsId || featuresId;
|
|
780
|
+
|
|
781
|
+
if (idToProcess) {
|
|
782
|
+
const looksLikeCanvasId = /\.jpg$|_\d{2,3}\.jpg/.test(String(idToProcess));
|
|
783
|
+
if (!looksLikeCanvasId) {
|
|
784
|
+
// It's a real XML ID, use it
|
|
785
|
+
this.xmlId = idToProcess;
|
|
786
|
+
} else {
|
|
787
|
+
// If it's a canvas ID, store it in _additionalParams so getParameters can use it
|
|
788
|
+
// This ensures the request includes the correct canvas ID even though we don't set xmlId
|
|
789
|
+
this._additionalParams = this._additionalParams || {};
|
|
790
|
+
this._additionalParams.id = idToProcess;
|
|
791
|
+
}
|
|
792
|
+
// If it's a canvas ID, don't set xmlId - use root/position for navigation instead
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Use event odd if present, otherwise use stateToUse.odd, otherwise keep current
|
|
796
|
+
this.odd = oddToApply || this.odd;
|
|
797
|
+
|
|
798
|
+
if (stateToUse.columnSeparator !== undefined) {
|
|
799
|
+
this.columnSeparator = stateToUse.columnSeparator;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
this.view = stateToUse.view || this.getView();
|
|
803
|
+
this.fill = stateToUse.fill || this.fill;
|
|
804
|
+
|
|
805
|
+
if (stateToUse.xpath) {
|
|
806
|
+
this.xpath = stateToUse.xpath;
|
|
807
|
+
this.nodeId = null;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Handle root/position (prioritize registry state)
|
|
811
|
+
|
|
812
|
+
let newNodeId = this.nodeId;
|
|
813
|
+
if (stateToUse.root === null) {
|
|
814
|
+
newNodeId = null;
|
|
815
|
+
} else {
|
|
816
|
+
newNodeId = (stateToUse.position !== undefined ? stateToUse.position : stateToUse.root) || this.nodeId;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Check if nodeId actually changed - if not, skip loading to avoid unnecessary requests
|
|
820
|
+
const nodeIdChanged = newNodeId !== oldNodeId;
|
|
821
|
+
|
|
822
|
+
// Also check if id changed - even if nodeId (root) is the same, id change means different page
|
|
823
|
+
// Use ev.detail.id directly (from the event) to avoid issues with filtered/overridden state
|
|
824
|
+
// Fall back to stateToUse.id, _additionalParams.id, _features.id, or this.xmlId if event doesn't have id
|
|
825
|
+
// CRITICAL: For _refresh(null) case, check if _setState set an ID that's different from oldXmlId
|
|
826
|
+
const eventIdForChangeCheck = ev && ev.detail && ev.detail.id ? ev.detail.id : null;
|
|
827
|
+
const additionalParamsIdForCheck = this._additionalParams && this._additionalParams.id ? this._additionalParams.id : null;
|
|
828
|
+
const featuresIdForCheck = this._features && this._features.id ? this._features.id : null;
|
|
829
|
+
const newId = eventIdForChangeCheck || (stateToUse.id !== undefined ? stateToUse.id : null) || additionalParamsIdForCheck || featuresIdForCheck || this.xmlId;
|
|
830
|
+
// Check if ID changed: compare against oldXmlId OR lastLoadedIdForComparison (what was actually loaded)
|
|
831
|
+
// For _refresh(null) case where _setState was called first, _setState already updated this.xmlId,
|
|
832
|
+
// so oldXmlId might be the new value. Compare against lastLoadedIdForComparison to detect changes.
|
|
833
|
+
// Also check if _setState set an ID in _features/_additionalParams that's different from what was loaded
|
|
834
|
+
// Check if ID changed: compare against oldXmlId OR lastLoadedIdForComparison (what was actually loaded)
|
|
835
|
+
// For _refresh(null) case where _setState was called first, _setState already updated this.xmlId,
|
|
836
|
+
// so oldXmlId might be the new value. Compare against lastLoadedIdForComparison to detect changes.
|
|
837
|
+
// Also check if _setState set an ID in _features/_additionalParams that's different from what was loaded
|
|
838
|
+
// CRITICAL: If _setState set a new ID (setIdChanged is true), always reload regardless of other conditions
|
|
839
|
+
// Also reload if we have a new ID but _lastLoadedId is null (first load or ID was set externally)
|
|
840
|
+
const idChanged = newId && (
|
|
841
|
+
newId !== oldXmlId ||
|
|
842
|
+
(newId !== lastLoadedIdForComparison && lastLoadedIdForComparison !== null) ||
|
|
843
|
+
setIdChanged || // _setState set a new ID - always reload
|
|
844
|
+
(lastLoadedIdForComparison === null && newId) // First load with ID, or ID set externally
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
// Check if path changed - path change means different document, should always reload
|
|
848
|
+
// oldPath was calculated above before updating doc.path
|
|
849
|
+
// newPath was calculated above (event path takes precedence over registry state)
|
|
850
|
+
const pathChanged = newPath && newPath !== oldPath;
|
|
851
|
+
|
|
852
|
+
// Check if odd changed - odd change means different document format, should reload
|
|
853
|
+
// newOdd was calculated above (event odd takes precedence over registry state)
|
|
854
|
+
const oldOdd = this.odd;
|
|
855
|
+
const oddChanged = newOdd && newOdd !== oldOdd;
|
|
856
|
+
|
|
857
|
+
// CRITICAL: If we received a pb-refresh event, check if it's for a different page
|
|
858
|
+
// This ensures we reload when navigation occurs, even if registry already updated our state
|
|
859
|
+
const eventHasId = ev && ev.detail && ev.detail.id;
|
|
860
|
+
// _lastLoadedId tracks what we actually loaded via API, not what registry set
|
|
861
|
+
// If null, we haven't loaded anything yet, so we should load
|
|
862
|
+
const lastLoadedId = this._lastLoadedId;
|
|
863
|
+
// If we received an event with an id different from what we last loaded, reload
|
|
864
|
+
// OR if we haven't loaded anything yet (_lastLoadedId is null), reload
|
|
865
|
+
// Also check against oldXmlId as fallback (in case _lastLoadedId wasn't set)
|
|
866
|
+
const shouldReloadFromEvent = eventHasId && (
|
|
867
|
+
lastLoadedId === null ||
|
|
868
|
+
ev.detail.id !== lastLoadedId ||
|
|
869
|
+
(oldXmlId && ev.detail.id !== oldXmlId)
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
const willSkip = !nodeIdChanged && !idChanged && !pathChanged && !oddChanged && !shouldReloadFromEvent && this._hasLoadedOnce;
|
|
873
|
+
|
|
874
|
+
if (willSkip) {
|
|
875
|
+
// Neither nodeId nor id changed and event id hasn't changed - skip loading
|
|
876
|
+
// This prevents unnecessary /api/parts/ requests when navigation doesn't actually change the content
|
|
877
|
+
|
|
878
|
+
// Still update other properties that might have changed
|
|
879
|
+
registry.pathParams.forEach(key => {
|
|
880
|
+
if (stateToUse[key] !== undefined) {
|
|
881
|
+
this._additionalParams[key] = stateToUse[key];
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
// Update xmlId if it changed (even if we're not reloading)
|
|
886
|
+
// this.xmlId was already updated above if stateToUse.id was set
|
|
887
|
+
|
|
888
|
+
if (!this.noScroll) {
|
|
889
|
+
this._scrollTarget = stateToUse.hash;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
this._updateStyles();
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// NodeId changed or id changed or first load - update and load
|
|
897
|
+
this.nodeId = newNodeId;
|
|
898
|
+
|
|
899
|
+
// xmlId was already updated above if stateToUse.id was set and not a canvas ID
|
|
900
|
+
|
|
901
|
+
// Track the id we're about to load so we can detect if a pb-refresh event
|
|
902
|
+
// is for a different page than what we last loaded
|
|
903
|
+
const idToLoad = mergedState.id || this.xmlId;
|
|
904
|
+
|
|
905
|
+
// Check if the URL template needs any other parameters
|
|
906
|
+
// For metadata panel, canvas IDs are already filtered out in filteredState
|
|
907
|
+
registry.pathParams.forEach(key => {
|
|
908
|
+
if (stateToUse[key] !== undefined) {
|
|
909
|
+
this._additionalParams[key] = stateToUse[key];
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
if (!this.noScroll) {
|
|
914
|
+
this._scrollTarget = stateToUse.hash;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
this._updateStyles();
|
|
918
|
+
if (this.infiniteScroll) {
|
|
919
|
+
this._clear();
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
this._load(this.nodeId);
|
|
923
|
+
|
|
924
|
+
// Don't update _lastLoadedId here - only update it in _handleContent after content is actually loaded
|
|
925
|
+
// This ensures _lastLoadedId only tracks what was actually loaded, not what we're trying to load
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
_load(pos, direction) {
|
|
929
|
+
const doc = this.getDocument ? this.getDocument() : null;
|
|
930
|
+
|
|
931
|
+
// In smoke/CT, pb-view may be mounted without a pb-document; bail safely
|
|
932
|
+
if (!doc || !doc.path) {
|
|
933
|
+
console.warn('<pb-view> No path');
|
|
934
|
+
return;
|
|
633
935
|
}
|
|
634
936
|
|
|
635
|
-
|
|
636
|
-
|
|
937
|
+
if (this._loading && this._lastRequestKey) {
|
|
938
|
+
const testParams = this.getParameters(pos);
|
|
939
|
+
const testReqKey = JSON.stringify({ url: this.url || '', doc: doc.path, params: testParams });
|
|
940
|
+
if (this._lastRequestKey === testReqKey) {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
// Cancel the pending request
|
|
944
|
+
const loader = this.shadowRoot.getElementById('loadContent');
|
|
945
|
+
if (loader) {
|
|
946
|
+
loader.abort();
|
|
947
|
+
}
|
|
948
|
+
// Clear the old request key so new requests aren't blocked
|
|
949
|
+
this._lastRequestKey = null;
|
|
950
|
+
this._loading = false;
|
|
951
|
+
}
|
|
952
|
+
this._loading = true;
|
|
637
953
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
954
|
+
const params = this.getParameters(pos);
|
|
955
|
+
if (direction) {
|
|
956
|
+
params._dir = direction;
|
|
957
|
+
}
|
|
642
958
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
959
|
+
this._doLoad(params);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
_doLoad(params) {
|
|
963
|
+
// De-duplicate identical requests to avoid hammering the backend
|
|
964
|
+
const docPath = this.getDocument && this.getDocument() ? this.getDocument().path : '';
|
|
965
|
+
const reqKey = JSON.stringify({ url: this.url || '', doc: docPath, params });
|
|
966
|
+
if (this._lastRequestKey === reqKey) {
|
|
967
|
+
// nothing changed; unlock and skip
|
|
968
|
+
this._loading = false;
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
this._lastRequestKey = reqKey;
|
|
651
972
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (this.static !== null) {
|
|
655
|
-
this._staticUrl(params).then((url) => {
|
|
656
|
-
loadContent.url = url;
|
|
657
|
-
loadContent.generateRequest();
|
|
658
|
-
});
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
973
|
+
this.emitTo('pb-start-update', params);
|
|
661
974
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
} else {
|
|
666
|
-
this.url = "modules/lib/components.xql";
|
|
667
|
-
}
|
|
668
|
-
}
|
|
975
|
+
if (!this.infiniteScroll) {
|
|
976
|
+
this._clear();
|
|
977
|
+
}
|
|
669
978
|
|
|
670
|
-
|
|
979
|
+
if (this._scrollObserver) {
|
|
980
|
+
if (this._bottomObserver) {
|
|
981
|
+
this._scrollObserver.unobserve(this._bottomObserver);
|
|
982
|
+
}
|
|
983
|
+
if (this._topObserver) {
|
|
984
|
+
this._scrollObserver.unobserve(this._topObserver);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
671
987
|
|
|
672
|
-
|
|
673
|
-
url += `/${encodeURIComponent(this.getDocument().path)}/json`;
|
|
674
|
-
}
|
|
988
|
+
const loadContent = this.shadowRoot.getElementById('loadContent');
|
|
675
989
|
|
|
990
|
+
if (this.static !== null) {
|
|
991
|
+
this._staticUrl(params).then(url => {
|
|
676
992
|
loadContent.url = url;
|
|
677
|
-
loadContent.
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
* Use a static URL to load pre-generated content.
|
|
683
|
-
*/
|
|
684
|
-
async _staticUrl(params) {
|
|
685
|
-
function createKey(paramNames) {
|
|
686
|
-
const urlComponents = [];
|
|
687
|
-
paramNames.sort().forEach(key => {
|
|
688
|
-
if (params.hasOwnProperty(key)) {
|
|
689
|
-
urlComponents.push(`${key}=${params[key]}`);
|
|
690
|
-
}
|
|
691
|
-
});
|
|
692
|
-
return urlComponents.join('&');
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
const index = await fetch(`index.json`)
|
|
696
|
-
.then((response) => response.json());
|
|
697
|
-
const paramNames = ['odd', 'view', 'xpath', 'map'];
|
|
698
|
-
this.querySelectorAll('pb-param').forEach((param) => paramNames.push(`user.${param.getAttribute('name')}`));
|
|
699
|
-
let url = params.id ? createKey([...paramNames, 'id']) : createKey([...paramNames, 'root']);
|
|
700
|
-
let file = index[url];
|
|
701
|
-
if (!file) {
|
|
702
|
-
url = createKey(paramNames);
|
|
703
|
-
file = index[url];
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
console.log('<pb-view> Static lookup %s: %s', url, file);
|
|
707
|
-
return `${file}`;
|
|
993
|
+
loadContent.generateRequest().catch(() => {
|
|
994
|
+
// Error handled by @error event listener
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
return;
|
|
708
998
|
}
|
|
709
999
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
this._column1 = null;
|
|
718
|
-
this._column2 = null;
|
|
719
|
-
this._footnotes = null;
|
|
720
|
-
this._chunks = [];
|
|
1000
|
+
if (!this.url) {
|
|
1001
|
+
if (this.minApiVersion('1.0.0')) {
|
|
1002
|
+
this.url = 'api/parts';
|
|
1003
|
+
} else {
|
|
1004
|
+
this.url = 'modules/lib/components.xql';
|
|
1005
|
+
}
|
|
721
1006
|
}
|
|
722
1007
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
if (this.notFound != null) {
|
|
737
|
-
content = `<p>${this.notFound}</p>`;
|
|
738
|
-
} else {
|
|
739
|
-
content = `<p><pb-i18n key="dialogs.serverError"></pb-i18n>: ${message} </p>`;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
this._replaceContent({ content });
|
|
743
|
-
this.emitTo('pb-end-update');
|
|
1008
|
+
let url = `${this.getEndpoint()}/${this.url}`;
|
|
1009
|
+
|
|
1010
|
+
if (this.minApiVersion('1.0.0')) {
|
|
1011
|
+
// Encode the entire path as a single unit for the API
|
|
1012
|
+
const doc = this.getDocument();
|
|
1013
|
+
if (doc && doc.path) {
|
|
1014
|
+
const docPath = encodeURIComponent(doc.path);
|
|
1015
|
+
url += `/${docPath}/json`;
|
|
1016
|
+
} else {
|
|
1017
|
+
console.warn('<pb-view> No document path available for URL construction');
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
744
1021
|
|
|
1022
|
+
loadContent.url = url;
|
|
1023
|
+
loadContent.params = params;
|
|
1024
|
+
loadContent.generateRequest().catch((error) => {
|
|
1025
|
+
console.error('[pb-view] _doLoad: request failed', error);
|
|
1026
|
+
// Error handled by @error event listener
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Use a static URL to load pre-generated content.
|
|
1032
|
+
*/
|
|
1033
|
+
async _staticUrl(params) {
|
|
1034
|
+
function createKey(paramNames) {
|
|
1035
|
+
const urlComponents = [];
|
|
1036
|
+
paramNames.sort().forEach(key => {
|
|
1037
|
+
if (params.hasOwnProperty(key)) {
|
|
1038
|
+
urlComponents.push(`${key}=${params[key]}`);
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
return urlComponents.join('&');
|
|
745
1042
|
}
|
|
746
1043
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1044
|
+
const baseDir = this.static ? this.static.replace(/\/$/, '') : '.';
|
|
1045
|
+
const baseUrl = new URL(`${baseDir}/`, window.location.href);
|
|
1046
|
+
const indexUrl = new URL('index.json', baseUrl).href;
|
|
1047
|
+
const index = await fetch(indexUrl).then(response => response.json());
|
|
1048
|
+
const paramNames = ['odd', 'view', 'xpath', 'map'];
|
|
1049
|
+
this.querySelectorAll('pb-param').forEach(param =>
|
|
1050
|
+
paramNames.push(`user.${param.getAttribute('name')}`),
|
|
1051
|
+
);
|
|
1052
|
+
let url = params.id ? createKey([...paramNames, 'id']) : createKey([...paramNames, 'root']);
|
|
1053
|
+
let file = index[url];
|
|
1054
|
+
if (!file) {
|
|
1055
|
+
url = createKey(paramNames);
|
|
1056
|
+
file = index[url];
|
|
1057
|
+
}
|
|
750
1058
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1059
|
+
if (!file) {
|
|
1060
|
+
console.warn('<pb-view> No static mapping found for %s', url);
|
|
1061
|
+
const fallback = Object.values(index)[0];
|
|
1062
|
+
if (!fallback) {
|
|
1063
|
+
return baseUrl.href;
|
|
1064
|
+
}
|
|
1065
|
+
file = fallback;
|
|
1066
|
+
}
|
|
1067
|
+
return new URL(file, baseUrl).href;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
_clear() {
|
|
1071
|
+
if (this.infiniteScroll) {
|
|
1072
|
+
this._content = document.createElement('div');
|
|
1073
|
+
this._content.className = 'infinite-content';
|
|
1074
|
+
} else {
|
|
1075
|
+
this._content = null;
|
|
1076
|
+
}
|
|
1077
|
+
this._column1 = null;
|
|
1078
|
+
this._column2 = null;
|
|
1079
|
+
this._footnotes = null;
|
|
1080
|
+
this._chunks = [];
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
_handleError() {
|
|
1084
|
+
this._clear();
|
|
1085
|
+
this._loading = false;
|
|
1086
|
+
const loader = this.shadowRoot.getElementById('loadContent');
|
|
1087
|
+
console.error('<pb-view> Error details:', loader.lastError);
|
|
1088
|
+
let message;
|
|
1089
|
+
const { response } = loader.lastError;
|
|
1090
|
+
|
|
1091
|
+
if (response) {
|
|
1092
|
+
message = response.description;
|
|
1093
|
+
} else {
|
|
1094
|
+
message = '<pb-i18n key="dialogs.serverError"></pb-i18n>';
|
|
1095
|
+
}
|
|
762
1096
|
|
|
763
|
-
|
|
1097
|
+
let content;
|
|
1098
|
+
if (this.notFound != null) {
|
|
1099
|
+
content = `<p>${this.notFound}</p>`;
|
|
1100
|
+
} else {
|
|
1101
|
+
content = `<p><pb-i18n key="dialogs.serverError"></pb-i18n>: ${message} </p>`;
|
|
1102
|
+
}
|
|
764
1103
|
|
|
765
|
-
|
|
1104
|
+
this._replaceContent({ content });
|
|
1105
|
+
this.emitTo('pb-end-update');
|
|
1106
|
+
}
|
|
766
1107
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
this.shadowRoot.querySelector(`[node-id="${this._scrollTarget}"]`);
|
|
771
|
-
if (target) {
|
|
772
|
-
window.requestAnimationFrame(() =>
|
|
773
|
-
setTimeout(() => {
|
|
774
|
-
target.scrollIntoView({block: 'nearest'});
|
|
775
|
-
}, 400)
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
this._scrollTarget = null;
|
|
779
|
-
});
|
|
780
|
-
}
|
|
1108
|
+
_handleContent() {
|
|
1109
|
+
const loader = this.shadowRoot.getElementById('loadContent');
|
|
1110
|
+
const resp = loader.lastResponse;
|
|
781
1111
|
|
|
782
|
-
this.next = resp.next;
|
|
783
|
-
this.nextId = resp.nextId;
|
|
784
|
-
this.previous = resp.previous;
|
|
785
|
-
this.previousId = resp.previousId;
|
|
786
|
-
this.nodeId = resp.root;
|
|
787
|
-
this.switchView = resp.switchView;
|
|
788
|
-
|
|
789
|
-
this.updateComplete.then(() => {
|
|
790
|
-
const view = this.shadowRoot.getElementById('view');
|
|
791
|
-
this._applyToggles(view);
|
|
792
|
-
this._fixLinks(view);
|
|
793
|
-
typesetMath(view);
|
|
794
|
-
|
|
795
|
-
const eventOptions = {
|
|
796
|
-
data: resp,
|
|
797
|
-
root: view,
|
|
798
|
-
params: loader.params,
|
|
799
|
-
id: this.xmlId,
|
|
800
|
-
position: this.nodeId
|
|
801
|
-
};
|
|
802
|
-
this.emitTo('pb-update', eventOptions);
|
|
803
|
-
this._scroll();
|
|
804
|
-
});
|
|
805
1112
|
|
|
806
|
-
|
|
1113
|
+
if (!resp) {
|
|
1114
|
+
this._loading = false;
|
|
1115
|
+
console.error('<pb-view> No response received');
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
if (resp.error) {
|
|
1119
|
+
console.error('<pb-view> Response has error:', resp.error);
|
|
1120
|
+
if (this.notFound != null) {
|
|
1121
|
+
this._content = this.notFound;
|
|
1122
|
+
}
|
|
1123
|
+
this.emitTo('pb-end-update', null);
|
|
1124
|
+
this._loading = false;
|
|
1125
|
+
return;
|
|
807
1126
|
}
|
|
808
1127
|
|
|
809
|
-
_replaceContent(resp,
|
|
810
|
-
const fragment = document.createDocumentFragment();
|
|
811
|
-
const elem = document.createElement('div');
|
|
812
|
-
// elem.style.opacity = 0; //hide it - animation has to make sure to blend it in
|
|
813
|
-
fragment.appendChild(elem);
|
|
814
|
-
elem.innerHTML = resp.content;
|
|
1128
|
+
this._replaceContent(resp, loader.params._dir);
|
|
815
1129
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1130
|
+
this.animate();
|
|
1131
|
+
|
|
1132
|
+
if (this._scrollTarget) {
|
|
1133
|
+
this.updateComplete.then(() => {
|
|
1134
|
+
const target =
|
|
1135
|
+
this.shadowRoot.getElementById(this._scrollTarget) ||
|
|
1136
|
+
this.shadowRoot.querySelector(`[node-id="${this._scrollTarget}"]`);
|
|
1137
|
+
if (target) {
|
|
1138
|
+
window.requestAnimationFrame(() =>
|
|
1139
|
+
setTimeout(() => {
|
|
1140
|
+
target.scrollIntoView({ block: 'nearest' });
|
|
1141
|
+
}, 400),
|
|
1142
|
+
);
|
|
828
1143
|
}
|
|
1144
|
+
this._scrollTarget = null;
|
|
1145
|
+
});
|
|
829
1146
|
}
|
|
830
1147
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1148
|
+
this.next = resp.next;
|
|
1149
|
+
this.nextId = resp.nextId;
|
|
1150
|
+
this.previous = resp.previous;
|
|
1151
|
+
this.previousId = resp.previousId;
|
|
1152
|
+
this.nodeId = resp.root;
|
|
1153
|
+
this.switchView = resp.switchView;
|
|
1154
|
+
|
|
1155
|
+
this.updateComplete.then(() => {
|
|
1156
|
+
const view = this.shadowRoot.getElementById('view');
|
|
1157
|
+
this._applyToggles(view);
|
|
1158
|
+
this._fixLinks(view);
|
|
1159
|
+
typesetMath(view);
|
|
1160
|
+
|
|
1161
|
+
const eventOptions = {
|
|
1162
|
+
data: resp,
|
|
1163
|
+
root: view,
|
|
1164
|
+
params: loader.params,
|
|
1165
|
+
id: this.xmlId,
|
|
1166
|
+
position: this.nodeId,
|
|
1167
|
+
};
|
|
1168
|
+
this.emitTo('pb-update', eventOptions);
|
|
1169
|
+
this._scroll();
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
this.emitTo('pb-end-update', null);
|
|
1173
|
+
// allow subsequent loads with new params
|
|
1174
|
+
this._loading = false;
|
|
1175
|
+
// Mark that this view has loaded at least once
|
|
1176
|
+
this._hasLoadedOnce = true;
|
|
1177
|
+
// Track the id that was actually loaded (for detecting navigation events)
|
|
1178
|
+
// This is critical - we only update _lastLoadedId when content is actually loaded,
|
|
1179
|
+
// not when registry just updates our state
|
|
1180
|
+
if (this.xmlId) {
|
|
1181
|
+
this._lastLoadedId = this.xmlId;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
_replaceContent(resp, direction) {
|
|
1186
|
+
const fragment = document.createDocumentFragment();
|
|
1187
|
+
const elem = document.createElement('div');
|
|
1188
|
+
// elem.style.opacity = 0; //hide it - animation has to make sure to blend it in
|
|
1189
|
+
fragment.appendChild(elem);
|
|
1190
|
+
elem.innerHTML = resp.content;
|
|
1191
|
+
|
|
1192
|
+
// if before-update-event is set, we do not replace the content immediately,
|
|
1193
|
+
// but emit an event
|
|
1194
|
+
if (this.beforeUpdate) {
|
|
1195
|
+
this.emitTo(this.beforeUpdate, {
|
|
1196
|
+
data: resp,
|
|
1197
|
+
root: elem,
|
|
1198
|
+
render: content => {
|
|
1199
|
+
this._doReplaceContent(content, resp, direction);
|
|
1200
|
+
},
|
|
1201
|
+
});
|
|
1202
|
+
} else {
|
|
1203
|
+
this._doReplaceContent(elem, resp, direction);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
_doReplaceContent(elem, resp, direction) {
|
|
1208
|
+
|
|
1209
|
+
if (this.columnSeparator) {
|
|
1210
|
+
this._replaceColumns(elem);
|
|
1211
|
+
this._loading = false;
|
|
1212
|
+
} else if (this.infiniteScroll) {
|
|
1213
|
+
elem.className = 'scroll-fragment';
|
|
1214
|
+
elem.setAttribute('data-root', resp.root);
|
|
1215
|
+
if (resp.next) {
|
|
1216
|
+
elem.setAttribute('data-next', resp.next);
|
|
1217
|
+
}
|
|
1218
|
+
if (resp.previous) {
|
|
1219
|
+
elem.setAttribute('data-previous', resp.previous);
|
|
1220
|
+
}
|
|
1221
|
+
let refNode;
|
|
1222
|
+
switch (direction) {
|
|
1223
|
+
case 'backward':
|
|
1224
|
+
refNode = this._content.firstElementChild;
|
|
1225
|
+
this._chunks.unshift(elem);
|
|
1226
|
+
this.updateComplete.then(() => {
|
|
1227
|
+
refNode.scrollIntoView(true);
|
|
834
1228
|
this._loading = false;
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
}
|
|
844
|
-
let refNode;
|
|
845
|
-
switch (direction) {
|
|
846
|
-
case 'backward':
|
|
847
|
-
refNode = this._content.firstElementChild;
|
|
848
|
-
this._chunks.unshift(elem);
|
|
849
|
-
this.updateComplete.then(() => {
|
|
850
|
-
refNode.scrollIntoView(true);
|
|
851
|
-
this._loading = false;
|
|
852
|
-
this._checkVisibility();
|
|
853
|
-
this._scrollObserver.observe(this._bottomObserver);
|
|
854
|
-
this._scrollObserver.observe(this._topObserver);
|
|
855
|
-
});
|
|
856
|
-
this._content.insertBefore(elem, refNode);
|
|
857
|
-
break;
|
|
858
|
-
default:
|
|
859
|
-
this.updateComplete.then(() => {
|
|
860
|
-
this._loading = false;
|
|
861
|
-
this._checkVisibility();
|
|
862
|
-
this._scrollObserver.observe(this._bottomObserver);
|
|
863
|
-
this._scrollObserver.observe(this._topObserver);
|
|
864
|
-
});
|
|
865
|
-
this._chunks.push(elem);
|
|
866
|
-
this._content.appendChild(elem);
|
|
867
|
-
break;
|
|
868
|
-
}
|
|
869
|
-
} else {
|
|
870
|
-
this._content = elem;
|
|
1229
|
+
this._checkVisibility();
|
|
1230
|
+
this._scrollObserver.observe(this._bottomObserver);
|
|
1231
|
+
this._scrollObserver.observe(this._topObserver);
|
|
1232
|
+
});
|
|
1233
|
+
this._content.insertBefore(elem, refNode);
|
|
1234
|
+
break;
|
|
1235
|
+
default:
|
|
1236
|
+
this.updateComplete.then(() => {
|
|
871
1237
|
this._loading = false;
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1238
|
+
this._checkVisibility();
|
|
1239
|
+
this._scrollObserver.observe(this._bottomObserver);
|
|
1240
|
+
this._scrollObserver.observe(this._topObserver);
|
|
1241
|
+
});
|
|
1242
|
+
this._chunks.push(elem);
|
|
1243
|
+
this._content.appendChild(elem);
|
|
1244
|
+
break;
|
|
1245
|
+
}
|
|
1246
|
+
} else {
|
|
1247
|
+
this._content = elem;
|
|
1248
|
+
this._loading = false;
|
|
1249
|
+
}
|
|
883
1250
|
|
|
884
|
-
|
|
1251
|
+
if (this.appendFootnotes) {
|
|
1252
|
+
const footnotes = document.createElement('div');
|
|
1253
|
+
if (resp.footnotes) {
|
|
1254
|
+
footnotes.innerHTML = resp.footnotes;
|
|
1255
|
+
}
|
|
1256
|
+
this._footnotes = footnotes;
|
|
885
1257
|
}
|
|
886
1258
|
|
|
887
|
-
|
|
888
|
-
const bottomActive = this._chunks[this._chunks.length - 1].hasAttribute('data-next');
|
|
889
|
-
this._bottomObserver.style.display = bottomActive ? '' : 'none';
|
|
1259
|
+
this._initFootnotes(this._footnotes);
|
|
890
1260
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
}
|
|
1261
|
+
return elem;
|
|
1262
|
+
}
|
|
894
1263
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
const cbs = elem.querySelectorAll(this.columnSeparator);
|
|
899
|
-
// use last separator only
|
|
900
|
-
if (cbs.length > 1) {
|
|
901
|
-
cb = cbs[cbs.length - 1];
|
|
902
|
-
}
|
|
903
|
-
}
|
|
1264
|
+
_checkVisibility() {
|
|
1265
|
+
const bottomActive = this._chunks[this._chunks.length - 1].hasAttribute('data-next');
|
|
1266
|
+
this._bottomObserver.style.display = bottomActive ? '' : 'none';
|
|
904
1267
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
}
|
|
1268
|
+
const topActive = this._chunks[0].hasAttribute('data-previous');
|
|
1269
|
+
this._topObserver.style.display = topActive ? '' : 'none';
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
_replaceColumns(elem) {
|
|
1273
|
+
let cb;
|
|
1274
|
+
if (this.columnSeparator) {
|
|
1275
|
+
const cbs = elem.querySelectorAll(this.columnSeparator);
|
|
1276
|
+
// use last separator only
|
|
1277
|
+
if (cbs.length > 1) {
|
|
1278
|
+
cb = cbs[cbs.length - 1];
|
|
1279
|
+
}
|
|
918
1280
|
}
|
|
919
1281
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
);
|
|
933
|
-
}
|
|
934
|
-
}
|
|
1282
|
+
if (!cb) {
|
|
1283
|
+
this._content = elem;
|
|
1284
|
+
} else {
|
|
1285
|
+
const fragmentBefore = this._getFragmentBefore(elem, cb);
|
|
1286
|
+
const fragmentAfter = this._getFragmentAfter(elem, cb);
|
|
1287
|
+
if (this.direction === 'ltr') {
|
|
1288
|
+
this._column1 = fragmentBefore;
|
|
1289
|
+
this._column2 = fragmentAfter;
|
|
1290
|
+
} else {
|
|
1291
|
+
this._column2 = fragmentBefore;
|
|
1292
|
+
this._column1 = fragmentAfter;
|
|
1293
|
+
}
|
|
935
1294
|
}
|
|
1295
|
+
}
|
|
936
1296
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
ev.preventDefault();
|
|
941
|
-
console.log('<pb-view> Scrolling to element %s', target.id);
|
|
942
|
-
target.scrollIntoView({ block: "center", inline: "nearest" });
|
|
943
|
-
}
|
|
1297
|
+
_scroll() {
|
|
1298
|
+
if (this.noScroll) {
|
|
1299
|
+
return;
|
|
944
1300
|
}
|
|
1301
|
+
if (registry.hash) {
|
|
1302
|
+
const target = this.shadowRoot.getElementById(registry.hash.substring(1));
|
|
1303
|
+
if (target) {
|
|
1304
|
+
window.requestAnimationFrame(() =>
|
|
1305
|
+
setTimeout(() => {
|
|
1306
|
+
target.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
1307
|
+
}, 400),
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
945
1312
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1313
|
+
_scrollToElement(ev, link) {
|
|
1314
|
+
const target = this.shadowRoot.getElementById(link.hash.substring(1));
|
|
1315
|
+
if (target) {
|
|
1316
|
+
ev.preventDefault();
|
|
1317
|
+
target.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
_updateStyles() {
|
|
1322
|
+
const link = document.createElement('link');
|
|
1323
|
+
link.setAttribute('rel', 'stylesheet');
|
|
1324
|
+
link.setAttribute('type', 'text/css');
|
|
1325
|
+
if (this.static !== null) {
|
|
1326
|
+
link.setAttribute('href', `${this.static}/css/${this.getOdd()}.css`);
|
|
1327
|
+
} else {
|
|
1328
|
+
link.setAttribute('href', `${this.getEndpoint()}/transform/${this.getOdd()}.css`);
|
|
1329
|
+
}
|
|
1330
|
+
this._style = link;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
_fixLinks(content) {
|
|
1334
|
+
if (this.fixLinks) {
|
|
1335
|
+
const doc = this.getDocument ? this.getDocument() : null;
|
|
1336
|
+
const base = this.toAbsoluteURL(doc && doc.path ? doc.path : '');
|
|
1337
|
+
content.querySelectorAll('img').forEach(image => {
|
|
1338
|
+
const oldSrc = image.getAttribute('src');
|
|
1339
|
+
const src = new URL(oldSrc, base);
|
|
1340
|
+
image.src = src.href;
|
|
1341
|
+
});
|
|
1342
|
+
content.querySelectorAll('a').forEach(link => {
|
|
1343
|
+
const oldHref = link.getAttribute('href');
|
|
1344
|
+
if (oldHref === link.hash) {
|
|
1345
|
+
link.addEventListener('click', ev => this._scrollToElement(ev, link));
|
|
976
1346
|
} else {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
if (oldHref === link.hash) {
|
|
980
|
-
link.addEventListener('click', (ev) => this._scrollToElement(ev, link));
|
|
981
|
-
}
|
|
982
|
-
});
|
|
1347
|
+
const href = new URL(oldHref, base);
|
|
1348
|
+
link.href = href.href;
|
|
983
1349
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
ev.preventDefault();
|
|
991
|
-
const fn = this.shadowRoot.getElementById('content').querySelector(elem.hash);
|
|
992
|
-
if (fn) {
|
|
993
|
-
fn.scrollIntoView();
|
|
994
|
-
}
|
|
995
|
-
});
|
|
996
|
-
});
|
|
1350
|
+
});
|
|
1351
|
+
} else {
|
|
1352
|
+
content.querySelectorAll('a').forEach(link => {
|
|
1353
|
+
const oldHref = link.getAttribute('href');
|
|
1354
|
+
if (oldHref === link.hash) {
|
|
1355
|
+
link.addEventListener('click', ev => this._scrollToElement(ev, link));
|
|
997
1356
|
}
|
|
1357
|
+
});
|
|
998
1358
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
_initFootnotes(content) {
|
|
1362
|
+
if (content) {
|
|
1363
|
+
content.querySelectorAll('.note, .fn-back').forEach(elem => {
|
|
1364
|
+
elem.addEventListener('click', ev => {
|
|
1365
|
+
ev.preventDefault();
|
|
1366
|
+
const fn = this.shadowRoot.getElementById('content').querySelector(elem.hash);
|
|
1367
|
+
if (fn) {
|
|
1368
|
+
fn.scrollIntoView();
|
|
1369
|
+
}
|
|
1004
1370
|
});
|
|
1005
|
-
|
|
1006
|
-
for (const [key, value] of Object.entries(this._features)) {
|
|
1007
|
-
params['user.' + key] = value;
|
|
1008
|
-
}
|
|
1009
|
-
// add parameters for user-defined parameters supplied via pb-link
|
|
1010
|
-
if (this._additionalParams) {
|
|
1011
|
-
for (const [key, value] of Object.entries(this._additionalParams)) {
|
|
1012
|
-
params[key] = value;
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
return params;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
/**
|
|
1019
|
-
* Return the parameter object which would be passed to the server by this component
|
|
1020
|
-
*/
|
|
1021
|
-
getParameters(pos) {
|
|
1022
|
-
pos = pos || this.nodeId;
|
|
1023
|
-
const doc = this.getDocument();
|
|
1024
|
-
const params = this._getParameters();
|
|
1025
|
-
if (!this.minApiVersion('1.0.0')) {
|
|
1026
|
-
params.doc = doc.path;
|
|
1027
|
-
}
|
|
1028
|
-
params.odd = this.getOdd() + '.odd';
|
|
1029
|
-
params.view = this.getView();
|
|
1030
|
-
if (pos) {
|
|
1031
|
-
params['root'] = pos;
|
|
1032
|
-
}
|
|
1033
|
-
if (this.xpath) {
|
|
1034
|
-
params.xpath = this.xpath;
|
|
1035
|
-
}
|
|
1036
|
-
if (this.xmlId) {
|
|
1037
|
-
params.id = this.xmlId;
|
|
1038
|
-
}
|
|
1039
|
-
if (!this.suppressHighlight && this.highlight) {
|
|
1040
|
-
params.highlight = "yes";
|
|
1041
|
-
}
|
|
1042
|
-
if (this.map) {
|
|
1043
|
-
params.map = this.map;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
return params;
|
|
1371
|
+
});
|
|
1047
1372
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
_getParameters() {
|
|
1376
|
+
const params = {}; // Use object, not array
|
|
1377
|
+
this.querySelectorAll('pb-param').forEach(param => {
|
|
1378
|
+
const name = param.getAttribute('name');
|
|
1379
|
+
const value = param.getAttribute('value');
|
|
1380
|
+
// For metadata panel, filter out canvas IDs
|
|
1381
|
+
if (name === 'id') {
|
|
1382
|
+
const looksLikeCanvasId = /\.jpg$|_\d{2,3}\.jpg/.test(String(value));
|
|
1383
|
+
if (looksLikeCanvasId) {
|
|
1384
|
+
// Check if this is a metadata panel
|
|
1385
|
+
const modeParamEl = this.querySelector('pb-param[name="mode"]');
|
|
1386
|
+
if (modeParamEl && modeParamEl.getAttribute('value') === 'metadata-panel') {
|
|
1387
|
+
return; // Skip canvas IDs for metadata panel
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
params[`user.${name}`] = value;
|
|
1392
|
+
});
|
|
1393
|
+
// add parameters for features set with pb-toggle-feature
|
|
1394
|
+
for (const [key, value] of Object.entries(this._features)) {
|
|
1395
|
+
// For metadata panel, filter out canvas IDs
|
|
1396
|
+
if (key === 'id') {
|
|
1397
|
+
const looksLikeCanvasId = /\.jpg$|_\d{2,3}\.jpg/.test(String(value));
|
|
1398
|
+
if (looksLikeCanvasId) {
|
|
1399
|
+
// Check if this is a metadata panel
|
|
1400
|
+
const modeParamEl = this.querySelector('pb-param[name="mode"]');
|
|
1401
|
+
if (modeParamEl && modeParamEl.getAttribute('value') === 'metadata-panel') {
|
|
1402
|
+
continue; // Skip canvas IDs for metadata panel
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
params[`user.${key}`] = value;
|
|
1063
1407
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
* Load a part of the document identified by the given xml:id
|
|
1076
|
-
*
|
|
1077
|
-
* @param {String} xmlId The xml:id to be loaded
|
|
1078
|
-
*/
|
|
1079
|
-
gotoId(xmlId) {
|
|
1080
|
-
this.xmlId = xmlId;
|
|
1081
|
-
this._load();
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
/**
|
|
1085
|
-
* Navigate the document either forward or backward and refresh the view.
|
|
1086
|
-
* The navigation method is determined by property `view`.
|
|
1087
|
-
*
|
|
1088
|
-
* @param {string} direction either `backward` or `forward`
|
|
1089
|
-
*/
|
|
1090
|
-
navigate(direction) {
|
|
1091
|
-
// in single view mode there should be no navigation
|
|
1092
|
-
if (this.getView() === 'single') {
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
this.lastDirection = direction;
|
|
1097
|
-
|
|
1098
|
-
if (direction === 'backward') {
|
|
1099
|
-
if (this.previous) {
|
|
1100
|
-
if (!this.disableHistory && !this.map) {
|
|
1101
|
-
registry.commit(this, {
|
|
1102
|
-
id: this.previousId || null,
|
|
1103
|
-
root: this.previousId ? null : this.previous
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
this.xmlId = this.previousId;
|
|
1107
|
-
this._load(this.xmlId ? null : this.previous, direction);
|
|
1408
|
+
// add parameters for user-defined parameters supplied via pb-link
|
|
1409
|
+
if (this._additionalParams) {
|
|
1410
|
+
for (const [key, value] of Object.entries(this._additionalParams)) {
|
|
1411
|
+
// For metadata panel, filter out canvas IDs
|
|
1412
|
+
if (key === 'id' || key === 'user.id') {
|
|
1413
|
+
const looksLikeCanvasId = /\.jpg$|_\d{2,3}\.jpg/.test(String(value));
|
|
1414
|
+
if (looksLikeCanvasId) {
|
|
1415
|
+
// Check if this is a metadata panel
|
|
1416
|
+
const modeParamEl = this.querySelector('pb-param[name="mode"]');
|
|
1417
|
+
if (modeParamEl && modeParamEl.getAttribute('value') === 'metadata-panel') {
|
|
1418
|
+
continue; // Skip canvas IDs for metadata panel
|
|
1108
1419
|
}
|
|
1109
|
-
|
|
1110
|
-
if (!this.disableHistory && !this.map) {
|
|
1111
|
-
registry.commit(this, {
|
|
1112
|
-
id: this.nextId || null,
|
|
1113
|
-
root: this.nextId ? null : this.next
|
|
1114
|
-
});
|
|
1115
|
-
}
|
|
1116
|
-
this.xmlId = this.nextId;
|
|
1117
|
-
this._load(this.xmlId ? null : this.next, direction);
|
|
1420
|
+
}
|
|
1118
1421
|
}
|
|
1422
|
+
params[key] = value;
|
|
1423
|
+
}
|
|
1119
1424
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
if (this._chunks.length === this.infiniteScrollMax) {
|
|
1134
|
-
switch (direction) {
|
|
1135
|
-
case 'forward':
|
|
1136
|
-
this._content.removeChild(this._chunks.shift());
|
|
1137
|
-
break;
|
|
1138
|
-
default:
|
|
1139
|
-
this._content.removeChild(this._chunks.pop());
|
|
1140
|
-
break;
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
this.emitTo('pb-navigate', {
|
|
1144
|
-
direction,
|
|
1145
|
-
source: this
|
|
1146
|
-
});
|
|
1425
|
+
return params;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* Return the parameter object which would be passed to the server by this component
|
|
1430
|
+
*/
|
|
1431
|
+
getParameters(pos) {
|
|
1432
|
+
pos = pos || this.nodeId;
|
|
1433
|
+
const doc = this.getDocument ? this.getDocument() : null;
|
|
1434
|
+
const params = this._getParameters();
|
|
1435
|
+
if (!this.minApiVersion('1.0.0') && doc && doc.path) {
|
|
1436
|
+
params.doc = doc.path;
|
|
1147
1437
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
const size = parseInt(fontSize.replace(/^(\d+)px/, "$1"));
|
|
1158
|
-
|
|
1159
|
-
if (direction === 'in') {
|
|
1160
|
-
view.style.fontSize = (size + 1) + 'px';
|
|
1161
|
-
} else {
|
|
1162
|
-
view.style.fontSize = (size - 1) + 'px';
|
|
1163
|
-
}
|
|
1438
|
+
params.odd = `${this.getOdd()}.odd`;
|
|
1439
|
+
// For metadata panel, use 'single' view to ensure teiHeader is returned
|
|
1440
|
+
// and don't set root parameter - it should return teiHeader, not a specific page
|
|
1441
|
+
const modeParamEl = this.querySelector('pb-param[name="mode"]');
|
|
1442
|
+
const isMetadataPanel = modeParamEl && modeParamEl.getAttribute('value') === 'metadata-panel';
|
|
1443
|
+
if (isMetadataPanel) {
|
|
1444
|
+
params.view = 'single';
|
|
1445
|
+
} else {
|
|
1446
|
+
params.view = this.getView();
|
|
1164
1447
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1448
|
+
params.fill = this.fill;
|
|
1449
|
+
if (pos && !isMetadataPanel) {
|
|
1450
|
+
params.root = pos;
|
|
1451
|
+
}
|
|
1452
|
+
if (this.xpath) {
|
|
1453
|
+
params.xpath = this.xpath;
|
|
1454
|
+
}
|
|
1455
|
+
// Check if we have an id in _additionalParams first (from registry/event, including canvas IDs)
|
|
1456
|
+
// This takes precedence over xmlId because it's the most recent state
|
|
1457
|
+
if (this._additionalParams && this._additionalParams.id) {
|
|
1458
|
+
// For metadata panel, don't add canvas IDs
|
|
1459
|
+
const modeParamEl = this.querySelector('pb-param[name="mode"]');
|
|
1460
|
+
const isMetadataPanel = modeParamEl && modeParamEl.getAttribute('value') === 'metadata-panel';
|
|
1461
|
+
if (isMetadataPanel) {
|
|
1462
|
+
const looksLikeCanvasId = /\.jpg$|_\d{2,3}\.jpg/.test(String(this._additionalParams.id));
|
|
1463
|
+
if (!looksLikeCanvasId) {
|
|
1464
|
+
params.id = this._additionalParams.id;
|
|
1465
|
+
}
|
|
1466
|
+
} else {
|
|
1467
|
+
params.id = this._additionalParams.id;
|
|
1468
|
+
}
|
|
1469
|
+
} else if (this.xmlId) {
|
|
1470
|
+
// Fall back to xmlId if no id in _additionalParams
|
|
1471
|
+
// For metadata panel, don't add canvas IDs
|
|
1472
|
+
const modeParamEl = this.querySelector('pb-param[name="mode"]');
|
|
1473
|
+
const isMetadataPanel = modeParamEl && modeParamEl.getAttribute('value') === 'metadata-panel';
|
|
1474
|
+
if (isMetadataPanel) {
|
|
1475
|
+
const looksLikeCanvasId = /\.jpg$|_\d{2,3}\.jpg/.test(String(this.xmlId));
|
|
1476
|
+
if (!looksLikeCanvasId) {
|
|
1477
|
+
params.id = this.xmlId;
|
|
1478
|
+
}
|
|
1479
|
+
} else {
|
|
1480
|
+
params.id = this.xmlId;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
if (!this.suppressHighlight && this.highlight) {
|
|
1484
|
+
params.highlight = 'yes';
|
|
1485
|
+
}
|
|
1486
|
+
if (this.map) {
|
|
1487
|
+
params.map = this.map;
|
|
1181
1488
|
}
|
|
1182
1489
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
// check if URL template needs the parameter and if
|
|
1186
|
-
// yes, add it to the additional parameter list
|
|
1187
|
-
if (registry.pathParams.has(key)) {
|
|
1188
|
-
this._additionalParams[key] = value;
|
|
1189
|
-
} else {
|
|
1190
|
-
switch (key) {
|
|
1191
|
-
case 'odd':
|
|
1192
|
-
case 'view':
|
|
1193
|
-
case 'columnSeparator':
|
|
1194
|
-
case 'xpath':
|
|
1195
|
-
case 'nodeId':
|
|
1196
|
-
case 'path':
|
|
1197
|
-
case 'root':
|
|
1198
|
-
break;
|
|
1199
|
-
default:
|
|
1200
|
-
this._features[key] = value;
|
|
1201
|
-
break;
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
if (properties.odd && !this.getAttribute('odd')) {
|
|
1206
|
-
this.odd = properties.odd;
|
|
1207
|
-
}
|
|
1208
|
-
if (properties.view && !this.getAttribute('view')) {
|
|
1209
|
-
this.view = properties.view;
|
|
1210
|
-
if (this.view === 'single') {
|
|
1211
|
-
// when switching to single view, clear current node id
|
|
1212
|
-
this.nodeId = null;
|
|
1213
|
-
} else {
|
|
1214
|
-
// otherwise use value for alternate view returned from server
|
|
1215
|
-
this.nodeId = this.switchView;
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
if (properties.xpath && !this.getAttribute('xpath')) {
|
|
1219
|
-
this.xpath = properties.xpath;
|
|
1220
|
-
}
|
|
1221
|
-
if (properties.hasOwnProperty('columnSeparator')) {
|
|
1222
|
-
this.columnSeparator = properties.columnSeparator;
|
|
1223
|
-
}
|
|
1224
|
-
this.xmlId = (!this.getAttribute('xml-id') && properties.id) || this.xmlId;
|
|
1225
|
-
this.nodeId = (!this.getAttribute('xml-id') && properties.root) || null;
|
|
1490
|
+
return params;
|
|
1491
|
+
}
|
|
1226
1492
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1493
|
+
_applyToggles(elem) {
|
|
1494
|
+
for (const [selector, setting] of Object.entries(this._selector)) {
|
|
1495
|
+
elem.querySelectorAll(selector).forEach(node => {
|
|
1496
|
+
const command = setting.command || 'toggle';
|
|
1497
|
+
if (node.command) {
|
|
1498
|
+
node.command(command, setting.state);
|
|
1229
1499
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
state: sc.state,
|
|
1235
|
-
command: sc.command || 'toggle'
|
|
1236
|
-
};
|
|
1237
|
-
});
|
|
1500
|
+
if (setting.state) {
|
|
1501
|
+
node.classList.add(command);
|
|
1502
|
+
} else {
|
|
1503
|
+
node.classList.remove(command);
|
|
1238
1504
|
}
|
|
1505
|
+
});
|
|
1239
1506
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
/**
|
|
1510
|
+
* Load a part of the document identified by the given eXist nodeId
|
|
1511
|
+
*
|
|
1512
|
+
* @param {String} nodeId The eXist nodeId of the root element to load
|
|
1513
|
+
*/
|
|
1514
|
+
goto(nodeId) {
|
|
1515
|
+
this._load(nodeId);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Load a part of the document identified by the given xml:id
|
|
1520
|
+
*
|
|
1521
|
+
* @param {String} xmlId The xml:id to be loaded
|
|
1522
|
+
*/
|
|
1523
|
+
gotoId(xmlId) {
|
|
1524
|
+
this.xmlId = xmlId;
|
|
1525
|
+
this._load();
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* Navigate the document either forward or backward and refresh the view.
|
|
1530
|
+
* The navigation method is determined by property `view`.
|
|
1531
|
+
*
|
|
1532
|
+
* @param {string} direction either `backward` or `forward`
|
|
1533
|
+
*/
|
|
1534
|
+
navigate(direction) {
|
|
1535
|
+
// in single view mode there should be no navigation
|
|
1536
|
+
if (this.getView() === 'single') {
|
|
1537
|
+
return;
|
|
1247
1538
|
}
|
|
1248
1539
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1540
|
+
this.lastDirection = direction;
|
|
1541
|
+
|
|
1542
|
+
if (direction === 'backward') {
|
|
1543
|
+
if (this.previous) {
|
|
1544
|
+
const isReadOnly = this.readOnlyRegistry || this.hasAttribute('read-only-registry');
|
|
1545
|
+
if (!this.disableHistory && !this.map && !isReadOnly) {
|
|
1546
|
+
registry.commit(this, {
|
|
1547
|
+
id: this.previousId || null,
|
|
1548
|
+
root: this.previousId ? null : this.previous,
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
this.xmlId = this.previousId;
|
|
1552
|
+
this._load(this.xmlId ? null : this.previous, direction);
|
|
1553
|
+
}
|
|
1554
|
+
} else if (this.next) {
|
|
1555
|
+
const isReadOnly = this.readOnlyRegistry || this.hasAttribute('read-only-registry');
|
|
1556
|
+
if (!this.disableHistory && !this.map && !isReadOnly) {
|
|
1557
|
+
registry.commit(this, {
|
|
1558
|
+
id: this.nextId || null,
|
|
1559
|
+
root: this.nextId ? null : this.next,
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
this.xmlId = this.nextId;
|
|
1563
|
+
this._load(this.xmlId ? null : this.next, direction);
|
|
1255
1564
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/**
|
|
1568
|
+
* Check the number of fragments which were already loaded in infinite
|
|
1569
|
+
* scroll mode. If they exceed `infiniteScrollMax`, remove either the
|
|
1570
|
+
* first or last fragment from the DOM, depending on the scroll direction.
|
|
1571
|
+
*
|
|
1572
|
+
* @param {string} direction either 'forward' or 'backward'
|
|
1573
|
+
*/
|
|
1574
|
+
_checkChunks(direction) {
|
|
1575
|
+
if (!this.infiniteScroll || this.infiniteScrollMax === 0) {
|
|
1576
|
+
return;
|
|
1264
1577
|
}
|
|
1265
1578
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
#view {
|
|
1288
|
-
position: relative;
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
.columns {
|
|
1292
|
-
display: grid;
|
|
1293
|
-
grid-template-columns: calc(50% - var(--pb-view-column-gap, 10px) / 2) calc(50% - var(--pb-view-column-gap, 10px) / 2);
|
|
1294
|
-
grid-column-gap: var(--pb-view-column-gap, 10px);
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
.margin-note {
|
|
1298
|
-
display: none;
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
@media (min-width: 769px) {
|
|
1302
|
-
.content.margin-right {
|
|
1303
|
-
margin-right: 200px;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
.margin-note {
|
|
1307
|
-
background: rgba(153, 153, 153, 0.2);
|
|
1308
|
-
display: block;
|
|
1309
|
-
font-size: small;
|
|
1310
|
-
margin-right: -200px;
|
|
1311
|
-
margin-bottom: 5px;
|
|
1312
|
-
padding: 5px 0;
|
|
1313
|
-
float: right;
|
|
1314
|
-
clear: both;
|
|
1315
|
-
width: 180px;
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
.margin-note .n {
|
|
1319
|
-
color: #777777;
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
a[rel=footnote] {
|
|
1324
|
-
font-size: var(--pb-footnote-font-size, var(--pb-content-font-size, 75%));
|
|
1325
|
-
font-family: var(--pb-footnote-font-family, --pb-content-font-family);
|
|
1326
|
-
vertical-align: super;
|
|
1327
|
-
color: var(--pb-footnote-color, var(--pb-color-primary, #333333));
|
|
1328
|
-
text-decoration: none;
|
|
1329
|
-
padding: var(--pb-footnote-padding, 0 0 0 .25em);
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
.list dt {
|
|
1333
|
-
float: left;
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
.footnote .fn-number {
|
|
1337
|
-
float: left;
|
|
1338
|
-
font-size: var(--pb-footnote-font-size, var(--pb-content-font-size, 75%));
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
.observer {
|
|
1342
|
-
display: block;
|
|
1343
|
-
width: 100%;
|
|
1344
|
-
height: var(--pb-view-loader-height, 16px);
|
|
1345
|
-
font-family: var(--pb-view-loader-font, --pb-base-font);
|
|
1346
|
-
color: var(--pb-view-loader-color, black);
|
|
1347
|
-
background: var(--pb-view-loader-background, #909090);
|
|
1348
|
-
background-image: var(--pb-view-loader-background-image, repeating-linear-gradient(45deg, transparent, transparent 35px, rgba(255,255,255,.5) 35px, rgba(255,255,255,.5) 70px));
|
|
1349
|
-
animation-name: loader;
|
|
1350
|
-
animation-timing-function: linear;
|
|
1351
|
-
animation-duration: 2s;
|
|
1352
|
-
animation-fill-mode: forwards;
|
|
1353
|
-
animation-iteration-count: infinite;
|
|
1354
|
-
}
|
|
1579
|
+
if (this._chunks.length === this.infiniteScrollMax) {
|
|
1580
|
+
switch (direction) {
|
|
1581
|
+
case 'forward':
|
|
1582
|
+
this._content.removeChild(this._chunks.shift());
|
|
1583
|
+
break;
|
|
1584
|
+
default:
|
|
1585
|
+
this._content.removeChild(this._chunks.pop());
|
|
1586
|
+
break;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
this.emitTo('pb-navigate', {
|
|
1590
|
+
direction,
|
|
1591
|
+
source: this,
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
toggleFeature(ev) {
|
|
1596
|
+
const properties = registry.getState(this);
|
|
1597
|
+
if (properties) {
|
|
1598
|
+
this._setState(properties);
|
|
1599
|
+
}
|
|
1355
1600
|
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1601
|
+
if (ev.detail.refresh) {
|
|
1602
|
+
this._updateStyles();
|
|
1603
|
+
this._load();
|
|
1604
|
+
} else {
|
|
1605
|
+
const view = this.shadowRoot.getElementById('view');
|
|
1606
|
+
this._applyToggles(view);
|
|
1607
|
+
}
|
|
1608
|
+
// Only commit to registry if not in read-only mode
|
|
1609
|
+
// In read-only mode, pb-view only reads from registry, never writes
|
|
1610
|
+
// Check both property (LitElement) and attribute (XML/XHTML compatibility)
|
|
1611
|
+
const isReadOnly = this.readOnlyRegistry || this.hasAttribute('read-only-registry');
|
|
1612
|
+
if (!isReadOnly) {
|
|
1613
|
+
registry.commit(this, properties);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
_setState(properties) {
|
|
1618
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
1619
|
+
// check if URL template needs the parameter and if
|
|
1620
|
+
// yes, add it to the additional parameter list
|
|
1621
|
+
if (registry.pathParams.has(key)) {
|
|
1622
|
+
this._additionalParams[key] = value;
|
|
1623
|
+
} else {
|
|
1624
|
+
switch (key) {
|
|
1625
|
+
case 'odd':
|
|
1626
|
+
case 'view':
|
|
1627
|
+
case 'columnSeparator':
|
|
1628
|
+
case 'xpath':
|
|
1629
|
+
case 'nodeId':
|
|
1630
|
+
case 'path':
|
|
1631
|
+
case 'root':
|
|
1632
|
+
break;
|
|
1633
|
+
default:
|
|
1634
|
+
this._features[key] = value;
|
|
1635
|
+
break;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
if (properties.odd && !this.getAttribute('odd')) {
|
|
1640
|
+
this.odd = properties.odd;
|
|
1641
|
+
}
|
|
1642
|
+
if (properties.view && !this.getAttribute('view')) {
|
|
1643
|
+
this.view = properties.view;
|
|
1644
|
+
if (this.view === 'single') {
|
|
1645
|
+
// when switching to single view, clear current node id
|
|
1646
|
+
this.nodeId = null;
|
|
1647
|
+
} else {
|
|
1648
|
+
// otherwise use value for alternate view returned from server
|
|
1649
|
+
this.nodeId = this.switchView;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
if (properties.fill && !this.getAttribute('fill')) {
|
|
1653
|
+
this.fill = properties.fill;
|
|
1654
|
+
}
|
|
1655
|
+
if (properties.xpath && !this.getAttribute('xpath')) {
|
|
1656
|
+
this.xpath = properties.xpath;
|
|
1657
|
+
}
|
|
1658
|
+
if (properties.hasOwnProperty('columnSeparator')) {
|
|
1659
|
+
this.columnSeparator = properties.columnSeparator;
|
|
1660
|
+
}
|
|
1661
|
+
this.xmlId = (!this.getAttribute('xml-id') && properties.id) || this.xmlId;
|
|
1662
|
+
this.nodeId = (!this.getAttribute('xml-id') && properties.root) || null;
|
|
1365
1663
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1664
|
+
if (properties.path) {
|
|
1665
|
+
const doc = this.getDocument ? this.getDocument() : null;
|
|
1666
|
+
if (doc) doc.path = properties.path;
|
|
1667
|
+
}
|
|
1369
1668
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
</div>
|
|
1403
|
-
</paper-dialog>
|
|
1404
|
-
<iron-ajax
|
|
1405
|
-
id="loadContent"
|
|
1406
|
-
verbose
|
|
1407
|
-
handle-as="json"
|
|
1408
|
-
method="get"
|
|
1409
|
-
with-credentials
|
|
1410
|
-
@response="${this._handleContent}"
|
|
1411
|
-
@error="${this._handleError}"></iron-ajax>
|
|
1412
|
-
`
|
|
1413
|
-
]
|
|
1669
|
+
if (properties.selectors) {
|
|
1670
|
+
properties.selectors.forEach(sc => {
|
|
1671
|
+
this._selector[sc.selector] = {
|
|
1672
|
+
state: sc.state,
|
|
1673
|
+
command: sc.command || 'toggle',
|
|
1674
|
+
};
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
_getFragmentBefore(node, ms) {
|
|
1680
|
+
const range = document.createRange();
|
|
1681
|
+
range.setStartBefore(node);
|
|
1682
|
+
range.setEndBefore(ms);
|
|
1683
|
+
|
|
1684
|
+
return range.cloneContents();
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
_getFragmentAfter(node, ms) {
|
|
1688
|
+
const range = document.createRange();
|
|
1689
|
+
range.setStartBefore(ms);
|
|
1690
|
+
range.setEndAfter(node);
|
|
1691
|
+
|
|
1692
|
+
return range.cloneContents();
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
_updateSource(newVal, oldVal) {
|
|
1696
|
+
if (typeof oldVal !== 'undefined' && newVal !== oldVal) {
|
|
1697
|
+
this.xpath = null;
|
|
1698
|
+
this.odd = null;
|
|
1699
|
+
this.xmlId = null;
|
|
1700
|
+
this.nodeId = null;
|
|
1414
1701
|
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
static get styles() {
|
|
1705
|
+
return css`
|
|
1706
|
+
:host {
|
|
1707
|
+
display: block;
|
|
1708
|
+
background: transparent;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
:host(.noscroll) {
|
|
1712
|
+
scrollbar-width: none; /* Firefox 64 */
|
|
1713
|
+
-ms-overflow-style: none;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
:host(.noscroll)::-webkit-scrollbar {
|
|
1717
|
+
width: 0 !important;
|
|
1718
|
+
display: none;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
[id] {
|
|
1722
|
+
scroll-margin-top: var(--pb-view-scroll-margin-top);
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
#view {
|
|
1726
|
+
position: relative;
|
|
1727
|
+
font-size: clamp(
|
|
1728
|
+
calc(var(--pb-content-font-size, 1rem) * var(--pb-min-zoom, 0.5)),
|
|
1729
|
+
calc(var(--pb-content-font-size, 1rem) * var(--pb-zoom-factor)),
|
|
1730
|
+
calc(var(--pb-content-font-size, 1rem) * var(--pb-max-zoom, 3))
|
|
1731
|
+
);
|
|
1732
|
+
line-height: calc(var(--pb-content-line-height, 1.5) * var(--pb-zoom-factor));
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
.columns {
|
|
1736
|
+
display: grid;
|
|
1737
|
+
grid-template-columns: calc(50% - var(--pb-view-column-gap, 10px) / 2) calc(
|
|
1738
|
+
50% - var(--pb-view-column-gap, 10px) / 2
|
|
1739
|
+
);
|
|
1740
|
+
grid-column-gap: var(--pb-view-column-gap, 10px);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
.margin-note {
|
|
1744
|
+
display: none;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
@media (min-width: 769px) {
|
|
1748
|
+
.content.margin-right {
|
|
1749
|
+
margin-right: 200px;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
.margin-note {
|
|
1753
|
+
background: rgba(153, 153, 153, 0.2);
|
|
1754
|
+
display: block;
|
|
1755
|
+
font-size: small;
|
|
1756
|
+
margin-right: -200px;
|
|
1757
|
+
margin-bottom: 5px;
|
|
1758
|
+
padding: 5px 0;
|
|
1759
|
+
float: right;
|
|
1760
|
+
clear: both;
|
|
1761
|
+
width: 180px;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
.margin-note .n {
|
|
1765
|
+
color: #777777;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
a[rel='footnote'] {
|
|
1770
|
+
font-size: calc(
|
|
1771
|
+
var(--pb-footnote-font-size, var(--pb-content-font-size, 75%)) * var(--pb-zoom-factor, 1)
|
|
1772
|
+
);
|
|
1773
|
+
font-family: var(--pb-footnote-font-family, --pb-content-font-family);
|
|
1774
|
+
vertical-align: super;
|
|
1775
|
+
color: var(--pb-footnote-color, var(--pb-color-primary, #333333));
|
|
1776
|
+
text-decoration: none;
|
|
1777
|
+
padding: var(--pb-footnote-padding, 0 0 0 0.25em);
|
|
1778
|
+
line-height: 1;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
.list dt {
|
|
1782
|
+
float: left;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
.footnote .fn-number {
|
|
1786
|
+
float: left;
|
|
1787
|
+
font-size: var(--pb-footnote-font-size, var(--pb-content-font-size, 75%));
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
.observer {
|
|
1791
|
+
display: block;
|
|
1792
|
+
width: 100%;
|
|
1793
|
+
height: var(--pb-view-loader-height, 16px);
|
|
1794
|
+
font-family: var(--pb-view-loader-font, --pb-base-font);
|
|
1795
|
+
color: var(--pb-view-loader-color, black);
|
|
1796
|
+
background: var(--pb-view-loader-background, #909090);
|
|
1797
|
+
background-image: var(
|
|
1798
|
+
--pb-view-loader-background-image,
|
|
1799
|
+
repeating-linear-gradient(
|
|
1800
|
+
45deg,
|
|
1801
|
+
transparent,
|
|
1802
|
+
transparent 35px,
|
|
1803
|
+
rgba(255, 255, 255, 0.5) 35px,
|
|
1804
|
+
rgba(255, 255, 255, 0.5) 70px
|
|
1805
|
+
)
|
|
1806
|
+
);
|
|
1807
|
+
animation-name: loader;
|
|
1808
|
+
animation-timing-function: linear;
|
|
1809
|
+
animation-duration: 2s;
|
|
1810
|
+
animation-fill-mode: forwards;
|
|
1811
|
+
animation-iteration-count: infinite;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
@keyframes loader {
|
|
1815
|
+
0% {
|
|
1816
|
+
background-position: 3rem 0;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
100% {
|
|
1820
|
+
background-position: 0 0;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
.scroll-fragment {
|
|
1825
|
+
animation: fadeIn ease 500ms;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
@keyframes fadeIn {
|
|
1829
|
+
0% {
|
|
1830
|
+
opacity: 0;
|
|
1831
|
+
}
|
|
1832
|
+
100% {
|
|
1833
|
+
opacity: 1;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
`;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
render() {
|
|
1840
|
+
return [
|
|
1841
|
+
html`
|
|
1842
|
+
<div id="view" part="content">
|
|
1843
|
+
${this._style}
|
|
1844
|
+
${this.infiniteScroll ? html`<div id="top-observer" class="observer"></div>` : null}
|
|
1845
|
+
<div class="columns">
|
|
1846
|
+
<div id="column1">${this._column1}</div>
|
|
1847
|
+
<div id="column2">${this._column2}</div>
|
|
1848
|
+
</div>
|
|
1849
|
+
<div id="content">${this._content}</div>
|
|
1850
|
+
${this.infiniteScroll ? html`<div id="bottom-observer" class="observer"></div>` : null}
|
|
1851
|
+
<div id="footnotes" part="footnotes">${this._footnotes}</div>
|
|
1852
|
+
</div>
|
|
1853
|
+
<pb-fetch
|
|
1854
|
+
id="loadContent"
|
|
1855
|
+
verbose
|
|
1856
|
+
handle-as="json"
|
|
1857
|
+
method="get"
|
|
1858
|
+
with-credentials
|
|
1859
|
+
@response="${this._handleContent}"
|
|
1860
|
+
@error="${this._handleError}"
|
|
1861
|
+
></pb-fetch>
|
|
1862
|
+
`,
|
|
1863
|
+
];
|
|
1864
|
+
}
|
|
1415
1865
|
}
|
|
1416
1866
|
|
|
1417
1867
|
customElements.define('pb-view', PbView);
|