@teipublisher/pb-components 2.6.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/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,46 @@ 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
+ if (!(this._pathParams.has(param) || this.urlIgnore.has(param))) {
284
+ setParam(value, param);
230
285
  }
286
+ } else if (
287
+ (param !== 'path' || !this.usePath) &&
288
+ (param !== 'id' || !this.idHash) &&
289
+ (!this.urlIgnore.has(param))
290
+ ) {
291
+ setParam(value, param);
231
292
  }
232
293
  }
233
294
 
234
- if (this.usePath && this.state.path && this.state.path.length > 0) {
235
- newUrl.pathname = `${this.rootPath}/${this.state.path}`;
295
+ if (this.state.path && this.state.path.length > 0) {
296
+ if (this.urlPattern) {
297
+ const encoded = this._encodePath(this.state);
298
+ newUrl.pathname = `${this.rootPath}/${encoded}`;
299
+ } else if (this.usePath) {
300
+ newUrl.pathname = `${this.rootPath}/${this.state.path}`;
301
+ }
236
302
  }
237
303
 
238
- if (this.state.id) {
304
+ if (this.state.id && !this.urlPattern) {
239
305
  newUrl.hash = `#${this.state.id}`;
240
306
  }
241
307