@teipublisher/pb-components 1.43.5 → 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/.github/workflows/main.yml +21 -0
- package/CHANGELOG.md +7 -0
- package/dist/demo/pb-table-grid.html +16 -16
- package/dist/demo/pb-view2.html +135 -135
- package/dist/demo/redirect.html +3 -0
- package/dist/pb-code-editor.js +1 -1
- package/dist/pb-components-bundle.js +95 -104
- package/dist/pb-edit-app.js +1 -1
- package/dist/pb-elements.json +1009 -690
- package/dist/{pb-i18n-dc551878.js → pb-i18n-3963b098.js} +1 -1
- package/dist/pb-leaflet-map.js +1 -1
- package/dist/pb-odd-editor.js +1 -1
- package/dist/{pb-message-fbc1b645.js → vaadin-element-mixin-08cf11b5.js} +21 -19
- package/gh-pages.js +23 -0
- package/i18n/common/en.json +7 -2
- package/package.json +5 -3
- package/pb-elements.json +1009 -690
- package/src/pb-ajax.js +37 -19
- package/src/pb-browse-docs.js +520 -520
- package/src/pb-browse.js +77 -0
- package/src/pb-components.js +1 -0
- package/src/pb-message.js +6 -10
- package/src/pb-mixin.js +542 -542
- package/src/pb-page.js +399 -399
- package/src/pb-split-list.js +188 -188
- package/src/pb-table-grid.js +218 -218
- package/src/pb-view.js +1366 -1366
- /package/dist/{pb-mixin-f8b22e51.js → pb-mixin-88125cb2.js} +0 -0
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);
|