@teipublisher/pb-components 2.26.0-next-3.12 → 2.26.0-next-3.14

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 (136) 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 +43 -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-grid.html +19 -6
  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/{iron-form-277f9d42.js → iron-form-78b43d38.js} +1 -1
  14. package/dist/{paper-checkbox-4f410b1f.js → paper-checkbox-d16f23be.js} +44 -44
  15. package/dist/{paper-icon-button-0fb125c4.js → paper-icon-button-2cd9e0b4.js} +3 -3
  16. package/dist/{paper-listbox-c2468542.js → paper-listbox-2ad5c882.js} +7 -7
  17. package/dist/pb-code-editor.js +25 -20
  18. package/dist/pb-component-docs.js +58 -54
  19. package/dist/pb-components-bundle.js +2057 -2351
  20. package/dist/pb-edit-app.js +167 -107
  21. package/dist/pb-elements.json +45 -45
  22. package/dist/{pb-i18n-0611135a.js → pb-i18n-4cc00bfe.js} +1 -1
  23. package/dist/pb-leaflet-map.js +23 -23
  24. package/dist/pb-mei.js +56 -41
  25. package/dist/{pb-mixin-b1caa22e.js → pb-mixin-886ece32.js} +1 -1
  26. package/dist/pb-odd-editor.js +923 -756
  27. package/dist/pb-tify.js +2 -2
  28. package/dist/{vaadin-element-mixin-49ab4037.js → vaadin-element-mixin-c200b196.js} +179 -164
  29. package/gh-pages.js +5 -3
  30. package/i18n/common/pl.json +2 -2
  31. package/lib/openseadragon.min.js +1 -1
  32. package/package.json +2 -2
  33. package/pb-elements.json +45 -45
  34. package/src/assets/components.css +5 -5
  35. package/src/authority/airtable.js +20 -21
  36. package/src/authority/anton.js +129 -129
  37. package/src/authority/custom.js +23 -21
  38. package/src/authority/geonames.js +38 -32
  39. package/src/authority/gnd.js +47 -42
  40. package/src/authority/kbga.js +137 -134
  41. package/src/authority/metagrid.js +44 -46
  42. package/src/authority/reconciliation.js +66 -67
  43. package/src/authority/registry.js +4 -4
  44. package/src/docs/pb-component-docs.js +2 -2
  45. package/src/docs/pb-component-view.js +5 -5
  46. package/src/docs/pb-components-list.js +2 -2
  47. package/src/docs/pb-demo-snippet.js +2 -2
  48. package/src/dts-client.js +299 -297
  49. package/src/dts-select-endpoint.js +90 -82
  50. package/src/parse-date-service.js +184 -135
  51. package/src/pb-ajax.js +150 -146
  52. package/src/pb-authority-lookup.js +183 -146
  53. package/src/pb-autocomplete.js +292 -280
  54. package/src/pb-blacklab-highlight.js +264 -259
  55. package/src/pb-blacklab-results.js +236 -221
  56. package/src/pb-browse-docs.js +540 -475
  57. package/src/pb-browse.js +68 -65
  58. package/src/pb-clipboard.js +79 -76
  59. package/src/pb-code-editor.js +110 -102
  60. package/src/pb-code-highlight.js +209 -204
  61. package/src/pb-codepen.js +79 -72
  62. package/src/pb-collapse.js +212 -207
  63. package/src/pb-combo-box.js +190 -190
  64. package/src/pb-components-bundle.js +1 -1
  65. package/src/pb-custom-form.js +151 -149
  66. package/src/pb-dialog.js +94 -85
  67. package/src/pb-document.js +89 -90
  68. package/src/pb-download.js +210 -198
  69. package/src/pb-drawer.js +145 -148
  70. package/src/pb-edit-app.js +301 -229
  71. package/src/pb-edit-xml.js +98 -96
  72. package/src/pb-events.js +114 -107
  73. package/src/pb-facs-link.js +104 -102
  74. package/src/pb-facsimile.js +419 -414
  75. package/src/pb-formula.js +151 -153
  76. package/src/pb-geolocation.js +129 -131
  77. package/src/pb-grid-action.js +53 -56
  78. package/src/pb-grid.js +231 -228
  79. package/src/pb-highlight.js +140 -140
  80. package/src/pb-hotkeys.js +40 -42
  81. package/src/pb-i18n.js +101 -104
  82. package/src/pb-image-strip.js +84 -78
  83. package/src/pb-lang.js +132 -128
  84. package/src/pb-leaflet-map.js +488 -485
  85. package/src/pb-link.js +126 -124
  86. package/src/pb-load.js +431 -426
  87. package/src/pb-login.js +291 -248
  88. package/src/pb-manage-odds.js +364 -318
  89. package/src/pb-map-icon.js +89 -89
  90. package/src/pb-map-layer.js +85 -85
  91. package/src/pb-markdown.js +90 -99
  92. package/src/pb-media-query.js +74 -72
  93. package/src/pb-mei.js +306 -295
  94. package/src/pb-message.js +144 -144
  95. package/src/pb-mixin.js +269 -264
  96. package/src/pb-navigation.js +80 -82
  97. package/src/pb-observable.js +38 -38
  98. package/src/pb-odd-editor.js +1053 -955
  99. package/src/pb-odd-elementspec-editor.js +348 -297
  100. package/src/pb-odd-model-editor.js +1061 -901
  101. package/src/pb-odd-parameter-editor.js +200 -178
  102. package/src/pb-odd-rendition-editor.js +136 -124
  103. package/src/pb-page.js +431 -421
  104. package/src/pb-paginate.js +202 -190
  105. package/src/pb-panel.js +198 -182
  106. package/src/pb-popover-themes.js +7 -5
  107. package/src/pb-popover.js +296 -287
  108. package/src/pb-print-preview.js +127 -127
  109. package/src/pb-progress.js +51 -51
  110. package/src/pb-repeat.js +105 -104
  111. package/src/pb-restricted.js +84 -77
  112. package/src/pb-search.js +252 -241
  113. package/src/pb-select-feature.js +127 -120
  114. package/src/pb-select-odd.js +132 -124
  115. package/src/pb-select-template.js +89 -78
  116. package/src/pb-select.js +251 -227
  117. package/src/pb-split-list.js +179 -174
  118. package/src/pb-svg.js +80 -79
  119. package/src/pb-table-column.js +54 -54
  120. package/src/pb-table-grid.js +221 -203
  121. package/src/pb-tabs.js +61 -63
  122. package/src/pb-tify.js +154 -154
  123. package/src/pb-timeline.js +271 -229
  124. package/src/pb-toggle-feature.js +182 -175
  125. package/src/pb-upload.js +184 -174
  126. package/src/pb-version.js +30 -30
  127. package/src/pb-view-annotate.js +132 -98
  128. package/src/pb-view.js +1290 -1270
  129. package/src/pb-zoom.js +75 -59
  130. package/src/polymer-hack.js +1 -1
  131. package/src/search-result-service.js +256 -223
  132. package/src/seed-element.js +13 -20
  133. package/src/settings.js +4 -4
  134. package/src/theming.js +96 -96
  135. package/src/urls.js +289 -289
  136. 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,1387 @@ 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
+
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
+ this.subscribeTo('pb-refresh', this._refresh.bind(this));
522
+ }
523
+
524
+ /**
525
+ * Returns the ODD used to render content.
526
+ *
527
+ * @returns the ODD being used
528
+ */
529
+ getOdd() {
530
+ return this.odd || this.getDocument().odd || 'teipublisher';
531
+ }
532
+
533
+ getView() {
534
+ return this.view || this.getDocument().view || 'single';
535
+ }
536
+
537
+ /**
538
+ * Trigger an update of this element's content
539
+ */
540
+ forceUpdate() {
541
+ this._load(this.nodeId);
542
+ }
543
+
544
+ animate() {
545
+ // animate new element if 'animation' property is 'true'
546
+ if (this.animation) {
547
+ if (this.lastDirection === 'forward') {
548
+ anime({
549
+ targets: this.shadowRoot.getElementById('view'),
550
+ opacity: [0, 1],
551
+ translateX: [1000, 0],
552
+ duration: 300,
553
+ easing: 'linear',
441
554
  });
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
- }
555
+ } else {
556
+ anime({
557
+ targets: this.shadowRoot.getElementById('view'),
558
+ opacity: [0, 1],
559
+ translateX: [-1000, 0],
560
+ duration: 300,
561
+ easing: 'linear',
562
+ });
563
+ }
458
564
  }
565
+ }
459
566
 
460
- disconnectedCallback() {
461
- super.disconnectedCallback();
462
- if (this._scrollObserver) {
463
- this._scrollObserver.disconnect();
464
- }
567
+ enableScrollbar(enable) {
568
+ if (enable) {
569
+ this.classList.add('noscroll');
570
+ } else {
571
+ this.classList.remove('noscroll');
465
572
  }
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
- });
573
+ }
574
+
575
+ _refresh(ev) {
576
+ if (ev && ev.detail) {
577
+ if (
578
+ ev.detail.hash &&
579
+ !this.noScroll &&
580
+ !(ev.detail.id || ev.detail.path || ev.detail.odd || ev.detail.view || ev.detail.position)
581
+ ) {
582
+ // if only the scroll target has changed: scroll to the element without reloading
583
+ this._scrollTarget = ev.detail.hash;
584
+ const target = this.shadowRoot.getElementById(this._scrollTarget);
585
+ if (target) {
586
+ setTimeout(() => target.scrollIntoView({ block: 'nearest' }));
517
587
  }
588
+ return;
589
+ }
590
+ if (ev.detail.path) {
591
+ const doc = this.getDocument();
592
+ doc.path = ev.detail.path;
593
+ }
594
+ if (ev.detail.id) {
595
+ this.xmlId = ev.detail.id;
596
+ } else if (ev.detail.id == null) {
597
+ this.xmlId = null;
598
+ }
599
+ this.odd = ev.detail.odd || this.odd;
600
+ if (ev.detail.columnSeparator !== undefined) {
601
+ this.columnSeparator = ev.detail.columnSeparator;
602
+ }
603
+ this.view = ev.detail.view || this.getView();
604
+ this.fill = ev.detail.fill || this.fill;
605
+ if (ev.detail.xpath) {
606
+ this.xpath = ev.detail.xpath;
607
+ this.nodeId = null;
608
+ }
609
+ // clear nodeId if set to null
610
+ if (ev.detail.root === null) {
611
+ this.nodeId = null;
612
+ } else {
613
+ this.nodeId =
614
+ (ev.detail.position !== undefined ? ev.detail.position : ev.detail.root) || this.nodeId;
615
+ }
616
+
617
+ // check if the URL template needs any other parameters
618
+ // and set them on this._additionalParams
619
+ registry.pathParams.forEach(key => {
620
+ this._additionalParams[key] = ev.detail[key];
621
+ });
622
+
623
+ if (!this.noScroll) {
624
+ this._scrollTarget = ev.detail.hash;
625
+ }
518
626
  }
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";
627
+ this._updateStyles();
628
+ if (this.infiniteScroll) {
629
+ this._clear();
531
630
  }
631
+ this._load(this.nodeId);
632
+ }
532
633
 
533
- /**
534
- * Trigger an update of this element's content
535
- */
536
- forceUpdate() {
537
- this._load(this.nodeId);
634
+ _load(pos, direction) {
635
+ const doc = this.getDocument();
538
636
 
637
+ if (!doc.path) {
638
+ console.log('No path');
639
+ return;
539
640
  }
540
641
 
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
- }
642
+ if (this._loading) {
643
+ return;
562
644
  }
563
-
564
- enableScrollbar(enable) {
565
- if (enable) {
566
- this.classList.add('noscroll');
567
- } else {
568
- this.classList.remove('noscroll');
569
- }
645
+ this._loading = true;
646
+ const params = this.getParameters(pos);
647
+ if (direction) {
648
+ params._dir = direction;
570
649
  }
650
+ // this.$.view.style.opacity=0;
571
651
 
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
- }
652
+ this._doLoad(params);
653
+ }
608
654
 
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
- });
655
+ _doLoad(params) {
656
+ this.emitTo('pb-start-update', params);
614
657
 
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);
658
+ console.log('<pb-view> Loading view with params %o', params);
659
+ if (!this.infiniteScroll) {
660
+ this._clear();
624
661
  }
625
662
 
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);
663
+ if (this._scrollObserver) {
664
+ if (this._bottomObserver) {
665
+ this._scrollObserver.unobserve(this._bottomObserver);
666
+ }
667
+ if (this._topObserver) {
668
+ this._scrollObserver.unobserve(this._topObserver);
669
+ }
645
670
  }
646
671
 
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
- }
672
+ const loadContent = this.shadowRoot.getElementById('loadContent');
687
673
 
674
+ if (this.static !== null) {
675
+ this._staticUrl(params).then(url => {
688
676
  loadContent.url = url;
689
- loadContent.params = params;
690
677
  loadContent.generateRequest();
678
+ });
679
+ return;
691
680
  }
692
681
 
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
- }
682
+ if (!this.url) {
683
+ if (this.minApiVersion('1.0.0')) {
684
+ this.url = 'api/parts';
685
+ } else {
686
+ this.url = 'modules/lib/components.xql';
687
+ }
688
+ }
706
689
 
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
- }
690
+ let url = `${this.getEndpoint()}/${this.url}`;
717
691
 
718
- console.log('<pb-view> Static lookup %s: %s', url, file);
719
- return `${file}`;
692
+ if (this.minApiVersion('1.0.0')) {
693
+ url += `/${encodeURIComponent(this.getDocument().path)}/json`;
720
694
  }
721
695
 
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 = [];
696
+ loadContent.url = url;
697
+ loadContent.params = params;
698
+ loadContent.generateRequest();
699
+ }
700
+
701
+ /**
702
+ * Use a static URL to load pre-generated content.
703
+ */
704
+ async _staticUrl(params) {
705
+ function createKey(paramNames) {
706
+ const urlComponents = [];
707
+ paramNames.sort().forEach(key => {
708
+ if (params.hasOwnProperty(key)) {
709
+ urlComponents.push(`${key}=${params[key]}`);
710
+ }
711
+ });
712
+ return urlComponents.join('&');
733
713
  }
734
714
 
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
-
715
+ const index = await fetch(`index.json`).then(response => response.json());
716
+ const paramNames = ['odd', 'view', 'xpath', 'map'];
717
+ this.querySelectorAll('pb-param').forEach(param =>
718
+ paramNames.push(`user.${param.getAttribute('name')}`),
719
+ );
720
+ let url = params.id ? createKey([...paramNames, 'id']) : createKey([...paramNames, 'root']);
721
+ let file = index[url];
722
+ if (!file) {
723
+ url = createKey(paramNames);
724
+ file = index[url];
757
725
  }
758
726
 
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
- }
727
+ console.log('<pb-view> Static lookup %s: %s', url, file);
728
+ return `${file}`;
729
+ }
774
730
 
775
- this._replaceContent(resp, loader.params._dir);
731
+ _clear() {
732
+ if (this.infiniteScroll) {
733
+ this._content = document.createElement('div');
734
+ this._content.className = 'infinite-content';
735
+ } else {
736
+ this._content = null;
737
+ }
738
+ this._column1 = null;
739
+ this._column2 = null;
740
+ this._footnotes = null;
741
+ this._chunks = [];
742
+ }
743
+
744
+ _handleError() {
745
+ this._clear();
746
+ const loader = this.shadowRoot.getElementById('loadContent');
747
+ let message;
748
+ const { response } = loader.lastError;
749
+
750
+ if (response) {
751
+ message = response.description;
752
+ } else {
753
+ message = '<pb-i18n key="dialogs.serverError"></pb-i18n>';
754
+ }
776
755
 
777
- this.animate();
756
+ let content;
757
+ if (this.notFound != null) {
758
+ content = `<p>${this.notFound}</p>`;
759
+ } else {
760
+ content = `<p><pb-i18n key="dialogs.serverError"></pb-i18n>: ${message} </p>`;
761
+ }
778
762
 
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
- }
763
+ this._replaceContent({ content });
764
+ this.emitTo('pb-end-update');
765
+ }
793
766
 
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
- });
767
+ _handleContent() {
768
+ const loader = this.shadowRoot.getElementById('loadContent');
769
+ const resp = loader.lastResponse;
817
770
 
818
- this.emitTo('pb-end-update', null);
771
+ if (!resp) {
772
+ console.error('<pb-view> No response received');
773
+ return;
774
+ }
775
+ if (resp.error) {
776
+ if (this.notFound != null) {
777
+ this._content = this.notFound;
778
+ }
779
+ this.emitTo('pb-end-update', null);
780
+ return;
819
781
  }
820
782
 
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;
783
+ this._replaceContent(resp, loader.params._dir);
827
784
 
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);
785
+ this.animate();
786
+
787
+ if (this._scrollTarget) {
788
+ this.updateComplete.then(() => {
789
+ const target =
790
+ this.shadowRoot.getElementById(this._scrollTarget) ||
791
+ this.shadowRoot.querySelector(`[node-id="${this._scrollTarget}"]`);
792
+ if (target) {
793
+ window.requestAnimationFrame(() =>
794
+ setTimeout(() => {
795
+ target.scrollIntoView({ block: 'nearest' });
796
+ }, 400),
797
+ );
840
798
  }
799
+ this._scrollTarget = null;
800
+ });
841
801
  }
842
802
 
843
- _doReplaceContent(elem, resp, direction) {
844
- if (this.columnSeparator) {
845
- this._replaceColumns(elem);
803
+ this.next = resp.next;
804
+ this.nextId = resp.nextId;
805
+ this.previous = resp.previous;
806
+ this.previousId = resp.previousId;
807
+ this.nodeId = resp.root;
808
+ this.switchView = resp.switchView;
809
+
810
+ this.updateComplete.then(() => {
811
+ const view = this.shadowRoot.getElementById('view');
812
+ this._applyToggles(view);
813
+ this._fixLinks(view);
814
+ typesetMath(view);
815
+
816
+ const eventOptions = {
817
+ data: resp,
818
+ root: view,
819
+ params: loader.params,
820
+ id: this.xmlId,
821
+ position: this.nodeId,
822
+ };
823
+ this.emitTo('pb-update', eventOptions);
824
+ this._scroll();
825
+ });
826
+
827
+ this.emitTo('pb-end-update', null);
828
+ }
829
+
830
+ _replaceContent(resp, direction) {
831
+ const fragment = document.createDocumentFragment();
832
+ const elem = document.createElement('div');
833
+ // elem.style.opacity = 0; //hide it - animation has to make sure to blend it in
834
+ fragment.appendChild(elem);
835
+ elem.innerHTML = resp.content;
836
+
837
+ // if before-update-event is set, we do not replace the content immediately,
838
+ // but emit an event
839
+ if (this.beforeUpdate) {
840
+ this.emitTo(this.beforeUpdate, {
841
+ data: resp,
842
+ root: elem,
843
+ render: content => {
844
+ this._doReplaceContent(content, resp, direction);
845
+ },
846
+ });
847
+ } else {
848
+ this._doReplaceContent(elem, resp, direction);
849
+ }
850
+ }
851
+
852
+ _doReplaceContent(elem, resp, direction) {
853
+ if (this.columnSeparator) {
854
+ this._replaceColumns(elem);
855
+ this._loading = false;
856
+ } else if (this.infiniteScroll) {
857
+ elem.className = 'scroll-fragment';
858
+ elem.setAttribute('data-root', resp.root);
859
+ if (resp.next) {
860
+ elem.setAttribute('data-next', resp.next);
861
+ }
862
+ if (resp.previous) {
863
+ elem.setAttribute('data-previous', resp.previous);
864
+ }
865
+ let refNode;
866
+ switch (direction) {
867
+ case 'backward':
868
+ refNode = this._content.firstElementChild;
869
+ this._chunks.unshift(elem);
870
+ this.updateComplete.then(() => {
871
+ refNode.scrollIntoView(true);
846
872
  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;
873
+ this._checkVisibility();
874
+ this._scrollObserver.observe(this._bottomObserver);
875
+ this._scrollObserver.observe(this._topObserver);
876
+ });
877
+ this._content.insertBefore(elem, refNode);
878
+ break;
879
+ default:
880
+ this.updateComplete.then(() => {
883
881
  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);
882
+ this._checkVisibility();
883
+ this._scrollObserver.observe(this._bottomObserver);
884
+ this._scrollObserver.observe(this._topObserver);
885
+ });
886
+ this._chunks.push(elem);
887
+ this._content.appendChild(elem);
888
+ break;
889
+ }
890
+ } else {
891
+ this._content = elem;
892
+ this._loading = false;
893
+ }
895
894
 
896
- return elem;
895
+ if (this.appendFootnotes) {
896
+ const footnotes = document.createElement('div');
897
+ if (resp.footnotes) {
898
+ footnotes.innerHTML = resp.footnotes;
899
+ }
900
+ this._footnotes = footnotes;
897
901
  }
898
902
 
899
- _checkVisibility() {
900
- const bottomActive = this._chunks[this._chunks.length - 1].hasAttribute('data-next');
901
- this._bottomObserver.style.display = bottomActive ? '' : 'none';
903
+ this._initFootnotes(this._footnotes);
902
904
 
903
- const topActive = this._chunks[0].hasAttribute('data-previous');
904
- this._topObserver.style.display = topActive ? '' : 'none';
905
- }
905
+ return elem;
906
+ }
906
907
 
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
- }
908
+ _checkVisibility() {
909
+ const bottomActive = this._chunks[this._chunks.length - 1].hasAttribute('data-next');
910
+ this._bottomObserver.style.display = bottomActive ? '' : 'none';
916
911
 
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
- }
912
+ const topActive = this._chunks[0].hasAttribute('data-previous');
913
+ this._topObserver.style.display = topActive ? '' : 'none';
914
+ }
931
915
 
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
- }
916
+ _replaceColumns(elem) {
917
+ let cb;
918
+ if (this.columnSeparator) {
919
+ const cbs = elem.querySelectorAll(this.columnSeparator);
920
+ // use last separator only
921
+ if (cbs.length > 1) {
922
+ cb = cbs[cbs.length - 1];
923
+ }
947
924
  }
948
925
 
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
- }
926
+ if (!cb) {
927
+ this._content = elem;
928
+ } else {
929
+ const fragmentBefore = this._getFragmentBefore(elem, cb);
930
+ const fragmentAfter = this._getFragmentAfter(elem, cb);
931
+ if (this.direction === 'ltr') {
932
+ this._column1 = fragmentBefore;
933
+ this._column2 = fragmentAfter;
934
+ } else {
935
+ this._column2 = fragmentBefore;
936
+ this._column1 = fragmentAfter;
937
+ }
956
938
  }
939
+ }
957
940
 
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
- });
941
+ _scroll() {
942
+ if (this.noScroll) {
943
+ return;
944
+ }
945
+ if (registry.hash) {
946
+ const target = this.shadowRoot.getElementById(registry.hash.substring(1));
947
+ console.log('hash target: %o', target);
948
+ if (target) {
949
+ window.requestAnimationFrame(() =>
950
+ setTimeout(() => {
951
+ target.scrollIntoView({ block: 'center', inline: 'nearest' });
952
+ }, 400),
953
+ );
954
+ }
955
+ }
956
+ }
957
+
958
+ _scrollToElement(ev, link) {
959
+ const target = this.shadowRoot.getElementById(link.hash.substring(1));
960
+ if (target) {
961
+ ev.preventDefault();
962
+ console.log('<pb-view> Scrolling to element %s', target.id);
963
+ target.scrollIntoView({ block: 'center', inline: 'nearest' });
964
+ }
965
+ }
966
+
967
+ _updateStyles() {
968
+ const link = document.createElement('link');
969
+ link.setAttribute('rel', 'stylesheet');
970
+ link.setAttribute('type', 'text/css');
971
+ if (this.static !== null) {
972
+ link.setAttribute('href', `${this.static}/css/${this.getOdd()}.css`);
973
+ } else {
974
+ link.setAttribute('href', `${this.getEndpoint()}/transform/${this.getOdd()}.css`);
975
+ }
976
+ this._style = link;
977
+ }
978
+
979
+ _fixLinks(content) {
980
+ if (this.fixLinks) {
981
+ const doc = this.getDocument();
982
+ const base = this.toAbsoluteURL(doc.path);
983
+ content.querySelectorAll('img').forEach(image => {
984
+ const oldSrc = image.getAttribute('src');
985
+ const src = new URL(oldSrc, base);
986
+ image.src = src;
987
+ });
988
+ content.querySelectorAll('a').forEach(link => {
989
+ const oldHref = link.getAttribute('href');
990
+ if (oldHref === link.hash) {
991
+ link.addEventListener('click', ev => this._scrollToElement(ev, link));
988
992
  } 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
- });
993
+ const href = new URL(oldHref, base);
994
+ link.href = href;
995
995
  }
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
- });
996
+ });
997
+ } else {
998
+ content.querySelectorAll('a').forEach(link => {
999
+ const oldHref = link.getAttribute('href');
1000
+ if (oldHref === link.hash) {
1001
+ link.addEventListener('click', ev => this._scrollToElement(ev, link));
1009
1002
  }
1003
+ });
1010
1004
  }
1011
-
1012
- _getParameters() {
1013
- const params = [];
1014
- this.querySelectorAll('pb-param').forEach(function (param) {
1015
- params['user.' + param.getAttribute('name')] = param.getAttribute('value');
1005
+ }
1006
+
1007
+ _initFootnotes(content) {
1008
+ if (content) {
1009
+ content.querySelectorAll('.note, .fn-back').forEach(elem => {
1010
+ elem.addEventListener('click', ev => {
1011
+ ev.preventDefault();
1012
+ const fn = this.shadowRoot.getElementById('content').querySelector(elem.hash);
1013
+ if (fn) {
1014
+ fn.scrollIntoView();
1015
+ }
1016
1016
  });
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;
1017
+ });
1028
1018
  }
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;
1019
+ }
1020
+
1021
+ _getParameters() {
1022
+ const params = [];
1023
+ this.querySelectorAll('pb-param').forEach(param => {
1024
+ params[`user.${param.getAttribute('name')}`] = param.getAttribute('value');
1025
+ });
1026
+ // add parameters for features set with pb-toggle-feature
1027
+ for (const [key, value] of Object.entries(this._features)) {
1028
+ params[`user.${key}`] = value;
1060
1029
  }
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
- }
1030
+ // add parameters for user-defined parameters supplied via pb-link
1031
+ if (this._additionalParams) {
1032
+ for (const [key, value] of Object.entries(this._additionalParams)) {
1033
+ params[key] = value;
1034
+ }
1076
1035
  }
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
- }
1036
+ return params;
1037
+ }
1038
+
1039
+ /**
1040
+ * Return the parameter object which would be passed to the server by this component
1041
+ */
1042
+ getParameters(pos) {
1043
+ pos = pos || this.nodeId;
1044
+ const doc = this.getDocument();
1045
+ const params = this._getParameters();
1046
+ if (!this.minApiVersion('1.0.0')) {
1047
+ params.doc = doc.path;
1132
1048
  }
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
- });
1049
+ params.odd = `${this.getOdd()}.odd`;
1050
+ params.view = this.getView();
1051
+ params.fill = this.fill;
1052
+ if (pos) {
1053
+ params.root = pos;
1160
1054
  }
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
- }
1055
+ if (this.xpath) {
1056
+ params.xpath = this.xpath;
1057
+ }
1058
+ if (this.xmlId) {
1059
+ params.id = this.xmlId;
1060
+ }
1061
+ if (!this.suppressHighlight && this.highlight) {
1062
+ params.highlight = 'yes';
1063
+ }
1064
+ if (this.map) {
1065
+ params.map = this.map;
1177
1066
  }
1178
1067
 
1179
- toggleFeature(ev) {
1180
- const properties = registry.getState(this);
1181
- if (properties) {
1182
- this._setState(properties);
1068
+ return params;
1069
+ }
1183
1070
 
1071
+ _applyToggles(elem) {
1072
+ for (const [selector, setting] of Object.entries(this._selector)) {
1073
+ elem.querySelectorAll(selector).forEach(node => {
1074
+ const command = setting.command || 'toggle';
1075
+ if (node.command) {
1076
+ node.command(command, setting.state);
1184
1077
  }
1185
-
1186
- if (ev.detail.refresh) {
1187
- this._updateStyles();
1188
- this._load();
1078
+ if (setting.state) {
1079
+ node.classList.add(command);
1189
1080
  } else {
1190
- const view = this.shadowRoot.getElementById('view');
1191
- this._applyToggles(view);
1081
+ node.classList.remove(command);
1192
1082
  }
1193
- registry.commit(this, properties);
1083
+ });
1194
1084
  }
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
- }
1085
+ }
1086
+
1087
+ /**
1088
+ * Load a part of the document identified by the given eXist nodeId
1089
+ *
1090
+ * @param {String} nodeId The eXist nodeId of the root element to load
1091
+ */
1092
+ goto(nodeId) {
1093
+ this._load(nodeId);
1094
+ }
1095
+
1096
+ /**
1097
+ * Load a part of the document identified by the given xml:id
1098
+ *
1099
+ * @param {String} xmlId The xml:id to be loaded
1100
+ */
1101
+ gotoId(xmlId) {
1102
+ this.xmlId = xmlId;
1103
+ this._load();
1104
+ }
1105
+
1106
+ /**
1107
+ * Navigate the document either forward or backward and refresh the view.
1108
+ * The navigation method is determined by property `view`.
1109
+ *
1110
+ * @param {string} direction either `backward` or `forward`
1111
+ */
1112
+ navigate(direction) {
1113
+ // in single view mode there should be no navigation
1114
+ if (this.getView() === 'single') {
1115
+ return;
1255
1116
  }
1256
1117
 
1257
- _getFragmentBefore(node, ms) {
1258
- const range = document.createRange();
1259
- range.setStartBefore(node);
1260
- range.setEndBefore(ms);
1261
-
1262
- return range.cloneContents();
1118
+ this.lastDirection = direction;
1119
+
1120
+ if (direction === 'backward') {
1121
+ if (this.previous) {
1122
+ if (!this.disableHistory && !this.map) {
1123
+ registry.commit(this, {
1124
+ id: this.previousId || null,
1125
+ root: this.previousId ? null : this.previous,
1126
+ });
1127
+ }
1128
+ this.xmlId = this.previousId;
1129
+ this._load(this.xmlId ? null : this.previous, direction);
1130
+ }
1131
+ } else if (this.next) {
1132
+ if (!this.disableHistory && !this.map) {
1133
+ registry.commit(this, {
1134
+ id: this.nextId || null,
1135
+ root: this.nextId ? null : this.next,
1136
+ });
1137
+ }
1138
+ this.xmlId = this.nextId;
1139
+ this._load(this.xmlId ? null : this.next, direction);
1263
1140
  }
1264
-
1265
- _getFragmentAfter(node, ms) {
1266
- const range = document.createRange();
1267
- range.setStartBefore(ms);
1268
- range.setEndAfter(node);
1269
-
1270
- return range.cloneContents();
1141
+ }
1142
+
1143
+ /**
1144
+ * Check the number of fragments which were already loaded in infinite
1145
+ * scroll mode. If they exceed `infiniteScrollMax`, remove either the
1146
+ * first or last fragment from the DOM, depending on the scroll direction.
1147
+ *
1148
+ * @param {string} direction either 'forward' or 'backward'
1149
+ */
1150
+ _checkChunks(direction) {
1151
+ if (!this.infiniteScroll || this.infiniteScrollMax === 0) {
1152
+ return;
1271
1153
  }
1272
1154
 
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
- }
1155
+ if (this._chunks.length === this.infiniteScrollMax) {
1156
+ switch (direction) {
1157
+ case 'forward':
1158
+ this._content.removeChild(this._chunks.shift());
1159
+ break;
1160
+ default:
1161
+ this._content.removeChild(this._chunks.pop());
1162
+ break;
1163
+ }
1280
1164
  }
1165
+ this.emitTo('pb-navigate', {
1166
+ direction,
1167
+ source: this,
1168
+ });
1169
+ }
1170
+
1171
+ /**
1172
+ * Zoom the displayed content by increasing or decreasing font size.
1173
+ *
1174
+ * @param {string} direction either `in` or `out`
1175
+ */
1176
+ zoom(direction) {
1177
+ const view = this.shadowRoot.getElementById('view');
1178
+ const fontSize = window.getComputedStyle(view).getPropertyValue('font-size');
1179
+ const size = parseInt(fontSize.replace(/^(\d+)px/, '$1'));
1180
+
1181
+ if (direction === 'in') {
1182
+ view.style.fontSize = `${size + 1}px`;
1183
+ } else {
1184
+ view.style.fontSize = `${size - 1}px`;
1185
+ }
1186
+ }
1281
1187
 
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
- }
1188
+ toggleFeature(ev) {
1189
+ const properties = registry.getState(this);
1190
+ if (properties) {
1191
+ this._setState(properties);
1192
+ }
1376
1193
 
1377
- 100% {
1378
- background-position: 0 0;
1379
- }
1380
- }
1194
+ if (ev.detail.refresh) {
1195
+ this._updateStyles();
1196
+ this._load();
1197
+ } else {
1198
+ const view = this.shadowRoot.getElementById('view');
1199
+ this._applyToggles(view);
1200
+ }
1201
+ registry.commit(this, properties);
1202
+ }
1203
+
1204
+ _setState(properties) {
1205
+ for (const [key, value] of Object.entries(properties)) {
1206
+ // check if URL template needs the parameter and if
1207
+ // yes, add it to the additional parameter list
1208
+ if (registry.pathParams.has(key)) {
1209
+ this._additionalParams[key] = value;
1210
+ } else {
1211
+ switch (key) {
1212
+ case 'odd':
1213
+ case 'view':
1214
+ case 'columnSeparator':
1215
+ case 'xpath':
1216
+ case 'nodeId':
1217
+ case 'path':
1218
+ case 'root':
1219
+ break;
1220
+ default:
1221
+ this._features[key] = value;
1222
+ break;
1223
+ }
1224
+ }
1225
+ }
1226
+ if (properties.odd && !this.getAttribute('odd')) {
1227
+ this.odd = properties.odd;
1228
+ }
1229
+ if (properties.view && !this.getAttribute('view')) {
1230
+ this.view = properties.view;
1231
+ if (this.view === 'single') {
1232
+ // when switching to single view, clear current node id
1233
+ this.nodeId = null;
1234
+ } else {
1235
+ // otherwise use value for alternate view returned from server
1236
+ this.nodeId = this.switchView;
1237
+ }
1238
+ }
1239
+ if (properties.fill && !this.getAttribute('fill')) {
1240
+ this.fill = properties.fill;
1241
+ }
1242
+ if (properties.xpath && !this.getAttribute('xpath')) {
1243
+ this.xpath = properties.xpath;
1244
+ }
1245
+ if (properties.hasOwnProperty('columnSeparator')) {
1246
+ this.columnSeparator = properties.columnSeparator;
1247
+ }
1248
+ this.xmlId = (!this.getAttribute('xml-id') && properties.id) || this.xmlId;
1249
+ this.nodeId = (!this.getAttribute('xml-id') && properties.root) || null;
1381
1250
 
1382
- .scroll-fragment {
1383
- animation: fadeIn ease 500ms;
1384
- }
1251
+ if (properties.path) {
1252
+ this.getDocument().path = properties.path;
1253
+ }
1385
1254
 
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
- ]
1255
+ if (properties.selectors) {
1256
+ properties.selectors.forEach(sc => {
1257
+ this._selector[sc.selector] = {
1258
+ state: sc.state,
1259
+ command: sc.command || 'toggle',
1260
+ };
1261
+ });
1262
+ }
1263
+ }
1264
+
1265
+ _getFragmentBefore(node, ms) {
1266
+ const range = document.createRange();
1267
+ range.setStartBefore(node);
1268
+ range.setEndBefore(ms);
1269
+
1270
+ return range.cloneContents();
1271
+ }
1272
+
1273
+ _getFragmentAfter(node, ms) {
1274
+ const range = document.createRange();
1275
+ range.setStartBefore(ms);
1276
+ range.setEndAfter(node);
1277
+
1278
+ return range.cloneContents();
1279
+ }
1280
+
1281
+ _updateSource(newVal, oldVal) {
1282
+ if (typeof oldVal !== 'undefined' && newVal !== oldVal) {
1283
+ this.xpath = null;
1284
+ this.odd = null;
1285
+ this.xmlId = null;
1286
+ this.nodeId = null;
1421
1287
  }
1288
+ }
1289
+
1290
+ static get styles() {
1291
+ return css`
1292
+ :host {
1293
+ display: block;
1294
+ background: transparent;
1295
+ }
1296
+
1297
+ :host(.noscroll) {
1298
+ scrollbar-width: none; /* Firefox 64 */
1299
+ -ms-overflow-style: none;
1300
+ }
1301
+
1302
+ :host(.noscroll)::-webkit-scrollbar {
1303
+ width: 0 !important;
1304
+ display: none;
1305
+ }
1306
+
1307
+ [id] {
1308
+ scroll-margin-top: var(--pb-view-scroll-margin-top);
1309
+ }
1310
+
1311
+ #view {
1312
+ position: relative;
1313
+ }
1314
+
1315
+ .columns {
1316
+ display: grid;
1317
+ grid-template-columns: calc(50% - var(--pb-view-column-gap, 10px) / 2) calc(
1318
+ 50% - var(--pb-view-column-gap, 10px) / 2
1319
+ );
1320
+ grid-column-gap: var(--pb-view-column-gap, 10px);
1321
+ }
1322
+
1323
+ .margin-note {
1324
+ display: none;
1325
+ }
1326
+
1327
+ @media (min-width: 769px) {
1328
+ .content.margin-right {
1329
+ margin-right: 200px;
1330
+ }
1331
+
1332
+ .margin-note {
1333
+ background: rgba(153, 153, 153, 0.2);
1334
+ display: block;
1335
+ font-size: small;
1336
+ margin-right: -200px;
1337
+ margin-bottom: 5px;
1338
+ padding: 5px 0;
1339
+ float: right;
1340
+ clear: both;
1341
+ width: 180px;
1342
+ }
1343
+
1344
+ .margin-note .n {
1345
+ color: #777777;
1346
+ }
1347
+ }
1348
+
1349
+ a[rel='footnote'] {
1350
+ font-size: var(--pb-footnote-font-size, var(--pb-content-font-size, 75%));
1351
+ font-family: var(--pb-footnote-font-family, --pb-content-font-family);
1352
+ vertical-align: super;
1353
+ color: var(--pb-footnote-color, var(--pb-color-primary, #333333));
1354
+ text-decoration: none;
1355
+ padding: var(--pb-footnote-padding, 0 0 0 0.25em);
1356
+ }
1357
+
1358
+ .list dt {
1359
+ float: left;
1360
+ }
1361
+
1362
+ .footnote .fn-number {
1363
+ float: left;
1364
+ font-size: var(--pb-footnote-font-size, var(--pb-content-font-size, 75%));
1365
+ }
1366
+
1367
+ .observer {
1368
+ display: block;
1369
+ width: 100%;
1370
+ height: var(--pb-view-loader-height, 16px);
1371
+ font-family: var(--pb-view-loader-font, --pb-base-font);
1372
+ color: var(--pb-view-loader-color, black);
1373
+ background: var(--pb-view-loader-background, #909090);
1374
+ background-image: var(
1375
+ --pb-view-loader-background-image,
1376
+ repeating-linear-gradient(
1377
+ 45deg,
1378
+ transparent,
1379
+ transparent 35px,
1380
+ rgba(255, 255, 255, 0.5) 35px,
1381
+ rgba(255, 255, 255, 0.5) 70px
1382
+ )
1383
+ );
1384
+ animation-name: loader;
1385
+ animation-timing-function: linear;
1386
+ animation-duration: 2s;
1387
+ animation-fill-mode: forwards;
1388
+ animation-iteration-count: infinite;
1389
+ }
1390
+
1391
+ @keyframes loader {
1392
+ 0% {
1393
+ background-position: 3rem 0;
1394
+ }
1395
+
1396
+ 100% {
1397
+ background-position: 0 0;
1398
+ }
1399
+ }
1400
+
1401
+ .scroll-fragment {
1402
+ animation: fadeIn ease 500ms;
1403
+ }
1404
+
1405
+ @keyframes fadeIn {
1406
+ 0% {
1407
+ opacity: 0;
1408
+ }
1409
+ 100% {
1410
+ opacity: 1;
1411
+ }
1412
+ }
1413
+ `;
1414
+ }
1415
+
1416
+ render() {
1417
+ return [
1418
+ html`
1419
+ <div id="view" part="content">
1420
+ ${this._style}
1421
+ ${this.infiniteScroll ? html`<div id="top-observer" class="observer"></div>` : null}
1422
+ <div class="columns">
1423
+ <div id="column1">${this._column1}</div>
1424
+ <div id="column2">${this._column2}</div>
1425
+ </div>
1426
+ <div id="content">${this._content}</div>
1427
+ ${this.infiniteScroll ? html`<div id="bottom-observer" class="observer"></div>` : null}
1428
+ <div id="footnotes" part="footnotes">${this._footnotes}</div>
1429
+ </div>
1430
+ <iron-ajax
1431
+ id="loadContent"
1432
+ verbose
1433
+ handle-as="json"
1434
+ method="get"
1435
+ with-credentials
1436
+ @response="${this._handleContent}"
1437
+ @error="${this._handleError}"
1438
+ ></iron-ajax>
1439
+ `,
1440
+ ];
1441
+ }
1422
1442
  }
1423
1443
 
1424
1444
  customElements.define('pb-view', PbView);