Merge remote-tracking branch 'misskey-original/develop' into develop

# Conflicts:
#	packages/frontend/src/components/MkEmojiEditDialog.vue
#	packages/frontend/src/components/MkMenu.vue
#	packages/frontend/src/pages/about.vue
#	packages/frontend/src/pages/admin/index.vue
#	packages/frontend/src/pages/custom-emojis-manager.vue
#	packages/frontend/src/pages/settings/mute-block.vue
#	packages/frontend/src/pages/settings/theme.vue
#	packages/frontend/src/ui/_common_/navbar-for-mobile.vue
This commit is contained in:
mattyatea 2023-12-26 23:38:43 +09:00
commit 8d058b0529
208 changed files with 1583 additions and 904 deletions

View file

@ -24,9 +24,11 @@ export class ffVisibility1702718871541 {
async down(queryRunner) {
await queryRunner.query(`CREATE TYPE "public"."user_profile_ffvisibility_enum" AS ENUM('public', 'followers', 'private')`);
await queryRunner.query(`ALTER TABLE "user_profile" ADD "ffVisibility" "public"."user_profile_ffvisibility_enum" NOT NULL DEFAULT 'public'`);
await queryRunner.query(`CREATE CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum") WITH INOUT AS ASSIGNMENT`);
await queryRunner.query(`UPDATE "user_profile" SET ffVisibility = "user_profile"."followingVisibility"`);
await queryRunner.query(`UPDATE "user_profile" SET "ffVisibility" = "followingVisibility"`);
await queryRunner.query(`DROP CAST ("public"."user_profile_followingvisibility_enum" AS "public"."user_profile_ffvisibility_enum")`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followersVisibility"`);
await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "followingVisibility"`);
await queryRunner.query(`DROP TYPE "public"."user_profile_followersVisibility_enum"`);

View file

@ -7,7 +7,6 @@ import { URLSearchParams } from 'node:url';
import * as nodemailer from 'nodemailer';
import { Inject, Injectable } from '@nestjs/common';
import { validate as validateEmail } from 'deep-email-validator';
import { SubOutputFormat } from 'deep-email-validator/dist/output/output.js';
import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { DI } from '@/di-symbols.js';
@ -166,7 +165,10 @@ export class EmailService {
email: emailAddress,
});
let validated;
let validated: {
valid: boolean,
reason?: string | null,
};
if (meta.enableActiveEmailValidation) {
if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) {

View file

@ -15,6 +15,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { FeaturedService } from '@/core/FeaturedService.js';
import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js';
@Injectable()
export class HashtagService {
@ -29,6 +30,7 @@ export class HashtagService {
private featuredService: FeaturedService,
private idService: IdService,
private metaService: MetaService,
private utilityService: UtilityService,
) {
}
@ -161,6 +163,7 @@ export class HashtagService {
const instance = await this.metaService.fetch();
const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
if (hiddenTags.includes(hashtag)) return;
if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return;
// YYYYMMDDHHmm (10分間隔)
const now = new Date();

View file

@ -222,7 +222,7 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.visibility === 'public' && data.channel == null) {
const sensitiveWords = meta.sensitiveWords;
if (this.isSensitive(data, sensitiveWords)) {
if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
data.visibility = 'home';
@ -672,31 +672,6 @@ export class NoteCreateService implements OnApplicationShutdown {
this.index(note);
}
@bindThis
private isSensitive(note: Option, sensitiveWord: string[]): boolean {
if (sensitiveWord.length > 0) {
const text = note.cw ?? note.text ?? '';
if (text === '') return false;
const matched = sensitiveWord.some(filter => {
// represents RegExp
const regexp = filter.match(/^\/(.+)\/(.*)$/);
// This should never happen due to input sanitisation.
if (!regexp) {
const words = filter.split(' ');
return words.every(keyword => text.includes(keyword));
}
try {
return new RE2(regexp[1], regexp[2]).test(text);
} catch (err) {
// This should never happen due to input sanitisation.
return false;
}
});
if (matched) return true;
}
return false;
}
@bindThis
private isQuote(note: Option): note is Option & { renote: MiNote } {
// sync with misc/is-quote.ts
@ -880,6 +855,7 @@ export class NoteCreateService implements OnApplicationShutdown {
// ダイレクトのとき、そのリストが対象外のユーザーの場合
if (
note.visibility === 'specified' &&
note.userId !== userListMembership.userListUserId &&
!note.visibleUserIds.some(v => v === userListMembership.userListUserId)
) continue;

View file

@ -138,7 +138,7 @@ export class ReactionService {
reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
// センシティブ
if ((note.reactionAcceptance === 'nonSensitiveOnly') && emoji.isSensitive) {
if ((note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && emoji.isSensitive) {
reaction = FALLBACK;
}
} else {

View file

@ -6,6 +6,7 @@
import { URL } from 'node:url';
import { toASCII } from 'punycode';
import { Inject, Injectable } from '@nestjs/common';
import RE2 from 're2';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
@ -41,6 +42,33 @@ export class UtilityService {
return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
}
@bindThis
public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
if (sensitiveWords.length === 0) return false;
if (text === '') return false;
const regexpregexp = /^\/(.+)\/(.*)$/;
const matched = sensitiveWords.some(filter => {
// represents RegExp
const regexp = filter.match(regexpregexp);
// This should never happen due to input sanitisation.
if (!regexp) {
const words = filter.split(' ');
return words.every(keyword => text.includes(keyword));
}
try {
// TODO: RE2インスタンスをキャッシュ
return new RE2(regexp[1], regexp[2]).test(text);
} catch (err) {
// This should never happen due to input sanitisation.
return false;
}
});
return matched;
}
@bindThis
public extractDbHost(uri: string): string {
const url = new URL(uri);

View file

@ -381,6 +381,10 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
shortName: {
type: 'string',
optional: false, nullable: true,
},
objectStorageS3ForcePathStyle: {
type: 'boolean',
optional: false, nullable: false,

View file

@ -29,37 +29,10 @@ export const meta = {
optional: false,
nullable: false,
items: {
properties: {
id: { type: 'string' },
firstRetrievedAt: { type: 'string' },
host: { type: 'string' },
usersCount: { type: 'number' },
notesCount: { type: 'number' },
followingCount: { type: 'number' },
followersCount: { type: 'number' },
isNotResponding: { type: 'boolean' },
isSuspended: { type: 'boolean' },
isBlocked: { type: 'boolean' },
softwareName: { type: 'string' },
softwareVersion: { type: 'string' },
openRegistrations: { type: 'boolean' },
name: { type: 'string' },
description: { type: 'string' },
maintainerName: { type: 'string' },
maintainerEmail: { type: 'string' },
isSilenced: { type: 'boolean' },
iconUrl: { type: 'string' },
faviconUrl: { type: 'string' },
themeColor: { type: 'string' },
infoUpdatedAt: {
type: 'string',
nullable: true,
},
latestRequestReceivedAt: {
type: 'string',
nullable: true,
},
}
type: 'object',
optional: false,
nullable: false,
ref: 'FederationInstance',
},
},
otherFollowersCount: { type: 'number' },
@ -68,42 +41,15 @@ export const meta = {
optional: false,
nullable: false,
items: {
properties: {
id: { type: 'string' },
firstRetrievedAt: { type: 'string' },
host: { type: 'string' },
usersCount: { type: 'number' },
notesCount: { type: 'number' },
followingCount: { type: 'number' },
followersCount: { type: 'number' },
isNotResponding: { type: 'boolean' },
isSuspended: { type: 'boolean' },
isBlocked: { type: 'boolean' },
softwareName: { type: 'string' },
softwareVersion: { type: 'string' },
openRegistrations: { type: 'boolean' },
name: { type: 'string' },
description: { type: 'string' },
maintainerName: { type: 'string' },
maintainerEmail: { type: 'string' },
isSilenced: { type: 'boolean' },
iconUrl: { type: 'string' },
faviconUrl: { type: 'string' },
themeColor: { type: 'string' },
infoUpdatedAt: {
type: 'string',
nullable: true,
},
latestRequestReceivedAt: {
type: 'string',
nullable: true,
},
}
type: 'object',
optional: false,
nullable: false,
ref: 'FederationInstance',
},
},
otherFollowingCount: { type: 'number' },
},
}
},
} as const;
export const paramDef = {

View file

@ -164,20 +164,34 @@ export const meta = {
type: 'object',
optional: false, nullable: false,
properties: {
place: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
url: {
type: 'string',
optional: false, nullable: false,
format: 'url',
},
place: {
type: 'string',
optional: false, nullable: false,
},
ratio: {
type: 'number',
optional: false, nullable: false,
},
imageUrl: {
type: 'string',
optional: false, nullable: false,
format: 'url',
},
dayOfWeek: {
type: 'integer',
optional: false, nullable: false,
},
},
},
},

View file

@ -21,6 +21,10 @@ export const meta = {
res: {
type: 'object',
optional: false, nullable: false,
properties: {
sourceLang: { type: 'string' },
text: { type: 'string' },
},
},
errors: {

View file

@ -6,7 +6,7 @@
import { parse } from 'acorn';
import { generate } from 'astring';
import { describe, expect, it } from 'vitest';
import { normalizeClass, unwindCssModuleClassName } from './rollup-plugin-unwind-css-module-class-name';
import { normalizeClass, unwindCssModuleClassName } from './rollup-plugin-unwind-css-module-class-name.js';
import type * as estree from 'estree';
function parseExpression(code: string): estree.Expression {

View file

@ -7,8 +7,8 @@
import { action } from '@storybook/addon-actions';
import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw';
import { abuseUserReport } from '../../.storybook/fakes';
import { commonHandlers } from '../../.storybook/mocks';
import { abuseUserReport } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAbuseReport from './MkAbuseReport.vue';
export const Default = {
render(args) {

View file

@ -7,8 +7,8 @@
import { action } from '@storybook/addon-actions';
import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw';
import { userDetailed } from '../../.storybook/fakes';
import { commonHandlers } from '../../.storybook/mocks';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAbuseReportWindow from './MkAbuseReportWindow.vue';
export const Default = {
render(args) {

View file

@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../.storybook/fakes';
import { userDetailed } from '../../.storybook/fakes.js';
import MkAccountMoved from './MkAccountMoved.vue';
export const Default = {
render(args) {

View file

@ -6,8 +6,8 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw';
import { userDetailed } from '../../.storybook/fakes';
import { commonHandlers } from '../../.storybook/mocks';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAchievements from './MkAchievements.vue';
import { ACHIEVEMENT_TYPES } from '@/scripts/achievements.js';
export const Empty = {

View file

@ -67,7 +67,7 @@ const props = withDefaults(defineProps<{
withDescription: true,
});
const achievements = ref();
const achievements = ref<Misskey.entities.UsersAchievementsResponse | null>(null);
const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x)));
function fetch() {

View file

@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</div>
<span v-else-if="c.type === 'text'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }">{{ c.text }}</span>
<Mfm v-else-if="c.type === 'mfm'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }" :text="c.text"/>
<Mfm v-else-if="c.type === 'mfm'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }" :text="c.text" @clickEv="c.onClickEv"/>
<MkButton v-else-if="c.type === 'button'" :primary="c.primary" :rounded="c.rounded" :disabled="c.disabled" :small="size === 'small'" inline @click="c.onClick">{{ c.text }}</MkButton>
<div v-else-if="c.type === 'buttons'" class="_buttons" :style="{ justifyContent: align }">
<MkButton v-for="button in c.buttons" :primary="button.primary" :rounded="button.rounded" :disabled="button.disabled" inline :small="size === 'small'" @click="button.onClick">{{ button.text }}</MkButton>

View file

@ -9,8 +9,8 @@ import { expect } from '@storybook/jest';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw';
import { userDetailed } from '../../.storybook/fakes';
import { commonHandlers } from '../../.storybook/mocks';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAutocomplete from './MkAutocomplete.vue';
import MkInput from './MkInput.vue';
import { tick } from '@/scripts/test-utils.js';

View file

@ -6,8 +6,8 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw';
import { userDetailed } from '../../.storybook/fakes';
import { commonHandlers } from '../../.storybook/mocks';
import { userDetailed } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkAvatars from './MkAvatars.vue';
export const Default = {
render(args) {

View file

@ -26,7 +26,7 @@ export type Captcha = {
getResponse(id: string): string;
};
type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile';
export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile';
type CaptchaContainer = {
readonly [_ in CaptchaProvider]?: Captcha;

View file

@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
import MkMenu from './MkMenu.vue';
import { MenuItem } from './types/menu.vue';
import { MenuItem } from '@/types/menu.js';
import contains from '@/scripts/contains.js';
import { defaultStore } from '@/store.js';
import * as os from '@/os.js';

View file

@ -6,11 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts">
import { defineComponent, h, PropType, TransitionGroup, useCssModule } from 'vue';
import MkAd from '@/components/global/MkAd.vue';
import { isDebuggerEnabled, stackTraceInstances } from '@/debug';
import { isDebuggerEnabled, stackTraceInstances } from '@/debug.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { defaultStore } from '@/store.js';
import { MisskeyEntity } from '@/types/date-separated-list';
import { MisskeyEntity } from '@/types/date-separated-list.js';
export default defineComponent({
props: {

View file

@ -104,7 +104,7 @@ const props = defineProps<{
isRequest: boolean,
}>();
let dialog = ref(null);
let dialog = ref<InstanceType<typeof MkModalWindow> | null>(null);
let name = ref(props.emoji ? props.emoji.name : '');
let category = ref(props.emoji ? props.emoji.category : '');
let aliases = ref(props.emoji ? props.emoji.aliases.join(' ') : '');
@ -112,7 +112,7 @@ let license = ref(props.emoji ? (props.emoji.license ?? '') : '');
let isSensitive = ref(props.emoji ? props.emoji.isSensitive : false);
let localOnly = ref(props.emoji ? props.emoji.localOnly : false);
let roleIdsThatCanBeUsedThisEmojiAsReaction = ref((props.emoji && props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction) ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
let rolesThatCanBeUsedThisEmojiAsReaction = ref([]);
let rolesThatCanBeUsedThisEmojiAsReaction = ref<Misskey.entities.Role[]>([]);
let file = ref<Misskey.entities.DriveFile>();
let isRequest = ref(props.isRequest ?? false);
watch((roleIdsThatCanBeUsedThisEmojiAsReaction), async () => {

View file

@ -38,14 +38,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import MkPagination from '@/components/MkPagination.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import bytes from '@/filters/bytes.js';
import { i18n } from '@/i18n.js';
import { dateString } from '@/filters/date.js';
const props = defineProps<{
pagination: any;
pagination: Paging;
viewMode: 'grid' | 'list';
}>();
</script>

View file

@ -7,7 +7,7 @@
import { expect } from '@storybook/jest';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3';
import { galleryPost } from '../../.storybook/fakes';
import { galleryPost } from '../../.storybook/fakes.js';
import MkGalleryPostPreview from './MkGalleryPostPreview.vue';
export const Default = {
render(args) {

View file

@ -6,8 +6,8 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw';
import { userDetailed, inviteCode } from '../../.storybook/fakes';
import { commonHandlers } from '../../.storybook/mocks';
import { userDetailed, inviteCode } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkInviteCode from './MkInviteCode.vue';
export const Default = {

View file

@ -29,7 +29,7 @@ const self = props.url.startsWith(local);
const attr = self ? 'to' : 'href';
const target = self ? null : '_blank';
const el = ref();
const el = ref<HTMLElement>();
useTooltip(el, (showing) => {
os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {

View file

@ -27,7 +27,7 @@ export default {
},
},
setup(props) {
const contentEl = ref();
const contentEl = ref<HTMLElement>();
function calc() {
const eachLength = contentEl.value.offsetWidth / props.repeat;

View file

@ -37,7 +37,7 @@ import XBanner from '@/components/MkMediaBanner.vue';
import XImage from '@/components/MkMediaImage.vue';
import XVideo from '@/components/MkMediaVideo.vue';
import * as os from '@/os.js';
import { FILE_TYPE_BROWSERSAFE } from '@/const';
import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
import { defaultStore } from '@/store.js';
const props = defineProps<{

View file

@ -76,7 +76,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts">
import { computed, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
import { focusPrev, focusNext } from '@/scripts/focus.js';
import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu';
import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { isTouchUsing } from '@/scripts/touch.js';

View file

@ -257,7 +257,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong);
const isDeleted = ref(false);
const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords));
const translation = ref<any>(null);
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
const translating = ref(false);
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i.id));

View file

@ -218,7 +218,7 @@ import { claimAchievement } from '@/scripts/achievements.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
import MkButton from '@/components/MkButton.vue';
@ -299,7 +299,7 @@ const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false);
const isDeleted = ref(false);
const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : false);
const translation = ref(null);
const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
const translating = ref(false);
const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
const urls = parsed ? extractUrlFromMfm(parsed) : null;
@ -325,7 +325,7 @@ provide('react', (reaction: string) => {
});
const tab = ref('replies');
const reactionTabType = ref(null);
const reactionTabType = ref<string | null>(null);
const renotesPagination = computed(() => ({
endpoint: 'notes/renotes',
@ -333,7 +333,7 @@ const renotesPagination = computed(() => ({
params: {
noteId: appearNote.value.id,
},
}));
} satisfies Paging));
const reactionsPagination = computed(() => ({
endpoint: 'notes/reactions',
@ -342,7 +342,7 @@ const reactionsPagination = computed(() => ({
noteId: appearNote.value.id,
type: reactionTabType.value,
},
}));
} satisfies Paging));
useNoteCapture({
rootEl: el,

View file

@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i>
<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
<i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i>
<img v-else-if="notification.type === 'roleAssigned'" :src="notification.role.iconUrl" alt=""/>
<img v-else-if="notification.type === 'roleAssigned'" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/>
<!-- notification.reaction null になることはまずないがここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
<MkReactionIcon
v-else-if="notification.type === 'reaction'"

View file

@ -37,7 +37,7 @@ import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { url } from '@/config.js';
import { mainRouter, routes, page } from '@/router.js';
import { $i } from '@/account.js';
import { Router, useScrollPositionManager } from '@/nirax';
import { Router, useScrollPositionManager } from '@/nirax.js';
import { i18n } from '@/i18n.js';
import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
import { openingWindowsCount } from '@/os.js';

View file

@ -49,7 +49,7 @@ import * as os from '@/os.js';
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js';
import { useDocumentVisibility } from '@/scripts/use-document-visibility.js';
import { defaultStore } from '@/store.js';
import { MisskeyEntity } from '@/types/date-separated-list';
import { MisskeyEntity } from '@/types/date-separated-list.js';
import { i18n } from '@/i18n.js';
const SECOND_FETCH_LIMIT = 30;

View file

@ -52,7 +52,7 @@ const emit = defineEmits<{
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const passwordInput = shallowRef<InstanceType<typeof MkInput>>();
const password = ref('');
const token = ref(null);
const token = ref<string | null>(null);
function onClose() {
emit('cancelled');

View file

@ -203,14 +203,14 @@ watch(showPreview, () => defaultStore.set('showPreview', showPreview.value));
const cw = ref<string | null>(props.initialCw ?? null);
const localOnly = ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]);
const visibleUsers = ref([]);
const visibleUsers = ref<Misskey.entities.UserDetailed[]>([]);
if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser);
}
const reactionAcceptance = ref(defaultStore.state.reactionAcceptance);
const autocomplete = ref(null);
const draghover = ref(false);
const quoteId = ref(null);
const quoteId = ref<string | null>(null);
const hasNotSpecifiedMentions = ref(false);
const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
const imeText = ref('');

View file

@ -28,10 +28,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { } from 'vue';
import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
role: any;
role: Misskey.entities.Role;
forModeration: boolean;
detailed: boolean;
}>(), {

View file

@ -65,10 +65,10 @@ const opening = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = ref(null);
const prefixEl = ref(null);
const suffixEl = ref(null);
const container = ref(null);
const inputEl = ref<HTMLObjectElement | null>(null);
const prefixEl = ref<HTMLElement | null>(null);
const suffixEl = ref<HTMLElement | null>(null);
const container = ref<HTMLElement | null>(null);
const height =
props.small ? 33 :
props.large ? 39 :

View file

@ -71,8 +71,6 @@ const host = ref(toUnicode(configHost));
const totpLogin = ref(false);
const queryingKey = ref(false);
const credentialRequest = ref<CredentialRequestOptions | null>(null);
const hCaptchaResponse = ref(null);
const reCaptchaResponse = ref(null);
const emit = defineEmits<{
(ev: 'login', v: any): void;
@ -126,8 +124,6 @@ async function queryKey(): Promise<void> {
username: username.value,
password: password.value,
credential: credential.toJSON(),
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
});
}).then(res => {
emit('login', res);
@ -149,8 +145,6 @@ function onSubmit(): void {
os.api('signin', {
username: username.value,
password: password.value,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
}).then(res => {
totpLogin.value = true;
signing.value = false;
@ -168,8 +162,6 @@ function onSubmit(): void {
os.api('signin', {
username: username.value,
password: password.value,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
token: user.value?.twoFactorEnabled ? token.value : undefined,
}).then(res => {
emit('login', res);

View file

@ -154,9 +154,9 @@ const emailState = ref<null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:
const passwordStrength = ref<'' | 'low' | 'medium' | 'high'>('');
const passwordRetypeState = ref<null | 'match' | 'not-match'>(null);
const submitting = ref<boolean>(false);
const hCaptchaResponse = ref(null);
const reCaptchaResponse = ref(null);
const turnstileResponse = ref(null);
const hCaptchaResponse = ref<string | null>(null);
const reCaptchaResponse = ref<string | null>(null);
const turnstileResponse = ref<string | null>(null);
const usernameAbortController = ref<null | AbortController>(null);
const emailAbortController = ref<null | AbortController>(null);

View file

@ -72,7 +72,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
const particles = ref([]);
const particles = ref<{
id: string,
x: number,
y: number,
size: number,
dur: number,
color: string
}[]>([]);
const el = shallowRef<HTMLElement>();
const width = ref(0);
const height = ref(0);

View file

@ -66,7 +66,7 @@ const props = defineProps<{
announcement?: any,
}>();
const dialog = ref(null);
const dialog = ref<InstanceType<typeof MkModalWindow> | null>(null);
const title = ref<string>(props.announcement ? props.announcement.title : '');
const text = ref<string>(props.announcement ? props.announcement.text : '');
const icon = ref<string>(props.announcement ? props.announcement.icon : 'info');

View file

@ -6,8 +6,8 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw';
import { commonHandlers } from '../../.storybook/mocks';
import { userDetailed } from '../../.storybook/fakes';
import { commonHandlers } from '../../.storybook/mocks.js';
import { userDetailed } from '../../.storybook/fakes.js';
import MkUserSetupDialog_Follow from './MkUserSetupDialog.Follow.vue';
export const Default = {
render(args) {

View file

@ -37,15 +37,15 @@ SPDX-License-Identifier: AGPL-3.0-only
import { i18n } from '@/i18n.js';
import MkFolder from '@/components/MkFolder.vue';
import XUser from '@/components/MkUserSetupDialog.User.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
const pinnedUsers = { endpoint: 'pinned-users', noPaging: true };
const pinnedUsers = { endpoint: 'pinned-users', noPaging: true } satisfies Paging;
const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
state: 'alive',
origin: 'local',
sort: '+follower',
} };
} } satisfies Paging;
</script>
<style lang="scss" module>

View file

@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../.storybook/fakes';
import { userDetailed } from '../../.storybook/fakes.js';
import MkUserSetupDialog_User from './MkUserSetupDialog.User.vue';
export const Default = {
render(args) {

View file

@ -6,8 +6,8 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw';
import { commonHandlers } from '../../.storybook/mocks';
import { userDetailed } from '../../.storybook/fakes';
import { commonHandlers } from '../../.storybook/mocks.js';
import { userDetailed } from '../../.storybook/fakes.js';
import MkUserSetupDialog from './MkUserSetupDialog.vue';
export const Default = {
render(args) {

View file

@ -54,7 +54,7 @@ import { defineAsyncComponent, ref } from 'vue';
import { v4 as uuid } from 'uuid';
import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
import { widgets as widgetDefs } from '@/widgets';
import { widgets as widgetDefs } from '@/widgets/index.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
@ -77,7 +77,7 @@ const widgetRefs = {};
const configWidget = (id: string) => {
widgetRefs[id].configure();
};
const widgetAdderSelected = ref(null);
const widgetAdderSelected = ref<string | null>(null);
const addWidget = () => {
if (widgetAdderSelected.value == null) return;

View file

@ -30,7 +30,7 @@ const props = defineProps<{
const pending = ref(true);
const resolved = ref(false);
const rejected = ref(false);
const result = ref(null);
const result = ref<any>(null);
const process = () => {
if (props.p == null) {

View file

@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes';
import { userDetailed } from '../../../.storybook/fakes.js';
import MkAcct from './MkAcct.vue';
export const Default = {
render(args) {

View file

@ -5,7 +5,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes';
import { userDetailed } from '../../../.storybook/fakes.js';
import MkAvatar from './MkAvatar.vue';
const common = {
render(args) {

View file

@ -7,7 +7,7 @@
import { StoryObj } from '@storybook/vue3';
import { within } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.ts';
import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.js';
export const Default = {
render(args) {
return {

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { VNode, h } from 'vue';
import { VNode, h, SetupContext } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import MkUrl from '@/components/global/MkUrl.vue';
@ -77,8 +77,12 @@ type MfmProps = {
enableEmojiMenuReaction?: boolean;
};
type MfmEvents = {
clickEv(id: string): void;
};
// eslint-disable-next-line import/no-default-export
export default function(props: MfmProps) {
export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
const isNote = props.isNote ?? true;
const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat : false : false;
const shouldUhoize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isGorilla : false : false;
@ -354,6 +358,13 @@ export default function(props: MfmProps) {
}),
]);
}
case 'clickable': {
return h('span', { onClick(ev: MouseEvent): void {
ev.stopPropagation();
ev.preventDefault();
context.emit('clickEv', token.props.args.ev ?? '');
} }, genEl(token.children, scale));
}
}
if (style === undefined) {
return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']);

View file

@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, shallowRef } from 'vue';
import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@/const';
import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@/const.js';
const rootEl = shallowRef<HTMLElement>();
const headerEl = shallowRef<HTMLElement>();

View file

@ -8,7 +8,7 @@ import { expect } from '@storybook/jest';
import { userEvent, waitFor, within } from '@storybook/testing-library';
import { StoryObj } from '@storybook/vue3';
import { rest } from 'msw';
import { commonHandlers } from '../../../.storybook/mocks';
import { commonHandlers } from '../../../.storybook/mocks.js';
import MkUrl from './MkUrl.vue';
export const Default = {
render(args) {

View file

@ -6,7 +6,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest';
import { StoryObj } from '@storybook/vue3';
import { userDetailed } from '../../../.storybook/fakes';
import { userDetailed } from '../../../.storybook/fakes.js';
import MkUserName from './MkUserName.vue';
export const Default = {
render(args) {

View file

@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { inject, onBeforeUnmount, provide, shallowRef, ref } from 'vue';
import { Resolved, Router } from '@/nirax';
import { Resolved, Router } from '@/nirax.js';
import { defaultStore } from '@/store.js';
const props = defineProps<{

View file

@ -14,7 +14,7 @@ import XText from './page.text.vue';
import XSection from './page.section.vue';
import XImage from './page.image.vue';
import XNote from './page.note.vue';
import { Block } from './block.type';
import { Block } from './block.type.js';
function getComponent(type: string) {
switch (type) {

View file

@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import { ImageBlock } from './block.type';
import { ImageBlock } from './block.type.js';
import MediaImage from '@/components/MkMediaImage.vue';
const props = defineProps<{

View file

@ -11,9 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, Ref, ref } from 'vue';
import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { NoteBlock } from './block.type';
import { NoteBlock } from './block.type.js';
import MkNote from '@/components/MkNote.vue';
import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
import * as os from '@/os.js';
@ -23,7 +23,7 @@ const props = defineProps<{
page: Misskey.entities.Page,
}>();
const note: Ref<Misskey.entities.Note | null> = ref(null);
const note = ref<Misskey.entities.Note | null>(null);
onMounted(() => {
os.api('notes/show', { noteId: props.block.note })

View file

@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js';
import { SectionBlock } from './block.type';
import { SectionBlock } from './block.type.js';
const XBlock = defineAsyncComponent(() => import('./page.block.vue'));

View file

@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { defineAsyncComponent } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { TextBlock } from './block.type';
import { TextBlock } from './block.type.js';
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue'));

View file

@ -4,7 +4,7 @@
*/
import { Directive } from 'vue';
import { makeHotkey } from '../scripts/hotkey';
import { makeHotkey } from '../scripts/hotkey.js';
export default {
mounted(el, binding) {

View file

@ -5,17 +5,17 @@
import { App } from 'vue';
import userPreview from './user-preview';
import getSize from './get-size';
import ripple from './ripple';
import tooltip from './tooltip';
import hotkey from './hotkey';
import appear from './appear';
import anim from './anim';
import clickAnime from './click-anime';
import panel from './panel';
import adaptiveBorder from './adaptive-border';
import adaptiveBg from './adaptive-bg';
import userPreview from './user-preview.js';
import getSize from './get-size.js';
import ripple from './ripple.js';
import tooltip from './tooltip.js';
import hotkey from './hotkey.js';
import appear from './appear.js';
import anim from './anim.js';
import clickAnime from './click-anime.js';
import panel from './panel.js';
import adaptiveBorder from './adaptive-border.js';
import adaptiveBg from './adaptive-bg.js';
export default function(app: App) {
for (const [key, value] of Object.entries(directives)) {

View file

@ -313,8 +313,13 @@ const patrons = [
const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure'));
let easterEggReady = false;
const easterEggEmojis = ref([]);
const easterEggEngine = ref(null);
const easterEggEmojis = ref<{
id: string,
top: number,
left: number,
emoji: string
}[]>([]);
const easterEggEngine = ref<{ stop: () => void } | null>(null);
const containerEl = shallowRef<HTMLElement>();
function iconLoaded() {

View file

@ -83,7 +83,7 @@ const pagination = {
state.value === 'notResponding' ? { notResponding: true } :
{}),
})),
} as Paging;
} satisfies Paging;
function getStatus(instance) {
if (instance.isSuspended) return 'Suspended';

View file

@ -101,6 +101,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import {computed, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import XEmojis from './about.emojis.vue';
import XFederation from './about.federation.vue';
import { version, host } from '@/config.js';
@ -125,7 +126,7 @@ const props = withDefaults(defineProps<{
initialTab: 'overview',
});
const stats = ref(null);
const stats = ref<Misskey.entities.StatsResponse | null>(null);
const tab = ref(props.initialTab);
watch(tab, () => {

View file

@ -68,6 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkObjectView from '@/components/MkObjectView.vue';
@ -83,8 +84,8 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { iAmAdmin, iAmModerator } from '@/account.js';
const tab = ref('overview');
const file = ref<any>(null);
const info = ref<any>(null);
const file = ref<Misskey.entities.DriveFile | null>(null);
const info = ref<Misskey.entities.AdminDriveShowFileResponse | null>(null);
const isSensitive = ref<boolean>(false);
const props = defineProps<{

View file

@ -225,7 +225,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
import { iAmAdmin, $i } from '@/account.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
const props = withDefaults(defineProps<{
userId: string;
@ -238,9 +238,9 @@ const tab = ref(props.initialTab);
const chartSrc = ref('per-user-notes');
const user = ref<null | Misskey.entities.UserDetailed>();
const init = ref<ReturnType<typeof createFetcher>>();
const info = ref();
const ips = ref(null);
const ap = ref(null);
const info = ref<any>();
const ips = ref<Misskey.entities.AdminGetUserIpsResponse | null>(null);
const ap = ref<any>(null);
const moderator = ref(false);
const silenced = ref(false);
const suspended = ref(false);
@ -258,7 +258,7 @@ const announcementsPagination = {
params: computed(() => ({
userId: props.userId,
})),
};
} satisfies Paging;
const expandedRoles = ref([]);
function createFetcher() {

View file

@ -38,7 +38,7 @@ import tinycolor from 'tinycolor2';
import { popupMenu } from '@/os.js';
import { scrollToTop } from '@/scripts/scroll.js';
import MkButton from '@/components/MkButton.vue';
import { globalEvents } from '@/events';
import { globalEvents } from '@/events.js';
import { injectPageMetadata } from '@/scripts/page-metadata.js';
type Tab = {
@ -70,7 +70,7 @@ const metadata = injectPageMetadata();
const el = shallowRef<HTMLElement>(null);
const tabRefs = {};
const tabHighlightEl = shallowRef<HTMLElement | null>(null);
const bg = ref(null);
const bg = ref<string | null>(null);
const height = ref(0);
const hasTabs = computed(() => {
return props.tabs && props.tabs.length > 0;

View file

@ -56,7 +56,7 @@ import { computed, shallowRef, ref } from 'vue';
import XHeader from './_header_.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import XAbuseReport from '@/components/MkAbuseReport.vue';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
@ -77,7 +77,7 @@ const pagination = {
reporterOrigin: reporterOrigin.value,
targetUserOrigin: targetUserOrigin.value,
})),
};
} satisfies Paging;
function resolved(reportId) {
reports.value.removeItem(reportId);

View file

@ -86,6 +86,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
@ -98,7 +99,7 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const ads = ref<any[]>([]);
const ads = ref<Misskey.entities.Ad[]>([]);
// ISOTZUTCTZ
const localTime = new Date();

View file

@ -65,6 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue';
import type { CaptchaProvider } from '@/components/MkCaptcha.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
@ -76,7 +77,7 @@ import { i18n } from '@/i18n.js';
const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
const provider = ref(null);
const provider = ref<CaptchaProvider | null>(null);
const hcaptchaSiteKey = ref<string | null>(null);
const hcaptchaSecretKey = ref<string | null>(null);
const recaptchaSiteKey = ref<string | null>(null);

View file

@ -113,9 +113,9 @@ const app192IconUrl = ref<string | null>(null);
const app512IconUrl = ref<string | null>(null);
const bannerUrl = ref<string | null>(null);
const backgroundImageUrl = ref<string | null>(null);
const themeColor = ref<any>(null);
const defaultLightTheme = ref<any>(null);
const defaultDarkTheme = ref<any>(null);
const themeColor = ref<string | null>(null);
const defaultLightTheme = ref<string | null>(null);
const defaultDarkTheme = ref<string | null>(null);
const serverErrorImageUrl = ref<string | null>(null);
const infoImageUrl = ref<string | null>(null);
const notFoundImageUrl = ref<string | null>(null);

View file

@ -79,7 +79,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkButton from '@/components/MkButton.vue';
const enableEmail = ref<boolean>(false);
const email = ref<any>(null);
const email = ref<string | null>(null);
const smtpSecure = ref<boolean>(false);
const smtpHost = ref<string>('');
const smtpPort = ref<number>(0);

View file

@ -62,7 +62,7 @@ import { computed, ref } from 'vue';
import XHeader from './_header_.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
import FormSplit from '@/components/form/split.vue';
import { i18n } from '@/i18n.js';
@ -88,7 +88,7 @@ const pagination = {
state.value === 'notResponding' ? { notResponding: true } :
{}),
})),
};
} satisfies Paging;
function getStatus(instance) {
if (instance.isSuspended) return 'Suspended';

View file

@ -46,7 +46,7 @@ import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const origin = ref('local');
const type = ref(null);
const type = ref<string | null>(null);
const searchHost = ref('');
const userId = ref('');
const viewMode = ref('grid');

View file

@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import {computed, onActivated, onMounted, onUnmounted, provide, ref, watch } from 'vue';
import { watch,ComputedRef, Ref, onActivated, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
import { i18n } from '@/i18n.js';
import MkSuperMenu from '@/components/MkSuperMenu.vue';
import MkInfo from '@/components/MkInfo.vue';
@ -36,7 +36,7 @@ import { instance } from '@/instance.js';
import * as os from '@/os.js';
import { lookupUser, lookupUserByEmail } from '@/scripts/lookup-user.js';
import { useRouter } from '@/router.js';
import { definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
import { PageMetadata, definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
import {bannerDark, bannerLight, defaultStore, iconDark, iconLight} from "@/store.js";
const isEmpty = (x: string | null) => x == null || x === '';
@ -52,10 +52,10 @@ const indexInfo = {
provide('shouldOmitHeaderTitle', false);
const INFO = ref(indexInfo);
const childInfo = ref(null);
const childInfo: Ref<ComputedRef<PageMetadata> | null> = ref(null);
const narrow = ref(false);
const view = ref(null);
const el = ref(null);
const el = ref<HTMLDivElement | null>(null);
const pageProps = ref({});
let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile;

View file

@ -73,7 +73,7 @@ const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
const type = ref('all');
const sort = ref('+createdAt');
const pagination: Paging = {
const pagination = {
endpoint: 'admin/invite/list' as const,
limit: 10,
params: computed(() => ({
@ -81,7 +81,7 @@ const pagination: Paging = {
sort: sort.value,
})),
offsetMode: true,
};
} satisfies Paging;
const expiresAt = ref('');
const noExpirationDate = ref(true);
@ -97,10 +97,10 @@ async function createWithOptions() {
os.alert({
type: 'success',
title: i18n.ts.inviteCodeCreated,
text: tickets?.map(x => x.code).join('\n'),
text: tickets.map(x => x.code).join('\n'),
});
tickets?.forEach(ticket => pagingComponent.value?.prepend(ticket));
tickets.forEach(ticket => pagingComponent.value?.prepend(ticket));
}
function deleted(id: string) {

View file

@ -145,7 +145,7 @@ const props = defineProps<{
}
.logYellow {
color: var(--warning);
color: var(--warn);
}
.logRed {

View file

@ -36,13 +36,13 @@ import XHeader from './_header_.vue';
import XModLog from './modlog.ModLog.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkInput from '@/components/MkInput.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const logs = shallowRef<InstanceType<typeof MkPagination>>();
const type = ref(null);
const type = ref<string | null>(null);
const moderatorId = ref('');
const pagination = {
@ -52,7 +52,7 @@ const pagination = {
type: type.value,
userId: moderatorId.value === '' ? null : moderatorId.value,
})),
};
} satisfies Paging;
console.log(Misskey);

View file

@ -47,15 +47,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import XPie from './overview.pie.vue';
import XPie, { type InstanceForPie } from './overview.pie.vue';
import * as os from '@/os.js';
import number from '@/filters/number.js';
import MkNumberDiff from '@/components/MkNumberDiff.vue';
import { i18n } from '@/i18n.js';
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
const topSubInstancesForPie = ref<any>(null);
const topPubInstancesForPie = ref<any>(null);
const topSubInstancesForPie = ref<InstanceForPie[] | null>(null);
const topPubInstancesForPie = ref<InstanceForPie[] | null>(null);
const federationPubActive = ref<number | null>(null);
const federationPubActiveDiff = ref<number | null>(null);
const federationSubActive = ref<number | null>(null);
@ -72,22 +72,28 @@ onMounted(async () => {
federationSubActiveDiff.value = chart.subActive[0] - chart.subActive[1];
os.apiGet('federation/stats', { limit: 10 }).then(res => {
topSubInstancesForPie.value = res.topSubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followersCount,
onClick: () => {
os.pageWindow(`/instance-info/${x.host}`);
},
})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowersCount }]);
topPubInstancesForPie.value = res.topPubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followingCount,
onClick: () => {
os.pageWindow(`/instance-info/${x.host}`);
},
})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowingCount }]);
topSubInstancesForPie.value = [
...res.topSubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followersCount,
onClick: () => {
os.pageWindow(`/instance-info/${x.host}`);
},
})),
{ name: '(other)', color: '#80808080', value: res.otherFollowersCount },
];
topPubInstancesForPie.value = [
...res.topPubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followingCount,
onClick: () => {
os.pageWindow(`/instance-info/${x.host}`);
},
})),
{ name: '(other)', color: '#80808080', value: res.otherFollowingCount },
];
});
fetching.value = false;

View file

@ -18,12 +18,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { useInterval } from '@/scripts/use-interval.js';
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
import { defaultStore } from '@/store.js';
const instances = ref([]);
const instances = ref<Misskey.entities.FederationInstance[]>([]);
const fetching = ref(true);
const fetch = async () => {

View file

@ -18,10 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { defaultStore } from '@/store.js';
const moderators = ref<any>(null);
const moderators = ref<Misskey.entities.UserDetailed[] | null>(null);
const fetching = ref(true);
onMounted(async () => {

View file

@ -13,10 +13,17 @@ import { Chart } from 'chart.js';
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
import { initChart } from '@/scripts/init-chart.js';
export type InstanceForPie = {
name: string,
color: string | null,
value: number,
onClick?: () => void
};
initChart();
const props = defineProps<{
data: { name: string; value: number; color: string; onClick?: () => void }[];
data: InstanceForPie[];
}>();
const chartEl = shallowRef<HTMLCanvasElement>(null);

View file

@ -62,6 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import MkNumberDiff from '@/components/MkNumberDiff.vue';
import MkNumber from '@/components/MkNumber.vue';
@ -69,7 +70,7 @@ import { i18n } from '@/i18n.js';
import { customEmojis } from '@/custom-emojis.js';
import { defaultStore } from '@/store.js';
const stats = ref<any>(null);
const stats = ref<Misskey.entities.StatsResponse | null>(null);
const usersComparedToThePrevDay = ref<number>();
const notesComparedToThePrevDay = ref<number>();
const onlineUsersCount = ref(0);

View file

@ -18,12 +18,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { useInterval } from '@/scripts/use-interval.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import { defaultStore } from '@/store.js';
const newUsers = ref(null);
const newUsers = ref<Misskey.entities.UserDetailed[] | null>(null);
const fetching = ref(true);
const fetch = async () => {

View file

@ -66,6 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { markRaw, onMounted, onBeforeUnmount, nextTick, shallowRef, ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import XFederation from './overview.federation.vue';
import XInstances from './overview.instances.vue';
import XQueue from './overview.queue.vue';
@ -76,6 +77,7 @@ import XStats from './overview.stats.vue';
import XRetention from './overview.retention.vue';
import XModerators from './overview.moderators.vue';
import XHeatmap from './overview.heatmap.vue';
import type { InstanceForPie } from './overview.pie.vue';
import * as os from '@/os.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
@ -83,15 +85,15 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
const rootEl = shallowRef<HTMLElement>();
const serverInfo = ref<any>(null);
const topSubInstancesForPie = ref<any>(null);
const topPubInstancesForPie = ref<any>(null);
const serverInfo = ref<Misskey.entities.ServerInfoResponse | null>(null);
const topSubInstancesForPie = ref<InstanceForPie[] | null>(null);
const topPubInstancesForPie = ref<InstanceForPie[] | null>(null);
const federationPubActive = ref<number | null>(null);
const federationPubActiveDiff = ref<number | null>(null);
const federationSubActive = ref<number | null>(null);
const federationSubActiveDiff = ref<number | null>(null);
const newUsers = ref(null);
const activeInstances = shallowRef(null);
const newUsers = ref<Misskey.entities.UserDetailed[] | null>(null);
const activeInstances = shallowRef<Misskey.entities.FederationInstance | null>(null);
const queueStatsConnection = markRaw(useStream().useChannel('queueStats'));
const now = new Date();
const filesPagination = {
@ -123,22 +125,28 @@ onMounted(async () => {
});
os.apiGet('federation/stats', { limit: 10 }).then(res => {
topSubInstancesForPie.value = res.topSubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followersCount,
onClick: () => {
os.pageWindow(`/instance-info/${x.host}`);
},
})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowersCount }]);
topPubInstancesForPie.value = res.topPubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followingCount,
onClick: () => {
os.pageWindow(`/instance-info/${x.host}`);
},
})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowingCount }]);
topSubInstancesForPie.value = [
...res.topSubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followersCount,
onClick: () => {
os.pageWindow(`/instance-info/${x.host}`);
},
})),
{ name: '(other)', color: '#80808080', value: res.otherFollowersCount },
];
topPubInstancesForPie.value = [
...res.topPubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followingCount,
onClick: () => {
os.pageWindow(`/instance-info/${x.host}`);
},
})),
{ name: '(other)', color: '#80808080', value: res.otherFollowingCount },
];
});
os.api('admin/server-info').then(serverInfoResponse => {

View file

@ -22,6 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
@ -31,8 +32,8 @@ import { fetchInstance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const proxyAccount = ref<any>(null);
const proxyAccountId = ref<any>(null);
const proxyAccount = ref<Misskey.entities.UserDetailed | null>(null);
const proxyAccountId = ref<string | null>(null);
async function init() {
const meta = await os.api('admin/meta');

View file

@ -62,7 +62,7 @@ const activeSincePrevTick = ref(0);
const active = ref(0);
const delayed = ref(0);
const waiting = ref(0);
const jobs = ref([]);
const jobs = ref<(string | number)[][]>([]);
const chartProcess = shallowRef<InstanceType<typeof XChart>>();
const chartActive = shallowRef<InstanceType<typeof XChart>>();
const chartDelayed = shallowRef<InstanceType<typeof XChart>>();
@ -104,9 +104,11 @@ const onStatsLog = (statsLog) => {
};
onMounted(() => {
os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => {
jobs.value = result;
});
if (props.domain === 'inbox' || props.domain === 'deliver') {
os.api(`admin/queue/${props.domain}-delayed`).then(result => {
jobs.value = result;
});
}
connection.on('stats', onStats);
connection.on('statsLog', onStatsLog);

View file

@ -25,13 +25,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const relays = ref<any[]>([]);
const relays = ref<Misskey.entities.AdminRelaysListResponse>([]);
async function addRelay() {
const { canceled, result: inbox } = await os.inputText({
@ -66,7 +67,7 @@ function remove(inbox: string) {
}
function refresh() {
os.api('admin/relays/list').then((relayList: any) => {
os.api('admin/relays/list').then(relayList => {
relays.value = relayList;
});
}

View file

@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { v4 as uuid } from 'uuid';
import XHeader from './_header_.vue';
import XEditor from './roles.editor.vue';
@ -31,7 +32,7 @@ import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { useRouter } from '@/router.js';
import MkButton from '@/components/MkButton.vue';
import { rolesCache } from '@/cache';
import { rolesCache } from '@/cache.js';
const router = useRouter();
@ -39,8 +40,8 @@ const props = defineProps<{
id?: string;
}>();
const role = ref(null);
const data = ref(null);
const role = ref<Misskey.entities.Role | null>(null);
const data = ref<any>(null);
if (props.id) {
role.value = await os.api('admin/roles/show', {

View file

@ -73,7 +73,7 @@ import { useRouter } from '@/router.js';
import MkButton from '@/components/MkButton.vue';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import { infoImageUrl } from '@/instance.js';
const router = useRouter();
@ -88,7 +88,7 @@ const usersPagination = {
params: computed(() => ({
roleId: props.id,
})),
};
} satisfies Paging;
const expandedItems = ref([]);

View file

@ -262,7 +262,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { instance } from '@/instance.js';
import { useRouter } from '@/router.js';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { ROLE_POLICIES } from '@/const';
import { ROLE_POLICIES } from '@/const.js';
const router = useRouter();
const baseRoleQ = ref('');

View file

@ -173,8 +173,8 @@ const pinnedUsers = ref<string>('');
const cacheRemoteFiles = ref<boolean>(false);
const cacheRemoteSensitiveFiles = ref<boolean>(false);
const enableServiceWorker = ref<boolean>(false);
const swPublicKey = ref<any>(null);
const swPrivateKey = ref<any>(null);
const swPublicKey = ref<string | null>(null);
const swPrivateKey = ref<string | null>(null);
const enableFanoutTimeline = ref<boolean>(false);
const enableFanoutTimelineDbFallback = ref<boolean>(false);
const perLocalUserUserTimelineCacheMax = ref<number>(0);

View file

@ -61,7 +61,7 @@ import { computed, shallowRef, ref } from 'vue';
import XHeader from './_header_.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import * as os from '@/os.js';
import { lookupUser } from '@/scripts/lookup-user.js';
import { i18n } from '@/i18n.js';
@ -87,7 +87,7 @@ const pagination = {
hostname: searchHost.value,
})),
offsetMode: true,
};
} satisfies Paging;
function searchUser() {
os.selectUser().then(user => {

View file

@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import MkPagination from '@/components/MkPagination.vue';
import MkPagination, { Paging } from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os.js';
@ -55,7 +55,7 @@ const paginationCurrent = {
params: {
isActive: true,
},
};
} satisfies Paging;
const paginationPast = {
endpoint: 'announcements' as const,
@ -63,7 +63,7 @@ const paginationPast = {
params: {
isActive: false,
},
};
} satisfies Paging;
const paginationEl = ref<InstanceType<typeof MkPagination>>();

View file

@ -25,6 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, watch, ref, shallowRef } from 'vue';
import * as Misskey from 'misskey-js';
import MkTimeline from '@/components/MkTimeline.vue';
import { scroll } from '@/scripts/scroll.js';
import * as os from '@/os.js';
@ -38,7 +39,7 @@ const props = defineProps<{
antennaId: string;
}>();
const antenna = ref(null);
const antenna = ref<Misskey.entities.Antenna | null>(null);
const queue = ref(0);
const rootEl = shallowRef<HTMLElement>();
const tlEl = shallowRef<InstanceType<typeof MkTimeline>>();

View file

@ -46,7 +46,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
const body = ref('{}');
const endpoint = ref('');
const endpoints = ref<any[]>([]);
const endpoints = ref<string[]>([]);
const sending = ref(false);
const res = ref('');
const withCredential = ref(true);

View file

@ -35,6 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
@ -43,7 +44,7 @@ import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkFolder from '@/components/MkFolder.vue';
const avatarDecorations = ref<any[]>([]);
const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]);
function add() {
avatarDecorations.value.unshift({

Some files were not shown because too many files have changed in this diff Show more