Browse Source

feat: random

Carson 9 months ago
parent
commit
3f1821f369
3 changed files with 96 additions and 40 deletions
  1. 16 17
      .vitepress/config.mts
  2. 3 1
      components/Edit/AppendModal.vue
  3. 77 22
      components/Edit/index.vue

+ 16 - 17
.vitepress/config.mts

@@ -73,7 +73,6 @@ await Promise.all(
 
 // 构建sideBar数据
 let { rootSideBar, zhHKSideBar } = buildSideBar(contents);
-// rootSideBar = rootSideBar.map(sb => { sb.items = undefined;return sb })
 import util from "util";
 console.log(
   util.inspect(rootSideBar, { showHidden: false, depth: null, colors: true }),
@@ -192,22 +191,22 @@ export default defineConfig({
         //   { text: 'Home', link: '/' },
         //   { text: 'Examples', link: '/markdown-examples' }
         // ],
-        // sidebar: [
-        //   { text: "关于CocoBlockly X", link: "/docs" },
-        //   { text: "常见问题解答", link: "/docs/常见问题解答" },
-        //   {
-        //     text: "开始使用CocoBlockly X",
-        //     link: "/docs/start-using-cocoblockly-x",
-        //     collapsed: true,
-        //     items: [],
-        //   },
-        //   {
-        //     text: "电子模块基本教学",
-        //     collapsed: true,
-        //     items: [{ text: "todo", link: "/docs" }],
-        //   },
-        // ],
-        sidebar: rootSideBar,
+        sidebar: [
+          { text: "关于CocoBlockly X", link: "/docs" },
+          { text: "常见问题解答", link: "/docs/常见问题解答" },
+          {
+            text: "开始使用CocoBlockly X",
+            link: "/docs/start-using-cocoblockly-x",
+            collapsed: true,
+            items: [],
+          },
+          {
+            text: "电子模块基本教学",
+            collapsed: true,
+            items: [{ text: "todo", link: "/docs" }],
+          },
+        ],
+        // sidebar: rootSideBar,
         // socialLinks: [
         //   { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
         // ]

+ 3 - 1
components/Edit/AppendModal.vue

@@ -15,7 +15,6 @@ const formData = reactive({
 });
 const rules = reactive<FormRules<typeof formData>>({
   filename: [
-    { required: true, trigger: "change" },
     {
       validator: (rule, value, callback, source, options) => {
         if (
@@ -30,6 +29,9 @@ const rules = reactive<FormRules<typeof formData>>({
       },
       trigger: "change",
     },
+    {
+      pattern: /[^\\/:"*?<>|]/, message: '有非法字符: \\/:"*?<>|', trigger: 'change'
+    }
   ],
 });
 

+ 77 - 22
components/Edit/index.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref, onMounted } from "vue";
+import { ref, onMounted, computed, nextTick } from "vue";
 import {v4 as uuid4} from 'uuid'
 import {
   S3Client,
@@ -19,6 +19,12 @@ import { ElNotification, type ElTree } from "element-plus";
 import { MdEditor } from "md-editor-v3";
 import { s3ContentsToTree, getTreeLeafs } 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'
+import type {
+  AllowDropType,
+  NodeDropType,
+} from 'element-plus/es/components/tree/src/tree.type'
 
 const s3 = new S3Client({
   credentials: {
@@ -30,7 +36,22 @@ const s3 = new S3Client({
 
 const tree$ = ref<InstanceType<typeof ElTree>>();
 
-const dataSource = ref<TreeData[]>([]);
+const langSelect = ref('zh-CN')
+const langKeyPrefix = computed(() => {
+  return {
+    'zh-CN': 'docs/',
+    'zh-HK': 'zh-HK/docs/',
+    '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 dataSource = ref([]);
 const sideLoading = ref(false);
 const loadS3DocsListObjects = async () => {
   const command = new ListObjectsCommand({ Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET });
@@ -39,10 +60,7 @@ const loadS3DocsListObjects = async () => {
 };
 const loadSideBar = async () => {
   sideLoading.value = true;
-  dataSource.value = s3ContentsToTree(await loadS3DocsListObjects(), {}, (r, label, i, a, thisContent) => ({
-    isDir: a.length !== i + 1,
-    ...( a.length === i + 1 ? thisContent : {} )
-  }));
+  dataSource.value = await loadS3DocsListObjects();
   sideLoading.value = false;
 };
 onMounted(() => {
@@ -58,7 +76,7 @@ const onNodeClick = async (data: TreeData, node) => {
   textLoading.value = true;
   const command = new GetObjectCommand({
     Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
-    Key: data.key,
+    Key: `${langKeyPrefix.value}${data.key}`,
     ResponseCacheControl: "no-cache",
   });
   const file = await s3.send(command);
@@ -100,7 +118,7 @@ const onSave = async () => {
   try {
     const command = new PutObjectCommand({
       Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
-      Key: currentOpenData.value?.key,
+      Key: `${langKeyPrefix.value}${currentOpenData.value?.key}`,
       Body: text.value,
     });
     await s3.send(command);
@@ -133,7 +151,7 @@ const onAppend = async (data: TreeData) => {
   }
   try {
     appendLoading.value = true
-    const key = `${data.key ? `${data.key}/` : ''}${filename}`
+    const key = `${langKeyPrefix.value}${data.key ? `${data.key}/` : ''}${filename}`
     if (!isDir) {
       const command = new PutObjectCommand({
         Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
@@ -152,6 +170,9 @@ const onAppend = async (data: TreeData) => {
       data.children = [];
     }
     data.children.push(newChild);
+    if (!data.key) {
+      loadSideBar()
+    }
   } catch (e) {
     console.error(e);
     ElNotification.error('添加失败')
@@ -160,7 +181,7 @@ const onAppend = async (data: TreeData) => {
     showAppendModal.value = false;
   }
 };
-const onRemove = async (node: Node, data: TreeData) => {
+const onRemove = async (node: ETNode, data: TreeData) => {
   const parent = node.parent;
   const children = parent.data.children || parent.data;
   const index = children.findIndex((d) => d.key === data.key);
@@ -171,13 +192,15 @@ const onRemove = async (node: Node, data: TreeData) => {
     const command = new DeleteObjectsCommand({
       Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
       Delete: {
-        Objects: leafDatas.map(d => ({Key: d.key})),
+        Objects: leafDatas.map(d => ({Key: `${langKeyPrefix.value}${d.key}`})),
         Quiet: false,
       }
     })
     await s3.send(command)
-    // TODO request S3 delete
     children.splice(index, 1);
+    if (parent.level === 0) {
+      nextTick(loadSideBar)
+    }
   } catch (e) {
     console.error(e)
     ElNotification.error('删除失败')
@@ -185,26 +208,53 @@ const onRemove = async (node: Node, data: TreeData) => {
     sideLoading.value = false
   }
 };
+const onDragEnd = (
+  draggingNode: ETNode,
+  dropNode: ETNode,
+  dropType: NodeDropType,
+  ev: DragEvents
+) => {
+  sideLoading.value = true
+  console.log('tree drag end:', draggingNode, dropNode, dropType)
+  // TODO copy object
+  // TODO save redis
+  sideLoading.value = false
+}
+const allowDrop = (draggingNode: Node, dropNode: Node, type: AllowDropType) => {
+   return !( !dropNode.data.isDir && type === 'inner' )
+}
 </script>
 <template>
   <div class="md-container">
     <div class="left" v-loading="sideLoading">
-      <el-button-group>
-        <el-button
-          @click="onAppend({ key: '', label: '', children: dataSource })"
-          type="primary"
-          :icon="Plus"
-        >
-        </el-button>
-        <el-button @click="loadSideBar" type="info" :icon="Refresh"></el-button>
-      </el-button-group>
+      <div class="toolbar">
+        <el-radio-group v-model="langSelect" size="small">
+          <el-radio-button label="简体中文" value="zh-CN" />
+          <el-radio-button label="繁体中文" value="zh-HK" />
+          <el-radio-button label="English" value="en-US" />
+        </el-radio-group>
+        <el-button-group>
+          <el-button
+            @click="onAppend({ key: '', label: '', children: langDataSource })"
+            type="primary"
+            text
+            :icon="Plus"
+          >
+          </el-button>
+          <el-button @click="loadSideBar" type="info" text :icon="Refresh"></el-button>
+        </el-button-group>
+      </div>
+      <el-divider />
       <el-tree
         ref="tree$"
-        :data="dataSource"
+        :data="langDataSource"
         node-key="key"
         default-expand-all
         :expand-on-click-node="false"
         @node-click="onNodeClick"
+        draggable
+        :allow-drop="allowDrop"
+        @node-drag-end="onDragEnd"
       >
         <template #default="{ node, data }">
           <Node
@@ -249,6 +299,11 @@ const onRemove = async (node: Node, data: TreeData) => {
     display: flex;
     flex-direction: column;
     align-items: stretch;
+    .toolbar {
+      display: flex;
+      flex-direction: column;
+      gap: 5px;
+    }
     .el-tree {
       overflow: auto;
       flex: 1;