Browse Source

feat: pages

Carson 10 months ago
parent
commit
b876359fa7

+ 2 - 1
.gitignore

@@ -16,4 +16,5 @@ node_modules
 pnpm-global
 TODOs.md
 *.timestamp-*.mjs
-.env
+.env
+pages/

+ 2 - 0
.vitepress/config.mts

@@ -75,6 +75,8 @@ await Promise.all(
 );
 
 // 构建sideBar数据
+// TODO
+const sideBarSortMap = {}
 let { rootSideBar, zhHKSideBar } = buildSideBar(contents);
 import util from "util";
 console.log(

+ 176 - 106
components/Edit/index.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { ref, onMounted, computed, nextTick } from "vue";
-import _ from 'lodash'
-import {v4 as uuid4} from 'uuid'
+import _ from "lodash";
+import { v4 as uuid4 } from "uuid";
 import {
   S3Client,
   PutObjectCommand,
@@ -10,10 +10,7 @@ import {
   DeleteObjectsCommand,
   CopyObjectCommand,
 } from "@aws-sdk/client-s3";
-import {
-  Plus,
-  Refresh,
-} from "@element-plus/icons-vue";
+import { Plus, Refresh } from "@element-plus/icons-vue";
 import Node from "./Node.vue";
 import AppendModal from "./AppendModal.vue";
 import type { TreeData } from "./Tree";
@@ -21,12 +18,12 @@ import { ElNotification, type ElTree } from "element-plus";
 import { MdEditor } from "md-editor-v3";
 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'
+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'
+} from "element-plus/es/components/tree/src/tree.type";
 import path from "path-browserify";
 
 const s3 = new S3Client({
@@ -37,42 +34,77 @@ const s3 = new S3Client({
   region: import.meta.env.VITE_AWS_S3_REGION,
 });
 
+const SIDEBAR_SORTED_MAP_KEY = "SIDEBAR_SORTED_MAP.json";
+
 const tree$ = ref<InstanceType<typeof ElTree>>();
 
-const langSelect = ref<'zh-CN'|'zh-HK'|'en-US'>('zh-CN')
+const langSelect = ref<"zh-CN" | "zh-HK" | "en-US">("zh-CN");
 const langKeyPrefix = computed(() => {
   return {
-    'zh-CN': 'docs/',
-    'zh-HK': 'zh-HK/docs/',
-    'en-US': 'en-US/docs/',
-  }[langSelect.value]
-})
-const langDataSource = ref<Partial<Record<'zh-CN'|'zh-HK'|'en-US', TreeData[]>>>({})
+    "zh-CN": "docs/",
+    "zh-HK": "zh-HK/docs/",
+    "en-US": "en-US/docs/",
+  }[langSelect.value];
+});
+const langDataSource = ref<
+  Partial<Record<"zh-CN" | "zh-HK" | "en-US", TreeData[]>>
+>({});
 const langSelectedDataSource = computed(() => {
-  return langDataSource.value[langSelect.value]
-})
+  return langDataSource.value[langSelect.value];
+});
 const dataSource = ref<unknown[]>([]);
 const sideLoading = ref(false);
 const loadS3DocsListObjects = async () => {
-  const command = new ListObjectsCommand({ Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET });
+  const command = new ListObjectsCommand({
+    Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
+  });
   const result = await s3.send(command);
   return result.Contents!;
 };
+const loadS3SideBarSort = async (lang) => {
+  try {
+    const command = new GetObjectCommand({
+      Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
+      Key: `${lang}::${SIDEBAR_SORTED_MAP_KEY}`,
+      ResponseCacheControl: "no-cache",
+    });
+    const file = await s3.send(command);
+    return JSON.parse(await file.Body?.transformToString()!);
+  } catch (e) {
+    console.warn(e);
+  }
+  return {};
+};
 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 : {} )
-    }));
-  })
+  const allLangs: ("zh-CN" | "zh-HK" | "en-US")[] = ["zh-CN", "zh-HK", "en-US"];
+  await Promise.all(
+    allLangs.map(async (lang: "zh-CN" | "zh-HK" | "en-US") => {
+      const prefix = {
+        "zh-CN": "docs/",
+        "zh-HK": "zh-HK/docs/",
+        "en-US": "en-US/docs/",
+      }[lang];
+      const sideBarSortMap = await loadS3SideBarSort(lang);
+      const filtered = dataSource.value
+        .filter((cont) => cont.Key.startsWith(prefix))
+        .map((cont) => ({
+          ...cont,
+          Key: cont.Key.replace(new RegExp(`^${prefix}`), ""),
+        }))
+        .sort((a, b) => sideBarSortMap[a.Key] - sideBarSortMap[b.Key]);
+      // TODO sort
+      langDataSource.value[lang] = s3ContentsToTree(
+        filtered,
+        {},
+        (r, label, i, a, thisContent) => ({
+          isDir: a.length !== i + 1,
+          ...(a.length === i + 1 ? thisContent : {}),
+        })
+      );
+    })
+  );
 
   sideLoading.value = false;
 };
@@ -102,32 +134,43 @@ const onNodeClick = async (data: TreeData, node) => {
 const text = ref("");
 const textLoading = ref(false);
 
-const onUploadImg = async (files: File[], callback: (urls: string[] | { url: string; alt: string; title: string }[]) => void) => {
-  let result = []
+const onUploadImg = async (
+  files: File[],
+  callback: (
+    urls: string[] | { url: string; alt: string; title: string }[]
+  ) => void
+) => {
+  let result = [];
   for (const file of files) {
     try {
-      const key = `${uuid4()}::${file.name}`
+      const key = `${uuid4()}::${file.name}`;
       const command = new PutObjectCommand({
         Bucket: import.meta.env.DOCS_MEDIA_BUCKET,
         Key: key,
         Body: file,
-        ACL: 'public-read',
+        ACL: "public-read",
       });
       const res = await s3.send(command);
-      result.push({url: `https://${import.meta.env.DOCS_MEDIA_BUCKET}.s3.amazonaws.com/${key}`, alt: file.name, title: file.name})
+      result.push({
+        url: `https://${
+          import.meta.env.DOCS_MEDIA_BUCKET
+        }.s3.amazonaws.com/${key}`,
+        alt: file.name,
+        title: file.name,
+      });
     } catch (e) {
-      console.error(e)
-      ElNotification.error(`${file.name} 上传失败`)
+      console.error(e);
+      ElNotification.error(`${file.name} 上传失败`);
     }
   }
-  callback(result)
+  callback(result);
 };
 const onSave = async () => {
   if (!currentOpenData.value) {
-    ElNotification.info('请先选择文件')
+    ElNotification.info("请先选择文件");
     return;
   }
-  textLoading.value = true
+  textLoading.value = true;
   try {
     const command = new PutObjectCommand({
       Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
@@ -135,36 +178,37 @@ const onSave = async () => {
       Body: text.value,
     });
     await s3.send(command);
-    ElNotification.success(`${currentOpenData.value?.key} 保存成功`)
+    ElNotification.success(`${currentOpenData.value?.key} 保存成功`);
   } catch (e) {
-    console.error(e)
-    ElNotification.error(`${currentOpenData.value?.key} 保存失败`)
+    console.error(e);
+    ElNotification.error(`${currentOpenData.value?.key} 保存失败`);
   } finally {
-    textLoading.value = false
+    textLoading.value = false;
   }
 };
 
 let resolveAppend: Function | undefined;
 let rejectAppend: Function | undefined;
 const showAppendModal = ref(false);
-const appendLoading = ref(false)
-const appendContextData = ref()
+const appendLoading = ref(false);
+const appendContextData = ref();
 const onAppend = async (data: TreeData) => {
   const modalConfirmPromise = new Promise((resolve, reject) => {
     resolveAppend = resolve;
     rejectAppend = reject;
   });
-  appendContextData.value = data
+  appendContextData.value = data;
   showAppendModal.value = true;
-  let filename, isDir
+  let filename, isDir;
   try {
-    ( { filename, isDir }  = await modalConfirmPromise);
+    ({ filename, isDir } = await modalConfirmPromise);
   } catch (e) {
-    return
+    return;
   }
   try {
-    appendLoading.value = true
-    const key = `${langKeyPrefix.value}${data.key ? `${data.key}/` : ''}${filename}`
+    appendLoading.value = true;
+    // const key = `${langKeyPrefix.value}${data.key ? `${data.key}/` : ''}${filename}`
+    const key = path.join(langKeyPrefix.value, data.key ?? "", filename);
     if (!isDir) {
       const command = new PutObjectCommand({
         Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
@@ -174,7 +218,7 @@ const onAppend = async (data: TreeData) => {
       await s3.send(command);
     }
     const newChild: TreeData = {
-      key: `${data.key ? `${data.key}/` : ''}${filename}`,
+      key: `${data.key ? `${data.key}/` : ""}${filename}`,
       label: filename,
       children: [],
       isDir,
@@ -183,84 +227,103 @@ const onAppend = async (data: TreeData) => {
       data.children = [];
     }
     data.children.push(newChild);
-    // if (!data.key) {
-    //   loadSideBar()
-    // }
   } catch (e) {
     console.error(e);
-    ElNotification.error('添加失败')
+    ElNotification.error("添加失败");
   } finally {
-    appendLoading.value = false
+    appendLoading.value = false;
     showAppendModal.value = false;
   }
+  if (!isDir) {
+    saveSideBarSort();
+  }
 };
 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);
 
-  sideLoading.value = true
+  sideLoading.value = true;
   try {
-    const leafDatas = getTreeFlatten(data)
+    const leafDatas = getTreeFlatten(data);
     const command = new DeleteObjectsCommand({
       Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
       Delete: {
-        Objects: leafDatas.map(d => ({Key: `${langKeyPrefix.value}${d.key}`})),
+        Objects: leafDatas.map((d) => ({
+          Key: `${langKeyPrefix.value}${d.key}`,
+        })),
         Quiet: false,
-      }
-    })
-    await s3.send(command)
+      },
+    });
+    await s3.send(command);
     children.splice(index, 1);
-    // if (parent.level === 0) {
-    //   nextTick(loadSideBar)
-    // }
   } catch (e) {
-    console.error(e)
-    ElNotification.error('删除失败')
+    console.error(e);
+    ElNotification.error("删除失败");
   } finally {
-    sideLoading.value = false
+    sideLoading.value = false;
   }
 };
-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 saveSideBarSort = async () => {
+  sideLoading.value = true;
+  const sortedNodes = getTreeFlatten(
+    { children: tree$.value!.data } as TreeData,
+    "preorder",
+    "all"
+  );
+  const sortedKeyMap = sortedNodes.reduce(
+    (a, v, i) => ({ ...a, [path.join(...v.__path__!)]: i }),
+    {}
+  );
+  const command = new PutObjectCommand({
+    Bucket: import.meta.env.VITE_DOCS_LIST_BUCKET,
+    Key: `${langSelect.value}::${SIDEBAR_SORTED_MAP_KEY}`,
+    Body: JSON.stringify(sortedKeyMap),
+  });
+  await s3.send(command);
+  sideLoading.value = false;
+};
 const onDragEnd = async (
   draggingNode: ETNode,
   dropNode: ETNode,
   dropType: NodeDropType,
   ev: DragEvents
 ) => {
-  if (dropType === 'none') {
-    return
+  if (dropType === "none") {
+    return;
   }
-  sideLoading.value = true
-  const copyTargetPath = path.join(langKeyPrefix.value, dropType === 'inner' ? dropNode.data.key : dropNode.parent.data.key ?? '')
+  sideLoading.value = true;
 
-  const copySourceNodes = getTreeFlatten(draggingNode.data)
-  const waitForDelete = []
+  const copySourceNodes = getTreeFlatten(draggingNode.data);
+  const waitForDelete = [];
   for (const sourceNode of copySourceNodes) {
     if (sourceNode.isDir) {
-      continue
+      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) )
+    const copySource = path.join(langKeyPrefix.value, sourceNode.key);
+    const copyTarget = path.join(
+      langKeyPrefix.value,
+      dropType === "inner" ? dropNode.data.key : dropNode.parent.data.key ?? "",
+      path.basename(draggingNode.data.key),
+      path.relative(draggingNode.data.key, sourceNode.key)
+    );
     if (copySource === copyTarget) {
-      continue
+      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 })
+        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 } 复制失败`)
+      console.error(e);
+      console.warn(`${copySource} 复制失败`);
     }
   }
   if (waitForDelete.length) {
@@ -269,20 +332,20 @@ const onDragEnd = async (
       Delete: {
         Objects: waitForDelete,
         Quiet: false,
-      }
-    })
-    await s3.send(command)
+      },
+    });
+    await s3.send(command);
   }
 
-  await loadSideBar()
-  sideLoading.value = false
-  nextTick(() => {
-    saveSideBarSort()
-  })
-}
+  sideLoading.value = false;
+  nextTick(async () => {
+    await saveSideBarSort();
+    await loadSideBar();
+  });
+};
 const allowDrop = (draggingNode: Node, dropNode: Node, type: AllowDropType) => {
-   return !( !dropNode.data.isDir && type === 'inner' )
-}
+  return !(!dropNode.data.isDir && type === "inner");
+};
 </script>
 <template>
   <div class="md-container">
@@ -295,13 +358,20 @@ const allowDrop = (draggingNode: Node, dropNode: Node, type: AllowDropType) => {
         </el-radio-group>
         <el-button-group>
           <el-button
-            @click="onAppend({ key: '', label: '', children: langSelectedDataSource })"
+            @click="
+              onAppend({ key: '', label: '', children: langSelectedDataSource })
+            "
             type="primary"
             text
             :icon="Plus"
           >
           </el-button>
-          <el-button @click="loadSideBar" type="info" text :icon="Refresh"></el-button>
+          <el-button
+            @click="loadSideBar"
+            type="info"
+            text
+            :icon="Refresh"
+          ></el-button>
         </el-button-group>
       </div>
       <el-divider />

+ 0 - 0
pages/docs/dir1/dir1-file1.md


+ 0 - 0
pages/docs/dir1/dir1-file2.md


+ 0 - 0
pages/docs/dir1/f1-1


+ 0 - 0
pages/docs/dir2/dir2-1/f2-1-1


+ 0 - 0
pages/docs/dir2/dir2-1/f2-1-2


+ 0 - 0
pages/docs/dir2/f2-1


+ 0 - 45
pages/docs/index.md

@@ -1,45 +0,0 @@
-# CocoBlockly X 在线帮助文档
-
-
-CocoBlockly X是一个在线编程平台,集成图形化编程与Python代码编程,工具界面友好,功能强大。结合人工智能套件模块,CocoBlockly X可进行程序编写及上传,从而学习人工智能、物联网及Python基础。
-
-###### 1. AI模块编程界面
-
-<img src="../../media/关于CocoBlockly X/关于CocoBlockly X(1).png" width="650">
-
-
-###### 2. IoT模块编程界面
-
-<img src="../../media/关于CocoBlockly X/关于CocoBlockly X(2).png" width="650"/>
-
----
-
-## 教程索引
-
-- [关于CocoBlockly X](index)
-- [常见问题解答](faq)
-- [开始使用CocoBlockly X](start-using-cocoblockly-x)
-- 电子模块基本教学
-	- [整体模块预览](docs/preview)
-	- [使用AI模块](md/使用AI模块)
-	- [使用IoT模块](md/使用IoT模块)
-	- [使用屏幕模块](md/使用屏幕模块)
-	- [使用摄像头](md/使用摄像头)
-	- [使用LED灯屏模块](md/使用LED灯屏模块)
-	- [使用电机驱动模块](md/使用电机驱动模块)
-	- [使用扩展转接模块](md/使用扩展转接模块)
-	- [使用游戏手柄模块](md/使用游戏手柄模块)
-	- [使用多线程](md/使用多线程)
-- 其他使用帮助
-	- [固件更新与烧录](md/固件更新与烧录)
-	- [SD卡文件更新(针对AI模块)](md/SD)
-	- [图形处理](md/图形处理)
-	- [视频处理](md/视频处理)
-	- [音频处理](md/音频处理)
-	- [AI模型](md/AI模型)
-	- [IoT蓝牙](md/IoT蓝牙)
-	- [机器狗](md/机器狗)
-	- [第三方主控模块通信-盛思](md/盛思)
-	- [AI模块案例说明](help-docs/md/AI)
-	- [模块使用介绍视频](md/模块使用介绍视频)
-- [版本更新日志](changelog)

+ 0 - 2
pages/docs/test.md

@@ -1,2 +0,0 @@
-### test
-![close-btn.png](https://cococlass-help-docs-medias.s3.amazonaws.com/77f06475-9352-4364-a763-7c4315eda69f::close-btn.png 'close-btn.png')

+ 0 - 135
pages/docs/常见问题解答.md

@@ -1,135 +0,0 @@
-# 常见问题解答
-
-- [关于模块使用](/md/faq?id=关于模块使用)
-- [关于平台使用](/md/faq?id=关于平台使用)
-- [关于常见报错](/md/faq?id=关于常见报错)
-
-## 关于模块使用
-
-**1.AI模块与屏幕模块的组合在连接电脑或电源后,屏幕一直黑屏。**
-
-A:如果您无TF 卡读卡器,请在CocoBlockly X上按下图步骤上传程序,尝试解决问题。
-
-<img src="@/media/常见问题解答/常见问题解答(1).png" width="650"/><br>
-
-B:如果您有TF 卡读卡器,请您用 TF 卡读卡器检查一下 AI 模块中 TF 卡,确认其中是否有包含main.py这个文件,如果没有的话,可以从<a href="/download/main.py">这里</a>下载后放入 TF 卡便可以了。
-
-**2.我的AI模块显示以下效果,应该怎么办?**
-
-<img src="@/media/常见问题解答/AI_lack_sd.jpg" width="365"/><br>
-
-A:首先检查AI模块上是否已经插入SD卡。
-
-B:可能是运输过程中SD卡松动导致的,这种情况下需要重新插拔一下SD卡即可。
-
-**3.AI模块与屏幕模块的组合在连接电脑或电源后,屏幕红屏显示“Welcome to MaixPy”。**
-
-<img src="../../media/常见问题解答/w4.png" width="365"/><br>
-
-A:插拔电源线重新连接看能否正常启动。
-
-B:格式化SD卡,重新拷贝预置文件。
-
-C:更换SD卡,重新拷贝预置文件再试试。
-
-
-## 关于平台使用
-
-### 1. **电脑Windows 7系统,使用Chrome浏览器编程,CocoBlockly X上传区一直显示“正在连接模块”。**
-
-  描述:Windows 7系统的更新补丁没有安装,可以通过下载安装补丁解决问题。
-
-  解决办法:补丁下载地址:[32位](https://cocorobo.cn/downloads/Windows6.1-KB2999226-x86.msu) [64位](https://cocorobo.cn/downloads/Windows6.1-KB2999226-x64.msu)
-
-
-
-## 关于常见报错
-
-### 1. 关键字:Timeout
-
-描述1:模块还在连接中/重启中。
-
-解决办法1:等待模块连接/重启完成后再上传。如果使用的是AI模块与屏幕模块的组合,可以观察屏幕模块,当AI模块重启时屏幕会短暂黑屏刷新一次,等待黑屏刷新后重新上传程序即可解决问题。
-
-描述2:AI模块SD卡内“user_lastest_code.py”文件编辑权限被锁定。
-
-解决办法2:取出模块内SD卡并连接至电脑,将SD卡内根目录下“user_lastest_code.py”文件删除,重新将SD卡安装至AI模块并上传程序即可解决问题。
-
-### 2. 关键字:name 'xxx' isn't defined
-
-描述:程序中有变量没有被定义。
-
-解决办法:检查程序并修改即可解决问题。
-
-### 3. 关键字:Sipeed_M1 with kendryte-k210
-
-描述:摄像头没有插好/摄像头损坏。
-
-解决办法:重新插好摄像头/更换摄像头即可解决问题。
-
-### 4. 关键字:could not open port 'xxx': PermissionError
-
-描述:USB接口被占用。
-
-解决办法:重新插拔模块或重启CocoBlockly X Uploader即可解决问题。
-
-### 5. 关键字:'/private/tmp/PKInstallSandbox.rELsyx/tmp/python'
-
-描述:Mac系统出现此报错的原因是无法访问Python,一般发生在用户安装的时候没有输入密码给软件绝对权限安装。
-
-解决办法:重启CocoBlockly X Uploader即可。
-
-### 6. 关键字:file “&lt;stadin&gt;” line xx in &lt;module&gt;
-
-描述:代码运行错误。
-
-解决办法:固件版本过期,因此部分函数不支持,需要更新固件,更新方式见[AI模块固件更新](md/固件更新与烧录?id=AI模块固件)及[IoT模块固件更新](md/固件更新与烧录?id=IoT模块固件);或程序错误,尝试检查程序结构。
-
-### 7. 上传一直卡在77%的问题
-
-描述:Windows电脑有多个版本的Python。
-
-解决办法:卸载电脑所有的Python版本,然后重新安装Uploader。卸载方式如下图(以Windows系统为例):
-
-(1)点击控制面板;
-
-​         <img src="../../media/常见问题解答/w1.png" width="400"/>
-
-(2)点击程序里面的卸载程序;
-
-​         <img src="../../media/常见问题解答/w2.png"/>
-
-(3)找到所有的Python,全部右键卸载。
-
-​         <img src="../../media/常见问题解答/w3.png"/>
-
-
-
-### 8. 关键字:[Errno 5] EIO(AI)或[Errno 19] ENODEV (IOT)
-描述:编程时使用了电机驱动、MLX90614传感器、RTC模块等设备的编程积木,但实际设备未连接模块(物理线路)或者连接故障(接线错误、接触不良)。
-
-解决办法:检查对应设备的电路接线。
-
-### 10. 关键字:[MAIXPY] no senor 
-
-描述:没有检测到外部传感器。
-
-解决办法:摄像头松动,尝试重新连接摄像头。
-
-### 11. 关键词:IoT模块-【ERRNO 19】No Such Device Error
-
-描述:需要更新IoT固件。
-
-解决办法:更新IoT固件至最新版本,更新方式见[IoT模块固件更新](md/固件更新与烧录?id=IoT模块固件)。
-
-### 12. 关键词:free XX heap memory
-
-描述:程序中调用的kmodel文件路径非法或SD卡中不存在此文件。
-
-解决办法:检查SD卡中的kmodel文件是否存在,或与程序中路径是否一致。
-
-### 13. 关键词:only support kmodel V3/V4 now
-
-描述:AI模块无法使用.emodel文件。
-
-解决办法:联系CocoRobo工作人员[support@cocorobo.cc](mailto:support@cocorobo.cc)。

+ 0 - 1
pages/en-US/docs/index.md

@@ -1 +0,0 @@
-qqa

+ 10 - 0
pages/en-US/index.md

@@ -0,0 +1,10 @@
+---
+# https://vitepress.dev/reference/default-theme-home-page
+layout: home
+---
+
+<script setup lang="ts">
+import HomeContent from '@/components/HomeContent.vue'
+</script>
+
+<HomeContent />

+ 0 - 0
pages/zh-HK/docs/dir1/f1-1


+ 0 - 42
pages/zh-HK/docs/index.md

@@ -1,42 +0,0 @@
-# 什麽是 CocoBlockly X 
-
-
-CocoBlockly X是一個程式編寫平臺,集成了視覺化編程與Python代碼編程。結合人工智能套件模組,CocoBlockly X可以進行程式編寫與運行,從而學習人工智慧、物聯網、Python基礎。
-
-###### 1. AI 模組編程界面
-
-<img src="../../../media/关于CocoBlockly X/关于CocoBlockly X(1).png" width="650">
-
-
-###### 2. IoT 模組編程界面
-
-<img src="../../../media/关于CocoBlockly X/关于CocoBlockly X(2).png" width="650"/>
-
----
-
-## 教程索引
-
-- [什麽是CocoBlockly X](index)
-- [常見問題解答](faq)
-- [開始使用CocoBlockly X](start-using-cocoblockly-x)
-- 電子模組基本教學
-	- [整體模組預覽](md/preview)
-	- [使用AI模組](md/使用AI模組)
-	- [使用IoT模組](md/使用IoT模組)
-	- [使用螢幕模組](md/使用螢幕模組)
-	- [使用相機模組](md/使用相機)
-	- [使用LED燈屏模組](md/使用LED燈屏模組)
-	- [使用馬達驅動模組](md/使用馬達驅動模組)
-	- [使用擴展轉接模組](md/使用擴展轉接模組)
-	- [使用遊戲手柄模組](md/使用遊戲手柄模組)
-	- [使用多線程](md/使用多線程)
-- 其他使用幫助
-	- [固件更新與燒錄](md/固件更新與燒錄)
-	- [SD卡文件更新(AI模組)](md/SD)
-	- [圖形處理](md/圖形處理)
-	- [視訊處理](md/視訊處理)
-	- [音訊處理](md/音訊處理)
-	- [AI模型](md/AI模型)
-	- [IoT藍牙](md/IoT藍牙)
-	- [機器狗](md/機器狗)
-- [版本更新日誌](changelog)

+ 15 - 11
utils/s3Helper.ts

@@ -39,26 +39,30 @@ export const s3ContentsToTree = (
 export const trimTreeForSideBar = (tree) => {};
 
 type TreeNodeLike = {
+  label?: string;
   children?: TreeNodeLike[];
-};
+  __path__?: string[];
+} & { [K: string]: unknown };
 
 export const getTreeFlatten = <T extends TreeNodeLike>(
   treeNode: T,
   order: "preorder" | "postorder" = "preorder",
-  mode: "all" | "leaf" = "leaf"
-): T[] => {
+  mode: "all" | "leaf" = "leaf",
+  path: string[] = []
+): (T & TreeNodeLike)[] => {
+  treeNode.__path__ = [...path, treeNode.label ?? ""];
   if (treeNode.children?.length) {
     const result = _.flatMap<T, T>(treeNode.children as T[], (node) =>
-      getTreeFlatten(node, order, mode)
-    )
-    if (mode === 'all') {
-      if (order === 'preorder') {
-        result.unshift(treeNode)
-      } else if (order === 'postorder') {
-        result.push(treeNode)
+      getTreeFlatten(node, order, mode, treeNode.__path__)
+    );
+    if (mode === "all") {
+      if (order === "preorder") {
+        result.unshift(treeNode);
+      } else if (order === "postorder") {
+        result.push(treeNode);
       }
     }
-    return result
+    return result;
   }
   return [treeNode];
 };