diff --git a/CHANGELOG.md b/CHANGELOG.md index 266525277d..5d0eefb2dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,19 @@ mongodb: 8. master ブランチに戻す 9. enjoy +11.8.0 (2019/05/01) +------------------- +### Improvements +* MisskeyPagesで関数を作成できるように +* MisskeyPagesでソースを表示できるように +* MisskeyPagesにシードを与えるランダム関数を追加 +* MisskeyPagesに複数行テキストをテキストのリストに変換する関数を追加 + +### Fixes +* APIドキュメントが見れなくなっていたのを修正 +* mention (あなた宛て) streaming にミュートが効かない問題を修正 +* デザインの調整 + 11.7.0 (2019/04/30) ------------------- ### Improvements @@ -50,6 +63,7 @@ mongodb: * MisskeyPagesに 複数行テキスト入力 を追加 * MisskeyPagesに 投稿フォーム を追加 * MisskeyPagesに 変換系関数 を追加 +* MisskeyPagesに 環境変数 URL を追加 * MisskeyPagesでボタンやスイッチなどのテキストに変数使えるように ### Fixes diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index cd29135bdb..a8a5cbafd1 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1848,11 +1848,15 @@ pages: are-you-sure-delete: "このページを削除しますか?" page-deleted: "ページを削除しました" edit-this-page: "このページを編集" + view-source: "ソースを表示" view-page: "ページを見る" + inspector: "インスペクター" + content: "ページブロック" variables: "変数" variables-info: "変数を使うことで動的なページを作成できます。テキスト内で { 変数名 } と書くとそこに変数の値を埋め込めます。例えば Hello { thing } world! というテキストで、変数(thing)の値が ai だった場合、テキストは Hello ai world! になります。" variables-info2: "変数の評価(値を算出すること)は上から下に行われるので、ある変数の中で自分より下の変数を参照することはできません。例えば上から A、B、C と3つの変数を定義したとき、Cの中でABを参照することはできますが、Aの中でBCを参照することはできません。" variables-info3: "ユーザーからの入力を受け取るには、ページに「ユーザー入力」ブロックを設置し、「変数名」に入力を格納したい変数名を設定します(変数は自動で作成されます)。その変数を使ってユーザー入力に応じた動作を行えます。" + variables-info4: "関数を使うと、値の算出処理を再利用可能な形にまとめることができます。関数を作るには、「関数」タイプの変数を作成します。関数にはスロット(引数)を設定することができ、スロットの値は関数内で変数として利用可能です。また、AiScript標準で関数を引数に取る関数(高階関数と呼ばれます)も存在します。関数は予め定義しておくほかに、このような高階関数のスロットに即席でセットすることもできます。" more-details: "詳しい説明" title: "タイトル" url: "ページURL" @@ -1952,6 +1956,10 @@ pages: strReverse: "テキストを反転" _strReverse: arg1: "テキスト" + join: "テキストを連結" + _join: + arg1: "リスト" + arg2: "区切り" add: "+ 足す" _add: arg1: "A" @@ -2028,20 +2036,39 @@ pages: dailyRandomPick: "リストからランダムに選択 (ユーザーごとに日替わり)" _dailyRandomPick: arg1: "リスト" - number: "数" + seedRandom: "ランダム (シード)" + _seedRandom: + arg1: "シード" + arg2: "確率" + seedRannum: "乱数 (シード)" + _seedRannum: + arg1: "シード" + arg2: "最小" + arg3: "最大" + seedRandomPick: "リストからランダムに選択 (シード)" + _seedRandomPick: + arg1: "シード" + arg2: "リスト" + number: "数値" stringToNumber: "テキストを数値に" _stringToNumber: arg1: "テキスト" numberToString: "数値をテキストに" _numberToString: arg1: "数値" + splitStrByLine: "テキストを行で分割" + _splitStrByLine: + arg1: "テキスト" ref: "変数" - in: "引数" - _in: - arg1: "スロット番号" fn: "関数" _fn: + slots: "スロット" + slots-info: "スロットひとつひとつを改行で区切ってください" arg1: "出力" + for: "繰り返し" + _for: + arg1: "回数" + arg2: "処理" typeError: "スロット{slot}は\"{expect}\"を受け付けますが、\"{actual}\"が入れられています!" thereIsEmptySlot: "スロット{slot}が空です!" types: @@ -2053,3 +2080,4 @@ pages: emptySlot: "空のスロット" enviromentVariables: "環境変数" pageVariables: "ページ要素" + argVariables: "入力スロット" diff --git a/package.json b/package.json index 3afb01dc51..3b3ad742a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "11.7.0", + "version": "11.8.0", "codename": "daybreak", "repository": { "type": "git", diff --git a/src/client/app/common/scripts/aiscript.ts b/src/client/app/common/scripts/aiscript.ts deleted file mode 100644 index 99caec8c15..0000000000 --- a/src/client/app/common/scripts/aiscript.ts +++ /dev/null @@ -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; - - 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 = {}): 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; - } -} diff --git a/src/client/app/common/scripts/collect-page-vars.ts b/src/client/app/common/scripts/collect-page-vars.ts index 92727ce6db..683f9b73a5 100644 --- a/src/client/app/common/scripts/collect-page-vars.ts +++ b/src/client/app/common/scripts/collect-page-vars.ts @@ -6,25 +6,25 @@ export function collectPageVars(content) { pageVars.push({ name: x.name, type: 'string', - value: x.default + value: x.default || '' }); } else if (x.type === 'textareaInput') { pageVars.push({ name: x.name, type: 'string', - value: x.default + value: x.default || '' }); } else if (x.type === 'numberInput') { pageVars.push({ name: x.name, type: 'number', - value: x.default + value: x.default || 0 }); } else if (x.type === 'switch') { pageVars.push({ name: x.name, type: 'boolean', - value: x.default + value: x.default || false }); } else if (x.children) { collect(x.children); diff --git a/src/client/app/common/views/components/page-editor/els/page-editor.el.number-input.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.number-input.vue index aff6cf6b6b..923f4ea339 100644 --- a/src/client/app/common/views/components/page-editor/els/page-editor.el.number-input.vue +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.number-input.vue @@ -3,7 +3,7 @@
- {{ $t('blocks._numberInput.name') }} + {{ $t('blocks._numberInput.name') }} {{ $t('blocks._numberInput.text') }} {{ $t('blocks._numberInput.default') }}
@@ -12,7 +12,7 @@