Merge remote-branch 'misskey/develop'
This commit is contained in:
commit
42f60035c9
|
@ -33,6 +33,7 @@
|
|||
- Feat: TL上からノートが見えなくなるワードミュートであるハードミュートを追加
|
||||
- Enhance: アイコンデコレーションを複数設定できるように
|
||||
- Enhance: アイコンデコレーションの位置を微調整できるように
|
||||
- Enhance: つながりの公開範囲をフォロー/フォロワーで個別に設定可能に #12072
|
||||
- Fix: MFM `$[unixtime ]` に不正な値を入力した際に発生する各種エラーを修正
|
||||
|
||||
### Client
|
||||
|
@ -76,6 +77,8 @@
|
|||
- Fix: ノート中の絵文字をタップして「リアクションする」からリアクションした際にリアクションサウンドが鳴らない不具合を修正
|
||||
- Fix: ノート中のリアクションの表示を微調整 #12650
|
||||
- Fix: AiScriptの`readline`が不正な値を返すことがある問題を修正
|
||||
- Fix: 投票のみ/画像のみの引用RNが、通知欄でただのRNとして判定されるバグを修正
|
||||
- Fix: CWをつけて引用RNしても、普通のRNとして扱われてしまうバグを修正しました。
|
||||
|
||||
### Server
|
||||
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
|
||||
|
|
4
locales/index.d.ts
vendored
4
locales/index.d.ts
vendored
|
@ -977,8 +977,8 @@ export interface Locale {
|
|||
"classic": string;
|
||||
"muteThread": string;
|
||||
"unmuteThread": string;
|
||||
"ffVisibility": string;
|
||||
"ffVisibilityDescription": string;
|
||||
"followingVisibility": string;
|
||||
"followersVisibility": string;
|
||||
"continueThread": string;
|
||||
"deleteAccountConfirm": string;
|
||||
"incorrectPassword": string;
|
||||
|
|
|
@ -974,8 +974,8 @@ makeReactionsPublicDescription: "あなたがしたリアクション一覧を
|
|||
classic: "クラシック"
|
||||
muteThread: "スレッドをミュート"
|
||||
unmuteThread: "スレッドのミュートを解除"
|
||||
ffVisibility: "つながりの公開範囲"
|
||||
ffVisibilityDescription: "自分のフォロー/フォロワー情報の公開範囲を設定できます。"
|
||||
followingVisibility: "フォローの公開範囲"
|
||||
followersVisibility: "フォロワーの公開範囲"
|
||||
continueThread: "さらにスレッドを見る"
|
||||
deleteAccountConfirm: "アカウントが削除されます。よろしいですか?"
|
||||
incorrectPassword: "パスワードが間違っています。"
|
||||
|
|
35
packages/backend/migration/1702718871541-ffVisibility.js
Normal file
35
packages/backend/migration/1702718871541-ffVisibility.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class ffVisibility1702718871541 {
|
||||
constructor() {
|
||||
this.name = 'ffVisibility1702718871541';
|
||||
}
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_followingvisibility_enum" AS ENUM('public', 'followers', 'private')`);
|
||||
await queryRunner.query(`CREATE CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followingvisibility_enum") WITH INOUT AS ASSIGNMENT`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_followersVisibility_enum" AS ENUM('public', 'followers', 'private')`);
|
||||
await queryRunner.query(`CREATE CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followersVisibility_enum") WITH INOUT AS ASSIGNMENT`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "followingVisibility" "public"."user_profile_followingvisibility_enum" NOT NULL DEFAULT 'public'`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "followersVisibility" "public"."user_profile_followersVisibility_enum" NOT NULL DEFAULT 'public'`);
|
||||
await queryRunner.query(`UPDATE "user_profile" SET "followingVisibility" = "ffVisibility"`);
|
||||
await queryRunner.query(`UPDATE "user_profile" SET "followersVisibility" = "ffVisibility"`);
|
||||
await queryRunner.query(`DROP CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followersVisibility_enum")`);
|
||||
await queryRunner.query(`DROP CAST ("public"."user_profile_ffvisibility_enum" AS "public"."user_profile_followingvisibility_enum")`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "ffVisibility"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_ffvisibility_enum"`);
|
||||
}
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum" AS ENUM('public', 'followers', 'private')`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ADD "ffVisibility" "public"."user_profile_ffvisibility_enum" NOT NULL DEFAULT 'public'`);
|
||||
await queryRunner.query(`CREATE CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum") WITH INOUT AS ASSIGNMENT`);
|
||||
await queryRunner.query(`UPDATE "user_profile" SET ffVisibility = "user_profile"."followingVisibility"`);
|
||||
await queryRunner.query(`DROP CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum")`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followersVisibility"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followingVisibility"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_followersVisibility_enum"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_followingvisibility_enum"`);
|
||||
}
|
||||
}
|
|
@ -299,7 +299,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
// Check blocking
|
||||
if (data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)) {
|
||||
if (data.renote && this.isQuote(data)) {
|
||||
if (data.renote.userHost === null) {
|
||||
if (data.renote.userId !== user.id) {
|
||||
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
|
||||
|
@ -647,7 +647,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
|
||||
// If it is renote
|
||||
if (data.renote) {
|
||||
const type = data.text ? 'quote' : 'renote';
|
||||
const type = this.isQuote(data) ? 'quote' : 'renote';
|
||||
|
||||
// Notify
|
||||
if (data.renote.userHost === null) {
|
||||
|
@ -754,6 +754,11 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
return false;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private isQuote(note: Option): boolean {
|
||||
return !!note.text || !!note.cw || !!note.files || !!note.poll;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private incRenoteCount(renote: MiNote) {
|
||||
this.notesRepository.createQueryBuilder().update()
|
||||
|
@ -819,7 +824,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
private async renderNoteOrRenoteActivity(data: Option, note: MiNote) {
|
||||
if (data.localOnly) return null;
|
||||
|
||||
const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)
|
||||
const content = data.renote && this.isQuote(data)
|
||||
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
|
||||
: this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);
|
||||
|
||||
|
|
|
@ -368,13 +368,13 @@ export class UserEntityService implements OnModuleInit {
|
|||
const profile = opts.detail ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null;
|
||||
|
||||
const followingCount = profile == null ? null :
|
||||
(profile.ffVisibility === 'public') || isMe ? user.followingCount :
|
||||
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
|
||||
(profile.followingVisibility === 'public') || isMe ? user.followingCount :
|
||||
(profile.followingVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
|
||||
null;
|
||||
|
||||
const followersCount = profile == null ? null :
|
||||
(profile.ffVisibility === 'public') || isMe ? user.followersCount :
|
||||
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
|
||||
(profile.followersVisibility === 'public') || isMe ? user.followersCount :
|
||||
(profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
|
||||
null;
|
||||
|
||||
const isModerator = isMe && opts.detail ? this.roleService.isModerator(user) : null;
|
||||
|
@ -455,7 +455,8 @@ export class UserEntityService implements OnModuleInit {
|
|||
pinnedPageId: profile!.pinnedPageId,
|
||||
pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null,
|
||||
publicReactions: profile!.publicReactions,
|
||||
ffVisibility: profile!.ffVisibility,
|
||||
followersVisibility: profile!.followersVisibility,
|
||||
followingVisibility: profile!.followingVisibility,
|
||||
twoFactorEnabled: profile!.twoFactorEnabled,
|
||||
usePasswordLessLogin: profile!.usePasswordLessLogin,
|
||||
securityKeys: profile!.twoFactorEnabled
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
||||
import { obsoleteNotificationTypes, ffVisibility, notificationTypes } from '@/types.js';
|
||||
import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes } from '@/types.js';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
import { MiPage } from './Page.js';
|
||||
|
@ -94,10 +94,16 @@ export class MiUserProfile {
|
|||
public publicReactions: boolean;
|
||||
|
||||
@Column('enum', {
|
||||
enum: ffVisibility,
|
||||
enum: followingVisibilities,
|
||||
default: 'public',
|
||||
})
|
||||
public ffVisibility: typeof ffVisibility[number];
|
||||
public followingVisibility: typeof followingVisibilities[number];
|
||||
|
||||
@Column('enum', {
|
||||
enum: followersVisibilities,
|
||||
default: 'public',
|
||||
})
|
||||
public followersVisibility: typeof followersVisibilities[number];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
|
|
|
@ -319,7 +319,12 @@ export const packedUserDetailedNotMeOnlySchema = {
|
|||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
ffVisibility: {
|
||||
followingVisibility: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: ['public', 'followers', 'private'],
|
||||
},
|
||||
followersVisibility: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: ['public', 'followers', 'private'],
|
||||
|
|
|
@ -195,11 +195,11 @@ export class ActivityPubServerService {
|
|||
//#region Check ff visibility
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
if (profile.ffVisibility === 'private') {
|
||||
if (profile.followersVisibility === 'private') {
|
||||
reply.code(403);
|
||||
reply.header('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
} else if (profile.ffVisibility === 'followers') {
|
||||
} else if (profile.followersVisibility === 'followers') {
|
||||
reply.code(403);
|
||||
reply.header('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
|
@ -287,11 +287,11 @@ export class ActivityPubServerService {
|
|||
//#region Check ff visibility
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
if (profile.ffVisibility === 'private') {
|
||||
if (profile.followingVisibility === 'private') {
|
||||
reply.code(403);
|
||||
reply.header('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
} else if (profile.ffVisibility === 'followers') {
|
||||
} else if (profile.followingVisibility === 'followers') {
|
||||
reply.code(403);
|
||||
reply.header('Cache-Control', 'public, max-age=30');
|
||||
return;
|
||||
|
|
|
@ -178,7 +178,8 @@ export const paramDef = {
|
|||
receiveAnnouncementEmail: { type: 'boolean' },
|
||||
alwaysMarkNsfw: { type: 'boolean' },
|
||||
autoSensitive: { type: 'boolean' },
|
||||
ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
|
||||
followingVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
|
||||
followersVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
|
||||
pinnedPageId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
mutedWords: muteWords,
|
||||
hardMutedWords: muteWords,
|
||||
|
@ -243,7 +244,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
|
||||
if (ps.location !== undefined) profileUpdates.location = ps.location;
|
||||
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
|
||||
if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility;
|
||||
if (ps.followingVisibility !== undefined) profileUpdates.followingVisibility = ps.followingVisibility;
|
||||
if (ps.followersVisibility !== undefined) profileUpdates.followersVisibility = ps.followersVisibility;
|
||||
|
||||
function checkMuteWordCount(mutedWords: (string[] | string)[], limit: number) {
|
||||
// TODO: ちゃんと数える
|
||||
|
|
|
@ -93,11 +93,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
if (profile.ffVisibility === 'private') {
|
||||
if (profile.followersVisibility === 'private') {
|
||||
if (me == null || (me.id !== user.id)) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
}
|
||||
} else if (profile.ffVisibility === 'followers') {
|
||||
} else if (profile.followersVisibility === 'followers') {
|
||||
if (me == null) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
} else if (me.id !== user.id) {
|
||||
|
|
|
@ -101,11 +101,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
|
||||
if (profile.ffVisibility === 'private') {
|
||||
if (profile.followingVisibility === 'private') {
|
||||
if (me == null || (me.id !== user.id)) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
}
|
||||
} else if (profile.ffVisibility === 'followers') {
|
||||
} else if (profile.followingVisibility === 'followers') {
|
||||
if (me == null) {
|
||||
throw new ApiError(meta.errors.forbidden);
|
||||
} else if (me.id !== user.id) {
|
||||
|
|
|
@ -60,7 +60,7 @@ export class FeedService {
|
|||
title: `${author.name} (@${user.username}@${this.config.host})`,
|
||||
updated: notes.length !== 0 ? this.idService.parse(notes[0].id).date : undefined,
|
||||
generator: 'CherryPick',
|
||||
description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
|
||||
description: `${user.notesCount} Notes, ${profile.followingVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.followersVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
|
||||
link: author.link,
|
||||
image: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
|
||||
feedLinks: {
|
||||
|
|
|
@ -26,7 +26,8 @@ export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as
|
|||
|
||||
export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
|
||||
|
||||
export const ffVisibility = ['public', 'followers', 'private'] as const;
|
||||
export const followingVisibilities = ['public', 'followers', 'private'] as const;
|
||||
export const followersVisibilities = ['public', 'followers', 'private'] as const;
|
||||
|
||||
export const moderationLogTypes = [
|
||||
'updateServerSettings',
|
||||
|
|
|
@ -26,9 +26,10 @@ describe('FF visibility', () => {
|
|||
await app.close();
|
||||
});
|
||||
|
||||
test('ffVisibility が public なユーザーのフォロー/フォロワーを誰でも見れる', async () => {
|
||||
test('followingVisibility, followersVisibility がともに public なユーザーのフォロー/フォロワーを誰でも見れる', async () => {
|
||||
await api('/i/update', {
|
||||
ffVisibility: 'public',
|
||||
followingVisibility: 'public',
|
||||
followersVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
|
@ -44,9 +45,88 @@ describe('FF visibility', () => {
|
|||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
});
|
||||
|
||||
test('ffVisibility が followers なユーザーのフォロー/フォロワーを自分で見れる', async () => {
|
||||
test('followingVisibility が public であれば followersVisibility の設定に関わらずユーザーのフォローを誰でも見れる', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
ffVisibility: 'followers',
|
||||
followingVisibility: 'public',
|
||||
followersVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'public',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'public',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
});
|
||||
|
||||
test('followersVisibility が public であれば followingVisibility の設定に関わらずユーザーのフォロワーを誰でも見れる', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'public',
|
||||
followersVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
});
|
||||
|
||||
test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーを自分で見れる', async () => {
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
|
@ -62,9 +142,88 @@ describe('FF visibility', () => {
|
|||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
});
|
||||
|
||||
test('ffVisibility が followers なユーザーのフォロー/フォロワーを非フォロワーが見れない', async () => {
|
||||
test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらず自分で見れる', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
ffVisibility: 'followers',
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
});
|
||||
|
||||
test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらず自分で見れる', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'public',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
});
|
||||
|
||||
test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーを非フォロワーが見れない', async () => {
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
|
@ -78,9 +237,82 @@ describe('FF visibility', () => {
|
|||
assert.strictEqual(followersRes.status, 400);
|
||||
});
|
||||
|
||||
test('ffVisibility が followers なユーザーのフォロー/フォロワーをフォロワーが見れる', async () => {
|
||||
test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらず非フォロワーが見れない', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
ffVisibility: 'followers',
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 400);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 400);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 400);
|
||||
}
|
||||
});
|
||||
|
||||
test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらず非フォロワーが見れない', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'public',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 400);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 400);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 400);
|
||||
}
|
||||
});
|
||||
|
||||
test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーをフォロワーが見れる', async () => {
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
await api('/following/create', {
|
||||
|
@ -100,9 +332,106 @@ describe('FF visibility', () => {
|
|||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
});
|
||||
|
||||
test('ffVisibility が private なユーザーのフォロー/フォロワーを自分で見れる', async () => {
|
||||
test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらずフォロワーが見れる', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
ffVisibility: 'private',
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'public',
|
||||
}, alice);
|
||||
await api('/following/create', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
await api('/following/create', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
await api('/following/create', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
});
|
||||
|
||||
test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらずフォロワーが見れる', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'public',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
await api('/following/create', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
await api('/following/create', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
await api('/following/create', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
});
|
||||
|
||||
test('followingVisibility, followersVisibility がともに private なユーザーのフォロー/フォロワーを自分で見れる', async () => {
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
|
@ -118,9 +447,88 @@ describe('FF visibility', () => {
|
|||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
});
|
||||
|
||||
test('ffVisibility が private なユーザーのフォロー/フォロワーを他人が見れない', async () => {
|
||||
test('followingVisibility が private なユーザーのフォローを followersVisibility の設定に関わらず自分で見れる', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
ffVisibility: 'private',
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
||||
}
|
||||
});
|
||||
|
||||
test('followersVisibility が private なユーザーのフォロワーを followingVisibility の設定に関わらず自分で見れる', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'public',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, alice);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
||||
}
|
||||
});
|
||||
|
||||
test('followingVisibility, followersVisibility がともに private なユーザーのフォロー/フォロワーを他人が見れない', async () => {
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
|
@ -134,36 +542,129 @@ describe('FF visibility', () => {
|
|||
assert.strictEqual(followersRes.status, 400);
|
||||
});
|
||||
|
||||
describe('AP', () => {
|
||||
test('ffVisibility が public 以外ならばAPからは取得できない', async () => {
|
||||
test('followingVisibility が private なユーザーのフォローを followersVisibility の設定に関わらず他人が見れない', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
ffVisibility: 'public',
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 400);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 400);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await api('/users/following', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followingRes.status, 400);
|
||||
}
|
||||
});
|
||||
|
||||
test('followersVisibility が private なユーザーのフォロワーを followingVisibility の設定に関わらず他人が見れない', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'public',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 400);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 400);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await api('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, bob);
|
||||
assert.strictEqual(followersRes.status, 400);
|
||||
}
|
||||
});
|
||||
|
||||
describe('AP', () => {
|
||||
test('followingVisibility が public 以外ならばAPからはフォローを取得できない', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json');
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json');
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
followingVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json');
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
}
|
||||
});
|
||||
|
||||
test('followersVisibility が public 以外ならばAPからはフォロワーを取得できない', async () => {
|
||||
{
|
||||
await api('/i/update', {
|
||||
followersVisibility: 'public',
|
||||
}, alice);
|
||||
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
ffVisibility: 'followers',
|
||||
followersVisibility: 'followers',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json');
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
assert.strictEqual(followersRes.status, 403);
|
||||
}
|
||||
{
|
||||
await api('/i/update', {
|
||||
ffVisibility: 'private',
|
||||
followersVisibility: 'private',
|
||||
}, alice);
|
||||
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json');
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
assert.strictEqual(followersRes.status, 403);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -112,7 +112,8 @@ describe('ユーザー', () => {
|
|||
pinnedPageId: user.pinnedPageId,
|
||||
pinnedPage: user.pinnedPage,
|
||||
publicReactions: user.publicReactions,
|
||||
ffVisibility: user.ffVisibility,
|
||||
followingVisibility: user.followingVisibility,
|
||||
followersVisibility: user.followersVisibility,
|
||||
twoFactorEnabled: user.twoFactorEnabled,
|
||||
usePasswordLessLogin: user.usePasswordLessLogin,
|
||||
securityKeys: user.securityKeys,
|
||||
|
@ -387,7 +388,8 @@ describe('ユーザー', () => {
|
|||
assert.strictEqual(response.pinnedPageId, null);
|
||||
assert.strictEqual(response.pinnedPage, null);
|
||||
assert.strictEqual(response.publicReactions, true);
|
||||
assert.strictEqual(response.ffVisibility, 'public');
|
||||
assert.strictEqual(response.followingVisibility, 'public');
|
||||
assert.strictEqual(response.followersVisibility, 'public');
|
||||
assert.strictEqual(response.twoFactorEnabled, false);
|
||||
assert.strictEqual(response.usePasswordLessLogin, false);
|
||||
assert.strictEqual(response.securityKeys, false);
|
||||
|
@ -497,9 +499,12 @@ describe('ユーザー', () => {
|
|||
{ parameters: (): object => ({ alwaysMarkNsfw: false }) },
|
||||
{ parameters: (): object => ({ autoSensitive: true }) },
|
||||
{ parameters: (): object => ({ autoSensitive: false }) },
|
||||
{ parameters: (): object => ({ ffVisibility: 'private' }) },
|
||||
{ parameters: (): object => ({ ffVisibility: 'followers' }) },
|
||||
{ parameters: (): object => ({ ffVisibility: 'public' }) },
|
||||
{ parameters: (): object => ({ followingVisibility: 'private' }) },
|
||||
{ parameters: (): object => ({ followingVisibility: 'followers' }) },
|
||||
{ parameters: (): object => ({ followingVisibility: 'public' }) },
|
||||
{ parameters: (): object => ({ followersVisibility: 'private' }) },
|
||||
{ parameters: (): object => ({ followersVisibility: 'followers' }) },
|
||||
{ parameters: (): object => ({ followersVisibility: 'public' }) },
|
||||
{ parameters: (): object => ({ mutedWords: Array(19).fill(['xxxxx']) }) },
|
||||
{ parameters: (): object => ({ mutedWords: [['x'.repeat(194)]] }) },
|
||||
{ parameters: (): object => ({ mutedWords: [] }) },
|
||||
|
|
|
@ -4,7 +4,9 @@ export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as
|
|||
|
||||
export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
|
||||
|
||||
export const ffVisibility = ['public', 'followers', 'private'] as const;
|
||||
export const followingVisibilities = ['public', 'followers', 'private'] as const;
|
||||
|
||||
export const followersVisibilities = ['public', 'followers', 'private'] as const;
|
||||
|
||||
export const permissions = [
|
||||
'read:account',
|
||||
|
|
|
@ -16,7 +16,8 @@ export const permissions = consts.permissions;
|
|||
export const notificationTypes = consts.notificationTypes;
|
||||
export const noteVisibilities = consts.noteVisibilities;
|
||||
export const mutedNoteReasons = consts.mutedNoteReasons;
|
||||
export const ffVisibility = consts.ffVisibility;
|
||||
export const followingVisibilities = consts.followingVisibilities;
|
||||
export const followersVisibilities = consts.followersVisibilities;
|
||||
export const moderationLogTypes = consts.moderationLogTypes;
|
||||
|
||||
// api extractor not supported yet
|
||||
|
|
|
@ -82,7 +82,8 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi
|
|||
birthday: '2014-06-20',
|
||||
createdAt: '2016-12-28T22:49:51.000Z',
|
||||
description: 'I am a cool user!',
|
||||
ffVisibility: 'public',
|
||||
followingVisibility: 'public',
|
||||
followersVisibility: 'public',
|
||||
roles: [],
|
||||
fields: [
|
||||
{
|
||||
|
|
|
@ -17,6 +17,7 @@ import MkButton from '@/components/MkButton.vue';
|
|||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
text: string | null;
|
||||
renote: Misskey.entities.Note | null;
|
||||
files: Misskey.entities.DriveFile[];
|
||||
poll?: {
|
||||
expiresAt: string | null;
|
||||
|
@ -41,6 +42,7 @@ const emit = defineEmits<{
|
|||
const label = computed(() => {
|
||||
return concat([
|
||||
props.text ? [i18n.t('_cw.chars', { count: props.text.length })] : [],
|
||||
props.renote != null ? [i18n.ts.quote] : [],
|
||||
props.files.length !== 0 ? [i18n.t('_cw.files', { count: props.files.length })] : [],
|
||||
props.poll != null ? [i18n.ts.poll] : [],
|
||||
] as string[][]).join(' / ');
|
||||
|
|
|
@ -69,7 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkEvent v-if="appearNote.event" :note="appearNote"/>
|
||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||
<Mfm v-if="appearNote.cw != ''" :text="appearNote.cw" :author="appearNote.user" :nyaize="noNyaize ? false : 'respect'"/>
|
||||
<MkCwButton v-model="showContent" :text="appearNote.text" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;" @click.stop/>
|
||||
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;" @click.stop/>
|
||||
</p>
|
||||
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
|
||||
<div :class="$style.text">
|
||||
|
@ -287,6 +287,7 @@ if (noteViewInterruptors.length > 0) {
|
|||
const isRenote = (
|
||||
note.value.renote != null &&
|
||||
note.value.text == null &&
|
||||
note.value.cw == null &&
|
||||
note.value.fileIds.length === 0 &&
|
||||
note.value.poll == null
|
||||
);
|
||||
|
|
|
@ -22,10 +22,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div :class="$style.statusItem">
|
||||
<p :class="$style.statusItemLabel">{{ i18n.ts.notes }}</p><span :class="$style.statusItemValue">{{ number(user.notesCount) }}</span>
|
||||
</div>
|
||||
<div v-if="isFfVisibleForMe(user)" :class="$style.statusItem">
|
||||
<div v-if="isFollowingVisibleForMe(user)" :class="$style.statusItem">
|
||||
<p :class="$style.statusItemLabel">{{ i18n.ts.following }}</p><span :class="$style.statusItemValue">{{ number(user.followingCount) }}</span>
|
||||
</div>
|
||||
<div v-if="isFfVisibleForMe(user)" :class="$style.statusItem">
|
||||
<div v-if="isFollowersVisibleForMe(user)" :class="$style.statusItem">
|
||||
<p :class="$style.statusItemLabel">{{ i18n.ts.followers }}</p><span :class="$style.statusItemValue">{{ number(user.followersCount) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -40,7 +40,7 @@ import number from '@/filters/number.js';
|
|||
import { userPage } from '@/filters/user.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { isFfVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
|
||||
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
|
||||
|
||||
defineProps<{
|
||||
user: Misskey.entities.UserDetailed;
|
||||
|
|
|
@ -35,11 +35,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div :class="$style.statusItemLabel">{{ i18n.ts.notes }}</div>
|
||||
<div>{{ number(user.notesCount) }}</div>
|
||||
</div>
|
||||
<div v-if="isFfVisibleForMe(user)" :class="$style.statusItem">
|
||||
<div v-if="isFollowingVisibleForMe(user)" :class="$style.statusItem">
|
||||
<div :class="$style.statusItemLabel">{{ i18n.ts.following }}</div>
|
||||
<div>{{ number(user.followingCount) }}</div>
|
||||
</div>
|
||||
<div v-if="isFfVisibleForMe(user)" :class="$style.statusItem">
|
||||
<div v-if="isFollowersVisibleForMe(user)" :class="$style.statusItem">
|
||||
<div :class="$style.statusItemLabel">{{ i18n.ts.followers }}</div>
|
||||
<div>{{ number(user.followersCount) }}</div>
|
||||
</div>
|
||||
|
@ -65,7 +65,7 @@ import number from '@/filters/number.js';
|
|||
import { i18n } from '@/i18n.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { isFfVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
|
||||
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
|
||||
|
||||
const props = defineProps<{
|
||||
showing: boolean;
|
||||
|
|
|
@ -13,12 +13,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #caption>{{ i18n.ts.makeReactionsPublicDescription }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSelect v-model="ffVisibility" @update:modelValue="save()">
|
||||
<template #label>{{ i18n.ts.ffVisibility }}</template>
|
||||
<MkSelect v-model="followingVisibility" @update:modelValue="save()">
|
||||
<template #label>{{ i18n.ts.followingVisibility }}</template>
|
||||
<option value="public">{{ i18n.ts._ffVisibility.public }}</option>
|
||||
<option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
|
||||
<option value="private">{{ i18n.ts._ffVisibility.private }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkSelect v-model="followersVisibility" @update:modelValue="save()">
|
||||
<template #label>{{ i18n.ts.followersVisibility }}</template>
|
||||
<option value="public">{{ i18n.ts._ffVisibility.public }}</option>
|
||||
<option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
|
||||
<option value="private">{{ i18n.ts._ffVisibility.private }}</option>
|
||||
<template #caption>{{ i18n.ts.ffVisibilityDescription }}</template>
|
||||
</MkSelect>
|
||||
|
||||
<MkSwitch v-model="hideOnlineStatus" @update:modelValue="save()">
|
||||
|
@ -84,7 +90,8 @@ const preventAiLearning = ref($i.preventAiLearning);
|
|||
const isExplorable = ref($i.isExplorable);
|
||||
const hideOnlineStatus = ref($i.hideOnlineStatus);
|
||||
const publicReactions = ref($i.publicReactions);
|
||||
const ffVisibility = ref($i.ffVisibility);
|
||||
const followingVisibility = ref($i?.followingVisibility);
|
||||
const followersVisibility = ref($i?.followersVisibility);
|
||||
|
||||
const defaultNoteVisibility = computed(defaultStore.makeGetterSetter('defaultNoteVisibility'));
|
||||
const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
|
||||
|
@ -100,7 +107,8 @@ function save() {
|
|||
isExplorable: !!isExplorable.value,
|
||||
hideOnlineStatus: !!hideOnlineStatus.value,
|
||||
publicReactions: !!publicReactions.value,
|
||||
ffVisibility: ffVisibility.value,
|
||||
followingVisibility: followingVisibility.value,
|
||||
followersVisibility: followersVisibility.value,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -127,11 +127,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<b>{{ number(user.notesCount) }}</b>
|
||||
<span>{{ i18n.ts.notes }}</span>
|
||||
</MkA>
|
||||
<MkA v-if="isFfVisibleForMe(user)" :to="userPage(user, 'following')">
|
||||
<MkA v-if="isFollowingVisibleForMe(user)" :to="userPage(user, 'following')">
|
||||
<b>{{ number(user.followingCount) }}</b>
|
||||
<span>{{ i18n.ts.following }}</span>
|
||||
</MkA>
|
||||
<MkA v-if="isFfVisibleForMe(user)" :to="userPage(user, 'followers')">
|
||||
<MkA v-if="isFollowersVisibleForMe(user)" :to="userPage(user, 'followers')">
|
||||
<b>{{ number(user.followersCount) }}</b>
|
||||
<span>{{ i18n.ts.followers }}</span>
|
||||
</MkA>
|
||||
|
@ -189,7 +189,7 @@ import { $i, iAmModerator } from '@/account.js';
|
|||
import { dateString } from '@/filters/date.js';
|
||||
import { confetti } from '@/scripts/confetti.js';
|
||||
import { api } from '@/os.js';
|
||||
import { isFfVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
|
||||
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { editNickname } from '@/scripts/edit-nickname.js';
|
||||
|
|
|
@ -6,11 +6,19 @@
|
|||
import * as Misskey from 'cherrypick-js';
|
||||
import { $i } from '@/account.js';
|
||||
|
||||
export function isFfVisibleForMe(user: Misskey.entities.UserDetailed): boolean {
|
||||
export function isFollowingVisibleForMe(user: Misskey.entities.UserDetailed): boolean {
|
||||
if ($i && $i.id === user.id) return true;
|
||||
|
||||
if (user.ffVisibility === 'private') return false;
|
||||
if (user.ffVisibility === 'followers' && !user.isFollowing) return false;
|
||||
if (user.followingVisibility === 'private') return false;
|
||||
if (user.followingVisibility === 'followers' && !user.isFollowing) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
export function isFollowersVisibleForMe(user: Misskey.entities.UserDetailed): boolean {
|
||||
if ($i && $i.id === user.id) return true;
|
||||
|
||||
if (user.followersVisibility === 'private') return false;
|
||||
if (user.followersVisibility === 'followers' && !user.isFollowing) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue