@teipublisher/pb-components 2.26.0-next-3.11 → 2.26.0-next-3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/.github/workflows/main.yml +3 -3
  2. package/.github/workflows/node.js.yml +3 -3
  3. package/.github/workflows/release.js.yml +3 -3
  4. package/CHANGELOG.md +38 -0
  5. package/Dockerfile +78 -70
  6. package/css/components.css +5 -5
  7. package/dist/demo/components.css +2 -8
  8. package/dist/demo/pb-drawer2.html +1 -1
  9. package/dist/demo/pb-leaflet-map.html +1 -1
  10. package/dist/demo/pb-progress.html +2 -2
  11. package/dist/demo/pb-repeat.html +1 -3
  12. package/dist/demo/pb-view3.html +1 -1
  13. package/dist/{paper-icon-button-0fb125c4.js → paper-icon-button-72125e67.js} +1 -1
  14. package/dist/pb-code-editor.js +25 -20
  15. package/dist/pb-component-docs.js +58 -54
  16. package/dist/pb-components-bundle.js +1937 -1782
  17. package/dist/pb-edit-app.js +167 -107
  18. package/dist/pb-elements.json +45 -45
  19. package/dist/{pb-i18n-0611135a.js → pb-i18n-4cc00bfe.js} +1 -1
  20. package/dist/pb-leaflet-map.js +23 -23
  21. package/dist/pb-mei.js +56 -41
  22. package/dist/{pb-mixin-b1caa22e.js → pb-mixin-886ece32.js} +1 -1
  23. package/dist/pb-odd-editor.js +923 -756
  24. package/dist/pb-tify.js +2 -2
  25. package/dist/{vaadin-element-mixin-6633322b.js → vaadin-element-mixin-84fb7d82.js} +181 -143
  26. package/gh-pages.js +5 -3
  27. package/i18n/common/pl.json +2 -2
  28. package/lib/openseadragon.min.js +1 -1
  29. package/package.json +2 -2
  30. package/pb-elements.json +45 -45
  31. package/src/assets/components.css +5 -5
  32. package/src/authority/airtable.js +20 -21
  33. package/src/authority/anton.js +129 -129
  34. package/src/authority/custom.js +23 -21
  35. package/src/authority/geonames.js +38 -32
  36. package/src/authority/gnd.js +47 -42
  37. package/src/authority/kbga.js +137 -134
  38. package/src/authority/metagrid.js +44 -46
  39. package/src/authority/reconciliation.js +66 -67
  40. package/src/authority/registry.js +4 -4
  41. package/src/docs/pb-component-docs.js +2 -2
  42. package/src/docs/pb-component-view.js +5 -5
  43. package/src/docs/pb-components-list.js +2 -2
  44. package/src/docs/pb-demo-snippet.js +2 -2
  45. package/src/dts-client.js +299 -297
  46. package/src/dts-select-endpoint.js +90 -82
  47. package/src/parse-date-service.js +184 -135
  48. package/src/pb-ajax.js +150 -146
  49. package/src/pb-authority-lookup.js +183 -146
  50. package/src/pb-autocomplete.js +292 -280
  51. package/src/pb-blacklab-highlight.js +264 -259
  52. package/src/pb-blacklab-results.js +236 -221
  53. package/src/pb-browse-docs.js +540 -475
  54. package/src/pb-browse.js +68 -65
  55. package/src/pb-clipboard.js +79 -76
  56. package/src/pb-code-editor.js +110 -102
  57. package/src/pb-code-highlight.js +209 -204
  58. package/src/pb-codepen.js +79 -72
  59. package/src/pb-collapse.js +212 -207
  60. package/src/pb-combo-box.js +190 -190
  61. package/src/pb-components-bundle.js +1 -1
  62. package/src/pb-custom-form.js +151 -149
  63. package/src/pb-dialog.js +96 -60
  64. package/src/pb-document.js +89 -90
  65. package/src/pb-download.js +210 -198
  66. package/src/pb-drawer.js +145 -148
  67. package/src/pb-edit-app.js +301 -229
  68. package/src/pb-edit-xml.js +98 -96
  69. package/src/pb-events.js +114 -107
  70. package/src/pb-facs-link.js +104 -102
  71. package/src/pb-facsimile.js +411 -413
  72. package/src/pb-formula.js +151 -153
  73. package/src/pb-geolocation.js +129 -131
  74. package/src/pb-grid-action.js +53 -56
  75. package/src/pb-grid.js +231 -228
  76. package/src/pb-highlight.js +140 -140
  77. package/src/pb-hotkeys.js +40 -42
  78. package/src/pb-i18n.js +101 -104
  79. package/src/pb-image-strip.js +84 -78
  80. package/src/pb-lang.js +132 -128
  81. package/src/pb-leaflet-map.js +488 -485
  82. package/src/pb-link.js +126 -124
  83. package/src/pb-load.js +431 -426
  84. package/src/pb-login.js +291 -248
  85. package/src/pb-manage-odds.js +364 -318
  86. package/src/pb-map-icon.js +89 -89
  87. package/src/pb-map-layer.js +85 -85
  88. package/src/pb-markdown.js +90 -99
  89. package/src/pb-media-query.js +74 -72
  90. package/src/pb-mei.js +306 -295
  91. package/src/pb-message.js +144 -144
  92. package/src/pb-mixin.js +269 -264
  93. package/src/pb-navigation.js +80 -82
  94. package/src/pb-observable.js +38 -38
  95. package/src/pb-odd-editor.js +1053 -955
  96. package/src/pb-odd-elementspec-editor.js +348 -297
  97. package/src/pb-odd-model-editor.js +1061 -901
  98. package/src/pb-odd-parameter-editor.js +200 -178
  99. package/src/pb-odd-rendition-editor.js +136 -124
  100. package/src/pb-page.js +431 -421
  101. package/src/pb-paginate.js +202 -190
  102. package/src/pb-panel.js +191 -179
  103. package/src/pb-popover-themes.js +7 -5
  104. package/src/pb-popover.js +296 -287
  105. package/src/pb-print-preview.js +127 -127
  106. package/src/pb-progress.js +51 -51
  107. package/src/pb-repeat.js +105 -104
  108. package/src/pb-restricted.js +84 -77
  109. package/src/pb-search.js +252 -241
  110. package/src/pb-select-feature.js +127 -120
  111. package/src/pb-select-odd.js +132 -124
  112. package/src/pb-select-template.js +89 -78
  113. package/src/pb-select.js +251 -227
  114. package/src/pb-split-list.js +179 -174
  115. package/src/pb-svg.js +80 -79
  116. package/src/pb-table-column.js +54 -54
  117. package/src/pb-table-grid.js +221 -203
  118. package/src/pb-tabs.js +61 -63
  119. package/src/pb-tify.js +154 -154
  120. package/src/pb-timeline.js +271 -229
  121. package/src/pb-toggle-feature.js +182 -175
  122. package/src/pb-upload.js +184 -174
  123. package/src/pb-version.js +30 -30
  124. package/src/pb-view-annotate.js +132 -98
  125. package/src/pb-view.js +1289 -1270
  126. package/src/pb-zoom.js +75 -59
  127. package/src/polymer-hack.js +1 -1
  128. package/src/search-result-service.js +256 -223
  129. package/src/seed-element.js +13 -20
  130. package/src/settings.js +4 -4
  131. package/src/theming.js +96 -96
  132. package/src/urls.js +289 -289
  133. package/src/utils.js +53 -51
package/src/pb-view.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { LitElement, html, css } from 'lit-element';
2
2
  import anime from 'animejs';
3
- import { pbMixin, waitOnce } from "./pb-mixin.js";
4
- import { registry } from "./urls.js";
5
- import { translate } from "./pb-i18n.js";
6
- import { typesetMath } from "./pb-formula.js";
7
- import { loadStylesheets, themableMixin } from "./theming.js";
3
+ import { pbMixin, waitOnce } from './pb-mixin.js';
4
+ import { registry } from './urls.js';
5
+ import { translate } from './pb-i18n.js';
6
+ import { typesetMath } from './pb-formula.js';
7
+ import { loadStylesheets, themableMixin } from './theming.js';
8
8
  import '@polymer/iron-ajax';
9
9
 
10
10
  /**
@@ -58,1367 +58,1386 @@ import '@polymer/iron-ajax';
58
58
  * @fires pb-toggle - When received, toggle content properties
59
59
  */
60
60
  export class PbView extends themableMixin(pbMixin(LitElement)) {
61
-
62
- static get properties() {
63
- return {
64
- /**
65
- * The id of a `pb-document` element this view should display.
66
- * Settings like `odd` or `view` will be taken from the `pb-document`
67
- * unless overwritten by properties in this component.
68
- *
69
- * This property is **required** and **must** point to an existing `pb-document` with
70
- * the given id.
71
- *
72
- * Setting the property after initialization will clear the properties xmlId, nodeId and odd.
73
- */
74
- src: {
75
- type: String
76
- },
77
- /**
78
- * The ODD to use for rendering the document. Overwrites an ODD defined on
79
- * `pb-document`. The odd should be specified by its name without path
80
- * or the `.odd` suffix.
81
- */
82
- odd: {
83
- type: String
84
- },
85
- /**
86
- * The view type to use for paginating the document. Either `page`, `div` or `single`.
87
- * Overwrites the same property specified on `pb-document`. Values have the following meaning:
88
- *
89
- * Value | Displayed content
90
- * ------|------------------
91
- * `page` | content is displayed page by page as determined by tei:pb
92
- * `div` | content is displayed by divisions
93
- * `single` | do not paginate but display entire content at once
94
- */
95
- view: {
96
- type: String
97
- },
98
- /**
99
- * Controls the pagination-by-div algorithm: if a page would have less than
100
- * `fill` elements, it tries to fill
101
- * up the page by pulling following divs in. When set to 0, it will never
102
- * attempt to fill up the page. For the annotation editor this should
103
- * always be 0.
104
- */
105
- fill: {
106
- type: Number
107
- },
108
- /**
109
- * An eXist nodeId. If specified, selects the root of the fragment of the document
110
- * which should be displayed. Normally this property is set automatically by pagination.
111
- */
112
- nodeId: {
113
- type: String,
114
- attribute: 'node-id'
115
- },
116
- /**
117
- * An xml:id to be displayed. If specified, this determines the root of the fragment to be
118
- * displayed. Use to directly navigate to a specific section.
119
- */
120
- xmlId: {
121
- type: Array,
122
- attribute: 'xml-id'
123
- },
124
- /**
125
- * An optional XPath expression: the root of the fragment to be processed is determined
126
- * by evaluating the given XPath expression. The XPath expression should be absolute.
127
- * The namespace of the document is declared as default namespace, so no prefixes should
128
- * be used.
129
- *
130
- * If the `map` property is used, it may change scope for the displayed fragment.
131
- */
132
- xpath: {
133
- type: String
134
- },
135
- /**
136
- * If defined denotes the local name of an XQuery function in `modules/map.xql`, which will be called
137
- * with the current root node and should return the node of a mapped fragment. This is helpful if one
138
- * wants, for example, to show a translation fragment aligned with the part of the transcription currently
139
- * shown. In this case, the properties of the `pb-view` would still point to the transcription, but the function
140
- * identified by map would return the corresponding fragment from the translation to be processed.
141
- *
142
- * Navigation in the document is still determined by the current root as defined through the `root`, `xpath`
143
- * and `xmlId` properties.
144
- */
145
- map: {
146
- type: String
147
- },
148
- /**
149
- * If set to true, the component will not load automatically. Instead it will wait until it receives a `pb-update`
150
- * event. Use this to make one `pb-view` component dependent on another one. Default is 'false'.
151
- */
152
- onUpdate: {
153
- type: Boolean,
154
- attribute: 'on-update'
155
- },
156
- /**
157
- * Message to display if no content was returned by the server.
158
- * Set to empty string to show nothing.
159
- */
160
- notFound: {
161
- type: String,
162
- attribute: 'not-found'
163
- },
164
- /**
165
- * The relative URL to the script on the server which will be called for loading content.
166
- */
167
- url: {
168
- type: String
169
- },
170
- /**
171
- * If set, rewrite URLs to load pages as static HTML files,
172
- * so no TEI Publisher instance is required. Use this in combination with
173
- * [tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).
174
- * The value should point to the HTTP root path under which the static version
175
- * will be hosted. This is used to resolve CSS stylesheets.
176
- */
177
- static: {
178
- type: String
179
- },
180
- /**
181
- * The server returns footnotes separately. Set this property
182
- * if you wish to append them to the main text.
183
- */
184
- appendFootnotes: {
185
- type: Boolean,
186
- attribute: 'append-footnotes'
187
- },
188
- /**
189
- * Should matches be highlighted if a search has been executed?
190
- */
191
- suppressHighlight: {
192
- type: Boolean,
193
- attribute: 'suppress-highlight'
194
- },
195
- /**
196
- * CSS selector to find column breaks in the content returned
197
- * from the server. If this property is set and column breaks
198
- * are found, the component will display two columns side by side.
199
- */
200
- columnSeparator: {
201
- type: String,
202
- attribute: 'column-separator'
203
- },
204
- /**
205
- * The reading direction, i.e. 'ltr' or 'rtl'.
206
- *
207
- * @type {"ltr"|"rtl"}
208
- */
209
- direction: {
210
- type: String
211
- },
212
- /**
213
- * If set, points to an external stylesheet which should be applied to
214
- * the text *after* the ODD-generated styles.
215
- */
216
- loadCss: {
217
- type: String,
218
- attribute: 'load-css'
219
- },
220
- /**
221
- * If set, relative links (img, a) will be made absolute.
222
- */
223
- fixLinks: {
224
- type: Boolean,
225
- attribute: 'fix-links'
226
- },
227
- /**
228
- * If set, a refresh will be triggered if a `pb-i18n-update` event is received,
229
- * e.g. due to the user selecting a different interface language.
230
- *
231
- * Also requires `requireLanguage` to be set on the surrounding `pb-page`.
232
- * See there for more information.
233
- */
234
- useLanguage: {
235
- type: Boolean,
236
- attribute: 'use-language'
237
- },
238
- /**
239
- * wether to animate the view when new page is loaded. Defaults to 'false' meaning that no
240
- * animation takes place. If 'true' will apply a translateX transistion in forward/backward direction.
241
- */
242
- animation: {
243
- type: Boolean
244
- },
245
- /**
246
- * Experimental: if enabled, the view will incrementally load new document fragments if the user tries to scroll
247
- * beyond the start or end of the visible text. The feature inserts a small blank section at the top
248
- * and bottom. If this section becomes visible, a load operation will be triggered.
249
- *
250
- * Note: only browsers implementing the `IntersectionObserver` API are supported. Also the feature
251
- * does not work in two-column mode or with animations.
252
- */
253
- infiniteScroll: {
254
- type: Boolean,
255
- attribute: 'infinite-scroll'
256
- },
257
- /**
258
- * Maximum number of fragments to keep in memory if `infinite-scroll`
259
- * is enabled. If the user is scrolling beyond the maximum, fragements
260
- * will be removed from the DOM before or after the current reading position.
261
- * Default is 10. Set to zero to allow loading the entire document.
262
- */
263
- infiniteScrollMax: {
264
- type: Number,
265
- attribute: 'infinite-scroll-max'
266
- },
267
- /**
268
- * A selector pointing to other components this component depends on.
269
- * When method `wait` is called, it will wait until all referenced
270
- * components signal with a `pb-ready` event that they are ready and listening
271
- * to events.
272
- *
273
- * `pb-view` by default sets this property to select `pb-toggle-feature` and `pb-select-feature`
274
- * elements.
275
- */
276
- waitFor: {
277
- type: String,
278
- attribute: 'wait-for'
279
- },
280
- /**
281
- * By default, navigating to next/previous page will update browser parameters,
282
- * so reloading the page will load the correct position within the document.
283
- *
284
- * Set this property to disable location tracking for the component altogether.
285
- */
286
- disableHistory: {
287
- type: Boolean,
288
- attribute: 'disable-history'
289
- },
290
- /**
291
- * If set to the name of an event, the content of the pb-view will not be replaced
292
- * immediately upon updates. Instead, an event is emitted, which contains the new content
293
- * in property `root`. An event handler intercepting the event can thus modify the content.
294
- * Once it is done, it should pass the modified content to the callback function provided
295
- * in the event detail under the name `render`. See the demo for an example.
296
- */
297
- beforeUpdate: {
298
- type: String,
299
- attribute: 'before-update-event'
300
- },
301
- /**
302
- * If set, do not scroll the view to target node (e.g. given in URL hash)
303
- * after content was loaded.
304
- */
305
- noScroll: {
306
- type: Boolean,
307
- attribute: 'no-scroll'
308
- },
309
- _features: {
310
- type: Object
311
- },
312
- _content: {
313
- type: Node,
314
- attribute: false
315
- },
316
- _column1: {
317
- type: Node,
318
- attribute: false
319
- },
320
- _column2: {
321
- type: Node,
322
- attribute: false
323
- },
324
- _footnotes: {
325
- type: Node,
326
- attribute: false
327
- },
328
- _style: {
329
- type: Node,
330
- attribute: false
331
- },
332
- _additionalParams: {
333
- type: Object
334
- },
335
- ...super.properties
336
- };
61
+ static get properties() {
62
+ return {
63
+ /**
64
+ * The id of a `pb-document` element this view should display.
65
+ * Settings like `odd` or `view` will be taken from the `pb-document`
66
+ * unless overwritten by properties in this component.
67
+ *
68
+ * This property is **required** and **must** point to an existing `pb-document` with
69
+ * the given id.
70
+ *
71
+ * Setting the property after initialization will clear the properties xmlId, nodeId and odd.
72
+ */
73
+ src: {
74
+ type: String,
75
+ },
76
+ /**
77
+ * The ODD to use for rendering the document. Overwrites an ODD defined on
78
+ * `pb-document`. The odd should be specified by its name without path
79
+ * or the `.odd` suffix.
80
+ */
81
+ odd: {
82
+ type: String,
83
+ },
84
+ /**
85
+ * The view type to use for paginating the document. Either `page`, `div` or `single`.
86
+ * Overwrites the same property specified on `pb-document`. Values have the following meaning:
87
+ *
88
+ * Value | Displayed content
89
+ * ------|------------------
90
+ * `page` | content is displayed page by page as determined by tei:pb
91
+ * `div` | content is displayed by divisions
92
+ * `single` | do not paginate but display entire content at once
93
+ */
94
+ view: {
95
+ type: String,
96
+ },
97
+ /**
98
+ * Controls the pagination-by-div algorithm: if a page would have less than
99
+ * `fill` elements, it tries to fill
100
+ * up the page by pulling following divs in. When set to 0, it will never
101
+ * attempt to fill up the page. For the annotation editor this should
102
+ * always be 0.
103
+ */
104
+ fill: {
105
+ type: Number,
106
+ },
107
+ /**
108
+ * An eXist nodeId. If specified, selects the root of the fragment of the document
109
+ * which should be displayed. Normally this property is set automatically by pagination.
110
+ */
111
+ nodeId: {
112
+ type: String,
113
+ attribute: 'node-id',
114
+ },
115
+ /**
116
+ * An xml:id to be displayed. If specified, this determines the root of the fragment to be
117
+ * displayed. Use to directly navigate to a specific section.
118
+ */
119
+ xmlId: {
120
+ type: Array,
121
+ attribute: 'xml-id',
122
+ },
123
+ /**
124
+ * An optional XPath expression: the root of the fragment to be processed is determined
125
+ * by evaluating the given XPath expression. The XPath expression should be absolute.
126
+ * The namespace of the document is declared as default namespace, so no prefixes should
127
+ * be used.
128
+ *
129
+ * If the `map` property is used, it may change scope for the displayed fragment.
130
+ */
131
+ xpath: {
132
+ type: String,
133
+ },
134
+ /**
135
+ * If defined denotes the local name of an XQuery function in `modules/map.xql`, which will be called
136
+ * with the current root node and should return the node of a mapped fragment. This is helpful if one
137
+ * wants, for example, to show a translation fragment aligned with the part of the transcription currently
138
+ * shown. In this case, the properties of the `pb-view` would still point to the transcription, but the function
139
+ * identified by map would return the corresponding fragment from the translation to be processed.
140
+ *
141
+ * Navigation in the document is still determined by the current root as defined through the `root`, `xpath`
142
+ * and `xmlId` properties.
143
+ */
144
+ map: {
145
+ type: String,
146
+ },
147
+ /**
148
+ * If set to true, the component will not load automatically. Instead it will wait until it receives a `pb-update`
149
+ * event. Use this to make one `pb-view` component dependent on another one. Default is 'false'.
150
+ */
151
+ onUpdate: {
152
+ type: Boolean,
153
+ attribute: 'on-update',
154
+ },
155
+ /**
156
+ * Message to display if no content was returned by the server.
157
+ * Set to empty string to show nothing.
158
+ */
159
+ notFound: {
160
+ type: String,
161
+ attribute: 'not-found',
162
+ },
163
+ /**
164
+ * The relative URL to the script on the server which will be called for loading content.
165
+ */
166
+ url: {
167
+ type: String,
168
+ },
169
+ /**
170
+ * If set, rewrite URLs to load pages as static HTML files,
171
+ * so no TEI Publisher instance is required. Use this in combination with
172
+ * [tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).
173
+ * The value should point to the HTTP root path under which the static version
174
+ * will be hosted. This is used to resolve CSS stylesheets.
175
+ */
176
+ static: {
177
+ type: String,
178
+ },
179
+ /**
180
+ * The server returns footnotes separately. Set this property
181
+ * if you wish to append them to the main text.
182
+ */
183
+ appendFootnotes: {
184
+ type: Boolean,
185
+ attribute: 'append-footnotes',
186
+ },
187
+ /**
188
+ * Should matches be highlighted if a search has been executed?
189
+ */
190
+ suppressHighlight: {
191
+ type: Boolean,
192
+ attribute: 'suppress-highlight',
193
+ },
194
+ /**
195
+ * CSS selector to find column breaks in the content returned
196
+ * from the server. If this property is set and column breaks
197
+ * are found, the component will display two columns side by side.
198
+ */
199
+ columnSeparator: {
200
+ type: String,
201
+ attribute: 'column-separator',
202
+ },
203
+ /**
204
+ * The reading direction, i.e. 'ltr' or 'rtl'.
205
+ *
206
+ * @type {"ltr"|"rtl"}
207
+ */
208
+ direction: {
209
+ type: String,
210
+ },
211
+ /**
212
+ * If set, points to an external stylesheet which should be applied to
213
+ * the text *after* the ODD-generated styles.
214
+ */
215
+ loadCss: {
216
+ type: String,
217
+ attribute: 'load-css',
218
+ },
219
+ /**
220
+ * If set, relative links (img, a) will be made absolute.
221
+ */
222
+ fixLinks: {
223
+ type: Boolean,
224
+ attribute: 'fix-links',
225
+ },
226
+ /**
227
+ * If set, a refresh will be triggered if a `pb-i18n-update` event is received,
228
+ * e.g. due to the user selecting a different interface language.
229
+ *
230
+ * Also requires `requireLanguage` to be set on the surrounding `pb-page`.
231
+ * See there for more information.
232
+ */
233
+ useLanguage: {
234
+ type: Boolean,
235
+ attribute: 'use-language',
236
+ },
237
+ /**
238
+ * wether to animate the view when new page is loaded. Defaults to 'false' meaning that no
239
+ * animation takes place. If 'true' will apply a translateX transistion in forward/backward direction.
240
+ */
241
+ animation: {
242
+ type: Boolean,
243
+ },
244
+ /**
245
+ * Experimental: if enabled, the view will incrementally load new document fragments if the user tries to scroll
246
+ * beyond the start or end of the visible text. The feature inserts a small blank section at the top
247
+ * and bottom. If this section becomes visible, a load operation will be triggered.
248
+ *
249
+ * Note: only browsers implementing the `IntersectionObserver` API are supported. Also the feature
250
+ * does not work in two-column mode or with animations.
251
+ */
252
+ infiniteScroll: {
253
+ type: Boolean,
254
+ attribute: 'infinite-scroll',
255
+ },
256
+ /**
257
+ * Maximum number of fragments to keep in memory if `infinite-scroll`
258
+ * is enabled. If the user is scrolling beyond the maximum, fragements
259
+ * will be removed from the DOM before or after the current reading position.
260
+ * Default is 10. Set to zero to allow loading the entire document.
261
+ */
262
+ infiniteScrollMax: {
263
+ type: Number,
264
+ attribute: 'infinite-scroll-max',
265
+ },
266
+ /**
267
+ * A selector pointing to other components this component depends on.
268
+ * When method `wait` is called, it will wait until all referenced
269
+ * components signal with a `pb-ready` event that they are ready and listening
270
+ * to events.
271
+ *
272
+ * `pb-view` by default sets this property to select `pb-toggle-feature` and `pb-select-feature`
273
+ * elements.
274
+ */
275
+ waitFor: {
276
+ type: String,
277
+ attribute: 'wait-for',
278
+ },
279
+ /**
280
+ * By default, navigating to next/previous page will update browser parameters,
281
+ * so reloading the page will load the correct position within the document.
282
+ *
283
+ * Set this property to disable location tracking for the component altogether.
284
+ */
285
+ disableHistory: {
286
+ type: Boolean,
287
+ attribute: 'disable-history',
288
+ },
289
+ /**
290
+ * If set to the name of an event, the content of the pb-view will not be replaced
291
+ * immediately upon updates. Instead, an event is emitted, which contains the new content
292
+ * in property `root`. An event handler intercepting the event can thus modify the content.
293
+ * Once it is done, it should pass the modified content to the callback function provided
294
+ * in the event detail under the name `render`. See the demo for an example.
295
+ */
296
+ beforeUpdate: {
297
+ type: String,
298
+ attribute: 'before-update-event',
299
+ },
300
+ /**
301
+ * If set, do not scroll the view to target node (e.g. given in URL hash)
302
+ * after content was loaded.
303
+ */
304
+ noScroll: {
305
+ type: Boolean,
306
+ attribute: 'no-scroll',
307
+ },
308
+ _features: {
309
+ type: Object,
310
+ },
311
+ _content: {
312
+ type: Node,
313
+ attribute: false,
314
+ },
315
+ _column1: {
316
+ type: Node,
317
+ attribute: false,
318
+ },
319
+ _column2: {
320
+ type: Node,
321
+ attribute: false,
322
+ },
323
+ _footnotes: {
324
+ type: Node,
325
+ attribute: false,
326
+ },
327
+ _style: {
328
+ type: Node,
329
+ attribute: false,
330
+ },
331
+ _additionalParams: {
332
+ type: Object,
333
+ },
334
+ ...super.properties,
335
+ };
336
+ }
337
+
338
+ constructor() {
339
+ super();
340
+ this.src = null;
341
+ this.url = null;
342
+ this.onUpdate = false;
343
+ this.appendFootnotes = false;
344
+ this.notFound = null;
345
+ this.animation = false;
346
+ this.direction = 'ltr';
347
+ this.suppressHighlight = false;
348
+ this.highlight = false;
349
+ this.infiniteScrollMax = 10;
350
+ this.disableHistory = false;
351
+ this.beforeUpdate = null;
352
+ this.noScroll = false;
353
+ this._features = {};
354
+ this._additionalParams = {};
355
+ this._selector = {};
356
+ this._chunks = [];
357
+ this._scrollTarget = null;
358
+ this.static = null;
359
+ }
360
+
361
+ attributeChangedCallback(name, oldVal, newVal) {
362
+ super.attributeChangedCallback(name, oldVal, newVal);
363
+ switch (name) {
364
+ case 'src':
365
+ this._updateSource(newVal, oldVal);
366
+ break;
337
367
  }
368
+ }
338
369
 
339
- constructor() {
340
- super();
341
- this.src = null;
342
- this.url = null;
343
- this.onUpdate = false;
344
- this.appendFootnotes = false;
345
- this.notFound = null;
346
- this.animation = false;
347
- this.direction = 'ltr';
348
- this.suppressHighlight = false;
349
- this.highlight = false;
350
- this.infiniteScrollMax = 10;
351
- this.disableHistory = false;
352
- this.beforeUpdate = null;
353
- this.noScroll = false;
354
- this._features = {};
355
- this._additionalParams = {};
356
- this._selector = {};
357
- this._chunks = [];
358
- this._scrollTarget = null;
359
- this.static = null;
360
- }
370
+ connectedCallback() {
371
+ super.connectedCallback();
361
372
 
362
- attributeChangedCallback(name, oldVal, newVal) {
363
- super.attributeChangedCallback(name, oldVal, newVal);
364
- switch (name) {
365
- case 'src':
366
- this._updateSource(newVal, oldVal);
367
- break;
368
- }
373
+ if (this.loadCss) {
374
+ waitOnce('pb-page-ready', () => {
375
+ loadStylesheets([this.toAbsoluteURL(this.loadCss)]).then(theme => {
376
+ this.shadowRoot.adoptedStyleSheets = [...this.shadowRoot.adoptedStyleSheets, theme];
377
+ });
378
+ });
369
379
  }
370
380
 
371
- connectedCallback() {
372
- super.connectedCallback();
373
-
374
- if (this.loadCss) {
375
- waitOnce('pb-page-ready', () => {
376
- loadStylesheets([this.toAbsoluteURL(this.loadCss)])
377
- .then((theme) => {
378
- this.shadowRoot.adoptedStyleSheets = [...this.shadowRoot.adoptedStyleSheets, theme];
379
- });
380
- });
381
- }
382
-
383
- if (this.infiniteScroll) {
384
- this.columnSeparator = null;
385
- this.animation = false;
386
- this._content = document.createElement('div');
387
- this._content.className = 'infinite-content';
388
- }
389
-
390
- if (!this.disableHistory) {
391
- if (registry.state.id && !this.xmlId) {
392
- this.xmlId = registry.state.id;
393
- }
394
-
395
- if (registry.state.action && registry.state.action === 'search') {
396
- this.highlight = true;
397
- }
398
-
399
- if (this.view === 'single') {
400
- this.nodeId = null;
401
- } else if (registry.state.root && !this.nodeId) {
402
- this.nodeId = registry.state.root;
403
- }
381
+ if (this.infiniteScroll) {
382
+ this.columnSeparator = null;
383
+ this.animation = false;
384
+ this._content = document.createElement('div');
385
+ this._content.className = 'infinite-content';
386
+ }
404
387
 
405
- const newState = {
406
- id: this.xmlId,
407
- view: this.getView(),
408
- odd: this.getOdd(),
409
- path: this.getDocument().path
410
- };
411
- if (this.view !== 'single') {
412
- newState.root = this.nodeId;
413
- }
414
- if (this.fill) {
415
- newState.fill = this.fill;
416
- }
417
- console.log('id: %s; state: %o', this.id, newState);
418
- registry.replace(this, newState);
388
+ if (!this.disableHistory) {
389
+ if (registry.state.id && !this.xmlId) {
390
+ this.xmlId = registry.state.id;
391
+ }
392
+
393
+ if (registry.state.action && registry.state.action === 'search') {
394
+ this.highlight = true;
395
+ }
396
+
397
+ if (this.view === 'single') {
398
+ this.nodeId = null;
399
+ } else if (registry.state.root && !this.nodeId) {
400
+ this.nodeId = registry.state.root;
401
+ }
402
+
403
+ const newState = {
404
+ id: this.xmlId,
405
+ view: this.getView(),
406
+ odd: this.getOdd(),
407
+ path: this.getDocument().path,
408
+ };
409
+ if (this.view !== 'single') {
410
+ newState.root = this.nodeId;
411
+ }
412
+ if (this.fill) {
413
+ newState.fill = this.fill;
414
+ }
415
+ console.log('id: %s; state: %o', this.id, newState);
416
+ registry.replace(this, newState);
417
+
418
+ registry.subscribe(this, state => {
419
+ this._setState(state);
420
+ this._refresh();
421
+ });
422
+ }
423
+ if (!this.waitFor) {
424
+ this.waitFor = 'pb-toggle-feature,pb-select-feature,pb-navigation';
425
+ }
419
426
 
420
- registry.subscribe(this, (state) => {
421
- this._setState(state);
422
- this._refresh();
423
- });
424
- }
425
- if (!this.waitFor) {
426
- this.waitFor = 'pb-toggle-feature,pb-select-feature,pb-navigation';
427
- }
427
+ this.subscribeTo('pb-navigate', ev => {
428
+ if (ev.detail.source && ev.detail.source === this) {
429
+ return;
430
+ }
431
+ this.navigate(ev.detail.direction);
432
+ });
433
+ this.subscribeTo('pb-refresh', this._refresh.bind(this));
434
+ this.subscribeTo('pb-toggle', ev => {
435
+ this.toggleFeature(ev);
436
+ });
437
+ this.subscribeTo('pb-zoom', ev => {
438
+ this.zoom(ev.detail.direction);
439
+ });
440
+ this.subscribeTo(
441
+ 'pb-i18n-update',
442
+ ev => {
443
+ const needsRefresh =
444
+ this._features.language && this._features.language !== ev.detail.language;
445
+ this._features.language = ev.detail.language;
446
+ if (this.useLanguage && needsRefresh) {
447
+ this._setState(registry.getState(this));
448
+ this._refresh();
449
+ }
450
+ },
451
+ [],
452
+ );
453
+
454
+ this.signalReady();
455
+
456
+ if (this.onUpdate) {
457
+ this.subscribeTo('pb-update', ev => {
458
+ this._refresh(ev);
459
+ });
460
+ }
461
+ }
428
462
 
429
- this.subscribeTo('pb-navigate', ev => {
430
- if (ev.detail.source && ev.detail.source === this) {
431
- return;
463
+ disconnectedCallback() {
464
+ super.disconnectedCallback();
465
+ if (this._scrollObserver) {
466
+ this._scrollObserver.disconnect();
467
+ }
468
+ }
469
+
470
+ firstUpdated() {
471
+ super.firstUpdated();
472
+ this.enableScrollbar(true);
473
+ if (this.infiniteScroll) {
474
+ this._topObserver = this.shadowRoot.getElementById('top-observer');
475
+ this._bottomObserver = this.shadowRoot.getElementById('bottom-observer');
476
+ this._bottomObserver.style.display = 'none';
477
+ this._topObserver.style.display = 'none';
478
+ this._scrollObserver = new IntersectionObserver(entries => {
479
+ if (!this._content) {
480
+ return;
481
+ }
482
+ entries.forEach(entry => {
483
+ if (entry.isIntersecting) {
484
+ if (entry.target.id === 'bottom-observer') {
485
+ const lastChild = this._content.lastElementChild;
486
+ if (lastChild) {
487
+ const next = lastChild.getAttribute('data-next');
488
+ if (next && !this._content.querySelector(`[data-root="${next}"]`)) {
489
+ console.log('<pb-view> Loading next page: %s', next);
490
+ this._checkChunks('forward');
491
+ this._load(next, 'forward');
492
+ }
493
+ }
494
+ } else {
495
+ const firstChild = this._content.firstElementChild;
496
+ if (firstChild) {
497
+ const previous = firstChild.getAttribute('data-previous');
498
+ if (previous && !this._content.querySelector(`[data-root="${previous}"]`)) {
499
+ this._checkChunks('backward');
500
+ this._load(previous, 'backward');
501
+ }
502
+ }
432
503
  }
433
- this.navigate(ev.detail.direction);
504
+ }
434
505
  });
435
- this.subscribeTo('pb-refresh', this._refresh.bind(this));
436
- this.subscribeTo('pb-toggle', ev => {
437
- this.toggleFeature(ev);
506
+ });
507
+ }
508
+ if (!this.onUpdate) {
509
+ waitOnce('pb-page-ready', data => {
510
+ if (data && data.language) {
511
+ this._features.language = data.language;
512
+ }
513
+ this.wait(() => {
514
+ if (!this.disableHistory) {
515
+ this._setState(registry.state);
516
+ }
517
+ this._refresh();
438
518
  });
439
- this.subscribeTo('pb-zoom', ev => {
440
- this.zoom(ev.detail.direction);
519
+ });
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Returns the ODD used to render content.
525
+ *
526
+ * @returns the ODD being used
527
+ */
528
+ getOdd() {
529
+ return this.odd || this.getDocument().odd || 'teipublisher';
530
+ }
531
+
532
+ getView() {
533
+ return this.view || this.getDocument().view || 'single';
534
+ }
535
+
536
+ /**
537
+ * Trigger an update of this element's content
538
+ */
539
+ forceUpdate() {
540
+ this._load(this.nodeId);
541
+ }
542
+
543
+ animate() {
544
+ // animate new element if 'animation' property is 'true'
545
+ if (this.animation) {
546
+ if (this.lastDirection === 'forward') {
547
+ anime({
548
+ targets: this.shadowRoot.getElementById('view'),
549
+ opacity: [0, 1],
550
+ translateX: [1000, 0],
551
+ duration: 300,
552
+ easing: 'linear',
441
553
  });
442
- this.subscribeTo('pb-i18n-update', ev => {
443
- const needsRefresh = this._features.language && this._features.language !== ev.detail.language;
444
- this._features.language = ev.detail.language;
445
- if (this.useLanguage && needsRefresh) {
446
- this._setState(registry.getState(this));
447
- this._refresh();
448
- }
449
- }, []);
450
-
451
- this.signalReady();
452
-
453
- if (this.onUpdate) {
454
- this.subscribeTo('pb-update', (ev) => {
455
- this._refresh(ev);
456
- });
457
- }
554
+ } else {
555
+ anime({
556
+ targets: this.shadowRoot.getElementById('view'),
557
+ opacity: [0, 1],
558
+ translateX: [-1000, 0],
559
+ duration: 300,
560
+ easing: 'linear',
561
+ });
562
+ }
458
563
  }
564
+ }
459
565
 
460
- disconnectedCallback() {
461
- super.disconnectedCallback();
462
- if (this._scrollObserver) {
463
- this._scrollObserver.disconnect();
464
- }
566
+ enableScrollbar(enable) {
567
+ if (enable) {
568
+ this.classList.add('noscroll');
569
+ } else {
570
+ this.classList.remove('noscroll');
465
571
  }
466
-
467
- firstUpdated() {
468
- super.firstUpdated();
469
- this.enableScrollbar(true);
470
- if (this.infiniteScroll) {
471
- this._topObserver = this.shadowRoot.getElementById('top-observer');
472
- this._bottomObserver = this.shadowRoot.getElementById('bottom-observer');
473
- this._bottomObserver.style.display = 'none';
474
- this._topObserver.style.display = 'none';
475
- this._scrollObserver = new IntersectionObserver((entries) => {
476
- if (!this._content) {
477
- return;
478
- }
479
- entries.forEach((entry) => {
480
- if (entry.isIntersecting) {
481
- if (entry.target.id === 'bottom-observer') {
482
- const lastChild = this._content.lastElementChild;
483
- if (lastChild) {
484
- const next = lastChild.getAttribute('data-next');
485
- if (next && !this._content.querySelector(`[data-root="${next}"]`)) {
486
- console.log('<pb-view> Loading next page: %s', next);
487
- this._checkChunks('forward');
488
- this._load(next, 'forward');
489
- }
490
- }
491
- } else {
492
- const firstChild = this._content.firstElementChild;
493
- if (firstChild) {
494
- const previous = firstChild.getAttribute('data-previous');
495
- if (previous && !this._content.querySelector(`[data-root="${previous}"]`)) {
496
- this._checkChunks('backward');
497
- this._load(previous, 'backward');
498
- }
499
- }
500
- }
501
- }
502
- });
503
- });
504
- }
505
- if (!this.onUpdate) {
506
- waitOnce('pb-page-ready', (data) => {
507
- if (data && data.language) {
508
- this._features.language = data.language;
509
- }
510
- this.wait(() => {
511
- if (!this.disableHistory) {
512
- this._setState(registry.state);
513
- }
514
- this._refresh();
515
- });
516
- });
572
+ }
573
+
574
+ _refresh(ev) {
575
+ if (ev && ev.detail) {
576
+ if (
577
+ ev.detail.hash &&
578
+ !this.noScroll &&
579
+ !(ev.detail.id || ev.detail.path || ev.detail.odd || ev.detail.view || ev.detail.position)
580
+ ) {
581
+ // if only the scroll target has changed: scroll to the element without reloading
582
+ this._scrollTarget = ev.detail.hash;
583
+ const target = this.shadowRoot.getElementById(this._scrollTarget);
584
+ if (target) {
585
+ setTimeout(() => target.scrollIntoView({ block: 'nearest' }));
517
586
  }
587
+ return;
588
+ }
589
+ if (ev.detail.path) {
590
+ const doc = this.getDocument();
591
+ doc.path = ev.detail.path;
592
+ }
593
+ if (ev.detail.id) {
594
+ this.xmlId = ev.detail.id;
595
+ } else if (ev.detail.id == null) {
596
+ this.xmlId = null;
597
+ }
598
+ this.odd = ev.detail.odd || this.odd;
599
+ if (ev.detail.columnSeparator !== undefined) {
600
+ this.columnSeparator = ev.detail.columnSeparator;
601
+ }
602
+ this.view = ev.detail.view || this.getView();
603
+ this.fill = ev.detail.fill || this.fill;
604
+ if (ev.detail.xpath) {
605
+ this.xpath = ev.detail.xpath;
606
+ this.nodeId = null;
607
+ }
608
+ // clear nodeId if set to null
609
+ if (ev.detail.root === null) {
610
+ this.nodeId = null;
611
+ } else {
612
+ this.nodeId =
613
+ (ev.detail.position !== undefined ? ev.detail.position : ev.detail.root) || this.nodeId;
614
+ }
615
+
616
+ // check if the URL template needs any other parameters
617
+ // and set them on this._additionalParams
618
+ registry.pathParams.forEach(key => {
619
+ this._additionalParams[key] = ev.detail[key];
620
+ });
621
+
622
+ if (!this.noScroll) {
623
+ this._scrollTarget = ev.detail.hash;
624
+ }
518
625
  }
519
-
520
- /**
521
- * Returns the ODD used to render content.
522
- *
523
- * @returns the ODD being used
524
- */
525
- getOdd() {
526
- return this.odd || this.getDocument().odd || "teipublisher";
527
- }
528
-
529
- getView() {
530
- return this.view || this.getDocument().view || "single";
626
+ this._updateStyles();
627
+ if (this.infiniteScroll) {
628
+ this._clear();
531
629
  }
630
+ this._load(this.nodeId);
631
+ }
532
632
 
533
- /**
534
- * Trigger an update of this element's content
535
- */
536
- forceUpdate() {
537
- this._load(this.nodeId);
633
+ _load(pos, direction) {
634
+ const doc = this.getDocument();
538
635
 
636
+ if (!doc.path) {
637
+ console.log('No path');
638
+ return;
539
639
  }
540
640
 
541
- animate() {
542
- // animate new element if 'animation' property is 'true'
543
- if (this.animation) {
544
- if (this.lastDirection === 'forward') {
545
- anime({
546
- targets: this.shadowRoot.getElementById('view'),
547
- opacity: [0, 1],
548
- translateX: [1000, 0],
549
- duration: 300,
550
- easing: 'linear'
551
- });
552
- } else {
553
- anime({
554
- targets: this.shadowRoot.getElementById('view'),
555
- opacity: [0, 1],
556
- translateX: [-1000, 0],
557
- duration: 300,
558
- easing: 'linear'
559
- });
560
- }
561
- }
641
+ if (this._loading) {
642
+ return;
562
643
  }
563
-
564
- enableScrollbar(enable) {
565
- if (enable) {
566
- this.classList.add('noscroll');
567
- } else {
568
- this.classList.remove('noscroll');
569
- }
644
+ this._loading = true;
645
+ const params = this.getParameters(pos);
646
+ if (direction) {
647
+ params._dir = direction;
570
648
  }
649
+ // this.$.view.style.opacity=0;
571
650
 
572
- _refresh(ev) {
573
- if (ev && ev.detail) {
574
- if (ev.detail.hash && !this.noScroll && !(ev.detail.id || ev.detail.path || ev.detail.odd || ev.detail.view || ev.detail.position)) {
575
- // if only the scroll target has changed: scroll to the element without reloading
576
- this._scrollTarget = ev.detail.hash;
577
- const target = this.shadowRoot.getElementById(this._scrollTarget);
578
- if (target) {
579
- setTimeout(() => target.scrollIntoView({block: 'nearest'}));
580
- }
581
- return;
582
- }
583
- if (ev.detail.path) {
584
- const doc = this.getDocument();
585
- doc.path = ev.detail.path;
586
- }
587
- if (ev.detail.id) {
588
- this.xmlId = ev.detail.id;
589
- } else if (ev.detail.id == null) {
590
- this.xmlId = null;
591
- }
592
- this.odd = ev.detail.odd || this.odd;
593
- if (ev.detail.columnSeparator !== undefined) {
594
- this.columnSeparator = ev.detail.columnSeparator;
595
- }
596
- this.view = ev.detail.view || this.getView();
597
- this.fill = ev.detail.fill || this.fill;
598
- if (ev.detail.xpath) {
599
- this.xpath = ev.detail.xpath;
600
- this.nodeId = null;
601
- }
602
- // clear nodeId if set to null
603
- if (ev.detail.root === null) {
604
- this.nodeId = null;
605
- } else {
606
- this.nodeId = (ev.detail.position !== undefined ? ev.detail.position : ev.detail.root) || this.nodeId;
607
- }
651
+ this._doLoad(params);
652
+ }
608
653
 
609
- // check if the URL template needs any other parameters
610
- // and set them on this._additionalParams
611
- registry.pathParams.forEach((key) => {
612
- this._additionalParams[key] = ev.detail[key];
613
- });
654
+ _doLoad(params) {
655
+ this.emitTo('pb-start-update', params);
614
656
 
615
- if (!this.noScroll) {
616
- this._scrollTarget = ev.detail.hash;
617
- }
618
- }
619
- this._updateStyles();
620
- if (this.infiniteScroll) {
621
- this._clear();
622
- }
623
- this._load(this.nodeId);
657
+ console.log('<pb-view> Loading view with params %o', params);
658
+ if (!this.infiniteScroll) {
659
+ this._clear();
624
660
  }
625
661
 
626
- _load(pos, direction) {
627
- const doc = this.getDocument();
628
-
629
- if (!doc.path) {
630
- console.log("No path");
631
- return;
632
- }
633
-
634
- if (this._loading) {
635
- return;
636
- }
637
- this._loading = true;
638
- const params = this.getParameters(pos);
639
- if (direction) {
640
- params._dir = direction;
641
- }
642
- // this.$.view.style.opacity=0;
643
-
644
- this._doLoad(params);
662
+ if (this._scrollObserver) {
663
+ if (this._bottomObserver) {
664
+ this._scrollObserver.unobserve(this._bottomObserver);
665
+ }
666
+ if (this._topObserver) {
667
+ this._scrollObserver.unobserve(this._topObserver);
668
+ }
645
669
  }
646
670
 
647
- _doLoad(params) {
648
- this.emitTo('pb-start-update', params);
649
-
650
- console.log("<pb-view> Loading view with params %o", params);
651
- if (!this.infiniteScroll) {
652
- this._clear();
653
- }
654
-
655
- if (this._scrollObserver) {
656
- if (this._bottomObserver) {
657
- this._scrollObserver.unobserve(this._bottomObserver);
658
- }
659
- if (this._topObserver) {
660
- this._scrollObserver.unobserve(this._topObserver);
661
- }
662
- }
663
-
664
- const loadContent = this.shadowRoot.getElementById('loadContent');
665
-
666
- if (this.static !== null) {
667
- this._staticUrl(params).then((url) => {
668
- loadContent.url = url;
669
- loadContent.generateRequest();
670
- });
671
- return;
672
- }
673
-
674
- if (!this.url) {
675
- if (this.minApiVersion('1.0.0')) {
676
- this.url = "api/parts";
677
- } else {
678
- this.url = "modules/lib/components.xql";
679
- }
680
- }
681
-
682
- let url = `${this.getEndpoint()}/${this.url}`;
683
-
684
- if (this.minApiVersion('1.0.0')) {
685
- url += `/${encodeURIComponent(this.getDocument().path)}/json`;
686
- }
671
+ const loadContent = this.shadowRoot.getElementById('loadContent');
687
672
 
673
+ if (this.static !== null) {
674
+ this._staticUrl(params).then(url => {
688
675
  loadContent.url = url;
689
- loadContent.params = params;
690
676
  loadContent.generateRequest();
677
+ });
678
+ return;
691
679
  }
692
680
 
693
- /**
694
- * Use a static URL to load pre-generated content.
695
- */
696
- async _staticUrl(params) {
697
- function createKey(paramNames) {
698
- const urlComponents = [];
699
- paramNames.sort().forEach(key => {
700
- if (params.hasOwnProperty(key)) {
701
- urlComponents.push(`${key}=${params[key]}`);
702
- }
703
- });
704
- return urlComponents.join('&');
705
- }
681
+ if (!this.url) {
682
+ if (this.minApiVersion('1.0.0')) {
683
+ this.url = 'api/parts';
684
+ } else {
685
+ this.url = 'modules/lib/components.xql';
686
+ }
687
+ }
706
688
 
707
- const index = await fetch(`index.json`)
708
- .then((response) => response.json());
709
- const paramNames = ['odd', 'view', 'xpath', 'map'];
710
- this.querySelectorAll('pb-param').forEach((param) => paramNames.push(`user.${param.getAttribute('name')}`));
711
- let url = params.id ? createKey([...paramNames, 'id']) : createKey([...paramNames, 'root']);
712
- let file = index[url];
713
- if (!file) {
714
- url = createKey(paramNames);
715
- file = index[url];
716
- }
689
+ let url = `${this.getEndpoint()}/${this.url}`;
717
690
 
718
- console.log('<pb-view> Static lookup %s: %s', url, file);
719
- return `${file}`;
691
+ if (this.minApiVersion('1.0.0')) {
692
+ url += `/${encodeURIComponent(this.getDocument().path)}/json`;
720
693
  }
721
694
 
722
- _clear() {
723
- if (this.infiniteScroll) {
724
- this._content = document.createElement('div');
725
- this._content.className = 'infinite-content';
726
- } else {
727
- this._content = null;
728
- }
729
- this._column1 = null;
730
- this._column2 = null;
731
- this._footnotes = null;
732
- this._chunks = [];
695
+ loadContent.url = url;
696
+ loadContent.params = params;
697
+ loadContent.generateRequest();
698
+ }
699
+
700
+ /**
701
+ * Use a static URL to load pre-generated content.
702
+ */
703
+ async _staticUrl(params) {
704
+ function createKey(paramNames) {
705
+ const urlComponents = [];
706
+ paramNames.sort().forEach(key => {
707
+ if (params.hasOwnProperty(key)) {
708
+ urlComponents.push(`${key}=${params[key]}`);
709
+ }
710
+ });
711
+ return urlComponents.join('&');
733
712
  }
734
713
 
735
- _handleError() {
736
- this._clear();
737
- const loader = this.shadowRoot.getElementById('loadContent');
738
- let message;
739
- const { response } = loader.lastError;
740
-
741
- if (response) {
742
- message = response.description;
743
- } else {
744
- message = '<pb-i18n key="dialogs.serverError"></pb-i18n>';
745
- }
746
-
747
- let content;
748
- if (this.notFound != null) {
749
- content = `<p>${this.notFound}</p>`;
750
- } else {
751
- content = `<p><pb-i18n key="dialogs.serverError"></pb-i18n>: ${message} </p>`;
752
- }
753
-
754
- this._replaceContent({ content });
755
- this.emitTo('pb-end-update');
756
-
714
+ const index = await fetch(`index.json`).then(response => response.json());
715
+ const paramNames = ['odd', 'view', 'xpath', 'map'];
716
+ this.querySelectorAll('pb-param').forEach(param =>
717
+ paramNames.push(`user.${param.getAttribute('name')}`),
718
+ );
719
+ let url = params.id ? createKey([...paramNames, 'id']) : createKey([...paramNames, 'root']);
720
+ let file = index[url];
721
+ if (!file) {
722
+ url = createKey(paramNames);
723
+ file = index[url];
757
724
  }
758
725
 
759
- _handleContent() {
760
- const loader = this.shadowRoot.getElementById('loadContent');
761
- const resp = loader.lastResponse;
762
-
763
- if (!resp) {
764
- console.error('<pb-view> No response received');
765
- return;
766
- }
767
- if (resp.error) {
768
- if (this.notFound != null) {
769
- this._content = this.notFound;
770
- }
771
- this.emitTo('pb-end-update', null);
772
- return;
773
- }
726
+ console.log('<pb-view> Static lookup %s: %s', url, file);
727
+ return `${file}`;
728
+ }
774
729
 
775
- this._replaceContent(resp, loader.params._dir);
730
+ _clear() {
731
+ if (this.infiniteScroll) {
732
+ this._content = document.createElement('div');
733
+ this._content.className = 'infinite-content';
734
+ } else {
735
+ this._content = null;
736
+ }
737
+ this._column1 = null;
738
+ this._column2 = null;
739
+ this._footnotes = null;
740
+ this._chunks = [];
741
+ }
742
+
743
+ _handleError() {
744
+ this._clear();
745
+ const loader = this.shadowRoot.getElementById('loadContent');
746
+ let message;
747
+ const { response } = loader.lastError;
748
+
749
+ if (response) {
750
+ message = response.description;
751
+ } else {
752
+ message = '<pb-i18n key="dialogs.serverError"></pb-i18n>';
753
+ }
776
754
 
777
- this.animate();
755
+ let content;
756
+ if (this.notFound != null) {
757
+ content = `<p>${this.notFound}</p>`;
758
+ } else {
759
+ content = `<p><pb-i18n key="dialogs.serverError"></pb-i18n>: ${message} </p>`;
760
+ }
778
761
 
779
- if (this._scrollTarget) {
780
- this.updateComplete.then(() => {
781
- const target = this.shadowRoot.getElementById(this._scrollTarget) ||
782
- this.shadowRoot.querySelector(`[node-id="${this._scrollTarget}"]`);
783
- if (target) {
784
- window.requestAnimationFrame(() =>
785
- setTimeout(() => {
786
- target.scrollIntoView({block: 'nearest'});
787
- }, 400)
788
- );
789
- }
790
- this._scrollTarget = null;
791
- });
792
- }
762
+ this._replaceContent({ content });
763
+ this.emitTo('pb-end-update');
764
+ }
793
765
 
794
- this.next = resp.next;
795
- this.nextId = resp.nextId;
796
- this.previous = resp.previous;
797
- this.previousId = resp.previousId;
798
- this.nodeId = resp.root;
799
- this.switchView = resp.switchView;
800
-
801
- this.updateComplete.then(() => {
802
- const view = this.shadowRoot.getElementById('view');
803
- this._applyToggles(view);
804
- this._fixLinks(view);
805
- typesetMath(view);
806
-
807
- const eventOptions = {
808
- data: resp,
809
- root: view,
810
- params: loader.params,
811
- id: this.xmlId,
812
- position: this.nodeId
813
- };
814
- this.emitTo('pb-update', eventOptions);
815
- this._scroll();
816
- });
766
+ _handleContent() {
767
+ const loader = this.shadowRoot.getElementById('loadContent');
768
+ const resp = loader.lastResponse;
817
769
 
818
- this.emitTo('pb-end-update', null);
770
+ if (!resp) {
771
+ console.error('<pb-view> No response received');
772
+ return;
773
+ }
774
+ if (resp.error) {
775
+ if (this.notFound != null) {
776
+ this._content = this.notFound;
777
+ }
778
+ this.emitTo('pb-end-update', null);
779
+ return;
819
780
  }
820
781
 
821
- _replaceContent(resp, direction) {
822
- const fragment = document.createDocumentFragment();
823
- const elem = document.createElement('div');
824
- // elem.style.opacity = 0; //hide it - animation has to make sure to blend it in
825
- fragment.appendChild(elem);
826
- elem.innerHTML = resp.content;
782
+ this._replaceContent(resp, loader.params._dir);
827
783
 
828
- // if before-update-event is set, we do not replace the content immediately,
829
- // but emit an event
830
- if (this.beforeUpdate) {
831
- this.emitTo(this.beforeUpdate, {
832
- data: resp,
833
- root: elem,
834
- render: (content) => {
835
- this._doReplaceContent(content, resp, direction);
836
- }
837
- });
838
- } else {
839
- this._doReplaceContent(elem, resp, direction);
784
+ this.animate();
785
+
786
+ if (this._scrollTarget) {
787
+ this.updateComplete.then(() => {
788
+ const target =
789
+ this.shadowRoot.getElementById(this._scrollTarget) ||
790
+ this.shadowRoot.querySelector(`[node-id="${this._scrollTarget}"]`);
791
+ if (target) {
792
+ window.requestAnimationFrame(() =>
793
+ setTimeout(() => {
794
+ target.scrollIntoView({ block: 'nearest' });
795
+ }, 400),
796
+ );
840
797
  }
798
+ this._scrollTarget = null;
799
+ });
841
800
  }
842
801
 
843
- _doReplaceContent(elem, resp, direction) {
844
- if (this.columnSeparator) {
845
- this._replaceColumns(elem);
802
+ this.next = resp.next;
803
+ this.nextId = resp.nextId;
804
+ this.previous = resp.previous;
805
+ this.previousId = resp.previousId;
806
+ this.nodeId = resp.root;
807
+ this.switchView = resp.switchView;
808
+
809
+ this.updateComplete.then(() => {
810
+ const view = this.shadowRoot.getElementById('view');
811
+ this._applyToggles(view);
812
+ this._fixLinks(view);
813
+ typesetMath(view);
814
+
815
+ const eventOptions = {
816
+ data: resp,
817
+ root: view,
818
+ params: loader.params,
819
+ id: this.xmlId,
820
+ position: this.nodeId,
821
+ };
822
+ this.emitTo('pb-update', eventOptions);
823
+ this._scroll();
824
+ });
825
+
826
+ this.emitTo('pb-end-update', null);
827
+ }
828
+
829
+ _replaceContent(resp, direction) {
830
+ const fragment = document.createDocumentFragment();
831
+ const elem = document.createElement('div');
832
+ // elem.style.opacity = 0; //hide it - animation has to make sure to blend it in
833
+ fragment.appendChild(elem);
834
+ elem.innerHTML = resp.content;
835
+
836
+ // if before-update-event is set, we do not replace the content immediately,
837
+ // but emit an event
838
+ if (this.beforeUpdate) {
839
+ this.emitTo(this.beforeUpdate, {
840
+ data: resp,
841
+ root: elem,
842
+ render: content => {
843
+ this._doReplaceContent(content, resp, direction);
844
+ },
845
+ });
846
+ } else {
847
+ this._doReplaceContent(elem, resp, direction);
848
+ }
849
+ }
850
+
851
+ _doReplaceContent(elem, resp, direction) {
852
+ if (this.columnSeparator) {
853
+ this._replaceColumns(elem);
854
+ this._loading = false;
855
+ } else if (this.infiniteScroll) {
856
+ elem.className = 'scroll-fragment';
857
+ elem.setAttribute('data-root', resp.root);
858
+ if (resp.next) {
859
+ elem.setAttribute('data-next', resp.next);
860
+ }
861
+ if (resp.previous) {
862
+ elem.setAttribute('data-previous', resp.previous);
863
+ }
864
+ let refNode;
865
+ switch (direction) {
866
+ case 'backward':
867
+ refNode = this._content.firstElementChild;
868
+ this._chunks.unshift(elem);
869
+ this.updateComplete.then(() => {
870
+ refNode.scrollIntoView(true);
846
871
  this._loading = false;
847
- } else if (this.infiniteScroll) {
848
- elem.className = 'scroll-fragment';
849
- elem.setAttribute('data-root', resp.root);
850
- if (resp.next) {
851
- elem.setAttribute('data-next', resp.next);
852
- }
853
- if (resp.previous) {
854
- elem.setAttribute('data-previous', resp.previous);
855
- }
856
- let refNode;
857
- switch (direction) {
858
- case 'backward':
859
- refNode = this._content.firstElementChild;
860
- this._chunks.unshift(elem);
861
- this.updateComplete.then(() => {
862
- refNode.scrollIntoView(true);
863
- this._loading = false;
864
- this._checkVisibility();
865
- this._scrollObserver.observe(this._bottomObserver);
866
- this._scrollObserver.observe(this._topObserver);
867
- });
868
- this._content.insertBefore(elem, refNode);
869
- break;
870
- default:
871
- this.updateComplete.then(() => {
872
- this._loading = false;
873
- this._checkVisibility();
874
- this._scrollObserver.observe(this._bottomObserver);
875
- this._scrollObserver.observe(this._topObserver);
876
- });
877
- this._chunks.push(elem);
878
- this._content.appendChild(elem);
879
- break;
880
- }
881
- } else {
882
- this._content = elem;
872
+ this._checkVisibility();
873
+ this._scrollObserver.observe(this._bottomObserver);
874
+ this._scrollObserver.observe(this._topObserver);
875
+ });
876
+ this._content.insertBefore(elem, refNode);
877
+ break;
878
+ default:
879
+ this.updateComplete.then(() => {
883
880
  this._loading = false;
884
- }
885
-
886
- if (this.appendFootnotes) {
887
- const footnotes = document.createElement('div');
888
- if (resp.footnotes) {
889
- footnotes.innerHTML = resp.footnotes;
890
- }
891
- this._footnotes = footnotes;
892
- }
893
-
894
- this._initFootnotes(this._footnotes);
881
+ this._checkVisibility();
882
+ this._scrollObserver.observe(this._bottomObserver);
883
+ this._scrollObserver.observe(this._topObserver);
884
+ });
885
+ this._chunks.push(elem);
886
+ this._content.appendChild(elem);
887
+ break;
888
+ }
889
+ } else {
890
+ this._content = elem;
891
+ this._loading = false;
892
+ }
895
893
 
896
- return elem;
894
+ if (this.appendFootnotes) {
895
+ const footnotes = document.createElement('div');
896
+ if (resp.footnotes) {
897
+ footnotes.innerHTML = resp.footnotes;
898
+ }
899
+ this._footnotes = footnotes;
897
900
  }
898
901
 
899
- _checkVisibility() {
900
- const bottomActive = this._chunks[this._chunks.length - 1].hasAttribute('data-next');
901
- this._bottomObserver.style.display = bottomActive ? '' : 'none';
902
+ this._initFootnotes(this._footnotes);
902
903
 
903
- const topActive = this._chunks[0].hasAttribute('data-previous');
904
- this._topObserver.style.display = topActive ? '' : 'none';
905
- }
904
+ return elem;
905
+ }
906
906
 
907
- _replaceColumns(elem) {
908
- let cb;
909
- if (this.columnSeparator) {
910
- const cbs = elem.querySelectorAll(this.columnSeparator);
911
- // use last separator only
912
- if (cbs.length > 1) {
913
- cb = cbs[cbs.length - 1];
914
- }
915
- }
907
+ _checkVisibility() {
908
+ const bottomActive = this._chunks[this._chunks.length - 1].hasAttribute('data-next');
909
+ this._bottomObserver.style.display = bottomActive ? '' : 'none';
916
910
 
917
- if (!cb) {
918
- this._content = elem;
919
- } else {
920
- const fragmentBefore = this._getFragmentBefore(elem, cb);
921
- const fragmentAfter = this._getFragmentAfter(elem, cb);
922
- if (this.direction === 'ltr') {
923
- this._column1 = fragmentBefore;
924
- this._column2 = fragmentAfter;
925
- } else {
926
- this._column2 = fragmentBefore;
927
- this._column1 = fragmentAfter;
928
- }
929
- }
930
- }
911
+ const topActive = this._chunks[0].hasAttribute('data-previous');
912
+ this._topObserver.style.display = topActive ? '' : 'none';
913
+ }
931
914
 
932
- _scroll() {
933
- if (this.noScroll) {
934
- return;
935
- }
936
- if (registry.hash) {
937
- const target = this.shadowRoot.getElementById(registry.hash.substring(1));
938
- console.log('hash target: %o', target);
939
- if (target) {
940
- window.requestAnimationFrame(() =>
941
- setTimeout(() => {
942
- target.scrollIntoView({ block: "center", inline: "nearest" });
943
- }, 400)
944
- );
945
- }
946
- }
915
+ _replaceColumns(elem) {
916
+ let cb;
917
+ if (this.columnSeparator) {
918
+ const cbs = elem.querySelectorAll(this.columnSeparator);
919
+ // use last separator only
920
+ if (cbs.length > 1) {
921
+ cb = cbs[cbs.length - 1];
922
+ }
947
923
  }
948
924
 
949
- _scrollToElement(ev, link) {
950
- const target = this.shadowRoot.getElementById(link.hash.substring(1));
951
- if (target) {
952
- ev.preventDefault();
953
- console.log('<pb-view> Scrolling to element %s', target.id);
954
- target.scrollIntoView({ block: "center", inline: "nearest" });
955
- }
925
+ if (!cb) {
926
+ this._content = elem;
927
+ } else {
928
+ const fragmentBefore = this._getFragmentBefore(elem, cb);
929
+ const fragmentAfter = this._getFragmentAfter(elem, cb);
930
+ if (this.direction === 'ltr') {
931
+ this._column1 = fragmentBefore;
932
+ this._column2 = fragmentAfter;
933
+ } else {
934
+ this._column2 = fragmentBefore;
935
+ this._column1 = fragmentAfter;
936
+ }
956
937
  }
938
+ }
957
939
 
958
- _updateStyles() {
959
- let link = document.createElement('link');
960
- link.setAttribute('rel', 'stylesheet');
961
- link.setAttribute('type', 'text/css');
962
- if (this.static !== null) {
963
- link.setAttribute('href', `${this.static}/css/${this.getOdd()}.css`);
964
- } else {
965
- link.setAttribute('href', `${this.getEndpoint()}/transform/${this.getOdd()}.css`);
966
- }
967
- this._style = link;
968
- }
969
-
970
- _fixLinks(content) {
971
- if (this.fixLinks) {
972
- const doc = this.getDocument();
973
- const base = this.toAbsoluteURL(doc.path);
974
- content.querySelectorAll('img').forEach((image) => {
975
- const oldSrc = image.getAttribute('src');
976
- const src = new URL(oldSrc, base);
977
- image.src = src;
978
- });
979
- content.querySelectorAll('a').forEach((link) => {
980
- const oldHref = link.getAttribute('href');
981
- if (oldHref === link.hash) {
982
- link.addEventListener('click', (ev) => this._scrollToElement(ev, link));
983
- } else {
984
- const href = new URL(oldHref, base);
985
- link.href = href;
986
- }
987
- });
940
+ _scroll() {
941
+ if (this.noScroll) {
942
+ return;
943
+ }
944
+ if (registry.hash) {
945
+ const target = this.shadowRoot.getElementById(registry.hash.substring(1));
946
+ console.log('hash target: %o', target);
947
+ if (target) {
948
+ window.requestAnimationFrame(() =>
949
+ setTimeout(() => {
950
+ target.scrollIntoView({ block: 'center', inline: 'nearest' });
951
+ }, 400),
952
+ );
953
+ }
954
+ }
955
+ }
956
+
957
+ _scrollToElement(ev, link) {
958
+ const target = this.shadowRoot.getElementById(link.hash.substring(1));
959
+ if (target) {
960
+ ev.preventDefault();
961
+ console.log('<pb-view> Scrolling to element %s', target.id);
962
+ target.scrollIntoView({ block: 'center', inline: 'nearest' });
963
+ }
964
+ }
965
+
966
+ _updateStyles() {
967
+ const link = document.createElement('link');
968
+ link.setAttribute('rel', 'stylesheet');
969
+ link.setAttribute('type', 'text/css');
970
+ if (this.static !== null) {
971
+ link.setAttribute('href', `${this.static}/css/${this.getOdd()}.css`);
972
+ } else {
973
+ link.setAttribute('href', `${this.getEndpoint()}/transform/${this.getOdd()}.css`);
974
+ }
975
+ this._style = link;
976
+ }
977
+
978
+ _fixLinks(content) {
979
+ if (this.fixLinks) {
980
+ const doc = this.getDocument();
981
+ const base = this.toAbsoluteURL(doc.path);
982
+ content.querySelectorAll('img').forEach(image => {
983
+ const oldSrc = image.getAttribute('src');
984
+ const src = new URL(oldSrc, base);
985
+ image.src = src;
986
+ });
987
+ content.querySelectorAll('a').forEach(link => {
988
+ const oldHref = link.getAttribute('href');
989
+ if (oldHref === link.hash) {
990
+ link.addEventListener('click', ev => this._scrollToElement(ev, link));
988
991
  } else {
989
- content.querySelectorAll('a').forEach((link) => {
990
- const oldHref = link.getAttribute('href');
991
- if (oldHref === link.hash) {
992
- link.addEventListener('click', (ev) => this._scrollToElement(ev, link));
993
- }
994
- });
992
+ const href = new URL(oldHref, base);
993
+ link.href = href;
995
994
  }
996
- }
997
-
998
- _initFootnotes(content) {
999
- if (content) {
1000
- content.querySelectorAll('.note, .fn-back').forEach(elem => {
1001
- elem.addEventListener('click', (ev) => {
1002
- ev.preventDefault();
1003
- const fn = this.shadowRoot.getElementById('content').querySelector(elem.hash);
1004
- if (fn) {
1005
- fn.scrollIntoView();
1006
- }
1007
- });
1008
- });
995
+ });
996
+ } else {
997
+ content.querySelectorAll('a').forEach(link => {
998
+ const oldHref = link.getAttribute('href');
999
+ if (oldHref === link.hash) {
1000
+ link.addEventListener('click', ev => this._scrollToElement(ev, link));
1009
1001
  }
1002
+ });
1010
1003
  }
1011
-
1012
- _getParameters() {
1013
- const params = [];
1014
- this.querySelectorAll('pb-param').forEach(function (param) {
1015
- params['user.' + param.getAttribute('name')] = param.getAttribute('value');
1004
+ }
1005
+
1006
+ _initFootnotes(content) {
1007
+ if (content) {
1008
+ content.querySelectorAll('.note, .fn-back').forEach(elem => {
1009
+ elem.addEventListener('click', ev => {
1010
+ ev.preventDefault();
1011
+ const fn = this.shadowRoot.getElementById('content').querySelector(elem.hash);
1012
+ if (fn) {
1013
+ fn.scrollIntoView();
1014
+ }
1016
1015
  });
1017
- // add parameters for features set with pb-toggle-feature
1018
- for (const [key, value] of Object.entries(this._features)) {
1019
- params['user.' + key] = value;
1020
- }
1021
- // add parameters for user-defined parameters supplied via pb-link
1022
- if (this._additionalParams) {
1023
- for (const [key, value] of Object.entries(this._additionalParams)) {
1024
- params[key] = value;
1025
- }
1026
- }
1027
- return params;
1016
+ });
1028
1017
  }
1029
-
1030
- /**
1031
- * Return the parameter object which would be passed to the server by this component
1032
- */
1033
- getParameters(pos) {
1034
- pos = pos || this.nodeId;
1035
- const doc = this.getDocument();
1036
- const params = this._getParameters();
1037
- if (!this.minApiVersion('1.0.0')) {
1038
- params.doc = doc.path;
1039
- }
1040
- params.odd = this.getOdd() + '.odd';
1041
- params.view = this.getView();
1042
- params.fill = this.fill;
1043
- if (pos) {
1044
- params['root'] = pos;
1045
- }
1046
- if (this.xpath) {
1047
- params.xpath = this.xpath;
1048
- }
1049
- if (this.xmlId) {
1050
- params.id = this.xmlId;
1051
- }
1052
- if (!this.suppressHighlight && this.highlight) {
1053
- params.highlight = "yes";
1054
- }
1055
- if (this.map) {
1056
- params.map = this.map;
1057
- }
1058
-
1059
- return params;
1018
+ }
1019
+
1020
+ _getParameters() {
1021
+ const params = [];
1022
+ this.querySelectorAll('pb-param').forEach(param => {
1023
+ params[`user.${param.getAttribute('name')}`] = param.getAttribute('value');
1024
+ });
1025
+ // add parameters for features set with pb-toggle-feature
1026
+ for (const [key, value] of Object.entries(this._features)) {
1027
+ params[`user.${key}`] = value;
1060
1028
  }
1061
-
1062
- _applyToggles(elem) {
1063
- for (const [selector, setting] of Object.entries(this._selector)) {
1064
- elem.querySelectorAll(selector).forEach(node => {
1065
- const command = setting.command || 'toggle';
1066
- if (node.command) {
1067
- node.command(command, setting.state);
1068
- }
1069
- if (setting.state) {
1070
- node.classList.add(command);
1071
- } else {
1072
- node.classList.remove(command);
1073
- }
1074
- });
1075
- }
1029
+ // add parameters for user-defined parameters supplied via pb-link
1030
+ if (this._additionalParams) {
1031
+ for (const [key, value] of Object.entries(this._additionalParams)) {
1032
+ params[key] = value;
1033
+ }
1076
1034
  }
1077
-
1078
- /**
1079
- * Load a part of the document identified by the given eXist nodeId
1080
- *
1081
- * @param {String} nodeId The eXist nodeId of the root element to load
1082
- */
1083
- goto(nodeId) {
1084
- this._load(nodeId);
1085
- }
1086
-
1087
- /**
1088
- * Load a part of the document identified by the given xml:id
1089
- *
1090
- * @param {String} xmlId The xml:id to be loaded
1091
- */
1092
- gotoId(xmlId) {
1093
- this.xmlId = xmlId;
1094
- this._load();
1095
- }
1096
-
1097
- /**
1098
- * Navigate the document either forward or backward and refresh the view.
1099
- * The navigation method is determined by property `view`.
1100
- *
1101
- * @param {string} direction either `backward` or `forward`
1102
- */
1103
- navigate(direction) {
1104
- // in single view mode there should be no navigation
1105
- if (this.getView() === 'single') {
1106
- return;
1107
- }
1108
-
1109
- this.lastDirection = direction;
1110
-
1111
- if (direction === 'backward') {
1112
- if (this.previous) {
1113
- if (!this.disableHistory && !this.map) {
1114
- registry.commit(this, {
1115
- id: this.previousId || null,
1116
- root: this.previousId ? null : this.previous
1117
- });
1118
- }
1119
- this.xmlId = this.previousId;
1120
- this._load(this.xmlId ? null : this.previous, direction);
1121
- }
1122
- } else if (this.next) {
1123
- if (!this.disableHistory && !this.map) {
1124
- registry.commit(this, {
1125
- id: this.nextId || null,
1126
- root: this.nextId ? null : this.next
1127
- });
1128
- }
1129
- this.xmlId = this.nextId;
1130
- this._load(this.xmlId ? null : this.next, direction);
1131
- }
1035
+ return params;
1036
+ }
1037
+
1038
+ /**
1039
+ * Return the parameter object which would be passed to the server by this component
1040
+ */
1041
+ getParameters(pos) {
1042
+ pos = pos || this.nodeId;
1043
+ const doc = this.getDocument();
1044
+ const params = this._getParameters();
1045
+ if (!this.minApiVersion('1.0.0')) {
1046
+ params.doc = doc.path;
1132
1047
  }
1133
-
1134
- /**
1135
- * Check the number of fragments which were already loaded in infinite
1136
- * scroll mode. If they exceed `infiniteScrollMax`, remove either the
1137
- * first or last fragment from the DOM, depending on the scroll direction.
1138
- *
1139
- * @param {string} direction either 'forward' or 'backward'
1140
- */
1141
- _checkChunks(direction) {
1142
- if (!this.infiniteScroll || this.infiniteScrollMax === 0) {
1143
- return;
1144
- }
1145
-
1146
- if (this._chunks.length === this.infiniteScrollMax) {
1147
- switch (direction) {
1148
- case 'forward':
1149
- this._content.removeChild(this._chunks.shift());
1150
- break;
1151
- default:
1152
- this._content.removeChild(this._chunks.pop());
1153
- break;
1154
- }
1155
- }
1156
- this.emitTo('pb-navigate', {
1157
- direction,
1158
- source: this
1159
- });
1048
+ params.odd = `${this.getOdd()}.odd`;
1049
+ params.view = this.getView();
1050
+ params.fill = this.fill;
1051
+ if (pos) {
1052
+ params.root = pos;
1160
1053
  }
1161
-
1162
- /**
1163
- * Zoom the displayed content by increasing or decreasing font size.
1164
- *
1165
- * @param {string} direction either `in` or `out`
1166
- */
1167
- zoom(direction) {
1168
- const view = this.shadowRoot.getElementById('view');
1169
- const fontSize = window.getComputedStyle(view).getPropertyValue('font-size');
1170
- const size = parseInt(fontSize.replace(/^(\d+)px/, "$1"));
1171
-
1172
- if (direction === 'in') {
1173
- view.style.fontSize = (size + 1) + 'px';
1174
- } else {
1175
- view.style.fontSize = (size - 1) + 'px';
1176
- }
1054
+ if (this.xpath) {
1055
+ params.xpath = this.xpath;
1056
+ }
1057
+ if (this.xmlId) {
1058
+ params.id = this.xmlId;
1059
+ }
1060
+ if (!this.suppressHighlight && this.highlight) {
1061
+ params.highlight = 'yes';
1062
+ }
1063
+ if (this.map) {
1064
+ params.map = this.map;
1177
1065
  }
1178
1066
 
1179
- toggleFeature(ev) {
1180
- const properties = registry.getState(this);
1181
- if (properties) {
1182
- this._setState(properties);
1067
+ return params;
1068
+ }
1183
1069
 
1070
+ _applyToggles(elem) {
1071
+ for (const [selector, setting] of Object.entries(this._selector)) {
1072
+ elem.querySelectorAll(selector).forEach(node => {
1073
+ const command = setting.command || 'toggle';
1074
+ if (node.command) {
1075
+ node.command(command, setting.state);
1184
1076
  }
1185
-
1186
- if (ev.detail.refresh) {
1187
- this._updateStyles();
1188
- this._load();
1077
+ if (setting.state) {
1078
+ node.classList.add(command);
1189
1079
  } else {
1190
- const view = this.shadowRoot.getElementById('view');
1191
- this._applyToggles(view);
1080
+ node.classList.remove(command);
1192
1081
  }
1193
- registry.commit(this, properties);
1082
+ });
1194
1083
  }
1195
-
1196
- _setState(properties) {
1197
- for (const [key, value] of Object.entries(properties)) {
1198
- // check if URL template needs the parameter and if
1199
- // yes, add it to the additional parameter list
1200
- if (registry.pathParams.has(key)) {
1201
- this._additionalParams[key] = value;
1202
- } else {
1203
- switch (key) {
1204
- case 'odd':
1205
- case 'view':
1206
- case 'columnSeparator':
1207
- case 'xpath':
1208
- case 'nodeId':
1209
- case 'path':
1210
- case 'root':
1211
- break;
1212
- default:
1213
- this._features[key] = value;
1214
- break;
1215
- }
1216
- }
1217
- }
1218
- if (properties.odd && !this.getAttribute('odd')) {
1219
- this.odd = properties.odd;
1220
- }
1221
- if (properties.view && !this.getAttribute('view')) {
1222
- this.view = properties.view;
1223
- if (this.view === 'single') {
1224
- // when switching to single view, clear current node id
1225
- this.nodeId = null;
1226
- } else {
1227
- // otherwise use value for alternate view returned from server
1228
- this.nodeId = this.switchView;
1229
- }
1230
- }
1231
- if (properties.fill && !this.getAttribute('fill')) {
1232
- this.fill = properties.fill;
1233
- }
1234
- if (properties.xpath && !this.getAttribute('xpath')) {
1235
- this.xpath = properties.xpath;
1236
- }
1237
- if (properties.hasOwnProperty('columnSeparator')) {
1238
- this.columnSeparator = properties.columnSeparator;
1239
- }
1240
- this.xmlId = (!this.getAttribute('xml-id') && properties.id) || this.xmlId;
1241
- this.nodeId = (!this.getAttribute('xml-id') && properties.root) || null;
1242
-
1243
- if (properties.path) {
1244
- this.getDocument().path = properties.path;
1245
- }
1246
-
1247
- if (properties.selectors) {
1248
- properties.selectors.forEach(sc => {
1249
- this._selector[sc.selector] = {
1250
- state: sc.state,
1251
- command: sc.command || 'toggle'
1252
- };
1253
- });
1254
- }
1084
+ }
1085
+
1086
+ /**
1087
+ * Load a part of the document identified by the given eXist nodeId
1088
+ *
1089
+ * @param {String} nodeId The eXist nodeId of the root element to load
1090
+ */
1091
+ goto(nodeId) {
1092
+ this._load(nodeId);
1093
+ }
1094
+
1095
+ /**
1096
+ * Load a part of the document identified by the given xml:id
1097
+ *
1098
+ * @param {String} xmlId The xml:id to be loaded
1099
+ */
1100
+ gotoId(xmlId) {
1101
+ this.xmlId = xmlId;
1102
+ this._load();
1103
+ }
1104
+
1105
+ /**
1106
+ * Navigate the document either forward or backward and refresh the view.
1107
+ * The navigation method is determined by property `view`.
1108
+ *
1109
+ * @param {string} direction either `backward` or `forward`
1110
+ */
1111
+ navigate(direction) {
1112
+ // in single view mode there should be no navigation
1113
+ if (this.getView() === 'single') {
1114
+ return;
1255
1115
  }
1256
1116
 
1257
- _getFragmentBefore(node, ms) {
1258
- const range = document.createRange();
1259
- range.setStartBefore(node);
1260
- range.setEndBefore(ms);
1261
-
1262
- return range.cloneContents();
1117
+ this.lastDirection = direction;
1118
+
1119
+ if (direction === 'backward') {
1120
+ if (this.previous) {
1121
+ if (!this.disableHistory && !this.map) {
1122
+ registry.commit(this, {
1123
+ id: this.previousId || null,
1124
+ root: this.previousId ? null : this.previous,
1125
+ });
1126
+ }
1127
+ this.xmlId = this.previousId;
1128
+ this._load(this.xmlId ? null : this.previous, direction);
1129
+ }
1130
+ } else if (this.next) {
1131
+ if (!this.disableHistory && !this.map) {
1132
+ registry.commit(this, {
1133
+ id: this.nextId || null,
1134
+ root: this.nextId ? null : this.next,
1135
+ });
1136
+ }
1137
+ this.xmlId = this.nextId;
1138
+ this._load(this.xmlId ? null : this.next, direction);
1263
1139
  }
1264
-
1265
- _getFragmentAfter(node, ms) {
1266
- const range = document.createRange();
1267
- range.setStartBefore(ms);
1268
- range.setEndAfter(node);
1269
-
1270
- return range.cloneContents();
1140
+ }
1141
+
1142
+ /**
1143
+ * Check the number of fragments which were already loaded in infinite
1144
+ * scroll mode. If they exceed `infiniteScrollMax`, remove either the
1145
+ * first or last fragment from the DOM, depending on the scroll direction.
1146
+ *
1147
+ * @param {string} direction either 'forward' or 'backward'
1148
+ */
1149
+ _checkChunks(direction) {
1150
+ if (!this.infiniteScroll || this.infiniteScrollMax === 0) {
1151
+ return;
1271
1152
  }
1272
1153
 
1273
- _updateSource(newVal, oldVal) {
1274
- if (typeof oldVal !== 'undefined' && newVal !== oldVal) {
1275
- this.xpath = null;
1276
- this.odd = null;
1277
- this.xmlId = null;
1278
- this.nodeId = null;
1279
- }
1154
+ if (this._chunks.length === this.infiniteScrollMax) {
1155
+ switch (direction) {
1156
+ case 'forward':
1157
+ this._content.removeChild(this._chunks.shift());
1158
+ break;
1159
+ default:
1160
+ this._content.removeChild(this._chunks.pop());
1161
+ break;
1162
+ }
1280
1163
  }
1164
+ this.emitTo('pb-navigate', {
1165
+ direction,
1166
+ source: this,
1167
+ });
1168
+ }
1169
+
1170
+ /**
1171
+ * Zoom the displayed content by increasing or decreasing font size.
1172
+ *
1173
+ * @param {string} direction either `in` or `out`
1174
+ */
1175
+ zoom(direction) {
1176
+ const view = this.shadowRoot.getElementById('view');
1177
+ const fontSize = window.getComputedStyle(view).getPropertyValue('font-size');
1178
+ const size = parseInt(fontSize.replace(/^(\d+)px/, '$1'));
1179
+
1180
+ if (direction === 'in') {
1181
+ view.style.fontSize = `${size + 1}px`;
1182
+ } else {
1183
+ view.style.fontSize = `${size - 1}px`;
1184
+ }
1185
+ }
1281
1186
 
1282
- static get styles() {
1283
- return css`
1284
- :host {
1285
- display: block;
1286
- background: transparent;
1287
- }
1288
-
1289
- :host(.noscroll) {
1290
- scrollbar-width: none; /* Firefox 64 */
1291
- -ms-overflow-style: none;
1292
- }
1293
-
1294
- :host(.noscroll)::-webkit-scrollbar {
1295
- width: 0 !important;
1296
- display: none;
1297
- }
1298
-
1299
- [id] {
1300
- scroll-margin-top: var(--pb-view-scroll-margin-top);
1301
- }
1302
-
1303
- #view {
1304
- position: relative;
1305
- }
1306
-
1307
- .columns {
1308
- display: grid;
1309
- grid-template-columns: calc(50% - var(--pb-view-column-gap, 10px) / 2) calc(50% - var(--pb-view-column-gap, 10px) / 2);
1310
- grid-column-gap: var(--pb-view-column-gap, 10px);
1311
- }
1312
-
1313
- .margin-note {
1314
- display: none;
1315
- }
1316
-
1317
- @media (min-width: 769px) {
1318
- .content.margin-right {
1319
- margin-right: 200px;
1320
- }
1321
-
1322
- .margin-note {
1323
- background: rgba(153, 153, 153, 0.2);
1324
- display: block;
1325
- font-size: small;
1326
- margin-right: -200px;
1327
- margin-bottom: 5px;
1328
- padding: 5px 0;
1329
- float: right;
1330
- clear: both;
1331
- width: 180px;
1332
- }
1333
-
1334
- .margin-note .n {
1335
- color: #777777;
1336
- }
1337
- }
1338
-
1339
- a[rel=footnote] {
1340
- font-size: var(--pb-footnote-font-size, var(--pb-content-font-size, 75%));
1341
- font-family: var(--pb-footnote-font-family, --pb-content-font-family);
1342
- vertical-align: super;
1343
- color: var(--pb-footnote-color, var(--pb-color-primary, #333333));
1344
- text-decoration: none;
1345
- padding: var(--pb-footnote-padding, 0 0 0 .25em);
1346
- }
1347
-
1348
- .list dt {
1349
- float: left;
1350
- }
1351
-
1352
- .footnote .fn-number {
1353
- float: left;
1354
- font-size: var(--pb-footnote-font-size, var(--pb-content-font-size, 75%));
1355
- }
1356
-
1357
- .observer {
1358
- display: block;
1359
- width: 100%;
1360
- height: var(--pb-view-loader-height, 16px);
1361
- font-family: var(--pb-view-loader-font, --pb-base-font);
1362
- color: var(--pb-view-loader-color, black);
1363
- background: var(--pb-view-loader-background, #909090);
1364
- 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));
1365
- animation-name: loader;
1366
- animation-timing-function: linear;
1367
- animation-duration: 2s;
1368
- animation-fill-mode: forwards;
1369
- animation-iteration-count: infinite;
1370
- }
1371
-
1372
- @keyframes loader {
1373
- 0% {
1374
- background-position: 3rem 0;
1375
- }
1187
+ toggleFeature(ev) {
1188
+ const properties = registry.getState(this);
1189
+ if (properties) {
1190
+ this._setState(properties);
1191
+ }
1376
1192
 
1377
- 100% {
1378
- background-position: 0 0;
1379
- }
1380
- }
1193
+ if (ev.detail.refresh) {
1194
+ this._updateStyles();
1195
+ this._load();
1196
+ } else {
1197
+ const view = this.shadowRoot.getElementById('view');
1198
+ this._applyToggles(view);
1199
+ }
1200
+ registry.commit(this, properties);
1201
+ }
1202
+
1203
+ _setState(properties) {
1204
+ for (const [key, value] of Object.entries(properties)) {
1205
+ // check if URL template needs the parameter and if
1206
+ // yes, add it to the additional parameter list
1207
+ if (registry.pathParams.has(key)) {
1208
+ this._additionalParams[key] = value;
1209
+ } else {
1210
+ switch (key) {
1211
+ case 'odd':
1212
+ case 'view':
1213
+ case 'columnSeparator':
1214
+ case 'xpath':
1215
+ case 'nodeId':
1216
+ case 'path':
1217
+ case 'root':
1218
+ break;
1219
+ default:
1220
+ this._features[key] = value;
1221
+ break;
1222
+ }
1223
+ }
1224
+ }
1225
+ if (properties.odd && !this.getAttribute('odd')) {
1226
+ this.odd = properties.odd;
1227
+ }
1228
+ if (properties.view && !this.getAttribute('view')) {
1229
+ this.view = properties.view;
1230
+ if (this.view === 'single') {
1231
+ // when switching to single view, clear current node id
1232
+ this.nodeId = null;
1233
+ } else {
1234
+ // otherwise use value for alternate view returned from server
1235
+ this.nodeId = this.switchView;
1236
+ }
1237
+ }
1238
+ if (properties.fill && !this.getAttribute('fill')) {
1239
+ this.fill = properties.fill;
1240
+ }
1241
+ if (properties.xpath && !this.getAttribute('xpath')) {
1242
+ this.xpath = properties.xpath;
1243
+ }
1244
+ if (properties.hasOwnProperty('columnSeparator')) {
1245
+ this.columnSeparator = properties.columnSeparator;
1246
+ }
1247
+ this.xmlId = (!this.getAttribute('xml-id') && properties.id) || this.xmlId;
1248
+ this.nodeId = (!this.getAttribute('xml-id') && properties.root) || null;
1381
1249
 
1382
- .scroll-fragment {
1383
- animation: fadeIn ease 500ms;
1384
- }
1250
+ if (properties.path) {
1251
+ this.getDocument().path = properties.path;
1252
+ }
1385
1253
 
1386
- @keyframes fadeIn {
1387
- 0% {opacity:0;}
1388
- 100% {opacity:1;}
1389
- }
1390
- `;
1391
- }
1392
-
1393
- render() {
1394
- return [
1395
- html`
1396
- <div id="view" part="content">
1397
- ${this._style}
1398
- ${this.infiniteScroll ? html`<div id="top-observer" class="observer"></div>` : null}
1399
- <div class="columns">
1400
- <div id="column1">${this._column1}</div>
1401
- <div id="column2">${this._column2}</div>
1402
- </div>
1403
- <div id="content">${this._content}</div>
1404
- ${
1405
- this.infiniteScroll ?
1406
- html`<div id="bottom-observer" class="observer"></div>` :
1407
- null
1408
- }
1409
- <div id="footnotes" part="footnotes">${this._footnotes}</div>
1410
- </div>
1411
- <iron-ajax
1412
- id="loadContent"
1413
- verbose
1414
- handle-as="json"
1415
- method="get"
1416
- with-credentials
1417
- @response="${this._handleContent}"
1418
- @error="${this._handleError}"></iron-ajax>
1419
- `
1420
- ]
1254
+ if (properties.selectors) {
1255
+ properties.selectors.forEach(sc => {
1256
+ this._selector[sc.selector] = {
1257
+ state: sc.state,
1258
+ command: sc.command || 'toggle',
1259
+ };
1260
+ });
1261
+ }
1262
+ }
1263
+
1264
+ _getFragmentBefore(node, ms) {
1265
+ const range = document.createRange();
1266
+ range.setStartBefore(node);
1267
+ range.setEndBefore(ms);
1268
+
1269
+ return range.cloneContents();
1270
+ }
1271
+
1272
+ _getFragmentAfter(node, ms) {
1273
+ const range = document.createRange();
1274
+ range.setStartBefore(ms);
1275
+ range.setEndAfter(node);
1276
+
1277
+ return range.cloneContents();
1278
+ }
1279
+
1280
+ _updateSource(newVal, oldVal) {
1281
+ if (typeof oldVal !== 'undefined' && newVal !== oldVal) {
1282
+ this.xpath = null;
1283
+ this.odd = null;
1284
+ this.xmlId = null;
1285
+ this.nodeId = null;
1421
1286
  }
1287
+ }
1288
+
1289
+ static get styles() {
1290
+ return css`
1291
+ :host {
1292
+ display: block;
1293
+ background: transparent;
1294
+ }
1295
+
1296
+ :host(.noscroll) {
1297
+ scrollbar-width: none; /* Firefox 64 */
1298
+ -ms-overflow-style: none;
1299
+ }
1300
+
1301
+ :host(.noscroll)::-webkit-scrollbar {
1302
+ width: 0 !important;
1303
+ display: none;
1304
+ }
1305
+
1306
+ [id] {
1307
+ scroll-margin-top: var(--pb-view-scroll-margin-top);
1308
+ }
1309
+
1310
+ #view {
1311
+ position: relative;
1312
+ }
1313
+
1314
+ .columns {
1315
+ display: grid;
1316
+ grid-template-columns: calc(50% - var(--pb-view-column-gap, 10px) / 2) calc(
1317
+ 50% - var(--pb-view-column-gap, 10px) / 2
1318
+ );
1319
+ grid-column-gap: var(--pb-view-column-gap, 10px);
1320
+ }
1321
+
1322
+ .margin-note {
1323
+ display: none;
1324
+ }
1325
+
1326
+ @media (min-width: 769px) {
1327
+ .content.margin-right {
1328
+ margin-right: 200px;
1329
+ }
1330
+
1331
+ .margin-note {
1332
+ background: rgba(153, 153, 153, 0.2);
1333
+ display: block;
1334
+ font-size: small;
1335
+ margin-right: -200px;
1336
+ margin-bottom: 5px;
1337
+ padding: 5px 0;
1338
+ float: right;
1339
+ clear: both;
1340
+ width: 180px;
1341
+ }
1342
+
1343
+ .margin-note .n {
1344
+ color: #777777;
1345
+ }
1346
+ }
1347
+
1348
+ a[rel='footnote'] {
1349
+ font-size: var(--pb-footnote-font-size, var(--pb-content-font-size, 75%));
1350
+ font-family: var(--pb-footnote-font-family, --pb-content-font-family);
1351
+ vertical-align: super;
1352
+ color: var(--pb-footnote-color, var(--pb-color-primary, #333333));
1353
+ text-decoration: none;
1354
+ padding: var(--pb-footnote-padding, 0 0 0 0.25em);
1355
+ }
1356
+
1357
+ .list dt {
1358
+ float: left;
1359
+ }
1360
+
1361
+ .footnote .fn-number {
1362
+ float: left;
1363
+ font-size: var(--pb-footnote-font-size, var(--pb-content-font-size, 75%));
1364
+ }
1365
+
1366
+ .observer {
1367
+ display: block;
1368
+ width: 100%;
1369
+ height: var(--pb-view-loader-height, 16px);
1370
+ font-family: var(--pb-view-loader-font, --pb-base-font);
1371
+ color: var(--pb-view-loader-color, black);
1372
+ background: var(--pb-view-loader-background, #909090);
1373
+ background-image: var(
1374
+ --pb-view-loader-background-image,
1375
+ repeating-linear-gradient(
1376
+ 45deg,
1377
+ transparent,
1378
+ transparent 35px,
1379
+ rgba(255, 255, 255, 0.5) 35px,
1380
+ rgba(255, 255, 255, 0.5) 70px
1381
+ )
1382
+ );
1383
+ animation-name: loader;
1384
+ animation-timing-function: linear;
1385
+ animation-duration: 2s;
1386
+ animation-fill-mode: forwards;
1387
+ animation-iteration-count: infinite;
1388
+ }
1389
+
1390
+ @keyframes loader {
1391
+ 0% {
1392
+ background-position: 3rem 0;
1393
+ }
1394
+
1395
+ 100% {
1396
+ background-position: 0 0;
1397
+ }
1398
+ }
1399
+
1400
+ .scroll-fragment {
1401
+ animation: fadeIn ease 500ms;
1402
+ }
1403
+
1404
+ @keyframes fadeIn {
1405
+ 0% {
1406
+ opacity: 0;
1407
+ }
1408
+ 100% {
1409
+ opacity: 1;
1410
+ }
1411
+ }
1412
+ `;
1413
+ }
1414
+
1415
+ render() {
1416
+ return [
1417
+ html`
1418
+ <div id="view" part="content">
1419
+ ${this._style}
1420
+ ${this.infiniteScroll ? html`<div id="top-observer" class="observer"></div>` : null}
1421
+ <div class="columns">
1422
+ <div id="column1">${this._column1}</div>
1423
+ <div id="column2">${this._column2}</div>
1424
+ </div>
1425
+ <div id="content">${this._content}</div>
1426
+ ${this.infiniteScroll ? html`<div id="bottom-observer" class="observer"></div>` : null}
1427
+ <div id="footnotes" part="footnotes">${this._footnotes}</div>
1428
+ </div>
1429
+ <iron-ajax
1430
+ id="loadContent"
1431
+ verbose
1432
+ handle-as="json"
1433
+ method="get"
1434
+ with-credentials
1435
+ @response="${this._handleContent}"
1436
+ @error="${this._handleError}"
1437
+ ></iron-ajax>
1438
+ `,
1439
+ ];
1440
+ }
1422
1441
  }
1423
1442
 
1424
1443
  customElements.define('pb-view', PbView);