@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-mixin.js
CHANGED
|
@@ -1,542 +1,542 @@
|
|
|
1
|
-
import { cmpVersion } from './utils.js';
|
|
2
|
-
|
|
3
|
-
if (!window.TeiPublisher) {
|
|
4
|
-
window.TeiPublisher = {};
|
|
5
|
-
|
|
6
|
-
TeiPublisher.url = new URL(window.location.href);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Global set to record the names of the channels for which a
|
|
11
|
-
* `pb-ready` event was fired.
|
|
12
|
-
*/
|
|
13
|
-
const readyEventsFired = new Set();
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Gobal map to record the initialization events which have
|
|
17
|
-
* been received.
|
|
18
|
-
*/
|
|
19
|
-
const initEventsFired = new Map();
|
|
20
|
-
|
|
21
|
-
export function clearPageEvents() {
|
|
22
|
-
initEventsFired.clear();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Wait until the global event identified by name
|
|
27
|
-
* has been fired once. This is mainly used to wait for initialization
|
|
28
|
-
* events like `pb-page-ready`.
|
|
29
|
-
*
|
|
30
|
-
* @param {string} name
|
|
31
|
-
* @param {Function} callback
|
|
32
|
-
*/
|
|
33
|
-
export function waitOnce(name, callback) {
|
|
34
|
-
if (initEventsFired.has(name)) {
|
|
35
|
-
callback(initEventsFired.get(name));
|
|
36
|
-
} else {
|
|
37
|
-
document.addEventListener(name, (ev) => {
|
|
38
|
-
initEventsFired.set(name, ev.detail);
|
|
39
|
-
callback(ev.detail);
|
|
40
|
-
}, {
|
|
41
|
-
once: true
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Implements the core channel/event mechanism used by components in TEI Publisher
|
|
48
|
-
* to communicate.
|
|
49
|
-
*
|
|
50
|
-
* As there might be several documents/fragments being displayed on a page at the same time,
|
|
51
|
-
* a simple event mechanism is not enough for components to exchange messages. They need to
|
|
52
|
-
* be able to target a specific view. The mechanism implemented by this mixin thus combines
|
|
53
|
-
* events and channels. Components may emit an event into a named channel to which other
|
|
54
|
-
* components might subscribe. For example, there might be a view which subscribes to the
|
|
55
|
-
* channel *transcription* and another one subscribing to *translation*. By using distinct
|
|
56
|
-
* channels, other components can address only one of the two.
|
|
57
|
-
*
|
|
58
|
-
* @polymer
|
|
59
|
-
* @mixinFunction
|
|
60
|
-
*/
|
|
61
|
-
export const pbMixin = (superclass) => class PbMixin extends superclass {
|
|
62
|
-
|
|
63
|
-
static get properties() {
|
|
64
|
-
return {
|
|
65
|
-
/**
|
|
66
|
-
* The name of the channel to subscribe to. Only events on a channel corresponding
|
|
67
|
-
* to this property are listened to.
|
|
68
|
-
*/
|
|
69
|
-
subscribe: {
|
|
70
|
-
type: String
|
|
71
|
-
},
|
|
72
|
-
/**
|
|
73
|
-
* Configuration object to define a channel/event mapping. Every property
|
|
74
|
-
* in the object is interpreted as the name of a channel and its value should
|
|
75
|
-
* be an array of event names to listen to.
|
|
76
|
-
*/
|
|
77
|
-
subscribeConfig: {
|
|
78
|
-
type: Object,
|
|
79
|
-
attribute: 'subscribe-config'
|
|
80
|
-
},
|
|
81
|
-
/**
|
|
82
|
-
* The name of the channel to send events to.
|
|
83
|
-
*/
|
|
84
|
-
emit: {
|
|
85
|
-
type: String
|
|
86
|
-
},
|
|
87
|
-
/**
|
|
88
|
-
* Configuration object to define a channel/event mapping. Every property
|
|
89
|
-
* in the object is interpreted as the name of a channel and its value should
|
|
90
|
-
* be an array of event names to be dispatched.
|
|
91
|
-
*/
|
|
92
|
-
emitConfig: {
|
|
93
|
-
type: Object,
|
|
94
|
-
attribute: 'emit-config'
|
|
95
|
-
},
|
|
96
|
-
/**
|
|
97
|
-
* A selector pointing to other components this component depends on.
|
|
98
|
-
* When method `wait` is called, it will wait until all referenced
|
|
99
|
-
* components signal with a `pb-ready` event that they are ready and listening
|
|
100
|
-
* to events.
|
|
101
|
-
*/
|
|
102
|
-
waitFor: {
|
|
103
|
-
type: String,
|
|
104
|
-
attribute: 'wait-for'
|
|
105
|
-
},
|
|
106
|
-
_isReady: {
|
|
107
|
-
type: Boolean
|
|
108
|
-
},
|
|
109
|
-
/**
|
|
110
|
-
* Common property to disable the functionality associated with a component.
|
|
111
|
-
* `pb-highlight` and `pb-popover` react to this.
|
|
112
|
-
*/
|
|
113
|
-
disabled: {
|
|
114
|
-
type: Boolean,
|
|
115
|
-
reflect: true
|
|
116
|
-
},
|
|
117
|
-
_endpoint: {
|
|
118
|
-
type: String
|
|
119
|
-
},
|
|
120
|
-
_apiVersion: {
|
|
121
|
-
type: String
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
constructor() {
|
|
127
|
-
super();
|
|
128
|
-
this._isReady = false;
|
|
129
|
-
this.disabled = false;
|
|
130
|
-
this._subscriptions = new Map();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
connectedCallback() {
|
|
134
|
-
super.connectedCallback();
|
|
135
|
-
waitOnce('pb-page-ready', (options) => {
|
|
136
|
-
this._endpoint = options.endpoint;
|
|
137
|
-
this._apiVersion = options.apiVersion;
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
disconnectedCallback() {
|
|
142
|
-
super.disconnectedCallback();
|
|
143
|
-
this._subscriptions.forEach((handlers, type) => {
|
|
144
|
-
handlers.forEach((handler) => {
|
|
145
|
-
document.removeEventListener(type, handler);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Enable or disable certain features of a component. Called by `pb-toggle-feature`
|
|
152
|
-
* and `pb-select-feature` to change the components behaviour.
|
|
153
|
-
*
|
|
154
|
-
* By default only one command is known: `disable` will disable any interactive feature
|
|
155
|
-
* of the component.
|
|
156
|
-
*
|
|
157
|
-
* @param {string} command name of an action to take or setting to be toggled
|
|
158
|
-
* @param {Boolean} state the state to set the setting to
|
|
159
|
-
*/
|
|
160
|
-
command(command, state) {
|
|
161
|
-
if (command === 'disable') {
|
|
162
|
-
this.disabled = state;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Wait for the components referenced by the selector given in property `waitFor`
|
|
168
|
-
* to signal that they are ready to respond to events. Only wait for elements which
|
|
169
|
-
* emit to one of the channels this component subscribes to.
|
|
170
|
-
*
|
|
171
|
-
* @param callback function to be called when all components are ready
|
|
172
|
-
*/
|
|
173
|
-
wait(callback) {
|
|
174
|
-
if (this.waitFor) {
|
|
175
|
-
const targetNodes = Array.from(document.querySelectorAll(this.waitFor));
|
|
176
|
-
const targets = targetNodes.filter(target => this.emitsOnSameChannel(target));
|
|
177
|
-
const targetCount = targets.length;
|
|
178
|
-
if (targetCount === 0) {
|
|
179
|
-
// selector did not return any targets
|
|
180
|
-
return callback();
|
|
181
|
-
}
|
|
182
|
-
let count = 0;
|
|
183
|
-
targets.forEach((target) => {
|
|
184
|
-
if (target._isReady) {
|
|
185
|
-
count++;
|
|
186
|
-
if (targetCount === count) {
|
|
187
|
-
callback();
|
|
188
|
-
}
|
|
189
|
-
} else {
|
|
190
|
-
const handler = target.addEventListener('pb-ready', (ev) => {
|
|
191
|
-
if (ev.detail.source == this) {
|
|
192
|
-
// same source: ignore
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
count++;
|
|
196
|
-
if (targetCount === count) {
|
|
197
|
-
target.removeEventListener('pb-ready', handler);
|
|
198
|
-
callback();
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
} else {
|
|
204
|
-
callback();
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Wait until a `pb-ready` event is received from one of the channels
|
|
210
|
-
* this component subscribes to. Used internally by components which depend
|
|
211
|
-
* on a particular `pb-view` to be ready and listening to events.
|
|
212
|
-
*
|
|
213
|
-
* @param callback function to be called when `pb-ready` is received
|
|
214
|
-
*/
|
|
215
|
-
waitForChannel(callback) {
|
|
216
|
-
// check first if a `pb-ready` event has already been received on one of the channels
|
|
217
|
-
if (this.subscribeConfig) {
|
|
218
|
-
for (const key in this.subscribeConfig) {
|
|
219
|
-
this.subscribeConfig[key].forEach(t => {
|
|
220
|
-
if (t === 'pb-ready' && readyEventsFired.has(key)) {
|
|
221
|
-
return callback();
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
} else if (
|
|
226
|
-
(this.subscribe && readyEventsFired.has(this.subscribe)) ||
|
|
227
|
-
(!this.subscribe && readyEventsFired.has('__default__'))
|
|
228
|
-
) {
|
|
229
|
-
return callback();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const listeners = this.subscribeTo('pb-ready', (ev) => {
|
|
233
|
-
if (ev.detail._source == this) {
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
listeners.forEach(listener => document.removeEventListener('pb-ready', listener));
|
|
237
|
-
callback();
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Wait until the global event identified by name
|
|
243
|
-
* has been fired once. This is mainly used to wait for initialization
|
|
244
|
-
* events like `pb-page-ready`.
|
|
245
|
-
*
|
|
246
|
-
* @param {string} name
|
|
247
|
-
* @param {Function} callback
|
|
248
|
-
* @deprecated Use exported `waitOnce` function
|
|
249
|
-
*/
|
|
250
|
-
static waitOnce(name, callback) {
|
|
251
|
-
waitOnce(name, callback);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Signal that the component is ready to respond to events.
|
|
256
|
-
* Emits an event to all channels the component is registered with.
|
|
257
|
-
*/
|
|
258
|
-
signalReady(name = 'pb-ready', data) {
|
|
259
|
-
this._isReady = true;
|
|
260
|
-
initEventsFired.set(name, data);
|
|
261
|
-
this.dispatchEvent(new CustomEvent(name, { detail: { data, source: this } }));
|
|
262
|
-
this.emitTo(name, data);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Get the list of channels this element subscribes to.
|
|
267
|
-
*
|
|
268
|
-
* @returns an array of channel names
|
|
269
|
-
*/
|
|
270
|
-
getSubscribedChannels() {
|
|
271
|
-
const chs = [];
|
|
272
|
-
if (this.subscribeConfig) {
|
|
273
|
-
Object.keys(this.subscribeConfig).forEach((key) => {
|
|
274
|
-
chs.push(key);
|
|
275
|
-
});
|
|
276
|
-
} else if (this.subscribe) {
|
|
277
|
-
chs.push(this.subscribe);
|
|
278
|
-
}
|
|
279
|
-
return chs;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Check if the other element emits to one of the channels this
|
|
284
|
-
* element subscribes to.
|
|
285
|
-
*
|
|
286
|
-
* @param {Element} other the other element to compare with
|
|
287
|
-
*/
|
|
288
|
-
emitsOnSameChannel(other) {
|
|
289
|
-
const myChannels = this.getSubscribedChannels();
|
|
290
|
-
const otherChannels = PbMixin.getEmittedChannels(other);
|
|
291
|
-
if (myChannels.length === 0 && otherChannels.length === 0) {
|
|
292
|
-
// both emit to the default channel
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
return myChannels.some((channel) => otherChannels.includes(channel));
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Get the list of channels this element emits to.
|
|
300
|
-
*
|
|
301
|
-
* @returns an array of channel names
|
|
302
|
-
*/
|
|
303
|
-
static getEmittedChannels(elem) {
|
|
304
|
-
const chs = [];
|
|
305
|
-
const emitConfig = elem.getAttribute('emit-config');
|
|
306
|
-
if (emitConfig) {
|
|
307
|
-
const json = JSON.parse(emitConfig);
|
|
308
|
-
Object.keys(json).forEach(key => {
|
|
309
|
-
chs.push(key);
|
|
310
|
-
});
|
|
311
|
-
} else {
|
|
312
|
-
const emitAttr = elem.getAttribute('emit');
|
|
313
|
-
if (emitAttr) {
|
|
314
|
-
chs.push(emitAttr);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
return chs;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Listen to the event defined by type. If property `subscribe` or `subscribe-config`
|
|
322
|
-
* is defined, this method will trigger the listener only if the event has a key
|
|
323
|
-
* equal to the key defined in `subscribe` or `subscribe-config`.
|
|
324
|
-
*
|
|
325
|
-
* @param {String} type Name of the event, usually starting with `pb-`
|
|
326
|
-
* @param {Function} listener Callback function
|
|
327
|
-
* @param {Array} [channels] Optional: explicitely specify the channels to emit to. This overwrites
|
|
328
|
-
* the emit property. Pass empty array to target the default channel.
|
|
329
|
-
*/
|
|
330
|
-
subscribeTo(type, listener, channels) {
|
|
331
|
-
let handlers;
|
|
332
|
-
const chs = channels || this.getSubscribedChannels();
|
|
333
|
-
if (chs.length === 0) {
|
|
334
|
-
// no channel defined: listen for all events not targetted at a channel
|
|
335
|
-
const handle = (ev) => {
|
|
336
|
-
if (ev.detail && ev.detail.key) {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
listener(ev);
|
|
340
|
-
};
|
|
341
|
-
document.addEventListener(type, handle);
|
|
342
|
-
handlers = [handle];
|
|
343
|
-
} else {
|
|
344
|
-
handlers = chs.map(key => {
|
|
345
|
-
const handle = ev => {
|
|
346
|
-
if (ev.detail && ev.detail.key && ev.detail.key === key) {
|
|
347
|
-
listener(ev);
|
|
348
|
-
}
|
|
349
|
-
};
|
|
350
|
-
document.addEventListener(type, handle);
|
|
351
|
-
return handle;
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
// add new handlers to list of active subscriptions
|
|
355
|
-
this._subscriptions.set(type, handlers);
|
|
356
|
-
return handlers;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Dispatch an event of the given type. If the properties `emit` or `emit-config`
|
|
361
|
-
* are defined, the event will be limited to the channel specified there.
|
|
362
|
-
*
|
|
363
|
-
* @param {String} type Name of the event, usually starting with `pb-`
|
|
364
|
-
* @param {Object} [options] Options to be passed in ev.detail
|
|
365
|
-
* @param {Array} [channels] Optional: explicitely specify the channels to emit to. This overwrites
|
|
366
|
-
* the 'emit' property setting. Pass empty array to target the default channel.
|
|
367
|
-
*/
|
|
368
|
-
emitTo(type, options, channels) {
|
|
369
|
-
const chs = channels || PbMixin.getEmittedChannels(this);
|
|
370
|
-
if (chs.length == 0) {
|
|
371
|
-
if (type === 'pb-ready') {
|
|
372
|
-
readyEventsFired.add('__default__');
|
|
373
|
-
}
|
|
374
|
-
if (options) {
|
|
375
|
-
options = Object.assign({ _source: this }, options);
|
|
376
|
-
} else {
|
|
377
|
-
options = { _source: this };
|
|
378
|
-
}
|
|
379
|
-
const ev = new CustomEvent(type, {
|
|
380
|
-
detail: options,
|
|
381
|
-
composed: true,
|
|
382
|
-
bubbles: true
|
|
383
|
-
});
|
|
384
|
-
this.dispatchEvent(ev);
|
|
385
|
-
} else {
|
|
386
|
-
chs.forEach(key => {
|
|
387
|
-
const detail = {
|
|
388
|
-
key: key,
|
|
389
|
-
_source: this
|
|
390
|
-
};
|
|
391
|
-
if (options) {
|
|
392
|
-
for (const opt in options) {
|
|
393
|
-
if (options.hasOwnProperty(opt)) {
|
|
394
|
-
detail[opt] = options[opt];
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
if (type === 'pb-ready') {
|
|
399
|
-
readyEventsFired.add(key);
|
|
400
|
-
}
|
|
401
|
-
const ev = new CustomEvent(type, {
|
|
402
|
-
detail: detail,
|
|
403
|
-
composed: true,
|
|
404
|
-
bubbles: true
|
|
405
|
-
});
|
|
406
|
-
this.dispatchEvent(ev);
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Returns the `pb-document` element this component is connected to.
|
|
413
|
-
*
|
|
414
|
-
* @returns the document component or undefined if not set/found
|
|
415
|
-
*/
|
|
416
|
-
getDocument() {
|
|
417
|
-
if (this.src) {
|
|
418
|
-
const doc = document.getElementById(this.src);
|
|
419
|
-
if (doc) {
|
|
420
|
-
return doc;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return null;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
getParameter(name, fallback) {
|
|
427
|
-
const params = TeiPublisher.url.searchParams && TeiPublisher.url.searchParams.getAll(name);
|
|
428
|
-
if (params && params.length == 1) {
|
|
429
|
-
return params[0];
|
|
430
|
-
}else if (params && params.length > 1) {
|
|
431
|
-
return params
|
|
432
|
-
}
|
|
433
|
-
return fallback;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
getParameterValues(name) {
|
|
437
|
-
return TeiPublisher.url.searchParams.getAll(name);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
setParameter(name, value) {
|
|
441
|
-
if (typeof value === 'undefined') {
|
|
442
|
-
TeiPublisher.url.searchParams.delete(name);
|
|
443
|
-
} else if (Array.isArray(value)) {
|
|
444
|
-
TeiPublisher.url.searchParams.delete(name);
|
|
445
|
-
value.forEach(function (val) {
|
|
446
|
-
TeiPublisher.url.searchParams.append(name, val);
|
|
447
|
-
});
|
|
448
|
-
} else {
|
|
449
|
-
TeiPublisher.url.searchParams.set(name, value);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
setParameters(obj) {
|
|
454
|
-
TeiPublisher.url.search = '';
|
|
455
|
-
for (let key in obj) {
|
|
456
|
-
if (obj.hasOwnProperty(key)) {
|
|
457
|
-
this.setParameter(key, obj[key]);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
getParameters() {
|
|
463
|
-
const params = {};
|
|
464
|
-
for (let key of TeiPublisher.url.searchParams.keys()) {
|
|
465
|
-
params[key] = this.getParameter(key);
|
|
466
|
-
}
|
|
467
|
-
return params;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
getParametersMatching(regex) {
|
|
471
|
-
const params = {};
|
|
472
|
-
for (let pair of TeiPublisher.url.searchParams.entries()) {
|
|
473
|
-
if (regex.test(pair[0])) {
|
|
474
|
-
const param = params[pair[0]];
|
|
475
|
-
if (param) {
|
|
476
|
-
param.push(pair[1]);
|
|
477
|
-
} else {
|
|
478
|
-
params[pair[0]] = [pair[1]];
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
return params;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
clearParametersMatching(regex) {
|
|
486
|
-
for (let pair of TeiPublisher.url.searchParams.entries()) {
|
|
487
|
-
if (regex.test(pair[0])) {
|
|
488
|
-
TeiPublisher.url.searchParams.delete(pair[0]);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
setPath(path) {
|
|
494
|
-
const page = document.querySelector('pb-page');
|
|
495
|
-
if (page) {
|
|
496
|
-
const appRoot = page.appRoot;
|
|
497
|
-
|
|
498
|
-
this.getUrl().pathname = appRoot + '/' + path;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
getUrl() {
|
|
503
|
-
return TeiPublisher.url;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
pushHistory(msg, state) {
|
|
507
|
-
history.pushState(state, msg, TeiPublisher.url.toString());
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
getEndpoint() {
|
|
511
|
-
return this._endpoint;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
toAbsoluteURL(relative, server) {
|
|
515
|
-
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(relative)) {
|
|
516
|
-
return relative;
|
|
517
|
-
}
|
|
518
|
-
const endpoint = server || this.getEndpoint();
|
|
519
|
-
let base;
|
|
520
|
-
if (endpoint === '.') {
|
|
521
|
-
base = new URL(window.location.href);
|
|
522
|
-
// loaded in iframe
|
|
523
|
-
} else if (window.location.protocol === 'about:') {
|
|
524
|
-
base = document.baseURI
|
|
525
|
-
} else {
|
|
526
|
-
base = new URL(`${endpoint}/`, `${window.location.protocol}//${window.location.host}`);
|
|
527
|
-
}
|
|
528
|
-
return new URL(relative, base).href;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
minApiVersion(requiredVersion) {
|
|
532
|
-
return cmpVersion(this._apiVersion, requiredVersion) >= 0;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
lessThanApiVersion(requiredVersion) {
|
|
536
|
-
return cmpVersion(this._apiVersion, requiredVersion) < 0;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
compareApiVersion(requiredVersion) {
|
|
540
|
-
return cmpVersion(this._apiVersion, requiredVersion);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
1
|
+
import { cmpVersion } from './utils.js';
|
|
2
|
+
|
|
3
|
+
if (!window.TeiPublisher) {
|
|
4
|
+
window.TeiPublisher = {};
|
|
5
|
+
|
|
6
|
+
TeiPublisher.url = new URL(window.location.href);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Global set to record the names of the channels for which a
|
|
11
|
+
* `pb-ready` event was fired.
|
|
12
|
+
*/
|
|
13
|
+
const readyEventsFired = new Set();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Gobal map to record the initialization events which have
|
|
17
|
+
* been received.
|
|
18
|
+
*/
|
|
19
|
+
const initEventsFired = new Map();
|
|
20
|
+
|
|
21
|
+
export function clearPageEvents() {
|
|
22
|
+
initEventsFired.clear();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wait until the global event identified by name
|
|
27
|
+
* has been fired once. This is mainly used to wait for initialization
|
|
28
|
+
* events like `pb-page-ready`.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} name
|
|
31
|
+
* @param {Function} callback
|
|
32
|
+
*/
|
|
33
|
+
export function waitOnce(name, callback) {
|
|
34
|
+
if (initEventsFired.has(name)) {
|
|
35
|
+
callback(initEventsFired.get(name));
|
|
36
|
+
} else {
|
|
37
|
+
document.addEventListener(name, (ev) => {
|
|
38
|
+
initEventsFired.set(name, ev.detail);
|
|
39
|
+
callback(ev.detail);
|
|
40
|
+
}, {
|
|
41
|
+
once: true
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Implements the core channel/event mechanism used by components in TEI Publisher
|
|
48
|
+
* to communicate.
|
|
49
|
+
*
|
|
50
|
+
* As there might be several documents/fragments being displayed on a page at the same time,
|
|
51
|
+
* a simple event mechanism is not enough for components to exchange messages. They need to
|
|
52
|
+
* be able to target a specific view. The mechanism implemented by this mixin thus combines
|
|
53
|
+
* events and channels. Components may emit an event into a named channel to which other
|
|
54
|
+
* components might subscribe. For example, there might be a view which subscribes to the
|
|
55
|
+
* channel *transcription* and another one subscribing to *translation*. By using distinct
|
|
56
|
+
* channels, other components can address only one of the two.
|
|
57
|
+
*
|
|
58
|
+
* @polymer
|
|
59
|
+
* @mixinFunction
|
|
60
|
+
*/
|
|
61
|
+
export const pbMixin = (superclass) => class PbMixin extends superclass {
|
|
62
|
+
|
|
63
|
+
static get properties() {
|
|
64
|
+
return {
|
|
65
|
+
/**
|
|
66
|
+
* The name of the channel to subscribe to. Only events on a channel corresponding
|
|
67
|
+
* to this property are listened to.
|
|
68
|
+
*/
|
|
69
|
+
subscribe: {
|
|
70
|
+
type: String
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Configuration object to define a channel/event mapping. Every property
|
|
74
|
+
* in the object is interpreted as the name of a channel and its value should
|
|
75
|
+
* be an array of event names to listen to.
|
|
76
|
+
*/
|
|
77
|
+
subscribeConfig: {
|
|
78
|
+
type: Object,
|
|
79
|
+
attribute: 'subscribe-config'
|
|
80
|
+
},
|
|
81
|
+
/**
|
|
82
|
+
* The name of the channel to send events to.
|
|
83
|
+
*/
|
|
84
|
+
emit: {
|
|
85
|
+
type: String
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Configuration object to define a channel/event mapping. Every property
|
|
89
|
+
* in the object is interpreted as the name of a channel and its value should
|
|
90
|
+
* be an array of event names to be dispatched.
|
|
91
|
+
*/
|
|
92
|
+
emitConfig: {
|
|
93
|
+
type: Object,
|
|
94
|
+
attribute: 'emit-config'
|
|
95
|
+
},
|
|
96
|
+
/**
|
|
97
|
+
* A selector pointing to other components this component depends on.
|
|
98
|
+
* When method `wait` is called, it will wait until all referenced
|
|
99
|
+
* components signal with a `pb-ready` event that they are ready and listening
|
|
100
|
+
* to events.
|
|
101
|
+
*/
|
|
102
|
+
waitFor: {
|
|
103
|
+
type: String,
|
|
104
|
+
attribute: 'wait-for'
|
|
105
|
+
},
|
|
106
|
+
_isReady: {
|
|
107
|
+
type: Boolean
|
|
108
|
+
},
|
|
109
|
+
/**
|
|
110
|
+
* Common property to disable the functionality associated with a component.
|
|
111
|
+
* `pb-highlight` and `pb-popover` react to this.
|
|
112
|
+
*/
|
|
113
|
+
disabled: {
|
|
114
|
+
type: Boolean,
|
|
115
|
+
reflect: true
|
|
116
|
+
},
|
|
117
|
+
_endpoint: {
|
|
118
|
+
type: String
|
|
119
|
+
},
|
|
120
|
+
_apiVersion: {
|
|
121
|
+
type: String
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
constructor() {
|
|
127
|
+
super();
|
|
128
|
+
this._isReady = false;
|
|
129
|
+
this.disabled = false;
|
|
130
|
+
this._subscriptions = new Map();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
connectedCallback() {
|
|
134
|
+
super.connectedCallback();
|
|
135
|
+
waitOnce('pb-page-ready', (options) => {
|
|
136
|
+
this._endpoint = options.endpoint;
|
|
137
|
+
this._apiVersion = options.apiVersion;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
disconnectedCallback() {
|
|
142
|
+
super.disconnectedCallback();
|
|
143
|
+
this._subscriptions.forEach((handlers, type) => {
|
|
144
|
+
handlers.forEach((handler) => {
|
|
145
|
+
document.removeEventListener(type, handler);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Enable or disable certain features of a component. Called by `pb-toggle-feature`
|
|
152
|
+
* and `pb-select-feature` to change the components behaviour.
|
|
153
|
+
*
|
|
154
|
+
* By default only one command is known: `disable` will disable any interactive feature
|
|
155
|
+
* of the component.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} command name of an action to take or setting to be toggled
|
|
158
|
+
* @param {Boolean} state the state to set the setting to
|
|
159
|
+
*/
|
|
160
|
+
command(command, state) {
|
|
161
|
+
if (command === 'disable') {
|
|
162
|
+
this.disabled = state;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Wait for the components referenced by the selector given in property `waitFor`
|
|
168
|
+
* to signal that they are ready to respond to events. Only wait for elements which
|
|
169
|
+
* emit to one of the channels this component subscribes to.
|
|
170
|
+
*
|
|
171
|
+
* @param callback function to be called when all components are ready
|
|
172
|
+
*/
|
|
173
|
+
wait(callback) {
|
|
174
|
+
if (this.waitFor) {
|
|
175
|
+
const targetNodes = Array.from(document.querySelectorAll(this.waitFor));
|
|
176
|
+
const targets = targetNodes.filter(target => this.emitsOnSameChannel(target));
|
|
177
|
+
const targetCount = targets.length;
|
|
178
|
+
if (targetCount === 0) {
|
|
179
|
+
// selector did not return any targets
|
|
180
|
+
return callback();
|
|
181
|
+
}
|
|
182
|
+
let count = 0;
|
|
183
|
+
targets.forEach((target) => {
|
|
184
|
+
if (target._isReady) {
|
|
185
|
+
count++;
|
|
186
|
+
if (targetCount === count) {
|
|
187
|
+
callback();
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
const handler = target.addEventListener('pb-ready', (ev) => {
|
|
191
|
+
if (ev.detail.source == this) {
|
|
192
|
+
// same source: ignore
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
count++;
|
|
196
|
+
if (targetCount === count) {
|
|
197
|
+
target.removeEventListener('pb-ready', handler);
|
|
198
|
+
callback();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
callback();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Wait until a `pb-ready` event is received from one of the channels
|
|
210
|
+
* this component subscribes to. Used internally by components which depend
|
|
211
|
+
* on a particular `pb-view` to be ready and listening to events.
|
|
212
|
+
*
|
|
213
|
+
* @param callback function to be called when `pb-ready` is received
|
|
214
|
+
*/
|
|
215
|
+
waitForChannel(callback) {
|
|
216
|
+
// check first if a `pb-ready` event has already been received on one of the channels
|
|
217
|
+
if (this.subscribeConfig) {
|
|
218
|
+
for (const key in this.subscribeConfig) {
|
|
219
|
+
this.subscribeConfig[key].forEach(t => {
|
|
220
|
+
if (t === 'pb-ready' && readyEventsFired.has(key)) {
|
|
221
|
+
return callback();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
} else if (
|
|
226
|
+
(this.subscribe && readyEventsFired.has(this.subscribe)) ||
|
|
227
|
+
(!this.subscribe && readyEventsFired.has('__default__'))
|
|
228
|
+
) {
|
|
229
|
+
return callback();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const listeners = this.subscribeTo('pb-ready', (ev) => {
|
|
233
|
+
if (ev.detail._source == this) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
listeners.forEach(listener => document.removeEventListener('pb-ready', listener));
|
|
237
|
+
callback();
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Wait until the global event identified by name
|
|
243
|
+
* has been fired once. This is mainly used to wait for initialization
|
|
244
|
+
* events like `pb-page-ready`.
|
|
245
|
+
*
|
|
246
|
+
* @param {string} name
|
|
247
|
+
* @param {Function} callback
|
|
248
|
+
* @deprecated Use exported `waitOnce` function
|
|
249
|
+
*/
|
|
250
|
+
static waitOnce(name, callback) {
|
|
251
|
+
waitOnce(name, callback);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Signal that the component is ready to respond to events.
|
|
256
|
+
* Emits an event to all channels the component is registered with.
|
|
257
|
+
*/
|
|
258
|
+
signalReady(name = 'pb-ready', data) {
|
|
259
|
+
this._isReady = true;
|
|
260
|
+
initEventsFired.set(name, data);
|
|
261
|
+
this.dispatchEvent(new CustomEvent(name, { detail: { data, source: this } }));
|
|
262
|
+
this.emitTo(name, data);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get the list of channels this element subscribes to.
|
|
267
|
+
*
|
|
268
|
+
* @returns an array of channel names
|
|
269
|
+
*/
|
|
270
|
+
getSubscribedChannels() {
|
|
271
|
+
const chs = [];
|
|
272
|
+
if (this.subscribeConfig) {
|
|
273
|
+
Object.keys(this.subscribeConfig).forEach((key) => {
|
|
274
|
+
chs.push(key);
|
|
275
|
+
});
|
|
276
|
+
} else if (this.subscribe) {
|
|
277
|
+
chs.push(this.subscribe);
|
|
278
|
+
}
|
|
279
|
+
return chs;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Check if the other element emits to one of the channels this
|
|
284
|
+
* element subscribes to.
|
|
285
|
+
*
|
|
286
|
+
* @param {Element} other the other element to compare with
|
|
287
|
+
*/
|
|
288
|
+
emitsOnSameChannel(other) {
|
|
289
|
+
const myChannels = this.getSubscribedChannels();
|
|
290
|
+
const otherChannels = PbMixin.getEmittedChannels(other);
|
|
291
|
+
if (myChannels.length === 0 && otherChannels.length === 0) {
|
|
292
|
+
// both emit to the default channel
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
return myChannels.some((channel) => otherChannels.includes(channel));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get the list of channels this element emits to.
|
|
300
|
+
*
|
|
301
|
+
* @returns an array of channel names
|
|
302
|
+
*/
|
|
303
|
+
static getEmittedChannels(elem) {
|
|
304
|
+
const chs = [];
|
|
305
|
+
const emitConfig = elem.getAttribute('emit-config');
|
|
306
|
+
if (emitConfig) {
|
|
307
|
+
const json = JSON.parse(emitConfig);
|
|
308
|
+
Object.keys(json).forEach(key => {
|
|
309
|
+
chs.push(key);
|
|
310
|
+
});
|
|
311
|
+
} else {
|
|
312
|
+
const emitAttr = elem.getAttribute('emit');
|
|
313
|
+
if (emitAttr) {
|
|
314
|
+
chs.push(emitAttr);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return chs;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Listen to the event defined by type. If property `subscribe` or `subscribe-config`
|
|
322
|
+
* is defined, this method will trigger the listener only if the event has a key
|
|
323
|
+
* equal to the key defined in `subscribe` or `subscribe-config`.
|
|
324
|
+
*
|
|
325
|
+
* @param {String} type Name of the event, usually starting with `pb-`
|
|
326
|
+
* @param {Function} listener Callback function
|
|
327
|
+
* @param {Array} [channels] Optional: explicitely specify the channels to emit to. This overwrites
|
|
328
|
+
* the emit property. Pass empty array to target the default channel.
|
|
329
|
+
*/
|
|
330
|
+
subscribeTo(type, listener, channels) {
|
|
331
|
+
let handlers;
|
|
332
|
+
const chs = channels || this.getSubscribedChannels();
|
|
333
|
+
if (chs.length === 0) {
|
|
334
|
+
// no channel defined: listen for all events not targetted at a channel
|
|
335
|
+
const handle = (ev) => {
|
|
336
|
+
if (ev.detail && ev.detail.key) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
listener(ev);
|
|
340
|
+
};
|
|
341
|
+
document.addEventListener(type, handle);
|
|
342
|
+
handlers = [handle];
|
|
343
|
+
} else {
|
|
344
|
+
handlers = chs.map(key => {
|
|
345
|
+
const handle = ev => {
|
|
346
|
+
if (ev.detail && ev.detail.key && ev.detail.key === key) {
|
|
347
|
+
listener(ev);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
document.addEventListener(type, handle);
|
|
351
|
+
return handle;
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
// add new handlers to list of active subscriptions
|
|
355
|
+
this._subscriptions.set(type, handlers);
|
|
356
|
+
return handlers;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Dispatch an event of the given type. If the properties `emit` or `emit-config`
|
|
361
|
+
* are defined, the event will be limited to the channel specified there.
|
|
362
|
+
*
|
|
363
|
+
* @param {String} type Name of the event, usually starting with `pb-`
|
|
364
|
+
* @param {Object} [options] Options to be passed in ev.detail
|
|
365
|
+
* @param {Array} [channels] Optional: explicitely specify the channels to emit to. This overwrites
|
|
366
|
+
* the 'emit' property setting. Pass empty array to target the default channel.
|
|
367
|
+
*/
|
|
368
|
+
emitTo(type, options, channels) {
|
|
369
|
+
const chs = channels || PbMixin.getEmittedChannels(this);
|
|
370
|
+
if (chs.length == 0) {
|
|
371
|
+
if (type === 'pb-ready') {
|
|
372
|
+
readyEventsFired.add('__default__');
|
|
373
|
+
}
|
|
374
|
+
if (options) {
|
|
375
|
+
options = Object.assign({ _source: this }, options);
|
|
376
|
+
} else {
|
|
377
|
+
options = { _source: this };
|
|
378
|
+
}
|
|
379
|
+
const ev = new CustomEvent(type, {
|
|
380
|
+
detail: options,
|
|
381
|
+
composed: true,
|
|
382
|
+
bubbles: true
|
|
383
|
+
});
|
|
384
|
+
this.dispatchEvent(ev);
|
|
385
|
+
} else {
|
|
386
|
+
chs.forEach(key => {
|
|
387
|
+
const detail = {
|
|
388
|
+
key: key,
|
|
389
|
+
_source: this
|
|
390
|
+
};
|
|
391
|
+
if (options) {
|
|
392
|
+
for (const opt in options) {
|
|
393
|
+
if (options.hasOwnProperty(opt)) {
|
|
394
|
+
detail[opt] = options[opt];
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (type === 'pb-ready') {
|
|
399
|
+
readyEventsFired.add(key);
|
|
400
|
+
}
|
|
401
|
+
const ev = new CustomEvent(type, {
|
|
402
|
+
detail: detail,
|
|
403
|
+
composed: true,
|
|
404
|
+
bubbles: true
|
|
405
|
+
});
|
|
406
|
+
this.dispatchEvent(ev);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Returns the `pb-document` element this component is connected to.
|
|
413
|
+
*
|
|
414
|
+
* @returns the document component or undefined if not set/found
|
|
415
|
+
*/
|
|
416
|
+
getDocument() {
|
|
417
|
+
if (this.src) {
|
|
418
|
+
const doc = document.getElementById(this.src);
|
|
419
|
+
if (doc) {
|
|
420
|
+
return doc;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
getParameter(name, fallback) {
|
|
427
|
+
const params = TeiPublisher.url.searchParams && TeiPublisher.url.searchParams.getAll(name);
|
|
428
|
+
if (params && params.length == 1) {
|
|
429
|
+
return params[0];
|
|
430
|
+
}else if (params && params.length > 1) {
|
|
431
|
+
return params
|
|
432
|
+
}
|
|
433
|
+
return fallback;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
getParameterValues(name) {
|
|
437
|
+
return TeiPublisher.url.searchParams.getAll(name);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
setParameter(name, value) {
|
|
441
|
+
if (typeof value === 'undefined') {
|
|
442
|
+
TeiPublisher.url.searchParams.delete(name);
|
|
443
|
+
} else if (Array.isArray(value)) {
|
|
444
|
+
TeiPublisher.url.searchParams.delete(name);
|
|
445
|
+
value.forEach(function (val) {
|
|
446
|
+
TeiPublisher.url.searchParams.append(name, val);
|
|
447
|
+
});
|
|
448
|
+
} else {
|
|
449
|
+
TeiPublisher.url.searchParams.set(name, value);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
setParameters(obj) {
|
|
454
|
+
TeiPublisher.url.search = '';
|
|
455
|
+
for (let key in obj) {
|
|
456
|
+
if (obj.hasOwnProperty(key)) {
|
|
457
|
+
this.setParameter(key, obj[key]);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
getParameters() {
|
|
463
|
+
const params = {};
|
|
464
|
+
for (let key of TeiPublisher.url.searchParams.keys()) {
|
|
465
|
+
params[key] = this.getParameter(key);
|
|
466
|
+
}
|
|
467
|
+
return params;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
getParametersMatching(regex) {
|
|
471
|
+
const params = {};
|
|
472
|
+
for (let pair of TeiPublisher.url.searchParams.entries()) {
|
|
473
|
+
if (regex.test(pair[0])) {
|
|
474
|
+
const param = params[pair[0]];
|
|
475
|
+
if (param) {
|
|
476
|
+
param.push(pair[1]);
|
|
477
|
+
} else {
|
|
478
|
+
params[pair[0]] = [pair[1]];
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return params;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
clearParametersMatching(regex) {
|
|
486
|
+
for (let pair of TeiPublisher.url.searchParams.entries()) {
|
|
487
|
+
if (regex.test(pair[0])) {
|
|
488
|
+
TeiPublisher.url.searchParams.delete(pair[0]);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
setPath(path) {
|
|
494
|
+
const page = document.querySelector('pb-page');
|
|
495
|
+
if (page) {
|
|
496
|
+
const appRoot = page.appRoot;
|
|
497
|
+
|
|
498
|
+
this.getUrl().pathname = appRoot + '/' + path;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
getUrl() {
|
|
503
|
+
return TeiPublisher.url;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
pushHistory(msg, state) {
|
|
507
|
+
history.pushState(state, msg, TeiPublisher.url.toString());
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
getEndpoint() {
|
|
511
|
+
return this._endpoint;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
toAbsoluteURL(relative, server) {
|
|
515
|
+
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(relative)) {
|
|
516
|
+
return relative;
|
|
517
|
+
}
|
|
518
|
+
const endpoint = server || this.getEndpoint();
|
|
519
|
+
let base;
|
|
520
|
+
if (endpoint === '.') {
|
|
521
|
+
base = new URL(window.location.href);
|
|
522
|
+
// loaded in iframe
|
|
523
|
+
} else if (window.location.protocol === 'about:') {
|
|
524
|
+
base = document.baseURI
|
|
525
|
+
} else {
|
|
526
|
+
base = new URL(`${endpoint}/`, `${window.location.protocol}//${window.location.host}`);
|
|
527
|
+
}
|
|
528
|
+
return new URL(relative, base).href;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
minApiVersion(requiredVersion) {
|
|
532
|
+
return cmpVersion(this._apiVersion, requiredVersion) >= 0;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
lessThanApiVersion(requiredVersion) {
|
|
536
|
+
return cmpVersion(this._apiVersion, requiredVersion) < 0;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
compareApiVersion(requiredVersion) {
|
|
540
|
+
return cmpVersion(this._apiVersion, requiredVersion);
|
|
541
|
+
}
|
|
542
|
+
}
|