diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index f1cfec046a3a987233783fd5941363be6aa51867..14b8daad484e759590ef475ca754c33c1122b517 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -338,6 +338,7 @@ auth/views/index.vue: sign-in: "サインインã—ã¦ãã ã•ã„" common/views/pages/explore.vue: + pinned-users: "ピン留ã‚ã•ã‚ŒãŸãƒ¦ãƒ¼ã‚¶ãƒ¼" popular-users: "人気ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼" recently-updated-users: "最近投稿ã—ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼" recently-registered-users: "æ–°è¦ãƒ¦ãƒ¼ã‚¶ãƒ¼" @@ -1262,7 +1263,7 @@ admin/views/instance.vue: invite: "招待" save: "ä¿å˜" saved: "ä¿å˜ã—ã¾ã—ãŸ" - user-recommendation-config: "ãŠã™ã™ã‚ユーザー" + pinned-users: "ピン留ã‚ユーザー" email-config: "メールサーãƒãƒ¼ã®è¨å®š" email-config-info: "メールアドレス確èªã‚„パスワードリセットã®éš›ã«ä½¿ã‚ã‚Œã¾ã™ã€‚" enable-email: "メールé…信を有効ã«ã™ã‚‹" diff --git a/migration/1557476068003-PinnedUsers.ts b/migration/1557476068003-PinnedUsers.ts new file mode 100644 index 0000000000000000000000000000000000000000..4e7222aafc8fb7278f153a10792846327fbf9c5e --- /dev/null +++ b/migration/1557476068003-PinnedUsers.ts @@ -0,0 +1,13 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class PinnedUsers1557476068003 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise<any> { + await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedUsers" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`); + } + + public async down(queryRunner: QueryRunner): Promise<any> { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedUsers"`); + } + +} diff --git a/src/client/app/admin/views/instance.vue b/src/client/app/admin/views/instance.vue index cca4e5f66975c21128520df16089088065ae96a9..d81edc8fe66a03f7108d4477bc59011d561853b0 100644 --- a/src/client/app/admin/views/instance.vue +++ b/src/client/app/admin/views/instance.vue @@ -82,6 +82,14 @@ </section> </ui-card> + <ui-card> + <template #title>{{ $t('pinned-users') }}</template> + <section> + <ui-textarea v-model="pinnedUsers"></ui-textarea> + <ui-button @click="updateMeta">{{ $t('save') }}</ui-button> + </section> + </ui-card> + <ui-card> <template #title>{{ $t('invite') }}</template> <section> @@ -190,6 +198,7 @@ export default Vue.extend({ enableServiceWorker: false, swPublicKey: null, swPrivateKey: null, + pinnedUsers: [], faHeadset, faShieldAlt, faGhost, faUserPlus, farEnvelope, faBolt }; }, @@ -239,6 +248,7 @@ export default Vue.extend({ this.enableServiceWorker = meta.enableServiceWorker; this.swPublicKey = meta.swPublickey; this.swPrivateKey = meta.swPrivateKey; + this.pinnedUsers = meta.pinnedUsers.join('\n'); }); }, @@ -297,7 +307,8 @@ export default Vue.extend({ smtpPass: this.smtpAuth ? this.smtpPass : '', enableServiceWorker: this.enableServiceWorker, swPublicKey: this.swPublicKey, - swPrivateKey: this.swPrivateKey + swPrivateKey: this.swPrivateKey, + pinnedUsers: this.pinnedUsers.split('\n') }).then(() => { this.$root.dialog({ type: 'success', diff --git a/src/client/app/common/views/pages/explore.vue b/src/client/app/common/views/pages/explore.vue index 107603d69e6e56545d1185e2df6e2b84362a60cd..d0e98035f8fd37e2933f80690bb3c353019d434b 100644 --- a/src/client/app/common/views/pages/explore.vue +++ b/src/client/app/common/views/pages/explore.vue @@ -26,6 +26,9 @@ </mk-user-list> <template v-if="tag == null"> + <mk-user-list :make-promise="pinnedUsers"> + <fa :icon="faBookmark" fixed-width/>{{ $t('pinned-users') }} + </mk-user-list> <mk-user-list :make-promise="popularUsers"> <fa :icon="faChartLine" fixed-width/>{{ $t('popular-users') }} </mk-user-list> @@ -57,6 +60,7 @@ export default Vue.extend({ data() { return { + pinnedUsers: () => this.$root.api('pinned-users'), popularUsers: () => this.$root.api('users', { state: 'alive', origin: 'local', diff --git a/src/models/entities/meta.ts b/src/models/entities/meta.ts index be41cf3589410515b7c76a720fc107431bad6f12..2c36b8333fceda75c74cdcc9801e9674783f7d98 100644 --- a/src/models/entities/meta.ts +++ b/src/models/entities/meta.ts @@ -69,6 +69,11 @@ export class Meta { }) public langs: string[]; + @Column('varchar', { + length: 256, array: true, default: '{}' + }) + public pinnedUsers: string[]; + @Column('varchar', { length: 256, array: true, default: '{}' }) diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index 1f5dd5364ff7baa1645795666327f80eb5afda99..8a2019fcc1a95f345758e308e89eed69dbfdb67a 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -56,6 +56,13 @@ export const meta = { } }, + pinnedUsers: { + validator: $.optional.nullable.arr($.str), + desc: { + 'ja-JP': 'ピン留ã‚ユーザー' + } + }, + hiddenTags: { validator: $.optional.nullable.arr($.str), desc: { @@ -353,6 +360,10 @@ export default define(meta, async (ps) => { set.useStarForReactionFallback = ps.useStarForReactionFallback; } + if (Array.isArray(ps.pinnedUsers)) { + set.pinnedUsers = ps.pinnedUsers; + } + if (Array.isArray(ps.hiddenTags)) { set.hiddenTags = ps.hiddenTags; } diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index 793eb5a20471da7d556cc84ce11f9c03cdfa5e65..5667e7fbb4ba1ded04fcaf3c1ca113e6e4033c97 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -160,6 +160,7 @@ export default define(meta, async (ps, me) => { if (me && (me.isAdmin || me.isModerator)) { response.useStarForReactionFallback = instance.useStarForReactionFallback; + response.pinnedUsers = instance.pinnedUsers; response.hiddenTags = instance.hiddenTags; response.recaptchaSecretKey = instance.recaptchaSecretKey; response.proxyAccount = instance.proxyAccount; diff --git a/src/server/api/endpoints/pinned-users.ts b/src/server/api/endpoints/pinned-users.ts new file mode 100644 index 0000000000000000000000000000000000000000..de0e17a2ecc9ea0f800c48f1e78a38e5fb02f381 --- /dev/null +++ b/src/server/api/endpoints/pinned-users.ts @@ -0,0 +1,33 @@ +import define from '../define'; +import { Users } from '../../../models'; +import { types, bool } from '../../../misc/schema'; +import { fetchMeta } from '../../../misc/fetch-meta'; +import parseAcct from '../../../misc/acct/parse'; +import { User } from '../../../models/entities/user'; + +export const meta = { + tags: ['users'], + + requireCredential: false, + + params: { + }, + + res: { + type: types.array, + optional: bool.false, nullable: bool.false, + items: { + type: types.object, + optional: bool.false, nullable: bool.false, + ref: 'User', + } + }, +}; + +export default define(meta, async (ps, me) => { + const meta = await fetchMeta(); + + const users = await Promise.all(meta.pinnedUsers.map(acct => Users.findOne(parseAcct(acct)))); + + return await Users.packMany(users.filter(x => x !== undefined) as User[], me, { detail: true }); +});