Compare commits

...

30 Commits

Author SHA1 Message Date
Daiki Mizukami
dda7928f22
Merge 9a7162a199 into e8bf6285cb 2024-12-11 07:53:17 +00:00
かっこかり
e8bf6285cb
fix(frontend): ノートがログインしているユーザーしか見れない場合にログインをキャンセルした場合その後の動線がなくなる問題を修正 (#15101)
* fix(frontend): ノートがログインしているユーザーしか見れない場合にログインをキャンセルすると一切の処理が停止する問題を修正

* Update Changelog

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2024-12-10 10:42:12 +09:00
かっこかり
074b7b0bee
fix(frontend): 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正 (#15102)
* Resolve frontend/backend contradiction for home visibility embeds

This now uses the same check from `packages/frontend/src/scripts/get-note-menu.ts`

* Update Changelog

---------

Co-authored-by: CenTdemeern1 <timo.herngreen@gmail.com>
2024-12-10 10:36:03 +09:00
かっこかり
020c191e2c
fix(frontend): MiAuth認可画面のデザイン修正 (#15106) 2024-12-10 10:29:40 +09:00
syuilo
dac3b1f405 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-11-30 13:20:51 +09:00
syuilo
fa271cf84e Update about-misskey.vue 2024-11-30 13:20:49 +09:00
Daiki Mizukami
9a7162a199
Merge branch 'develop' into remote-public-reactions 2024-11-25 12:19:03 +09:00
Daiki Mizukami
8842c56956
test(backend): skip federation test for updating publicReactions
ローカルでは通るが、何故かCIでは通らないため。
2024-11-09 18:53:48 +09:00
Daiki Mizukami
38c0ce7634
docs(changelog): update CHANGELOG.md 2024-11-09 18:43:12 +09:00
Daiki Mizukami
ed39b8c485
Merge branch 'develop' into remote-public-reactions 2024-11-09 18:40:35 +09:00
Daiki Mizukami
2004d5e2f8
test(backend): add federation test for publicReactions 2024-11-09 18:31:41 +09:00
Daiki Mizukami
5257591c16
Merge branch 'develop' into remote-public-reactions 2024-11-09 11:32:57 +09:00
Daiki Mizukami
97f96ab92d
Merge branch 'develop' into remote-public-reactions 2024-10-03 21:50:55 +09:00
Daiki Mizukami
04616ff996
enhance(backend): always render URI of liked collection
https://socialhub.activitypub.rocks/t/fep-c0e0-emoji-reactions/4443/46
2024-08-20 02:16:14 +09:00
Daiki Mizukami
0949c0e0cd
enhance(backend): embed liked collection into actor object 2024-08-20 01:48:58 +09:00
Daiki Mizukami
f4ea906516
Merge branch 'develop' into remote-public-reactions 2024-08-20 00:24:36 +09:00
Daiki Mizukami
8d41e3c8e4
fix(backend): filter out notes by suspended users in liked collection 2024-08-19 23:28:36 +09:00
Daiki Mizukami
17da05ca54
fix(backend): align visibility check of liked collection with outbox 2024-08-19 23:22:25 +09:00
Daiki Mizukami
7074f80ea2
refactor(backend): remove redundant SQL clause 2024-08-19 23:16:08 +09:00
Daiki Mizukami
2f2a0e3c5e
enhance(backend): remove totalItems from liked collection 2024-08-14 18:12:45 +09:00
Daiki Mizukami
69bf40341d
enhance(backend): embed local notes in liked collection 2024-08-14 14:40:53 +09:00
Daiki Mizukami
985d582166
fix(backend): fix liked collection
The `liked` collection is a list of objects liked by the actor, not the
associated `Like` activities.
2024-08-14 14:06:12 +09:00
Daiki Mizukami
d88193a16e
style(backend): tweak 2024-08-10 07:28:44 +09:00
Daiki Mizukami
8b7ae92b78
test(backend): update tests 2024-08-10 07:09:50 +09:00
Daiki Mizukami
699cbd0d77
fix(backend): fix 1723208290742-remote-public-reactions-set-false.js
https://github.com/misskey-dev/misskey/pull/14383#pullrequestreview-2230486515

Co-authored-by: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
2024-08-10 00:47:17 +09:00
Daiki Mizukami
dabe38a991
fix: set publicReactions of remote users to false 2024-08-09 22:10:50 +09:00
Daiki Mizukami
eec885e2f5
docs(changelog): update CHANGELOG.md 2024-08-09 20:05:22 +09:00
Daiki Mizukami
34c201ff01
Revert "fix: Hide reactions of all remote users"
This reverts commit 32b1c3de0e.
2024-08-09 19:24:31 +09:00
Daiki Mizukami
630bee0cc4
enhance(backend): check visibility of reactions of remote users 2024-08-09 19:24:31 +09:00
Daiki Mizukami
4ad43bca42
feat(backend): implement liked collection of ActivityPub actors 2024-08-09 19:22:45 +09:00
14 changed files with 185 additions and 36 deletions

View File

@ -1,11 +1,14 @@
## 2024.11.1 ## 2024.11.1
### General ### General
- - Enhance: リアクションの一覧の公開設定が連合されるように
### Client ### Client
- Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正
- Fix: サーバー情報メニューに区切り線が不足していたのを修正 - Fix: サーバー情報メニューに区切り線が不足していたのを修正
- Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正
- Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803)
### Server ### Server
- Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 )

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class RemotePublicReactionsSetFalse1723208290742 {
name = 'RemotePublicReactionsSetFalse1723208290742'
async up(queryRunner) {
await queryRunner.query(`UPDATE "user_profile" SET "publicReactions" = FALSE WHERE "userHost" IS NOT NULL`);
}
async down(queryRunner) {
// no valid down migration
}
}

View File

@ -472,6 +472,13 @@ export class ApRendererService {
const hashtagTags = user.tags.map(tag => this.renderHashtag(tag)); const hashtagTags = user.tags.map(tag => this.renderHashtag(tag));
const likedId = `${id}/liked`;
const liked = this.renderOrderedCollection(
likedId,
undefined,
profile.publicReactions ? `${likedId}?page=true` : undefined,
);
const tag = [ const tag = [
...apemojis, ...apemojis,
...hashtagTags, ...hashtagTags,
@ -486,6 +493,7 @@ export class ApRendererService {
outbox: `${id}/outbox`, outbox: `${id}/outbox`,
followers: `${id}/followers`, followers: `${id}/followers`,
following: `${id}/following`, following: `${id}/following`,
liked,
featured: `${id}/collections/featured`, featured: `${id}/collections/featured`,
sharedInbox: `${this.config.url}/inbox`, sharedInbox: `${this.config.url}/inbox`,
endpoints: { sharedInbox: `${this.config.url}/inbox` }, endpoints: { sharedInbox: `${this.config.url}/inbox` },
@ -670,7 +678,7 @@ export class ApRendererService {
* @param orderedItems attached objects (optional) * @param orderedItems attached objects (optional)
*/ */
@bindThis @bindThis
public renderOrderedCollection(id: string, totalItems: number, first?: string, last?: string, orderedItems?: IObject[]) { public renderOrderedCollection(id: string, totalItems?: number, first?: string, last?: string, orderedItems?: IObject[]) {
const page: any = { const page: any = {
id, id,
type: 'OrderedCollection', type: 'OrderedCollection',

View File

@ -321,17 +321,17 @@ export class ApPersonService implements OnModuleInit {
const isBot = getApType(object) === 'Service' || getApType(object) === 'Application'; const isBot = getApType(object) === 'Service' || getApType(object) === 'Application';
const [followingVisibility, followersVisibility] = await Promise.all( const [publicReactions, followingIsPublic, followersIsPublic] = await Promise.all(
[ [
this.isPublicCollection(person.liked, resolver),
this.isPublicCollection(person.following, resolver), this.isPublicCollection(person.following, resolver),
this.isPublicCollection(person.followers, resolver), this.isPublicCollection(person.followers, resolver),
].map((p): Promise<'public' | 'private'> => p ].map((p): Promise<boolean> => p
.then(isPublic => isPublic ? 'public' : 'private')
.catch(err => { .catch(err => {
if (!(err instanceof StatusError) || err.isRetryable) { if (!(err instanceof StatusError) || err.isRetryable) {
this.logger.error('error occurred while fetching following/followers collection', { stack: err }); this.logger.error('error occurred while fetching actor collection', { stack: err });
} }
return 'private'; return false;
}), }),
), ),
); );
@ -411,8 +411,9 @@ export class ApPersonService implements OnModuleInit {
followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null, followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null,
url, url,
fields, fields,
followingVisibility, publicReactions,
followersVisibility, followingVisibility: followingIsPublic ? 'public' : 'private',
followersVisibility: followersIsPublic ? 'public' : 'private',
birthday: bday?.[0] ?? null, birthday: bday?.[0] ?? null,
location: person['vcard:Address'] ?? null, location: person['vcard:Address'] ?? null,
userHost: host, userHost: host,
@ -522,19 +523,19 @@ export class ApPersonService implements OnModuleInit {
const tags = extractApHashtags(person.tag).map(normalizeForSearch).splice(0, 32); const tags = extractApHashtags(person.tag).map(normalizeForSearch).splice(0, 32);
const [followingVisibility, followersVisibility] = await Promise.all( const [publicReactions, followingIsPublic, followersIsPublic] = await Promise.all(
[ [
this.isPublicCollection(person.liked, resolver),
this.isPublicCollection(person.following, resolver), this.isPublicCollection(person.following, resolver),
this.isPublicCollection(person.followers, resolver), this.isPublicCollection(person.followers, resolver),
].map((p): Promise<'public' | 'private' | undefined> => p ].map((p): Promise<boolean | undefined> => p
.then(isPublic => isPublic ? 'public' : 'private')
.catch(err => { .catch(err => {
if (!(err instanceof StatusError) || err.isRetryable) { if (!(err instanceof StatusError) || err.isRetryable) {
this.logger.error('error occurred while fetching following/followers collection', { stack: err }); this.logger.error('error occurred while fetching actor collection', { stack: err });
// Do not update the visibiility on transient errors. // Do not update the visibiility on transient errors.
return undefined; return undefined;
} }
return 'private'; return false;
}), }),
), ),
); );
@ -618,8 +619,9 @@ export class ApPersonService implements OnModuleInit {
fields, fields,
description: _description, description: _description,
followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null, followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null,
followingVisibility, publicReactions,
followersVisibility, followingVisibility: followingIsPublic ? 'public' : followingIsPublic === false ? 'private' : undefined,
followersVisibility: followersIsPublic ? 'public' : followersIsPublic === false ? 'private' : undefined,
birthday: bday?.[0] ?? null, birthday: bday?.[0] ?? null,
location: person['vcard:Address'] ?? null, location: person['vcard:Address'] ?? null,
}); });

View File

@ -103,14 +103,14 @@ export interface IActivity extends IObject {
export interface ICollection extends IObject { export interface ICollection extends IObject {
type: 'Collection'; type: 'Collection';
totalItems: number; totalItems?: number;
first?: IObject | string; first?: IObject | string;
items?: ApObject; items?: ApObject;
} }
export interface IOrderedCollection extends IObject { export interface IOrderedCollection extends IObject {
type: 'OrderedCollection'; type: 'OrderedCollection';
totalItems: number; totalItems?: number;
first?: IObject | string; first?: IObject | string;
orderedItems?: ApObject; orderedItems?: ApObject;
} }
@ -190,6 +190,7 @@ export interface IActor extends IObject {
following?: string | ICollection | IOrderedCollection; following?: string | ICollection | IOrderedCollection;
featured?: string | IOrderedCollection; featured?: string | IOrderedCollection;
outbox: string | IOrderedCollection; outbox: string | IOrderedCollection;
liked?: string | ICollection | IOrderedCollection;
endpoints?: { endpoints?: {
sharedInbox?: string; sharedInbox?: string;
}; };

View File

@ -545,7 +545,7 @@ export class UserEntityService implements OnModuleInit {
}), }),
pinnedPageId: profile!.pinnedPageId, pinnedPageId: profile!.pinnedPageId,
pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null, pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null,
publicReactions: this.isLocalUser(user) ? profile!.publicReactions : false, // https://github.com/misskey-dev/misskey/issues/12964 publicReactions: profile!.publicReactions,
followersVisibility: profile!.followersVisibility, followersVisibility: profile!.followersVisibility,
followingVisibility: profile!.followingVisibility, followingVisibility: profile!.followingVisibility,
roles: this.roleService.getUserRoles(user.id).then(roles => roles.filter(role => role.isPublic).sort((a, b) => b.displayOrder - a.displayOrder).map(role => ({ roles: this.roleService.getUserRoles(user.id).then(roles => roles.filter(role => role.isPublic).sort((a, b) => b.displayOrder - a.displayOrder).map(role => ({

View File

@ -352,6 +352,98 @@ export class ActivityPubServerService {
} }
} }
@bindThis
private async liked(
request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>,
reply: FastifyReply,
) {
const userId = request.params.user;
const cursor = request.query.cursor;
if (cursor != null && typeof cursor !== 'string') {
reply.code(400);
return;
}
const page = request.query.page === 'true';
const user = await this.usersRepository.findOneBy({
id: userId,
host: IsNull(),
});
if (user == null) {
reply.code(404);
return;
}
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
if (!profile.publicReactions) {
reply.code(403);
reply.header('Cache-Control', 'public, max-age=30');
return;
}
const limit = 10;
const partOf = `${this.config.url}/users/${userId}/liked`;
if (page) {
const query = this.noteReactionsRepository.createQueryBuilder('reaction')
.andWhere('reaction.userId = :userId', { userId: user.id });
// カーソルが指定されている場合
if (cursor) {
query.andWhere('reaction.id < :id', { id: cursor });
}
const reactions = await query
.limit(limit + 1)
.orderBy('reaction.id', 'DESC')
.innerJoinAndSelect('reaction.note', 'note')
.leftJoinAndSelect('note.user', 'noteUser')
.andWhere(new Brackets(qb => {
qb
.where('note.visibility = \'public\'')
.orWhere('note.visibility = \'home\'');
}))
.andWhere('note.localOnly = FALSE')
.andWhere('noteUser.isSuspended = FALSE')
.getMany();
// 「次のページ」があるかどうか
const inStock = reactions.length === limit + 1;
if (inStock) reactions.pop();
const reactedNotes = await Promise.all(reactions.map(({ note }) => note!.uri || this.apRendererService.renderNote(note!, false)));
const rendered = this.apRendererService.renderOrderedCollectionPage(
`${partOf}?${url.query({
page: 'true',
cursor,
})}`,
undefined, reactedNotes, partOf,
undefined,
inStock ? `${partOf}?${url.query({
page: 'true',
cursor: reactions.at(-1)!.id,
})}` : undefined,
);
this.setResponseType(request, reply);
return (this.apRendererService.addContext(rendered));
} else {
// index page
const rendered = this.apRendererService.renderOrderedCollection(
partOf,
undefined,
`${partOf}?page=true`,
);
reply.header('Cache-Control', 'public, max-age=180');
this.setResponseType(request, reply);
return (this.apRendererService.addContext(rendered));
}
}
@bindThis @bindThis
private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) { private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) {
const userId = request.params.user; const userId = request.params.user;
@ -629,6 +721,12 @@ export class ActivityPubServerService {
Querystring: { cursor?: string; page?: string; }; Querystring: { cursor?: string; page?: string; };
}>('/users/:user/following', async (request, reply) => await this.following(request, reply)); }>('/users/:user/following', async (request, reply) => await this.following(request, reply));
// liked
fastify.get<{
Params: { user: string; };
Querystring: { cursor?: string; page?: string; };
}>('/users/:user/liked', async (request, reply) => await this.liked(request, reply));
// featured // featured
fastify.get<{ Params: { user: string; }; }>('/users/:user/collections/featured', async (request, reply) => await this.featured(request, reply)); fastify.get<{ Params: { user: string; }; }>('/users/:user/collections/featured', async (request, reply) => await this.featured(request, reply));

View File

@ -10,7 +10,6 @@ import { QueryService } from '@/core/QueryService.js';
import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js'; import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { CacheService } from '@/core/CacheService.js'; import { CacheService } from '@/core/CacheService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { isUserRelated } from '@/misc/is-user-related.js'; import { isUserRelated } from '@/misc/is-user-related.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
@ -38,11 +37,6 @@ export const meta = {
code: 'REACTIONS_NOT_PUBLIC', code: 'REACTIONS_NOT_PUBLIC',
id: '673a7dd2-6924-1093-e0c0-e68456ceae5c', id: '673a7dd2-6924-1093-e0c0-e68456ceae5c',
}, },
isRemoteUser: {
message: 'Currently unavailable to display reactions of remote users. See https://github.com/misskey-dev/misskey/issues/12964',
code: 'IS_REMOTE_USER',
id: '6b95fa98-8cf9-2350-e284-f0ffdb54a805',
},
}, },
} as const; } as const;
@ -69,7 +63,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private noteReactionsRepository: NoteReactionsRepository, private noteReactionsRepository: NoteReactionsRepository,
private cacheService: CacheService, private cacheService: CacheService,
private userEntityService: UserEntityService,
private noteReactionEntityService: NoteReactionEntityService, private noteReactionEntityService: NoteReactionEntityService,
private queryService: QueryService, private queryService: QueryService,
private roleService: RoleService, private roleService: RoleService,
@ -78,11 +71,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const userIdsWhoBlockingMe = me ? await this.cacheService.userBlockedCache.fetch(me.id) : new Set<string>(); const userIdsWhoBlockingMe = me ? await this.cacheService.userBlockedCache.fetch(me.id) : new Set<string>();
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see reactions of all users const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see reactions of all users
if (!iAmModerator) { if (!iAmModerator) {
const user = await this.cacheService.findUserById(ps.userId);
if (this.userEntityService.isRemoteUser(user)) {
throw new ApiError(meta.errors.isRemoteUser);
}
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId }); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId });
if ((me == null || me.id !== ps.userId) && !profile.publicReactions) { if ((me == null || me.id !== ps.userId) && !profile.publicReactions) {
throw new ApiError(meta.errors.reactionsNotPublic); throw new ApiError(meta.errors.reactionsNotPublic);

View File

@ -871,7 +871,7 @@ export class ClientServerService {
}); });
if (note == null) return; if (note == null) return;
if (note.visibility !== 'public') return; if (['specified', 'followers'].includes(note.visibility)) return;
if (note.userHost != null) return; if (note.userHost != null) return;
const _note = await this.noteEntityService.pack(note, null, { detail: true }); const _note = await this.noteEntityService.pack(note, null, { detail: true });

View File

@ -43,12 +43,11 @@ describe('User', () => {
'uri', 'uri',
'createdAt', 'createdAt',
'lastFetchedAt', 'lastFetchedAt',
'publicReactions',
]); ]);
}); });
}); });
describe('ffVisibility is federated', () => { describe('ff/reactions visibility is federated', () => {
let alice: LoginUser, bob: LoginUser; let alice: LoginUser, bob: LoginUser;
let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
@ -78,6 +77,7 @@ describe('User', () => {
])) { ])) {
strictEqual(user.followersVisibility, 'public'); strictEqual(user.followersVisibility, 'public');
strictEqual(user.followingVisibility, 'public'); strictEqual(user.followingVisibility, 'public');
strictEqual(user.publicReactions, true);
} }
}); });
@ -113,6 +113,22 @@ describe('User', () => {
strictEqual(user.followingVisibility, 'private'); strictEqual(user.followingVisibility, 'private');
} }
}); });
test.skip('Setting false for publicReactions is federated', async () => {
await Promise.all([
alice.client.request('i/update', { publicReactions: false }),
bob.client.request('i/update', { publicReactions: false }),
]);
await sleep();
for (const user of await Promise.all([
alice.client.request('users/show', { userId: bobInA.id }),
bob.client.request('users/show', { userId: aliceInB.id }),
])) {
strictEqual(user.publicReactions, false);
strictEqual(user.publicReactions, false);
}
});
}); });
describe('isCat is federated', () => { describe('isCat is federated', () => {

View File

@ -213,7 +213,7 @@ describe('ActivityPub', () => {
}); });
describe('Collection visibility', () => { describe('Collection visibility', () => {
test('Public following/followers', async () => { test('Public following/followers/reactions', async () => {
const actor = createRandomActor(); const actor = createRandomActor();
actor.following = { actor.following = {
id: `${actor.id}/following`, id: `${actor.id}/following`,
@ -222,6 +222,12 @@ describe('ActivityPub', () => {
first: `${actor.id}/following?page=1`, first: `${actor.id}/following?page=1`,
}; };
actor.followers = `${actor.id}/followers`; actor.followers = `${actor.id}/followers`;
actor.liked = {
id: `${actor.id}/following`,
type: 'OrderedCollection',
totalItems: 0,
orderedItems: [],
};
resolver.register(actor.id, actor); resolver.register(actor.id, actor);
resolver.register(actor.followers, { resolver.register(actor.followers, {
@ -236,9 +242,10 @@ describe('ActivityPub', () => {
assert.deepStrictEqual(userProfile.followingVisibility, 'public'); assert.deepStrictEqual(userProfile.followingVisibility, 'public');
assert.deepStrictEqual(userProfile.followersVisibility, 'public'); assert.deepStrictEqual(userProfile.followersVisibility, 'public');
assert.deepStrictEqual(userProfile.publicReactions, true);
}); });
test('Private following/followers', async () => { test('Private following/followers/reactions', async () => {
const actor = createRandomActor(); const actor = createRandomActor();
actor.following = { actor.following = {
id: `${actor.id}/following`, id: `${actor.id}/following`,
@ -247,6 +254,7 @@ describe('ActivityPub', () => {
// first: … // first: …
}; };
actor.followers = `${actor.id}/followers`; actor.followers = `${actor.id}/followers`;
// actor.liked = …;
resolver.register(actor.id, actor); resolver.register(actor.id, actor);
//resolver.register(actor.followers, { … }); //resolver.register(actor.followers, { … });
@ -256,6 +264,7 @@ describe('ActivityPub', () => {
assert.deepStrictEqual(userProfile.followingVisibility, 'private'); assert.deepStrictEqual(userProfile.followingVisibility, 'private');
assert.deepStrictEqual(userProfile.followersVisibility, 'private'); assert.deepStrictEqual(userProfile.followersVisibility, 'private');
assert.deepStrictEqual(userProfile.publicReactions, false);
}); });
}); });

View File

@ -384,6 +384,7 @@ const patrons = [
'こまつぶり', 'こまつぶり',
'まゆつな空高', 'まゆつな空高',
'asata', 'asata',
'ruru',
]; ];
const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure')); const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure'));

View File

@ -117,5 +117,6 @@ definePageMetadata(() => ({
border-radius: var(--MI-radius); border-radius: var(--MI-radius);
background-color: var(--MI_THEME-panel); background-color: var(--MI_THEME-panel);
overflow-x: scroll; overflow-x: scroll;
white-space: nowrap;
} }
</style> </style>

View File

@ -50,6 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch, ref } from 'vue'; import { computed, watch, ref } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { host } from '@@/js/config.js';
import type { Paging } from '@/components/MkPagination.vue'; import type { Paging } from '@/components/MkPagination.vue';
import MkNoteDetailed from '@/components/MkNoteDetailed.vue'; import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
import MkNotes from '@/components/MkNotes.vue'; import MkNotes from '@/components/MkNotes.vue';
@ -140,7 +141,12 @@ function fetchNote() {
}).catch(err => { }).catch(err => {
if (err.id === '8e75455b-738c-471d-9f80-62693f33372e') { if (err.id === '8e75455b-738c-471d-9f80-62693f33372e') {
pleaseLogin({ pleaseLogin({
path: '/',
message: i18n.ts.thisContentsAreMarkedAsSigninRequiredByAuthor, message: i18n.ts.thisContentsAreMarkedAsSigninRequiredByAuthor,
openOnRemote: {
type: 'lookup',
url: `https://${host}/notes/${props.noteId}`,
},
}); });
} }
error.value = err; error.value = err;