From 2e8e5c2751aba86a03c564dc7aa2dc7e595caac5 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 30 Apr 2019 06:40:02 +0900
Subject: [PATCH] Improve MisskeyPages
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* ifブロック を追加
* ボタンやスイッチなどのテキストに変数使えるようにした
---
 locales/ja-JP.yml                             |   4 +
 .../page-editor.el.button.vue}                |   4 +-
 .../page-editor/els/page-editor.el.if.vue     | 113 ++++++++++++++++++
 .../page-editor.el.image.vue}                 |   6 +-
 .../page-editor.el.input.vue}                 |   4 +-
 .../page-editor.el.section.vue}               |  29 ++---
 .../page-editor.el.switch.vue}                |   4 +-
 .../page-editor.el.text.vue}                  |   4 +-
 .../page-editor/page-editor.block.vue         |  22 ++--
 .../components/page-editor/page-editor.vue    |  62 ++++++----
 .../common/views/pages/page/page.block.vue    |   3 +-
 .../common/views/pages/page/page.button.vue   |   2 +-
 .../app/common/views/pages/page/page.if.vue   |  30 +++++
 .../common/views/pages/page/page.input.vue    |   2 +-
 .../common/views/pages/page/page.switch.vue   |   2 +-
 .../app/common/views/pages/page/page.vue      |   1 +
 16 files changed, 227 insertions(+), 65 deletions(-)
 rename src/client/app/common/views/components/page-editor/{page-editor.button.vue => els/page-editor.el.button.vue} (93%)
 create mode 100644 src/client/app/common/views/components/page-editor/els/page-editor.el.if.vue
 rename src/client/app/common/views/components/page-editor/{page-editor.image.vue => els/page-editor.el.image.vue} (89%)
 rename src/client/app/common/views/components/page-editor/{page-editor.input.vue => els/page-editor.el.input.vue} (94%)
 rename src/client/app/common/views/components/page-editor/{page-editor.section.vue => els/page-editor.el.section.vue} (80%)
 rename src/client/app/common/views/components/page-editor/{page-editor.switch.vue => els/page-editor.el.switch.vue} (91%)
 rename src/client/app/common/views/components/page-editor/{page-editor.text.vue => els/page-editor.el.text.vue} (90%)
 create mode 100644 src/client/app/common/views/pages/page/page.if.vue

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index e2bfb2e896..343d9cfc39 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: "このページを削除しますか?"
@@ -1871,6 +1872,9 @@ pages:
     section: "セクション"
     image: "画像"
     button: "ボタン"
+    if: "もし"
+    _if:
+      variable: "変数"
     input: "ユーザー入力"
     _input:
       name: "変数名"
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..46920fafdc
--- /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: {
+					items: 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/page-editor.input.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.input.vue
similarity index 94%
rename from src/client/app/common/views/components/page-editor/page-editor.input.vue
rename to src/client/app/common/views/components/page-editor/els/page-editor.el.input.vue
index 4e13840439..b2bcb33c3d 100644
--- a/src/client/app/common/views/components/page-editor/page-editor.input.vue
+++ b/src/client/app/common/views/components/page-editor/els/page-editor.el.input.vue
@@ -17,9 +17,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/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..6133faf666 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')
-					}]
+					items: 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/page-editor.text.vue b/src/client/app/common/views/components/page-editor/els/page-editor.el.text.vue
similarity index 90%
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..b6b56a29fd 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'),
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..86ee3f6888 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,29 @@
 <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 XImage from './els/page-editor.el.image.vue';
+import XButton from './els/page-editor.el.button.vue';
+import XInput from './els/page-editor.el.input.vue';
+import XSwitch from './els/page-editor.el.switch.vue';
+import XIf from './els/page-editor.el.if.vue';
 
 export default Vue.extend({
 	components: {
-		XSection, XText, XImage, XButton, XInput, XSwitch
+		XSection, XText, XImage, XButton, XInput, XSwitch, XIf
 	},
 
 	props: {
 		value: {
 			required: true
-		}
+		},
+		aiScript: {
+			required: true,
+		},
 	},
 });
 </script>
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..f39985952b 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')
-					}]
+					items: this.getPageBlockList()
 				},
 				showCancelButton: true
 			});
@@ -324,6 +321,24 @@ export default Vue.extend({
 			this.variables = newValue;
 		},
 
+		getPageBlockList() {
+			return [{
+				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')
+			}, {
+				value: 'if', text: this.$t('blocks.if')
+			}];
+		},
+
 		getScriptBlockList(type: string = null) {
 			const list = [];
 
@@ -436,6 +451,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..e3a758ed4e 100644
--- a/src/client/app/common/views/pages/page/page.block.vue
+++ b/src/client/app/common/views/pages/page/page.block.vue
@@ -10,10 +10,11 @@ import XImage from './page.image.vue';
 import XButton from './page.button.vue';
 import XInput from './page.input.vue';
 import XSwitch from './page.switch.vue';
+import XIf from './page.if.vue';
 
 export default Vue.extend({
 	components: {
-		XText, XSection, XImage, XButton, XInput, XSwitch
+		XText, XSection, XImage, XButton, XInput, 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.input.vue
index cda5550337..9f4cfd91f3 100644
--- a/src/client/app/common/views/pages/page/page.input.vue
+++ b/src/client/app/common/views/pages/page/page.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="value.inputType">{{ script.interpolate(value.text) }}</ui-input>
 </div>
 </template>
 
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..d36ecbfba1 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>
 
diff --git a/src/client/app/common/views/pages/page/page.vue b/src/client/app/common/views/pages/page/page.vue
index e7e8f76d53..7cbd3ed81b 100644
--- a/src/client/app/common/views/pages/page/page.vue
+++ b/src/client/app/common/views/pages/page/page.vue
@@ -38,6 +38,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();