mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-04 07:24:13 +00:00 
			
		
		
		
	Merge pull request #74 from transfem-org/feature/separate-quote
Separate Quote from Boost
This commit is contained in:
		
						commit
						5180b4093f
					
				
					 7 changed files with 412 additions and 205 deletions
				
			
		| 
						 | 
					@ -535,7 +535,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (data.renote && data.renote.userId !== user.id && !user.isBot) {
 | 
							if (data.renote && data.text == null && data.renote.userId !== user.id && !user.isBot) {
 | 
				
			||||||
			this.incRenoteCount(data.renote);
 | 
								this.incRenoteCount(data.renote);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,6 +34,7 @@ export const paramDef = {
 | 
				
			||||||
		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 | 
							limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 | 
				
			||||||
		sinceId: { type: 'string', format: 'misskey:id' },
 | 
							sinceId: { type: 'string', format: 'misskey:id' },
 | 
				
			||||||
		untilId: { type: 'string', format: 'misskey:id' },
 | 
							untilId: { type: 'string', format: 'misskey:id' },
 | 
				
			||||||
 | 
							showQuotes: { type: 'boolean', default: true },
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	required: ['noteId'],
 | 
						required: ['noteId'],
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
| 
						 | 
					@ -51,17 +52,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
 | 
								const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
 | 
				
			||||||
				.andWhere(new Brackets(qb => {
 | 
									.andWhere(new Brackets(qb => {
 | 
				
			||||||
					qb
 | 
										qb
 | 
				
			||||||
						.where('note.replyId = :noteId', { noteId: ps.noteId })
 | 
											.where('note.replyId = :noteId', { noteId: ps.noteId });
 | 
				
			||||||
						.orWhere(new Brackets(qb => {
 | 
											if (ps.showQuotes) {
 | 
				
			||||||
							qb
 | 
												qb.orWhere(new Brackets(qb => {
 | 
				
			||||||
								.where('note.renoteId = :noteId', { noteId: ps.noteId })
 | 
													qb
 | 
				
			||||||
								.andWhere(new Brackets(qb => {
 | 
														.where('note.renoteId = :noteId', { noteId: ps.noteId })
 | 
				
			||||||
									qb
 | 
														.andWhere(new Brackets(qb => {
 | 
				
			||||||
										.where('note.text IS NOT NULL')
 | 
															qb
 | 
				
			||||||
										.orWhere('note.fileIds != \'{}\'')
 | 
																.where('note.text IS NOT NULL')
 | 
				
			||||||
										.orWhere('note.hasPoll = TRUE');
 | 
																.orWhere('note.fileIds != \'{}\'')
 | 
				
			||||||
								}));
 | 
																.orWhere('note.hasPoll = TRUE');
 | 
				
			||||||
						}));
 | 
														}));
 | 
				
			||||||
 | 
												}));
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
				}))
 | 
									}))
 | 
				
			||||||
				.innerJoinAndSelect('note.user', 'user')
 | 
									.innerJoinAndSelect('note.user', 'user')
 | 
				
			||||||
				.leftJoinAndSelect('note.reply', 'reply')
 | 
									.leftJoinAndSelect('note.reply', 'reply')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,6 +44,7 @@ export const paramDef = {
 | 
				
			||||||
		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 | 
							limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 | 
				
			||||||
		sinceId: { type: 'string', format: 'misskey:id' },
 | 
							sinceId: { type: 'string', format: 'misskey:id' },
 | 
				
			||||||
		untilId: { type: 'string', format: 'misskey:id' },
 | 
							untilId: { type: 'string', format: 'misskey:id' },
 | 
				
			||||||
 | 
							quote: { type: 'boolean', default: false },
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	required: ['noteId'],
 | 
						required: ['noteId'],
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
| 
						 | 
					@ -74,7 +75,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			if (ps.userId) {
 | 
								if (ps.userId) {
 | 
				
			||||||
				query.andWhere("user.id = :userId", { userId: ps.userId });
 | 
									query.andWhere("user.id = :userId", { userId: ps.userId });
 | 
				
			||||||
			}			
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (ps.quote) {
 | 
				
			||||||
 | 
									query.andWhere("note.text IS NOT NULL");
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									query.andWhere("note.text IS NULL");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.queryService.generateVisibilityQuery(query, me);
 | 
								this.queryService.generateVisibilityQuery(query, me);
 | 
				
			||||||
			if (me) this.queryService.generateMutedUserQuery(query, me);
 | 
								if (me) this.queryService.generateMutedUserQuery(query, me);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,6 +38,7 @@ export const paramDef = {
 | 
				
			||||||
	type: 'object',
 | 
						type: 'object',
 | 
				
			||||||
	properties: {
 | 
						properties: {
 | 
				
			||||||
		noteId: { type: 'string', format: 'misskey:id' },
 | 
							noteId: { type: 'string', format: 'misskey:id' },
 | 
				
			||||||
 | 
							quote: { type: 'boolean', default: false },
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	required: ['noteId'],
 | 
						required: ['noteId'],
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
| 
						 | 
					@ -66,7 +67,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for (const note of renotes) {
 | 
								for (const note of renotes) {
 | 
				
			||||||
				this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: me.id }), note, false);
 | 
									if (ps.quote) {
 | 
				
			||||||
 | 
										if (note.text) this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: me.id }), note, false);
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										if (!note.text) this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: me.id }), note, false);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,6 +110,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
				<button v-else :class="$style.footerButton" class="_button" disabled>
 | 
									<button v-else :class="$style.footerButton" class="_button" disabled>
 | 
				
			||||||
					<i class="ph-prohibit ph-bold ph-lg"></i>
 | 
										<i class="ph-prohibit ph-bold ph-lg"></i>
 | 
				
			||||||
				</button>
 | 
									</button>
 | 
				
			||||||
 | 
									<button
 | 
				
			||||||
 | 
										v-if="canRenote"
 | 
				
			||||||
 | 
										ref="quoteButton"
 | 
				
			||||||
 | 
										:class="$style.footerButton"
 | 
				
			||||||
 | 
										class="_button"
 | 
				
			||||||
 | 
										:style="quoted ? 'color: var(--accent) !important;' : ''"
 | 
				
			||||||
 | 
										v-on:click.stop
 | 
				
			||||||
 | 
										@mousedown="quoted ? undoQuote(appearNote) : quote()"
 | 
				
			||||||
 | 
									>
 | 
				
			||||||
 | 
										<i class="ph-quotes ph-bold ph-lg"></i>
 | 
				
			||||||
 | 
									</button>
 | 
				
			||||||
				<button v-if="appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.footerButton" class="_button" v-on:click.stop @click="like()">
 | 
									<button v-if="appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.footerButton" class="_button" v-on:click.stop @click="like()">
 | 
				
			||||||
					<i class="ph-heart ph-bold ph-lg"></i>
 | 
										<i class="ph-heart ph-bold ph-lg"></i>
 | 
				
			||||||
				</button>
 | 
									</button>
 | 
				
			||||||
| 
						 | 
					@ -216,6 +227,7 @@ const menuButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const renoteButton = shallowRef<HTMLElement>();
 | 
					const renoteButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const renoteTime = shallowRef<HTMLElement>();
 | 
					const renoteTime = shallowRef<HTMLElement>();
 | 
				
			||||||
const reactButton = shallowRef<HTMLElement>();
 | 
					const reactButton = shallowRef<HTMLElement>();
 | 
				
			||||||
 | 
					const quoteButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const clipButton = shallowRef<HTMLElement>();
 | 
					const clipButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const likeButton = shallowRef<HTMLElement>();
 | 
					const likeButton = shallowRef<HTMLElement>();
 | 
				
			||||||
let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note);
 | 
					let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note);
 | 
				
			||||||
| 
						 | 
					@ -229,6 +241,7 @@ const isLong = shouldCollapsed(appearNote);
 | 
				
			||||||
const collapsed = ref(appearNote.cw == null && isLong);
 | 
					const collapsed = ref(appearNote.cw == null && isLong);
 | 
				
			||||||
const isDeleted = ref(false);
 | 
					const isDeleted = ref(false);
 | 
				
			||||||
const renoted = ref(false);
 | 
					const renoted = ref(false);
 | 
				
			||||||
 | 
					const quoted = ref(false);
 | 
				
			||||||
const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
 | 
					const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
 | 
				
			||||||
const translation = ref<any>(null);
 | 
					const translation = ref<any>(null);
 | 
				
			||||||
const translating = ref(false);
 | 
					const translating = ref(false);
 | 
				
			||||||
| 
						 | 
					@ -271,6 +284,25 @@ useTooltip(renoteButton, async (showing) => {
 | 
				
			||||||
	}, {}, 'closed');
 | 
						}, {}, 'closed');
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					useTooltip(quoteButton, async (showing) => {
 | 
				
			||||||
 | 
						const renotes = await os.api('notes/renotes', {
 | 
				
			||||||
 | 
							noteId: appearNote.id,
 | 
				
			||||||
 | 
							limit: 11,
 | 
				
			||||||
 | 
							quote: true,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const users = renotes.map(x => x.user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (users.length < 1) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						os.popup(MkUsersTooltip, {
 | 
				
			||||||
 | 
							showing,
 | 
				
			||||||
 | 
							users,
 | 
				
			||||||
 | 
							count: appearNote.renoteCount,
 | 
				
			||||||
 | 
							targetElement: quoteButton.value,
 | 
				
			||||||
 | 
						}, {}, 'closed');
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if ($i) {
 | 
					if ($i) {
 | 
				
			||||||
	os.api("notes/renotes", {
 | 
						os.api("notes/renotes", {
 | 
				
			||||||
		noteId: appearNote.id,
 | 
							noteId: appearNote.id,
 | 
				
			||||||
| 
						 | 
					@ -279,6 +311,15 @@ if ($i) {
 | 
				
			||||||
	}).then((res) => {
 | 
						}).then((res) => {
 | 
				
			||||||
		renoted.value = res.length > 0;
 | 
							renoted.value = res.length > 0;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						os.api("notes/renotes", {
 | 
				
			||||||
 | 
							noteId: appearNote.id,
 | 
				
			||||||
 | 
							userId: $i.id,
 | 
				
			||||||
 | 
							limit: 1,
 | 
				
			||||||
 | 
							quote: true,
 | 
				
			||||||
 | 
						}).then((res) => {
 | 
				
			||||||
 | 
							quoted.value = res.length > 0;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Visibility = 'public' | 'home' | 'followers' | 'specified';
 | 
					type Visibility = 'public' | 'home' | 'followers' | 'specified';
 | 
				
			||||||
| 
						 | 
					@ -292,88 +333,103 @@ function smallerVisibility(a: Visibility | string, b: Visibility | string): Visi
 | 
				
			||||||
	return 'public';
 | 
						return 'public';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function renote(viaKeyboard = false) {
 | 
					function renote() {
 | 
				
			||||||
	pleaseLogin();
 | 
						pleaseLogin();
 | 
				
			||||||
	showMovedDialog();
 | 
						showMovedDialog();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let items = [] as MenuItem[];
 | 
						if (appearNote.channel) {
 | 
				
			||||||
 | 
							const el = renoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
 | 
							if (el) {
 | 
				
			||||||
 | 
								const rect = el.getBoundingClientRect();
 | 
				
			||||||
 | 
								const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
 | 
								const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
 | 
								os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							os.api('notes/create', {
 | 
				
			||||||
 | 
								renoteId: appearNote.id,
 | 
				
			||||||
 | 
								channelId: appearNote.channelId,
 | 
				
			||||||
 | 
							}).then(() => {
 | 
				
			||||||
 | 
								os.toast(i18n.ts.renoted);
 | 
				
			||||||
 | 
								renoted.value = true;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							const el = renoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
 | 
							if (el) {
 | 
				
			||||||
 | 
								const rect = el.getBoundingClientRect();
 | 
				
			||||||
 | 
								const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
 | 
								const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
 | 
								os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility;
 | 
				
			||||||
 | 
							const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let visibility = appearNote.visibility;
 | 
				
			||||||
 | 
							visibility = smallerVisibility(visibility, configuredVisibility);
 | 
				
			||||||
 | 
							if (appearNote.channel?.isSensitive) {
 | 
				
			||||||
 | 
								visibility = smallerVisibility(visibility, 'home');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							os.api('notes/create', {
 | 
				
			||||||
 | 
								localOnly,
 | 
				
			||||||
 | 
								visibility,
 | 
				
			||||||
 | 
								renoteId: appearNote.id,
 | 
				
			||||||
 | 
							}).then(() => {
 | 
				
			||||||
 | 
								os.toast(i18n.ts.renoted);
 | 
				
			||||||
 | 
								renoted.value = true;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function quote() {
 | 
				
			||||||
 | 
						pleaseLogin();
 | 
				
			||||||
 | 
						showMovedDialog();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (appearNote.channel) {
 | 
						if (appearNote.channel) {
 | 
				
			||||||
		items = items.concat([{
 | 
							os.post({
 | 
				
			||||||
			text: i18n.ts.inChannelRenote,
 | 
								renote: appearNote,
 | 
				
			||||||
			icon: 'ph-rocket-launch ph-bold ph-lg',
 | 
								channel: appearNote.channel,
 | 
				
			||||||
			action: () => {
 | 
							}).then(() => {
 | 
				
			||||||
				const el = renoteButton.value as HTMLElement | null | undefined;
 | 
								os.api("notes/renotes", {
 | 
				
			||||||
				if (el) {
 | 
									noteId: appearNote.id,
 | 
				
			||||||
 | 
									userId: $i.id,
 | 
				
			||||||
 | 
									limit: 1,
 | 
				
			||||||
 | 
									quote: true,
 | 
				
			||||||
 | 
								}).then((res) => {
 | 
				
			||||||
 | 
									const el = quoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
 | 
									if (el && res.length > 0) {
 | 
				
			||||||
					const rect = el.getBoundingClientRect();
 | 
										const rect = el.getBoundingClientRect();
 | 
				
			||||||
					const x = rect.left + (el.offsetWidth / 2);
 | 
										const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
					const y = rect.top + (el.offsetHeight / 2);
 | 
										const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
					os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
										os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				os.api('notes/create', {
 | 
									quoted.value = res.length > 0;
 | 
				
			||||||
					renoteId: appearNote.id,
 | 
								});
 | 
				
			||||||
					channelId: appearNote.channelId,
 | 
							});
 | 
				
			||||||
				}).then(() => {
 | 
						} else {
 | 
				
			||||||
					os.toast(i18n.ts.renoted);
 | 
							os.post({
 | 
				
			||||||
					renoted.value = true;
 | 
								renote: appearNote,
 | 
				
			||||||
				});
 | 
							}).then(() => {
 | 
				
			||||||
			},
 | 
								os.api("notes/renotes", {
 | 
				
			||||||
		}, {
 | 
									noteId: appearNote.id,
 | 
				
			||||||
			text: i18n.ts.inChannelQuote,
 | 
									userId: $i.id,
 | 
				
			||||||
			icon: 'ph-quotes ph-bold ph-lg',
 | 
									limit: 1,
 | 
				
			||||||
			action: () => {
 | 
									quote: true,
 | 
				
			||||||
				os.post({
 | 
								}).then((res) => {
 | 
				
			||||||
					renote: appearNote,
 | 
									const el = quoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
					channel: appearNote.channel,
 | 
									if (el && res.length > 0) {
 | 
				
			||||||
				});
 | 
										const rect = el.getBoundingClientRect();
 | 
				
			||||||
			},
 | 
										const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
		}, null]);
 | 
										const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
 | 
										os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									quoted.value = res.length > 0;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	items = items.concat([{
 | 
					 | 
				
			||||||
		text: i18n.ts.renote,
 | 
					 | 
				
			||||||
		icon: 'ph-rocket-launch ph-bold ph-lg',
 | 
					 | 
				
			||||||
		action: () => {
 | 
					 | 
				
			||||||
			const el = renoteButton.value as HTMLElement | null | undefined;
 | 
					 | 
				
			||||||
			if (el) {
 | 
					 | 
				
			||||||
				const rect = el.getBoundingClientRect();
 | 
					 | 
				
			||||||
				const x = rect.left + (el.offsetWidth / 2);
 | 
					 | 
				
			||||||
				const y = rect.top + (el.offsetHeight / 2);
 | 
					 | 
				
			||||||
				os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility;
 | 
					 | 
				
			||||||
			const localOnly = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			let visibility = appearNote.visibility;
 | 
					 | 
				
			||||||
			visibility = smallerVisibility(visibility, configuredVisibility);
 | 
					 | 
				
			||||||
			if (appearNote.channel?.isSensitive) {
 | 
					 | 
				
			||||||
				visibility = smallerVisibility(visibility, 'home');
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			os.api('notes/create', {
 | 
					 | 
				
			||||||
				localOnly,
 | 
					 | 
				
			||||||
				visibility,
 | 
					 | 
				
			||||||
				renoteId: appearNote.id,
 | 
					 | 
				
			||||||
			}).then(() => {
 | 
					 | 
				
			||||||
				os.toast(i18n.ts.renoted);
 | 
					 | 
				
			||||||
				renoted.value = true;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}, {
 | 
					 | 
				
			||||||
		text: i18n.ts.quote,
 | 
					 | 
				
			||||||
		icon: 'ph-quotes ph-bold ph-lg',
 | 
					 | 
				
			||||||
		action: () => {
 | 
					 | 
				
			||||||
			os.post({
 | 
					 | 
				
			||||||
				renote: appearNote,
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	os.popupMenu(items, renoteButton.value, {
 | 
					 | 
				
			||||||
		viaKeyboard,
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function reply(viaKeyboard = false): void {
 | 
					function reply(viaKeyboard = false): void {
 | 
				
			||||||
| 
						 | 
					@ -443,13 +499,20 @@ function undoReact(note): void {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function undoRenote(note) : void {
 | 
					function undoRenote(note) : void {
 | 
				
			||||||
	if (!renoted.value) return;
 | 
					 | 
				
			||||||
	os.api("notes/unrenote", {
 | 
						os.api("notes/unrenote", {
 | 
				
			||||||
		noteId: note.id,
 | 
							noteId: note.id
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
	renoted.value = false;
 | 
						renoted.value = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function undoQuote(note) : void {
 | 
				
			||||||
 | 
						os.api("notes/unrenote", {
 | 
				
			||||||
 | 
							noteId: note.id,
 | 
				
			||||||
 | 
							quote: true
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						quoted.value = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function onContextmenu(ev: MouseEvent): void {
 | 
					function onContextmenu(ev: MouseEvent): void {
 | 
				
			||||||
	const isLink = (el: HTMLElement) => {
 | 
						const isLink = (el: HTMLElement) => {
 | 
				
			||||||
		if (el.tagName === 'A') return true;
 | 
							if (el.tagName === 'A') return true;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -120,6 +120,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
			<button v-else class="_button" :class="$style.noteFooterButton" disabled>
 | 
								<button v-else class="_button" :class="$style.noteFooterButton" disabled>
 | 
				
			||||||
				<i class="ph-prohibit ph-bold ph-lg"></i>
 | 
									<i class="ph-prohibit ph-bold ph-lg"></i>
 | 
				
			||||||
			</button>
 | 
								</button>
 | 
				
			||||||
 | 
								<button
 | 
				
			||||||
 | 
									v-if="canRenote"
 | 
				
			||||||
 | 
									ref="quoteButton"
 | 
				
			||||||
 | 
									class="_button"
 | 
				
			||||||
 | 
									:class="$style.noteFooterButton"
 | 
				
			||||||
 | 
									:style="quoted ? 'color: var(--accent) !important;' : ''"
 | 
				
			||||||
 | 
									@mousedown="quoted ? undoQuote() : quote()"
 | 
				
			||||||
 | 
								>
 | 
				
			||||||
 | 
									<i class="ph-quotes ph-bold ph-lg"></i>
 | 
				
			||||||
 | 
								</button>
 | 
				
			||||||
			<button v-if="appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.noteFooterButton" class="_button" @mousedown="like()">
 | 
								<button v-if="appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.noteFooterButton" class="_button" @mousedown="like()">
 | 
				
			||||||
				<i class="ph-heart ph-bold ph-lg"></i>
 | 
									<i class="ph-heart ph-bold ph-lg"></i>
 | 
				
			||||||
			</button>
 | 
								</button>
 | 
				
			||||||
| 
						 | 
					@ -141,6 +151,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
	<div :class="$style.tabs">
 | 
						<div :class="$style.tabs">
 | 
				
			||||||
		<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'replies' }]" @click="tab = 'replies'"><i class="ph-arrow-u-up-left ph-bold pg-lg"></i> {{ i18n.ts.replies }}</button>
 | 
							<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'replies' }]" @click="tab = 'replies'"><i class="ph-arrow-u-up-left ph-bold pg-lg"></i> {{ i18n.ts.replies }}</button>
 | 
				
			||||||
		<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'renotes' }]" @click="tab = 'renotes'"><i class="ph-rocket-launch ph-bold ph-lg"></i> {{ i18n.ts.renotes }}</button>
 | 
							<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'renotes' }]" @click="tab = 'renotes'"><i class="ph-rocket-launch ph-bold ph-lg"></i> {{ i18n.ts.renotes }}</button>
 | 
				
			||||||
 | 
							<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'quotes' }]" @click="tab = 'quotes'"><i class="ph-quotes ph-bold ph-lg"></i> {{ i18n.ts._notification._types.quote }}</button>
 | 
				
			||||||
		<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'reactions' }]" @click="tab = 'reactions'"><i class="ph-smiley ph-bold pg-lg"></i> {{ i18n.ts.reactions }}</button>
 | 
							<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'reactions' }]" @click="tab = 'reactions'"><i class="ph-smiley ph-bold pg-lg"></i> {{ i18n.ts.reactions }}</button>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div>
 | 
						<div>
 | 
				
			||||||
| 
						 | 
					@ -161,6 +172,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
				</template>
 | 
									</template>
 | 
				
			||||||
			</MkPagination>
 | 
								</MkPagination>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
 | 
							<div v-if="tab === 'quotes'" :class="$style.tab_replies">
 | 
				
			||||||
 | 
								<div v-if="!quotesLoaded" style="padding: 16px">
 | 
				
			||||||
 | 
									<MkButton style="margin: 0 auto;" primary rounded @click="loadQuotes">{{ i18n.ts.loadReplies }}</MkButton>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<MkNoteSub v-for="note in quotes" :key="note.id" :note="note" :class="$style.reply" :detail="true"/>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
		<div v-else-if="tab === 'reactions'" :class="$style.tab_reactions">
 | 
							<div v-else-if="tab === 'reactions'" :class="$style.tab_reactions">
 | 
				
			||||||
			<div :class="$style.reactionTabs">
 | 
								<div :class="$style.reactionTabs">
 | 
				
			||||||
				<button v-for="reaction in Object.keys(appearNote.reactions)" :key="reaction" :class="[$style.reactionTab, { [$style.reactionTabActive]: reactionTabType === reaction }]" class="_button" @click="reactionTabType = reaction">
 | 
									<button v-for="reaction in Object.keys(appearNote.reactions)" :key="reaction" :class="[$style.reactionTab, { [$style.reactionTabActive]: reactionTabType === reaction }]" class="_button" @click="reactionTabType = reaction">
 | 
				
			||||||
| 
						 | 
					@ -258,6 +275,7 @@ const menuButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const renoteButton = shallowRef<HTMLElement>();
 | 
					const renoteButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const renoteTime = shallowRef<HTMLElement>();
 | 
					const renoteTime = shallowRef<HTMLElement>();
 | 
				
			||||||
const reactButton = shallowRef<HTMLElement>();
 | 
					const reactButton = shallowRef<HTMLElement>();
 | 
				
			||||||
 | 
					const quoteButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const clipButton = shallowRef<HTMLElement>();
 | 
					const clipButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const likeButton = shallowRef<HTMLElement>();
 | 
					const likeButton = shallowRef<HTMLElement>();
 | 
				
			||||||
let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note);
 | 
					let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note);
 | 
				
			||||||
| 
						 | 
					@ -268,6 +286,7 @@ const isMyRenote = $i && ($i.id === note.userId);
 | 
				
			||||||
const showContent = ref(false);
 | 
					const showContent = ref(false);
 | 
				
			||||||
const isDeleted = ref(false);
 | 
					const isDeleted = ref(false);
 | 
				
			||||||
const renoted = ref(false);
 | 
					const renoted = ref(false);
 | 
				
			||||||
 | 
					const quoted = ref(false);
 | 
				
			||||||
const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
 | 
					const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
 | 
				
			||||||
const translation = ref(null);
 | 
					const translation = ref(null);
 | 
				
			||||||
const translating = ref(false);
 | 
					const translating = ref(false);
 | 
				
			||||||
| 
						 | 
					@ -275,6 +294,7 @@ const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)).fil
 | 
				
			||||||
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
 | 
					const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
 | 
				
			||||||
const conversation = ref<Misskey.entities.Note[]>([]);
 | 
					const conversation = ref<Misskey.entities.Note[]>([]);
 | 
				
			||||||
const replies = ref<Misskey.entities.Note[]>([]);
 | 
					const replies = ref<Misskey.entities.Note[]>([]);
 | 
				
			||||||
 | 
					const quotes = ref<Misskey.entities.Note[]>([]);
 | 
				
			||||||
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
 | 
					const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if ($i) {
 | 
					if ($i) {
 | 
				
			||||||
| 
						 | 
					@ -285,6 +305,15 @@ if ($i) {
 | 
				
			||||||
	}).then((res) => {
 | 
						}).then((res) => {
 | 
				
			||||||
		renoted.value = res.length > 0;
 | 
							renoted.value = res.length > 0;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						os.api("notes/renotes", {
 | 
				
			||||||
 | 
							noteId: appearNote.id,
 | 
				
			||||||
 | 
							userId: $i.id,
 | 
				
			||||||
 | 
							limit: 1,
 | 
				
			||||||
 | 
							quote: true,
 | 
				
			||||||
 | 
						}).then((res) => {
 | 
				
			||||||
 | 
							quoted.value = res.length > 0;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const keymap = {
 | 
					const keymap = {
 | 
				
			||||||
| 
						 | 
					@ -340,77 +369,111 @@ useTooltip(renoteButton, async (showing) => {
 | 
				
			||||||
	}, {}, 'closed');
 | 
						}, {}, 'closed');
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function renote(viaKeyboard = false) {
 | 
					useTooltip(quoteButton, async (showing) => {
 | 
				
			||||||
 | 
						const renotes = await os.api('notes/renotes', {
 | 
				
			||||||
 | 
							noteId: appearNote.id,
 | 
				
			||||||
 | 
							limit: 11,
 | 
				
			||||||
 | 
							quote: true,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const users = renotes.map(x => x.user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (users.length < 1) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						os.popup(MkUsersTooltip, {
 | 
				
			||||||
 | 
							showing,
 | 
				
			||||||
 | 
							users,
 | 
				
			||||||
 | 
							count: appearNote.renoteCount,
 | 
				
			||||||
 | 
							targetElement: quoteButton.value,
 | 
				
			||||||
 | 
						}, {}, 'closed');
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function renote() {
 | 
				
			||||||
	pleaseLogin();
 | 
						pleaseLogin();
 | 
				
			||||||
	showMovedDialog();
 | 
						showMovedDialog();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let items = [] as MenuItem[];
 | 
						if (appearNote.channel) {
 | 
				
			||||||
 | 
							const el = renoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
 | 
							if (el) {
 | 
				
			||||||
 | 
								const rect = el.getBoundingClientRect();
 | 
				
			||||||
 | 
								const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
 | 
								const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
 | 
								os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							os.api('notes/create', {
 | 
				
			||||||
 | 
								renoteId: appearNote.id,
 | 
				
			||||||
 | 
								channelId: appearNote.channelId,
 | 
				
			||||||
 | 
							}).then(() => {
 | 
				
			||||||
 | 
								os.toast(i18n.ts.renoted);
 | 
				
			||||||
 | 
								renoted.value = true;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							const el = renoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
 | 
							if (el) {
 | 
				
			||||||
 | 
								const rect = el.getBoundingClientRect();
 | 
				
			||||||
 | 
								const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
 | 
								const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
 | 
								os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							os.api('notes/create', {
 | 
				
			||||||
 | 
								renoteId: appearNote.id,
 | 
				
			||||||
 | 
							}).then(() => {
 | 
				
			||||||
 | 
								os.toast(i18n.ts.renoted);
 | 
				
			||||||
 | 
								renoted.value = true;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function quote() {
 | 
				
			||||||
 | 
						pleaseLogin();
 | 
				
			||||||
 | 
						showMovedDialog();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (appearNote.channel) {
 | 
						if (appearNote.channel) {
 | 
				
			||||||
		items = items.concat([{
 | 
							os.post({
 | 
				
			||||||
			text: i18n.ts.inChannelRenote,
 | 
								renote: appearNote,
 | 
				
			||||||
			icon: 'ph-rocket-launch ph-bold ph-lg',
 | 
								channel: appearNote.channel,
 | 
				
			||||||
			action: () => {
 | 
							}).then(() => {
 | 
				
			||||||
				const el = renoteButton.value as HTMLElement | null | undefined;
 | 
								os.api("notes/renotes", {
 | 
				
			||||||
				if (el) {
 | 
									noteId: appearNote.id,
 | 
				
			||||||
 | 
									userId: $i.id,
 | 
				
			||||||
 | 
									limit: 1,
 | 
				
			||||||
 | 
									quote: true,
 | 
				
			||||||
 | 
								}).then((res) => {
 | 
				
			||||||
 | 
									const el = quoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
 | 
									if (el && res.length > 0) {
 | 
				
			||||||
					const rect = el.getBoundingClientRect();
 | 
										const rect = el.getBoundingClientRect();
 | 
				
			||||||
					const x = rect.left + (el.offsetWidth / 2);
 | 
										const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
					const y = rect.top + (el.offsetHeight / 2);
 | 
										const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
					os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
										os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				os.api('notes/create', {
 | 
									quoted.value = res.length > 0;
 | 
				
			||||||
					renoteId: appearNote.id,
 | 
								});
 | 
				
			||||||
					channelId: appearNote.channelId,
 | 
							});
 | 
				
			||||||
				}).then(() => {
 | 
						} else {
 | 
				
			||||||
					os.toast(i18n.ts.renoted);
 | 
							os.post({
 | 
				
			||||||
					renoted.value = true;
 | 
								renote: appearNote,
 | 
				
			||||||
				});
 | 
							}).then(() => {
 | 
				
			||||||
			},
 | 
								os.api("notes/renotes", {
 | 
				
			||||||
		}, {
 | 
									noteId: appearNote.id,
 | 
				
			||||||
			text: i18n.ts.inChannelQuote,
 | 
									userId: $i.id,
 | 
				
			||||||
			icon: 'ph-quotes ph-bold ph-lg',
 | 
									limit: 1,
 | 
				
			||||||
			action: () => {
 | 
									quote: true,
 | 
				
			||||||
				os.post({
 | 
								}).then((res) => {
 | 
				
			||||||
					renote: appearNote,
 | 
									const el = quoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
					channel: appearNote.channel,
 | 
									if (el && res.length > 0) {
 | 
				
			||||||
				});
 | 
										const rect = el.getBoundingClientRect();
 | 
				
			||||||
			},
 | 
										const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
		}, null]);
 | 
										const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
 | 
										os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									quoted.value = res.length > 0;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	items = items.concat([{
 | 
					 | 
				
			||||||
		text: i18n.ts.renote,
 | 
					 | 
				
			||||||
		icon: 'ph-rocket-launch ph-bold ph-lg',
 | 
					 | 
				
			||||||
		action: () => {
 | 
					 | 
				
			||||||
			const el = renoteButton.value as HTMLElement | null | undefined;
 | 
					 | 
				
			||||||
			if (el) {
 | 
					 | 
				
			||||||
				const rect = el.getBoundingClientRect();
 | 
					 | 
				
			||||||
				const x = rect.left + (el.offsetWidth / 2);
 | 
					 | 
				
			||||||
				const y = rect.top + (el.offsetHeight / 2);
 | 
					 | 
				
			||||||
				os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			os.api('notes/create', {
 | 
					 | 
				
			||||||
				renoteId: appearNote.id,
 | 
					 | 
				
			||||||
			}).then(() => {
 | 
					 | 
				
			||||||
				os.toast(i18n.ts.renoted);
 | 
					 | 
				
			||||||
				renoted.value = true;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}, {
 | 
					 | 
				
			||||||
		text: i18n.ts.quote,
 | 
					 | 
				
			||||||
		icon: 'ph-quotes ph-bold ph-lg',
 | 
					 | 
				
			||||||
		action: () => {
 | 
					 | 
				
			||||||
			os.post({
 | 
					 | 
				
			||||||
				renote: appearNote,
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	os.popupMenu(items, renoteButton.value, {
 | 
					 | 
				
			||||||
		viaKeyboard,
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function reply(viaKeyboard = false): void {
 | 
					function reply(viaKeyboard = false): void {
 | 
				
			||||||
| 
						 | 
					@ -488,6 +551,14 @@ function undoRenote() : void {
 | 
				
			||||||
	renoted.value = false;
 | 
						renoted.value = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function undoQuote() : void {
 | 
				
			||||||
 | 
						os.api("notes/unrenote", {
 | 
				
			||||||
 | 
							noteId: appearNote.id,
 | 
				
			||||||
 | 
							quote: true
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						quoted.value = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function onContextmenu(ev: MouseEvent): void {
 | 
					function onContextmenu(ev: MouseEvent): void {
 | 
				
			||||||
	const isLink = (el: HTMLElement) => {
 | 
						const isLink = (el: HTMLElement) => {
 | 
				
			||||||
		if (el.tagName === 'A') return true;
 | 
							if (el.tagName === 'A') return true;
 | 
				
			||||||
| 
						 | 
					@ -550,12 +621,26 @@ function loadReplies() {
 | 
				
			||||||
	os.api('notes/children', {
 | 
						os.api('notes/children', {
 | 
				
			||||||
		noteId: appearNote.id,
 | 
							noteId: appearNote.id,
 | 
				
			||||||
		limit: 30,
 | 
							limit: 30,
 | 
				
			||||||
 | 
							showQuotes: false,
 | 
				
			||||||
	}).then(res => {
 | 
						}).then(res => {
 | 
				
			||||||
		replies.value = res;
 | 
							replies.value = res;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
loadReplies();
 | 
					loadReplies();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const quotesLoaded = ref(false);
 | 
				
			||||||
 | 
					function loadQuotes() {
 | 
				
			||||||
 | 
						quotesLoaded.value = true;
 | 
				
			||||||
 | 
						os.api('notes/renotes', {
 | 
				
			||||||
 | 
							noteId: appearNote.id,
 | 
				
			||||||
 | 
							limit: 30,
 | 
				
			||||||
 | 
							quote: true,
 | 
				
			||||||
 | 
						}).then(res => {
 | 
				
			||||||
 | 
							quotes.value = res;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					loadQuotes();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const conversationLoaded = ref(false);
 | 
					const conversationLoaded = ref(false);
 | 
				
			||||||
function loadConversation() {
 | 
					function loadConversation() {
 | 
				
			||||||
	conversationLoaded.value = true;
 | 
						conversationLoaded.value = true;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,6 +36,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
					<i class="ph-rocket-launch ph-bold ph-lg"></i>
 | 
										<i class="ph-rocket-launch ph-bold ph-lg"></i>
 | 
				
			||||||
					<p v-if="note.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ note.renoteCount }}</p>
 | 
										<p v-if="note.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ note.renoteCount }}</p>
 | 
				
			||||||
				</button>
 | 
									</button>
 | 
				
			||||||
 | 
									<button
 | 
				
			||||||
 | 
										v-if="canRenote"
 | 
				
			||||||
 | 
										ref="quoteButton"
 | 
				
			||||||
 | 
										class="_button"
 | 
				
			||||||
 | 
										:class="$style.noteFooterButton"
 | 
				
			||||||
 | 
										:style="quoted ? 'color: var(--accent) !important;' : ''"
 | 
				
			||||||
 | 
										@mousedown="quoted ? undoQuote() : quote()"
 | 
				
			||||||
 | 
									>
 | 
				
			||||||
 | 
										<i class="ph-quotes ph-bold ph-lg"></i>
 | 
				
			||||||
 | 
									</button>
 | 
				
			||||||
				<button v-else class="_button" :class="$style.noteFooterButton" disabled>
 | 
									<button v-else class="_button" :class="$style.noteFooterButton" disabled>
 | 
				
			||||||
					<i class="ph-prohibit ph-bold ph-lg"></i>
 | 
										<i class="ph-prohibit ph-bold ph-lg"></i>
 | 
				
			||||||
				</button>
 | 
									</button>
 | 
				
			||||||
| 
						 | 
					@ -114,8 +124,10 @@ const translation = ref(null);
 | 
				
			||||||
const translating = ref(false);
 | 
					const translating = ref(false);
 | 
				
			||||||
const isDeleted = ref(false);
 | 
					const isDeleted = ref(false);
 | 
				
			||||||
const renoted = ref(false);
 | 
					const renoted = ref(false);
 | 
				
			||||||
 | 
					const quoted = ref(false);
 | 
				
			||||||
const reactButton = shallowRef<HTMLElement>();
 | 
					const reactButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const renoteButton = shallowRef<HTMLElement>();
 | 
					const renoteButton = shallowRef<HTMLElement>();
 | 
				
			||||||
 | 
					const quoteButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const menuButton = shallowRef<HTMLElement>();
 | 
					const menuButton = shallowRef<HTMLElement>();
 | 
				
			||||||
const likeButton = shallowRef<HTMLElement>();
 | 
					const likeButton = shallowRef<HTMLElement>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -142,6 +154,15 @@ if ($i) {
 | 
				
			||||||
	}).then((res) => {
 | 
						}).then((res) => {
 | 
				
			||||||
		renoted.value = res.length > 0;
 | 
							renoted.value = res.length > 0;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						os.api("notes/renotes", {
 | 
				
			||||||
 | 
							noteId: appearNote.id,
 | 
				
			||||||
 | 
							userId: $i.id,
 | 
				
			||||||
 | 
							limit: 1,
 | 
				
			||||||
 | 
							quote: true,
 | 
				
			||||||
 | 
						}).then((res) => {
 | 
				
			||||||
 | 
							quoted.value = res.length > 0;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function focus() {
 | 
					function focus() {
 | 
				
			||||||
| 
						 | 
					@ -223,80 +244,103 @@ function undoRenote() : void {
 | 
				
			||||||
	renoted.value = false;
 | 
						renoted.value = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function undoQuote() : void {
 | 
				
			||||||
 | 
						os.api("notes/unrenote", {
 | 
				
			||||||
 | 
							noteId: appearNote.id,
 | 
				
			||||||
 | 
							quote: true
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						quoted.value = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let showContent = $ref(false);
 | 
					let showContent = $ref(false);
 | 
				
			||||||
let replies: Misskey.entities.Note[] = $ref([]);
 | 
					let replies: Misskey.entities.Note[] = $ref([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function renote(viaKeyboard = false) {
 | 
					function renote() {
 | 
				
			||||||
	pleaseLogin();
 | 
						pleaseLogin();
 | 
				
			||||||
	showMovedDialog();
 | 
						showMovedDialog();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let items = [] as MenuItem[];
 | 
						if (appearNote.channel) {
 | 
				
			||||||
 | 
							const el = renoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
 | 
							if (el) {
 | 
				
			||||||
 | 
								const rect = el.getBoundingClientRect();
 | 
				
			||||||
 | 
								const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
 | 
								const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
 | 
								os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (props.note.channel) {
 | 
							os.api('notes/create', {
 | 
				
			||||||
		items = items.concat([{
 | 
								renoteId: props.note.id,
 | 
				
			||||||
			text: i18n.ts.inChannelRenote,
 | 
								channelId: props.note.channelId,
 | 
				
			||||||
			icon: 'ph-rocket-launch ph-bold ph-lg',
 | 
							}).then(() => {
 | 
				
			||||||
			action: () => {
 | 
								os.toast(i18n.ts.renoted);
 | 
				
			||||||
				const el = renoteButton.value as HTMLElement | null | undefined;
 | 
								renoted.value = true;
 | 
				
			||||||
				if (el) {
 | 
							});
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							const el = renoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
 | 
							if (el) {
 | 
				
			||||||
 | 
								const rect = el.getBoundingClientRect();
 | 
				
			||||||
 | 
								const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
 | 
								const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
 | 
								os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							os.api('notes/create', {
 | 
				
			||||||
 | 
								renoteId: props.note.id,
 | 
				
			||||||
 | 
							}).then(() => {
 | 
				
			||||||
 | 
								os.toast(i18n.ts.renoted);
 | 
				
			||||||
 | 
								renoted.value = true;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function quote() {
 | 
				
			||||||
 | 
						pleaseLogin();
 | 
				
			||||||
 | 
						showMovedDialog();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (appearNote.channel) {
 | 
				
			||||||
 | 
							os.post({
 | 
				
			||||||
 | 
								renote: appearNote,
 | 
				
			||||||
 | 
								channel: appearNote.channel,
 | 
				
			||||||
 | 
							}).then(() => {
 | 
				
			||||||
 | 
								os.api("notes/renotes", {
 | 
				
			||||||
 | 
									noteId: props.note.id,
 | 
				
			||||||
 | 
									userId: $i.id,
 | 
				
			||||||
 | 
									limit: 1,
 | 
				
			||||||
 | 
									quote: true,
 | 
				
			||||||
 | 
								}).then((res) => {
 | 
				
			||||||
 | 
									const el = quoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
 | 
									if (el && res.length > 0) {
 | 
				
			||||||
					const rect = el.getBoundingClientRect();
 | 
										const rect = el.getBoundingClientRect();
 | 
				
			||||||
					const x = rect.left + (el.offsetWidth / 2);
 | 
										const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
					const y = rect.top + (el.offsetHeight / 2);
 | 
										const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
					os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
										os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				os.api('notes/create', {
 | 
									quoted.value = res.length > 0;
 | 
				
			||||||
					renoteId: props.note.id,
 | 
								});
 | 
				
			||||||
					channelId: props.note.channelId,
 | 
							});
 | 
				
			||||||
				}).then(() => {
 | 
						} else {
 | 
				
			||||||
					os.toast(i18n.ts.renoted);
 | 
							os.post({
 | 
				
			||||||
					renoted.value = true;
 | 
								renote: appearNote,
 | 
				
			||||||
				});
 | 
							}).then(() => {
 | 
				
			||||||
			},
 | 
								os.api("notes/renotes", {
 | 
				
			||||||
		}, {
 | 
									noteId: props.note.id,
 | 
				
			||||||
			text: i18n.ts.inChannelQuote,
 | 
									userId: $i.id,
 | 
				
			||||||
			icon: 'ph-quotes ph-bold ph-lg',
 | 
									limit: 1,
 | 
				
			||||||
			action: () => {
 | 
									quote: true,
 | 
				
			||||||
				os.post({
 | 
								}).then((res) => {
 | 
				
			||||||
					renote: props.note,
 | 
									const el = quoteButton.value as HTMLElement | null | undefined;
 | 
				
			||||||
					channel: props.note.channel,
 | 
									if (el && res.length > 0) {
 | 
				
			||||||
				});
 | 
										const rect = el.getBoundingClientRect();
 | 
				
			||||||
			},
 | 
										const x = rect.left + (el.offsetWidth / 2);
 | 
				
			||||||
		}, null]);
 | 
										const y = rect.top + (el.offsetHeight / 2);
 | 
				
			||||||
 | 
										os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									quoted.value = res.length > 0;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	items = items.concat([{
 | 
					 | 
				
			||||||
		text: i18n.ts.renote,
 | 
					 | 
				
			||||||
		icon: 'ph-rocket-launch ph-bold ph-lg',
 | 
					 | 
				
			||||||
		action: () => {
 | 
					 | 
				
			||||||
			const el = renoteButton.value as HTMLElement | null | undefined;
 | 
					 | 
				
			||||||
			if (el) {
 | 
					 | 
				
			||||||
				const rect = el.getBoundingClientRect();
 | 
					 | 
				
			||||||
				const x = rect.left + (el.offsetWidth / 2);
 | 
					 | 
				
			||||||
				const y = rect.top + (el.offsetHeight / 2);
 | 
					 | 
				
			||||||
				os.popup(MkRippleEffect, { x, y }, {}, 'end');
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			os.api('notes/create', {
 | 
					 | 
				
			||||||
				renoteId: props.note.id,
 | 
					 | 
				
			||||||
			}).then(() => {
 | 
					 | 
				
			||||||
				os.toast(i18n.ts.renoted);
 | 
					 | 
				
			||||||
				renoted.value = true;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}, {
 | 
					 | 
				
			||||||
		text: i18n.ts.quote,
 | 
					 | 
				
			||||||
		icon: 'ph-quotes ph-bold ph-lg',
 | 
					 | 
				
			||||||
		action: () => {
 | 
					 | 
				
			||||||
			os.post({
 | 
					 | 
				
			||||||
				renote: props.note,
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	os.popupMenu(items, renoteButton.value, {
 | 
					 | 
				
			||||||
		viaKeyboard,
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function menu(viaKeyboard = false): void {
 | 
					function menu(viaKeyboard = false): void {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue