parent
3a105024c7
commit
5dfbce7571
|
@ -27,6 +27,8 @@
|
||||||
* ユーザーメニューから追加できます。
|
* ユーザーメニューから追加できます。
|
||||||
(デスクトップ表示ではusernameの右側のボタンからも追加可能)
|
(デスクトップ表示ではusernameの右側のボタンからも追加可能)
|
||||||
- チャンネルに色を設定できるようになりました。各ノートに設定した色のインジケーターが表示されます。
|
- チャンネルに色を設定できるようになりました。各ノートに設定した色のインジケーターが表示されます。
|
||||||
|
- チャンネルをアーカイブできるようになりました。
|
||||||
|
* アーカイブすると、チャンネル一覧や検索結果に表示されなくなり、新たな書き込みもできなくなります。
|
||||||
- ロールタイムラインをロールごとに表示するかどうかの選択できるようになりました。
|
- ロールタイムラインをロールごとに表示するかどうかの選択できるようになりました。
|
||||||
* デフォルトがオフになるので、ロールタイムラインを表示する場合はオンにしてください。
|
* デフォルトがオフになるので、ロールタイムラインを表示する場合はオンにしてください。
|
||||||
- ロールに強制的にNSFWを付与するポリシーを追加
|
- ロールに強制的にNSFWを付与するポリシーを追加
|
||||||
|
@ -46,10 +48,10 @@
|
||||||
- データセーバーモードを追加
|
- データセーバーモードを追加
|
||||||
* 画像が全て隠れた状態で表示されるようになります
|
* 画像が全て隠れた状態で表示されるようになります
|
||||||
- 1枚だけのメディアリストの画像のアスペクト比を画像に応じて縦長にするように
|
- 1枚だけのメディアリストの画像のアスペクト比を画像に応じて縦長にするように
|
||||||
|
- プロフィール設定「追加情報」の項目の削除と並び替えができるように
|
||||||
- 新しい実績を追加
|
- 新しい実績を追加
|
||||||
- Fix: AiScript APIのMk:dialogで何も返していなかったのをNULLを返すように修正
|
- Fix: AiScript APIのMk:dialogで何も返していなかったのをNULLを返すように修正
|
||||||
- Fix: リアクションをホバーした時のユーザーリストで猫耳が切れてしまっていた問題を修正
|
- Fix: リアクションをホバーした時のユーザーリストで猫耳が切れてしまっていた問題を修正
|
||||||
- プロフィール設定「追加情報」の項目の削除と並び替えができるように
|
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- channel/searchのqueryが空の場合に全てのチャンネルを返すように変更
|
- channel/searchのqueryが空の場合に全てのチャンネルを返すように変更
|
||||||
|
|
|
@ -1031,6 +1031,10 @@ continue: "続ける"
|
||||||
preservedUsernames: "予約ユーザー名"
|
preservedUsernames: "予約ユーザー名"
|
||||||
preservedUsernamesDescription: "予約するユーザー名を改行で列挙します。ここで指定されたユーザー名はアカウント作成時に使えなくなりますが、管理者によるアカウント作成時はこの制限を受けません。また、既に存在するアカウントも影響を受けません。"
|
preservedUsernamesDescription: "予約するユーザー名を改行で列挙します。ここで指定されたユーザー名はアカウント作成時に使えなくなりますが、管理者によるアカウント作成時はこの制限を受けません。また、既に存在するアカウントも影響を受けません。"
|
||||||
createNoteFromTheFile: "このファイルからノートを作成"
|
createNoteFromTheFile: "このファイルからノートを作成"
|
||||||
|
archive: "アーカイブ"
|
||||||
|
channelArchiveConfirmTitle: "{name}をアーカイブしますか?"
|
||||||
|
channelArchiveConfirmDescription: "アーカイブすると、チャンネル一覧や検索結果に表示されなくなり、新たな書き込みもできなくなります。"
|
||||||
|
thisChannelArchived: "このチャンネルはアーカイブされています。"
|
||||||
|
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
export class ChannelArchive1683328299359 {
|
||||||
|
name = 'ChannelArchive1683328299359'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "channel" ADD "isArchived" boolean NOT NULL DEFAULT false`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_cc7c72974f1b2f385a8921f094" ON "channel" ("isArchived") `);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_cc7c72974f1b2f385a8921f094"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "isArchived"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,6 +75,7 @@ export class ChannelEntityService {
|
||||||
bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null,
|
bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null,
|
||||||
pinnedNoteIds: channel.pinnedNoteIds,
|
pinnedNoteIds: channel.pinnedNoteIds,
|
||||||
color: channel.color,
|
color: channel.color,
|
||||||
|
isArchived: channel.isArchived,
|
||||||
usersCount: channel.usersCount,
|
usersCount: channel.usersCount,
|
||||||
notesCount: channel.notesCount,
|
notesCount: channel.notesCount,
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,12 @@ export class Channel {
|
||||||
})
|
})
|
||||||
public color: string;
|
public color: string;
|
||||||
|
|
||||||
|
@Index()
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public isArchived: boolean;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('integer', {
|
@Column('integer', {
|
||||||
default: 0,
|
default: 0,
|
||||||
|
|
|
@ -30,6 +30,10 @@ export const packedChannelSchema = {
|
||||||
format: 'url',
|
format: 'url',
|
||||||
nullable: true, optional: false,
|
nullable: true, optional: false,
|
||||||
},
|
},
|
||||||
|
isArchived: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
notesCount: {
|
notesCount: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
nullable: false, optional: false,
|
nullable: false, optional: false,
|
||||||
|
|
|
@ -38,6 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const query = this.channelsRepository.createQueryBuilder('channel')
|
const query = this.channelsRepository.createQueryBuilder('channel')
|
||||||
.where('channel.lastNotedAt IS NOT NULL')
|
.where('channel.lastNotedAt IS NOT NULL')
|
||||||
|
.andWhere('channel.isArchived = FALSE')
|
||||||
.orderBy('channel.lastNotedAt', 'DESC');
|
.orderBy('channel.lastNotedAt', 'DESC');
|
||||||
|
|
||||||
const channels = await query.take(10).getMany();
|
const channels = await query.take(10).getMany();
|
||||||
|
|
|
@ -45,6 +45,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder(), ps.sinceId, ps.untilId)
|
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder(), ps.sinceId, ps.untilId)
|
||||||
|
.andWhere('channel.isArchived = FALSE')
|
||||||
.andWhere({ userId: me.id });
|
.andWhere({ userId: me.id });
|
||||||
|
|
||||||
const channels = await query
|
const channels = await query
|
||||||
|
|
|
@ -46,7 +46,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
private queryService: QueryService,
|
private queryService: QueryService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId);
|
const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId)
|
||||||
|
.andWhere('channel.isArchived = FALSE');
|
||||||
|
|
||||||
if (ps.query !== '') {
|
if (ps.query !== '') {
|
||||||
if (ps.type === 'nameAndDescription') {
|
if (ps.type === 'nameAndDescription') {
|
||||||
|
|
|
@ -47,6 +47,7 @@ export const paramDef = {
|
||||||
name: { type: 'string', minLength: 1, maxLength: 128 },
|
name: { type: 'string', minLength: 1, maxLength: 128 },
|
||||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||||
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
|
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
|
isArchived: { type: 'boolean', nullable: true },
|
||||||
pinnedNoteIds: {
|
pinnedNoteIds: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
|
@ -106,6 +107,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
...(ps.description !== undefined ? { description: ps.description } : {}),
|
...(ps.description !== undefined ? { description: ps.description } : {}),
|
||||||
...(ps.pinnedNoteIds !== undefined ? { pinnedNoteIds: ps.pinnedNoteIds } : {}),
|
...(ps.pinnedNoteIds !== undefined ? { pinnedNoteIds: ps.pinnedNoteIds } : {}),
|
||||||
...(ps.color !== undefined ? { color: ps.color } : {}),
|
...(ps.color !== undefined ? { color: ps.color } : {}),
|
||||||
|
...(typeof ps.isArchived === 'boolean' ? { isArchived: ps.isArchived } : {}),
|
||||||
...(banner ? { bannerId: banner.id } : {}),
|
...(banner ? { bannerId: banner.id } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -262,7 +262,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
|
|
||||||
let channel: Channel | null = null;
|
let channel: Channel | null = null;
|
||||||
if (ps.channelId != null) {
|
if (ps.channelId != null) {
|
||||||
channel = await this.channelsRepository.findOneBy({ id: ps.channelId });
|
channel = await this.channelsRepository.findOneBy({ id: ps.channelId, isArchived: false });
|
||||||
|
|
||||||
if (channel == null) {
|
if (channel == null) {
|
||||||
throw new ApiError(meta.errors.noSuchChannel);
|
throw new ApiError(meta.errors.noSuchChannel);
|
||||||
|
|
|
@ -46,8 +46,9 @@
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
<div>
|
<div class="_buttons">
|
||||||
<MkButton primary @click="save()"><i class="ti ti-device-floppy"></i> {{ channelId ? i18n.ts.save : i18n.ts.create }}</MkButton>
|
<MkButton primary @click="save()"><i class="ti ti-device-floppy"></i> {{ channelId ? i18n.ts.save : i18n.ts.create }}</MkButton>
|
||||||
|
<MkButton v-if="channelId" danger @click="archive()"><i class="ti ti-trash"></i> {{ i18n.ts.archive }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
@ -151,6 +152,23 @@ function save() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function archive() {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
title: i18n.t('channelArchiveConfirmTitle', { name: name }),
|
||||||
|
text: i18n.ts.channelArchiveConfirmDescription,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
os.api('channels/update', {
|
||||||
|
channelId: props.channelId,
|
||||||
|
isArchived: true,
|
||||||
|
}).then(() => {
|
||||||
|
os.success();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function setBannerImage(evt) {
|
function setBannerImage(evt) {
|
||||||
selectFile(evt.currentTarget ?? evt.target, null).then(file => {
|
selectFile(evt.currentTarget ?? evt.target, null).then(file => {
|
||||||
bannerId = file.id;
|
bannerId = file.id;
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="channel && tab === 'timeline'" class="_gaps">
|
<div v-if="channel && tab === 'timeline'" class="_gaps">
|
||||||
|
<MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo>
|
||||||
|
|
||||||
<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
|
<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
|
||||||
<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
|
<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
|
||||||
|
|
||||||
|
@ -77,6 +79,7 @@ import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import { defaultStore } from '@/store';
|
import { defaultStore } from '@/store';
|
||||||
import MkNote from '@/components/MkNote.vue';
|
import MkNote from '@/components/MkNote.vue';
|
||||||
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
Loading…
Reference in New Issue