@teipublisher/pb-components 1.33.0 → 1.34.2

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.
@@ -1328,6 +1328,12 @@
1328
1328
  "type": "string",
1329
1329
  "default": "\"title\""
1330
1330
  },
1331
+ {
1332
+ "name": "static",
1333
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required",
1334
+ "type": "boolean",
1335
+ "default": "false"
1336
+ },
1331
1337
  {
1332
1338
  "name": "url",
1333
1339
  "description": "The URL for the AJAX request. If a relative URL is passed, it will be resolved\nagainst the current API endpoint.",
@@ -1497,6 +1503,13 @@
1497
1503
  "type": "string",
1498
1504
  "default": "\"title\""
1499
1505
  },
1506
+ {
1507
+ "name": "static",
1508
+ "attribute": "static",
1509
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required",
1510
+ "type": "boolean",
1511
+ "default": "false"
1512
+ },
1500
1513
  {
1501
1514
  "name": "url",
1502
1515
  "attribute": "url",
@@ -10209,6 +10222,11 @@
10209
10222
  "description": "If set to the name of an event, the content of the pb-view will not be replaced\nimmediately upon updates. Instead, an event is emitted, which contains the new content\nin property `root`. An event handler intercepting the event can thus modify the content.\nOnce it is done, it should pass the modified content to the callback function provided\nin the event detail under the name `render`. See the demo for an example.",
10210
10223
  "type": "string"
10211
10224
  },
10225
+ {
10226
+ "name": "static",
10227
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10228
+ "type": "string"
10229
+ },
10212
10230
  {
10213
10231
  "name": "subscribe",
10214
10232
  "description": "The name of the channel to subscribe to. Only events on a channel corresponding\nto this property are listened to.",
@@ -10404,6 +10422,12 @@
10404
10422
  "description": "If set to the name of an event, the content of the pb-view will not be replaced\nimmediately upon updates. Instead, an event is emitted, which contains the new content\nin property `root`. An event handler intercepting the event can thus modify the content.\nOnce it is done, it should pass the modified content to the callback function provided\nin the event detail under the name `render`. See the demo for an example.",
10405
10423
  "type": "string"
10406
10424
  },
10425
+ {
10426
+ "name": "static",
10427
+ "attribute": "static",
10428
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10429
+ "type": "string"
10430
+ },
10407
10431
  {
10408
10432
  "name": "subscribe",
10409
10433
  "attribute": "subscribe",
@@ -10660,6 +10684,11 @@
10660
10684
  "description": "If set to the name of an event, the content of the pb-view will not be replaced\nimmediately upon updates. Instead, an event is emitted, which contains the new content\nin property `root`. An event handler intercepting the event can thus modify the content.\nOnce it is done, it should pass the modified content to the callback function provided\nin the event detail under the name `render`. See the demo for an example.",
10661
10685
  "type": "string"
10662
10686
  },
10687
+ {
10688
+ "name": "static",
10689
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10690
+ "type": "string"
10691
+ },
10663
10692
  {
10664
10693
  "name": "subscribe",
10665
10694
  "description": "The name of the channel to subscribe to. Only events on a channel corresponding\nto this property are listened to.",
@@ -10838,6 +10867,12 @@
10838
10867
  "description": "If set to the name of an event, the content of the pb-view will not be replaced\nimmediately upon updates. Instead, an event is emitted, which contains the new content\nin property `root`. An event handler intercepting the event can thus modify the content.\nOnce it is done, it should pass the modified content to the callback function provided\nin the event detail under the name `render`. See the demo for an example.",
10839
10868
  "type": "string"
10840
10869
  },
10870
+ {
10871
+ "name": "static",
10872
+ "attribute": "static",
10873
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10874
+ "type": "string"
10875
+ },
10841
10876
  {
10842
10877
  "name": "subscribe",
10843
10878
  "attribute": "subscribe",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teipublisher/pb-components",
3
- "version": "1.33.0",
3
+ "version": "1.34.2",
4
4
  "description": "Collection of webcomponents underlying TEI Publisher",
5
5
  "repository": "https://github.com/eeditiones/tei-publisher-components.git",
6
6
  "main": "index.html",
package/pb-elements.json CHANGED
@@ -1328,6 +1328,12 @@
1328
1328
  "type": "string",
1329
1329
  "default": "\"title\""
1330
1330
  },
1331
+ {
1332
+ "name": "static",
1333
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required",
1334
+ "type": "boolean",
1335
+ "default": "false"
1336
+ },
1331
1337
  {
1332
1338
  "name": "url",
1333
1339
  "description": "The URL for the AJAX request. If a relative URL is passed, it will be resolved\nagainst the current API endpoint.",
@@ -1497,6 +1503,13 @@
1497
1503
  "type": "string",
1498
1504
  "default": "\"title\""
1499
1505
  },
1506
+ {
1507
+ "name": "static",
1508
+ "attribute": "static",
1509
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required",
1510
+ "type": "boolean",
1511
+ "default": "false"
1512
+ },
1500
1513
  {
1501
1514
  "name": "url",
1502
1515
  "attribute": "url",
@@ -10209,6 +10222,11 @@
10209
10222
  "description": "If set to the name of an event, the content of the pb-view will not be replaced\nimmediately upon updates. Instead, an event is emitted, which contains the new content\nin property `root`. An event handler intercepting the event can thus modify the content.\nOnce it is done, it should pass the modified content to the callback function provided\nin the event detail under the name `render`. See the demo for an example.",
10210
10223
  "type": "string"
10211
10224
  },
10225
+ {
10226
+ "name": "static",
10227
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10228
+ "type": "string"
10229
+ },
10212
10230
  {
10213
10231
  "name": "subscribe",
10214
10232
  "description": "The name of the channel to subscribe to. Only events on a channel corresponding\nto this property are listened to.",
@@ -10404,6 +10422,12 @@
10404
10422
  "description": "If set to the name of an event, the content of the pb-view will not be replaced\nimmediately upon updates. Instead, an event is emitted, which contains the new content\nin property `root`. An event handler intercepting the event can thus modify the content.\nOnce it is done, it should pass the modified content to the callback function provided\nin the event detail under the name `render`. See the demo for an example.",
10405
10423
  "type": "string"
10406
10424
  },
10425
+ {
10426
+ "name": "static",
10427
+ "attribute": "static",
10428
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10429
+ "type": "string"
10430
+ },
10407
10431
  {
10408
10432
  "name": "subscribe",
10409
10433
  "attribute": "subscribe",
@@ -10660,6 +10684,11 @@
10660
10684
  "description": "If set to the name of an event, the content of the pb-view will not be replaced\nimmediately upon updates. Instead, an event is emitted, which contains the new content\nin property `root`. An event handler intercepting the event can thus modify the content.\nOnce it is done, it should pass the modified content to the callback function provided\nin the event detail under the name `render`. See the demo for an example.",
10661
10685
  "type": "string"
10662
10686
  },
10687
+ {
10688
+ "name": "static",
10689
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10690
+ "type": "string"
10691
+ },
10663
10692
  {
10664
10693
  "name": "subscribe",
10665
10694
  "description": "The name of the channel to subscribe to. Only events on a channel corresponding\nto this property are listened to.",
@@ -10838,6 +10867,12 @@
10838
10867
  "description": "If set to the name of an event, the content of the pb-view will not be replaced\nimmediately upon updates. Instead, an event is emitted, which contains the new content\nin property `root`. An event handler intercepting the event can thus modify the content.\nOnce it is done, it should pass the modified content to the callback function provided\nin the event detail under the name `render`. See the demo for an example.",
10839
10868
  "type": "string"
10840
10869
  },
10870
+ {
10871
+ "name": "static",
10872
+ "attribute": "static",
10873
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10874
+ "type": "string"
10875
+ },
10841
10876
  {
10842
10877
  "name": "subscribe",
10843
10878
  "attribute": "subscribe",
@@ -30,12 +30,12 @@ export class ParseDateService {
30
30
  const resultWeekMatch = this.input.match(this._weekMatchRegex());
31
31
  const resultYearAndMonthMatch = this.input.match(this._yearAndMonthRegex());
32
32
  if (resultIsoMatch) {
33
- const split = resultIsoMatch[0].split(/-|\/|\s/);
33
+ const split = resultIsoMatch[1].split(/-|\/|\s/);
34
34
  this.year = split[0];
35
35
  this.month = this._setWithLeadingZero(split[1]);
36
36
  this.day = this._setWithLeadingZero(split[2]);
37
37
  } else if (resultYearAndMonthMatch) {
38
- const split = resultYearAndMonthMatch[0].split("-");
38
+ const split = resultYearAndMonthMatch[1].split("-");
39
39
  this.year = split[0];
40
40
  this.month = this._setWithLeadingZero(split[1]);
41
41
  this.day = "01";
@@ -73,7 +73,7 @@ export class ParseDateService {
73
73
  * | 2012/1/31 | 2012 1 31 | 2012 01 31 |
74
74
  */
75
75
  _isoMatchRegex() {
76
- return /(?<=\s|^)\d{4}(-|\s|\/)([0][1-9]|[1-9]|10|11|12)(-|\s|\/)([0][1-9]|[1-2][0-9]|3[01]|[1-9])(?=\s|$|\.)/;
76
+ return /(?:\s|^)(\d{4}(-|\s|\/)([0][1-9]|[1-9]|10|11|12)(-|\s|\/)([0][1-9]|[1-2][0-9]|3[01]|[1-9]))(?=\s|$|\.)/;
77
77
  /* | | year | 01-09 | 1-9 | 10-12 | |01-09 |10-29 |30,31| 1-9 |
78
78
  * | | dash or slash | dash or slash |
79
79
  * |preceding with space or start of string end with space endofstr or dot <-|*/
@@ -111,7 +111,7 @@ export class ParseDateService {
111
111
  * | 2020-01 | 2020-12 | 2012-1 |
112
112
  */
113
113
  _yearAndMonthRegex() {
114
- return /(?<=\s|^)\d{4}-([0][1-9]|[1-9]|10|11|12)(?=\s|$)/;
114
+ return /(?:\s|^)(\d{4}-([0][1-9]|[1-9]|10|11|12))(?=\s|$)/;
115
115
  }
116
116
 
117
117
  _findYear() {
@@ -126,14 +126,14 @@ export class ParseDateService {
126
126
  _findMonth() {
127
127
  const months = this._monthDictionaryValues();
128
128
  months.forEach(month => {
129
- let re = new RegExp(`(?<=\\s|^)(${month})(?=\\s|$|\\.)`, "i")
129
+ const re = new RegExp(`(?:\\s|^)(${month})(?=\\s|$|\\.)`, "i")
130
130
  const result = this.input.match(re);
131
131
  if (result) { // yes => get dict and value + return
132
- this.month = this._monthDictionary()[result[0].toLowerCase()];
132
+ this.month = this._monthDictionary()[result[1].toLowerCase()];
133
133
  this._removeMatchFromInput(result);
134
134
  return this.month;
135
135
  }
136
- })
136
+ });
137
137
  return undefined;
138
138
  }
139
139
 
@@ -141,14 +141,14 @@ export class ParseDateService {
141
141
  * find single numbers from 1-31
142
142
  */
143
143
  _findDay() {
144
- let regex = /(?<=\s|^)([0][1-9]|[1-2][0-9]|3[01]|[1-9])(?=\s|$|\.|st|nd|rd|th)/;
144
+ const regex = /(?:\s|^)([0][1-9]|[1-2][0-9]|3[01]|[1-9])(?=\s|$|\.|st|nd|rd|th)/;
145
145
  /* | | 01-09 | 10-29 |30,31|1-9 | ends with whitespace, endofstr or dot.
146
146
  * | starts with whitepace or startoftr | won't be included in match (lookbehind operator)
147
147
  * | look behind operator (not included)
148
148
  * | https://stackoverflow.com/a/6713378/6272061 */
149
149
  const result = this.input.match(regex)
150
150
  if (result) {
151
- this.day = this._setWithLeadingZero(result[0]);
151
+ this.day = this._setWithLeadingZero(result[1]);
152
152
  }
153
153
  }
154
154
 
@@ -87,6 +87,13 @@ export class PbBrowseDocs extends PbLoad {
87
87
  subforms: {
88
88
  type: String
89
89
  },
90
+ /**
91
+ * If set, rewrite URLs to load pages as static HTML files,
92
+ * so no TEI Publisher instance is required
93
+ */
94
+ static: {
95
+ type: Boolean
96
+ },
90
97
  _file: {
91
98
  type: String
92
99
  },
@@ -120,6 +127,8 @@ export class PbBrowseDocs extends PbLoad {
120
127
  this.filterBy = 'title';
121
128
  this._allowModification = false;
122
129
  this._suggestions = [];
130
+
131
+ this.static = false;
123
132
  }
124
133
 
125
134
  connectedCallback() {
@@ -177,14 +186,16 @@ export class PbBrowseDocs extends PbLoad {
177
186
  });
178
187
  this.shadowRoot.getElementById('autocomplete').addEventListener('autocomplete-change', this._autocomplete.bind(this));
179
188
 
180
- const login = document.getElementById(this.login);
181
- if (!login) {
182
- console.error('<pb-browse-docs> connected pb-login element not found!');
183
- } else {
184
- this.subscribeTo('pb-login', (ev) => {
185
- this._allowModification = this._loggedIn(ev.detail.user, ev.detail.group);
186
- }, []);
187
- this._allowModification = login.loggedIn && this._loggedIn(login.user, login.groups);
189
+ if (this.login) {
190
+ const login = document.getElementById(this.login);
191
+ if (!login) {
192
+ console.error('<pb-browse-docs> connected pb-login element not found!');
193
+ } else {
194
+ this.subscribeTo('pb-login', (ev) => {
195
+ this._allowModification = this._loggedIn(ev.detail.user, ev.detail.group);
196
+ }, []);
197
+ this._allowModification = login.loggedIn && this._loggedIn(login.user, login.groups);
198
+ }
188
199
  }
189
200
 
190
201
  this.shadowRoot.getElementById('sort-list').addEventListener('selected-item-changed', this._sort.bind(this));
@@ -313,6 +324,10 @@ export class PbBrowseDocs extends PbLoad {
313
324
  }
314
325
 
315
326
  getURL(params) {
327
+ if (this.static) {
328
+ // use a static URL
329
+ return `collections/${this.collection ? this.collection + '/' : ''}${params.start || '1'}.html`;
330
+ }
316
331
  const url = super.getURL(params);
317
332
  return this.collection ? `${url}/${this.collection}` : url;
318
333
  }
@@ -425,6 +440,7 @@ export class PbBrowseDocs extends PbLoad {
425
440
  link.addEventListener('click', (ev) => {
426
441
  ev.preventDefault();
427
442
  this.collection = link.getAttribute('data-collection');
443
+ this.start = 1;
428
444
  this.setParameter('collection', this.collection);
429
445
  this.pushHistory('browse collection');
430
446
  console.log('<pb-browse-docs> loading collection %s', this.collection);
package/src/pb-load.js CHANGED
@@ -288,13 +288,12 @@ export class PbLoad extends pbMixin(LitElement) {
288
288
  }
289
289
 
290
290
  _handleContent(ev) {
291
- this._parseHeaders(ev.detail.xhr);
292
-
293
291
  const resp = this.shadowRoot.getElementById('loadContent').lastResponse;
294
292
  if (this.container) {
295
293
  this.style.display = 'none';
296
294
  document.querySelectorAll(this.container).forEach((elem) => {
297
- elem.innerHTML = resp
295
+ elem.innerHTML = resp;
296
+ this._parseHeaders(ev.detail.xhr, elem);
298
297
  this._fixLinks(elem);
299
298
  this._onLoad(elem);
300
299
  });
@@ -304,6 +303,7 @@ export class PbLoad extends pbMixin(LitElement) {
304
303
 
305
304
  const div = document.createElement('div');
306
305
  div.innerHTML = resp;
306
+ this._parseHeaders(ev.detail.xhr, div);
307
307
  div.slot = '';
308
308
  this.appendChild(div);
309
309
  this._fixLinks(div);
@@ -338,9 +338,20 @@ export class PbLoad extends pbMixin(LitElement) {
338
338
  dialog.open();
339
339
  }
340
340
 
341
- _parseHeaders(xhr) {
342
- const total = xhr.getResponseHeader('pb-total');
343
- const start = xhr.getResponseHeader('pb-start');
341
+ _parseHeaders(xhr, content) {
342
+ // Try to determine number of pages and current position
343
+ // Search for data-pagination-* attributes first and if they
344
+ // can't be found, check HTTP headers
345
+ function getPaginationParam(type) {
346
+ const elem = content.querySelector(`[data-pagination-${type}]`);
347
+ if (elem) {
348
+ return elem.getAttribute(`data-pagination-${type}`);
349
+ }
350
+ return xhr.getResponseHeader(`pb-${type}`);
351
+ }
352
+
353
+ const total = getPaginationParam('total');
354
+ const start = getPaginationParam('start');
344
355
 
345
356
  if (this.start !== start) {
346
357
  this.start = parseInt(start);
@@ -177,8 +177,9 @@ export class PbTimeline extends pbMixin(LitElement) {
177
177
  text-align: left;
178
178
  border-radius: 6px;
179
179
  padding: 5px 10px;
180
- top: calc(var(--pb-timeline-height, 80px) - 5px);
180
+ top: var(--pb-timeline-height, 80px);
181
181
  left: 0;
182
+ z-index: 1000;
182
183
  }
183
184
  #tooltip ul {
184
185
  list-style: none;
@@ -194,12 +195,16 @@ export class PbTimeline extends pbMixin(LitElement) {
194
195
  content: "";
195
196
  position: absolute;
196
197
  bottom: 100%;
197
- left: 50%;
198
+ left: 10px;
198
199
  margin-left: -5px;
199
200
  border-width: 5px;
200
201
  border-style: solid;
201
202
  border-color: transparent transparent black transparent;
202
203
  }
204
+ #tooltip.right::after {
205
+ right: 10px;
206
+ left: auto;
207
+ }
203
208
  /* pure css close button for tooltip */
204
209
  .close{
205
210
  position: relative;
@@ -294,6 +299,9 @@ export class PbTimeline extends pbMixin(LitElement) {
294
299
  },
295
300
  resettable: {
296
301
  type: Boolean
302
+ },
303
+ _language: {
304
+ type: String
297
305
  }
298
306
  };
299
307
  }
@@ -310,6 +318,7 @@ export class PbTimeline extends pbMixin(LitElement) {
310
318
  this.url = '';
311
319
  this.auto = false;
312
320
  this.resettable = false;
321
+ this._language = 'en';
313
322
  this._resetSelectionProperty();
314
323
  }
315
324
 
@@ -322,6 +331,9 @@ export class PbTimeline extends pbMixin(LitElement) {
322
331
  loader.url = url;
323
332
  loader.generateRequest();
324
333
  });
334
+ this.subscribeTo('pb-i18n-update', (ev) => {
335
+ this._language = ev.detail.language;
336
+ });
325
337
  }
326
338
 
327
339
  firstUpdated() {
@@ -526,7 +538,14 @@ export class PbTimeline extends pbMixin(LitElement) {
526
538
 
527
539
  _showtooltip(event) {
528
540
  const interval = this._getElementInterval(event.currentTarget);
529
- const offset = Math.round((((interval[0] + interval[1]) / 2) - this.tooltip.offsetWidth / 2));
541
+ let offset;
542
+ if (interval[0] < interval[2]) {
543
+ offset = Math.round((((interval[0] + interval[1]) / 2)) - 10);
544
+ this.tooltip.classList.remove('right');
545
+ } else {
546
+ offset = Math.round((((interval[0] + interval[1]) / 2) - this.tooltip.offsetWidth)) + 10;
547
+ this.tooltip.classList.add('right');
548
+ }
530
549
  this.tooltip.style.left = offset + "px";
531
550
  const datestr = event.currentTarget.dataset.tooltip;
532
551
  const value = this._numberWithCommas(event.currentTarget.dataset.value);
@@ -574,7 +593,7 @@ export class PbTimeline extends pbMixin(LitElement) {
574
593
  let interval = [bin.getBoundingClientRect().x, bin.getBoundingClientRect().x + bin.getBoundingClientRect().width]
575
594
  let x1 = interval[0] - rect.left + 1; //x position within the element.
576
595
  let x2 = interval[1] - rect.left + 1; //x position within the element.
577
- return [x1, x2];
596
+ return [x1, x2, rect.width / 2];
578
597
  }
579
598
 
580
599
  _getSelectionInterval() {
@@ -608,7 +627,7 @@ export class PbTimeline extends pbMixin(LitElement) {
608
627
  }
609
628
 
610
629
  _numberWithCommas(input) {
611
- return input.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, "'");
630
+ return new Intl.NumberFormat(this._language, {style: 'decimal'}).format(input);
612
631
  }
613
632
 
614
633
  _areOverlapping(A, B) { // check if 2 intervals are overlapping
@@ -690,7 +709,7 @@ export class PbTimeline extends pbMixin(LitElement) {
690
709
  }
691
710
 
692
711
  renderInfo(binObj) {
693
- if (binObj.info && binObj.info.length > 0 && binObj.info.length < 6) {
712
+ if (binObj.info && binObj.info.length > 0 && binObj.info.length <= 10) {
694
713
  return html`
695
714
  <ul class="info">
696
715
  ${ binObj.info.map(info => html`<li>${unsafeHTML(info)}</li>`) }
package/src/pb-view.js CHANGED
@@ -161,6 +161,16 @@ export class PbView extends pbMixin(LitElement) {
161
161
  url: {
162
162
  type: String
163
163
  },
164
+ /**
165
+ * If set, rewrite URLs to load pages as static HTML files,
166
+ * so no TEI Publisher instance is required. Use this in combination with
167
+ * [tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).
168
+ * The value should point to the HTTP root path under which the static version
169
+ * will be hosted. This is used to resolve CSS stylesheets.
170
+ */
171
+ static: {
172
+ type: String
173
+ },
164
174
  /**
165
175
  * The server returns footnotes separately. Set this property
166
176
  * if you wish to append them to the main text.
@@ -328,6 +338,7 @@ export class PbView extends pbMixin(LitElement) {
328
338
  this._selector = new Map();
329
339
  this._chunks = [];
330
340
  this._scrollTarget = null;
341
+ this.static = null;
331
342
  }
332
343
 
333
344
  attributeChangedCallback(name, oldVal, newVal) {
@@ -584,20 +595,56 @@ export class PbView extends pbMixin(LitElement) {
584
595
 
585
596
  const loadContent = this.shadowRoot.getElementById('loadContent');
586
597
 
587
- if (!this.url) {
598
+ if (this.static) {
599
+ this._staticUrl(params).then((url) => {
600
+ loadContent.url = url;
601
+ loadContent.generateRequest();
602
+ });
603
+ } else {
604
+ if (!this.url) {
605
+ if (this.minApiVersion('1.0.0')) {
606
+ this.url = "api/parts";
607
+ } else {
608
+ this.url = "modules/lib/components.xql";
609
+ }
610
+ }
588
611
  if (this.minApiVersion('1.0.0')) {
589
- this.url = "api/parts";
612
+ loadContent.url = `${this.getEndpoint()}/${this.url}/${encodeURIComponent(this.getDocument().path)}/json`;
590
613
  } else {
591
- this.url = "modules/lib/components.xql";
614
+ loadContent.url = `${this.getEndpoint()}/${this.url}`;
592
615
  }
616
+ loadContent.params = params;
617
+ loadContent.generateRequest();
593
618
  }
594
- if (this.minApiVersion('1.0.0')) {
595
- loadContent.url = `${this.getEndpoint()}/${this.url}/${encodeURIComponent(this.getDocument().path)}/json`;
596
- } else {
597
- loadContent.url = `${this.getEndpoint()}/${this.url}`;
619
+ }
620
+
621
+ /**
622
+ * Use a static URL to load pre-generated content.
623
+ */
624
+ async _staticUrl(params) {
625
+ function createKey(paramNames) {
626
+ const urlComponents = [];
627
+ paramNames.sort().forEach(key => {
628
+ if (params.hasOwnProperty(key)) {
629
+ urlComponents.push(`${key}=${params[key]}`);
630
+ }
631
+ });
632
+ return urlComponents.join('&');
598
633
  }
599
- loadContent.params = params;
600
- loadContent.generateRequest();
634
+
635
+ const index = await fetch(`index.json`)
636
+ .then((response) => response.json());
637
+ const paramNames = ['odd', 'view', 'xpath', 'map'];
638
+ this.querySelectorAll('pb-param').forEach((param) => paramNames.push(`user.${param.getAttribute('name')}`));
639
+ let url = params.id ? createKey([...paramNames, 'id']) : createKey([...paramNames, 'root']);
640
+ let file = index[url];
641
+ if (!file) {
642
+ url = createKey(paramNames);
643
+ file = index[url];
644
+ }
645
+
646
+ console.log('<pb-view> Static lookup %s: %s', url, file);
647
+ return `${file}`;
601
648
  }
602
649
 
603
650
  _clear() {
@@ -838,14 +885,18 @@ export class PbView extends pbMixin(LitElement) {
838
885
  let link = document.createElement('link');
839
886
  link.setAttribute('rel', 'stylesheet');
840
887
  link.setAttribute('type', 'text/css');
841
- link.setAttribute('href', `${this.getEndpoint()}/transform/${this.getOdd()}.css`);
888
+ if (this.static) {
889
+ link.setAttribute('href', `${this.static}/css/${this.getOdd()}.css`);
890
+ } else {
891
+ link.setAttribute('href', `${this.getEndpoint()}/transform/${this.getOdd()}.css`);
892
+ }
842
893
  links.push(link);
843
894
 
844
895
  if (this.loadCss) {
845
896
  link = document.createElement('link');
846
897
  link.setAttribute('rel', 'stylesheet');
847
898
  link.setAttribute('type', 'text/css');
848
- link.setAttribute('href', `${this.getEndpoint()}/${this.loadCss}`);
899
+ link.setAttribute('href', this.toAbsoluteURL(this.loadCss));
849
900
  links.push(link);
850
901
  }
851
902
 
@@ -109,7 +109,15 @@ export class SearchResultService {
109
109
  });
110
110
  if (this.data.invalid) {
111
111
  let invalid = 0;
112
- Object.values(this.data.invalid).forEach((count) => { invalid += count });
112
+ let info = [];
113
+ Object.values(this.data.invalid).forEach((value) => {
114
+ if (typeof value === 'object') {
115
+ invalid += value.count || 0;
116
+ info = info.concat(value.info);
117
+ } else {
118
+ invalid += value;
119
+ }
120
+ });
113
121
  if (invalid > 0) {
114
122
  exportData.data.push({
115
123
  tooltip: i18n('timeline.unknown'),
@@ -117,7 +125,8 @@ export class SearchResultService {
117
125
  // binTitle: i18n('timeline.unknown'),
118
126
  category: '?',
119
127
  separator: true,
120
- value: invalid
128
+ value: invalid,
129
+ info
121
130
  });
122
131
  }
123
132
  }