@teipublisher/pb-components 1.43.4 → 1.44.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.
package/src/pb-page.js CHANGED
@@ -1,400 +1,400 @@
1
- import { LitElement, html, css } from 'lit-element';
2
- import i18next from 'i18next';
3
- import LanguageDetector from 'i18next-browser-languagedetector';
4
- import XHR from 'i18next-xhr-backend';
5
- import Backend from 'i18next-chained-backend';
6
- import { pbMixin, clearPageEvents } from './pb-mixin.js';
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
-
12
- /**
13
- * Make sure there's only one instance of pb-page active at any time.
14
- */
15
- let _instance;
16
-
17
- /**
18
- * Configuration element which should wrap around other `pb-` elements.
19
- * Among other things, this element determines the TEI Publisher
20
- * instance to which all elements will talk (property `endpoint`), and
21
- * initializes the i18n language module.
22
- *
23
- * @slot - default unnamed slot for content
24
- * @fires pb-page-ready - fired when the endpoint and language settings have been determined
25
- * @fires pb-i18n-update - fired when the user selected a different display language
26
- * @fires pb-i18n-language - when received, changes the language to the one passed in the event and proceeds to pb-i18-update
27
- * @fires pb-toggle - when received, dispatch state changes to the elements on the page (see `pb-toggle-feature`, `pb-select-feature`)
28
- */
29
- export class PbPage extends pbMixin(LitElement) {
30
-
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
- * TEI Publisher internal: set to the current page template.
43
- */
44
- template: {
45
- type: String
46
- },
47
- /**
48
- * The base URL of the TEI Publisher instance. All nested elements will
49
- * talk to this instance. By default it is set to the URL the
50
- * page was loaded from.
51
- *
52
- * The endpoint can be overwritten by providing an HTTP request parameter
53
- * `_target` with an URL.
54
- */
55
- endpoint: {
56
- type: String,
57
- reflect: true
58
- },
59
- apiVersion: {
60
- type: String,
61
- attribute: 'api-version',
62
- reflect: true
63
- },
64
- /**
65
- * Optional URL pointing to a directory from which additional i18n
66
- * language files will be loaded. The URL should contain placeholders
67
- * for the language (`lng`) and the namespace (`ns`), e.g.
68
- *
69
- * `resources/i18n/{{ns}}_{{lng}}.json`
70
- *
71
- * or
72
- *
73
- * `resources/i18n/{{ns}}/{{lng}}.json`
74
- *
75
- * The latter assumes custom language files in a subdirectory, the first
76
- * expects the namespace to be specified at the start of the file name.
77
- *
78
- * The default namespace for custom language files is assumed to be `app`,
79
- * but you can define additional namespaces via `localeFallbackNS`.
80
- */
81
- locales: {
82
- type: String
83
- },
84
- /**
85
- * Optional list of whitespace separated namespaces which should be searched
86
- * for translations. By default, only the namespace `common` is queried.
87
- * If the `locales` property is specified, an additional namespace `app` is added.
88
- * You can add more namespace here, e.g. `custom`, if you want to provide
89
- * translations for custom apps or components.
90
- */
91
- localeFallbackNs: {
92
- type: String,
93
- attribute: 'locale-fallback-ns'
94
- },
95
- /**
96
- * Set a language for i18n (e.g. 'en' or 'de'). If not set, browser language
97
- * detection will be used.
98
- */
99
- language: {
100
- type: String
101
- },
102
- /**
103
- * If set, the element will wait for a language being set by i18n before
104
- * it sends a `pb-page-ready` event. Elements like `pb-view` will wait
105
- * for this event before displaying content.
106
- *
107
- * Also, `pb-view` will pass the configured language to the server endpoint
108
- * where it will be available to ODD processing models in variable
109
- * `$parameters?language` and can thus be used to change output depending on
110
- * the user interface language.
111
- *
112
- * If you would like `pb-view` to refresh automatically whenever the language
113
- * setting changes, specify property `useLanguage` on the corresponding `pb-view`.
114
- */
115
- requireLanguage: {
116
- type: Boolean,
117
- attribute: 'require-language'
118
- },
119
- /**
120
- * Will be set while the component is loading and unset when
121
- * it is fully loaded. Use to avoid flash of unstyled content
122
- * via CSS: set `unresolved` on `pb-page` in the HTML and
123
- * add a CSS rule like:
124
- *
125
- * ```css
126
- * pb-page[unresolved] {
127
- * display: none;
128
- * }
129
- * ```
130
- */
131
- unresolved: {
132
- type: Boolean,
133
- reflect: true
134
- },
135
- theme: {
136
- type: String
137
- }
138
- };
139
- }
140
-
141
- constructor() {
142
- super();
143
- this.unresolved = true;
144
- this.endpoint = ".";
145
- this.apiVersion = undefined;
146
- this.requireLanguage = false;
147
- this.theme = null;
148
- this._localeFallbacks = [];
149
- this._i18nInstance = null;
150
-
151
- if (_instance) {
152
- this.disabled = true;
153
- } else {
154
- _instance = this;
155
-
156
- // clear global page events which might have been set by other pb-page instances.
157
- // important while running the test suite.
158
- clearPageEvents();
159
- }
160
- }
161
-
162
- set localeFallbackNs(value) {
163
- value.split(/\s+/).forEach(v => this._localeFallbacks.push(v));
164
- }
165
-
166
- disconnectedCallback() {
167
- super.disconnectedCallback();
168
- this._i18nInstance = null;
169
- if (_instance === this) {
170
- // clear to allow future instances
171
- _instance = null;
172
- }
173
- }
174
-
175
- async connectedCallback() {
176
- super.connectedCallback();
177
-
178
- if (this.disabled) {
179
- return;
180
- }
181
-
182
- this.endpoint = this.endpoint.replace(/\/+$/, '');
183
-
184
- if (this.locales && this._localeFallbacks.indexOf('app') === -1) {
185
- this._localeFallbacks.push('app');
186
- }
187
- this._localeFallbacks.push('common');
188
-
189
- const target = this.getParameter('_target');
190
- if (target) {
191
- this.endpoint = target;
192
- }
193
-
194
- const apiVersion = this.getParameter('_api');
195
- if (apiVersion) {
196
- this.apiVersion = apiVersion;
197
- }
198
-
199
- const stylesheetURLs = [
200
- resolveURL('../css/components.css')
201
- ];
202
- if (this.theme) {
203
- stylesheetURLs.push(this.toAbsoluteURL(this.theme, this.endpoint));
204
- }
205
- console.log('<pb-page> Loading component theme stylesheets from %s', stylesheetURLs.join(', '));
206
- this._themeSheet = await loadStylesheets(stylesheetURLs);
207
-
208
- // try to figure out what version of TEI Publisher the server is running
209
- if (!this.apiVersion) {
210
- // first check if it has a login endpoint, i.e. runs a version < 7
211
- // this is necessary to prevent a CORS failure
212
- const json = await fetch(`${this.endpoint}/login`)
213
- .then((res) => {
214
- if (res.ok) {
215
- return null;
216
- }
217
- // if not, access the actual /api/version endpoint to retrieve the API version
218
- return fetch(`${this.endpoint}/api/version`)
219
- .then((res2) => res2.json());
220
- })
221
- .catch(() => fetch(`${this.endpoint}/api/version`)
222
- .then((res2) => res2.json())
223
- );
224
-
225
- if (json) {
226
- this.apiVersion = json.api;
227
- 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}`);
228
- } else {
229
- console.log('<pb-page> No API version reported by server, assuming 0.9.0');
230
- this.apiVersion = '0.9.0';
231
- }
232
- }
233
-
234
- if (!this.requireLanguage) {
235
- this.signalReady('pb-page-ready', {
236
- endpoint: this.endpoint,
237
- template: this.template,
238
- apiVersion: this.apiVersion
239
- });
240
- } else if (this._i18nInstance) {
241
- this.signalReady('pb-page-ready', {
242
- endpoint: this.endpoint,
243
- apiVersion: this.apiVersion,
244
- template: this.template,
245
- language: this._i18nInstance.language
246
- });
247
- }
248
- }
249
-
250
- firstUpdated() {
251
- super.firstUpdated();
252
-
253
- if (this.disabled) {
254
- return;
255
- }
256
-
257
- const slot = this.shadowRoot.querySelector('slot');
258
- slot.addEventListener('slotchange', () => {
259
- const ev = new CustomEvent('pb-page-loaded', {
260
- bubbles: true,
261
- composed: true
262
- });
263
- this.dispatchEvent(ev);
264
- }, { once: true });
265
-
266
- const defaultLocales = resolveURL('../i18n/') + '{{ns}}/{{lng}}.json';
267
- console.log('<pb-page> Loading locales. common: %s; additional: %s; namespaces: %o',
268
- defaultLocales, this.locales, this._localeFallbacks);
269
- const backends = this.locales ? [XHR, XHR] : [XHR];
270
- const backendOptions = [{
271
- loadPath: defaultLocales,
272
- crossDomain: true
273
- }];
274
- if (this.locales) {
275
- backendOptions.unshift({
276
- loadPath: this.locales,
277
- crossDomain: true
278
- });
279
- }
280
- const options = {
281
- fallbackLng: 'en',
282
- defaultNS: 'common',
283
- ns: ['common'],
284
- debug: false,
285
- load: 'languageOnly',
286
- detection: {
287
- lookupQuerystring: 'lang'
288
- },
289
- backend: {
290
- backends,
291
- backendOptions
292
- }
293
- };
294
- if (this.language) {
295
- options.lng = this.language;
296
- }
297
-
298
- if (this._localeFallbacks.length > 0) {
299
- const fallbacks = this._localeFallbacks.slice();
300
- options.defaultNS = fallbacks[0];
301
- options.fallbackNS = fallbacks.slice(1);
302
- options.ns = fallbacks;
303
- }
304
- console.log('<pb-page> i18next options: %o', options);
305
- this._i18nInstance = i18next.createInstance();
306
- this._i18nInstance
307
- .use(LanguageDetector)
308
- .use(Backend);
309
- this._i18nInstance.init(options)
310
- .then((t) => {
311
- initTranslation(t);
312
- // initialized and ready to go!
313
- this._updateI18n(t);
314
- this.signalReady('pb-i18n-update', { t, language: this._i18nInstance.language });
315
- if (this.requireLanguage && this.apiVersion) {
316
- this.signalReady('pb-page-ready', {
317
- endpoint: this.endpoint,
318
- apiVersion: this.apiVersion,
319
- template: this.template,
320
- language: this._i18nInstance.language
321
- });
322
- }
323
- });
324
-
325
- this.subscribeTo('pb-i18n-language', ev => {
326
- const { language } = ev.detail;
327
- this._i18nInstance.changeLanguage(language).then(t => {
328
- this._updateI18n(t);
329
- this.emitTo('pb-i18n-update', { t, language: this._i18nInstance.language }, []);
330
- }, []);
331
- });
332
-
333
-
334
- this.subscribeTo('pb-toggle', this._toggleFeatures.bind(this));
335
-
336
- this.unresolved = false;
337
-
338
- console.log('<pb-page> endpoint: %s; trigger window resize', this.endpoint);
339
- this.querySelectorAll('app-header').forEach(h => h._notifyLayoutChanged());
340
-
341
- typesetMath(this);
342
- }
343
-
344
- _updateI18n(t) {
345
- this.querySelectorAll('[data-i18n]').forEach(elem => {
346
- const targets = elem.getAttribute('data-i18n');
347
- const regex = /(?:\[([^\]]+)\])?([^;]+)/g;
348
- let m = regex.exec(targets);
349
- while (m) {
350
- const translated = t(m[2]);
351
- if (m[1]) {
352
- elem.setAttribute(m[1], translated);
353
- } else {
354
- elem.innerHTML = translated;
355
- }
356
- m = regex.exec(targets);
357
- }
358
- });
359
- }
360
-
361
- get stylesheet() {
362
- return this._themeSheet;
363
- }
364
-
365
- /**
366
- * Handle the `pb-toggle` event sent by `pb-select-feature` or `pb-toggle-feature`
367
- * and dispatch actions to the elements on the page.
368
- */
369
- _toggleFeatures(ev) {
370
- if (ev.detail.selectors) {
371
- ev.detail.selectors.forEach(sc => {
372
- this.querySelectorAll(sc.selector).forEach(node => {
373
- const command = sc.command || 'toggle';
374
- if (node.command) {
375
- node.command(command, sc.state);
376
- }
377
- if (sc.state) {
378
- node.classList.add(command);
379
- } else {
380
- node.classList.remove(command);
381
- }
382
- });
383
- });
384
- }
385
- }
386
-
387
- render() {
388
- return html`<slot></slot>`;
389
- }
390
-
391
- static get styles() {
392
- return css`
393
- :host {
394
- display: block;
395
- }
396
- `;
397
- }
398
- }
399
-
1
+ import { LitElement, html, css } from 'lit-element';
2
+ import i18next from 'i18next';
3
+ import LanguageDetector from 'i18next-browser-languagedetector';
4
+ import XHR from 'i18next-xhr-backend';
5
+ import Backend from 'i18next-chained-backend';
6
+ import { pbMixin, clearPageEvents } from './pb-mixin.js';
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
+
12
+ /**
13
+ * Make sure there's only one instance of pb-page active at any time.
14
+ */
15
+ let _instance;
16
+
17
+ /**
18
+ * Configuration element which should wrap around other `pb-` elements.
19
+ * Among other things, this element determines the TEI Publisher
20
+ * instance to which all elements will talk (property `endpoint`), and
21
+ * initializes the i18n language module.
22
+ *
23
+ * @slot - default unnamed slot for content
24
+ * @fires pb-page-ready - fired when the endpoint and language settings have been determined
25
+ * @fires pb-i18n-update - fired when the user selected a different display language
26
+ * @fires pb-i18n-language - when received, changes the language to the one passed in the event and proceeds to pb-i18-update
27
+ * @fires pb-toggle - when received, dispatch state changes to the elements on the page (see `pb-toggle-feature`, `pb-select-feature`)
28
+ */
29
+ export class PbPage extends pbMixin(LitElement) {
30
+
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
+ * TEI Publisher internal: set to the current page template.
43
+ */
44
+ template: {
45
+ type: String
46
+ },
47
+ /**
48
+ * The base URL of the TEI Publisher instance. All nested elements will
49
+ * talk to this instance. By default it is set to the URL the
50
+ * page was loaded from.
51
+ *
52
+ * The endpoint can be overwritten by providing an HTTP request parameter
53
+ * `_target` with an URL.
54
+ */
55
+ endpoint: {
56
+ type: String,
57
+ reflect: true
58
+ },
59
+ apiVersion: {
60
+ type: String,
61
+ attribute: 'api-version',
62
+ reflect: true
63
+ },
64
+ /**
65
+ * Optional URL pointing to a directory from which additional i18n
66
+ * language files will be loaded. The URL should contain placeholders
67
+ * for the language (`lng`) and the namespace (`ns`), e.g.
68
+ *
69
+ * `resources/i18n/{{ns}}_{{lng}}.json`
70
+ *
71
+ * or
72
+ *
73
+ * `resources/i18n/{{ns}}/{{lng}}.json`
74
+ *
75
+ * The latter assumes custom language files in a subdirectory, the first
76
+ * expects the namespace to be specified at the start of the file name.
77
+ *
78
+ * The default namespace for custom language files is assumed to be `app`,
79
+ * but you can define additional namespaces via `localeFallbackNS`.
80
+ */
81
+ locales: {
82
+ type: String
83
+ },
84
+ /**
85
+ * Optional list of whitespace separated namespaces which should be searched
86
+ * for translations. By default, only the namespace `common` is queried.
87
+ * If the `locales` property is specified, an additional namespace `app` is added.
88
+ * You can add more namespace here, e.g. `custom`, if you want to provide
89
+ * translations for custom apps or components.
90
+ */
91
+ localeFallbackNs: {
92
+ type: String,
93
+ attribute: 'locale-fallback-ns'
94
+ },
95
+ /**
96
+ * Set a language for i18n (e.g. 'en' or 'de'). If not set, browser language
97
+ * detection will be used.
98
+ */
99
+ language: {
100
+ type: String
101
+ },
102
+ /**
103
+ * If set, the element will wait for a language being set by i18n before
104
+ * it sends a `pb-page-ready` event. Elements like `pb-view` will wait
105
+ * for this event before displaying content.
106
+ *
107
+ * Also, `pb-view` will pass the configured language to the server endpoint
108
+ * where it will be available to ODD processing models in variable
109
+ * `$parameters?language` and can thus be used to change output depending on
110
+ * the user interface language.
111
+ *
112
+ * If you would like `pb-view` to refresh automatically whenever the language
113
+ * setting changes, specify property `useLanguage` on the corresponding `pb-view`.
114
+ */
115
+ requireLanguage: {
116
+ type: Boolean,
117
+ attribute: 'require-language'
118
+ },
119
+ /**
120
+ * Will be set while the component is loading and unset when
121
+ * it is fully loaded. Use to avoid flash of unstyled content
122
+ * via CSS: set `unresolved` on `pb-page` in the HTML and
123
+ * add a CSS rule like:
124
+ *
125
+ * ```css
126
+ * pb-page[unresolved] {
127
+ * display: none;
128
+ * }
129
+ * ```
130
+ */
131
+ unresolved: {
132
+ type: Boolean,
133
+ reflect: true
134
+ },
135
+ theme: {
136
+ type: String
137
+ }
138
+ };
139
+ }
140
+
141
+ constructor() {
142
+ super();
143
+ this.unresolved = true;
144
+ this.endpoint = ".";
145
+ this.apiVersion = undefined;
146
+ this.requireLanguage = false;
147
+ this.theme = null;
148
+ this._localeFallbacks = [];
149
+ this._i18nInstance = null;
150
+
151
+ if (_instance) {
152
+ this.disabled = true;
153
+ } else {
154
+ _instance = this;
155
+
156
+ // clear global page events which might have been set by other pb-page instances.
157
+ // important while running the test suite.
158
+ clearPageEvents();
159
+ }
160
+ }
161
+
162
+ set localeFallbackNs(value) {
163
+ value.split(/\s+/).forEach(v => this._localeFallbacks.push(v));
164
+ }
165
+
166
+ disconnectedCallback() {
167
+ super.disconnectedCallback();
168
+ this._i18nInstance = null;
169
+ if (_instance === this) {
170
+ // clear to allow future instances
171
+ _instance = null;
172
+ }
173
+ }
174
+
175
+ async connectedCallback() {
176
+ super.connectedCallback();
177
+
178
+ if (this.disabled) {
179
+ return;
180
+ }
181
+
182
+ this.endpoint = this.endpoint.replace(/\/+$/, '');
183
+
184
+ if (this.locales && this._localeFallbacks.indexOf('app') === -1) {
185
+ this._localeFallbacks.push('app');
186
+ }
187
+ this._localeFallbacks.push('common');
188
+
189
+ const target = this.getParameter('_target');
190
+ if (target) {
191
+ this.endpoint = target;
192
+ }
193
+
194
+ const apiVersion = this.getParameter('_api');
195
+ if (apiVersion) {
196
+ this.apiVersion = apiVersion;
197
+ }
198
+
199
+ const stylesheetURLs = [
200
+ resolveURL('../css/components.css')
201
+ ];
202
+ if (this.theme) {
203
+ stylesheetURLs.push(this.toAbsoluteURL(this.theme, this.endpoint));
204
+ }
205
+ console.log('<pb-page> Loading component theme stylesheets from %s', stylesheetURLs.join(', '));
206
+ this._themeSheet = await loadStylesheets(stylesheetURLs);
207
+
208
+ // try to figure out what version of TEI Publisher the server is running
209
+ if (!this.apiVersion) {
210
+ // first check if it has a login endpoint, i.e. runs a version < 7
211
+ // this is necessary to prevent a CORS failure
212
+ const json = await fetch(`${this.endpoint}/login`)
213
+ .then((res) => {
214
+ if (res.ok) {
215
+ return null;
216
+ }
217
+ // if not, access the actual /api/version endpoint to retrieve the API version
218
+ return fetch(`${this.endpoint}/api/version`)
219
+ .then((res2) => res2.json());
220
+ })
221
+ .catch(() => fetch(`${this.endpoint}/api/version`)
222
+ .then((res2) => res2.json())
223
+ );
224
+
225
+ if (json) {
226
+ this.apiVersion = json.api;
227
+ 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}`);
228
+ } else {
229
+ console.log('<pb-page> No API version reported by server, assuming 0.9.0');
230
+ this.apiVersion = '0.9.0';
231
+ }
232
+ }
233
+
234
+ if (!this.requireLanguage) {
235
+ this.signalReady('pb-page-ready', {
236
+ endpoint: this.endpoint,
237
+ template: this.template,
238
+ apiVersion: this.apiVersion
239
+ });
240
+ } else if (this._i18nInstance) {
241
+ this.signalReady('pb-page-ready', {
242
+ endpoint: this.endpoint,
243
+ apiVersion: this.apiVersion,
244
+ template: this.template,
245
+ language: this._i18nInstance.language
246
+ });
247
+ }
248
+ }
249
+
250
+ firstUpdated() {
251
+ super.firstUpdated();
252
+
253
+ if (this.disabled) {
254
+ return;
255
+ }
256
+
257
+ const slot = this.shadowRoot.querySelector('slot');
258
+ slot.addEventListener('slotchange', () => {
259
+ const ev = new CustomEvent('pb-page-loaded', {
260
+ bubbles: true,
261
+ composed: true
262
+ });
263
+ this.dispatchEvent(ev);
264
+ }, { once: true });
265
+
266
+ const defaultLocales = resolveURL('../i18n/') + '{{ns}}/{{lng}}.json';
267
+ console.log('<pb-page> Loading locales. common: %s; additional: %s; namespaces: %o',
268
+ defaultLocales, this.locales, this._localeFallbacks);
269
+ const backends = this.locales ? [XHR, XHR] : [XHR];
270
+ const backendOptions = [{
271
+ loadPath: defaultLocales,
272
+ crossDomain: true
273
+ }];
274
+ if (this.locales) {
275
+ backendOptions.unshift({
276
+ loadPath: this.locales,
277
+ crossDomain: true
278
+ });
279
+ }
280
+ const options = {
281
+ fallbackLng: 'en',
282
+ defaultNS: 'common',
283
+ ns: ['common'],
284
+ debug: false,
285
+ load: 'languageOnly',
286
+ detection: {
287
+ lookupQuerystring: 'lang'
288
+ },
289
+ backend: {
290
+ backends,
291
+ backendOptions
292
+ }
293
+ };
294
+ if (this.language) {
295
+ options.lng = this.language;
296
+ }
297
+
298
+ if (this._localeFallbacks.length > 0) {
299
+ const fallbacks = this._localeFallbacks.slice();
300
+ options.defaultNS = fallbacks[0];
301
+ options.fallbackNS = fallbacks.slice(1);
302
+ options.ns = fallbacks;
303
+ }
304
+ console.log('<pb-page> i18next options: %o', options);
305
+ this._i18nInstance = i18next.createInstance();
306
+ this._i18nInstance
307
+ .use(LanguageDetector)
308
+ .use(Backend);
309
+ this._i18nInstance.init(options)
310
+ .then((t) => {
311
+ initTranslation(t);
312
+ // initialized and ready to go!
313
+ this._updateI18n(t);
314
+ this.signalReady('pb-i18n-update', { t, language: this._i18nInstance.language });
315
+ if (this.requireLanguage && this.apiVersion) {
316
+ this.signalReady('pb-page-ready', {
317
+ endpoint: this.endpoint,
318
+ apiVersion: this.apiVersion,
319
+ template: this.template,
320
+ language: this._i18nInstance.language
321
+ });
322
+ }
323
+ });
324
+
325
+ this.subscribeTo('pb-i18n-language', ev => {
326
+ const { language } = ev.detail;
327
+ this._i18nInstance.changeLanguage(language).then(t => {
328
+ this._updateI18n(t);
329
+ this.emitTo('pb-i18n-update', { t, language: this._i18nInstance.language }, []);
330
+ }, []);
331
+ });
332
+
333
+
334
+ this.subscribeTo('pb-toggle', this._toggleFeatures.bind(this));
335
+
336
+ this.unresolved = false;
337
+
338
+ console.log('<pb-page> endpoint: %s; trigger window resize', this.endpoint);
339
+ this.querySelectorAll('app-header').forEach(h => h._notifyLayoutChanged());
340
+
341
+ typesetMath(this);
342
+ }
343
+
344
+ _updateI18n(t) {
345
+ this.querySelectorAll('[data-i18n]').forEach(elem => {
346
+ const targets = elem.getAttribute('data-i18n');
347
+ const regex = /(?:\[([^\]]+)\])?([^;]+)/g;
348
+ let m = regex.exec(targets);
349
+ while (m) {
350
+ const translated = t(m[2]);
351
+ if (m[1]) {
352
+ elem.setAttribute(m[1], translated);
353
+ } else {
354
+ elem.innerHTML = translated;
355
+ }
356
+ m = regex.exec(targets);
357
+ }
358
+ });
359
+ }
360
+
361
+ get stylesheet() {
362
+ return this._themeSheet;
363
+ }
364
+
365
+ /**
366
+ * Handle the `pb-toggle` event sent by `pb-select-feature` or `pb-toggle-feature`
367
+ * and dispatch actions to the elements on the page.
368
+ */
369
+ _toggleFeatures(ev) {
370
+ if (ev.detail.selectors) {
371
+ ev.detail.selectors.forEach(sc => {
372
+ this.querySelectorAll(sc.selector).forEach(node => {
373
+ const command = sc.command || 'toggle';
374
+ if (node.command) {
375
+ node.command(command, sc.state);
376
+ }
377
+ if (sc.state) {
378
+ node.classList.add(command);
379
+ } else {
380
+ node.classList.remove(command);
381
+ }
382
+ });
383
+ });
384
+ }
385
+ }
386
+
387
+ render() {
388
+ return html`<slot></slot>`;
389
+ }
390
+
391
+ static get styles() {
392
+ return css`
393
+ :host {
394
+ display: block;
395
+ }
396
+ `;
397
+ }
398
+ }
399
+
400
400
  customElements.define('pb-page', PbPage);