Merge branch 'drive' into develop
# Conflicts: # locales/index.d.ts # locales/ja-JP.yml # packages/frontend/src/components/MkDrive.folder.vue # packages/frontend/src/components/MkDrive.navFolder.vue # packages/frontend/src/components/MkDrive.vue
This commit is contained in:
commit
ef3dac4b1e
3
locales/index.d.ts
vendored
3
locales/index.d.ts
vendored
|
@ -79,6 +79,9 @@ export interface Locale {
|
||||||
"files": string;
|
"files": string;
|
||||||
"download": string;
|
"download": string;
|
||||||
"driveFileDeleteConfirm": string;
|
"driveFileDeleteConfirm": string;
|
||||||
|
"driveFilesDeleteConfirm": string;
|
||||||
|
"driveFilesSensitiveonConfirm": string;
|
||||||
|
"driveFilesSensitiveoffConfirm": string;
|
||||||
"driveFolderDeleteConfirm": string;
|
"driveFolderDeleteConfirm": string;
|
||||||
"unfollowConfirm": string;
|
"unfollowConfirm": string;
|
||||||
"exportRequested": string;
|
"exportRequested": string;
|
||||||
|
|
|
@ -76,6 +76,9 @@ export: "エクスポート"
|
||||||
files: "ファイル"
|
files: "ファイル"
|
||||||
download: "ダウンロード"
|
download: "ダウンロード"
|
||||||
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。"
|
driveFileDeleteConfirm: "ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。"
|
||||||
|
driveFilesDeleteConfirm: "{name}つのファイルを削除しますか?このファイルを使用した一部のコンテンツも削除されます。"
|
||||||
|
driveFilesSensitiveonConfirm: "{name}つのファイルをセンシティブにしますか?"
|
||||||
|
driveFilesSensitiveoffConfirm: "{name}つのファイルのセンシティブを解除しますか?"
|
||||||
driveFolderDeleteConfirm: "フォルダ「{name}」を削除しますか?このフォルダの中に存在するファイルを使用した一部のコンテンツも削除されます。"
|
driveFolderDeleteConfirm: "フォルダ「{name}」を削除しますか?このフォルダの中に存在するファイルを使用した一部のコンテンツも削除されます。"
|
||||||
unfollowConfirm: "{name}のフォローを解除しますか?"
|
unfollowConfirm: "{name}のフォローを解除しますか?"
|
||||||
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
|
exportRequested: "エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2023.9.3-prismisskey.2",
|
"version": "2023.9.3-prismisskey.3",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
"build-assets": "node ./scripts/build-assets.mjs",
|
"build-assets": "node ./scripts/build-assets.mjs",
|
||||||
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
|
||||||
"build-storybook": "pnpm --filter frontend build-storybook",
|
"build-storybook": "pnpm --filter frontend build-storybook",
|
||||||
|
"build-and-start": "pnpm build && pnpm start",
|
||||||
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
|
||||||
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
|
||||||
"init": "pnpm migrate",
|
"init": "pnpm migrate",
|
||||||
|
|
|
@ -18,7 +18,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@drop.prevent.stop="onDrop"
|
@drop.prevent.stop="onDrop"
|
||||||
@dragstart="onDragstart"
|
@dragstart="onDragstart"
|
||||||
@dragend="onDragend"
|
@dragend="onDragend"
|
||||||
|
|
||||||
>
|
>
|
||||||
<p :class="$style.name">
|
<p :class="$style.name">
|
||||||
<template v-if="hover"><i :class="$style.icon" class="ti ti-folder ti-fw"></i></template>
|
<template v-if="hover"><i :class="$style.icon" class="ti ti-folder ti-fw"></i></template>
|
||||||
|
@ -45,7 +44,7 @@ const props = withDefaults(defineProps<{
|
||||||
folder: Misskey.entities.DriveFolder;
|
folder: Misskey.entities.DriveFolder;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
selectMode?: boolean;
|
selectMode?: boolean;
|
||||||
multipleselect?;
|
selectedFiles?: string[];
|
||||||
}>(), {
|
}>(), {
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
selectMode: false,
|
selectMode: false,
|
||||||
|
@ -145,8 +144,8 @@ function onDrop(ev: DragEvent) {
|
||||||
if (driveFile != null && driveFile !== '') {
|
if (driveFile != null && driveFile !== '') {
|
||||||
const file = JSON.parse(driveFile);
|
const file = JSON.parse(driveFile);
|
||||||
emit('removeFile', file.id);
|
emit('removeFile', file.id);
|
||||||
if (props.multipleselect.length > 0) {
|
if (props.selectedFiles.length > 0) {
|
||||||
props.multipleselect.forEach((e)=>{
|
props.selectedFiles.forEach((e)=>{
|
||||||
os.api('drive/files/update', {
|
os.api('drive/files/update', {
|
||||||
fileId: e.id,
|
fileId: e.id,
|
||||||
folderId: props.folder.id,
|
folderId: props.folder.id,
|
||||||
|
@ -158,9 +157,7 @@ function onDrop(ev: DragEvent) {
|
||||||
folderId: props.folder.id,
|
folderId: props.folder.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region ドライブのフォルダ
|
//#region ドライブのフォルダ
|
||||||
|
@ -234,120 +231,120 @@ function rename() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteFolder() {
|
function deleteFolder() {
|
||||||
os.api('drive/folders/show', {
|
os.api('drive/folders/show', {
|
||||||
|
folderId: props.folder.id,
|
||||||
|
}).then(async (r) => {
|
||||||
|
|
||||||
|
if (r.foldersCount > 0) {
|
||||||
|
await os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: i18n.ts.unableToDelete,
|
||||||
|
text: 'フォルダ内にフォルダが存在するため、削除できません。 \n フォルダ内のフォルダを削除してから試してみてください。',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.filesCount > 0) {
|
||||||
|
|
||||||
|
const {canceled} = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.t('driveFolderDeleteConfirm', {name: props.folder.name}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
let allResults = [];
|
||||||
|
let Result = await os.api('drive/files', {folderId: props.folder.id, limit: 31});
|
||||||
|
allResults = allResults.concat(Result)
|
||||||
|
while (Result.length >= 31) {
|
||||||
|
const untilId = Result[Result.length - 1].id;
|
||||||
|
Result = await os.api('drive/files', { folderId: props.folder.id, limit: 31, untilId });
|
||||||
|
allResults = allResults.concat(Result); // pushをconcatに変更
|
||||||
|
}
|
||||||
|
allResults.forEach((r,i)=>{
|
||||||
|
os.api('drive/files/delete',{fileId: r.id})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
os.api('drive/folders/show', {
|
||||||
folderId: props.folder.id,
|
folderId: props.folder.id,
|
||||||
}).then(async (r) => {
|
}).then(async (r) =>{
|
||||||
|
if (r.filesCount > 0) {
|
||||||
|
|
||||||
if (r.foldersCount > 0) {
|
let allResults = [];
|
||||||
await os.alert({
|
let Result = await os.api('drive/files', {folderId: props.folder.id, limit: 31});
|
||||||
type: 'error',
|
allResults = allResults.concat(Result)
|
||||||
title: i18n.ts.unableToDelete,
|
while (Result.length >= 31) {
|
||||||
text: 'フォルダ内にフォルダが存在するため、削除できません。 \n フォルダ内のフォルダを削除してから試してみてください。',
|
const untilId = Result[Result.length - 1].id;
|
||||||
});
|
Result = await os.api('drive/files', { folderId: props.folder.id, limit: 31, untilId });
|
||||||
}
|
allResults = allResults.concat(Result);
|
||||||
|
}
|
||||||
|
allResults.forEach((r,i)=>{
|
||||||
|
os.api('drive/files/delete',{fileId: r.id})
|
||||||
|
})
|
||||||
|
|
||||||
if (r.filesCount > 0) {
|
os.api('drive/folders/delete', {
|
||||||
|
folderId: props.folder.id,
|
||||||
const {canceled} = await os.confirm({
|
}).then(() => {
|
||||||
type: 'warning',
|
if (defaultStore.state.uploadFolder === props.folder.id) {
|
||||||
text: i18n.t('driveFolderDeleteConfirm', {name: props.folder.name}),
|
defaultStore.set('uploadFolder', null);
|
||||||
});
|
|
||||||
|
|
||||||
if (canceled) return;
|
|
||||||
|
|
||||||
let allResults = [];
|
|
||||||
let Result = await os.api('drive/files', {folderId: props.folder.id, limit: 31});
|
|
||||||
allResults = allResults.concat(Result)
|
|
||||||
while (Result.length >= 31) {
|
|
||||||
const untilId = Result[Result.length - 1].id;
|
|
||||||
Result = await os.api('drive/files', { folderId: props.folder.id, limit: 31, untilId });
|
|
||||||
allResults = allResults.concat(Result); // pushをconcatに変更
|
|
||||||
}
|
|
||||||
allResults.forEach((r,i)=>{
|
|
||||||
os.api('drive/files/delete',{fileId: r.id})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
os.api('drive/folders/show', {
|
|
||||||
folderId: props.folder.id,
|
|
||||||
}).then(async (r) =>{
|
|
||||||
if (r.filesCount > 0) {
|
|
||||||
|
|
||||||
let allResults = [];
|
|
||||||
let Result = await os.api('drive/files', {folderId: props.folder.id, limit: 31});
|
|
||||||
allResults = allResults.concat(Result)
|
|
||||||
while (Result.length >= 31) {
|
|
||||||
const untilId = Result[Result.length - 1].id;
|
|
||||||
Result = await os.api('drive/files', { folderId: props.folder.id, limit: 31, untilId });
|
|
||||||
allResults = allResults.concat(Result); // pushをconcatに変更
|
|
||||||
}
|
}
|
||||||
allResults.forEach((r,i)=>{
|
}).catch(err => {
|
||||||
console.log(r)
|
switch (err.id) {
|
||||||
os.api('drive/files/delete',{fileId: r.id})
|
case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
|
||||||
})
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: i18n.ts.unableToDelete,
|
||||||
|
text: i18n.ts.hasChildFilesOrFolders,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
text: i18n.ts.unableToDelete,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
os.api('drive/folders/delete', {
|
os.api('drive/folders/delete', {
|
||||||
folderId: props.folder.id,
|
folderId: props.folder.id,
|
||||||
}).then(() => {
|
})
|
||||||
if (defaultStore.state.uploadFolder === props.folder.id) {
|
}else{
|
||||||
defaultStore.set('uploadFolder', null);
|
os.api('drive/folders/delete', {
|
||||||
}
|
folderId: props.folder.id,
|
||||||
}).catch(err => {
|
})
|
||||||
switch (err.id) {
|
}
|
||||||
case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
|
})
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
} else {
|
||||||
title: i18n.ts.unableToDelete,
|
|
||||||
text: i18n.ts.hasChildFilesOrFolders,
|
await os.api('drive/folders/delete', {
|
||||||
});
|
folderId: props.folder.id,
|
||||||
break;
|
}).then(() => {
|
||||||
default:
|
if (defaultStore.state.uploadFolder === props.folder.id) {
|
||||||
os.alert({
|
defaultStore.set('uploadFolder', null);
|
||||||
type: 'error',
|
}
|
||||||
text: i18n.ts.unableToDelete,
|
}).catch(err => {
|
||||||
});
|
switch (err.id) {
|
||||||
}
|
case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: i18n.ts.unableToDelete,
|
||||||
|
text: i18n.ts.hasChildFilesOrFolders,
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
os.api('drive/folders/delete', {
|
default:
|
||||||
folderId: props.folder.id,
|
os.alert({
|
||||||
})
|
type: 'error',
|
||||||
}else{
|
text: i18n.ts.unableToDelete,
|
||||||
os.api('drive/folders/delete', {
|
});
|
||||||
folderId: props.folder.id,
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
await os.api('drive/folders/delete', {
|
|
||||||
folderId: props.folder.id,
|
|
||||||
}).then(() => {
|
|
||||||
if (defaultStore.state.uploadFolder === props.folder.id) {
|
|
||||||
defaultStore.set('uploadFolder', null);
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
switch (err.id) {
|
|
||||||
case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
title: i18n.ts.unableToDelete,
|
|
||||||
text: i18n.ts.hasChildFilesOrFolders,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
text: i18n.ts.unableToDelete,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function setAsUploadFolder() {
|
function setAsUploadFolder() {
|
||||||
defaultStore.set('uploadFolder', props.folder.id);
|
defaultStore.set('uploadFolder', props.folder.id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { i18n } from '@/i18n.js';
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
folder?: Misskey.entities.DriveFolder;
|
folder?: Misskey.entities.DriveFolder;
|
||||||
parentFolder: Misskey.entities.DriveFolder | null;
|
parentFolder: Misskey.entities.DriveFolder | null;
|
||||||
multipleselect?;
|
selectedFiles: string[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -113,22 +113,19 @@ function onDrop(ev: DragEvent) {
|
||||||
if (driveFile != null && driveFile !== '') {
|
if (driveFile != null && driveFile !== '') {
|
||||||
const file = JSON.parse(driveFile);
|
const file = JSON.parse(driveFile);
|
||||||
emit('removeFile', file.id);
|
emit('removeFile', file.id);
|
||||||
|
if (props.selectedFiles.length > 0) {
|
||||||
if (props.multipleselect.length > 0) {
|
props.selectedFiles.forEach((e) => {
|
||||||
|
os.api('drive/files/update', {
|
||||||
props.multipleselect.forEach((e)=>{
|
fileId: e.id,
|
||||||
os.api('drive/files/update', {
|
folderId: props.folder ? props.folder.id : null,
|
||||||
fileId: e.id,
|
});
|
||||||
folderId: props.folder ? props.folder.id : null,
|
});
|
||||||
});
|
} else {
|
||||||
})
|
os.api('drive/files/update', {
|
||||||
|
fileId: file.id,
|
||||||
}else{
|
folderId: props.folder ? props.folder.id : null,
|
||||||
os.api('drive/files/update', {
|
});
|
||||||
fileId: file.id,
|
}
|
||||||
folderId: props.folder ? props.folder.id : null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.root" @scroll="handleScroll">
|
<div :class="$style.root">
|
||||||
<nav :class="$style.nav">
|
<nav :class="$style.nav">
|
||||||
<div :class="$style.navPath" @contextmenu.prevent.stop="() => {}">
|
<div :class="$style.navPath" @contextmenu.prevent.stop="() => {}">
|
||||||
<XNavFolder
|
<XNavFolder
|
||||||
:class="[$style.navPathItem, { [$style.navCurrent]: folder == null }]"
|
:class="[$style.navPathItem, { [$style.navCurrent]: folder == null }]"
|
||||||
:parentFolder="folder"
|
:parentFolder="folder"
|
||||||
|
:selectedFiles="selectedFiles"
|
||||||
@move="move"
|
@move="move"
|
||||||
@upload="upload"
|
@upload="upload"
|
||||||
@removeFile="removeFile"
|
@removeFile="removeFile"
|
||||||
|
@ -27,10 +28,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@removeFolder="removeFolder"
|
@removeFolder="removeFolder"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<span v-if="folder != null" :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span>
|
<span v-if="folder != null" :class="[$style.navPathItem, $style.navSeparator]"><i
|
||||||
|
class="ti ti-chevron-right"
|
||||||
|
></i></span>
|
||||||
<span v-if="folder != null" :class="[$style.navPathItem, $style.navCurrent]">{{ folder.name }}</span>
|
<span v-if="folder != null" :class="[$style.navPathItem, $style.navCurrent]">{{ folder.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="_button" :class="$style.navMenu" @click="showMenu"><i class="ti ti-dots"></i></button>
|
<button v-if="!multiple" class="_button" :class="$style.navMenu" @click="filesSelect">複数選択モード</button>
|
||||||
|
<span v-if="multiple && selectedFiles.length > 0" style="padding-right: 12px; margin-top: auto; margin-bottom: auto;opacity: 0.5;">
|
||||||
|
({{ number(selectedFiles.length) }})
|
||||||
|
</span>
|
||||||
|
<button v-if="multiple" class="_button" :class="$style.navMenu" @click="filesSelect">複数選択モード解除</button>
|
||||||
|
<button v-if="multiple && selectedFiles.length === 0" style="padding-right: 12px;" class="_button" @click="filesAllSelect">
|
||||||
|
全選択
|
||||||
|
</button>
|
||||||
|
<button v-if="multiple && selectedFiles.length !== 0" style="padding-right: 12px;" class="_button" @click="filesAllSelect">
|
||||||
|
全選択解除
|
||||||
|
</button>
|
||||||
|
<button class="_button" @click="showMenu"><i class="ti ti-dots"></i></button>
|
||||||
</nav>
|
</nav>
|
||||||
<div
|
<div
|
||||||
ref="main"
|
ref="main"
|
||||||
|
@ -51,6 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:folder="f"
|
:folder="f"
|
||||||
:selectMode="select === 'folder'"
|
:selectMode="select === 'folder'"
|
||||||
:isSelected="selectedFolders.some(x => x.id === f.id)"
|
:isSelected="selectedFolders.some(x => x.id === f.id)"
|
||||||
|
:selectedFiles="selectedFiles"
|
||||||
@chosen="chooseFolder"
|
@chosen="chooseFolder"
|
||||||
@move="move"
|
@move="move"
|
||||||
@upload="upload"
|
@upload="upload"
|
||||||
|
@ -73,15 +88,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:folder="folder"
|
:folder="folder"
|
||||||
:selectMode="select === 'file'"
|
:selectMode="select === 'file'"
|
||||||
:isSelected="selectedFiles.some(x => x.id === file.id)"
|
:isSelected="selectedFiles.some(x => x.id === file.id)"
|
||||||
:SelectFiles="SelectFiles"
|
@click.shift.left.exact="filesSelect"
|
||||||
@chosen="chooseFile"
|
@chosen="chooseFile"
|
||||||
@dragstart="isDragSource = true"
|
@dragstart="isDragSource = true"
|
||||||
@dragend="isDragSource = false"
|
@dragend="isDragSource = false"
|
||||||
@pointerdown="startLongPress(file)"
|
|
||||||
@pointerup="endLongPress"
|
|
||||||
@pointermove="debounceEvent"
|
|
||||||
@pointercancel="debounceEvent"
|
|
||||||
@click.shift.left.exact="multipleSelect(file)"
|
|
||||||
/>
|
/>
|
||||||
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
|
<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
|
||||||
<div v-for="(n, i) in 16" :key="i" :class="$style.padding"></div>
|
<div v-for="(n, i) in 16" :key="i" :class="$style.padding"></div>
|
||||||
|
@ -89,14 +99,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div v-if="files.length == 0 && folders.length == 0 && !fetching" :class="$style.empty">
|
<div v-if="files.length == 0 && folders.length == 0 && !fetching" :class="$style.empty">
|
||||||
<div v-if="draghover">{{ i18n.t('empty-draghover') }}</div>
|
<div v-if="draghover">{{ i18n.t('empty-draghover') }}</div>
|
||||||
<div v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</div>
|
<div v-if="!draghover && folder == null">
|
||||||
|
<strong>{{
|
||||||
|
i18n.ts.emptyDrive
|
||||||
|
}}</strong><br/>{{ i18n.t('empty-drive-description') }}
|
||||||
|
</div>
|
||||||
<div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div>
|
<div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkLoading v-if="fetching"/>
|
<MkLoading v-if="fetching"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="draghover" :class="$style.dropzone"></div>
|
<div v-if="draghover" :class="$style.dropzone"></div>
|
||||||
<input ref="fileInput" style="display: none;" type="file" accept="*/*" multiple tabindex="-1" @change="onChangeFileInput"/>
|
<input
|
||||||
|
ref="fileInput" style="display: none;" type="file" accept="*/*" multiple tabindex="-1"
|
||||||
|
@change="onChangeFileInput"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -113,23 +130,24 @@ import { defaultStore } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { uploadFile, uploads } from '@/scripts/upload.js';
|
import { uploadFile, uploads } from '@/scripts/upload.js';
|
||||||
import { claimAchievement } from '@/scripts/achievements.js';
|
import { claimAchievement } from '@/scripts/achievements.js';
|
||||||
import { isTouchUsing } from '@/scripts/touch.js';
|
import number from "@/filters/number.js";
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
initialFolder?: Misskey.entities.DriveFolder;
|
initialFolder?: Misskey.entities.DriveFolder;
|
||||||
type?: string;
|
type?: string;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
select?: 'file' | 'folder' | null;
|
select?: 'file' | 'folder' | null;
|
||||||
}>(), {
|
}>(), {
|
||||||
multiple: false,
|
multiple: false,
|
||||||
select: null,
|
select: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void;
|
(ev: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void;
|
||||||
(ev: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void;
|
(ev: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void;
|
||||||
(ev: 'move-root'): void;
|
(ev: 'move-root'): void;
|
||||||
(ev: 'cd', v: Misskey.entities.DriveFolder | null): void;
|
(ev: 'cd', v: Misskey.entities.DriveFolder | null): void;
|
||||||
(ev: 'open-folder', v: Misskey.entities.DriveFolder): void;
|
(ev: 'open-folder', v: Misskey.entities.DriveFolder): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const loadMoreFiles = shallowRef<InstanceType<typeof MkButton>>();
|
const loadMoreFiles = shallowRef<InstanceType<typeof MkButton>>();
|
||||||
|
@ -146,10 +164,8 @@ const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]);
|
||||||
const uploadings = uploads;
|
const uploadings = uploads;
|
||||||
const connection = useStream().useChannel('drive');
|
const connection = useStream().useChannel('drive');
|
||||||
const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい
|
const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい
|
||||||
const SelectFiles = ref([]);
|
const multiple = ref(props.multiple || false);
|
||||||
const isLongPressing = ref(false);
|
const select = ref(props.select || null);
|
||||||
let longPressTimeout = null;
|
|
||||||
const isScrolling = ref(false); // スクロール中でない状態で初期化
|
|
||||||
|
|
||||||
// ドロップされようとしているか
|
// ドロップされようとしているか
|
||||||
const draghover = ref(false);
|
const draghover = ref(false);
|
||||||
|
@ -169,47 +185,6 @@ watch(folder, () => emit('cd', folder.value));
|
||||||
function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) {
|
function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) {
|
||||||
addFile(file, true);
|
addFile(file, true);
|
||||||
}
|
}
|
||||||
let timeout;
|
|
||||||
const debounceEvent = (event) => {
|
|
||||||
if (isTouchUsing && SelectFiles.value.length === 0) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
isLongPressing.value = false;
|
|
||||||
}, 900);
|
|
||||||
}else{
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
isLongPressing.value = false;
|
|
||||||
}, 80);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function startLongPress(file) {
|
|
||||||
console.log(SelectFiles.value.length)
|
|
||||||
if (isTouchUsing && SelectFiles.value.length === 0) {
|
|
||||||
isLongPressing.value = true;
|
|
||||||
longPressTimeout = setTimeout(() => {
|
|
||||||
if (isLongPressing.value) {
|
|
||||||
multipleSelect(file);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}else{
|
|
||||||
isLongPressing.value = true;
|
|
||||||
longPressTimeout = setTimeout(() => {
|
|
||||||
if (isLongPressing.value) {
|
|
||||||
multipleSelect(file);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function endLongPress() {
|
|
||||||
if (isTouchUsing) {
|
|
||||||
isLongPressing.value = false;
|
|
||||||
clearTimeout(longPressTimeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onStreamDriveFileUpdated(file: Misskey.entities.DriveFile) {
|
function onStreamDriveFileUpdated(file: Misskey.entities.DriveFile) {
|
||||||
const current = folder.value ? folder.value.id : null;
|
const current = folder.value ? folder.value.id : null;
|
||||||
|
@ -227,14 +202,7 @@ function onStreamDriveFileDeleted(fileId: string) {
|
||||||
function onStreamDriveFolderCreated(createdFolder: Misskey.entities.DriveFolder) {
|
function onStreamDriveFolderCreated(createdFolder: Misskey.entities.DriveFolder) {
|
||||||
addFolder(createdFolder, true);
|
addFolder(createdFolder, true);
|
||||||
}
|
}
|
||||||
function multipleSelect(file) {
|
|
||||||
const index = SelectFiles.value.findIndex(item => item.id === file.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
SelectFiles.value.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
SelectFiles.value.push(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function onStreamDriveFolderUpdated(updatedFolder: Misskey.entities.DriveFolder) {
|
function onStreamDriveFolderUpdated(updatedFolder: Misskey.entities.DriveFolder) {
|
||||||
const current = folder.value ? folder.value.id : null;
|
const current = folder.value ? folder.value.id : null;
|
||||||
if (current !== updatedFolder.parentId) {
|
if (current !== updatedFolder.parentId) {
|
||||||
|
@ -448,7 +416,7 @@ function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null
|
||||||
|
|
||||||
function chooseFile(file: Misskey.entities.DriveFile) {
|
function chooseFile(file: Misskey.entities.DriveFile) {
|
||||||
const isAlreadySelected = selectedFiles.value.some(f => f.id === file.id);
|
const isAlreadySelected = selectedFiles.value.some(f => f.id === file.id);
|
||||||
if (props.multiple) {
|
if (multiple.value) {
|
||||||
if (isAlreadySelected) {
|
if (isAlreadySelected) {
|
||||||
selectedFiles.value = selectedFiles.value.filter(f => f.id !== file.id);
|
selectedFiles.value = selectedFiles.value.filter(f => f.id !== file.id);
|
||||||
} else {
|
} else {
|
||||||
|
@ -467,7 +435,7 @@ function chooseFile(file: Misskey.entities.DriveFile) {
|
||||||
|
|
||||||
function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) {
|
function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) {
|
||||||
const isAlreadySelected = selectedFolders.value.some(f => f.id === folderToChoose.id);
|
const isAlreadySelected = selectedFolders.value.some(f => f.id === folderToChoose.id);
|
||||||
if (props.multiple) {
|
if (multiple.value) {
|
||||||
if (isAlreadySelected) {
|
if (isAlreadySelected) {
|
||||||
selectedFolders.value = selectedFolders.value.filter(f => f.id !== folderToChoose.id);
|
selectedFolders.value = selectedFolders.value.filter(f => f.id !== folderToChoose.id);
|
||||||
} else {
|
} else {
|
||||||
|
@ -554,6 +522,7 @@ function removeFolder(folderToRemove: Misskey.entities.DriveFolder | string) {
|
||||||
function removeFile(file: Misskey.entities.DriveFile | string) {
|
function removeFile(file: Misskey.entities.DriveFile | string) {
|
||||||
const fileId = typeof file === 'object' ? file.id : file;
|
const fileId = typeof file === 'object' ? file.id : file;
|
||||||
files.value = files.value.filter(f => f.id !== fileId);
|
files.value = files.value.filter(f => f.id !== fileId);
|
||||||
|
selectedFiles.value = selectedFiles.value.filter(f => f.id !== fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendFile(file: Misskey.entities.DriveFile) {
|
function appendFile(file: Misskey.entities.DriveFile) {
|
||||||
|
@ -563,6 +532,7 @@ function appendFile(file: Misskey.entities.DriveFile) {
|
||||||
function appendFolder(folderToAppend: Misskey.entities.DriveFolder) {
|
function appendFolder(folderToAppend: Misskey.entities.DriveFolder) {
|
||||||
addFolder(folderToAppend);
|
addFolder(folderToAppend);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
function prependFile(file: Misskey.entities.DriveFile) {
|
function prependFile(file: Misskey.entities.DriveFile) {
|
||||||
addFile(file, true);
|
addFile(file, true);
|
||||||
|
@ -679,33 +649,130 @@ function getMenu() {
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.upload,
|
text: i18n.ts.upload,
|
||||||
icon: 'ti ti-upload',
|
icon: 'ti ti-upload',
|
||||||
action: () => { selectLocalFile(); },
|
action: () => {
|
||||||
|
selectLocalFile();
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.fromUrl,
|
text: i18n.ts.fromUrl,
|
||||||
icon: 'ti ti-link',
|
icon: 'ti ti-link',
|
||||||
action: () => { urlUpload(); },
|
action: () => {
|
||||||
|
urlUpload();
|
||||||
|
},
|
||||||
}, null, {
|
}, null, {
|
||||||
text: folder.value ? folder.value.name : i18n.ts.drive,
|
text: folder.value ? folder.value.name : i18n.ts.drive,
|
||||||
type: 'label',
|
type: 'label',
|
||||||
}, folder.value ? {
|
}, folder.value ? {
|
||||||
text: i18n.ts.renameFolder,
|
text: i18n.ts.renameFolder,
|
||||||
icon: 'ti ti-forms',
|
icon: 'ti ti-forms',
|
||||||
action: () => { renameFolder(folder.value); },
|
action: () => {
|
||||||
|
renameFolder(folder.value);
|
||||||
|
},
|
||||||
} : undefined, folder.value ? {
|
} : undefined, folder.value ? {
|
||||||
text: i18n.ts.deleteFolder,
|
text: i18n.ts.deleteFolder,
|
||||||
icon: 'ti ti-trash',
|
icon: 'ti ti-trash',
|
||||||
action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); },
|
action: () => {
|
||||||
|
deleteFolder(folder.value as Misskey.entities.DriveFolder);
|
||||||
|
},
|
||||||
} : undefined, {
|
} : undefined, {
|
||||||
text: i18n.ts.createFolder,
|
text: i18n.ts.createFolder,
|
||||||
icon: 'ti ti-folder-plus',
|
icon: 'ti ti-folder-plus',
|
||||||
action: () => { createFolder(); },
|
action: () => {
|
||||||
|
createFolder();
|
||||||
|
},
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMenu(ev: MouseEvent) {
|
async function isSensitive(Files, isSensitive: boolean) {
|
||||||
os.popupMenu(getMenu(), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.t(isSensitive ? 'driveFilesSensitiveonConfirm' : 'driveFilesSensitiveoffConfirm', { name: Files.length }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
Files.forEach((file) => {
|
||||||
|
os.api('drive/files/update', {
|
||||||
|
fileId: file.id,
|
||||||
|
isSensitive,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const contents = ref(null);
|
|
||||||
|
async function fileDelete(Files) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.t('driveFilesDeleteConfirm', { name: Files.length }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
Files.forEach((file) => {
|
||||||
|
os.api('drive/files/delete', {
|
||||||
|
fileId: file.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilesMenu(Files) {
|
||||||
|
return [{
|
||||||
|
text: i18n.ts.createNoteFromTheFile,
|
||||||
|
icon: 'ti ti-pencil',
|
||||||
|
action: () => {
|
||||||
|
if (Files.length >= 16) {
|
||||||
|
os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: '16ファイル以上添付しようとしています',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
os.post({
|
||||||
|
initialFiles: [...Files],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
text: i18n.ts.unmarkAsSensitive,
|
||||||
|
icon: 'ti ti-eye',
|
||||||
|
action: () => {
|
||||||
|
isSensitive(Files, false);
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
text: i18n.ts.markAsSensitive,
|
||||||
|
icon: 'ti ti-eye-exclamation',
|
||||||
|
action: () => {
|
||||||
|
isSensitive(Files, true);
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
text: i18n.ts.delete,
|
||||||
|
icon: 'ti ti-trash',
|
||||||
|
danger: true,
|
||||||
|
action: () => {
|
||||||
|
fileDelete(Files);
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
function filesSelect() {
|
||||||
|
multiple.value = !multiple.value;
|
||||||
|
select.value = (select.value === null) ? 'file' : null;
|
||||||
|
selectedFiles.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function filesAllSelect() {
|
||||||
|
if (selectedFiles.value.length === 0) {
|
||||||
|
selectedFiles.value = files.value;
|
||||||
|
} else {
|
||||||
|
selectedFiles.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMenu(ev: MouseEvent) {
|
||||||
|
console.log(selectedFiles.value.length);
|
||||||
|
if (selectedFiles.value.length === 0) {
|
||||||
|
os.popupMenu(getMenu(), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
|
||||||
|
} else {
|
||||||
|
os.popupMenu(getFilesMenu(selectedFiles.value), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onContextmenu(ev: MouseEvent) {
|
function onContextmenu(ev: MouseEvent) {
|
||||||
os.contextMenu(getMenu(), ev);
|
os.contextMenu(getMenu(), ev);
|
||||||
}
|
}
|
||||||
|
@ -743,119 +810,121 @@ onBeforeUnmount(() => {
|
||||||
connection.dispose();
|
connection.dispose();
|
||||||
ilFilesObserver.disconnect();
|
ilFilesObserver.disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.root {
|
.root {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
z-index: 2;
|
top: 0;
|
||||||
width: 100%;
|
position: sticky;
|
||||||
padding: 0 8px;
|
z-index: 1000;
|
||||||
box-sizing: border-box;
|
background-color: var(--bg);
|
||||||
overflow: auto;
|
width: 100%;
|
||||||
font-size: 0.9em;
|
padding: 0 8px;
|
||||||
box-shadow: 0 1px 0 var(--divider);
|
box-sizing: border-box;
|
||||||
user-select: none;
|
overflow: auto;
|
||||||
|
font-size: 0.9em;
|
||||||
|
box-shadow: 0 1px 0 var(--divider);
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navPath {
|
.navPath {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
line-height: 42px;
|
line-height: 42px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navPathItem {
|
.navPathItem {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
line-height: 42px;
|
line-height: 42px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.navCurrent {
|
&.navCurrent {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.navSeparator {
|
&.navSeparator {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navMenu {
|
.navMenu {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: var(--margin);
|
padding: var(--margin);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&.fetching {
|
&.fetching {
|
||||||
cursor: wait !important;
|
cursor: wait !important;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.uploading {
|
&.uploading {
|
||||||
height: calc(100% - 38px - 100px);
|
height: calc(100% - 38px - 100px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.folders,
|
.folders,
|
||||||
.files {
|
.files {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder,
|
.folder,
|
||||||
.file {
|
.file {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
width: 128px;
|
width: 128px;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.padding {
|
.padding {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
width: 128px + 8px;
|
width: 128px + 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropzone {
|
.dropzone {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 38px;
|
top: 38px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 38px);
|
height: calc(100% - 38px);
|
||||||
border: dashed 2px var(--focus);
|
border: dashed 2px var(--focus);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue