parent
							
								
									debc0086fa
								
							
						
					
					
						commit
						9b73e897df
					
				
					 13 changed files with 293 additions and 17 deletions
				
			
		|  | @ -523,6 +523,9 @@ themeEditor: "テーマエディター" | ||||||
| description: "説明" | description: "説明" | ||||||
| author: "作者" | author: "作者" | ||||||
| leaveConfirm: "未保存の変更があります。破棄しますか?" | leaveConfirm: "未保存の変更があります。破棄しますか?" | ||||||
|  | manage: "管理" | ||||||
|  | plugins: "プラグイン" | ||||||
|  | pluginInstallWarn: "信頼できないプラグインはインストールしないでください。" | ||||||
| deck: "デッキ" | deck: "デッキ" | ||||||
| undeck: "デッキ解除" | undeck: "デッキ解除" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -48,7 +48,7 @@ | ||||||
| 		"@koa/multer": "3.0.0", | 		"@koa/multer": "3.0.0", | ||||||
| 		"@koa/router": "9.3.1", | 		"@koa/router": "9.3.1", | ||||||
| 		"@sinonjs/fake-timers": "6.0.1", | 		"@sinonjs/fake-timers": "6.0.1", | ||||||
| 		"@syuilo/aiscript": "0.7.0", | 		"@syuilo/aiscript": "0.7.2", | ||||||
| 		"@types/bcryptjs": "2.4.2", | 		"@types/bcryptjs": "2.4.2", | ||||||
| 		"@types/bull": "3.14.0", | 		"@types/bull": "3.14.0", | ||||||
| 		"@types/cbor": "5.0.0", | 		"@types/cbor": "5.0.0", | ||||||
|  |  | ||||||
|  | @ -89,7 +89,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import { faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faEllipsisH } from '@fortawesome/free-solid-svg-icons'; | import { faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { faCopy, faTrashAlt, faEdit, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; | import { faCopy, faTrashAlt, faEdit, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; | ||||||
| import { parse } from '../../mfm/parse'; | import { parse } from '../../mfm/parse'; | ||||||
| import { sum, unique } from '../../prelude/array'; | import { sum, unique } from '../../prelude/array'; | ||||||
|  | @ -108,7 +108,6 @@ import { url } from '../config'; | ||||||
| import copyToClipboard from '../scripts/copy-to-clipboard'; | import copyToClipboard from '../scripts/copy-to-clipboard'; | ||||||
| 
 | 
 | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	 |  | ||||||
| 	components: { | 	components: { | ||||||
| 		XSub, | 		XSub, | ||||||
| 		XNoteHeader, | 		XNoteHeader, | ||||||
|  | @ -145,7 +144,7 @@ export default Vue.extend({ | ||||||
| 			showContent: false, | 			showContent: false, | ||||||
| 			hideThisNote: false, | 			hideThisNote: false, | ||||||
| 			noteBody: this.$refs.noteBody, | 			noteBody: this.$refs.noteBody, | ||||||
| 			faEdit, faBolt, faTimes, faBullhorn, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faBiohazard, faEllipsisH | 			faEdit, faBolt, faTimes, faBullhorn, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faBiohazard, faPlug | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
|  | @ -612,6 +611,16 @@ export default Vue.extend({ | ||||||
| 				.filter(x => x !== undefined); | 				.filter(x => x !== undefined); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			if (this.$store.state.noteActions.length > 0) { | ||||||
|  | 				menu = menu.concat([null, ...this.$store.state.noteActions.map(action => ({ | ||||||
|  | 					icon: faPlug, | ||||||
|  | 					text: action.title, | ||||||
|  | 					action: () => { | ||||||
|  | 						action.handler(this.appearNote); | ||||||
|  | 					} | ||||||
|  | 				}))]); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
| 			this.$root.menu({ | 			this.$root.menu({ | ||||||
| 				items: menu, | 				items: menu, | ||||||
| 				source: this.$refs.menuButton, | 				source: this.$refs.menuButton, | ||||||
|  |  | ||||||
|  | @ -44,6 +44,7 @@ | ||||||
| 			<button class="_button" @click="useCw = !useCw" :class="{ active: useCw }" v-tooltip="$t('useCw')"><fa :icon="faEyeSlash"/></button> | 			<button class="_button" @click="useCw = !useCw" :class="{ active: useCw }" v-tooltip="$t('useCw')"><fa :icon="faEyeSlash"/></button> | ||||||
| 			<button class="_button" @click="insertMention" v-tooltip="$t('mention')"><fa :icon="faAt"/></button> | 			<button class="_button" @click="insertMention" v-tooltip="$t('mention')"><fa :icon="faAt"/></button> | ||||||
| 			<button class="_button" @click="insertEmoji" v-tooltip="$t('emoji')"><fa :icon="faLaughSquint"/></button> | 			<button class="_button" @click="insertEmoji" v-tooltip="$t('emoji')"><fa :icon="faLaughSquint"/></button> | ||||||
|  | 			<button class="_button" @click="showActions" v-tooltip="$t('plugin')" v-if="$store.state.postFormActions.length > 0"><fa :icon="faPlug"/></button> | ||||||
| 		</footer> | 		</footer> | ||||||
| 		<input ref="file" class="file _button" type="file" multiple="multiple" @change="onChangeFile"/> | 		<input ref="file" class="file _button" type="file" multiple="multiple" @change="onChangeFile"/> | ||||||
| 	</div> | 	</div> | ||||||
|  | @ -52,7 +53,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import { faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faPlus, faPhotoVideo, faCloud, faLink, faAt, faBiohazard } from '@fortawesome/free-solid-svg-icons'; | import { faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faPlus, faPhotoVideo, faCloud, faLink, faAt, faBiohazard, faPlug } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { faEyeSlash, faLaughSquint } from '@fortawesome/free-regular-svg-icons'; | import { faEyeSlash, faLaughSquint } from '@fortawesome/free-regular-svg-icons'; | ||||||
| import insertTextAtCursor from 'insert-text-at-cursor'; | import insertTextAtCursor from 'insert-text-at-cursor'; | ||||||
| import { length } from 'stringz'; | import { length } from 'stringz'; | ||||||
|  | @ -133,7 +134,7 @@ export default Vue.extend({ | ||||||
| 			draghover: false, | 			draghover: false, | ||||||
| 			quoteId: null, | 			quoteId: null, | ||||||
| 			recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), | 			recentHashtags: JSON.parse(localStorage.getItem('hashtags') || '[]'), | ||||||
| 			faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faCloud, faLink, faAt, faBiohazard | 			faReply, faQuoteRight, faPaperPlane, faTimes, faUpload, faPollH, faGlobe, faHome, faUnlock, faEnvelope, faEyeSlash, faLaughSquint, faPlus, faPhotoVideo, faCloud, faLink, faAt, faBiohazard, faPlug | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
|  | @ -580,6 +581,22 @@ export default Vue.extend({ | ||||||
| 				vm.close(); | 				vm.close(); | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
|  | 
 | ||||||
|  | 		showActions(ev) { | ||||||
|  | 			this.$root.menu({ | ||||||
|  | 				items: this.$store.state.postFormActions.map(action => ({ | ||||||
|  | 					text: action.title, | ||||||
|  | 					action: () => { | ||||||
|  | 						action.handler({ | ||||||
|  | 							text: this.text | ||||||
|  | 						}, (key, value) => { | ||||||
|  | 							if (key === 'text') { this.text = value; } | ||||||
|  | 						}); | ||||||
|  | 					} | ||||||
|  | 				})), | ||||||
|  | 				source: ev.currentTarget || ev.target, | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import { faAt, faListUl, faEye, faEyeSlash, faBan, faPencilAlt, faComments, faUsers, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons'; | import { faAt, faListUl, faEye, faEyeSlash, faBan, faPencilAlt, faComments, faUsers, faMicrophoneSlash, faPlug } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { faSnowflake, faEnvelope } from '@fortawesome/free-regular-svg-icons'; | import { faSnowflake, faEnvelope } from '@fortawesome/free-regular-svg-icons'; | ||||||
| import XMenu from './menu.vue'; | import XMenu from './menu.vue'; | ||||||
| import copyToClipboard from '../scripts/copy-to-clipboard'; | import copyToClipboard from '../scripts/copy-to-clipboard'; | ||||||
|  | @ -80,6 +80,16 @@ export default Vue.extend({ | ||||||
| 			}]); | 			}]); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if (this.$store.state.userActions.length > 0) { | ||||||
|  | 			menu = menu.concat([null, ...this.$store.state.userActions.map(action => ({ | ||||||
|  | 				icon: faPlug, | ||||||
|  | 				text: action.title, | ||||||
|  | 				action: () => { | ||||||
|  | 					action.handler(this.user); | ||||||
|  | 				} | ||||||
|  | 			}))]); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		return { | 		return { | ||||||
| 			items: menu | 			items: menu | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
|  | @ -25,6 +25,8 @@ import { isDeviceDarkmode } from './scripts/is-device-darkmode'; | ||||||
| import createStore from './store'; | import createStore from './store'; | ||||||
| import { clientDb, get, count } from './db'; | import { clientDb, get, count } from './db'; | ||||||
| import { setI18nContexts } from './scripts/set-i18n-contexts'; | import { setI18nContexts } from './scripts/set-i18n-contexts'; | ||||||
|  | import { createPluginEnv } from './scripts/aiscript/api'; | ||||||
|  | import { AiScript } from '@syuilo/aiscript'; | ||||||
| 
 | 
 | ||||||
| Vue.use(Vuex); | Vue.use(Vuex); | ||||||
| Vue.use(VueHotkey); | Vue.use(VueHotkey); | ||||||
|  | @ -231,6 +233,35 @@ os.init(async () => { | ||||||
| 		//store.commit('instance/set', );
 | 		//store.commit('instance/set', );
 | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
|  | 	for (const plugin of store.state.deviceUser.plugins) { | ||||||
|  | 		console.info('Plugin installed:', plugin.name, 'v' + plugin.version); | ||||||
|  | 
 | ||||||
|  | 		const aiscript = new AiScript(createPluginEnv(app, { | ||||||
|  | 			plugin: plugin, | ||||||
|  | 			storageKey: 'plugins:' + plugin.id | ||||||
|  | 		}), { | ||||||
|  | 			in: (q) => { | ||||||
|  | 				return new Promise(ok => { | ||||||
|  | 					app.dialog({ | ||||||
|  | 						title: q, | ||||||
|  | 						input: {} | ||||||
|  | 					}).then(({ canceled, result: a }) => { | ||||||
|  | 						ok(a); | ||||||
|  | 					}); | ||||||
|  | 				}); | ||||||
|  | 			}, | ||||||
|  | 			out: (value) => { | ||||||
|  | 				console.log(value); | ||||||
|  | 			}, | ||||||
|  | 			log: (type, params) => { | ||||||
|  | 			}, | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		store.commit('initPlugin', { plugin, aiscript }); | ||||||
|  | 
 | ||||||
|  | 		aiscript.exec(plugin.ast); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if (store.getters.isSignedIn) { | 	if (store.getters.isSignedIn) { | ||||||
| 		const main = os.stream.useSharedConnection('main'); | 		const main = os.stream.useSharedConnection('main'); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,6 +9,8 @@ | ||||||
| 
 | 
 | ||||||
| 	<x-sidebar/> | 	<x-sidebar/> | ||||||
| 
 | 
 | ||||||
|  | 	<x-plugins/> | ||||||
|  | 
 | ||||||
| 	<section class="_card"> | 	<section class="_card"> | ||||||
| 		<div class="_title"><fa :icon="faMusic"/> {{ $t('sounds') }}</div> | 		<div class="_title"><fa :icon="faMusic"/> {{ $t('sounds') }}</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
|  | @ -115,6 +117,7 @@ import MkRadio from '../../components/ui/radio.vue'; | ||||||
| import MkRange from '../../components/ui/range.vue'; | import MkRange from '../../components/ui/range.vue'; | ||||||
| import XTheme from './theme.vue'; | import XTheme from './theme.vue'; | ||||||
| import XSidebar from './sidebar.vue'; | import XSidebar from './sidebar.vue'; | ||||||
|  | import XPlugins from './plugins.vue'; | ||||||
| import { langs } from '../../config'; | import { langs } from '../../config'; | ||||||
| import { clientDb, set } from '../../db'; | import { clientDb, set } from '../../db'; | ||||||
| 
 | 
 | ||||||
|  | @ -146,11 +149,12 @@ export default Vue.extend({ | ||||||
| 	components: { | 	components: { | ||||||
| 		XTheme, | 		XTheme, | ||||||
| 		XSidebar, | 		XSidebar, | ||||||
|  | 		XPlugins, | ||||||
| 		MkButton, | 		MkButton, | ||||||
| 		MkSwitch, | 		MkSwitch, | ||||||
| 		MkSelect, | 		MkSelect, | ||||||
| 		MkRadio, | 		MkRadio, | ||||||
| 		MkRange | 		MkRange, | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	data() { | 	data() { | ||||||
|  |  | ||||||
							
								
								
									
										134
									
								
								src/client/pages/preferences/plugins.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/client/pages/preferences/plugins.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,134 @@ | ||||||
|  | <template> | ||||||
|  | <section class="_card"> | ||||||
|  | 	<div class="_title"><fa :icon="faPlug"/> {{ $t('plugins') }}</div> | ||||||
|  | 	<div class="_content"> | ||||||
|  | 		<details> | ||||||
|  | 			<summary><fa :icon="faDownload"/> {{ $t('install') }}</summary> | ||||||
|  | 			<mk-info warn>{{ $t('pluginInstallWarn') }}</mk-info> | ||||||
|  | 			<mk-textarea v-model="script" tall> | ||||||
|  | 				<span>{{ $t('script') }}</span> | ||||||
|  | 			</mk-textarea> | ||||||
|  | 			<mk-button @click="install()" primary><fa :icon="faSave"/> {{ $t('install') }}</mk-button> | ||||||
|  | 		</details> | ||||||
|  | 	</div> | ||||||
|  | 	<div class="_content"> | ||||||
|  | 		<details> | ||||||
|  | 			<summary><fa :icon="faFolderOpen"/> {{ $t('manage') }}</summary> | ||||||
|  | 			<mk-select v-model="selectedPluginId"> | ||||||
|  | 				<option v-for="x in $store.state.deviceUser.plugins" :value="x.id" :key="x.id">{{ x.name }}</option> | ||||||
|  | 			</mk-select> | ||||||
|  | 			<template v-if="selectedPlugin"> | ||||||
|  | 				<div class="_keyValue"> | ||||||
|  | 					<div>{{ $t('version') }}:</div> | ||||||
|  | 					<div>{{ selectedPlugin.version }}</div> | ||||||
|  | 				</div> | ||||||
|  | 				<div class="_keyValue"> | ||||||
|  | 					<div>{{ $t('author') }}:</div> | ||||||
|  | 					<div>{{ selectedPlugin.author }}</div> | ||||||
|  | 				</div> | ||||||
|  | 				<div class="_keyValue"> | ||||||
|  | 					<div>{{ $t('description') }}:</div> | ||||||
|  | 					<div>{{ selectedPlugin.description }}</div> | ||||||
|  | 				</div> | ||||||
|  | 				<mk-button @click="uninstall()" style="margin-top: 8px;"><fa :icon="faTrashAlt"/> {{ $t('uninstall') }}</mk-button> | ||||||
|  | 			</template> | ||||||
|  | 		</details> | ||||||
|  | 	</div> | ||||||
|  | </section> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import { faPlug, faSave, faTrashAlt, faFolderOpen, faDownload } from '@fortawesome/free-solid-svg-icons'; | ||||||
|  | import MkButton from '../../components/ui/button.vue'; | ||||||
|  | import MkTextarea from '../../components/ui/textarea.vue'; | ||||||
|  | import MkSelect from '../../components/ui/select.vue'; | ||||||
|  | import MkInfo from '../../components/ui/info.vue'; | ||||||
|  | import { AiScript, parse } from '@syuilo/aiscript'; | ||||||
|  | 
 | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	components: { | ||||||
|  | 		MkButton, | ||||||
|  | 		MkTextarea, | ||||||
|  | 		MkSelect, | ||||||
|  | 		MkInfo, | ||||||
|  | 	}, | ||||||
|  | 	 | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			script: '', | ||||||
|  | 			selectedPluginId: null, | ||||||
|  | 			faPlug, faSave, faTrashAlt, faFolderOpen, faDownload | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	computed: { | ||||||
|  | 		selectedPlugin() { | ||||||
|  | 			if (this.selectedPluginId == null) return null; | ||||||
|  | 			return this.$store.state.deviceUser.plugins.find(x => x.id === this.selectedPluginId); | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	methods: { | ||||||
|  | 		install() { | ||||||
|  | 			let ast; | ||||||
|  | 			try { | ||||||
|  | 				ast = parse(this.script); | ||||||
|  | 			} catch (e) { | ||||||
|  | 				this.$root.dialog({ | ||||||
|  | 					type: 'error', | ||||||
|  | 					text: 'Syntax error :(' | ||||||
|  | 				}); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			const meta = AiScript.collectMetadata(ast); | ||||||
|  | 			console.log(meta); | ||||||
|  | 			if (meta == null) { | ||||||
|  | 				this.$root.dialog({ | ||||||
|  | 					type: 'error', | ||||||
|  | 					text: 'No metadata found :(' | ||||||
|  | 				}); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			const data = meta.get(null); | ||||||
|  | 			if (data == null) { | ||||||
|  | 				this.$root.dialog({ | ||||||
|  | 					type: 'error', | ||||||
|  | 					text: 'No metadata found :(' | ||||||
|  | 				}); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			const { id, name, version, author, description } = data; | ||||||
|  | 			if (id == null || name == null || version == null || author == null) { | ||||||
|  | 				this.$root.dialog({ | ||||||
|  | 					type: 'error', | ||||||
|  | 					text: 'Required property not found :(' | ||||||
|  | 				}); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			this.$store.commit('deviceUser/installPlugin', { | ||||||
|  | 				meta: { | ||||||
|  | 					id, name, version, author, description | ||||||
|  | 				}, | ||||||
|  | 				ast | ||||||
|  | 			}); | ||||||
|  | 			this.$root.dialog({ | ||||||
|  | 				type: 'success', | ||||||
|  | 				iconOnly: true, autoClose: true | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		uninstall() { | ||||||
|  | 			this.$store.commit('deviceUser/uninstallPlugin', this.selectedPluginId); | ||||||
|  | 			this.$root.dialog({ | ||||||
|  | 				type: 'success', | ||||||
|  | 				iconOnly: true, autoClose: true | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | 
 | ||||||
|  | </style> | ||||||
|  | @ -30,7 +30,7 @@ import PrismEditor from 'vue-prism-editor'; | ||||||
| import { AiScript, parse, utils, values } from '@syuilo/aiscript'; | import { AiScript, parse, utils, values } from '@syuilo/aiscript'; | ||||||
| import MkContainer from '../components/ui/container.vue'; | import MkContainer from '../components/ui/container.vue'; | ||||||
| import MkButton from '../components/ui/button.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({ | export default Vue.extend({ | ||||||
| 	metaInfo() { | 	metaInfo() { | ||||||
|  |  | ||||||
|  | @ -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 }); | ||||||
|  | 		}), | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | @ -3,7 +3,7 @@ import * as seedrandom from 'seedrandom'; | ||||||
| import { Variable, PageVar, envVarsDef, funcDefs, Block, isFnBlock } from '.'; | import { Variable, PageVar, envVarsDef, funcDefs, Block, isFnBlock } from '.'; | ||||||
| import { version } from '../../config'; | import { version } from '../../config'; | ||||||
| import { AiScript, utils, values } from '@syuilo/aiscript'; | 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 { collectPageVars } from '../collect-page-vars'; | ||||||
| import { initLib } from './lib'; | import { initLib } from './lib'; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ import createPersistedState from 'vuex-persistedstate'; | ||||||
| import * as nestedProperty from 'nested-property'; | 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 { 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 { faBell, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  | import { AiScript, utils, values } from '@syuilo/aiscript'; | ||||||
| import { apiUrl, deckmode } from './config'; | import { apiUrl, deckmode } from './config'; | ||||||
| import { erase } from '../prelude/array'; | import { erase } from '../prelude/array'; | ||||||
| 
 | 
 | ||||||
|  | @ -43,6 +44,7 @@ export const defaultDeviceUserSettings = { | ||||||
| 		columns: [], | 		columns: [], | ||||||
| 		layout: [], | 		layout: [], | ||||||
| 	}, | 	}, | ||||||
|  | 	plugins: [], | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const defaultDeviceSettings = { | export const defaultDeviceSettings = { | ||||||
|  | @ -93,7 +95,13 @@ export default () => new Vuex.Store({ | ||||||
| 	state: { | 	state: { | ||||||
| 		i: null, | 		i: null, | ||||||
| 		pendingApiRequestsCount: 0, | 		pendingApiRequestsCount: 0, | ||||||
| 		spinner: null | 		spinner: null, | ||||||
|  | 
 | ||||||
|  | 		// Plugin
 | ||||||
|  | 		pluginContexts: new Map<string, AiScript>(), | ||||||
|  | 		postFormActions: [], | ||||||
|  | 		userActions: [], | ||||||
|  | 		noteActions: [], | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	getters: { | 	getters: { | ||||||
|  | @ -224,8 +232,38 @@ export default () => new Vuex.Store({ | ||||||
| 			state.i = x; | 			state.i = x; | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		updateIKeyValue(state, x) { | 		updateIKeyValue(state, { key, value }) { | ||||||
| 			state.i[x.key] = x.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; | 					column = x; | ||||||
| 				}, | 				}, | ||||||
| 				//#endregion
 | 				//#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); | ||||||
|  | 				}, | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -197,10 +197,10 @@ | ||||||
|   dependencies: |   dependencies: | ||||||
|     "@sinonjs/commons" "^1.7.0" |     "@sinonjs/commons" "^1.7.0" | ||||||
| 
 | 
 | ||||||
| "@syuilo/aiscript@0.7.0": | "@syuilo/aiscript@0.7.2": | ||||||
|   version "0.7.0" |   version "0.7.2" | ||||||
|   resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.7.0.tgz#1394511a789891e844d32e536a203fe0d92b3039" |   resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.7.2.tgz#2f30adb14ffa9f1180af83c059927ab306b175a5" | ||||||
|   integrity sha512-X4TaP/FO7RD8MpFSPDFwKAI4KX7byn8ApqmSSmf2bxcwCTcdevsbyxsLrvkbNaWclIoqTgXwtJjY+2Tc2exeXA== |   integrity sha512-l8HVTJTq9KLzDqGswOIGlBepkacudUp70EScrLjL7nEL2NKcti7Ui5fwZCrmxazxgGz6NrVNX5UBIOFFyrwr0A== | ||||||
|   dependencies: |   dependencies: | ||||||
|     autobind-decorator "2.4.0" |     autobind-decorator "2.4.0" | ||||||
|     chalk "4.0.0" |     chalk "4.0.0" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue