Merge branch 'develop'
This commit is contained in:
		
						commit
						6d35872af5
					
				
					 29 changed files with 842 additions and 581 deletions
				
			
		
							
								
								
									
										14
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								CHANGELOG.md
									
										
									
									
									
								
							| 
						 | 
					@ -42,6 +42,19 @@ mongodb:
 | 
				
			||||||
8. master ブランチに戻す
 | 
					8. master ブランチに戻す
 | 
				
			||||||
9. enjoy
 | 
					9. enjoy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					11.8.0 (2019/05/01)
 | 
				
			||||||
 | 
					-------------------
 | 
				
			||||||
 | 
					### Improvements
 | 
				
			||||||
 | 
					* MisskeyPagesで関数を作成できるように
 | 
				
			||||||
 | 
					* MisskeyPagesでソースを表示できるように
 | 
				
			||||||
 | 
					* MisskeyPagesにシードを与えるランダム関数を追加
 | 
				
			||||||
 | 
					* MisskeyPagesに複数行テキストをテキストのリストに変換する関数を追加
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixes
 | 
				
			||||||
 | 
					* APIドキュメントが見れなくなっていたのを修正
 | 
				
			||||||
 | 
					* mention (あなた宛て) streaming にミュートが効かない問題を修正
 | 
				
			||||||
 | 
					* デザインの調整
 | 
				
			||||||
 | 
					
 | 
				
			||||||
11.7.0 (2019/04/30)
 | 
					11.7.0 (2019/04/30)
 | 
				
			||||||
-------------------
 | 
					-------------------
 | 
				
			||||||
### Improvements
 | 
					### Improvements
 | 
				
			||||||
| 
						 | 
					@ -50,6 +63,7 @@ mongodb:
 | 
				
			||||||
* MisskeyPagesに 複数行テキスト入力 を追加
 | 
					* MisskeyPagesに 複数行テキスト入力 を追加
 | 
				
			||||||
* MisskeyPagesに 投稿フォーム を追加
 | 
					* MisskeyPagesに 投稿フォーム を追加
 | 
				
			||||||
* MisskeyPagesに 変換系関数 を追加
 | 
					* MisskeyPagesに 変換系関数 を追加
 | 
				
			||||||
 | 
					* MisskeyPagesに 環境変数 URL を追加
 | 
				
			||||||
* MisskeyPagesでボタンやスイッチなどのテキストに変数使えるように
 | 
					* MisskeyPagesでボタンやスイッチなどのテキストに変数使えるように
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Fixes
 | 
					### Fixes
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1848,11 +1848,15 @@ pages:
 | 
				
			||||||
  are-you-sure-delete: "このページを削除しますか?"
 | 
					  are-you-sure-delete: "このページを削除しますか?"
 | 
				
			||||||
  page-deleted: "ページを削除しました"
 | 
					  page-deleted: "ページを削除しました"
 | 
				
			||||||
  edit-this-page: "このページを編集"
 | 
					  edit-this-page: "このページを編集"
 | 
				
			||||||
 | 
					  view-source: "ソースを表示"
 | 
				
			||||||
  view-page: "ページを見る"
 | 
					  view-page: "ページを見る"
 | 
				
			||||||
 | 
					  inspector: "インスペクター"
 | 
				
			||||||
 | 
					  content: "ページブロック"
 | 
				
			||||||
  variables: "変数"
 | 
					  variables: "変数"
 | 
				
			||||||
  variables-info: "変数を使うことで動的なページを作成できます。テキスト内で <b>{ 変数名 }</b> と書くとそこに変数の値を埋め込めます。例えば <b>Hello { thing } world!</b> というテキストで、変数(thing)の値が <b>ai</b> だった場合、テキストは <b>Hello ai world!</b> になります。"
 | 
					  variables-info: "変数を使うことで動的なページを作成できます。テキスト内で <b>{ 変数名 }</b> と書くとそこに変数の値を埋め込めます。例えば <b>Hello { thing } world!</b> というテキストで、変数(thing)の値が <b>ai</b> だった場合、テキストは <b>Hello ai world!</b> になります。"
 | 
				
			||||||
  variables-info2: "変数の評価(値を算出すること)は上から下に行われるので、ある変数の中で自分より下の変数を参照することはできません。例えば上から <b>A、B、C</b> と3つの変数を定義したとき、<b>C</b>の中で<b>A</b>や<b>B</b>を参照することはできますが、<b>A</b>の中で<b>B</b>や<b>C</b>を参照することはできません。"
 | 
					  variables-info2: "変数の評価(値を算出すること)は上から下に行われるので、ある変数の中で自分より下の変数を参照することはできません。例えば上から <b>A、B、C</b> と3つの変数を定義したとき、<b>C</b>の中で<b>A</b>や<b>B</b>を参照することはできますが、<b>A</b>の中で<b>B</b>や<b>C</b>を参照することはできません。"
 | 
				
			||||||
  variables-info3: "ユーザーからの入力を受け取るには、ページに「ユーザー入力」ブロックを設置し、「変数名」に入力を格納したい変数名を設定します(変数は自動で作成されます)。その変数を使ってユーザー入力に応じた動作を行えます。"
 | 
					  variables-info3: "ユーザーからの入力を受け取るには、ページに「ユーザー入力」ブロックを設置し、「変数名」に入力を格納したい変数名を設定します(変数は自動で作成されます)。その変数を使ってユーザー入力に応じた動作を行えます。"
 | 
				
			||||||
 | 
					  variables-info4: "関数を使うと、値の算出処理を再利用可能な形にまとめることができます。関数を作るには、「関数」タイプの変数を作成します。関数にはスロット(引数)を設定することができ、スロットの値は関数内で変数として利用可能です。また、AiScript標準で関数を引数に取る関数(高階関数と呼ばれます)も存在します。関数は予め定義しておくほかに、このような高階関数のスロットに即席でセットすることもできます。"
 | 
				
			||||||
  more-details: "詳しい説明"
 | 
					  more-details: "詳しい説明"
 | 
				
			||||||
  title: "タイトル"
 | 
					  title: "タイトル"
 | 
				
			||||||
  url: "ページURL"
 | 
					  url: "ページURL"
 | 
				
			||||||
| 
						 | 
					@ -1952,6 +1956,10 @@ pages:
 | 
				
			||||||
      strReverse: "テキストを反転"
 | 
					      strReverse: "テキストを反転"
 | 
				
			||||||
      _strReverse:
 | 
					      _strReverse:
 | 
				
			||||||
        arg1: "テキスト"
 | 
					        arg1: "テキスト"
 | 
				
			||||||
 | 
					      join: "テキストを連結"
 | 
				
			||||||
 | 
					      _join:
 | 
				
			||||||
 | 
					        arg1: "リスト"
 | 
				
			||||||
 | 
					        arg2: "区切り"
 | 
				
			||||||
      add: "+ 足す"
 | 
					      add: "+ 足す"
 | 
				
			||||||
      _add:
 | 
					      _add:
 | 
				
			||||||
        arg1: "A"
 | 
					        arg1: "A"
 | 
				
			||||||
| 
						 | 
					@ -2028,20 +2036,39 @@ pages:
 | 
				
			||||||
      dailyRandomPick: "リストからランダムに選択 (ユーザーごとに日替わり)"
 | 
					      dailyRandomPick: "リストからランダムに選択 (ユーザーごとに日替わり)"
 | 
				
			||||||
      _dailyRandomPick:
 | 
					      _dailyRandomPick:
 | 
				
			||||||
        arg1: "リスト"
 | 
					        arg1: "リスト"
 | 
				
			||||||
      number: "数"
 | 
					      seedRandom: "ランダム (シード)"
 | 
				
			||||||
 | 
					      _seedRandom:
 | 
				
			||||||
 | 
					        arg1: "シード"
 | 
				
			||||||
 | 
					        arg2: "確率"
 | 
				
			||||||
 | 
					      seedRannum: "乱数 (シード)"
 | 
				
			||||||
 | 
					      _seedRannum:
 | 
				
			||||||
 | 
					        arg1: "シード"
 | 
				
			||||||
 | 
					        arg2: "最小"
 | 
				
			||||||
 | 
					        arg3: "最大"
 | 
				
			||||||
 | 
					      seedRandomPick: "リストからランダムに選択 (シード)"
 | 
				
			||||||
 | 
					      _seedRandomPick:
 | 
				
			||||||
 | 
					        arg1: "シード"
 | 
				
			||||||
 | 
					        arg2: "リスト"
 | 
				
			||||||
 | 
					      number: "数値"
 | 
				
			||||||
      stringToNumber: "テキストを数値に"
 | 
					      stringToNumber: "テキストを数値に"
 | 
				
			||||||
      _stringToNumber:
 | 
					      _stringToNumber:
 | 
				
			||||||
        arg1: "テキスト"
 | 
					        arg1: "テキスト"
 | 
				
			||||||
      numberToString: "数値をテキストに"
 | 
					      numberToString: "数値をテキストに"
 | 
				
			||||||
      _numberToString:
 | 
					      _numberToString:
 | 
				
			||||||
        arg1: "数値"
 | 
					        arg1: "数値"
 | 
				
			||||||
 | 
					      splitStrByLine: "テキストを行で分割"
 | 
				
			||||||
 | 
					      _splitStrByLine:
 | 
				
			||||||
 | 
					        arg1: "テキスト"
 | 
				
			||||||
      ref: "変数"
 | 
					      ref: "変数"
 | 
				
			||||||
      in: "引数"
 | 
					 | 
				
			||||||
      _in:
 | 
					 | 
				
			||||||
        arg1: "スロット番号"
 | 
					 | 
				
			||||||
      fn: "関数"
 | 
					      fn: "関数"
 | 
				
			||||||
      _fn:
 | 
					      _fn:
 | 
				
			||||||
 | 
					        slots: "スロット"
 | 
				
			||||||
 | 
					        slots-info: "スロットひとつひとつを改行で区切ってください"
 | 
				
			||||||
        arg1: "出力"
 | 
					        arg1: "出力"
 | 
				
			||||||
 | 
					      for: "繰り返し"
 | 
				
			||||||
 | 
					      _for:
 | 
				
			||||||
 | 
					        arg1: "回数"
 | 
				
			||||||
 | 
					        arg2: "処理"
 | 
				
			||||||
    typeError: "スロット{slot}は\"{expect}\"を受け付けますが、\"{actual}\"が入れられています!"
 | 
					    typeError: "スロット{slot}は\"{expect}\"を受け付けますが、\"{actual}\"が入れられています!"
 | 
				
			||||||
    thereIsEmptySlot: "スロット{slot}が空です!"
 | 
					    thereIsEmptySlot: "スロット{slot}が空です!"
 | 
				
			||||||
    types:
 | 
					    types:
 | 
				
			||||||
| 
						 | 
					@ -2053,3 +2080,4 @@ pages:
 | 
				
			||||||
    emptySlot: "空のスロット"
 | 
					    emptySlot: "空のスロット"
 | 
				
			||||||
    enviromentVariables: "環境変数"
 | 
					    enviromentVariables: "環境変数"
 | 
				
			||||||
    pageVariables: "ページ要素"
 | 
					    pageVariables: "ページ要素"
 | 
				
			||||||
 | 
					    argVariables: "入力スロット"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"name": "misskey",
 | 
						"name": "misskey",
 | 
				
			||||||
	"author": "syuilo <i@syuilo.com>",
 | 
						"author": "syuilo <i@syuilo.com>",
 | 
				
			||||||
	"version": "11.7.0",
 | 
						"version": "11.8.0",
 | 
				
			||||||
	"codename": "daybreak",
 | 
						"codename": "daybreak",
 | 
				
			||||||
	"repository": {
 | 
						"repository": {
 | 
				
			||||||
		"type": "git",
 | 
							"type": "git",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,487 +0,0 @@
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * AiScript
 | 
					 | 
				
			||||||
 * evaluator & type checker
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import autobind from 'autobind-decorator';
 | 
					 | 
				
			||||||
import * as seedrandom from 'seedrandom';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
	faSuperscript,
 | 
					 | 
				
			||||||
	faAlignLeft,
 | 
					 | 
				
			||||||
	faShareAlt,
 | 
					 | 
				
			||||||
	faSquareRootAlt,
 | 
					 | 
				
			||||||
	faPlus,
 | 
					 | 
				
			||||||
	faMinus,
 | 
					 | 
				
			||||||
	faTimes,
 | 
					 | 
				
			||||||
	faDivide,
 | 
					 | 
				
			||||||
	faList,
 | 
					 | 
				
			||||||
	faQuoteRight,
 | 
					 | 
				
			||||||
	faEquals,
 | 
					 | 
				
			||||||
	faGreaterThan,
 | 
					 | 
				
			||||||
	faLessThan,
 | 
					 | 
				
			||||||
	faGreaterThanEqual,
 | 
					 | 
				
			||||||
	faLessThanEqual,
 | 
					 | 
				
			||||||
	faExclamation,
 | 
					 | 
				
			||||||
	faNotEqual,
 | 
					 | 
				
			||||||
	faDice,
 | 
					 | 
				
			||||||
	faSortNumericUp,
 | 
					 | 
				
			||||||
	faExchangeAlt,
 | 
					 | 
				
			||||||
} from '@fortawesome/free-solid-svg-icons';
 | 
					 | 
				
			||||||
import { faFlag } from '@fortawesome/free-regular-svg-icons';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { version } from '../../config';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type Block = {
 | 
					 | 
				
			||||||
	id: string;
 | 
					 | 
				
			||||||
	type: string;
 | 
					 | 
				
			||||||
	args: Block[];
 | 
					 | 
				
			||||||
	value: any;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type Variable = Block & {
 | 
					 | 
				
			||||||
	name: string;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Type = 'string' | 'number' | 'boolean' | 'stringArray';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type TypeError = {
 | 
					 | 
				
			||||||
	arg: number;
 | 
					 | 
				
			||||||
	expect: Type;
 | 
					 | 
				
			||||||
	actual: Type;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const funcDefs = {
 | 
					 | 
				
			||||||
	if:              { in: ['boolean', 0, 0],              out: 0,         category: 'flow',       icon: faShareAlt, },
 | 
					 | 
				
			||||||
	not:             { in: ['boolean'],                    out: 'boolean', category: 'logical',    icon: faFlag, },
 | 
					 | 
				
			||||||
	or:              { in: ['boolean', 'boolean'],         out: 'boolean', category: 'logical',    icon: faFlag, },
 | 
					 | 
				
			||||||
	and:             { in: ['boolean', 'boolean'],         out: 'boolean', category: 'logical',    icon: faFlag, },
 | 
					 | 
				
			||||||
	add:             { in: ['number', 'number'],           out: 'number',  category: 'operation',  icon: faPlus, },
 | 
					 | 
				
			||||||
	subtract:        { in: ['number', 'number'],           out: 'number',  category: 'operation',  icon: faMinus, },
 | 
					 | 
				
			||||||
	multiply:        { in: ['number', 'number'],           out: 'number',  category: 'operation',  icon: faTimes, },
 | 
					 | 
				
			||||||
	divide:          { in: ['number', 'number'],           out: 'number',  category: 'operation',  icon: faDivide, },
 | 
					 | 
				
			||||||
	eq:              { in: [0, 0],                         out: 'boolean', category: 'comparison', icon: faEquals, },
 | 
					 | 
				
			||||||
	notEq:           { in: [0, 0],                         out: 'boolean', category: 'comparison', icon: faNotEqual, },
 | 
					 | 
				
			||||||
	gt:              { in: ['number', 'number'],           out: 'boolean', category: 'comparison', icon: faGreaterThan, },
 | 
					 | 
				
			||||||
	lt:              { in: ['number', 'number'],           out: 'boolean', category: 'comparison', icon: faLessThan, },
 | 
					 | 
				
			||||||
	gtEq:            { in: ['number', 'number'],           out: 'boolean', category: 'comparison', icon: faGreaterThanEqual, },
 | 
					 | 
				
			||||||
	ltEq:            { in: ['number', 'number'],           out: 'boolean', category: 'comparison', icon: faLessThanEqual, },
 | 
					 | 
				
			||||||
	strLen:          { in: ['string'],                     out: 'number',  category: 'text',       icon: faQuoteRight, },
 | 
					 | 
				
			||||||
	strPick:         { in: ['string', 'number'],           out: 'string',  category: 'text',       icon: faQuoteRight, },
 | 
					 | 
				
			||||||
	strReplace:      { in: ['string', 'string', 'string'], out: 'string',  category: 'text',       icon: faQuoteRight, },
 | 
					 | 
				
			||||||
	strReverse:      { in: ['string'],                     out: 'string',  category: 'text',       icon: faQuoteRight, },
 | 
					 | 
				
			||||||
	stringToNumber:  { in: ['string'],                     out: 'number',  category: 'convert',    icon: faExchangeAlt, },
 | 
					 | 
				
			||||||
	numberToString:  { in: ['number'],                     out: 'string',  category: 'convert',    icon: faExchangeAlt, },
 | 
					 | 
				
			||||||
	rannum:          { in: ['number', 'number'],           out: 'number',  category: 'random',     icon: faDice, },
 | 
					 | 
				
			||||||
	dailyRannum:     { in: ['number', 'number'],           out: 'number',  category: 'random',     icon: faDice, },
 | 
					 | 
				
			||||||
	random:          { in: ['number'],                     out: 'boolean', category: 'random',     icon: faDice, },
 | 
					 | 
				
			||||||
	dailyRandom:     { in: ['number'],                     out: 'boolean', category: 'random',     icon: faDice, },
 | 
					 | 
				
			||||||
	randomPick:      { in: [0],                            out: 0,         category: 'random',     icon: faDice, },
 | 
					 | 
				
			||||||
	dailyRandomPick: { in: [0],                            out: 0,         category: 'random',     icon: faDice, },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const blockDefs = [
 | 
					 | 
				
			||||||
	{ type: 'text',          out: 'string',      category: 'value', icon: faQuoteRight, },
 | 
					 | 
				
			||||||
	{ type: 'multiLineText', out: 'string',      category: 'value', icon: faAlignLeft, },
 | 
					 | 
				
			||||||
	{ type: 'textList',      out: 'stringArray', category: 'value', icon: faList, },
 | 
					 | 
				
			||||||
	{ type: 'number',        out: 'number',      category: 'value', icon: faSortNumericUp, },
 | 
					 | 
				
			||||||
	{ type: 'ref',           out: null,          category: 'value', icon: faSuperscript, },
 | 
					 | 
				
			||||||
	{ type: 'in',            out: null,          category: 'value', icon: faSuperscript, },
 | 
					 | 
				
			||||||
	{ type: 'fn',            out: 'function',    category: 'value', icon: faSuperscript, },
 | 
					 | 
				
			||||||
	...Object.entries(funcDefs).map(([k, v]) => ({
 | 
					 | 
				
			||||||
		type: k, out: v.out || null, category: v.category, icon: v.icon
 | 
					 | 
				
			||||||
	}))
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type PageVar = { name: string; value: any; type: Type; };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const envVarsDef = {
 | 
					 | 
				
			||||||
	AI: 'string',
 | 
					 | 
				
			||||||
	URL: 'string',
 | 
					 | 
				
			||||||
	VERSION: 'string',
 | 
					 | 
				
			||||||
	LOGIN: 'boolean',
 | 
					 | 
				
			||||||
	NAME: 'string',
 | 
					 | 
				
			||||||
	USERNAME: 'string',
 | 
					 | 
				
			||||||
	USERID: 'string',
 | 
					 | 
				
			||||||
	NOTES_COUNT: 'number',
 | 
					 | 
				
			||||||
	FOLLOWERS_COUNT: 'number',
 | 
					 | 
				
			||||||
	FOLLOWING_COUNT: 'number',
 | 
					 | 
				
			||||||
	IS_CAT: 'boolean',
 | 
					 | 
				
			||||||
	MY_NOTES_COUNT: 'number',
 | 
					 | 
				
			||||||
	MY_FOLLOWERS_COUNT: 'number',
 | 
					 | 
				
			||||||
	MY_FOLLOWING_COUNT: 'number',
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class AiScript {
 | 
					 | 
				
			||||||
	private variables: Variable[];
 | 
					 | 
				
			||||||
	private pageVars: PageVar[];
 | 
					 | 
				
			||||||
	private envVars: Record<keyof typeof envVarsDef, any>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static envVarsDef = envVarsDef;
 | 
					 | 
				
			||||||
	public static blockDefs = blockDefs;
 | 
					 | 
				
			||||||
	public static funcDefs = funcDefs;
 | 
					 | 
				
			||||||
	private opts: {
 | 
					 | 
				
			||||||
		randomSeed?: string; user?: any; visitor?: any; page?: any; url?: string;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	constructor(variables: Variable[] = [], pageVars: PageVar[] = [], opts: AiScript['opts'] = {}) {
 | 
					 | 
				
			||||||
		this.variables = variables;
 | 
					 | 
				
			||||||
		this.pageVars = pageVars;
 | 
					 | 
				
			||||||
		this.opts = opts;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.envVars = {
 | 
					 | 
				
			||||||
			AI: 'kawaii',
 | 
					 | 
				
			||||||
			VERSION: version,
 | 
					 | 
				
			||||||
			URL: opts.page ? `${opts.url}/@${opts.page.user.username}/pages/${opts.page.name}` : '',
 | 
					 | 
				
			||||||
			LOGIN: opts.visitor != null,
 | 
					 | 
				
			||||||
			NAME: opts.visitor ? opts.visitor.name : '',
 | 
					 | 
				
			||||||
			USERNAME: opts.visitor ? opts.visitor.username : '',
 | 
					 | 
				
			||||||
			USERID: opts.visitor ? opts.visitor.id : '',
 | 
					 | 
				
			||||||
			NOTES_COUNT: opts.visitor ? opts.visitor.notesCount : 0,
 | 
					 | 
				
			||||||
			FOLLOWERS_COUNT: opts.visitor ? opts.visitor.followersCount : 0,
 | 
					 | 
				
			||||||
			FOLLOWING_COUNT: opts.visitor ? opts.visitor.followingCount : 0,
 | 
					 | 
				
			||||||
			IS_CAT: opts.visitor ? opts.visitor.isCat : false,
 | 
					 | 
				
			||||||
			MY_NOTES_COUNT: opts.user ? opts.user.notesCount : 0,
 | 
					 | 
				
			||||||
			MY_FOLLOWERS_COUNT: opts.user ? opts.user.followersCount : 0,
 | 
					 | 
				
			||||||
			MY_FOLLOWING_COUNT: opts.user ? opts.user.followingCount : 0,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public injectVars(vars: Variable[]) {
 | 
					 | 
				
			||||||
		this.variables = vars;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public injectPageVars(pageVars: PageVar[]) {
 | 
					 | 
				
			||||||
		this.pageVars = pageVars;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public updatePageVar(name: string, value: any) {
 | 
					 | 
				
			||||||
		this.pageVars.find(v => v.name === name).value = value;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public updateRandomSeed(seed: string) {
 | 
					 | 
				
			||||||
		this.opts.randomSeed = seed;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public static isLiteralBlock(v: Block) {
 | 
					 | 
				
			||||||
		if (v.type === null) return true;
 | 
					 | 
				
			||||||
		if (v.type === 'text') return true;
 | 
					 | 
				
			||||||
		if (v.type === 'multiLineText') return true;
 | 
					 | 
				
			||||||
		if (v.type === 'textList') return true;
 | 
					 | 
				
			||||||
		if (v.type === 'number') return true;
 | 
					 | 
				
			||||||
		if (v.type === 'ref') return true;
 | 
					 | 
				
			||||||
		if (v.type === 'fn') return true;
 | 
					 | 
				
			||||||
		if (v.type === 'in') return true;
 | 
					 | 
				
			||||||
		return false;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public typeCheck(v: Block): TypeError | null {
 | 
					 | 
				
			||||||
		if (AiScript.isLiteralBlock(v)) return null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const def = AiScript.funcDefs[v.type];
 | 
					 | 
				
			||||||
		if (def == null) {
 | 
					 | 
				
			||||||
			throw new Error('Unknown type: ' + v.type);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const generic: Type[] = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for (let i = 0; i < def.in.length; i++) {
 | 
					 | 
				
			||||||
			const arg = def.in[i];
 | 
					 | 
				
			||||||
			const type = this.typeInference(v.args[i]);
 | 
					 | 
				
			||||||
			if (type === null) continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (typeof arg === 'number') {
 | 
					 | 
				
			||||||
				if (generic[arg] === undefined) {
 | 
					 | 
				
			||||||
					generic[arg] = type;
 | 
					 | 
				
			||||||
				} else if (type !== generic[arg]) {
 | 
					 | 
				
			||||||
					return {
 | 
					 | 
				
			||||||
						arg: i,
 | 
					 | 
				
			||||||
						expect: generic[arg],
 | 
					 | 
				
			||||||
						actual: type
 | 
					 | 
				
			||||||
					};
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else if (type !== arg) {
 | 
					 | 
				
			||||||
				return {
 | 
					 | 
				
			||||||
					arg: i,
 | 
					 | 
				
			||||||
					expect: arg,
 | 
					 | 
				
			||||||
					actual: type
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return null;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public getExpectedType(v: Block, slot: number): Type | null {
 | 
					 | 
				
			||||||
		const def = AiScript.funcDefs[v.type];
 | 
					 | 
				
			||||||
		if (def == null) {
 | 
					 | 
				
			||||||
			throw new Error('Unknown type: ' + v.type);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const generic: Type[] = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for (let i = 0; i < def.in.length; i++) {
 | 
					 | 
				
			||||||
			const arg = def.in[i];
 | 
					 | 
				
			||||||
			const type = this.typeInference(v.args[i]);
 | 
					 | 
				
			||||||
			if (type === null) continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (typeof arg === 'number') {
 | 
					 | 
				
			||||||
				if (generic[arg] === undefined) {
 | 
					 | 
				
			||||||
					generic[arg] = type;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (typeof def.in[slot] === 'number') {
 | 
					 | 
				
			||||||
			return generic[def.in[slot]] || null;
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return def.in[slot];
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public typeInference(v: Block): Type | null {
 | 
					 | 
				
			||||||
		if (v.type === null) return null;
 | 
					 | 
				
			||||||
		if (v.type === 'text') return 'string';
 | 
					 | 
				
			||||||
		if (v.type === 'multiLineText') return 'string';
 | 
					 | 
				
			||||||
		if (v.type === 'textList') return 'stringArray';
 | 
					 | 
				
			||||||
		if (v.type === 'number') return 'number';
 | 
					 | 
				
			||||||
		if (v.type === 'ref') {
 | 
					 | 
				
			||||||
			const variable = this.variables.find(va => va.name === v.value);
 | 
					 | 
				
			||||||
			if (variable) {
 | 
					 | 
				
			||||||
				return this.typeInference(variable);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const pageVar = this.pageVars.find(va => va.name === v.value);
 | 
					 | 
				
			||||||
			if (pageVar) {
 | 
					 | 
				
			||||||
				return pageVar.type;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const envVar = AiScript.envVarsDef[v.value];
 | 
					 | 
				
			||||||
			if (envVar) {
 | 
					 | 
				
			||||||
				return envVar;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			return null;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if (v.type === 'fn') return null; // todo
 | 
					 | 
				
			||||||
		if (v.type === 'in') return null; // todo
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const generic: Type[] = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const def = AiScript.funcDefs[v.type];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for (let i = 0; i < def.in.length; i++) {
 | 
					 | 
				
			||||||
			const arg = def.in[i];
 | 
					 | 
				
			||||||
			if (typeof arg === 'number') {
 | 
					 | 
				
			||||||
				const type = this.typeInference(v.args[i]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if (generic[arg] === undefined) {
 | 
					 | 
				
			||||||
					generic[arg] = type;
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					if (type !== generic[arg]) {
 | 
					 | 
				
			||||||
						generic[arg] = null;
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (typeof def.out === 'number') {
 | 
					 | 
				
			||||||
			return generic[def.out];
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return def.out;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public getVarsByType(type: Type | null): Variable[] {
 | 
					 | 
				
			||||||
		if (type == null) return this.variables;
 | 
					 | 
				
			||||||
		return this.variables.filter(x => (this.typeInference(x) === null) || (this.typeInference(x) === type));
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public getVarByName(name: string): Variable {
 | 
					 | 
				
			||||||
		return this.variables.find(x => x.name === name);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public getEnvVarsByType(type: Type | null): string[] {
 | 
					 | 
				
			||||||
		if (type == null) return Object.keys(AiScript.envVarsDef);
 | 
					 | 
				
			||||||
		return Object.entries(AiScript.envVarsDef).filter(([k, v]) => type === v).map(([k, v]) => k);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public getPageVarsByType(type: Type | null): string[] {
 | 
					 | 
				
			||||||
		if (type == null) return this.pageVars.map(v => v.name);
 | 
					 | 
				
			||||||
		return this.pageVars.filter(v => type === v.type).map(v => v.name);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	private interpolate(str: string, values: { name: string, value: any }[]) {
 | 
					 | 
				
			||||||
		return str.replace(/\{(.+?)\}/g, match => {
 | 
					 | 
				
			||||||
			const v = this.getVariableValue(match.slice(1, -1).trim(), values);
 | 
					 | 
				
			||||||
			return v == null ? 'NULL' : v.toString();
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public evaluateVars() {
 | 
					 | 
				
			||||||
		const values: { name: string, value: any }[] = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for (const v of this.variables) {
 | 
					 | 
				
			||||||
			values.push({
 | 
					 | 
				
			||||||
				name: v.name,
 | 
					 | 
				
			||||||
				value: this.evaluate(v, values)
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for (const v of this.pageVars) {
 | 
					 | 
				
			||||||
			values.push({
 | 
					 | 
				
			||||||
				name: v.name,
 | 
					 | 
				
			||||||
				value: v.value
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for (const [k, v] of Object.entries(this.envVars)) {
 | 
					 | 
				
			||||||
			values.push({
 | 
					 | 
				
			||||||
				name: k,
 | 
					 | 
				
			||||||
				value: v
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return values;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	private evaluate(block: Block, values: { name: string, value: any }[], slotArg: Record<string, any> = {}): any {
 | 
					 | 
				
			||||||
		if (block.type === null) {
 | 
					 | 
				
			||||||
			return null;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (block.type === 'number') {
 | 
					 | 
				
			||||||
			return parseInt(block.value, 10);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (block.type === 'text' || block.type === 'multiLineText') {
 | 
					 | 
				
			||||||
			return this.interpolate(block.value, values);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (block.type === 'textList') {
 | 
					 | 
				
			||||||
			return block.value.trim().split('\n');
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (block.type === 'ref') {
 | 
					 | 
				
			||||||
			return this.getVariableValue(block.value, values);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (block.type === 'in') {
 | 
					 | 
				
			||||||
			return slotArg[block.value];
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (block.type === 'fn') { // ユーザー関数定義
 | 
					 | 
				
			||||||
			return {
 | 
					 | 
				
			||||||
				slots: block.value.slots,
 | 
					 | 
				
			||||||
				exec: slotArg => this.evaluate(block.value.expression, values, slotArg)
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (block.type.startsWith('fn:')) { // ユーザー関数呼び出し
 | 
					 | 
				
			||||||
			const fnName = block.type.split(':')[1];
 | 
					 | 
				
			||||||
			const fn = this.getVariableValue(fnName, values);
 | 
					 | 
				
			||||||
			for (let i = 0; i < fn.slots.length; i++) {
 | 
					 | 
				
			||||||
				const name = fn.slots[i];
 | 
					 | 
				
			||||||
				slotArg[name] = this.evaluate(block.args[i], values);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			return fn.exec(slotArg);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (block.args === undefined) return null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const date = new Date();
 | 
					 | 
				
			||||||
		const day = `${this.opts.visitor ? this.opts.visitor.id : ''} ${date.getFullYear()}/${date.getMonth()}/${date.getDate()}`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const funcs: { [p in keyof typeof funcDefs]: any } = {
 | 
					 | 
				
			||||||
			not: (a) => !a,
 | 
					 | 
				
			||||||
			eq: (a, b) => a === b,
 | 
					 | 
				
			||||||
			notEq: (a, b) => a !== b,
 | 
					 | 
				
			||||||
			gt: (a, b) => a > b,
 | 
					 | 
				
			||||||
			lt: (a, b) => a < b,
 | 
					 | 
				
			||||||
			gtEq: (a, b) => a >= b,
 | 
					 | 
				
			||||||
			ltEq: (a, b) => a <= b,
 | 
					 | 
				
			||||||
			or: (a, b) => a || b,
 | 
					 | 
				
			||||||
			and: (a, b) => a && b,
 | 
					 | 
				
			||||||
			if: (bool, a, b) => bool ? a : b,
 | 
					 | 
				
			||||||
			add: (a, b) => a + b,
 | 
					 | 
				
			||||||
			subtract: (a, b) => a - b,
 | 
					 | 
				
			||||||
			multiply: (a, b) => a * b,
 | 
					 | 
				
			||||||
			divide: (a, b) => a / b,
 | 
					 | 
				
			||||||
			strLen: (a) => a.length,
 | 
					 | 
				
			||||||
			strPick: (a, b) => a[b - 1],
 | 
					 | 
				
			||||||
			strReplace: (a, b, c) => a.split(b).join(c),
 | 
					 | 
				
			||||||
			strReverse: (a) => a.split('').reverse().join(''),
 | 
					 | 
				
			||||||
			stringToNumber: (a) => parseInt(a),
 | 
					 | 
				
			||||||
			numberToString: (a) => a.toString(),
 | 
					 | 
				
			||||||
			random: (probability) => Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * 100) < probability,
 | 
					 | 
				
			||||||
			rannum: (min, max) => min + Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * (max - min + 1)),
 | 
					 | 
				
			||||||
			randomPick: (list) => list[Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * list.length)],
 | 
					 | 
				
			||||||
			dailyRandom: (probability) => Math.floor(seedrandom(`${day}:${block.id}`)() * 100) < probability,
 | 
					 | 
				
			||||||
			dailyRannum: (min, max) => min + Math.floor(seedrandom(`${day}:${block.id}`)() * (max - min + 1)),
 | 
					 | 
				
			||||||
			dailyRandomPick: (list) => list[Math.floor(seedrandom(`${day}:${block.id}`)() * list.length)],
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const fnName = block.type;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const fn = funcs[fnName];
 | 
					 | 
				
			||||||
		if (fn == null) {
 | 
					 | 
				
			||||||
			console.error('Unknown function: ' + fnName);
 | 
					 | 
				
			||||||
			throw new Error('Unknown function: ' + fnName);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const args = block.args.map(x => this.evaluate(x, values, slotArg));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return fn(...args);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	private getVariableValue(name: string, values: { name: string, value: any }[]): any {
 | 
					 | 
				
			||||||
		const v = values.find(v => v.name === name);
 | 
					 | 
				
			||||||
		if (v) {
 | 
					 | 
				
			||||||
			return v.value;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const pageVar = this.pageVars.find(v => v.name === name);
 | 
					 | 
				
			||||||
		if (pageVar) {
 | 
					 | 
				
			||||||
			return pageVar.value;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (AiScript.envVarsDef[name]) {
 | 
					 | 
				
			||||||
			return this.envVars[name];
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		throw new Error(`Script: No such variable '${name}'`);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@autobind
 | 
					 | 
				
			||||||
	public isUsedName(name: string) {
 | 
					 | 
				
			||||||
		if (this.variables.some(v => v.name === name)) {
 | 
					 | 
				
			||||||
			return true;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (this.pageVars.some(v => v.name === name)) {
 | 
					 | 
				
			||||||
			return true;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (AiScript.envVarsDef[name]) {
 | 
					 | 
				
			||||||
			return true;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return false;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -6,25 +6,25 @@ export function collectPageVars(content) {
 | 
				
			||||||
				pageVars.push({
 | 
									pageVars.push({
 | 
				
			||||||
					name: x.name,
 | 
										name: x.name,
 | 
				
			||||||
					type: 'string',
 | 
										type: 'string',
 | 
				
			||||||
					value: x.default
 | 
										value: x.default || ''
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			} else if (x.type === 'textareaInput') {
 | 
								} else if (x.type === 'textareaInput') {
 | 
				
			||||||
				pageVars.push({
 | 
									pageVars.push({
 | 
				
			||||||
					name: x.name,
 | 
										name: x.name,
 | 
				
			||||||
					type: 'string',
 | 
										type: 'string',
 | 
				
			||||||
					value: x.default
 | 
										value: x.default || ''
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			} else if (x.type === 'numberInput') {
 | 
								} else if (x.type === 'numberInput') {
 | 
				
			||||||
				pageVars.push({
 | 
									pageVars.push({
 | 
				
			||||||
					name: x.name,
 | 
										name: x.name,
 | 
				
			||||||
					type: 'number',
 | 
										type: 'number',
 | 
				
			||||||
					value: x.default
 | 
										value: x.default || 0
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			} else if (x.type === 'switch') {
 | 
								} else if (x.type === 'switch') {
 | 
				
			||||||
				pageVars.push({
 | 
									pageVars.push({
 | 
				
			||||||
					name: x.name,
 | 
										name: x.name,
 | 
				
			||||||
					type: 'boolean',
 | 
										type: 'boolean',
 | 
				
			||||||
					value: x.default
 | 
										value: x.default || false
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			} else if (x.children) {
 | 
								} else if (x.children) {
 | 
				
			||||||
				collect(x.children);
 | 
									collect(x.children);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
	<template #header><fa :icon="faBolt"/> {{ $t('blocks.numberInput') }}</template>
 | 
						<template #header><fa :icon="faBolt"/> {{ $t('blocks.numberInput') }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<section style="padding: 0 16px 0 16px;">
 | 
						<section style="padding: 0 16px 0 16px;">
 | 
				
			||||||
		<ui-input v-model="value.name"><template #prefix><fa :icon="faSquareRootAlt"/></template><span>{{ $t('blocks._numberInput.name') }}</span></ui-input>
 | 
							<ui-input v-model="value.name"><template #prefix><fa :icon="faMagic"/></template><span>{{ $t('blocks._numberInput.name') }}</span></ui-input>
 | 
				
			||||||
		<ui-input v-model="value.text"><span>{{ $t('blocks._numberInput.text') }}</span></ui-input>
 | 
							<ui-input v-model="value.text"><span>{{ $t('blocks._numberInput.text') }}</span></ui-input>
 | 
				
			||||||
		<ui-input v-model="value.default" type="number"><span>{{ $t('blocks._numberInput.default') }}</span></ui-input>
 | 
							<ui-input v-model="value.default" type="number"><span>{{ $t('blocks._numberInput.default') }}</span></ui-input>
 | 
				
			||||||
	</section>
 | 
						</section>
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { faBolt, faSquareRootAlt } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import i18n from '../../../../../i18n';
 | 
					import i18n from '../../../../../i18n';
 | 
				
			||||||
import XContainer from '../page-editor.container.vue';
 | 
					import XContainer from '../page-editor.container.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			faBolt, faSquareRootAlt
 | 
								faBolt, faMagic
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
	<template #header><fa :icon="faBolt"/> {{ $t('blocks.switch') }}</template>
 | 
						<template #header><fa :icon="faBolt"/> {{ $t('blocks.switch') }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<section class="kjuadyyj">
 | 
						<section class="kjuadyyj">
 | 
				
			||||||
		<ui-input v-model="value.name"><template #prefix><fa :icon="faSquareRootAlt"/></template><span>{{ $t('blocks._switch.name') }}</span></ui-input>
 | 
							<ui-input v-model="value.name"><template #prefix><fa :icon="faMagic"/></template><span>{{ $t('blocks._switch.name') }}</span></ui-input>
 | 
				
			||||||
		<ui-input v-model="value.text"><span>{{ $t('blocks._switch.text') }}</span></ui-input>
 | 
							<ui-input v-model="value.text"><span>{{ $t('blocks._switch.text') }}</span></ui-input>
 | 
				
			||||||
		<ui-switch v-model="value.default"><span>{{ $t('blocks._switch.default') }}</span></ui-switch>
 | 
							<ui-switch v-model="value.default"><span>{{ $t('blocks._switch.default') }}</span></ui-switch>
 | 
				
			||||||
	</section>
 | 
						</section>
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { faBolt, faSquareRootAlt } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import i18n from '../../../../../i18n';
 | 
					import i18n from '../../../../../i18n';
 | 
				
			||||||
import XContainer from '../page-editor.container.vue';
 | 
					import XContainer from '../page-editor.container.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			faBolt, faSquareRootAlt
 | 
								faBolt, faMagic
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
	<template #header><fa :icon="faBolt"/> {{ $t('blocks.textInput') }}</template>
 | 
						<template #header><fa :icon="faBolt"/> {{ $t('blocks.textInput') }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<section style="padding: 0 16px 0 16px;">
 | 
						<section style="padding: 0 16px 0 16px;">
 | 
				
			||||||
		<ui-input v-model="value.name"><template #prefix><fa :icon="faSquareRootAlt"/></template><span>{{ $t('blocks._textInput.name') }}</span></ui-input>
 | 
							<ui-input v-model="value.name"><template #prefix><fa :icon="faMagic"/></template><span>{{ $t('blocks._textInput.name') }}</span></ui-input>
 | 
				
			||||||
		<ui-input v-model="value.text"><span>{{ $t('blocks._textInput.text') }}</span></ui-input>
 | 
							<ui-input v-model="value.text"><span>{{ $t('blocks._textInput.text') }}</span></ui-input>
 | 
				
			||||||
		<ui-input v-model="value.default" type="text"><span>{{ $t('blocks._textInput.default') }}</span></ui-input>
 | 
							<ui-input v-model="value.default" type="text"><span>{{ $t('blocks._textInput.default') }}</span></ui-input>
 | 
				
			||||||
	</section>
 | 
						</section>
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { faBolt, faSquareRootAlt } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import i18n from '../../../../../i18n';
 | 
					import i18n from '../../../../../i18n';
 | 
				
			||||||
import XContainer from '../page-editor.container.vue';
 | 
					import XContainer from '../page-editor.container.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			faBolt, faSquareRootAlt
 | 
								faBolt, faMagic
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
	<template #header><fa :icon="faBolt"/> {{ $t('blocks.textareaInput') }}</template>
 | 
						<template #header><fa :icon="faBolt"/> {{ $t('blocks.textareaInput') }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<section style="padding: 0 16px 16px 16px;">
 | 
						<section style="padding: 0 16px 16px 16px;">
 | 
				
			||||||
		<ui-input v-model="value.name"><template #prefix><fa :icon="faSquareRootAlt"/></template><span>{{ $t('blocks._textareaInput.name') }}</span></ui-input>
 | 
							<ui-input v-model="value.name"><template #prefix><fa :icon="faMagic"/></template><span>{{ $t('blocks._textareaInput.name') }}</span></ui-input>
 | 
				
			||||||
		<ui-input v-model="value.text"><span>{{ $t('blocks._textareaInput.text') }}</span></ui-input>
 | 
							<ui-input v-model="value.text"><span>{{ $t('blocks._textareaInput.text') }}</span></ui-input>
 | 
				
			||||||
		<ui-textarea v-model="value.default"><span>{{ $t('blocks._textareaInput.default') }}</span></ui-textarea>
 | 
							<ui-textarea v-model="value.default"><span>{{ $t('blocks._textareaInput.default') }}</span></ui-textarea>
 | 
				
			||||||
	</section>
 | 
						</section>
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import { faBolt, faSquareRootAlt } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faBolt, faMagic } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import i18n from '../../../../../i18n';
 | 
					import i18n from '../../../../../i18n';
 | 
				
			||||||
import XContainer from '../page-editor.container.vue';
 | 
					import XContainer from '../page-editor.container.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			faBolt, faSquareRootAlt
 | 
								faBolt, faMagic
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,9 @@
 | 
				
			||||||
	<section v-else-if="value.type === 'ref'" class="hpdwcrvs">
 | 
						<section v-else-if="value.type === 'ref'" class="hpdwcrvs">
 | 
				
			||||||
		<select v-model="value.value">
 | 
							<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 aiScript.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option>
 | 
				
			||||||
 | 
								<optgroup :label="$t('script.argVariables')">
 | 
				
			||||||
 | 
									<option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option>
 | 
				
			||||||
 | 
								</optgroup>
 | 
				
			||||||
			<optgroup :label="$t('script.pageVariables')">
 | 
								<optgroup :label="$t('script.pageVariables')">
 | 
				
			||||||
				<option v-for="v in aiScript.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
 | 
									<option v-for="v in aiScript.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
 | 
				
			||||||
			</optgroup>
 | 
								</optgroup>
 | 
				
			||||||
| 
						 | 
					@ -33,17 +36,15 @@
 | 
				
			||||||
			</optgroup>
 | 
								</optgroup>
 | 
				
			||||||
		</select>
 | 
							</select>
 | 
				
			||||||
	</section>
 | 
						</section>
 | 
				
			||||||
	<section v-else-if="value.type === 'in'" class="hpdwcrvs">
 | 
						<section v-else-if="value.type === 'fn'" class="" style="padding:0 16px 16px 16px;">
 | 
				
			||||||
		<select v-model="value.value">
 | 
							<ui-textarea v-model="slots">
 | 
				
			||||||
			<option v-for="v in fnSlots" :value="v">{{ v }}</option>
 | 
								<span>{{ $t('script.blocks._fn.slots') }}</span>
 | 
				
			||||||
		</select>
 | 
								<template #desc>{{ $t('script.blocks._fn.slots-info') }}</template>
 | 
				
			||||||
	</section>
 | 
							</ui-textarea>
 | 
				
			||||||
	<section v-else-if="value.type === 'fn'" class="" style="padding:16px;">
 | 
					 | 
				
			||||||
		<ui-textarea v-model="slots"></ui-textarea>
 | 
					 | 
				
			||||||
		<x-v v-if="value.value.expression" v-model="value.value.expression" :title="$t(`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(`script.blocks._fn.arg1`)" :get-expected-type="() => null" :ai-script="aiScript" :fn-slots="value.value.slots" :name="name"/>
 | 
				
			||||||
	</section>
 | 
						</section>
 | 
				
			||||||
	<section v-else-if="value.type.startsWith('fn:')" class="" style="padding:16px;">
 | 
						<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]" :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="aiScript.getVarByName(value.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :ai-script="aiScript" :name="name" :key="i"/>
 | 
				
			||||||
	</section>
 | 
						</section>
 | 
				
			||||||
	<section v-else class="" style="padding:16px;">
 | 
						<section v-else class="" style="padding:16px;">
 | 
				
			||||||
		<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="$t(`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(`script.blocks._${value.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :ai-script="aiScript" :name="name" :fn-slots="fnSlots" :key="i"/>
 | 
				
			||||||
| 
						 | 
					@ -55,8 +56,8 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../../i18n';
 | 
					import i18n from '../../../../i18n';
 | 
				
			||||||
import XContainer from './page-editor.container.vue';
 | 
					import XContainer from './page-editor.container.vue';
 | 
				
			||||||
import { faSuperscript, faPencilAlt, faSquareRootAlt } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faPencilAlt, faPlug } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { AiScript } from '../../../scripts/aiscript';
 | 
					import { isLiteralBlock, funcDefs, blockDefs } from '../../../../../../misc/aiscript/index';
 | 
				
			||||||
import * as uuid from 'uuid';
 | 
					import * as uuid from 'uuid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
| 
						 | 
					@ -96,29 +97,32 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			AiScript,
 | 
					 | 
				
			||||||
			error: null,
 | 
								error: null,
 | 
				
			||||||
			warn: null,
 | 
								warn: null,
 | 
				
			||||||
			slots: '',
 | 
								slots: '',
 | 
				
			||||||
			faSuperscript, faPencilAlt, faSquareRootAlt
 | 
								faPencilAlt
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		icon(): any {
 | 
							icon(): any {
 | 
				
			||||||
			if (this.value.type === null) return null;
 | 
								if (this.value.type === null) return null;
 | 
				
			||||||
			if (this.value.type.startsWith('fn:')) return null;
 | 
								if (this.value.type.startsWith('fn:')) return faPlug;
 | 
				
			||||||
			return AiScript.blockDefs.find(x => x.type === this.value.type).icon;
 | 
								return blockDefs.find(x => x.type === this.value.type).icon;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		typeText(): any {
 | 
							typeText(): any {
 | 
				
			||||||
			if (this.value.type === null) return null;
 | 
								if (this.value.type === null) return null;
 | 
				
			||||||
 | 
								if (this.value.type.startsWith('fn:')) return this.value.type.split(':')[1];
 | 
				
			||||||
			return this.$t(`script.blocks.${this.value.type}`);
 | 
								return this.$t(`script.blocks.${this.value.type}`);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
		slots() {
 | 
							slots() {
 | 
				
			||||||
			this.value.value.slots = this.slots.split('\n');
 | 
								this.value.value.slots = this.slots.split('\n').map(x => ({
 | 
				
			||||||
 | 
									name: x,
 | 
				
			||||||
 | 
									type: null
 | 
				
			||||||
 | 
								}));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -129,7 +133,7 @@ export default Vue.extend({
 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
		if (this.value.value == null) Vue.set(this.value, 'value', null);
 | 
							if (this.value.value == null) Vue.set(this.value, 'value', null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (this.value.value && this.value.value.slots) this.slots = this.value.value.slots.join('\n');
 | 
							if (this.value.value && this.value.value.slots) this.slots = this.value.value.slots.map(x => x.name).join('\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.$watch('value.type', (t) => {
 | 
							this.$watch('value.type', (t) => {
 | 
				
			||||||
			this.warn = null;
 | 
								this.warn = null;
 | 
				
			||||||
| 
						 | 
					@ -155,17 +159,17 @@ export default Vue.extend({
 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (AiScript.isLiteralBlock(this.value)) return;
 | 
								if (isLiteralBlock(this.value)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const empties = [];
 | 
								const empties = [];
 | 
				
			||||||
			for (let i = 0; i < AiScript.funcDefs[this.value.type].in.length; i++) {
 | 
								for (let i = 0; i < funcDefs[this.value.type].in.length; i++) {
 | 
				
			||||||
				const id = uuid.v4();
 | 
									const id = uuid.v4();
 | 
				
			||||||
				empties.push({ id, type: null });
 | 
									empties.push({ id, type: null });
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			Vue.set(this.value, 'args', empties);
 | 
								Vue.set(this.value, 'args', empties);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for (let i = 0; i < AiScript.funcDefs[this.value.type].in.length; i++) {
 | 
								for (let i = 0; i < funcDefs[this.value.type].in.length; i++) {
 | 
				
			||||||
				const inType = AiScript.funcDefs[this.value.type].in[i];
 | 
									const inType = funcDefs[this.value.type].in[i];
 | 
				
			||||||
				if (typeof inType !== 'number') {
 | 
									if (typeof inType !== 'number') {
 | 
				
			||||||
					if (inType === 'number') this.value.args[i].type = 'number';
 | 
										if (inType === 'number') this.value.args[i].type = 'number';
 | 
				
			||||||
					if (inType === 'string') this.value.args[i].type = 'text';
 | 
										if (inType === 'string') this.value.args[i].type = 'text';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,10 +36,10 @@
 | 
				
			||||||
				</ui-select>
 | 
									</ui-select>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				<div class="eyeCatch">
 | 
									<div class="eyeCatch">
 | 
				
			||||||
					<ui-button v-if="eyeCatchingImageId == null" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('set-eye-catchig-image') }}</ui-button>
 | 
										<ui-button v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('set-eye-catchig-image') }}</ui-button>
 | 
				
			||||||
					<div v-else-if="eyeCatchingImage">
 | 
										<div v-else-if="eyeCatchingImage">
 | 
				
			||||||
						<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name"/>
 | 
											<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name"/>
 | 
				
			||||||
						<ui-button @click="removeEyeCatchingImage()"><fa :icon="faTrashAlt"/> {{ $t('remove-eye-catchig-image') }}</ui-button>
 | 
											<ui-button @click="removeEyeCatchingImage()" v-if="!readonly"><fa :icon="faTrashAlt"/> {{ $t('remove-eye-catchig-image') }}</ui-button>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</template>
 | 
								</template>
 | 
				
			||||||
| 
						 | 
					@ -53,7 +53,7 @@
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ui-container :body-togglable="true">
 | 
						<ui-container :body-togglable="true">
 | 
				
			||||||
		<template #header><fa :icon="faSquareRootAlt"/> {{ $t('variables') }}</template>
 | 
							<template #header><fa :icon="faMagic"/> {{ $t('variables') }}</template>
 | 
				
			||||||
		<div class="qmuvgica">
 | 
							<div class="qmuvgica">
 | 
				
			||||||
			<div class="variables" v-show="variables.length > 0">
 | 
								<div class="variables" v-show="variables.length > 0">
 | 
				
			||||||
				<template v-for="variable in variables">
 | 
									<template v-for="variable in variables">
 | 
				
			||||||
| 
						 | 
					@ -77,21 +77,31 @@
 | 
				
			||||||
			<template v-if="moreDetails">
 | 
								<template v-if="moreDetails">
 | 
				
			||||||
				<ui-info><span v-html="$t('variables-info2')"></span></ui-info>
 | 
									<ui-info><span v-html="$t('variables-info2')"></span></ui-info>
 | 
				
			||||||
				<ui-info><span v-html="$t('variables-info3')"></span></ui-info>
 | 
									<ui-info><span v-html="$t('variables-info3')"></span></ui-info>
 | 
				
			||||||
 | 
									<ui-info><span v-html="$t('variables-info4')"></span></ui-info>
 | 
				
			||||||
			</template>
 | 
								</template>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</ui-container>
 | 
						</ui-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<ui-container :body-togglable="true" :expanded="false">
 | 
				
			||||||
 | 
							<template #header><fa :icon="faCode"/> {{ $t('inspector') }}</template>
 | 
				
			||||||
 | 
							<div style="padding:0 32px 32px 32px;">
 | 
				
			||||||
 | 
								<ui-textarea :value="JSON.stringify(content, null, 2)" readonly tall>{{ $t('content') }}</ui-textarea>
 | 
				
			||||||
 | 
								<ui-textarea :value="JSON.stringify(variables, null, 2)" readonly tall>{{ $t('variables') }}</ui-textarea>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</ui-container>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../../i18n';
 | 
					import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faICursor, faPlus, faSquareRootAlt, faCog, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
 | 
					 | 
				
			||||||
import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
 | 
					import i18n from '../../../../i18n';
 | 
				
			||||||
import XVariable from './page-editor.script-block.vue';
 | 
					import XVariable from './page-editor.script-block.vue';
 | 
				
			||||||
import XBlock from './page-editor.block.vue';
 | 
					import XBlock from './page-editor.block.vue';
 | 
				
			||||||
import * as uuid from 'uuid';
 | 
					import * as uuid from 'uuid';
 | 
				
			||||||
import { AiScript } from '../../../scripts/aiscript';
 | 
					import { blockDefs } from '../../../../../../misc/aiscript/index';
 | 
				
			||||||
 | 
					import { ASTypeChecker } from '../../../../../../misc/aiscript/type-checker';
 | 
				
			||||||
import { url } from '../../../../config';
 | 
					import { url } from '../../../../config';
 | 
				
			||||||
import { collectPageVars } from '../../../scripts/collect-page-vars';
 | 
					import { collectPageVars } from '../../../scripts/collect-page-vars';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,7 +114,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		page: {
 | 
							page: {
 | 
				
			||||||
			type: String,
 | 
								type: Object,
 | 
				
			||||||
			required: false
 | 
								required: false
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		readonly: {
 | 
							readonly: {
 | 
				
			||||||
| 
						 | 
					@ -132,7 +142,7 @@ export default Vue.extend({
 | 
				
			||||||
			showOptions: false,
 | 
								showOptions: false,
 | 
				
			||||||
			moreDetails: false,
 | 
								moreDetails: false,
 | 
				
			||||||
			url,
 | 
								url,
 | 
				
			||||||
			faPlus, faICursor, faSave, faStickyNote, faSquareRootAlt, faCog, faTrashAlt, faExternalLinkSquareAlt
 | 
								faPlus, faICursor, faSave, faStickyNote, faMagic, faCog, faTrashAlt, faExternalLinkSquareAlt, faCode
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -149,32 +159,28 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
		this.aiScript = new AiScript();
 | 
							this.aiScript = new ASTypeChecker();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.$watch('variables', () => {
 | 
							this.$watch('variables', () => {
 | 
				
			||||||
			this.aiScript.injectVars(this.variables);
 | 
								this.aiScript.variables = this.variables;
 | 
				
			||||||
		}, { deep: true });
 | 
							}, { deep: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.$watch('content', () => {
 | 
							this.$watch('content', () => {
 | 
				
			||||||
			this.aiScript.injectPageVars(collectPageVars(this.content));
 | 
								this.aiScript.pageVars = collectPageVars(this.content);
 | 
				
			||||||
		}, { deep: true });
 | 
							}, { deep: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (this.page) {
 | 
							if (this.page) {
 | 
				
			||||||
			this.$root.api('pages/show', {
 | 
								this.author = this.page.user;
 | 
				
			||||||
				pageId: this.page,
 | 
								this.pageId = this.page.id;
 | 
				
			||||||
			}).then(page => {
 | 
								this.title = this.page.title;
 | 
				
			||||||
				this.author = page.user;
 | 
								this.name = this.page.name;
 | 
				
			||||||
				this.pageId = page.id;
 | 
								this.currentName = this.page.name;
 | 
				
			||||||
				this.title = page.title;
 | 
								this.summary = this.page.summary;
 | 
				
			||||||
				this.name = page.name;
 | 
								this.font = this.page.font;
 | 
				
			||||||
				this.currentName = page.name;
 | 
								this.alignCenter = this.page.alignCenter;
 | 
				
			||||||
				this.summary = page.summary;
 | 
								this.content = this.page.content;
 | 
				
			||||||
				this.font = page.font;
 | 
								this.variables = this.page.variables;
 | 
				
			||||||
				this.alignCenter = page.alignCenter;
 | 
								this.eyeCatchingImageId = this.page.eyeCatchingImageId;
 | 
				
			||||||
				this.content = page.content;
 | 
					 | 
				
			||||||
				this.variables = page.variables;
 | 
					 | 
				
			||||||
				this.eyeCatchingImageId = page.eyeCatchingImageId;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			const id = uuid.v4();
 | 
								const id = uuid.v4();
 | 
				
			||||||
			this.content = [{
 | 
								this.content = [{
 | 
				
			||||||
| 
						 | 
					@ -351,7 +357,7 @@ export default Vue.extend({
 | 
				
			||||||
		getScriptBlockList(type: string = null) {
 | 
							getScriptBlockList(type: string = null) {
 | 
				
			||||||
			const list = [];
 | 
								const list = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const blocks = AiScript.blockDefs.filter(block => type === null || block.out === null || block.out === type);
 | 
								const blocks = blockDefs.filter(block => type === null || block.out === null || block.out === type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for (const block of blocks) {
 | 
								for (const block of blocks) {
 | 
				
			||||||
				const category = list.find(x => x.category === block.category);
 | 
									const category = list.find(x => x.category === block.category);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,13 +20,13 @@ export default Vue.extend({
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		click() {
 | 
							click() {
 | 
				
			||||||
			if (this.value.action === 'dialog') {
 | 
								if (this.value.action === 'dialog') {
 | 
				
			||||||
				this.script.reEval();
 | 
									this.script.eval();
 | 
				
			||||||
				this.$root.dialog({
 | 
									this.$root.dialog({
 | 
				
			||||||
					text: this.script.interpolate(this.value.content)
 | 
										text: this.script.interpolate(this.value.content)
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
			} else if (this.value.action === 'resetRandom') {
 | 
								} else if (this.value.action === 'resetRandom') {
 | 
				
			||||||
				this.script.aiScript.updateRandomSeed(Math.random());
 | 
									this.script.aiScript.updateRandomSeed(Math.random());
 | 
				
			||||||
				this.script.reEval();
 | 
									this.script.eval();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div v-show="script.vars.find(x => x.name === value.var).value">
 | 
					<div v-show="script.vars[value.var]">
 | 
				
			||||||
	<x-block v-for="child in value.children" :value="child" :page="page" :script="script" :key="child.id" :h="h"/>
 | 
						<x-block v-for="child in value.children" :value="child" :page="page" :script="script" :key="child.id" :h="h"/>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ export default Vue.extend({
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
		v() {
 | 
							v() {
 | 
				
			||||||
			this.script.aiScript.updatePageVar(this.value.name, this.v);
 | 
								this.script.aiScript.updatePageVar(this.value.name, this.v);
 | 
				
			||||||
			this.script.reEval();
 | 
								this.script.eval();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ export default Vue.extend({
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
		v() {
 | 
							v() {
 | 
				
			||||||
			this.script.aiScript.updatePageVar(this.value.name, this.v);
 | 
								this.script.aiScript.updatePageVar(this.value.name, this.v);
 | 
				
			||||||
			this.script.reEval();
 | 
								this.script.eval();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ export default Vue.extend({
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
		v() {
 | 
							v() {
 | 
				
			||||||
			this.script.aiScript.updatePageVar(this.value.name, this.v);
 | 
								this.script.aiScript.updatePageVar(this.value.name, this.v);
 | 
				
			||||||
			this.script.reEval();
 | 
								this.script.eval();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ export default Vue.extend({
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
		v() {
 | 
							v() {
 | 
				
			||||||
			this.script.aiScript.updatePageVar(this.value.name, this.v);
 | 
								this.script.aiScript.updatePageVar(this.value.name, this.v);
 | 
				
			||||||
			this.script.reEval();
 | 
								this.script.eval();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@
 | 
				
			||||||
	<footer>
 | 
						<footer>
 | 
				
			||||||
		<small>@{{ page.user.username }}</small>
 | 
							<small>@{{ page.user.username }}</small>
 | 
				
			||||||
		<router-link v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId" :to="`/i/pages/edit/${page.id}`">{{ $t('edit-this-page') }}</router-link>
 | 
							<router-link v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId" :to="`/i/pages/edit/${page.id}`">{{ $t('edit-this-page') }}</router-link>
 | 
				
			||||||
 | 
							<router-link :to="`./${page.name}/view-source`">{{ $t('view-source') }}</router-link>
 | 
				
			||||||
	</footer>
 | 
						</footer>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -18,30 +19,36 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../../i18n';
 | 
					import i18n from '../../../../i18n';
 | 
				
			||||||
import { faICursor, faPlus, faSquareRootAlt } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faICursor, faPlus } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faSave, faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faSave, faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
import XBlock from './page.block.vue';
 | 
					import XBlock from './page.block.vue';
 | 
				
			||||||
import { AiScript } from '../../../scripts/aiscript';
 | 
					import { ASEvaluator } from '../../../../../../misc/aiscript/evaluator';
 | 
				
			||||||
import { collectPageVars } from '../../../scripts/collect-page-vars';
 | 
					import { collectPageVars } from '../../../scripts/collect-page-vars';
 | 
				
			||||||
import { url } from '../../../../config';
 | 
					import { url } from '../../../../config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Script {
 | 
					class Script {
 | 
				
			||||||
	public aiScript: AiScript;
 | 
						public aiScript: ASEvaluator;
 | 
				
			||||||
	public vars: any;
 | 
						private onError: any;
 | 
				
			||||||
 | 
						public vars: Record<string, any>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(aiScript) {
 | 
						constructor(aiScript, onError) {
 | 
				
			||||||
		this.aiScript = aiScript;
 | 
							this.aiScript = aiScript;
 | 
				
			||||||
		this.vars = this.aiScript.evaluateVars();
 | 
							this.onError = onError;
 | 
				
			||||||
 | 
							this.eval();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public reEval() {
 | 
						public eval() {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
			this.vars = this.aiScript.evaluateVars();
 | 
								this.vars = this.aiScript.evaluateVars();
 | 
				
			||||||
 | 
							} catch (e) {
 | 
				
			||||||
 | 
								this.onError(e);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public interpolate(str: string) {
 | 
						public interpolate(str: string) {
 | 
				
			||||||
		if (str == null) return null;
 | 
							if (str == null) return null;
 | 
				
			||||||
		return str.replace(/\{(.+?)\}/g, match => {
 | 
							return str.replace(/\{(.+?)\}/g, match => {
 | 
				
			||||||
			const v = this.vars.find(x => x.name === match.slice(1, -1).trim()).value;
 | 
								const v = this.vars[match.slice(1, -1).trim()];
 | 
				
			||||||
			return v == null ? 'NULL' : v.toString();
 | 
								return v == null ? 'NULL' : v.toString();
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -69,7 +76,7 @@ export default Vue.extend({
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			page: null,
 | 
								page: null,
 | 
				
			||||||
			script: null,
 | 
								script: null,
 | 
				
			||||||
			faPlus, faICursor, faSave, faStickyNote, faSquareRootAlt
 | 
								faPlus, faICursor, faSave, faStickyNote
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,13 +87,15 @@ export default Vue.extend({
 | 
				
			||||||
		}).then(page => {
 | 
							}).then(page => {
 | 
				
			||||||
			this.page = page;
 | 
								this.page = page;
 | 
				
			||||||
			const pageVars = this.getPageVars();
 | 
								const pageVars = this.getPageVars();
 | 
				
			||||||
			this.script = new Script(new AiScript(this.page.variables, pageVars, {
 | 
								this.script = new Script(new ASEvaluator(this.page.variables, pageVars, {
 | 
				
			||||||
				randomSeed: Math.random(),
 | 
									randomSeed: Math.random(),
 | 
				
			||||||
				user: page.user,
 | 
									user: page.user,
 | 
				
			||||||
				visitor: this.$store.state.i,
 | 
									visitor: this.$store.state.i,
 | 
				
			||||||
				page: page,
 | 
									page: page,
 | 
				
			||||||
				url: url
 | 
									url: url
 | 
				
			||||||
			}));
 | 
								}), e => {
 | 
				
			||||||
 | 
									console.dir(e);
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,4 +155,10 @@ export default Vue.extend({
 | 
				
			||||||
			display block
 | 
								display block
 | 
				
			||||||
			opacity 0.5
 | 
								opacity 0.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> a
 | 
				
			||||||
 | 
								font-size 14px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> a + a
 | 
				
			||||||
 | 
								margin-left 8px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -159,8 +159,9 @@ init(async (launch, os) => {
 | 
				
			||||||
					{ path: '/i/pages', component: () => import('./views/home/pages.vue').then(m => m.default) },
 | 
										{ path: '/i/pages', component: () => import('./views/home/pages.vue').then(m => m.default) },
 | 
				
			||||||
				]},
 | 
									]},
 | 
				
			||||||
			{ path: '/@:user/pages/:page', props: true, component: () => import('./views/pages/page.vue').then(m => m.default) },
 | 
								{ path: '/@:user/pages/:page', props: true, component: () => import('./views/pages/page.vue').then(m => m.default) },
 | 
				
			||||||
 | 
								{ path: '/@:user/pages/:pageName/view-source', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/i/pages/new', component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
								{ path: '/i/pages/new', component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/i/pages/edit/:page', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
								{ path: '/i/pages/edit/:pageId', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/i/messaging/:user', component: MkMessagingRoom },
 | 
								{ path: '/i/messaging/:user', component: MkMessagingRoom },
 | 
				
			||||||
			{ path: '/i/drive', component: MkDrive },
 | 
								{ path: '/i/drive', component: MkDrive },
 | 
				
			||||||
			{ path: '/i/drive/folder/:folder', component: MkDrive },
 | 
								{ path: '/i/drive/folder/:folder', component: MkDrive },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<mk-ui>
 | 
					<mk-ui>
 | 
				
			||||||
	<main>
 | 
						<main>
 | 
				
			||||||
		<x-page-editor :page="page"/>
 | 
							<x-page-editor v-if="page !== undefined" :page="page" :readonly="readonly"/>
 | 
				
			||||||
	</main>
 | 
						</main>
 | 
				
			||||||
</mk-ui>
 | 
					</mk-ui>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -15,9 +15,44 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		page: {
 | 
							pageId: {
 | 
				
			||||||
			type: String,
 | 
								type: String,
 | 
				
			||||||
			required: false
 | 
								required: false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							pageName: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								required: false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							user: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								required: false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								page: undefined,
 | 
				
			||||||
 | 
								readonly: false
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						created() {
 | 
				
			||||||
 | 
							if (this.pageId) {
 | 
				
			||||||
 | 
								this.$root.api('pages/show', {
 | 
				
			||||||
 | 
									pageId: this.pageId,
 | 
				
			||||||
 | 
								}).then(page => {
 | 
				
			||||||
 | 
									this.page = page;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							} else if (this.pageName && this.user) {
 | 
				
			||||||
 | 
								this.$root.api('pages/show', {
 | 
				
			||||||
 | 
									name: this.pageName,
 | 
				
			||||||
 | 
									username: this.user,
 | 
				
			||||||
 | 
								}).then(page => {
 | 
				
			||||||
 | 
									this.readonly = true;
 | 
				
			||||||
 | 
									this.page = page;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								this.page = null;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,7 +146,7 @@ init((launch, os) => {
 | 
				
			||||||
			{ path: '/i/drive/folder/:folder', component: MkDrive },
 | 
								{ path: '/i/drive/folder/:folder', component: MkDrive },
 | 
				
			||||||
			{ path: '/i/drive/file/:file', component: MkDrive },
 | 
								{ path: '/i/drive/file/:file', component: MkDrive },
 | 
				
			||||||
			{ path: '/i/pages/new', component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
								{ path: '/i/pages/new', component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/i/pages/edit/:page', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
								{ path: '/i/pages/edit/:pageId', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/selectdrive', component: MkSelectDrive },
 | 
								{ path: '/selectdrive', component: MkSelectDrive },
 | 
				
			||||||
			{ path: '/search', component: MkSearch },
 | 
								{ path: '/search', component: MkSearch },
 | 
				
			||||||
			{ path: '/tags/:tag', component: MkTag },
 | 
								{ path: '/tags/:tag', component: MkTag },
 | 
				
			||||||
| 
						 | 
					@ -160,6 +160,7 @@ init((launch, os) => {
 | 
				
			||||||
				{ path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) },
 | 
									{ path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) },
 | 
				
			||||||
			]},
 | 
								]},
 | 
				
			||||||
			{ path: '/@:user/pages/:page', props: true, component: () => import('./views/pages/page.vue').then(m => m.default) },
 | 
								{ path: '/@:user/pages/:page', props: true, component: () => import('./views/pages/page.vue').then(m => m.default) },
 | 
				
			||||||
 | 
								{ path: '/@:user/pages/:pageName/view-source', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/notes/:note', component: MkNote },
 | 
								{ path: '/notes/:note', component: MkNote },
 | 
				
			||||||
			{ path: '/authorize-follow', component: MkFollow },
 | 
								{ path: '/authorize-follow', component: MkFollow },
 | 
				
			||||||
			{ path: '*', component: MkNotFound }
 | 
								{ path: '*', component: MkNotFound }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<mk-ui>
 | 
					<mk-ui>
 | 
				
			||||||
	<main>
 | 
						<main>
 | 
				
			||||||
		<x-page-editor :page="page"/>
 | 
							<x-page-editor v-if="page !== undefined" :page="page" :readonly="readonly"/>
 | 
				
			||||||
	</main>
 | 
						</main>
 | 
				
			||||||
</mk-ui>
 | 
					</mk-ui>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -15,9 +15,44 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		page: {
 | 
							pageId: {
 | 
				
			||||||
			type: String,
 | 
								type: String,
 | 
				
			||||||
			required: false
 | 
								required: false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							pageName: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								required: false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							user: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								required: false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								page: undefined,
 | 
				
			||||||
 | 
								readonly: false
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						created() {
 | 
				
			||||||
 | 
							if (this.pageId) {
 | 
				
			||||||
 | 
								this.$root.api('pages/show', {
 | 
				
			||||||
 | 
									pageId: this.pageId,
 | 
				
			||||||
 | 
								}).then(page => {
 | 
				
			||||||
 | 
									this.page = page;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							} else if (this.pageName && this.user) {
 | 
				
			||||||
 | 
								this.$root.api('pages/show', {
 | 
				
			||||||
 | 
									name: this.pageName,
 | 
				
			||||||
 | 
									username: this.user,
 | 
				
			||||||
 | 
								}).then(page => {
 | 
				
			||||||
 | 
									this.readonly = true;
 | 
				
			||||||
 | 
									this.page = page;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								this.page = null;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										240
									
								
								src/misc/aiscript/evaluator.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								src/misc/aiscript/evaluator.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,240 @@
 | 
				
			||||||
 | 
					import autobind from 'autobind-decorator';
 | 
				
			||||||
 | 
					import * as seedrandom from 'seedrandom';
 | 
				
			||||||
 | 
					import { Variable, PageVar, envVarsDef, funcDefs, Block, isFnBlock } from '.';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Fn = {
 | 
				
			||||||
 | 
						slots: string[];
 | 
				
			||||||
 | 
						exec: (args: Record<string, any>) => ReturnType<ASEvaluator['evaluate']>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AiScriptError extends Error {
 | 
				
			||||||
 | 
						public info?: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constructor(message: string, info?: any) {
 | 
				
			||||||
 | 
							super(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.info = info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Maintains proper stack trace for where our error was thrown (only available on V8)
 | 
				
			||||||
 | 
							if (Error.captureStackTrace) {
 | 
				
			||||||
 | 
								Error.captureStackTrace(this, AiScriptError);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Scope {
 | 
				
			||||||
 | 
						private layerdStates: Record<string, any>[];
 | 
				
			||||||
 | 
						public name: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constructor(layerdStates: Scope['layerdStates'], name?: Scope['name']) {
 | 
				
			||||||
 | 
							this.layerdStates = layerdStates;
 | 
				
			||||||
 | 
							this.name = name || 'anonymous';
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public createChildScope(states: Record<string, any>, name?: Scope['name']): Scope {
 | 
				
			||||||
 | 
							const layer = [states, ...this.layerdStates];
 | 
				
			||||||
 | 
							return new Scope(layer, name);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * 指定した名前の変数の値を取得します
 | 
				
			||||||
 | 
						 * @param name 変数名
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public getState(name: string): any {
 | 
				
			||||||
 | 
							for (const later of this.layerdStates) {
 | 
				
			||||||
 | 
								const state = later[name];
 | 
				
			||||||
 | 
								if (state !== undefined) {
 | 
				
			||||||
 | 
									return state;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							throw new AiScriptError(
 | 
				
			||||||
 | 
								`No such variable '${name}' in scope '${this.name}'`, {
 | 
				
			||||||
 | 
									scope: this.layerdStates
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * AiScript evaluator
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class ASEvaluator {
 | 
				
			||||||
 | 
						private variables: Variable[];
 | 
				
			||||||
 | 
						private pageVars: PageVar[];
 | 
				
			||||||
 | 
						private envVars: Record<keyof typeof envVarsDef, any>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private opts: {
 | 
				
			||||||
 | 
							randomSeed: string; user?: any; visitor?: any; page?: any; url?: string; version: string;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constructor(variables: Variable[], pageVars: PageVar[], opts: ASEvaluator['opts']) {
 | 
				
			||||||
 | 
							this.variables = variables;
 | 
				
			||||||
 | 
							this.pageVars = pageVars;
 | 
				
			||||||
 | 
							this.opts = opts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const date = new Date();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.envVars = {
 | 
				
			||||||
 | 
								AI: 'kawaii',
 | 
				
			||||||
 | 
								VERSION: opts.version,
 | 
				
			||||||
 | 
								URL: opts.page ? `${opts.url}/@${opts.page.user.username}/pages/${opts.page.name}` : '',
 | 
				
			||||||
 | 
								LOGIN: opts.visitor != null,
 | 
				
			||||||
 | 
								NAME: opts.visitor ? opts.visitor.name : '',
 | 
				
			||||||
 | 
								USERNAME: opts.visitor ? opts.visitor.username : '',
 | 
				
			||||||
 | 
								USERID: opts.visitor ? opts.visitor.id : '',
 | 
				
			||||||
 | 
								NOTES_COUNT: opts.visitor ? opts.visitor.notesCount : 0,
 | 
				
			||||||
 | 
								FOLLOWERS_COUNT: opts.visitor ? opts.visitor.followersCount : 0,
 | 
				
			||||||
 | 
								FOLLOWING_COUNT: opts.visitor ? opts.visitor.followingCount : 0,
 | 
				
			||||||
 | 
								IS_CAT: opts.visitor ? opts.visitor.isCat : false,
 | 
				
			||||||
 | 
								MY_NOTES_COUNT: opts.user ? opts.user.notesCount : 0,
 | 
				
			||||||
 | 
								MY_FOLLOWERS_COUNT: opts.user ? opts.user.followersCount : 0,
 | 
				
			||||||
 | 
								MY_FOLLOWING_COUNT: opts.user ? opts.user.followingCount : 0,
 | 
				
			||||||
 | 
								SEED: opts.randomSeed ? opts.randomSeed : '',
 | 
				
			||||||
 | 
								YMD: `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public updatePageVar(name: string, value: any) {
 | 
				
			||||||
 | 
							const pageVar = this.pageVars.find(v => v.name === name);
 | 
				
			||||||
 | 
							if (pageVar !== undefined) {
 | 
				
			||||||
 | 
								pageVar.value = value;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								throw new AiScriptError(`No such page var '${name}'`);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public updateRandomSeed(seed: string) {
 | 
				
			||||||
 | 
							this.opts.randomSeed = seed;
 | 
				
			||||||
 | 
							this.envVars.SEED = seed;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						private interpolate(str: string, scope: Scope) {
 | 
				
			||||||
 | 
							return str.replace(/\{(.+?)\}/g, match => {
 | 
				
			||||||
 | 
								const v = scope.getState(match.slice(1, -1).trim());
 | 
				
			||||||
 | 
								return v == null ? 'NULL' : v.toString();
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public evaluateVars(): Record<string, any> {
 | 
				
			||||||
 | 
							const values: Record<string, any> = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (const [k, v] of Object.entries(this.envVars)) {
 | 
				
			||||||
 | 
								values[k] = v;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (const v of this.pageVars) {
 | 
				
			||||||
 | 
								values[v.name] = v.value;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (const v of this.variables) {
 | 
				
			||||||
 | 
								values[v.name] = this.evaluate(v, new Scope([values]));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return values;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						private evaluate(block: Block, scope: Scope): any {
 | 
				
			||||||
 | 
							if (block.type === null) {
 | 
				
			||||||
 | 
								return null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (block.type === 'number') {
 | 
				
			||||||
 | 
								return parseInt(block.value, 10);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (block.type === 'text' || block.type === 'multiLineText') {
 | 
				
			||||||
 | 
								return this.interpolate(block.value || '', scope);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (block.type === 'textList') {
 | 
				
			||||||
 | 
								return block.value.trim().split('\n');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (block.type === 'ref') {
 | 
				
			||||||
 | 
								return scope.getState(block.value);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (isFnBlock(block)) { // ユーザー関数定義
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									slots: block.value.slots.map(x => x.name),
 | 
				
			||||||
 | 
									exec: (slotArg: Record<string, any>) => {
 | 
				
			||||||
 | 
										return this.evaluate(block.value.expression, scope.createChildScope(slotArg, block.id));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} as Fn;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (block.type.startsWith('fn:')) { // ユーザー関数呼び出し
 | 
				
			||||||
 | 
								const fnName = block.type.split(':')[1];
 | 
				
			||||||
 | 
								const fn = scope.getState(fnName);
 | 
				
			||||||
 | 
								const args = {} as Record<string, any>;
 | 
				
			||||||
 | 
								for (let i = 0; i < fn.slots.length; i++) {
 | 
				
			||||||
 | 
									const name = fn.slots[i];
 | 
				
			||||||
 | 
									args[name] = this.evaluate(block.args[i], scope);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return fn.exec(args);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (block.args === undefined) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const date = new Date();
 | 
				
			||||||
 | 
							const day = `${this.opts.visitor ? this.opts.visitor.id : ''} ${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const funcs: { [p in keyof typeof funcDefs]: Function } = {
 | 
				
			||||||
 | 
								not: (a: boolean) => !a,
 | 
				
			||||||
 | 
								or: (a: boolean, b: boolean) => a || b,
 | 
				
			||||||
 | 
								and: (a: boolean, b: boolean) => a && b,
 | 
				
			||||||
 | 
								eq: (a: any, b: any) => a === b,
 | 
				
			||||||
 | 
								notEq: (a: any, b: any) => a !== b,
 | 
				
			||||||
 | 
								gt: (a: number, b: number) => a > b,
 | 
				
			||||||
 | 
								lt: (a: number, b: number) => a < b,
 | 
				
			||||||
 | 
								gtEq: (a: number, b: number) => a >= b,
 | 
				
			||||||
 | 
								ltEq: (a: number, b: number) => a <= b,
 | 
				
			||||||
 | 
								if: (bool: boolean, a: any, b: any) => bool ? a : b,
 | 
				
			||||||
 | 
								for: (times: number, fn: Fn) => {
 | 
				
			||||||
 | 
									const result = [];
 | 
				
			||||||
 | 
									for (let i = 0; i < times; i++) {
 | 
				
			||||||
 | 
										result.push(fn.exec({
 | 
				
			||||||
 | 
											[fn.slots[0]]: i + 1
 | 
				
			||||||
 | 
										}));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return result;
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								add: (a: number, b: number) => a + b,
 | 
				
			||||||
 | 
								subtract: (a: number, b: number) => a - b,
 | 
				
			||||||
 | 
								multiply: (a: number, b: number) => a * b,
 | 
				
			||||||
 | 
								divide: (a: number, b: number) => a / b,
 | 
				
			||||||
 | 
								strLen: (a: string) => a.length,
 | 
				
			||||||
 | 
								strPick: (a: string, b: number) => a[b - 1],
 | 
				
			||||||
 | 
								strReplace: (a: string, b: string, c: string) => a.split(b).join(c),
 | 
				
			||||||
 | 
								strReverse: (a: string) => a.split('').reverse().join(''),
 | 
				
			||||||
 | 
								join: (texts: string[], separator: string) => texts.join(separator || ''),
 | 
				
			||||||
 | 
								stringToNumber: (a: string) => parseInt(a),
 | 
				
			||||||
 | 
								numberToString: (a: number) => a.toString(),
 | 
				
			||||||
 | 
								splitStrByLine: (a: string) => a.split('\n'),
 | 
				
			||||||
 | 
								random: (probability: number) => Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * 100) < probability,
 | 
				
			||||||
 | 
								rannum: (min: number, max: number) => min + Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * (max - min + 1)),
 | 
				
			||||||
 | 
								randomPick: (list: any[]) => list[Math.floor(seedrandom(`${this.opts.randomSeed}:${block.id}`)() * list.length)],
 | 
				
			||||||
 | 
								dailyRandom: (probability: number) => Math.floor(seedrandom(`${day}:${block.id}`)() * 100) < probability,
 | 
				
			||||||
 | 
								dailyRannum: (min: number, max: number) => min + Math.floor(seedrandom(`${day}:${block.id}`)() * (max - min + 1)),
 | 
				
			||||||
 | 
								dailyRandomPick: (list: any[]) => list[Math.floor(seedrandom(`${day}:${block.id}`)() * list.length)],
 | 
				
			||||||
 | 
								seedRandom: (seed: any, probability: number) => Math.floor(seedrandom(seed)() * 100) < probability,
 | 
				
			||||||
 | 
								seedRannum: (seed: any, min: number, max: number) => min + Math.floor(seedrandom(seed)() * (max - min + 1)),
 | 
				
			||||||
 | 
								seedRandomPick: (seed: any, list: any[]) => list[Math.floor(seedrandom(seed)() * list.length)],
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const fnName = block.type;
 | 
				
			||||||
 | 
							const fn = (funcs as any)[fnName];
 | 
				
			||||||
 | 
							if (fn == null) {
 | 
				
			||||||
 | 
								throw new AiScriptError(`No such function '${fnName}'`);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return fn(...block.args.map(x => this.evaluate(x, scope)));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										132
									
								
								src/misc/aiscript/index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/misc/aiscript/index.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,132 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * AiScript
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
						faMagic,
 | 
				
			||||||
 | 
						faSquareRootAlt,
 | 
				
			||||||
 | 
						faAlignLeft,
 | 
				
			||||||
 | 
						faShareAlt,
 | 
				
			||||||
 | 
						faPlus,
 | 
				
			||||||
 | 
						faMinus,
 | 
				
			||||||
 | 
						faTimes,
 | 
				
			||||||
 | 
						faDivide,
 | 
				
			||||||
 | 
						faList,
 | 
				
			||||||
 | 
						faQuoteRight,
 | 
				
			||||||
 | 
						faEquals,
 | 
				
			||||||
 | 
						faGreaterThan,
 | 
				
			||||||
 | 
						faLessThan,
 | 
				
			||||||
 | 
						faGreaterThanEqual,
 | 
				
			||||||
 | 
						faLessThanEqual,
 | 
				
			||||||
 | 
						faNotEqual,
 | 
				
			||||||
 | 
						faDice,
 | 
				
			||||||
 | 
						faSortNumericUp,
 | 
				
			||||||
 | 
						faExchangeAlt,
 | 
				
			||||||
 | 
						faRecycle,
 | 
				
			||||||
 | 
					} from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					import { faFlag } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Block<V = any> = {
 | 
				
			||||||
 | 
						id: string;
 | 
				
			||||||
 | 
						type: string;
 | 
				
			||||||
 | 
						args: Block[];
 | 
				
			||||||
 | 
						value: V;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type FnBlock = Block<{
 | 
				
			||||||
 | 
						slots: {
 | 
				
			||||||
 | 
							name: string;
 | 
				
			||||||
 | 
							type: Type;
 | 
				
			||||||
 | 
						}[];
 | 
				
			||||||
 | 
						expression: Block;
 | 
				
			||||||
 | 
					}>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Variable = Block & {
 | 
				
			||||||
 | 
						name: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = {
 | 
				
			||||||
 | 
						if:              { in: ['boolean', 0, 0],              out: 0,             category: 'flow',       icon: faShareAlt, },
 | 
				
			||||||
 | 
						for:             { in: ['number', 'function'],         out: null,          category: 'flow',       icon: faRecycle, },
 | 
				
			||||||
 | 
						not:             { in: ['boolean'],                    out: 'boolean',     category: 'logical',    icon: faFlag, },
 | 
				
			||||||
 | 
						or:              { in: ['boolean', 'boolean'],         out: 'boolean',     category: 'logical',    icon: faFlag, },
 | 
				
			||||||
 | 
						and:             { in: ['boolean', 'boolean'],         out: 'boolean',     category: 'logical',    icon: faFlag, },
 | 
				
			||||||
 | 
						add:             { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faPlus, },
 | 
				
			||||||
 | 
						subtract:        { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faMinus, },
 | 
				
			||||||
 | 
						multiply:        { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faTimes, },
 | 
				
			||||||
 | 
						divide:          { in: ['number', 'number'],           out: 'number',      category: 'operation',  icon: faDivide, },
 | 
				
			||||||
 | 
						eq:              { in: [0, 0],                         out: 'boolean',     category: 'comparison', icon: faEquals, },
 | 
				
			||||||
 | 
						notEq:           { in: [0, 0],                         out: 'boolean',     category: 'comparison', icon: faNotEqual, },
 | 
				
			||||||
 | 
						gt:              { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faGreaterThan, },
 | 
				
			||||||
 | 
						lt:              { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faLessThan, },
 | 
				
			||||||
 | 
						gtEq:            { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faGreaterThanEqual, },
 | 
				
			||||||
 | 
						ltEq:            { in: ['number', 'number'],           out: 'boolean',     category: 'comparison', icon: faLessThanEqual, },
 | 
				
			||||||
 | 
						strLen:          { in: ['string'],                     out: 'number',      category: 'text',       icon: faQuoteRight, },
 | 
				
			||||||
 | 
						strPick:         { in: ['string', 'number'],           out: 'string',      category: 'text',       icon: faQuoteRight, },
 | 
				
			||||||
 | 
						strReplace:      { in: ['string', 'string', 'string'], out: 'string',      category: 'text',       icon: faQuoteRight, },
 | 
				
			||||||
 | 
						strReverse:      { in: ['string'],                     out: 'string',      category: 'text',       icon: faQuoteRight, },
 | 
				
			||||||
 | 
						join:            { in: ['stringArray', 'string'],      out: 'string',      category: 'text',       icon: faQuoteRight, },
 | 
				
			||||||
 | 
						stringToNumber:  { in: ['string'],                     out: 'number',      category: 'convert',    icon: faExchangeAlt, },
 | 
				
			||||||
 | 
						numberToString:  { in: ['number'],                     out: 'string',      category: 'convert',    icon: faExchangeAlt, },
 | 
				
			||||||
 | 
						splitStrByLine:  { in: ['string'],                     out: 'stringArray', category: 'convert',    icon: faExchangeAlt, },
 | 
				
			||||||
 | 
						rannum:          { in: ['number', 'number'],           out: 'number',      category: 'random',     icon: faDice, },
 | 
				
			||||||
 | 
						dailyRannum:     { in: ['number', 'number'],           out: 'number',      category: 'random',     icon: faDice, },
 | 
				
			||||||
 | 
						seedRannum:      { in: [null, 'number', 'number'],     out: 'number',      category: 'random',     icon: faDice, },
 | 
				
			||||||
 | 
						random:          { in: ['number'],                     out: 'boolean',     category: 'random',     icon: faDice, },
 | 
				
			||||||
 | 
						dailyRandom:     { in: ['number'],                     out: 'boolean',     category: 'random',     icon: faDice, },
 | 
				
			||||||
 | 
						seedRandom:      { in: [null, 'number'],               out: 'boolean',     category: 'random',     icon: faDice, },
 | 
				
			||||||
 | 
						randomPick:      { in: [0],                            out: 0,             category: 'random',     icon: faDice, },
 | 
				
			||||||
 | 
						dailyRandomPick: { in: [0],                            out: 0,             category: 'random',     icon: faDice, },
 | 
				
			||||||
 | 
						seedRandomPick:  { in: [null, 0],                      out: 0,             category: 'random',     icon: faDice, },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = {
 | 
				
			||||||
 | 
						text:          { out: 'string',      category: 'value', icon: faQuoteRight, },
 | 
				
			||||||
 | 
						multiLineText: { out: 'string',      category: 'value', icon: faAlignLeft, },
 | 
				
			||||||
 | 
						textList:      { out: 'stringArray', category: 'value', icon: faList, },
 | 
				
			||||||
 | 
						number:        { out: 'number',      category: 'value', icon: faSortNumericUp, },
 | 
				
			||||||
 | 
						ref:           { out: null,          category: 'value', icon: faMagic, },
 | 
				
			||||||
 | 
						fn:            { out: 'function',    category: 'value', icon: faSquareRootAlt, },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const blockDefs = [
 | 
				
			||||||
 | 
						...Object.entries(literalDefs).map(([k, v]) => ({
 | 
				
			||||||
 | 
							type: k, out: v.out, category: v.category, icon: v.icon
 | 
				
			||||||
 | 
						})),
 | 
				
			||||||
 | 
						...Object.entries(funcDefs).map(([k, v]) => ({
 | 
				
			||||||
 | 
							type: k, out: v.out, category: v.category, icon: v.icon
 | 
				
			||||||
 | 
						}))
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function isFnBlock(block: Block): block is FnBlock {
 | 
				
			||||||
 | 
						return block.type === 'fn';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type PageVar = { name: string; value: any; type: Type; };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const envVarsDef: Record<string, Type> = {
 | 
				
			||||||
 | 
						AI: 'string',
 | 
				
			||||||
 | 
						URL: 'string',
 | 
				
			||||||
 | 
						VERSION: 'string',
 | 
				
			||||||
 | 
						LOGIN: 'boolean',
 | 
				
			||||||
 | 
						NAME: 'string',
 | 
				
			||||||
 | 
						USERNAME: 'string',
 | 
				
			||||||
 | 
						USERID: 'string',
 | 
				
			||||||
 | 
						NOTES_COUNT: 'number',
 | 
				
			||||||
 | 
						FOLLOWERS_COUNT: 'number',
 | 
				
			||||||
 | 
						FOLLOWING_COUNT: 'number',
 | 
				
			||||||
 | 
						IS_CAT: 'boolean',
 | 
				
			||||||
 | 
						MY_NOTES_COUNT: 'number',
 | 
				
			||||||
 | 
						MY_FOLLOWERS_COUNT: 'number',
 | 
				
			||||||
 | 
						MY_FOLLOWING_COUNT: 'number',
 | 
				
			||||||
 | 
						SEED: null,
 | 
				
			||||||
 | 
						YMD: 'string',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function isLiteralBlock(v: Block) {
 | 
				
			||||||
 | 
						if (v.type === null) return true;
 | 
				
			||||||
 | 
						if (literalDefs[v.type]) return true;
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										186
									
								
								src/misc/aiscript/type-checker.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/misc/aiscript/type-checker.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,186 @@
 | 
				
			||||||
 | 
					import autobind from 'autobind-decorator';
 | 
				
			||||||
 | 
					import { Type, Block, funcDefs, envVarsDef, Variable, PageVar, isLiteralBlock } from '.';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TypeError = {
 | 
				
			||||||
 | 
						arg: number;
 | 
				
			||||||
 | 
						expect: Type;
 | 
				
			||||||
 | 
						actual: Type;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * AiScript type checker
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class ASTypeChecker {
 | 
				
			||||||
 | 
						public variables: Variable[];
 | 
				
			||||||
 | 
						public pageVars: PageVar[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constructor(variables: ASTypeChecker['variables'] = [], pageVars: ASTypeChecker['pageVars'] = []) {
 | 
				
			||||||
 | 
							this.variables = variables;
 | 
				
			||||||
 | 
							this.pageVars = pageVars;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public typeCheck(v: Block): TypeError | null {
 | 
				
			||||||
 | 
							if (isLiteralBlock(v)) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const def = funcDefs[v.type];
 | 
				
			||||||
 | 
							if (def == null) {
 | 
				
			||||||
 | 
								throw new Error('Unknown type: ' + v.type);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const generic: Type[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (let i = 0; i < def.in.length; i++) {
 | 
				
			||||||
 | 
								const arg = def.in[i];
 | 
				
			||||||
 | 
								const type = this.infer(v.args[i]);
 | 
				
			||||||
 | 
								if (type === null) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (typeof arg === 'number') {
 | 
				
			||||||
 | 
									if (generic[arg] === undefined) {
 | 
				
			||||||
 | 
										generic[arg] = type;
 | 
				
			||||||
 | 
									} else if (type !== generic[arg]) {
 | 
				
			||||||
 | 
										return {
 | 
				
			||||||
 | 
											arg: i,
 | 
				
			||||||
 | 
											expect: generic[arg],
 | 
				
			||||||
 | 
											actual: type
 | 
				
			||||||
 | 
										};
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else if (type !== arg) {
 | 
				
			||||||
 | 
									return {
 | 
				
			||||||
 | 
										arg: i,
 | 
				
			||||||
 | 
										expect: arg,
 | 
				
			||||||
 | 
										actual: type
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public getExpectedType(v: Block, slot: number): Type {
 | 
				
			||||||
 | 
							const def = funcDefs[v.type];
 | 
				
			||||||
 | 
							if (def == null) {
 | 
				
			||||||
 | 
								throw new Error('Unknown type: ' + v.type);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const generic: Type[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (let i = 0; i < def.in.length; i++) {
 | 
				
			||||||
 | 
								const arg = def.in[i];
 | 
				
			||||||
 | 
								const type = this.infer(v.args[i]);
 | 
				
			||||||
 | 
								if (type === null) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (typeof arg === 'number') {
 | 
				
			||||||
 | 
									if (generic[arg] === undefined) {
 | 
				
			||||||
 | 
										generic[arg] = type;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (typeof def.in[slot] === 'number') {
 | 
				
			||||||
 | 
								return generic[def.in[slot]] || null;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return def.in[slot];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public infer(v: Block): Type {
 | 
				
			||||||
 | 
							if (v.type === null) return null;
 | 
				
			||||||
 | 
							if (v.type === 'text') return 'string';
 | 
				
			||||||
 | 
							if (v.type === 'multiLineText') return 'string';
 | 
				
			||||||
 | 
							if (v.type === 'textList') return 'stringArray';
 | 
				
			||||||
 | 
							if (v.type === 'number') return 'number';
 | 
				
			||||||
 | 
							if (v.type === 'ref') {
 | 
				
			||||||
 | 
								const variable = this.variables.find(va => va.name === v.value);
 | 
				
			||||||
 | 
								if (variable) {
 | 
				
			||||||
 | 
									return this.infer(variable);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const pageVar = this.pageVars.find(va => va.name === v.value);
 | 
				
			||||||
 | 
								if (pageVar) {
 | 
				
			||||||
 | 
									return pageVar.type;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const envVar = envVarsDef[v.value];
 | 
				
			||||||
 | 
								if (envVar !== undefined) {
 | 
				
			||||||
 | 
									return envVar;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (v.type === 'fn') return null; // todo
 | 
				
			||||||
 | 
							if (v.type.startsWith('fn:')) return null; // todo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const generic: Type[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const def = funcDefs[v.type];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (let i = 0; i < def.in.length; i++) {
 | 
				
			||||||
 | 
								const arg = def.in[i];
 | 
				
			||||||
 | 
								if (typeof arg === 'number') {
 | 
				
			||||||
 | 
									const type = this.infer(v.args[i]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (generic[arg] === undefined) {
 | 
				
			||||||
 | 
										generic[arg] = type;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										if (type !== generic[arg]) {
 | 
				
			||||||
 | 
											generic[arg] = null;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (typeof def.out === 'number') {
 | 
				
			||||||
 | 
								return generic[def.out];
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return def.out;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public getVarByName(name: string): Variable {
 | 
				
			||||||
 | 
							const v = this.variables.find(x => x.name === name);
 | 
				
			||||||
 | 
							if (v !== undefined) {
 | 
				
			||||||
 | 
								return v;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								throw new Error(`No such variable '${name}'`);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public getVarsByType(type: Type): Variable[] {
 | 
				
			||||||
 | 
							if (type == null) return this.variables;
 | 
				
			||||||
 | 
							return this.variables.filter(x => (this.infer(x) === null) || (this.infer(x) === type));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public getEnvVarsByType(type: Type): string[] {
 | 
				
			||||||
 | 
							if (type == null) return Object.keys(envVarsDef);
 | 
				
			||||||
 | 
							return Object.entries(envVarsDef).filter(([k, v]) => v === null || type === v).map(([k, v]) => k);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public getPageVarsByType(type: Type): string[] {
 | 
				
			||||||
 | 
							if (type == null) return this.pageVars.map(v => v.name);
 | 
				
			||||||
 | 
							return this.pageVars.filter(v => type === v.type).map(v => v.name);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@autobind
 | 
				
			||||||
 | 
						public isUsedName(name: string) {
 | 
				
			||||||
 | 
							if (this.variables.some(v => v.name === name)) {
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (this.pageVars.some(v => v.name === name)) {
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (envVarsDef[name]) {
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -84,5 +84,51 @@ export const packedPageSchema = {
 | 
				
			||||||
	type: types.object,
 | 
						type: types.object,
 | 
				
			||||||
	optional: bool.false, nullable: bool.false,
 | 
						optional: bool.false, nullable: bool.false,
 | 
				
			||||||
	properties: {
 | 
						properties: {
 | 
				
			||||||
 | 
							id: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								format: 'id',
 | 
				
			||||||
 | 
								example: 'xxxxxxxxxx',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							createdAt: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								format: 'date-time',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							updatedAt: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								format: 'date-time',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							title: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							name: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							summary: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							content: {
 | 
				
			||||||
 | 
								type: types.array,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							variables: {
 | 
				
			||||||
 | 
								type: types.array,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							userId: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								format: 'id',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							user: {
 | 
				
			||||||
 | 
								type: types.object,
 | 
				
			||||||
 | 
								ref: 'User',
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,5 +18,7 @@ export const kinds = [
 | 
				
			||||||
	'write:notifications',
 | 
						'write:notifications',
 | 
				
			||||||
	'read:reactions',
 | 
						'read:reactions',
 | 
				
			||||||
	'write:reactions',
 | 
						'write:reactions',
 | 
				
			||||||
	'write:votes'
 | 
						'write:votes',
 | 
				
			||||||
 | 
						'read:pages',
 | 
				
			||||||
 | 
						'write:pages',
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ import { packedMutingSchema } from '../../../models/repositories/muting';
 | 
				
			||||||
import { packedBlockingSchema } from '../../../models/repositories/blocking';
 | 
					import { packedBlockingSchema } from '../../../models/repositories/blocking';
 | 
				
			||||||
import { packedNoteReactionSchema } from '../../../models/repositories/note-reaction';
 | 
					import { packedNoteReactionSchema } from '../../../models/repositories/note-reaction';
 | 
				
			||||||
import { packedHashtagSchema } from '../../../models/repositories/hashtag';
 | 
					import { packedHashtagSchema } from '../../../models/repositories/hashtag';
 | 
				
			||||||
 | 
					import { packedPageSchema } from '../../../models/repositories/page';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function convertSchemaToOpenApiSchema(schema: Schema) {
 | 
					export function convertSchemaToOpenApiSchema(schema: Schema) {
 | 
				
			||||||
	const res: any = schema;
 | 
						const res: any = schema;
 | 
				
			||||||
| 
						 | 
					@ -76,4 +77,5 @@ export const schemas = {
 | 
				
			||||||
	Blocking: convertSchemaToOpenApiSchema(packedBlockingSchema),
 | 
						Blocking: convertSchemaToOpenApiSchema(packedBlockingSchema),
 | 
				
			||||||
	NoteReaction: convertSchemaToOpenApiSchema(packedNoteReactionSchema),
 | 
						NoteReaction: convertSchemaToOpenApiSchema(packedNoteReactionSchema),
 | 
				
			||||||
	Hashtag: convertSchemaToOpenApiSchema(packedHashtagSchema),
 | 
						Hashtag: convertSchemaToOpenApiSchema(packedHashtagSchema),
 | 
				
			||||||
 | 
						Page: convertSchemaToOpenApiSchema(packedPageSchema),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,6 +22,7 @@ export default class extends Channel {
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				case 'mention': {
 | 
									case 'mention': {
 | 
				
			||||||
 | 
										if (mute.map(m => m.muteeId).includes(body.userId)) return;
 | 
				
			||||||
					if (body.isHidden) return;
 | 
										if (body.isHidden) return;
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue