Merge branch 'develop' into qr
This commit is contained in:
commit
906fe76bb6
|
@ -54,6 +54,9 @@
|
||||||
- Fix: 複数のメンションを1行に記述した場合に、サジェストが正しく表示されない問題を修正
|
- Fix: 複数のメンションを1行に記述した場合に、サジェストが正しく表示されない問題を修正
|
||||||
- Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正
|
- Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正
|
||||||
- Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正
|
- Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正
|
||||||
|
- Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正
|
||||||
|
- Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正
|
||||||
|
- Fix: 管理中アカウント一覧で正しい表示が行われない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Feat: サーバー管理コマンド
|
- Feat: サーバー管理コマンド
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { abuseUserReport } from '../packages/frontend/.storybook/fakes.js';
|
import { abuseUserReport } from '../packages/frontend/.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../packages/frontend/.storybook/mocks.js';
|
import { commonHandlers } from '../packages/frontend/.storybook/mocks.js';
|
||||||
|
|
|
@ -12039,6 +12039,10 @@ export interface Locale extends ILocale {
|
||||||
* 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。
|
* 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。
|
||||||
*/
|
*/
|
||||||
"remoteContentsCleaning_description": string;
|
"remoteContentsCleaning_description": string;
|
||||||
|
/**
|
||||||
|
* ハイパーリンクなど、一部の参照方法はシステム上で検知できません。
|
||||||
|
*/
|
||||||
|
"remoteContentsCleaning_description2": string;
|
||||||
/**
|
/**
|
||||||
* 管理者情報
|
* 管理者情報
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3218,6 +3218,7 @@ _serverSetupWizard:
|
||||||
youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。"
|
youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。"
|
||||||
remoteContentsCleaning: "受信コンテンツの自動クリーニング"
|
remoteContentsCleaning: "受信コンテンツの自動クリーニング"
|
||||||
remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。"
|
remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。"
|
||||||
|
remoteContentsCleaning_description2: "ハイパーリンクなど、一部の参照方法はシステム上で検知できません。"
|
||||||
adminInfo: "管理者情報"
|
adminInfo: "管理者情報"
|
||||||
adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。"
|
adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。"
|
||||||
adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。"
|
adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。"
|
||||||
|
|
|
@ -49,15 +49,12 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiNoteReaction['id'] | MiNoteReaction,
|
src: MiNoteReaction['id'] | MiNoteReaction,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
options?: {
|
options?: object,
|
||||||
withNote: boolean;
|
|
||||||
},
|
|
||||||
hints?: {
|
hints?: {
|
||||||
packedUser?: Packed<'UserLite'>
|
packedUser?: Packed<'UserLite'>
|
||||||
},
|
},
|
||||||
): Promise<Packed<'NoteReaction'>> {
|
): Promise<Packed<'NoteReaction'>> {
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
withNote: false,
|
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src });
|
const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src });
|
||||||
|
@ -67,9 +64,6 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
createdAt: this.idService.parse(reaction.id).date.toISOString(),
|
createdAt: this.idService.parse(reaction.id).date.toISOString(),
|
||||||
user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me),
|
user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me),
|
||||||
type: this.reactionService.convertLegacyReaction(reaction.reaction),
|
type: this.reactionService.convertLegacyReaction(reaction.reaction),
|
||||||
...(opts.withNote ? {
|
|
||||||
note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me),
|
|
||||||
} : {}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,16 +71,50 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
public async packMany(
|
public async packMany(
|
||||||
reactions: MiNoteReaction[],
|
reactions: MiNoteReaction[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
options?: {
|
options?: object,
|
||||||
withNote: boolean;
|
|
||||||
},
|
|
||||||
): Promise<Packed<'NoteReaction'>[]> {
|
): Promise<Packed<'NoteReaction'>[]> {
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
withNote: false,
|
|
||||||
}, options);
|
}, options);
|
||||||
const _users = reactions.map(({ user, userId }) => user ?? userId);
|
const _users = reactions.map(({ user, userId }) => user ?? userId);
|
||||||
const _userMap = await this.userEntityService.packMany(_users, me)
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
.then(users => new Map(users.map(u => [u.id, u])));
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) })));
|
return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) })));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async packWithNote(
|
||||||
|
src: MiNoteReaction['id'] | MiNoteReaction,
|
||||||
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
options?: object,
|
||||||
|
hints?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
|
): Promise<Packed<'NoteReactionWithNote'>> {
|
||||||
|
const opts = Object.assign({
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: reaction.id,
|
||||||
|
createdAt: this.idService.parse(reaction.id).date.toISOString(),
|
||||||
|
user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me),
|
||||||
|
type: this.reactionService.convertLegacyReaction(reaction.reaction),
|
||||||
|
note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async packManyWithNote(
|
||||||
|
reactions: MiNoteReaction[],
|
||||||
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
options?: object,
|
||||||
|
): Promise<Packed<'NoteReactionWithNote'>[]> {
|
||||||
|
const opts = Object.assign({
|
||||||
|
}, options);
|
||||||
|
const _users = reactions.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(reactions.map(reaction => this.packWithNote(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) })));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { packedFollowingSchema } from '@/models/json-schema/following.js';
|
||||||
import { packedMutingSchema } from '@/models/json-schema/muting.js';
|
import { packedMutingSchema } from '@/models/json-schema/muting.js';
|
||||||
import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js';
|
import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js';
|
||||||
import { packedBlockingSchema } from '@/models/json-schema/blocking.js';
|
import { packedBlockingSchema } from '@/models/json-schema/blocking.js';
|
||||||
import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js';
|
import { packedNoteReactionSchema, packedNoteReactionWithNoteSchema } from '@/models/json-schema/note-reaction.js';
|
||||||
import { packedHashtagSchema } from '@/models/json-schema/hashtag.js';
|
import { packedHashtagSchema } from '@/models/json-schema/hashtag.js';
|
||||||
import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js';
|
import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js';
|
||||||
import { packedPageBlockSchema, packedPageSchema } from '@/models/json-schema/page.js';
|
import { packedPageBlockSchema, packedPageSchema } from '@/models/json-schema/page.js';
|
||||||
|
@ -92,6 +92,7 @@ export const refs = {
|
||||||
Note: packedNoteSchema,
|
Note: packedNoteSchema,
|
||||||
NoteDraft: packedNoteDraftSchema,
|
NoteDraft: packedNoteDraftSchema,
|
||||||
NoteReaction: packedNoteReactionSchema,
|
NoteReaction: packedNoteReactionSchema,
|
||||||
|
NoteReactionWithNote: packedNoteReactionWithNoteSchema,
|
||||||
NoteFavorite: packedNoteFavoriteSchema,
|
NoteFavorite: packedNoteFavoriteSchema,
|
||||||
Notification: packedNotificationSchema,
|
Notification: packedNotificationSchema,
|
||||||
DriveFile: packedDriveFileSchema,
|
DriveFile: packedDriveFileSchema,
|
||||||
|
|
|
@ -10,7 +10,6 @@ export const packedNoteReactionSchema = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
format: 'id',
|
format: 'id',
|
||||||
example: 'xxxxxxxxxx',
|
|
||||||
},
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -28,3 +27,33 @@ export const packedNoteReactionSchema = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const packedNoteReactionWithNoteSchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'date-time',
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: 'UserLite',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
note: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: 'Note',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
|
@ -49,6 +49,34 @@ export const meta = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
icon: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
|
display: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
forExistingUsers: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
silence: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
needConfirmationToRead: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
imageUrl: {
|
imageUrl: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
|
|
|
@ -73,8 +73,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
Object.entries(ps).filter(
|
Object.entries(ps).filter(
|
||||||
([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key)
|
([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key),
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,6 +23,16 @@ export const meta = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
ref: 'UserList',
|
ref: 'UserList',
|
||||||
|
properties: {
|
||||||
|
likedCount: {
|
||||||
|
type: 'number',
|
||||||
|
optional: true, nullable: false,
|
||||||
|
},
|
||||||
|
isLiked: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true, nullable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
errors: {
|
errors: {
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const meta = {
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
ref: 'NoteReaction',
|
ref: 'NoteReactionWithNote',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true });
|
return await this.noteReactionEntityService.packManyWithNote(reactions, me);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// TODO: (可能な部分を)sharedに抽出して frontend と共通化
|
||||||
|
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import lightTheme from '@@/themes/_light.json5';
|
import lightTheme from '@@/themes/_light.json5';
|
||||||
import darkTheme from '@@/themes/_dark.json5';
|
import darkTheme from '@@/themes/_dark.json5';
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import type { DefaultBodyType, HttpResponseResolver, JsonBodyType, PathParams } from 'msw';
|
import type { DefaultBodyType, HttpResponseResolver, JsonBodyType, PathParams } from 'msw';
|
||||||
import seedrandom from 'seedrandom';
|
import seedrandom from 'seedrandom';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
|
|
||||||
function getChartArray(seed: string, limit: number, option?: { accumulate?: boolean, mul?: number }): number[] {
|
function getChartArray(seed: string, limit: number, option?: { accumulate?: boolean, mul?: number }): number[] {
|
||||||
const rng = seedrandom(seed);
|
const rng = seedrandom(seed);
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
"prefix": "storyimplevent",
|
"prefix": "storyimplevent",
|
||||||
"body": [
|
"body": [
|
||||||
"/* eslint-disable @typescript-eslint/explicit-function-return-type */",
|
"/* eslint-disable @typescript-eslint/explicit-function-return-type */",
|
||||||
"import { action } from '@storybook/addon-actions';",
|
"import { action } from 'storybook/actions';",
|
||||||
"import { StoryObj } from '@storybook/vue3';",
|
"import { StoryObj } from '@storybook/vue3';",
|
||||||
"import $1 from './$1.vue';",
|
"import $1 from './$1.vue';",
|
||||||
"export const Default = {",
|
"export const Default = {",
|
||||||
|
|
|
@ -85,7 +85,6 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@misskey-dev/summaly": "5.2.3",
|
"@misskey-dev/summaly": "5.2.3",
|
||||||
"@storybook/addon-actions": "9.0.8",
|
|
||||||
"@storybook/addon-essentials": "8.6.14",
|
"@storybook/addon-essentials": "8.6.14",
|
||||||
"@storybook/addon-interactions": "8.6.14",
|
"@storybook/addon-interactions": "8.6.14",
|
||||||
"@storybook/addon-links": "9.1.3",
|
"@storybook/addon-links": "9.1.3",
|
||||||
|
|
|
@ -23,7 +23,7 @@ export async function getAccounts(): Promise<{
|
||||||
host: string;
|
host: string;
|
||||||
id: Misskey.entities.User['id'];
|
id: Misskey.entities.User['id'];
|
||||||
username: Misskey.entities.User['username'];
|
username: Misskey.entities.User['username'];
|
||||||
user?: Misskey.entities.User | null;
|
user?: Misskey.entities.MeDetailed | null;
|
||||||
token: string | null;
|
token: string | null;
|
||||||
}[]> {
|
}[]> {
|
||||||
const tokens = store.s.accountTokens;
|
const tokens = store.s.accountTokens;
|
||||||
|
@ -38,7 +38,7 @@ export async function getAccounts(): Promise<{
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addAccount(host: string, user: Misskey.entities.User, token: AccountWithToken['token']) {
|
async function addAccount(host: string, user: Misskey.entities.MeDetailed, token: AccountWithToken['token']) {
|
||||||
if (!prefer.s.accounts.some(x => x[0] === host && x[1].id === user.id)) {
|
if (!prefer.s.accounts.some(x => x[0] === host && x[1].id === user.id)) {
|
||||||
store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + user.id]: token });
|
store.set('accountTokens', { ...store.s.accountTokens, [host + '/' + user.id]: token });
|
||||||
store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + user.id]: user });
|
store.set('accountInfos', { ...store.s.accountInfos, [host + '/' + user.id]: user });
|
||||||
|
@ -149,9 +149,10 @@ export function updateCurrentAccountPartial(accountData: Partial<Misskey.entitie
|
||||||
|
|
||||||
export async function refreshCurrentAccount() {
|
export async function refreshCurrentAccount() {
|
||||||
if (!$i) return;
|
if (!$i) return;
|
||||||
|
const me = $i;
|
||||||
return fetchAccount($i.token, $i.id).then(updateCurrentAccount).catch(reason => {
|
return fetchAccount($i.token, $i.id).then(updateCurrentAccount).catch(reason => {
|
||||||
if (reason === isAccountDeleted) {
|
if (reason === isAccountDeleted) {
|
||||||
removeAccount(host, $i.id);
|
removeAccount(host, me.id);
|
||||||
if (Object.keys(store.s.accountTokens).length > 0) {
|
if (Object.keys(store.s.accountTokens).length > 0) {
|
||||||
login(Object.values(store.s.accountTokens)[0]);
|
login(Object.values(store.s.accountTokens)[0]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -214,19 +215,37 @@ export async function openAccountMenu(opts: {
|
||||||
includeCurrentAccount?: boolean;
|
includeCurrentAccount?: boolean;
|
||||||
withExtraOperation: boolean;
|
withExtraOperation: boolean;
|
||||||
active?: Misskey.entities.User['id'];
|
active?: Misskey.entities.User['id'];
|
||||||
onChoose?: (account: Misskey.entities.User) => void;
|
onChoose?: (account: Misskey.entities.MeDetailed) => void;
|
||||||
}, ev: MouseEvent) {
|
}, ev: MouseEvent) {
|
||||||
if (!$i) return;
|
if (!$i) return;
|
||||||
|
const me = $i;
|
||||||
|
|
||||||
function createItem(host: string, id: Misskey.entities.User['id'], username: Misskey.entities.User['username'], account: Misskey.entities.User | null | undefined, token: string): MenuItem {
|
const callback = opts.onChoose;
|
||||||
|
|
||||||
|
function createItem(host: string, id: Misskey.entities.User['id'], username: Misskey.entities.User['username'], account: Misskey.entities.MeDetailed | null | undefined, token: string | null): MenuItem {
|
||||||
if (account) {
|
if (account) {
|
||||||
return {
|
return {
|
||||||
type: 'user' as const,
|
type: 'user' as const,
|
||||||
user: account,
|
user: account,
|
||||||
active: opts.active != null ? opts.active === id : false,
|
active: opts.active != null ? opts.active === id : false,
|
||||||
action: async () => {
|
action: async () => {
|
||||||
if (opts.onChoose) {
|
if (callback) {
|
||||||
opts.onChoose(account);
|
callback(account);
|
||||||
|
} else {
|
||||||
|
switchAccount(host, id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (token != null) {
|
||||||
|
return {
|
||||||
|
type: 'button' as const,
|
||||||
|
text: username,
|
||||||
|
active: opts.active != null ? opts.active === id : false,
|
||||||
|
action: async () => {
|
||||||
|
if (callback) {
|
||||||
|
fetchAccount(token, id).then(account => {
|
||||||
|
callback(account);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
switchAccount(host, id);
|
switchAccount(host, id);
|
||||||
}
|
}
|
||||||
|
@ -238,13 +257,7 @@ export async function openAccountMenu(opts: {
|
||||||
text: username,
|
text: username,
|
||||||
active: opts.active != null ? opts.active === id : false,
|
active: opts.active != null ? opts.active === id : false,
|
||||||
action: async () => {
|
action: async () => {
|
||||||
if (opts.onChoose) {
|
// TODO
|
||||||
fetchAccount(token, id).then(account => {
|
|
||||||
opts.onChoose(account);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
switchAccount(host, id);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -253,7 +266,7 @@ export async function openAccountMenu(opts: {
|
||||||
const menuItems: MenuItem[] = [];
|
const menuItems: MenuItem[] = [];
|
||||||
|
|
||||||
// TODO: $iのホストも比較したいけど通常null
|
// TODO: $iのホストも比較したいけど通常null
|
||||||
const accountItems = (await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id))).map(a => createItem(a.host, a.id, a.username, a.user, a.token));
|
const accountItems = (await getAccounts().then(accounts => accounts.filter(x => x.id !== me.id))).map(a => createItem(a.host, a.id, a.username, a.user, a.token));
|
||||||
|
|
||||||
if (opts.withExtraOperation) {
|
if (opts.withExtraOperation) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
|
|
|
@ -368,11 +368,6 @@ export async function mainBoot() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('unreadAntenna', () => {
|
|
||||||
updateCurrentAccountPartial({ hasUnreadAntenna: true });
|
|
||||||
sound.playMisskeySfx('antenna');
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('newChatMessage', () => {
|
main.on('newChatMessage', () => {
|
||||||
updateCurrentAccountPartial({ hasUnreadChatMessages: true });
|
updateCurrentAccountPartial({ hasUnreadChatMessages: true });
|
||||||
sound.playMisskeySfx('chatMessage');
|
sound.playMisskeySfx('chatMessage');
|
||||||
|
|
|
@ -3,13 +3,12 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
import { action } from 'storybook/actions';
|
||||||
import { action } from '@storybook/addon-actions';
|
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { userDetailed } from '../../.storybook/fakes.js';
|
import { userDetailed } from '../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkAbuseReportWindow from './MkAbuseReportWindow.vue';
|
import MkAbuseReportWindow from './MkAbuseReportWindow.vue';
|
||||||
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
export const Default = {
|
export const Default = {
|
||||||
render(args) {
|
render(args) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
|
|
|
@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
[$style.iconFrame_platinum]: ACHIEVEMENT_BADGES[achievement.name].frame === 'platinum',
|
[$style.iconFrame_platinum]: ACHIEVEMENT_BADGES[achievement.name].frame === 'platinum',
|
||||||
}]"
|
}]"
|
||||||
>
|
>
|
||||||
<div :class="[$style.iconInner]" :style="{ background: ACHIEVEMENT_BADGES[achievement.name].bg }">
|
<div :class="[$style.iconInner]" :style="{ background: ACHIEVEMENT_BADGES[achievement.name].bg ?? '' }">
|
||||||
<img :class="$style.iconImg" :src="ACHIEVEMENT_BADGES[achievement.name].img">
|
<img :class="$style.iconImg" :src="ACHIEVEMENT_BADGES[achievement.name].img">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,7 +71,7 @@ const props = withDefaults(defineProps<{
|
||||||
const achievements = ref<Misskey.entities.UsersAchievementsResponse | null>(null);
|
const achievements = ref<Misskey.entities.UsersAchievementsResponse | null>(null);
|
||||||
const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x)));
|
const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x)));
|
||||||
|
|
||||||
function fetch() {
|
function _fetch_() {
|
||||||
misskeyApi('users/achievements', { userId: props.user.id }).then(res => {
|
misskeyApi('users/achievements', { userId: props.user.id }).then(res => {
|
||||||
achievements.value = [];
|
achievements.value = [];
|
||||||
for (const t of ACHIEVEMENT_TYPES) {
|
for (const t of ACHIEVEMENT_TYPES) {
|
||||||
|
@ -84,11 +84,11 @@ function fetch() {
|
||||||
|
|
||||||
function clickHere() {
|
function clickHere() {
|
||||||
claimAchievement('clickedClickHere');
|
claimAchievement('clickedClickHere');
|
||||||
fetch();
|
_fetch_();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetch();
|
_fetch_();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -265,6 +265,8 @@ onUnmounted(() => {
|
||||||
if (handle) {
|
if (handle) {
|
||||||
window.cancelAnimationFrame(handle);
|
window.cancelAnimationFrame(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: WebGLリソースの解放
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
/* eslint-disable import/no-default-export */
|
/* eslint-disable import/no-default-export */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import MkButton from './MkButton.vue';
|
import MkButton from './MkButton.vue';
|
||||||
export const Default = {
|
export const Default = {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { expect, userEvent, within } from '@storybook/test';
|
import { expect, userEvent, within } from '@storybook/test';
|
||||||
import { channel } from '../../.storybook/fakes.js';
|
import { channel } from '../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
/* eslint-disable import/no-default-export */
|
/* eslint-disable import/no-default-export */
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { channel } from '../../.storybook/fakes.js';
|
import { channel } from '../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkChannelList from './MkChannelList.vue';
|
import MkChannelList from './MkChannelList.vue';
|
||||||
|
|
|
@ -589,7 +589,10 @@ const fetchDriveFilesChart = async (): Promise<typeof chartData> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => {
|
const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => {
|
||||||
const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
|
const host = props.args?.host;
|
||||||
|
if (host == null) return { series: [] };
|
||||||
|
|
||||||
|
const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span });
|
||||||
return {
|
return {
|
||||||
series: [{
|
series: [{
|
||||||
name: 'In',
|
name: 'In',
|
||||||
|
@ -611,7 +614,10 @@ const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData> => {
|
const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData> => {
|
||||||
const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
|
const host = props.args?.host;
|
||||||
|
if (host == null) return { series: [] };
|
||||||
|
|
||||||
|
const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span });
|
||||||
return {
|
return {
|
||||||
series: [{
|
series: [{
|
||||||
name: 'Users',
|
name: 'Users',
|
||||||
|
@ -626,7 +632,10 @@ const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData> => {
|
const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData> => {
|
||||||
const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
|
const host = props.args?.host;
|
||||||
|
if (host == null) return { series: [] };
|
||||||
|
|
||||||
|
const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span });
|
||||||
return {
|
return {
|
||||||
series: [{
|
series: [{
|
||||||
name: 'Notes',
|
name: 'Notes',
|
||||||
|
@ -641,7 +650,10 @@ const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> => {
|
const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> => {
|
||||||
const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
|
const host = props.args?.host;
|
||||||
|
if (host == null) return { series: [] };
|
||||||
|
|
||||||
|
const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span });
|
||||||
return {
|
return {
|
||||||
series: [{
|
series: [{
|
||||||
name: 'Following',
|
name: 'Following',
|
||||||
|
@ -664,7 +676,10 @@ const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> =
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof chartData> => {
|
const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof chartData> => {
|
||||||
const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
|
const host = props.args?.host;
|
||||||
|
if (host == null) return { series: [] };
|
||||||
|
|
||||||
|
const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span });
|
||||||
return {
|
return {
|
||||||
bytes: true,
|
bytes: true,
|
||||||
series: [{
|
series: [{
|
||||||
|
@ -680,7 +695,10 @@ const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof char
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof chartData> => {
|
const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof chartData> => {
|
||||||
const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
|
const host = props.args?.host;
|
||||||
|
if (host == null) return { series: [] };
|
||||||
|
|
||||||
|
const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span });
|
||||||
return {
|
return {
|
||||||
series: [{
|
series: [{
|
||||||
name: 'Drive files',
|
name: 'Drive files',
|
||||||
|
@ -695,7 +713,10 @@ const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof char
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchPerUserNotesChart = async (): Promise<typeof chartData> => {
|
const fetchPerUserNotesChart = async (): Promise<typeof chartData> => {
|
||||||
const raw = await misskeyApiGet('charts/user/notes', { userId: props.args?.user?.id, limit: props.limit, span: props.span });
|
const userId = props.args?.user?.id;
|
||||||
|
if (userId == null) return { series: [] };
|
||||||
|
|
||||||
|
const raw = await misskeyApiGet('charts/user/notes', { userId: userId, limit: props.limit, span: props.span });
|
||||||
return {
|
return {
|
||||||
series: [...(props.args?.withoutAll ? [] : [{
|
series: [...(props.args?.withoutAll ? [] : [{
|
||||||
name: 'All',
|
name: 'All',
|
||||||
|
@ -727,7 +748,10 @@ const fetchPerUserNotesChart = async (): Promise<typeof chartData> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchPerUserPvChart = async (): Promise<typeof chartData> => {
|
const fetchPerUserPvChart = async (): Promise<typeof chartData> => {
|
||||||
const raw = await misskeyApiGet('charts/user/pv', { userId: props.args?.user?.id, limit: props.limit, span: props.span });
|
const userId = props.args?.user?.id;
|
||||||
|
if (userId == null) return { series: [] };
|
||||||
|
|
||||||
|
const raw = await misskeyApiGet('charts/user/pv', { userId: userId, limit: props.limit, span: props.span });
|
||||||
return {
|
return {
|
||||||
series: [{
|
series: [{
|
||||||
name: 'Unique PV (user)',
|
name: 'Unique PV (user)',
|
||||||
|
@ -754,7 +778,10 @@ const fetchPerUserPvChart = async (): Promise<typeof chartData> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => {
|
const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => {
|
||||||
const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span });
|
const userId = props.args?.user?.id;
|
||||||
|
if (userId == null) return { series: [] };
|
||||||
|
|
||||||
|
const raw = await misskeyApiGet('charts/user/following', { userId: userId, limit: props.limit, span: props.span });
|
||||||
return {
|
return {
|
||||||
series: [{
|
series: [{
|
||||||
name: 'Local',
|
name: 'Local',
|
||||||
|
@ -769,7 +796,10 @@ const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => {
|
const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => {
|
||||||
const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span });
|
const userId = props.args?.user?.id;
|
||||||
|
if (userId == null) return { series: [] };
|
||||||
|
|
||||||
|
const raw = await misskeyApiGet('charts/user/following', { userId: userId, limit: props.limit, span: props.span });
|
||||||
return {
|
return {
|
||||||
series: [{
|
series: [{
|
||||||
name: 'Local',
|
name: 'Local',
|
||||||
|
@ -784,7 +814,10 @@ const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchPerUserDriveChart = async (): Promise<typeof chartData> => {
|
const fetchPerUserDriveChart = async (): Promise<typeof chartData> => {
|
||||||
const raw = await misskeyApiGet('charts/user/drive', { userId: props.args?.user?.id, limit: props.limit, span: props.span });
|
const userId = props.args?.user?.id;
|
||||||
|
if (userId == null) return { series: [] };
|
||||||
|
|
||||||
|
const raw = await misskeyApiGet('charts/user/drive', { userId: userId, limit: props.limit, span: props.span });
|
||||||
return {
|
return {
|
||||||
bytes: true,
|
bytes: true,
|
||||||
series: [{
|
series: [{
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { http, HttpResponse } from 'msw';
|
import { http, HttpResponse } from 'msw';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { chatMessage } from '../../.storybook/fakes';
|
import { chatMessage } from '../../.storybook/fakes';
|
||||||
import MkChatHistories from './MkChatHistories.vue';
|
import MkChatHistories from './MkChatHistories.vue';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { expect, userEvent, within } from '@storybook/test';
|
import { expect, userEvent, within } from '@storybook/test';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkClickerGame from './MkClickerGame.vue';
|
import MkClickerGame from './MkClickerGame.vue';
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
/* eslint-disable import/no-default-export */
|
/* eslint-disable import/no-default-export */
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import MkCodeEditor from './MkCodeEditor.vue';
|
import MkCodeEditor from './MkCodeEditor.vue';
|
||||||
const code = `for (let i, 100) {
|
const code = `for (let i, 100) {
|
||||||
<: if (i % 15 == 0) "FizzBuzz"
|
<: if (i % 15 == 0) "FizzBuzz"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
/* eslint-disable import/no-default-export */
|
/* eslint-disable import/no-default-export */
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import MkColorInput from './MkColorInput.vue';
|
import MkColorInput from './MkColorInput.vue';
|
||||||
export const Default = {
|
export const Default = {
|
||||||
render(args) {
|
render(args) {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { HttpResponse, http } from 'msw';
|
import { HttpResponse, http } from 'msw';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { file } from '../../.storybook/fakes.js';
|
import { file } from '../../.storybook/fakes.js';
|
||||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||||
import MkCropperDialog from './MkCropperDialog.vue';
|
import MkCropperDialog from './MkCropperDialog.vue';
|
||||||
|
|
|
@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, useTemplateRef, ref } from 'vue';
|
import { onMounted, useTemplateRef, ref, onUnmounted } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import Cropper from 'cropperjs';
|
import Cropper from 'cropperjs';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
|
@ -55,17 +55,19 @@ const imgEl = useTemplateRef('imgEl');
|
||||||
let cropper: Cropper | null = null;
|
let cropper: Cropper | null = null;
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|
||||||
const ok = async () => {
|
async function ok() {
|
||||||
const promise = new Promise<Misskey.entities.DriveFile>(async (res) => {
|
const promise = new Promise<Blob>(async (res) => {
|
||||||
const croppedImage = await cropper?.getCropperImage();
|
if (cropper == null) throw new Error('Cropper is not initialized');
|
||||||
const croppedSection = await cropper?.getCropperSelection();
|
|
||||||
|
const croppedImage = await cropper.getCropperImage()!;
|
||||||
|
const croppedSection = await cropper.getCropperSelection()!;
|
||||||
|
|
||||||
// 拡大率を計算し、(ほぼ)元の大きさに戻す
|
// 拡大率を計算し、(ほぼ)元の大きさに戻す
|
||||||
const zoomedRate = croppedImage.getBoundingClientRect().width / croppedImage.clientWidth;
|
const zoomedRate = croppedImage.getBoundingClientRect().width / croppedImage.clientWidth;
|
||||||
const widthToRender = croppedSection.getBoundingClientRect().width / zoomedRate;
|
const widthToRender = croppedSection.getBoundingClientRect().width / zoomedRate;
|
||||||
|
|
||||||
const croppedCanvas = await croppedSection?.$toCanvas({ width: widthToRender });
|
const croppedCanvas = await croppedSection.$toCanvas({ width: widthToRender });
|
||||||
croppedCanvas?.toBlob(blob => {
|
croppedCanvas.toBlob(blob => {
|
||||||
if (!blob) return;
|
if (!blob) return;
|
||||||
res(blob);
|
res(blob);
|
||||||
});
|
});
|
||||||
|
@ -74,25 +76,27 @@ const ok = async () => {
|
||||||
const f = await promise;
|
const f = await promise;
|
||||||
|
|
||||||
emit('ok', f);
|
emit('ok', f);
|
||||||
dialogEl.value!.close();
|
if (dialogEl.value != null) dialogEl.value.close();
|
||||||
};
|
}
|
||||||
|
|
||||||
const cancel = () => {
|
function cancel() {
|
||||||
emit('cancel');
|
emit('cancel');
|
||||||
dialogEl.value!.close();
|
if (dialogEl.value != null) dialogEl.value.close();
|
||||||
};
|
}
|
||||||
|
|
||||||
const onImageLoad = () => {
|
function onImageLoad() {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|
||||||
if (cropper) {
|
if (cropper) {
|
||||||
cropper.getCropperImage()!.$center('contain');
|
cropper.getCropperImage()!.$center('contain');
|
||||||
cropper.getCropperSelection()!.$center();
|
cropper.getCropperSelection()!.$center();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
cropper = new Cropper(imgEl.value!, {
|
if (imgEl.value == null) return; // TSを黙らすため
|
||||||
|
|
||||||
|
cropper = new Cropper(imgEl.value, {
|
||||||
});
|
});
|
||||||
|
|
||||||
const computedStyle = getComputedStyle(window.document.documentElement);
|
const computedStyle = getComputedStyle(window.document.documentElement);
|
||||||
|
@ -104,16 +108,22 @@ onMounted(() => {
|
||||||
selection.outlined = true;
|
selection.outlined = true;
|
||||||
|
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
cropper!.getCropperImage()!.$center('contain');
|
if (cropper == null) return;
|
||||||
|
cropper.getCropperImage()!.$center('contain');
|
||||||
selection.$center();
|
selection.$center();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
// モーダルオープンアニメーションが終わったあとで再度調整
|
// モーダルオープンアニメーションが終わったあとで再度調整
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
cropper!.getCropperImage()!.$center('contain');
|
if (cropper == null) return;
|
||||||
|
cropper.getCropperImage()!.$center('contain');
|
||||||
selection.$center();
|
selection.$center();
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
URL.revokeObjectURL(imgUrl);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
/* eslint-disable import/no-default-export */
|
/* eslint-disable import/no-default-export */
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { expect, userEvent, within } from '@storybook/test';
|
import { expect, userEvent, within } from '@storybook/test';
|
||||||
import { file } from '../../.storybook/fakes.js';
|
import { file } from '../../.storybook/fakes.js';
|
||||||
import MkCwButton from './MkCwButton.vue';
|
import MkCwButton from './MkCwButton.vue';
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { onBeforeUnmount } from 'vue';
|
import { onBeforeUnmount } from 'vue';
|
||||||
import MkDonation from './MkDonation.vue';
|
import MkDonation from './MkDonation.vue';
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import MkDrive_file from './MkDrive.file.vue';
|
import MkDrive_file from './MkDrive.file.vue';
|
||||||
import { file } from '../../.storybook/fakes.js';
|
import { file } from '../../.storybook/fakes.js';
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { http, HttpResponse } from 'msw';
|
import { http, HttpResponse } from 'msw';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { http, HttpResponse } from 'msw';
|
import { http, HttpResponse } from 'msw';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
|
|
@ -293,7 +293,7 @@ function onDragleave() {
|
||||||
draghover.value = false;
|
draghover.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDrop(ev: DragEvent) {
|
function onDrop(ev: DragEvent): void | boolean {
|
||||||
draghover.value = false;
|
draghover.value = false;
|
||||||
|
|
||||||
if (!ev.dataTransfer) return;
|
if (!ev.dataTransfer) return;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
|
@ -152,7 +152,7 @@ const props = withDefaults(defineProps<{
|
||||||
asDrawer?: boolean;
|
asDrawer?: boolean;
|
||||||
asWindow?: boolean;
|
asWindow?: boolean;
|
||||||
asReactionPicker?: boolean; // 今は使われてないが将来的に使いそう
|
asReactionPicker?: boolean; // 今は使われてないが将来的に使いそう
|
||||||
targetNote?: Misskey.entities.Note;
|
targetNote?: Misskey.entities.Note | null;
|
||||||
}>(), {
|
}>(), {
|
||||||
showPinned: true,
|
showPinned: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,11 +44,11 @@ import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
manualShowing?: boolean | null;
|
manualShowing?: boolean | null;
|
||||||
anchorElement?: HTMLElement;
|
anchorElement?: HTMLElement | null;
|
||||||
showPinned?: boolean;
|
showPinned?: boolean;
|
||||||
pinnedEmojis?: string[],
|
pinnedEmojis?: string[],
|
||||||
asReactionPicker?: boolean;
|
asReactionPicker?: boolean;
|
||||||
targetNote?: Misskey.entities.Note;
|
targetNote?: Misskey.entities.Note | null;
|
||||||
choseAndClose?: boolean;
|
choseAndClose?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
manualShowing: null,
|
manualShowing: null,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { file } from '../../.storybook/fakes.js';
|
import { file } from '../../.storybook/fakes.js';
|
||||||
import MkImgPreviewDialog from './MkImgPreviewDialog.vue';
|
import MkImgPreviewDialog from './MkImgPreviewDialog.vue';
|
||||||
export const Default = {
|
export const Default = {
|
||||||
|
|
|
@ -39,10 +39,12 @@ const el = ref<HTMLElement | { $el: HTMLElement }>();
|
||||||
|
|
||||||
if (isEnabledUrlPreview.value) {
|
if (isEnabledUrlPreview.value) {
|
||||||
useTooltip(el, (showing) => {
|
useTooltip(el, (showing) => {
|
||||||
|
const anchorElement = el.value instanceof HTMLElement ? el.value : el.value?.$el;
|
||||||
|
if (anchorElement == null) return;
|
||||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {
|
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {
|
||||||
showing,
|
showing,
|
||||||
url: props.url,
|
url: props.url,
|
||||||
anchorElement: el.value instanceof HTMLElement ? el.value : el.value?.$el,
|
anchorElement: anchorElement,
|
||||||
}, {
|
}, {
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -91,7 +91,7 @@ const emit = defineEmits<{
|
||||||
(ev: 'opened'): void;
|
(ev: 'opened'): void;
|
||||||
(ev: 'click'): void;
|
(ev: 'click'): void;
|
||||||
(ev: 'esc'): void;
|
(ev: 'esc'): void;
|
||||||
(ev: 'close'): void;
|
(ev: 'close'): void; // TODO: (refactor) closing に改名する
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -148,7 +148,6 @@ function close(opts: { useSendAnimation?: boolean } = {}) {
|
||||||
useSendAnime.value = true;
|
useSendAnime.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
if (props.anchorElement) props.anchorElement.style.pointerEvents = 'auto';
|
if (props.anchorElement) props.anchorElement.style.pointerEvents = 'auto';
|
||||||
showing.value = false;
|
showing.value = false;
|
||||||
emit('close');
|
emit('close');
|
||||||
|
@ -319,7 +318,6 @@ const alignObserver = new ResizeObserver((entries, observer) => {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
watch(() => props.anchorElement, async () => {
|
watch(() => props.anchorElement, async () => {
|
||||||
if (props.anchorElement) {
|
if (props.anchorElement) {
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.anchorElement.style.pointerEvents = 'none';
|
props.anchorElement.style.pointerEvents = 'none';
|
||||||
}
|
}
|
||||||
fixed.value = (type.value === 'drawer') || (getFixedContainer(props.anchorElement) != null);
|
fixed.value = (type.value === 'drawer') || (getFixedContainer(props.anchorElement) != null);
|
||||||
|
|
|
@ -654,7 +654,7 @@ function showRenoteMenu(): void {
|
||||||
getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
|
getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
getAbuseNoteMenu(note, i18n.ts.reportAbuseRenote),
|
getAbuseNoteMenu(note, i18n.ts.reportAbuseRenote),
|
||||||
($i?.isModerator || $i?.isAdmin) ? getUnrenote() : undefined,
|
...(($i?.isModerator || $i?.isAdmin) ? [getUnrenote()] : []),
|
||||||
], renoteTime.value);
|
], renoteTime.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -392,6 +392,9 @@ const reactionsPaginator = markRaw(new Paginator('notes/reactions', {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
useTooltip(renoteButton, async (showing) => {
|
useTooltip(renoteButton, async (showing) => {
|
||||||
|
const anchorElement = renoteButton.value;
|
||||||
|
if (anchorElement == null) return;
|
||||||
|
|
||||||
const renotes = await misskeyApi('notes/renotes', {
|
const renotes = await misskeyApi('notes/renotes', {
|
||||||
noteId: appearNote.id,
|
noteId: appearNote.id,
|
||||||
limit: 11,
|
limit: 11,
|
||||||
|
@ -405,7 +408,7 @@ useTooltip(renoteButton, async (showing) => {
|
||||||
showing,
|
showing,
|
||||||
users,
|
users,
|
||||||
count: appearNote.renoteCount,
|
count: appearNote.renoteCount,
|
||||||
anchorElement: renoteButton.value,
|
anchorElement: anchorElement,
|
||||||
}, {
|
}, {
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -57,7 +57,7 @@ async function _close() {
|
||||||
modal.value?.close();
|
modal.value?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEsc(ev: KeyboardEvent) {
|
function onEsc() {
|
||||||
_close();
|
_close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,18 +58,22 @@ const emit = defineEmits<{
|
||||||
const buttonEl = useTemplateRef('buttonEl');
|
const buttonEl = useTemplateRef('buttonEl');
|
||||||
|
|
||||||
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
|
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
|
||||||
const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction));
|
|
||||||
|
|
||||||
const canToggle = computed(() => {
|
const canToggle = computed(() => {
|
||||||
|
const emoji = customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction);
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
//return !props.reaction.match(/@\w/) && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value);
|
//return !props.reaction.match(/@\w/) && $i && emoji && checkReactionPermissions($i, props.note, emoji);
|
||||||
return !props.reaction.match(/@\w/) && $i && emoji.value;
|
return !props.reaction.match(/@\w/) && $i && emoji;
|
||||||
});
|
});
|
||||||
const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
|
const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
|
||||||
const isLocalCustomEmoji = props.reaction[0] === ':' && props.reaction.includes('@.');
|
const isLocalCustomEmoji = props.reaction[0] === ':' && props.reaction.includes('@.');
|
||||||
|
|
||||||
async function toggleReaction() {
|
async function toggleReaction() {
|
||||||
if (!canToggle.value) return;
|
if (!canToggle.value) return;
|
||||||
|
if ($i == null) return;
|
||||||
|
|
||||||
|
const me = $i;
|
||||||
|
|
||||||
const oldReaction = props.myReaction;
|
const oldReaction = props.myReaction;
|
||||||
if (oldReaction) {
|
if (oldReaction) {
|
||||||
|
@ -93,7 +97,7 @@ async function toggleReaction() {
|
||||||
noteId: props.noteId,
|
noteId: props.noteId,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
noteEvents.emit(`unreacted:${props.noteId}`, {
|
noteEvents.emit(`unreacted:${props.noteId}`, {
|
||||||
userId: $i!.id,
|
userId: me.id,
|
||||||
reaction: oldReaction,
|
reaction: oldReaction,
|
||||||
});
|
});
|
||||||
if (oldReaction !== props.reaction) {
|
if (oldReaction !== props.reaction) {
|
||||||
|
@ -101,10 +105,12 @@ async function toggleReaction() {
|
||||||
noteId: props.noteId,
|
noteId: props.noteId,
|
||||||
reaction: props.reaction,
|
reaction: props.reaction,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
const emoji = customEmojisMap.get(emojiName.value);
|
||||||
|
if (emoji == null) return;
|
||||||
noteEvents.emit(`reacted:${props.noteId}`, {
|
noteEvents.emit(`reacted:${props.noteId}`, {
|
||||||
userId: $i!.id,
|
userId: me.id,
|
||||||
reaction: props.reaction,
|
reaction: props.reaction,
|
||||||
emoji: emoji.value,
|
emoji: emoji,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -131,10 +137,13 @@ async function toggleReaction() {
|
||||||
noteId: props.noteId,
|
noteId: props.noteId,
|
||||||
reaction: props.reaction,
|
reaction: props.reaction,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
const emoji = customEmojisMap.get(emojiName.value);
|
||||||
|
if (emoji == null) return;
|
||||||
|
|
||||||
noteEvents.emit(`reacted:${props.noteId}`, {
|
noteEvents.emit(`reacted:${props.noteId}`, {
|
||||||
userId: $i!.id,
|
userId: me.id,
|
||||||
reaction: props.reaction,
|
reaction: props.reaction,
|
||||||
emoji: emoji.value,
|
emoji: emoji,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// TODO: 上位コンポーネントでやる
|
// TODO: 上位コンポーネントでやる
|
||||||
|
@ -217,6 +226,8 @@ onMounted(() => {
|
||||||
|
|
||||||
if (!mock) {
|
if (!mock) {
|
||||||
useTooltip(buttonEl, async (showing) => {
|
useTooltip(buttonEl, async (showing) => {
|
||||||
|
if (buttonEl.value == null) return;
|
||||||
|
|
||||||
const reactions = await misskeyApiGet('notes/reactions', {
|
const reactions = await misskeyApiGet('notes/reactions', {
|
||||||
noteId: props.noteId,
|
noteId: props.noteId,
|
||||||
type: props.reaction,
|
type: props.reaction,
|
||||||
|
|
|
@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<MkSwitch v-if="q_federation === 'yes'" v-model="q_remoteContentsCleaning">
|
<MkSwitch v-if="q_federation === 'yes'" v-model="q_remoteContentsCleaning">
|
||||||
<template #label>{{ i18n.ts._serverSetupWizard.remoteContentsCleaning }}</template>
|
<template #label>{{ i18n.ts._serverSetupWizard.remoteContentsCleaning }}</template>
|
||||||
<template #caption>{{ i18n.ts._serverSetupWizard.remoteContentsCleaning_description }}</template>
|
<template #caption>{{ i18n.ts._serverSetupWizard.remoteContentsCleaning_description }} ({{ i18n.ts._serverSetupWizard.remoteContentsCleaning_description2 }})</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
|
@ -297,76 +297,97 @@ function prepend(note: Misskey.entities.Note & MisskeyEntity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let connection: Misskey.IChannelConnection | null = null;
|
|
||||||
let connection2: Misskey.IChannelConnection | null = null;
|
|
||||||
|
|
||||||
const stream = store.s.realtimeMode ? useStream() : null;
|
const stream = store.s.realtimeMode ? useStream() : null;
|
||||||
|
|
||||||
|
const connections = {
|
||||||
|
antenna: null as Misskey.IChannelConnection<Misskey.Channels['antenna']> | null,
|
||||||
|
homeTimeline: null as Misskey.IChannelConnection<Misskey.Channels['homeTimeline']> | null,
|
||||||
|
localTimeline: null as Misskey.IChannelConnection<Misskey.Channels['localTimeline']> | null,
|
||||||
|
hybridTimeline: null as Misskey.IChannelConnection<Misskey.Channels['hybridTimeline']> | null,
|
||||||
|
globalTimeline: null as Misskey.IChannelConnection<Misskey.Channels['globalTimeline']> | null,
|
||||||
|
main: null as Misskey.IChannelConnection<Misskey.Channels['main']> | null,
|
||||||
|
userList: null as Misskey.IChannelConnection<Misskey.Channels['userList']> | null,
|
||||||
|
channel: null as Misskey.IChannelConnection<Misskey.Channels['channel']> | null,
|
||||||
|
roleTimeline: null as Misskey.IChannelConnection<Misskey.Channels['roleTimeline']> | null,
|
||||||
|
};
|
||||||
|
|
||||||
function connectChannel() {
|
function connectChannel() {
|
||||||
if (stream == null) return;
|
if (stream == null) return;
|
||||||
if (props.src === 'antenna') {
|
if (props.src === 'antenna') {
|
||||||
if (props.antenna == null) return;
|
if (props.antenna == null) return;
|
||||||
connection = stream.useChannel('antenna', {
|
connections.antenna = stream.useChannel('antenna', {
|
||||||
antennaId: props.antenna,
|
antennaId: props.antenna,
|
||||||
});
|
});
|
||||||
|
connections.antenna.on('note', prepend);
|
||||||
} else if (props.src === 'home') {
|
} else if (props.src === 'home') {
|
||||||
connection = stream.useChannel('homeTimeline', {
|
connections.homeTimeline = stream.useChannel('homeTimeline', {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
});
|
});
|
||||||
connection2 = stream.useChannel('main');
|
connections.main = stream.useChannel('main');
|
||||||
|
connections.homeTimeline.on('note', prepend);
|
||||||
} else if (props.src === 'local') {
|
} else if (props.src === 'local') {
|
||||||
connection = stream.useChannel('localTimeline', {
|
connections.localTimeline = stream.useChannel('localTimeline', {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withReplies: props.withReplies,
|
withReplies: props.withReplies,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
});
|
});
|
||||||
|
connections.localTimeline.on('note', prepend);
|
||||||
} else if (props.src === 'social') {
|
} else if (props.src === 'social') {
|
||||||
connection = stream.useChannel('hybridTimeline', {
|
connections.hybridTimeline = stream.useChannel('hybridTimeline', {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withReplies: props.withReplies,
|
withReplies: props.withReplies,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
});
|
});
|
||||||
|
connections.hybridTimeline.on('note', prepend);
|
||||||
} else if (props.src === 'global') {
|
} else if (props.src === 'global') {
|
||||||
connection = stream.useChannel('globalTimeline', {
|
connections.globalTimeline = stream.useChannel('globalTimeline', {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
});
|
});
|
||||||
|
connections.globalTimeline.on('note', prepend);
|
||||||
} else if (props.src === 'mentions') {
|
} else if (props.src === 'mentions') {
|
||||||
connection = stream.useChannel('main');
|
connections.main = stream.useChannel('main');
|
||||||
connection.on('mention', prepend);
|
connections.main.on('mention', prepend);
|
||||||
} else if (props.src === 'directs') {
|
} else if (props.src === 'directs') {
|
||||||
const onNote = note => {
|
const onNote = note => {
|
||||||
if (note.visibility === 'specified') {
|
if (note.visibility === 'specified') {
|
||||||
prepend(note);
|
prepend(note);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
connection = stream.useChannel('main');
|
connections.main = stream.useChannel('main');
|
||||||
connection.on('mention', onNote);
|
connections.main.on('mention', onNote);
|
||||||
} else if (props.src === 'list') {
|
} else if (props.src === 'list') {
|
||||||
if (props.list == null) return;
|
if (props.list == null) return;
|
||||||
connection = stream.useChannel('userList', {
|
connections.userList = stream.useChannel('userList', {
|
||||||
withRenotes: props.withRenotes,
|
withRenotes: props.withRenotes,
|
||||||
withFiles: props.onlyFiles ? true : undefined,
|
withFiles: props.onlyFiles ? true : undefined,
|
||||||
listId: props.list,
|
listId: props.list,
|
||||||
});
|
});
|
||||||
|
connections.userList.on('note', prepend);
|
||||||
} else if (props.src === 'channel') {
|
} else if (props.src === 'channel') {
|
||||||
if (props.channel == null) return;
|
if (props.channel == null) return;
|
||||||
connection = stream.useChannel('channel', {
|
connections.channel = stream.useChannel('channel', {
|
||||||
channelId: props.channel,
|
channelId: props.channel,
|
||||||
});
|
});
|
||||||
|
connections.channel.on('note', prepend);
|
||||||
} else if (props.src === 'role') {
|
} else if (props.src === 'role') {
|
||||||
if (props.role == null) return;
|
if (props.role == null) return;
|
||||||
connection = stream.useChannel('roleTimeline', {
|
connections.roleTimeline = stream.useChannel('roleTimeline', {
|
||||||
roleId: props.role,
|
roleId: props.role,
|
||||||
});
|
});
|
||||||
|
connections.roleTimeline.on('note', prepend);
|
||||||
}
|
}
|
||||||
if (props.src !== 'directs' && props.src !== 'mentions') connection?.on('note', prepend);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function disconnectChannel() {
|
function disconnectChannel() {
|
||||||
if (connection) connection.dispose();
|
for (const key in connections) {
|
||||||
if (connection2) connection2.dispose();
|
const conn = connections[key as keyof typeof connections];
|
||||||
|
if (conn != null) {
|
||||||
|
conn.dispose();
|
||||||
|
connections[key as keyof typeof connections] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (store.s.realtimeMode) {
|
if (store.s.realtimeMode) {
|
||||||
|
|
|
@ -39,17 +39,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
export type Tab = {
|
export type Tab = {
|
||||||
key: string;
|
key: string;
|
||||||
onClick?: (ev: MouseEvent) => void;
|
onClick?: (ev: MouseEvent) => void;
|
||||||
} & (
|
iconOnly?: boolean;
|
||||||
| {
|
|
||||||
iconOnly?: false;
|
|
||||||
title: string;
|
title: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
}
|
};
|
||||||
| {
|
|
||||||
iconOnly: true;
|
|
||||||
icon: string;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
/* eslint-disable import/no-default-export */
|
/* eslint-disable import/no-default-export */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import MkTagItem from './MkTagItem.vue';
|
import MkTagItem from './MkTagItem.vue';
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ import { prefer } from '@/preferences.js';
|
||||||
type Ad = (typeof instance)['ads'][number];
|
type Ad = (typeof instance)['ads'][number];
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
preferForms: string[];
|
preferForms?: string[];
|
||||||
specify?: Ad;
|
specify?: Ad;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ const choseAd = (): Ad | null => {
|
||||||
ratio: 0,
|
ratio: 0,
|
||||||
} : ad);
|
} : ad);
|
||||||
|
|
||||||
let ads = allAds.filter(ad => props.preferForms.includes(ad.place));
|
let ads = props.preferForms ? allAds.filter(ad => props.preferForms!.includes(ad.place)) : allAds;
|
||||||
|
|
||||||
if (ads.length === 0) {
|
if (ads.length === 0) {
|
||||||
ads = allAds.filter(ad => ad.place === 'square');
|
ads = allAds.filter(ad => ad.place === 'square');
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { expect, waitFor } from '@storybook/test';
|
import { expect, waitFor } from '@storybook/test';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import MkError from './MkError.vue';
|
import MkError from './MkError.vue';
|
||||||
|
|
|
@ -39,17 +39,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
export type Tab = {
|
export type Tab = {
|
||||||
key: string;
|
key: string;
|
||||||
onClick?: (ev: MouseEvent) => void;
|
onClick?: (ev: MouseEvent) => void;
|
||||||
} & (
|
iconOnly?: boolean;
|
||||||
| {
|
|
||||||
iconOnly?: false;
|
|
||||||
title: string;
|
title: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
}
|
};
|
||||||
| {
|
|
||||||
iconOnly: true;
|
|
||||||
icon: string;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -59,7 +52,7 @@ import { prefer } from '@/preferences.js';
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
tabs?: Tab[];
|
tabs?: Tab[];
|
||||||
tab?: string;
|
tab?: string;
|
||||||
rootEl?: HTMLElement;
|
rootEl?: HTMLElement | null;
|
||||||
}>(), {
|
}>(), {
|
||||||
tabs: () => ([] as Tab[]),
|
tabs: () => ([] as Tab[]),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<slot></slot>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
</style>
|
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { commonHandlers } from '../../../.storybook/mocks.js';
|
import { commonHandlers } from '../../../.storybook/mocks.js';
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { Component, Ref } from 'vue';
|
import type { Component, Ref } from 'vue';
|
||||||
import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
import type { ComponentEmit, ComponentProps as CP } from 'vue-component-type-helpers';
|
||||||
import type { Form, GetFormResultType } from '@/utility/form.js';
|
import type { Form, GetFormResultType } from '@/utility/form.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import type { PostFormProps } from '@/types/post-form.js';
|
import type { PostFormProps } from '@/types/post-form.js';
|
||||||
|
@ -157,28 +157,9 @@ export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number {
|
||||||
return zIndexes[priority];
|
return zIndexes[priority];
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstanceType<typeof Component>['$emit'] だとインターセクション型が返ってきて
|
|
||||||
// 使い物にならないので、代わりに ['$props'] から色々省くことで emit の型を生成する
|
|
||||||
// FIXME: 何故か *.ts ファイルからだと型がうまく取れない?ことがあるのをなんとかしたい
|
|
||||||
type ComponentEmit<T> = T extends new () => { $props: infer Props }
|
|
||||||
? [keyof Pick<T, Extract<keyof T, `on${string}`>>] extends [never]
|
|
||||||
? Record<string, unknown> // *.ts ファイルから型がうまく取れないとき用(これがないと {} になって型エラーがうるさい)
|
|
||||||
: EmitsExtractor<Props>
|
|
||||||
: T extends (...args: any) => any
|
|
||||||
? ReturnType<T> extends { [x: string]: any; __ctx?: { [x: string]: any; props: infer Props } }
|
|
||||||
? [keyof Pick<T, Extract<keyof T, `on${string}`>>] extends [never]
|
|
||||||
? Record<string, unknown>
|
|
||||||
: EmitsExtractor<Props>
|
|
||||||
: never
|
|
||||||
: never;
|
|
||||||
|
|
||||||
// props に ref を許可するようにする
|
// props に ref を許可するようにする
|
||||||
type ComponentProps<T extends Component> = { [K in keyof CP<T>]: CP<T>[K] | Ref<CP<T>[K]> };
|
type ComponentProps<T extends Component> = { [K in keyof CP<T>]: CP<T>[K] | Ref<CP<T>[K]> };
|
||||||
|
|
||||||
type EmitsExtractor<T> = {
|
|
||||||
[K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K];
|
|
||||||
};
|
|
||||||
|
|
||||||
export function popup<T extends Component>(
|
export function popup<T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
props: ComponentProps<T>,
|
props: ComponentProps<T>,
|
||||||
|
@ -703,7 +684,7 @@ export async function cropImageFile(imageFile: File | Blob, options: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function popupMenu(items: MenuItem[], anchorElement?: HTMLElement | EventTarget | null, options?: {
|
export function popupMenu(items: (MenuItem | null)[], anchorElement?: HTMLElement | EventTarget | null, options?: {
|
||||||
align?: string;
|
align?: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
onClosing?: () => void;
|
onClosing?: () => void;
|
||||||
|
@ -715,7 +696,7 @@ export function popupMenu(items: MenuItem[], anchorElement?: HTMLElement | Event
|
||||||
let returnFocusTo = getHTMLElementOrNull(anchorElement) ?? getHTMLElementOrNull(window.document.activeElement);
|
let returnFocusTo = getHTMLElementOrNull(anchorElement) ?? getHTMLElementOrNull(window.document.activeElement);
|
||||||
return new Promise(resolve => nextTick(() => {
|
return new Promise(resolve => nextTick(() => {
|
||||||
const { dispose } = popup(MkPopupMenu, {
|
const { dispose } = popup(MkPopupMenu, {
|
||||||
items,
|
items: items.filter(x => x != null),
|
||||||
anchorElement,
|
anchorElement,
|
||||||
width: options?.width,
|
width: options?.width,
|
||||||
align: options?.align,
|
align: options?.align,
|
||||||
|
|
|
@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
|
|
||||||
<MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category">
|
<MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category ?? '___root___'">
|
||||||
<template #header>{{ category || i18n.ts.other }}</template>
|
<template #header>{{ category || i18n.ts.other }}</template>
|
||||||
<div :class="$style.emojis">
|
<div :class="$style.emojis">
|
||||||
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" :emoji="emoji"/>
|
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" :emoji="emoji"/>
|
||||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
|
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
|
||||||
<div style="overflow: clip;">
|
<div style="overflow: clip;">
|
||||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
|
<img :src="instance.iconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
|
||||||
<div :class="$style.bannerName">
|
<div :class="$style.bannerName">
|
||||||
<b>{{ instance.name ?? host }}</b>
|
<b>{{ instance.name ?? host }}</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -111,13 +111,13 @@ const props = defineProps<{
|
||||||
fileId: string,
|
fileId: string,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
async function fetch() {
|
async function _fetch_() {
|
||||||
file.value = await misskeyApi('drive/files/show', { fileId: props.fileId });
|
file.value = await misskeyApi('drive/files/show', { fileId: props.fileId });
|
||||||
info.value = await misskeyApi('admin/drive/show-file', { fileId: props.fileId });
|
info.value = await misskeyApi('admin/drive/show-file', { fileId: props.fileId });
|
||||||
isSensitive.value = file.value.isSensitive;
|
isSensitive.value = file.value.isSensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch();
|
_fetch_();
|
||||||
|
|
||||||
async function del() {
|
async function del() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
|
@ -172,7 +172,7 @@ const headerTabs = computed(() => [{
|
||||||
key: 'raw',
|
key: 'raw',
|
||||||
title: 'Raw data',
|
title: 'Raw data',
|
||||||
icon: 'ti ti-code',
|
icon: 'ti ti-code',
|
||||||
}]);
|
}].filter(x => x != null));
|
||||||
|
|
||||||
definePage(() => ({
|
definePage(() => ({
|
||||||
title: file.value ? `${i18n.ts.file}: ${file.value.name}` : i18n.ts.file,
|
title: file.value ? `${i18n.ts.file}: ${file.value.name}` : i18n.ts.file,
|
||||||
|
|
|
@ -143,8 +143,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
|
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="expandedRoleIds.includes(role.id)" :class="$style.roleItemSub">
|
<div v-if="expandedRoleIds.includes(role.id)" :class="$style.roleItemSub">
|
||||||
<div>Assigned: <MkTime :time="info.roleAssigns.find(a => a.roleId === role.id).createdAt" mode="detail"/></div>
|
<div>Assigned: <MkTime :time="info.roleAssigns.find(a => a.roleId === role.id)!.createdAt" mode="detail"/></div>
|
||||||
<div v-if="info.roleAssigns.find(a => a.roleId === role.id).expiresAt">Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id).expiresAt).toLocaleString() }}</div>
|
<div v-if="info.roleAssigns.find(a => a.roleId === role.id)!.expiresAt">Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id)!.expiresAt!).toLocaleString() }}</div>
|
||||||
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -140,15 +140,15 @@ function toggleDayOfWeek(ad, index) {
|
||||||
|
|
||||||
function add() {
|
function add() {
|
||||||
ads.value.unshift({
|
ads.value.unshift({
|
||||||
id: null,
|
id: '',
|
||||||
memo: '',
|
memo: '',
|
||||||
place: 'square',
|
place: 'square',
|
||||||
priority: 'middle',
|
priority: 'middle',
|
||||||
ratio: 1,
|
ratio: 1,
|
||||||
url: '',
|
url: '',
|
||||||
imageUrl: null,
|
imageUrl: '',
|
||||||
expiresAt: null,
|
expiresAt: new Date().toISOString(),
|
||||||
startsAt: null,
|
startsAt: new Date().toISOString(),
|
||||||
dayOfWeek: 0,
|
dayOfWeek: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ function remove(ad) {
|
||||||
}).then(({ canceled }) => {
|
}).then(({ canceled }) => {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
ads.value = ads.value.filter(x => x !== ad);
|
ads.value = ads.value.filter(x => x !== ad);
|
||||||
if (ad.id == null) return;
|
if (ad.id === '') return;
|
||||||
os.apiWithDialog('admin/ad/delete', {
|
os.apiWithDialog('admin/ad/delete', {
|
||||||
id: ad.id,
|
id: ad.id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
@ -170,7 +170,7 @@ function remove(ad) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(ad) {
|
function save(ad) {
|
||||||
if (ad.id == null) {
|
if (ad.id === '') {
|
||||||
misskeyApi('admin/ad/create', {
|
misskeyApi('admin/ad/create', {
|
||||||
...ad,
|
...ad,
|
||||||
expiresAt: new Date(ad.expiresAt).getTime(),
|
expiresAt: new Date(ad.expiresAt).getTime(),
|
||||||
|
@ -207,7 +207,7 @@ function save(ad) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function more() {
|
function more() {
|
||||||
misskeyApi('admin/ad/list', { untilId: ads.value.reduce((acc, ad) => ad.id != null ? ad : acc).id, publishing: publishing }).then(adsResponse => {
|
misskeyApi('admin/ad/list', { untilId: ads.value.reduce((acc, ad) => ad.id !== '' ? ad : acc).id, publishing: publishing }).then(adsResponse => {
|
||||||
if (adsResponse == null) return;
|
if (adsResponse == null) return;
|
||||||
ads.value = ads.value.concat(adsResponse.map(r => {
|
ads.value = ads.value.concat(adsResponse.map(r => {
|
||||||
const exdate = new Date(r.expiresAt);
|
const exdate = new Date(r.expiresAt);
|
||||||
|
|
|
@ -25,11 +25,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</SearchMarker>
|
</SearchMarker>
|
||||||
|
|
||||||
<SearchMarker :keywords="['ugc', 'content', 'visibility', 'visitor', 'guest']">
|
<SearchMarker :keywords="['ugc', 'content', 'visibility', 'visitor', 'guest']">
|
||||||
<MkSelect v-model="ugcVisibilityForVisitor" @update:modelValue="onChange_ugcVisibilityForVisitor">
|
<MkSelect
|
||||||
|
v-model="ugcVisibilityForVisitor" :items="[{
|
||||||
|
value: 'all',
|
||||||
|
label: i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.all,
|
||||||
|
}, {
|
||||||
|
value: 'local',
|
||||||
|
label: i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.localOnly + ' (' + i18n.ts.recommended + ')',
|
||||||
|
}, {
|
||||||
|
value: 'none',
|
||||||
|
label: i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.none,
|
||||||
|
}] as const" @update:modelValue="onChange_ugcVisibilityForVisitor"
|
||||||
|
>
|
||||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor }}</SearchLabel></template>
|
<template #label><SearchLabel>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor }}</SearchLabel></template>
|
||||||
<option value="all">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.all }}</option>
|
|
||||||
<option value="local">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.localOnly }} ({{ i18n.ts.recommended }})</option>
|
|
||||||
<option value="none">{{ i18n.ts._serverSettings._userGeneratedContentsVisibilityForVisitor.none }}</option>
|
|
||||||
<template #caption>
|
<template #caption>
|
||||||
<div><SearchText>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description }}</SearchText></div>
|
<div><SearchText>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description }}</SearchText></div>
|
||||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> <SearchText>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description2 }}</SearchText></div>
|
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> <SearchText>{{ i18n.ts._serverSettings.userGeneratedContentsVisibilityForVisitor_description2 }}</SearchText></div>
|
||||||
|
@ -158,6 +166,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import XServerRules from './server-rules.vue';
|
import XServerRules from './server-rules.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
@ -212,7 +221,7 @@ function onChange_emailRequiredForSignup(value: boolean) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChange_ugcVisibilityForVisitor(value: string) {
|
function onChange_ugcVisibilityForVisitor(value: Misskey.entities.AdminUpdateMetaRequest['ugcVisibilityForVisitor']) {
|
||||||
os.apiWithDialog('admin/update-meta', {
|
os.apiWithDialog('admin/update-meta', {
|
||||||
ugcVisibilityForVisitor: value,
|
ugcVisibilityForVisitor: value,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|
|
@ -77,7 +77,7 @@ paginator.init();
|
||||||
const timeline = computed(() => {
|
const timeline = computed(() => {
|
||||||
return paginator.items.value.map(x => ({
|
return paginator.items.value.map(x => ({
|
||||||
id: x.id,
|
id: x.id,
|
||||||
timestamp: x.createdAt,
|
timestamp: new Date(x.createdAt).getTime(),
|
||||||
data: x,
|
data: x,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import type { StoryObj } from '@storybook/vue3';
|
import type { StoryObj } from '@storybook/vue3';
|
||||||
import { http, HttpResponse } from 'msw';
|
import { http, HttpResponse } from 'msw';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from 'storybook/actions';
|
||||||
import { commonHandlers } from '../../../.storybook/mocks.js';
|
import { commonHandlers } from '../../../.storybook/mocks.js';
|
||||||
import overview_ap_requests from './overview.ap-requests.vue';
|
import overview_ap_requests from './overview.ap-requests.vue';
|
||||||
export const Default = {
|
export const Default = {
|
||||||
|
|
|
@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton primary @click="read(announcement)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton>
|
<MkButton primary @click="read(announcement)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkError v-else-if="error" @retry="fetch()"/>
|
<MkError v-else-if="error" @retry="_fetch_()"/>
|
||||||
<MkLoading v-else/>
|
<MkLoading v-else/>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,7 +66,7 @@ const announcement = ref<Misskey.entities.Announcement | null>(null);
|
||||||
const error = ref<any>(null);
|
const error = ref<any>(null);
|
||||||
const path = computed(() => props.announcementId);
|
const path = computed(() => props.announcementId);
|
||||||
|
|
||||||
function fetch() {
|
function _fetch_() {
|
||||||
announcement.value = null;
|
announcement.value = null;
|
||||||
misskeyApi('announcements/show', {
|
misskeyApi('announcements/show', {
|
||||||
announcementId: props.announcementId,
|
announcementId: props.announcementId,
|
||||||
|
@ -96,7 +96,7 @@ async function read(target: Misskey.entities.Announcement): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => path.value, fetch, { immediate: true });
|
watch(() => path.value, _fetch_, { immediate: true });
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
|
|
|
@ -75,14 +75,15 @@ onMounted(async () => {
|
||||||
if (!$i) return;
|
if (!$i) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.value = await misskeyApi('auth/session/show', {
|
const result = await misskeyApi('auth/session/show', {
|
||||||
token: props.token,
|
token: props.token,
|
||||||
});
|
});
|
||||||
|
session.value = result;
|
||||||
|
|
||||||
// 既に連携していた場合
|
// 既に連携していた場合
|
||||||
if (session.value.app.isAuthorized) {
|
if (result.app.isAuthorized) {
|
||||||
await misskeyApi('auth/accept', {
|
await misskeyApi('auth/accept', {
|
||||||
token: session.value.token,
|
token: result.token,
|
||||||
});
|
});
|
||||||
accepted();
|
accepted();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -92,7 +92,7 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const channel = ref<Misskey.entities.Channel | null>(null);
|
const channel = ref<Misskey.entities.Channel | null>(null);
|
||||||
const name = ref<string | null>(null);
|
const name = ref<string>('');
|
||||||
const description = ref<string | null>(null);
|
const description = ref<string | null>(null);
|
||||||
const bannerUrl = ref<string | null>(null);
|
const bannerUrl = ref<string | null>(null);
|
||||||
const bannerId = ref<string | null>(null);
|
const bannerId = ref<string | null>(null);
|
||||||
|
@ -114,20 +114,22 @@ watch(() => bannerId.value, async () => {
|
||||||
async function fetchChannel() {
|
async function fetchChannel() {
|
||||||
if (props.channelId == null) return;
|
if (props.channelId == null) return;
|
||||||
|
|
||||||
channel.value = await misskeyApi('channels/show', {
|
const result = await misskeyApi('channels/show', {
|
||||||
channelId: props.channelId,
|
channelId: props.channelId,
|
||||||
});
|
});
|
||||||
|
|
||||||
name.value = channel.value.name;
|
name.value = result.name;
|
||||||
description.value = channel.value.description;
|
description.value = result.description;
|
||||||
bannerId.value = channel.value.bannerId;
|
bannerId.value = result.bannerId;
|
||||||
bannerUrl.value = channel.value.bannerUrl;
|
bannerUrl.value = result.bannerUrl;
|
||||||
isSensitive.value = channel.value.isSensitive;
|
isSensitive.value = result.isSensitive;
|
||||||
pinnedNotes.value = channel.value.pinnedNoteIds.map(id => ({
|
pinnedNotes.value = result.pinnedNoteIds.map(id => ({
|
||||||
id,
|
id,
|
||||||
}));
|
}));
|
||||||
color.value = channel.value.color;
|
color.value = result.color;
|
||||||
allowRenoteToExternal.value = channel.value.allowRenoteToExternal;
|
allowRenoteToExternal.value = result.allowRenoteToExternal;
|
||||||
|
|
||||||
|
channel.value = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchChannel();
|
fetchChannel();
|
||||||
|
@ -154,15 +156,17 @@ function save() {
|
||||||
name: name.value,
|
name: name.value,
|
||||||
description: description.value,
|
description: description.value,
|
||||||
bannerId: bannerId.value,
|
bannerId: bannerId.value,
|
||||||
pinnedNoteIds: pinnedNotes.value.map(x => x.id),
|
|
||||||
color: color.value,
|
color: color.value,
|
||||||
isSensitive: isSensitive.value,
|
isSensitive: isSensitive.value,
|
||||||
allowRenoteToExternal: allowRenoteToExternal.value,
|
allowRenoteToExternal: allowRenoteToExternal.value,
|
||||||
};
|
} satisfies Misskey.entities.ChannelsCreateRequest;
|
||||||
|
|
||||||
if (props.channelId) {
|
if (props.channelId != null) {
|
||||||
params.channelId = props.channelId;
|
os.apiWithDialog('channels/update', {
|
||||||
os.apiWithDialog('channels/update', params);
|
...params,
|
||||||
|
channelId: props.channelId,
|
||||||
|
pinnedNoteIds: pinnedNotes.value.map(x => x.id),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
os.apiWithDialog('channels/create', params).then(created => {
|
os.apiWithDialog('channels/create', params).then(created => {
|
||||||
router.push('/channels/:channelId', {
|
router.push('/channels/:channelId', {
|
||||||
|
@ -175,12 +179,13 @@ function save() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function archive() {
|
async function archive() {
|
||||||
|
if (props.channelId == null) return;
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
title: i18n.tsx.channelArchiveConfirmTitle({ name: name.value }),
|
title: i18n.tsx.channelArchiveConfirmTitle({ name: name.value }),
|
||||||
text: i18n.ts.channelArchiveConfirmDescription,
|
text: i18n.ts.channelArchiveConfirmDescription,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
misskeyApi('channels/update', {
|
misskeyApi('channels/update', {
|
||||||
|
|
|
@ -312,6 +312,7 @@ const headerActions = computed(() => [{
|
||||||
handler: add,
|
handler: add,
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-dots',
|
icon: 'ti ti-dots',
|
||||||
|
text: i18n.ts.more,
|
||||||
handler: menu,
|
handler: menu,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ const folderHierarchy = computed(() => {
|
||||||
});
|
});
|
||||||
const isImage = computed(() => file.value?.type.startsWith('image/'));
|
const isImage = computed(() => file.value?.type.startsWith('image/'));
|
||||||
|
|
||||||
async function fetch() {
|
async function _fetch_() {
|
||||||
fetching.value = true;
|
fetching.value = true;
|
||||||
|
|
||||||
file.value = await misskeyApi('drive/files/show', {
|
file.value = await misskeyApi('drive/files/show', {
|
||||||
|
@ -119,7 +119,7 @@ async function fetch() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function postThis() {
|
function postThis() {
|
||||||
if (!file.value) return;
|
if (file.value == null) return;
|
||||||
|
|
||||||
os.post({
|
os.post({
|
||||||
initialFiles: [file.value],
|
initialFiles: [file.value],
|
||||||
|
@ -127,26 +127,28 @@ function postThis() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function move() {
|
function move() {
|
||||||
if (!file.value) return;
|
if (file.value == null) return;
|
||||||
|
|
||||||
|
const f = file.value;
|
||||||
|
|
||||||
selectDriveFolder(null).then(folder => {
|
selectDriveFolder(null).then(folder => {
|
||||||
misskeyApi('drive/files/update', {
|
misskeyApi('drive/files/update', {
|
||||||
fileId: file.value.id,
|
fileId: f.id,
|
||||||
folderId: folder[0] ? folder[0].id : null,
|
folderId: folder[0] ? folder[0].id : null,
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
await fetch();
|
await _fetch_();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSensitive() {
|
function toggleSensitive() {
|
||||||
if (!file.value) return;
|
if (file.value == null) return;
|
||||||
|
|
||||||
os.apiWithDialog('drive/files/update', {
|
os.apiWithDialog('drive/files/update', {
|
||||||
fileId: file.value.id,
|
fileId: file.value.id,
|
||||||
isSensitive: !file.value.isSensitive,
|
isSensitive: !file.value.isSensitive,
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
await fetch();
|
await _fetch_();
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
@ -157,7 +159,9 @@ function toggleSensitive() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function rename() {
|
function rename() {
|
||||||
if (!file.value) return;
|
if (file.value == null) return;
|
||||||
|
|
||||||
|
const f = file.value;
|
||||||
|
|
||||||
os.inputText({
|
os.inputText({
|
||||||
title: i18n.ts.renameFile,
|
title: i18n.ts.renameFile,
|
||||||
|
@ -166,16 +170,18 @@ function rename() {
|
||||||
}).then(({ canceled, result: name }) => {
|
}).then(({ canceled, result: name }) => {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
os.apiWithDialog('drive/files/update', {
|
os.apiWithDialog('drive/files/update', {
|
||||||
fileId: file.value.id,
|
fileId: f.id,
|
||||||
name: name,
|
name: name,
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
await fetch();
|
await _fetch_();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function describe() {
|
async function describe() {
|
||||||
if (!file.value) return;
|
if (file.value == null) return;
|
||||||
|
|
||||||
|
const f = file.value;
|
||||||
|
|
||||||
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkFileCaptionEditWindow.vue').then(x => x.default), {
|
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkFileCaptionEditWindow.vue').then(x => x.default), {
|
||||||
default: file.value.comment ?? '',
|
default: file.value.comment ?? '',
|
||||||
|
@ -183,10 +189,10 @@ async function describe() {
|
||||||
}, {
|
}, {
|
||||||
done: caption => {
|
done: caption => {
|
||||||
os.apiWithDialog('drive/files/update', {
|
os.apiWithDialog('drive/files/update', {
|
||||||
fileId: file.value.id,
|
fileId: f.id,
|
||||||
comment: caption.length === 0 ? null : caption,
|
comment: caption.length === 0 ? null : caption,
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
await fetch();
|
await _fetch_();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
|
@ -194,7 +200,7 @@ async function describe() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteFile() {
|
async function deleteFile() {
|
||||||
if (!file.value) return;
|
if (file.value == null) return;
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
@ -212,7 +218,7 @@ async function deleteFile() {
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await fetch();
|
await _fetch_();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -26,18 +26,12 @@ import { definePage } from '@/page.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
tag?: string;
|
|
||||||
initialTab?: string;
|
initialTab?: string;
|
||||||
}>(), {
|
}>(), {
|
||||||
initialTab: 'featured',
|
initialTab: 'featured',
|
||||||
});
|
});
|
||||||
|
|
||||||
const tab = ref(props.initialTab);
|
const tab = ref(props.initialTab);
|
||||||
const tagsEl = useTemplateRef('tagsEl');
|
|
||||||
|
|
||||||
watch(() => props.tag, () => {
|
|
||||||
if (tagsEl.value) tagsEl.value.toggleContent(props.tag == null);
|
|
||||||
});
|
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
|
|
|
@ -383,7 +383,7 @@ if (props.id) {
|
||||||
|
|
||||||
const title = ref(flash.value?.title ?? 'New Play');
|
const title = ref(flash.value?.title ?? 'New Play');
|
||||||
const summary = ref(flash.value?.summary ?? '');
|
const summary = ref(flash.value?.summary ?? '');
|
||||||
const permissions = ref(flash.value?.permissions ?? []);
|
const permissions = ref([]); // not implemented yet
|
||||||
const visibility = ref<'private' | 'public'>(flash.value?.visibility ?? 'public');
|
const visibility = ref<'private' | 'public'>(flash.value?.visibility ?? 'public');
|
||||||
const script = ref(flash.value?.script ?? PRESET_DEFAULT);
|
const script = ref(flash.value?.script ?? PRESET_DEFAULT);
|
||||||
|
|
||||||
|
@ -412,9 +412,9 @@ function selectPreset(ev: MouseEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
if (flash.value) {
|
if (flash.value != null) {
|
||||||
os.apiWithDialog('flash/update', {
|
os.apiWithDialog('flash/update', {
|
||||||
flashId: props.id,
|
flashId: flash.value.id,
|
||||||
title: title.value,
|
title: title.value,
|
||||||
summary: summary.value,
|
summary: summary.value,
|
||||||
permissions: permissions.value,
|
permissions: permissions.value,
|
||||||
|
@ -448,6 +448,8 @@ function show() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function del() {
|
async function del() {
|
||||||
|
if (flash.value == null) return;
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.tsx.deleteAreYouSure({ x: flash.value.title }),
|
text: i18n.tsx.deleteAreYouSure({ x: flash.value.title }),
|
||||||
|
@ -455,7 +457,7 @@ async function del() {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
await os.apiWithDialog('flash/delete', {
|
await os.apiWithDialog('flash/delete', {
|
||||||
flashId: props.id,
|
flashId: flash.value.id,
|
||||||
});
|
});
|
||||||
router.push('/play');
|
router.push('/play');
|
||||||
}
|
}
|
||||||
|
@ -468,6 +470,7 @@ definePage(() => ({
|
||||||
title: flash.value ? `${i18n.ts._play.edit}: ${flash.value.title}` : i18n.ts._play.new,
|
title: flash.value ? `${i18n.ts._play.edit}: ${flash.value.title}` : i18n.ts._play.new,
|
||||||
}));
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.footer {
|
.footer {
|
||||||
backdrop-filter: var(--MI-blur, blur(15px));
|
backdrop-filter: var(--MI-blur, blur(15px));
|
||||||
|
|
|
@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
|
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
|
<div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : '' }">
|
||||||
<div class="name">{{ file.name }}</div>
|
<div class="name">{{ file.name }}</div>
|
||||||
<button v-tooltip="i18n.ts.remove" class="remove _button" @click="remove(file)"><i class="ti ti-x"></i></button>
|
<button v-tooltip="i18n.ts.remove" class="remove _button" @click="remove(file)"><i class="ti ti-x"></i></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,7 +88,7 @@ async function save() {
|
||||||
router.push('/gallery/:postId', {
|
router.push('/gallery/:postId', {
|
||||||
params: {
|
params: {
|
||||||
postId: props.postId,
|
postId: props.postId,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const created = await os.apiWithDialog('gallery/posts/create', {
|
const created = await os.apiWithDialog('gallery/posts/create', {
|
||||||
|
@ -100,7 +100,7 @@ async function save() {
|
||||||
router.push('/gallery/:postId', {
|
router.push('/gallery/:postId', {
|
||||||
params: {
|
params: {
|
||||||
postId: created.id,
|
postId: created.id,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ function close_(): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetch() {
|
async function _fetch_() {
|
||||||
if (!url.value || !hash.value) {
|
if (!url.value || !hash.value) {
|
||||||
errorKV.value = {
|
errorKV.value = {
|
||||||
title: i18n.ts._externalResourceInstaller._errors._invalidParams.title,
|
title: i18n.ts._externalResourceInstaller._errors._invalidParams.title,
|
||||||
|
@ -161,7 +161,7 @@ async function fetch() {
|
||||||
},
|
},
|
||||||
raw: res.data,
|
raw: res.data,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
switch (err.message.toLowerCase()) {
|
switch (err.message.toLowerCase()) {
|
||||||
case 'this theme is already installed':
|
case 'this theme is already installed':
|
||||||
errorKV.value = {
|
errorKV.value = {
|
||||||
|
@ -229,7 +229,7 @@ async function install() {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
url.value = urlParams.get('url');
|
url.value = urlParams.get('url');
|
||||||
hash.value = urlParams.get('hash');
|
hash.value = urlParams.get('hash');
|
||||||
fetch();
|
_fetch_();
|
||||||
|
|
||||||
definePage(() => ({
|
definePage(() => ({
|
||||||
title: i18n.ts._externalResourceInstaller.title,
|
title: i18n.ts._externalResourceInstaller.title,
|
||||||
|
|
|
@ -198,7 +198,7 @@ if (iAmModerator) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetch(): Promise<void> {
|
async function _fetch_(): Promise<void> {
|
||||||
if (iAmAdmin) {
|
if (iAmAdmin) {
|
||||||
meta.value = await misskeyApi('admin/meta');
|
meta.value = await misskeyApi('admin/meta');
|
||||||
}
|
}
|
||||||
|
@ -276,7 +276,7 @@ function refreshMetadata(): void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch();
|
_fetch_();
|
||||||
|
|
||||||
const headerActions = computed(() => [{
|
const headerActions = computed(() => [{
|
||||||
text: `https://${props.host}`,
|
text: `https://${props.host}`,
|
||||||
|
|
|
@ -103,6 +103,7 @@ definePage(() => ({
|
||||||
icon: 'ti ti-list',
|
icon: 'ti ti-list',
|
||||||
}));
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.userItem {
|
.userItem {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -29,7 +29,7 @@ import MkButton from '@/components/MkButton.vue';
|
||||||
|
|
||||||
const state = ref<'fetching' | 'done'>('fetching');
|
const state = ref<'fetching' | 'done'>('fetching');
|
||||||
|
|
||||||
function fetch() {
|
function _fetch_() {
|
||||||
const params = new URL(window.location.href).searchParams;
|
const params = new URL(window.location.href).searchParams;
|
||||||
|
|
||||||
// acctのほうはdeprecated
|
// acctのほうはdeprecated
|
||||||
|
@ -44,20 +44,18 @@ function fetch() {
|
||||||
if (uri.startsWith('https://')) {
|
if (uri.startsWith('https://')) {
|
||||||
promise = misskeyApi('ap/show', {
|
promise = misskeyApi('ap/show', {
|
||||||
uri,
|
uri,
|
||||||
});
|
}).then(res => {
|
||||||
|
|
||||||
promise.then(res => {
|
|
||||||
if (res.type === 'User') {
|
if (res.type === 'User') {
|
||||||
mainRouter.replace('/@:acct/:page?', {
|
mainRouter.replace('/@:acct/:page?', {
|
||||||
params: {
|
params: {
|
||||||
acct: res.host != null ? `${res.object.username}@${res.object.host}` : res.object.username,
|
acct: res.host != null ? `${res.object.username}@${res.object.host}` : res.object.username,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} else if (res.type === 'Note') {
|
} else if (res.type === 'Note') {
|
||||||
mainRouter.replace('/notes/:noteId/:initialTab?', {
|
mainRouter.replace('/notes/:noteId/:initialTab?', {
|
||||||
params: {
|
params: {
|
||||||
noteId: res.object.id,
|
noteId: res.object.id,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
os.alert({
|
os.alert({
|
||||||
|
@ -70,12 +68,11 @@ function fetch() {
|
||||||
if (uri.startsWith('acct:')) {
|
if (uri.startsWith('acct:')) {
|
||||||
uri = uri.slice(5);
|
uri = uri.slice(5);
|
||||||
}
|
}
|
||||||
promise = misskeyApi('users/show', Misskey.acct.parse(uri));
|
promise = misskeyApi('users/show', Misskey.acct.parse(uri)).then(user => {
|
||||||
promise.then(user => {
|
|
||||||
mainRouter.replace('/@:acct/:page?', {
|
mainRouter.replace('/@:acct/:page?', {
|
||||||
params: {
|
params: {
|
||||||
acct: user.host != null ? `${user.username}@${user.host}` : user.username,
|
acct: user.host != null ? `${user.username}@${user.host}` : user.username,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -96,7 +93,7 @@ function goToMisskey(): void {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch();
|
_fetch_();
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
|
|
|
@ -30,11 +30,11 @@ import { antennasCache } from '@/cache.js';
|
||||||
|
|
||||||
const antennas = computed(() => antennasCache.value.value ?? []);
|
const antennas = computed(() => antennasCache.value.value ?? []);
|
||||||
|
|
||||||
function fetch() {
|
function _fetch_() {
|
||||||
antennasCache.fetch();
|
antennasCache.fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch();
|
_fetch_();
|
||||||
|
|
||||||
const headerActions = computed(() => [{
|
const headerActions = computed(() => [{
|
||||||
asFullButton: true,
|
asFullButton: true,
|
||||||
|
@ -42,7 +42,7 @@ const headerActions = computed(() => [{
|
||||||
text: i18n.ts.reload,
|
text: i18n.ts.reload,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
antennasCache.delete();
|
antennasCache.delete();
|
||||||
fetch();
|
_fetch_();
|
||||||
},
|
},
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<div v-if="items.length > 0" class="_gaps">
|
<div v-if="items.length > 0" class="_gaps">
|
||||||
<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/my/lists/${ list.id }`">
|
<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/my/lists/${ list.id }`">
|
||||||
<div style="margin-bottom: 4px;">{{ list.name }} <span :class="$style.nUsers">({{ i18n.tsx.nUsers({ n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }})</span></div>
|
<div style="margin-bottom: 4px;">{{ list.name }} <span :class="$style.nUsers">({{ i18n.tsx.nUsers({ n: `${list.userIds!.length}/${$i.policies['userEachUserListsLimit']}` }) }})</span></div>
|
||||||
<MkAvatars :userIds="list.userIds" :limit="10"/>
|
<MkAvatars :userIds="list.userIds!" :limit="10"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,20 +40,20 @@ const $i = ensureSignin();
|
||||||
|
|
||||||
const items = computed(() => userListsCache.value.value ?? []);
|
const items = computed(() => userListsCache.value.value ?? []);
|
||||||
|
|
||||||
function fetch() {
|
function _fetch_() {
|
||||||
userListsCache.fetch();
|
userListsCache.fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch();
|
_fetch_();
|
||||||
|
|
||||||
async function create() {
|
async function create() {
|
||||||
const { canceled, result: name } = await os.inputText({
|
const { canceled, result: name } = await os.inputText({
|
||||||
title: i18n.ts.enterListName,
|
title: i18n.ts.enterListName,
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled || name == null) return;
|
||||||
await os.apiWithDialog('users/lists/create', { name: name });
|
await os.apiWithDialog('users/lists/create', { name: name });
|
||||||
userListsCache.delete();
|
userListsCache.delete();
|
||||||
fetch();
|
_fetch_();
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => [{
|
const headerActions = computed(() => [{
|
||||||
|
@ -62,7 +62,7 @@ const headerActions = computed(() => [{
|
||||||
text: i18n.ts.reload,
|
text: i18n.ts.reload,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
userListsCache.delete();
|
userListsCache.delete();
|
||||||
fetch();
|
_fetch_();
|
||||||
},
|
},
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ definePage(() => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
fetch();
|
_fetch_();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<MkFolder defaultOpen>
|
<MkFolder defaultOpen>
|
||||||
<template #label>{{ i18n.ts.members }}</template>
|
<template #label>{{ i18n.ts.members }}</template>
|
||||||
<template #caption>{{ i18n.tsx.nUsers({ n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }}</template>
|
<template #caption>{{ i18n.tsx.nUsers({ n: `${list.userIds!.length}/${$i.policies['userEachUserListsLimit']}` }) }}</template>
|
||||||
|
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
|
<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
|
||||||
|
|
|
@ -126,7 +126,7 @@ function fetchNote() {
|
||||||
noteId: props.noteId,
|
noteId: props.noteId,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
note.value = res;
|
note.value = res;
|
||||||
const appearNote = getAppearNote(res);
|
const appearNote = getAppearNote(res) ?? res;
|
||||||
// 古いノートは被クリップ数をカウントしていないので、2023-10-01以前のものは強制的にnotes/clipsを叩く
|
// 古いノートは被クリップ数をカウントしていないので、2023-10-01以前のものは強制的にnotes/clipsを叩く
|
||||||
if ((appearNote.clippedCount ?? 0) > 0 || new Date(appearNote.createdAt).getTime() < new Date('2023-10-01').getTime()) {
|
if ((appearNote.clippedCount ?? 0) > 0 || new Date(appearNote.createdAt).getTime() < new Date('2023-10-01').getTime()) {
|
||||||
misskeyApi('notes/clips', {
|
misskeyApi('notes/clips', {
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { Paginator } from '@/utility/paginator.js';
|
||||||
|
|
||||||
const tab = ref('all');
|
const tab = ref('all');
|
||||||
const includeTypes = ref<string[] | null>(null);
|
const includeTypes = ref<string[] | null>(null);
|
||||||
const excludeTypes = computed(() => includeTypes.value ? notificationTypes.filter(t => !includeTypes.value.includes(t)) : null);
|
const excludeTypes = computed(() => includeTypes.value ? notificationTypes.filter(t => !includeTypes.value!.includes(t)) : null);
|
||||||
|
|
||||||
const mentionsPaginator = markRaw(new Paginator('notes/mentions', {
|
const mentionsPaginator = markRaw(new Paginator('notes/mentions', {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
@ -71,7 +71,7 @@ const headerActions = computed(() => [tab.value === 'all' ? {
|
||||||
text: i18n.ts.markAllAsRead,
|
text: i18n.ts.markAllAsRead,
|
||||||
icon: 'ti ti-check',
|
icon: 'ti ti-check',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
os.apiWithDialog('notifications/mark-all-as-read');
|
os.apiWithDialog('notifications/mark-all-as-read', {});
|
||||||
},
|
},
|
||||||
} : undefined].filter(x => x !== undefined));
|
} : undefined].filter(x => x !== undefined));
|
||||||
|
|
||||||
|
|
|
@ -62,10 +62,10 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const scope = computed(() => props.path.split('/').slice(0, -1));
|
const scope = computed(() => props.path.split('/').slice(0, -1));
|
||||||
const key = computed(() => props.path.split('/').at(-1));
|
const key = computed(() => props.path.split('/').at(-1)!);
|
||||||
|
|
||||||
const value = ref<any>(null);
|
const value = ref<any>(null);
|
||||||
const valueForEditor = ref<string | null>(null);
|
const valueForEditor = ref<string>('');
|
||||||
|
|
||||||
function fetchValue() {
|
function fetchValue() {
|
||||||
misskeyApi('i/registry/get-detail', {
|
misskeyApi('i/registry/get-detail', {
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton>
|
<MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton>
|
||||||
|
|
||||||
<div v-if="scopesWithDomain" class="_gaps_m">
|
<div v-if="scopesWithDomain" class="_gaps_m">
|
||||||
<FormSection v-for="domain in scopesWithDomain" :key="domain.domain">
|
<FormSection v-for="domain in scopesWithDomain" :key="domain.domain ?? 'system'">
|
||||||
<template #label>{{ domain.domain ? domain.domain.toUpperCase() : i18n.ts.system }}</template>
|
<template #label>{{ domain.domain ? domain.domain.toUpperCase() : i18n.ts.system }}</template>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<FormLink v-for="scope in domain.scopes" :to="`/registry/keys/${domain.domain ?? '@'}/${scope.join('/')}`" class="_monospace">{{ scope.length === 0 ? '(root)' : scope.join('/') }}</FormLink>
|
<FormLink v-for="scope in domain.scopes" :to="`/registry/keys/${domain.domain ?? '@'}/${scope.join('/')}`" class="_monospace">{{ scope.length === 0 ? '(root)' : scope.join('/') }}</FormLink>
|
||||||
|
|
|
@ -34,6 +34,7 @@ const props = defineProps<{
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
|
if (props.token == null) return;
|
||||||
await os.apiWithDialog('reset-password', {
|
await os.apiWithDialog('reset-password', {
|
||||||
token: props.token,
|
token: props.token,
|
||||||
password: password.value,
|
password: password.value,
|
||||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<div v-if="$i.twoFactorEnabled" class="_gaps_s">
|
<div v-if="$i.twoFactorEnabled" class="_gaps_s">
|
||||||
<div v-text="i18n.ts._2fa.alreadyRegistered"/>
|
<div v-text="i18n.ts._2fa.alreadyRegistered"/>
|
||||||
<template v-if="$i.securityKeysList.length > 0">
|
<template v-if="$i.securityKeysList!.length > 0">
|
||||||
<MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton>
|
<MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton>
|
||||||
<MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo>
|
<MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo>
|
||||||
</template>
|
</template>
|
||||||
|
@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<MkButton primary @click="addSecurityKey">{{ i18n.ts._2fa.registerSecurityKey }}</MkButton>
|
<MkButton primary @click="addSecurityKey">{{ i18n.ts._2fa.registerSecurityKey }}</MkButton>
|
||||||
<MkFolder v-for="key in $i.securityKeysList" :key="key.id">
|
<MkFolder v-for="key in $i.securityKeysList!" :key="key.id">
|
||||||
<template #label>{{ key.name }}</template>
|
<template #label>{{ key.name }}</template>
|
||||||
<template #suffix><I18n :src="i18n.ts.lastUsedAt"><template #t><MkTime :time="key.lastUsed"/></template></I18n></template>
|
<template #suffix><I18n :src="i18n.ts.lastUsedAt"><template #t><MkTime :time="key.lastUsed"/></template></I18n></template>
|
||||||
<div class="_buttons">
|
<div class="_buttons">
|
||||||
|
@ -72,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</SearchMarker>
|
</SearchMarker>
|
||||||
|
|
||||||
<SearchMarker :keywords="['password', 'less', 'key', 'passkey', 'login', 'signin']">
|
<SearchMarker :keywords="['password', 'less', 'key', 'passkey', 'login', 'signin']">
|
||||||
<MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :modelValue="usePasswordLessLogin" @update:modelValue="v => updatePasswordLessLogin(v)">
|
<MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList!.length === 0" :modelValue="usePasswordLessLogin" @update:modelValue="v => updatePasswordLessLogin(v)">
|
||||||
<template #label><SearchLabel>{{ i18n.ts.passwordLessLogin }}</SearchLabel></template>
|
<template #label><SearchLabel>{{ i18n.ts.passwordLessLogin }}</SearchLabel></template>
|
||||||
<template #caption><SearchText>{{ i18n.ts.passwordLessLoginDescription }}</SearchText></template>
|
<template #caption><SearchText>{{ i18n.ts.passwordLessLoginDescription }}</SearchText></template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
|
|
|
@ -11,7 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<!--<MkButton @click="refreshAllAccounts"><i class="ti ti-refresh"></i></MkButton>-->
|
<!--<MkButton @click="refreshAllAccounts"><i class="ti ti-refresh"></i></MkButton>-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkUserCardMini v-for="x in accounts" :key="x[0] + x[1].id" :user="x[1]" :class="$style.user" @click.prevent="menu(x[0], x[1], $event)"/>
|
<template v-for="x in accounts" :key="x.host + x.id">
|
||||||
|
<MkUserCardMini v-if="x.user" :user="x.user" :class="$style.user" @click.prevent="showMenu(x.host, x.id, $event)"/>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</SearchMarker>
|
</SearchMarker>
|
||||||
</template>
|
</template>
|
||||||
|
@ -24,29 +26,29 @@ import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
import { switchAccount, removeAccount, login, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/accounts.js';
|
import { switchAccount, removeAccount, login, getAccountWithSigninDialog, getAccountWithSignupDialog, getAccounts } from '@/accounts.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
const accounts = prefer.r.accounts;
|
const accounts = await getAccounts();
|
||||||
|
|
||||||
function refreshAllAccounts() {
|
function refreshAllAccounts() {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
function menu(host: string, account: Misskey.entities.UserDetailed, ev: MouseEvent) {
|
function showMenu(host: string, id: string, ev: MouseEvent) {
|
||||||
let menu: MenuItem[];
|
let menu: MenuItem[];
|
||||||
|
|
||||||
menu = [{
|
menu = [{
|
||||||
text: i18n.ts.switch,
|
text: i18n.ts.switch,
|
||||||
icon: 'ti ti-switch-horizontal',
|
icon: 'ti ti-switch-horizontal',
|
||||||
action: () => switchAccount(host, account.id),
|
action: () => switchAccount(host, id),
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.remove,
|
text: i18n.ts.remove,
|
||||||
icon: 'ti ti-trash',
|
icon: 'ti ti-trash',
|
||||||
action: () => removeAccount(host, account.id),
|
action: () => removeAccount(host, id),
|
||||||
}];
|
}];
|
||||||
|
|
||||||
os.popupMenu(menu, ev.currentTarget ?? ev.target);
|
os.popupMenu(menu, ev.currentTarget ?? ev.target);
|
||||||
|
|
|
@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
<template #label>{{ token.name }}</template>
|
<template #label>{{ token.name }}</template>
|
||||||
<template #caption>{{ token.description }}</template>
|
<template #caption>{{ token.description }}</template>
|
||||||
<template #suffix><MkTime :time="token.lastUsedAt"/></template>
|
<template v-if="token.lastUsedAt != null" #suffix><MkTime :time="token.lastUsedAt"/></template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<MkButton danger @click="revoke(token)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
<MkButton danger @click="revoke(token)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
</template>
|
</template>
|
||||||
|
@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #key>{{ i18n.ts.installedDate }}</template>
|
<template #key>{{ i18n.ts.installedDate }}</template>
|
||||||
<template #value><MkTime :time="token.createdAt" :mode="'detail'"/></template>
|
<template #value><MkTime :time="token.createdAt" :mode="'detail'"/></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
<MkKeyValue oneline>
|
<MkKeyValue v-if="token.lastUsedAt != null" oneline>
|
||||||
<template #key>{{ i18n.ts.lastUsedDate }}</template>
|
<template #key>{{ i18n.ts.lastUsedDate }}</template>
|
||||||
<template #value><MkTime :time="token.lastUsedAt" :mode="'detail'"/></template>
|
<template #value><MkTime :time="token.lastUsedAt" :mode="'detail'"/></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
|
|
|
@ -17,13 +17,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.decorations">
|
<div :class="$style.decorations">
|
||||||
<XDecoration
|
<XDecoration
|
||||||
v-for="(avatarDecoration, i) in $i.avatarDecorations"
|
v-for="(avatarDecoration, i) in $i.avatarDecorations"
|
||||||
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
|
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id) ?? { id: '', url: '', name: '?', roleIdsThatCanBeUsedThisDecoration: [] }"
|
||||||
:angle="avatarDecoration.angle"
|
:angle="avatarDecoration.angle"
|
||||||
:flipH="avatarDecoration.flipH"
|
:flipH="avatarDecoration.flipH"
|
||||||
:offsetX="avatarDecoration.offsetX"
|
:offsetX="avatarDecoration.offsetX"
|
||||||
:offsetY="avatarDecoration.offsetY"
|
:offsetY="avatarDecoration.offsetY"
|
||||||
:active="true"
|
:active="true"
|
||||||
@click="openDecoration(avatarDecoration, i)"
|
@click="openAttachedDecoration(i)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { ref, defineAsyncComponent, computed } from 'vue';
|
import { ref, defineAsyncComponent, computed } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XDecoration from './avatar-decoration.decoration.vue';
|
import XDecoration from './avatar-decoration.decoration.vue';
|
||||||
|
import XDialog from './avatar-decoration.dialog.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
@ -68,14 +69,24 @@ misskeyApi('get-avatar-decorations').then(_avatarDecorations => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function openDecoration(avatarDecoration, index?: number) {
|
function openAttachedDecoration(index: number) {
|
||||||
const { dispose } = await os.popupAsyncWithDialog(import('./avatar-decoration.dialog.vue').then(x => x.default), {
|
openDecoration(avatarDecorations.value.find(d => d.id === $i.avatarDecorations[index].id) ?? { id: '', url: '', name: '?', roleIdsThatCanBeUsedThisDecoration: [] }, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openDecoration(avatarDecoration: {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: string[];
|
||||||
|
}, index?: number) {
|
||||||
|
const { dispose } = os.popup(XDialog, {
|
||||||
decoration: avatarDecoration,
|
decoration: avatarDecoration,
|
||||||
usingIndex: index,
|
usingIndex: index ?? null,
|
||||||
}, {
|
}, {
|
||||||
'attach': async (payload) => {
|
'attach': async (payload) => {
|
||||||
const decoration = {
|
const decoration = {
|
||||||
id: avatarDecoration.id,
|
id: avatarDecoration.id,
|
||||||
|
url: avatarDecoration.url,
|
||||||
angle: payload.angle,
|
angle: payload.angle,
|
||||||
flipH: payload.flipH,
|
flipH: payload.flipH,
|
||||||
offsetX: payload.offsetX,
|
offsetX: payload.offsetX,
|
||||||
|
@ -90,13 +101,14 @@ async function openDecoration(avatarDecoration, index?: number) {
|
||||||
'update': async (payload) => {
|
'update': async (payload) => {
|
||||||
const decoration = {
|
const decoration = {
|
||||||
id: avatarDecoration.id,
|
id: avatarDecoration.id,
|
||||||
|
url: avatarDecoration.url,
|
||||||
angle: payload.angle,
|
angle: payload.angle,
|
||||||
flipH: payload.flipH,
|
flipH: payload.flipH,
|
||||||
offsetX: payload.offsetX,
|
offsetX: payload.offsetX,
|
||||||
offsetY: payload.offsetY,
|
offsetY: payload.offsetY,
|
||||||
};
|
};
|
||||||
const update = [...$i.avatarDecorations];
|
const update = [...$i.avatarDecorations];
|
||||||
update[index] = decoration;
|
update[index!] = decoration;
|
||||||
await os.apiWithDialog('i/update', {
|
await os.apiWithDialog('i/update', {
|
||||||
avatarDecorations: update,
|
avatarDecorations: update,
|
||||||
});
|
});
|
||||||
|
@ -104,7 +116,7 @@ async function openDecoration(avatarDecoration, index?: number) {
|
||||||
},
|
},
|
||||||
'detach': async () => {
|
'detach': async () => {
|
||||||
const update = [...$i.avatarDecorations];
|
const update = [...$i.avatarDecorations];
|
||||||
update.splice(index, 1);
|
update.splice(index!, 1);
|
||||||
await os.apiWithDialog('i/update', {
|
await os.apiWithDialog('i/update', {
|
||||||
avatarDecorations: update,
|
avatarDecorations: update,
|
||||||
});
|
});
|
||||||
|
|
|
@ -43,7 +43,7 @@ async function edit() {
|
||||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkWatermarkEditorDialog.vue')), {
|
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkWatermarkEditorDialog.vue')), {
|
||||||
preset: deepClone(props.preset),
|
preset: deepClone(props.preset),
|
||||||
}, {
|
}, {
|
||||||
ok: (preset: WatermarkPreset) => {
|
ok: (preset) => {
|
||||||
emit('updatePreset', preset);
|
emit('updatePreset', preset);
|
||||||
},
|
},
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
|
|
|
@ -74,7 +74,7 @@ import { instance } from '@/instance.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
const emailAddress = ref($i.email);
|
const emailAddress = ref($i.email ?? '');
|
||||||
|
|
||||||
const onChangeReceiveAnnouncementEmail = (v) => {
|
const onChangeReceiveAnnouncementEmail = (v) => {
|
||||||
misskeyApi('i/update', {
|
misskeyApi('i/update', {
|
||||||
|
|
|
@ -188,6 +188,8 @@ const menuDef = computed<SuperMenuDef[]>(() => [{
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (el.value == null) return; // TSを黙らすため
|
||||||
|
|
||||||
ro.observe(el.value);
|
ro.observe(el.value);
|
||||||
|
|
||||||
narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
|
narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
|
||||||
|
@ -198,6 +200,8 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
|
if (el.value == null) return; // TSを黙らすため
|
||||||
|
|
||||||
narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
|
narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
|
||||||
|
|
||||||
if (!narrow.value && currentPage.value?.route.name == null) {
|
if (!narrow.value && currentPage.value?.route.name == null) {
|
||||||
|
@ -215,7 +219,7 @@ watch(router.currentRef, (to) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified));
|
const emailNotConfigured = computed(() => $i && instance.enableEmail && ($i.email == null || !$i.emailVerified));
|
||||||
|
|
||||||
provideMetadataReceiver((metadataGetter) => {
|
provideMetadataReceiver((metadataGetter) => {
|
||||||
const info = metadataGetter();
|
const info = metadataGetter();
|
||||||
|
|
|
@ -159,8 +159,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div v-if="expandedBlockItems.includes(item.id)" :class="$style.userItemSub">
|
<div v-if="expandedBlockItems.includes(item.id)" :class="$style.userItemSub">
|
||||||
<div>Blocked at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
<div>Blocked at: <MkTime :time="item.createdAt" mode="detail"/></div>
|
||||||
<div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
|
|
||||||
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -43,9 +43,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</FormSection>
|
</FormSection>
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<FormLink @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink>
|
<MkButton @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</MkButton>
|
||||||
<FormLink @click="testNotification">{{ i18n.ts._notification.sendTestNotification }}</FormLink>
|
<MkButton @click="testNotification">{{ i18n.ts._notification.sendTestNotification }}</MkButton>
|
||||||
<FormLink @click="flushNotification">{{ i18n.ts._notification.flushNotification }}</FormLink>
|
<MkButton @click="flushNotification">{{ i18n.ts._notification.flushNotification }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
<FormSection>
|
<FormSection>
|
||||||
|
@ -76,6 +76,7 @@ import FormLink from '@/components/form/link.vue';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
@ -96,7 +97,7 @@ const sendReadMessage = computed(() => pushRegistrationInServer.value?.sendReadM
|
||||||
const userLists = await misskeyApi('users/lists/list');
|
const userLists = await misskeyApi('users/lists/list');
|
||||||
|
|
||||||
async function readAllNotifications() {
|
async function readAllNotifications() {
|
||||||
await os.apiWithDialog('notifications/mark-all-as-read');
|
await os.apiWithDialog('notifications/mark-all-as-read', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateReceiveConfig(type: typeof notificationTypes[number], value: NotificationConfig) {
|
async function updateReceiveConfig(type: typeof notificationTypes[number], value: NotificationConfig) {
|
||||||
|
@ -134,7 +135,7 @@ async function flushNotification() {
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
os.apiWithDialog('notifications/flush');
|
os.apiWithDialog('notifications/flush', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
|
@ -1031,7 +1031,6 @@ function testNotification(): void {
|
||||||
const notification: Misskey.entities.Notification = {
|
const notification: Misskey.entities.Notification = {
|
||||||
id: genId(),
|
id: genId(),
|
||||||
createdAt: new Date().toUTCString(),
|
createdAt: new Date().toUTCString(),
|
||||||
isRead: false,
|
|
||||||
type: 'test',
|
type: 'test',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue