Skip to content

Commit

Permalink
Merge pull request #114 from getkirby/fiber/steps/plugins
Browse files Browse the repository at this point in the history
Plugins Module
  • Loading branch information
distantnative authored Apr 5, 2023
2 parents 8314b63 + b40536e commit e89021b
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 65 deletions.
69 changes: 4 additions & 65 deletions panel/src/config/plugins.js
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);
}
};
170 changes: 170 additions & 0 deletions panel/src/panel/plugins.js
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;
};
51 changes: 51 additions & 0 deletions panel/src/panel/plugins.test.js
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");
});
});

0 comments on commit e89021b

Please sign in to comment.