mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 12:36:57 +00:00
merge: Fix boosts showing as quote (!1126)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1126 Approved-by: dakkar <dakkar@thenautilus.net> Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
commit
1fb20a3b0f
11 changed files with 88 additions and 310 deletions
|
@ -731,7 +731,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
//#region AP deliver
|
//#region AP deliver
|
||||||
if (!data.localOnly && this.userEntityService.isLocalUser(user)) {
|
if (!data.localOnly && this.userEntityService.isLocalUser(user)) {
|
||||||
trackTask(async () => {
|
trackTask(async () => {
|
||||||
const noteActivity = await this.renderNoteOrRenoteActivity(data, note, user);
|
const noteActivity = await this.apRendererService.renderNoteOrRenoteActivity(note, user, { renote: data.renote });
|
||||||
const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity);
|
const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity);
|
||||||
|
|
||||||
// メンションされたリモートユーザーに配送
|
// メンションされたリモートユーザーに配送
|
||||||
|
@ -874,17 +874,6 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
this.notesRepository.increment({ id: reply.id }, 'repliesCount', 1);
|
this.notesRepository.increment({ id: reply.id }, 'repliesCount', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
private async renderNoteOrRenoteActivity(data: Option, note: MiNote, user: MiUser) {
|
|
||||||
if (data.localOnly) return null;
|
|
||||||
|
|
||||||
const content = this.isRenote(data) && !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, user, false), note);
|
|
||||||
|
|
||||||
return this.apRendererService.addContext(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private index(note: MiNote) {
|
private index(note: MiNote) {
|
||||||
if (note.text == null && note.cw == null) return;
|
if (note.text == null && note.cw == null) return;
|
||||||
|
|
|
@ -675,7 +675,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
//#region AP deliver
|
//#region AP deliver
|
||||||
if (!data.localOnly && this.userEntityService.isLocalUser(user)) {
|
if (!data.localOnly && this.userEntityService.isLocalUser(user)) {
|
||||||
trackTask(async () => {
|
trackTask(async () => {
|
||||||
const noteActivity = await this.renderNoteOrRenoteActivity(data, note, user);
|
const noteActivity = await this.apRendererService.renderNoteOrRenoteActivity(note, user, { renote: data.renote });
|
||||||
const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity);
|
const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity);
|
||||||
|
|
||||||
// メンションされたリモートユーザーに配送
|
// メンションされたリモートユーザーに配送
|
||||||
|
@ -770,17 +770,6 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
(note.files != null && note.files.length > 0);
|
(note.files != null && note.files.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
private async renderNoteOrRenoteActivity(data: Option, note: MiNote, user: MiUser) {
|
|
||||||
if (data.localOnly) return null;
|
|
||||||
|
|
||||||
const content = this.isRenote(data) && !this.isQuote(data)
|
|
||||||
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
|
|
||||||
: this.apRendererService.renderUpdate(await this.apRendererService.renderUpNote(note, user, false), user);
|
|
||||||
|
|
||||||
return this.apRendererService.addContext(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private index(note: MiNote) {
|
private index(note: MiNote) {
|
||||||
if (note.text == null && note.cw == null) return;
|
if (note.text == null && note.cw == null) return;
|
||||||
|
|
|
@ -32,6 +32,8 @@ import { IdService } from '@/core/IdService.js';
|
||||||
import { appendContentWarning } from '@/misc/append-content-warning.js';
|
import { appendContentWarning } from '@/misc/append-content-warning.js';
|
||||||
import { QueryService } from '@/core/QueryService.js';
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
|
import { isPureRenote, isQuote, isRenote } from '@/misc/is-renote.js';
|
||||||
import { JsonLdService } from './JsonLdService.js';
|
import { JsonLdService } from './JsonLdService.js';
|
||||||
import { ApMfmService } from './ApMfmService.js';
|
import { ApMfmService } from './ApMfmService.js';
|
||||||
import { CONTEXT } from './misc/contexts.js';
|
import { CONTEXT } from './misc/contexts.js';
|
||||||
|
@ -75,6 +77,7 @@ export class ApRendererService {
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private readonly queryService: QueryService,
|
private readonly queryService: QueryService,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
|
private readonly cacheService: CacheService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,7 +235,7 @@ export class ApRendererService {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async renderFollowUser(id: MiUser['id']): Promise<string> {
|
public async renderFollowUser(id: MiUser['id']): Promise<string> {
|
||||||
const user = await this.usersRepository.findOneByOrFail({ id: id }) as MiPartialLocalUser | MiPartialRemoteUser;
|
const user = await this.cacheService.findUserById(id) as MiPartialLocalUser | MiPartialRemoteUser;
|
||||||
return this.userEntityService.getUserUri(user);
|
return this.userEntityService.getUserUri(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,7 +405,7 @@ export class ApRendererService {
|
||||||
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
|
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
|
||||||
|
|
||||||
if (inReplyToNote != null) {
|
if (inReplyToNote != null) {
|
||||||
const inReplyToUser = await this.usersRepository.findOneBy({ id: inReplyToNote.userId });
|
const inReplyToUser = await this.cacheService.findUserById(inReplyToNote.userId);
|
||||||
|
|
||||||
if (inReplyToUser) {
|
if (inReplyToUser) {
|
||||||
if (inReplyToNote.uri) {
|
if (inReplyToNote.uri) {
|
||||||
|
@ -422,7 +425,7 @@ export class ApRendererService {
|
||||||
|
|
||||||
let quote: string | undefined = undefined;
|
let quote: string | undefined = undefined;
|
||||||
|
|
||||||
if (note.renoteId) {
|
if (isRenote(note) && isQuote(note)) {
|
||||||
const renote = await this.notesRepository.findOneBy({ id: note.renoteId });
|
const renote = await this.notesRepository.findOneBy({ id: note.renoteId });
|
||||||
|
|
||||||
if (renote) {
|
if (renote) {
|
||||||
|
@ -542,6 +545,7 @@ export class ApRendererService {
|
||||||
attributedTo,
|
attributedTo,
|
||||||
summary: summary ?? undefined,
|
summary: summary ?? undefined,
|
||||||
content: content ?? undefined,
|
content: content ?? undefined,
|
||||||
|
updated: note.updatedAt?.toISOString() ?? undefined,
|
||||||
_misskey_content: text,
|
_misskey_content: text,
|
||||||
source: {
|
source: {
|
||||||
content: text,
|
content: text,
|
||||||
|
@ -756,176 +760,6 @@ export class ApRendererService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async renderUpNote(note: MiNote, author: MiUser, dive = true): Promise<IPost> {
|
|
||||||
const getPromisedFiles = async (ids: string[]): Promise<MiDriveFile[]> => {
|
|
||||||
if (ids.length === 0) return [];
|
|
||||||
const items = await this.driveFilesRepository.findBy({ id: In(ids) });
|
|
||||||
return ids.map(id => items.find(item => item.id === id)).filter((item): item is MiDriveFile => item != null);
|
|
||||||
};
|
|
||||||
|
|
||||||
let inReplyTo;
|
|
||||||
let inReplyToNote: MiNote | null;
|
|
||||||
|
|
||||||
if (note.replyId) {
|
|
||||||
inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
|
|
||||||
|
|
||||||
if (inReplyToNote != null) {
|
|
||||||
const inReplyToUser = await this.usersRepository.findOneBy({ id: inReplyToNote.userId });
|
|
||||||
|
|
||||||
if (inReplyToUser) {
|
|
||||||
if (inReplyToNote.uri) {
|
|
||||||
inReplyTo = inReplyToNote.uri;
|
|
||||||
} else {
|
|
||||||
if (dive) {
|
|
||||||
inReplyTo = await this.renderUpNote(inReplyToNote, inReplyToUser, false);
|
|
||||||
} else {
|
|
||||||
inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
inReplyTo = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let quote: string | undefined = undefined;
|
|
||||||
|
|
||||||
if (note.renoteId) {
|
|
||||||
const renote = await this.notesRepository.findOneBy({ id: note.renoteId });
|
|
||||||
|
|
||||||
if (renote) {
|
|
||||||
quote = renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const attributedTo = this.userEntityService.genLocalUserUri(note.userId);
|
|
||||||
|
|
||||||
const mentions = note.mentionedRemoteUsers ? (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri) : [];
|
|
||||||
|
|
||||||
let to: string[] = [];
|
|
||||||
let cc: string[] = [];
|
|
||||||
|
|
||||||
if (note.visibility === 'public') {
|
|
||||||
to = ['https://www.w3.org/ns/activitystreams#Public'];
|
|
||||||
cc = [`${attributedTo}/followers`].concat(mentions);
|
|
||||||
} else if (note.visibility === 'home') {
|
|
||||||
to = [`${attributedTo}/followers`];
|
|
||||||
cc = ['https://www.w3.org/ns/activitystreams#Public'].concat(mentions);
|
|
||||||
} else if (note.visibility === 'followers') {
|
|
||||||
to = [`${attributedTo}/followers`];
|
|
||||||
cc = mentions;
|
|
||||||
} else {
|
|
||||||
to = mentions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mentionedUsers = note.mentions && note.mentions.length > 0 ? await this.usersRepository.findBy({
|
|
||||||
id: In(note.mentions),
|
|
||||||
}) : [];
|
|
||||||
|
|
||||||
const hashtagTags = note.tags.map(tag => this.renderHashtag(tag));
|
|
||||||
const mentionTags = mentionedUsers.map(u => this.renderMention(u as MiLocalUser | MiRemoteUser));
|
|
||||||
|
|
||||||
const files = await getPromisedFiles(note.fileIds);
|
|
||||||
|
|
||||||
const text = note.text ?? '';
|
|
||||||
let poll: MiPoll | null = null;
|
|
||||||
|
|
||||||
if (note.hasPoll) {
|
|
||||||
poll = await this.pollsRepository.findOneBy({ noteId: note.id });
|
|
||||||
}
|
|
||||||
|
|
||||||
const apAppend: Appender[] = [];
|
|
||||||
|
|
||||||
if (quote) {
|
|
||||||
// Append quote link as `<br><br><span class="quote-inline">RE: <a href="...">...</a></span>`
|
|
||||||
// the claas name `quote-inline` is used in non-misskey clients for styling quote notes.
|
|
||||||
// For compatibility, the span part should be kept as possible.
|
|
||||||
apAppend.push((doc, body) => {
|
|
||||||
body.childNodes.push(new Element('br', {}));
|
|
||||||
body.childNodes.push(new Element('br', {}));
|
|
||||||
const span = new Element('span', {
|
|
||||||
class: 'quote-inline',
|
|
||||||
});
|
|
||||||
span.childNodes.push(new Text('RE: '));
|
|
||||||
const link = new Element('a', {
|
|
||||||
href: quote,
|
|
||||||
});
|
|
||||||
link.childNodes.push(new Text(quote));
|
|
||||||
span.childNodes.push(link);
|
|
||||||
body.childNodes.push(span);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
|
|
||||||
|
|
||||||
// Apply mandatory CW, if applicable
|
|
||||||
if (author.mandatoryCW) {
|
|
||||||
summary = appendContentWarning(summary, author.mandatoryCW);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { content } = this.apMfmService.getNoteHtml(note, apAppend);
|
|
||||||
|
|
||||||
const emojis = await this.getEmojis(note.emojis);
|
|
||||||
const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
|
|
||||||
|
|
||||||
const tag: IObject[] = [
|
|
||||||
...hashtagTags,
|
|
||||||
...mentionTags,
|
|
||||||
...apemojis,
|
|
||||||
];
|
|
||||||
|
|
||||||
// https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md
|
|
||||||
if (quote) {
|
|
||||||
tag.push({
|
|
||||||
type: 'Link',
|
|
||||||
mediaType: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
|
||||||
rel: 'https://misskey-hub.net/ns#_misskey_quote',
|
|
||||||
href: quote,
|
|
||||||
} satisfies ILink);
|
|
||||||
}
|
|
||||||
|
|
||||||
const asPoll = poll ? {
|
|
||||||
type: 'Question',
|
|
||||||
[poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt,
|
|
||||||
[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
|
|
||||||
type: 'Note',
|
|
||||||
name: text,
|
|
||||||
replies: {
|
|
||||||
type: 'Collection',
|
|
||||||
totalItems: poll!.votes[i],
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
} as const : {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: `${this.config.url}/notes/${note.id}`,
|
|
||||||
type: 'Note',
|
|
||||||
attributedTo,
|
|
||||||
summary: summary ?? undefined,
|
|
||||||
content: content ?? undefined,
|
|
||||||
updated: note.updatedAt?.toISOString(),
|
|
||||||
_misskey_content: text,
|
|
||||||
source: {
|
|
||||||
content: text,
|
|
||||||
mediaType: 'text/x.misskeymarkdown',
|
|
||||||
},
|
|
||||||
_misskey_quote: quote,
|
|
||||||
quoteUrl: quote,
|
|
||||||
quoteUri: quote,
|
|
||||||
// https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md
|
|
||||||
quote: quote,
|
|
||||||
published: this.idService.parse(note.id).date.toISOString(),
|
|
||||||
to,
|
|
||||||
cc,
|
|
||||||
inReplyTo,
|
|
||||||
attachment: files.map(x => this.renderDocument(x)),
|
|
||||||
sensitive: note.cw != null || files.some(file => file.isSensitive),
|
|
||||||
tag,
|
|
||||||
...asPoll,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public renderVote(user: { id: MiUser['id'] }, vote: MiPollVote, note: MiNote, poll: MiPoll, pollOwner: MiRemoteUser): ICreate {
|
public renderVote(user: { id: MiUser['id'] }, vote: MiPollVote, note: MiNote, poll: MiPoll, pollOwner: MiRemoteUser): ICreate {
|
||||||
return {
|
return {
|
||||||
|
@ -1079,6 +913,27 @@ export class ApRendererService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async renderNoteOrRenoteActivity(note: MiNote, user: MiUser, hint?: { renote?: MiNote | null }) {
|
||||||
|
if (note.localOnly) return null;
|
||||||
|
|
||||||
|
if (isPureRenote(note)) {
|
||||||
|
const renote = hint?.renote ?? note.renote ?? await this.notesRepository.findOneByOrFail({ id: note.renoteId });
|
||||||
|
const apAnnounce = this.renderAnnounce(renote.uri ?? `${this.config.url}/notes/${renote.id}`, note);
|
||||||
|
return this.addContext(apAnnounce);
|
||||||
|
}
|
||||||
|
|
||||||
|
const apNote = await this.renderNote(note, user, false);
|
||||||
|
|
||||||
|
if (note.updatedAt != null) {
|
||||||
|
const apUpdate = this.renderUpdate(apNote, user);
|
||||||
|
return this.addContext(apUpdate);
|
||||||
|
} else {
|
||||||
|
const apCreate = this.renderCreate(apNote, note);
|
||||||
|
return this.addContext(apCreate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async getEmojis(names: string[]): Promise<MiEmoji[]> {
|
private async getEmojis(names: string[]): Promise<MiEmoji[]> {
|
||||||
if (names.length === 0) return [];
|
if (names.length === 0) return [];
|
||||||
|
|
|
@ -21,6 +21,8 @@ import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
|
||||||
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import { toArray } from '@/misc/prelude/array.js';
|
import { toArray } from '@/misc/prelude/array.js';
|
||||||
|
import { isPureRenote } from '@/misc/is-renote.js';
|
||||||
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { AnyCollection, getApId, getNullableApId, IObjectWithId, isCollection, isCollectionOrOrderedCollection, isCollectionPage, isOrderedCollection, isOrderedCollectionPage } from './type.js';
|
import { AnyCollection, getApId, getNullableApId, IObjectWithId, isCollection, isCollectionOrOrderedCollection, isCollectionPage, isOrderedCollection, isOrderedCollectionPage } from './type.js';
|
||||||
import { ApDbResolverService } from './ApDbResolverService.js';
|
import { ApDbResolverService } from './ApDbResolverService.js';
|
||||||
import { ApRendererService } from './ApRendererService.js';
|
import { ApRendererService } from './ApRendererService.js';
|
||||||
|
@ -49,6 +51,7 @@ export class Resolver {
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
private readonly apLogService: ApLogService,
|
private readonly apLogService: ApLogService,
|
||||||
private readonly apUtilityService: ApUtilityService,
|
private readonly apUtilityService: ApUtilityService,
|
||||||
|
private readonly cacheService: CacheService,
|
||||||
private recursionLimit = 256,
|
private recursionLimit = 256,
|
||||||
) {
|
) {
|
||||||
this.history = new Set();
|
this.history = new Set();
|
||||||
|
@ -355,18 +358,20 @@ export class Resolver {
|
||||||
|
|
||||||
switch (parsed.type) {
|
switch (parsed.type) {
|
||||||
case 'notes':
|
case 'notes':
|
||||||
return this.notesRepository.findOneByOrFail({ id: parsed.id, userHost: IsNull() })
|
return this.notesRepository.findOneOrFail({ where: { id: parsed.id, userHost: IsNull() }, relations: { user: true, renote: true } })
|
||||||
.then(async note => {
|
.then(async note => {
|
||||||
const author = await this.usersRepository.findOneByOrFail({ id: note.userId });
|
const author = note.user ?? await this.cacheService.findUserById(note.userId);
|
||||||
if (parsed.rest === 'activity') {
|
if (parsed.rest === 'activity') {
|
||||||
// this refers to the create activity and not the note itself
|
return await this.apRendererService.renderNoteOrRenoteActivity(note, author);
|
||||||
return this.apRendererService.addContext(this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, author), note));
|
} else if (!isPureRenote(note)) {
|
||||||
|
const apNote = await this.apRendererService.renderNote(note, author);
|
||||||
|
return this.apRendererService.addContext(apNote);
|
||||||
} else {
|
} else {
|
||||||
return this.apRendererService.renderNote(note, author);
|
throw new IdentifiableError('732c2633-3395-4d51-a9b7-c7084774e3e7', `Failed to resolve local ${url}: cannot resolve a boost as note`);
|
||||||
}
|
}
|
||||||
}) as Promise<IObjectWithId>;
|
}) as Promise<IObjectWithId>;
|
||||||
case 'users':
|
case 'users':
|
||||||
return this.usersRepository.findOneByOrFail({ id: parsed.id, host: IsNull() })
|
return this.cacheService.findLocalUserById(parsed.id)
|
||||||
.then(user => this.apRendererService.renderPerson(user as MiLocalUser));
|
.then(user => this.apRendererService.renderPerson(user as MiLocalUser));
|
||||||
case 'questions':
|
case 'questions':
|
||||||
// Polls are indexed by the note they are attached to.
|
// Polls are indexed by the note they are attached to.
|
||||||
|
@ -387,14 +392,8 @@ export class Resolver {
|
||||||
.then(async followRequest => {
|
.then(async followRequest => {
|
||||||
if (followRequest == null) throw new IdentifiableError('a9d946e5-d276-47f8-95fb-f04230289bb0', `failed to resolve local ${url}: invalid follow request ID`);
|
if (followRequest == null) throw new IdentifiableError('a9d946e5-d276-47f8-95fb-f04230289bb0', `failed to resolve local ${url}: invalid follow request ID`);
|
||||||
const [follower, followee] = await Promise.all([
|
const [follower, followee] = await Promise.all([
|
||||||
this.usersRepository.findOneBy({
|
this.cacheService.findLocalUserById(followRequest.followerId),
|
||||||
id: followRequest.followerId,
|
this.cacheService.findLocalUserById(followRequest.followeeId),
|
||||||
host: IsNull(),
|
|
||||||
}),
|
|
||||||
this.usersRepository.findOneBy({
|
|
||||||
id: followRequest.followeeId,
|
|
||||||
host: Not(IsNull()),
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
if (follower == null || followee == null) {
|
if (follower == null || followee == null) {
|
||||||
throw new IdentifiableError('06ae3170-1796-4d93-a697-2611ea6d83b6', `failed to resolve local ${url}: follower or followee does not exist`);
|
throw new IdentifiableError('06ae3170-1796-4d93-a697-2611ea6d83b6', `failed to resolve local ${url}: follower or followee does not exist`);
|
||||||
|
@ -440,6 +439,7 @@ export class ApResolverService {
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
private readonly apLogService: ApLogService,
|
private readonly apLogService: ApLogService,
|
||||||
private readonly apUtilityService: ApUtilityService,
|
private readonly apUtilityService: ApUtilityService,
|
||||||
|
private readonly cacheService: CacheService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,6 +465,7 @@ export class ApResolverService {
|
||||||
this.loggerService,
|
this.loggerService,
|
||||||
this.apLogService,
|
this.apLogService,
|
||||||
this.apUtilityService,
|
this.apUtilityService,
|
||||||
|
this.cacheService,
|
||||||
opts?.recursionLimit,
|
opts?.recursionLimit,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ import type Logger from '@/logger.js';
|
||||||
import { LoggerService } from '@/core/LoggerService.js';
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IActivity, IAnnounce, ICreate } from '@/core/activitypub/type.js';
|
import { IActivity, IAnnounce, ICreate } from '@/core/activitypub/type.js';
|
||||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
import { isPureRenote, isQuote, isRenote } from '@/misc/is-renote.js';
|
||||||
import * as Acct from '@/misc/acct.js';
|
import * as Acct from '@/misc/acct.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
||||||
|
@ -571,7 +571,7 @@ export class ActivityPubServerService {
|
||||||
|
|
||||||
const pinnedNotes = (await Promise.all(pinings.map(pining =>
|
const pinnedNotes = (await Promise.all(pinings.map(pining =>
|
||||||
this.notesRepository.findOneByOrFail({ id: pining.noteId }))))
|
this.notesRepository.findOneByOrFail({ id: pining.noteId }))))
|
||||||
.filter(note => !note.localOnly && ['public', 'home'].includes(note.visibility));
|
.filter(note => !note.localOnly && ['public', 'home'].includes(note.visibility) && !isPureRenote(note));
|
||||||
|
|
||||||
const renderedNotes = await Promise.all(pinnedNotes.map(note => this.apRendererService.renderNote(note, user)));
|
const renderedNotes = await Promise.all(pinnedNotes.map(note => this.apRendererService.renderNote(note, user)));
|
||||||
|
|
||||||
|
@ -842,6 +842,11 @@ export class ActivityPubServerService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Boosts don't federate directly - they should only be referenced as an activity
|
||||||
|
if (isPureRenote(note)) {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
this.setResponseType(request, reply);
|
this.setResponseType(request, reply);
|
||||||
|
|
||||||
const author = await this.usersRepository.findOneByOrFail({ id: note.userId });
|
const author = await this.usersRepository.findOneByOrFail({ id: note.userId });
|
||||||
|
|
|
@ -19,6 +19,7 @@ import type {
|
||||||
PollsRepository,
|
PollsRepository,
|
||||||
UsersRepository,
|
UsersRepository,
|
||||||
} from '@/models/_.js';
|
} from '@/models/_.js';
|
||||||
|
import type { CacheService } from '@/core/CacheService.js';
|
||||||
import { ApLogService } from '@/core/ApLogService.js';
|
import { ApLogService } from '@/core/ApLogService.js';
|
||||||
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
|
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
|
||||||
import { fromTuple } from '@/misc/from-tuple.js';
|
import { fromTuple } from '@/misc/from-tuple.js';
|
||||||
|
@ -53,6 +54,7 @@ export class MockResolver extends Resolver {
|
||||||
loggerService,
|
loggerService,
|
||||||
{} as ApLogService,
|
{} as ApLogService,
|
||||||
{} as ApUtilityService,
|
{} as ApUtilityService,
|
||||||
|
{} as CacheService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -674,59 +674,6 @@ describe('ActivityPub', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('renderUpnote', () => {
|
|
||||||
describe('summary', () => {
|
|
||||||
// I actually don't know why it does this, but the logic was already there so I've preserved it.
|
|
||||||
it('should be zero-width space when CW is empty string', async () => {
|
|
||||||
note.cw = '';
|
|
||||||
|
|
||||||
const result = await rendererService.renderUpNote(note, author, false);
|
|
||||||
|
|
||||||
expect(result.summary).toBe(String.fromCharCode(0x200B));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be undefined when CW is null', async () => {
|
|
||||||
const result = await rendererService.renderUpNote(note, author, false);
|
|
||||||
|
|
||||||
expect(result.summary).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be CW when present without mandatoryCW', async () => {
|
|
||||||
note.cw = 'original';
|
|
||||||
|
|
||||||
const result = await rendererService.renderUpNote(note, author, false);
|
|
||||||
|
|
||||||
expect(result.summary).toBe('original');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be mandatoryCW when present without CW', async () => {
|
|
||||||
author.mandatoryCW = 'mandatory';
|
|
||||||
|
|
||||||
const result = await rendererService.renderUpNote(note, author, false);
|
|
||||||
|
|
||||||
expect(result.summary).toBe('mandatory');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be merged when CW and mandatoryCW are both present', async () => {
|
|
||||||
note.cw = 'original';
|
|
||||||
author.mandatoryCW = 'mandatory';
|
|
||||||
|
|
||||||
const result = await rendererService.renderUpNote(note, author, false);
|
|
||||||
|
|
||||||
expect(result.summary).toBe('original, mandatory');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be CW when CW includes mandatoryCW', async () => {
|
|
||||||
note.cw = 'original and mandatory';
|
|
||||||
author.mandatoryCW = 'mandatory';
|
|
||||||
|
|
||||||
const result = await rendererService.renderUpNote(note, author, false);
|
|
||||||
|
|
||||||
expect(result.summary).toBe('original and mandatory');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('renderPersonRedacted', () => {
|
describe('renderPersonRedacted', () => {
|
||||||
it('should include minimal properties', async () => {
|
it('should include minimal properties', async () => {
|
||||||
const result = await rendererService.renderPersonRedacted(author);
|
const result = await rendererService.renderPersonRedacted(author);
|
||||||
|
|
|
@ -306,7 +306,7 @@ const galleryEl = useTemplateRef('galleryEl');
|
||||||
const isMyRenote = $i && ($i.id === note.value.userId);
|
const isMyRenote = $i && ($i.id === note.value.userId);
|
||||||
const showContent = ref(prefer.s.uncollapseCW);
|
const showContent = ref(prefer.s.uncollapseCW);
|
||||||
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
|
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
|
||||||
const urls = computed(() => parsed.value ? extractPreviewUrls(props.note, parsed.value) : []);
|
const urls = computed(() => parsed.value ? extractPreviewUrls(appearNote.value, parsed.value) : []);
|
||||||
const selfNoteIds = computed(() => getSelfNoteIds(props.note));
|
const selfNoteIds = computed(() => getSelfNoteIds(props.note));
|
||||||
const isLong = shouldCollapsed(appearNote.value, urls.value);
|
const isLong = shouldCollapsed(appearNote.value, urls.value);
|
||||||
const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
|
const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
|
||||||
|
|
|
@ -114,6 +114,7 @@ import { prefer } from '@/preferences.js';
|
||||||
import { useNoteCapture } from '@/use/use-note-capture.js';
|
import { useNoteCapture } from '@/use/use-note-capture.js';
|
||||||
import SkMutedNote from '@/components/SkMutedNote.vue';
|
import SkMutedNote from '@/components/SkMutedNote.vue';
|
||||||
import { instance, policies } from '@/instance';
|
import { instance, policies } from '@/instance';
|
||||||
|
import { getAppearNote } from '@/utility/get-appear-note';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
|
@ -128,7 +129,9 @@ const props = withDefaults(defineProps<{
|
||||||
onDeleteCallback: undefined,
|
onDeleteCallback: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i?.id);
|
const appearNote = computed(() => getAppearNote(props.note));
|
||||||
|
|
||||||
|
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i?.id);
|
||||||
|
|
||||||
const el = shallowRef<HTMLElement>();
|
const el = shallowRef<HTMLElement>();
|
||||||
const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(null);
|
const translation = ref<Misskey.entities.NotesTranslateResponse | false | null>(null);
|
||||||
|
@ -144,19 +147,11 @@ const likeButton = shallowRef<HTMLElement>();
|
||||||
|
|
||||||
const renoteTooltip = computeRenoteTooltip(renoted);
|
const renoteTooltip = computeRenoteTooltip(renoted);
|
||||||
|
|
||||||
const appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note);
|
|
||||||
const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null);
|
const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null);
|
||||||
const replies = ref<Misskey.entities.Note[]>([]);
|
const replies = ref<Misskey.entities.Note[]>([]);
|
||||||
|
|
||||||
const mergedCW = computed(() => computeMergedCw(appearNote.value));
|
const mergedCW = computed(() => computeMergedCw(appearNote.value));
|
||||||
|
|
||||||
const isRenote = (
|
|
||||||
props.note.renote != null &&
|
|
||||||
props.note.text == null &&
|
|
||||||
props.note.fileIds && props.note.fileIds.length === 0 &&
|
|
||||||
props.note.poll == null
|
|
||||||
);
|
|
||||||
|
|
||||||
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||||
type: 'lookup',
|
type: 'lookup',
|
||||||
url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
|
url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
|
||||||
|
@ -206,8 +201,8 @@ async function reply(viaKeyboard = false): Promise<void> {
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
await os.post({
|
await os.post({
|
||||||
reply: props.note,
|
reply: appearNote.value,
|
||||||
channel: props.note.channel ?? undefined,
|
channel: appearNote.value.channel ?? undefined,
|
||||||
animation: !viaKeyboard,
|
animation: !viaKeyboard,
|
||||||
});
|
});
|
||||||
focus();
|
focus();
|
||||||
|
@ -217,9 +212,9 @@ function react(): void {
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
if (props.note.reactionAcceptance === 'likeOnly') {
|
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
||||||
misskeyApi('notes/like', {
|
misskeyApi('notes/like', {
|
||||||
noteId: props.note.id,
|
noteId: appearNote.value.id,
|
||||||
override: defaultLike.value,
|
override: defaultLike.value,
|
||||||
});
|
});
|
||||||
const el = reactButton.value as HTMLElement | null | undefined;
|
const el = reactButton.value as HTMLElement | null | undefined;
|
||||||
|
@ -233,12 +228,12 @@ function react(): void {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
blur();
|
blur();
|
||||||
reactionPicker.show(reactButton.value ?? null, props.note, reaction => {
|
reactionPicker.show(reactButton.value ?? null, appearNote.value, reaction => {
|
||||||
misskeyApi('notes/reactions/create', {
|
misskeyApi('notes/reactions/create', {
|
||||||
noteId: props.note.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: reaction,
|
reaction: reaction,
|
||||||
});
|
});
|
||||||
if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
|
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
|
||||||
claimAchievement('reactWithoutRead');
|
claimAchievement('reactWithoutRead');
|
||||||
}
|
}
|
||||||
}, () => {
|
}, () => {
|
||||||
|
@ -252,7 +247,7 @@ function like(): void {
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
misskeyApi('notes/like', {
|
misskeyApi('notes/like', {
|
||||||
noteId: props.note.id,
|
noteId: appearNote.value.id,
|
||||||
override: defaultLike.value,
|
override: defaultLike.value,
|
||||||
});
|
});
|
||||||
const el = likeButton.value as HTMLElement | null | undefined;
|
const el = likeButton.value as HTMLElement | null | undefined;
|
||||||
|
@ -361,7 +356,7 @@ function quote() {
|
||||||
}).then((cancelled) => {
|
}).then((cancelled) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: props.note.id,
|
noteId: appearNote.value.id,
|
||||||
userId: $i?.id,
|
userId: $i?.id,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
quote: true,
|
quote: true,
|
||||||
|
@ -383,12 +378,12 @@ function quote() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function menu(): void {
|
function menu(): void {
|
||||||
const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
|
const { menu, cleanup } = getNoteMenu({ note: appearNote.value, translating, translation, isDeleted });
|
||||||
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clip(): Promise<void> {
|
async function clip(): Promise<void> {
|
||||||
os.popupMenu(await getNoteClipMenu({ note: props.note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
os.popupMenu(await getNoteClipMenu({ note: appearNote.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function translate() {
|
async function translate() {
|
||||||
|
@ -397,7 +392,7 @@ async function translate() {
|
||||||
|
|
||||||
if (props.detail) {
|
if (props.detail) {
|
||||||
misskeyApi('notes/children', {
|
misskeyApi('notes/children', {
|
||||||
noteId: props.note.id,
|
noteId: appearNote.value.id,
|
||||||
limit: prefer.s.numberOfReplies,
|
limit: prefer.s.numberOfReplies,
|
||||||
showQuotes: false,
|
showQuotes: false,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
|
|
@ -305,7 +305,7 @@ const galleryEl = useTemplateRef('galleryEl');
|
||||||
const isMyRenote = $i && ($i.id === note.value.userId);
|
const isMyRenote = $i && ($i.id === note.value.userId);
|
||||||
const showContent = ref(prefer.s.uncollapseCW);
|
const showContent = ref(prefer.s.uncollapseCW);
|
||||||
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
|
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
|
||||||
const urls = computed(() => parsed.value ? extractPreviewUrls(props.note, parsed.value) : []);
|
const urls = computed(() => parsed.value ? extractPreviewUrls(appearNote.value, parsed.value) : []);
|
||||||
const selfNoteIds = computed(() => getSelfNoteIds(props.note));
|
const selfNoteIds = computed(() => getSelfNoteIds(props.note));
|
||||||
const isLong = shouldCollapsed(appearNote.value, urls.value);
|
const isLong = shouldCollapsed(appearNote.value, urls.value);
|
||||||
const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
|
const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
|
||||||
|
|
|
@ -122,6 +122,7 @@ import { prefer } from '@/preferences.js';
|
||||||
import { useNoteCapture } from '@/use/use-note-capture.js';
|
import { useNoteCapture } from '@/use/use-note-capture.js';
|
||||||
import SkMutedNote from '@/components/SkMutedNote.vue';
|
import SkMutedNote from '@/components/SkMutedNote.vue';
|
||||||
import { instance, policies } from '@/instance';
|
import { instance, policies } from '@/instance';
|
||||||
|
import { getAppearNote } from '@/utility/get-appear-note';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
|
@ -141,7 +142,9 @@ const props = withDefaults(defineProps<{
|
||||||
onDeleteCallback: undefined,
|
onDeleteCallback: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i?.id);
|
const appearNote = computed(() => getAppearNote(props.note));
|
||||||
|
|
||||||
|
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i?.id);
|
||||||
const hideLine = computed(() => props.detail);
|
const hideLine = computed(() => props.detail);
|
||||||
|
|
||||||
const el = shallowRef<HTMLElement>();
|
const el = shallowRef<HTMLElement>();
|
||||||
|
@ -158,19 +161,11 @@ const likeButton = shallowRef<HTMLElement>();
|
||||||
|
|
||||||
const renoteTooltip = computeRenoteTooltip(renoted);
|
const renoteTooltip = computeRenoteTooltip(renoted);
|
||||||
|
|
||||||
let appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note);
|
|
||||||
const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null);
|
const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null);
|
||||||
const replies = ref<Misskey.entities.Note[]>([]);
|
const replies = ref<Misskey.entities.Note[]>([]);
|
||||||
|
|
||||||
const mergedCW = computed(() => computeMergedCw(appearNote.value));
|
const mergedCW = computed(() => computeMergedCw(appearNote.value));
|
||||||
|
|
||||||
const isRenote = (
|
|
||||||
props.note.renote != null &&
|
|
||||||
props.note.text == null &&
|
|
||||||
props.note.fileIds && props.note.fileIds.length === 0 &&
|
|
||||||
props.note.poll == null
|
|
||||||
);
|
|
||||||
|
|
||||||
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||||
type: 'lookup',
|
type: 'lookup',
|
||||||
url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
|
url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
|
||||||
|
@ -220,8 +215,8 @@ async function reply(viaKeyboard = false): Promise<void> {
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
await os.post({
|
await os.post({
|
||||||
reply: props.note,
|
reply: appearNote.value,
|
||||||
channel: props.note.channel ?? undefined,
|
channel: appearNote.value.channel ?? undefined,
|
||||||
animation: !viaKeyboard,
|
animation: !viaKeyboard,
|
||||||
});
|
});
|
||||||
focus();
|
focus();
|
||||||
|
@ -231,9 +226,9 @@ function react(): void {
|
||||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
if (props.note.reactionAcceptance === 'likeOnly') {
|
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
||||||
misskeyApi('notes/like', {
|
misskeyApi('notes/like', {
|
||||||
noteId: props.note.id,
|
noteId: appearNote.value.id,
|
||||||
override: defaultLike.value,
|
override: defaultLike.value,
|
||||||
});
|
});
|
||||||
const el = reactButton.value as HTMLElement | null | undefined;
|
const el = reactButton.value as HTMLElement | null | undefined;
|
||||||
|
@ -247,12 +242,12 @@ function react(): void {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
blur();
|
blur();
|
||||||
reactionPicker.show(reactButton.value ?? null, props.note, reaction => {
|
reactionPicker.show(reactButton.value ?? null, appearNote.value, reaction => {
|
||||||
misskeyApi('notes/reactions/create', {
|
misskeyApi('notes/reactions/create', {
|
||||||
noteId: props.note.id,
|
noteId: appearNote.value.id,
|
||||||
reaction: reaction,
|
reaction: reaction,
|
||||||
});
|
});
|
||||||
if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
|
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
|
||||||
claimAchievement('reactWithoutRead');
|
claimAchievement('reactWithoutRead');
|
||||||
}
|
}
|
||||||
}, () => {
|
}, () => {
|
||||||
|
@ -266,7 +261,7 @@ function like(): void {
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
misskeyApi('notes/like', {
|
misskeyApi('notes/like', {
|
||||||
noteId: props.note.id,
|
noteId: appearNote.value.id,
|
||||||
override: defaultLike.value,
|
override: defaultLike.value,
|
||||||
});
|
});
|
||||||
const el = likeButton.value as HTMLElement | null | undefined;
|
const el = likeButton.value as HTMLElement | null | undefined;
|
||||||
|
@ -375,7 +370,7 @@ function quote() {
|
||||||
}).then((cancelled) => {
|
}).then((cancelled) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: props.note.id,
|
noteId: appearNote.value.id,
|
||||||
userId: $i?.id,
|
userId: $i?.id,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
quote: true,
|
quote: true,
|
||||||
|
@ -397,12 +392,12 @@ function quote() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function menu(): void {
|
function menu(): void {
|
||||||
const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
|
const { menu, cleanup } = getNoteMenu({ note: appearNote.value, translating, translation, isDeleted });
|
||||||
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clip(): Promise<void> {
|
async function clip(): Promise<void> {
|
||||||
os.popupMenu(await getNoteClipMenu({ note: props.note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
os.popupMenu(await getNoteClipMenu({ note: appearNote.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function translate() {
|
async function translate() {
|
||||||
|
@ -411,7 +406,7 @@ async function translate() {
|
||||||
|
|
||||||
if (props.detail) {
|
if (props.detail) {
|
||||||
misskeyApi('notes/children', {
|
misskeyApi('notes/children', {
|
||||||
noteId: props.note.id,
|
noteId: appearNote.value.id,
|
||||||
limit: prefer.s.numberOfReplies,
|
limit: prefer.s.numberOfReplies,
|
||||||
showQuotes: false,
|
showQuotes: false,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue