Improve Page
* ページをピン留めできるように * デッキでカラム内でページを見れるように
This commit is contained in:
		
							parent
							
								
									4b747d20b7
								
							
						
					
					
						commit
						bd8d7c3d0f
					
				
					 32 changed files with 295 additions and 55 deletions
				
			
		|  | @ -1907,6 +1907,7 @@ deck/deck.user-column.vue: | ||||||
|   activity: "アクティビティ" |   activity: "アクティビティ" | ||||||
|   timeline: "タイムライン" |   timeline: "タイムライン" | ||||||
|   pinned-notes: "ピン留めされた投稿" |   pinned-notes: "ピン留めされた投稿" | ||||||
|  |   pinned-page: "ピン留めされたページ" | ||||||
| 
 | 
 | ||||||
| docs: | docs: | ||||||
|   edit-this-page-on-github: "間違いや改善点を見つけましたか?" |   edit-this-page-on-github: "間違いや改善点を見つけましたか?" | ||||||
|  | @ -1946,6 +1947,8 @@ pages: | ||||||
|   are-you-sure-delete: "このページを削除しますか?" |   are-you-sure-delete: "このページを削除しますか?" | ||||||
|   page-deleted: "ページを削除しました" |   page-deleted: "ページを削除しました" | ||||||
|   edit-this-page: "このページを編集" |   edit-this-page: "このページを編集" | ||||||
|  |   pin-this-page: "ピン留め" | ||||||
|  |   unpin-this-page: "ピン留め解除" | ||||||
|   view-source: "ソースを表示" |   view-source: "ソースを表示" | ||||||
|   view-page: "ページを見る" |   view-page: "ページを見る" | ||||||
|   like: "いいね" |   like: "いいね" | ||||||
|  | @ -1964,6 +1967,7 @@ pages: | ||||||
|   url: "ページURL" |   url: "ページURL" | ||||||
|   summary: "ページの要約" |   summary: "ページの要約" | ||||||
|   align-center: "中央寄せ" |   align-center: "中央寄せ" | ||||||
|  |   hide-title-when-pinned: "ピン留めされているときにタイトルを非表示" | ||||||
|   font: "フォント" |   font: "フォント" | ||||||
|   fontSerif: "セリフ" |   fontSerif: "セリフ" | ||||||
|   fontSansSerif: "サンセリフ" |   fontSansSerif: "サンセリフ" | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								migration/1562444565093-PinnedPage.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								migration/1562444565093-PinnedPage.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | import {MigrationInterface, QueryRunner} from "typeorm"; | ||||||
|  | 
 | ||||||
|  | export class PinnedPage1562444565093 implements MigrationInterface { | ||||||
|  | 
 | ||||||
|  |     public async up(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_profile" ADD "pinnedPageId" character varying(32)`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "UQ_6dc44f1ceb65b1e72bacef2ca27" UNIQUE ("pinnedPageId")`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "FK_6dc44f1ceb65b1e72bacef2ca27" FOREIGN KEY ("pinnedPageId") REFERENCES "page"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public async down(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "FK_6dc44f1ceb65b1e72bacef2ca27"`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "UQ_6dc44f1ceb65b1e72bacef2ca27"`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "pinnedPageId"`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								migration/1562448332510-PageTitleHideOption.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								migration/1562448332510-PageTitleHideOption.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | import {MigrationInterface, QueryRunner} from "typeorm"; | ||||||
|  | 
 | ||||||
|  | export class PageTitleHideOption1562448332510 implements MigrationInterface { | ||||||
|  | 
 | ||||||
|  |     public async up(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "page" ADD "hideTitleWhenPinned" boolean NOT NULL DEFAULT false`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public async down(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "hideTitleWhenPinned"`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -49,7 +49,7 @@ export default Vue.extend({ | ||||||
| <style lang="stylus" scoped> | <style lang="stylus" scoped> | ||||||
| .kudkigyw | .kudkigyw | ||||||
| 	display inline-block | 	display inline-block | ||||||
| 	min-width 300px | 	min-width 200px | ||||||
| 	max-width 450px | 	max-width 450px | ||||||
| 	margin 8px 0 | 	margin 8px 0 | ||||||
| </style> | </style> | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| <template> | <template> | ||||||
| <div v-if="page" class="iroscrza" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners, center: page.alignCenter }" :style="{ fontFamily: page.font }" :key="path"> | <div class="iroscrza" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners, center: page.alignCenter }" :style="{ fontFamily: page.font }"> | ||||||
| 	<header> | 	<header v-if="showTitle"> | ||||||
| 		<div class="title">{{ page.title }}</div> | 		<div class="title">{{ page.title }}</div> | ||||||
| 	</header> | 	</header> | ||||||
| 
 | 
 | ||||||
|  | @ -8,9 +8,13 @@ | ||||||
| 		<x-block v-for="child in page.content" :value="child" @input="v => updateBlock(v)" :page="page" :script="script" :key="child.id" :h="2"/> | 		<x-block v-for="child in page.content" :value="child" @input="v => updateBlock(v)" :page="page" :script="script" :key="child.id" :h="2"/> | ||||||
| 	</div> | 	</div> | ||||||
| 
 | 
 | ||||||
| 	<footer> | 	<footer v-if="showFooter"> | ||||||
| 		<small>@{{ page.user.username }}</small> | 		<small>@{{ page.user.username }}</small> | ||||||
| 		<router-link v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId" :to="`/i/pages/edit/${page.id}`">{{ $t('edit-this-page') }}</router-link> | 		<template v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId"> | ||||||
|  | 			<router-link :to="`/i/pages/edit/${page.id}`">{{ $t('edit-this-page') }}</router-link> | ||||||
|  | 			<a v-if="$store.state.i.pinnedPageId === page.id" @click="pin(false)">{{ $t('unpin-this-page') }}</a> | ||||||
|  | 			<a v-else @click="pin(true)">{{ $t('pin-this-page') }}</a> | ||||||
|  | 		</template> | ||||||
| 		<router-link :to="`./${page.name}/view-source`">{{ $t('view-source') }}</router-link> | 		<router-link :to="`./${page.name}/view-source`">{{ $t('view-source') }}</router-link> | ||||||
| 		<div class="like"> | 		<div class="like"> | ||||||
| 			<button @click="unlike()" v-if="page.isLiked" :title="$t('unlike')"><fa :icon="faHeartS"/></button> | 			<button @click="unlike()" v-if="page.isLiked" :title="$t('unlike')"><fa :icon="faHeartS"/></button> | ||||||
|  | @ -25,7 +29,7 @@ | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import i18n from '../../../../i18n'; | import i18n from '../../../../i18n'; | ||||||
| import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons'; | import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { faHeart, faStickyNote } from '@fortawesome/free-regular-svg-icons'; | import { faHeart } from '@fortawesome/free-regular-svg-icons'; | ||||||
| import XBlock from './page.block.vue'; | import XBlock from './page.block.vue'; | ||||||
| import { ASEvaluator } from '../../../../../../misc/aiscript/evaluator'; | import { ASEvaluator } from '../../../../../../misc/aiscript/evaluator'; | ||||||
| import { collectPageVars } from '../../../scripts/collect-page-vars'; | import { collectPageVars } from '../../../scripts/collect-page-vars'; | ||||||
|  | @ -69,64 +73,43 @@ export default Vue.extend({ | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	props: { | 	props: { | ||||||
| 		pageName: { | 		page: { | ||||||
| 			type: String, | 			type: Object, | ||||||
| 			required: true | 			required: true | ||||||
| 		}, | 		}, | ||||||
| 		username: { | 		showTitle: { | ||||||
| 			type: String, | 			type: Boolean, | ||||||
| 			required: true | 			required: false, | ||||||
|  | 			default: true | ||||||
|  | 		}, | ||||||
|  | 		showFooter: { | ||||||
|  | 			type: Boolean, | ||||||
|  | 			required: false, | ||||||
|  | 			default: false | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			page: null, |  | ||||||
| 			script: null, | 			script: null, | ||||||
| 			faHeartS, faHeart | 			faHeartS, faHeart | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	computed: { |  | ||||||
| 		path(): string { |  | ||||||
| 			return this.username + '/' + this.pageName; |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	watch: { |  | ||||||
| 		path() { |  | ||||||
| 			this.fetch(); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	created() { | 	created() { | ||||||
| 		this.fetch(); | 		const pageVars = this.getPageVars(); | ||||||
|  | 		this.script = new Script(this.page, new ASEvaluator(this.page.variables, pageVars, { | ||||||
|  | 			randomSeed: Math.random(), | ||||||
|  | 			user: this.page.user, | ||||||
|  | 			visitor: this.$store.state.i, | ||||||
|  | 			page: this.page, | ||||||
|  | 			url: url | ||||||
|  | 		}), e => { | ||||||
|  | 			console.dir(e); | ||||||
|  | 		}); | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	methods: { | 	methods: { | ||||||
| 		fetch() { |  | ||||||
| 			this.$root.api('pages/show', { |  | ||||||
| 				name: this.pageName, |  | ||||||
| 				username: this.username, |  | ||||||
| 			}).then(page => { |  | ||||||
| 				this.page = page; |  | ||||||
| 				this.$emit('init', { |  | ||||||
| 					title: this.page.title, |  | ||||||
| 					icon: faStickyNote |  | ||||||
| 				}); |  | ||||||
| 				const pageVars = this.getPageVars(); |  | ||||||
| 				this.script = new Script(this.page, new ASEvaluator(this.page.variables, pageVars, { |  | ||||||
| 					randomSeed: Math.random(), |  | ||||||
| 					user: page.user, |  | ||||||
| 					visitor: this.$store.state.i, |  | ||||||
| 					page: page, |  | ||||||
| 					url: url |  | ||||||
| 				}), e => { |  | ||||||
| 					console.dir(e); |  | ||||||
| 				}); |  | ||||||
| 			}); |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		getPageVars() { | 		getPageVars() { | ||||||
| 			return collectPageVars(this.page.content); | 			return collectPageVars(this.page.content); | ||||||
| 		}, | 		}, | ||||||
|  | @ -147,6 +130,17 @@ export default Vue.extend({ | ||||||
| 				this.page.isLiked = false; | 				this.page.isLiked = false; | ||||||
| 				this.page.likedCount--; | 				this.page.likedCount--; | ||||||
| 			}); | 			}); | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		pin(pin) { | ||||||
|  | 			this.$root.api('i/update', { | ||||||
|  | 				pinnedPageId: pin ? this.page.id : null, | ||||||
|  | 			}).then(() => { | ||||||
|  | 				this.$root.dialog({ | ||||||
|  | 					type: 'success', | ||||||
|  | 					splash: true | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
							
								
								
									
										69
									
								
								src/client/app/common/views/deck/deck.page-column.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/client/app/common/views/deck/deck.page-column.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | ||||||
|  | <template> | ||||||
|  | <x-column> | ||||||
|  | 	<template #header> | ||||||
|  | 		<fa :icon="faStickyNote"/>{{ page ? page.name : '' }} | ||||||
|  | 	</template> | ||||||
|  | 
 | ||||||
|  | 	<div v-if="page"> | ||||||
|  | 		<x-page :page="page" :key="page.id"/> | ||||||
|  | 	</div> | ||||||
|  | </x-column> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import { faStickyNote } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  | import i18n from '../../../i18n'; | ||||||
|  | import XColumn from './deck.column.vue'; | ||||||
|  | import XPage from '../../../common/views/components/page/page.vue'; | ||||||
|  | 
 | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	i18n: i18n(), | ||||||
|  | 
 | ||||||
|  | 	components: { | ||||||
|  | 		XColumn, | ||||||
|  | 		XPage | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	props: { | ||||||
|  | 		pageName: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 		username: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			page: null, | ||||||
|  | 			faStickyNote | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	watch: { | ||||||
|  | 		$route: 'fetch' | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	created() { | ||||||
|  | 		this.fetch(); | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	methods: { | ||||||
|  | 		fetch() { | ||||||
|  | 			this.$root.api('pages/show', { | ||||||
|  | 				name: this.pageName, | ||||||
|  | 				username: this.username, | ||||||
|  | 			}).then(page => { | ||||||
|  | 				this.page = page; | ||||||
|  | 				this.$emit('init', { | ||||||
|  | 					title: this.page.title, | ||||||
|  | 					icon: faStickyNote | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | @ -1,5 +1,11 @@ | ||||||
| <template> | <template> | ||||||
| <div> | <div> | ||||||
|  | 	<ui-container v-if="user.pinnedPage" :body-togglable="true"> | ||||||
|  | 		<template #header><fa icon="thumbtack"/> {{ $t('pinned-page') }}</template> | ||||||
|  | 		<div> | ||||||
|  | 			<x-page :page="user.pinnedPage" :key="user.pinnedPage.id" :show-title="!user.pinnedPage.hideTitleWhenPinned"/> | ||||||
|  | 		</div> | ||||||
|  | 	</ui-container> | ||||||
| 	<ui-container v-if="user.pinnedNotes && user.pinnedNotes.length > 0" :body-togglable="true"> | 	<ui-container v-if="user.pinnedNotes && user.pinnedNotes.length > 0" :body-togglable="true"> | ||||||
| 		<template #header><fa icon="thumbtack"/> {{ $t('pinned-notes') }}</template> | 		<template #header><fa icon="thumbtack"/> {{ $t('pinned-notes') }}</template> | ||||||
| 		<div> | 		<div> | ||||||
|  | @ -48,6 +54,7 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| 	components: { | 	components: { | ||||||
| 		XNotes, | 		XNotes, | ||||||
|  | 		XPage: () => import('../../../common/views/components/page/page.vue').then(m => m.default), | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	props: { | 	props: { | ||||||
|  |  | ||||||
|  | @ -35,6 +35,8 @@ | ||||||
| 					<option value="sans-serif">{{ $t('fontSansSerif') }}</option> | 					<option value="sans-serif">{{ $t('fontSansSerif') }}</option> | ||||||
| 				</ui-select> | 				</ui-select> | ||||||
| 
 | 
 | ||||||
|  | 				<ui-switch v-model="hideTitleWhenPinned">{{ $t('hide-title-when-pinned') }}</ui-switch> | ||||||
|  | 
 | ||||||
| 				<div class="eyeCatch"> | 				<div class="eyeCatch"> | ||||||
| 					<ui-button v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('set-eye-catching-image') }}</ui-button> | 					<ui-button v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('set-eye-catching-image') }}</ui-button> | ||||||
| 					<div v-else-if="eyeCatchingImage"> | 					<div v-else-if="eyeCatchingImage"> | ||||||
|  | @ -140,6 +142,7 @@ export default Vue.extend({ | ||||||
| 			font: 'sans-serif', | 			font: 'sans-serif', | ||||||
| 			content: [], | 			content: [], | ||||||
| 			alignCenter: false, | 			alignCenter: false, | ||||||
|  | 			hideTitleWhenPinned: false, | ||||||
| 			variables: [], | 			variables: [], | ||||||
| 			aiScript: null, | 			aiScript: null, | ||||||
| 			showOptions: false, | 			showOptions: false, | ||||||
|  | @ -192,6 +195,7 @@ export default Vue.extend({ | ||||||
| 			this.currentName = this.page.name; | 			this.currentName = this.page.name; | ||||||
| 			this.summary = this.page.summary; | 			this.summary = this.page.summary; | ||||||
| 			this.font = this.page.font; | 			this.font = this.page.font; | ||||||
|  | 			this.hideTitleWhenPinned = this.page.hideTitleWhenPinned; | ||||||
| 			this.alignCenter = this.page.alignCenter; | 			this.alignCenter = this.page.alignCenter; | ||||||
| 			this.content = this.page.content; | 			this.content = this.page.content; | ||||||
| 			this.variables = this.page.variables; | 			this.variables = this.page.variables; | ||||||
|  | @ -223,6 +227,7 @@ export default Vue.extend({ | ||||||
| 					name: this.name.trim(), | 					name: this.name.trim(), | ||||||
| 					summary: this.summary, | 					summary: this.summary, | ||||||
| 					font: this.font, | 					font: this.font, | ||||||
|  | 					hideTitleWhenPinned: this.hideTitleWhenPinned, | ||||||
| 					alignCenter: this.alignCenter, | 					alignCenter: this.alignCenter, | ||||||
| 					content: this.content, | 					content: this.content, | ||||||
| 					variables: this.variables, | 					variables: this.variables, | ||||||
|  | @ -240,6 +245,7 @@ export default Vue.extend({ | ||||||
| 					name: this.name.trim(), | 					name: this.name.trim(), | ||||||
| 					summary: this.summary, | 					summary: this.summary, | ||||||
| 					font: this.font, | 					font: this.font, | ||||||
|  | 					hideTitleWhenPinned: this.hideTitleWhenPinned, | ||||||
| 					alignCenter: this.alignCenter, | 					alignCenter: this.alignCenter, | ||||||
| 					content: this.content, | 					content: this.content, | ||||||
| 					variables: this.variables, | 					variables: this.variables, | ||||||
|  |  | ||||||
							
								
								
									
										63
									
								
								src/client/app/common/views/pages/page.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/client/app/common/views/pages/page.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | ||||||
|  | <template> | ||||||
|  | <x-page v-if="page" :page="page" :key="page.id" :show-footer="true"/> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import { faStickyNote } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  | import XPage from '../components/page/page.vue'; | ||||||
|  | 
 | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	components: { | ||||||
|  | 		XPage | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	props: { | ||||||
|  | 		pageName: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 		username: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			page: null, | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	computed: { | ||||||
|  | 		path(): string { | ||||||
|  | 			return this.username + '/' + this.pageName; | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	watch: { | ||||||
|  | 		path() { | ||||||
|  | 			this.fetch(); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	created() { | ||||||
|  | 		this.fetch(); | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	methods: { | ||||||
|  | 		fetch() { | ||||||
|  | 			this.$root.api('pages/show', { | ||||||
|  | 				name: this.pageName, | ||||||
|  | 				username: this.username, | ||||||
|  | 			}).then(page => { | ||||||
|  | 				this.page = page; | ||||||
|  | 				this.$emit('init', { | ||||||
|  | 					title: this.page.title, | ||||||
|  | 					icon: faStickyNote | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | @ -148,6 +148,7 @@ init(async (launch, os) => { | ||||||
| 					{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) }, | 					{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) }, | ||||||
| 					{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) }, | 					{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) }, | ||||||
| 					{ path: '/i/follow-requests', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) }, | 					{ path: '/i/follow-requests', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) }, | ||||||
|  | 					{ path: '/@:username/pages/:pageName', name: 'page', props: true, component: () => import('../common/views/deck/deck.page-column.vue').then(m => m.default) }, | ||||||
| 				]} | 				]} | ||||||
| 				: { path: '/', component: MkHome, children: [ | 				: { path: '/', component: MkHome, children: [ | ||||||
| 					{ path: '', name: 'index', component: MkHomeTimeline }, | 					{ path: '', name: 'index', component: MkHomeTimeline }, | ||||||
|  | @ -171,12 +172,11 @@ init(async (launch, os) => { | ||||||
| 					{ path: '/i/follow-requests', component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }, | 					{ path: '/i/follow-requests', component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }, | ||||||
| 					{ path: '/i/pages/new', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) }, | 					{ path: '/i/pages/new', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) }, | ||||||
| 					{ path: '/i/pages/edit/:pageId', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initPageId: route.params.pageId }) }, | 					{ path: '/i/pages/edit/:pageId', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initPageId: route.params.pageId }) }, | ||||||
| 					{ path: '/@:user/pages/:page', component: () => import('../common/views/pages/page/page.vue').then(m => m.default), props: route => ({ pageName: route.params.page, username: route.params.user }) }, | 					{ path: '/@:user/pages/:page', component: () => import('../common/views/pages/page.vue').then(m => m.default), props: route => ({ pageName: route.params.page, username: route.params.user }) }, | ||||||
| 					{ path: '/@:user/pages/:pageName/view-source', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, | 					{ path: '/@:user/pages/:pageName/view-source', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, | ||||||
| 				]}, | 				]}, | ||||||
| 			{ path: '/i/pages/new', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) }, | 			{ path: '/i/pages/new', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default) }, | ||||||
| 			{ path: '/i/pages/edit/:pageId', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initPageId: route.params.pageId }) }, | 			{ path: '/i/pages/edit/:pageId', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initPageId: route.params.pageId }) }, | ||||||
| 			{ path: '/@:user/pages/:page', component: () => import('../common/views/pages/page/page.vue').then(m => m.default), props: route => ({ pageName: route.params.page, username: route.params.user }) }, |  | ||||||
| 			{ path: '/@:user/pages/:pageName/view-source', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, | 			{ path: '/@:user/pages/:pageName/view-source', component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, | ||||||
| 			{ path: '/i/messaging/group/:group', component: MkMessagingRoom }, | 			{ path: '/i/messaging/group/:group', component: MkMessagingRoom }, | ||||||
| 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| <template> | <template> | ||||||
| <div class="lnctpgve"> | <div class="lnctpgve"> | ||||||
|  | 	<x-page v-if="user.pinnedPage" :page="user.pinnedPage" :key="user.pinnedPage.id" :show-title="!user.pinnedPage.hideTitleWhenPinned"/> | ||||||
| 	<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/> | 	<mk-note-detail v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/> | ||||||
| 	<!--<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>--> | 	<!--<mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/>--> | ||||||
| 	<div class="activity"> | 	<div class="activity"> | ||||||
|  | @ -21,13 +22,15 @@ import i18n from '../../../../i18n'; | ||||||
| import XTimeline from './user.timeline.vue'; | import XTimeline from './user.timeline.vue'; | ||||||
| import XPhotos from './user.photos.vue'; | import XPhotos from './user.photos.vue'; | ||||||
| import XActivity from '../../../../common/views/components/activity.vue'; | import XActivity from '../../../../common/views/components/activity.vue'; | ||||||
|  | import XPage from '../../../../common/views/components/page/page.vue'; | ||||||
| 
 | 
 | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n(), | 	i18n: i18n(), | ||||||
| 	components: { | 	components: { | ||||||
| 		XTimeline, | 		XTimeline, | ||||||
| 		XPhotos, | 		XPhotos, | ||||||
| 		XActivity | 		XActivity, | ||||||
|  | 		XPage, | ||||||
| 	}, | 	}, | ||||||
| 	props: { | 	props: { | ||||||
| 		user: { | 		user: { | ||||||
|  |  | ||||||
|  | @ -129,6 +129,7 @@ init((launch, os) => { | ||||||
| 					{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) }, | 					{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) }, | ||||||
| 					{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) }, | 					{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) }, | ||||||
| 					{ path: '/i/follow-requests', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) }, | 					{ path: '/i/follow-requests', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) }, | ||||||
|  | 					{ path: '/@:username/pages/:pageName', name: 'page', props: true, component: () => import('../common/views/deck/deck.page-column.vue').then(m => m.default) }, | ||||||
| 				]}] | 				]}] | ||||||
| 			: [ | 			: [ | ||||||
| 				{ path: '/', name: 'index', component: MkIndex }, | 				{ path: '/', name: 'index', component: MkIndex }, | ||||||
|  | @ -163,7 +164,7 @@ init((launch, os) => { | ||||||
| 				{ path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) }, | 				{ path: 'following', component: () => import('../common/views/pages/following.vue').then(m => m.default) }, | ||||||
| 				{ path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) }, | 				{ path: 'followers', component: () => import('../common/views/pages/followers.vue').then(m => m.default) }, | ||||||
| 			]}, | 			]}, | ||||||
| 			{ path: '/@:user/pages/:page', component: UI, props: route => ({ component: () => import('../common/views/pages/page/page.vue').then(m => m.default), pageName: route.params.page, username: route.params.user }) }, | 			{ path: '/@:user/pages/:page', component: UI, props: route => ({ component: () => import('../common/views/pages/page.vue').then(m => m.default), pageName: route.params.page, username: route.params.user }) }, | ||||||
| 			{ path: '/@:user/pages/:pageName/view-source', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initUser: route.params.user, initPageName: route.params.pageName }) }, | 			{ path: '/@:user/pages/:pageName/view-source', component: UI, props: route => ({ component: () => import('../common/views/pages/page-editor/page-editor.vue').then(m => m.default), initUser: route.params.user, initPageName: route.params.pageName }) }, | ||||||
| 			{ path: '/notes/:note', component: MkNote }, | 			{ path: '/notes/:note', component: MkNote }, | ||||||
| 			{ path: '/authorize-follow', component: MkFollow }, | 			{ path: '/authorize-follow', component: MkFollow }, | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| <template> | <template> | ||||||
| <div class="wojmldye"> | <div class="wojmldye"> | ||||||
|  | 	<x-page class="page" v-if="user.pinnedPage" :page="user.pinnedPage" :key="user.pinnedPage.id" :show-title="!user.pinnedPage.hideTitleWhenPinned"/> | ||||||
| 	<mk-note-detail class="note" v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/> | 	<mk-note-detail class="note" v-for="n in user.pinnedNotes" :key="n.id" :note="n" :compact="true"/> | ||||||
| 	<ui-container :body-togglable="true"> | 	<ui-container :body-togglable="true"> | ||||||
| 		<template #header><fa :icon="['far', 'comments']"/>{{ $t('recent-notes') }}</template> | 		<template #header><fa :icon="['far', 'comments']"/>{{ $t('recent-notes') }}</template> | ||||||
|  | @ -33,6 +34,7 @@ export default Vue.extend({ | ||||||
| 	components: { | 	components: { | ||||||
| 		XNotes, | 		XNotes, | ||||||
| 		XPhotos, | 		XPhotos, | ||||||
|  | 		XPage: () => import('../../../../common/views/components/page/page.vue').then(m => m.default), | ||||||
| 		XActivity: () => import('../../../../common/views/components/activity.vue').then(m => m.default) | 		XActivity: () => import('../../../../common/views/components/activity.vue').then(m => m.default) | ||||||
| 	}, | 	}, | ||||||
| 	props: ['user'], | 	props: ['user'], | ||||||
|  | @ -53,6 +55,12 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| <style lang="stylus" scoped> | <style lang="stylus" scoped> | ||||||
| .wojmldye | .wojmldye | ||||||
|  | 	> .page | ||||||
|  | 		margin 0 0 8px 0 | ||||||
|  | 
 | ||||||
|  | 		@media (min-width 500px) | ||||||
|  | 			margin 0 0 16px 0 | ||||||
|  | 	 | ||||||
| 	> .note | 	> .note | ||||||
| 		margin 0 0 8px 0 | 		margin 0 0 8px 0 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,6 +40,11 @@ export class Page { | ||||||
| 	@Column('boolean') | 	@Column('boolean') | ||||||
| 	public alignCenter: boolean; | 	public alignCenter: boolean; | ||||||
| 
 | 
 | ||||||
|  | 	@Column('boolean', { | ||||||
|  | 		default: false | ||||||
|  | 	}) | ||||||
|  | 	public hideTitleWhenPinned: boolean; | ||||||
|  | 
 | ||||||
| 	@Column('varchar', { | 	@Column('varchar', { | ||||||
| 		length: 32, | 		length: 32, | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; | import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; | ||||||
| import { id } from '../id'; | import { id } from '../id'; | ||||||
| import { User } from './user'; | import { User } from './user'; | ||||||
|  | import { Page } from './page'; | ||||||
| 
 | 
 | ||||||
| @Entity() | @Entity() | ||||||
| export class UserProfile { | export class UserProfile { | ||||||
|  | @ -118,6 +119,18 @@ export class UserProfile { | ||||||
| 	}) | 	}) | ||||||
| 	public carefulBot: boolean; | 	public carefulBot: boolean; | ||||||
| 
 | 
 | ||||||
|  | 	@Column({ | ||||||
|  | 		...id(), | ||||||
|  | 		nullable: true | ||||||
|  | 	}) | ||||||
|  | 	public pinnedPageId: Page['id'] | null; | ||||||
|  | 
 | ||||||
|  | 	@OneToOne(type => Page, { | ||||||
|  | 		onDelete: 'SET NULL' | ||||||
|  | 	}) | ||||||
|  | 	@JoinColumn() | ||||||
|  | 	public pinnedPage: Page | null; | ||||||
|  | 
 | ||||||
| 	//#region Linking
 | 	//#region Linking
 | ||||||
| 	@Column('boolean', { | 	@Column('boolean', { | ||||||
| 		default: false, | 		default: false, | ||||||
|  |  | ||||||
|  | @ -71,6 +71,7 @@ export class PageRepository extends Repository<Page> { | ||||||
| 			title: page.title, | 			title: page.title, | ||||||
| 			name: page.name, | 			name: page.name, | ||||||
| 			summary: page.summary, | 			summary: page.summary, | ||||||
|  | 			hideTitleWhenPinned: page.hideTitleWhenPinned, | ||||||
| 			alignCenter: page.alignCenter, | 			alignCenter: page.alignCenter, | ||||||
| 			font: page.font, | 			font: page.font, | ||||||
| 			eyeCatchingImageId: page.eyeCatchingImageId, | 			eyeCatchingImageId: page.eyeCatchingImageId, | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import $ from 'cafy'; | import $ from 'cafy'; | ||||||
| import { EntityRepository, Repository, In } from 'typeorm'; | import { EntityRepository, Repository, In } from 'typeorm'; | ||||||
| import { User, ILocalUser, IRemoteUser } from '../entities/user'; | import { User, ILocalUser, IRemoteUser } from '../entities/user'; | ||||||
| import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings } from '..'; | import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages } from '..'; | ||||||
| import { ensure } from '../../prelude/ensure'; | import { ensure } from '../../prelude/ensure'; | ||||||
| import config from '../../config'; | import config from '../../config'; | ||||||
| import { SchemaType } from '../../misc/schema'; | import { SchemaType } from '../../misc/schema'; | ||||||
|  | @ -155,6 +155,8 @@ export class UserRepository extends Repository<User> { | ||||||
| 				pinnedNotes: Notes.packMany(pins.map(pin => pin.noteId), meId, { | 				pinnedNotes: Notes.packMany(pins.map(pin => pin.noteId), meId, { | ||||||
| 					detail: true | 					detail: true | ||||||
| 				}), | 				}), | ||||||
|  | 				pinnedPageId: profile!.pinnedPageId, | ||||||
|  | 				pinnedPage: profile!.pinnedPageId ? Pages.pack(profile!.pinnedPageId, meId) : null, | ||||||
| 				twoFactorEnabled: profile!.twoFactorEnabled, | 				twoFactorEnabled: profile!.twoFactorEnabled, | ||||||
| 				usePasswordLessLogin: profile!.usePasswordLessLogin, | 				usePasswordLessLogin: profile!.usePasswordLessLogin, | ||||||
| 				securityKeys: profile!.twoFactorEnabled | 				securityKeys: profile!.twoFactorEnabled | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ import extractHashtags from '../../../../misc/extract-hashtags'; | ||||||
| import * as langmap from 'langmap'; | import * as langmap from 'langmap'; | ||||||
| import { updateHashtag } from '../../../../services/update-hashtag'; | import { updateHashtag } from '../../../../services/update-hashtag'; | ||||||
| import { ApiError } from '../../error'; | import { ApiError } from '../../error'; | ||||||
| import { Users, DriveFiles, UserProfiles } from '../../../../models'; | import { Users, DriveFiles, UserProfiles, Pages } from '../../../../models'; | ||||||
| import { User } from '../../../../models/entities/user'; | import { User } from '../../../../models/entities/user'; | ||||||
| import { UserProfile } from '../../../../models/entities/user-profile'; | import { UserProfile } from '../../../../models/entities/user-profile'; | ||||||
| import { ensure } from '../../../../prelude/ensure'; | import { ensure } from '../../../../prelude/ensure'; | ||||||
|  | @ -125,6 +125,13 @@ export const meta = { | ||||||
| 				'ja-JP': 'アップロードするメディアをデフォルトで「閲覧注意」として設定するか' | 				'ja-JP': 'アップロードするメディアをデフォルトで「閲覧注意」として設定するか' | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 
 | ||||||
|  | 		pinnedPageId: { | ||||||
|  | 			validator: $.optional.nullable.type(ID), | ||||||
|  | 			desc: { | ||||||
|  | 				'ja-JP': 'ピン留めするページID' | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	errors: { | 	errors: { | ||||||
|  | @ -150,7 +157,13 @@ export const meta = { | ||||||
| 			message: 'The file specified as a banner is not an image.', | 			message: 'The file specified as a banner is not an image.', | ||||||
| 			code: 'BANNER_NOT_AN_IMAGE', | 			code: 'BANNER_NOT_AN_IMAGE', | ||||||
| 			id: '75aedb19-2afd-4e6d-87fc-67941256fa60' | 			id: '75aedb19-2afd-4e6d-87fc-67941256fa60' | ||||||
| 		} | 		}, | ||||||
|  | 
 | ||||||
|  | 		noSuchPage: { | ||||||
|  | 			message: 'No such page.', | ||||||
|  | 			code: 'NO_SUCH_PAGE', | ||||||
|  | 			id: '8e01b590-7eb9-431b-a239-860e086c408e' | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -203,6 +216,16 @@ export default define(meta, async (ps, user, app) => { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if (ps.pinnedPageId) { | ||||||
|  | 		const page = await Pages.findOne(ps.pinnedPageId); | ||||||
|  | 
 | ||||||
|  | 		if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage); | ||||||
|  | 
 | ||||||
|  | 		profileUpdates.pinnedPageId = page.id; | ||||||
|  | 	} else if (ps.pinnedPageId === null) { | ||||||
|  | 		profileUpdates.pinnedPageId = null; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	//#region emojis/tags
 | 	//#region emojis/tags
 | ||||||
| 
 | 
 | ||||||
| 	let emojis = [] as string[]; | 	let emojis = [] as string[]; | ||||||
|  |  | ||||||
|  | @ -57,6 +57,11 @@ export const meta = { | ||||||
| 			validator: $.optional.bool, | 			validator: $.optional.bool, | ||||||
| 			default: false | 			default: false | ||||||
| 		}, | 		}, | ||||||
|  | 
 | ||||||
|  | 		hideTitleWhenPinned: { | ||||||
|  | 			validator: $.optional.bool, | ||||||
|  | 			default: false | ||||||
|  | 		}, | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	res: { | 	res: { | ||||||
|  | @ -100,6 +105,7 @@ export default define(meta, async (ps, user) => { | ||||||
| 		userId: user.id, | 		userId: user.id, | ||||||
| 		visibility: 'public', | 		visibility: 'public', | ||||||
| 		alignCenter: ps.alignCenter, | 		alignCenter: ps.alignCenter, | ||||||
|  | 		hideTitleWhenPinned: ps.hideTitleWhenPinned, | ||||||
| 		font: ps.font | 		font: ps.font | ||||||
| 	})); | 	})); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -61,6 +61,10 @@ export const meta = { | ||||||
| 		alignCenter: { | 		alignCenter: { | ||||||
| 			validator: $.optional.bool, | 			validator: $.optional.bool, | ||||||
| 		}, | 		}, | ||||||
|  | 
 | ||||||
|  | 		hideTitleWhenPinned: { | ||||||
|  | 			validator: $.optional.bool, | ||||||
|  | 		}, | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	errors: { | 	errors: { | ||||||
|  | @ -113,6 +117,7 @@ export default define(meta, async (ps, user) => { | ||||||
| 		content: ps.content, | 		content: ps.content, | ||||||
| 		variables: ps.variables, | 		variables: ps.variables, | ||||||
| 		alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter, | 		alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter, | ||||||
|  | 		hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned, | ||||||
| 		font: ps.font === undefined ? page.font : ps.font, | 		font: ps.font === undefined ? page.font : ps.font, | ||||||
| 		eyeCatchingImageId: ps.eyeCatchingImageId === null | 		eyeCatchingImageId: ps.eyeCatchingImageId === null | ||||||
| 			? null | 			? null | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue