mirror of
https://activitypub.software/TransFem-org/Sharkey.git
synced 2024-12-14 06:00:44 +01:00
Merge branch 'develop' into acid-chicken-patch-1
This commit is contained in:
commit
8a9847b06a
116 changed files with 553 additions and 541 deletions
|
@ -23,10 +23,13 @@
|
|||
### General
|
||||
- チャンネルをお気に入りに登録できるように
|
||||
- チャンネルにノートをピン留めできるように
|
||||
- アンテナのタイムライン取得時のパフォーマンスを向上
|
||||
- チャンネルのタイムライン取得時のパフォーマンスを向上
|
||||
|
||||
### Client
|
||||
- 検索ページでURLを入力した際に照会したときと同等の挙動をするように
|
||||
- ノートのリアクションを大きく表示するオプションを追加
|
||||
- オブジェクトストレージの設定画面を分かりやすく
|
||||
|
||||
### Server
|
||||
-
|
||||
|
|
|
@ -506,6 +506,7 @@ objectStorageUseSSLDesc: "API接続にhttpsを使用しない場合はオフに
|
|||
objectStorageUseProxy: "Proxyを利用する"
|
||||
objectStorageUseProxyDesc: "API接続にproxyを利用しない場合はオフにしてください"
|
||||
objectStorageSetPublicRead: "アップロード時に'public-read'を設定する"
|
||||
s3ForcePathStyleDesc: "s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。"
|
||||
serverLogs: "サーバーログ"
|
||||
deleteAll: "全て削除"
|
||||
showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
|
||||
|
|
10
packages/backend/migration/1680491187535-cleanup.js
Normal file
10
packages/backend/migration/1680491187535-cleanup.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
export class cleanup1680491187535 {
|
||||
name = 'cleanup1680491187535'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`DROP TABLE "antenna_note" `);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ import { PushNotificationService } from '@/core/PushNotificationService.js';
|
|||
import * as Acct from '@/misc/acct.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MutingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserListJoiningsRepository } from '@/models/index.js';
|
||||
import type { MutingsRepository, NotesRepository, AntennasRepository, UserListJoiningsRepository } from '@/models/index.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||
|
@ -24,6 +24,9 @@ export class AntennaService implements OnApplicationShutdown {
|
|||
private antennas: Antenna[];
|
||||
|
||||
constructor(
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.redisSubscriber)
|
||||
private redisSubscriber: Redis.Redis,
|
||||
|
||||
|
@ -33,9 +36,6 @@ export class AntennaService implements OnApplicationShutdown {
|
|||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
@Inject(DI.antennaNotesRepository)
|
||||
private antennaNotesRepository: AntennaNotesRepository,
|
||||
|
||||
@Inject(DI.antennasRepository)
|
||||
private antennasRepository: AntennasRepository,
|
||||
|
||||
|
@ -92,54 +92,13 @@ export class AntennaService implements OnApplicationShutdown {
|
|||
|
||||
@bindThis
|
||||
public async addNoteToAntenna(antenna: Antenna, note: Note, noteUser: { id: User['id']; }): Promise<void> {
|
||||
// 通知しない設定になっているか、自分自身の投稿なら既読にする
|
||||
const read = !antenna.notify || (antenna.userId === noteUser.id);
|
||||
|
||||
this.antennaNotesRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
antennaId: antenna.id,
|
||||
noteId: note.id,
|
||||
read: read,
|
||||
});
|
||||
this.redisClient.xadd(
|
||||
`antennaTimeline:${antenna.id}`,
|
||||
'MAXLEN', '~', '200',
|
||||
`${this.idService.parse(note.id).date.getTime()}-*`,
|
||||
'note', note.id);
|
||||
|
||||
this.globalEventService.publishAntennaStream(antenna.id, 'note', note);
|
||||
|
||||
if (!read) {
|
||||
const mutings = await this.mutingsRepository.find({
|
||||
where: {
|
||||
muterId: antenna.userId,
|
||||
},
|
||||
select: ['muteeId'],
|
||||
});
|
||||
|
||||
// Copy
|
||||
const _note: Note = {
|
||||
...note,
|
||||
};
|
||||
|
||||
if (note.replyId != null) {
|
||||
_note.reply = await this.notesRepository.findOneByOrFail({ id: note.replyId });
|
||||
}
|
||||
if (note.renoteId != null) {
|
||||
_note.renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId });
|
||||
}
|
||||
|
||||
if (isUserRelated(_note, new Set<string>(mutings.map(x => x.muteeId)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2秒経っても既読にならなかったら通知
|
||||
setTimeout(async () => {
|
||||
const unread = await this.antennaNotesRepository.findOneBy({ antennaId: antenna.id, read: false });
|
||||
if (unread) {
|
||||
this.globalEventService.publishMainStream(antenna.userId, 'unreadAntenna', antenna);
|
||||
this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', {
|
||||
antenna: { id: antenna.id, name: antenna.name },
|
||||
note: await this.noteEntityService.pack(note),
|
||||
});
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { ulid } from 'ulid';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { genAid } from '@/misc/id/aid.js';
|
||||
import { genAid, parseAid } from '@/misc/id/aid.js';
|
||||
import { genMeid } from '@/misc/id/meid.js';
|
||||
import { genMeidg } from '@/misc/id/meidg.js';
|
||||
import { genObjectId } from '@/misc/id/object-id.js';
|
||||
|
@ -32,4 +32,17 @@ export class IdService {
|
|||
default: throw new Error('unrecognized id generation method');
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public parse(id: string): { date: Date; } {
|
||||
switch (this.method) {
|
||||
case 'aid': return parseAid(id);
|
||||
// TODO
|
||||
//case 'meid':
|
||||
//case 'meidg':
|
||||
//case 'ulid':
|
||||
//case 'objectid':
|
||||
default: throw new Error('unrecognized id generation method');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { setImmediate } from 'node:timers/promises';
|
||||
import * as mfm from 'mfm-js';
|
||||
import { In, DataSource } from 'typeorm';
|
||||
import Redis from 'ioredis';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import { extractMentions } from '@/misc/extract-mentions.js';
|
||||
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
||||
|
@ -150,6 +151,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
@Inject(DI.db)
|
||||
private db: DataSource,
|
||||
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
|
@ -321,6 +325,14 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
|
||||
const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
|
||||
|
||||
if (data.channel) {
|
||||
this.redisClient.xadd(
|
||||
`channelTimeline:${data.channel.id}`,
|
||||
'MAXLEN', '~', '1000',
|
||||
`${this.idService.parse(note.id).date.getTime()}-*`,
|
||||
'note', note.id);
|
||||
}
|
||||
|
||||
setImmediate('post created', { signal: this.#shutdownController.signal }).then(
|
||||
() => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!),
|
||||
() => { /* aborted, ignore this */ },
|
||||
|
|
|
@ -8,7 +8,7 @@ import type { Packed } from '@/misc/json-schema.js';
|
|||
import type { Note } from '@/models/entities/Note.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository, FollowingsRepository, ChannelFollowingsRepository, AntennaNotesRepository } from '@/models/index.js';
|
||||
import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository, FollowingsRepository, ChannelFollowingsRepository } from '@/models/index.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { NotificationService } from './NotificationService.js';
|
||||
|
@ -38,9 +38,6 @@ export class NoteReadService implements OnApplicationShutdown {
|
|||
@Inject(DI.channelFollowingsRepository)
|
||||
private channelFollowingsRepository: ChannelFollowingsRepository,
|
||||
|
||||
@Inject(DI.antennaNotesRepository)
|
||||
private antennaNotesRepository: AntennaNotesRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private idService: IdService,
|
||||
private globalEventService: GlobalEventService,
|
||||
|
@ -121,7 +118,6 @@ export class NoteReadService implements OnApplicationShutdown {
|
|||
const readMentions: (Note | Packed<'Note'>)[] = [];
|
||||
const readSpecifiedNotes: (Note | Packed<'Note'>)[] = [];
|
||||
const readChannelNotes: (Note | Packed<'Note'>)[] = [];
|
||||
const readAntennaNotes: (Note | Packed<'Note'>)[] = [];
|
||||
|
||||
for (const note of notes) {
|
||||
if (note.mentions && note.mentions.includes(userId)) {
|
||||
|
@ -133,14 +129,6 @@ export class NoteReadService implements OnApplicationShutdown {
|
|||
if (note.channelId && followingChannels.has(note.channelId)) {
|
||||
readChannelNotes.push(note);
|
||||
}
|
||||
|
||||
if (note.user != null) { // たぶんnullになることは無いはずだけど一応
|
||||
for (const antenna of myAntennas) {
|
||||
if (await this.antennaService.checkHitAntenna(antenna, note, note.user)) {
|
||||
readAntennaNotes.push(note);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0) || (readChannelNotes.length > 0)) {
|
||||
|
@ -186,35 +174,6 @@ export class NoteReadService implements OnApplicationShutdown {
|
|||
noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]),
|
||||
});
|
||||
}
|
||||
|
||||
if (readAntennaNotes.length > 0) {
|
||||
await this.antennaNotesRepository.update({
|
||||
antennaId: In(myAntennas.map(a => a.id)),
|
||||
noteId: In(readAntennaNotes.map(n => n.id)),
|
||||
}, {
|
||||
read: true,
|
||||
});
|
||||
|
||||
// TODO: まとめてクエリしたい
|
||||
for (const antenna of myAntennas) {
|
||||
const count = await this.antennaNotesRepository.countBy({
|
||||
antennaId: antenna.id,
|
||||
read: false,
|
||||
});
|
||||
|
||||
if (count === 0) {
|
||||
this.globalEventService.publishMainStream(userId, 'readAntenna', antenna);
|
||||
this.pushNotificationService.pushNotification(userId, 'readAntenna', { antennaId: antenna.id });
|
||||
}
|
||||
}
|
||||
|
||||
this.userEntityService.getHasUnreadAntenna(userId).then(unread => {
|
||||
if (!unread) {
|
||||
this.globalEventService.publishMainStream(userId, 'readAllAntennas');
|
||||
this.pushNotificationService.pushNotification(userId, 'readAllAntennas', undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onApplicationShutdown(signal?: string | undefined): void {
|
||||
|
|
|
@ -37,7 +37,7 @@ export class VideoProcessingService {
|
|||
});
|
||||
});
|
||||
|
||||
return await this.imageProcessingService.convertToWebp(`${dir}/out.png`, 498, 280);
|
||||
return await this.imageProcessingService.convertToWebp(`${dir}/out.png`, 498, 422);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { AntennaNotesRepository, AntennasRepository } from '@/models/index.js';
|
||||
import type { AntennasRepository } from '@/models/index.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { Antenna } from '@/models/entities/Antenna.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@ -10,9 +10,6 @@ export class AntennaEntityService {
|
|||
constructor(
|
||||
@Inject(DI.antennasRepository)
|
||||
private antennasRepository: AntennasRepository,
|
||||
|
||||
@Inject(DI.antennaNotesRepository)
|
||||
private antennaNotesRepository: AntennaNotesRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -22,8 +19,6 @@ export class AntennaEntityService {
|
|||
): Promise<Packed<'Antenna'>> {
|
||||
const antenna = typeof src === 'object' ? src : await this.antennasRepository.findOneByOrFail({ id: src });
|
||||
|
||||
const hasUnreadNote = (await this.antennaNotesRepository.findOneBy({ antennaId: antenna.id, read: false })) != null;
|
||||
|
||||
return {
|
||||
id: antenna.id,
|
||||
createdAt: antenna.createdAt.toISOString(),
|
||||
|
@ -38,7 +33,7 @@ export class AntennaEntityService {
|
|||
withReplies: antenna.withReplies,
|
||||
withFile: antenna.withFile,
|
||||
isActive: antenna.isActive,
|
||||
hasUnreadNote,
|
||||
hasUnreadNote: false, // TODO
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { KVCache } from '@/misc/cache.js';
|
|||
import type { Instance } from '@/models/entities/Instance.js';
|
||||
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
|
||||
import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
|
||||
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository, UserProfile, RenoteMutingsRepository } from '@/models/index.js';
|
||||
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository } from '@/models/index.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
|
@ -108,9 +108,6 @@ export class UserEntityService implements OnModuleInit {
|
|||
@Inject(DI.announcementsRepository)
|
||||
private announcementsRepository: AnnouncementsRepository,
|
||||
|
||||
@Inject(DI.antennaNotesRepository)
|
||||
private antennaNotesRepository: AntennaNotesRepository,
|
||||
|
||||
@Inject(DI.pagesRepository)
|
||||
private pagesRepository: PagesRepository,
|
||||
|
||||
|
@ -223,6 +220,7 @@ export class UserEntityService implements OnModuleInit {
|
|||
|
||||
@bindThis
|
||||
public async getHasUnreadAntenna(userId: User['id']): Promise<boolean> {
|
||||
/*
|
||||
const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId);
|
||||
|
||||
const unread = myAntennas.length > 0 ? await this.antennaNotesRepository.findOneBy({
|
||||
|
@ -231,6 +229,8 @@ export class UserEntityService implements OnModuleInit {
|
|||
}) : null;
|
||||
|
||||
return unread != null;
|
||||
*/
|
||||
return false; // TODO
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
@ -54,7 +54,6 @@ export const DI = {
|
|||
clipNotesRepository: Symbol('clipNotesRepository'),
|
||||
clipFavoritesRepository: Symbol('clipFavoritesRepository'),
|
||||
antennasRepository: Symbol('antennasRepository'),
|
||||
antennaNotesRepository: Symbol('antennaNotesRepository'),
|
||||
promoNotesRepository: Symbol('promoNotesRepository'),
|
||||
promoReadsRepository: Symbol('promoReadsRepository'),
|
||||
relaysRepository: Symbol('relaysRepository'),
|
||||
|
|
|
@ -23,3 +23,8 @@ export function genAid(date: Date): string {
|
|||
counter++;
|
||||
return getTime(t) + getNoise();
|
||||
}
|
||||
|
||||
export function parseAid(id: string): { date: Date; } {
|
||||
const time = parseInt(id.slice(0, 8), 36) + TIME2000;
|
||||
return { date: new Date(time) };
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js';
|
||||
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js';
|
||||
import type { DataSource } from 'typeorm';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
|
||||
|
@ -298,12 +298,6 @@ const $antennasRepository: Provider = {
|
|||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $antennaNotesRepository: Provider = {
|
||||
provide: DI.antennaNotesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(AntennaNote),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $promoNotesRepository: Provider = {
|
||||
provide: DI.promoNotesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(PromoNote),
|
||||
|
@ -453,7 +447,6 @@ const $roleAssignmentsRepository: Provider = {
|
|||
$clipNotesRepository,
|
||||
$clipFavoritesRepository,
|
||||
$antennasRepository,
|
||||
$antennaNotesRepository,
|
||||
$promoNotesRepository,
|
||||
$promoReadsRepository,
|
||||
$relaysRepository,
|
||||
|
@ -521,7 +514,6 @@ const $roleAssignmentsRepository: Provider = {
|
|||
$clipNotesRepository,
|
||||
$clipFavoritesRepository,
|
||||
$antennasRepository,
|
||||
$antennaNotesRepository,
|
||||
$promoNotesRepository,
|
||||
$promoReadsRepository,
|
||||
$relaysRepository,
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import { Entity, Index, JoinColumn, Column, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
import { id } from '../id.js';
|
||||
import { Note } from './Note.js';
|
||||
import { Antenna } from './Antenna.js';
|
||||
|
||||
@Entity()
|
||||
@Index(['noteId', 'antennaId'], { unique: true })
|
||||
export class AntennaNote {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The note ID.',
|
||||
})
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: Note | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The antenna ID.',
|
||||
})
|
||||
public antennaId: Antenna['id'];
|
||||
|
||||
@ManyToOne(type => Antenna, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public antenna: Antenna | null;
|
||||
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public read: boolean;
|
||||
}
|
|
@ -4,7 +4,6 @@ import { Ad } from '@/models/entities/Ad.js';
|
|||
import { Announcement } from '@/models/entities/Announcement.js';
|
||||
import { AnnouncementRead } from '@/models/entities/AnnouncementRead.js';
|
||||
import { Antenna } from '@/models/entities/Antenna.js';
|
||||
import { AntennaNote } from '@/models/entities/AntennaNote.js';
|
||||
import { App } from '@/models/entities/App.js';
|
||||
import { AttestationChallenge } from '@/models/entities/AttestationChallenge.js';
|
||||
import { AuthSession } from '@/models/entities/AuthSession.js';
|
||||
|
@ -73,7 +72,6 @@ export {
|
|||
Announcement,
|
||||
AnnouncementRead,
|
||||
Antenna,
|
||||
AntennaNote,
|
||||
App,
|
||||
AttestationChallenge,
|
||||
AuthSession,
|
||||
|
@ -141,7 +139,6 @@ export type AdsRepository = Repository<Ad>;
|
|||
export type AnnouncementsRepository = Repository<Announcement>;
|
||||
export type AnnouncementReadsRepository = Repository<AnnouncementRead>;
|
||||
export type AntennasRepository = Repository<Antenna>;
|
||||
export type AntennaNotesRepository = Repository<AntennaNote>;
|
||||
export type AppsRepository = Repository<App>;
|
||||
export type AttestationChallengesRepository = Repository<AttestationChallenge>;
|
||||
export type AuthSessionsRepository = Repository<AuthSession>;
|
||||
|
|
|
@ -12,7 +12,6 @@ import { Ad } from '@/models/entities/Ad.js';
|
|||
import { Announcement } from '@/models/entities/Announcement.js';
|
||||
import { AnnouncementRead } from '@/models/entities/AnnouncementRead.js';
|
||||
import { Antenna } from '@/models/entities/Antenna.js';
|
||||
import { AntennaNote } from '@/models/entities/AntennaNote.js';
|
||||
import { App } from '@/models/entities/App.js';
|
||||
import { AttestationChallenge } from '@/models/entities/AttestationChallenge.js';
|
||||
import { AuthSession } from '@/models/entities/AuthSession.js';
|
||||
|
@ -168,7 +167,6 @@ export const entities = [
|
|||
ClipNote,
|
||||
ClipFavorite,
|
||||
Antenna,
|
||||
AntennaNote,
|
||||
PromoNote,
|
||||
PromoRead,
|
||||
Relay,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In, LessThan } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { AntennaNotesRepository, AntennasRepository, MutedNotesRepository, NotificationsRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/index.js';
|
||||
import type { AntennasRepository, MutedNotesRepository, NotificationsRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@ -29,9 +29,6 @@ export class CleanProcessorService {
|
|||
@Inject(DI.antennasRepository)
|
||||
private antennasRepository: AntennasRepository,
|
||||
|
||||
@Inject(DI.antennaNotesRepository)
|
||||
private antennaNotesRepository: AntennaNotesRepository,
|
||||
|
||||
@Inject(DI.roleAssignmentsRepository)
|
||||
private roleAssignmentsRepository: RoleAssignmentsRepository,
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { NotesRepository, AntennaNotesRepository, AntennasRepository } from '@/models/index.js';
|
||||
import type { NotesRepository, AntennasRepository } from '@/models/index.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -50,15 +52,16 @@ export const paramDef = {
|
|||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
@Inject(DI.antennasRepository)
|
||||
private antennasRepository: AntennasRepository,
|
||||
|
||||
@Inject(DI.antennaNotesRepository)
|
||||
private antennaNotesRepository: AntennaNotesRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
private noteReadService: NoteReadService,
|
||||
|
@ -73,9 +76,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
throw new ApiError(meta.errors.noSuchAntenna);
|
||||
}
|
||||
|
||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
|
||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.innerJoin(this.antennaNotesRepository.metadata.targetName, 'antennaNote', 'antennaNote.noteId = note.id')
|
||||
const noteIdsRes = await this.redisClient.xrevrange(
|
||||
`antennaTimeline:${antenna.id}`,
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
|
||||
'-',
|
||||
'COUNT', ps.limit + 1); // untilIdに指定したものも含まれるため+1
|
||||
|
||||
if (noteIdsRes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const query = this.notesRepository.createQueryBuilder('note')
|
||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
.leftJoinAndSelect('user.avatar', 'avatar')
|
||||
.leftJoinAndSelect('user.banner', 'banner')
|
||||
|
@ -86,16 +104,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
|
||||
.leftJoinAndSelect('renote.user', 'renoteUser')
|
||||
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
|
||||
.andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id });
|
||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
||||
|
||||
this.queryService.generateVisibilityQuery(query, me);
|
||||
this.queryService.generateMutedUserQuery(query, me);
|
||||
this.queryService.generateBlockedUserQuery(query, me);
|
||||
|
||||
const notes = await query
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
const notes = await query.getMany();
|
||||
notes.sort((a, b) => a.id > b.id ? -1 : 1);
|
||||
|
||||
if (notes.length > 0) {
|
||||
this.noteReadService.read(me.id, notes);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { ChannelsRepository, NotesRepository } from '@/models/index.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -48,12 +50,16 @@ export const paramDef = {
|
|||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
@Inject(DI.channelsRepository)
|
||||
private channelsRepository: ChannelsRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
private activeUsersChart: ActiveUsersChart,
|
||||
|
@ -67,9 +73,25 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
}
|
||||
|
||||
const noteIdsRes = await this.redisClient.xrevrange(
|
||||
`channelTimeline:${channel.id}`,
|
||||
ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
|
||||
'-',
|
||||
'COUNT', ps.limit + 1); // untilIdに指定したものも含まれるため+1
|
||||
|
||||
if (noteIdsRes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
|
||||
|
||||
if (noteIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
//#region Construct query
|
||||
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('note.channelId = :channelId', { channelId: channel.id })
|
||||
const query = this.notesRepository.createQueryBuilder('note')
|
||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
.leftJoinAndSelect('user.avatar', 'avatar')
|
||||
.leftJoinAndSelect('user.banner', 'banner')
|
||||
|
@ -90,7 +112,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
}
|
||||
//#endregion
|
||||
|
||||
const timeline = await query.take(ps.limit).getMany();
|
||||
const timeline = await query.getMany();
|
||||
timeline.sort((a, b) => a.id > b.id ? -1 : 1);
|
||||
|
||||
if (me) this.activeUsersChart.read(me);
|
||||
|
||||
|
|
16
packages/frontend/@types/vue.d.ts
vendored
16
packages/frontend/@types/vue.d.ts
vendored
|
@ -1,16 +0,0 @@
|
|||
/// <reference types="vue/macros-global" />
|
||||
|
||||
import type { $i } from '@/account';
|
||||
import type { defaultStore } from '@/store';
|
||||
import type { instance } from '@/instance';
|
||||
import type { i18n } from '@/i18n';
|
||||
|
||||
declare module 'vue' {
|
||||
interface ComponentCustomProperties {
|
||||
$i: typeof $i;
|
||||
$store: typeof defaultStore;
|
||||
$instance: typeof instance;
|
||||
$t: typeof i18n['t'];
|
||||
$ts: typeof i18n['ts'];
|
||||
}
|
||||
}
|
|
@ -14,10 +14,10 @@
|
|||
</div>
|
||||
</header>
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_toggle_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_toggle_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_toggle_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_toggle_leaveTo : ''"
|
||||
:enter-active-class="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
|
||||
:leave-active-class="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
|
||||
:enter-from-class="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''"
|
||||
:leave-to-class="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''"
|
||||
@enter="enter"
|
||||
@after-enter="afterEnter"
|
||||
@leave="leave"
|
||||
|
@ -26,7 +26,7 @@
|
|||
<div v-show="showBody" ref="content" :class="[$style.content, { [$style.omitted]: omitted }]">
|
||||
<slot></slot>
|
||||
<button v-if="omitted" :class="$style.fade" class="_button" @click="() => { ignoreOmit = true; omitted = false; }">
|
||||
<span :class="$style.fadeLabel">{{ $ts.showMore }}</span>
|
||||
<span :class="$style.fadeLabel">{{ i18n.ts.showMore }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
|
@ -35,6 +35,8 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defaultStore } from '@/store';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
@ -79,6 +81,7 @@ export default defineComponent({
|
|||
showBody: this.expanded,
|
||||
omitted: null,
|
||||
ignoreOmit: false,
|
||||
defaultStore,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<Transition
|
||||
appear
|
||||
:enter-active-class="$store.state.animation ? $style.transition_fade_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_fade_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_fade_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_fade_leaveTo : ''"
|
||||
:enter-active-class="defaultStore.state.animation ? $style.transition_fade_enterActive : ''"
|
||||
:leave-active-class="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''"
|
||||
:enter-from-class="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''"
|
||||
:leave-to-class="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''"
|
||||
>
|
||||
<div ref="rootEl" :class="$style.root" :style="{ zIndex }" @contextmenu.prevent.stop="() => {}">
|
||||
<MkMenu :items="items" :align="'left'" @close="$emit('closed')"/>
|
||||
|
@ -18,6 +18,7 @@ import MkMenu from './MkMenu.vue';
|
|||
import { MenuItem } from './types/menu.vue';
|
||||
import contains from '@/scripts/contains';
|
||||
import * as os from '@/os';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = defineProps<{
|
||||
items: MenuItem[];
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div :class="$style.text">
|
||||
<I18n :src="i18n.ts.pleaseDonate" tag="span">
|
||||
<template #host>
|
||||
{{ $instance.name ?? host }}
|
||||
{{ instance.name ?? host }}
|
||||
</template>
|
||||
</I18n>
|
||||
<div style="margin-top: 0.2em;">
|
||||
|
@ -37,6 +37,7 @@ import { host } from '@/config';
|
|||
import { i18n } from '@/i18n';
|
||||
import * as os from '@/os';
|
||||
import { miLocalStorage } from '@/local-storage';
|
||||
import { instance } from '@/instance';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</button>
|
||||
</header>
|
||||
<Transition
|
||||
:name="$store.state.animation ? 'folder-toggle' : ''"
|
||||
:name="defaultStore.state.animation ? 'folder-toggle' : ''"
|
||||
@enter="enter"
|
||||
@after-enter="afterEnter"
|
||||
@leave="leave"
|
||||
|
@ -26,6 +26,7 @@
|
|||
import { defineComponent } from 'vue';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { miLocalStorage } from '@/local-storage';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const miLocalStoragePrefix = 'ui:folder:' as const;
|
||||
|
||||
|
@ -44,6 +45,7 @@ export default defineComponent({
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
defaultStore,
|
||||
bg: null,
|
||||
showBody: (this.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`) === 't') : this.expanded,
|
||||
};
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
|
||||
<div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null, overflow: maxHeight ? `auto` : null }">
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_toggle_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_toggle_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_toggle_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_toggle_leaveTo : ''"
|
||||
:enter-active-class="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
|
||||
:leave-active-class="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
|
||||
:enter-from-class="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''"
|
||||
:leave-to-class="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''"
|
||||
@enter="enter"
|
||||
@after-enter="afterEnter"
|
||||
@leave="leave"
|
||||
|
@ -46,6 +46,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted } from 'vue';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
defaultOpen?: boolean;
|
||||
|
|
|
@ -18,15 +18,15 @@
|
|||
<div class="_gaps_m">
|
||||
<template v-for="item in Object.keys(form).filter(item => !form[item].hidden)">
|
||||
<MkInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1">
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
|
||||
<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model="values[item]" type="text">
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
|
||||
<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
|
||||
</MkInput>
|
||||
<MkTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model="values[item]">
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
|
||||
<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
|
||||
</MkTextarea>
|
||||
<MkSwitch v-else-if="form[item].type === 'boolean'" v-model="values[item]">
|
||||
|
@ -34,15 +34,15 @@
|
|||
<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSelect v-else-if="form[item].type === 'enum'" v-model="values[item]">
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
|
||||
<option v-for="item in form[item].enum" :key="item.value" :value="item.value">{{ item.label }}</option>
|
||||
</MkSelect>
|
||||
<MkRadios v-else-if="form[item].type === 'radio'" v-model="values[item]">
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
|
||||
<option v-for="item in form[item].options" :key="item.value" :value="item.value">{{ item.label }}</option>
|
||||
</MkRadios>
|
||||
<MkRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].min" :max="form[item].max" :step="form[item].step" :text-converter="form[item].textConverter">
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
|
||||
<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
|
||||
</MkRange>
|
||||
<MkButton v-else-if="form[item].type === 'button'" @click="form[item].action($event, values)">
|
||||
|
@ -64,6 +64,7 @@ import MkRange from './MkRange.vue';
|
|||
import MkButton from './MkButton.vue';
|
||||
import MkRadios from './MkRadios.vue';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -93,6 +94,7 @@ export default defineComponent({
|
|||
data() {
|
||||
return {
|
||||
values: {},
|
||||
i18n,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<template>
|
||||
<div :class="$style.root">
|
||||
<input v-model="query" :class="$style.input" type="search" :placeholder="q">
|
||||
<button :class="$style.button" @click="search"><i class="ti ti-search"></i> {{ $ts.searchByGoogle }}</button>
|
||||
<button :class="$style.button" @click="search"><i class="ti ti-search"></i> {{ i18n.ts.searchByGoogle }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const props = defineProps<{
|
||||
q: string;
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<div class="mk-media-banner">
|
||||
<div v-if="media.isSensitive && hide" class="sensitive" @click="hide = false">
|
||||
<span class="icon"><i class="ti ti-alert-triangle"></i></span>
|
||||
<b>{{ $ts.sensitive }}</b>
|
||||
<span>{{ $ts.clickToShow }}</span>
|
||||
<b>{{ i18n.ts.sensitive }}</b>
|
||||
<span>{{ i18n.ts.clickToShow }}</span>
|
||||
</div>
|
||||
<div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" class="audio">
|
||||
<VuePlyr :options="{ volume: 0.5 }">
|
||||
|
@ -33,6 +33,7 @@ import * as misskey from 'misskey-js';
|
|||
import VuePlyr from 'vue-plyr';
|
||||
import { ColdDeviceStorage } from '@/store';
|
||||
import 'vue-plyr/dist/vue-plyr.css';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
media: misskey.entities.DriveFile;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div v-if="hide" class="icozogqfvdetwohsdglrbswgrejoxbdj" @click="hide = false">
|
||||
<div>
|
||||
<b><i class="ti ti-alert-triangle"></i> {{ $ts.sensitive }}</b>
|
||||
<span>{{ $ts.clickToShow }}</span>
|
||||
<b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}</b>
|
||||
<span>{{ i18n.ts.clickToShow }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="kkjnbbplepmiyuadieoenjgutgcmtsvu">
|
||||
|
@ -28,6 +28,7 @@ import * as misskey from 'misskey-js';
|
|||
import VuePlyr from 'vue-plyr';
|
||||
import { defaultStore } from '@/store';
|
||||
import 'vue-plyr/dist/vue-plyr.css';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const props = defineProps<{
|
||||
video: misskey.entities.DriveFile;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<img :class="$style.icon" :src="`/avatar/@${username}@${host}`" alt="">
|
||||
<span>
|
||||
<span :class="$style.username">@{{ username }}</span>
|
||||
<span v-if="(host != localHost) || $store.state.showFullAcct" :class="$style.host">@{{ toUnicode(host) }}</span>
|
||||
<span v-if="(host != localHost) || defaultStore.state.showFullAcct" :class="$style.host">@{{ toUnicode(host) }}</span>
|
||||
</span>
|
||||
</MkA>
|
||||
</template>
|
||||
|
@ -14,6 +14,7 @@ import { } from 'vue';
|
|||
import tinycolor from 'tinycolor2';
|
||||
import { host as localHost } from '@/config';
|
||||
import { $i } from '@/account';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = defineProps<{
|
||||
username: string;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
|
||||
<div ref="rootEl" class="hrmcaedk" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }">
|
||||
<div class="header" @contextmenu="onContextmenu">
|
||||
<button v-if="history.length > 0" v-tooltip="$ts.goBack" class="_button" @click="back()"><i class="ti ti-arrow-left"></i></button>
|
||||
<button v-if="history.length > 0" v-tooltip="i18n.ts.goBack" class="_button" @click="back()"><i class="ti ti-arrow-left"></i></button>
|
||||
<span v-else style="display: inline-block; width: 20px"></span>
|
||||
<span v-if="pageMetadata?.value" class="title">
|
||||
<i v-if="pageMetadata?.value.icon" class="icon" :class="pageMetadata?.value.icon"></i>
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
<div v-if="translating || translation" :class="$style.translation">
|
||||
<MkLoading v-if="translating" mini/>
|
||||
<div v-else :class="$style.translated">
|
||||
<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b>
|
||||
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
|
||||
<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
<div v-if="translating || translation" class="translation">
|
||||
<MkLoading v-if="translating" mini/>
|
||||
<div v-else class="translated">
|
||||
<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b>
|
||||
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
|
||||
<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import { $i } from '@/account';
|
||||
|
||||
const props = defineProps<{
|
||||
text: string;
|
||||
|
|
|
@ -22,6 +22,7 @@ import * as misskey from 'misskey-js';
|
|||
import MkNoteHeader from '@/components/MkNoteHeader.vue';
|
||||
import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
|
||||
import MkCwButton from '@/components/MkCwButton.vue';
|
||||
import { $i } from '@/account';
|
||||
|
||||
const props = defineProps<{
|
||||
note: misskey.entities.Note;
|
||||
|
|
|
@ -33,6 +33,7 @@ import MkCwButton from '@/components/MkCwButton.vue';
|
|||
import { notePage } from '@/filters/note';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { $i } from '@/account';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: misskey.entities.Note;
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
<div ref="content" :class="[$style.content, { [$style.omitted]: omitted }]">
|
||||
<slot></slot>
|
||||
<button v-if="omitted" :class="$style.fade" class="_button" @click="() => { ignoreOmit = true; omitted = false; }">
|
||||
<span :class="$style.fadeLabel">{{ $ts.showMore }}</span>
|
||||
<span :class="$style.fadeLabel">{{ i18n.ts.showMore }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
maxHeight: number;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_fade_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_fade_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_fade_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_fade_leaveTo : ''"
|
||||
:enter-active-class="defaultStore.state.animation ? $style.transition_fade_enterActive : ''"
|
||||
:leave-active-class="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''"
|
||||
:enter-from-class="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''"
|
||||
:leave-to-class="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''"
|
||||
mode="out-in"
|
||||
>
|
||||
<MkLoading v-if="fetching"/>
|
||||
|
@ -163,21 +163,22 @@ async function init(): Promise<void> {
|
|||
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
|
||||
await os.api(props.pagination.endpoint, {
|
||||
...params,
|
||||
limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1,
|
||||
limit: props.pagination.limit ?? 10,
|
||||
}).then(res => {
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
const item = res[i];
|
||||
if (i === 3) item._shouldInsertAd_ = true;
|
||||
}
|
||||
if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) {
|
||||
res.pop();
|
||||
|
||||
if (res.length === 0 || props.pagination.noPaging) {
|
||||
items.value = res;
|
||||
more.value = false;
|
||||
} else {
|
||||
if (props.pagination.reversed) moreFetching.value = true;
|
||||
items.value = res;
|
||||
more.value = true;
|
||||
} else {
|
||||
items.value = res;
|
||||
more.value = false;
|
||||
}
|
||||
|
||||
offset.value = res.length;
|
||||
error.value = false;
|
||||
fetching.value = false;
|
||||
|
@ -198,7 +199,7 @@ const fetchMore = async (): Promise<void> => {
|
|||
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
|
||||
await os.api(props.pagination.endpoint, {
|
||||
...params,
|
||||
limit: SECOND_FETCH_LIMIT + 1,
|
||||
limit: SECOND_FETCH_LIMIT,
|
||||
...(props.pagination.offsetMode ? {
|
||||
offset: offset.value,
|
||||
} : {
|
||||
|
@ -227,28 +228,26 @@ const fetchMore = async (): Promise<void> => {
|
|||
});
|
||||
};
|
||||
|
||||
if (res.length > SECOND_FETCH_LIMIT) {
|
||||
res.pop();
|
||||
|
||||
if (res.length === 0) {
|
||||
if (props.pagination.reversed) {
|
||||
reverseConcat(res).then(() => {
|
||||
more.value = true;
|
||||
more.value = false;
|
||||
moreFetching.value = false;
|
||||
});
|
||||
} else {
|
||||
items.value = items.value.concat(res);
|
||||
more.value = true;
|
||||
more.value = false;
|
||||
moreFetching.value = false;
|
||||
}
|
||||
} else {
|
||||
if (props.pagination.reversed) {
|
||||
reverseConcat(res).then(() => {
|
||||
more.value = false;
|
||||
more.value = true;
|
||||
moreFetching.value = false;
|
||||
});
|
||||
} else {
|
||||
items.value = items.value.concat(res);
|
||||
more.value = false;
|
||||
more.value = true;
|
||||
moreFetching.value = false;
|
||||
}
|
||||
}
|
||||
|
@ -264,20 +263,19 @@ const fetchMoreAhead = async (): Promise<void> => {
|
|||
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
|
||||
await os.api(props.pagination.endpoint, {
|
||||
...params,
|
||||
limit: SECOND_FETCH_LIMIT + 1,
|
||||
limit: SECOND_FETCH_LIMIT,
|
||||
...(props.pagination.offsetMode ? {
|
||||
offset: offset.value,
|
||||
} : {
|
||||
sinceId: items.value[items.value.length - 1].id,
|
||||
}),
|
||||
}).then(res => {
|
||||
if (res.length > SECOND_FETCH_LIMIT) {
|
||||
res.pop();
|
||||
items.value = items.value.concat(res);
|
||||
more.value = true;
|
||||
} else {
|
||||
if (res.length === 0) {
|
||||
items.value = items.value.concat(res);
|
||||
more.value = false;
|
||||
} else {
|
||||
items.value = items.value.concat(res);
|
||||
more.value = true;
|
||||
}
|
||||
offset.value += res.length;
|
||||
moreFetching.value = false;
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
<span>
|
||||
<template v-if="choice.isVoted"><i class="ti ti-check"></i></template>
|
||||
<Mfm :text="choice.text" :plain="true"/>
|
||||
<span v-if="showResult" class="votes">({{ $t('_poll.votesCount', { n: choice.votes }) }})</span>
|
||||
<span v-if="showResult" class="votes">({{ i18n.t('_poll.votesCount', { n: choice.votes }) }})</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-if="!readOnly">
|
||||
<span>{{ $t('_poll.totalVotes', { n: total }) }}</span>
|
||||
<span>{{ i18n.t('_poll.totalVotes', { n: total }) }}</span>
|
||||
<span> · </span>
|
||||
<a v-if="!closed && !isVoted" @click="showResult = !showResult">{{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }}</a>
|
||||
<span v-if="isVoted">{{ i18n.ts._poll.voted }}</span>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</p>
|
||||
<ul>
|
||||
<li v-for="(choice, i) in choices" :key="i">
|
||||
<MkInput class="input" small :model-value="choice" :placeholder="$t('_poll.choiceN', { n: i + 1 })" @update:model-value="onInput(i, $event)">
|
||||
<MkInput class="input" small :model-value="choice" :placeholder="i18n.t('_poll.choiceN', { n: i + 1 })" @update:model-value="onInput(i, $event)">
|
||||
</MkInput>
|
||||
<button class="_button" @click="remove(i)">
|
||||
<i class="ti ti-x"></i>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<TransitionGroup
|
||||
:enter-active-class="$store.state.animation ? $style.transition_x_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_x_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_x_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_x_leaveTo : ''"
|
||||
:move-class="$store.state.animation ? $style.transition_x_move : ''"
|
||||
:enter-active-class="defaultStore.state.animation ? $style.transition_x_enterActive : ''"
|
||||
:leave-active-class="defaultStore.state.animation ? $style.transition_x_leaveActive : ''"
|
||||
:enter-from-class="defaultStore.state.animation ? $style.transition_x_enterFrom : ''"
|
||||
:leave-to-class="defaultStore.state.animation ? $style.transition_x_leaveTo : ''"
|
||||
:move-class="defaultStore.state.animation ? $style.transition_x_move : ''"
|
||||
tag="div" :class="$style.root"
|
||||
>
|
||||
<XReaction v-for="[reaction, count] in reactions" :key="reaction" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note"/>
|
||||
|
@ -14,8 +14,9 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import * as misskey from 'misskey-js';
|
||||
import XReaction from '@/components/MkReactionsViewer.reaction.vue';
|
||||
import { watch } from 'vue';
|
||||
import XReaction from '@/components/MkReactionsViewer.reaction.vue';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: misskey.entities.Note;
|
||||
|
|
|
@ -36,6 +36,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
|
|||
import MkRadio from '@/components/MkRadio.vue';
|
||||
import * as os from '@/os';
|
||||
import * as config from '@/config';
|
||||
import { $i } from '@/account';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -51,6 +52,7 @@ export default defineComponent({
|
|||
text: '',
|
||||
flag: true,
|
||||
radio: 'misskey',
|
||||
$i,
|
||||
mfm: `Hello world! This is an @example mention. BTW you are @${this.$i ? this.$i.username : 'guest'}.\nAlso, here is ${config.url} and [example link](${config.url}). for more details, see https://example.com.\nAs you know #misskey is open-source software.`,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<MkA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
||||
</div>
|
||||
<details v-if="note.files.length > 0">
|
||||
<summary>({{ $t('withNFiles', { n: note.files.length }) }})</summary>
|
||||
<summary>({{ i18n.t('withNFiles', { n: note.files.length }) }})</summary>
|
||||
<MkMediaList :media-list="note.files"/>
|
||||
</details>
|
||||
<details v-if="note.poll">
|
||||
|
@ -27,6 +27,7 @@ import * as misskey from 'misskey-js';
|
|||
import MkMediaList from '@/components/MkMediaList.vue';
|
||||
import MkPoll from '@/components/MkPoll.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { $i } from '@/account';
|
||||
|
||||
const props = defineProps<{
|
||||
note: misskey.entities.Note;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<MkNotes ref="tlComponent" :no-gap="!$store.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/>
|
||||
<MkNotes ref="tlComponent" :no-gap="!defaultStore.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -8,6 +8,7 @@ import MkNotes from '@/components/MkNotes.vue';
|
|||
import { stream } from '@/stream';
|
||||
import * as sound from '@/scripts/sound';
|
||||
import { $i } from '@/account';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = defineProps<{
|
||||
src: string;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div>
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_toast_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_toast_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_toast_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_toast_leaveTo : ''"
|
||||
:enter-active-class="defaultStore.state.animation ? $style.transition_toast_enterActive : ''"
|
||||
:leave-active-class="defaultStore.state.animation ? $style.transition_toast_leaveActive : ''"
|
||||
:enter-from-class="defaultStore.state.animation ? $style.transition_toast_enterFrom : ''"
|
||||
:leave-to-class="defaultStore.state.animation ? $style.transition_toast_leaveTo : ''"
|
||||
appear @after-leave="emit('closed')"
|
||||
>
|
||||
<div v-if="showing" class="_acrylic" :class="$style.root" :style="{ zIndex }">
|
||||
|
@ -19,6 +19,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue';
|
||||
import * as os from '@/os';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
defineProps<{
|
||||
message: string;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
@closed="$emit('closed')"
|
||||
@ok="ok()"
|
||||
>
|
||||
<template #header>{{ title || $ts.generateAccessToken }}</template>
|
||||
<template #header>{{ title || i18n.ts.generateAccessToken }}</template>
|
||||
|
||||
<MkSpacer :margin-min="20" :margin-max="28">
|
||||
<div class="_gaps_m">
|
||||
|
@ -19,15 +19,15 @@
|
|||
</div>
|
||||
<div>
|
||||
<MkInput v-model="name">
|
||||
<template #label>{{ $ts.name }}</template>
|
||||
<template #label>{{ i18n.ts.name }}</template>
|
||||
</MkInput>
|
||||
</div>
|
||||
<div><b>{{ $ts.permission }}</b></div>
|
||||
<div><b>{{ i18n.ts.permission }}</b></div>
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton>
|
||||
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
|
||||
</div>
|
||||
<MkSwitch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model="permissions[kind]">{{ $t(`_permissions.${kind}`) }}</MkSwitch>
|
||||
<MkSwitch v-for="kind in (initialPermissions || kinds)" :key="kind" v-model="permissions[kind]">{{ i18n.t(`_permissions.${kind}`) }}</MkSwitch>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</MkModalWindow>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_tooltip_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_tooltip_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_tooltip_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_tooltip_leaveTo : ''"
|
||||
:enter-active-class="defaultStore.state.animation ? $style.transition_tooltip_enterActive : ''"
|
||||
:leave-active-class="defaultStore.state.animation ? $style.transition_tooltip_leaveActive : ''"
|
||||
:enter-from-class="defaultStore.state.animation ? $style.transition_tooltip_enterFrom : ''"
|
||||
:leave-to-class="defaultStore.state.animation ? $style.transition_tooltip_leaveTo : ''"
|
||||
appear @after-leave="emit('closed')"
|
||||
>
|
||||
<div v-show="showing" ref="el" :class="$style.root" class="_acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
|
||||
|
@ -19,6 +19,7 @@
|
|||
import { nextTick, onMounted, onUnmounted, shallowRef } from 'vue';
|
||||
import * as os from '@/os';
|
||||
import { calcPopupPosition } from '@/scripts/popup-position';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
showing: boolean;
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</template>
|
||||
<template v-else-if="tweetId && tweetExpanded">
|
||||
<div ref="twitter" :class="$style.twitter">
|
||||
<iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${$store.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"></iframe>
|
||||
<iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`"></iframe>
|
||||
</div>
|
||||
<div :class="$style.action">
|
||||
<MkButton :small="true" inline @click="tweetExpanded = false">
|
||||
|
@ -77,6 +77,7 @@ import * as os from '@/os';
|
|||
import { deviceKind } from '@/scripts/device-kind';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { versatileLang } from '@/scripts/intl-const';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
type SummalyResult = Awaited<ReturnType<typeof summaly>>;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="fgmtyycl" :style="{ zIndex, top: top + 'px', left: left + 'px' }">
|
||||
<Transition :name="$store.state.animation ? '_transition_zoom' : ''" @after-leave="emit('closed')">
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" @after-leave="emit('closed')">
|
||||
<MkUrlPreview v-if="showing" class="_popup _shadow" :url="url"/>
|
||||
</Transition>
|
||||
</div>
|
||||
|
@ -10,6 +10,7 @@
|
|||
import { onMounted } from 'vue';
|
||||
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||
import * as os from '@/os';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = defineProps<{
|
||||
showing: boolean;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA>
|
||||
<p class="username"><MkAcct :user="user"/></p>
|
||||
</div>
|
||||
<span v-if="$i && $i.id !== user.id && user.isFollowed" class="followed">{{ $ts.followsYou }}</span>
|
||||
<span v-if="$i && $i.id !== user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
|
||||
<div class="description">
|
||||
<div v-if="user.description" class="mfm">
|
||||
<Mfm :text="user.description" :author="user" :i="$i"/>
|
||||
|
@ -33,6 +33,7 @@ import * as misskey from 'misskey-js';
|
|||
import MkFollowButton from '@/components/MkFollowButton.vue';
|
||||
import { userPage } from '@/filters/user';
|
||||
import { i18n } from '@/i18n';
|
||||
import { $i } from '@/account';
|
||||
|
||||
defineProps<{
|
||||
user: misskey.entities.UserDetailed;
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<template>
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_popup_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_popup_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_popup_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_popup_leaveTo : ''"
|
||||
:enter-active-class="defaultStore.state.animation ? $style.transition_popup_enterActive : ''"
|
||||
:leave-active-class="defaultStore.state.animation ? $style.transition_popup_leaveActive : ''"
|
||||
:enter-from-class="defaultStore.state.animation ? $style.transition_popup_enterFrom : ''"
|
||||
:leave-to-class="defaultStore.state.animation ? $style.transition_popup_leaveTo : ''"
|
||||
appear @after-leave="emit('closed')"
|
||||
>
|
||||
<div v-if="showing" :class="$style.root" class="_popup _shadow" :style="{ zIndex, top: top + 'px', left: left + 'px' }" @mouseover="() => { emit('mouseover'); }" @mouseleave="() => { emit('mouseleave'); }">
|
||||
<div v-if="user != null">
|
||||
<div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''">
|
||||
<span v-if="$i && $i.id != user.id && user.isFollowed" :class="$style.followed">{{ $ts.followsYou }}</span>
|
||||
<span v-if="$i && $i.id != user.id && user.isFollowed" :class="$style.followed">{{ i18n.ts.followsYou }}</span>
|
||||
</div>
|
||||
<svg viewBox="0 0 128 128" :class="$style.avatarBack">
|
||||
<g transform="matrix(1.6,0,0,1.6,-38.4,-51.2)">
|
||||
|
@ -27,15 +27,15 @@
|
|||
</div>
|
||||
<div :class="$style.status">
|
||||
<div :class="$style.statusItem">
|
||||
<div :class="$style.statusItemLabel">{{ $ts.notes }}</div>
|
||||
<div :class="$style.statusItemLabel">{{ i18n.ts.notes }}</div>
|
||||
<div>{{ number(user.notesCount) }}</div>
|
||||
</div>
|
||||
<div :class="$style.statusItem">
|
||||
<div :class="$style.statusItemLabel">{{ $ts.following }}</div>
|
||||
<div :class="$style.statusItemLabel">{{ i18n.ts.following }}</div>
|
||||
<div>{{ number(user.followingCount) }}</div>
|
||||
</div>
|
||||
<div :class="$style.statusItem">
|
||||
<div :class="$style.statusItemLabel">{{ $ts.followers }}</div>
|
||||
<div :class="$style.statusItemLabel">{{ i18n.ts.followers }}</div>
|
||||
<div>{{ number(user.followersCount) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -59,6 +59,8 @@ import * as os from '@/os';
|
|||
import { getUserMenu } from '@/scripts/get-user-menu';
|
||||
import number from '@/filters/number';
|
||||
import { i18n } from '@/i18n';
|
||||
import { defaultStore } from '@/store';
|
||||
import { $i } from '@/account';
|
||||
|
||||
const props = defineProps<{
|
||||
showing: boolean;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<Transition
|
||||
:enter-active-class="$store.state.animation ? $style.transition_window_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_window_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_window_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_window_leaveTo : ''"
|
||||
:enter-active-class="defaultStore.state.animation ? $style.transition_window_enterActive : ''"
|
||||
:leave-active-class="defaultStore.state.animation ? $style.transition_window_leaveActive : ''"
|
||||
:enter-from-class="defaultStore.state.animation ? $style.transition_window_enterFrom : ''"
|
||||
:leave-to-class="defaultStore.state.animation ? $style.transition_window_leaveTo : ''"
|
||||
appear
|
||||
@after-leave="$emit('closed')"
|
||||
>
|
||||
|
@ -47,6 +47,7 @@ import contains from '@/scripts/contains';
|
|||
import * as os from '@/os';
|
||||
import { MenuItem } from '@/types/menu';
|
||||
import { i18n } from '@/i18n';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const minHeight = 50;
|
||||
const minWidth = 250;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</template>
|
||||
|
||||
<div class="poamfof">
|
||||
<Transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="player.url && (player.url.startsWith('http://') || player.url.startsWith('https://'))" class="player">
|
||||
<iframe v-if="!fetching" :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen/>
|
||||
</div>
|
||||
|
@ -21,6 +21,7 @@
|
|||
<script lang="ts" setup>
|
||||
import MkWindow from '@/components/MkWindow.vue';
|
||||
import { versatileLang } from '@/scripts/intl-const';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = defineProps<{
|
||||
url: string;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<Transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="pending">
|
||||
<MkLoading/>
|
||||
</div>
|
||||
|
@ -8,8 +8,8 @@
|
|||
</div>
|
||||
<div v-else>
|
||||
<div class="wszdbhzo">
|
||||
<div><i class="ti ti-alert-triangle"></i> {{ $ts.somethingHappened }}</div>
|
||||
<MkButton inline class="retry" @click="retry"><i class="ti ti-reload"></i> {{ $ts.retry }}</MkButton>
|
||||
<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</div>
|
||||
<MkButton inline class="retry" @click="retry"><i class="ti ti-reload"></i> {{ i18n.ts.retry }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
@ -18,6 +18,8 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, watch } from 'vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { defaultStore } from '@/store';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -72,6 +74,8 @@ export default defineComponent({
|
|||
rejected,
|
||||
result,
|
||||
retry,
|
||||
defaultStore,
|
||||
i18n,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<span>
|
||||
<span>@{{ user.username }}</span>
|
||||
<span v-if="user.host || detail || $store.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
|
||||
<span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
|||
import * as misskey from 'misskey-js';
|
||||
import { toUnicode } from 'punycode/';
|
||||
import { host as hostRaw } from '@/config';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
defineProps<{
|
||||
user: misskey.entities.UserDetailed;
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
<div v-else :class="$style.menu">
|
||||
<div :class="$style.menuContainer">
|
||||
<div>Ads by {{ host }}</div>
|
||||
<!--<MkButton class="button" primary>{{ $ts._ad.like }}</MkButton>-->
|
||||
<MkButton v-if="chosen.ratio !== 0" :class="$style.menuButton" @click="reduceFrequency">{{ $ts._ad.reduceFrequencyOfThisAd }}</MkButton>
|
||||
<button class="_textButton" @click="toggleMenu">{{ $ts._ad.back }}</button>
|
||||
<!--<MkButton class="button" primary>{{ i18n.ts._ad.like }}</MkButton>-->
|
||||
<MkButton v-if="chosen.ratio !== 0" :class="$style.menuButton" @click="reduceFrequency">{{ i18n.ts._ad.reduceFrequencyOfThisAd }}</MkButton>
|
||||
<button class="_textButton" @click="toggleMenu">{{ i18n.ts._ad.back }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -26,6 +26,7 @@ import MkButton from '@/components/MkButton.vue';
|
|||
import { defaultStore } from '@/store';
|
||||
import * as os from '@/os';
|
||||
import { $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
type Ad = (typeof instance)['ads'][number];
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<Transition :name="$store.state.animation ? '_transition_zoom' : ''" appear>
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear>
|
||||
<div :class="$style.root">
|
||||
<img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
|
||||
<p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p>
|
||||
|
@ -11,6 +11,7 @@
|
|||
<script lang="ts" setup>
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'retry'): void;
|
||||
|
|
|
@ -16,6 +16,8 @@ import { apiUrl } from '@/config';
|
|||
import * as os from '@/os';
|
||||
import { PostBlock } from '@/scripts/hpml/block';
|
||||
import { Hpml } from '@/scripts/hpml/evaluator';
|
||||
import { defaultStore } from '@/store';
|
||||
import { $i } from '@/account';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -54,9 +56,9 @@ export default defineComponent({
|
|||
canvas.toBlob(blob => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', blob);
|
||||
formData.append('i', this.$i.token);
|
||||
if (this.$store.state.uploadFolder) {
|
||||
formData.append('folderId', this.$store.state.uploadFolder);
|
||||
formData.append('i', $i.token);
|
||||
if (defaultStore.state.uploadFolder) {
|
||||
formData.append('folderId', defaultStore.state.uploadFolder);
|
||||
}
|
||||
|
||||
window.fetch(apiUrl + '/drive/files/create', {
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { TextBlock } from '@/scripts/hpml/block';
|
||||
import { Hpml } from '@/scripts/hpml/evaluator';
|
||||
import { defineAsyncComponent, defineComponent, PropType } from 'vue';
|
||||
import * as mfm from 'mfm-js';
|
||||
import { TextBlock } from '@/scripts/hpml/block';
|
||||
import { Hpml } from '@/scripts/hpml/evaluator';
|
||||
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm';
|
||||
import { $i } from '@/account';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -29,6 +30,7 @@ export default defineComponent({
|
|||
data() {
|
||||
return {
|
||||
text: this.hpml.interpolate(this.block.text),
|
||||
$i,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -198,15 +198,6 @@ if (_DEV_) {
|
|||
app.config.performance = true;
|
||||
}
|
||||
|
||||
// TODO: 廃止
|
||||
app.config.globalProperties = {
|
||||
$i,
|
||||
$store: defaultStore,
|
||||
$instance: instance,
|
||||
$t: i18n.t,
|
||||
$ts: i18n.ts,
|
||||
};
|
||||
|
||||
widgets(app);
|
||||
directives(app);
|
||||
components(app);
|
||||
|
@ -356,7 +347,7 @@ const hotkeys = {
|
|||
},
|
||||
's': (): void => {
|
||||
mainRouter.push('/search');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if ($i) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<MkLoading v-if="!loaded"/>
|
||||
<Transition :name="$store.state.animation ? '_transition_zoom' : ''" appear>
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear>
|
||||
<div v-show="loaded" class="mjndxjch">
|
||||
<img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
|
||||
<p><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></p>
|
||||
|
@ -27,6 +27,7 @@ import { unisonReload } from '@/scripts/unison-reload';
|
|||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { miLocalStorage } from '@/local-storage';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
error?: Error;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<MkButton v-if="$i && ($i.isModerator || $i.policies.canManageCustomEmojis)" primary link to="/custom-emojis-manager">{{ i18n.ts.manageCustomEmojis }}</MkButton>
|
||||
|
||||
<div class="query">
|
||||
<MkInput v-model="q" class="" :placeholder="$ts.search">
|
||||
<MkInput v-model="q" class="" :placeholder="i18n.ts.search">
|
||||
<template #prefix><i class="ti ti-search"></i></template>
|
||||
</MkInput>
|
||||
|
||||
|
@ -15,14 +15,14 @@
|
|||
</div>
|
||||
|
||||
<MkFoldableSection v-if="searchEmojis" class="emojis">
|
||||
<template #header>{{ $ts.searchResult }}</template>
|
||||
<template #header>{{ i18n.ts.searchResult }}</template>
|
||||
<div class="zuvgdzyt">
|
||||
<XEmoji v-for="emoji in searchEmojis" :key="emoji.name" class="emoji" :emoji="emoji"/>
|
||||
</div>
|
||||
</MkFoldableSection>
|
||||
|
||||
<MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category" class="emojis">
|
||||
<template #header>{{ category || $ts.other }}</template>
|
||||
<template #header>{{ category || i18n.ts.other }}</template>
|
||||
<div class="zuvgdzyt">
|
||||
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji" :emoji="emoji"/>
|
||||
</div>
|
||||
|
@ -32,13 +32,14 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import XEmoji from './emojis.emoji.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
import { customEmojis, customEmojiCategories, getCustomEmojiTags } from '@/custom-emojis';
|
||||
import { i18n } from '@/i18n';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { $i } from '@/account';
|
||||
|
||||
const customEmojiTags = getCustomEmojiTags();
|
||||
let q = $ref('');
|
||||
|
|
|
@ -3,18 +3,18 @@
|
|||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer v-if="tab === 'overview'" :content-max="600" :margin-min="20">
|
||||
<div class="_gaps_m">
|
||||
<div class="fwhjspax" :style="{ backgroundImage: `url(${ $instance.bannerUrl })` }">
|
||||
<div class="fwhjspax" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
|
||||
<div class="content">
|
||||
<img :src="$instance.iconUrl ?? $instance.faviconUrl ?? '/favicon.ico'" alt="" class="icon"/>
|
||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" class="icon"/>
|
||||
<div class="name">
|
||||
<b>{{ $instance.name ?? host }}</b>
|
||||
<b>{{ instance.name ?? host }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.description }}</template>
|
||||
<template #value><div v-html="$instance.description"></div></template>
|
||||
<template #value><div v-html="instance.description"></div></template>
|
||||
</MkKeyValue>
|
||||
|
||||
<FormSection>
|
||||
|
@ -23,7 +23,7 @@
|
|||
<template #key>Misskey</template>
|
||||
<template #value>{{ version }}</template>
|
||||
</MkKeyValue>
|
||||
<div v-html="i18n.t('poweredByMisskeyDescription', { name: $instance.name ?? host })">
|
||||
<div v-html="i18n.t('poweredByMisskeyDescription', { name: instance.name ?? host })">
|
||||
</div>
|
||||
<FormLink to="/about-misskey">{{ i18n.ts.aboutMisskey }}</FormLink>
|
||||
</div>
|
||||
|
@ -34,14 +34,14 @@
|
|||
<FormSplit>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.administrator }}</template>
|
||||
<template #value>{{ $instance.maintainerName }}</template>
|
||||
<template #value>{{ instance.maintainerName }}</template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue>
|
||||
<template #key>{{ i18n.ts.contact }}</template>
|
||||
<template #value>{{ $instance.maintainerEmail }}</template>
|
||||
<template #value>{{ instance.maintainerEmail }}</template>
|
||||
</MkKeyValue>
|
||||
</FormSplit>
|
||||
<FormLink v-if="$instance.tosUrl" :to="$instance.tosUrl" external>{{ i18n.ts.tos }}</FormLink>
|
||||
<FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>{{ i18n.ts.tos }}</FormLink>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
|
@ -101,6 +101,7 @@ import number from '@/filters/number';
|
|||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { claimAchievement } from '@/scripts/achievements';
|
||||
import { instance } from '@/instance';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
initialTab?: string;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<MkSpacer :content-max="700" :margin-min="16">
|
||||
<div class="lxpfedzu">
|
||||
<div class="banner">
|
||||
<img :src="$instance.iconUrl || '/favicon.ico'" alt="" class="icon"/>
|
||||
<img :src="instance.iconUrl || '/favicon.ico'" alt="" class="icon"/>
|
||||
</div>
|
||||
|
||||
<MkInfo v-if="thereIsUnresolvedAbuseReport" warn class="info">{{ i18n.ts.thereIsUnresolvedAbuseReportWarning }} <MkA to="/admin/abuses" class="_link">{{ i18n.ts.check }}</MkA></MkInfo>
|
||||
|
@ -221,7 +221,7 @@ onUnmounted(() => {
|
|||
});
|
||||
|
||||
watch(router.currentRef, (to) => {
|
||||
if (to.route.path === "/admin" && to.child?.route.name == null && !narrow) {
|
||||
if (to.route.path === '/admin' && to.child?.route.name == null && !narrow) {
|
||||
router.replace('/admin/overview');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<MkSwitch v-model="useObjectStorage">{{ i18n.ts.useObjectStorage }}</MkSwitch>
|
||||
|
||||
<template v-if="useObjectStorage">
|
||||
<MkInput v-model="objectStorageBaseUrl">
|
||||
<MkInput v-model="objectStorageBaseUrl" :placeholder="'https://example.com'">
|
||||
<template #label>{{ i18n.ts.objectStorageBaseUrl }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template>
|
||||
</MkInput>
|
||||
|
@ -22,8 +22,9 @@
|
|||
<template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="objectStorageEndpoint">
|
||||
<MkInput v-model="objectStorageEndpoint" :placeholder="'example.com'">
|
||||
<template #label>{{ i18n.ts.objectStorageEndpoint }}</template>
|
||||
<template #prefix>https://</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template>
|
||||
</MkInput>
|
||||
|
||||
|
@ -60,6 +61,7 @@
|
|||
|
||||
<MkSwitch v-model="objectStorageS3ForcePathStyle">
|
||||
<template #label>s3ForcePathStyle</template>
|
||||
<template #caption>{{ i18n.ts.s3ForcePathStyleDesc }}</template>
|
||||
</MkSwitch>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="wbrkwale">
|
||||
<Transition :name="$store.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<MkLoading v-if="fetching"/>
|
||||
<div v-else class="instances">
|
||||
<MkA v-for="(instance, i) in instances" :key="instance.id" v-tooltip.mfm.noDelay="`${instance.name}\n${instance.host}\n${instance.softwareName} ${instance.softwareVersion}`" :to="`/instance-info/${instance.host}`" class="instance">
|
||||
|
@ -16,6 +16,7 @@ import { ref } from 'vue';
|
|||
import * as os from '@/os';
|
||||
import { useInterval } from '@/scripts/use-interval';
|
||||
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const instances = ref([]);
|
||||
const fetching = ref(true);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<Transition :name="$store.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<MkLoading v-if="fetching"/>
|
||||
<div v-else :class="$style.root" class="_panel">
|
||||
<MkA v-for="user in moderators" :key="user.id" class="user" :to="`/user-info/${user.id}`">
|
||||
|
@ -14,6 +14,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue';
|
||||
import * as os from '@/os';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
let moderators: any = $ref(null);
|
||||
let fetching = $ref(true);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<Transition :name="$store.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<MkLoading v-if="fetching"/>
|
||||
<div v-else :class="$style.root">
|
||||
<div class="item _panel users">
|
||||
|
@ -62,6 +62,7 @@ import MkNumberDiff from '@/components/MkNumberDiff.vue';
|
|||
import MkNumber from '@/components/MkNumber.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { customEmojis } from '@/custom-emojis';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
let stats: any = $ref(null);
|
||||
let usersComparedToThePrevDay = $ref<number>();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div :class="$style.root">
|
||||
<Transition :name="$store.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
|
||||
<MkLoading v-if="fetching"/>
|
||||
<div v-else class="users">
|
||||
<MkA v-for="(user, i) in newUsers" :key="user.id" :to="`/user-info/${user.id}`" class="user">
|
||||
|
@ -15,6 +15,7 @@
|
|||
import * as os from '@/os';
|
||||
import { useInterval } from '@/scripts/use-interval';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
let newUsers = $ref(null);
|
||||
let fetching = $ref(true);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<i v-if="relay.status === 'accepted'" class="ti ti-check icon accepted"></i>
|
||||
<i v-else-if="relay.status === 'rejected'" class="ti ti-ban icon rejected"></i>
|
||||
<i v-else class="ti ti-clock icon requesting"></i>
|
||||
<span>{{ $t(`_relayStatus.${relay.status}`) }}</span>
|
||||
<span>{{ i18n.t(`_relayStatus.${relay.status}`) }}</span>
|
||||
</div>
|
||||
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<img v-if="announcement.imageUrl" :src="announcement.imageUrl"/>
|
||||
</div>
|
||||
<div v-if="$i && !announcement.isRead" class="footer">
|
||||
<MkButton primary @click="read(items, announcement, i)"><i class="ti ti-check"></i> {{ $ts.gotIt }}</MkButton>
|
||||
<MkButton primary @click="read(items, announcement, i)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton>
|
||||
</div>
|
||||
</section>
|
||||
</MkPagination>
|
||||
|
@ -25,6 +25,7 @@ import MkButton from '@/components/MkButton.vue';
|
|||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { $i } from '@/account';
|
||||
|
||||
const pagination = {
|
||||
endpoint: 'announcements' as const,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<div ref="rootEl" v-hotkey.global="keymap" class="tqmomfks">
|
||||
<div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div>
|
||||
<div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
<div class="tl">
|
||||
<MkTimeline
|
||||
ref="tlEl" :key="antennaId"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<section>
|
||||
<div v-if="app.permission.length > 0">
|
||||
<p>{{ $t('_auth.permission', { name }) }}</p>
|
||||
<p>{{ i18n.t('_auth.permission', { name }) }}</p>
|
||||
<ul>
|
||||
<li v-for="p in app.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li>
|
||||
<li v-for="p in app.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>{{ i18n.t('_auth.shareAccess', { name: `${name} (${app.id})` }) }}</div>
|
||||
|
@ -16,10 +16,10 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import { AuthSession } from 'misskey-js/built/entities';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { AuthSession } from 'misskey-js/built/entities';
|
||||
|
||||
const props = defineProps<{
|
||||
session: AuthSession;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<h1>{{ i18n.ts._auth.denied }}</h1>
|
||||
</div>
|
||||
<div v-if="state == 'accepted' && session">
|
||||
<h1>{{ session.app.isAuthorized ? $t('already-authorized') : i18n.ts.allowed }}</h1>
|
||||
<h1>{{ session.app.isAuthorized ? i18n.t('already-authorized') : i18n.ts.allowed }}</h1>
|
||||
<p v-if="session.app.callbackUrl">
|
||||
{{ i18n.ts._auth.callback }}
|
||||
<MkEllipsis/>
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="700">
|
||||
<Transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="flash" :key="flash.id">
|
||||
<Transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in">
|
||||
<Transition :name="defaultStore.state.animation ? 'zoom' : ''" mode="out-in">
|
||||
<div v-if="started" :class="$style.started">
|
||||
<div class="main _panel">
|
||||
<MkAsUi v-if="root" :component="root" :components="components"/>
|
||||
|
@ -63,6 +63,8 @@ import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui'
|
|||
import { createAiScriptEnv } from '@/scripts/aiscript/api';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkCode from '@/components/MkCode.vue';
|
||||
import { defaultStore } from '@/store';
|
||||
import { $i } from '@/account';
|
||||
|
||||
const props = defineProps<{
|
||||
id: string;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="1000" :margin-min="16" :margin-max="32">
|
||||
<div class="_root">
|
||||
<Transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="post" class="rkxwuolj">
|
||||
<div class="files">
|
||||
<div v-for="file in post.files" :key="file.id" class="file">
|
||||
|
@ -67,6 +67,8 @@ import { url } from '@/config';
|
|||
import { useRouter } from '@/router';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { defaultStore } from '@/store';
|
||||
import { $i } from '@/account';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
|
|
@ -15,13 +15,13 @@
|
|||
</div>
|
||||
<div v-else>
|
||||
<div v-if="_permissions.length > 0">
|
||||
<p v-if="name">{{ $t('_auth.permission', { name }) }}</p>
|
||||
<p v-if="name">{{ i18n.t('_auth.permission', { name }) }}</p>
|
||||
<p v-else>{{ i18n.ts._auth.permissionAsk }}</p>
|
||||
<ul>
|
||||
<li v-for="p in _permissions" :key="p">{{ $t(`_permissions.${p}`) }}</li>
|
||||
<li v-for="p in _permissions" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="name">{{ $t('_auth.shareAccess', { name }) }}</div>
|
||||
<div v-if="name">{{ i18n.t('_auth.shareAccess', { name }) }}</div>
|
||||
<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
|
||||
<div :class="$style.buttons">
|
||||
<MkButton inline @click="deny">{{ i18n.ts.cancel }}</MkButton>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="800">
|
||||
<div class="fcuexfpr">
|
||||
<Transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="note" class="note">
|
||||
<div v-if="showNext" class="_margin">
|
||||
<MkNotes class="" :pagination="nextPagination" :no-gap="true"/>
|
||||
|
@ -50,6 +50,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
|
|||
import { i18n } from '@/i18n';
|
||||
import { dateString } from '@/filters/date';
|
||||
import MkClipPreview from '@/components/MkClipPreview.vue';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = defineProps<{
|
||||
noteId: string;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
||||
<template #header><i class="ti ti-photo"></i> {{ $ts._pages.blocks.image }}</template>
|
||||
<template #header><i class="ti ti-photo"></i> {{ i18n.ts._pages.blocks.image }}</template>
|
||||
<template #func>
|
||||
<button @click="choose()">
|
||||
<i class="ti ti-folder"></i>
|
||||
|
@ -20,6 +20,7 @@ import { onMounted } from 'vue';
|
|||
import XContainer from '../page-editor.container.vue';
|
||||
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: any
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
||||
<template #header><i class="ti ti-note"></i> {{ $ts._pages.blocks.note }}</template>
|
||||
<template #header><i class="ti ti-note"></i> {{ i18n.ts._pages.blocks.note }}</template>
|
||||
|
||||
<section style="padding: 0 16px 0 16px;">
|
||||
<MkInput v-model="id">
|
||||
<template #label>{{ $ts._pages.blocks._note.id }}</template>
|
||||
<template #caption>{{ $ts._pages.blocks._note.idDescription }}</template>
|
||||
<template #label>{{ i18n.ts._pages.blocks._note.id }}</template>
|
||||
<template #caption>{{ i18n.ts._pages.blocks._note.idDescription }}</template>
|
||||
</MkInput>
|
||||
<MkSwitch v-model="props.modelValue.detailed"><span>{{ $ts._pages.blocks._note.detailed }}</span></MkSwitch>
|
||||
<MkSwitch v-model="props.modelValue.detailed"><span>{{ i18n.ts._pages.blocks._note.detailed }}</span></MkSwitch>
|
||||
|
||||
<MkNote v-if="note && !props.modelValue.detailed" :key="note.id + ':normal'" v-model:note="note" style="margin-bottom: 16px;"/>
|
||||
<MkNoteDetailed v-if="note && props.modelValue.detailed" :key="note.id + ':detail'" v-model:note="note" style="margin-bottom: 16px;"/>
|
||||
|
@ -25,6 +25,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
|||
import MkNote from '@/components/MkNote.vue';
|
||||
import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: any
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<XContainer :draggable="true" @remove="() => $emit('remove')">
|
||||
<template #header><i class="ti ti-align-left"></i> {{ $ts._pages.blocks.text }}</template>
|
||||
<template #header><i class="ti ti-align-left"></i> {{ i18n.ts._pages.blocks.text }}</template>
|
||||
|
||||
<section class="vckmsadr">
|
||||
<textarea v-model="text"></textarea>
|
||||
|
@ -13,6 +13,7 @@
|
|||
/* eslint-disable vue/no-mutating-props */
|
||||
import { watch } from 'vue';
|
||||
import XContainer from '../page-editor.container.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: any
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<p v-show="showBody" v-if="error != null" class="error">{{ $t('_pages.script.typeError', { slot: error.arg + 1, expect: $t(`script.types.${error.expect}`), actual: $t(`script.types.${error.actual}`) }) }}</p>
|
||||
<p v-show="showBody" v-if="warn != null" class="warn">{{ $t('_pages.script.thereIsEmptySlot', { slot: warn.slot + 1 }) }}</p>
|
||||
<p v-show="showBody" v-if="error != null" class="error">{{ i18n.t('_pages.script.typeError', { slot: error.arg + 1, expect: i18n.t(`script.types.${error.expect}`), actual: i18n.t(`script.types.${error.actual}`) }) }}</p>
|
||||
<p v-show="showBody" v-if="warn != null" class="warn">{{ i18n.t('_pages.script.thereIsEmptySlot', { slot: warn.slot + 1 }) }}</p>
|
||||
<div v-show="showBody" class="body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
@ -26,6 +26,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
@ -54,6 +55,7 @@ export default defineComponent({
|
|||
data() {
|
||||
return {
|
||||
showBody: this.expanded,
|
||||
i18n,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -3,42 +3,42 @@
|
|||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="700">
|
||||
<div class="jqqmcavi">
|
||||
<MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="ti ti-external-link"></i> {{ $ts._pages.viewPage }}</MkButton>
|
||||
<MkButton v-if="!readonly" inline primary class="button" @click="save"><i class="ti ti-device-floppy"></i> {{ $ts.save }}</MkButton>
|
||||
<MkButton v-if="pageId" inline class="button" @click="duplicate"><i class="ti ti-copy"></i> {{ $ts.duplicate }}</MkButton>
|
||||
<MkButton v-if="pageId && !readonly" inline class="button" danger @click="del"><i class="ti ti-trash"></i> {{ $ts.delete }}</MkButton>
|
||||
<MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="ti ti-external-link"></i> {{ i18n.ts._pages.viewPage }}</MkButton>
|
||||
<MkButton v-if="!readonly" inline primary class="button" @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||
<MkButton v-if="pageId" inline class="button" @click="duplicate"><i class="ti ti-copy"></i> {{ i18n.ts.duplicate }}</MkButton>
|
||||
<MkButton v-if="pageId && !readonly" inline class="button" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
|
||||
<div v-if="tab === 'settings'">
|
||||
<div class="_gaps_m">
|
||||
<MkInput v-model="title">
|
||||
<template #label>{{ $ts._pages.title }}</template>
|
||||
<template #label>{{ i18n.ts._pages.title }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="summary">
|
||||
<template #label>{{ $ts._pages.summary }}</template>
|
||||
<template #label>{{ i18n.ts._pages.summary }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="name">
|
||||
<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
|
||||
<template #label>{{ $ts._pages.url }}</template>
|
||||
<template #label>{{ i18n.ts._pages.url }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkSwitch v-model="alignCenter">{{ $ts._pages.alignCenter }}</MkSwitch>
|
||||
<MkSwitch v-model="alignCenter">{{ i18n.ts._pages.alignCenter }}</MkSwitch>
|
||||
|
||||
<MkSelect v-model="font">
|
||||
<template #label>{{ $ts._pages.font }}</template>
|
||||
<option value="serif">{{ $ts._pages.fontSerif }}</option>
|
||||
<option value="sans-serif">{{ $ts._pages.fontSansSerif }}</option>
|
||||
<template #label>{{ i18n.ts._pages.font }}</template>
|
||||
<option value="serif">{{ i18n.ts._pages.fontSerif }}</option>
|
||||
<option value="sans-serif">{{ i18n.ts._pages.fontSansSerif }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkSwitch v-model="hideTitleWhenPinned">{{ $ts._pages.hideTitleWhenPinned }}</MkSwitch>
|
||||
<MkSwitch v-model="hideTitleWhenPinned">{{ i18n.ts._pages.hideTitleWhenPinned }}</MkSwitch>
|
||||
|
||||
<div class="eyeCatch">
|
||||
<MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><i class="ti ti-plus"></i> {{ $ts._pages.eyeCatchingImageSet }}</MkButton>
|
||||
<MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><i class="ti ti-plus"></i> {{ i18n.ts._pages.eyeCatchingImageSet }}</MkButton>
|
||||
<div v-else-if="eyeCatchingImage">
|
||||
<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/>
|
||||
<MkButton v-if="!readonly" @click="removeEyeCatchingImage()"><i class="ti ti-trash"></i> {{ $ts._pages.eyeCatchingImageRemove }}</MkButton>
|
||||
<MkButton v-if="!readonly" @click="removeEyeCatchingImage()"><i class="ti ti-trash"></i> {{ i18n.ts._pages.eyeCatchingImageRemove }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="700">
|
||||
<Transition :name="$store.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="page" :key="page.id" class="xcukqgmh">
|
||||
<div class="main">
|
||||
<!--
|
||||
|
@ -75,8 +75,9 @@ import MkPagination from '@/components/MkPagination.vue';
|
|||
import MkPagePreview from '@/components/MkPagePreview.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { pageViewInterruptors } from '@/store';
|
||||
import { pageViewInterruptors, defaultStore } from '@/store';
|
||||
import { deepClone } from '@/scripts/clone';
|
||||
import { $i } from '@/account';
|
||||
|
||||
const props = defineProps<{
|
||||
pageName: string;
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<details>
|
||||
<summary>{{ i18n.ts.details }}</summary>
|
||||
<ul>
|
||||
<li v-for="p in token.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li>
|
||||
<li v-for="p in token.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
<div class="actions">
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
import FormInfo from '@/components/MkInfo.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os';
|
||||
import { signout } from '@/account';
|
||||
import { signout, $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<template #label>{{ i18n.ts.sounds }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder v-for="type in Object.keys(sounds)" :key="type">
|
||||
<template #label>{{ $t('_sfx.' + type) }}</template>
|
||||
<template #label>{{ i18n.t('_sfx.' + type) }}</template>
|
||||
<template #suffix>{{ sounds[type].type ?? i18n.ts.none }}</template>
|
||||
|
||||
<XSound :type="sounds[type].type" :volume="sounds[type].volume" @update="(res) => updated(type, res)"/>
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :display-my-avatar="true"/></template>
|
||||
<MkSpacer :content-max="800">
|
||||
<div ref="rootEl" v-hotkey.global="keymap">
|
||||
<XTutorial v-if="$i && $store.reactiveState.tutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/>
|
||||
<MkPostForm v-if="$store.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
|
||||
<XTutorial v-if="$i && defaultStore.reactiveState.tutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/>
|
||||
<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
|
||||
|
||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
<div :class="$style.tl">
|
||||
|
|
|
@ -192,7 +192,7 @@ import { url } from '@/config';
|
|||
import { userPage, acct } from '@/filters/user';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { i18n } from '@/i18n';
|
||||
import { iAmAdmin, iAmModerator } from '@/account';
|
||||
import { iAmAdmin, iAmModerator, $i } from '@/account';
|
||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
</dl>
|
||||
<dl v-if="user.birthday" class="field">
|
||||
<dt class="name"><i class="ti ti-cake ti-fw"></i> {{ i18n.ts.birthday }}</dt>
|
||||
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
|
||||
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ i18n.t('yearsOld', { age }) }})</dd>
|
||||
</dl>
|
||||
<dl class="field">
|
||||
<dt class="name"><i class="ti ti-calendar ti-fw"></i> {{ i18n.ts.registeredDate }}</dt>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<MkContainer>
|
||||
<template #icon><i class="ti ti-chart-line"></i></template>
|
||||
<template #header>{{ $ts.activity }}</template>
|
||||
<template #header>{{ i18n.ts.activity }}</template>
|
||||
<template #func="{ buttonStyleClass }">
|
||||
<button class="_button" :class="buttonStyleClass" @click="showMenu">
|
||||
<i class="ti ti-dots"></i>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<MkContainer :max-height="300" :foldable="true">
|
||||
<template #icon><i class="ti ti-photo"></i></template>
|
||||
<template #header>{{ $ts.images }}</template>
|
||||
<template #header>{{ i18n.ts.images }}</template>
|
||||
<div :class="$style.root">
|
||||
<MkLoading v-if="fetching"/>
|
||||
<div v-if="!fetching && images.length > 0" :class="$style.stream">
|
||||
|
@ -14,7 +14,7 @@
|
|||
<ImgWithBlurhash :hash="image.file.blurhash" :src="thumbnail(image.file)" :title="image.file.name"/>
|
||||
</MkA>
|
||||
</div>
|
||||
<p v-if="!fetching && images.length == 0" :class="$style.empty">{{ $ts.nothing }}</p>
|
||||
<p v-if="!fetching && images.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
|
||||
</div>
|
||||
</MkContainer>
|
||||
</template>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
</div>
|
||||
<div class="contents">
|
||||
<div class="main">
|
||||
<img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
|
||||
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
|
||||
<button class="_button _acrylic menu" @click="showMenu"><i class="ti ti-dots"></i></button>
|
||||
<div class="fg">
|
||||
<h1>
|
||||
|
|
|
@ -10,22 +10,22 @@
|
|||
</h1>
|
||||
<div class="about">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="desc" v-html="meta.description || $ts.headlineMisskey"></div>
|
||||
<div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div>
|
||||
</div>
|
||||
<div class="action">
|
||||
<MkButton class="signup" inline gradate @click="signup()">{{ $ts.signup }}</MkButton>
|
||||
<MkButton class="signin" inline @click="signin()">{{ $ts.login }}</MkButton>
|
||||
<MkButton class="signup" inline gradate @click="signup()">{{ i18n.ts.signup }}</MkButton>
|
||||
<MkButton class="signin" inline @click="signin()">{{ i18n.ts.login }}</MkButton>
|
||||
</div>
|
||||
<div v-if="onlineUsersCount && stats" class="status">
|
||||
<div>
|
||||
<I18n :src="$ts.nUsers" text-tag="span" class="users">
|
||||
<I18n :src="i18n.ts.nUsers" text-tag="span" class="users">
|
||||
<template #n><b>{{ number(stats.originalUsersCount) }}</b></template>
|
||||
</I18n>
|
||||
<I18n :src="$ts.nNotes" text-tag="span" class="notes">
|
||||
<I18n :src="i18n.ts.nNotes" text-tag="span" class="notes">
|
||||
<template #n><b>{{ number(stats.originalNotesCount) }}</b></template>
|
||||
</I18n>
|
||||
</div>
|
||||
<I18n :src="$ts.onlineUsersCount" text-tag="span" class="online">
|
||||
<I18n :src="i18n.ts.onlineUsersCount" text-tag="span" class="online">
|
||||
<template #n><b>{{ onlineUsersCount }}</b></template>
|
||||
</I18n>
|
||||
</div>
|
||||
|
@ -47,6 +47,7 @@ import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
|
|||
import { host, instanceName } from '@/config';
|
||||
import * as os from '@/os';
|
||||
import number from '@/filters/number';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -64,6 +65,7 @@ export default defineComponent({
|
|||
stats: null,
|
||||
tags: [],
|
||||
onlineUsersCount: null,
|
||||
i18n,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -103,19 +105,19 @@ export default defineComponent({
|
|||
|
||||
showMenu(ev) {
|
||||
os.popupMenu([{
|
||||
text: this.$t('aboutX', { x: instanceName }),
|
||||
text: i18n.t('aboutX', { x: instanceName }),
|
||||
icon: 'ti ti-info-circle',
|
||||
action: () => {
|
||||
os.pageWindow('/about');
|
||||
},
|
||||
}, {
|
||||
text: this.$ts.aboutMisskey,
|
||||
text: i18n.ts.aboutMisskey,
|
||||
icon: 'ti ti-info-circle',
|
||||
action: () => {
|
||||
os.pageWindow('/about-misskey');
|
||||
},
|
||||
}, null, {
|
||||
text: this.$ts.help,
|
||||
text: i18n.ts.help,
|
||||
icon: 'ti ti-question-circle',
|
||||
action: () => {
|
||||
window.open('https://misskey-hub.net/help.md', '_blank');
|
||||
|
|
|
@ -22,22 +22,22 @@
|
|||
</h1>
|
||||
<div class="about">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="desc" v-html="meta.description || $ts.headlineMisskey"></div>
|
||||
<div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div>
|
||||
</div>
|
||||
<div class="action">
|
||||
<MkButton inline gradate @click="signup()">{{ $ts.signup }}</MkButton>
|
||||
<MkButton inline @click="signin()">{{ $ts.login }}</MkButton>
|
||||
<MkButton inline gradate @click="signup()">{{ i18n.ts.signup }}</MkButton>
|
||||
<MkButton inline @click="signin()">{{ i18n.ts.login }}</MkButton>
|
||||
</div>
|
||||
<div v-if="onlineUsersCount && stats" class="status">
|
||||
<div>
|
||||
<I18n :src="$ts.nUsers" text-tag="span" class="users">
|
||||
<I18n :src="i18n.ts.nUsers" text-tag="span" class="users">
|
||||
<template #n><b>{{ number(stats.originalUsersCount) }}</b></template>
|
||||
</I18n>
|
||||
<I18n :src="$ts.nNotes" text-tag="span" class="notes">
|
||||
<I18n :src="i18n.ts.nNotes" text-tag="span" class="notes">
|
||||
<template #n><b>{{ number(stats.originalNotesCount) }}</b></template>
|
||||
</I18n>
|
||||
</div>
|
||||
<I18n :src="$ts.onlineUsersCount" text-tag="span" class="online">
|
||||
<I18n :src="i18n.ts.onlineUsersCount" text-tag="span" class="online">
|
||||
<template #n><b>{{ onlineUsersCount }}</b></template>
|
||||
</I18n>
|
||||
</div>
|
||||
|
@ -45,10 +45,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<nav class="nav">
|
||||
<MkA to="/announcements">{{ $ts.announcements }}</MkA>
|
||||
<MkA to="/explore">{{ $ts.explore }}</MkA>
|
||||
<MkA to="/channels">{{ $ts.channel }}</MkA>
|
||||
<MkA to="/featured">{{ $ts.featured }}</MkA>
|
||||
<MkA to="/announcements">{{ i18n.ts.announcements }}</MkA>
|
||||
<MkA to="/explore">{{ i18n.ts.explore }}</MkA>
|
||||
<MkA to="/channels">{{ i18n.ts.channel }}</MkA>
|
||||
<MkA to="/featured">{{ i18n.ts.featured }}</MkA>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -58,15 +58,16 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { toUnicode } from 'punycode/';
|
||||
import XTimeline from './welcome.timeline.vue';
|
||||
import XSigninDialog from '@/components/MkSigninDialog.vue';
|
||||
import XSignupDialog from '@/components/MkSignupDialog.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkNote from '@/components/MkNote.vue';
|
||||
import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
|
||||
import XTimeline from './welcome.timeline.vue';
|
||||
import { host, instanceName } from '@/config';
|
||||
import * as os from '@/os';
|
||||
import number from '@/filters/number';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -84,6 +85,7 @@ export default defineComponent({
|
|||
stats: null,
|
||||
tags: [],
|
||||
onlineUsersCount: null,
|
||||
i18n,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -123,22 +125,22 @@ export default defineComponent({
|
|||
|
||||
showMenu(ev) {
|
||||
os.popupMenu([{
|
||||
text: this.$t('aboutX', { x: instanceName }),
|
||||
text: i18n.t('aboutX', { x: instanceName }),
|
||||
icon: 'ti ti-info-circle',
|
||||
action: () => {
|
||||
os.pageWindow('/about');
|
||||
},
|
||||
}, {
|
||||
text: this.$ts.aboutMisskey,
|
||||
text: i18n.ts.aboutMisskey,
|
||||
icon: 'ti ti-info-circle',
|
||||
action: () => {
|
||||
os.pageWindow('/about-misskey');
|
||||
},
|
||||
}, null, {
|
||||
text: this.$ts.help,
|
||||
text: i18n.ts.help,
|
||||
icon: 'ti ti-question-circle',
|
||||
action: () => {
|
||||
window.open(`https://misskey-hub.net/help.md`, '_blank');
|
||||
window.open('https://misskey-hub.net/help.md', '_blank');
|
||||
},
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
},
|
||||
|
|
|
@ -2,19 +2,19 @@
|
|||
<form class="mk-setup" @submit.prevent="submit()">
|
||||
<h1>Welcome to Misskey!</h1>
|
||||
<div class="_gaps_m">
|
||||
<p>{{ $ts.intro }}</p>
|
||||
<p>{{ i18n.ts.intro }}</p>
|
||||
<MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username>
|
||||
<template #label>{{ $ts.username }}</template>
|
||||
<template #label>{{ i18n.ts.username }}</template>
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="password" type="password" data-cy-admin-password>
|
||||
<template #label>{{ $ts.password }}</template>
|
||||
<template #label>{{ i18n.ts.password }}</template>
|
||||
<template #prefix><i class="ti ti-lock"></i></template>
|
||||
</MkInput>
|
||||
<div class="bottom">
|
||||
<MkButton gradate type="submit" :disabled="submitting" data-cy-admin-ok>
|
||||
{{ submitting ? $ts.processing : $ts.done }}<MkEllipsis v-if="submitting"/>
|
||||
{{ submitting ? i18n.ts.processing : i18n.ts.done }}<MkEllipsis v-if="submitting"/>
|
||||
</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<MkMediaList :media-list="note.files"/>
|
||||
</div>
|
||||
<div v-if="note.poll">
|
||||
<MkPoll :note="note" :readOnly="true" />
|
||||
<MkPoll :note="note" :read-only="true"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkReactionsViewer ref="reactionsViewer" :note="note"/>
|
||||
|
@ -22,13 +22,14 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Note } from 'misskey-js/built/entities';
|
||||
import { onUpdated } from 'vue';
|
||||
import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
|
||||
import MkMediaList from '@/components/MkMediaList.vue';
|
||||
import MkPoll from '@/components/MkPoll.vue';
|
||||
import * as os from '@/os';
|
||||
import { Note } from 'misskey-js/built/entities';
|
||||
import { onUpdated } from 'vue';
|
||||
import { getScrollContainer } from '@/scripts/scroll';
|
||||
import { $i } from '@/account';
|
||||
|
||||
let notes = $ref<Note[]>([]);
|
||||
let isScrolling = $ref(false);
|
||||
|
|
|
@ -11,11 +11,11 @@
|
|||
|
||||
<TransitionGroup
|
||||
tag="div" :class="$style.notifications"
|
||||
:move-class="$store.state.animation ? $style.transition_notification_move : ''"
|
||||
:enter-active-class="$store.state.animation ? $style.transition_notification_enterActive : ''"
|
||||
:leave-active-class="$store.state.animation ? $style.transition_notification_leaveActive : ''"
|
||||
:enter-from-class="$store.state.animation ? $style.transition_notification_enterFrom : ''"
|
||||
:leave-to-class="$store.state.animation ? $style.transition_notification_leaveTo : ''"
|
||||
:move-class="defaultStore.state.animation ? $style.transition_notification_move : ''"
|
||||
:enter-active-class="defaultStore.state.animation ? $style.transition_notification_enterActive : ''"
|
||||
:leave-active-class="defaultStore.state.animation ? $style.transition_notification_leaveActive : ''"
|
||||
:enter-from-class="defaultStore.state.animation ? $style.transition_notification_enterFrom : ''"
|
||||
:leave-to-class="defaultStore.state.animation ? $style.transition_notification_leaveTo : ''"
|
||||
>
|
||||
<XNotification v-for="notification in notifications" :key="notification.id" :notification="notification" :class="$style.notification"/>
|
||||
</TransitionGroup>
|
||||
|
@ -40,6 +40,7 @@ import * as sound from '@/scripts/sound';
|
|||
import { $i } from '@/account';
|
||||
import { stream } from '@/stream';
|
||||
import { i18n } from '@/i18n';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
|
||||
const XUpload = defineAsyncComponent(() => import('./upload.vue'));
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
<div class="kmwsukvl">
|
||||
<div class="body">
|
||||
<div class="top">
|
||||
<div class="banner" :style="{ backgroundImage: `url(${ $instance.bannerUrl })` }"></div>
|
||||
<div class="banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
|
||||
<button v-click-anime class="item _button instance" @click="openInstanceMenu">
|
||||
<img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
|
||||
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="middle">
|
||||
|
@ -47,9 +47,10 @@ import { computed, defineAsyncComponent, toRef } from 'vue';
|
|||
import { openInstanceMenu } from './common';
|
||||
import * as os from '@/os';
|
||||
import { navbarItemDef } from '@/navbar';
|
||||
import { openAccountMenu as openAccountMenu_ } from '@/account';
|
||||
import { $i, openAccountMenu as openAccountMenu_ } from '@/account';
|
||||
import { defaultStore } from '@/store';
|
||||
import { i18n } from '@/i18n';
|
||||
import { instance } from '@/instance';
|
||||
|
||||
const menu = toRef(defaultStore.state, 'menu');
|
||||
const otherMenuItemIndicated = computed(() => {
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
<div class="mvcprjjd" :class="{ iconOnly }">
|
||||
<div class="body">
|
||||
<div class="top">
|
||||
<div class="banner" :style="{ backgroundImage: `url(${ $instance.bannerUrl })` }"></div>
|
||||
<button v-click-anime v-tooltip.noDelay.right="$instance.name ?? i18n.ts.instance" class="item _button instance" @click="openInstanceMenu">
|
||||
<img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
|
||||
<div class="banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
|
||||
<button v-click-anime v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="item _button instance" @click="openInstanceMenu">
|
||||
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="middle">
|
||||
|
@ -60,6 +60,7 @@ import { navbarItemDef } from '@/navbar';
|
|||
import { $i, openAccountMenu as openAccountMenu_ } from '@/account';
|
||||
import { defaultStore } from '@/store';
|
||||
import { i18n } from '@/i18n';
|
||||
import { instance } from '@/instance';
|
||||
|
||||
const iconOnly = ref(false);
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue