diff --git a/CHANGELOG.md b/CHANGELOG.md index c447084e65..266525277d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,20 @@ mongodb: 8. master ブランチに戻す 9. enjoy +11.7.0 (2019/04/30) +------------------- +### Improvements +* MisskeyPagesに ifブロック を追加 +* MisskeyPagesに テキストエリア を追加 +* MisskeyPagesに 複数行テキスト入力 を追加 +* MisskeyPagesに 投稿フォーム を追加 +* MisskeyPagesに 変換系関数 を追加 +* MisskeyPagesでボタンやスイッチなどのテキストに変数使えるように + +### Fixes +* OGPのサイト名を修正 +* デザインの調整 + 11.6.0 (2019/04/29) ------------------- ### Improvements diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e2bfb2e896..cd29135bdb 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1842,6 +1842,7 @@ dev/views/new-app.vue: pages: new-page: "ページの作成" edit-page: "ページの編集" + read-page: "ソースを表示中" page-created: "ページを作成しました" page-updated: "ページを更新しました" are-you-sure-delete: "このページを削除しますか?" @@ -1866,25 +1867,50 @@ pages: select-type: "種類を選択" enter-variable-name: "変数名を決めてください" the-variable-name-is-already-used: "その変数名は既に使われています" + content-blocks: "コンテンツ" + input-blocks: "入力" + special-blocks: "特殊" + post-from-post-form: "この内容を投稿" + posted-from-post-form: "投稿しました" blocks: text: "テキスト" + textarea: "テキストエリア" section: "セクション" image: "画像" button: "ボタン" - input: "ユーザー入力" - _input: + + if: "もし" + _if: + variable: "変数" + + post: "投稿フォーム" + _post: + text: "内容" + + textInput: "テキスト入力" + _textInput: name: "変数名" text: "タイトル" default: "デフォルト値" - inputType: "入力の種類" - _inputType: - string: "テキスト" - number: "数値" + + textareaInput: "複数行テキスト入力" + _textareaInput: + name: "変数名" + text: "タイトル" + default: "デフォルト値" + + numberInput: "数値入力" + _numberInput: + name: "変数名" + text: "タイトル" + default: "デフォルト値" + switch: "スイッチ" _switch: name: "変数名" text: "タイトル" default: "デフォルト値" + _button: text: "タイトル" action: "ボタンを押したときの動作" @@ -1893,6 +1919,7 @@ pages: _dialog: content: "内容" resetRandom: "乱数をリセット" + script: categories: flow: "制御" @@ -1903,6 +1930,7 @@ pages: value: "値" fn: "関数" text: "テキスト操作" + convert: "変換" blocks: text: "テキスト" multiLineText: "テキスト(複数行)" @@ -2001,8 +2029,14 @@ pages: _dailyRandomPick: arg1: "リスト" number: "数" + stringToNumber: "テキストを数値に" + _stringToNumber: + arg1: "テキスト" + numberToString: "数値をテキストに" + _numberToString: + arg1: "数値" ref: "変数" - in: "入力" + in: "引数" _in: arg1: "スロット番号" fn: "関数" diff --git a/package.json b/package.json index 4161d89e77..3afb01dc51 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo <i@syuilo.com>", - "version": "11.6.0", + "version": "11.7.0", "codename": "daybreak", "repository": { "type": "git", diff --git a/src/client/app/common/scripts/aiscript.ts b/src/client/app/common/scripts/aiscript.ts index fe9a295000..99caec8c15 100644 --- a/src/client/app/common/scripts/aiscript.ts +++ b/src/client/app/common/scripts/aiscript.ts @@ -26,6 +26,7 @@ import { faNotEqual, faDice, faSortNumericUp, + faExchangeAlt, } from '@fortawesome/free-solid-svg-icons'; import { faFlag } from '@fortawesome/free-regular-svg-icons'; @@ -69,11 +70,13 @@ const funcDefs = { 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, }, - random: { in: ['number'], out: 'boolean', category: 'random', icon: faDice, }, - randomPick: { in: [0], out: 0, 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, }, }; @@ -94,6 +97,7 @@ type PageVar = { name: string; value: any; type: Type; }; const envVarsDef = { AI: 'string', + URL: 'string', VERSION: 'string', LOGIN: 'boolean', NAME: 'string', @@ -117,7 +121,7 @@ export class AiScript { public static blockDefs = blockDefs; public static funcDefs = funcDefs; private opts: { - randomSeed?: string; user?: any; visitor?: any; + randomSeed?: string; user?: any; visitor?: any; page?: any; url?: string; }; constructor(variables: Variable[] = [], pageVars: PageVar[] = [], opts: AiScript['opts'] = {}) { @@ -128,6 +132,7 @@ export class AiScript { 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 : '', @@ -421,6 +426,8 @@ export class AiScript { 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)], diff --git a/src/client/app/common/scripts/collect-page-vars.ts b/src/client/app/common/scripts/collect-page-vars.ts index 86687e21f4..92727ce6db 100644 --- a/src/client/app/common/scripts/collect-page-vars.ts +++ b/src/client/app/common/scripts/collect-page-vars.ts @@ -2,10 +2,22 @@ export function collectPageVars(content) { const pageVars = []; const collect = (xs: any[]) => { for (const x of xs) { - if (x.type === 'input') { + if (x.type === 'textInput') { pageVars.push({ name: x.name, - type: x.inputType, + type: 'string', + value: x.default + }); + } else if (x.type === 'textareaInput') { + pageVars.push({ + name: x.name, + type: 'string', + value: x.default + }); + } else if (x.type === 'numberInput') { + pageVars.push({ + name: x.name, + type: 'number', value: x.default }); } else if (x.type === 'switch') { diff --git a/src/client/app/common/views/components/page-editor/page-editor.button.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.button.vue similarity index 93% rename from src/client/app/common/views/components/page-editor/page-editor.button.vue rename to src/client/app/common/views/components/page-editor/els/page-editor.el.button.vue index d5fc243818..3e2d3fe19d 100644 --- a/src/client/app/common/views/components/page-editor/page-editor.button.vue +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.button.vue @@ -16,9 +16,9 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../../../../i18n'; import { faBolt } from '@fortawesome/free-solid-svg-icons'; -import XContainer from './page-editor.container.vue'; +import i18n from '../../../../../i18n'; +import XContainer from '../page-editor.container.vue'; export default Vue.extend({ i18n: i18n('pages'), diff --git a/src/client/app/common/views/components/page-editor/els/page-editor.el.if.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.if.vue new file mode 100644 index 0000000000..87fc9e6faf --- /dev/null +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.if.vue @@ -0,0 +1,113 @@ +<template> +<x-container @remove="() => $emit('remove')"> + <template #header><fa :icon="faQuestion"/> {{ $t('blocks.if') }}</template> + <template #func> + <button @click="add()"> + <fa :icon="faPlus"/> + </button> + </template> + + <section class="romcojzs"> + <ui-select v-model="value.var"> + <template #label>{{ $t('blocks._if.variable') }}</template> + <option v-for="v in aiScript.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option> + <optgroup :label="$t('script.pageVariables')"> + <option v-for="v in aiScript.getPageVarsByType('boolean')" :value="v">{{ v }}</option> + </optgroup> + <optgroup :label="$t('script.enviromentVariables')"> + <option v-for="v in aiScript.getEnvVarsByType('boolean')" :value="v">{{ v }}</option> + </optgroup> + </ui-select> + + <div class="children"> + <x-block v-for="child in value.children" :value="child" @input="v => updateItem(v)" @remove="() => remove(child)" :key="child.id" :ai-script="aiScript"/> + </div> + </section> +</x-container> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import * as uuid from 'uuid'; +import { faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons'; +import i18n from '../../../../../i18n'; +import XContainer from '../page-editor.container.vue'; + +export default Vue.extend({ + i18n: i18n('pages'), + + components: { + XContainer + }, + + inject: ['getPageBlockList'], + + props: { + value: { + required: true + }, + aiScript: { + required: true, + }, + }, + + data() { + return { + faPlus, faQuestion + }; + }, + + beforeCreate() { + this.$options.components.XBlock = require('../page-editor.block.vue').default + }, + + created() { + if (this.value.children == null) Vue.set(this.value, 'children', []); + if (this.value.var === undefined) Vue.set(this.value, 'var', null); + }, + + methods: { + async add() { + const { canceled, result: type } = await this.$root.dialog({ + type: null, + title: this.$t('choose-block'), + select: { + groupedItems: this.getPageBlockList() + }, + showCancelButton: true + }); + if (canceled) return; + + const id = uuid.v4(); + this.value.children.push({ id, type }); + }, + + updateItem(v) { + const i = this.value.children.findIndex(x => x.id === v.id); + const newValue = [ + ...this.value.children.slice(0, i), + v, + ...this.value.children.slice(i + 1) + ]; + this.value.children = newValue; + this.$emit('input', this.value); + }, + + remove(el) { + const i = this.value.children.findIndex(x => x.id === el.id); + const newValue = [ + ...this.value.children.slice(0, i), + ...this.value.children.slice(i + 1) + ]; + this.value.children = newValue; + this.$emit('input', this.value); + } + } +}); +</script> + +<style lang="stylus" scoped> +.romcojzs + padding 0 16px 16px 16px + +</style> diff --git a/src/client/app/common/views/components/page-editor/page-editor.image.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.image.vue similarity index 89% rename from src/client/app/common/views/components/page-editor/page-editor.image.vue rename to src/client/app/common/views/components/page-editor/els/page-editor.el.image.vue index 0bc1816e8d..5ada8c77ba 100644 --- a/src/client/app/common/views/components/page-editor/page-editor.image.vue +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.image.vue @@ -15,11 +15,11 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../../../../i18n'; import { faPencilAlt } from '@fortawesome/free-solid-svg-icons'; import { faImage, faFolderOpen } from '@fortawesome/free-regular-svg-icons'; -import XContainer from './page-editor.container.vue'; -import XFileThumbnail from '../drive-file-thumbnail.vue'; +import i18n from '../../../../../i18n'; +import XContainer from '../page-editor.container.vue'; +import XFileThumbnail from '../../drive-file-thumbnail.vue'; export default Vue.extend({ i18n: i18n('pages'), 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 new file mode 100644 index 0000000000..aff6cf6b6b --- /dev/null +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.number-input.vue @@ -0,0 +1,42 @@ +<template> +<x-container @remove="() => $emit('remove')"> + <template #header><fa :icon="faBolt"/> {{ $t('blocks.numberInput') }}</template> + + <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.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> + </section> +</x-container> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import { faBolt, faSquareRootAlt } from '@fortawesome/free-solid-svg-icons'; +import i18n from '../../../../../i18n'; +import XContainer from '../page-editor.container.vue'; + +export default Vue.extend({ + i18n: i18n('pages'), + + components: { + XContainer + }, + + props: { + value: { + required: true + }, + }, + + data() { + return { + faBolt, faSquareRootAlt + }; + }, + + created() { + if (this.value.name == null) Vue.set(this.value, 'name', ''); + }, +}); +</script> diff --git a/src/client/app/common/views/components/page-editor/els/page-editor.el.post.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.post.vue new file mode 100644 index 0000000000..a01fc57f26 --- /dev/null +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.post.vue @@ -0,0 +1,44 @@ +<template> +<x-container @remove="() => $emit('remove')"> + <template #header><fa :icon="faPaperPlane"/> {{ $t('blocks.post') }}</template> + + <section style="padding: 0 16px 16px 16px;"> + <ui-textarea v-model="value.text">{{ $t('blocks._post.text') }}</ui-textarea> + </section> +</x-container> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import { faPaperPlane } from '@fortawesome/free-regular-svg-icons'; +import i18n from '../../../../../i18n'; +import XContainer from '../page-editor.container.vue'; + +export default Vue.extend({ + i18n: i18n('pages'), + + components: { + XContainer + }, + + props: { + value: { + required: true + }, + }, + + data() { + return { + faPaperPlane + }; + }, + + beforeCreate() { + this.$options.components.XBlock = require('../page-editor.block.vue').default + }, + + created() { + if (this.value.text == null) Vue.set(this.value, 'text', ''); + }, +}); +</script> diff --git a/src/client/app/common/views/components/page-editor/page-editor.section.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.section.vue similarity index 80% rename from src/client/app/common/views/components/page-editor/page-editor.section.vue rename to src/client/app/common/views/components/page-editor/els/page-editor.el.section.vue index d7a247b0b1..747de481a6 100644 --- a/src/client/app/common/views/components/page-editor/page-editor.section.vue +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.section.vue @@ -12,7 +12,7 @@ <section class="ilrvjyvi"> <div class="children"> - <x-block v-for="child in value.children" :value="child" @input="v => updateItem(v)" @remove="() => remove(child)" :key="child.id"/> + <x-block v-for="child in value.children" :value="child" @input="v => updateItem(v)" @remove="() => remove(child)" :key="child.id" :ai-script="aiScript"/> </div> </section> </x-container> @@ -20,11 +20,11 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../../../../i18n'; +import * as uuid from 'uuid'; import { faPlus, faPencilAlt } from '@fortawesome/free-solid-svg-icons'; import { faStickyNote } from '@fortawesome/free-regular-svg-icons'; -import XContainer from './page-editor.container.vue'; -import * as uuid from 'uuid'; +import i18n from '../../../../../i18n'; +import XContainer from '../page-editor.container.vue'; export default Vue.extend({ i18n: i18n('pages'), @@ -33,10 +33,15 @@ export default Vue.extend({ XContainer }, + inject: ['getPageBlockList'], + props: { value: { required: true }, + aiScript: { + required: true, + }, }, data() { @@ -46,7 +51,7 @@ export default Vue.extend({ }, beforeCreate() { - this.$options.components.XBlock = require('./page-editor.block.vue').default + this.$options.components.XBlock = require('../page-editor.block.vue').default }, created() { @@ -79,19 +84,7 @@ export default Vue.extend({ type: null, title: this.$t('choose-block'), select: { - items: [{ - value: 'section', text: this.$t('blocks.section') - }, { - value: 'text', text: this.$t('blocks.text') - }, { - value: 'image', text: this.$t('blocks.image') - }, { - value: 'button', text: this.$t('blocks.button') - }, { - value: 'input', text: this.$t('blocks.input') - }, { - value: 'switch', text: this.$t('blocks.switch') - }] + groupedItems: this.getPageBlockList() }, showCancelButton: true }); diff --git a/src/client/app/common/views/components/page-editor/page-editor.switch.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.switch.vue similarity index 91% rename from src/client/app/common/views/components/page-editor/page-editor.switch.vue rename to src/client/app/common/views/components/page-editor/els/page-editor.el.switch.vue index a9cfa2844f..3404404d99 100644 --- a/src/client/app/common/views/components/page-editor/page-editor.switch.vue +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.switch.vue @@ -12,9 +12,9 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../../../../i18n'; import { faBolt, faSquareRootAlt } from '@fortawesome/free-solid-svg-icons'; -import XContainer from './page-editor.container.vue'; +import i18n from '../../../../../i18n'; +import XContainer from '../page-editor.container.vue'; export default Vue.extend({ i18n: i18n('pages'), diff --git a/src/client/app/common/views/components/page-editor/els/page-editor.el.text-input.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.text-input.vue new file mode 100644 index 0000000000..6c4783fd7e --- /dev/null +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.text-input.vue @@ -0,0 +1,42 @@ +<template> +<x-container @remove="() => $emit('remove')"> + <template #header><fa :icon="faBolt"/> {{ $t('blocks.textInput') }}</template> + + <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.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> + </section> +</x-container> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import { faBolt, faSquareRootAlt } from '@fortawesome/free-solid-svg-icons'; +import i18n from '../../../../../i18n'; +import XContainer from '../page-editor.container.vue'; + +export default Vue.extend({ + i18n: i18n('pages'), + + components: { + XContainer + }, + + props: { + value: { + required: true + }, + }, + + data() { + return { + faBolt, faSquareRootAlt + }; + }, + + created() { + if (this.value.name == null) Vue.set(this.value, 'name', ''); + }, +}); +</script> diff --git a/src/client/app/common/views/components/page-editor/page-editor.text.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.text.vue similarity index 89% rename from src/client/app/common/views/components/page-editor/page-editor.text.vue rename to src/client/app/common/views/components/page-editor/els/page-editor.el.text.vue index 7368931b2f..1d07c9157a 100644 --- a/src/client/app/common/views/components/page-editor/page-editor.text.vue +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.text.vue @@ -10,9 +10,9 @@ <script lang="ts"> import Vue from 'vue'; -import i18n from '../../../../i18n'; import { faAlignLeft } from '@fortawesome/free-solid-svg-icons'; -import XContainer from './page-editor.container.vue'; +import i18n from '../../../../../i18n'; +import XContainer from '../page-editor.container.vue'; export default Vue.extend({ i18n: i18n('pages'), @@ -54,4 +54,5 @@ export default Vue.extend({ padding 16px background transparent color var(--text) + font-size 14px </style> diff --git a/src/client/app/common/views/components/page-editor/els/page-editor.el.textarea-input.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.textarea-input.vue new file mode 100644 index 0000000000..68edf13824 --- /dev/null +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.textarea-input.vue @@ -0,0 +1,42 @@ +<template> +<x-container @remove="() => $emit('remove')"> + <template #header><fa :icon="faBolt"/> {{ $t('blocks.textareaInput') }}</template> + + <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.text"><span>{{ $t('blocks._textareaInput.text') }}</span></ui-input> + <ui-textarea v-model="value.default"><span>{{ $t('blocks._textareaInput.default') }}</span></ui-textarea> + </section> +</x-container> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import { faBolt, faSquareRootAlt } from '@fortawesome/free-solid-svg-icons'; +import i18n from '../../../../../i18n'; +import XContainer from '../page-editor.container.vue'; + +export default Vue.extend({ + i18n: i18n('pages'), + + components: { + XContainer + }, + + props: { + value: { + required: true + }, + }, + + data() { + return { + faBolt, faSquareRootAlt + }; + }, + + created() { + if (this.value.name == null) Vue.set(this.value, 'name', ''); + }, +}); +</script> diff --git a/src/client/app/common/views/components/page-editor/els/page-editor.el.textarea.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.textarea.vue new file mode 100644 index 0000000000..4fbe497960 --- /dev/null +++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.textarea.vue @@ -0,0 +1,58 @@ +<template> +<x-container @remove="() => $emit('remove')"> + <template #header><fa :icon="faAlignLeft"/> {{ $t('blocks.textarea') }}</template> + + <section class="ihymsbbe"> + <textarea v-model="value.text"></textarea> + </section> +</x-container> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import { faAlignLeft } from '@fortawesome/free-solid-svg-icons'; +import i18n from '../../../../../i18n'; +import XContainer from '../page-editor.container.vue'; + +export default Vue.extend({ + i18n: i18n('pages'), + + components: { + XContainer + }, + + props: { + value: { + required: true + }, + }, + + data() { + return { + faAlignLeft, + }; + }, + + created() { + if (this.value.text == null) Vue.set(this.value, 'text', ''); + }, +}); +</script> + +<style lang="stylus" scoped> +.ihymsbbe + > textarea + display block + -webkit-appearance none + -moz-appearance none + appearance none + width 100% + min-width 100% + min-height 150px + border none + box-shadow none + padding 16px + background transparent + color var(--text) + font-size 14px +</style> diff --git a/src/client/app/common/views/components/page-editor/page-editor.block.vue b/src/client/app/common/views/components/page-editor/page-editor.block.vue index a3e1488d1b..7d4505fca8 100644 --- a/src/client/app/common/views/components/page-editor/page-editor.block.vue +++ b/src/client/app/common/views/components/page-editor/page-editor.block.vue @@ -1,25 +1,33 @@ <template> -<component :is="'x-' + value.type" :value="value" @input="v => updateItem(v)" @remove="() => $emit('remove', value)" :key="value.id"/> +<component :is="'x-' + value.type" :value="value" @input="v => updateItem(v)" @remove="() => $emit('remove', value)" :key="value.id" :ai-script="aiScript"/> </template> <script lang="ts"> import Vue from 'vue'; -import XSection from './page-editor.section.vue'; -import XText from './page-editor.text.vue'; -import XImage from './page-editor.image.vue'; -import XButton from './page-editor.button.vue'; -import XInput from './page-editor.input.vue'; -import XSwitch from './page-editor.switch.vue'; +import XSection from './els/page-editor.el.section.vue'; +import XText from './els/page-editor.el.text.vue'; +import XTextarea from './els/page-editor.el.textarea.vue'; +import XImage from './els/page-editor.el.image.vue'; +import XButton from './els/page-editor.el.button.vue'; +import XTextInput from './els/page-editor.el.text-input.vue'; +import XTextareaInput from './els/page-editor.el.textarea-input.vue'; +import XNumberInput from './els/page-editor.el.text-input.vue'; +import XSwitch from './els/page-editor.el.switch.vue'; +import XIf from './els/page-editor.el.if.vue'; +import XPost from './els/page-editor.el.post.vue'; export default Vue.extend({ components: { - XSection, XText, XImage, XButton, XInput, XSwitch + XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost }, props: { value: { required: true - } + }, + aiScript: { + required: true, + }, }, }); </script> diff --git a/src/client/app/common/views/components/page-editor/page-editor.input.vue b/src/client/app/common/views/components/page-editor/page-editor.input.vue deleted file mode 100644 index 4e13840439..0000000000 --- a/src/client/app/common/views/components/page-editor/page-editor.input.vue +++ /dev/null @@ -1,61 +0,0 @@ -<template> -<x-container @remove="() => $emit('remove')"> - <template #header><fa :icon="faBolt"/> {{ $t('blocks.input') }}</template> - - <section class="dnvasjon"> - <ui-input v-model="value.name"><template #prefix><fa :icon="faSquareRootAlt"/></template><span>{{ $t('blocks._input.name') }}</span></ui-input> - <ui-input v-model="value.text"><span>{{ $t('blocks._input.text') }}</span></ui-input> - <ui-select v-model="value.inputType"> - <template #label>{{ $t('blocks._input.inputType') }}</template> - <option value="string">{{ $t('blocks._input._inputType.string') }}</option> - <option value="number">{{ $t('blocks._input._inputType.number') }}</option> - </ui-select> - <ui-input v-model="value.default" :type="value.inputType"><span>{{ $t('blocks._input.default') }}</span></ui-input> - </section> -</x-container> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import i18n from '../../../../i18n'; -import { faBolt, faSquareRootAlt } from '@fortawesome/free-solid-svg-icons'; -import XContainer from './page-editor.container.vue'; - -export default Vue.extend({ - i18n: i18n('pages'), - - components: { - XContainer - }, - - props: { - value: { - required: true - }, - }, - - data() { - return { - faBolt, faSquareRootAlt - }; - }, - - created() { - if (this.value.name == null) Vue.set(this.value, 'name', ''); - if (this.value.inputType == null) Vue.set(this.value, 'inputType', 'string'); - - this.$watch('value.inputType', t => { - if (this.value.default != null) { - if (t === 'number') this.value.default = parseInt(this.value.default, 10); - if (t === 'string') this.value.default = this.value.default.toString(); - } - }); - }, -}); -</script> - -<style lang="stylus" scoped> -.dnvasjon - padding 0 16px 0 16px - -</style> diff --git a/src/client/app/common/views/components/page-editor/page-editor.vue b/src/client/app/common/views/components/page-editor/page-editor.vue index 8b25828515..f8959fb0f1 100644 --- a/src/client/app/common/views/components/page-editor/page-editor.vue +++ b/src/client/app/common/views/components/page-editor/page-editor.vue @@ -2,16 +2,16 @@ <div> <div class="gwbmwxkm" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"> <header> - <div class="title"><fa :icon="faStickyNote"/> {{ pageId ? $t('edit-page') : $t('new-page') }}</div> + <div class="title"><fa :icon="faStickyNote"/> {{ readonly ? $t('read-page') : pageId ? $t('edit-page') : $t('new-page') }}</div> <div class="buttons"> - <button @click="del()"><fa :icon="faTrashAlt"/></button> + <button @click="del()" v-if="!readonly"><fa :icon="faTrashAlt"/></button> <button @click="() => showOptions = !showOptions"><fa :icon="faCog"/></button> - <button @click="save()"><fa :icon="faSave"/></button> + <button @click="save()" v-if="!readonly"><fa :icon="faSave"/></button> </div> </header> <section> - <a class="view" v-if="pageId" :href="`/@${ $store.state.i.username }/pages/${ currentName }`" target="_blank"><fa :icon="faExternalLinkSquareAlt"/> {{ $t('view-page') }}</a> + <a class="view" v-if="pageId" :href="`/@${ author.username }/pages/${ currentName }`" target="_blank"><fa :icon="faExternalLinkSquareAlt"/> {{ $t('view-page') }}</a> <ui-input v-model="title"> <span>{{ $t('title') }}</span> @@ -23,7 +23,7 @@ </ui-input> <ui-input v-model="name"> - <template #prefix>{{ url }}/@{{ $store.state.i.username }}/pages/</template> + <template #prefix>{{ url }}/@{{ author.username }}/pages/</template> <span>{{ $t('url') }}</span> </ui-input> @@ -45,10 +45,10 @@ </template> <div class="content" v-for="child in content"> - <x-block :value="child" @input="v => updateItem(v)" @remove="() => remove(child)" :key="child.id"/> + <x-block :value="child" @input="v => updateItem(v)" @remove="() => remove(child)" :key="child.id" :ai-script="aiScript"/> </div> - <ui-button @click="add()"><fa :icon="faPlus"/></ui-button> + <ui-button @click="add()" v-if="!readonly"><fa :icon="faPlus"/></ui-button> </section> </div> @@ -70,7 +70,7 @@ </template> </div> - <ui-button @click="addVariable()" class="add"><fa :icon="faPlus"/></ui-button> + <ui-button @click="addVariable()" class="add" v-if="!readonly"><fa :icon="faPlus"/></ui-button> <ui-info><span v-html="$t('variables-info')"></span><a @click="() => moreDetails = true" style="display:block;">{{ $t('more-details') }}</a></ui-info> @@ -106,11 +106,17 @@ export default Vue.extend({ page: { type: String, required: false - } + }, + readonly: { + type: Boolean, + required: false, + default: false + }, }, data() { return { + author: this.$store.state.i, pageId: null, currentName: null, title: '', @@ -157,6 +163,7 @@ export default Vue.extend({ this.$root.api('pages/show', { pageId: this.page, }).then(page => { + this.author = page.user; this.pageId = page.id; this.title = page.title; this.name = page.name; @@ -180,7 +187,9 @@ export default Vue.extend({ provide() { return { - getScriptBlockList: this.getScriptBlockList + readonly: this.readonly, + getScriptBlockList: this.getScriptBlockList, + getPageBlockList: this.getPageBlockList } }, @@ -250,19 +259,7 @@ export default Vue.extend({ type: null, title: this.$t('choose-block'), select: { - items: [{ - value: 'section', text: this.$t('blocks.section') - }, { - value: 'text', text: this.$t('blocks.text') - }, { - value: 'image', text: this.$t('blocks.image') - }, { - value: 'button', text: this.$t('blocks.button') - }, { - value: 'input', text: this.$t('blocks.input') - }, { - value: 'switch', text: this.$t('blocks.switch') - }] + groupedItems: this.getPageBlockList() }, showCancelButton: true }); @@ -324,6 +321,33 @@ export default Vue.extend({ this.variables = newValue; }, + getPageBlockList() { + return [{ + label: this.$t('content-blocks'), + items: [ + { value: 'section', text: this.$t('blocks.section') }, + { value: 'text', text: this.$t('blocks.text') }, + { value: 'image', text: this.$t('blocks.image') }, + { value: 'textarea', text: this.$t('blocks.textarea') }, + ] + }, { + label: this.$t('input-blocks'), + items: [ + { value: 'button', text: this.$t('blocks.button') }, + { value: 'textInput', text: this.$t('blocks.textInput') }, + { value: 'textareaInput', text: this.$t('blocks.textareaInput') }, + { value: 'numberInput', text: this.$t('blocks.numberInput') }, + { value: 'switch', text: this.$t('blocks.switch') } + ] + }, { + label: this.$t('special-blocks'), + items: [ + { value: 'if', text: this.$t('blocks.if') }, + { value: 'post', text: this.$t('blocks.post') } + ] + }]; + }, + getScriptBlockList(type: string = null) { const list = []; @@ -436,6 +460,7 @@ export default Vue.extend({ > .view display inline-block margin 16px 0 0 0 + font-size 14px > .content margin-bottom 16px diff --git a/src/client/app/common/views/pages/page/page.block.vue b/src/client/app/common/views/pages/page/page.block.vue index 48a89f9de7..f348107cc7 100644 --- a/src/client/app/common/views/pages/page/page.block.vue +++ b/src/client/app/common/views/pages/page/page.block.vue @@ -8,12 +8,17 @@ import XText from './page.text.vue'; import XSection from './page.section.vue'; import XImage from './page.image.vue'; import XButton from './page.button.vue'; -import XInput from './page.input.vue'; +import XNumberInput from './page.number-input.vue'; +import XTextInput from './page.text-input.vue'; +import XTextareaInput from './page.textarea-input.vue'; import XSwitch from './page.switch.vue'; +import XIf from './page.if.vue'; +import XTextarea from './page.textarea.vue'; +import XPost from './page.post.vue'; export default Vue.extend({ components: { - XText, XSection, XImage, XButton, XInput, XSwitch + XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf }, props: { diff --git a/src/client/app/common/views/pages/page/page.button.vue b/src/client/app/common/views/pages/page/page.button.vue index 5063d27122..b77d856d5d 100644 --- a/src/client/app/common/views/pages/page/page.button.vue +++ b/src/client/app/common/views/pages/page/page.button.vue @@ -1,6 +1,6 @@ <template> <div> - <ui-button class="kudkigyw" @click="click()">{{ value.text }}</ui-button> + <ui-button class="kudkigyw" @click="click()">{{ script.interpolate(value.text) }}</ui-button> </div> </template> diff --git a/src/client/app/common/views/pages/page/page.if.vue b/src/client/app/common/views/pages/page/page.if.vue new file mode 100644 index 0000000000..9dbeaf64fb --- /dev/null +++ b/src/client/app/common/views/pages/page/page.if.vue @@ -0,0 +1,30 @@ +<template> +<div v-show="script.vars.find(x => x.name === value.var).value"> + <x-block v-for="child in value.children" :value="child" :page="page" :script="script" :key="child.id" :h="h"/> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + props: { + value: { + required: true + }, + script: { + required: true + }, + page: { + required: true + }, + h: { + required: true + } + }, + + beforeCreate() { + this.$options.components.XBlock = require('./page.block.vue').default + }, +}); +</script> diff --git a/src/client/app/common/views/pages/page/page.input.vue b/src/client/app/common/views/pages/page/page.number-input.vue similarity index 64% rename from src/client/app/common/views/pages/page/page.input.vue rename to src/client/app/common/views/pages/page/page.number-input.vue index cda5550337..51d538c369 100644 --- a/src/client/app/common/views/pages/page/page.input.vue +++ b/src/client/app/common/views/pages/page/page.number-input.vue @@ -1,6 +1,6 @@ <template> <div> - <ui-input class="kudkigyw" v-model="v" :type="value.inputType">{{ value.text }}</ui-input> + <ui-input class="kudkigyw" v-model="v" type="number">{{ script.interpolate(value.text) }}</ui-input> </div> </template> @@ -25,9 +25,7 @@ export default Vue.extend({ watch: { v() { - let v = this.v; - if (this.value.inputType === 'number') v = parseInt(v, 10); - this.script.aiScript.updatePageVar(this.value.name, v); + this.script.aiScript.updatePageVar(this.value.name, this.v); this.script.reEval(); } } diff --git a/src/client/app/common/views/pages/page/page.post.vue b/src/client/app/common/views/pages/page/page.post.vue new file mode 100644 index 0000000000..cb695e21e9 --- /dev/null +++ b/src/client/app/common/views/pages/page/page.post.vue @@ -0,0 +1,68 @@ +<template> +<div class="ngbfujlo"> + <ui-textarea class="textarea" :value="text" readonly></ui-textarea> + <ui-button primary @click="post()" :disabled="posting || posted">{{ posted ? $t('posted-from-post-form') : $t('post-from-post-form') }}</ui-button> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import i18n from '../../../../i18n'; + +export default Vue.extend({ + i18n: i18n('pages'), + + props: { + value: { + required: true + }, + script: { + required: true + } + }, + + data() { + return { + text: this.script.interpolate(this.value.text), + posted: false, + posting: false, + }; + }, + + created() { + this.$watch('script.vars', () => { + this.text = this.script.interpolate(this.value.text); + }, { deep: true }); + }, + + methods: { + post() { + this.posting = true; + this.$root.api('notes/create', { + text: this.text, + }).then(() => { + this.posted = true; + this.$root.dialog({ + type: 'success', + splash: true + }); + }); + } + } +}); +</script> + +<style lang="stylus" scoped> +.ngbfujlo + padding 0 32px 32px 32px + border solid 2px var(--pageBlockBorder) + border-radius 6px + + @media (max-width 600px) + padding 0 16px 16px 16px + + > .textarea + margin-top 16px + margin-bottom 16px + +</style> diff --git a/src/client/app/common/views/pages/page/page.switch.vue b/src/client/app/common/views/pages/page/page.switch.vue index 962ab84bb5..a401e5dfbf 100644 --- a/src/client/app/common/views/pages/page/page.switch.vue +++ b/src/client/app/common/views/pages/page/page.switch.vue @@ -1,6 +1,6 @@ <template> <div class="hkcxmtwj"> - <ui-switch v-model="v">{{ value.text }}</ui-switch> + <ui-switch v-model="v">{{ script.interpolate(value.text) }}</ui-switch> </div> </template> @@ -36,4 +36,8 @@ export default Vue.extend({ .hkcxmtwj display inline-block margin 16px auto + + & + .hkcxmtwj + margin-left 16px + </style> diff --git a/src/client/app/common/views/pages/page/page.text-input.vue b/src/client/app/common/views/pages/page/page.text-input.vue new file mode 100644 index 0000000000..3d659edd5e --- /dev/null +++ b/src/client/app/common/views/pages/page/page.text-input.vue @@ -0,0 +1,41 @@ +<template> +<div> + <ui-input class="kudkigyw" v-model="v" type="text">{{ script.interpolate(value.text) }}</ui-input> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + props: { + value: { + required: true + }, + script: { + required: true + } + }, + + data() { + return { + v: this.value.default, + }; + }, + + watch: { + v() { + this.script.aiScript.updatePageVar(this.value.name, this.v); + this.script.reEval(); + } + } +}); +</script> + +<style lang="stylus" scoped> +.kudkigyw + display inline-block + min-width 300px + max-width 450px + margin 8px 0 +</style> diff --git a/src/client/app/common/views/pages/page/page.textarea-input.vue b/src/client/app/common/views/pages/page/page.textarea-input.vue new file mode 100644 index 0000000000..fd8174c273 --- /dev/null +++ b/src/client/app/common/views/pages/page/page.textarea-input.vue @@ -0,0 +1,36 @@ +<template> +<div> + <ui-textarea class="" v-model="v">{{ script.interpolate(value.text) }}</ui-textarea> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + props: { + value: { + required: true + }, + script: { + required: true + } + }, + + data() { + return { + v: this.value.default, + }; + }, + + watch: { + v() { + this.script.aiScript.updatePageVar(this.value.name, this.v); + this.script.reEval(); + } + } +}); +</script> + +<style lang="stylus" scoped> +</style> diff --git a/src/client/app/common/views/pages/page/page.textarea.vue b/src/client/app/common/views/pages/page/page.textarea.vue new file mode 100644 index 0000000000..03c8542cb0 --- /dev/null +++ b/src/client/app/common/views/pages/page/page.textarea.vue @@ -0,0 +1,33 @@ +<template> +<ui-textarea class="" :value="text" readonly></ui-textarea> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + props: { + value: { + required: true + }, + script: { + required: true + } + }, + + data() { + return { + text: this.script.interpolate(this.value.text), + }; + }, + + created() { + this.$watch('script.vars', () => { + this.text = this.script.interpolate(this.value.text); + }, { deep: true }); + } +}); +</script> + +<style lang="stylus" scoped> +</style> diff --git a/src/client/app/common/views/pages/page/page.vue b/src/client/app/common/views/pages/page/page.vue index e7e8f76d53..88598d3527 100644 --- a/src/client/app/common/views/pages/page/page.vue +++ b/src/client/app/common/views/pages/page/page.vue @@ -23,6 +23,7 @@ import { faSave, faStickyNote } from '@fortawesome/free-regular-svg-icons'; import XBlock from './page.block.vue'; import { AiScript } from '../../../scripts/aiscript'; import { collectPageVars } from '../../../scripts/collect-page-vars'; +import { url } from '../../../../config'; class Script { public aiScript: AiScript; @@ -38,6 +39,7 @@ class Script { } public interpolate(str: string) { + if (str == null) return null; return str.replace(/\{(.+?)\}/g, match => { const v = this.vars.find(x => x.name === match.slice(1, -1).trim()).value; return v == null ? 'NULL' : v.toString(); @@ -81,7 +83,9 @@ export default Vue.extend({ this.script = new Script(new AiScript(this.page.variables, pageVars, { randomSeed: Math.random(), user: page.user, - visitor: this.$store.state.i + visitor: this.$store.state.i, + page: page, + url: url })); }); }, diff --git a/src/client/app/mobile/views/pages/page.vue b/src/client/app/mobile/views/pages/page.vue index 27ade4a398..f494abffd7 100644 --- a/src/client/app/mobile/views/pages/page.vue +++ b/src/client/app/mobile/views/pages/page.vue @@ -33,4 +33,7 @@ main padding 16px max-width 1000px + @media (min-width 600px) + padding 32px + </style> diff --git a/src/models/entities/instance.ts b/src/models/entities/instance.ts index 977054263c..52c5215f14 100644 --- a/src/models/entities/instance.ts +++ b/src/models/entities/instance.ts @@ -71,7 +71,7 @@ export class Instance { /** * ドライブ使用量 */ - @Column('integer', { + @Column('bigint', { default: 0, }) public driveUsage: number; diff --git a/src/models/repositories/page.ts b/src/models/repositories/page.ts index 4c1b4cc793..cbe385568e 100644 --- a/src/models/repositories/page.ts +++ b/src/models/repositories/page.ts @@ -27,6 +27,33 @@ export class PageRepository extends Repository<Page> { } }; collectFile(src.content); + + // 後方互換性のため + let migrated = false; + const migrate = (xs: any[]) => { + for (const x of xs) { + if (x.type === 'input') { + if (x.inputType === 'text') { + x.type = 'textInput'; + } + if (x.inputType === 'number') { + x.type = 'numberInput'; + if (x.default) x.default = parseInt(x.default, 10); + } + migrated = true; + } + if (x.children) { + migrate(x.children); + } + } + }; + migrate(src.content); + if (migrated) { + this.update(src.id, { + content: src.content + }); + } + return await awaitAll({ id: src.id, createdAt: src.createdAt.toISOString(), diff --git a/src/server/web/views/base.pug b/src/server/web/views/base.pug index e61d29685a..733a306d56 100644 --- a/src/server/web/views/base.pug +++ b/src/server/web/views/base.pug @@ -11,7 +11,7 @@ html meta(name='application-name' content='Misskey') meta(name='referrer' content='origin') meta(name='theme-color' content='#105779') - meta(property='og:site_name' content= title || 'Misskey') + meta(property='og:site_name' content= instanceName || 'Misskey') link(rel='icon' href= icon || '/favicon.ico') link(rel='manifest' href='/manifest.json')