-
-
Notifications
You must be signed in to change notification settings - Fork 173
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #114 from getkirby/fiber/steps/plugins
Plugins Module
- Loading branch information
Showing
3 changed files
with
225 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,72 +1,11 @@ | ||
import store from "@/store/store.js"; | ||
import section from "../mixins/section.js"; | ||
import Plugins from "@/panel/plugins.js"; | ||
|
||
export default { | ||
install(app) { | ||
const components = { ...app.options.components }; | ||
|
||
const mixins = { | ||
section: section | ||
}; | ||
|
||
/** | ||
* Components | ||
*/ | ||
for (const [name, options] of Object.entries( | ||
window.panel.plugins.components | ||
)) { | ||
// make sure component has something to show | ||
if (!options.template && !options.render && !options.extends) { | ||
store.dispatch( | ||
"notification/error", | ||
`Neither template or render method provided nor extending a component when loading plugin component "${name}". The component has not been registered.` | ||
); | ||
continue; | ||
} | ||
|
||
// resolve extending via component name | ||
if (typeof options?.extends === "string") { | ||
// only extend if referenced component exists | ||
if (components[options.extends]) { | ||
options.extends = components[options.extends].extend({ | ||
options, | ||
components: { | ||
...components, | ||
...(options.components || {}) | ||
} | ||
}); | ||
} else { | ||
// if component doesn't exist, don't extend | ||
window.console.warn( | ||
`Problem with plugin trying to register component "${name}": cannot extend non-existent component "${options.extends}"` | ||
); | ||
options.extends = null; | ||
} | ||
} | ||
|
||
if (options.template) { | ||
options.render = null; | ||
} | ||
|
||
if (options.mixins) { | ||
options.mixins = options.mixins.map((mixin) => | ||
typeof mixin === "string" ? mixins[mixin] : mixin | ||
); | ||
} | ||
|
||
if (components[name]) { | ||
window.console.warn(`Plugin is replacing "${name}"`); | ||
} | ||
|
||
app.component(name, options); | ||
components[name] = app.options.components[name]; | ||
} | ||
|
||
/** | ||
* `Vue.use` | ||
* Temporary polyfill until this is all | ||
* bundled under window.panel | ||
*/ | ||
for (const plugin of window.panel.plugins.use) { | ||
app.use(plugin); | ||
} | ||
window.panel.plugins = Plugins(app, window.panel.plugins); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import { isObject } from "@/helpers/object.js"; | ||
import isComponent from "@/helpers/isComponent.js"; | ||
import section from "@/mixins/section.js"; | ||
|
||
/** | ||
* Installs a plugin component | ||
* | ||
* @param {Vue} app | ||
* @param {String} name | ||
* @param {Object} options | ||
* @returns {Object} The updated component options | ||
*/ | ||
export const installComponent = (app, name, options) => { | ||
// make sure component has something to show | ||
if (!options.template && !options.render && !options.extends) { | ||
throw new Error( | ||
`Neither template nor render method provided. Nor extending a component when loading plugin component "${name}". The component has not been registered.` | ||
); | ||
} | ||
|
||
// extend the component if it defines extensions | ||
options = installComponentExtension(app, name, options); | ||
|
||
// remove a render method if there’s a template | ||
if (options.template) { | ||
options.render = null; | ||
} | ||
|
||
// add mixins | ||
options = installComponentMixins(options); | ||
|
||
// check if the component is replacing a core component | ||
if (isComponent(name) === true) { | ||
window.console.warn(`Plugin is replacing "${name}"`); | ||
} | ||
|
||
// register the component | ||
app.component(name, options); | ||
|
||
// return its options | ||
return options; | ||
}; | ||
|
||
/** | ||
* Installs all components in the given object | ||
* | ||
* @param {Vue} app | ||
* @param {Object} components | ||
* @returns {Object} Returns all installed components | ||
*/ | ||
export const installComponents = (app, components) => { | ||
if (isObject(components) === false) { | ||
return; | ||
} | ||
|
||
const installed = {}; | ||
|
||
for (const [name, options] of Object.entries(components)) { | ||
try { | ||
installed[name] = installComponent(app, name, options); | ||
} catch (error) { | ||
window.console.warn(error.message); | ||
} | ||
} | ||
|
||
return installed; | ||
}; | ||
|
||
/** | ||
* Extends a component if it defines an extension | ||
* | ||
* @param {Vue} app | ||
* @param {String} name | ||
* @param {Object} options | ||
* @returns {Object} The updated/extended options | ||
*/ | ||
export const installComponentExtension = (app, name, options) => { | ||
if (typeof options?.extends !== "string") { | ||
return options; | ||
} | ||
|
||
// only extend if referenced component exists | ||
if (isComponent(options.extends) === false) { | ||
window.console.warn( | ||
`Problem with plugin trying to register component "${name}": cannot extend non-existent component "${options.extends}"` | ||
); | ||
|
||
// remove the extension | ||
options.extends = null; | ||
|
||
return options; | ||
} | ||
|
||
options.extends = app.options.components[options.extends].extend({ | ||
options, | ||
components: { | ||
...app.options.components, | ||
...(options.components || {}) | ||
} | ||
}); | ||
|
||
return options; | ||
}; | ||
|
||
/** | ||
* Install available mixins if they | ||
* are required | ||
* | ||
* @param {Object} options | ||
* @returns {Object} The updated options | ||
*/ | ||
export const installComponentMixins = (options) => { | ||
if (Array.isArray(options.mixins) === false) { | ||
return options; | ||
} | ||
|
||
const mixins = { | ||
section: section | ||
}; | ||
|
||
options.mixins = options.mixins.map((mixin) => | ||
typeof mixin === "string" ? mixins[mixin] : mixin | ||
); | ||
|
||
return options; | ||
}; | ||
|
||
/** | ||
* Installs plugins | ||
* | ||
* @param {Vue} app | ||
* @param {Object} plugins | ||
* @returns {Object} Returns all installed plugins | ||
*/ | ||
export const installPlugins = (app, plugins) => { | ||
if (Array.isArray(plugins) === false) { | ||
return []; | ||
} | ||
|
||
for (const plugin of plugins) { | ||
app.use(plugin); | ||
} | ||
|
||
return plugins; | ||
}; | ||
|
||
/** | ||
* The plugin module installs all | ||
* given plugins and makes them accessible | ||
* at window.panel.plugins | ||
*/ | ||
export default (app, plugins = {}) => { | ||
plugins = { | ||
components: {}, | ||
created: [], | ||
icons: {}, | ||
login: null, | ||
textareaButtons: {}, | ||
use: [], | ||
thirdParty: {}, | ||
writerMarks: {}, | ||
writerNodes: {}, | ||
...plugins | ||
}; | ||
|
||
plugins.use = installPlugins(app, plugins.use); | ||
plugins.components = installComponents(app, plugins.components); | ||
|
||
return plugins; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import Plugins from "./plugins.js"; | ||
import Vue from "vue"; | ||
import isComponent from "@/helpers/isComponent.js"; | ||
|
||
describe.concurrent("panel.plugins", () => { | ||
it("should have defaults", async () => { | ||
const plugins = Plugins(Vue); | ||
|
||
const expected = { | ||
components: {}, | ||
created: [], | ||
icons: {}, | ||
login: null, | ||
textareaButtons: {}, | ||
use: [], | ||
thirdParty: {}, | ||
writerMarks: {}, | ||
writerNodes: {} | ||
}; | ||
|
||
expect(plugins).toStrictEqual(expected); | ||
}); | ||
|
||
it("should install components", async () => { | ||
const component = { | ||
template: `<p>test</p>` | ||
}; | ||
|
||
const plugins = Plugins(Vue, { | ||
components: { | ||
"k-test": component | ||
} | ||
}); | ||
|
||
expect(plugins.components["k-test"]).toStrictEqual(component); | ||
expect(isComponent("k-test")).true; | ||
}); | ||
|
||
it("should install plugin", async () => { | ||
Plugins(Vue, { | ||
use: [ | ||
(Vue) => (Vue.prototype.$a = "A"), | ||
(Vue) => (Vue.prototype.$b = "B") | ||
] | ||
}); | ||
|
||
expect(Vue.prototype.$a).toStrictEqual("A"); | ||
expect(Vue.prototype.$b).toStrictEqual("B"); | ||
}); | ||
}); |