parent
1838511766
commit
ecb3c43520
@ -16,6 +16,7 @@ You should also include the user name that made the change.
|
|||||||
- プッシュ通知にクリックやactionを設定 #7667 @tamaina
|
- プッシュ通知にクリックやactionを設定 #7667 @tamaina
|
||||||
- ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
|
- ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
|
||||||
- Server: always remove completed tasks of job queue @Johann150
|
- Server: always remove completed tasks of job queue @Johann150
|
||||||
|
- Client: アバターの設定で画像をクロップできるように @syuilo
|
||||||
- Client: make emoji stand out more on reaction button @Johann150
|
- Client: make emoji stand out more on reaction button @Johann150
|
||||||
- Client: display URL of QR code for TOTP registration @tamaina
|
- Client: display URL of QR code for TOTP registration @tamaina
|
||||||
- Client: render quote renote CWs as MFM @pixeldesu
|
- Client: render quote renote CWs as MFM @pixeldesu
|
||||||
|
@ -843,6 +843,8 @@ oneWeek: "1週間"
|
|||||||
reflectMayTakeTime: "反映されるまで時間がかかる場合があります。"
|
reflectMayTakeTime: "反映されるまで時間がかかる場合があります。"
|
||||||
failedToFetchAccountInformation: "アカウント情報の取得に失敗しました"
|
failedToFetchAccountInformation: "アカウント情報の取得に失敗しました"
|
||||||
rateLimitExceeded: "レート制限を超えました"
|
rateLimitExceeded: "レート制限を超えました"
|
||||||
|
cropImage: "画像のクロップ"
|
||||||
|
cropImageAsk: "画像をクロップしますか?"
|
||||||
|
|
||||||
_emailUnavailable:
|
_emailUnavailable:
|
||||||
used: "既に使用されています"
|
used: "既に使用されています"
|
||||||
|
@ -12,7 +12,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordapp/twemoji": "14.0.2",
|
"@discordapp/twemoji": "14.0.2",
|
||||||
"@fortawesome/fontawesome-free": "6.1.1",
|
"@fortawesome/fontawesome-free": "6.1.1",
|
||||||
|
"@rollup/plugin-alias": "3.1.9",
|
||||||
|
"@rollup/plugin-json": "4.1.0",
|
||||||
"@syuilo/aiscript": "0.11.1",
|
"@syuilo/aiscript": "0.11.1",
|
||||||
|
"@vitejs/plugin-vue": "2.3.3",
|
||||||
|
"@vue/compiler-sfc": "3.2.37",
|
||||||
"abort-controller": "3.0.0",
|
"abort-controller": "3.0.0",
|
||||||
"autobind-decorator": "2.4.0",
|
"autobind-decorator": "2.4.0",
|
||||||
"autosize": "5.0.1",
|
"autosize": "5.0.1",
|
||||||
@ -26,6 +30,7 @@
|
|||||||
"chartjs-plugin-zoom": "1.2.1",
|
"chartjs-plugin-zoom": "1.2.1",
|
||||||
"compare-versions": "4.1.3",
|
"compare-versions": "4.1.3",
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
|
"cropperjs": "2.0.0-beta",
|
||||||
"date-fns": "2.28.0",
|
"date-fns": "2.28.0",
|
||||||
"escape-regexp": "0.0.1",
|
"escape-regexp": "0.0.1",
|
||||||
"eventemitter3": "4.0.7",
|
"eventemitter3": "4.0.7",
|
||||||
@ -51,6 +56,7 @@
|
|||||||
"random-seed": "0.3.0",
|
"random-seed": "0.3.0",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rndstr": "1.0.0",
|
"rndstr": "1.0.0",
|
||||||
|
"rollup": "2.75.6",
|
||||||
"s-age": "1.1.2",
|
"s-age": "1.1.2",
|
||||||
"sass": "1.52.3",
|
"sass": "1.52.3",
|
||||||
"seedrandom": "3.0.5",
|
"seedrandom": "3.0.5",
|
||||||
@ -64,21 +70,16 @@
|
|||||||
"tsc-alias": "1.6.9",
|
"tsc-alias": "1.6.9",
|
||||||
"tsconfig-paths": "4.0.0",
|
"tsconfig-paths": "4.0.0",
|
||||||
"twemoji-parser": "14.0.0",
|
"twemoji-parser": "14.0.0",
|
||||||
|
"typescript": "4.7.3",
|
||||||
"uuid": "8.3.2",
|
"uuid": "8.3.2",
|
||||||
"v-debounce": "0.1.2",
|
"v-debounce": "0.1.2",
|
||||||
"vanilla-tilt": "1.7.2",
|
"vanilla-tilt": "1.7.2",
|
||||||
|
"vite": "2.9.10",
|
||||||
"vue": "3.2.37",
|
"vue": "3.2.37",
|
||||||
"vue-prism-editor": "2.0.0-alpha.2",
|
"vue-prism-editor": "2.0.0-alpha.2",
|
||||||
"vue-router": "4.0.16",
|
"vue-router": "4.0.16",
|
||||||
"vuedraggable": "4.0.1",
|
"vuedraggable": "4.0.1",
|
||||||
"websocket": "1.0.34",
|
"websocket": "1.0.34",
|
||||||
"@vitejs/plugin-vue": "2.3.3",
|
|
||||||
"@vue/compiler-sfc": "3.2.37",
|
|
||||||
"@rollup/plugin-alias": "3.1.9",
|
|
||||||
"@rollup/plugin-json": "4.1.0",
|
|
||||||
"rollup": "2.75.6",
|
|
||||||
"typescript": "4.7.3",
|
|
||||||
"vite": "2.9.10",
|
|
||||||
"ws": "8.8.0"
|
"ws": "8.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -102,11 +103,11 @@
|
|||||||
"@types/ws": "8.5.3",
|
"@types/ws": "8.5.3",
|
||||||
"@typescript-eslint/eslint-plugin": "5.27.1",
|
"@typescript-eslint/eslint-plugin": "5.27.1",
|
||||||
"@typescript-eslint/parser": "5.27.1",
|
"@typescript-eslint/parser": "5.27.1",
|
||||||
"eslint": "8.17.0",
|
|
||||||
"eslint-plugin-vue": "9.1.0",
|
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "10.0.3",
|
"cypress": "10.0.3",
|
||||||
|
"eslint": "8.17.0",
|
||||||
"eslint-plugin-import": "2.26.0",
|
"eslint-plugin-import": "2.26.0",
|
||||||
|
"eslint-plugin-vue": "9.1.0",
|
||||||
"start-server-and-test": "1.14.0"
|
"start-server-and-test": "1.14.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
171
packages/client/src/components/cropper-dialog.vue
Normal file
171
packages/client/src/components/cropper-dialog.vue
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<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="file.url" 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 } from '@/config';
|
||||||
|
|
||||||
|
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;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
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>
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" :prefer-type="'dialog'" @click="$emit('click')" @closed="$emit('closed')">
|
<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @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 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 class="header">
|
<div ref="headerEl" class="header">
|
||||||
<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button>
|
<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button>
|
||||||
<span class="title">
|
<span class="title">
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
@ -11,82 +11,82 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="padding" class="body">
|
<div v-if="padding" class="body">
|
||||||
<div class="_section">
|
<div class="_section">
|
||||||
<slot></slot>
|
<slot :width="bodyWidth" :height="bodyHeight"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="body">
|
<div v-else class="body">
|
||||||
<slot></slot>
|
<slot :width="bodyWidth" :height="bodyHeight"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkModal>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, onUnmounted } from 'vue';
|
||||||
import MkModal from './modal.vue';
|
import MkModal from './modal.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
const props = withDefaults(defineProps<{
|
||||||
components: {
|
withOkButton: boolean;
|
||||||
MkModal
|
okButtonDisabled: boolean;
|
||||||
},
|
padding: boolean;
|
||||||
props: {
|
width: number;
|
||||||
withOkButton: {
|
height: number | null;
|
||||||
type: Boolean,
|
scroll: boolean;
|
||||||
required: false,
|
}>(), {
|
||||||
default: false
|
withOkButton: false,
|
||||||
},
|
okButtonDisabled: false,
|
||||||
okButtonDisabled: {
|
padding: false,
|
||||||
type: Boolean,
|
width: 400,
|
||||||
required: false,
|
height: null,
|
||||||
default: false
|
scroll: true,
|
||||||
},
|
});
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ['click', 'close', 'closed', 'ok'],
|
const emit = defineEmits<{
|
||||||
|
(event: 'click'): void;
|
||||||
|
(event: 'close'): void;
|
||||||
|
(event: 'closed'): void;
|
||||||
|
(event: 'ok'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
data() {
|
let modal = $ref<InstanceType<typeof MkModal>>();
|
||||||
return {
|
let rootEl = $ref<HTMLElement>();
|
||||||
};
|
let headerEl = $ref<HTMLElement>();
|
||||||
},
|
let bodyWidth = $ref(0);
|
||||||
|
let bodyHeight = $ref(0);
|
||||||
|
|
||||||
methods: {
|
const close = () => {
|
||||||
close() {
|
modal.close();
|
||||||
this.$refs.modal.close();
|
};
|
||||||
},
|
|
||||||
|
|
||||||
onKeydown(evt) {
|
const onBgClick = () => {
|
||||||
if (evt.which === 27) { // Esc
|
emit('click');
|
||||||
evt.preventDefault();
|
};
|
||||||
evt.stopPropagation();
|
|
||||||
this.close();
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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 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 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">
|
<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<{
|
const emit = defineEmits<{
|
||||||
(ev: 'opening'): void;
|
(ev: 'opening'): void;
|
||||||
|
(ev: 'opened'): void;
|
||||||
(ev: 'click'): void;
|
(ev: 'click'): void;
|
||||||
(ev: 'esc'): void;
|
(ev: 'esc'): void;
|
||||||
(ev: 'close'): void;
|
(ev: 'close'): void;
|
||||||
@ -212,7 +213,9 @@ const align = () => {
|
|||||||
popover.style.top = top + 'px';
|
popover.style.top = top + 'px';
|
||||||
};
|
};
|
||||||
|
|
||||||
const childRendered = () => {
|
const onOpened = () => {
|
||||||
|
emit('opened');
|
||||||
|
|
||||||
// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
|
// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
|
||||||
const el = content.value!.children[0];
|
const el = content.value!.children[0];
|
||||||
el.addEventListener('mousedown', ev => {
|
el.addEventListener('mousedown', ev => {
|
||||||
@ -237,7 +240,7 @@ onMounted(() => {
|
|||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
align();
|
align();
|
||||||
}, { immediate: true, });
|
}, { immediate: true });
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const popover = content.value;
|
const popover = content.value;
|
||||||
|
@ -34,7 +34,7 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
credentials: 'omit',
|
credentials: 'omit',
|
||||||
cache: 'no-cache'
|
cache: 'no-cache',
|
||||||
}).then(async (res) => {
|
}).then(async (res) => {
|
||||||
const body = res.status === 204 ? null : await res.json();
|
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,
|
props,
|
||||||
events: disposeEvent ? {
|
events: disposeEvent ? {
|
||||||
...events,
|
...events,
|
||||||
[disposeEvent]: dispose
|
[disposeEvent]: dispose,
|
||||||
} : events,
|
} : events,
|
||||||
id,
|
id,
|
||||||
};
|
};
|
||||||
@ -174,7 +174,7 @@ export function modalPageWindow(path: string) {
|
|||||||
|
|
||||||
export function toast(message: string) {
|
export function toast(message: string) {
|
||||||
popup(defineAsyncComponent(() => import('@/components/toast.vue')), {
|
popup(defineAsyncComponent(() => import('@/components/toast.vue')), {
|
||||||
message
|
message,
|
||||||
}, {}, 'closed');
|
}, {}, 'closed');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ export function inputText(props: {
|
|||||||
type: props.type,
|
type: props.type,
|
||||||
placeholder: props.placeholder,
|
placeholder: props.placeholder,
|
||||||
default: props.default,
|
default: props.default,
|
||||||
}
|
},
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
resolve(result ? result : { canceled: true });
|
resolve(result ? result : { canceled: true });
|
||||||
@ -251,7 +251,7 @@ export function inputNumber(props: {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
placeholder: props.placeholder,
|
placeholder: props.placeholder,
|
||||||
default: props.default,
|
default: props.default,
|
||||||
}
|
},
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
resolve(result ? result : { canceled: true });
|
resolve(result ? result : { canceled: true });
|
||||||
@ -276,7 +276,7 @@ export function inputDate(props: {
|
|||||||
type: 'date',
|
type: 'date',
|
||||||
placeholder: props.placeholder,
|
placeholder: props.placeholder,
|
||||||
default: props.default,
|
default: props.default,
|
||||||
}
|
},
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
resolve(result ? { result: new Date(result.result), canceled: false } : { canceled: true });
|
resolve(result ? { result: new Date(result.result), canceled: false } : { canceled: true });
|
||||||
@ -313,7 +313,7 @@ export function select<C = any>(props: {
|
|||||||
items: props.items,
|
items: props.items,
|
||||||
groupedItems: props.groupedItems,
|
groupedItems: props.groupedItems,
|
||||||
default: props.default,
|
default: props.default,
|
||||||
}
|
},
|
||||||
}, {
|
}, {
|
||||||
done: result => {
|
done: result => {
|
||||||
resolve(result ? result : { canceled: true });
|
resolve(result ? result : { canceled: true });
|
||||||
@ -330,7 +330,7 @@ export function success() {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
|
popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
|
||||||
success: true,
|
success: true,
|
||||||
showing: showing
|
showing: showing,
|
||||||
}, {
|
}, {
|
||||||
done: () => resolve(),
|
done: () => resolve(),
|
||||||
}, 'closed');
|
}, 'closed');
|
||||||
@ -342,7 +342,7 @@ export function waiting() {
|
|||||||
const showing = ref(true);
|
const showing = ref(true);
|
||||||
popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
|
popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
|
||||||
success: false,
|
success: false,
|
||||||
showing: showing
|
showing: showing,
|
||||||
}, {
|
}, {
|
||||||
done: () => resolve(),
|
done: () => resolve(),
|
||||||
}, 'closed');
|
}, 'closed');
|
||||||
@ -373,7 +373,7 @@ export async function selectDriveFile(multiple: boolean) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
|
popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
|
||||||
type: 'file',
|
type: 'file',
|
||||||
multiple
|
multiple,
|
||||||
}, {
|
}, {
|
||||||
done: files => {
|
done: files => {
|
||||||
if (files) {
|
if (files) {
|
||||||
@ -388,7 +388,7 @@ export async function selectDriveFolder(multiple: boolean) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
|
popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
multiple
|
multiple,
|
||||||
}, {
|
}, {
|
||||||
done: folders => {
|
done: folders => {
|
||||||
if (folders) {
|
if (folders) {
|
||||||
@ -403,7 +403,7 @@ export async function pickEmoji(src: HTMLElement | null, opts) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), {
|
popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), {
|
||||||
src,
|
src,
|
||||||
...opts
|
...opts,
|
||||||
}, {
|
}, {
|
||||||
done: emoji => {
|
done: emoji => {
|
||||||
resolve(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> =
|
type AwaitType<T> =
|
||||||
T extends Promise<infer U> ? U :
|
T extends Promise<infer U> ? U :
|
||||||
T extends (...args: any[]) => Promise<infer V> ? V :
|
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')), {
|
openingEmojiPicker = await popup(defineAsyncComponent(() => import('@/components/emoji-picker-window.vue')), {
|
||||||
src,
|
src,
|
||||||
...opts
|
...opts,
|
||||||
}, {
|
}, {
|
||||||
chosen: emoji => {
|
chosen: emoji => {
|
||||||
insertTextAtCursor(activeTextarea, emoji);
|
insertTextAtCursor(activeTextarea, emoji);
|
||||||
@ -462,7 +477,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea:
|
|||||||
openingEmojiPicker!.dispose();
|
openingEmojiPicker!.dispose();
|
||||||
openingEmojiPicker = null;
|
openingEmojiPicker = null;
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,7 +493,7 @@ export function popupMenu(items: MenuItem[] | Ref<MenuItem[]>, src?: HTMLElement
|
|||||||
src,
|
src,
|
||||||
width: options?.width,
|
width: options?.width,
|
||||||
align: options?.align,
|
align: options?.align,
|
||||||
viaKeyboard: options?.viaKeyboard
|
viaKeyboard: options?.viaKeyboard,
|
||||||
}, {
|
}, {
|
||||||
closed: () => {
|
closed: () => {
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineComponent, reactive, watch } from 'vue';
|
import { reactive, watch } from 'vue';
|
||||||
import MkButton from '@/components/ui/button.vue';
|
import MkButton from '@/components/ui/button.vue';
|
||||||
import FormInput from '@/components/form/input.vue';
|
import FormInput from '@/components/form/input.vue';
|
||||||
import FormTextarea from '@/components/form/textarea.vue';
|
import FormTextarea from '@/components/form/textarea.vue';
|
||||||
@ -132,8 +132,21 @@ function save() {
|
|||||||
|
|
||||||
function changeAvatar(ev) {
|
function changeAvatar(ev) {
|
||||||
selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => {
|
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', {
|
const i = await os.apiWithDialog('i/update', {
|
||||||
avatarId: file.id,
|
avatarId: originalOrCropped.id,
|
||||||
});
|
});
|
||||||
$i.avatarId = i.avatarId;
|
$i.avatarId = i.avatarId;
|
||||||
$i.avatarUrl = i.avatarUrl;
|
$i.avatarUrl = i.avatarUrl;
|
||||||
@ -142,8 +155,21 @@ function changeAvatar(ev) {
|
|||||||
|
|
||||||
function changeBanner(ev) {
|
function changeBanner(ev) {
|
||||||
selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => {
|
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', {
|
const i = await os.apiWithDialog('i/update', {
|
||||||
bannerId: file.id,
|
bannerId: originalOrCropped.id,
|
||||||
});
|
});
|
||||||
$i.bannerId = i.bannerId;
|
$i.bannerId = i.bannerId;
|
||||||
$i.bannerUrl = i.bannerUrl;
|
$i.bannerUrl = i.bannerUrl;
|
||||||
|
@ -40,6 +40,105 @@
|
|||||||
lodash "^4.17.19"
|
lodash "^4.17.19"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
|
"@cropper/element-canvas@^2.0.0-beta":
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cropper/element-canvas/-/element-canvas-2.0.0-beta.tgz#9501e6a2512a78c7503f2974b1fc65f90c7fecca"
|
||||||
|
integrity sha512-cKbox0AsUx3pMCjT7mQZx3i5FoZTR/Lzz9awuRR8/EciViMN4KkfodGHWSUrIX3zSr0fECsrb2CyNKV8DKZdpQ==
|
||||||
|
dependencies:
|
||||||
|
"@cropper/element" "^2.0.0-beta"
|
||||||
|
"@cropper/utils" "^2.0.0-beta"
|
||||||
|
|
||||||
|
"@cropper/element-crosshair@^2.0.0-beta":
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cropper/element-crosshair/-/element-crosshair-2.0.0-beta.tgz#9d6ee1e6ed90196b6d4d2425f84909b83ffc66df"
|
||||||
|
integrity sha512-V58xxH3+8TrT9PrUzNouRhcyucyX/xBV5hBv03g0zCu09C5p0BZjrhaPo3hkt8oQvnhYT9SbMTe+k5hIoZgkbQ==
|
||||||
|
dependencies:
|
||||||
|
"@cropper/element" "^2.0.0-beta"
|
||||||
|
"@cropper/utils" "^2.0.0-beta"
|
||||||
|
|
||||||
|
"@cropper/element-grid@^2.0.0-beta":
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cropper/element-grid/-/element-grid-2.0.0-beta.tgz#af6f3fce213307403ad83d9935839bde39c9beeb"
|
||||||
|
integrity sha512-F+qVLrjuHjJbaut1Gd6qSruMqYOHudhDB/r0dcLtnRW4b1yPd/QyhM5F0KLtCX7Lh6GUvpz2V9Vb/EYQLZuOkw==
|
||||||
|
dependencies:
|
||||||
|
"@cropper/element" "^2.0.0-beta"
|
||||||
|
"@cropper/utils" "^2.0.0-beta"
|
||||||
|
|
||||||
|
"@cropper/element-handle@^2.0.0-beta":
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cropper/element-handle/-/element-handle-2.0.0-beta.tgz#bd55667e133df402616d44a694110fd0e61eef0b"
|
||||||
|
integrity sha512-Ty12mLpiUM8XRGQN0lRNB7TKP5SOXbTWaW2Uvli1Tu3Y6iLTtXUvs2VZ/fGR8XvhB7v7Lvo+OPfzuxIRx4gwKg==
|
||||||
|
dependencies:
|
||||||
|
"@cropper/element" "^2.0.0-beta"
|
||||||
|
"@cropper/utils" "^2.0.0-beta"
|
||||||
|
|
||||||
|
"@cropper/element-image@^2.0.0-beta":
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cropper/element-image/-/element-image-2.0.0-beta.tgz#170dbdfbeef75de2f2c0089d4739ad980d69390a"
|
||||||
|
integrity sha512-CrHEMBo5svjj72qePBPGV4ut70RTI6n5U2k2YKcZihHSNU2h6SUEx8zkN8lNIgelsv2Bpb/PvSd1eu26BrJbtA==
|
||||||
|
dependencies:
|
||||||
|
"@cropper/element" "^2.0.0-beta"
|
||||||
|
"@cropper/element-canvas" "^2.0.0-beta"
|
||||||
|
"@cropper/utils" "^2.0.0-beta"
|
||||||
|
|
||||||
|
"@cropper/element-selection@^2.0.0-beta":
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cropper/element-selection/-/element-selection-2.0.0-beta.tgz#7e1e498773bc26bb09ddaf09b0cafbe5b359ed7b"
|
||||||
|
integrity sha512-MEK+pn2Bma5cXf1N9mC3fRKNvzi6Aj9V2TdhaCl6KdOn6Bp10a+SR8y555MXd80zzFAU/eR1e7TMTyJiPRJFcw==
|
||||||
|
dependencies:
|
||||||
|
"@cropper/element" "^2.0.0-beta"
|
||||||
|
"@cropper/element-canvas" "^2.0.0-beta"
|
||||||
|
"@cropper/element-image" "^2.0.0-beta"
|
||||||
|
"@cropper/utils" "^2.0.0-beta"
|
||||||
|
|
||||||
|
"@cropper/element-shade@^2.0.0-beta":
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cropper/element-shade/-/element-shade-2.0.0-beta.tgz#55400aec3e352d959a706bfff1b82afca955d33e"
|
||||||
|
integrity sha512-vfKTTkRFio/bi0ueIbdyg2ukhS35/ufsgA13dfzOgkyUT/TUsqTLONNJA2fxO0WLKSajTtvrl1ShdrSXE+EKCQ==
|
||||||
|
dependencies:
|
||||||
|
"@cropper/element" "^2.0.0-beta"
|
||||||
|
"@cropper/element-canvas" "^2.0.0-beta"
|
||||||
|
"@cropper/element-selection" "^2.0.0-beta"
|
||||||
|
"@cropper/utils" "^2.0.0-beta"
|
||||||
|
|
||||||
|
"@cropper/element-viewer@^2.0.0-beta":
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cropper/element-viewer/-/element-viewer-2.0.0-beta.tgz#9a83b670f5cc667d7fc0071f08a1476817e0ed4e"
|
||||||
|
integrity sha512-ZsqdOWJ8OIrK1JR00ibmYrvVMYQVFXOudXezYtf8C5lc7DdtN4elmjVOfLQQM2kxG0WvflIVo6oqqyOzFnsAFg==
|
||||||
|
dependencies:
|
||||||
|
"@cropper/element" "^2.0.0-beta"
|
||||||
|
"@cropper/element-canvas" "^2.0.0-beta"
|
||||||
|
"@cropper/element-image" "^2.0.0-beta"
|
||||||
|
"@cropper/element-selection" "^2.0.0-beta"
|
||||||
|
"@cropper/utils" "^2.0.0-beta"
|
||||||
|
|
||||||
|
"@cropper/element@^2.0.0-beta":
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cropper/element/-/element-2.0.0-beta.tgz#7833a92471a16e8860530e10658add42e8781959"
|
||||||
|
integrity sha512-seS8oDe2+Vpsy+yyqUIHzjIP6WUQRxwhFjLml/s2e+L6jF9o+g0KHzLJkBCV/ASKBnyb00aLjAt0dBXPLW/KgQ==
|
||||||
|
dependencies:
|
||||||
|
"@cropper/utils" "^2.0.0-beta"
|
||||||
|
|
||||||
|
"@cropper/elements@^2.0.0-beta":
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cropper/elements/-/elements-2.0.0-beta.tgz#e73a4edaeff7e41dcca8d096bd1bc2bdc6a376e9"
|
||||||
|
integrity sha512-Huyptek2Q6141fRiuejhOyec/viX4zmUeMnpi+5h7OBuorTYUowZ823mmfgBZ4bb7+VPdAl79vUECV9EYq/ciw==
|
||||||
|
dependencies:
|
||||||
|
"@cropper/element" "^2.0.0-beta"
|
||||||
|
"@cropper/element-canvas" "^2.0.0-beta"
|
||||||
|
"@cropper/element-crosshair" "^2.0.0-beta"
|
||||||
|
"@cropper/element-grid" "^2.0.0-beta"
|
||||||
|
"@cropper/element-handle" "^2.0.0-beta"
|
||||||
|
"@cropper/element-image" "^2.0.0-beta"
|
||||||
|
"@cropper/element-selection" "^2.0.0-beta"
|
||||||
|
"@cropper/element-shade" "^2.0.0-beta"
|
||||||
|
"@cropper/element-viewer" "^2.0.0-beta"
|
||||||
|
|
||||||
|
"@cropper/utils@^2.0.0-beta":
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cropper/utils/-/utils-2.0.0-beta.tgz#7290b03c8c1dc7a2f33406c8aecc80b339425f0e"
|
||||||
|
integrity sha512-Bb3hCyHK2w0l0i8OtRw6C9Q5ytUC5qN+l+kx7F3GiAAFZMX7jGyfPB0uLiZ2TwDm5mosnWjyLVXmCGDcTUnYaQ==
|
||||||
|
|
||||||
"@cypress/request@^2.88.10":
|
"@cypress/request@^2.88.10":
|
||||||
version "2.88.10"
|
version "2.88.10"
|
||||||
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce"
|
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce"
|
||||||
@ -1132,6 +1231,14 @@ core-util-is@1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||||
|
|
||||||
|
cropperjs@2.0.0-beta:
|
||||||
|
version "2.0.0-beta"
|
||||||
|
resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-2.0.0-beta.tgz#bf3f9c19c426657d63c1e6dd55f635546ccec0a5"
|
||||||
|
integrity sha512-mwupI1Ct84PUynnC9S7KenCtgXiuRYAfLwzxPlJwc392iNX8fZUPP6a8gEpmRQTgvsE9Ubme1tXLM6/HLXksiQ==
|
||||||
|
dependencies:
|
||||||
|
"@cropper/elements" "^2.0.0-beta"
|
||||||
|
"@cropper/utils" "^2.0.0-beta"
|
||||||
|
|
||||||
cross-env@7.0.3:
|
cross-env@7.0.3:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
|
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
|
||||||
|
Loading…
Reference in New Issue
Block a user