diff --git a/extension.schema.json b/extension.schema.json index 7989236ba..7eb2f11c8 100644 --- a/extension.schema.json +++ b/extension.schema.json @@ -214,20 +214,20 @@ }, "type": "object", - "required": ["name", "version"], + "required": ["$name", "$version"], "properties": { - "name": { + "$name": { "description": "Extension name", "type": "string" }, - "version": { + "$version": { "description": "Extension version", "type": "string" }, - "description": { + "$description": { "description": "Brief description on what the extension does" }, - "references": { + "$references": { "description": "References to external files", "type": "array", "items": { diff --git a/src/app/extensions/extension.config.ts b/src/app/extensions/extension.config.ts index 7c6f3869d..b284a86d0 100644 --- a/src/app/extensions/extension.config.ts +++ b/src/app/extensions/extension.config.ts @@ -29,8 +29,10 @@ import { RuleRef } from './rule.extensions'; import { ActionRef, ContentActionRef } from './action.extensions'; export interface ExtensionConfig { - version: string; - references?: Array; + $name: string; + $version: string; + $description?: string; + $references?: Array; rules?: Array; routes?: Array; actions?: Array; diff --git a/src/app/extensions/extension.service.spec.ts b/src/app/extensions/extension.service.spec.ts index eb20d9502..6038ba15d 100644 --- a/src/app/extensions/extension.service.spec.ts +++ b/src/app/extensions/extension.service.spec.ts @@ -43,10 +43,59 @@ describe('ExtensionService', () => { extensions = TestBed.get(ExtensionService); }); + describe('configs', () => { + it('should merge two arrays based on [id] keys', () => { + const left = [ + { + name: 'item0' + }, + { + id: '#1', + name: 'item1' + }, + { + id: '#2', + name: 'item2' + } + ]; + + const right = [ + { + name: 'extra-1' + }, + { + id: '#2', + name: 'custom2', + tag: 'extra tag' + } + ]; + + const result = extensions.mergeArrays(left, right); + expect(result).toEqual([ + { + id: '#1', + name: 'item1' + }, + { + id: '#2', + name: 'custom2', + tag: 'extra tag' + }, + { + name: 'item0' + }, + { + name: 'extra-1' + }, + ]); + }); + }); + describe('actions', () => { beforeEach(() => { extensions.setup({ - version: '1.0.0', + $name: 'test', + $version: '1.0.0', actions: [ { id: 'aca:actions/create-folder', @@ -179,7 +228,8 @@ describe('ExtensionService', () => { beforeEach(() => { extensions.setup({ - version: '1.0.0', + $name: 'test', + $version: '1.0.0', routes: [ { id: 'aca:routes/about', @@ -237,7 +287,8 @@ describe('ExtensionService', () => { describe('content actions', () => { it('should load content actions from the config', () => { extensions.setup({ - version: '1.0.0', + $name: 'test', + $version: '1.0.0', features: { content: { actions: [ @@ -263,7 +314,8 @@ describe('ExtensionService', () => { it('should sort content actions by order', () => { extensions.setup({ - version: '1.0.0', + $name: 'test', + $version: '1.0.0', features: { content: { actions: [ @@ -297,7 +349,8 @@ describe('ExtensionService', () => { describe('open with', () => { it('should load [open with] actions for the viewer', () => { extensions.setup({ - version: '1.0.0', + $name: 'test', + $version: '1.0.0', features: { viewer: { openWith: [ @@ -322,7 +375,8 @@ describe('ExtensionService', () => { it('should load only enabled [open with] actions for the viewer', () => { extensions.setup({ - version: '1.0.0', + $name: 'test', + $version: '1.0.0', features: { viewer: { openWith: [ @@ -358,7 +412,8 @@ describe('ExtensionService', () => { it('should sort [open with] actions by order', () => { extensions.setup({ - version: '1.0.0', + $name: 'test', + $version: '1.0.0', features: { viewer: { openWith: [ @@ -396,7 +451,8 @@ describe('ExtensionService', () => { describe('create', () => { it('should load [create] actions from config', () => { extensions.setup({ - version: '1.0.0', + $name: 'test', + $version: '1.0.0', features: { create: [ { @@ -415,7 +471,8 @@ describe('ExtensionService', () => { it('should sort [create] actions by order', () => { extensions.setup({ - version: '1.0.0', + $name: 'test', + $version: '1.0.0', features: { create: [ { diff --git a/src/app/extensions/extension.service.ts b/src/app/extensions/extension.service.ts index e9db7d14a..3c6292895 100644 --- a/src/app/extensions/extension.service.ts +++ b/src/app/extensions/extension.service.ts @@ -74,8 +74,8 @@ export class ExtensionService implements RuleContext { this.loadConfig(this.configPath, 0).then(result => { let config = result.config; - if (config.references && config.references.length > 0) { - const plugins = config.references.map( + if (config.$references && config.$references.length > 0) { + const plugins = config.$references.map( (name, idx) => this.loadConfig(`${this.pluginsPath}/${name}`, idx) ); @@ -86,7 +86,7 @@ export class ExtensionService implements RuleContext { .map(entry => entry.config); if (configs.length > 0) { - config = this.mergeConfigs(config, ...configs); + config = this.mergeObjects(config, ...configs); } this.setup(config); @@ -411,23 +411,50 @@ export class ExtensionService implements RuleContext { return false; } - // todo: requires overwrite support for array entries - // todo: overwrite only particular areas, don't touch version or other top-level props - protected mergeConfigs(...objects): any { + mergeObjects(...objects): any { const result = {}; objects.forEach(source => { Object.keys(source).forEach(prop => { - if (prop in result && Array.isArray(result[prop])) { - result[prop] = result[prop].concat(source[prop]); - } else if (prop in result && typeof result[prop] === 'object') { - result[prop] = this.mergeConfigs(result[prop], source[prop]); - } else { - result[prop] = source[prop]; + if (!prop.startsWith('$')) { + if (prop in result && Array.isArray(result[prop])) { + // result[prop] = result[prop].concat(source[prop]); + result[prop] = this.mergeArrays(result[prop], source[prop]); + } else if (prop in result && typeof result[prop] === 'object') { + result[prop] = this.mergeObjects(result[prop], source[prop]); + } else { + result[prop] = source[prop]; + } } }); }); return result; } + + mergeArrays(left: any[], right: any[]): any[] { + const result = []; + const map = {}; + + (left || []).forEach(entry => { + const element = entry; + if (element && element.hasOwnProperty('id')) { + map[element.id] = element; + } else { + result.push(element); + } + }); + + (right || []).forEach(entry => { + const element = entry; + if (element && element.hasOwnProperty('id') && map[element.id]) { + const merged = this.mergeObjects(map[element.id], element); + map[element.id] = merged; + } else { + result.push(element); + } + }); + + return Object.values(map).concat(result); + } } diff --git a/src/assets/app.extensions.json b/src/assets/app.extensions.json index b3f57d5dc..f7df9b1a3 100644 --- a/src/assets/app.extensions.json +++ b/src/assets/app.extensions.json @@ -1,9 +1,8 @@ { "$schema": "../../extension.schema.json", - "name": "app", - "version": "1.0.0", - - "references": [ + "$name": "app", + "$version": "1.0.0", + "$references": [ "plugin1.json", "plugin2.json" ], diff --git a/src/assets/plugins/plugin1.json b/src/assets/plugins/plugin1.json index 184a657b4..031cd9b61 100644 --- a/src/assets/plugins/plugin1.json +++ b/src/assets/plugins/plugin1.json @@ -1,8 +1,8 @@ { "$schema": "../../../extension.schema.json", - "version": "1.0.0", - "name": "plugin1", - "description": "demo plugin", + "$version": "1.0.0", + "$name": "plugin1", + "$description": "demo plugin", "actions": [ { @@ -36,6 +36,19 @@ } ] }, + "navbar": [ + { + "id": "app.navbar.primary", + "items": [ + { + "id": "app.navbar.personalFiles", + "icon": "extension", + "title": "APP.BROWSE.PERSONAL.SIDENAV_LINK.LABEL", + "route": "personal-files" + } + ] + } + ], "content": { "actions": [ { diff --git a/src/assets/plugins/plugin2.json b/src/assets/plugins/plugin2.json index 5bd71d37e..9fcb9069f 100644 --- a/src/assets/plugins/plugin2.json +++ b/src/assets/plugins/plugin2.json @@ -1,8 +1,8 @@ { "$schema": "../../../extension.schema.json", - "version": "1.0.0", - "name": "plugin2", - "description": "demo plugin", + "$version": "1.0.0", + "$name": "plugin2", + "$description": "demo plugin", "routes": [ {