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

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 (262) hide show
  1. package/.github/workflows/docker-cypress.yml +54 -0
  2. package/.github/workflows/main.yml +6 -4
  3. package/.github/workflows/node.js.yml +56 -21
  4. package/.github/workflows/release.js.yml +19 -17
  5. package/.releaserc.json +1 -1
  6. package/CHANGELOG.md +351 -9
  7. package/Dockerfile +78 -70
  8. package/README.md +112 -4
  9. package/css/components.css +5 -5
  10. package/css/gridjs/mermaid.min.css +1 -1
  11. package/css/leaflet/Control.Geocoder.css +1 -126
  12. package/css/leaflet/images/layers.png +0 -0
  13. package/css/tify/tify.css +6 -5
  14. package/css/tom-select/tom-select.bootstrap4.min.css +1 -1
  15. package/css/tom-select/tom-select.bootstrap5.min.css +1 -1
  16. package/css/tom-select/tom-select.default.min.css +1 -1
  17. package/css/tom-select/tom-select.default.min.css.map +1 -0
  18. package/css/tom-select/tom-select.min.css +1 -1
  19. package/cypress.config.js +84 -0
  20. package/dist/api.html +1 -1
  21. package/dist/css/design-system.css +607 -0
  22. package/dist/demo/bundle-test.html +4 -3
  23. package/dist/demo/components.css +46 -1
  24. package/dist/demo/design-system.html +710 -0
  25. package/dist/demo/dts-client.html +2 -2
  26. package/dist/demo/pb-autocomplete.html +23 -11
  27. package/dist/demo/pb-autocomplete2.html +66 -55
  28. package/dist/demo/pb-autocomplete3.html +17 -8
  29. package/dist/demo/pb-blacklab-highlight.html +28 -11
  30. package/dist/demo/pb-blacklab-results.html +3 -2
  31. package/dist/demo/pb-browse-docs.html +24 -24
  32. package/dist/demo/pb-browse-docs2.html +3 -3
  33. package/dist/demo/pb-clipboard.html +32 -28
  34. package/dist/demo/pb-code-editor.html +6 -6
  35. package/dist/demo/pb-code-highlight.html +63 -63
  36. package/dist/demo/pb-codepen.html +1 -1
  37. package/dist/demo/pb-collapse.html +1 -1
  38. package/dist/demo/pb-collapse2.html +2 -2
  39. package/dist/demo/pb-combo-box.html +135 -130
  40. package/dist/demo/pb-custom-form.html +64 -55
  41. package/dist/demo/pb-dialog.html +12 -6
  42. package/dist/demo/pb-document.html +1 -1
  43. package/dist/demo/pb-download.html +68 -59
  44. package/dist/demo/pb-drawer.html +67 -46
  45. package/dist/demo/pb-drawer2.html +65 -58
  46. package/dist/demo/pb-edit-app.html +2 -2
  47. package/dist/demo/pb-edit-xml.html +1 -1
  48. package/dist/demo/pb-facsimile-2.html +26 -11
  49. package/dist/demo/pb-facsimile-3.html +25 -10
  50. package/dist/demo/pb-facsimile-dedup-test-2.html +48 -0
  51. package/dist/demo/pb-facsimile-dedup-test.html +48 -0
  52. package/dist/demo/pb-facsimile.html +4 -4
  53. package/dist/demo/pb-formula.html +1 -1
  54. package/dist/demo/pb-grid.html +22 -8
  55. package/dist/demo/pb-highlight.html +2 -2
  56. package/dist/demo/pb-i18n-simple.html +1 -0
  57. package/dist/demo/pb-i18n.html +15 -5
  58. package/dist/demo/pb-image-strip-standalone.html +2 -2
  59. package/dist/demo/pb-image-strip-view.html +2 -2
  60. package/dist/demo/pb-leaflet-map.html +3 -3
  61. package/dist/demo/pb-leaflet-map2.html +2 -2
  62. package/dist/demo/pb-leaflet-map3.html +3 -3
  63. package/dist/demo/pb-link.html +1 -1
  64. package/dist/demo/pb-load.html +2 -6
  65. package/dist/demo/pb-login.html +1 -3
  66. package/dist/demo/pb-manage-odds.html +9 -4
  67. package/dist/demo/pb-markdown.html +1 -1
  68. package/dist/demo/pb-media-query.html +2 -2
  69. package/dist/demo/pb-mei.html +2 -2
  70. package/dist/demo/pb-mei2.html +2 -2
  71. package/dist/demo/pb-message.html +2 -3
  72. package/dist/demo/pb-odd-editor.html +54 -52
  73. package/dist/demo/pb-page-header.html +27 -0
  74. package/dist/demo/pb-popover.html +1 -1
  75. package/dist/demo/pb-print-preview.html +2 -2
  76. package/dist/demo/pb-progress.html +4 -4
  77. package/dist/demo/pb-repeat.html +32 -36
  78. package/dist/demo/pb-search.html +16 -5
  79. package/dist/demo/pb-search2.html +4 -4
  80. package/dist/demo/pb-search3.html +3 -3
  81. package/dist/demo/pb-search4.html +3 -3
  82. package/dist/demo/pb-select-feature.html +4 -4
  83. package/dist/demo/pb-select-feature2.html +4 -4
  84. package/dist/demo/pb-select-feature3.html +2 -2
  85. package/dist/demo/pb-select-i18n.html +58 -53
  86. package/dist/demo/pb-select-odd.html +1 -1
  87. package/dist/demo/pb-select.html +190 -75
  88. package/dist/demo/pb-select2.html +91 -37
  89. package/dist/demo/pb-select3.html +109 -41
  90. package/dist/demo/pb-svg.html +1 -1
  91. package/dist/demo/pb-table-grid.html +26 -15
  92. package/dist/demo/pb-tabs.html +15 -7
  93. package/dist/demo/pb-tify.html +7 -7
  94. package/dist/demo/pb-timeline.html +1 -1
  95. package/dist/demo/pb-timeline2.html +1 -1
  96. package/dist/demo/pb-toggle-feature.html +26 -23
  97. package/dist/demo/pb-toggle-feature2.html +4 -4
  98. package/dist/demo/pb-toggle-feature3.html +2 -2
  99. package/dist/demo/pb-toggle-feature4.html +56 -54
  100. package/dist/demo/pb-version.html +2 -2
  101. package/dist/demo/pb-view.html +78 -40
  102. package/dist/demo/pb-view2.html +69 -46
  103. package/dist/demo/pb-view3.html +53 -48
  104. package/dist/demo/pb-view4.html +70 -49
  105. package/dist/demo/pb-zoom.html +2 -2
  106. package/dist/{es-global-bridge-d8ce175d.js → es-global-bridge-D8ZcUcx_.js} +0 -4
  107. package/dist/focus-mixin-VCsFap6b.js +768 -0
  108. package/dist/images/icons.svg +217 -0
  109. package/dist/jinn-codemirror-DETLdm08.js +1 -0
  110. package/dist/lib/openseadragon.min.js +80 -0
  111. package/dist/lib/openseadragon.min.js.map +1 -0
  112. package/dist/pb-code-editor.js +25 -20
  113. package/dist/pb-component-docs.js +414 -3225
  114. package/dist/pb-components-bundle.js +3046 -4402
  115. package/dist/pb-dialog-tklYGWfc.js +121 -0
  116. package/dist/pb-edit-app.js +208 -107
  117. package/dist/pb-elements.json +716 -249
  118. package/dist/pb-facsimile.js +46 -0
  119. package/dist/pb-i18n-C0NDma4h.js +1 -0
  120. package/dist/pb-leaflet-map.js +23 -23
  121. package/dist/pb-mei.js +152 -134
  122. package/dist/pb-mixin-DHoWQheB.js +1 -0
  123. package/dist/pb-odd-editor.js +1671 -1231
  124. package/dist/pb-tify.js +1 -27
  125. package/dist/unsafe-html-D5VGo9Oq.js +1 -0
  126. package/dist/urls-BEONu_g4.js +1 -0
  127. package/eslint.config.mjs +92 -0
  128. package/gh-pages.js +5 -3
  129. package/i18n/common/en.json +6 -0
  130. package/i18n/common/pl.json +2 -2
  131. package/images/icons.svg +217 -0
  132. package/index.html +0 -5
  133. package/lib/leaflet-src.js.map +1 -0
  134. package/lib/leaflet.markercluster-src.js.map +1 -0
  135. package/lib/openseadragon.min.js +6 -6
  136. package/package.json +56 -81
  137. package/pb-elements.json +716 -249
  138. package/rollup.config.mjs +312 -0
  139. package/src/assets/components.css +5 -5
  140. package/src/assets/design-system.css +607 -0
  141. package/src/authority/airtable.js +20 -21
  142. package/src/authority/anton.js +129 -129
  143. package/src/authority/custom.js +70 -27
  144. package/src/authority/geonames.js +38 -32
  145. package/src/authority/gnd.js +50 -42
  146. package/src/authority/kbga.js +136 -134
  147. package/src/authority/metagrid.js +44 -46
  148. package/src/authority/reconciliation.js +66 -68
  149. package/src/authority/registry.js +4 -4
  150. package/src/docs/demo-utils.js +91 -0
  151. package/src/docs/pb-component-docs.js +287 -147
  152. package/src/docs/pb-component-view.js +380 -273
  153. package/src/docs/pb-components-list.js +115 -51
  154. package/src/docs/pb-demo-snippet.js +199 -174
  155. package/src/dts-client.js +306 -303
  156. package/src/dts-select-endpoint.js +125 -85
  157. package/src/parse-date-service.js +184 -135
  158. package/src/pb-ajax.js +175 -173
  159. package/src/pb-authority-lookup.js +198 -158
  160. package/src/pb-autocomplete.js +731 -313
  161. package/src/pb-blacklab-highlight.js +266 -260
  162. package/src/pb-blacklab-results.js +230 -225
  163. package/src/pb-browse-docs.js +601 -484
  164. package/src/pb-browse.js +68 -65
  165. package/src/pb-clipboard.js +97 -76
  166. package/src/pb-code-editor.js +111 -103
  167. package/src/pb-code-highlight.js +234 -204
  168. package/src/pb-codepen.js +81 -73
  169. package/src/pb-collapse.js +265 -152
  170. package/src/pb-combo-box.js +191 -191
  171. package/src/pb-components-bundle.js +1 -7
  172. package/src/pb-components.js +2 -6
  173. package/src/pb-custom-form.js +230 -141
  174. package/src/pb-dialog.js +99 -63
  175. package/src/pb-document.js +118 -91
  176. package/src/pb-download.js +214 -198
  177. package/src/pb-drawer.js +146 -149
  178. package/src/pb-edit-app.js +471 -240
  179. package/src/pb-edit-xml.js +101 -98
  180. package/src/pb-events.js +126 -107
  181. package/src/pb-facs-link.js +130 -101
  182. package/src/pb-facsimile.js +494 -410
  183. package/src/pb-fetch.js +389 -0
  184. package/src/pb-formula.js +152 -154
  185. package/src/pb-geolocation.js +130 -132
  186. package/src/pb-grid-action.js +59 -56
  187. package/src/pb-grid.js +388 -228
  188. package/src/pb-highlight.js +142 -142
  189. package/src/pb-hotkeys.js +40 -42
  190. package/src/pb-i18n.js +115 -127
  191. package/src/pb-icon-button.js +108 -0
  192. package/src/pb-icon.js +283 -0
  193. package/src/pb-image-strip.js +85 -79
  194. package/src/pb-lang.js +142 -57
  195. package/src/pb-leaflet-map.js +551 -483
  196. package/src/pb-link.js +132 -126
  197. package/src/pb-load.js +495 -428
  198. package/src/pb-login.js +303 -248
  199. package/src/pb-manage-odds.js +384 -338
  200. package/src/pb-map-icon.js +90 -90
  201. package/src/pb-map-layer.js +86 -86
  202. package/src/pb-markdown.js +107 -110
  203. package/src/pb-media-query.js +75 -73
  204. package/src/pb-mei.js +523 -303
  205. package/src/pb-message.js +144 -98
  206. package/src/pb-mixin.js +268 -265
  207. package/src/pb-navigation.js +83 -96
  208. package/src/pb-observable.js +39 -39
  209. package/src/pb-odd-editor.js +1209 -948
  210. package/src/pb-odd-elementspec-editor.js +375 -310
  211. package/src/pb-odd-model-editor.js +1189 -941
  212. package/src/pb-odd-parameter-editor.js +269 -170
  213. package/src/pb-odd-rendition-editor.js +184 -131
  214. package/src/pb-page.js +451 -422
  215. package/src/pb-paginate.js +260 -178
  216. package/src/pb-panel.js +217 -183
  217. package/src/pb-popover-themes.js +16 -9
  218. package/src/pb-popover.js +297 -288
  219. package/src/pb-print-preview.js +128 -128
  220. package/src/pb-progress.js +52 -52
  221. package/src/pb-repeat.js +141 -108
  222. package/src/pb-restricted.js +85 -78
  223. package/src/pb-search.js +258 -230
  224. package/src/pb-select-feature.js +210 -126
  225. package/src/pb-select-odd.js +184 -118
  226. package/src/pb-select-template.js +113 -78
  227. package/src/pb-select.js +330 -229
  228. package/src/pb-split-list.js +181 -176
  229. package/src/pb-svg.js +81 -80
  230. package/src/pb-table-column.js +55 -55
  231. package/src/pb-table-grid.js +334 -205
  232. package/src/pb-tabs.js +238 -61
  233. package/src/pb-tify.js +3331 -126
  234. package/src/pb-timeline.js +394 -255
  235. package/src/pb-toggle-feature.js +196 -188
  236. package/src/pb-upload.js +201 -176
  237. package/src/pb-version.js +22 -34
  238. package/src/pb-view-annotate.js +138 -102
  239. package/src/pb-view.js +1722 -1272
  240. package/src/pb-zoom.js +144 -46
  241. package/src/search-result-service.js +256 -223
  242. package/src/seed-element.js +14 -22
  243. package/src/settings.js +4 -4
  244. package/src/theming.js +98 -91
  245. package/src/urls.js +403 -289
  246. package/src/utils.js +53 -51
  247. package/vite.config.js +86 -0
  248. package/css/pb-styles.css +0 -51
  249. package/dist/iron-form-3b8dcaa7.js +0 -210
  250. package/dist/jinn-codemirror-da0e2d1f.js +0 -1
  251. package/dist/paper-checkbox-515a5284.js +0 -1597
  252. package/dist/paper-icon-button-b1d31571.js +0 -398
  253. package/dist/paper-listbox-a3b7175c.js +0 -1265
  254. package/dist/pb-i18n-0611135a.js +0 -1
  255. package/dist/pb-mixin-b1caa22e.js +0 -158
  256. package/dist/polymer-hack.js +0 -1
  257. package/dist/vaadin-element-mixin-6e4cee3a.js +0 -527
  258. package/lib/Control.Geocoder.min.js +0 -2
  259. package/lib/Control.Geocoder.min.js.map +0 -1
  260. package/src/assets/pb-styles.css +0 -51
  261. package/src/pb-light-dom.js +0 -40
  262. package/src/polymer-hack.js +0 -6
package/src/pb-page.js CHANGED
@@ -1,14 +1,14 @@
1
- import { LitElement, html, css } from 'lit-element';
1
+ import { LitElement, html, css } from 'lit';
2
2
  import i18next from 'i18next';
3
3
  import LanguageDetector from 'i18next-browser-languagedetector';
4
4
  import XHR from 'i18next-xhr-backend';
5
5
  import Backend from 'i18next-chained-backend';
6
6
  import { pbMixin, clearPageEvents } from './pb-mixin.js';
7
7
  import { resolveURL } from './utils.js';
8
- import { loadStylesheets } from "./theming.js";
9
- import { initTranslation } from "./pb-i18n.js";
10
- import { typesetMath } from "./pb-formula.js";
11
- import { registry } from "./urls.js";
8
+ import { loadStylesheets } from './theming.js';
9
+ import { initTranslation } from './pb-i18n.js';
10
+ import { typesetMath } from './pb-formula.js';
11
+ import { registry } from './urls.js';
12
12
 
13
13
  /**
14
14
  * Make sure there's only one instance of pb-page active at any time.
@@ -20,7 +20,7 @@ let _instance;
20
20
  * Among other things, this element determines the TEI Publisher
21
21
  * instance to which all elements will talk (property `endpoint`), and
22
22
  * initializes the i18n language module.
23
- *
23
+ *
24
24
  * @slot - default unnamed slot for content
25
25
  * @fires pb-page-ready - fired when the endpoint and language settings have been determined
26
26
  * @fires pb-i18n-update - fired when the user selected a different display language
@@ -28,446 +28,475 @@ let _instance;
28
28
  * @fires pb-toggle - when received, dispatch state changes to the elements on the page (see `pb-toggle-feature`, `pb-select-feature`)
29
29
  */
30
30
  export class PbPage extends pbMixin(LitElement) {
31
-
32
- static get properties() {
33
- return {
34
- ...super.properties,
35
- /**
36
- * TEI Publisher internal: set to the root URL of the current app
37
- */
38
- appRoot: {
39
- type: String,
40
- attribute: 'app-root'
41
- },
42
- /**
43
- * Can be used to define parameters which should be serialized in the
44
- * URL path rather than as query parameters. Expects a url pattern
45
- * relative to the application root
46
- * (supported patterns are documented in the
47
- * [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) library documentation).
48
- *
49
- * For example, a pattern `:lang/texts/:path/:id?` would support URLs like
50
- * `en/texts/text1/chapter1`. Whenever components change state e.g. due to a navigation
51
- * event – the standard parameters `path`, `lang` and `id` would be serialized into the
52
- * URL path pattern rather than query parameters.
53
- */
54
- urlTemplate: {
55
- type: String,
56
- attribute: 'url-template'
57
- },
58
- /**
59
- * A comma-separated list of parameter names which should not be reflected on the browser URL.
60
- * Use this to exclude e.g. the default `odd` parameter of a pb-view to be shown in the
61
- * browser URL.
62
- */
63
- urlIgnore: {
64
- type: String,
65
- attribute: 'url-ignore'
66
- },
67
- /**
68
- * Is the resource path part of the URL or should it be
69
- * encoded as a parameter? TEI Publisher uses the
70
- * URL path, but the webcomponent demos need to encode the resource path
71
- * in a query parameter.
72
- */
73
- urlPath: {
74
- type: String,
75
- attribute: 'url-path'
76
- },
77
- /**
78
- * If enabled, a hash in the URL (e.g. documentation.xml#introduction) will
79
- * be interpreted as an xml:id to navigate to when talking to the server.
80
- */
81
- idHash: {
82
- type: Boolean,
83
- attribute: 'id-hash'
84
- },
85
- /**
86
- * TEI Publisher internal: set to the current page template.
87
- */
88
- template: {
89
- type: String
90
- },
91
- /**
92
- * The base URL of the TEI Publisher instance. All nested elements will
93
- * talk to this instance. By default it is set to the URL the
94
- * page was loaded from.
95
- *
96
- * The endpoint can be overwritten by providing an HTTP request parameter
97
- * `_target` with an URL.
98
- */
99
- endpoint: {
100
- type: String,
101
- reflect: true
102
- },
103
- apiVersion: {
104
- type: String,
105
- attribute: 'api-version',
106
- reflect: true
107
- },
108
- /**
109
- * Optional URL pointing to a directory from which additional i18n
110
- * language files will be loaded. The URL should contain placeholders
111
- * for the language (`lng`) and the namespace (`ns`), e.g.
112
- *
113
- * `resources/i18n/{{ns}}_{{lng}}.json`
114
- *
115
- * or
116
- *
117
- * `resources/i18n/{{ns}}/{{lng}}.json`
118
- *
119
- * The latter assumes custom language files in a subdirectory, the first
120
- * expects the namespace to be specified at the start of the file name.
121
- *
122
- * The default namespace for custom language files is assumed to be `app`,
123
- * but you can define additional namespaces via `localeFallbackNS`.
124
- */
125
- locales: {
126
- type: String
127
- },
128
- /**
129
- * Optional list of whitespace separated namespaces which should be searched
130
- * for translations. By default, only the namespace `common` is queried.
131
- * If the `locales` property is specified, an additional namespace `app` is added.
132
- * You can add more namespace here, e.g. `custom`, if you want to provide
133
- * translations for custom apps or components.
134
- */
135
- localeFallbackNs: {
136
- type: String,
137
- attribute: 'locale-fallback-ns'
138
- },
139
- /**
140
- * Comma-separated list of languages supported. If the detected language
141
- * is not in this list, fall back to the configured fallback language.
142
- */
143
- supportedLanguages: {
144
- type: Array,
145
- attribute: 'supported-languages',
146
- converter(value) {
147
- return value.split(/\s*,\s*/);
148
- }
149
- },
150
- /**
151
- * The fallback language to use if the detected language is not supported.
152
- * Defaults to 'en'.
153
- */
154
- fallbackLanguage: {
155
- type: String,
156
- attribute: 'fallback-language'
157
- },
158
- /**
159
- * Set a language for i18n (e.g. 'en' or 'de'). If not set, browser language
160
- * detection will be used.
161
- */
162
- language: {
163
- type: String
164
- },
165
- /**
166
- * If set, the element will wait for a language being set by i18n before
167
- * it sends a `pb-page-ready` event. Elements like `pb-view` will wait
168
- * for this event before displaying content.
169
- *
170
- * Also, `pb-view` will pass the configured language to the server endpoint
171
- * where it will be available to ODD processing models in variable
172
- * `$parameters?language` and can thus be used to change output depending on
173
- * the user interface language.
174
- *
175
- * If you would like `pb-view` to refresh automatically whenever the language
176
- * setting changes, specify property `useLanguage` on the corresponding `pb-view`.
177
- */
178
- requireLanguage: {
179
- type: Boolean,
180
- attribute: 'require-language'
181
- },
182
- /**
183
- * Will be set while the component is loading and unset when
184
- * it is fully loaded. Use to avoid flash of unstyled content
185
- * via CSS: set `unresolved` on `pb-page` in the HTML and
186
- * add a CSS rule like:
187
- *
188
- * ```css
189
- * pb-page[unresolved] {
190
- * display: none;
191
- * }
192
- * ```
193
- */
194
- unresolved: {
195
- type: Boolean,
196
- reflect: true
197
- },
198
- theme: {
199
- type: String
200
- }
201
- };
31
+ static get properties() {
32
+ return {
33
+ ...super.properties,
34
+ /**
35
+ * TEI Publisher internal: set to the root URL of the current app
36
+ */
37
+ appRoot: {
38
+ type: String,
39
+ attribute: 'app-root',
40
+ },
41
+ /**
42
+ * Can be used to define parameters which should be serialized in the
43
+ * URL path rather than as query parameters. Expects a url pattern
44
+ * relative to the application root
45
+ * (supported patterns are documented in the
46
+ * [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) library documentation).
47
+ *
48
+ * For example, a pattern `:lang/texts/:path/:id?` would support URLs like
49
+ * `en/texts/text1/chapter1`. Whenever components change state e.g. due to a navigation
50
+ * event the standard parameters `path`, `lang` and `id` would be serialized into the
51
+ * URL path pattern rather than query parameters.
52
+ */
53
+ urlTemplate: {
54
+ type: String,
55
+ attribute: 'url-template',
56
+ },
57
+ /**
58
+ * A comma-separated list of parameter names which should not be reflected on the browser URL.
59
+ * Use this to exclude e.g. the default `odd` parameter of a pb-view to be shown in the
60
+ * browser URL.
61
+ */
62
+ urlIgnore: {
63
+ type: String,
64
+ attribute: 'url-ignore',
65
+ },
66
+ /**
67
+ * Is the resource path part of the URL or should it be
68
+ * encoded as a parameter? TEI Publisher uses the
69
+ * URL path, but the webcomponent demos need to encode the resource path
70
+ * in a query parameter.
71
+ */
72
+ urlPath: {
73
+ type: String,
74
+ attribute: 'url-path',
75
+ },
76
+ /**
77
+ * If enabled, a hash in the URL (e.g. documentation.xml#introduction) will
78
+ * be interpreted as an xml:id to navigate to when talking to the server.
79
+ */
80
+ idHash: {
81
+ type: Boolean,
82
+ attribute: 'id-hash',
83
+ },
84
+ /**
85
+ * TEI Publisher internal: set to the current page template.
86
+ */
87
+ template: {
88
+ type: String,
89
+ },
90
+ /**
91
+ * The base URL of the TEI Publisher instance. All nested elements will
92
+ * talk to this instance. By default it is set to the URL the
93
+ * page was loaded from.
94
+ *
95
+ * The endpoint can be overwritten by providing an HTTP request parameter
96
+ * `_target` with an URL.
97
+ */
98
+ endpoint: {
99
+ type: String,
100
+ reflect: true,
101
+ },
102
+ apiVersion: {
103
+ type: String,
104
+ attribute: 'api-version',
105
+ reflect: true,
106
+ },
107
+ /**
108
+ * Optional URL pointing to a directory from which additional i18n
109
+ * language files will be loaded. The URL should contain placeholders
110
+ * for the language (`lng`) and the namespace (`ns`), e.g.
111
+ *
112
+ * `resources/i18n/{{ns}}_{{lng}}.json`
113
+ *
114
+ * or
115
+ *
116
+ * `resources/i18n/{{ns}}/{{lng}}.json`
117
+ *
118
+ * The latter assumes custom language files in a subdirectory, the first
119
+ * expects the namespace to be specified at the start of the file name.
120
+ *
121
+ * The default namespace for custom language files is assumed to be `app`,
122
+ * but you can define additional namespaces via `localeFallbackNS`.
123
+ */
124
+ locales: {
125
+ type: String,
126
+ },
127
+ /**
128
+ * Optional list of whitespace separated namespaces which should be searched
129
+ * for translations. By default, only the namespace `common` is queried.
130
+ * If the `locales` property is specified, an additional namespace `app` is added.
131
+ * You can add more namespace here, e.g. `custom`, if you want to provide
132
+ * translations for custom apps or components.
133
+ */
134
+ localeFallbackNs: {
135
+ type: String,
136
+ attribute: 'locale-fallback-ns',
137
+ },
138
+ /**
139
+ * Comma-separated list of languages supported. If the detected language
140
+ * is not in this list, fall back to the configured fallback language.
141
+ */
142
+ supportedLanguages: {
143
+ type: Array,
144
+ attribute: 'supported-languages',
145
+ converter(value) {
146
+ return value.split(/\s*,\s*/);
147
+ },
148
+ },
149
+ /**
150
+ * The fallback language to use if the detected language is not supported.
151
+ * Defaults to 'en'.
152
+ */
153
+ fallbackLanguage: {
154
+ type: String,
155
+ attribute: 'fallback-language',
156
+ },
157
+ /**
158
+ * Set a language for i18n (e.g. 'en' or 'de'). If not set, browser language
159
+ * detection will be used.
160
+ */
161
+ language: {
162
+ type: String,
163
+ },
164
+ /**
165
+ * If set, the element will wait for a language being set by i18n before
166
+ * it sends a `pb-page-ready` event. Elements like `pb-view` will wait
167
+ * for this event before displaying content.
168
+ *
169
+ * Also, `pb-view` will pass the configured language to the server endpoint
170
+ * where it will be available to ODD processing models in variable
171
+ * `$parameters?language` and can thus be used to change output depending on
172
+ * the user interface language.
173
+ *
174
+ * If you would like `pb-view` to refresh automatically whenever the language
175
+ * setting changes, specify property `useLanguage` on the corresponding `pb-view`.
176
+ */
177
+ requireLanguage: {
178
+ type: Boolean,
179
+ attribute: 'require-language',
180
+ },
181
+ /**
182
+ * Will be set while the component is loading and unset when
183
+ * it is fully loaded. Use to avoid flash of unstyled content
184
+ * via CSS: set `unresolved` on `pb-page` in the HTML and
185
+ * add a CSS rule like:
186
+ *
187
+ * ```css
188
+ * pb-page[unresolved] {
189
+ * display: none;
190
+ * }
191
+ * ```
192
+ */
193
+ unresolved: {
194
+ type: Boolean,
195
+ reflect: true,
196
+ },
197
+ theme: {
198
+ type: String,
199
+ },
200
+ };
201
+ }
202
+
203
+ constructor() {
204
+ super();
205
+ this.unresolved = true;
206
+ this.endpoint = '.';
207
+ this.urlTemplate = null;
208
+ this.urlIgnore = null;
209
+ this.urlPath = 'path';
210
+ this.idHash = false;
211
+ this.apiVersion = undefined;
212
+ this.requireLanguage = false;
213
+ this.supportedLanguages = null;
214
+ this.fallbackLanguage = 'en';
215
+ this.theme = null;
216
+ this._localeFallbacks = [];
217
+ this._i18nInstance = null;
218
+
219
+ if (_instance) {
220
+ this.disabled = true;
221
+ } else {
222
+ _instance = this;
223
+
224
+ // clear global page events which might have been set by other pb-page instances.
225
+ // important while running the test suite.
226
+ clearPageEvents();
202
227
  }
203
-
204
- constructor() {
205
- super();
206
- this.unresolved = true;
207
- this.endpoint = ".";
208
- this.urlTemplate = null;
209
- this.urlIgnore = null;
210
- this.urlPath = 'path';
211
- this.idHash = false;
212
- this.apiVersion = undefined;
213
- this.requireLanguage = false;
214
- this.supportedLanguages = null;
215
- this.fallbackLanguage = 'en';
216
- this.theme = null;
217
- this._localeFallbacks = [];
218
- this._i18nInstance = null;
219
-
220
- if (_instance) {
221
- this.disabled = true;
222
- } else {
223
- _instance = this;
224
-
225
- // clear global page events which might have been set by other pb-page instances.
226
- // important while running the test suite.
227
- clearPageEvents();
228
- }
228
+ }
229
+
230
+ get localeFallbackNs() {
231
+ // Expose a space-separated view of the current fallback namespaces
232
+ return this._localeFallbacks && this._localeFallbacks.length
233
+ ? this._localeFallbacks.join(' ')
234
+ : '';
235
+ }
236
+
237
+ set localeFallbackNs(value) {
238
+ // Replace (not append) to avoid uncontrolled growth when attribute re-applies
239
+ const next = (value || '')
240
+ .split(/\s+/)
241
+ .map(s => s.trim())
242
+ .filter(Boolean);
243
+ // Deduplicate while preserving order
244
+ const seen = new Set();
245
+ this._localeFallbacks = next.filter(ns => (seen.has(ns) ? false : (seen.add(ns), true)));
246
+ }
247
+
248
+ disconnectedCallback() {
249
+ super.disconnectedCallback();
250
+ this._i18nInstance = null;
251
+ if (_instance === this) {
252
+ // clear to allow future instances
253
+ _instance = null;
229
254
  }
255
+ }
230
256
 
231
- set localeFallbackNs(value) {
232
- value.split(/\s+/).forEach(v => this._localeFallbacks.push(v));
233
- }
257
+ async connectedCallback() {
258
+ super.connectedCallback();
234
259
 
235
- disconnectedCallback() {
236
- super.disconnectedCallback();
237
- this._i18nInstance = null;
238
- if (_instance === this) {
239
- // clear to allow future instances
240
- _instance = null;
241
- }
260
+ if (this.disabled) {
261
+ return;
242
262
  }
243
263
 
244
- async connectedCallback() {
245
- super.connectedCallback();
246
-
247
- if (this.disabled) {
248
- return;
249
- }
250
-
251
- registry.configure(this.urlPath === 'path', this.idHash, this.appRoot, this.urlTemplate, this.urlIgnore);
252
-
253
- this.endpoint = this.endpoint.replace(/\/+$/, '');
254
-
255
- if (this.locales && this._localeFallbacks.indexOf('app') === -1) {
256
- this._localeFallbacks.push('app');
257
- }
258
- this._localeFallbacks.push('common');
259
-
260
- const target = registry.state._target;
261
- if (target) {
262
- this.endpoint = target;
263
- }
264
-
265
- const apiVersion = registry.state._api;
266
- if (apiVersion) {
267
- this.apiVersion = apiVersion;
268
- }
269
-
270
- const stylesheetURLs = [
271
- // TODO: replace with this.toAbsoluteURL
272
- this.toAbsoluteURL('resources/css/components.css', this.endpoint)
273
- ];
274
- if (this.theme) {
275
- stylesheetURLs.push(this.toAbsoluteURL(this.theme, this.endpoint));
276
- }
277
- console.log('<pb-page> Loading component theme stylesheets from %s', stylesheetURLs.join(', '));
278
- this._themeSheet = await loadStylesheets(stylesheetURLs);
279
-
280
- // try to figure out what version of TEI Publisher the server is running
281
- if (!this.apiVersion) {
282
- // first check if it has a login endpoint, i.e. runs a version < 7
283
- // this is necessary to prevent a CORS failure
284
- const json = await fetch(`${this.endpoint}/login`)
285
- .then((res) => {
286
- if (res.ok) {
287
- return null;
288
- }
289
- // if not, access the actual /api/version endpoint to retrieve the API version
290
- return fetch(`${this.endpoint}/api/version`)
291
- .then((res2) => res2.json());
292
- })
293
- .catch(() => fetch(`${this.endpoint}/api/version`)
294
- .then((res2) => res2.json())
295
- );
296
-
297
- if (json) {
298
- this.apiVersion = json.api;
299
- console.log(`<pb-page> Server reports API version ${this.apiVersion} with app ${json.app.name}/${json.app.version} running on ${json.engine.name}/${json.engine.version}`);
300
- } else {
301
- console.log('<pb-page> No API version reported by server, assuming 0.9.0');
302
- this.apiVersion = '0.9.0';
303
- }
304
- }
305
-
306
- if (!this.requireLanguage) {
307
- this.signalReady('pb-page-ready', {
308
- endpoint: this.endpoint,
309
- template: this.template,
310
- apiVersion: this.apiVersion
311
- });
312
- } else if (this._i18nInstance) {
313
- this.signalReady('pb-page-ready', {
314
- endpoint: this.endpoint,
315
- apiVersion: this.apiVersion,
316
- template: this.template,
317
- language: this._i18nInstance.language
318
- });
319
- }
264
+ // Ensure attribute-provided endpoint is honored even before first update
265
+ const attrEndpoint = this.getAttribute('endpoint');
266
+ if (attrEndpoint) {
267
+ this.endpoint = attrEndpoint;
320
268
  }
321
269
 
322
- firstUpdated() {
323
- super.firstUpdated();
270
+ registry.configure(
271
+ this.urlPath === 'path',
272
+ this.idHash,
273
+ this.appRoot,
274
+ this.urlTemplate,
275
+ this.urlIgnore,
276
+ );
324
277
 
325
- if (this.disabled) {
326
- return;
327
- }
278
+ this.endpoint = this.endpoint.replace(/\/+$/, '');
328
279
 
329
- const slot = this.shadowRoot.querySelector('slot');
330
- slot.addEventListener('slotchange', () => {
331
- const ev = new CustomEvent('pb-page-loaded', {
332
- bubbles: true,
333
- composed: true
334
- });
335
- this.dispatchEvent(ev);
336
- }, { once: true });
337
-
338
- const defaultLocales = resolveURL('../i18n/') + '{{ns}}/{{lng}}.json';
339
- console.log('<pb-page> Loading locales. common: %s; additional: %s; namespaces: %o',
340
- defaultLocales, this.locales, this._localeFallbacks);
341
- const backends = this.locales ? [XHR, XHR] : [XHR];
342
- const backendOptions = [{
343
- loadPath: defaultLocales,
344
- crossDomain: true
345
- }];
346
- if (this.locales) {
347
- backendOptions.unshift({
348
- loadPath: this.locales,
349
- crossDomain: true
350
- });
351
- }
352
- const options = {
353
- fallbackLng: this.fallbackLanguage,
354
- defaultNS: 'common',
355
- ns: ['common'],
356
- debug: false,
357
- load: 'languageOnly',
358
- detection: {
359
- lookupQuerystring: 'lang'
360
- },
361
- backend: {
362
- backends,
363
- backendOptions
364
- }
365
- };
366
- if (this.language) {
367
- options.lng = this.language;
368
- }
369
- console.log('supported langs: %o', this.supportedLanguages);
370
- if (this.supportedLanguages) {
371
- options.supportedLngs = this.supportedLanguages;
372
- }
373
280
 
374
- if (this._localeFallbacks.length > 0) {
375
- const fallbacks = this._localeFallbacks.slice();
376
- options.defaultNS = fallbacks[0];
377
- options.fallbackNS = fallbacks.slice(1);
378
- options.ns = fallbacks;
379
- }
380
- console.log('<pb-page> i18next options: %o', options);
381
- this._i18nInstance = i18next.createInstance();
382
- this._i18nInstance
383
- .use(LanguageDetector)
384
- .use(Backend);
385
- this._i18nInstance.init(options)
386
- .then((t) => {
387
- initTranslation(t);
388
- // initialized and ready to go!
389
- this._updateI18n(t);
390
- this.signalReady('pb-i18n-update', { t, language: this._i18nInstance.language });
391
- if (this.requireLanguage && this.apiVersion) {
392
- this.signalReady('pb-page-ready', {
393
- endpoint: this.endpoint,
394
- apiVersion: this.apiVersion,
395
- template: this.template,
396
- language: this._i18nInstance.language
397
- });
398
- }
399
- });
281
+ const target = registry.state._target;
282
+ if (target) {
283
+ this.endpoint = target;
284
+ }
400
285
 
401
- this.subscribeTo('pb-i18n-language', ev => {
402
- const { language } = ev.detail;
403
- this._i18nInstance.changeLanguage(language).then(t => {
404
- this._updateI18n(t);
405
- this.emitTo('pb-i18n-update', { t, language: this._i18nInstance.language }, []);
406
- }, []);
407
- });
286
+ const apiVersion = registry.state._api;
287
+ if (apiVersion) {
288
+ this.apiVersion = apiVersion;
289
+ }
408
290
 
291
+ const stylesheetURLs = [];
292
+ if (this.theme) {
293
+ stylesheetURLs.push(this.toAbsoluteURL(this.theme, this.endpoint));
294
+ } else {
295
+ stylesheetURLs.push('components.css');
296
+ }
297
+ console.log('<pb-page> Loading component theme stylesheets from %s', stylesheetURLs.join(', '));
298
+ this._themeSheet = await loadStylesheets(stylesheetURLs);
299
+
300
+ // try to figure out what version of TEI Publisher the server is running
301
+ if (!this.apiVersion) {
302
+ // first check if it has a login endpoint, i.e. runs a version < 7
303
+ // this is necessary to prevent a CORS failure
304
+ const json = await fetch(`${this.endpoint}/login`)
305
+ .then(res => {
306
+ if (res.ok) {
307
+ return null;
308
+ }
309
+ // if not, access the actual /api/version endpoint to retrieve the API version
310
+ return fetch(`${this.endpoint}/api/version`).then(res2 => res2.json());
311
+ })
312
+ .catch(() => fetch(`${this.endpoint}/api/version`).then(res2 => res2.json()));
313
+
314
+ if (json) {
315
+ this.apiVersion = json.api;
316
+ console.log(
317
+ `<pb-page> Server reports API version ${this.apiVersion} with app ${json.app.name}/${json.app.version} running on ${json.engine.name}/${json.engine.version}`,
318
+ );
319
+ } else {
320
+ console.log('<pb-page> No API version reported by server, assuming 0.9.0');
321
+ this.apiVersion = '0.9.0';
322
+ }
323
+ }
409
324
 
410
- // this.subscribeTo('pb-global-toggle', this._toggleFeatures.bind(this));
411
- this.addEventListener('pb-global-toggle', this._toggleFeatures.bind(this));
412
- this.unresolved = false;
325
+ if (!this.requireLanguage) {
326
+ this.signalReady('pb-page-ready', {
327
+ endpoint: this.endpoint,
328
+ template: this.template,
329
+ apiVersion: this.apiVersion,
330
+ });
331
+ }
332
+ // Note: If requireLanguage is true, pb-page-ready will be signaled after i18n initialization in firstUpdated()
333
+ }
413
334
 
414
- console.log('<pb-page> endpoint: %s; trigger window resize', this.endpoint);
415
- this.querySelectorAll('app-header').forEach(h => h._notifyLayoutChanged());
335
+ firstUpdated() {
336
+ super.firstUpdated();
416
337
 
417
- typesetMath(this);
338
+ if (this.disabled) {
339
+ return;
418
340
  }
419
341
 
420
- _updateI18n(t) {
421
- this.querySelectorAll('[data-i18n]').forEach(elem => {
422
- const targets = elem.getAttribute('data-i18n');
423
- const regex = /(?:\[([^\]]+)\])?([^;]+)/g;
424
- let m = regex.exec(targets);
425
- while (m) {
426
- const translated = t(m[2]);
427
- if (m[1]) {
428
- elem.setAttribute(m[1], translated);
429
- } else {
430
- elem.innerHTML = translated;
431
- }
432
- m = regex.exec(targets);
433
- }
342
+
343
+ const slot = this.shadowRoot.querySelector('slot');
344
+ slot.addEventListener(
345
+ 'slotchange',
346
+ () => {
347
+ const ev = new CustomEvent('pb-page-loaded', {
348
+ bubbles: true,
349
+ composed: true,
434
350
  });
351
+ this.dispatchEvent(ev);
352
+ },
353
+ { once: true },
354
+ );
355
+
356
+ const defaultLocales = this.endpoint
357
+ ? `${this.toAbsoluteURL('resources/i18n/', this.endpoint)}{{ns}}/{{lng}}.json`
358
+ : `${resolveURL('../i18n/')}{{ns}}/{{lng}}.json`;
359
+ console.log(
360
+ '<pb-page> Loading locales. common: %s; additional: %s; namespaces: %o',
361
+ defaultLocales,
362
+ this.locales,
363
+ this._localeFallbacks,
364
+ );
365
+ const backends = this.locales ? [XHR, XHR] : [XHR];
366
+ const backendOptions = [
367
+ {
368
+ loadPath: defaultLocales,
369
+ crossDomain: true,
370
+ },
371
+ ];
372
+ if (this.locales) {
373
+ backendOptions.unshift({
374
+ loadPath: this.locales,
375
+ crossDomain: true,
376
+ });
435
377
  }
436
-
437
- get stylesheet() {
438
- return this._themeSheet;
378
+ const options = {
379
+ fallbackLng: this.fallbackLanguage,
380
+ defaultNS: 'common',
381
+ ns: ['common'],
382
+ debug: false,
383
+ load: 'languageOnly',
384
+ detection: {
385
+ lookupQuerystring: 'lang',
386
+ },
387
+ backend: {
388
+ backends,
389
+ backendOptions,
390
+ },
391
+ };
392
+ if (this.language) {
393
+ options.lng = this.language;
439
394
  }
440
-
441
- /**
442
- * Handle the `pb-toggle` event sent by `pb-select-feature` or `pb-toggle-feature`
443
- * and dispatch actions to the elements on the page.
444
- */
445
- _toggleFeatures(ev) {
446
- const sc = ev.detail;
447
- this.querySelectorAll(sc.selector).forEach(node => {
448
- const command = sc.command || 'toggle';
449
- if (node.command) {
450
- node.command(command, sc.state);
451
- }
452
- if (sc.state) {
453
- node.classList.add(command);
454
- } else {
455
- node.classList.remove(command);
456
- }
457
- });
395
+ console.log('supported langs: %o', this.supportedLanguages);
396
+ if (this.supportedLanguages) {
397
+ options.supportedLngs = this.supportedLanguages;
458
398
  }
459
399
 
460
- render() {
461
- return html`<slot></slot>`;
400
+ if (this._localeFallbacks.length > 0) {
401
+ const fallbacks = this._localeFallbacks.slice();
402
+ options.defaultNS = fallbacks[0];
403
+ options.fallbackNS = fallbacks.slice(1);
404
+ options.ns = fallbacks;
462
405
  }
463
-
464
- static get styles() {
465
- return css`
466
- :host {
467
- display: block;
406
+ console.log('<pb-page> i18next options: %o', options);
407
+ this._i18nInstance = i18next.createInstance();
408
+ this._i18nInstance.use(LanguageDetector).use(Backend);
409
+ this._i18nInstance.init(options).then(t => {
410
+ initTranslation(t);
411
+ // initialized and ready to go!
412
+ this._updateI18n(t);
413
+ this.signalReady('pb-i18n-update', { t, language: this._i18nInstance?.language });
414
+ if (this.requireLanguage) {
415
+ this.signalReady('pb-page-ready', {
416
+ endpoint: this.endpoint,
417
+ apiVersion: this.apiVersion,
418
+ template: this.template,
419
+ language: this._i18nInstance?.language,
420
+ });
421
+ }
422
+ });
423
+
424
+ // React to language change events by updating i18n and notifying listeners
425
+ this.subscribeTo('pb-i18n-language', ev => {
426
+ const { language } = ev.detail;
427
+ this._i18nInstance.changeLanguage(language).then(t => {
428
+ this._updateI18n(t);
429
+ this.emitTo('pb-i18n-update', { t, language: this._i18nInstance?.language }, []);
430
+ }, []);
431
+ });
432
+
433
+ // this.subscribeTo('pb-global-toggle', this._toggleFeatures.bind(this));
434
+ this.addEventListener('pb-global-toggle', this._toggleFeatures.bind(this));
435
+ // Avoid a Lit reactive update here; just remove the attribute instead.
436
+ this.removeAttribute('unresolved');
437
+
438
+ console.log('<pb-page> endpoint: %s; trigger window resize', this.endpoint);
439
+ // Guard: some app-header implementations may not expose _notifyLayoutChanged
440
+ this.querySelectorAll('app-header').forEach(h => {
441
+ if (typeof h._notifyLayoutChanged === 'function') {
442
+ h._notifyLayoutChanged();
443
+ }
444
+ });
445
+
446
+ typesetMath(this);
447
+ }
448
+
449
+ _updateI18n(t) {
450
+ this.querySelectorAll('[data-i18n]').forEach(elem => {
451
+ const targets = elem.getAttribute('data-i18n');
452
+ const regex = /(?:\[([^\]]+)\])?([^;]+)/g;
453
+ let m = regex.exec(targets);
454
+ while (m) {
455
+ const translated = t(m[2]);
456
+ if (m[1]) {
457
+ elem.setAttribute(m[1], translated);
458
+ } else {
459
+ elem.innerHTML = translated;
468
460
  }
469
- `;
470
- }
461
+ m = regex.exec(targets);
462
+ }
463
+ });
464
+ }
465
+
466
+ get stylesheet() {
467
+ return this._themeSheet;
468
+ }
469
+
470
+ /**
471
+ * Handle the `pb-toggle` event sent by `pb-select-feature` or `pb-toggle-feature`
472
+ * and dispatch actions to the elements on the page.
473
+ */
474
+ _toggleFeatures(ev) {
475
+ const sc = ev.detail;
476
+ this.querySelectorAll(sc.selector).forEach(node => {
477
+ const command = sc.command || 'toggle';
478
+ if (node.command) {
479
+ node.command(command, sc.state);
480
+ }
481
+ if (sc.state) {
482
+ node.classList.add(command);
483
+ } else {
484
+ node.classList.remove(command);
485
+ }
486
+ });
487
+ }
488
+
489
+ render() {
490
+ return html`<slot></slot>`;
491
+ }
492
+
493
+ static get styles() {
494
+ return css`
495
+ :host {
496
+ display: block;
497
+ }
498
+ `;
499
+ }
471
500
  }
472
501
 
473
- customElements.define('pb-page', PbPage);
502
+ customElements.define('pb-page', PbPage);