Skip to content

Commit

Permalink
feat: 导入 md 支持资源文件
Browse files Browse the repository at this point in the history
  • Loading branch information
terwer committed Jun 1, 2023
1 parent 531628f commit 33a20c7
Show file tree
Hide file tree
Showing 6 changed files with 577 additions and 208 deletions.
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -36,6 +36,7 @@
"vitest": "^0.31.3"
},
"dependencies": {
"shorthash2": "^1.0.3"
"shorthash2": "^1.0.3",
"zhi-lib-base": "^0.0.2"
}
}
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/index.ts
Expand Up @@ -29,6 +29,7 @@ import KernelApi from "./api/kernel-api"
import { isDev, mediaDir } from "./Constants"
import "./index.styl"
import { initTopbar } from "./topbar"
import { simpleLogger } from "zhi-lib-base"

/**
* 导入插件
Expand All @@ -44,7 +45,7 @@ export default class ImporterPlugin extends Plugin {
constructor(options: { app: App; id: string; name: string; i18n: IObject }) {
super(options)

this.logger = createLogger("index")
this.logger = simpleLogger("index", "importer", isDev)
this.kernelApi = new KernelApi()
}

Expand Down
270 changes: 66 additions & 204 deletions src/lib/ImportForm.svelte
Expand Up @@ -24,12 +24,11 @@
-->

<script lang="ts">
import { showMessage } from "siyuan"
import ImporterPlugin from "../index"
import { removeEmptyLines, removeFootnotes, removeLinks, replaceImagePath } from "../utils/utils"
import { onMount } from "svelte"
import { loadImporterConfig, saveImporterConfig } from "../store/config"
import { isDev, workspaceDir } from "../Constants"
import { showMessage } from "siyuan"
import { onMount } from "svelte"
import { ImportService } from "../service/importService"
export let pluginInstance: ImporterPlugin
export let dialog
Expand All @@ -38,9 +37,64 @@
let notebooks = []
let toNotebookId
let toNotebookName
//用户指南不应该作为可以写入的笔记本
// 用户指南不应该作为可以写入的笔记本
const hiddenNotebook: Set<string> = new Set(["思源笔记用户指南", "SiYuan User Guide"])
let tempCount = 0
const allowedExtensions = ["docx", "epub", "md", "html", "opml"]
// events
const notebookChange = async function () {
// 显示当前选择的名称
const currentNotebook = notebooks.find((n) => n.id === toNotebookId)
toNotebookName = currentNotebook.name
importerConfig = await loadImporterConfig(pluginInstance)
importerConfig.notebook = toNotebookId
await saveImporterConfig(pluginInstance, importerConfig)
pluginInstance.logger.info(`${pluginInstance.i18n.notebookConfigUpdated}=>`, toNotebookId)
}
const cleanTemp = async () => {
const tempPath = `/temp/convert/pandoc`
await pluginInstance.kernelApi.removeFile(`${tempPath}`)
await reloadTempFiles()
showMessage(pluginInstance.i18n.msgTempFileCleaned, 5000, "info")
}
// lifecycle
onMount(async () => {
await reloadTempFiles()
// 加载配置
importerConfig = await loadImporterConfig(pluginInstance)
const res = await pluginInstance.kernelApi.lsNotebooks()
const data = res.data as any
notebooks = data.notebooks ?? []
// 没有必要把所有笔记本都列出来
notebooks = notebooks.filter((notebook) => !notebook.closed && !hiddenNotebook.has(notebook.name))
// 选中,若是没保存,获取第一个
toNotebookId = importerConfig?.notebook ?? notebooks[0].id
const currentNotebook = notebooks.find((n) => n.id === toNotebookId)
toNotebookName = currentNotebook.name
pluginInstance.logger.info(`${pluginInstance.i18n.selected} [${toNotebookName}] toNotebookId=>`, toNotebookId)
})
// utils
const reloadTempFiles = async () => {
const tempPath = `/temp/convert/pandoc`
// 临时文件
const tempFiles = await pluginInstance.kernelApi.readDir(tempPath)
if (tempFiles.code === 0 && tempFiles.data.length > 0) {
tempCount = tempFiles.data.length
}
if (tempFiles.code === 404) {
tempCount = 0
}
}
// =================
// 单文件转换开始
Expand All @@ -63,68 +117,11 @@
// 给个提示,免得用户以为界面是卡主了
showMessage(`${pluginInstance.i18n.msgConverting} ${file.name}...`, 1000, "info")
await doImport(file)
}
const doImport = async function (file: any) {
const fromFilename = file.name
let filename = fromFilename.substring(0, fromFilename.lastIndexOf("."))
// 去除标题多余的空格,包括开始中间以及结尾的空格
filename = filename.replace(/\s+/g, "")
const fromFilePath = `/temp/convert/pandoc/${fromFilename}`
const uploadResult = await pluginInstance.kernelApi.putFile(fromFilePath, file)
if (uploadResult.code !== 0) {
showMessage(`${pluginInstance.i18n.msgFileUploadError}:${uploadResult.msg}`, 7000, "error")
return
}
// 文件转换
let toFilename = `${filename}.md`
let toFilePath = `/temp/convert/pandoc/${toFilename}`
const ext = fromFilename.split(".").pop().toLowerCase()
// 不是 md 才需要转换
if (ext !== "md") {
const convertResult = await pluginInstance.kernelApi.convertPandoc(
"markdown_strict-raw_html",
fromFilename,
toFilename
)
if (convertResult.code !== 0) {
showMessage(`${pluginInstance.i18n.msgFileConvertError}:${convertResult.msg}`, 7000, "error")
return
}
// 读取文件
let mdText = (await pluginInstance.kernelApi.getFile(toFilePath, "text")) ?? ""
if (mdText === "") {
showMessage(pluginInstance.i18n.msgFileConvertEmpty, 7000, "error")
return
}
// 文本处理
// 删除目录中链接
mdText = removeLinks(mdText)
// 去除空行
mdText = removeEmptyLines(mdText)
// 资源路径
mdText = replaceImagePath(mdText)
// 去除脚注
mdText = removeFootnotes(mdText)
await pluginInstance.kernelApi.saveTextData(`${toFilename}`, mdText)
}
// 导入 MD 文档
const localPath = `${workspaceDir}/temp/convert/pandoc/${toFilename}`
const mdResult = await pluginInstance.kernelApi.importStdMd(localPath, toNotebookId, `/`)
if (mdResult.code !== 0) {
showMessage(`${pluginInstance.i18n.msgDocCreateFailed}=>${toFilePath}`, 7000, "error")
}
// 打开笔记本
await pluginInstance.kernelApi.openNotebook(toNotebookId)
showMessage(pluginInstance.i18n.msgImportSuccess, 5000, "info")
// 转换
const toFilePath = await ImportService.uploadAndConvert(pluginInstance, file)
// 导入
await ImportService.singleImport(pluginInstance, toFilePath, toNotebookId)
}
// =================
// 单文件转换结束
Expand All @@ -143,8 +140,6 @@
const result = await window.showDirectoryPicker()
dialog.destroy()
const allowedExtensions = ["docx", "epub", "md", "html", "opml"]
const entries = await result.values()
for await (const entry of entries) {
if (entry.kind === "directory") {
Expand All @@ -162,148 +157,15 @@
// 循环上传并转换
showMessage(`${fileName} ${pluginInstance.i18n.msgConverting}...`, 5000, "info")
const file = await readFile(entry)
await doUploadAndConvert(file)
await ImportService.uploadAndConvert(pluginInstance, file)
}
// 文件夹批量导入
await doBatchImport()
}
const doUploadAndConvert = async (file: any) => {
const fromFilename = file.name
let filename = fromFilename.substring(0, fromFilename.lastIndexOf("."))
// 去除标题多余的空格,包括开始中间以及结尾的空格
filename = filename.replace(/\s+/g, "")
const fromFilePath = `/temp/convert/pandoc/${fromFilename}`
const uploadResult = await pluginInstance.kernelApi.putFile(fromFilePath, file)
if (uploadResult.code !== 0) {
showMessage(`${pluginInstance.i18n.msgFileUploadError}:${uploadResult.msg}`, 7000, "error")
return
}
// 文件转换
let toFilename = `${filename}.md`
let toFilePath = `/temp/convert/pandoc/${toFilename}`
const ext = fromFilename.split(".").pop().toLowerCase()
// 不是 md 才需要转换
if (ext !== "md") {
const convertResult = await pluginInstance.kernelApi.convertPandoc(
"markdown_strict-raw_html",
fromFilename,
toFilename
)
if (convertResult.code !== 0) {
showMessage(`${pluginInstance.i18n.msgFileConvertError}:${convertResult.msg}`, 7000, "error")
return
}
// 读取文件
let mdText = (await pluginInstance.kernelApi.getFile(toFilePath, "text")) ?? ""
if (mdText === "") {
showMessage(pluginInstance.i18n.msgFileConvertEmpty, 7000, "error")
return
}
// 文本处理
// 删除目录中链接
mdText = removeLinks(mdText)
// 去除空行
mdText = removeEmptyLines(mdText)
// 资源路径
mdText = replaceImagePath(mdText)
// 去除脚注
mdText = removeFootnotes(mdText)
await pluginInstance.kernelApi.saveTextData(`${toFilename}`, mdText)
}
}
const doBatchImport = async () => {
// 导入 MD 文档
const localPath = `${workspaceDir}/temp/convert/pandoc`
const mdResult = await pluginInstance.kernelApi.importStdMd(localPath, toNotebookId, `/`)
if (mdResult.code !== 0) {
showMessage(`${pluginInstance.i18n.msgDocCreateFailed}=>${toFilePath}`, 7000, "error")
}
// 打开笔记本
await pluginInstance.kernelApi.openNotebook(toNotebookId)
showMessage(pluginInstance.i18n.msgImportSuccess, 5000, "info")
}
/**
* 读取文件
* @param entry
*/
const readFile = async (entry) => {
const file = await entry.getFile()
const reader = new FileReader()
reader.readAsArrayBuffer(file)
return new Promise((resolve, reject) => {
reader.onload = () => {
const arrayBuffer = reader.result
const fileContent = new Blob([arrayBuffer], { type: file.type })
const fileName = file.name
resolve(new File([fileContent], fileName))
}
reader.onerror = reject
})
// 批量导入
await ImportService.multiImport(pluginInstance, toNotebookId)
}
// =================
// 批量转换结束
// =================
const cleanTemp = async () => {
const tempPath = `/temp/convert/pandoc`
await pluginInstance.kernelApi.removeFile(`${tempPath}`)
await reloadTempFiles()
showMessage(pluginInstance.i18n.msgTempFileCleaned, 5000, "info")
}
const notebookChange = async function () {
// 显示当前选择的名称
const currentNotebook = notebooks.find((n) => n.id === toNotebookId)
toNotebookName = currentNotebook.name
importerConfig = await loadImporterConfig(pluginInstance)
importerConfig.notebook = toNotebookId
await saveImporterConfig(pluginInstance, importerConfig)
pluginInstance.logger.info(`${pluginInstance.i18n.notebookConfigUpdated}=>`, toNotebookId)
}
const reloadTempFiles = async () => {
const tempPath = `/temp/convert/pandoc`
// 临时文件
const tempFiles = await pluginInstance.kernelApi.readDir(tempPath)
if (tempFiles.code === 0 && tempFiles.data.length > 0) {
tempCount = tempFiles.data.length
}
if (tempFiles.code === 404) {
tempCount = 0
}
}
onMount(async () => {
await reloadTempFiles()
// 加载配置
importerConfig = await loadImporterConfig(pluginInstance)
const res = await pluginInstance.kernelApi.lsNotebooks()
const data = res.data as any
notebooks = data.notebooks ?? []
// 没有必要把所有笔记本都列出来
notebooks = notebooks.filter((notebook) => !notebook.closed && !hiddenNotebook.has(notebook.name))
// 选中,若是没保存,获取第一个
toNotebookId = importerConfig?.notebook ?? notebooks[0].id
const currentNotebook = notebooks.find((n) => n.id === toNotebookId)
toNotebookName = currentNotebook.name
pluginInstance.logger.info(`${pluginInstance.i18n.selected} [${toNotebookName}] toNotebookId=>`, toNotebookId)
})
</script>

<div class="b3-dialog__content importer-form-container">
Expand Down

0 comments on commit 33a20c7

Please sign in to comment.