parent
							
								
									4c2f7c64cc
								
							
						
					
					
						commit
						dd6569a1bb
					
				
					 17 changed files with 131 additions and 27 deletions
				
			
		| 
						 | 
					@ -14,6 +14,7 @@ You should also include the user name that made the change.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Improvements
 | 
					### Improvements
 | 
				
			||||||
- ユーザーごとにRenoteをミュートできるように
 | 
					- ユーザーごとにRenoteをミュートできるように
 | 
				
			||||||
 | 
					- ノートごとに絵文字リアクションを受け取るか設定できるように
 | 
				
			||||||
- enhance(client): DM作成時にメンションも含むように
 | 
					- enhance(client): DM作成時にメンションも含むように
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Bugfixes
 | 
					### Bugfixes
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -961,6 +961,9 @@ invitationRequiredToRegister: "現在このサーバーは招待制です。招
 | 
				
			||||||
emailNotSupported: "このサーバーではメール配信はサポートされていません"
 | 
					emailNotSupported: "このサーバーではメール配信はサポートされていません"
 | 
				
			||||||
postToTheChannel: "チャンネルに投稿"
 | 
					postToTheChannel: "チャンネルに投稿"
 | 
				
			||||||
cannotBeChangedLater: "後から変更できません。"
 | 
					cannotBeChangedLater: "後から変更できません。"
 | 
				
			||||||
 | 
					reactionAcceptance: "リアクションの受け入れ"
 | 
				
			||||||
 | 
					likeOnly: "いいねのみ"
 | 
				
			||||||
 | 
					likeOnlyForRemote: "リモートからはいいねのみ"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_achievements:
 | 
					_achievements:
 | 
				
			||||||
  earnedAt: "獲得日時"
 | 
					  earnedAt: "獲得日時"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					export class perNoteReactionAcceptance1678164627293 {
 | 
				
			||||||
 | 
					    name = 'perNoteReactionAcceptance1678164627293'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async up(queryRunner) {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "note" ADD "reactionAcceptance" character varying(64)`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async down(queryRunner) {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "reactionAcceptance"`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -125,6 +125,7 @@ type Option = {
 | 
				
			||||||
	files?: DriveFile[] | null;
 | 
						files?: DriveFile[] | null;
 | 
				
			||||||
	poll?: IPoll | null;
 | 
						poll?: IPoll | null;
 | 
				
			||||||
	localOnly?: boolean | null;
 | 
						localOnly?: boolean | null;
 | 
				
			||||||
 | 
						reactionAcceptance?: Note['reactionAcceptance'];
 | 
				
			||||||
	cw?: string | null;
 | 
						cw?: string | null;
 | 
				
			||||||
	visibility?: string;
 | 
						visibility?: string;
 | 
				
			||||||
	visibleUsers?: MinimumUser[] | null;
 | 
						visibleUsers?: MinimumUser[] | null;
 | 
				
			||||||
| 
						 | 
					@ -346,6 +347,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 | 
				
			||||||
			emojis,
 | 
								emojis,
 | 
				
			||||||
			userId: user.id,
 | 
								userId: user.id,
 | 
				
			||||||
			localOnly: data.localOnly!,
 | 
								localOnly: data.localOnly!,
 | 
				
			||||||
 | 
								reactionAcceptance: data.reactionAcceptance,
 | 
				
			||||||
			visibility: data.visibility as any,
 | 
								visibility: data.visibility as any,
 | 
				
			||||||
			visibleUserIds: data.visibility === 'specified'
 | 
								visibleUserIds: data.visibility === 'specified'
 | 
				
			||||||
				? data.visibleUsers
 | 
									? data.visibleUsers
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -99,8 +99,12 @@ export class ReactionService {
 | 
				
			||||||
			throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
 | 
								throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
							if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote') && (user.host != null))) {
 | 
				
			||||||
 | 
								reaction = '❤️';
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
			// TODO: cache
 | 
								// TODO: cache
 | 
				
			||||||
			reaction = await this.toDbReaction(reaction, user.host);
 | 
								reaction = await this.toDbReaction(reaction, user.host);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
		const record: NoteReaction = {
 | 
							const record: NoteReaction = {
 | 
				
			||||||
			id: this.idService.genId(),
 | 
								id: this.idService.genId(),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -314,6 +314,7 @@ export class NoteEntityService implements OnModuleInit {
 | 
				
			||||||
			cw: note.cw,
 | 
								cw: note.cw,
 | 
				
			||||||
			visibility: note.visibility,
 | 
								visibility: note.visibility,
 | 
				
			||||||
			localOnly: note.localOnly ?? undefined,
 | 
								localOnly: note.localOnly ?? undefined,
 | 
				
			||||||
 | 
								reactionAcceptance: note.reactionAcceptance,
 | 
				
			||||||
			visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
 | 
								visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
 | 
				
			||||||
			renoteCount: note.renoteCount,
 | 
								renoteCount: note.renoteCount,
 | 
				
			||||||
			repliesCount: note.repliesCount,
 | 
								repliesCount: note.repliesCount,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -95,7 +95,7 @@ export interface Schema extends OfSchema {
 | 
				
			||||||
	readonly example?: any;
 | 
						readonly example?: any;
 | 
				
			||||||
	readonly format?: string;
 | 
						readonly format?: string;
 | 
				
			||||||
	readonly ref?: keyof typeof refs;
 | 
						readonly ref?: keyof typeof refs;
 | 
				
			||||||
	readonly enum?: ReadonlyArray<string>;
 | 
						readonly enum?: ReadonlyArray<string | null>;
 | 
				
			||||||
	readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null;
 | 
						readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null;
 | 
				
			||||||
	readonly maxLength?: number;
 | 
						readonly maxLength?: number;
 | 
				
			||||||
	readonly minLength?: number;
 | 
						readonly minLength?: number;
 | 
				
			||||||
| 
						 | 
					@ -161,7 +161,7 @@ export type SchemaTypeDef<p extends Schema> =
 | 
				
			||||||
	p['type'] extends 'integer' ? number :
 | 
						p['type'] extends 'integer' ? number :
 | 
				
			||||||
	p['type'] extends 'number' ? number :
 | 
						p['type'] extends 'number' ? number :
 | 
				
			||||||
	p['type'] extends 'string' ? (
 | 
						p['type'] extends 'string' ? (
 | 
				
			||||||
		p['enum'] extends readonly string[] ?
 | 
							p['enum'] extends readonly (string | null)[] ?
 | 
				
			||||||
		p['enum'][number] :
 | 
							p['enum'][number] :
 | 
				
			||||||
		p['format'] extends 'date-time' ? string : // Dateにする??
 | 
							p['format'] extends 'date-time' ? string : // Dateにする??
 | 
				
			||||||
		string
 | 
							string
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -87,6 +87,11 @@ export class Note {
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public localOnly: boolean;
 | 
						public localOnly: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('varchar', {
 | 
				
			||||||
 | 
							length: 64, nullable: true,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Column('smallint', {
 | 
						@Column('smallint', {
 | 
				
			||||||
		default: 0,
 | 
							default: 0,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,6 +141,10 @@ export const packedNoteSchema = {
 | 
				
			||||||
			type: 'boolean',
 | 
								type: 'boolean',
 | 
				
			||||||
			optional: true, nullable: false,
 | 
								optional: true, nullable: false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							reactionAcceptance: {
 | 
				
			||||||
 | 
								type: 'string',
 | 
				
			||||||
 | 
								optional: false, nullable: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		reactions: {
 | 
							reactions: {
 | 
				
			||||||
			type: 'object',
 | 
								type: 'object',
 | 
				
			||||||
			optional: false, nullable: false,
 | 
								optional: false, nullable: false,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -148,6 +148,7 @@ function serialize(favorite: NoteFavorite & { note: Note & { user: User } }, pol
 | 
				
			||||||
			visibility: favorite.note.visibility,
 | 
								visibility: favorite.note.visibility,
 | 
				
			||||||
			visibleUserIds: favorite.note.visibleUserIds,
 | 
								visibleUserIds: favorite.note.visibleUserIds,
 | 
				
			||||||
			localOnly: favorite.note.localOnly,
 | 
								localOnly: favorite.note.localOnly,
 | 
				
			||||||
 | 
								reactionAcceptance: favorite.note.reactionAcceptance,
 | 
				
			||||||
			uri: favorite.note.uri,
 | 
								uri: favorite.note.uri,
 | 
				
			||||||
			url: favorite.note.url,
 | 
								url: favorite.note.url,
 | 
				
			||||||
			user: {
 | 
								user: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,10 +10,10 @@ import { DriveService } from '@/core/DriveService.js';
 | 
				
			||||||
import { createTemp } from '@/misc/create-temp.js';
 | 
					import { createTemp } from '@/misc/create-temp.js';
 | 
				
			||||||
import type { Poll } from '@/models/entities/Poll.js';
 | 
					import type { Poll } from '@/models/entities/Poll.js';
 | 
				
			||||||
import type { Note } from '@/models/entities/Note.js';
 | 
					import type { Note } from '@/models/entities/Note.js';
 | 
				
			||||||
 | 
					import { bindThis } from '@/decorators.js';
 | 
				
			||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
 | 
					import { QueueLoggerService } from '../QueueLoggerService.js';
 | 
				
			||||||
import type Bull from 'bull';
 | 
					import type Bull from 'bull';
 | 
				
			||||||
import type { DbUserJobData } from '../types.js';
 | 
					import type { DbUserJobData } from '../types.js';
 | 
				
			||||||
import { bindThis } from '@/decorators.js';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class ExportNotesProcessorService {
 | 
					export class ExportNotesProcessorService {
 | 
				
			||||||
| 
						 | 
					@ -141,5 +141,6 @@ function serialize(note: Note, poll: Poll | null = null): Record<string, unknown
 | 
				
			||||||
		visibility: note.visibility,
 | 
							visibility: note.visibility,
 | 
				
			||||||
		visibleUserIds: note.visibleUserIds,
 | 
							visibleUserIds: note.visibleUserIds,
 | 
				
			||||||
		localOnly: note.localOnly,
 | 
							localOnly: note.localOnly,
 | 
				
			||||||
 | 
							reactionAcceptance: note.reactionAcceptance,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -97,6 +97,7 @@ export const paramDef = {
 | 
				
			||||||
		} },
 | 
							} },
 | 
				
			||||||
		cw: { type: 'string', nullable: true, maxLength: 100 },
 | 
							cw: { type: 'string', nullable: true, maxLength: 100 },
 | 
				
			||||||
		localOnly: { type: 'boolean', default: false },
 | 
							localOnly: { type: 'boolean', default: false },
 | 
				
			||||||
 | 
							reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote'], default: null },
 | 
				
			||||||
		noExtractMentions: { type: 'boolean', default: false },
 | 
							noExtractMentions: { type: 'boolean', default: false },
 | 
				
			||||||
		noExtractHashtags: { type: 'boolean', default: false },
 | 
							noExtractHashtags: { type: 'boolean', default: false },
 | 
				
			||||||
		noExtractEmojis: { type: 'boolean', default: false },
 | 
							noExtractEmojis: { type: 'boolean', default: false },
 | 
				
			||||||
| 
						 | 
					@ -110,7 +111,7 @@ export const paramDef = {
 | 
				
			||||||
			type: 'string',
 | 
								type: 'string',
 | 
				
			||||||
			minLength: 1,
 | 
								minLength: 1,
 | 
				
			||||||
			maxLength: MAX_NOTE_TEXT_LENGTH,
 | 
								maxLength: MAX_NOTE_TEXT_LENGTH,
 | 
				
			||||||
			nullable: false
 | 
								nullable: false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		fileIds: {
 | 
							fileIds: {
 | 
				
			||||||
			type: 'array',
 | 
								type: 'array',
 | 
				
			||||||
| 
						 | 
					@ -280,6 +281,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 | 
				
			||||||
				renote,
 | 
									renote,
 | 
				
			||||||
				cw: ps.cw,
 | 
									cw: ps.cw,
 | 
				
			||||||
				localOnly: ps.localOnly,
 | 
									localOnly: ps.localOnly,
 | 
				
			||||||
 | 
									reactionAcceptance: ps.reactionAcceptance,
 | 
				
			||||||
				visibility: ps.visibility,
 | 
									visibility: ps.visibility,
 | 
				
			||||||
				visibleUsers,
 | 
									visibleUsers,
 | 
				
			||||||
				channel,
 | 
									channel,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -103,7 +103,8 @@
 | 
				
			||||||
					<i class="ti ti-ban"></i>
 | 
										<i class="ti ti-ban"></i>
 | 
				
			||||||
				</button>
 | 
									</button>
 | 
				
			||||||
				<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()">
 | 
									<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()">
 | 
				
			||||||
					<i class="ti ti-plus"></i>
 | 
										<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
 | 
				
			||||||
 | 
										<i v-else class="ti ti-plus"></i>
 | 
				
			||||||
				</button>
 | 
									</button>
 | 
				
			||||||
				<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)">
 | 
									<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)">
 | 
				
			||||||
					<i class="ti ti-minus"></i>
 | 
										<i class="ti ti-minus"></i>
 | 
				
			||||||
| 
						 | 
					@ -329,6 +330,19 @@ function reply(viaKeyboard = false): void {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function react(viaKeyboard = false): void {
 | 
					function react(viaKeyboard = false): void {
 | 
				
			||||||
	pleaseLogin();
 | 
						pleaseLogin();
 | 
				
			||||||
 | 
						if (appearNote.reactionAcceptance === 'likeOnly') {
 | 
				
			||||||
 | 
							os.api('notes/reactions/create', {
 | 
				
			||||||
 | 
								noteId: appearNote.id,
 | 
				
			||||||
 | 
								reaction: '❤️',
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							const el = reactButton.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');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
		blur();
 | 
							blur();
 | 
				
			||||||
		reactionPicker.show(reactButton.value, reaction => {
 | 
							reactionPicker.show(reactButton.value, reaction => {
 | 
				
			||||||
			os.api('notes/reactions/create', {
 | 
								os.api('notes/reactions/create', {
 | 
				
			||||||
| 
						 | 
					@ -342,6 +356,7 @@ function react(viaKeyboard = false): void {
 | 
				
			||||||
			focus();
 | 
								focus();
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function undoReact(note): void {
 | 
					function undoReact(note): void {
 | 
				
			||||||
	const oldReaction = note.myReaction;
 | 
						const oldReaction = note.myReaction;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -108,7 +108,8 @@
 | 
				
			||||||
					<i class="ti ti-ban"></i>
 | 
										<i class="ti ti-ban"></i>
 | 
				
			||||||
				</button>
 | 
									</button>
 | 
				
			||||||
				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()">
 | 
									<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()">
 | 
				
			||||||
					<i class="ti ti-plus"></i>
 | 
										<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
 | 
				
			||||||
 | 
										<i v-else class="ti ti-plus"></i>
 | 
				
			||||||
				</button>
 | 
									</button>
 | 
				
			||||||
				<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
 | 
									<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
 | 
				
			||||||
					<i class="ti ti-minus"></i>
 | 
										<i class="ti ti-minus"></i>
 | 
				
			||||||
| 
						 | 
					@ -323,6 +324,19 @@ function reply(viaKeyboard = false): void {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function react(viaKeyboard = false): void {
 | 
					function react(viaKeyboard = false): void {
 | 
				
			||||||
	pleaseLogin();
 | 
						pleaseLogin();
 | 
				
			||||||
 | 
						if (appearNote.reactionAcceptance === 'likeOnly') {
 | 
				
			||||||
 | 
							os.api('notes/reactions/create', {
 | 
				
			||||||
 | 
								noteId: appearNote.id,
 | 
				
			||||||
 | 
								reaction: '❤️',
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							const el = reactButton.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');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
		blur();
 | 
							blur();
 | 
				
			||||||
		reactionPicker.show(reactButton.value, reaction => {
 | 
							reactionPicker.show(reactButton.value, reaction => {
 | 
				
			||||||
			os.api('notes/reactions/create', {
 | 
								os.api('notes/reactions/create', {
 | 
				
			||||||
| 
						 | 
					@ -336,6 +350,7 @@ function react(viaKeyboard = false): void {
 | 
				
			||||||
			focus();
 | 
								focus();
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function undoReact(note): void {
 | 
					function undoReact(note): void {
 | 
				
			||||||
	const oldReaction = note.myReaction;
 | 
						const oldReaction = note.myReaction;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,14 +53,23 @@
 | 
				
			||||||
		<XPostFormAttaches v-model="files" :class="$style.attaches" @detach="detachFile" @change-sensitive="updateFileSensitive" @change-name="updateFileName"/>
 | 
							<XPostFormAttaches v-model="files" :class="$style.attaches" @detach="detachFile" @change-sensitive="updateFileSensitive" @change-name="updateFileName"/>
 | 
				
			||||||
		<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
 | 
							<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
 | 
				
			||||||
		<XNotePreview v-if="showPreview" :class="$style.preview" :text="text"/>
 | 
							<XNotePreview v-if="showPreview" :class="$style.preview" :text="text"/>
 | 
				
			||||||
 | 
							<div v-if="showingOptions" style="padding: 0 16px;">
 | 
				
			||||||
 | 
								<MkSelect v-model="reactionAcceptance" small>
 | 
				
			||||||
 | 
									<template #label>{{ i18n.ts.reactionAcceptance }}</template>
 | 
				
			||||||
 | 
									<option :value="null">{{ i18n.ts.all }}</option>
 | 
				
			||||||
 | 
									<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
 | 
				
			||||||
 | 
									<option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
 | 
				
			||||||
 | 
								</MkSelect>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<button v-tooltip="i18n.ts.emoji" class="_button" :class="$style.emojiButton" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
 | 
				
			||||||
		<footer :class="$style.footer">
 | 
							<footer :class="$style.footer">
 | 
				
			||||||
			<button v-tooltip="i18n.ts.attachFile" class="_button" :class="$style.footerButton" @click="chooseFileFrom"><i class="ti ti-photo-plus"></i></button>
 | 
								<button v-tooltip="i18n.ts.attachFile" class="_button" :class="$style.footerButton" @click="chooseFileFrom"><i class="ti ti-photo-plus"></i></button>
 | 
				
			||||||
			<button v-tooltip="i18n.ts.poll" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: poll }]" @click="togglePoll"><i class="ti ti-chart-arrows"></i></button>
 | 
								<button v-tooltip="i18n.ts.poll" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: poll }]" @click="togglePoll"><i class="ti ti-chart-arrows"></i></button>
 | 
				
			||||||
			<button v-tooltip="i18n.ts.useCw" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: useCw }]" @click="useCw = !useCw"><i class="ti ti-eye-off"></i></button>
 | 
								<button v-tooltip="i18n.ts.useCw" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: useCw }]" @click="useCw = !useCw"><i class="ti ti-eye-off"></i></button>
 | 
				
			||||||
			<button v-tooltip="i18n.ts.mention" class="_button" :class="$style.footerButton" @click="insertMention"><i class="ti ti-at"></i></button>
 | 
								<button v-tooltip="i18n.ts.mention" class="_button" :class="$style.footerButton" @click="insertMention"><i class="ti ti-at"></i></button>
 | 
				
			||||||
			<button v-tooltip="i18n.ts.hashtags" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: withHashtags }]" @click="withHashtags = !withHashtags"><i class="ti ti-hash"></i></button>
 | 
								<button v-tooltip="i18n.ts.hashtags" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: withHashtags }]" @click="withHashtags = !withHashtags"><i class="ti ti-hash"></i></button>
 | 
				
			||||||
			<button v-tooltip="i18n.ts.emoji" class="_button" :class="$style.footerButton" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
 | 
					 | 
				
			||||||
			<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button>
 | 
								<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button>
 | 
				
			||||||
 | 
								<button v-tooltip="i18n.ts.more" class="_button" :class="$style.footerButton" @click="showingOptions = !showingOptions"><i class="ti ti-dots"></i></button>
 | 
				
			||||||
		</footer>
 | 
							</footer>
 | 
				
			||||||
		<datalist id="hashtags">
 | 
							<datalist id="hashtags">
 | 
				
			||||||
			<option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/>
 | 
								<option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/>
 | 
				
			||||||
| 
						 | 
					@ -76,6 +85,7 @@ import * as misskey from 'misskey-js';
 | 
				
			||||||
import insertTextAtCursor from 'insert-text-at-cursor';
 | 
					import insertTextAtCursor from 'insert-text-at-cursor';
 | 
				
			||||||
import { toASCII } from 'punycode/';
 | 
					import { toASCII } from 'punycode/';
 | 
				
			||||||
import * as Acct from 'misskey-js/built/acct';
 | 
					import * as Acct from 'misskey-js/built/acct';
 | 
				
			||||||
 | 
					import MkSelect from './MkSelect.vue';
 | 
				
			||||||
import MkNoteSimple from '@/components/MkNoteSimple.vue';
 | 
					import MkNoteSimple from '@/components/MkNoteSimple.vue';
 | 
				
			||||||
import XNotePreview from '@/components/MkNotePreview.vue';
 | 
					import XNotePreview from '@/components/MkNotePreview.vue';
 | 
				
			||||||
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
 | 
					import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
 | 
				
			||||||
| 
						 | 
					@ -151,12 +161,14 @@ let visibleUsers = $ref([]);
 | 
				
			||||||
if (props.initialVisibleUsers) {
 | 
					if (props.initialVisibleUsers) {
 | 
				
			||||||
	props.initialVisibleUsers.forEach(pushVisibleUser);
 | 
						props.initialVisibleUsers.forEach(pushVisibleUser);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					let reactionAcceptance = $ref(defaultStore.state.reactionAcceptance);
 | 
				
			||||||
let autocomplete = $ref(null);
 | 
					let autocomplete = $ref(null);
 | 
				
			||||||
let draghover = $ref(false);
 | 
					let draghover = $ref(false);
 | 
				
			||||||
let quoteId = $ref(null);
 | 
					let quoteId = $ref(null);
 | 
				
			||||||
let hasNotSpecifiedMentions = $ref(false);
 | 
					let hasNotSpecifiedMentions = $ref(false);
 | 
				
			||||||
let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
 | 
					let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
 | 
				
			||||||
let imeText = $ref('');
 | 
					let imeText = $ref('');
 | 
				
			||||||
 | 
					let showingOptions = $ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const draftKey = $computed((): string => {
 | 
					const draftKey = $computed((): string => {
 | 
				
			||||||
	let key = props.channel ? `channel:${props.channel.id}` : '';
 | 
						let key = props.channel ? `channel:${props.channel.id}` : '';
 | 
				
			||||||
| 
						 | 
					@ -614,6 +626,7 @@ async function post(ev?: MouseEvent) {
 | 
				
			||||||
		localOnly: localOnly,
 | 
							localOnly: localOnly,
 | 
				
			||||||
		visibility: visibility,
 | 
							visibility: visibility,
 | 
				
			||||||
		visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
 | 
							visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
 | 
				
			||||||
 | 
							reactionAcceptance,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (withHashtags && hashtags && hashtags.trim() !== '') {
 | 
						if (withHashtags && hashtags && hashtags.trim() !== '') {
 | 
				
			||||||
| 
						 | 
					@ -1030,6 +1043,18 @@ defineExpose({
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.emojiButton {
 | 
				
			||||||
 | 
						position: absolute;
 | 
				
			||||||
 | 
						top: 55px;
 | 
				
			||||||
 | 
						right: 13px;
 | 
				
			||||||
 | 
						display: inline-block;
 | 
				
			||||||
 | 
						padding: 0;
 | 
				
			||||||
 | 
						margin: 0;
 | 
				
			||||||
 | 
						font-size: 1em;
 | 
				
			||||||
 | 
						width: 32px;
 | 
				
			||||||
 | 
						height: 32px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@container (max-width: 500px) {
 | 
					@container (max-width: 500px) {
 | 
				
			||||||
	.header {
 | 
						.header {
 | 
				
			||||||
		height: 50px;
 | 
							height: 50px;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,12 +64,19 @@
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</MkFolder>
 | 
						</MkFolder>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<MkSelect v-model="reactionAcceptance">
 | 
				
			||||||
 | 
							<template #label>{{ i18n.ts.reactionAcceptance }}</template>
 | 
				
			||||||
 | 
							<option :value="null">{{ i18n.ts.all }}</option>
 | 
				
			||||||
 | 
							<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
 | 
				
			||||||
 | 
							<option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
 | 
				
			||||||
 | 
						</MkSelect>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<MkSwitch v-model="profile.showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch>
 | 
						<MkSwitch v-model="profile.showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { reactive, watch } from 'vue';
 | 
					import { computed, reactive, watch } from 'vue';
 | 
				
			||||||
import MkButton from '@/components/MkButton.vue';
 | 
					import MkButton from '@/components/MkButton.vue';
 | 
				
			||||||
import MkInput from '@/components/MkInput.vue';
 | 
					import MkInput from '@/components/MkInput.vue';
 | 
				
			||||||
import MkTextarea from '@/components/MkTextarea.vue';
 | 
					import MkTextarea from '@/components/MkTextarea.vue';
 | 
				
			||||||
| 
						 | 
					@ -85,6 +92,9 @@ import { $i } from '@/account';
 | 
				
			||||||
import { langmap } from '@/scripts/langmap';
 | 
					import { langmap } from '@/scripts/langmap';
 | 
				
			||||||
import { definePageMetadata } from '@/scripts/page-metadata';
 | 
					import { definePageMetadata } from '@/scripts/page-metadata';
 | 
				
			||||||
import { claimAchievement } from '@/scripts/achievements';
 | 
					import { claimAchievement } from '@/scripts/achievements';
 | 
				
			||||||
 | 
					import { defaultStore } from '@/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const profile = reactive({
 | 
					const profile = reactive({
 | 
				
			||||||
	name: $i.name,
 | 
						name: $i.name,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,6 +81,10 @@ export const defaultStore = markRaw(new Storage('base', {
 | 
				
			||||||
		where: 'account',
 | 
							where: 'account',
 | 
				
			||||||
		default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
 | 
							default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						reactionAcceptance: {
 | 
				
			||||||
 | 
							where: 'account',
 | 
				
			||||||
 | 
							default: null,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	mutedWords: {
 | 
						mutedWords: {
 | 
				
			||||||
		where: 'account',
 | 
							where: 'account',
 | 
				
			||||||
		default: [],
 | 
							default: [],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue