mirror of
https://activitypub.software/TransFem-org/Sharkey
synced 2024-11-29 09:23:04 +00:00
新規にフォローした人のwithRepliesをtrueにする機能を追加 (#12048)
* feat: add defaultWithReplies to MiUser * feat: use defaultWithReplies when creating MiFollowing * feat: update defaultWithReplies from API * feat: return defaultWithReplies as a part of $i * feat(frontend): configure defaultWithReplies * docs(changelog): 新規にフォローした人のをデフォルトでTL二追加できるように * fix: typo * style: fix lint failure * chore: improve UI text * chore: make optional params of UserFollowingService.follow() object * chore: UserFollowingService.follow() accept withReplies * chore: add withReplies to MiFollowRequest * chore: process withReplies for follow request * feat: accept withReplies on 'following/create' endpoint * feat: store defaultWithReplies in client store * Revert "feat: return defaultWithReplies as a part of $i" This reverts commit f2cc4fe6 * Revert "feat: update defaultWithReplies from API" This reverts commit 95e3cee6 * Revert "feat: add defaultWithReplies to MiUser" This reverts commit 9f5ab14d7063532de2b049bc2ed40a15658168f5. * feat: configuring withReplies in import-following * feat(frontend): configure withReplies * fix(frontend): incorrectly showRepliesToOthersInTimeline can be shown * fix(backend): withReplies of following/create not working * fix(frontend): importFollowing error * fix: withReplies is not working with follow import * fix(frontend): use v-model * style: fix lint --------- Co-authored-by: Sayamame-beans <61457993+sayamame-beans@users.noreply.github.com> Co-authored-by: syuilo <syuilotan@yahoo.co.jp>
This commit is contained in:
parent
e9db0680c4
commit
5a3c6575dd
23 changed files with 105 additions and 23 deletions
|
@ -18,6 +18,7 @@
|
||||||
- Feat: アンテナでローカルの投稿のみ収集できるようになりました
|
- Feat: アンテナでローカルの投稿のみ収集できるようになりました
|
||||||
- Feat: サーバーサイレンス機能が追加されました
|
- Feat: サーバーサイレンス機能が追加されました
|
||||||
- Enhance: 依存関係の更新
|
- Enhance: 依存関係の更新
|
||||||
|
- Enhance: 新規にフォローした人のをデフォルトでTLに追加できるように
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Enhance: TLの返信表示オプションを記憶するように
|
- Enhance: TLの返信表示オプションを記憶するように
|
||||||
|
|
2
locales/index.d.ts
vendored
2
locales/index.d.ts
vendored
|
@ -537,6 +537,7 @@ export interface Locale {
|
||||||
"deleteAll": string;
|
"deleteAll": string;
|
||||||
"showFixedPostForm": string;
|
"showFixedPostForm": string;
|
||||||
"showFixedPostFormInChannel": string;
|
"showFixedPostFormInChannel": string;
|
||||||
|
"withRepliesByDefaultForNewlyFollowed": string;
|
||||||
"newNoteRecived": string;
|
"newNoteRecived": string;
|
||||||
"sounds": string;
|
"sounds": string;
|
||||||
"sound": string;
|
"sound": string;
|
||||||
|
@ -2054,6 +2055,7 @@ export interface Locale {
|
||||||
"userLists": string;
|
"userLists": string;
|
||||||
"excludeMutingUsers": string;
|
"excludeMutingUsers": string;
|
||||||
"excludeInactiveUsers": string;
|
"excludeInactiveUsers": string;
|
||||||
|
"withReplies": string;
|
||||||
};
|
};
|
||||||
"_charts": {
|
"_charts": {
|
||||||
"federation": string;
|
"federation": string;
|
||||||
|
|
|
@ -534,6 +534,7 @@ serverLogs: "サーバーログ"
|
||||||
deleteAll: "全て削除"
|
deleteAll: "全て削除"
|
||||||
showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
|
showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
|
||||||
showFixedPostFormInChannel: "タイムライン上部に投稿フォームを表示する(チャンネル)"
|
showFixedPostFormInChannel: "タイムライン上部に投稿フォームを表示する(チャンネル)"
|
||||||
|
withRepliesByDefaultForNewlyFollowed: "フォローする際、デフォルトで返信をTLに含むようにする"
|
||||||
newNoteRecived: "新しいノートがあります"
|
newNoteRecived: "新しいノートがあります"
|
||||||
sounds: "サウンド"
|
sounds: "サウンド"
|
||||||
sound: "サウンド"
|
sound: "サウンド"
|
||||||
|
@ -1969,6 +1970,7 @@ _exportOrImport:
|
||||||
userLists: "リスト"
|
userLists: "リスト"
|
||||||
excludeMutingUsers: "ミュートしているユーザーを除外"
|
excludeMutingUsers: "ミュートしているユーザーを除外"
|
||||||
excludeInactiveUsers: "使われていないアカウントを除外"
|
excludeInactiveUsers: "使われていないアカウントを除外"
|
||||||
|
withReplies: "インポートした人による返信をTLに含むようにする"
|
||||||
|
|
||||||
_charts:
|
_charts:
|
||||||
federation: "連合"
|
federation: "連合"
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
export class FollowRequestWithReplies1697441463087 {
|
||||||
|
name = 'FollowRequestWithReplies1697441463087'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "follow_request" ADD "withReplies" boolean NOT NULL DEFAULT false`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "follow_request" DROP COLUMN "withReplies"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -237,10 +237,11 @@ export class QueueService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public createImportFollowingJob(user: ThinUser, fileId: MiDriveFile['id']) {
|
public createImportFollowingJob(user: ThinUser, fileId: MiDriveFile['id'], withReplies?: boolean) {
|
||||||
return this.dbQueue.add('importFollowing', {
|
return this.dbQueue.add('importFollowing', {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
fileId: fileId,
|
fileId: fileId,
|
||||||
|
withReplies,
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
removeOnFail: true,
|
removeOnFail: true,
|
||||||
|
@ -248,8 +249,8 @@ export class QueueService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public createImportFollowingToDbJob(user: ThinUser, targets: string[]) {
|
public createImportFollowingToDbJob(user: ThinUser, targets: string[], withReplies?: boolean) {
|
||||||
const jobs = targets.map(rel => this.generateToDbJobData('importFollowingToDb', { user, target: rel }));
|
const jobs = targets.map(rel => this.generateToDbJobData('importFollowingToDb', { user, target: rel, withReplies }));
|
||||||
return this.dbQueue.addBulk(jobs);
|
return this.dbQueue.addBulk(jobs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,7 +343,7 @@ export class QueueService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public createFollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string, silent?: boolean }[]) {
|
public createFollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string, silent?: boolean, withReplies?: boolean }[]) {
|
||||||
const jobs = followings.map(rel => this.generateRelationshipJobData('follow', rel));
|
const jobs = followings.map(rel => this.generateRelationshipJobData('follow', rel));
|
||||||
return this.relationshipQueue.addBulk(jobs);
|
return this.relationshipQueue.addBulk(jobs);
|
||||||
}
|
}
|
||||||
|
@ -384,6 +385,7 @@ export class QueueService {
|
||||||
to: { id: data.to.id },
|
to: { id: data.to.id },
|
||||||
silent: data.silent,
|
silent: data.silent,
|
||||||
requestId: data.requestId,
|
requestId: data.requestId,
|
||||||
|
withReplies: data.withReplies,
|
||||||
},
|
},
|
||||||
opts: {
|
opts: {
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
|
|
|
@ -93,7 +93,15 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async follow(_follower: { id: MiUser['id'] }, _followee: { id: MiUser['id'] }, requestId?: string, silent = false): Promise<void> {
|
public async follow(
|
||||||
|
_follower: { id: MiUser['id'] },
|
||||||
|
_followee: { id: MiUser['id'] },
|
||||||
|
{ requestId, silent = false, withReplies }: {
|
||||||
|
requestId?: string,
|
||||||
|
silent?: boolean,
|
||||||
|
withReplies?: boolean,
|
||||||
|
} = {},
|
||||||
|
): Promise<void> {
|
||||||
const [follower, followee] = await Promise.all([
|
const [follower, followee] = await Promise.all([
|
||||||
this.usersRepository.findOneByOrFail({ id: _follower.id }),
|
this.usersRepository.findOneByOrFail({ id: _follower.id }),
|
||||||
this.usersRepository.findOneByOrFail({ id: _followee.id }),
|
this.usersRepository.findOneByOrFail({ id: _followee.id }),
|
||||||
|
@ -171,12 +179,12 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!autoAccept) {
|
if (!autoAccept) {
|
||||||
await this.createFollowRequest(follower, followee, requestId);
|
await this.createFollowRequest(follower, followee, requestId, withReplies);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.insertFollowingDoc(followee, follower, silent);
|
await this.insertFollowingDoc(followee, follower, silent, withReplies);
|
||||||
|
|
||||||
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
||||||
const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee));
|
const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee));
|
||||||
|
@ -193,6 +201,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']
|
id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox']
|
||||||
},
|
},
|
||||||
silent = false,
|
silent = false,
|
||||||
|
withReplies?: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (follower.id === followee.id) return;
|
if (follower.id === followee.id) return;
|
||||||
|
|
||||||
|
@ -202,6 +211,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
id: this.idService.gen(),
|
id: this.idService.gen(),
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
|
withReplies: withReplies,
|
||||||
|
|
||||||
// 非正規化
|
// 非正規化
|
||||||
followerHost: follower.host,
|
followerHost: follower.host,
|
||||||
|
@ -454,6 +464,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox'];
|
id: MiUser['id']; host: MiUser['host']; uri: MiUser['host']; inbox: MiUser['inbox']; sharedInbox: MiUser['sharedInbox'];
|
||||||
},
|
},
|
||||||
requestId?: string,
|
requestId?: string,
|
||||||
|
withReplies?: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (follower.id === followee.id) return;
|
if (follower.id === followee.id) return;
|
||||||
|
|
||||||
|
@ -471,6 +482,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
followerId: follower.id,
|
followerId: follower.id,
|
||||||
followeeId: followee.id,
|
followeeId: followee.id,
|
||||||
requestId,
|
requestId,
|
||||||
|
withReplies,
|
||||||
|
|
||||||
// 非正規化
|
// 非正規化
|
||||||
followerHost: follower.host,
|
followerHost: follower.host,
|
||||||
|
@ -555,7 +567,7 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.');
|
throw new IdentifiableError('8884c2dd-5795-4ac9-b27e-6a01d38190f9', 'No follow request.');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.insertFollowingDoc(followee, follower);
|
await this.insertFollowingDoc(followee, follower, false, request.withReplies);
|
||||||
|
|
||||||
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
||||||
const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as MiPartialLocalUser, request.requestId!), followee));
|
const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as MiPartialLocalUser, request.requestId!), followee));
|
||||||
|
|
|
@ -164,7 +164,7 @@ export class ApInboxService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't queue because the sender may attempt again when timeout
|
// don't queue because the sender may attempt again when timeout
|
||||||
await this.userFollowingService.follow(actor, followee, activity.id);
|
await this.userFollowingService.follow(actor, followee, { requestId: activity.id });
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,11 @@ export class MiFollowRequest {
|
||||||
})
|
})
|
||||||
public requestId: string | null;
|
public requestId: string | null;
|
||||||
|
|
||||||
|
@Column('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public withReplies: boolean;
|
||||||
|
|
||||||
//#region Denormalized fields
|
//#region Denormalized fields
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128, nullable: true,
|
length: 128, nullable: true,
|
||||||
|
|
|
@ -56,7 +56,7 @@ export class ImportFollowingProcessorService {
|
||||||
|
|
||||||
const csv = await this.downloadService.downloadTextFile(file.url);
|
const csv = await this.downloadService.downloadTextFile(file.url);
|
||||||
const targets = csv.trim().split('\n');
|
const targets = csv.trim().split('\n');
|
||||||
this.queueService.createImportFollowingToDbJob({ id: user.id }, targets);
|
this.queueService.createImportFollowingToDbJob({ id: user.id }, targets, job.data.withReplies);
|
||||||
|
|
||||||
this.logger.succ('Import jobs created');
|
this.logger.succ('Import jobs created');
|
||||||
}
|
}
|
||||||
|
@ -93,9 +93,9 @@ export class ImportFollowingProcessorService {
|
||||||
// skip myself
|
// skip myself
|
||||||
if (target.id === job.data.user.id) return;
|
if (target.id === job.data.user.id) return;
|
||||||
|
|
||||||
this.logger.info(`Follow ${target.id} ...`);
|
this.logger.info(`Follow ${target.id} ${job.data.withReplies ? 'with replies' : 'without replies'} ...`);
|
||||||
|
|
||||||
this.queueService.createFollowJob([{ from: user, to: { id: target.id }, silent: true }]);
|
this.queueService.createFollowJob([{ from: user, to: { id: target.id }, silent: true, withReplies: job.data.withReplies }]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.warn(`Error: ${e}`);
|
this.logger.warn(`Error: ${e}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,12 @@ export class RelationshipProcessorService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async processFollow(job: Bull.Job<RelationshipJobData>): Promise<string> {
|
public async processFollow(job: Bull.Job<RelationshipJobData>): Promise<string> {
|
||||||
this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id}`);
|
this.logger.info(`${job.data.from.id} is trying to follow ${job.data.to.id} ${job.data.withReplies ? "with replies" : "without replies"}`);
|
||||||
await this.userFollowingService.follow(job.data.from, job.data.to, job.data.requestId, job.data.silent);
|
await this.userFollowingService.follow(job.data.from, job.data.to, {
|
||||||
|
requestId: job.data.requestId,
|
||||||
|
silent: job.data.silent,
|
||||||
|
withReplies: job.data.withReplies,
|
||||||
|
});
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ export type RelationshipJobData = {
|
||||||
to: ThinUser;
|
to: ThinUser;
|
||||||
silent?: boolean;
|
silent?: boolean;
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
|
withReplies?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DbJobData<T extends keyof DbJobMap> = DbJobMap[T];
|
export type DbJobData<T extends keyof DbJobMap> = DbJobMap[T];
|
||||||
|
@ -79,6 +80,7 @@ export type DbUserDeleteJobData = {
|
||||||
export type DbUserImportJobData = {
|
export type DbUserImportJobData = {
|
||||||
user: ThinUser;
|
user: ThinUser;
|
||||||
fileId: MiDriveFile['id'];
|
fileId: MiDriveFile['id'];
|
||||||
|
withReplies?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DBAntennaImportJobData = {
|
export type DBAntennaImportJobData = {
|
||||||
|
@ -89,6 +91,7 @@ export type DBAntennaImportJobData = {
|
||||||
export type DbUserImportToDbJobData = {
|
export type DbUserImportToDbJobData = {
|
||||||
user: ThinUser;
|
user: ThinUser;
|
||||||
target: string;
|
target: string;
|
||||||
|
withReplies?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ObjectStorageJobData = ObjectStorageFileJobData | Record<string, unknown>;
|
export type ObjectStorageJobData = ObjectStorageFileJobData | Record<string, unknown>;
|
||||||
|
|
|
@ -71,6 +71,7 @@ export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
userId: { type: 'string', format: 'misskey:id' },
|
userId: { type: 'string', format: 'misskey:id' },
|
||||||
|
withReplies: { type: 'boolean' }
|
||||||
},
|
},
|
||||||
required: ['userId'],
|
required: ['userId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -112,7 +113,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.userFollowingService.follow(follower, followee);
|
await this.userFollowingService.follow(follower, followee, { withReplies: ps.withReplies });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof IdentifiableError) {
|
if (e instanceof IdentifiableError) {
|
||||||
if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking);
|
if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking);
|
||||||
|
|
|
@ -52,6 +52,7 @@ export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
fileId: { type: 'string', format: 'misskey:id' },
|
fileId: { type: 'string', format: 'misskey:id' },
|
||||||
|
withReplies: { type: 'boolean' },
|
||||||
},
|
},
|
||||||
required: ['fileId'],
|
required: ['fileId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -79,7 +80,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
);
|
);
|
||||||
if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
|
if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
|
||||||
|
|
||||||
this.queueService.createImportFollowingJob(me, file.id);
|
this.queueService.createImportFollowingJob(me, file.id, ps.withReplies);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ import { useStream } from '@/stream.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { claimAchievement } from '@/scripts/achievements.js';
|
import { claimAchievement } from '@/scripts/achievements.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
|
import { defaultStore } from "@/store.js";
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
user: Misskey.entities.UserDetailed,
|
user: Misskey.entities.UserDetailed,
|
||||||
|
@ -52,6 +53,10 @@ const props = withDefaults(defineProps<{
|
||||||
large: false,
|
large: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(_: 'update:user', value: Misskey.entities.UserDetailed): void
|
||||||
|
}>();
|
||||||
|
|
||||||
let isFollowing = $ref(props.user.isFollowing);
|
let isFollowing = $ref(props.user.isFollowing);
|
||||||
let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou);
|
let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou);
|
||||||
let wait = $ref(false);
|
let wait = $ref(false);
|
||||||
|
@ -95,6 +100,11 @@ async function onClick() {
|
||||||
} else {
|
} else {
|
||||||
await os.api('following/create', {
|
await os.api('following/create', {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
|
withReplies: defaultStore.state.defaultWithReplies,
|
||||||
|
});
|
||||||
|
emit('update:user', {
|
||||||
|
...props.user,
|
||||||
|
withReplies: defaultStore.state.defaultWithReplies
|
||||||
});
|
});
|
||||||
hasPendingFollowRequestFromYou = true;
|
hasPendingFollowRequestFromYou = true;
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="_button" :class="$style.menu" @click="showMenu"><i class="ti ti-dots"></i></button>
|
<button class="_button" :class="$style.menu" @click="showMenu"><i class="ti ti-dots"></i></button>
|
||||||
<MkFollowButton v-if="$i && user.id != $i.id" :class="$style.follow" :user="user" mini/>
|
<MkFollowButton v-if="$i && user.id != $i.id" v-model:user="user" :class="$style.follow" mini/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<MkLoading/>
|
<MkLoading/>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { mainRouter } from '@/router.js';
|
import { mainRouter } from '@/router.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { defaultStore } from "@/store.js";
|
||||||
|
|
||||||
async function follow(user): Promise<void> {
|
async function follow(user): Promise<void> {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
|
@ -28,7 +29,9 @@ async function follow(user): Promise<void> {
|
||||||
|
|
||||||
os.apiWithDialog('following/create', {
|
os.apiWithDialog('following/create', {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
withReplies: defaultStore.state.defaultWithReplies,
|
||||||
});
|
});
|
||||||
|
user.withReplies = defaultStore.state.defaultWithReplies;
|
||||||
}
|
}
|
||||||
|
|
||||||
const acct = new URL(location.href).searchParams.get('acct');
|
const acct = new URL(location.href).searchParams.get('acct');
|
||||||
|
|
|
@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkUserName :user="post.user" style="display: block;"/>
|
<MkUserName :user="post.user" style="display: block;"/>
|
||||||
<MkAcct :user="post.user"/>
|
<MkAcct :user="post.user"/>
|
||||||
</div>
|
</div>
|
||||||
<MkFollowButton v-if="!$i || $i.id != post.user.id" :user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
|
<MkFollowButton v-if="!$i || $i.id != post.user.id" v-model:user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
|
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
|
||||||
|
|
|
@ -29,6 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
|
<MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
|
||||||
<MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
|
<MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
|
||||||
|
<MkSwitch v-model="defaultWithReplies">{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</MkSwitch>
|
||||||
<MkFolder>
|
<MkFolder>
|
||||||
<template #label>{{ i18n.ts.pinnedList }}</template>
|
<template #label>{{ i18n.ts.pinnedList }}</template>
|
||||||
<!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
|
<!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
|
||||||
|
@ -249,6 +250,7 @@ const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('
|
||||||
const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
|
const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
|
||||||
const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
|
const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
|
||||||
const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
|
const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
|
||||||
|
const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies'));
|
||||||
|
|
||||||
watch(lang, () => {
|
watch(lang, () => {
|
||||||
miLocalStorage.setItem('lang', lang.value as string);
|
miLocalStorage.setItem('lang', lang.value as string);
|
||||||
|
|
|
@ -40,6 +40,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkFolder v-if="$i && !$i.movedTo">
|
<MkFolder v-if="$i && !$i.movedTo">
|
||||||
<template #label>{{ i18n.ts.import }}</template>
|
<template #label>{{ i18n.ts.import }}</template>
|
||||||
<template #icon><i class="ti ti-upload"></i></template>
|
<template #icon><i class="ti ti-upload"></i></template>
|
||||||
|
<MkSwitch v-model="withReplies">
|
||||||
|
{{ i18n.ts._exportOrImport.withReplies }}
|
||||||
|
</MkSwitch>
|
||||||
<MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
<MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,9 +121,11 @@ import { selectFile } from '@/scripts/select-file.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
|
import { defaultStore } from "@/store.js";
|
||||||
|
|
||||||
const excludeMutingUsers = ref(false);
|
const excludeMutingUsers = ref(false);
|
||||||
const excludeInactiveUsers = ref(false);
|
const excludeInactiveUsers = ref(false);
|
||||||
|
const withReplies = ref(defaultStore.state.defaultWithReplies);
|
||||||
|
|
||||||
const onExportSuccess = () => {
|
const onExportSuccess = () => {
|
||||||
os.alert({
|
os.alert({
|
||||||
|
@ -177,7 +182,10 @@ const exportAntennas = () => {
|
||||||
|
|
||||||
const importFollowing = async (ev) => {
|
const importFollowing = async (ev) => {
|
||||||
const file = await selectFile(ev.currentTarget ?? ev.target);
|
const file = await selectFile(ev.currentTarget ?? ev.target);
|
||||||
os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError);
|
os.api('i/import-following', {
|
||||||
|
fileId: file.id,
|
||||||
|
withReplies: withReplies.value,
|
||||||
|
}).then(onImportSuccess).catch(onError);
|
||||||
};
|
};
|
||||||
|
|
||||||
const importUserLists = async (ev) => {
|
const importUserLists = async (ev) => {
|
||||||
|
|
|
@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
|
<span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
|
||||||
<div v-if="$i" class="actions">
|
<div v-if="$i" class="actions">
|
||||||
<button class="menu _button" @click="menu"><i class="ti ti-dots"></i></button>
|
<button class="menu _button" @click="menu"><i class="ti ti-dots"></i></button>
|
||||||
<MkFollowButton v-if="$i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
|
<MkFollowButton v-if="$i.id != user.id" v-model:user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkAvatar class="avatar" :user="user" indicator/>
|
<MkAvatar class="avatar" :user="user" indicator/>
|
||||||
|
@ -198,6 +198,7 @@ const props = withDefaults(defineProps<{
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
let user = $ref(props.user);
|
||||||
let parallaxAnimationId = $ref<null | number>(null);
|
let parallaxAnimationId = $ref<null | number>(null);
|
||||||
let narrow = $ref<null | boolean>(null);
|
let narrow = $ref<null | boolean>(null);
|
||||||
let rootEl = $ref<null | HTMLElement>(null);
|
let rootEl = $ref<null | HTMLElement>(null);
|
||||||
|
@ -232,7 +233,7 @@ const age = $computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function menu(ev) {
|
function menu(ev) {
|
||||||
const { menu, cleanup } = getUserMenu(props.user, router);
|
const { menu, cleanup } = getUserMenu(user, router);
|
||||||
os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup);
|
os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -361,6 +361,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
defaultWithReplies: {
|
||||||
|
where: 'account',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// TODO: 他のタブと永続化されたstateを同期
|
// TODO: 他のタブと永続化されたstateを同期
|
||||||
|
|
|
@ -1185,6 +1185,7 @@ export type Endpoints = {
|
||||||
'following/create': {
|
'following/create': {
|
||||||
req: {
|
req: {
|
||||||
userId: User['id'];
|
userId: User['id'];
|
||||||
|
withReplies?: boolean;
|
||||||
};
|
};
|
||||||
res: User;
|
res: User;
|
||||||
};
|
};
|
||||||
|
@ -2985,7 +2986,7 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
|
||||||
//
|
//
|
||||||
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
||||||
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
||||||
// src/api.types.ts:630:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:633:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||||
// src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
|
// src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
|
||||||
// src/entities.ts:603:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
// src/entities.ts:603:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||||
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
||||||
|
|
|
@ -321,7 +321,10 @@ export type Endpoints = {
|
||||||
'federation/users': { req: { host: string; limit?: number; sinceId?: User['id']; untilId?: User['id']; }; res: UserDetailed[]; };
|
'federation/users': { req: { host: string; limit?: number; sinceId?: User['id']; untilId?: User['id']; }; res: UserDetailed[]; };
|
||||||
|
|
||||||
// following
|
// following
|
||||||
'following/create': { req: { userId: User['id'] }; res: User; };
|
'following/create': { req: {
|
||||||
|
userId: User['id'],
|
||||||
|
withReplies?: boolean,
|
||||||
|
}; res: User; };
|
||||||
'following/delete': { req: { userId: User['id'] }; res: User; };
|
'following/delete': { req: { userId: User['id'] }; res: User; };
|
||||||
'following/requests/accept': { req: { userId: User['id'] }; res: null; };
|
'following/requests/accept': { req: { userId: User['id'] }; res: null; };
|
||||||
'following/requests/cancel': { req: { userId: User['id'] }; res: User; };
|
'following/requests/cancel': { req: { userId: User['id'] }; res: User; };
|
||||||
|
|
Loading…
Reference in a new issue