@teipublisher/pb-components 2.6.1 → 2.8.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.
@@ -131,7 +131,10 @@ export class PbSplitList extends themableMixin(pbMixin(LitElement)) {
131
131
  }
132
132
 
133
133
  load() {
134
- const formParams = this._paramsFromSubforms({ category: this.selected });
134
+ const formParams = this._paramsFromSubforms({});
135
+ if (this.selected) {
136
+ formParams.category = this.selected;
137
+ }
135
138
  if (!this._initialized) {
136
139
  registry.replace(this, formParams);
137
140
  } else {
package/src/pb-view.js CHANGED
@@ -321,6 +321,9 @@ export class PbView extends themableMixin(pbMixin(LitElement)) {
321
321
  type: Node,
322
322
  attribute: false
323
323
  },
324
+ _additionalParams: {
325
+ type: Object
326
+ },
324
327
  ...super.properties
325
328
  };
326
329
  }
@@ -341,6 +344,7 @@ export class PbView extends themableMixin(pbMixin(LitElement)) {
341
344
  this.beforeUpdate = null;
342
345
  this.noScroll = false;
343
346
  this._features = {};
347
+ this._additionalParams = {};
344
348
  this._selector = {};
345
349
  this._chunks = [];
346
350
  this._scrollTarget = null;
@@ -436,8 +440,8 @@ export class PbView extends themableMixin(pbMixin(LitElement)) {
436
440
  this.signalReady();
437
441
 
438
442
  if (this.onUpdate) {
439
- this.subscribeTo('pb-update', () => {
440
- this._refresh();
443
+ this.subscribeTo('pb-update', (ev) => {
444
+ this._refresh(ev);
441
445
  });
442
446
  }
443
447
  }
@@ -587,6 +591,13 @@ export class PbView extends themableMixin(pbMixin(LitElement)) {
587
591
  } else {
588
592
  this.nodeId = ev.detail.root || this.nodeId;
589
593
  }
594
+
595
+ // check if the URL template needs any other parameters
596
+ // and set them on this._additionalParams
597
+ registry.pathParams.forEach((key) => {
598
+ this._additionalParams[key] = ev.detail[key];
599
+ });
600
+
590
601
  if (!this.noScroll) {
591
602
  this._scrollTarget = ev.detail.hash;
592
603
  }
@@ -994,6 +1005,12 @@ export class PbView extends themableMixin(pbMixin(LitElement)) {
994
1005
  for (const [key, value] of Object.entries(this._features)) {
995
1006
  params['user.' + key] = value;
996
1007
  }
1008
+ // add parameters for user-defined parameters supplied via pb-link
1009
+ if (this._additionalParams) {
1010
+ for (const [key, value] of Object.entries(this._additionalParams)) {
1011
+ params[key] = value;
1012
+ }
1013
+ }
997
1014
  return params;
998
1015
  }
999
1016
 
@@ -1162,18 +1179,24 @@ export class PbView extends themableMixin(pbMixin(LitElement)) {
1162
1179
 
1163
1180
  _setState(properties) {
1164
1181
  for (const [key, value] of Object.entries(properties)) {
1165
- switch (key) {
1166
- case 'odd':
1167
- case 'view':
1168
- case 'columnSeparator':
1169
- case 'xpath':
1170
- case 'nodeId':
1171
- case 'path':
1172
- case 'root':
1173
- break;
1174
- default:
1175
- this._features[key] = value;
1176
- break;
1182
+ // check if URL template needs the parameter and if
1183
+ // yes, add it to the additional parameter list
1184
+ if (registry.pathParams.has(key)) {
1185
+ this._additionalParams[key] = value;
1186
+ } else {
1187
+ switch (key) {
1188
+ case 'odd':
1189
+ case 'view':
1190
+ case 'columnSeparator':
1191
+ case 'xpath':
1192
+ case 'nodeId':
1193
+ case 'path':
1194
+ case 'root':
1195
+ break;
1196
+ default:
1197
+ this._features[key] = value;
1198
+ break;
1199
+ }
1177
1200
  }
1178
1201
  }
1179
1202
  if (properties.odd && !this.getAttribute('odd')) {
package/src/urls.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { match, compile, pathToRegexp } from "path-to-regexp";
1
2
  import { PbEvents } from "./pb-events.js";
2
3
  import { getSubscribedChannels } from "./pb-mixin.js";
3
4
 
@@ -60,12 +61,34 @@ class Registry {
60
61
  */
61
62
  this.idHash = true;
62
63
  this._listeners = [];
64
+
65
+ /**
66
+ * URL pattern to use for mapping parameters into the URL path
67
+ */
68
+ this.urlPattern = null;
69
+
70
+ this.urlIgnore = new Set();
71
+ this.pathParams = new Set();
63
72
  }
64
73
 
65
- configure(usePath = true, idHash = false, rootPath = '') {
74
+ configure(usePath = true, idHash = false, rootPath = '', urlPattern, ignoredParams) {
66
75
  this.rootPath = rootPath;
67
76
  this.usePath = usePath;
68
77
  this.idHash = idHash;
78
+ this.urlPattern = urlPattern;
79
+ if (ignoredParams) {
80
+ ignoredParams.split(/\s*,\s*/).forEach(param => this.urlIgnore.add(param));
81
+ }
82
+
83
+ if (this.urlPattern) {
84
+ // save a list of parameter names which go into the path
85
+ const pathParams = [];
86
+ pathToRegexp(this.urlPattern, pathParams);
87
+ pathParams.forEach((param) => this.pathParams.add(param.name));
88
+ // compile URL pattern into a decode and encode function
89
+ this._decodePath = match(this.urlPattern);
90
+ this._encodePath = compile(this.urlPattern);
91
+ }
69
92
 
70
93
  // determine initial state of the registry by parsing current URL
71
94
  const initialState = this._stateFromURL();
@@ -115,16 +138,35 @@ class Registry {
115
138
  if (this.idHash && this.hash.length > 0 && (!/^#\d+\./.test(this.hash))) {
116
139
  params.id = this.hash.substring(1);
117
140
  }
118
- if (this.usePath) {
119
- params.path = window.location.pathname.replace(new RegExp(`^${this.rootPath}/?`), '');
141
+ const relpath = window.location.pathname.replace(new RegExp(`^${this.rootPath}/?`), '');
142
+ if (this.urlPattern) {
143
+ const result = this._decodePath(relpath);
144
+ Object.assign(params, result.params);
145
+ log('decoded path %s using template %s: %o', relpath, this.urlPattern, params);
146
+ } else if (this.usePath) {
147
+ params.path = relpath;
120
148
  }
149
+
121
150
  const urlParams = new URLSearchParams(window.location.search);
122
151
  urlParams.forEach((value, key) => {
123
- if (this.usePath && key === 'path') {
152
+ if (
153
+ (this.urlPattern && this.pathParams.has(key)) ||
154
+ (this.usePath && key === 'path')
155
+ ) {
124
156
  console.warn("Found path parameter in query, but usePath is set to true. The path parameter will be ignored.");
125
157
  return;
126
158
  }
127
- params[key] = value;
159
+ // parameter already set
160
+ if ((key in params)) {
161
+ return;
162
+ }
163
+ // check for multiple entries
164
+ const allValues = urlParams.getAll(key);
165
+ if (allValues.length === 1) {
166
+ params[key] = value; // single entry
167
+ } else {
168
+ params[key] = allValues; // array
169
+ }
128
170
  });
129
171
  return params;
130
172
  }
@@ -196,7 +238,7 @@ class Registry {
196
238
  }
197
239
 
198
240
  _commit(elem, newState, overwrite, replace) {
199
- this.state = overwrite ? newState : Object.assign(this.state, newState);
241
+ this.state = overwrite ? newState : ({ ...this.state, ...newState});
200
242
  const resolved = this.urlFromState();
201
243
 
202
244
  const chs = getSubscribedChannels(elem);
@@ -220,22 +262,58 @@ class Registry {
220
262
 
221
263
  urlFromState() {
222
264
  const newUrl = new URL(window.location.href);
265
+
266
+ function setParam(value, param) {
267
+ if (value === null) {
268
+ newUrl.searchParams.delete(param);
269
+ } else if (Array.isArray(value)) {
270
+ // copy array before mutating it
271
+ const _v = Array.from(value);
272
+ // overwrite any previous value by setting the first member
273
+ newUrl.searchParams.set(param, _v.pop());
274
+ // add additional values
275
+ _v.forEach(v => newUrl.searchParams.append(param, v));
276
+ } else {
277
+ newUrl.searchParams.set(param, value);
278
+ }
279
+ }
280
+
223
281
  for (const [param, value] of Object.entries(this.state)) {
224
- if (( param !== 'path' || !this.usePath )
225
- && param !== 'id') {
226
- if (value === null) {
227
- newUrl.searchParams.delete(param)
228
- } else {
229
- newUrl.searchParams.set(param, value)
282
+ if (this.urlPattern) {
283
+ // check if param should be ignored or is required by the URL template
284
+ // fill up missing parameters by stripping potential "user." prefix
285
+ const normParam = param.replace(/^(?:user\.)?(.*)$/, '$1');
286
+ if (!(this.pathParams.has(normParam) || this.urlIgnore.has(normParam))) {
287
+ setParam(value, normParam);
230
288
  }
289
+ } else if (
290
+ (param !== 'path' || !this.usePath) &&
291
+ (param !== 'id' || !this.idHash) &&
292
+ (!this.urlIgnore.has(param))
293
+ ) {
294
+ setParam(value, param);
231
295
  }
232
296
  }
233
297
 
234
- if (this.usePath && this.state.path && this.state.path.length > 0) {
235
- newUrl.pathname = `${this.rootPath}/${this.state.path}`;
298
+ if (this.state.path && this.state.path.length > 0) {
299
+ if (this.urlPattern) {
300
+ // path parameters should not be the empty string
301
+ const normState = {};
302
+ for (const [key, value] of Object.entries(this.state)) {
303
+ if (this.pathParams.has(key) && value === '') {
304
+ normState[key] = null;
305
+ } else {
306
+ normState[key] = value;
307
+ }
308
+ }
309
+ const encoded = this._encodePath(normState);
310
+ newUrl.pathname = `${this.rootPath}/${encoded}`;
311
+ } else if (this.usePath) {
312
+ newUrl.pathname = `${this.rootPath}/${this.state.path}`;
313
+ }
236
314
  }
237
315
 
238
- if (this.state.id) {
316
+ if (this.state.id && !this.urlPattern) {
239
317
  newUrl.hash = `#${this.state.id}`;
240
318
  }
241
319