From f655b54937fd9815f37acf48bb9e57b227d7ff0c Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sun, 15 Nov 2020 13:42:04 +0900
Subject: [PATCH] Improve Pages

Resolve #6654
---
 locales/ja-JP.yml                             |  7 ++
 src/client/components/page-preview.vue        |  9 +-
 src/client/components/page/page.block.vue     |  3 +-
 src/client/components/page/page.note.vue      | 39 +++++++++
 .../page-editor/els/page-editor.el.note.vue   | 62 ++++++++++++++
 .../pages/page-editor/page-editor.blocks.vue  |  3 +-
 src/client/pages/page-editor/page-editor.vue  | 85 ++++++++++---------
 7 files changed, 159 insertions(+), 49 deletions(-)
 create mode 100644 src/client/components/page/page.note.vue
 create mode 100644 src/client/pages/page-editor/els/page-editor.el.note.vue

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 7d8ba1753f..ecba573bae 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1104,6 +1104,7 @@ _pages:
   created: "ページを作成しました"
   updated: "ページを更新しました"
   deleted: "ページを削除しました"
+  pageSetting: "ページ設定"
   nameAlreadyExists: "指定されたページURLは既に存在しています"
   invalidNameTitle: "不正なページURLです"
   invalidNameText: "空白でないか確認してください"
@@ -1115,6 +1116,7 @@ _pages:
   my: "自分のページ"
   liked: "いいねしたページ"
   inspector: "インスペクター"
+  contents: "コンテンツ"
   content: "ページブロック"
   variables: "変数"
   title: "タイトル"
@@ -1175,6 +1177,11 @@ _pages:
       width: "幅"
       height: "高さ"
 
+    note: "ノート埋め込み"
+    _note:
+      id: "ノートID"
+      idDescription: "ノートURLをペーストして設定することもできます。"
+
     switch: "スイッチ"
     _switch:
       name: "変数名"
diff --git a/src/client/components/page-preview.vue b/src/client/components/page-preview.vue
index 95ed8d0e38..d59813e00d 100644
--- a/src/client/components/page-preview.vue
+++ b/src/client/components/page-preview.vue
@@ -1,5 +1,5 @@
 <template>
-<MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1">
+<MkA :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj _panel" tabindex="-1">
 	<div class="thumbnail" v-if="page.eyeCatchingImage" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div>
 	<article>
 		<header>
@@ -35,16 +35,11 @@ export default defineComponent({
 <style lang="scss" scoped>
 .vhpxefrj {
 	display: block;
-	overflow: hidden;
 	width: 100%;
-	border: solid var(--lineWidth) var(--urlPreviewBorder);
-	border-radius: 4px;
-	overflow: hidden;
-	border: 1px solid var(--divider);
 
 	&:hover {
 		text-decoration: none;
-		border-color: var(--urlPreviewBorderHover);
+		color: var(--accent);
 	}
 
 	> .thumbnail {
diff --git a/src/client/components/page/page.block.vue b/src/client/components/page/page.block.vue
index 412c91ee0d..be933c8623 100644
--- a/src/client/components/page/page.block.vue
+++ b/src/client/components/page/page.block.vue
@@ -18,10 +18,11 @@ import XPost from './page.post.vue';
 import XCounter from './page.counter.vue';
 import XRadioButton from './page.radio-button.vue';
 import XCanvas from './page.canvas.vue';
+import XNote from './page.note.vue';
 
 export default defineComponent({
 	components: {
-		XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton, XCanvas
+		XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton, XCanvas, XNote
 	},
 	props: {
 		value: {
diff --git a/src/client/components/page/page.note.vue b/src/client/components/page/page.note.vue
new file mode 100644
index 0000000000..4f1df77675
--- /dev/null
+++ b/src/client/components/page/page.note.vue
@@ -0,0 +1,39 @@
+<template>
+<div class="voxdxuby">
+	<XNote v-if="note" v-model:note="note" :key="note.id"/>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import XNote from '@/components/note.vue';
+import * as os from '@/os';
+
+export default defineComponent({
+	components: {
+		XNote
+	},
+	props: {
+		value: {
+			required: true
+		},
+		hpml: {
+			required: true
+		}
+	},
+	data() {
+		return {
+			note: null,
+		};
+	},
+	async mounted() {
+		this.note = await os.api('notes/show', { noteId: this.value.note });
+	}
+});
+</script>
+
+<style lang="scss" scoped>
+.voxdxuby {
+	margin: 1em 0;
+}
+</style>
diff --git a/src/client/pages/page-editor/els/page-editor.el.note.vue b/src/client/pages/page-editor/els/page-editor.el.note.vue
new file mode 100644
index 0000000000..d80623a63e
--- /dev/null
+++ b/src/client/pages/page-editor/els/page-editor.el.note.vue
@@ -0,0 +1,62 @@
+<template>
+<XContainer @remove="() => $emit('remove')" :draggable="true">
+	<template #header><Fa :icon="faStickyNote"/> {{ $t('_pages.blocks.note') }}</template>
+
+	<section style="padding: 0 16px 0 16px;">
+		<MkInput v-model:value="id">
+			<span>{{ $t('_pages.blocks._note.id') }}</span>
+			<template #desc>{{ $t('_pages.blocks._note.idDescription') }}</template>
+		</MkInput>
+
+		<XNote v-if="note" v-model:note="note" :key="note.id" style="margin-bottom: 16px;"/>
+	</section>
+</XContainer>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import { faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import XContainer from '../page-editor.container.vue';
+import MkInput from '@/components/ui/input.vue';
+import XNote from '@/components/note.vue';
+import * as os from '@/os';
+
+export default defineComponent({
+	components: {
+		XContainer, MkInput, XNote
+	},
+
+	props: {
+		value: {
+			required: true
+		},
+	},
+
+	data() {
+		return {
+			id: this.value.note,
+			note: null,
+			faStickyNote
+		};
+	},
+
+	watch: {
+		id: {
+			async handler() {
+				if (this.id && (this.id.startsWith('http://') || this.id.startsWith('https://'))) {
+					this.value.note = this.id.endsWith('/') ? this.id.substr(0, this.id.length - 1).split('/').pop() : this.id.split('/').pop();
+				} else {
+					this.value.note = this.id;
+				}
+
+				this.note = await os.api('notes/show', { noteId: this.value.note });
+			},
+			immediate: true
+		},
+	},
+
+	created() {
+		if (this.value.note == null) this.value.note = null;
+	},
+});
+</script>
diff --git a/src/client/pages/page-editor/page-editor.blocks.vue b/src/client/pages/page-editor/page-editor.blocks.vue
index 48e7fde404..5cf9eb42f7 100644
--- a/src/client/pages/page-editor/page-editor.blocks.vue
+++ b/src/client/pages/page-editor/page-editor.blocks.vue
@@ -20,12 +20,13 @@ import XPost from './els/page-editor.el.post.vue';
 import XCounter from './els/page-editor.el.counter.vue';
 import XRadioButton from './els/page-editor.el.radio-button.vue';
 import XCanvas from './els/page-editor.el.canvas.vue';
+import XNote from './els/page-editor.el.note.vue';
 import * as os from '@/os';
 
 export default defineComponent({
 	components: {
 		XDraggable: defineAsyncComponent(() => import('vue-draggable-next').then(x => x.VueDraggableNext)),
-		XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton, XCanvas
+		XSection, XText, XImage, XButton, XTextarea, XTextInput, XTextareaInput, XNumberInput, XSwitch, XIf, XPost, XCounter, XRadioButton, XCanvas, XNote
 	},
 
 	props: {
diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue
index cd033219f0..30146b56ed 100644
--- a/src/client/pages/page-editor/page-editor.vue
+++ b/src/client/pages/page-editor/page-editor.vue
@@ -1,57 +1,54 @@
 <template>
 <div class="_section">
 	<div class="_content">
-		<div class="gwbmwxkm _panel _vMargin">
-			<header>
-				<div class="title"><Fa :icon="faStickyNote"/> {{ readonly ? $t('_pages.readPage') : pageId ? $t('_pages.editPage') : $t('_pages.newPage') }}</div>
-				<div class="buttons">
-					<button class="_button" @click="del()" v-if="!readonly"><Fa :icon="faTrashAlt"/></button>
-					<button class="_button" @click="() => showOptions = !showOptions"><Fa :icon="faCog"/></button>
-					<button class="_button" @click="save()" v-if="!readonly"><Fa :icon="faSave"/></button>
-				</div>
-			</header>
+		<MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $t('_pages.viewPage') }}</MkA>
 
-			<section>
-				<MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $t('_pages.viewPage') }}</MkA>
+		<MkButton @click="save" primary class="save" style="margin: 16px auto 16px auto;"><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
 
+		<MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
+			<template #header><Fa :icon="faCog"/> {{ $t('_pages.pageSetting') }}</template>
+			<div class="_section">
 				<MkInput v-model:value="title">
 					<span>{{ $t('_pages.title') }}</span>
 				</MkInput>
 
-				<template v-if="showOptions">
-					<MkInput v-model:value="summary">
-						<span>{{ $t('_pages.summary') }}</span>
-					</MkInput>
+				<MkInput v-model:value="summary">
+					<span>{{ $t('_pages.summary') }}</span>
+				</MkInput>
 
-					<MkInput v-model:value="name">
-						<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
-						<span>{{ $t('_pages.url') }}</span>
-					</MkInput>
+				<MkInput v-model:value="name">
+					<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
+					<span>{{ $t('_pages.url') }}</span>
+				</MkInput>
 
-					<MkSwitch v-model:value="alignCenter">{{ $t('_pages.alignCenter') }}</MkSwitch>
+				<MkSwitch v-model:value="alignCenter">{{ $t('_pages.alignCenter') }}</MkSwitch>
 
-					<MkSelect v-model:value="font">
-						<template #label>{{ $t('_pages.font') }}</template>
-						<option value="serif">{{ $t('_pages.fontSerif') }}</option>
-						<option value="sans-serif">{{ $t('_pages.fontSansSerif') }}</option>
-					</MkSelect>
+				<MkSelect v-model:value="font">
+					<template #label>{{ $t('_pages.font') }}</template>
+					<option value="serif">{{ $t('_pages.fontSerif') }}</option>
+					<option value="sans-serif">{{ $t('_pages.fontSansSerif') }}</option>
+				</MkSelect>
 
-					<MkSwitch v-model:value="hideTitleWhenPinned">{{ $t('_pages.hideTitleWhenPinned') }}</MkSwitch>
+				<MkSwitch v-model:value="hideTitleWhenPinned">{{ $t('_pages.hideTitleWhenPinned') }}</MkSwitch>
 
-					<div class="eyeCatch">
-						<MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><Fa :icon="faPlus"/> {{ $t('_pages.eyeCatchingImageSet') }}</MkButton>
-						<div v-else-if="eyeCatchingImage">
-							<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name"/>
-							<MkButton @click="removeEyeCatchingImage()" v-if="!readonly"><Fa :icon="faTrashAlt"/> {{ $t('_pages.eyeCatchingImageRemove') }}</MkButton>
-						</div>
+				<div class="eyeCatch">
+					<MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><Fa :icon="faPlus"/> {{ $t('_pages.eyeCatchingImageSet') }}</MkButton>
+					<div v-else-if="eyeCatchingImage">
+						<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/>
+						<MkButton @click="removeEyeCatchingImage()" v-if="!readonly"><Fa :icon="faTrashAlt"/> {{ $t('_pages.eyeCatchingImageRemove') }}</MkButton>
 					</div>
-				</template>
+				</div>
+			</div>
+		</MkContainer>
 
+		<MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
+			<template #header><Fa :icon="faStickyNote"/> {{ $t('_pages.contents') }}</template>
+			<div class="_section">
 				<XBlocks class="content" v-model:value="content" :hpml="hpml"/>
 
 				<MkButton @click="add()" v-if="!readonly"><Fa :icon="faPlus"/></MkButton>
-			</section>
-		</div>
+			</div>
+		</MkContainer>
 
 		<MkContainer :body-togglable="true" class="_vMargin">
 			<template #header><Fa :icon="faMagic"/> {{ $t('_pages.variables') }}</template>
@@ -85,14 +82,14 @@
 </template>
 
 <script lang="ts">
-import { defineComponent, defineAsyncComponent } from 'vue';
+import { defineComponent, defineAsyncComponent, computed } from 'vue';
 import 'prismjs';
 import { highlight, languages } from 'prismjs/components/prism-core';
 import 'prismjs/components/prism-clike';
 import 'prismjs/components/prism-javascript';
 import 'prismjs/themes/prism-okaidia.css';
 import 'vue-prism-editor/dist/prismeditor.min.css';
-import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
+import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
 import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
 import { v4 as uuid } from 'uuid';
 import XVariable from './page-editor.script-block.vue';
@@ -108,6 +105,7 @@ import { HpmlTypeChecker } from '@/scripts/hpml/type-checker';
 import { url } from '@/config';
 import { collectPageVars } from '@/scripts/collect-page-vars';
 import * as os from '@/os';
+import { selectFile } from '@/scripts/select-file';
 
 export default defineComponent({
 	components: {
@@ -132,6 +130,13 @@ export default defineComponent({
 
 	data() {
 		return {
+			INFO: computed(() => this.initPageId ? {
+				title: this.$t('_pages.editPage'),
+				icon: faPencilAlt,
+			} : {
+				title: this.$t('_pages.newPage'),
+				icon: faPencilAlt,
+			}),
 			author: this.$store.state.i,
 			readonly: false,
 			page: null,
@@ -149,7 +154,6 @@ export default defineComponent({
 			variables: [],
 			hpml: null,
 			script: '',
-			showOptions: false,
 			url,
 			faPlus, faICursor, faSave, faStickyNote, faMagic, faCog, faTrashAlt, faExternalLinkSquareAlt, faCode
 		};
@@ -353,6 +357,7 @@ export default defineComponent({
 					{ value: 'text', text: this.$t('_pages.blocks.text') },
 					{ value: 'image', text: this.$t('_pages.blocks.image') },
 					{ value: 'textarea', text: this.$t('_pages.blocks.textarea') },
+					{ value: 'note', text: this.$t('_pages.blocks.note') },
 					{ value: 'canvas', text: this.$t('_pages.blocks.canvas') },
 				]
 			}, {
@@ -413,8 +418,8 @@ export default defineComponent({
 			return list;
 		},
 
-		setEyeCatchingImage() {
-			os.selectDriveFile(false).then(file => {
+		setEyeCatchingImage(e) {
+			selectFile(e.currentTarget || e.target, null, false).then(file => {
 				this.eyeCatchingImageId = file.id;
 			});
 		},