@teipublisher/pb-components 2.25.5 → 2.25.6

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