wip
This commit is contained in:
parent
6f530b7cbf
commit
01976c02fb
|
|
@ -11453,22 +11453,6 @@ export interface Locale extends ILocale {
|
||||||
* ディレクトリをドラッグ・ドロップした時に、ディレクトリ名を"category"に入力します。
|
* ディレクトリをドラッグ・ドロップした時に、ディレクトリ名を"category"に入力します。
|
||||||
*/
|
*/
|
||||||
"directoryToCategoryCaption": string;
|
"directoryToCategoryCaption": string;
|
||||||
/**
|
|
||||||
* いずれかの方法で登録する絵文字を選択してください。
|
|
||||||
*/
|
|
||||||
"emojiInputAreaCaption": string;
|
|
||||||
/**
|
|
||||||
* この枠に画像ファイルまたはディレクトリをドラッグ&ドロップ
|
|
||||||
*/
|
|
||||||
"emojiInputAreaList1": string;
|
|
||||||
/**
|
|
||||||
* このリンクをクリックしてPCから選択する
|
|
||||||
*/
|
|
||||||
"emojiInputAreaList2": string;
|
|
||||||
/**
|
|
||||||
* このリンクをクリックしてドライブから選択する
|
|
||||||
*/
|
|
||||||
"emojiInputAreaList3": string;
|
|
||||||
/**
|
/**
|
||||||
* リストに表示されている絵文字を新たなカスタム絵文字として登録します。よろしいですか?(負荷を避けるため、一度の操作で登録可能な絵文字は{count}件までです)
|
* リストに表示されている絵文字を新たなカスタム絵文字として登録します。よろしいですか?(負荷を避けるため、一度の操作で登録可能な絵文字は{count}件までです)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ import { useStream } from '@/stream.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { claimAchievement } from '@/utility/achievements.js';
|
import { claimAchievement } from '@/utility/achievements.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { chooseFileFromPcAndUpload } from '@/utility/select-file.js';
|
import { chooseFileFromPcAndUpload } from '@/utility/drive.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import { isSeparatorNeeded, getSeparatorInfo, makeDateGroupedTimelineComputedRef } from '@/utility/timeline-date-separate.js';
|
import { isSeparatorNeeded, getSeparatorInfo, makeDateGroupedTimelineComputedRef } from '@/utility/timeline-date-separate.js';
|
||||||
import { usePagination } from '@/composables/use-pagination.js';
|
import { usePagination } from '@/composables/use-pagination.js';
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/drive.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
|
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
|
||||||
</div>
|
</div>
|
||||||
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
|
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
|
||||||
<XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName" @replaceFile="replaceFile"/>
|
<XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
|
||||||
<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
|
<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
|
||||||
<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text" :files="files" :poll="poll ?? undefined" :useCw="useCw" :cw="cw" :user="postAccount ?? $i"/>
|
<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text" :files="files" :poll="poll ?? undefined" :useCw="useCw" :cw="cw" :user="postAccount ?? $i"/>
|
||||||
<div v-if="showingOptions" style="padding: 8px 16px;">
|
<div v-if="showingOptions" style="padding: 8px 16px;">
|
||||||
|
|
@ -120,7 +120,7 @@ import { formatTimeString } from '@/utility/format-time-string.js';
|
||||||
import { Autocomplete } from '@/utility/autocomplete.js';
|
import { Autocomplete } from '@/utility/autocomplete.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { selectFiles } from '@/utility/select-file.js';
|
import { selectFiles } from '@/utility/drive.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
@ -459,10 +459,6 @@ function updateFileName(file, name) {
|
||||||
files.value[files.value.findIndex(x => x.id === file.id)].name = name;
|
files.value[files.value.findIndex(x => x.id === file.id)].name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceFile(file: Misskey.entities.DriveFile, newFile: Misskey.entities.DriveFile): void {
|
|
||||||
files.value[files.value.findIndex(x => x.id === file.id)] = newFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVisibility() {
|
function setVisibility() {
|
||||||
if (props.channel) {
|
if (props.channel) {
|
||||||
visibility.value = 'public';
|
visibility.value = 'public';
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ const emit = defineEmits<{
|
||||||
(ev: 'detach', id: string): void;
|
(ev: 'detach', id: string): void;
|
||||||
(ev: 'changeSensitive', file: Misskey.entities.DriveFile, isSensitive: boolean): void;
|
(ev: 'changeSensitive', file: Misskey.entities.DriveFile, isSensitive: boolean): void;
|
||||||
(ev: 'changeName', file: Misskey.entities.DriveFile, newName: string): void;
|
(ev: 'changeName', file: Misskey.entities.DriveFile, newName: string): void;
|
||||||
(ev: 'replaceFile', file: Misskey.entities.DriveFile, newFile: Misskey.entities.DriveFile): void;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let menuShowing = false;
|
let menuShowing = false;
|
||||||
|
|
@ -142,13 +141,6 @@ async function describe(file: Misskey.entities.DriveFile) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function crop(file: Misskey.entities.DriveFile): Promise<void> {
|
|
||||||
if (mock) return;
|
|
||||||
|
|
||||||
const newFile = await os.createCroppedImageDriveFileFromImageDriveFile(file, { aspectRatio: NaN });
|
|
||||||
emit('replaceFile', file, newFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | KeyboardEvent): void {
|
function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | KeyboardEvent): void {
|
||||||
if (menuShowing) return;
|
if (menuShowing) return;
|
||||||
|
|
||||||
|
|
@ -172,10 +164,6 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
|
||||||
|
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
text: i18n.ts.cropImage,
|
|
||||||
icon: 'ti ti-crop',
|
|
||||||
action: () : void => { crop(file); },
|
|
||||||
}, {
|
|
||||||
text: i18n.ts.preview,
|
text: i18n.ts.preview,
|
||||||
icon: 'ti ti-photo-search',
|
icon: 'ti ti-photo-search',
|
||||||
action: () => {
|
action: () => {
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
import MkRadio from '@/components/MkRadio.vue';
|
import MkRadio from '@/components/MkRadio.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
import { chooseDriveFile } from '@/utility/drive.js';
|
||||||
|
|
||||||
const text = ref('');
|
const text = ref('');
|
||||||
const flag = ref(true);
|
const flag = ref(true);
|
||||||
|
|
@ -79,7 +80,7 @@ const openForm = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDrive = async () => {
|
const openDrive = async () => {
|
||||||
await os.selectDriveFile({
|
await chooseDriveFile({
|
||||||
multiple: false,
|
multiple: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -79,21 +79,17 @@ import { computed, markRaw, onMounted, ref, useTemplateRef, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
|
import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
|
||||||
import { apiUrl } from '@@/js/config.js';
|
|
||||||
import isAnimated from 'is-file-animated';
|
import isAnimated from 'is-file-animated';
|
||||||
import type { BrowserImageResizerConfigWithConvertedOutput } from '@misskey-dev/browser-image-resizer';
|
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
|
||||||
import { instance } from '@/instance.js';
|
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import bytes from '@/filters/bytes.js';
|
import bytes from '@/filters/bytes.js';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import { isWebpSupported } from '@/utility/isWebpSupported.js';
|
import { isWebpSupported } from '@/utility/isWebpSupported.js';
|
||||||
import { uploadFile } from '@/utility/upload.js';
|
import { uploadFile } from '@/utility/drive.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
||||||
const COMPRESSION_SUPPORTED_TYPES = [
|
const COMPRESSION_SUPPORTED_TYPES = [
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { getProxiedImageUrl } from './utility/media-proxy.js';
|
import { getProxiedImageUrl } from './utility/media-proxy.js';
|
||||||
import { uploadFile } from './utility/upload.js';
|
|
||||||
import type { Component, Ref } from 'vue';
|
import type { Component, Ref } from 'vue';
|
||||||
import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
||||||
import type { Form, GetFormResultType } from '@/utility/form.js';
|
import type { Form, GetFormResultType } from '@/utility/form.js';
|
||||||
|
|
@ -594,21 +593,6 @@ export async function selectUser(opts: { includeSelf?: boolean; localOnly?: bool
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function selectDriveFile(options: { multiple?: boolean; } = {}): Promise<Misskey.entities.DriveFile[]> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDriveFileSelectDialog.vue')), {
|
|
||||||
multiple: options.multiple,
|
|
||||||
}, {
|
|
||||||
done: files => {
|
|
||||||
if (files) {
|
|
||||||
resolve(files);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
closed: () => dispose(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function selectDriveFolder(initialFolder: Misskey.entities.DriveFolder['id'] | null): Promise<Misskey.entities.DriveFolder[]> {
|
export async function selectDriveFolder(initialFolder: Misskey.entities.DriveFolder['id'] | null): Promise<Misskey.entities.DriveFolder[]> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDriveFolderSelectDialog.vue')), {
|
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDriveFolderSelectDialog.vue')), {
|
||||||
|
|
@ -671,35 +655,6 @@ export async function cropImageFile(imageFile: File | Blob, options: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createCroppedImageDriveFileFromImageDriveFile(imageDriveFile: Misskey.entities.DriveFile, options: {
|
|
||||||
aspectRatio: number;
|
|
||||||
}): Promise<Misskey.entities.DriveFile> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const imgUrl = getProxiedImageUrl(imageDriveFile.url, undefined, true);
|
|
||||||
const image = new Image();
|
|
||||||
image.src = imgUrl;
|
|
||||||
image.onload = () => {
|
|
||||||
const canvas = window.document.createElement('canvas');
|
|
||||||
const ctx = canvas.getContext('2d')!;
|
|
||||||
canvas.width = image.width;
|
|
||||||
canvas.height = image.height;
|
|
||||||
ctx.drawImage(image, 0, 0);
|
|
||||||
canvas.toBlob(blob => {
|
|
||||||
cropImageFile(blob, {
|
|
||||||
aspectRatio: options.aspectRatio,
|
|
||||||
}).then(croppedImageFile => {
|
|
||||||
uploadFile(croppedImageFile, {
|
|
||||||
name: imageDriveFile.name,
|
|
||||||
folderId: imageDriveFile.folderId,
|
|
||||||
}).then(driveFile => {
|
|
||||||
resolve(driveFile);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | null, options?: {
|
export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | null, options?: {
|
||||||
align?: string;
|
align?: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ import MkButton from '@/components/MkButton.vue';
|
||||||
import { validators } from '@/components/grid/cell-validators.js';
|
import { validators } from '@/components/grid/cell-validators.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import MkPagingButtons from '@/components/MkPagingButtons.vue';
|
import MkPagingButtons from '@/components/MkPagingButtons.vue';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/drive.js';
|
||||||
import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js';
|
import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js';
|
||||||
import { useLoading } from '@/composables/use-loading.js';
|
import { useLoading } from '@/composables/use-loading.js';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { validators } from '@/components/grid/cell-validators.js';
|
import { validators } from '@/components/grid/cell-validators.js';
|
||||||
import { chooseFileFromDrive, chooseFileFromPcAndUpload } from '@/utility/select-file.js';
|
import { chooseDriveFile, chooseFileFromPcAndUpload } from '@/utility/drive.js';
|
||||||
import { extractDroppedItems, flattenDroppedFiles } from '@/utility/file-drop.js';
|
import { extractDroppedItems, flattenDroppedFiles } from '@/utility/file-drop.js';
|
||||||
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
|
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
|
||||||
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
|
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
|
||||||
|
|
@ -311,7 +311,7 @@ async function onFileSelectClicked() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onDriveSelectClicked() {
|
async function onDriveSelectClicked() {
|
||||||
const driveFiles = await chooseFileFromDrive({
|
const driveFiles = await chooseDriveFile({
|
||||||
multiple: true,
|
multiple: true,
|
||||||
});
|
});
|
||||||
gridItems.value.push(...driveFiles.map(fromDriveFile));
|
gridItems.value.push(...driveFiles.map(fromDriveFile));
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkColorInput from '@/components/MkColorInput.vue';
|
import MkColorInput from '@/components/MkColorInput.vue';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/drive.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ import { onMounted, watch, ref, shallowRef, computed, nextTick, readonly, onBefo
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
//import insertTextAtCursor from 'insert-text-at-cursor';
|
//import insertTextAtCursor from 'insert-text-at-cursor';
|
||||||
import { formatTimeString } from '@/utility/format-time-string.js';
|
import { formatTimeString } from '@/utility/format-time-string.js';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/drive.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ import MkPagination from '@/components/MkPagination.vue';
|
||||||
import MkRemoteEmojiEditDialog from '@/components/MkRemoteEmojiEditDialog.vue';
|
import MkRemoteEmojiEditDialog from '@/components/MkRemoteEmojiEditDialog.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import FormSplit from '@/components/form/split.vue';
|
import FormSplit from '@/components/form/split.vue';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/drive.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
|
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-tooltip="i18n.ts.createNoteFromTheFile" class="_button" :class="$style.fileQuickActionsOthersButton" @click="postThis()">
|
<button v-tooltip="i18n.ts.createNoteFromTheFile" class="_button" :class="$style.fileQuickActionsOthersButton" @click="postThis()">
|
||||||
<i class="ti ti-pencil"></i>
|
<i class="ti ti-pencil"></i>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="isImage" v-tooltip="i18n.ts.cropImage" class="_button" :class="$style.fileQuickActionsOthersButton" @click="crop()">
|
|
||||||
<i class="ti ti-crop"></i>
|
|
||||||
</button>
|
|
||||||
<button v-if="file.isSensitive" v-tooltip="i18n.ts.unmarkAsSensitive" class="_button" :class="$style.fileQuickActionsOthersButton" @click="toggleSensitive()">
|
<button v-if="file.isSensitive" v-tooltip="i18n.ts.unmarkAsSensitive" class="_button" :class="$style.fileQuickActionsOthersButton" @click="toggleSensitive()">
|
||||||
<i class="ti ti-eye"></i>
|
<i class="ti ti-eye"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -127,15 +124,6 @@ function postThis() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function crop() {
|
|
||||||
if (!file.value) return;
|
|
||||||
|
|
||||||
os.createCroppedImageDriveFileFromImageDriveFile(file.value, {
|
|
||||||
aspectRatio: NaN,
|
|
||||||
uploadFolder: file.value.folderId ?? null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function move() {
|
function move() {
|
||||||
if (!file.value) return;
|
if (!file.value) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { customEmojiCategories } from '@/custom-emojis.js';
|
import { customEmojiCategories } from '@/custom-emojis.js';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/drive.js';
|
||||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ import MkInput from '@/components/MkInput.vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import FormSuspense from '@/components/form/suspense.vue';
|
import FormSuspense from '@/components/form/suspense.vue';
|
||||||
import { selectFiles } from '@/utility/select-file.js';
|
import { selectFiles } from '@/utility/drive.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ import { onMounted, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XContainer from '../page-editor.container.vue';
|
import XContainer from '../page-editor.container.vue';
|
||||||
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
|
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
|
||||||
import * as os from '@/os.js';
|
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { chooseDriveFile } from '@/utility/drive.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: Misskey.entities.PageBlock & { type: 'image' };
|
modelValue: Misskey.entities.PageBlock & { type: 'image' };
|
||||||
|
|
@ -41,7 +41,7 @@ const emit = defineEmits<{
|
||||||
const file = ref<Misskey.entities.DriveFile | null>(null);
|
const file = ref<Misskey.entities.DriveFile | null>(null);
|
||||||
|
|
||||||
async function choose() {
|
async function choose() {
|
||||||
os.selectDriveFile({ multiple: false }).then((fileResponse) => {
|
chooseDriveFile({ multiple: false }).then((fileResponse) => {
|
||||||
file.value = fileResponse[0];
|
file.value = fileResponse[0];
|
||||||
emit('update:modelValue', {
|
emit('update:modelValue', {
|
||||||
...props.modelValue,
|
...props.modelValue,
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/drive.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/drive.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ import { definePage } from '@/page.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||||
import { reloadAsk } from '@/utility/reload-ask.js';
|
import { reloadAsk } from '@/utility/reload-ask.js';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/drive.js';
|
||||||
|
|
||||||
const navWindow = prefer.model('deck.navWindow');
|
const navWindow = prefer.model('deck.navWindow');
|
||||||
const useSimpleUiForNonRootPages = prefer.model('deck.useSimpleUiForNonRootPages');
|
const useSimpleUiForNonRootPages = prefer.model('deck.useSimpleUiForNonRootPages');
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ import MkSelect from '@/components/MkSelect.vue';
|
||||||
import FormSplit from '@/components/form/split.vue';
|
import FormSplit from '@/components/form/split.vue';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import FormSlot from '@/components/form/slot.vue';
|
import FormSlot from '@/components/form/slot.vue';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { chooseDriveFile } from '@/utility/drive.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
|
|
@ -298,7 +298,7 @@ function changeAvatar(ev) {
|
||||||
text: i18n.ts.fromDrive,
|
text: i18n.ts.fromDrive,
|
||||||
icon: 'ti ti-cloud',
|
icon: 'ti ti-cloud',
|
||||||
action: () => {
|
action: () => {
|
||||||
os.selectDriveFile({ multiple: false }).then(files => {
|
chooseDriveFile({ multiple: false }).then(files => {
|
||||||
done(files[0]);
|
done(files[0]);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -346,7 +346,7 @@ function changeBanner(ev) {
|
||||||
text: i18n.ts.fromDrive,
|
text: i18n.ts.fromDrive,
|
||||||
icon: 'ti ti-cloud',
|
icon: 'ti ti-cloud',
|
||||||
action: () => {
|
action: () => {
|
||||||
os.selectDriveFile({ multiple: false }).then(files => {
|
chooseDriveFile({ multiple: false }).then(files => {
|
||||||
done(files[0]);
|
done(files[0]);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/utility/sound.js';
|
import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/utility/sound.js';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/drive.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
type: SoundType;
|
type: SoundType;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { apiUrl } from '@@/js/config.js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
import { useStream } from '@/stream.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { prefer } from '@/preferences.js';
|
||||||
|
import { $i } from '@/i.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
|
import { globalEvents } from '@/events.js';
|
||||||
|
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
|
||||||
|
|
||||||
|
export function uploadFile(file: File | Blob, options: {
|
||||||
|
name?: string;
|
||||||
|
folderId?: string | null;
|
||||||
|
onProgress?: (ctx: { total: number; loaded: number; }) => void;
|
||||||
|
} = {}): Promise<Misskey.entities.DriveFile> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if ($i == null) return reject();
|
||||||
|
|
||||||
|
if ((file.size > instance.maxFileSize) || (file.size > ($i.policies.maxFileSizeMb * 1024 * 1024))) {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: i18n.ts.failedToUpload,
|
||||||
|
text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit,
|
||||||
|
});
|
||||||
|
return reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', apiUrl + '/drive/files/create', true);
|
||||||
|
xhr.onload = ((ev: ProgressEvent<XMLHttpRequest>) => {
|
||||||
|
if (xhr.status !== 200 || ev.target == null || ev.target.response == null) {
|
||||||
|
if (xhr.status === 413) {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: i18n.ts.failedToUpload,
|
||||||
|
text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit,
|
||||||
|
});
|
||||||
|
} else if (ev.target?.response) {
|
||||||
|
const res = JSON.parse(ev.target.response);
|
||||||
|
if (res.error?.id === 'bec5bd69-fba3-43c9-b4fb-2894b66ad5d2') {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: i18n.ts.failedToUpload,
|
||||||
|
text: i18n.ts.cannotUploadBecauseInappropriate,
|
||||||
|
});
|
||||||
|
} else if (res.error?.id === 'd08dbc37-a6a9-463a-8c47-96c32ab5f064') {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: i18n.ts.failedToUpload,
|
||||||
|
text: i18n.ts.cannotUploadBecauseNoFreeSpace,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: i18n.ts.failedToUpload,
|
||||||
|
text: `${res.error?.message}\n${res.error?.code}\n${res.error?.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Failed to upload',
|
||||||
|
text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reject();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const driveFile = JSON.parse(ev.target.response);
|
||||||
|
globalEvents.emit('driveFileCreated', driveFile);
|
||||||
|
resolve(driveFile);
|
||||||
|
}) as (ev: ProgressEvent<EventTarget>) => any;
|
||||||
|
|
||||||
|
if (options.onProgress) {
|
||||||
|
xhr.upload.onprogress = ev => {
|
||||||
|
if (ev.lengthComputable) {
|
||||||
|
options.onProgress({
|
||||||
|
total: ev.total,
|
||||||
|
loaded: ev.loaded,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('i', $i.token);
|
||||||
|
formData.append('force', 'true');
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append('name', options.name ?? file.name ?? 'untitled');
|
||||||
|
if (options.folderId) formData.append('folderId', options.folderId);
|
||||||
|
|
||||||
|
xhr.send(formData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function chooseFileFromPcAndUpload(
|
||||||
|
options: {
|
||||||
|
multiple?: boolean;
|
||||||
|
folderId?: string | null;
|
||||||
|
} = {},
|
||||||
|
): Promise<Misskey.entities.DriveFile[]> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
os.chooseFileFromPc({ multiple: options.multiple }).then(files => {
|
||||||
|
if (files.length === 0) return;
|
||||||
|
os.launchUploader(files, {
|
||||||
|
folderId: options.folderId,
|
||||||
|
}).then(driveFiles => {
|
||||||
|
res(driveFiles);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function chooseDriveFile(options: {
|
||||||
|
multiple?: boolean;
|
||||||
|
} = {}): Promise<Misskey.entities.DriveFile[]> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkDriveFileSelectDialog.vue')), {
|
||||||
|
multiple: options.multiple,
|
||||||
|
}, {
|
||||||
|
done: files => {
|
||||||
|
if (files) {
|
||||||
|
resolve(files);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closed: () => dispose(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
os.inputText({
|
||||||
|
title: i18n.ts.uploadFromUrl,
|
||||||
|
type: 'url',
|
||||||
|
placeholder: i18n.ts.uploadFromUrlDescription,
|
||||||
|
}).then(({ canceled, result: url }) => {
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
const marker = Math.random().toString(); // TODO: UUIDとか使う
|
||||||
|
|
||||||
|
// TODO: no websocketモード対応
|
||||||
|
const connection = useStream().useChannel('main');
|
||||||
|
connection.on('urlUploadFinished', urlResponse => {
|
||||||
|
if (urlResponse.marker === marker) {
|
||||||
|
res(urlResponse.file);
|
||||||
|
connection.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
misskeyApi('drive/files/upload-from-url', {
|
||||||
|
url: url,
|
||||||
|
folderId: prefer.s.uploadFolder,
|
||||||
|
marker,
|
||||||
|
});
|
||||||
|
|
||||||
|
os.alert({
|
||||||
|
title: i18n.ts.uploadFromUrlRequested,
|
||||||
|
text: i18n.ts.uploadFromUrlMayTakeTime,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
os.popupMenu([label ? {
|
||||||
|
text: label,
|
||||||
|
type: 'label',
|
||||||
|
} : undefined, {
|
||||||
|
text: i18n.ts.upload,
|
||||||
|
icon: 'ti ti-upload',
|
||||||
|
action: () => chooseFileFromPcAndUpload({ multiple }).then(files => res(files)),
|
||||||
|
}, {
|
||||||
|
text: i18n.ts.fromDrive,
|
||||||
|
icon: 'ti ti-cloud',
|
||||||
|
action: () => chooseDriveFile({ multiple }).then(files => res(files)),
|
||||||
|
}, {
|
||||||
|
text: i18n.ts.fromUrl,
|
||||||
|
icon: 'ti ti-link',
|
||||||
|
action: () => chooseFileFromUrl().then(file => res([file])),
|
||||||
|
}], src);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> {
|
||||||
|
return select(src, label, false).then(files => files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
|
||||||
|
return select(src, label, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createCroppedImageDriveFileFromImageDriveFile(imageDriveFile: Misskey.entities.DriveFile, options: {
|
||||||
|
aspectRatio: number | null;
|
||||||
|
}): Promise<Misskey.entities.DriveFile> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const imgUrl = getProxiedImageUrl(imageDriveFile.url, undefined, true);
|
||||||
|
const image = new Image();
|
||||||
|
image.src = imgUrl;
|
||||||
|
image.onload = () => {
|
||||||
|
const canvas = window.document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
canvas.width = image.width;
|
||||||
|
canvas.height = image.height;
|
||||||
|
ctx.drawImage(image, 0, 0);
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
os.cropImageFile(blob, {
|
||||||
|
aspectRatio: options.aspectRatio,
|
||||||
|
}).then(croppedImageFile => {
|
||||||
|
uploadFile(croppedImageFile, {
|
||||||
|
name: imageDriveFile.name,
|
||||||
|
folderId: imageDriveFile.folderId,
|
||||||
|
}).then(driveFile => {
|
||||||
|
resolve(driveFile);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -112,17 +112,6 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
|
||||||
action: () => describe(file),
|
action: () => describe(file),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isImage) {
|
|
||||||
menuItems.push({
|
|
||||||
text: i18n.ts.cropImage,
|
|
||||||
icon: 'ti ti-crop',
|
|
||||||
action: () => os.createCroppedImageDriveFileFromImageDriveFile(file, {
|
|
||||||
aspectRatio: NaN,
|
|
||||||
uploadFolder: folder ? folder.id : folder,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
menuItems.push({ type: 'divider' }, {
|
menuItems.push({ type: 'divider' }, {
|
||||||
text: i18n.ts.createNoteFromTheFile,
|
text: i18n.ts.createNoteFromTheFile,
|
||||||
icon: 'ti ti-pencil',
|
icon: 'ti ti-pencil',
|
||||||
|
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { } from 'vue';
|
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import * as os from '@/os.js';
|
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
|
||||||
import { useStream } from '@/stream.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
|
||||||
import { prefer } from '@/preferences.js';
|
|
||||||
|
|
||||||
export function chooseFileFromPcAndUpload(
|
|
||||||
options: {
|
|
||||||
multiple?: boolean;
|
|
||||||
folderId?: string | null;
|
|
||||||
} = {},
|
|
||||||
): Promise<Misskey.entities.DriveFile[]> {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
os.chooseFileFromPc({ multiple: options.multiple }).then(files => {
|
|
||||||
if (files.length === 0) return;
|
|
||||||
os.launchUploader(files, {
|
|
||||||
folderId: options.folderId,
|
|
||||||
}).then(driveFiles => {
|
|
||||||
res(driveFiles);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function chooseFileFromDrive(options: {
|
|
||||||
multiple?: boolean;
|
|
||||||
} = {}): Promise<Misskey.entities.DriveFile[]> {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
os.selectDriveFile({
|
|
||||||
multiple: options.multiple,
|
|
||||||
}).then(files => {
|
|
||||||
res(files);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
os.inputText({
|
|
||||||
title: i18n.ts.uploadFromUrl,
|
|
||||||
type: 'url',
|
|
||||||
placeholder: i18n.ts.uploadFromUrlDescription,
|
|
||||||
}).then(({ canceled, result: url }) => {
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
const marker = Math.random().toString(); // TODO: UUIDとか使う
|
|
||||||
|
|
||||||
// TODO: no websocketモード対応
|
|
||||||
const connection = useStream().useChannel('main');
|
|
||||||
connection.on('urlUploadFinished', urlResponse => {
|
|
||||||
if (urlResponse.marker === marker) {
|
|
||||||
res(urlResponse.file);
|
|
||||||
connection.dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
misskeyApi('drive/files/upload-from-url', {
|
|
||||||
url: url,
|
|
||||||
folderId: prefer.s.uploadFolder,
|
|
||||||
marker,
|
|
||||||
});
|
|
||||||
|
|
||||||
os.alert({
|
|
||||||
title: i18n.ts.uploadFromUrlRequested,
|
|
||||||
text: i18n.ts.uploadFromUrlMayTakeTime,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
os.popupMenu([label ? {
|
|
||||||
text: label,
|
|
||||||
type: 'label',
|
|
||||||
} : undefined, {
|
|
||||||
text: i18n.ts.upload,
|
|
||||||
icon: 'ti ti-upload',
|
|
||||||
action: () => chooseFileFromPcAndUpload({ multiple }).then(files => res(files)),
|
|
||||||
}, {
|
|
||||||
text: i18n.ts.fromDrive,
|
|
||||||
icon: 'ti ti-cloud',
|
|
||||||
action: () => chooseFileFromDrive({ multiple }).then(files => res(files)),
|
|
||||||
}, {
|
|
||||||
text: i18n.ts.fromUrl,
|
|
||||||
icon: 'ti ti-link',
|
|
||||||
action: () => chooseFileFromUrl().then(file => res([file])),
|
|
||||||
}], src);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> {
|
|
||||||
return select(src, label, false).then(files => files[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
|
|
||||||
return select(src, label, true);
|
|
||||||
}
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import { apiUrl } from '@@/js/config.js';
|
|
||||||
import { $i } from '@/i.js';
|
|
||||||
import { instance } from '@/instance.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
|
||||||
import * as os from '@/os.js';
|
|
||||||
import { globalEvents } from '@/events.js';
|
|
||||||
|
|
||||||
export function uploadFile(file: File | Blob, options: {
|
|
||||||
name?: string;
|
|
||||||
folderId?: string | null;
|
|
||||||
onProgress?: (ctx: { total: number; loaded: number; }) => void;
|
|
||||||
} = {}): Promise<Misskey.entities.DriveFile> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if ($i == null) return reject();
|
|
||||||
|
|
||||||
if ((file.size > instance.maxFileSize) || (file.size > ($i.policies.maxFileSizeMb * 1024 * 1024))) {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
title: i18n.ts.failedToUpload,
|
|
||||||
text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit,
|
|
||||||
});
|
|
||||||
return reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('POST', apiUrl + '/drive/files/create', true);
|
|
||||||
xhr.onload = ((ev: ProgressEvent<XMLHttpRequest>) => {
|
|
||||||
if (xhr.status !== 200 || ev.target == null || ev.target.response == null) {
|
|
||||||
if (xhr.status === 413) {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
title: i18n.ts.failedToUpload,
|
|
||||||
text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit,
|
|
||||||
});
|
|
||||||
} else if (ev.target?.response) {
|
|
||||||
const res = JSON.parse(ev.target.response);
|
|
||||||
if (res.error?.id === 'bec5bd69-fba3-43c9-b4fb-2894b66ad5d2') {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
title: i18n.ts.failedToUpload,
|
|
||||||
text: i18n.ts.cannotUploadBecauseInappropriate,
|
|
||||||
});
|
|
||||||
} else if (res.error?.id === 'd08dbc37-a6a9-463a-8c47-96c32ab5f064') {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
title: i18n.ts.failedToUpload,
|
|
||||||
text: i18n.ts.cannotUploadBecauseNoFreeSpace,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
title: i18n.ts.failedToUpload,
|
|
||||||
text: `${res.error?.message}\n${res.error?.code}\n${res.error?.id}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
title: 'Failed to upload',
|
|
||||||
text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reject();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const driveFile = JSON.parse(ev.target.response);
|
|
||||||
globalEvents.emit('driveFileCreated', driveFile);
|
|
||||||
resolve(driveFile);
|
|
||||||
}) as (ev: ProgressEvent<EventTarget>) => any;
|
|
||||||
|
|
||||||
if (options.onProgress) {
|
|
||||||
xhr.upload.onprogress = ev => {
|
|
||||||
if (ev.lengthComputable) {
|
|
||||||
options.onProgress({
|
|
||||||
total: ev.total,
|
|
||||||
loaded: ev.loaded,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('i', $i.token);
|
|
||||||
formData.append('force', 'true');
|
|
||||||
formData.append('file', file);
|
|
||||||
formData.append('name', options.name ?? file.name ?? 'untitled');
|
|
||||||
if (options.folderId) formData.append('folderId', options.folderId);
|
|
||||||
|
|
||||||
xhr.send(formData);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue