Skip to content

Commit

Permalink
feat: チャンネル内お知らせ機能 (#138)
Browse files Browse the repository at this point in the history
* feat: チャンネル内お知らせ機能のbackend側を実装

* update: 型定義を更新

* fix: channel.annoucementのjson-schemaがoptionalを許容していたのを修正

* fix: 誤字修正

* fix: チャンネル作成時にお知らせを設定出来ないようになっていたのを修正

* feat: クライアント側を実装
  • Loading branch information
hideki0403 authored Mar 19, 2024
1 parent 75e9c29 commit 73d8359
Show file tree
Hide file tree
Showing 11 changed files with 56 additions and 0 deletions.
4 changes: 4 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5280,6 +5280,10 @@ export interface Locale extends ILocale {
* 自分のプロフィールのアクティビティ (概要/アクティビティタブ) を他人が見れないようにします。このオプションを有効にしても、自分であればプロフィールのアクティビティタブから引き続き閲覧できます。
*/
"hideActivityDescription": string;
/**
* このお知らせはチャンネルのタイムライン上部に表示されます。最初の1行がタイトルとして表示され、2行目以降はお知らせをタップすることで表示されるようになります。
*/
"channelAnnouncementDescription": string;
"_bubbleGame": {
/**
* 遊び方
Expand Down
1 change: 1 addition & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,7 @@ scheduledNoteDelete: "ノートの自己消滅"
noteDeletationAt: "このノートは{time}に消滅します"
hideActivity: "アクティビティを非公開にする"
hideActivityDescription: "自分のプロフィールのアクティビティ (概要/アクティビティタブ) を他人が見れないようにします。このオプションを有効にしても、自分であればプロフィールのアクティビティタブから引き続き閲覧できます。"
channelAnnouncementDescription: "このお知らせはチャンネルのタイムライン上部に表示されます。最初の1行がタイトルとして表示され、2行目以降はお知らせをタップすることで表示されるようになります。"

_bubbleGame:
howToPlay: "遊び方"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class FeatChannnelAnnouncement1710854614048 {
name = 'FeatChannnelAnnouncement1710854614048'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "channel" ADD "announcement" character varying(2048)`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "announcement"`);
}
}
1 change: 1 addition & 0 deletions packages/backend/src/core/entities/ChannelEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export class ChannelEntityService {

...(detailed ? {
pinnedNotes: (await this.noteEntityService.packMany(pinnedNotes, me)).sort((a, b) => channel.pinnedNoteIds.indexOf(a.id) - channel.pinnedNoteIds.indexOf(b.id)),
announcement: channel.announcement,
} : {}),
};
}
Expand Down
6 changes: 6 additions & 0 deletions packages/backend/src/models/Channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ export class MiChannel {
})
public pinnedNoteIds: string[];

@Column('varchar', {
length: 2048,
nullable: true,
})
public announcement: string | null;

@Column('varchar', {
length: 16,
default: '#86b300',
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/models/json-schema/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,9 @@ export const packedChannelSchema = {
ref: 'Note',
},
},
announcement: {
type: 'string',
optional: false, nullable: true,
},
},
} as const;
2 changes: 2 additions & 0 deletions packages/backend/src/server/api/endpoints/channels/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const paramDef = {
properties: {
name: { type: 'string', minLength: 1, maxLength: 128 },
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
announcement: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
color: { type: 'string', minLength: 1, maxLength: 16 },
isSensitive: { type: 'boolean', nullable: true },
Expand Down Expand Up @@ -89,6 +90,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive ?? false,
...(ps.color !== undefined ? { color: ps.color } : {}),
allowRenoteToExternal: ps.allowRenoteToExternal ?? true,
announcement: ps.announcement ?? null,
} as MiChannel).then(x => this.channelsRepository.findOneByOrFail(x.identifiers[0]));

return await this.channelEntityService.pack(channel, me);
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/server/api/endpoints/channels/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const paramDef = {
type: 'string', format: 'misskey:id',
},
},
announcement: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
color: { type: 'string', minLength: 1, maxLength: 16 },
isSensitive: { type: 'boolean', nullable: true },
allowRenoteToExternal: { type: 'boolean', nullable: true },
Expand Down Expand Up @@ -112,6 +113,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
...(ps.name !== undefined ? { name: ps.name } : {}),
...(ps.description !== undefined ? { description: ps.description } : {}),
...(ps.pinnedNoteIds !== undefined ? { pinnedNoteIds: ps.pinnedNoteIds } : {}),
...(ps.announcement !== undefined ? { announcement: ps.announcement } : {}),
...(ps.color !== undefined ? { color: ps.color } : {}),
...(typeof ps.isArchived === 'boolean' ? { isArchived: ps.isArchived } : {}),
...(banner ? { bannerId: banner.id } : {}),
Expand Down
8 changes: 8 additions & 0 deletions packages/frontend/src/pages/channel-editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.description }}</template>
</MkTextarea>

<MkTextarea v-model="announcement" mfmAutocomplete :mfmPreview="true">
<template #label>{{ i18n.ts.announcements }}<span class="_beta">{{ i18n.ts.originalFeature }}</span></template>
<template #caption>{{ i18n.ts.channelAnnouncementDescription }}</template>
</MkTextarea>

<MkColorInput v-model="color">
<template #label>{{ i18n.ts.color }}</template>
</MkColorInput>
Expand Down Expand Up @@ -101,6 +106,7 @@ const color = ref('#000');
const isSensitive = ref(false);
const allowRenoteToExternal = ref(true);
const pinnedNotes = ref<{ id: Misskey.entities.Note['id'] }[]>([]);
const announcement = ref<string | null>(null);

watch(() => bannerId.value, async () => {
if (bannerId.value == null) {
Expand All @@ -127,6 +133,7 @@ async function fetchChannel() {
pinnedNotes.value = channel.value.pinnedNoteIds.map(id => ({
id,
}));
announcement.value = channel.value.announcement;
color.value = channel.value.color;
allowRenoteToExternal.value = channel.value.allowRenoteToExternal;
}
Expand Down Expand Up @@ -156,6 +163,7 @@ function save() {
description: description.value,
bannerId: bannerId.value,
pinnedNoteIds: pinnedNotes.value.map(x => x.id),
announcement: announcement.value,
color: color.value,
isSensitive: isSensitive.value,
allowRenoteToExternal: allowRenoteToExternal.value,
Expand Down
15 changes: 15 additions & 0 deletions packages/frontend/src/pages/channel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="channel && tab === 'timeline'" key="timeline" class="_gaps">
<MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo>

<MkInfo v-if="channel.announcement" :class="$style.clickable" @click="showAnnouncement">{{ channel.announcement.split('\n')[0] }} ({{ i18n.ts.clickToShow }})</MkInfo>

<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>

Expand Down Expand Up @@ -240,6 +242,15 @@ const headerActions = computed(() => {
}
});

function showAnnouncement() {
if (!channel.value?.announcement) return;
const announce = channel.value.announcement.split('\n');
os.alert({
title: announce.shift(),
text: announce.join('\n'),
});
}

const headerTabs = computed(() => [{
key: 'overview',
title: i18n.ts.overview,
Expand Down Expand Up @@ -338,4 +349,8 @@ definePageMetadata(() => ({
font-size: 1em;
padding: 4px 7px;
}

.clickable {
cursor: pointer;
}
</style>
2 changes: 2 additions & 0 deletions packages/misskey-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4520,6 +4520,7 @@ export type components = {
isFollowing?: boolean;
isFavorited?: boolean;
pinnedNotes?: components['schemas']['Note'][];
annoucement: string | null;
};
QueueCount: {
waiting: number;
Expand Down Expand Up @@ -11317,6 +11318,7 @@ export type operations = {
bannerId?: string | null;
isArchived?: boolean | null;
pinnedNoteIds?: string[];
announcement?: string | null;
color?: string;
isSensitive?: boolean | null;
allowRenoteToExternal?: boolean | null;
Expand Down

0 comments on commit 73d8359

Please sign in to comment.