From 9b73e897df134ba57d4ac4d0e6e6924f8ddbc23d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 12 Jul 2020 00:38:55 +0900 Subject: [PATCH] Plugin system (#6479) * wip * wip * wip * wip * Update store.ts --- locales/ja-JP.yml | 3 + package.json | 2 +- src/client/components/note.vue | 15 +- src/client/components/post-form.vue | 21 ++- src/client/components/user-menu.vue | 12 +- src/client/init.ts | 31 ++++ src/client/pages/preferences/index.vue | 6 +- src/client/pages/preferences/plugins.vue | 134 ++++++++++++++++++ src/client/pages/scratchpad.vue | 2 +- .../api.ts} | 15 ++ src/client/scripts/hpml/evaluator.ts | 2 +- src/client/store.ts | 59 +++++++- yarn.lock | 8 +- 13 files changed, 293 insertions(+), 17 deletions(-) create mode 100644 src/client/pages/preferences/plugins.vue rename src/client/scripts/{create-aiscript-env.ts => aiscript/api.ts} (71%) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8ae0628471..7c19c982e0 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -523,6 +523,9 @@ themeEditor: "テーマエディター" description: "説明" author: "作者" leaveConfirm: "未保存の変更があります。破棄しますか?" +manage: "管理" +plugins: "プラグイン" +pluginInstallWarn: "信頼できないプラグインはインストールしないでください。" deck: "デッキ" undeck: "デッキ解除" diff --git a/package.json b/package.json index ad55e2e035..4a81ed9636 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@koa/multer": "3.0.0", "@koa/router": "9.3.1", "@sinonjs/fake-timers": "6.0.1", - "@syuilo/aiscript": "0.7.0", + "@syuilo/aiscript": "0.7.2", "@types/bcryptjs": "2.4.2", "@types/bull": "3.14.0", "@types/cbor": "5.0.0", diff --git a/src/client/components/note.vue b/src/client/components/note.vue index badb9f12f3..63a803c7f4 100644 --- a/src/client/components/note.vue +++ b/src/client/components/note.vue @@ -89,7 +89,7 @@ diff --git a/src/client/components/user-menu.vue b/src/client/components/user-menu.vue index 25937fb3c0..cbfa7b346d 100644 --- a/src/client/components/user-menu.vue +++ b/src/client/components/user-menu.vue @@ -4,7 +4,7 @@ + + diff --git a/src/client/pages/scratchpad.vue b/src/client/pages/scratchpad.vue index 81d4e60459..025505295b 100644 --- a/src/client/pages/scratchpad.vue +++ b/src/client/pages/scratchpad.vue @@ -30,7 +30,7 @@ import PrismEditor from 'vue-prism-editor'; import { AiScript, parse, utils, values } from '@syuilo/aiscript'; import MkContainer from '../components/ui/container.vue'; import MkButton from '../components/ui/button.vue'; -import { createAiScriptEnv } from '../scripts/create-aiscript-env'; +import { createAiScriptEnv } from '../scripts/aiscript/api'; export default Vue.extend({ metaInfo() { diff --git a/src/client/scripts/create-aiscript-env.ts b/src/client/scripts/aiscript/api.ts similarity index 71% rename from src/client/scripts/create-aiscript-env.ts rename to src/client/scripts/aiscript/api.ts index dfa38be385..29baa25b1a 100644 --- a/src/client/scripts/create-aiscript-env.ts +++ b/src/client/scripts/aiscript/api.ts @@ -40,3 +40,18 @@ export function createAiScriptEnv(vm, opts) { }), }; } + +export function createPluginEnv(vm, opts) { + return { + ...createAiScriptEnv(vm, opts), + 'Mk:register_post_form_action': values.FN_NATIVE(([title, handler]) => { + vm.$store.commit('registerPostFormAction', { pluginId: opts.plugin.id, title: title.value, handler }); + }), + 'Mk:register_user_action': values.FN_NATIVE(([title, handler]) => { + vm.$store.commit('registerUserAction', { pluginId: opts.plugin.id, title: title.value, handler }); + }), + 'Mk:register_note_action': values.FN_NATIVE(([title, handler]) => { + vm.$store.commit('registerNoteAction', { pluginId: opts.plugin.id, title: title.value, handler }); + }), + }; +} diff --git a/src/client/scripts/hpml/evaluator.ts b/src/client/scripts/hpml/evaluator.ts index f1fcdde0e5..a056884368 100644 --- a/src/client/scripts/hpml/evaluator.ts +++ b/src/client/scripts/hpml/evaluator.ts @@ -3,7 +3,7 @@ import * as seedrandom from 'seedrandom'; import { Variable, PageVar, envVarsDef, funcDefs, Block, isFnBlock } from '.'; import { version } from '../../config'; import { AiScript, utils, values } from '@syuilo/aiscript'; -import { createAiScriptEnv } from '../create-aiscript-env'; +import { createAiScriptEnv } from '../aiscript/api'; import { collectPageVars } from '../collect-page-vars'; import { initLib } from './lib'; diff --git a/src/client/store.ts b/src/client/store.ts index eaa8ea6a69..31febc782b 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -3,6 +3,7 @@ import createPersistedState from 'vuex-persistedstate'; import * as nestedProperty from 'nested-property'; import { faTerminal, faHashtag, faBroadcastTower, faFireAlt, faSearch, faStar, faAt, faListUl, faUserClock, faUsers, faCloud, faGamepad, faFileAlt, faSatellite, faDoorClosed, faColumns } from '@fortawesome/free-solid-svg-icons'; import { faBell, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons'; +import { AiScript, utils, values } from '@syuilo/aiscript'; import { apiUrl, deckmode } from './config'; import { erase } from '../prelude/array'; @@ -43,6 +44,7 @@ export const defaultDeviceUserSettings = { columns: [], layout: [], }, + plugins: [], }; export const defaultDeviceSettings = { @@ -93,7 +95,13 @@ export default () => new Vuex.Store({ state: { i: null, pendingApiRequestsCount: 0, - spinner: null + spinner: null, + + // Plugin + pluginContexts: new Map(), + postFormActions: [], + userActions: [], + noteActions: [], }, getters: { @@ -224,8 +232,38 @@ export default () => new Vuex.Store({ state.i = x; }, - updateIKeyValue(state, x) { - state.i[x.key] = x.value; + updateIKeyValue(state, { key, value }) { + state.i[key] = value; + }, + + initPlugin(state, { plugin, aiscript }) { + state.pluginContexts.set(plugin.id, aiscript); + }, + + registerPostFormAction(state, { pluginId, title, handler }) { + state.postFormActions.push({ + title, handler: (form, update) => { + state.pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(form), values.FN_NATIVE(([key, value]) => { + update(key.value, value.value); + })]); + } + }); + }, + + registerUserAction(state, { pluginId, title, handler }) { + state.userActions.push({ + title, handler: (user) => { + state.pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(user)]); + } + }); + }, + + registerNoteAction(state, { pluginId, title, handler }) { + state.noteActions.push({ + title, handler: (note) => { + state.pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(note)]); + } + }); }, }, @@ -546,6 +584,21 @@ export default () => new Vuex.Store({ column = x; }, //#endregion + + installPlugin(state, { meta, ast }) { + state.plugins.push({ + id: meta.id, + name: meta.name, + version: meta.version, + author: meta.author, + description: meta.description, + ast: ast + }); + }, + + uninstallPlugin(state, id) { + state.plugins = state.plugins.filter(x => x.id != id); + }, } }, diff --git a/yarn.lock b/yarn.lock index 2fd02e8dbc..ad6cb9ca37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -197,10 +197,10 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@syuilo/aiscript@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.7.0.tgz#1394511a789891e844d32e536a203fe0d92b3039" - integrity sha512-X4TaP/FO7RD8MpFSPDFwKAI4KX7byn8ApqmSSmf2bxcwCTcdevsbyxsLrvkbNaWclIoqTgXwtJjY+2Tc2exeXA== +"@syuilo/aiscript@0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.7.2.tgz#2f30adb14ffa9f1180af83c059927ab306b175a5" + integrity sha512-l8HVTJTq9KLzDqGswOIGlBepkacudUp70EScrLjL7nEL2NKcti7Ui5fwZCrmxazxgGz6NrVNX5UBIOFFyrwr0A== dependencies: autobind-decorator "2.4.0" chalk "4.0.0"