Merge branch 'develop' into pr/16456
This commit is contained in:
commit
6f3c744f7e
|
@ -31,6 +31,7 @@ import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
|||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
|
||||
|
||||
// misskey-js の rolePolicies と同期すべし
|
||||
export type RolePolicies = {
|
||||
gtlAvailable: boolean;
|
||||
ltlAvailable: boolean;
|
||||
|
|
|
@ -10,6 +10,7 @@ import { MiAccessToken } from './AccessToken.js';
|
|||
import { MiRole } from './Role.js';
|
||||
import { MiDriveFile } from './DriveFile.js';
|
||||
|
||||
// misskey-js の notificationTypes と同期すべし
|
||||
export type MiNotification = {
|
||||
type: 'note';
|
||||
id: string;
|
||||
|
|
|
@ -54,68 +54,6 @@ https://github.com/sindresorhus/file-type/blob/main/core.js
|
|||
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
|
||||
*/
|
||||
|
||||
export const notificationTypes = [
|
||||
'note',
|
||||
'follow',
|
||||
'mention',
|
||||
'reply',
|
||||
'renote',
|
||||
'quote',
|
||||
'reaction',
|
||||
'pollEnded',
|
||||
'receiveFollowRequest',
|
||||
'followRequestAccepted',
|
||||
'roleAssigned',
|
||||
'chatRoomInvitationReceived',
|
||||
'achievementEarned',
|
||||
'exportCompleted',
|
||||
'login',
|
||||
'createToken',
|
||||
'test',
|
||||
'app',
|
||||
] as const;
|
||||
export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
|
||||
|
||||
export const ROLE_POLICIES = [
|
||||
'gtlAvailable',
|
||||
'ltlAvailable',
|
||||
'canPublicNote',
|
||||
'mentionLimit',
|
||||
'canInvite',
|
||||
'inviteLimit',
|
||||
'inviteLimitCycle',
|
||||
'inviteExpirationTime',
|
||||
'canManageCustomEmojis',
|
||||
'canManageAvatarDecorations',
|
||||
'canSearchNotes',
|
||||
'canSearchUsers',
|
||||
'canUseTranslator',
|
||||
'canHideAds',
|
||||
'driveCapacityMb',
|
||||
'maxFileSizeMb',
|
||||
'alwaysMarkNsfw',
|
||||
'canUpdateBioMedia',
|
||||
'pinLimit',
|
||||
'antennaLimit',
|
||||
'wordMuteLimit',
|
||||
'webhookLimit',
|
||||
'clipLimit',
|
||||
'noteEachClipsLimit',
|
||||
'userListLimit',
|
||||
'userEachUserListsLimit',
|
||||
'rateLimitFactor',
|
||||
'avatarDecorationLimit',
|
||||
'canImportAntennas',
|
||||
'canImportBlocking',
|
||||
'canImportFollowing',
|
||||
'canImportMuting',
|
||||
'canImportUserLists',
|
||||
'chatAvailability',
|
||||
'uploadableFileTypes',
|
||||
'noteDraftLimit',
|
||||
'watermarkAvailable',
|
||||
] as const;
|
||||
|
||||
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime'];
|
||||
export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = {
|
||||
tada: ['speed=', 'delay='],
|
||||
|
|
|
@ -207,6 +207,7 @@ export function federationInstance(): entities.FederationInstance {
|
|||
isSuspended: false,
|
||||
suspensionState: 'none',
|
||||
isBlocked: false,
|
||||
isMediaSilenced: false,
|
||||
softwareName: 'misskey',
|
||||
softwareVersion: '2024.5.0',
|
||||
openRegistrations: false,
|
||||
|
@ -311,6 +312,8 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host: enti
|
|||
alsoKnownAs: null,
|
||||
notify: 'none',
|
||||
memo: null,
|
||||
canChat: true,
|
||||
chatScope: 'everyone',
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -378,6 +381,7 @@ export function role(params: {
|
|||
asBadge: params.asBadge ?? true,
|
||||
canEditMembersByModerator: params.canEditMembersByModerator ?? false,
|
||||
usersCount: params.usersCount ?? 10,
|
||||
preserveAssignmentOnMoveAccount: false,
|
||||
condFormula: {
|
||||
id: '',
|
||||
type: 'or',
|
||||
|
|
|
@ -25,12 +25,12 @@ defineProps<{
|
|||
showing: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
title?: string;
|
||||
title?: string | null;
|
||||
series?: {
|
||||
backgroundColor: string;
|
||||
borderColor: string;
|
||||
text: string;
|
||||
}[];
|
||||
}[] | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -8,7 +8,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div v-for="v, k in paramDefs" :key="k">
|
||||
<MkSwitch
|
||||
v-if="v.type === 'boolean'"
|
||||
v-model="params[k]">
|
||||
v-model="params[k]"
|
||||
>
|
||||
<template #label>{{ v.label ?? k }}</template>
|
||||
<template v-if="v.caption != null" #caption>{{ v.caption }}</template>
|
||||
</MkSwitch>
|
||||
|
@ -53,12 +54,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ImageEffectorRGB, ImageEffectorFxParamDefs } from '@/utility/image-effector/ImageEffector.js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import type { ImageEffectorRGB, ImageEffectorFxParamDefs } from '@/utility/image-effector/ImageEffector.js';
|
||||
|
||||
defineProps<{
|
||||
paramDefs: ImageEffectorFxParamDefs;
|
||||
|
|
|
@ -44,10 +44,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
type SupportedTypes = 'text' | 'password' | 'email' | 'url' | 'tel' | 'number' | 'search';
|
||||
type SupportedTypes = 'text' | 'password' | 'email' | 'url' | 'tel' | 'number' | 'search' | 'date' | 'time' | 'datetime-local' | 'color';
|
||||
type ModelValueType<T extends SupportedTypes> =
|
||||
T extends 'number' ? number :
|
||||
T extends 'text' | 'password' | 'email' | 'url' | 'tel' | 'search' ? string :
|
||||
T extends 'text' | 'password' | 'email' | 'url' | 'tel' | 'search' | 'date' | 'time' | 'datetime-local' | 'color' ? string :
|
||||
never;
|
||||
</script>
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import { notificationTypes } from '@@/js/const.js';
|
||||
import { notificationTypes } from 'misskey-js';
|
||||
import MkSwitch from './MkSwitch.vue';
|
||||
import MkInfo from './MkInfo.vue';
|
||||
import MkButton from './MkButton.vue';
|
||||
|
|
|
@ -191,7 +191,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { ROLE_POLICIES } from '@@/js/const.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import * as os from '@/os.js';
|
||||
|
|
|
@ -44,10 +44,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup, markRaw, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { notificationTypes } from 'misskey-js';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
|
||||
import { getScrollContainer, scrollToTop } from '@@/js/scroll.js';
|
||||
import type { notificationTypes } from '@@/js/const.js';
|
||||
import XNotification from '@/components/MkNotification.vue';
|
||||
import MkNote from '@/components/MkNote.vue';
|
||||
import { useStream } from '@/stream.js';
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@ -1,70 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
@ -12,8 +13,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<div v-else>
|
||||
<div :class="$style.error">
|
||||
<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</div>
|
||||
<MkButton inline style="margin-top: 16px;" @click="retry"><i class="ti ti-reload"></i> {{ i18n.ts.retry }}</MkButton>
|
||||
<slot name="error" :error="error">
|
||||
<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</div>
|
||||
<div v-if="error">{{ JSON.stringify(error) }}</div>
|
||||
<MkButton inline style="margin-top: 16px;" @click="retry"><i class="ti ti-reload"></i> {{ i18n.ts.retry }}</MkButton>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -27,15 +31,17 @@ const props = defineProps<{
|
|||
p: () => Promise<T>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'resolved', result: T): void;
|
||||
}>();
|
||||
|
||||
const pending = ref(true);
|
||||
const resolved = ref(false);
|
||||
const rejected = ref(false);
|
||||
const result = ref<T | null>(null);
|
||||
const error = ref<any | null>(null);
|
||||
|
||||
const process = () => {
|
||||
if (props.p == null) {
|
||||
return;
|
||||
}
|
||||
const promise = props.p();
|
||||
pending.value = true;
|
||||
resolved.value = false;
|
||||
|
@ -44,10 +50,12 @@ const process = () => {
|
|||
pending.value = false;
|
||||
resolved.value = true;
|
||||
result.value = _result;
|
||||
emit('resolved', _result);
|
||||
});
|
||||
promise.catch(() => {
|
||||
promise.catch((_error) => {
|
||||
pending.value = false;
|
||||
rejected.value = true;
|
||||
error.value = _error;
|
||||
});
|
||||
};
|
||||
|
|
@ -48,6 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<div v-else-if="cellType === 'image'">
|
||||
<img
|
||||
v-if="cell.value && typeof cell.value === 'string'"
|
||||
:src="cell.value"
|
||||
:alt="cell.value"
|
||||
:class="$style.viewImage"
|
||||
|
|
|
@ -20,6 +20,7 @@ import NestedRouterView from './global/NestedRouterView.vue';
|
|||
import StackingRouterView from './global/StackingRouterView.vue';
|
||||
import MkLoading from './global/MkLoading.vue';
|
||||
import MkError from './global/MkError.vue';
|
||||
import MkSuspense from './global/MkSuspense.vue';
|
||||
import MkAd from './global/MkAd.vue';
|
||||
import MkPageHeader from './global/MkPageHeader.vue';
|
||||
import MkStickyContainer from './global/MkStickyContainer.vue';
|
||||
|
@ -60,6 +61,7 @@ export const components = {
|
|||
MkUrl: MkUrl,
|
||||
MkLoading: MkLoading,
|
||||
MkError: MkError,
|
||||
MkSuspense: MkSuspense,
|
||||
MkAd: MkAd,
|
||||
MkPageHeader: MkPageHeader,
|
||||
MkStickyContainer: MkStickyContainer,
|
||||
|
@ -94,6 +96,7 @@ declare module '@vue/runtime-core' {
|
|||
MkUrl: typeof MkUrl;
|
||||
MkLoading: typeof MkLoading;
|
||||
MkError: typeof MkError;
|
||||
MkSuspense: typeof MkSuspense;
|
||||
MkAd: typeof MkAd;
|
||||
MkPageHeader: typeof MkPageHeader;
|
||||
MkStickyContainer: typeof MkStickyContainer;
|
||||
|
|
|
@ -15,7 +15,7 @@ import * as Misskey from 'misskey-js';
|
|||
import MkMediaList from '@/components/MkMediaList.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
block: Misskey.entities.PageBlock,
|
||||
block: Extract<Misskey.entities.PageBlock, { type: 'image' }>,
|
||||
page: Misskey.entities.Page,
|
||||
}>();
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
|
|||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
||||
const props = defineProps<{
|
||||
block: Misskey.entities.PageBlock,
|
||||
block: Extract<Misskey.entities.PageBlock, { type: 'note' }>,
|
||||
page: Misskey.entities.Page,
|
||||
}>();
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import * as Misskey from 'misskey-js';
|
|||
const XBlock = defineAsyncComponent(() => import('./page.block.vue'));
|
||||
|
||||
defineProps<{
|
||||
block: Misskey.entities.PageBlock,
|
||||
block: Extract<Misskey.entities.PageBlock, { type: 'section' }>,
|
||||
h: number,
|
||||
page: Misskey.entities.Page,
|
||||
}>();
|
||||
|
|
|
@ -22,7 +22,7 @@ import { isEnabledUrlPreview } from '@/utility/url-preview.js';
|
|||
const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue'));
|
||||
|
||||
const props = defineProps<{
|
||||
block: Misskey.entities.PageBlock,
|
||||
block: Extract<Misskey.entities.PageBlock, { type: 'text' }>,
|
||||
page: Misskey.entities.Page,
|
||||
}>();
|
||||
|
||||
|
|
|
@ -51,3 +51,9 @@ export async function fetchInstance(force = false): Promise<Misskey.entities.Met
|
|||
|
||||
return instance;
|
||||
}
|
||||
|
||||
export type ClientOptions = {
|
||||
entrancePageStyle: 'classic' | 'simple';
|
||||
showTimelineForVisitor: boolean;
|
||||
showActivitiesForVisitor: boolean;
|
||||
};
|
||||
|
|
|
@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSuspense v-slot="{ result: stats }" :p="initStats">
|
||||
<MkSuspense v-slot="{ result: stats }" :p="initStats">
|
||||
<FormSection>
|
||||
<template #label>{{ i18n.ts.statistics }}</template>
|
||||
<FormSplit>
|
||||
|
@ -110,7 +110,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkKeyValue>
|
||||
</FormSplit>
|
||||
</FormSection>
|
||||
</FormSuspense>
|
||||
</MkSuspense>
|
||||
|
||||
<FormSection>
|
||||
<template #label>Well-known resources</template>
|
||||
|
@ -134,7 +134,6 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
|||
import FormLink from '@/components/form/link.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
||||
<div v-if="file" class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<div v-if="tab === 'overview'" class="cxqhhsmd _gaps_m">
|
||||
<a class="thumbnail" :href="file.url" target="_blank">
|
||||
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
|
||||
</a>
|
||||
<div>
|
||||
<MkKeyValue :copy="file.type" oneline style="margin: 1em 0;">
|
||||
<template #key>MIME Type</template>
|
||||
<template #value><span class="_monospace">{{ file.type }}</span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>Size</template>
|
||||
<template #value><span class="_monospace">{{ bytes(file.size) }}</span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue :copy="file.id" oneline style="margin: 1em 0;">
|
||||
<template #key>ID</template>
|
||||
<template #value><span class="_monospace">{{ file.id }}</span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue :copy="file.md5" oneline style="margin: 1em 0;">
|
||||
<template #key>MD5</template>
|
||||
<template #value><span class="_monospace">{{ file.md5 }}</span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||
<template #value><span class="_monospace"><MkTime :time="file.createdAt" mode="detail" style="display: block;"/></span></template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
<MkA v-if="file.user" class="user" :to="`/admin/user/${file.user.id}`">
|
||||
<MkUserCardMini :user="file.user"/>
|
||||
</MkA>
|
||||
|
||||
<div>
|
||||
<MkSwitch :modelValue="isSensitive" @update:modelValue="toggleSensitive">{{ i18n.ts.sensitive }}</MkSwitch>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<MkButton danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="tab === 'usage' && info" class="_gaps_m">
|
||||
<MkTabs
|
||||
v-model:tab="usageTab"
|
||||
:tabs="[{
|
||||
key: 'note',
|
||||
title: 'Note',
|
||||
}, {
|
||||
key: 'chat',
|
||||
title: 'Chat',
|
||||
}]"
|
||||
/>
|
||||
<XNotes v-if="usageTab === 'note'" :fileId="props.file.id"/>
|
||||
<XChat v-else-if="usageTab === 'chat'" :fileId="props.file.id"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'ip' && info" class="_gaps_m">
|
||||
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
|
||||
<MkKeyValue v-if="info.requestIp" class="_monospace" :copy="info.requestIp" oneline>
|
||||
<template #key>IP</template>
|
||||
<template #value>{{ info.requestIp }}</template>
|
||||
</MkKeyValue>
|
||||
<FormSection v-if="info.requestHeaders">
|
||||
<template #label>Headers</template>
|
||||
<MkKeyValue v-for="(v, k) in info.requestHeaders" :key="k" class="_monospace">
|
||||
<template #key>{{ k }}</template>
|
||||
<template #value>{{ v }}</template>
|
||||
</MkKeyValue>
|
||||
</FormSection>
|
||||
</div>
|
||||
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
||||
<MkObjectView v-if="info" tall :value="info">
|
||||
</MkObjectView>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, 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';
|
||||
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import bytes from '@/filters/bytes.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { iAmAdmin, iAmModerator } from '@/i.js';
|
||||
import MkTabs from '@/components/MkTabs.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
file: Misskey.entities.DriveFile,
|
||||
info: Misskey.entities.AdminDriveShowFileResponse,
|
||||
}>();
|
||||
|
||||
const tab = ref('overview');
|
||||
const isSensitive = ref(props.file.isSensitive);
|
||||
const usageTab = ref<'note' | 'chat'>('note');
|
||||
const XNotes = defineAsyncComponent(() => import('./drive.file.notes.vue'));
|
||||
const XChat = defineAsyncComponent(() => import('./admin-file.chat.vue'));
|
||||
|
||||
async function del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.tsx.removeAreYouSure({ x: props.file.name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
os.apiWithDialog('drive/files/delete', {
|
||||
fileId: props.file.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function toggleSensitive() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: isSensitive.value ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
|
||||
});
|
||||
|
||||
if (canceled) return;
|
||||
isSensitive.value = !isSensitive.value;
|
||||
|
||||
os.apiWithDialog('drive/files/update', {
|
||||
fileId: props.file.id,
|
||||
isSensitive: !props.file.isSensitive,
|
||||
});
|
||||
}
|
||||
|
||||
const headerActions = computed(() => [{
|
||||
text: i18n.ts.openInNewTab,
|
||||
icon: 'ti ti-external-link',
|
||||
handler: () => {
|
||||
window.open(props.file.url, '_blank', 'noopener');
|
||||
},
|
||||
}]);
|
||||
|
||||
const headerTabs = computed(() => [{
|
||||
key: 'overview',
|
||||
title: i18n.ts.overview,
|
||||
icon: 'ti ti-info-circle',
|
||||
}, iAmModerator ? {
|
||||
key: 'usage',
|
||||
title: i18n.ts._fileViewer.usage,
|
||||
icon: 'ti ti-plus',
|
||||
} : null, iAmModerator ? {
|
||||
key: 'ip',
|
||||
title: 'IP',
|
||||
icon: 'ti ti-password',
|
||||
} : null, {
|
||||
key: 'raw',
|
||||
title: 'Raw data',
|
||||
icon: 'ti ti-code',
|
||||
}].filter(x => x != null));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cxqhhsmd {
|
||||
> .thumbnail {
|
||||
display: block;
|
||||
|
||||
> .thumbnail {
|
||||
height: 300px;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
> .user {
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -4,197 +4,37 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
||||
<div v-if="file" class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<div v-if="tab === 'overview'" class="cxqhhsmd _gaps_m">
|
||||
<a class="thumbnail" :href="file.url" target="_blank">
|
||||
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
|
||||
</a>
|
||||
<div>
|
||||
<MkKeyValue :copy="file.type" oneline style="margin: 1em 0;">
|
||||
<template #key>MIME Type</template>
|
||||
<template #value><span class="_monospace">{{ file.type }}</span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>Size</template>
|
||||
<template #value><span class="_monospace">{{ bytes(file.size) }}</span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue :copy="file.id" oneline style="margin: 1em 0;">
|
||||
<template #key>ID</template>
|
||||
<template #value><span class="_monospace">{{ file.id }}</span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue :copy="file.md5" oneline style="margin: 1em 0;">
|
||||
<template #key>MD5</template>
|
||||
<template #value><span class="_monospace">{{ file.md5 }}</span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline style="margin: 1em 0;">
|
||||
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||
<template #value><span class="_monospace"><MkTime :time="file.createdAt" mode="detail" style="display: block;"/></span></template>
|
||||
</MkKeyValue>
|
||||
</div>
|
||||
<MkA v-if="file.user" class="user" :to="`/admin/user/${file.user.id}`">
|
||||
<MkUserCardMini :user="file.user"/>
|
||||
</MkA>
|
||||
|
||||
<div>
|
||||
<MkSwitch :modelValue="isSensitive" @update:modelValue="toggleSensitive">{{ i18n.ts.sensitive }}</MkSwitch>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<MkButton danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="tab === 'usage' && info" class="_gaps_m">
|
||||
<MkTabs
|
||||
v-model:tab="usageTab"
|
||||
:tabs="[{
|
||||
key: 'note',
|
||||
title: 'Note',
|
||||
}, {
|
||||
key: 'chat',
|
||||
title: 'Chat',
|
||||
}]"
|
||||
/>
|
||||
<XNotes v-if="usageTab === 'note'" :fileId="fileId"/>
|
||||
<XChat v-else-if="usageTab === 'chat'" :fileId="fileId"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'ip' && info" class="_gaps_m">
|
||||
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
|
||||
<MkKeyValue v-if="info.requestIp" class="_monospace" :copy="info.requestIp" oneline>
|
||||
<template #key>IP</template>
|
||||
<template #value>{{ info.requestIp }}</template>
|
||||
</MkKeyValue>
|
||||
<FormSection v-if="info.requestHeaders">
|
||||
<template #label>Headers</template>
|
||||
<MkKeyValue v-for="(v, k) in info.requestHeaders" :key="k" class="_monospace">
|
||||
<template #key>{{ k }}</template>
|
||||
<template #value>{{ v }}</template>
|
||||
</MkKeyValue>
|
||||
</FormSection>
|
||||
</div>
|
||||
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
||||
<MkObjectView v-if="info" tall :value="info">
|
||||
</MkObjectView>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
<MkSuspense v-slot="{ result }" :p="_fetch_" @resolved="(result) => file = result.file">
|
||||
<XRoot v-if="result.file != null && result.info != null" :file="result.file" :info="result.info"/>
|
||||
</MkSuspense>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, ref } from 'vue';
|
||||
import { 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';
|
||||
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import bytes from '@/filters/bytes.js';
|
||||
import * as os from '@/os.js';
|
||||
import XRoot from './admin-file.root.vue';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { iAmAdmin, iAmModerator } from '@/i.js';
|
||||
import MkTabs from '@/components/MkTabs.vue';
|
||||
|
||||
const tab = ref('overview');
|
||||
const file = ref<Misskey.entities.DriveFile | null>(null);
|
||||
const info = ref<Misskey.entities.AdminDriveShowFileResponse | null>(null);
|
||||
const isSensitive = ref<boolean>(false);
|
||||
const usageTab = ref<'note' | 'chat'>('note');
|
||||
const XNotes = defineAsyncComponent(() => import('./drive.file.notes.vue'));
|
||||
const XChat = defineAsyncComponent(() => import('./admin-file.chat.vue'));
|
||||
|
||||
const props = defineProps<{
|
||||
fileId: string,
|
||||
}>();
|
||||
|
||||
async function _fetch_() {
|
||||
file.value = await misskeyApi('drive/files/show', { fileId: props.fileId });
|
||||
info.value = await misskeyApi('admin/drive/show-file', { fileId: props.fileId });
|
||||
isSensitive.value = file.value.isSensitive;
|
||||
function _fetch_() {
|
||||
return Promise.all([
|
||||
misskeyApi('drive/files/show', { fileId: props.fileId }),
|
||||
misskeyApi('admin/drive/show-file', { fileId: props.fileId }),
|
||||
]).then((result) => ({
|
||||
file: result[0],
|
||||
info: result[1],
|
||||
}));
|
||||
}
|
||||
|
||||
_fetch_();
|
||||
|
||||
async function del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.tsx.removeAreYouSure({ x: file.value.name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
os.apiWithDialog('drive/files/delete', {
|
||||
fileId: file.value.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function toggleSensitive() {
|
||||
if (!file.value) return;
|
||||
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: isSensitive.value ? i18n.ts.unmarkAsSensitiveConfirm : i18n.ts.markAsSensitiveConfirm,
|
||||
});
|
||||
|
||||
if (canceled) return;
|
||||
isSensitive.value = !isSensitive.value;
|
||||
|
||||
os.apiWithDialog('drive/files/update', {
|
||||
fileId: file.value.id,
|
||||
isSensitive: !file.value.isSensitive,
|
||||
});
|
||||
}
|
||||
|
||||
const headerActions = computed(() => [{
|
||||
text: i18n.ts.openInNewTab,
|
||||
icon: 'ti ti-external-link',
|
||||
handler: () => {
|
||||
window.open(file.value.url, '_blank', 'noopener');
|
||||
},
|
||||
}]);
|
||||
|
||||
const headerTabs = computed(() => [{
|
||||
key: 'overview',
|
||||
title: i18n.ts.overview,
|
||||
icon: 'ti ti-info-circle',
|
||||
}, iAmModerator ? {
|
||||
key: 'usage',
|
||||
title: i18n.ts._fileViewer.usage,
|
||||
icon: 'ti ti-plus',
|
||||
} : null, iAmModerator ? {
|
||||
key: 'ip',
|
||||
title: 'IP',
|
||||
icon: 'ti ti-password',
|
||||
} : null, {
|
||||
key: 'raw',
|
||||
title: 'Raw data',
|
||||
icon: 'ti ti-code',
|
||||
}].filter(x => x != null));
|
||||
const file = ref<Misskey.entities.DriveFile | null>(null);
|
||||
|
||||
definePage(() => ({
|
||||
title: file.value ? `${i18n.ts.file}: ${file.value.name}` : i18n.ts.file,
|
||||
icon: 'ti ti-file',
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cxqhhsmd {
|
||||
> .thumbnail {
|
||||
display: block;
|
||||
|
||||
> .thumbnail {
|
||||
height: 300px;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
> .user {
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -152,6 +152,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
import { ref, computed } from 'vue';
|
||||
import JSON5 from 'json5';
|
||||
import { host } from '@@/js/config.js';
|
||||
import type { ClientOptions } from '@/instance.js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import * as os from '@/os.js';
|
||||
|
@ -166,9 +167,13 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
|||
|
||||
const meta = await misskeyApi('admin/meta');
|
||||
|
||||
const entrancePageStyle = ref(meta.clientOptions.entrancePageStyle ?? 'classic');
|
||||
const showTimelineForVisitor = ref(meta.clientOptions.showTimelineForVisitor ?? true);
|
||||
const showActivitiesForVisitor = ref(meta.clientOptions.showActivitiesForVisitor ?? true);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
const entrancePageStyle = ref<ClientOptions['entrancePageStyle']>(meta.clientOptions.entrancePageStyle ?? 'classic');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
const showTimelineForVisitor = ref<ClientOptions['showTimelineForVisitor']>(meta.clientOptions.showTimelineForVisitor ?? true);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
const showActivitiesForVisitor = ref<ClientOptions['showActivitiesForVisitor']>(meta.clientOptions.showActivitiesForVisitor ?? true);
|
||||
|
||||
const iconUrl = ref(meta.iconUrl);
|
||||
const app192IconUrl = ref(meta.app192IconUrl);
|
||||
const app512IconUrl = ref(meta.app512IconUrl);
|
||||
|
@ -186,11 +191,11 @@ const manifestJsonOverride = ref(meta.manifestJsonOverride === '' ? '{}' : JSON.
|
|||
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
clientOptions: {
|
||||
clientOptions: ({
|
||||
entrancePageStyle: entrancePageStyle.value,
|
||||
showTimelineForVisitor: showTimelineForVisitor.value,
|
||||
showActivitiesForVisitor: showActivitiesForVisitor.value,
|
||||
},
|
||||
} as ClientOptions) as any,
|
||||
iconUrl: iconUrl.value,
|
||||
app192IconUrl: app192IconUrl.value,
|
||||
app512IconUrl: app512IconUrl.value,
|
||||
|
|
|
@ -6,19 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 800px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory">
|
||||
<MkSuspense v-slot="{ result: database }" :p="databasePromiseFactory">
|
||||
<MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;">
|
||||
<template #key>{{ table[0] }}</template>
|
||||
<template #value>{{ bytes(table[1].size) }} ({{ number(table[1].count) }} recs)</template>
|
||||
</MkKeyValue>
|
||||
</FormSuspense>
|
||||
</MkSuspense>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import bytes from '@/filters/bytes.js';
|
||||
|
|
|
@ -107,7 +107,7 @@ const smtpPass = ref(meta.smtpPass);
|
|||
|
||||
async function testEmail() {
|
||||
const { canceled, result: destination } = await os.inputText({
|
||||
title: i18n.ts.destination,
|
||||
title: 'To',
|
||||
type: 'email',
|
||||
default: instance.maintainerEmail ?? '',
|
||||
placeholder: 'test@example.com',
|
||||
|
|
|
@ -81,10 +81,10 @@ function onStats(stats: Misskey.entities.QueueStats) {
|
|||
delayed.value = stats[props.domain].delayed;
|
||||
waiting.value = stats[props.domain].waiting;
|
||||
|
||||
chartProcess.value.pushData(stats[props.domain].activeSincePrevTick);
|
||||
chartActive.value.pushData(stats[props.domain].active);
|
||||
chartDelayed.value.pushData(stats[props.domain].delayed);
|
||||
chartWaiting.value.pushData(stats[props.domain].waiting);
|
||||
if (chartProcess.value != null) chartProcess.value.pushData(stats[props.domain].activeSincePrevTick);
|
||||
if (chartActive.value != null) chartActive.value.pushData(stats[props.domain].active);
|
||||
if (chartDelayed.value != null) chartDelayed.value.pushData(stats[props.domain].delayed);
|
||||
if (chartWaiting.value != null) chartWaiting.value.pushData(stats[props.domain].waiting);
|
||||
}
|
||||
|
||||
function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) {
|
||||
|
@ -100,10 +100,10 @@ function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) {
|
|||
dataWaiting.push(stats[props.domain].waiting);
|
||||
}
|
||||
|
||||
chartProcess.value.setData(dataProcess);
|
||||
chartActive.value.setData(dataActive);
|
||||
chartDelayed.value.setData(dataDelayed);
|
||||
chartWaiting.value.setData(dataWaiting);
|
||||
if (chartProcess.value != null) chartProcess.value.setData(dataProcess);
|
||||
if (chartActive.value != null) chartActive.value.setData(dataActive);
|
||||
if (chartDelayed.value != null) chartDelayed.value.setData(dataDelayed);
|
||||
if (chartWaiting.value != null) chartWaiting.value.setData(dataWaiting);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
|
|
@ -109,7 +109,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
import { ref, computed } from 'vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
|
|
@ -37,6 +37,8 @@ async function renderChart() {
|
|||
chartInstance.destroy();
|
||||
}
|
||||
|
||||
if (chartEl.value == null) return;
|
||||
|
||||
const getDate = (ago: number) => {
|
||||
const y = now.getFullYear();
|
||||
const m = now.getMonth();
|
||||
|
@ -105,7 +107,6 @@ async function renderChart() {
|
|||
type: 'time',
|
||||
offset: true,
|
||||
time: {
|
||||
stepSize: 1,
|
||||
unit: 'day',
|
||||
displayFormats: {
|
||||
day: 'M/d',
|
||||
|
@ -149,7 +150,9 @@ async function renderChart() {
|
|||
},
|
||||
external: externalTooltipHandler,
|
||||
},
|
||||
gradient,
|
||||
...({ // TSを黙らすため
|
||||
gradient,
|
||||
}),
|
||||
},
|
||||
},
|
||||
plugins: [chartVLine(vLineColor)],
|
||||
|
|
|
@ -42,6 +42,9 @@ const { handler: externalTooltipHandler } = useChartTooltip();
|
|||
const { handler: externalTooltipHandler2 } = useChartTooltip();
|
||||
|
||||
onMounted(async () => {
|
||||
if (chartEl.value == null) return;
|
||||
if (chartEl2.value == null) return;
|
||||
|
||||
const now = isChromatic() ? new Date('2024-08-31T10:00:00Z') : new Date();
|
||||
|
||||
const getDate = (ago: number) => {
|
||||
|
@ -122,7 +125,6 @@ onMounted(async () => {
|
|||
stacked: true,
|
||||
offset: false,
|
||||
time: {
|
||||
stepSize: 1,
|
||||
unit: 'day',
|
||||
},
|
||||
grid: {
|
||||
|
@ -144,7 +146,7 @@ onMounted(async () => {
|
|||
ticks: {
|
||||
display: true,
|
||||
//mirror: true,
|
||||
callback: (value, index, values) => value < 0 ? -value : value,
|
||||
callback: (value, index, values) => (value as number) < 0 ? -value : value,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -173,7 +175,9 @@ onMounted(async () => {
|
|||
label: context => `${context.dataset.label}: ${Math.abs(context.parsed.y)}`,
|
||||
},
|
||||
},
|
||||
gradient,
|
||||
...({ // TSを黙らすため
|
||||
gradient,
|
||||
}),
|
||||
},
|
||||
},
|
||||
plugins: [chartVLine(vLineColor)],
|
||||
|
@ -213,7 +217,6 @@ onMounted(async () => {
|
|||
type: 'time',
|
||||
offset: false,
|
||||
time: {
|
||||
stepSize: 1,
|
||||
unit: 'day',
|
||||
displayFormats: {
|
||||
day: 'M/d',
|
||||
|
@ -260,7 +263,9 @@ onMounted(async () => {
|
|||
},
|
||||
external: externalTooltipHandler2,
|
||||
},
|
||||
gradient,
|
||||
...({ // TSを黙らすため
|
||||
gradient,
|
||||
}),
|
||||
},
|
||||
},
|
||||
plugins: [chartVLine(vLineColor)],
|
||||
|
|
|
@ -828,8 +828,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { watch, ref, computed } from 'vue';
|
||||
import { throttle } from 'throttle-debounce';
|
||||
import { ROLE_POLICIES } from '@@/js/const.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import RolesEditorFormula from './RolesEditorFormula.vue';
|
||||
import type { GetMkSelectValueTypesFromDef, MkSelectItem } from '@/components/MkSelect.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkColorInput from '@/components/MkColorInput.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
|
@ -854,7 +855,7 @@ const props = defineProps<{
|
|||
const role = ref(deepClone(props.modelValue));
|
||||
|
||||
// fill missing policy
|
||||
for (const ROLE_POLICY of ROLE_POLICIES) {
|
||||
for (const ROLE_POLICY of Misskey.rolePolicies) {
|
||||
if (role.value.policies[ROLE_POLICY] == null) {
|
||||
role.value.policies[ROLE_POLICY] = {
|
||||
useDefault: true,
|
||||
|
|
|
@ -331,7 +331,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { ROLE_POLICIES } from '@@/js/const.js';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
|
@ -353,8 +353,8 @@ const baseRoleQ = ref('');
|
|||
|
||||
const roles = await misskeyApi('admin/roles/list');
|
||||
|
||||
const policies = reactive<Record<typeof ROLE_POLICIES[number], any>>({});
|
||||
for (const ROLE_POLICY of ROLE_POLICIES) {
|
||||
const policies = reactive<Record<typeof Misskey.rolePolicies[number], any>>({});
|
||||
for (const ROLE_POLICY of Misskey.rolePolicies) {
|
||||
policies[ROLE_POLICY] = instance.policies[ROLE_POLICY];
|
||||
}
|
||||
|
||||
|
|
|
@ -194,7 +194,7 @@ const sensitiveMediaDetectionForm = useForm({
|
|||
state.sensitiveMediaDetectionSensitivity === 2 ? 'medium' :
|
||||
state.sensitiveMediaDetectionSensitivity === 3 ? 'high' :
|
||||
state.sensitiveMediaDetectionSensitivity === 4 ? 'veryHigh' :
|
||||
0,
|
||||
null as never,
|
||||
setSensitiveFlagAutomatically: state.setSensitiveFlagAutomatically,
|
||||
enableSensitiveMediaDetectionForVideos: state.enableSensitiveMediaDetectionForVideos,
|
||||
});
|
||||
|
|
|
@ -138,9 +138,10 @@ async function addPinnedNote() {
|
|||
const { canceled, result: value } = await os.inputText({
|
||||
title: i18n.ts.noteIdOrUrl,
|
||||
});
|
||||
if (canceled) return;
|
||||
if (canceled || value == null) return;
|
||||
const fromUrl = value.includes('/') ? value.split('/').pop() : null;
|
||||
const note = await os.apiWithDialog('notes/show', {
|
||||
noteId: value.includes('/') ? value.split('/').pop() : value,
|
||||
noteId: fromUrl ?? value,
|
||||
});
|
||||
pinnedNotes.value = [{
|
||||
id: note.id,
|
||||
|
|
|
@ -48,8 +48,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div><b>{{ i18n.tsx.lastNDays({ n: 7 }) }} {{ i18n.ts.ranking }}</b> ({{ gameMode.toUpperCase() }})</div>
|
||||
<div v-if="ranking" class="_gaps_s">
|
||||
<div v-for="r in ranking" :key="r.id" :class="$style.rankingRecord">
|
||||
<MkAvatar :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/>
|
||||
<MkUserName :user="r.user" :nowrap="true"/>
|
||||
<MkAvatar v-if="r.user" :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/>
|
||||
<MkUserName v-if="r.user" :user="r.user" :nowrap="true"/>
|
||||
<b style="margin-left: auto;">{{ r.score.toLocaleString() }} {{ getScoreUnit(gameMode) }}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -87,6 +87,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 XGame from './drop-and-fusion.game.vue';
|
||||
import { definePage } from '@/page.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
|
@ -98,7 +99,7 @@ import { misskeyApiGet } from '@/utility/misskey-api.js';
|
|||
const gameMode = ref<'normal' | 'square' | 'yen' | 'sweets' | 'space'>('normal');
|
||||
const gameStarted = ref(false);
|
||||
const mute = ref(false);
|
||||
const ranking = ref(null);
|
||||
const ranking = ref<Misskey.entities.BubbleGameRankingResponse | null>(null);
|
||||
|
||||
watch(gameMode, async () => {
|
||||
ranking.value = await misskeyApiGet('bubble-game/ranking', { gameMode: gameMode.value });
|
||||
|
|
|
@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkInput v-model="name" pattern="[a-z0-9_]" autocapitalize="off">
|
||||
<template #label>{{ i18n.ts.name }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="category" :datalist="customEmojiCategories">
|
||||
<MkInput v-model="category" :datalist="customEmojiCategories.filter(x => x != null)">
|
||||
<template #label>{{ i18n.ts.category }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="aliases" autocapitalize="off">
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 800px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<MkInput v-model="title">
|
||||
<template #label>{{ i18n.ts.title }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkTextarea v-model="description" :max="500">
|
||||
<template #label>{{ i18n.ts.description }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : '' }">
|
||||
<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>
|
||||
</div>
|
||||
<MkButton primary @click="chooseFile"><i class="ti ti-plus"></i> {{ i18n.ts.attachFile }}</MkButton>
|
||||
</div>
|
||||
|
||||
<MkSwitch v-model="isSensitive">{{ i18n.ts.markAsSensitive }}</MkSwitch>
|
||||
|
||||
<div class="_buttons">
|
||||
<MkButton v-if="props.post != null" primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||
<MkButton v-else primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.publish }}</MkButton>
|
||||
|
||||
<MkButton v-if="props.post != null" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref } 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';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import { selectFile } from '@/utility/drive.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const props = defineProps<{
|
||||
post: Misskey.entities.GalleryPost | null;
|
||||
}>();
|
||||
|
||||
const files = ref(props.post?.files ?? []);
|
||||
const description = ref(props.post?.description ?? null);
|
||||
const title = ref(props.post?.title ?? '');
|
||||
const isSensitive = ref(props.post?.isSensitive ?? false);
|
||||
|
||||
function chooseFile(evt) {
|
||||
selectFile({
|
||||
anchorElement: evt.currentTarget ?? evt.target,
|
||||
multiple: true,
|
||||
}).then(selected => {
|
||||
files.value = files.value.concat(selected);
|
||||
});
|
||||
}
|
||||
|
||||
function remove(file) {
|
||||
files.value = files.value.filter(f => f.id !== file.id);
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (props.post != null) {
|
||||
await os.apiWithDialog('gallery/posts/update', {
|
||||
postId: props.post.id,
|
||||
title: title.value,
|
||||
description: description.value,
|
||||
fileIds: files.value.map(file => file.id),
|
||||
isSensitive: isSensitive.value,
|
||||
});
|
||||
router.push('/gallery/:postId', {
|
||||
params: {
|
||||
postId: props.post.id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const created = await os.apiWithDialog('gallery/posts/create', {
|
||||
title: title.value,
|
||||
description: description.value,
|
||||
fileIds: files.value.map(file => file.id),
|
||||
isSensitive: isSensitive.value,
|
||||
});
|
||||
router.push('/gallery/:postId', {
|
||||
params: {
|
||||
postId: created.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function del() {
|
||||
if (props.post == null) return;
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.deleteConfirm,
|
||||
});
|
||||
if (canceled) return;
|
||||
await os.apiWithDialog('gallery/posts/delete', {
|
||||
postId: props.post.id,
|
||||
});
|
||||
router.push('/gallery');
|
||||
}
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wqugxsfx {
|
||||
height: 200px;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
|
||||
> .name {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 9px;
|
||||
padding: 8px;
|
||||
background: var(--MI_THEME-panel);
|
||||
}
|
||||
|
||||
> .remove {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 9px;
|
||||
padding: 8px;
|
||||
background: var(--MI_THEME-panel);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -4,162 +4,35 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 800px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<FormSuspense :p="init" class="_gaps">
|
||||
<MkInput v-model="title">
|
||||
<template #label>{{ i18n.ts.title }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkTextarea v-model="description" :max="500">
|
||||
<template #label>{{ i18n.ts.description }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : '' }">
|
||||
<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>
|
||||
</div>
|
||||
<MkButton primary @click="chooseFile"><i class="ti ti-plus"></i> {{ i18n.ts.attachFile }}</MkButton>
|
||||
</div>
|
||||
|
||||
<MkSwitch v-model="isSensitive">{{ i18n.ts.markAsSensitive }}</MkSwitch>
|
||||
|
||||
<div class="_buttons">
|
||||
<MkButton v-if="postId" primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||
<MkButton v-else primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.publish }}</MkButton>
|
||||
|
||||
<MkButton v-if="postId" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
</FormSuspense>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
<MkSuspense v-slot="{ result }" :p="_fetch_">
|
||||
<XRoot :post="result"/>
|
||||
</MkSuspense>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import { ref } 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';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import { selectFile } from '@/utility/drive.js';
|
||||
import * as os from '@/os.js';
|
||||
import XRoot from './edit.root.vue';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
|
||||
const router = useRouter();
|
||||
import { definePage } from '@/page.js';
|
||||
|
||||
const props = defineProps<{
|
||||
postId?: string;
|
||||
postId?: string,
|
||||
}>();
|
||||
|
||||
const init = ref<(() => Promise<any>) | null>(null);
|
||||
const files = ref<Misskey.entities.DriveFile[]>([]);
|
||||
const description = ref<string | null>(null);
|
||||
const title = ref<string | null>(null);
|
||||
const isSensitive = ref(false);
|
||||
|
||||
function chooseFile(evt) {
|
||||
selectFile({
|
||||
anchorElement: evt.currentTarget ?? evt.target,
|
||||
multiple: true,
|
||||
}).then(selected => {
|
||||
files.value = files.value.concat(selected);
|
||||
});
|
||||
}
|
||||
|
||||
function remove(file) {
|
||||
files.value = files.value.filter(f => f.id !== file.id);
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (props.postId) {
|
||||
await os.apiWithDialog('gallery/posts/update', {
|
||||
postId: props.postId,
|
||||
title: title.value,
|
||||
description: description.value,
|
||||
fileIds: files.value.map(file => file.id),
|
||||
isSensitive: isSensitive.value,
|
||||
});
|
||||
router.push('/gallery/:postId', {
|
||||
params: {
|
||||
postId: props.postId,
|
||||
},
|
||||
});
|
||||
function _fetch_() {
|
||||
if (props.postId == null) {
|
||||
return Promise.resolve(null);
|
||||
} else {
|
||||
const created = await os.apiWithDialog('gallery/posts/create', {
|
||||
title: title.value,
|
||||
description: description.value,
|
||||
fileIds: files.value.map(file => file.id),
|
||||
isSensitive: isSensitive.value,
|
||||
});
|
||||
router.push('/gallery/:postId', {
|
||||
params: {
|
||||
postId: created.id,
|
||||
},
|
||||
return misskeyApi('gallery/posts/show', {
|
||||
postId: props.postId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.deleteConfirm,
|
||||
});
|
||||
if (canceled) return;
|
||||
await os.apiWithDialog('gallery/posts/delete', {
|
||||
postId: props.postId,
|
||||
});
|
||||
router.push('/gallery');
|
||||
}
|
||||
|
||||
watch(() => props.postId, () => {
|
||||
init.value = () => props.postId ? misskeyApi('gallery/posts/show', {
|
||||
postId: props.postId,
|
||||
}).then(post => {
|
||||
files.value = post.files ?? [];
|
||||
title.value = post.title;
|
||||
description.value = post.description;
|
||||
isSensitive.value = post.isSensitive;
|
||||
}) : Promise.resolve(null);
|
||||
}, { immediate: true });
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePage(() => ({
|
||||
title: props.postId ? i18n.ts.edit : i18n.ts.postToGallery,
|
||||
icon: 'ti ti-pencil',
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wqugxsfx {
|
||||
height: 200px;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
|
||||
> .name {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 9px;
|
||||
padding: 8px;
|
||||
background: var(--MI_THEME-panel);
|
||||
}
|
||||
|
||||
> .remove {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 9px;
|
||||
padding: 8px;
|
||||
background: var(--MI_THEME-panel);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { computed, markRaw, ref } from 'vue';
|
||||
import { notificationTypes } from '@@/js/const.js';
|
||||
import { notificationTypes } from 'misskey-js';
|
||||
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
import * as os from '@/os.js';
|
||||
|
|
|
@ -24,8 +24,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import XContainer from '../page-editor.container.vue';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
|
@ -35,11 +35,11 @@ import { getPageBlockList } from '@/pages/page-editor/common.js';
|
|||
const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue'));
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Misskey.entities.PageBlock & { type: 'section'; },
|
||||
modelValue: Extract<Misskey.entities.PageBlock, { type: 'section'; }>,
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'section' }): void;
|
||||
(ev: 'update:modelValue', value: Extract<Misskey.entities.PageBlock, { type: 'section'; }>): void;
|
||||
(ev: 'remove'): void;
|
||||
}>();
|
||||
|
||||
|
@ -59,7 +59,7 @@ async function rename() {
|
|||
title: i18n.ts._pages.enterSectionTitle,
|
||||
default: props.modelValue.title,
|
||||
});
|
||||
if (canceled) return;
|
||||
if (canceled || title == null) return;
|
||||
emit('update:modelValue', {
|
||||
...props.modelValue,
|
||||
title,
|
||||
|
|
|
@ -135,7 +135,7 @@ const emit = defineEmits<{
|
|||
|
||||
const dialog = useTemplateRef('dialog');
|
||||
const page = ref(0);
|
||||
const token = ref<string | number | null>(null);
|
||||
const token = ref<string | null>(null);
|
||||
const backupCodes = ref<string[]>();
|
||||
|
||||
function cancel() {
|
||||
|
@ -145,7 +145,7 @@ function cancel() {
|
|||
async function tokenDone() {
|
||||
if (token.value == null) return;
|
||||
const res = await os.apiWithDialog('i/2fa/done', {
|
||||
token: typeof token.value === 'string' ? token.value : token.value.toString(),
|
||||
token: token.value.toString(), // 実装ミスなどでnumberが入る可能性を払拭できないため念のためtoString
|
||||
});
|
||||
|
||||
backupCodes.value = res.backupCodes;
|
||||
|
|
|
@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { useTemplateRef, computed } from 'vue';
|
||||
import { notificationTypes } from '@@/js/const.js';
|
||||
import { notificationTypes } from 'misskey-js';
|
||||
import XNotificationConfig from './notifications.notification-config.vue';
|
||||
import type { NotificationConfig } from './notifications.notification-config.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
|
|
|
@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import XStatusbar from './statusbar.statusbar.vue';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
@ -39,6 +39,7 @@ onMounted(() => {
|
|||
async function add() {
|
||||
prefer.commit('statusbars', [...statusbars.value, {
|
||||
id: genId(),
|
||||
name: null,
|
||||
type: null,
|
||||
black: false,
|
||||
size: 'medium',
|
||||
|
|
|
@ -117,7 +117,6 @@ async function renderChart() {
|
|||
offset: true,
|
||||
stacked: true,
|
||||
time: {
|
||||
stepSize: 1,
|
||||
unit: 'day',
|
||||
displayFormats: {
|
||||
day: 'M/d',
|
||||
|
@ -162,7 +161,9 @@ async function renderChart() {
|
|||
},
|
||||
external: externalTooltipHandler,
|
||||
},
|
||||
gradient,
|
||||
...({ // TSを黙らすため
|
||||
gradient,
|
||||
}),
|
||||
},
|
||||
},
|
||||
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
|
||||
|
|
|
@ -116,7 +116,6 @@ async function renderChart() {
|
|||
offset: true,
|
||||
stacked: true,
|
||||
time: {
|
||||
stepSize: 1,
|
||||
unit: 'day',
|
||||
displayFormats: {
|
||||
day: 'M/d',
|
||||
|
@ -161,7 +160,9 @@ async function renderChart() {
|
|||
},
|
||||
external: externalTooltipHandler,
|
||||
},
|
||||
gradient,
|
||||
...({ // TSを黙らすため
|
||||
gradient,
|
||||
}),
|
||||
},
|
||||
},
|
||||
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
|
||||
|
|
|
@ -117,7 +117,6 @@ async function renderChart() {
|
|||
offset: true,
|
||||
stacked: true,
|
||||
time: {
|
||||
stepSize: 1,
|
||||
unit: 'day',
|
||||
displayFormats: {
|
||||
day: 'M/d',
|
||||
|
@ -155,8 +154,6 @@ async function renderChart() {
|
|||
display: true,
|
||||
text: 'Unique/Natural PV',
|
||||
padding: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 12,
|
||||
},
|
||||
|
@ -172,7 +169,9 @@ async function renderChart() {
|
|||
},
|
||||
external: externalTooltipHandler,
|
||||
},
|
||||
gradient,
|
||||
...({ // TSを黙らすため
|
||||
gradient,
|
||||
}),
|
||||
},
|
||||
},
|
||||
plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
|
||||
|
|
|
@ -209,7 +209,7 @@ const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
|
|||
const props = withDefaults(defineProps<{
|
||||
user: Misskey.entities.UserDetailed;
|
||||
/** Test only; MkNotesTimeline currently causes problems in vitest */
|
||||
disableNotes: boolean;
|
||||
disableNotes?: boolean;
|
||||
}>(), {
|
||||
disableNotes: false,
|
||||
});
|
||||
|
|
|
@ -205,13 +205,13 @@ type HandlerDef = {
|
|||
handler: (note: Misskey.entities.Note) => void;
|
||||
};
|
||||
note_view_interruptor: {
|
||||
handler: (note: Misskey.entities.Note) => unknown;
|
||||
handler: (note: Misskey.entities.Note) => Misskey.entities.Note;
|
||||
};
|
||||
note_post_interruptor: {
|
||||
handler: (note: FIXME) => unknown;
|
||||
};
|
||||
page_view_interruptor: {
|
||||
handler: (page: Misskey.entities.Page) => unknown;
|
||||
handler: (page: Misskey.entities.Page) => Misskey.entities.Page;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -32,6 +32,15 @@ export type SoundStore = {
|
|||
volume: number;
|
||||
};
|
||||
|
||||
export type StatusbarStore = {
|
||||
name: string | null;
|
||||
id: string;
|
||||
type: string | null;
|
||||
size: 'verySmall' | 'small' | 'medium' | 'large' | 'veryLarge';
|
||||
black: boolean;
|
||||
props: Record<string, any>;
|
||||
};
|
||||
|
||||
type OmitStrict<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never;
|
||||
|
||||
// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
|
||||
|
@ -182,14 +191,7 @@ export const PREF_DEF = definePreferences({
|
|||
],
|
||||
},
|
||||
statusbars: {
|
||||
default: [] as {
|
||||
name: string;
|
||||
id: string;
|
||||
type: string;
|
||||
size: 'verySmall' | 'small' | 'medium' | 'large' | 'veryLarge';
|
||||
black: boolean;
|
||||
props: Record<string, any>;
|
||||
}[],
|
||||
default: [] as StatusbarStore[],
|
||||
},
|
||||
serverDisconnectedBehavior: {
|
||||
default: 'quiet' as 'quiet' | 'reload' | 'dialog',
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
import type { Plugin } from 'chart.js';
|
||||
import MkChartLegend from '@/components/MkChartLegend.vue';
|
||||
|
||||
export const chartLegend = (legend: InstanceType<typeof MkChartLegend>) => ({
|
||||
export const chartLegend = (legend: InstanceType<typeof MkChartLegend> | null | undefined) => ({
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(chart, args, options) {
|
||||
if (legend == null) return;
|
||||
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = chart.options.plugins!.legend!.labels!.generateLabels!(chart);
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import type { notificationTypes as notificationTypes_typeReferenceOnly } from '@@/js/const.js';
|
||||
import { useWidgetPropsManager } from './widget.js';
|
||||
import type { notificationTypes as notificationTypes_typeReferenceOnly } from 'misskey-js';
|
||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||
import MkContainer from '@/components/MkContainer.vue';
|
||||
|
|
|
@ -3210,7 +3210,7 @@ type Notification_2 = components['schemas']['Notification'];
|
|||
type NotificationsCreateRequest = operations['notifications___create']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "chatRoomInvitationReceived", "achievementEarned"];
|
||||
export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "chatRoomInvitationReceived", "achievementEarned", "exportCompleted", "test", "login", "createToken"];
|
||||
|
||||
// @public (undocumented)
|
||||
export function nyaize(text: string): string;
|
||||
|
@ -3424,6 +3424,9 @@ type RoleLite = components['schemas']['RoleLite'];
|
|||
// @public (undocumented)
|
||||
type RolePolicies = components['schemas']['RolePolicies'];
|
||||
|
||||
// @public (undocumented)
|
||||
export const rolePolicies: readonly ["gtlAvailable", "ltlAvailable", "canPublicNote", "mentionLimit", "canInvite", "inviteLimit", "inviteLimitCycle", "inviteExpirationTime", "canManageCustomEmojis", "canManageAvatarDecorations", "canSearchNotes", "canSearchUsers", "canUseTranslator", "canHideAds", "driveCapacityMb", "maxFileSizeMb", "alwaysMarkNsfw", "canUpdateBioMedia", "pinLimit", "antennaLimit", "wordMuteLimit", "webhookLimit", "clipLimit", "noteEachClipsLimit", "userListLimit", "userEachUserListsLimit", "rateLimitFactor", "avatarDecorationLimit", "canImportAntennas", "canImportBlocking", "canImportFollowing", "canImportMuting", "canImportUserLists", "chatAvailability", "uploadableFileTypes", "noteDraftLimit", "watermarkAvailable"];
|
||||
|
||||
// @public (undocumented)
|
||||
type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json'];
|
||||
|
||||
|
|
|
@ -17,7 +17,27 @@ import type {
|
|||
ChatRoom,
|
||||
} from './autogen/models.js';
|
||||
|
||||
export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'chatRoomInvitationReceived', 'achievementEarned'] as const;
|
||||
export const notificationTypes = [
|
||||
'note',
|
||||
'follow',
|
||||
'mention',
|
||||
'reply',
|
||||
'renote',
|
||||
'quote',
|
||||
'reaction',
|
||||
'pollEnded',
|
||||
'receiveFollowRequest',
|
||||
'followRequestAccepted',
|
||||
'groupInvited',
|
||||
'app',
|
||||
'roleAssigned',
|
||||
'chatRoomInvitationReceived',
|
||||
'achievementEarned',
|
||||
'exportCompleted',
|
||||
'test',
|
||||
'login',
|
||||
'createToken',
|
||||
] as const;
|
||||
|
||||
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
|
||||
|
||||
|
@ -170,6 +190,46 @@ export const moderationLogTypes = [
|
|||
'updateProxyAccountDescription',
|
||||
] as const;
|
||||
|
||||
export const rolePolicies = [
|
||||
'gtlAvailable',
|
||||
'ltlAvailable',
|
||||
'canPublicNote',
|
||||
'mentionLimit',
|
||||
'canInvite',
|
||||
'inviteLimit',
|
||||
'inviteLimitCycle',
|
||||
'inviteExpirationTime',
|
||||
'canManageCustomEmojis',
|
||||
'canManageAvatarDecorations',
|
||||
'canSearchNotes',
|
||||
'canSearchUsers',
|
||||
'canUseTranslator',
|
||||
'canHideAds',
|
||||
'driveCapacityMb',
|
||||
'maxFileSizeMb',
|
||||
'alwaysMarkNsfw',
|
||||
'canUpdateBioMedia',
|
||||
'pinLimit',
|
||||
'antennaLimit',
|
||||
'wordMuteLimit',
|
||||
'webhookLimit',
|
||||
'clipLimit',
|
||||
'noteEachClipsLimit',
|
||||
'userListLimit',
|
||||
'userEachUserListsLimit',
|
||||
'rateLimitFactor',
|
||||
'avatarDecorationLimit',
|
||||
'canImportAntennas',
|
||||
'canImportBlocking',
|
||||
'canImportFollowing',
|
||||
'canImportMuting',
|
||||
'canImportUserLists',
|
||||
'chatAvailability',
|
||||
'uploadableFileTypes',
|
||||
'noteDraftLimit',
|
||||
'watermarkAvailable',
|
||||
] as const;
|
||||
|
||||
export const queueTypes = [
|
||||
'system',
|
||||
'endedPollNotification',
|
||||
|
|
|
@ -13,6 +13,7 @@ export const mutedNoteReasons = consts.mutedNoteReasons;
|
|||
export const followingVisibilities = consts.followingVisibilities;
|
||||
export const followersVisibilities = consts.followersVisibilities;
|
||||
export const moderationLogTypes = consts.moderationLogTypes;
|
||||
export const rolePolicies = consts.rolePolicies;
|
||||
export const queueTypes = consts.queueTypes;
|
||||
export const reversiUpdateKeys = consts.reversiUpdateKeys;
|
||||
|
||||
|
|
|
@ -2282,6 +2282,7 @@ packages:
|
|||
|
||||
'@github/webauthn-json@2.1.1':
|
||||
resolution: {integrity: sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==}
|
||||
deprecated: 'Deprecated: Modern browsers support built-in WebAuthn JSON methods. Please use native browser methods instead. For more information, visit https://github.com/github/webauthn-json'
|
||||
hasBin: true
|
||||
|
||||
'@hapi/boom@10.0.1':
|
||||
|
@ -3699,6 +3700,7 @@ packages:
|
|||
|
||||
'@simplewebauthn/types@12.0.0':
|
||||
resolution: {integrity: sha512-q6y8MkoV8V8jB4zzp18Uyj2I7oFp2/ONL8c3j8uT06AOWu3cIChc1au71QYHrP2b+xDapkGTiv+9lX7xkTlAsA==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
|
||||
'@sinclair/typebox@0.27.8':
|
||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||
|
|
Loading…
Reference in New Issue