@teipublisher/pb-components 2.26.1-next.2 → 3.0.0

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