Merge branch 'develop' into pr/ThatOneCalculator/8764

This commit is contained in:
tamaina 2022-06-16 21:19:17 +09:00
commit 7ddfd049a4
234 changed files with 7586 additions and 783 deletions

View file

@ -15,6 +15,12 @@ module.exports = {
'plugin:vue/vue3-recommended',
],
rules: {
'@typescript-eslint/no-empty-interface': [
'error',
{
'allowSingleExtends': true,
},
],
// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
// data の禁止理由: 抽象的すぎるため
// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため

View file

@ -19,13 +19,13 @@
"@rollup/pluginutils": "^4.2.1",
"@syuilo/aiscript": "0.11.1",
"@vitejs/plugin-vue": "2.3.3",
"@vue/compiler-sfc": "3.2.36",
"@vue/compiler-sfc": "3.2.37",
"abort-controller": "3.0.0",
"autobind-decorator": "2.4.0",
"autosize": "5.0.1",
"autwh": "0.1.0",
"blurhash": "1.1.5",
"broadcast-channel": "4.12.0",
"broadcast-channel": "4.13.0",
"browser-image-resizer": "misskey-dev/browser-image-resizer#tag=v2.2.1-misskey.2",
"chart.js": "3.8.0",
"chartjs-adapter-date-fns": "2.0.0",
@ -33,7 +33,8 @@
"chartjs-plugin-zoom": "1.2.1",
"compare-versions": "4.1.3",
"content-disposition": "0.5.4",
"date-fns": "^2.28.0",
"cropperjs": "2.0.0-beta",
"date-fns": "2.28.0",
"escape-regexp": "0.0.1",
"eventemitter3": "4.0.7",
"feed": "4.2.2",
@ -58,9 +59,9 @@
"random-seed": "0.3.0",
"reflect-metadata": "0.1.13",
"rndstr": "1.0.0",
"rollup": "2.75.3",
"rollup": "2.75.6",
"s-age": "1.1.2",
"sass": "1.52.1",
"sass": "1.52.3",
"seedrandom": "3.0.5",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
@ -69,20 +70,20 @@
"three": "0.141.0",
"throttle-debounce": "5.0.0",
"tinycolor2": "1.4.2",
"tsc-alias": "1.6.7",
"tsc-alias": "1.6.9",
"tsconfig-paths": "4.0.0",
"twemoji-parser": "14.0.0",
"typescript": "4.7.2",
"typescript": "4.7.3",
"uuid": "8.3.2",
"v-debounce": "0.1.2",
"vanilla-tilt": "1.7.2",
"vite": "2.9.9",
"vue": "3.2.36",
"vite": "2.9.10",
"vue": "3.2.37",
"vue-prism-editor": "2.0.0-alpha.2",
"vue-router": "4.0.15",
"vuedraggable": "4.1.0",
"vue-router": "4.0.16",
"vuedraggable": "4.0.1",
"websocket": "1.0.34",
"ws": "8.7.0"
"ws": "8.8.0"
},
"devDependencies": {
"@types/escape-regexp": "0.0.1",
@ -103,13 +104,13 @@
"@types/uuid": "8.3.4",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "5.27.0",
"@typescript-eslint/parser": "5.27.0",
"@typescript-eslint/eslint-plugin": "5.27.1",
"@typescript-eslint/parser": "5.27.1",
"cross-env": "7.0.3",
"cypress": "9.7.0",
"eslint": "8.16.0",
"cypress": "10.0.3",
"eslint": "8.17.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-vue": "9.0.1",
"eslint-plugin-vue": "9.1.0",
"start-server-and-test": "1.14.0"
}
}

View file

@ -27,8 +27,7 @@ type CaptchaContainer = {
};
declare global {
interface Window extends CaptchaContainer {
}
interface Window extends CaptchaContainer { }
}
const props = defineProps<{

View file

@ -0,0 +1,175 @@
<template>
<XModalWindow
ref="dialogEl"
:width="800"
:height="500"
:scroll="false"
:with-ok-button="true"
@close="cancel()"
@ok="ok()"
@closed="$emit('closed')"
>
<template #header>{{ $ts.cropImage }}</template>
<template #default="{ width, height }">
<div class="mk-cropper-dialog" :style="`--vw: ${width}px; --vh: ${height}px;`">
<Transition name="fade">
<div v-if="loading" class="loading">
<MkLoading/>
</div>
</Transition>
<div class="container">
<img ref="imgEl" :src="imgUrl" style="display: none;" @load="onImageLoad">
</div>
</div>
</template>
</XModalWindow>
</template>
<script lang="ts" setup>
import { nextTick, onMounted } from 'vue';
import * as misskey from 'misskey-js';
import Cropper from 'cropperjs';
import tinycolor from 'tinycolor2';
import XModalWindow from '@/components/ui/modal-window.vue';
import * as os from '@/os';
import { $i } from '@/account';
import { defaultStore } from '@/store';
import { apiUrl, url } from '@/config';
import { query } from '@/scripts/url';
const emit = defineEmits<{
(ev: 'ok', cropped: misskey.entities.DriveFile): void;
(ev: 'cancel'): void;
(ev: 'closed'): void;
}>();
const props = defineProps<{
file: misskey.entities.DriveFile;
aspectRatio: number;
}>();
const imgUrl = `${url}/proxy/image.webp?${query({
url: props.file.url,
})}`;
let dialogEl = $ref<InstanceType<typeof XModalWindow>>();
let imgEl = $ref<HTMLImageElement>();
let cropper: Cropper | null = null;
let loading = $ref(true);
const ok = async () => {
const promise = new Promise<misskey.entities.DriveFile>(async (res) => {
const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas();
croppedCanvas.toBlob(blob => {
const formData = new FormData();
formData.append('file', blob);
formData.append('i', $i.token);
if (defaultStore.state.uploadFolder) {
formData.append('folderId', defaultStore.state.uploadFolder);
}
fetch(apiUrl + '/drive/files/create', {
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(f => {
res(f);
});
});
});
os.promiseDialog(promise);
const f = await promise;
emit('ok', f);
dialogEl.close();
};
const cancel = () => {
emit('cancel');
dialogEl.close();
};
const onImageLoad = () => {
loading = false;
if (cropper) {
cropper.getCropperImage()!.$center('contain');
cropper.getCropperSelection()!.$center();
}
};
onMounted(() => {
cropper = new Cropper(imgEl, {
});
const computedStyle = getComputedStyle(document.documentElement);
const selection = cropper.getCropperSelection()!;
selection.themeColor = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
selection.aspectRatio = props.aspectRatio;
selection.initialAspectRatio = props.aspectRatio;
selection.outlined = true;
window.setTimeout(() => {
cropper.getCropperImage()!.$center('contain');
selection.$center();
}, 100);
// 調
window.setTimeout(() => {
cropper.getCropperImage()!.$center('contain');
selection.$center();
}, 500);
});
</script>
<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease 0.5s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.mk-cropper-dialog {
display: flex;
flex-direction: column;
width: var(--vw);
height: var(--vh);
position: relative;
> .loading {
position: absolute;
z-index: 10;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
-webkit-backdrop-filter: var(--blur, blur(10px));
backdrop-filter: var(--blur, blur(10px));
background: rgba(0, 0, 0, 0.5);
}
> .container {
flex: 1;
width: 100%;
height: 100%;
> ::v-deep(cropper-canvas) {
width: 100%;
height: 100%;
> cropper-selection > cropper-handle[action="move"] {
background: transparent;
}
}
}
}
</style>

View file

@ -1,6 +1,6 @@
<template>
<div ref="thumbnail" class="zdjebgpv">
<ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :style="`object-fit: ${ fit }`"/>
<ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/>
<i v-else-if="is === 'image'" class="fas fa-file-image icon"></i>
<i v-else-if="is === 'video'" class="fas fa-file-video icon"></i>
<i v-else-if="is === 'audio' || is === 'midi'" class="fas fa-music icon"></i>
@ -33,16 +33,16 @@ const is = computed(() => {
if (props.file.type.endsWith('/pdf')) return 'pdf';
if (props.file.type.startsWith('text/')) return 'textfile';
if ([
"application/zip",
"application/x-cpio",
"application/x-bzip",
"application/x-bzip2",
"application/java-archive",
"application/x-rar-compressed",
"application/x-tar",
"application/gzip",
"application/x-7z-compressed"
].some(archiveType => archiveType === props.file.type)) return 'archive';
'application/zip',
'application/x-cpio',
'application/x-bzip',
'application/x-bzip2',
'application/java-archive',
'application/x-rar-compressed',
'application/x-tar',
'application/gzip',
'application/x-7z-compressed',
].some(archiveType => archiveType === props.file.type)) return 'archive';
return 'unknown';
});

View file

@ -71,7 +71,7 @@ function onMouseover() {
}
function onMouseout() {
hover.value = false
hover.value = false;
}
function onDragover(ev: DragEvent) {
@ -204,7 +204,7 @@ function deleteFolder() {
defaultStore.set('uploadFolder', null);
}
}).catch(err => {
switch(err.id) {
switch (err.id) {
case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
os.alert({
type: 'error',

View file

@ -143,7 +143,7 @@ const fetching = ref(true);
const ilFilesObserver = new IntersectionObserver(
(entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles()
)
);
watch(folder, () => emit('cd', folder.value));
@ -332,7 +332,7 @@ function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) {
//
move(folderToDelete.parentId);
}).catch(err => {
switch(err.id) {
switch (err.id) {
case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
os.alert({
type: 'error',
@ -607,7 +607,7 @@ function onContextmenu(ev: MouseEvent) {
onMounted(() => {
if (defaultStore.state.enableInfiniteScroll && loadMoreFiles.value) {
nextTick(() => {
ilFilesObserver.observe(loadMoreFiles.value?.$el)
ilFilesObserver.observe(loadMoreFiles.value?.$el);
});
}
@ -628,7 +628,7 @@ onMounted(() => {
onActivated(() => {
if (defaultStore.state.enableInfiniteScroll) {
nextTick(() => {
ilFilesObserver.observe(loadMoreFiles.value?.$el)
ilFilesObserver.observe(loadMoreFiles.value?.$el);
});
}
});

View file

@ -1,6 +1,6 @@
<template>
<div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
<input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" @paste.stop="paste" @keyup.enter="done()">
<input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @paste.stop="paste" @keyup.enter="done()">
<div ref="emojis" class="emojis">
<section class="result">
<div v-if="searchResultCustom.length > 0">

View file

@ -28,7 +28,7 @@
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { onBeforeUnmount, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os';
import { stream } from '@/stream';
@ -43,32 +43,30 @@ const props = withDefaults(defineProps<{
large: false,
});
const isFollowing = ref(props.user.isFollowing);
const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFromYou);
const wait = ref(false);
let isFollowing = $ref(props.user.isFollowing);
let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou);
let wait = $ref(false);
const connection = stream.useChannel('main');
if (props.user.isFollowing == null) {
os.api('users/show', {
userId: props.user.id
}).then(u => {
isFollowing.value = u.isFollowing;
hasPendingFollowRequestFromYou.value = u.hasPendingFollowRequestFromYou;
});
})
.then(onFollowChange);
}
function onFollowChange(user: Misskey.entities.UserDetailed) {
if (user.id === props.user.id) {
isFollowing.value = user.isFollowing;
hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou;
isFollowing = user.isFollowing;
hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
}
}
async function onClick() {
wait.value = true;
wait = true;
try {
if (isFollowing.value) {
if (isFollowing) {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }),
@ -80,26 +78,22 @@ async function onClick() {
userId: props.user.id
});
} else {
if (hasPendingFollowRequestFromYou.value) {
if (hasPendingFollowRequestFromYou) {
await os.api('following/requests/cancel', {
userId: props.user.id
});
} else if (props.user.isLocked) {
await os.api('following/create', {
userId: props.user.id
});
hasPendingFollowRequestFromYou.value = true;
hasPendingFollowRequestFromYou = false;
} else {
await os.api('following/create', {
userId: props.user.id
});
hasPendingFollowRequestFromYou.value = true;
hasPendingFollowRequestFromYou = true;
}
}
} catch (err) {
console.error(err);
} finally {
wait.value = false;
wait = false;
}
}

View file

@ -24,7 +24,7 @@ const props = withDefaults(defineProps<{
defaultOpen: boolean;
}>(), {
defaultOpen: false,
})
});
let opened = $ref(props.defaultOpen);
let openedAtLeastOnce = $ref(props.defaultOpen);

View file

@ -14,7 +14,7 @@ export default defineComponent({
data() {
return {
value: this.modelValue,
}
};
},
watch: {
value() {

View file

@ -8,7 +8,8 @@
<div v-else class="kkjnbbplepmiyuadieoenjgutgcmtsvu">
<video
:poster="video.thumbnailUrl"
:title="video.name"
:title="video.comment"
:alt="video.comment"
preload="none"
controls
@contextmenu.stop

View file

@ -39,8 +39,8 @@ export default defineComponent({
inject: {
sideViewHook: {
default: null
}
default: null,
},
},
provide() {
@ -94,31 +94,31 @@ export default defineComponent({
}, {
icon: 'fas fa-expand-alt',
text: this.$ts.showInPage,
action: this.expand
action: this.expand,
}, this.sideViewHook ? {
icon: 'fas fa-columns',
text: this.$ts.openInSideView,
action: () => {
this.sideViewHook(this.path);
this.$refs.window.close();
}
},
} : undefined, {
icon: 'fas fa-external-link-alt',
text: this.$ts.popout,
action: this.popout
action: this.popout,
}, null, {
icon: 'fas fa-external-link-alt',
text: this.$ts.openInNewTab,
action: () => {
window.open(this.url, '_blank');
this.$refs.window.close();
}
},
}, {
icon: 'fas fa-link',
text: this.$ts.copyLink,
action: () => {
copyToClipboard(this.url);
}
},
}];
},
},
@ -155,7 +155,7 @@ export default defineComponent({
onContextmenu(ev: MouseEvent) {
os.contextMenu(this.contextmenu, ev);
}
},
},
});
</script>

View file

@ -222,7 +222,7 @@ function react(viaKeyboard = false): void {
reactionPicker.show(reactButton.value, reaction => {
os.api('notes/reactions/create', {
noteId: appearNote.id,
reaction: reaction
reaction: reaction,
});
}, () => {
focus();
@ -233,7 +233,7 @@ function undoReact(note): void {
const oldReaction = note.myReaction;
if (!oldReaction) return;
os.api('notes/reactions/delete', {
noteId: note.id
noteId: note.id,
});
}
@ -257,7 +257,7 @@ function onContextmenu(ev: MouseEvent): void {
function menu(viaKeyboard = false): void {
os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, {
viaKeyboard
viaKeyboard,
}).then(focus);
}
@ -269,12 +269,12 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true,
action: () => {
os.api('notes/delete', {
noteId: note.id
noteId: note.id,
});
isDeleted.value = true;
}
},
}], renoteTime.value, {
viaKeyboard: viaKeyboard
viaKeyboard: viaKeyboard,
});
}
@ -288,14 +288,14 @@ function blur() {
os.api('notes/children', {
noteId: appearNote.id,
limit: 30
limit: 30,
}).then(res => {
replies.value = res;
});
if (appearNote.replyId) {
os.api('notes/conversation', {
noteId: appearNote.replyId
noteId: appearNote.replyId,
}).then(res => {
conversation.value = res.reverse();
});

View file

@ -5,7 +5,7 @@
<XNoteHeader class="header" :note="note" :mini="true"/>
<div class="body">
<p v-if="note.cw != null" class="cw">
<span v-if="note.cw != ''" class="text">{{ note.cw }}</span>
<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
<XCwButton v-model="showContent" :note="note"/>
</p>
<div v-show="note.cw == null || showContent" class="content">

View file

@ -210,7 +210,7 @@ function react(viaKeyboard = false): void {
reactionPicker.show(reactButton.value, reaction => {
os.api('notes/reactions/create', {
noteId: appearNote.id,
reaction: reaction
reaction: reaction,
});
}, () => {
focus();
@ -221,7 +221,7 @@ function undoReact(note): void {
const oldReaction = note.myReaction;
if (!oldReaction) return;
os.api('notes/reactions/delete', {
noteId: note.id
noteId: note.id,
});
}
@ -245,7 +245,7 @@ function onContextmenu(ev: MouseEvent): void {
function menu(viaKeyboard = false): void {
os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, {
viaKeyboard
viaKeyboard,
}).then(focus);
}
@ -257,12 +257,12 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true,
action: () => {
os.api('notes/delete', {
noteId: note.id
noteId: note.id,
});
isDeleted.value = true;
}
},
}], renoteTime.value, {
viaKeyboard: viaKeyboard
viaKeyboard: viaKeyboard,
});
}
@ -284,7 +284,7 @@ function focusAfter() {
function readPromo() {
os.api('promo/read', {
noteId: appearNote.id
noteId: appearNote.id,
});
isDeleted.value = true;
}

View file

@ -1,5 +1,6 @@
<template>
<XModalWindow ref="dialog"
<XModalWindow
ref="dialog"
:width="400"
:height="450"
:with-ok-button="true"
@ -28,18 +29,18 @@
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import XModalWindow from '@/components/ui/modal-window.vue';
import { notificationTypes } from 'misskey-js';
import MkSwitch from './form/switch.vue';
import MkInfo from './ui/info.vue';
import MkButton from './ui/button.vue';
import { notificationTypes } from 'misskey-js';
import XModalWindow from '@/components/ui/modal-window.vue';
export default defineComponent({
components: {
XModalWindow,
MkSwitch,
MkInfo,
MkButton
MkButton,
},
props: {
@ -53,7 +54,7 @@ export default defineComponent({
type: Boolean,
required: false,
default: true,
}
},
},
emits: ['done', 'closed'],
@ -93,7 +94,7 @@ export default defineComponent({
for (const type in this.typesMap) {
this.typesMap[type as typeof notificationTypes[number]] = true;
}
}
}
},
},
});
</script>

View file

@ -16,7 +16,8 @@
<i v-else-if="notification.type === 'pollVote'" class="fas fa-poll-h"></i>
<i v-else-if="notification.type === 'pollEnded'" class="fas fa-poll-h"></i>
<!-- notification.reaction null になることはまずないがここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
<XReactionIcon v-else-if="notification.type === 'reaction'"
<XReactionIcon
v-else-if="notification.type === 'reaction'"
ref="reactionRef"
:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction"
:custom-emojis="notification.note.emojis"
@ -74,10 +75,10 @@
<script lang="ts">
import { defineComponent, ref, onMounted, onUnmounted, watch } from 'vue';
import * as misskey from 'misskey-js';
import { getNoteSummary } from '@/scripts/get-note-summary';
import XReactionIcon from './reaction-icon.vue';
import MkFollowButton from './follow-button.vue';
import XReactionTooltip from './reaction-tooltip.vue';
import { getNoteSummary } from '@/scripts/get-note-summary';
import { notePage } from '@/filters/note';
import { userPage } from '@/filters/user';
import { i18n } from '@/i18n';
@ -87,7 +88,7 @@ import { useTooltip } from '@/scripts/use-tooltip';
export default defineComponent({
components: {
XReactionIcon, MkFollowButton
XReactionIcon, MkFollowButton,
},
props: {
@ -116,7 +117,7 @@ export default defineComponent({
const readObserver = new IntersectionObserver((entries, observer) => {
if (!entries.some(entry => entry.isIntersecting)) return;
stream.send('readNotification', {
id: props.notification.id
id: props.notification.id,
});
observer.disconnect();
});

View file

@ -19,8 +19,7 @@
<script lang="ts" setup>
import { defineComponent, markRaw, onUnmounted, onMounted, computed, ref } from 'vue';
import { notificationTypes } from 'misskey-js';
import MkPagination from '@/components/ui/pagination.vue';
import { Paging } from '@/components/ui/pagination.vue';
import MkPagination, { Paging } from '@/components/ui/pagination.vue';
import XNotification from '@/components/notification.vue';
import XList from '@/components/date-separated-list.vue';
import XNote from '@/components/note.vue';
@ -49,14 +48,14 @@ const onNotification = (notification) => {
const isMuted = props.includeTypes ? !props.includeTypes.includes(notification.type) : $i.mutingNotificationTypes.includes(notification.type);
if (isMuted || document.visibilityState === 'visible') {
stream.send('readNotification', {
id: notification.id
id: notification.id,
});
}
if (!isMuted) {
pagingComponent.value.prepend({
...notification,
isRead: document.visibilityState === 'visible'
isRead: document.visibilityState === 'visible',
});
}
};

View file

@ -12,7 +12,7 @@ export default defineComponent({
props: {
value: {
type: Number,
required: true
required: true,
},
},
@ -26,7 +26,7 @@ export default defineComponent({
isZero,
number,
};
}
},
});
</script>

View file

@ -66,7 +66,7 @@ export default defineComponent({
.then(response => response.json())
.then(f => {
ok(f);
})
});
});
});
os.promiseDialog(promise);

View file

@ -16,7 +16,7 @@
<script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue';
import MkDriveFileThumbnail from './drive-file-thumbnail.vue'
import MkDriveFileThumbnail from './drive-file-thumbnail.vue';
import * as os from '@/os';
export default defineComponent({
@ -114,19 +114,19 @@ export default defineComponent({
this.menu = os.popupMenu([{
text: this.$ts.renameFile,
icon: 'fas fa-i-cursor',
action: () => { this.rename(file) }
action: () => { this.rename(file); }
}, {
text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive,
icon: file.isSensitive ? 'fas fa-eye-slash' : 'fas fa-eye',
action: () => { this.toggleSensitive(file) }
action: () => { this.toggleSensitive(file); }
}, {
text: this.$ts.describeFile,
icon: 'fas fa-i-cursor',
action: () => { this.describe(file) }
action: () => { this.describe(file); }
}, {
text: this.$ts.attachCancel,
icon: 'fas fa-times-circle',
action: () => { this.detachMedia(file.id) }
action: () => { this.detachMedia(file.id); }
}], ev.currentTarget ?? ev.target).then(() => this.menu = null);
}
}

View file

@ -442,7 +442,7 @@ function onCompositionEnd(ev: CompositionEvent) {
}
async function onPaste(ev: ClipboardEvent) {
for (const { item, i } of Array.from(ev.clipboardData.items).map((item, i) => ({item, i}))) {
for (const { item, i } of Array.from(ev.clipboardData.items).map((item, i) => ({ item, i }))) {
if (item.kind === 'file') {
const file = item.getAsFile();
const lio = file.name.lastIndexOf('.');

View file

@ -222,7 +222,7 @@ export default defineComponent({
return {
chartEl,
}
};
},
});
</script>

View file

@ -52,7 +52,7 @@ export default defineComponent({
flag: true,
radio: 'misskey',
mfm: `Hello world! This is an @example mention. BTW you are @${this.$i ? this.$i.username : 'guest'}.\nAlso, here is ${config.url} and [example link](${config.url}). for more details, see https://example.com.\nAs you know #misskey is open-source software.`
}
};
},
methods: {

View file

@ -159,7 +159,7 @@ function queryKey() {
function onSubmit() {
signing = true;
console.log('submit')
console.log('submit');
if (!totpLogin && user && user.twoFactorEnabled) {
if (window.PublicKeyCredential && user.securityKeys) {
os.api('signin', {
@ -222,7 +222,7 @@ function loginFailed(err) {
break;
}
default: {
console.log(err)
console.log(err);
os.alert({
type: 'error',
title: i18n.ts.loginFailed,

View file

@ -1,11 +1,11 @@
<template>
<form class="qlvuhzng _formRoot" :autocomplete="Math.random()" @submit.prevent="onSubmit">
<form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit">
<template v-if="meta">
<MkInput v-if="meta.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :autocomplete="Math.random()" spellcheck="false" required>
<MkInput v-if="meta.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" spellcheck="false" required>
<template #label>{{ $ts.invitationCode }}</template>
<template #prefix><i class="fas fa-key"></i></template>
</MkInput>
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername">
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername">
<template #label>{{ $ts.username }} <div v-tooltip:dialog="$ts.usernameInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template>
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
@ -19,7 +19,7 @@
<span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span>
</template>
</MkInput>
<MkInput v-if="meta.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" :autocomplete="Math.random()" spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail">
<MkInput v-if="meta.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail">
<template #label>{{ $ts.emailAddress }} <div v-tooltip:dialog="$ts._signup.emailAddressInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template>
<template #prefix><i class="fas fa-envelope"></i></template>
<template #caption>
@ -34,7 +34,7 @@
<span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span>
</template>
</MkInput>
<MkInput v-model="password" class="_formBlock" type="password" :autocomplete="Math.random()" required data-cy-signup-password @update:modelValue="onChangePassword">
<MkInput v-model="password" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword">
<template #label>{{ $ts.password }}</template>
<template #prefix><i class="fas fa-lock"></i></template>
<template #caption>
@ -43,7 +43,7 @@
<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span>
</template>
</MkInput>
<MkInput v-model="retypedPassword" class="_formBlock" type="password" :autocomplete="Math.random()" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype">
<MkInput v-model="retypedPassword" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype">
<template #label>{{ $ts.password }} ({{ $ts.retype }})</template>
<template #prefix><i class="fas fa-lock"></i></template>
<template #caption>
@ -111,7 +111,7 @@ export default defineComponent({
ToSAgreement: false,
hCaptchaResponse: null,
reCaptchaResponse: null,
}
};
},
computed: {

View file

@ -96,11 +96,11 @@ export default defineComponent({
}
function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY) {
const origin = {x: circleCenterX, y: circleCenterY};
const dist1 = distance({x: 0, y: 0}, origin);
const dist2 = distance({x: boxW, y: 0}, origin);
const dist3 = distance({x: 0, y: boxH}, origin);
const dist4 = distance({x: boxW, y: boxH }, origin);
const origin = { x: circleCenterX, y: circleCenterY };
const dist1 = distance({ x: 0, y: 0 }, origin);
const dist2 = distance({ x: boxW, y: 0 }, origin);
const dist3 = distance({ x: 0, y: boxH }, origin);
const dist4 = distance({ x: boxW, y: boxH }, origin);
return Math.max(dist1, dist2, dist3, dist4) * 2;
}

View file

@ -1,5 +1,6 @@
<template>
<div ref="itemsEl" v-hotkey="keymap"
<div
ref="itemsEl" v-hotkey="keymap"
class="rrevdjwt"
:class="{ center: align === 'center', asDrawer }"
:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }"
@ -162,6 +163,15 @@ function focusDown() {
position: relative;
}
&:not(:disabled):hover {
color: var(--accent);
text-decoration: none;
&:before {
background: var(--accentedBg);
}
}
&.danger {
color: #ff2a2a;
@ -191,15 +201,6 @@ function focusDown() {
}
}
&:not(:disabled):hover {
color: var(--accent);
text-decoration: none;
&:before {
background: var(--accentedBg);
}
}
&:not(:active):focus-visible {
box-shadow: 0 0 0 2px var(--focus) inset;
}

View file

@ -1,7 +1,7 @@
<template>
<MkModal ref="modal" :prefer-type="'dialog'" @click="$emit('click')" @closed="$emit('closed')">
<div class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
<div class="header">
<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')">
<div ref="rootEl" class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
<div ref="headerEl" class="header">
<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button>
<span class="title">
<slot name="header"></slot>
@ -11,82 +11,82 @@
</div>
<div v-if="padding" class="body">
<div class="_section">
<slot></slot>
<slot :width="bodyWidth" :height="bodyHeight"></slot>
</div>
</div>
<div v-else class="body">
<slot></slot>
<slot :width="bodyWidth" :height="bodyHeight"></slot>
</div>
</div>
</MkModal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue';
import MkModal from './modal.vue';
export default defineComponent({
components: {
MkModal
},
props: {
withOkButton: {
type: Boolean,
required: false,
default: false
},
okButtonDisabled: {
type: Boolean,
required: false,
default: false
},
padding: {
type: Boolean,
required: false,
default: false
},
width: {
type: Number,
required: false,
default: 400
},
height: {
type: Number,
required: false,
default: null
},
canClose: {
type: Boolean,
required: false,
default: true,
},
scroll: {
type: Boolean,
required: false,
default: true,
},
},
const props = withDefaults(defineProps<{
withOkButton: boolean;
okButtonDisabled: boolean;
padding: boolean;
width: number;
height: number | null;
scroll: boolean;
}>(), {
withOkButton: false,
okButtonDisabled: false,
padding: false,
width: 400,
height: null,
scroll: true,
});
emits: ['click', 'close', 'closed', 'ok'],
const emit = defineEmits<{
(event: 'click'): void;
(event: 'close'): void;
(event: 'closed'): void;
(event: 'ok'): void;
}>();
data() {
return {
};
},
let modal = $ref<InstanceType<typeof MkModal>>();
let rootEl = $ref<HTMLElement>();
let headerEl = $ref<HTMLElement>();
let bodyWidth = $ref(0);
let bodyHeight = $ref(0);
methods: {
close() {
this.$refs.modal.close();
},
const close = () => {
modal.close();
};
onKeydown(evt) {
if (evt.which === 27) { // Esc
evt.preventDefault();
evt.stopPropagation();
this.close();
}
},
const onBgClick = () => {
emit('click');
};
const onKeydown = (evt) => {
if (evt.which === 27) { // Esc
evt.preventDefault();
evt.stopPropagation();
close();
}
};
const ro = new ResizeObserver((entries, observer) => {
bodyWidth = rootEl.offsetWidth;
bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
});
onMounted(() => {
bodyWidth = rootEl.offsetWidth;
bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
ro.observe(rootEl);
});
onUnmounted(() => {
ro.disconnect();
});
defineExpose({
close,
});
</script>

View file

@ -1,5 +1,5 @@
<template>
<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="childRendered">
<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened">
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick">
@ -48,6 +48,7 @@ const props = withDefaults(defineProps<{
const emit = defineEmits<{
(ev: 'opening'): void;
(ev: 'opened'): void;
(ev: 'click'): void;
(ev: 'esc'): void;
(ev: 'close'): void;
@ -212,7 +213,9 @@ const align = () => {
popover.style.top = top + 'px';
};
const childRendered = () => {
const onOpened = () => {
emit('opened');
//
const el = content.value!.children[0];
el.addEventListener('mousedown', ev => {
@ -234,10 +237,10 @@ onMounted(() => {
}
fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null);
await nextTick()
await nextTick();
align();
}, { immediate: true, });
}, { immediate: true });
nextTick(() => {
const popover = content.value;

View file

@ -1,7 +1,10 @@
<template>
<transition :name="$store.state.animation ? 'tooltip' : ''" appear @after-leave="emit('closed')">
<div v-show="showing" ref="el" class="buebdbiu _acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
<slot>{{ text }}</slot>
<slot>
<Mfm v-if="asMfm" :text="text"/>
<span v-else>{{ text }}</span>
</slot>
</div>
</transition>
</template>
@ -16,6 +19,7 @@ const props = withDefaults(defineProps<{
x?: number;
y?: number;
text?: string;
asMfm?: boolean;
maxWidth?: number;
direction?: 'top' | 'bottom' | 'right' | 'left';
innerMargin?: number;
@ -63,7 +67,7 @@ const setPosition = () => {
}
return [left, top];
}
};
const calcPosWhenBottom = () => {
let left: number;
@ -84,7 +88,7 @@ const setPosition = () => {
}
return [left, top];
}
};
const calcPosWhenLeft = () => {
let left: number;
@ -105,7 +109,7 @@ const setPosition = () => {
}
return [left, top];
}
};
const calcPosWhenRight = () => {
let left: number;
@ -126,7 +130,7 @@ const setPosition = () => {
}
return [left, top];
}
};
const calc = (): {
left: number;
@ -170,9 +174,7 @@ const setPosition = () => {
return { left, top, transformOrigin: 'left center' };
}
}
return null as never;
}
};
const { left, top, transformOrigin } = calc();
el.value.style.transformOrigin = transformOrigin;

View file

@ -90,7 +90,7 @@ fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).the
sitename = info.sitename;
fetching = false;
player = info.player;
})
});
});
function adjustTweetHeight(message: any) {

View file

@ -9,7 +9,7 @@ export default {
} else {
return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
}
}
};
const parentBg = getBgColor(src.parentElement);

View file

@ -25,12 +25,12 @@ function calc(src: Element) {
return;
}
if (info.intersection) {
info.intersection.disconnect()
info.intersection.disconnect();
delete info.intersection;
};
}
info.fn(width, height);
};
}
export default {
mounted(src, binding, vn) {

View file

@ -9,7 +9,7 @@ export default {
} else {
return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
}
}
};
const parentBg = getBgColor(src.parentElement);

View file

@ -60,9 +60,9 @@ function calc(el: Element) {
return;
}
if (info.intersection) {
info.intersection.disconnect()
info.intersection.disconnect();
delete info.intersection;
};
}
mountings.set(el, Object.assign(info, { previousWidth: width }));

View file

@ -48,6 +48,7 @@ export default {
popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), {
showing,
text: self.text,
asMfm: binding.modifiers.mfm,
targetElement: el,
}, {}, 'closed');

View file

@ -34,7 +34,7 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s
method: 'POST',
body: JSON.stringify(data),
credentials: 'omit',
cache: 'no-cache'
cache: 'no-cache',
}).then(async (res) => {
const body = res.status === 204 ? null : await res.json();
@ -142,7 +142,7 @@ export async function popup(component: Component, props: Record<string, any>, ev
props,
events: disposeEvent ? {
...events,
[disposeEvent]: dispose
[disposeEvent]: dispose,
} : events,
id,
};
@ -174,7 +174,7 @@ export function modalPageWindow(path: string) {
export function toast(message: string) {
popup(defineAsyncComponent(() => import('@/components/toast.vue')), {
message
message,
}, {}, 'closed');
}
@ -226,7 +226,7 @@ export function inputText(props: {
type: props.type,
placeholder: props.placeholder,
default: props.default,
}
},
}, {
done: result => {
resolve(result ? result : { canceled: true });
@ -251,7 +251,7 @@ export function inputNumber(props: {
type: 'number',
placeholder: props.placeholder,
default: props.default,
}
},
}, {
done: result => {
resolve(result ? result : { canceled: true });
@ -276,7 +276,7 @@ export function inputDate(props: {
type: 'date',
placeholder: props.placeholder,
default: props.default,
}
},
}, {
done: result => {
resolve(result ? { result: new Date(result.result), canceled: false } : { canceled: true });
@ -285,7 +285,7 @@ export function inputDate(props: {
});
}
export function select<C extends any = any>(props: {
export function select<C = any>(props: {
title?: string | null;
text?: string | null;
default?: string | null;
@ -313,7 +313,7 @@ export function select<C extends any = any>(props: {
items: props.items,
groupedItems: props.groupedItems,
default: props.default,
}
},
}, {
done: result => {
resolve(result ? result : { canceled: true });
@ -330,7 +330,7 @@ export function success() {
}, 1000);
popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
success: true,
showing: showing
showing: showing,
}, {
done: () => resolve(),
}, 'closed');
@ -342,7 +342,7 @@ export function waiting() {
const showing = ref(true);
popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
success: false,
showing: showing
showing: showing,
}, {
done: () => resolve(),
}, 'closed');
@ -373,7 +373,7 @@ export async function selectDriveFile(multiple: boolean) {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
type: 'file',
multiple
multiple,
}, {
done: files => {
if (files) {
@ -388,7 +388,7 @@ export async function selectDriveFolder(multiple: boolean) {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
type: 'folder',
multiple
multiple,
}, {
done: folders => {
if (folders) {
@ -403,7 +403,7 @@ export async function pickEmoji(src: HTMLElement | null, opts) {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), {
src,
...opts
...opts,
}, {
done: emoji => {
resolve(emoji);
@ -412,6 +412,21 @@ export async function pickEmoji(src: HTMLElement | null, opts) {
});
}
export async function cropImage(image: Misskey.entities.DriveFile, options: {
aspectRatio: number;
}): Promise<Misskey.entities.DriveFile> {
return new Promise((resolve, reject) => {
popup(defineAsyncComponent(() => import('@/components/cropper-dialog.vue')), {
file: image,
aspectRatio: options.aspectRatio,
}, {
ok: x => {
resolve(x);
},
}, 'closed');
});
}
type AwaitType<T> =
T extends Promise<infer U> ? U :
T extends (...args: any[]) => Promise<infer V> ? V :
@ -453,7 +468,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea:
openingEmojiPicker = await popup(defineAsyncComponent(() => import('@/components/emoji-picker-window.vue')), {
src,
...opts
...opts,
}, {
chosen: emoji => {
insertTextAtCursor(activeTextarea, emoji);
@ -462,7 +477,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea:
openingEmojiPicker!.dispose();
openingEmojiPicker = null;
observer.disconnect();
}
},
});
}
@ -478,7 +493,7 @@ export function popupMenu(items: MenuItem[] | Ref<MenuItem[]>, src?: HTMLElement
src,
width: options?.width,
align: options?.align,
viaKeyboard: options?.viaKeyboard
viaKeyboard: options?.viaKeyboard,
}, {
closed: () => {
resolve();

View file

@ -159,7 +159,7 @@ const remoteMenu = (emoji, ev: MouseEvent) => {
}, {
text: i18n.ts.import,
icon: 'fas fa-plus',
action: () => { im(emoji) }
action: () => { im(emoji); }
}], ev.currentTarget ?? ev.target);
};

View file

@ -1,61 +1,50 @@
<template>
<div class="xrmjdkdw">
<MkContainer :foldable="true" class="lookup">
<template #header><i class="fas fa-search"></i> {{ $ts.lookup }}</template>
<div class="xrmjdkdw-lookup">
<MkInput v-model="q" class="item" type="text" @enter="find()">
<template #label>{{ $ts.fileIdOrUrl }}</template>
<div>
<div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
<MkSelect v-model="origin" style="margin: 0; flex: 1;">
<template #label>{{ $ts.instance }}</template>
<option value="combined">{{ $ts.all }}</option>
<option value="local">{{ $ts.local }}</option>
<option value="remote">{{ $ts.remote }}</option>
</MkSelect>
<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'">
<template #label>{{ $ts.host }}</template>
</MkInput>
<MkButton primary @click="find()"><i class="fas fa-search"></i> {{ $ts.lookup }}</MkButton>
</div>
</MkContainer>
<div class="_section">
<div class="_content">
<div class="inputs" style="display: flex;">
<MkSelect v-model="origin" style="margin: 0; flex: 1;">
<template #label>{{ $ts.instance }}</template>
<option value="combined">{{ $ts.all }}</option>
<option value="local">{{ $ts.local }}</option>
<option value="remote">{{ $ts.remote }}</option>
</MkSelect>
<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'">
<template #label>{{ $ts.host }}</template>
</MkInput>
</div>
<div class="inputs" style="display: flex; padding-top: 1.2em;">
<MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;">
<template #label>MIME type</template>
</MkInput>
</div>
<MkPagination v-slot="{items}" :pagination="pagination" class="urempief">
<button v-for="file in items" :key="file.id" class="file _panel _button _gap" @click="show(file, $event)">
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
<div class="body">
<div>
<small style="opacity: 0.7;">{{ file.name }}</small>
</div>
<div>
<MkAcct v-if="file.user" :user="file.user"/>
<div v-else>{{ $ts.system }}</div>
</div>
<div>
<span style="margin-right: 1em;">{{ file.type }}</span>
<span>{{ bytes(file.size) }}</span>
</div>
<div>
<span>{{ $ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span>
</div>
<div class="inputs" style="display: flex; padding-top: 1.2em;">
<MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;">
<template #label>MIME type</template>
</MkInput>
</div>
<MkPagination v-slot="{items}" :pagination="pagination" class="urempief" :class="{ grid: viewMode === 'grid' }">
<button v-for="file in items" :key="file.id" v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${new Date(file.createdAt).toLocaleString()}\nby ${file.user ? '@' + Acct.toString(file.user) : 'system'}`" class="file _panel _button" @click="show(file, $event)">
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
<div v-if="viewMode === 'list'" class="body">
<div>
<small style="opacity: 0.7;">{{ file.name }}</small>
</div>
</button>
</MkPagination>
</div>
<div>
<MkAcct v-if="file.user" :user="file.user"/>
<div v-else>{{ $ts.system }}</div>
</div>
<div>
<span style="margin-right: 1em;">{{ file.type }}</span>
<span>{{ bytes(file.size) }}</span>
</div>
<div>
<span>{{ $ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span>
</div>
</div>
</button>
</MkPagination>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent } from 'vue';
import * as Acct from 'misskey-js/built/acct';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
import MkSelect from '@/components/form/select.vue';
@ -67,10 +56,10 @@ import * as os from '@/os';
import * as symbols from '@/symbols';
import { i18n } from '@/i18n';
let q = $ref(null);
let origin = $ref('local');
let type = $ref(null);
let searchHost = $ref('');
let viewMode = $ref('grid');
const pagination = {
endpoint: 'admin/drive/files' as const,
limit: 10,
@ -94,18 +83,24 @@ function clear() {
function show(file) {
os.popup(defineAsyncComponent(() => import('./file-dialog.vue')), {
fileId: file.id
fileId: file.id,
}, {}, 'closed');
}
function find() {
async function find() {
const { canceled, result: q } = await os.inputText({
title: i18n.ts.fileIdOrUrl,
allowEmpty: false,
});
if (canceled) return;
os.api('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => {
show(file);
}).catch(err => {
if (err.code === 'NO_SUCH_FILE') {
os.alert({
type: 'error',
text: i18n.ts.notFound
text: i18n.ts.notFound,
});
}
});
@ -117,6 +112,10 @@ defineExpose({
icon: 'fas fa-cloud',
bg: 'var(--bg)',
actions: [{
text: i18n.ts.lookup,
icon: 'fas fa-search',
handler: find,
}, {
text: i18n.ts.clearCachedFiles,
icon: 'fas fa-trash-alt',
handler: clear,
@ -129,47 +128,53 @@ defineExpose({
.xrmjdkdw {
margin: var(--margin);
> .lookup {
margin-bottom: 16px;
}
.urempief {
margin-top: var(--margin);
> .file {
display: flex;
width: 100%;
box-sizing: border-box;
text-align: left;
align-items: center;
&.list {
> .file {
display: flex;
width: 100%;
box-sizing: border-box;
text-align: left;
align-items: center;
&:hover {
color: var(--accent);
&:hover {
color: var(--accent);
}
> .thumbnail {
width: 128px;
height: 128px;
}
> .body {
margin-left: 0.3em;
padding: 8px;
flex: 1;
@media (max-width: 500px) {
font-size: 14px;
}
}
}
}
> .thumbnail {
width: 128px;
height: 128px;
}
&.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
grid-gap: 12px;
margin: var(--margin) 0;
> .body {
margin-left: 0.3em;
padding: 8px;
flex: 1;
@media (max-width: 500px) {
font-size: 14px;
> .file {
aspect-ratio: 1;
> .thumbnail {
width: 100%;
height: 100%;
}
}
}
}
}
.xrmjdkdw-lookup {
padding: 16px;
> .item {
margin-bottom: 16px;
}
}
</style>

View file

@ -132,7 +132,7 @@ export default defineComponent({
overviewHeight: '1fr',
queueHeight: '1fr',
paused: false,
}
};
},
computed: {

View file

@ -20,7 +20,7 @@ import * as symbols from '@/symbols';
import * as config from '@/config';
import { i18n } from '@/i18n';
const connection = markRaw(stream.useChannel('queueStats'))
const connection = markRaw(stream.useChannel('queueStats'));
function clear() {
os.confirm({
@ -41,7 +41,7 @@ onMounted(() => {
length: 200
});
});
})
});
onBeforeUnmount(() => {
connection.dispose();

View file

@ -32,7 +32,7 @@ export default defineComponent({
computed: {
name(): string {
const el = document.createElement('div');
el.textContent = this.app.name
el.textContent = this.app.name;
return el.innerHTML;
},
app(): any {

View file

@ -58,7 +58,7 @@ export default defineComponent({
tags: emojiTags,
selectedTags: new Set(),
searchEmojis: null,
}
};
},
watch: {

View file

@ -127,7 +127,7 @@ function getStatus(instance) {
if (instance.isSuspended) return 'suspended';
if (instance.isNotResponding) return 'error';
return 'alive';
};
}
defineExpose({
[symbols.PAGE_INFO]: {

View file

@ -71,7 +71,7 @@ export default defineComponent({
description: null,
title: null,
isSensitive: false,
}
};
},
watch: {

View file

@ -123,11 +123,11 @@ export default defineComponent({
os.popupMenu([{
text: this.$ts.messagingWithUser,
icon: 'fas fa-user',
action: () => { this.startUser() }
action: () => { this.startUser(); }
}, {
text: this.$ts.messagingWithGroup,
icon: 'fas fa-users',
action: () => { this.startGroup() }
action: () => { this.startGroup(); }
}], ev.currentTarget ?? ev.target);
},

View file

@ -200,7 +200,7 @@ export default defineComponent({
text: this.text,
file: this.file
}
}
};
localStorage.setItem('message_drafts', JSON.stringify(drafts));
},

View file

@ -341,7 +341,7 @@ export default defineComponent({
preview_rainbow: `$[rainbow 🍮] $[rainbow.speed=5s 🍮]`,
preview_sparkle: `$[sparkle 🍮]`,
preview_rotate: `$[rotate 🍮]`,
}
};
},
});
</script>

View file

@ -32,7 +32,7 @@ defineExpose({
icon: 'fas fa-satellite',
bg: 'var(--bg)'
}
})
});
</script>
<style lang="scss" scoped>

View file

@ -114,7 +114,7 @@ export default defineComponent({
readonly: this.readonly,
getScriptBlockList: this.getScriptBlockList,
getPageBlockList: this.getPageBlockList
}
};
},
props: {

View file

@ -100,7 +100,7 @@ async function run() {
text: error.message
});
}
};
}
function highlighter(code) {
return highlight(code, languages.js, 'javascript');

View file

@ -142,7 +142,7 @@ function registerKey() {
registration.value = null;
key.lastUsed = new Date();
os.success();
})
});
}
function unregisterKey(key) {

View file

@ -45,7 +45,7 @@ const init = async () => {
accounts.value = response;
console.log(accounts.value);
});
}
};
function menu(account, ev) {
os.popupMenu([{

View file

@ -52,7 +52,7 @@ const pagination = {
params: {
sort: '+lastUsedAt'
}
}
};
function revoke(token) {
os.api('i/revoke-token', { tokenId: token.id }).then(() => {

View file

@ -62,7 +62,7 @@
</template>
<script lang="ts" setup>
import { defineComponent, reactive, watch } from 'vue';
import { reactive, watch } from 'vue';
import MkButton from '@/components/ui/button.vue';
import FormInput from '@/components/form/input.vue';
import FormTextarea from '@/components/form/textarea.vue';
@ -132,8 +132,21 @@ function save() {
function changeAvatar(ev) {
selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => {
let originalOrCropped = file;
const { canceled } = await os.confirm({
type: 'question',
text: i18n.t('cropImageAsk'),
});
if (!canceled) {
originalOrCropped = await os.cropImage(file, {
aspectRatio: 1,
});
}
const i = await os.apiWithDialog('i/update', {
avatarId: file.id,
avatarId: originalOrCropped.id,
});
$i.avatarId = i.avatarId;
$i.avatarUrl = i.avatarUrl;
@ -142,8 +155,21 @@ function changeAvatar(ev) {
function changeBanner(ev) {
selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => {
let originalOrCropped = file;
const { canceled } = await os.confirm({
type: 'question',
text: i18n.t('cropImageAsk'),
});
if (!canceled) {
originalOrCropped = await os.cropImage(file, {
aspectRatio: 2,
});
}
const i = await os.apiWithDialog('i/update', {
bannerId: file.id,
bannerId: originalOrCropped.id,
});
$i.bannerId = i.bannerId;
$i.bannerUrl = i.bannerUrl;

View file

@ -120,7 +120,7 @@ const darkThemeId = computed({
return darkTheme.value.id;
},
set(id) {
ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id))
ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id));
}
});
const lightTheme = ColdDeviceStorage.ref('lightTheme');
@ -129,7 +129,7 @@ const lightThemeId = computed({
return lightTheme.value.id;
},
set(id) {
ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id))
ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id));
}
});
const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));

View file

@ -75,7 +75,7 @@ async function save() {
// check each line if it is a RegExp or not
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
const line = lines[i];
const regexp = line.match(/^\/(.+)\/(.*)$/);
if (regexp) {
// check that the RegExp is valid

View file

@ -56,7 +56,7 @@ export default defineComponent({
localOnly: null as boolean | null,
files: [] as Misskey.entities.DriveFile[],
visibleUsers: [] as Misskey.entities.User[],
}
};
},
async created() {

View file

@ -68,7 +68,7 @@
import { watch } from 'vue';
import { toUnicode } from 'punycode/';
import tinycolor from 'tinycolor2';
import { v4 as uuid} from 'uuid';
import { v4 as uuid } from 'uuid';
import JSON5 from 'json5';
import FormButton from '@/components/ui/button.vue';

View file

@ -20,7 +20,7 @@
<script lang="ts">
export default {
name: 'MkTimelinePage',
}
};
</script>
<script lang="ts" setup>

View file

@ -41,7 +41,7 @@ export default defineComponent({
password: '',
submitting: false,
host,
}
};
},
methods: {

View file

@ -39,7 +39,7 @@ export default defineComponent({
return {
notes: [],
isScrolling: false,
}
};
},
created() {

View file

@ -41,7 +41,7 @@ const defaultRoutes = [
{ path: '/gallery', component: page(() => import('./pages/gallery/index.vue')) },
{ path: '/gallery/new', component: page(() => import('./pages/gallery/edit.vue')) },
{ path: '/gallery/:postId/edit', component: page(() => import('./pages/gallery/edit.vue')), props: route => ({ postId: route.params.postId }) },
{ path: '/gallery/:postId', component: page(() => import('./pages/gallery/edit.vue')), props: route => ({ postId: route.params.postId }) },
{ path: '/gallery/:postId', component: page(() => import('./pages/gallery/post.vue')), props: route => ({ postId: route.params.postId }) },
{ path: '/channels', component: page('channels') },
{ path: '/channels/new', component: page('channel-editor') },
{ path: '/channels/:channelId/edit', component: page('channel-editor'), props: true },

View file

@ -13,7 +13,7 @@ const defaultLocaleStringFormats: {[index: string]: string} = {
function formatLocaleString(date: Date, format: string): string {
return format.replace(/\{\{(\w+)(:(\w+))?\}\}/g, (match: string, kind: string, unused?, option?: string) => {
if (['weekday', 'era', 'year', 'month', 'day', 'hour', 'minute', 'second', 'timeZoneName'].includes(kind)) {
return date.toLocaleString(window.navigator.language, {[kind]: option ? option : defaultLocaleStringFormats[kind]});
return date.toLocaleString(window.navigator.language, { [kind]: option ? option : defaultLocaleStringFormats[kind] });
} else {
return match;
}
@ -24,8 +24,8 @@ export function formatDateTimeString(date: Date, format: string): string {
return format
.replace(/yyyy/g, date.getFullYear().toString())
.replace(/yy/g, date.getFullYear().toString().slice(-2))
.replace(/MMMM/g, date.toLocaleString(window.navigator.language, { month: 'long'}))
.replace(/MMM/g, date.toLocaleString(window.navigator.language, { month: 'short'}))
.replace(/MMMM/g, date.toLocaleString(window.navigator.language, { month: 'long' }))
.replace(/MMM/g, date.toLocaleString(window.navigator.language, { month: 'short' }))
.replace(/MM/g, (`0${date.getMonth() + 1}`).slice(-2))
.replace(/M/g, (date.getMonth() + 1).toString())
.replace(/dd/g, (`0${date.getDate()}`).slice(-2))

View file

@ -22,7 +22,7 @@ export function getNoteMenu(props: {
props.note.poll == null
);
let appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note;
const appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note;
function del(): void {
os.confirm({

View file

@ -148,7 +148,7 @@ export function getUserMenu(user) {
userId: user.id
}).then(() => {
user.isFollowed = !user.isFollowed;
})
});
}
let menu = [{

View file

@ -36,7 +36,7 @@ export class Hpml {
if (this.opts.enableAiScript) {
this.aiscript = markRaw(new AiScript({ ...createAiScriptEnv({
storageKey: 'pages:' + this.page.id
}), ...initAiLib(this)}, {
}), ...initAiLib(this) }, {
in: (q) => {
return new Promise(ok => {
os.inputText({

View file

@ -41,9 +41,9 @@ export function physics(container: HTMLElement) {
const groundThickness = 1024;
const ground = Matter.Bodies.rectangle(containerCenterX, containerHeight + (groundThickness / 2), containerWidth, groundThickness, {
isStatic: true,
restitution: 0.1,
friction: 2
isStatic: true,
restitution: 0.1,
friction: 2
});
//const wallRight = Matter.Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, wallopts);

View file

@ -1,4 +1,4 @@
import { v4 as uuid} from 'uuid';
import { v4 as uuid } from 'uuid';
import { themeProps, Theme } from './theme';

View file

@ -42,7 +42,7 @@ export const getBuiltinThemesRef = () => {
const builtinThemes = ref<Theme[]>([]);
getBuiltinThemes().then(themes => builtinThemes.value = themes);
return builtinThemes;
}
};
let timeout = null;

View file

@ -256,7 +256,7 @@ type Plugin = {
* ()
*/
import lightTheme from '@/themes/l-light.json5';
import darkTheme from '@/themes/d-dark.json5'
import darkTheme from '@/themes/d-dark.json5';
export class ColdDeviceStorage {
public static default = {

View file

@ -61,7 +61,7 @@ export default defineComponent({
otherMenuItemIndicated,
post: os.post,
search,
openAccountMenu:(ev) => {
openAccountMenu: (ev) => {
openAccountMenu({
withExtraOperation: true,
}, ev);

View file

@ -126,7 +126,7 @@ export default defineComponent({
}, {}, 'closed');
},
openAccountMenu:(ev) => {
openAccountMenu: (ev) => {
openAccountMenu({
withExtraOperation: true,
}, ev);

View file

@ -94,7 +94,6 @@ onBeforeUnmount(() => {
os.deckGlobalEvents.off('column.dragEnd', onOtherDragEnd);
});
function onOtherDragStart() {
dropready = true;
}

View file

@ -1,9 +1,9 @@
import { throttle } from 'throttle-debounce';
import { markRaw } from 'vue';
import { notificationTypes } from 'misskey-js';
import { Storage } from '../../pizzax';
import { i18n } from '@/i18n';
import { api } from '@/os';
import { markRaw } from 'vue';
import { Storage } from '../../pizzax';
import { notificationTypes } from 'misskey-js';
type ColumnWidget = {
name: string;
@ -32,35 +32,35 @@ function copy<T>(x: T): T {
export const deckStore = markRaw(new Storage('deck', {
profile: {
where: 'deviceAccount',
default: 'default'
default: 'default',
},
columns: {
where: 'deviceAccount',
default: [] as Column[]
default: [] as Column[],
},
layout: {
where: 'deviceAccount',
default: [] as Column['id'][][]
default: [] as Column['id'][][],
},
columnAlign: {
where: 'deviceAccount',
default: 'left' as 'left' | 'right' | 'center'
default: 'left' as 'left' | 'right' | 'center',
},
alwaysShowMainColumn: {
where: 'deviceAccount',
default: true
default: true,
},
navWindow: {
where: 'deviceAccount',
default: true
default: true,
},
columnMargin: {
where: 'deviceAccount',
default: 16
default: 16,
},
columnHeaderHeight: {
where: 'deviceAccount',
default: 42
default: 42,
},
}));
@ -109,7 +109,7 @@ export const saveDeck = throttle(1000, () => {
value: {
columns: deckStore.reactiveState.columns.value,
layout: deckStore.reactiveState.layout.value,
}
},
});
});
@ -276,7 +276,7 @@ export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
saveDeck();
}
export function updateColumnWidget(id: Column['id'], widgetId: string, WidgetData: any) {
export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) {
const columns = copy(deckStore.state.columns);
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
const column = copy(deckStore.state.columns[columnIndex]);

View file

@ -10,9 +10,9 @@
<script lang="ts" setup>
import { } from 'vue';
import XWidgets from '@/components/widgets.vue';
import XColumn from './column.vue';
import { addColumnWidget, Column, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store';
import XWidgets from '@/components/widgets.vue';
const props = defineProps<{
column: Column;

View file

@ -103,19 +103,19 @@ const choose = async (ev) => {
os.popupMenu([{
text: i18n.ts._timelines.home,
icon: 'fas fa-home',
action: () => { setSrc('home') }
action: () => { setSrc('home'); }
}, {
text: i18n.ts._timelines.local,
icon: 'fas fa-comments',
action: () => { setSrc('local') }
action: () => { setSrc('local'); }
}, {
text: i18n.ts._timelines.social,
icon: 'fas fa-share-alt',
action: () => { setSrc('social') }
action: () => { setSrc('social'); }
}, {
text: i18n.ts._timelines.global,
icon: 'fas fa-globe',
action: () => { setSrc('global') }
action: () => { setSrc('global'); }
}, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget ?? ev.target).then(() => {
menuOpened.value = false;
});

View file

@ -45,7 +45,7 @@ export const useWidgetPropsManager = <F extends Form & Record<string, { default:
}, { deep: true, immediate: true, });
const save = throttle(3000, () => {
emit('updateProps', widgetProps)
emit('updateProps', widgetProps);
});
const configure = async () => {