Resolve #5755
This commit is contained in:
		
							parent
							
								
									36b9a0d42f
								
							
						
					
					
						commit
						11cc9cbc7c
					
				
					 28 changed files with 195 additions and 63 deletions
				
			
		|  | @ -481,6 +481,8 @@ descendingOrder: "降順" | |||
| scratchpad: "スクラッチパッド" | ||||
| scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。" | ||||
| output: "出力" | ||||
| script: "スクリプト" | ||||
| disablePagesScript: "Pagesのスクリプトを無効にする" | ||||
| 
 | ||||
| _theme: | ||||
|   explore: "テーマを探す" | ||||
|  | @ -813,6 +815,9 @@ _pages: | |||
|           message: "押したときに表示するメッセージ" | ||||
|           variable: "送信する変数" | ||||
|           no-variable: "なし" | ||||
|         callAiScript: "AiScript呼び出し" | ||||
|         _callAiScript: | ||||
|           functionName: "関数名" | ||||
| 
 | ||||
|     radioButton: "選択肢" | ||||
|     _radioButton: | ||||
|  | @ -975,6 +980,7 @@ _pages: | |||
|       _splitStrByLine: | ||||
|         arg1: "テキスト" | ||||
|       ref: "変数" | ||||
|       aiScriptVar: "AiScript変数" | ||||
|       fn: "関数" | ||||
|       _fn: | ||||
|         slots: "スロット" | ||||
|  |  | |||
							
								
								
									
										14
									
								
								migration/1586708940386-pageAiScript.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1586708940386-pageAiScript.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| import {MigrationInterface, QueryRunner} from "typeorm"; | ||||
| 
 | ||||
| export class pageAiScript1586708940386 implements MigrationInterface { | ||||
|     name = 'pageAiScript1586708940386' | ||||
| 
 | ||||
|     public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "page" ADD "script" character varying(16384) NOT NULL DEFAULT ''`, undefined); | ||||
|     } | ||||
| 
 | ||||
|     public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "script"`, undefined); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -42,7 +42,7 @@ | |||
| 		"@koa/cors": "3.0.0", | ||||
| 		"@koa/multer": "2.0.2", | ||||
| 		"@koa/router": "8.0.8", | ||||
| 		"@syuilo/aiscript": "0.1.2", | ||||
| 		"@syuilo/aiscript": "0.1.4", | ||||
| 		"@types/bcryptjs": "2.4.2", | ||||
| 		"@types/bull": "3.12.1", | ||||
| 		"@types/cbor": "5.0.0", | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ export default Vue.extend({ | |||
| 					text: this.script.interpolate(this.value.content) | ||||
| 				}); | ||||
| 			} else if (this.value.action === 'resetRandom') { | ||||
| 				this.script.aiScript.updateRandomSeed(Math.random()); | ||||
| 				this.script.aoiScript.updateRandomSeed(Math.random()); | ||||
| 				this.script.eval(); | ||||
| 			} else if (this.value.action === 'pushEvent') { | ||||
| 				this.$root.api('page-push', { | ||||
|  | @ -43,6 +43,8 @@ export default Vue.extend({ | |||
| 					type: 'success', | ||||
| 					text: this.script.interpolate(this.value.message) | ||||
| 				}); | ||||
| 			} else if (this.value.action === 'callAiScript') { | ||||
| 				this.script.callAiScript(this.value.fn); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.script.aiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.aoiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.eval(); | ||||
| 		} | ||||
| 	}, | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.script.aiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.aoiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.eval(); | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.script.aiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.aoiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.eval(); | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.script.aiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.aoiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.eval(); | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.script.aiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.aoiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.eval(); | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 	watch: { | ||||
| 		v() { | ||||
| 			this.script.aiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.aoiScript.updatePageVar(this.value.name, this.v); | ||||
| 			this.script.eval(); | ||||
| 		} | ||||
| 	} | ||||
|  |  | |||
|  | @ -6,30 +6,57 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../i18n'; | ||||
| import { AiScript, parse, values } from '@syuilo/aiscript'; | ||||
| import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faHeart } from '@fortawesome/free-regular-svg-icons'; | ||||
| import i18n from '../../i18n'; | ||||
| import XBlock from './page.block.vue'; | ||||
| import { ASEvaluator } from '../../scripts/aoiscript/evaluator'; | ||||
| import { collectPageVars } from '../../scripts/collect-page-vars'; | ||||
| import { url } from '../../config'; | ||||
| 
 | ||||
| class Script { | ||||
| 	public aiScript: ASEvaluator; | ||||
| 	public aoiScript: ASEvaluator; | ||||
| 	private onError: any; | ||||
| 	public vars: Record<string, any>; | ||||
| 	public page: Record<string, any>; | ||||
| 
 | ||||
| 	constructor(page, aiScript, onError) { | ||||
| 	constructor(page, aoiScript, onError, cb) { | ||||
| 		this.page = page; | ||||
| 		this.aiScript = aiScript; | ||||
| 		this.aoiScript = aoiScript; | ||||
| 		this.onError = onError; | ||||
| 		this.eval(); | ||||
| 
 | ||||
| 		if (this.page.script) { | ||||
| 			let ast; | ||||
| 			try { | ||||
| 				ast = parse(this.page.script); | ||||
| 			} catch (e) { | ||||
| 				console.error(e); | ||||
| 				/*this.$root.dialog({ | ||||
| 					type: 'error', | ||||
| 					text: 'Syntax error :(' | ||||
| 				});*/ | ||||
| 				return; | ||||
| 			} | ||||
| 			this.aoiScript.aiscript.exec(ast).then(() => { | ||||
| 				this.eval(); | ||||
| 				cb(); | ||||
| 			}).catch(e => { | ||||
| 				console.error(e); | ||||
| 				/*this.$root.dialog({ | ||||
| 					type: 'error', | ||||
| 					text: e | ||||
| 				});*/ | ||||
| 			}); | ||||
| 		} else { | ||||
| 			this.eval(); | ||||
| 			cb(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	public eval() { | ||||
| 		try { | ||||
| 			this.vars = this.aiScript.evaluateVars(); | ||||
| 			this.vars = this.aoiScript.evaluateVars(); | ||||
| 		} catch (e) { | ||||
| 			this.onError(e); | ||||
| 		} | ||||
|  | @ -42,6 +69,10 @@ class Script { | |||
| 			return v == null ? 'NULL' : v.toString(); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	public callAiScript(fn: string) { | ||||
| 		this.aoiScript.aiscript.execFn(this.aoiScript.aiscript.scope.get(fn), []); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
|  | @ -67,14 +98,21 @@ export default Vue.extend({ | |||
| 
 | ||||
| 	created() { | ||||
| 		const pageVars = this.getPageVars(); | ||||
| 		this.script = new Script(this.page, new ASEvaluator(this.page.variables, pageVars, { | ||||
| 		 | ||||
| 		const s = new Script(this.page, new ASEvaluator(this, this.page.variables, pageVars, { | ||||
| 			randomSeed: Math.random(), | ||||
| 			visitor: this.$store.state.i, | ||||
| 			page: this.page, | ||||
| 			url: url | ||||
| 		}), e => { | ||||
| 			console.dir(e); | ||||
| 		}, () => { | ||||
| 			this.script = s; | ||||
| 		}); | ||||
| 
 | ||||
| 		s.aoiScript.aiscript.scope.opts.onUpdated = (name, value) => { | ||||
| 			s.eval(); | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
| 			<option value="dialog">{{ $t('_pages.blocks._button._action.dialog') }}</option> | ||||
| 			<option value="resetRandom">{{ $t('_pages.blocks._button._action.resetRandom') }}</option> | ||||
| 			<option value="pushEvent">{{ $t('_pages.blocks._button._action.pushEvent') }}</option> | ||||
| 			<option value="callAiScript">{{ $t('_pages.blocks._button._action.callAiScript') }}</option> | ||||
| 		</mk-select> | ||||
| 		<template v-if="value.action === 'dialog'"> | ||||
| 			<mk-input v-model="value.content"><span>{{ $t('_pages.blocks._button._action._dialog.content') }}</span></mk-input> | ||||
|  | @ -20,15 +21,18 @@ | |||
| 			<mk-select v-model="value.var"> | ||||
| 				<template #label>{{ $t('_pages.blocks._button._action._pushEvent.variable') }}</template> | ||||
| 				<option :value="null">{{ $t('_pages.blocks._button._action._pushEvent.no-variable') }}</option> | ||||
| 				<option v-for="v in aiScript.getVarsByType()" :value="v.name">{{ v.name }}</option> | ||||
| 				<option v-for="v in aoiScript.getVarsByType()" :value="v.name">{{ v.name }}</option> | ||||
| 				<optgroup :label="$t('_pages.script.pageVariables')"> | ||||
| 					<option v-for="v in aiScript.getPageVarsByType()" :value="v">{{ v }}</option> | ||||
| 					<option v-for="v in aoiScript.getPageVarsByType()" :value="v">{{ v }}</option> | ||||
| 				</optgroup> | ||||
| 				<optgroup :label="$t('_pages.script.enviromentVariables')"> | ||||
| 					<option v-for="v in aiScript.getEnvVarsByType()" :value="v">{{ v }}</option> | ||||
| 					<option v-for="v in aoiScript.getEnvVarsByType()" :value="v">{{ v }}</option> | ||||
| 				</optgroup> | ||||
| 			</mk-select> | ||||
| 		</template> | ||||
| 		<template v-else-if="value.action === 'callAiScript'"> | ||||
| 			<mk-input v-model="value.fn"><span>{{ $t('_pages.blocks._button._action._callAiScript.functionName') }}</span></mk-input> | ||||
| 		</template> | ||||
| 	</section> | ||||
| </x-container> | ||||
| </template> | ||||
|  | @ -53,7 +57,7 @@ export default Vue.extend({ | |||
| 		value: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		aiScript: { | ||||
| 		aoiScript: { | ||||
| 			required: true, | ||||
| 		}, | ||||
| 	}, | ||||
|  | @ -72,6 +76,7 @@ export default Vue.extend({ | |||
| 		if (this.value.message == null) Vue.set(this.value, 'message', null); | ||||
| 		if (this.value.primary == null) Vue.set(this.value, 'primary', false); | ||||
| 		if (this.value.var == null) Vue.set(this.value, 'var', null); | ||||
| 		if (this.value.fn == null) Vue.set(this.value, 'fn', null); | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -10,16 +10,16 @@ | |||
| 	<section class="romcojzs"> | ||||
| 		<mk-select v-model="value.var"> | ||||
| 			<template #label>{{ $t('_pages.blocks._if.variable') }}</template> | ||||
| 			<option v-for="v in aiScript.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option> | ||||
| 			<option v-for="v in aoiScript.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option> | ||||
| 			<optgroup :label="$t('_pages.script.pageVariables')"> | ||||
| 				<option v-for="v in aiScript.getPageVarsByType('boolean')" :value="v">{{ v }}</option> | ||||
| 				<option v-for="v in aoiScript.getPageVarsByType('boolean')" :value="v">{{ v }}</option> | ||||
| 			</optgroup> | ||||
| 			<optgroup :label="$t('_pages.script.enviromentVariables')"> | ||||
| 				<option v-for="v in aiScript.getEnvVarsByType('boolean')" :value="v">{{ v }}</option> | ||||
| 				<option v-for="v in aoiScript.getEnvVarsByType('boolean')" :value="v">{{ v }}</option> | ||||
| 			</optgroup> | ||||
| 		</mk-select> | ||||
| 
 | ||||
| 		<x-blocks class="children" v-model="value.children" :ai-script="aiScript"/> | ||||
| 		<x-blocks class="children" v-model="value.children" :aoi-script="aoiScript"/> | ||||
| 	</section> | ||||
| </x-container> | ||||
| </template> | ||||
|  | @ -45,7 +45,7 @@ export default Vue.extend({ | |||
| 		value: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		aiScript: { | ||||
| 		aoiScript: { | ||||
| 			required: true, | ||||
| 		}, | ||||
| 	}, | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ | |||
| 	</template> | ||||
| 
 | ||||
| 	<section class="ilrvjyvi"> | ||||
| 		<x-blocks class="children" v-model="value.children" :ai-script="aiScript"/> | ||||
| 		<x-blocks class="children" v-model="value.children" :aoi-script="aoiScript"/> | ||||
| 	</section> | ||||
| </x-container> | ||||
| </template> | ||||
|  | @ -37,7 +37,7 @@ export default Vue.extend({ | |||
| 		value: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		aiScript: { | ||||
| 		aoiScript: { | ||||
| 			required: true, | ||||
| 		}, | ||||
| 	}, | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| <x-container @remove="() => $emit('remove')" :draggable="true"> | ||||
| 	<template #header><fa :icon="faAlignLeft"/> {{ $t('_pages.blocks.text') }}</template> | ||||
| 
 | ||||
| 	<section class="ihymsbbe"> | ||||
| 	<section class="vckmsadr"> | ||||
| 		<textarea v-model="value.text"></textarea> | ||||
| 	</section> | ||||
| </x-container> | ||||
|  | @ -40,7 +40,7 @@ export default Vue.extend({ | |||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .ihymsbbe { | ||||
| .vckmsadr { | ||||
| 	> textarea { | ||||
| 		display: block; | ||||
| 		-webkit-appearance: none; | ||||
|  | @ -55,6 +55,7 @@ export default Vue.extend({ | |||
| 		background: transparent; | ||||
| 		color: var(--fg); | ||||
| 		font-size: 14px; | ||||
| 		box-sizing: border-box; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -55,6 +55,7 @@ export default Vue.extend({ | |||
| 		background: transparent; | ||||
| 		color: var(--fg); | ||||
| 		font-size: 14px; | ||||
| 		box-sizing: border-box; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <template> | ||||
| <x-draggable tag="div" :list="blocks" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5"> | ||||
| 	<component v-for="block in blocks" :is="'x-' + block.type" :value="block" @input="updateItem" @remove="() => removeItem(block)" :key="block.id" :ai-script="aiScript"/> | ||||
| 	<component v-for="block in blocks" :is="'x-' + block.type" :value="block" @input="updateItem" @remove="() => removeItem(block)" :key="block.id" :aoi-script="aoiScript"/> | ||||
| </x-draggable> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -31,7 +31,7 @@ export default Vue.extend({ | |||
| 			type: Array, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		aiScript: { | ||||
| 		aoiScript: { | ||||
| 			required: true, | ||||
| 		}, | ||||
| 	}, | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| <x-container :removable="removable" @remove="() => $emit('remove')" :error="error" :warn="warn" :draggable="draggable"> | ||||
| 	<template #header><fa v-if="icon" :icon="icon"/> <template v-if="title">{{ title }} <span class="turmquns" v-if="typeText">({{ typeText }})</span></template><template v-else-if="typeText">{{ typeText }}</template></template> | ||||
| 	<template #func> | ||||
| 		<button @click="changeType()"> | ||||
| 		<button @click="changeType()" class="_button"> | ||||
| 			<fa :icon="faPencilAlt"/> | ||||
| 		</button> | ||||
| 	</template> | ||||
|  | @ -24,30 +24,33 @@ | |||
| 	</section> | ||||
| 	<section v-else-if="value.type === 'ref'" class="hpdwcrvs"> | ||||
| 		<select v-model="value.value"> | ||||
| 			<option v-for="v in aiScript.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option> | ||||
| 			<option v-for="v in aoiScript.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option> | ||||
| 			<optgroup :label="$t('_pages.script.argVariables')"> | ||||
| 				<option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option> | ||||
| 			</optgroup> | ||||
| 			<optgroup :label="$t('_pages.script.pageVariables')"> | ||||
| 				<option v-for="v in aiScript.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option> | ||||
| 				<option v-for="v in aoiScript.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option> | ||||
| 			</optgroup> | ||||
| 			<optgroup :label="$t('_pages.script.enviromentVariables')"> | ||||
| 				<option v-for="v in aiScript.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option> | ||||
| 				<option v-for="v in aoiScript.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option> | ||||
| 			</optgroup> | ||||
| 		</select> | ||||
| 	</section> | ||||
| 	<section v-else-if="value.type === 'aiScriptVar'" class="tbwccoaw"> | ||||
| 		<input v-model="value.value"/> | ||||
| 	</section> | ||||
| 	<section v-else-if="value.type === 'fn'" class="" style="padding:0 16px 16px 16px;"> | ||||
| 		<mk-textarea v-model="slots"> | ||||
| 			<span>{{ $t('_pages.script.blocks._fn.slots') }}</span> | ||||
| 			<template #desc>{{ $t('_pages.script.blocks._fn.slots-info') }}</template> | ||||
| 		</mk-textarea> | ||||
| 		<x-v v-if="value.value.expression" v-model="value.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :ai-script="aiScript" :fn-slots="value.value.slots" :name="name"/> | ||||
| 		<x-v v-if="value.value.expression" v-model="value.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :aoi-script="aoiScript" :fn-slots="value.value.slots" :name="name"/> | ||||
| 	</section> | ||||
| 	<section v-else-if="value.type.startsWith('fn:')" class="" style="padding:16px;"> | ||||
| 		<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="aiScript.getVarByName(value.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :ai-script="aiScript" :name="name" :key="i"/> | ||||
| 		<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="aoiScript.getVarByName(value.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :aoi-script="aoiScript" :name="name" :key="i"/> | ||||
| 	</section> | ||||
| 	<section v-else class="" style="padding:16px;"> | ||||
| 		<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="$t(`_pages.script.blocks._${value.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :ai-script="aiScript" :name="name" :fn-slots="fnSlots" :key="i"/> | ||||
| 		<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="$t(`_pages.script.blocks._${value.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :aoi-script="aoiScript" :name="name" :fn-slots="fnSlots" :key="i"/> | ||||
| 	</section> | ||||
| </x-container> | ||||
| </template> | ||||
|  | @ -85,7 +88,7 @@ export default Vue.extend({ | |||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 		aiScript: { | ||||
| 		aoiScript: { | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		name: { | ||||
|  | @ -153,7 +156,7 @@ export default Vue.extend({ | |||
| 
 | ||||
| 			if (this.value.type && this.value.type.startsWith('fn:')) { | ||||
| 				const fnName = this.value.type.split(':')[1]; | ||||
| 				const fn = this.aiScript.getVarByName(fnName); | ||||
| 				const fn = this.aoiScript.getVarByName(fnName); | ||||
| 
 | ||||
| 				const empties = []; | ||||
| 				for (let i = 0; i < fn.value.slots.length; i++) { | ||||
|  | @ -199,9 +202,9 @@ export default Vue.extend({ | |||
| 			deep: true | ||||
| 		}); | ||||
| 
 | ||||
| 		this.$watch('aiScript.variables', () => { | ||||
| 		this.$watch('aoiScript.variables', () => { | ||||
| 			if (this.type != null && this.value) { | ||||
| 				this.error = this.aiScript.typeCheck(this.value); | ||||
| 				this.error = this.aoiScript.typeCheck(this.value); | ||||
| 			} | ||||
| 		}, { | ||||
| 			deep: true | ||||
|  | @ -223,7 +226,7 @@ export default Vue.extend({ | |||
| 		}, | ||||
| 
 | ||||
| 		_getExpectedType(slot: number) { | ||||
| 			return this.aiScript.getExpectedType(this.value, slot); | ||||
| 			return this.aoiScript.getExpectedType(this.value, slot); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  | @ -258,6 +261,7 @@ export default Vue.extend({ | |||
| 		font-size: 16px; | ||||
| 		background: transparent; | ||||
| 		color: var(--fg); | ||||
| 		box-sizing: border-box; | ||||
| 	} | ||||
| 
 | ||||
| 	> textarea { | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ | |||
| 				</div> | ||||
| 			</template> | ||||
| 
 | ||||
| 			<x-blocks class="content" v-model="content" :ai-script="aiScript"/> | ||||
| 			<x-blocks class="content" v-model="content" :aoi-script="aoiScript"/> | ||||
| 
 | ||||
| 			<mk-button @click="add()" v-if="!readonly"><fa :icon="faPlus"/></mk-button> | ||||
| 		</section> | ||||
|  | @ -62,7 +62,7 @@ | |||
| 					@input="v => updateVariable(v)" | ||||
| 					@remove="() => removeVariable(variable)" | ||||
| 					:key="variable.name" | ||||
| 					:ai-script="aiScript" | ||||
| 					:aoi-script="aoiScript" | ||||
| 					:name="variable.name" | ||||
| 					:title="variable.name" | ||||
| 					:draggable="true" | ||||
|  | @ -73,11 +73,10 @@ | |||
| 		</div> | ||||
| 	</mk-container> | ||||
| 
 | ||||
| 	<mk-container :body-togglable="true" :expanded="false"> | ||||
| 		<template #header><fa :icon="faCode"/> {{ $t('_pages.inspector') }}</template> | ||||
| 		<div style="padding:0 32px 32px 32px;"> | ||||
| 			<mk-textarea :value="JSON.stringify(content, null, 2)" readonly tall>{{ $t('_pages.content') }}</mk-textarea> | ||||
| 			<mk-textarea :value="JSON.stringify(variables, null, 2)" readonly tall>{{ $t('_pages.variables') }}</mk-textarea> | ||||
| 	<mk-container :body-togglable="true" :expanded="true"> | ||||
| 		<template #header><fa :icon="faCode"/> {{ $t('script') }}</template> | ||||
| 		<div> | ||||
| 			<prism-editor v-model="script" :line-numbers="false" language="js"/> | ||||
| 		</div> | ||||
| 	</mk-container> | ||||
| </div> | ||||
|  | @ -86,6 +85,9 @@ | |||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import * as XDraggable from 'vuedraggable'; | ||||
| import "prismjs"; | ||||
| import "prismjs/themes/prism.css"; | ||||
| import PrismEditor from 'vue-prism-editor'; | ||||
| import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
|  | @ -108,7 +110,7 @@ export default Vue.extend({ | |||
| 	i18n, | ||||
| 
 | ||||
| 	components: { | ||||
| 		XDraggable, XVariable, XBlocks, MkTextarea, MkContainer, MkButton, MkSelect, MkSwitch, MkInput | ||||
| 		XDraggable, XVariable, XBlocks, MkTextarea, MkContainer, MkButton, MkSelect, MkSwitch, MkInput, PrismEditor | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
|  | @ -143,7 +145,8 @@ export default Vue.extend({ | |||
| 			alignCenter: false, | ||||
| 			hideTitleWhenPinned: false, | ||||
| 			variables: [], | ||||
| 			aiScript: null, | ||||
| 			aoiScript: null, | ||||
| 			script: '', | ||||
| 			showOptions: false, | ||||
| 			url, | ||||
| 			faPlus, faICursor, faSave, faStickyNote, faMagic, faCog, faTrashAlt, faExternalLinkSquareAlt, faCode | ||||
|  | @ -163,14 +166,14 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 
 | ||||
| 	async created() { | ||||
| 		this.aiScript = new ASTypeChecker(); | ||||
| 		this.aoiScript = new ASTypeChecker(); | ||||
| 
 | ||||
| 		this.$watch('variables', () => { | ||||
| 			this.aiScript.variables = this.variables; | ||||
| 			this.aoiScript.variables = this.variables; | ||||
| 		}, { deep: true }); | ||||
| 
 | ||||
| 		this.$watch('content', () => { | ||||
| 			this.aiScript.pageVars = collectPageVars(this.content); | ||||
| 			this.aoiScript.pageVars = collectPageVars(this.content); | ||||
| 		}, { deep: true }); | ||||
| 
 | ||||
| 		if (this.initPageId) { | ||||
|  | @ -193,6 +196,7 @@ export default Vue.extend({ | |||
| 			this.currentName = this.page.name; | ||||
| 			this.summary = this.page.summary; | ||||
| 			this.font = this.page.font; | ||||
| 			this.script = this.page.script; | ||||
| 			this.hideTitleWhenPinned = this.page.hideTitleWhenPinned; | ||||
| 			this.alignCenter = this.page.alignCenter; | ||||
| 			this.content = this.page.content; | ||||
|  | @ -223,6 +227,7 @@ export default Vue.extend({ | |||
| 				name: this.name.trim(), | ||||
| 				summary: this.summary, | ||||
| 				font: this.font, | ||||
| 				script: this.script, | ||||
| 				hideTitleWhenPinned: this.hideTitleWhenPinned, | ||||
| 				alignCenter: this.alignCenter, | ||||
| 				content: this.content, | ||||
|  | @ -317,7 +322,7 @@ export default Vue.extend({ | |||
| 
 | ||||
| 			name = name.trim(); | ||||
| 
 | ||||
| 			if (this.aiScript.isUsedName(name)) { | ||||
| 			if (this.aoiScript.isUsedName(name)) { | ||||
| 				this.$root.dialog({ | ||||
| 					type: 'error', | ||||
| 					text: this.$t('_pages.variableNameIsAlreadyUsed') | ||||
|  | @ -382,7 +387,7 @@ export default Vue.extend({ | |||
| 				} else { | ||||
| 					list.push({ | ||||
| 						category: block.category, | ||||
| 						label: this.$t(`script.categories.${block.category}`), | ||||
| 						label: this.$t(`_pages.script.categories.${block.category}`), | ||||
| 						items: [{ | ||||
| 							value: block.type, | ||||
| 							text: this.$t(`_pages.script.blocks.${block.type}`) | ||||
|  | @ -394,7 +399,7 @@ export default Vue.extend({ | |||
| 			const userFns = this.variables.filter(x => x.type === 'fn'); | ||||
| 			if (userFns.length > 0) { | ||||
| 				list.unshift({ | ||||
| 					label: this.$t(`script.categories.fn`), | ||||
| 					label: this.$t(`_pages.script.categories.fn`), | ||||
| 					items: userFns.map(v => ({ | ||||
| 						value: 'fn:' + v.name, | ||||
| 						text: v.name | ||||
|  |  | |||
|  | @ -23,9 +23,9 @@ | |||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import { faTerminal, faPlay } from '@fortawesome/free-solid-svg-icons'; | ||||
| import "prismjs"; | ||||
| import "prismjs/themes/prism.css"; | ||||
| import { faTerminal, faPlay } from '@fortawesome/free-solid-svg-icons'; | ||||
| import PrismEditor from 'vue-prism-editor'; | ||||
| import { AiScript, parse, utils, values } from '@syuilo/aiscript'; | ||||
| import i18n from '../i18n'; | ||||
|  |  | |||
|  | @ -2,6 +2,8 @@ import autobind from 'autobind-decorator'; | |||
| import * as seedrandom from 'seedrandom'; | ||||
| import { Variable, PageVar, envVarsDef, funcDefs, Block, isFnBlock } from '.'; | ||||
| import { version } from '../../config'; | ||||
| import { AiScript, utils, parse, values } from '@syuilo/aiscript'; | ||||
| import { createAiScriptEnv } from '../create-aiscript-env'; | ||||
| 
 | ||||
| type Fn = { | ||||
| 	slots: string[]; | ||||
|  | @ -15,15 +17,41 @@ export class ASEvaluator { | |||
| 	private variables: Variable[]; | ||||
| 	private pageVars: PageVar[]; | ||||
| 	private envVars: Record<keyof typeof envVarsDef, any>; | ||||
| 	public aiscript: AiScript; | ||||
| 	private pageVarUpdatedCallback; | ||||
| 
 | ||||
| 	private opts: { | ||||
| 		randomSeed: string; visitor?: any; page?: any; url?: string; | ||||
| 	}; | ||||
| 
 | ||||
| 	constructor(variables: Variable[], pageVars: PageVar[], opts: ASEvaluator['opts']) { | ||||
| 	constructor(vm: any, variables: Variable[], pageVars: PageVar[], opts: ASEvaluator['opts']) { | ||||
| 		this.variables = variables; | ||||
| 		this.pageVars = pageVars; | ||||
| 		this.opts = opts; | ||||
| 		this.aiscript = new AiScript({ ...createAiScriptEnv(vm, { | ||||
| 			storageKey: 'pages:' + opts.page.id | ||||
| 		}), ...{ | ||||
| 			'MkPages:updated': values.FN_NATIVE(([callback]) => { | ||||
| 				this.pageVarUpdatedCallback = callback; | ||||
| 			}) | ||||
| 		}}, { | ||||
| 			in: (q) => { | ||||
| 				return new Promise(ok => { | ||||
| 					vm.$root.dialog({ | ||||
| 						title: q, | ||||
| 						input: {} | ||||
| 					}).then(({ canceled, result: a }) => { | ||||
| 						ok(a); | ||||
| 					}); | ||||
| 				}); | ||||
| 			}, | ||||
| 			out: (value) => { | ||||
| 				console.log(value); | ||||
| 			}, | ||||
| 			log: (type, params) => { | ||||
| 			}, | ||||
| 			maxStep: 16384 | ||||
| 		}); | ||||
| 
 | ||||
| 		const date = new Date(); | ||||
| 
 | ||||
|  | @ -50,6 +78,9 @@ export class ASEvaluator { | |||
| 		const pageVar = this.pageVars.find(v => v.name === name); | ||||
| 		if (pageVar !== undefined) { | ||||
| 			pageVar.value = value; | ||||
| 			if (this.pageVarUpdatedCallback) { | ||||
| 				this.aiscript.execFn(this.pageVarUpdatedCallback, [values.STR(name), utils.jsToVal(value)]); | ||||
| 			} | ||||
| 		} else { | ||||
| 			throw new AoiScriptError(`No such page var '${name}'`); | ||||
| 		} | ||||
|  | @ -110,6 +141,10 @@ export class ASEvaluator { | |||
| 			return scope.getState(block.value); | ||||
| 		} | ||||
| 
 | ||||
| 		if (block.type === 'aiScriptVar') { | ||||
| 			return utils.valToJs(this.aiscript.scope.get(block.value)); | ||||
| 		} | ||||
| 
 | ||||
| 		if (isFnBlock(block)) { // ユーザー関数定義
 | ||||
| 			return { | ||||
| 				slots: block.value.slots.map(x => x.name), | ||||
|  |  | |||
|  | @ -95,6 +95,7 @@ export const literalDefs: Record<string, { out: any; category: string; icon: any | |||
| 	textList:      { out: 'stringArray', category: 'value', icon: faList, }, | ||||
| 	number:        { out: 'number',      category: 'value', icon: faSortNumericUp, }, | ||||
| 	ref:           { out: null,          category: 'value', icon: faMagic, }, | ||||
| 	aiScriptVar:   { out: null,          category: 'value', icon: faMagic, }, | ||||
| 	fn:            { out: 'function',    category: 'value', icon: faSquareRootAlt, }, | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import { utils, values } from '@syuilo/aiscript'; | ||||
| 
 | ||||
| export function createAiScriptEnv(vm, opts) { | ||||
| 	let apiRequests = 0; | ||||
| 	return { | ||||
| 		USER_ID: values.STR(vm.$store.state.i.id), | ||||
| 		USER_USERNAME: values.STR(vm.$store.state.i.username), | ||||
|  | @ -21,6 +22,8 @@ export function createAiScriptEnv(vm, opts) { | |||
| 			return confirm.canceled ? values.FALSE : values.TRUE | ||||
| 		}), | ||||
| 		'Mk:api': values.FN_NATIVE(async ([ep, param, token]) => { | ||||
| 			apiRequests++; | ||||
| 			if (apiRequests > 16) return values.NULL; | ||||
| 			const res = await vm.$root.api(ep.value, utils.valToJs(param), token || null); | ||||
| 			return utils.jsToVal(res); | ||||
| 		}), | ||||
|  |  | |||
|  | @ -85,6 +85,12 @@ export class Page { | |||
| 	}) | ||||
| 	public variables: Record<string, any>[]; | ||||
| 
 | ||||
| 	@Column('varchar', { | ||||
| 		length: 16384, | ||||
| 		default: '' | ||||
| 	}) | ||||
| 	public script: string; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * public ... 公開 | ||||
| 	 * followers ... フォロワーのみ | ||||
|  |  | |||
|  | @ -74,6 +74,7 @@ export class PageRepository extends Repository<Page> { | |||
| 			hideTitleWhenPinned: page.hideTitleWhenPinned, | ||||
| 			alignCenter: page.alignCenter, | ||||
| 			font: page.font, | ||||
| 			script: page.script, | ||||
| 			eyeCatchingImageId: page.eyeCatchingImageId, | ||||
| 			eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null, | ||||
| 			attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)), | ||||
|  |  | |||
|  | @ -44,6 +44,10 @@ export const meta = { | |||
| 			validator: $.arr($.obj()) | ||||
| 		}, | ||||
| 
 | ||||
| 		script: { | ||||
| 			validator: $.str, | ||||
| 		}, | ||||
| 
 | ||||
| 		eyeCatchingImageId: { | ||||
| 			validator: $.optional.nullable.type(ID), | ||||
| 		}, | ||||
|  | @ -115,6 +119,7 @@ export default define(meta, async (ps, user) => { | |||
| 		summary: ps.summary, | ||||
| 		content: ps.content, | ||||
| 		variables: ps.variables, | ||||
| 		script: ps.script, | ||||
| 		eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null, | ||||
| 		userId: user.id, | ||||
| 		visibility: 'public', | ||||
|  |  | |||
|  | @ -51,6 +51,10 @@ export const meta = { | |||
| 			validator: $.arr($.obj()) | ||||
| 		}, | ||||
| 
 | ||||
| 		script: { | ||||
| 			validator: $.str, | ||||
| 		}, | ||||
| 
 | ||||
| 		eyeCatchingImageId: { | ||||
| 			validator: $.optional.nullable.type(ID), | ||||
| 		}, | ||||
|  | @ -132,6 +136,7 @@ export default define(meta, async (ps, user) => { | |||
| 		summary: ps.name === undefined ? page.summary : ps.summary, | ||||
| 		content: ps.content, | ||||
| 		variables: ps.variables, | ||||
| 		script: ps.script, | ||||
| 		alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter, | ||||
| 		hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned, | ||||
| 		font: ps.font === undefined ? page.font : ps.font, | ||||
|  |  | |||
|  | @ -144,10 +144,10 @@ | |||
|   dependencies: | ||||
|     type-detect "4.0.8" | ||||
| 
 | ||||
| "@syuilo/aiscript@0.1.2": | ||||
|   version "0.1.2" | ||||
|   resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.1.2.tgz#65c42793c38707d862b3a64f5edc845789372ade" | ||||
|   integrity sha512-W0G/JuVkD9jARPhKFaaHp+59Iv+2LapQ2zKjM08hoB/6hEzHjis0uRbw07TXyughQb17iU452rp1gJEUkXV3Mg== | ||||
| "@syuilo/aiscript@0.1.4": | ||||
|   version "0.1.4" | ||||
|   resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.1.4.tgz#ff027552f32990ae3e29145ce6efe0a7a516b442" | ||||
|   integrity sha512-SMDlBInsGTL3DOe0U394X7na0N6ryYg0RGQPPtCVhXkJpVDZiaqUe5vDO+DkRyuRlkmBbN82LWToou19j/Uv8g== | ||||
|   dependencies: | ||||
|     autobind-decorator "2.4.0" | ||||
|     chalk "4.0.0" | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue