summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2019-07-07 06:56:13 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2019-07-07 06:56:13 +0900
commitbd8d7c3d0f1bb831f26a75a6107e34a85a1bc804 (patch)
treef5c7f4d850ab00da5c800fa893f8319cf71b384a
parentUse npx to avoid global install of ts-node (diff)
downloadsharkey-bd8d7c3d0f1bb831f26a75a6107e34a85a1bc804.tar.gz
sharkey-bd8d7c3d0f1bb831f26a75a6107e34a85a1bc804.tar.bz2
sharkey-bd8d7c3d0f1bb831f26a75a6107e34a85a1bc804.zip
Improve Page
* ページをピン留めできるように * デッキでカラム内でページを見れるように
-rw-r--r--locales/ja-JP.yml4
-rw-r--r--migration/1562444565093-PinnedPage.ts17
-rw-r--r--migration/1562448332510-PageTitleHideOption.ts13
-rw-r--r--src/client/app/common/views/components/page/page.block.vue (renamed from src/client/app/common/views/pages/page/page.block.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.button.vue (renamed from src/client/app/common/views/pages/page/page.button.vue)2
-rw-r--r--src/client/app/common/views/components/page/page.counter.vue (renamed from src/client/app/common/views/pages/page/page.counter.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.if.vue (renamed from src/client/app/common/views/pages/page/page.if.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.image.vue (renamed from src/client/app/common/views/pages/page/page.image.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.number-input.vue (renamed from src/client/app/common/views/pages/page/page.number-input.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.post.vue (renamed from src/client/app/common/views/pages/page/page.post.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.section.vue (renamed from src/client/app/common/views/pages/page/page.section.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.switch.vue (renamed from src/client/app/common/views/pages/page/page.switch.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.text-input.vue (renamed from src/client/app/common/views/pages/page/page.text-input.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.text.vue (renamed from src/client/app/common/views/pages/page/page.text.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.textarea-input.vue (renamed from src/client/app/common/views/pages/page/page.textarea-input.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.textarea.vue (renamed from src/client/app/common/views/pages/page/page.textarea.vue)0
-rw-r--r--src/client/app/common/views/components/page/page.vue (renamed from src/client/app/common/views/pages/page/page.vue)88
-rw-r--r--src/client/app/common/views/deck/deck.page-column.vue69
-rw-r--r--src/client/app/common/views/deck/deck.user-column.home.vue7
-rw-r--r--src/client/app/common/views/pages/page-editor/page-editor.vue6
-rw-r--r--src/client/app/common/views/pages/page.vue63
-rw-r--r--src/client/app/desktop/script.ts4
-rw-r--r--src/client/app/desktop/views/home/user/user.home.vue5
-rw-r--r--src/client/app/mobile/script.ts3
-rw-r--r--src/client/app/mobile/views/pages/user/home.vue8
-rw-r--r--src/models/entities/page.ts5
-rw-r--r--src/models/entities/user-profile.ts13
-rw-r--r--src/models/repositories/page.ts1
-rw-r--r--src/models/repositories/user.ts4
-rw-r--r--src/server/api/endpoints/i/update.ts27
-rw-r--r--src/server/api/endpoints/pages/create.ts6
-rw-r--r--src/server/api/endpoints/pages/update.ts5
32 files changed, 295 insertions, 55 deletions
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index d29b96af1d..32a7fad398 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1907,6 +1907,7 @@ deck/deck.user-column.vue:
activity: "アクティビティ"
timeline: "タイムライン"
pinned-notes: "ピン留めされた投稿"
+ pinned-page: "ピン留めされたページ"
docs:
edit-this-page-on-github: "間違いや改善点を見つけましたか?"
@@ -1946,6 +1947,8 @@ pages:
are-you-sure-delete: "このページを削除しますか?"
page-deleted: "ページを削除しました"
edit-this-page: "このページを編集"
+ pin-this-page: "ピン留め"
+ unpin-this-page: "ピン留め解除"
view-source: "ソースを表示"
view-page: "ページを見る"
like: "いいね"
@@ -1964,6 +1967,7 @@ pages:
url: "ページURL"
summary: "ページの要約"
align-center: "中央寄せ"
+ hide-title-when-pinned: "ピン留めされているときにタイトルを非表示"
font: "フォント"
fontSerif: "セリフ"
fontSansSerif: "サンセリフ"
diff --git a/migration/1562444565093-PinnedPage.ts b/migration/1562444565093-PinnedPage.ts
new file mode 100644
index 0000000000..4bdee22748
--- /dev/null
+++ b/migration/1562444565093-PinnedPage.ts
@@ -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"`);
+ }
+
+}
diff --git a/migration/1562448332510-PageTitleHideOption.ts b/migration/1562448332510-PageTitleHideOption.ts
new file mode 100644
index 0000000000..acc9b7e26f
--- /dev/null
+++ b/migration/1562448332510-PageTitleHideOption.ts
@@ -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"`);
+ }
+
+}
diff --git a/src/client/app/common/views/pages/page/page.block.vue b/src/client/app/common/views/components/page/page.block.vue
index 1c421fc2c0..1c421fc2c0 100644
--- a/src/client/app/common/views/pages/page/page.block.vue
+++ b/src/client/app/common/views/components/page/page.block.vue
diff --git a/src/client/app/common/views/pages/page/page.button.vue b/src/client/app/common/views/components/page/page.button.vue
index 4dc6570019..87112aca0d 100644
--- a/src/client/app/common/views/pages/page/page.button.vue
+++ b/src/client/app/common/views/components/page/page.button.vue
@@ -49,7 +49,7 @@ export default Vue.extend({
<style lang="stylus" scoped>
.kudkigyw
display inline-block
- min-width 300px
+ min-width 200px
max-width 450px
margin 8px 0
</style>
diff --git a/src/client/app/common/views/pages/page/page.counter.vue b/src/client/app/common/views/components/page/page.counter.vue
index 8d55319fe9..8d55319fe9 100644
--- a/src/client/app/common/views/pages/page/page.counter.vue
+++ b/src/client/app/common/views/components/page/page.counter.vue
diff --git a/src/client/app/common/views/pages/page/page.if.vue b/src/client/app/common/views/components/page/page.if.vue
index 417ef0c553..417ef0c553 100644
--- a/src/client/app/common/views/pages/page/page.if.vue
+++ b/src/client/app/common/views/components/page/page.if.vue
diff --git a/src/client/app/common/views/pages/page/page.image.vue b/src/client/app/common/views/components/page/page.image.vue
index 1285445eb0..1285445eb0 100644
--- a/src/client/app/common/views/pages/page/page.image.vue
+++ b/src/client/app/common/views/components/page/page.image.vue
diff --git a/src/client/app/common/views/pages/page/page.number-input.vue b/src/client/app/common/views/components/page/page.number-input.vue
index 31da37330a..31da37330a 100644
--- a/src/client/app/common/views/pages/page/page.number-input.vue
+++ b/src/client/app/common/views/components/page/page.number-input.vue
diff --git a/src/client/app/common/views/pages/page/page.post.vue b/src/client/app/common/views/components/page/page.post.vue
index cb695e21e9..cb695e21e9 100644
--- a/src/client/app/common/views/pages/page/page.post.vue
+++ b/src/client/app/common/views/components/page/page.post.vue
diff --git a/src/client/app/common/views/pages/page/page.section.vue b/src/client/app/common/views/components/page/page.section.vue
index 03c009d9c3..03c009d9c3 100644
--- a/src/client/app/common/views/pages/page/page.section.vue
+++ b/src/client/app/common/views/components/page/page.section.vue
diff --git a/src/client/app/common/views/pages/page/page.switch.vue b/src/client/app/common/views/components/page/page.switch.vue
index 53695f1b36..53695f1b36 100644
--- a/src/client/app/common/views/pages/page/page.switch.vue
+++ b/src/client/app/common/views/components/page/page.switch.vue
diff --git a/src/client/app/common/views/pages/page/page.text-input.vue b/src/client/app/common/views/components/page/page.text-input.vue
index cf917dd5a8..cf917dd5a8 100644
--- a/src/client/app/common/views/pages/page/page.text-input.vue
+++ b/src/client/app/common/views/components/page/page.text-input.vue
diff --git a/src/client/app/common/views/pages/page/page.text.vue b/src/client/app/common/views/components/page/page.text.vue
index 326fd39050..326fd39050 100644
--- a/src/client/app/common/views/pages/page/page.text.vue
+++ b/src/client/app/common/views/components/page/page.text.vue
diff --git a/src/client/app/common/views/pages/page/page.textarea-input.vue b/src/client/app/common/views/components/page/page.textarea-input.vue
index eece59fefb..eece59fefb 100644
--- a/src/client/app/common/views/pages/page/page.textarea-input.vue
+++ b/src/client/app/common/views/components/page/page.textarea-input.vue
diff --git a/src/client/app/common/views/pages/page/page.textarea.vue b/src/client/app/common/views/components/page/page.textarea.vue
index 03c8542cb0..03c8542cb0 100644
--- a/src/client/app/common/views/pages/page/page.textarea.vue
+++ b/src/client/app/common/views/components/page/page.textarea.vue
diff --git a/src/client/app/common/views/pages/page/page.vue b/src/client/app/common/views/components/page/page.vue
index a93d5316d5..99e627fd89 100644
--- a/src/client/app/common/views/pages/page/page.vue
+++ b/src/client/app/common/views/components/page/page.vue
@@ -1,6 +1,6 @@
<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">
- <header>
+<div class="iroscrza" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners, center: page.alignCenter }" :style="{ fontFamily: page.font }">
+ <header v-if="showTitle">
<div class="title">{{ page.title }}</div>
</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"/>
</div>
- <footer>
+ <footer v-if="showFooter">
<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>
<div class="like">
<button @click="unlike()" v-if="page.isLiked" :title="$t('unlike')"><fa :icon="faHeartS"/></button>
@@ -25,7 +29,7 @@
import Vue from 'vue';
import i18n from '../../../../i18n';
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 { ASEvaluator } from '../../../../../../misc/aiscript/evaluator';
import { collectPageVars } from '../../../scripts/collect-page-vars';
@@ -69,64 +73,43 @@ export default Vue.extend({
},
props: {
- pageName: {
- type: String,
+ page: {
+ type: Object,
required: true
},
- username: {
- type: String,
- required: true
+ showTitle: {
+ type: Boolean,
+ required: false,
+ default: true
+ },
+ showFooter: {
+ type: Boolean,
+ required: false,
+ default: false
},
},
data() {
return {
- page: null,
script: null,
faHeartS, faHeart
};
},
- computed: {
- path(): string {
- return this.username + '/' + this.pageName;
- }
- },
-
- watch: {
- path() {
- this.fetch();
- }
- },
-
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: {
- 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() {
return collectPageVars(this.page.content);
},
@@ -147,6 +130,17 @@ export default Vue.extend({
this.page.isLiked = false;
this.page.likedCount--;
});
+ },
+
+ pin(pin) {
+ this.$root.api('i/update', {
+ pinnedPageId: pin ? this.page.id : null,
+ }).then(() => {
+ this.$root.dialog({
+ type: 'success',
+ splash: true
+ });
+ });
}
}
});
diff --git a/src/client/app/common/views/deck/deck.page-column.vue b/src/client/app/common/views/deck/deck.page-column.vue
new file mode 100644
index 0000000000..0ef391a51d
--- /dev/null
+++ b/src/client/app/common/views/deck/deck.page-column.vue
@@ -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>
diff --git a/src/client/app/common/views/deck/deck.user-column.home.vue b/src/client/app/common/views/deck/deck.user-column.home.vue
index 56b117a7dd..9fb50a6672 100644
--- a/src/client/app/common/views/deck/deck.user-column.home.vue
+++ b/src/client/app/common/views/deck/deck.user-column.home.vue
@@ -1,5 +1,11 @@
<template>
<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">
<template #header><fa icon="thumbtack"/> {{ $t('pinned-notes') }}</template>
<div>
@@ -48,6 +54,7 @@ export default Vue.extend({
components: {
XNotes,
+ XPage: () => import('../../../common/views/components/page/page.vue').then(m => m.default),
},
props: {
diff --git a/src/client/app/common/views/pages/page-editor/page-editor.vue b/src/client/app/common/views/pages/page-editor/page-editor.vue
index ebe0f4688d..ade7d86991 100644
--- a/src/client/app/common/views/pages/page-editor/page-editor.vue
+++ b/src/client/app/common/views/pages/page-editor/page-editor.vue
@@ -35,6 +35,8 @@
<option value="sans-serif">{{ $t('fontSansSerif') }}</option>
</ui-select>
+ <ui-switch v-model="hideTitleWhenPinned">{{ $t('hide-title-when-pinned') }}</ui-switch>
+
<div class="eyeCatch">
<ui-button v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage()"><fa :icon="faPlus"/> {{ $t('set-eye-catching-image') }}</ui-button>
<div v-else-if="eyeCatchingImage">
@@ -140,6 +142,7 @@ export default Vue.extend({
font: 'sans-serif',
content: [],
alignCenter: false,
+ hideTitleWhenPinned: false,
variables: [],
aiScript: null,
showOptions: false,
@@ -192,6 +195,7 @@ export default Vue.extend({
this.currentName = this.page.name;
this.summary = this.page.summary;
this.font = this.page.font;
+ this.hideTitleWhenPinned = this.page.hideTitleWhenPinned;
this.alignCenter = this.page.alignCenter;
this.content = this.page.content;
this.variables = this.page.variables;
@@ -223,6 +227,7 @@ export default Vue.extend({
name: this.name.trim(),
summary: this.summary,
font: this.font,
+ hideTitleWhenPinned: this.hideTitleWhenPinned,
alignCenter: this.alignCenter,
content: this.content,
variables: this.variables,
@@ -240,6 +245,7 @@ export default Vue.extend({
name: this.name.trim(),
summary: this.summary,
font: this.font,
+ hideTitleWhenPinned: this.hideTitleWhenPinned,
alignCenter: this.alignCenter,
content: this.content,
variables: this.variables,
diff --git a/src/client/app/common/views/pages/page.vue b/src/client/app/common/views/pages/page.vue
new file mode 100644
index 0000000000..d1c4c2be43
--- /dev/null
+++ b/src/client/app/common/views/pages/page.vue
@@ -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>
diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts
index 8065241714..1a4be33020 100644
--- a/src/client/app/desktop/script.ts
+++ b/src/client/app/desktop/script.ts
@@ -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/: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: '/@: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: '', 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/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: '/@: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: '/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: '/@: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: '/i/messaging/group/:group', component: MkMessagingRoom },
{ path: '/i/messaging/:user', component: MkMessagingRoom },
diff --git a/src/client/app/desktop/views/home/user/user.home.vue b/src/client/app/desktop/views/home/user/user.home.vue
index ec533efd3e..c47e0a0771 100644
--- a/src/client/app/desktop/views/home/user/user.home.vue
+++ b/src/client/app/desktop/views/home/user/user.home.vue
@@ -1,5 +1,6 @@
<template>
<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-calendar @chosen="warp" :start="new Date(user.createdAt)"/>-->
<div class="activity">
@@ -21,13 +22,15 @@ import i18n from '../../../../i18n';
import XTimeline from './user.timeline.vue';
import XPhotos from './user.photos.vue';
import XActivity from '../../../../common/views/components/activity.vue';
+import XPage from '../../../../common/views/components/page/page.vue';
export default Vue.extend({
i18n: i18n(),
components: {
XTimeline,
XPhotos,
- XActivity
+ XActivity,
+ XPage,
},
props: {
user: {
diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts
index d04662cc1f..6222409931 100644
--- a/src/client/app/mobile/script.ts
+++ b/src/client/app/mobile/script.ts
@@ -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/: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: '/@: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 },
@@ -163,7 +164,7 @@ init((launch, os) => {
{ 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: '/@: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: '/notes/:note', component: MkNote },
{ path: '/authorize-follow', component: MkFollow },
diff --git a/src/client/app/mobile/views/pages/user/home.vue b/src/client/app/mobile/views/pages/user/home.vue
index 1d7b0a4e6d..316b2a12fe 100644
--- a/src/client/app/mobile/views/pages/user/home.vue
+++ b/src/client/app/mobile/views/pages/user/home.vue
@@ -1,5 +1,6 @@
<template>
<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"/>
<ui-container :body-togglable="true">
<template #header><fa :icon="['far', 'comments']"/>{{ $t('recent-notes') }}</template>
@@ -33,6 +34,7 @@ export default Vue.extend({
components: {
XNotes,
XPhotos,
+ XPage: () => import('../../../../common/views/components/page/page.vue').then(m => m.default),
XActivity: () => import('../../../../common/views/components/activity.vue').then(m => m.default)
},
props: ['user'],
@@ -53,6 +55,12 @@ export default Vue.extend({
<style lang="stylus" scoped>
.wojmldye
+ > .page
+ margin 0 0 8px 0
+
+ @media (min-width 500px)
+ margin 0 0 16px 0
+
> .note
margin 0 0 8px 0
diff --git a/src/models/entities/page.ts b/src/models/entities/page.ts
index 05015ba175..2163f9997f 100644
--- a/src/models/entities/page.ts
+++ b/src/models/entities/page.ts
@@ -40,6 +40,11 @@ export class Page {
@Column('boolean')
public alignCenter: boolean;
+ @Column('boolean', {
+ default: false
+ })
+ public hideTitleWhenPinned: boolean;
+
@Column('varchar', {
length: 32,
})
diff --git a/src/models/entities/user-profile.ts b/src/models/entities/user-profile.ts
index 4a588ebfbf..61e80049c3 100644
--- a/src/models/entities/user-profile.ts
+++ b/src/models/entities/user-profile.ts
@@ -1,6 +1,7 @@
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
import { id } from '../id';
import { User } from './user';
+import { Page } from './page';
@Entity()
export class UserProfile {
@@ -118,6 +119,18 @@ export class UserProfile {
})
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
@Column('boolean', {
default: false,
diff --git a/src/models/repositories/page.ts b/src/models/repositories/page.ts
index 33126274a1..1335ada73f 100644
--- a/src/models/repositories/page.ts
+++ b/src/models/repositories/page.ts
@@ -71,6 +71,7 @@ export class PageRepository extends Repository<Page> {
title: page.title,
name: page.name,
summary: page.summary,
+ hideTitleWhenPinned: page.hideTitleWhenPinned,
alignCenter: page.alignCenter,
font: page.font,
eyeCatchingImageId: page.eyeCatchingImageId,
diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts
index 06da74197f..4e85fd7b93 100644
--- a/src/models/repositories/user.ts
+++ b/src/models/repositories/user.ts
@@ -1,7 +1,7 @@
import $ from 'cafy';
import { EntityRepository, Repository, In } from 'typeorm';
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 config from '../../config';
import { SchemaType } from '../../misc/schema';
@@ -155,6 +155,8 @@ export class UserRepository extends Repository<User> {
pinnedNotes: Notes.packMany(pins.map(pin => pin.noteId), meId, {
detail: true
}),
+ pinnedPageId: profile!.pinnedPageId,
+ pinnedPage: profile!.pinnedPageId ? Pages.pack(profile!.pinnedPageId, meId) : null,
twoFactorEnabled: profile!.twoFactorEnabled,
usePasswordLessLogin: profile!.usePasswordLessLogin,
securityKeys: profile!.twoFactorEnabled
diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts
index 10521d12d8..a454cdb940 100644
--- a/src/server/api/endpoints/i/update.ts
+++ b/src/server/api/endpoints/i/update.ts
@@ -10,7 +10,7 @@ import extractHashtags from '../../../../misc/extract-hashtags';
import * as langmap from 'langmap';
import { updateHashtag } from '../../../../services/update-hashtag';
import { ApiError } from '../../error';
-import { Users, DriveFiles, UserProfiles } from '../../../../models';
+import { Users, DriveFiles, UserProfiles, Pages } from '../../../../models';
import { User } from '../../../../models/entities/user';
import { UserProfile } from '../../../../models/entities/user-profile';
import { ensure } from '../../../../prelude/ensure';
@@ -125,6 +125,13 @@ export const meta = {
'ja-JP': 'アップロードするメディアをデフォルトで「閲覧注意」として設定するか'
}
},
+
+ pinnedPageId: {
+ validator: $.optional.nullable.type(ID),
+ desc: {
+ 'ja-JP': 'ピン留めするページID'
+ }
+ }
},
errors: {
@@ -150,7 +157,13 @@ export const meta = {
message: 'The file specified as a banner is not an image.',
code: 'BANNER_NOT_AN_IMAGE',
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
let emojis = [] as string[];
diff --git a/src/server/api/endpoints/pages/create.ts b/src/server/api/endpoints/pages/create.ts
index ffe0d38ea6..a49a5d37b8 100644
--- a/src/server/api/endpoints/pages/create.ts
+++ b/src/server/api/endpoints/pages/create.ts
@@ -57,6 +57,11 @@ export const meta = {
validator: $.optional.bool,
default: false
},
+
+ hideTitleWhenPinned: {
+ validator: $.optional.bool,
+ default: false
+ },
},
res: {
@@ -100,6 +105,7 @@ export default define(meta, async (ps, user) => {
userId: user.id,
visibility: 'public',
alignCenter: ps.alignCenter,
+ hideTitleWhenPinned: ps.hideTitleWhenPinned,
font: ps.font
}));
diff --git a/src/server/api/endpoints/pages/update.ts b/src/server/api/endpoints/pages/update.ts
index 8ee34fc3ba..9daf5e9cae 100644
--- a/src/server/api/endpoints/pages/update.ts
+++ b/src/server/api/endpoints/pages/update.ts
@@ -61,6 +61,10 @@ export const meta = {
alignCenter: {
validator: $.optional.bool,
},
+
+ hideTitleWhenPinned: {
+ validator: $.optional.bool,
+ },
},
errors: {
@@ -113,6 +117,7 @@ export default define(meta, async (ps, user) => {
content: ps.content,
variables: ps.variables,
alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter,
+ hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
font: ps.font === undefined ? page.font : ps.font,
eyeCatchingImageId: ps.eyeCatchingImageId === null
? null