|
@@ -1,5 +1,6 @@
|
|
|
<script setup lang="ts">
|
|
|
import { ref, onMounted, computed, nextTick } from "vue";
|
|
|
+import _ from 'lodash'
|
|
|
import {v4 as uuid4} from 'uuid'
|
|
|
import {
|
|
|
S3Client,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
ListObjectsCommand,
|
|
|
GetObjectCommand,
|
|
|
DeleteObjectsCommand,
|
|
|
+ CopyObjectCommand,
|
|
|
} from "@aws-sdk/client-s3";
|
|
|
import {
|
|
|
Plus,
|
|
@@ -17,7 +19,7 @@ import AppendModal from "./AppendModal.vue";
|
|
|
import type { TreeData } from "./Tree";
|
|
|
import { ElNotification, type ElTree } from "element-plus";
|
|
|
import { MdEditor } from "md-editor-v3";
|
|
|
-import { s3ContentsToTree, getTreeLeafs } from "@/utils/s3Helper";
|
|
|
+import { s3ContentsToTree, getTreeFlatten } from "@/utils/s3Helper";
|
|
|
import "md-editor-v3/lib/style.css";
|
|
|
import type ETNode from 'element-plus/es/components/tree/src/model/node'
|
|
|
import type { DragEvents } from 'element-plus/es/components/tree/src/model/useDragNode'
|
|
@@ -25,6 +27,7 @@ import type {
|
|
|
AllowDropType,
|
|
|
NodeDropType,
|
|
|
} from 'element-plus/es/components/tree/src/tree.type'
|
|
|
+import path from "path-browserify";
|
|
|
|
|
|
const s3 = new S3Client({
|
|
|
credentials: {
|
|
@@ -36,7 +39,7 @@ const s3 = new S3Client({
|
|
|
|
|
|
const tree$ = ref<InstanceType<typeof ElTree>>();
|
|
|
|
|
|
-const langSelect = ref('zh-CN')
|
|
|
+const langSelect = ref<'zh-CN'|'zh-HK'|'en-US'>('zh-CN')
|
|
|
const langKeyPrefix = computed(() => {
|
|
|
return {
|
|
|
'zh-CN': 'docs/',
|
|
@@ -44,14 +47,11 @@ const langKeyPrefix = computed(() => {
|
|
|
'en-US': 'en-US/docs/',
|
|
|
}[langSelect.value]
|
|
|
})
|
|
|
-const langDataSource = computed<TreeData[]>(() => {
|
|
|
- const filtered = dataSource.value.filter(cont => cont.Key.startsWith(langKeyPrefix.value)).map(cont => ({...cont, Key: cont.Key.replace(new RegExp(`^${langKeyPrefix.value}`), '')}))
|
|
|
- return s3ContentsToTree(filtered, {}, (r, label, i, a, thisContent) => ({
|
|
|
- isDir: a.length !== i + 1,
|
|
|
- ...( a.length === i + 1 ? thisContent : {} )
|
|
|
- }));
|
|
|
+const langDataSource = ref<Partial<Record<'zh-CN'|'zh-HK'|'en-US', TreeData[]>>>({})
|
|
|
+const langSelectedDataSource = computed(() => {
|
|
|
+ return langDataSource.value[langSelect.value]
|
|
|
})
|
|
|
-const dataSource = ref([]);
|
|
|
+const dataSource = ref<unknown[]>([]);
|
|
|
const sideLoading = ref(false);
|
|
|
const loadS3DocsListObjects = async () => {
|
|
|
const command = new ListObjectsCommand({ Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET });
|
|
@@ -61,6 +61,19 @@ const loadS3DocsListObjects = async () => {
|
|
|
const loadSideBar = async () => {
|
|
|
sideLoading.value = true;
|
|
|
dataSource.value = await loadS3DocsListObjects();
|
|
|
+ [ 'zh-CN','zh-HK','en-US' ].forEach((lang) => {
|
|
|
+ const prefix = {
|
|
|
+ 'zh-CN': 'docs/',
|
|
|
+ 'zh-HK': 'zh-HK/docs/',
|
|
|
+ 'en-US': 'en-US/docs/',
|
|
|
+ }[lang]
|
|
|
+ const filtered = dataSource.value.filter(cont => cont.Key.startsWith(prefix)).map(cont => ({...cont, Key: cont.Key.replace(new RegExp(`^${prefix}`), '')}))
|
|
|
+ langDataSource.value[lang] = s3ContentsToTree(filtered, {}, (r, label, i, a, thisContent) => ({
|
|
|
+ isDir: a.length !== i + 1,
|
|
|
+ ...( a.length === i + 1 ? thisContent : {} )
|
|
|
+ }));
|
|
|
+ })
|
|
|
+
|
|
|
sideLoading.value = false;
|
|
|
};
|
|
|
onMounted(() => {
|
|
@@ -161,7 +174,7 @@ const onAppend = async (data: TreeData) => {
|
|
|
await s3.send(command);
|
|
|
}
|
|
|
const newChild: TreeData = {
|
|
|
- key,
|
|
|
+ key: `${data.key ? `${data.key}/` : ''}${filename}`,
|
|
|
label: filename,
|
|
|
children: [],
|
|
|
isDir,
|
|
@@ -170,9 +183,9 @@ const onAppend = async (data: TreeData) => {
|
|
|
data.children = [];
|
|
|
}
|
|
|
data.children.push(newChild);
|
|
|
- if (!data.key) {
|
|
|
- loadSideBar()
|
|
|
- }
|
|
|
+ // if (!data.key) {
|
|
|
+ // loadSideBar()
|
|
|
+ // }
|
|
|
} catch (e) {
|
|
|
console.error(e);
|
|
|
ElNotification.error('添加失败')
|
|
@@ -188,7 +201,7 @@ const onRemove = async (node: ETNode, data: TreeData) => {
|
|
|
|
|
|
sideLoading.value = true
|
|
|
try {
|
|
|
- const leafDatas = getTreeLeafs(data)
|
|
|
+ const leafDatas = getTreeFlatten(data)
|
|
|
const command = new DeleteObjectsCommand({
|
|
|
Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
|
|
|
Delete: {
|
|
@@ -198,9 +211,9 @@ const onRemove = async (node: ETNode, data: TreeData) => {
|
|
|
})
|
|
|
await s3.send(command)
|
|
|
children.splice(index, 1);
|
|
|
- if (parent.level === 0) {
|
|
|
- nextTick(loadSideBar)
|
|
|
- }
|
|
|
+ // if (parent.level === 0) {
|
|
|
+ // nextTick(loadSideBar)
|
|
|
+ // }
|
|
|
} catch (e) {
|
|
|
console.error(e)
|
|
|
ElNotification.error('删除失败')
|
|
@@ -208,17 +221,64 @@ const onRemove = async (node: ETNode, data: TreeData) => {
|
|
|
sideLoading.value = false
|
|
|
}
|
|
|
};
|
|
|
-const onDragEnd = (
|
|
|
+const saveSideBarSort = () => {
|
|
|
+ // TODO
|
|
|
+ const sortedNodes = getTreeFlatten( { children: tree$.value!.data } as TreeData, 'preorder', 'all' )
|
|
|
+ const sortedKeyMap = sortedNodes.reduce((a, v, i)=> ( {...a, [v.key ?? '']: i} ), {})
|
|
|
+}
|
|
|
+const onDragEnd = async (
|
|
|
draggingNode: ETNode,
|
|
|
dropNode: ETNode,
|
|
|
dropType: NodeDropType,
|
|
|
ev: DragEvents
|
|
|
) => {
|
|
|
+ if (dropType === 'none') {
|
|
|
+ return
|
|
|
+ }
|
|
|
sideLoading.value = true
|
|
|
- console.log('tree drag end:', draggingNode, dropNode, dropType)
|
|
|
- // TODO copy object
|
|
|
- // TODO save redis
|
|
|
+ const copyTargetPath = path.join(langKeyPrefix.value, dropType === 'inner' ? dropNode.data.key : dropNode.parent.data.key ?? '')
|
|
|
+
|
|
|
+ const copySourceNodes = getTreeFlatten(draggingNode.data)
|
|
|
+ const waitForDelete = []
|
|
|
+ for (const sourceNode of copySourceNodes) {
|
|
|
+ if (sourceNode.isDir) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ const copySource = path.join(langKeyPrefix.value, sourceNode.key)
|
|
|
+ const copyTarget = path.join( copyTargetPath, path.basename(draggingNode.data.key), path.relative(draggingNode.data.key, sourceNode.key) )
|
|
|
+ if (copySource === copyTarget) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const command = new CopyObjectCommand({
|
|
|
+ Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
|
|
|
+ CopySource: encodeURIComponent(`/${import.meta.env.VITE_DOCS_LIST_BUCKET}/${copySource}`),
|
|
|
+ Key: copyTarget
|
|
|
+ })
|
|
|
+ await s3.send(command)
|
|
|
+ waitForDelete.push({ Key: copySource })
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e)
|
|
|
+ console.warn(`${ copySource } 复制失败`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (waitForDelete.length) {
|
|
|
+ const command = new DeleteObjectsCommand({
|
|
|
+ Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
|
|
|
+ Delete: {
|
|
|
+ Objects: waitForDelete,
|
|
|
+ Quiet: false,
|
|
|
+ }
|
|
|
+ })
|
|
|
+ await s3.send(command)
|
|
|
+ }
|
|
|
+
|
|
|
+ await loadSideBar()
|
|
|
sideLoading.value = false
|
|
|
+ nextTick(() => {
|
|
|
+ saveSideBarSort()
|
|
|
+ })
|
|
|
}
|
|
|
const allowDrop = (draggingNode: Node, dropNode: Node, type: AllowDropType) => {
|
|
|
return !( !dropNode.data.isDir && type === 'inner' )
|
|
@@ -235,7 +295,7 @@ const allowDrop = (draggingNode: Node, dropNode: Node, type: AllowDropType) => {
|
|
|
</el-radio-group>
|
|
|
<el-button-group>
|
|
|
<el-button
|
|
|
- @click="onAppend({ key: '', label: '', children: langDataSource })"
|
|
|
+ @click="onAppend({ key: '', label: '', children: langSelectedDataSource })"
|
|
|
type="primary"
|
|
|
text
|
|
|
:icon="Plus"
|
|
@@ -247,7 +307,7 @@ const allowDrop = (draggingNode: Node, dropNode: Node, type: AllowDropType) => {
|
|
|
<el-divider />
|
|
|
<el-tree
|
|
|
ref="tree$"
|
|
|
- :data="langDataSource"
|
|
|
+ :data="langSelectedDataSource"
|
|
|
node-key="key"
|
|
|
default-expand-all
|
|
|
:expand-on-click-node="false"
|