@jupyterlab/settingregistry 4.0.0-alpha.8 → 4.0.0-beta.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.
@@ -56,6 +56,13 @@
56
56
  },
57
57
  "additionalProperties": false
58
58
  },
59
+ "jupyter.lab.metadataforms": {
60
+ "items": {
61
+ "$ref": "#/definitions/metadataForm"
62
+ },
63
+ "type": "array",
64
+ "default": []
65
+ },
59
66
  "jupyter.lab.setting-deprecated": {
60
67
  "type": "boolean",
61
68
  "default": false
@@ -295,6 +302,87 @@
295
302
  "required": ["name"],
296
303
  "additionalProperties": false,
297
304
  "type": "object"
305
+ },
306
+ "metadataForm": {
307
+ "type": "object",
308
+ "properties": {
309
+ "id": {
310
+ "type": "string",
311
+ "description": "The section ID"
312
+ },
313
+ "metadataSchema": {
314
+ "type": "object",
315
+ "items": {
316
+ "$ref": "#/definitions/metadataSchema"
317
+ }
318
+ },
319
+ "uiSchema": {
320
+ "type": "object"
321
+ },
322
+ "metadataOptions": {
323
+ "type": "object",
324
+ "items": {
325
+ "$ref": "#/definitions/metadataOptions"
326
+ }
327
+ },
328
+ "label": {
329
+ "type": "string",
330
+ "description": "The section label"
331
+ },
332
+ "rank": {
333
+ "type": "integer",
334
+ "description": "The rank of the section in the right panel"
335
+ },
336
+ "showModified": {
337
+ "type": "boolean",
338
+ "description": "Whether to show modified values from defaults"
339
+ }
340
+ },
341
+ "required": ["id", "metadataSchema"]
342
+ },
343
+ "metadataSchema": {
344
+ "properties": {
345
+ "properties": {
346
+ "type": "object",
347
+ "description": "The property set up by extension",
348
+ "properties": {
349
+ "title": {
350
+ "type": "string"
351
+ },
352
+ "description": {
353
+ "type": "string"
354
+ },
355
+ "type": {
356
+ "type": "string"
357
+ }
358
+ }
359
+ }
360
+ },
361
+ "type": "object",
362
+ "required": ["properties"]
363
+ },
364
+ "metadataOptions": {
365
+ "properties": {
366
+ "customRenderer": {
367
+ "type": "string"
368
+ },
369
+ "metadataLevel": {
370
+ "type": "string",
371
+ "enum": ["cell", "notebook"],
372
+ "default": "cell"
373
+ },
374
+ "cellTypes": {
375
+ "type": "array",
376
+ "items": {
377
+ "type": "string",
378
+ "enum": ["code", "markdown", "raw"]
379
+ }
380
+ },
381
+ "writeDefault": {
382
+ "type": "boolean"
383
+ }
384
+ },
385
+ "type": "object"
298
386
  }
299
387
  }
300
388
  }
@@ -29,18 +29,14 @@ export declare namespace ISchemaValidator {
29
29
  * A schema validation error definition.
30
30
  */
31
31
  interface IError {
32
- /**
33
- * The path in the data where the error occurred.
34
- */
35
- dataPath: string;
36
32
  /**
37
33
  * The keyword whose validation failed.
38
34
  */
39
- keyword: string;
35
+ keyword: string | string[];
40
36
  /**
41
37
  * The error message.
42
38
  */
43
- message: string;
39
+ message?: string;
44
40
  /**
45
41
  * Optional parameter metadata that might be included in an error.
46
42
  */
@@ -49,6 +45,26 @@ export declare namespace ISchemaValidator {
49
45
  * The path in the schema where the error occurred.
50
46
  */
51
47
  schemaPath: string;
48
+ /**
49
+ * @todo handle new fields from ajv8
50
+ **/
51
+ schema?: unknown;
52
+ /**
53
+ * @todo handle new fields from ajv8
54
+ **/
55
+ instancePath: string;
56
+ /**
57
+ * @todo handle new fields from ajv8
58
+ **/
59
+ propertyName?: string;
60
+ /**
61
+ * @todo handle new fields from ajv8
62
+ **/
63
+ data?: unknown;
64
+ /**
65
+ * @todo handle new fields from ajv8
66
+ **/
67
+ parentSchema?: unknown;
52
68
  }
53
69
  }
54
70
  /**
@@ -229,10 +245,35 @@ export declare class SettingRegistry implements ISettingRegistry {
229
245
  private _timeout;
230
246
  private _transformers;
231
247
  }
248
+ /**
249
+ * Base settings specified by a JSON schema.
250
+ */
251
+ export declare class BaseSettings<T extends ISettingRegistry.IProperty = ISettingRegistry.IProperty> {
252
+ constructor(options: {
253
+ schema: T;
254
+ });
255
+ /**
256
+ * The plugin's schema.
257
+ */
258
+ get schema(): T;
259
+ /**
260
+ * Checks if any fields are different from the default value.
261
+ */
262
+ isDefault(user: ReadonlyPartialJSONObject): boolean;
263
+ /**
264
+ * Calculate the default value of a setting by iterating through the schema.
265
+ *
266
+ * @param key - The name of the setting whose default value is calculated.
267
+ *
268
+ * @returns A calculated default JSON value for a specific setting.
269
+ */
270
+ default(key?: string): PartialJSONValue | undefined;
271
+ private _schema;
272
+ }
232
273
  /**
233
274
  * A manager for a specific plugin's settings.
234
275
  */
235
- export declare class Settings implements ISettingRegistry.ISettings {
276
+ export declare class Settings extends BaseSettings<ISettingRegistry.ISchema> implements ISettingRegistry.ISettings {
236
277
  /**
237
278
  * Instantiate a new plugin settings manager.
238
279
  */
@@ -258,18 +299,13 @@ export declare class Settings implements ISettingRegistry.ISettings {
258
299
  */
259
300
  get isDisposed(): boolean;
260
301
  get plugin(): ISettingRegistry.IPlugin;
261
- /**
262
- * The plugin's schema.
263
- */
264
- get schema(): ISettingRegistry.ISchema;
265
302
  /**
266
303
  * The plugin settings raw text value.
267
304
  */
268
305
  get raw(): string;
269
306
  /**
270
- * Checks if any fields are different from the default value.
307
+ * Whether the settings have been modified by the user or not.
271
308
  */
272
- isDefault(user: ReadonlyPartialJSONObject): boolean;
273
309
  get isModified(): boolean;
274
310
  /**
275
311
  * The user settings.
@@ -283,14 +319,6 @@ export declare class Settings implements ISettingRegistry.ISettings {
283
319
  * Return the defaults in a commented JSON format.
284
320
  */
285
321
  annotatedDefaults(): string;
286
- /**
287
- * Calculate the default value of a setting by iterating through the schema.
288
- *
289
- * @param key - The name of the setting whose default value is calculated.
290
- *
291
- * @returns A calculated default JSON value for a specific setting.
292
- */
293
- default(key?: string): PartialJSONValue | undefined;
294
322
  /**
295
323
  * Dispose of the plugin settings resources.
296
324
  */
@@ -11,6 +11,17 @@ import SCHEMA from './plugin-schema.json';
11
11
  * An alias for the JSON deep copy function.
12
12
  */
13
13
  const copy = JSONExt.deepCopy;
14
+ /** Default arguments for Ajv instances.
15
+ *
16
+ * https://ajv.js.org/options.html
17
+ */
18
+ const AJV_DEFAULT_OPTIONS = {
19
+ /**
20
+ * @todo the implications of enabling strict mode are beyond the scope of
21
+ * the initial PR
22
+ */
23
+ strict: false
24
+ };
14
25
  /**
15
26
  * The default number of milliseconds before a `load()` call to the registry
16
27
  * will wait before timing out if it requires a transformation that has not been
@@ -29,8 +40,11 @@ export class DefaultSchemaValidator {
29
40
  * Instantiate a schema validator.
30
41
  */
31
42
  constructor() {
32
- this._composer = new Ajv({ useDefaults: true });
33
- this._validator = new Ajv();
43
+ this._composer = new Ajv({
44
+ useDefaults: true,
45
+ ...AJV_DEFAULT_OPTIONS
46
+ });
47
+ this._validator = new Ajv({ ...AJV_DEFAULT_OPTIONS });
34
48
  this._composer.addSchema(SCHEMA, 'jupyterlab-plugin-schema');
35
49
  this._validator.addSchema(SCHEMA, 'jupyterlab-plugin-schema');
36
50
  }
@@ -55,7 +69,7 @@ export class DefaultSchemaValidator {
55
69
  const keyword = 'schema';
56
70
  const message = `Setting registry schemas' root-level type must be ` +
57
71
  `'object', rejecting type: ${plugin.schema.type}`;
58
- return [{ dataPath: 'type', keyword, schemaPath: '', message }];
72
+ return [{ instancePath: 'type', keyword, schemaPath: '', message }];
59
73
  }
60
74
  const errors = this._addSchema(plugin.id, plugin.schema);
61
75
  return errors || this.validateData(plugin);
@@ -69,7 +83,7 @@ export class DefaultSchemaValidator {
69
83
  if (error instanceof SyntaxError) {
70
84
  return [
71
85
  {
72
- dataPath: '',
86
+ instancePath: '',
73
87
  keyword: 'syntax',
74
88
  schemaPath: '',
75
89
  message: error.message
@@ -80,7 +94,7 @@ export class DefaultSchemaValidator {
80
94
  const line = error.lineNumber;
81
95
  return [
82
96
  {
83
- dataPath: '',
97
+ instancePath: '',
84
98
  keyword: 'parse',
85
99
  schemaPath: '',
86
100
  message: `${description} (line ${line} column ${column})`
@@ -226,7 +240,7 @@ export class SettingRegistry {
226
240
  if (fetched === undefined) {
227
241
  throw [
228
242
  {
229
- dataPath: '',
243
+ instancePath: '',
230
244
  keyword: 'id',
231
245
  message: `Could not fetch settings for ${plugin}.`,
232
246
  schemaPath: ''
@@ -281,7 +295,10 @@ export class SettingRegistry {
281
295
  }
282
296
  // Parse the raw JSON string removing all comments and return an object.
283
297
  const raw = json5.parse(plugins[plugin].raw);
284
- plugins[plugin].raw = Private.annotatedPlugin(plugins[plugin], Object.assign(Object.assign({}, raw), { [key]: value }));
298
+ plugins[plugin].raw = Private.annotatedPlugin(plugins[plugin], {
299
+ ...raw,
300
+ [key]: value
301
+ });
285
302
  return this._save(plugin);
286
303
  }
287
304
  /**
@@ -305,7 +322,9 @@ export class SettingRegistry {
305
322
  transform(plugin, transforms) {
306
323
  const transformers = this._transformers;
307
324
  if (plugin in transformers) {
308
- throw new Error(`${plugin} already has a transformer.`);
325
+ const error = new Error(`${plugin} already has a transformer.`);
326
+ error.name = 'TransformError';
327
+ throw error;
309
328
  }
310
329
  transformers[plugin] = {
311
330
  fetch: transforms.fetch || (plugin => plugin),
@@ -347,9 +366,9 @@ export class SettingRegistry {
347
366
  catch (errors) {
348
367
  const output = [`Validating ${plugin} failed:`];
349
368
  errors.forEach((error, index) => {
350
- const { dataPath, schemaPath, keyword, message } = error;
351
- if (dataPath || schemaPath) {
352
- output.push(`${index} - schema @ ${schemaPath}, data @ ${dataPath}`);
369
+ const { instancePath, schemaPath, keyword, message } = error;
370
+ if (instancePath || schemaPath) {
371
+ output.push(`${index} - schema @ ${schemaPath}, data @ ${instancePath}`);
353
372
  }
354
373
  output.push(`{${keyword}} ${message}`);
355
374
  });
@@ -396,7 +415,7 @@ export class SettingRegistry {
396
415
  if (fetched === undefined) {
397
416
  throw [
398
417
  {
399
- dataPath: '',
418
+ instancePath: '',
400
419
  keyword: 'id',
401
420
  message: `Could not fetch settings for ${plugin}.`,
402
421
  schemaPath: ''
@@ -422,7 +441,7 @@ export class SettingRegistry {
422
441
  if (transformed.id !== id) {
423
442
  throw [
424
443
  {
425
- dataPath: '',
444
+ instancePath: '',
426
445
  keyword: 'id',
427
446
  message: 'Plugin transformations cannot change plugin IDs.',
428
447
  schemaPath: ''
@@ -442,7 +461,7 @@ export class SettingRegistry {
442
461
  }
443
462
  throw [
444
463
  {
445
- dataPath: '',
464
+ instancePath: '',
446
465
  keyword: 'timeout',
447
466
  message: `Transforming ${plugin.id} timed out.`,
448
467
  schemaPath: ''
@@ -462,14 +481,58 @@ export class SettingRegistry {
462
481
  this.plugins[plugin.id] = await this._transform('compose', plugin);
463
482
  }
464
483
  }
484
+ /**
485
+ * Base settings specified by a JSON schema.
486
+ */
487
+ export class BaseSettings {
488
+ constructor(options) {
489
+ this._schema = options.schema;
490
+ }
491
+ /**
492
+ * The plugin's schema.
493
+ */
494
+ get schema() {
495
+ return this._schema;
496
+ }
497
+ /**
498
+ * Checks if any fields are different from the default value.
499
+ */
500
+ isDefault(user) {
501
+ for (const key in this.schema.properties) {
502
+ const value = user[key];
503
+ const defaultValue = this.default(key);
504
+ if (value === undefined ||
505
+ defaultValue === undefined ||
506
+ JSONExt.deepEqual(value, JSONExt.emptyObject) ||
507
+ JSONExt.deepEqual(value, JSONExt.emptyArray)) {
508
+ continue;
509
+ }
510
+ if (!JSONExt.deepEqual(value, defaultValue)) {
511
+ return false;
512
+ }
513
+ }
514
+ return true;
515
+ }
516
+ /**
517
+ * Calculate the default value of a setting by iterating through the schema.
518
+ *
519
+ * @param key - The name of the setting whose default value is calculated.
520
+ *
521
+ * @returns A calculated default JSON value for a specific setting.
522
+ */
523
+ default(key) {
524
+ return Private.reifyDefault(this.schema, key);
525
+ }
526
+ }
465
527
  /**
466
528
  * A manager for a specific plugin's settings.
467
529
  */
468
- export class Settings {
530
+ export class Settings extends BaseSettings {
469
531
  /**
470
532
  * Instantiate a new plugin settings manager.
471
533
  */
472
534
  constructor(options) {
535
+ super({ schema: options.plugin.schema });
473
536
  this._changed = new Signal(this);
474
537
  this._isDisposed = false;
475
538
  this.id = options.plugin.id;
@@ -497,12 +560,6 @@ export class Settings {
497
560
  get plugin() {
498
561
  return this.registry.plugins[this.id];
499
562
  }
500
- /**
501
- * The plugin's schema.
502
- */
503
- get schema() {
504
- return this.plugin.schema;
505
- }
506
563
  /**
507
564
  * The plugin settings raw text value.
508
565
  */
@@ -510,24 +567,8 @@ export class Settings {
510
567
  return this.plugin.raw;
511
568
  }
512
569
  /**
513
- * Checks if any fields are different from the default value.
570
+ * Whether the settings have been modified by the user or not.
514
571
  */
515
- isDefault(user) {
516
- for (const key in this.schema.properties) {
517
- const value = user[key];
518
- const defaultValue = this.default(key);
519
- if (value === undefined ||
520
- defaultValue === undefined ||
521
- JSONExt.deepEqual(value, JSONExt.emptyObject) ||
522
- JSONExt.deepEqual(value, JSONExt.emptyArray)) {
523
- continue;
524
- }
525
- if (!JSONExt.deepEqual(value, defaultValue)) {
526
- return false;
527
- }
528
- }
529
- return true;
530
- }
531
572
  get isModified() {
532
573
  return !this.isDefault(this.user);
533
574
  }
@@ -549,16 +590,6 @@ export class Settings {
549
590
  annotatedDefaults() {
550
591
  return Private.annotatedDefaults(this.schema, this.id);
551
592
  }
552
- /**
553
- * Calculate the default value of a setting by iterating through the schema.
554
- *
555
- * @param key - The name of the setting whose default value is calculated.
556
- *
557
- * @returns A calculated default JSON value for a specific setting.
558
- */
559
- default(key) {
560
- return Private.reifyDefault(this.schema, key);
561
- }
562
593
  /**
563
594
  * Dispose of the plugin settings resources.
564
595
  */
@@ -667,7 +698,11 @@ export class Settings {
667
698
  addition.forEach(menu => {
668
699
  const refIndex = merged.findIndex(ref => ref.id === menu.id);
669
700
  if (refIndex >= 0) {
670
- merged[refIndex] = Object.assign(Object.assign(Object.assign({}, merged[refIndex]), menu), { items: reconcileItems(merged[refIndex].items, menu.items, warn, addNewItems) });
701
+ merged[refIndex] = {
702
+ ...merged[refIndex],
703
+ ...menu,
704
+ items: reconcileItems(merged[refIndex].items, menu.items, warn, addNewItems)
705
+ };
671
706
  }
672
707
  else {
673
708
  if (addNewItems) {
@@ -700,7 +735,7 @@ export class Settings {
700
735
  switch ((_a = item.type) !== null && _a !== void 0 ? _a : 'command') {
701
736
  case 'separator':
702
737
  if (addNewItems) {
703
- items.push(Object.assign({}, item));
738
+ items.push({ ...item });
704
739
  }
705
740
  break;
706
741
  case 'submenu':
@@ -712,9 +747,13 @@ export class Settings {
712
747
  }
713
748
  }
714
749
  else {
715
- items[refIndex] = Object.assign(Object.assign(Object.assign({}, items[refIndex]), item), { submenu: reconcileMenus(items[refIndex].submenu
750
+ items[refIndex] = {
751
+ ...items[refIndex],
752
+ ...item,
753
+ submenu: reconcileMenus(items[refIndex].submenu
716
754
  ? [items[refIndex].submenu]
717
- : null, [item.submenu], warn, addNewItems)[0] });
755
+ : null, [item.submenu], warn, addNewItems)[0]
756
+ };
718
757
  }
719
758
  }
720
759
  break;
@@ -728,14 +767,14 @@ export class Settings {
728
767
  });
729
768
  if (refIndex < 0) {
730
769
  if (addNewItems) {
731
- items.push(Object.assign({}, item));
770
+ items.push({ ...item });
732
771
  }
733
772
  }
734
773
  else {
735
774
  if (warn) {
736
775
  console.warn(`Menu entry for command '${item.command}' is duplicated.`);
737
776
  }
738
- items[refIndex] = Object.assign(Object.assign({}, items[refIndex]), item);
777
+ items[refIndex] = { ...items[refIndex], ...item };
739
778
  }
740
779
  }
741
780
  }
@@ -752,12 +791,15 @@ export class Settings {
752
791
  function filterDisabledItems(items) {
753
792
  return items.reduce((final, value) => {
754
793
  var _a;
755
- const copy = Object.assign({}, value);
794
+ const copy = { ...value };
756
795
  if (!copy.disabled) {
757
796
  if (copy.type === 'submenu') {
758
797
  const { submenu } = copy;
759
798
  if (submenu && !submenu.disabled) {
760
- copy.submenu = Object.assign(Object.assign({}, submenu), { items: filterDisabledItems((_a = submenu.items) !== null && _a !== void 0 ? _a : []) });
799
+ copy.submenu = {
800
+ ...submenu,
801
+ items: filterDisabledItems((_a = submenu.items) !== null && _a !== void 0 ? _a : [])
802
+ };
761
803
  }
762
804
  }
763
805
  final.push(copy);
@@ -829,7 +871,7 @@ export class Settings {
829
871
  .filter(shortcut => !shortcut.disabled)
830
872
  // Fix shortcuts comparison in rjsf Form to avoid polluting the user settings
831
873
  .map(shortcut => {
832
- return Object.assign({ args: {} }, shortcut);
874
+ return { args: {}, ...shortcut };
833
875
  }));
834
876
  }
835
877
  SettingRegistry.reconcileShortcuts = reconcileShortcuts;
@@ -851,39 +893,17 @@ export class Settings {
851
893
  const items = JSONExt.deepCopy(reference);
852
894
  // Merge array element depending on the type
853
895
  addition.forEach(item => {
854
- switch (item.type) {
855
- case 'command':
856
- if (item.command) {
857
- const refIndex = items.findIndex(ref => {
858
- var _a, _b;
859
- return ref.name === item.name &&
860
- ref.command === item.command &&
861
- JSONExt.deepEqual((_a = ref.args) !== null && _a !== void 0 ? _a : {}, (_b = item.args) !== null && _b !== void 0 ? _b : {});
862
- });
863
- if (refIndex < 0) {
864
- items.push(Object.assign({}, item));
865
- }
866
- else {
867
- if (warn) {
868
- console.warn(`Toolbar item for command '${item.command}' is duplicated.`);
869
- }
870
- items[refIndex] = Object.assign(Object.assign({}, items[refIndex]), item);
871
- }
872
- }
873
- break;
874
- case 'spacer':
875
- default: {
876
- const refIndex = items.findIndex(ref => ref.name === item.name);
877
- if (refIndex < 0) {
878
- items.push(Object.assign({}, item));
879
- }
880
- else {
881
- if (warn) {
882
- console.warn(`Toolbar item '${item.name}' is duplicated.`);
883
- }
884
- items[refIndex] = Object.assign(Object.assign({}, items[refIndex]), item);
885
- }
896
+ // Name must be unique so it's sufficient to only compare it
897
+ const refIndex = items.findIndex(ref => ref.name === item.name);
898
+ if (refIndex < 0) {
899
+ items.push({ ...item });
900
+ }
901
+ else {
902
+ if (warn &&
903
+ JSONExt.deepEqual(Object.keys(item), Object.keys(items[refIndex]))) {
904
+ console.warn(`Toolbar item '${item.name}' is duplicated.`);
886
905
  }
906
+ items[refIndex] = { ...items[refIndex], ...item };
887
907
  }
888
908
  });
889
909
  return items;
@@ -999,9 +1019,9 @@ var Private;
999
1019
  /**
1000
1020
  * Create a fully extrapolated default value for a root key in a schema.
1001
1021
  */
1002
- function reifyDefault(schema, root) {
1003
- var _a, _b, _c;
1004
- const definitions = schema.definitions;
1022
+ function reifyDefault(schema, root, definitions) {
1023
+ var _a, _b, _c, _d;
1024
+ definitions = definitions !== null && definitions !== void 0 ? definitions : schema.definitions;
1005
1025
  // If the property is at the root level, traverse its schema.
1006
1026
  schema = (root ? (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[root] : schema) || {};
1007
1027
  if (schema.type === 'object') {
@@ -1010,7 +1030,7 @@ var Private;
1010
1030
  // Iterate through and populate each child property.
1011
1031
  const props = schema.properties || {};
1012
1032
  for (const property in props) {
1013
- result[property] = reifyDefault(props[property]);
1033
+ result[property] = reifyDefault(props[property], undefined, definitions);
1014
1034
  }
1015
1035
  return result;
1016
1036
  }
@@ -1027,9 +1047,9 @@ var Private;
1027
1047
  // Iterate through the items in the array and fill in defaults
1028
1048
  for (const item in result) {
1029
1049
  // Use the values that are hard-coded in the default array over the defaults for each field.
1030
- const reified = reifyDefault(props) || {};
1050
+ const reified = (_c = reifyDefault(props, undefined, definitions)) !== null && _c !== void 0 ? _c : {};
1031
1051
  for (const prop in reified) {
1032
- if ((_c = result[item]) === null || _c === void 0 ? void 0 : _c[prop]) {
1052
+ if ((_d = result[item]) === null || _d === void 0 ? void 0 : _d[prop]) {
1033
1053
  reified[prop] = result[item][prop];
1034
1054
  }
1035
1055
  }