Skip to content

Commit 33a20c7

Browse files
committedJun 1, 2023
feat: 导入 md 支持资源文件
·
v1.8.1v1.6.0
1 parent 531628f commit 33a20c7

File tree

6 files changed

+577
-208
lines changed

6 files changed

+577
-208
lines changed
 

‎package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"vitest": "^0.31.3"
3737
},
3838
"dependencies": {
39-
"shorthash2": "^1.0.3"
39+
"shorthash2": "^1.0.3",
40+
"zhi-lib-base": "^0.0.2"
4041
}
4142
}

‎pnpm-lock.yaml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import KernelApi from "./api/kernel-api"
2929
import { isDev, mediaDir } from "./Constants"
3030
import "./index.styl"
3131
import { initTopbar } from "./topbar"
32+
import { simpleLogger } from "zhi-lib-base"
3233

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

47-
this.logger = createLogger("index")
48+
this.logger = simpleLogger("index", "importer", isDev)
4849
this.kernelApi = new KernelApi()
4950
}
5051

‎src/lib/ImportForm.svelte

Lines changed: 66 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@
2424
-->
2525

2626
<script lang="ts">
27-
import { showMessage } from "siyuan"
2827
import ImporterPlugin from "../index"
29-
import { removeEmptyLines, removeFootnotes, removeLinks, replaceImagePath } from "../utils/utils"
30-
import { onMount } from "svelte"
3128
import { loadImporterConfig, saveImporterConfig } from "../store/config"
32-
import { isDev, workspaceDir } from "../Constants"
29+
import { showMessage } from "siyuan"
30+
import { onMount } from "svelte"
31+
import { ImportService } from "../service/importService"
3332
3433
export let pluginInstance: ImporterPlugin
3534
export let dialog
@@ -38,9 +37,64 @@
3837
let notebooks = []
3938
let toNotebookId
4039
let toNotebookName
41-
//用户指南不应该作为可以写入的笔记本
40+
// 用户指南不应该作为可以写入的笔记本
4241
const hiddenNotebook: Set<string> = new Set(["思源笔记用户指南", "SiYuan User Guide"])
4342
let tempCount = 0
43+
const allowedExtensions = ["docx", "epub", "md", "html", "opml"]
44+
45+
// events
46+
const notebookChange = async function () {
47+
// 显示当前选择的名称
48+
const currentNotebook = notebooks.find((n) => n.id === toNotebookId)
49+
toNotebookName = currentNotebook.name
50+
51+
importerConfig = await loadImporterConfig(pluginInstance)
52+
importerConfig.notebook = toNotebookId
53+
54+
await saveImporterConfig(pluginInstance, importerConfig)
55+
pluginInstance.logger.info(`${pluginInstance.i18n.notebookConfigUpdated}=>`, toNotebookId)
56+
}
57+
58+
const cleanTemp = async () => {
59+
const tempPath = `/temp/convert/pandoc`
60+
await pluginInstance.kernelApi.removeFile(`${tempPath}`)
61+
await reloadTempFiles()
62+
63+
showMessage(pluginInstance.i18n.msgTempFileCleaned, 5000, "info")
64+
}
65+
66+
// lifecycle
67+
onMount(async () => {
68+
await reloadTempFiles()
69+
70+
// 加载配置
71+
importerConfig = await loadImporterConfig(pluginInstance)
72+
73+
const res = await pluginInstance.kernelApi.lsNotebooks()
74+
const data = res.data as any
75+
notebooks = data.notebooks ?? []
76+
// 没有必要把所有笔记本都列出来
77+
notebooks = notebooks.filter((notebook) => !notebook.closed && !hiddenNotebook.has(notebook.name))
78+
// 选中,若是没保存,获取第一个
79+
toNotebookId = importerConfig?.notebook ?? notebooks[0].id
80+
const currentNotebook = notebooks.find((n) => n.id === toNotebookId)
81+
toNotebookName = currentNotebook.name
82+
83+
pluginInstance.logger.info(`${pluginInstance.i18n.selected} [${toNotebookName}] toNotebookId=>`, toNotebookId)
84+
})
85+
86+
// utils
87+
const reloadTempFiles = async () => {
88+
const tempPath = `/temp/convert/pandoc`
89+
// 临时文件
90+
const tempFiles = await pluginInstance.kernelApi.readDir(tempPath)
91+
if (tempFiles.code === 0 && tempFiles.data.length > 0) {
92+
tempCount = tempFiles.data.length
93+
}
94+
if (tempFiles.code === 404) {
95+
tempCount = 0
96+
}
97+
}
4498
4599
// =================
46100
// 单文件转换开始
@@ -63,68 +117,11 @@
63117
64118
// 给个提示,免得用户以为界面是卡主了
65119
showMessage(`${pluginInstance.i18n.msgConverting} ${file.name}...`, 1000, "info")
66-
await doImport(file)
67-
}
68-
69-
const doImport = async function (file: any) {
70-
const fromFilename = file.name
71-
let filename = fromFilename.substring(0, fromFilename.lastIndexOf("."))
72-
// 去除标题多余的空格,包括开始中间以及结尾的空格
73-
filename = filename.replace(/\s+/g, "")
74-
75-
const fromFilePath = `/temp/convert/pandoc/${fromFilename}`
76-
const uploadResult = await pluginInstance.kernelApi.putFile(fromFilePath, file)
77-
if (uploadResult.code !== 0) {
78-
showMessage(`${pluginInstance.i18n.msgFileUploadError}:${uploadResult.msg}`, 7000, "error")
79-
return
80-
}
81-
82-
// 文件转换
83-
let toFilename = `${filename}.md`
84-
let toFilePath = `/temp/convert/pandoc/${toFilename}`
85-
const ext = fromFilename.split(".").pop().toLowerCase()
86-
// 不是 md 才需要转换
87-
if (ext !== "md") {
88-
const convertResult = await pluginInstance.kernelApi.convertPandoc(
89-
"markdown_strict-raw_html",
90-
fromFilename,
91-
toFilename
92-
)
93-
if (convertResult.code !== 0) {
94-
showMessage(`${pluginInstance.i18n.msgFileConvertError}:${convertResult.msg}`, 7000, "error")
95-
return
96-
}
97120
98-
// 读取文件
99-
let mdText = (await pluginInstance.kernelApi.getFile(toFilePath, "text")) ?? ""
100-
if (mdText === "") {
101-
showMessage(pluginInstance.i18n.msgFileConvertEmpty, 7000, "error")
102-
return
103-
}
104-
105-
// 文本处理
106-
// 删除目录中链接
107-
mdText = removeLinks(mdText)
108-
// 去除空行
109-
mdText = removeEmptyLines(mdText)
110-
// 资源路径
111-
mdText = replaceImagePath(mdText)
112-
// 去除脚注
113-
mdText = removeFootnotes(mdText)
114-
await pluginInstance.kernelApi.saveTextData(`${toFilename}`, mdText)
115-
}
116-
117-
// 导入 MD 文档
118-
const localPath = `${workspaceDir}/temp/convert/pandoc/${toFilename}`
119-
const mdResult = await pluginInstance.kernelApi.importStdMd(localPath, toNotebookId, `/`)
120-
if (mdResult.code !== 0) {
121-
showMessage(`${pluginInstance.i18n.msgDocCreateFailed}=>${toFilePath}`, 7000, "error")
122-
}
123-
124-
// 打开笔记本
125-
await pluginInstance.kernelApi.openNotebook(toNotebookId)
126-
127-
showMessage(pluginInstance.i18n.msgImportSuccess, 5000, "info")
121+
// 转换
122+
const toFilePath = await ImportService.uploadAndConvert(pluginInstance, file)
123+
// 导入
124+
await ImportService.singleImport(pluginInstance, toFilePath, toNotebookId)
128125
}
129126
// =================
130127
// 单文件转换结束
@@ -143,8 +140,6 @@
143140
const result = await window.showDirectoryPicker()
144141
dialog.destroy()
145142
146-
const allowedExtensions = ["docx", "epub", "md", "html", "opml"]
147-
148143
const entries = await result.values()
149144
for await (const entry of entries) {
150145
if (entry.kind === "directory") {
@@ -162,148 +157,15 @@
162157
// 循环上传并转换
163158
showMessage(`${fileName} ${pluginInstance.i18n.msgConverting}...`, 5000, "info")
164159
const file = await readFile(entry)
165-
await doUploadAndConvert(file)
160+
await ImportService.uploadAndConvert(pluginInstance, file)
166161
}
167162
168-
// 文件夹批量导入
169-
await doBatchImport()
170-
}
171-
172-
const doUploadAndConvert = async (file: any) => {
173-
const fromFilename = file.name
174-
let filename = fromFilename.substring(0, fromFilename.lastIndexOf("."))
175-
// 去除标题多余的空格,包括开始中间以及结尾的空格
176-
filename = filename.replace(/\s+/g, "")
177-
178-
const fromFilePath = `/temp/convert/pandoc/${fromFilename}`
179-
const uploadResult = await pluginInstance.kernelApi.putFile(fromFilePath, file)
180-
if (uploadResult.code !== 0) {
181-
showMessage(`${pluginInstance.i18n.msgFileUploadError}:${uploadResult.msg}`, 7000, "error")
182-
return
183-
}
184-
185-
// 文件转换
186-
let toFilename = `${filename}.md`
187-
let toFilePath = `/temp/convert/pandoc/${toFilename}`
188-
const ext = fromFilename.split(".").pop().toLowerCase()
189-
// 不是 md 才需要转换
190-
if (ext !== "md") {
191-
const convertResult = await pluginInstance.kernelApi.convertPandoc(
192-
"markdown_strict-raw_html",
193-
fromFilename,
194-
toFilename
195-
)
196-
if (convertResult.code !== 0) {
197-
showMessage(`${pluginInstance.i18n.msgFileConvertError}:${convertResult.msg}`, 7000, "error")
198-
return
199-
}
200-
201-
// 读取文件
202-
let mdText = (await pluginInstance.kernelApi.getFile(toFilePath, "text")) ?? ""
203-
if (mdText === "") {
204-
showMessage(pluginInstance.i18n.msgFileConvertEmpty, 7000, "error")
205-
return
206-
}
207-
208-
// 文本处理
209-
// 删除目录中链接
210-
mdText = removeLinks(mdText)
211-
// 去除空行
212-
mdText = removeEmptyLines(mdText)
213-
// 资源路径
214-
mdText = replaceImagePath(mdText)
215-
// 去除脚注
216-
mdText = removeFootnotes(mdText)
217-
await pluginInstance.kernelApi.saveTextData(`${toFilename}`, mdText)
218-
}
219-
}
220-
221-
const doBatchImport = async () => {
222-
// 导入 MD 文档
223-
const localPath = `${workspaceDir}/temp/convert/pandoc`
224-
const mdResult = await pluginInstance.kernelApi.importStdMd(localPath, toNotebookId, `/`)
225-
if (mdResult.code !== 0) {
226-
showMessage(`${pluginInstance.i18n.msgDocCreateFailed}=>${toFilePath}`, 7000, "error")
227-
}
228-
229-
// 打开笔记本
230-
await pluginInstance.kernelApi.openNotebook(toNotebookId)
231-
232-
showMessage(pluginInstance.i18n.msgImportSuccess, 5000, "info")
233-
}
234-
235-
/**
236-
* 读取文件
237-
* @param entry
238-
*/
239-
const readFile = async (entry) => {
240-
const file = await entry.getFile()
241-
const reader = new FileReader()
242-
reader.readAsArrayBuffer(file)
243-
return new Promise((resolve, reject) => {
244-
reader.onload = () => {
245-
const arrayBuffer = reader.result
246-
const fileContent = new Blob([arrayBuffer], { type: file.type })
247-
const fileName = file.name
248-
resolve(new File([fileContent], fileName))
249-
}
250-
reader.onerror = reject
251-
})
163+
// 批量导入
164+
await ImportService.multiImport(pluginInstance, toNotebookId)
252165
}
253166
// =================
254167
// 批量转换结束
255168
// =================
256-
257-
const cleanTemp = async () => {
258-
const tempPath = `/temp/convert/pandoc`
259-
await pluginInstance.kernelApi.removeFile(`${tempPath}`)
260-
await reloadTempFiles()
261-
262-
showMessage(pluginInstance.i18n.msgTempFileCleaned, 5000, "info")
263-
}
264-
265-
const notebookChange = async function () {
266-
// 显示当前选择的名称
267-
const currentNotebook = notebooks.find((n) => n.id === toNotebookId)
268-
toNotebookName = currentNotebook.name
269-
270-
importerConfig = await loadImporterConfig(pluginInstance)
271-
importerConfig.notebook = toNotebookId
272-
273-
await saveImporterConfig(pluginInstance, importerConfig)
274-
pluginInstance.logger.info(`${pluginInstance.i18n.notebookConfigUpdated}=>`, toNotebookId)
275-
}
276-
277-
const reloadTempFiles = async () => {
278-
const tempPath = `/temp/convert/pandoc`
279-
// 临时文件
280-
const tempFiles = await pluginInstance.kernelApi.readDir(tempPath)
281-
if (tempFiles.code === 0 && tempFiles.data.length > 0) {
282-
tempCount = tempFiles.data.length
283-
}
284-
if (tempFiles.code === 404) {
285-
tempCount = 0
286-
}
287-
}
288-
289-
onMount(async () => {
290-
await reloadTempFiles()
291-
292-
// 加载配置
293-
importerConfig = await loadImporterConfig(pluginInstance)
294-
295-
const res = await pluginInstance.kernelApi.lsNotebooks()
296-
const data = res.data as any
297-
notebooks = data.notebooks ?? []
298-
// 没有必要把所有笔记本都列出来
299-
notebooks = notebooks.filter((notebook) => !notebook.closed && !hiddenNotebook.has(notebook.name))
300-
// 选中,若是没保存,获取第一个
301-
toNotebookId = importerConfig?.notebook ?? notebooks[0].id
302-
const currentNotebook = notebooks.find((n) => n.id === toNotebookId)
303-
toNotebookName = currentNotebook.name
304-
305-
pluginInstance.logger.info(`${pluginInstance.i18n.selected} [${toNotebookName}] toNotebookId=>`, toNotebookId)
306-
})
307169
</script>
308170

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

‎src/lib/ImportForm_bak.svelte

Lines changed: 402 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,402 @@
1+
<!--
2+
- Copyright (c) 2023, Terwer . All rights reserved.
3+
- DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
-
5+
- This code is free software; you can redistribute it and/or modify it
6+
- under the terms of the GNU General Public License version 2 only, as
7+
- published by the Free Software Foundation. Terwer designates this
8+
- particular file as subject to the "Classpath" exception as provided
9+
- by Terwer in the LICENSE file that accompanied this code.
10+
-
11+
- This code is distributed in the hope that it will be useful, but WITHOUT
12+
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
- version 2 for more details (a copy is included in the LICENSE file that
15+
- accompanied this code).
16+
-
17+
- You should have received a copy of the GNU General Public License version
18+
- 2 along with this work; if not, write to the Free Software Foundation,
19+
- Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
-
21+
- Please contact Terwer, Shenzhen, Guangdong, China, youweics@163.com
22+
- or visit www.terwer.space if you need additional information or have any
23+
- questions.
24+
-->
25+
26+
<script lang="ts">
27+
import { showMessage } from "siyuan"
28+
import ImporterPlugin from "../index"
29+
import { removeEmptyLines, removeFootnotes, removeLinks, replaceImagePath } from "../utils/utils"
30+
import { onMount } from "svelte"
31+
import { loadImporterConfig, saveImporterConfig } from "../store/config"
32+
import { isDev, workspaceDir } from "../Constants"
33+
34+
export let pluginInstance: ImporterPlugin
35+
export let dialog
36+
37+
let importerConfig
38+
let notebooks = []
39+
let toNotebookId
40+
let toNotebookName
41+
//用户指南不应该作为可以写入的笔记本
42+
const hiddenNotebook: Set<string> = new Set(["思源笔记用户指南", "SiYuan User Guide"])
43+
let tempCount = 0
44+
45+
// =================
46+
// 单文件转换开始
47+
// =================
48+
const selectFile = async (
49+
event: InputEvent & {
50+
target: HTMLInputElement
51+
}
52+
) => {
53+
pluginInstance.logger.debug(`${pluginInstance.i18n.startImport}...`)
54+
dialog.destroy()
55+
56+
// showMessage(`文件上传中,请稍后...`, 1000, "info")
57+
const files = event.target.files ?? []
58+
if (files.length === 0) {
59+
showMessage(`${pluginInstance.i18n.msgFileNotEmpty}`, 7000, "error")
60+
return
61+
}
62+
const file = files[0]
63+
64+
// 给个提示,免得用户以为界面是卡主了
65+
showMessage(`${pluginInstance.i18n.msgConverting} ${file.name}...`, 1000, "info")
66+
await doImport(file)
67+
}
68+
69+
const doImport = async function (file: any) {
70+
const fromFilename = file.name
71+
let filename = fromFilename.substring(0, fromFilename.lastIndexOf("."))
72+
// 去除标题多余的空格,包括开始中间以及结尾的空格
73+
filename = filename.replace(/\s+/g, "")
74+
75+
const fromFilePath = `/temp/convert/pandoc/${fromFilename}`
76+
const uploadResult = await pluginInstance.kernelApi.putFile(fromFilePath, file)
77+
if (uploadResult.code !== 0) {
78+
showMessage(`${pluginInstance.i18n.msgFileUploadError}:${uploadResult.msg}`, 7000, "error")
79+
return
80+
}
81+
82+
// 文件转换
83+
let toFilename = `${filename}.md`
84+
let toFilePath = `/temp/convert/pandoc/${toFilename}`
85+
const ext = fromFilename.split(".").pop().toLowerCase()
86+
// 不是 md 才需要转换
87+
if (ext !== "md") {
88+
const convertResult = await pluginInstance.kernelApi.convertPandoc(
89+
"markdown_strict-raw_html",
90+
fromFilename,
91+
toFilename
92+
)
93+
if (convertResult.code !== 0) {
94+
showMessage(`${pluginInstance.i18n.msgFileConvertError}:${convertResult.msg}`, 7000, "error")
95+
return
96+
}
97+
98+
// 读取文件
99+
let mdText = (await pluginInstance.kernelApi.getFile(toFilePath, "text")) ?? ""
100+
if (mdText === "") {
101+
showMessage(pluginInstance.i18n.msgFileConvertEmpty, 7000, "error")
102+
return
103+
}
104+
105+
// 文本处理
106+
// 删除目录中链接
107+
mdText = removeLinks(mdText)
108+
// 去除空行
109+
mdText = removeEmptyLines(mdText)
110+
// 资源路径
111+
mdText = replaceImagePath(mdText)
112+
// 去除脚注
113+
mdText = removeFootnotes(mdText)
114+
await pluginInstance.kernelApi.saveTextData(`${toFilename}`, mdText)
115+
}
116+
117+
// 导入 MD 文档
118+
const localPath = `${workspaceDir}/temp/convert/pandoc/${toFilename}`
119+
const mdResult = await pluginInstance.kernelApi.importStdMd(localPath, toNotebookId, `/`)
120+
if (mdResult.code !== 0) {
121+
showMessage(`${pluginInstance.i18n.msgDocCreateFailed}=>${toFilePath}`, 7000, "error")
122+
}
123+
124+
// 打开笔记本
125+
await pluginInstance.kernelApi.openNotebook(toNotebookId)
126+
127+
showMessage(pluginInstance.i18n.msgImportSuccess, 5000, "info")
128+
}
129+
// =================
130+
// 单文件转换结束
131+
// =================
132+
133+
// =================
134+
// 批量转换开始
135+
// =================
136+
const selectFolder = async () => {
137+
// 批量导入之前先清空临时文件
138+
if (tempCount > 0) {
139+
showMessage(`${pluginInstance.i18n.tempCountExists}`, 1000, "error")
140+
return
141+
}
142+
143+
const result = await window.showDirectoryPicker()
144+
dialog.destroy()
145+
146+
const allowedExtensions = ["docx", "epub", "md", "html", "opml"]
147+
148+
const entries = await result.values()
149+
for await (const entry of entries) {
150+
if (entry.kind === "directory") {
151+
continue
152+
}
153+
154+
const fileName = entry.name
155+
const ext = fileName.split(".").pop().toLowerCase()
156+
157+
if (!allowedExtensions.includes(ext)) {
158+
console.warn(`${pluginInstance.i18n.importTipNotAllowed} ${fileName}`)
159+
continue
160+
}
161+
162+
// 循环上传并转换
163+
showMessage(`${fileName} ${pluginInstance.i18n.msgConverting}...`, 5000, "info")
164+
const file = await readFile(entry)
165+
await doUploadAndConvert(file)
166+
}
167+
168+
// 文件夹批量导入
169+
await doBatchImport()
170+
}
171+
172+
const doUploadAndConvert = async (file: any) => {
173+
const fromFilename = file.name
174+
let filename = fromFilename.substring(0, fromFilename.lastIndexOf("."))
175+
// 去除标题多余的空格,包括开始中间以及结尾的空格
176+
filename = filename.replace(/\s+/g, "")
177+
178+
const fromFilePath = `/temp/convert/pandoc/${fromFilename}`
179+
const uploadResult = await pluginInstance.kernelApi.putFile(fromFilePath, file)
180+
if (uploadResult.code !== 0) {
181+
showMessage(`${pluginInstance.i18n.msgFileUploadError}:${uploadResult.msg}`, 7000, "error")
182+
return
183+
}
184+
185+
// 文件转换
186+
let toFilename = `${filename}.md`
187+
let toFilePath = `/temp/convert/pandoc/${toFilename}`
188+
const ext = fromFilename.split(".").pop().toLowerCase()
189+
// 不是 md 才需要转换
190+
if (ext !== "md") {
191+
const convertResult = await pluginInstance.kernelApi.convertPandoc(
192+
"markdown_strict-raw_html",
193+
fromFilename,
194+
toFilename
195+
)
196+
if (convertResult.code !== 0) {
197+
showMessage(`${pluginInstance.i18n.msgFileConvertError}:${convertResult.msg}`, 7000, "error")
198+
return
199+
}
200+
201+
// 读取文件
202+
let mdText = (await pluginInstance.kernelApi.getFile(toFilePath, "text")) ?? ""
203+
if (mdText === "") {
204+
showMessage(pluginInstance.i18n.msgFileConvertEmpty, 7000, "error")
205+
return
206+
}
207+
208+
// 文本处理
209+
// 删除目录中链接
210+
mdText = removeLinks(mdText)
211+
// 去除空行
212+
mdText = removeEmptyLines(mdText)
213+
// 资源路径
214+
mdText = replaceImagePath(mdText)
215+
// 去除脚注
216+
mdText = removeFootnotes(mdText)
217+
await pluginInstance.kernelApi.saveTextData(`${toFilename}`, mdText)
218+
}
219+
}
220+
221+
const doBatchImport = async (toNotebookId,) => {
222+
// 导入 MD 文档
223+
const localPath = `${workspaceDir}/temp/convert/pandoc`
224+
const mdResult = await pluginInstance.kernelApi.importStdMd(localPath, toNotebookId, `/`)
225+
if (mdResult.code !== 0) {
226+
showMessage(`${pluginInstance.i18n.msgDocCreateFailed}=>${toFilePath}`, 7000, "error")
227+
}
228+
229+
// 打开笔记本
230+
await pluginInstance.kernelApi.openNotebook(toNotebookId)
231+
232+
showMessage(pluginInstance.i18n.msgImportSuccess, 5000, "info")
233+
}
234+
235+
/**
236+
* 读取文件
237+
* @param entry
238+
*/
239+
const readFile = async (entry) => {
240+
const file = await entry.getFile()
241+
const reader = new FileReader()
242+
reader.readAsArrayBuffer(file)
243+
return new Promise((resolve, reject) => {
244+
reader.onload = () => {
245+
const arrayBuffer = reader.result
246+
const fileContent = new Blob([arrayBuffer], { type: file.type })
247+
const fileName = file.name
248+
resolve(new File([fileContent], fileName))
249+
}
250+
reader.onerror = reject
251+
})
252+
}
253+
// =================
254+
// 批量转换结束
255+
// =================
256+
257+
const cleanTemp = async () => {
258+
const tempPath = `/temp/convert/pandoc`
259+
await pluginInstance.kernelApi.removeFile(`${tempPath}`)
260+
await reloadTempFiles()
261+
262+
showMessage(pluginInstance.i18n.msgTempFileCleaned, 5000, "info")
263+
}
264+
265+
const notebookChange = async function () {
266+
// 显示当前选择的名称
267+
const currentNotebook = notebooks.find((n) => n.id === toNotebookId)
268+
toNotebookName = currentNotebook.name
269+
270+
importerConfig = await loadImporterConfig(pluginInstance)
271+
importerConfig.notebook = toNotebookId
272+
273+
await saveImporterConfig(pluginInstance, importerConfig)
274+
pluginInstance.logger.info(`${pluginInstance.i18n.notebookConfigUpdated}=>`, toNotebookId)
275+
}
276+
277+
const reloadTempFiles = async () => {
278+
const tempPath = `/temp/convert/pandoc`
279+
// 临时文件
280+
const tempFiles = await pluginInstance.kernelApi.readDir(tempPath)
281+
if (tempFiles.code === 0 && tempFiles.data.length > 0) {
282+
tempCount = tempFiles.data.length
283+
}
284+
if (tempFiles.code === 404) {
285+
tempCount = 0
286+
}
287+
}
288+
289+
onMount(async () => {
290+
await reloadTempFiles()
291+
292+
// 加载配置
293+
importerConfig = await loadImporterConfig(pluginInstance)
294+
295+
const res = await pluginInstance.kernelApi.lsNotebooks()
296+
const data = res.data as any
297+
notebooks = data.notebooks ?? []
298+
// 没有必要把所有笔记本都列出来
299+
notebooks = notebooks.filter((notebook) => !notebook.closed && !hiddenNotebook.has(notebook.name))
300+
// 选中,若是没保存,获取第一个
301+
toNotebookId = importerConfig?.notebook ?? notebooks[0].id
302+
const currentNotebook = notebooks.find((n) => n.id === toNotebookId)
303+
toNotebookName = currentNotebook.name
304+
305+
pluginInstance.logger.info(`${pluginInstance.i18n.selected} [${toNotebookName}] toNotebookId=>`, toNotebookId)
306+
})
307+
</script>
308+
309+
<div class="b3-dialog__content importer-form-container">
310+
<div class="config__tab-container">
311+
<label class="fn__flex b3-label config__item">
312+
<div class="fn__flex-1">
313+
{pluginInstance.i18n.targetNotebook}
314+
<div class="b3-label__text">
315+
{pluginInstance.i18n.selectNotebookTip}<span class="selected">[{toNotebookName}]</span>
316+
</div>
317+
</div>
318+
<span class="fn__space" />
319+
<select
320+
id="blockEmbedMode"
321+
class="b3-select fn__flex-center fn__size200"
322+
bind:value={toNotebookId}
323+
on:change={notebookChange}
324+
>
325+
{#each notebooks as notebook}
326+
<option value={notebook.id}>{notebook.name}</option>
327+
{:else}
328+
<option value="0">{pluginInstance.i18n.loading}...</option>
329+
{/each}
330+
</select>
331+
</label>
332+
333+
<div class="fn__flex b3-label config__item">
334+
<div class="fn__flex-1 fn__flex-center">
335+
{pluginInstance.i18n.importFile}
336+
<div class="b3-label__text">{pluginInstance.i18n.importTip}</div>
337+
</div>
338+
<span class="fn__space" />
339+
<button class="b3-button b3-button--outline fn__flex-center fn__size200" style="position: relative">
340+
<input
341+
id="importData"
342+
class="b3-form__upload"
343+
type="file"
344+
accept=".md,.docx,.epub,.html,.opml"
345+
on:change={selectFile}
346+
/>
347+
<svg>
348+
<use xlink:href="#iconDownload" />
349+
</svg>{pluginInstance.i18n.startImport}
350+
</button>
351+
</div>
352+
353+
<div class="fn__flex b3-label config__item">
354+
<div class="fn__flex-1 fn__flex-center">
355+
{pluginInstance.i18n.importFolder}
356+
<div class="b3-label__text">
357+
{pluginInstance.i18n.importFolderTip}<span class="selected">{pluginInstance.i18n.importNotRecursive}</span>
358+
</div>
359+
</div>
360+
<span class="fn__space" />
361+
<button class="b3-button b3-button--outline fn__flex-center fn__size200" style="position: relative">
362+
<input id="batchImportData" class="b3-form__upload" on:click={selectFolder} />
363+
<svg>
364+
<use xlink:href="#iconDownload" />
365+
</svg>{pluginInstance.i18n.importFolder}
366+
</button>
367+
</div>
368+
369+
<div class="fn__flex b3-label config__item">
370+
<div class="fn__flex-1 fn__flex-center">
371+
{pluginInstance.i18n.cleanTemp}
372+
<div class="b3-label__text">
373+
{pluginInstance.i18n.tempTotal} <span class="selected"> [ {tempCount} ] </span>
374+
{pluginInstance.i18n.tempCount}
375+
</div>
376+
</div>
377+
<span class="fn__space" />
378+
<button
379+
id="removeAll"
380+
class="b3-button b3-button--outline fn__flex-center fn__size200"
381+
style="position: relative"
382+
>
383+
<input id="batchRemoveData" class="b3-form__upload" on:click={cleanTemp} />
384+
<svg class="svg"><use xlink:href="#iconTrashcan" /></svg>
385+
{pluginInstance.i18n.clean}
386+
</button>
387+
</div>
388+
389+
<div class="fn__flex b3-label config__item">{pluginInstance.i18n.supportedTypes}</div>
390+
</div>
391+
</div>
392+
393+
<style>
394+
.importer-form-container .config__tab-container {
395+
height: unset;
396+
}
397+
398+
.selected {
399+
color: red;
400+
padding: 0 4px;
401+
}
402+
</style>

‎src/service/importService.ts

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,106 @@
2323
* questions.
2424
*/
2525

26+
import { workspaceDir } from "../Constants"
27+
import { showMessage } from "siyuan"
28+
import ImporterPlugin from "../index"
29+
import { removeEmptyLines, removeFootnotes, removeLinks, replaceImagePath } from "../utils/utils"
30+
2631
export class ImportService {
27-
public static async simpleImport() {}
32+
public static async uploadAndConvert(pluginInstance: ImporterPlugin, file: any) {
33+
const fromFilename = file.name
34+
35+
// 修正文件名
36+
let filename = fromFilename.substring(0, fromFilename.lastIndexOf("."))
37+
// 去除标题多余的空格,包括开始中间以及结尾的空格
38+
filename = filename.replace(/\s+/g, "")
39+
const toFilename = `${filename}.md`
40+
41+
// 扩展名
42+
const ext = fromFilename.split(".").pop().toLowerCase()
43+
if (ext === "md") {
44+
pluginInstance.logger.info(`import md from ${file.path}`)
45+
return file.path
46+
}
47+
48+
const fromFilePath = `/temp/convert/pandoc/${fromFilename}`
49+
const toFilePath = `/temp/convert/pandoc/${toFilename}`
50+
51+
// 文件上传
52+
if (ext !== "md") {
53+
const uploadFilePath = fromFilePath
54+
pluginInstance.logger.info(`upload file from ${uploadFilePath} to /temp/convert/pandoc`)
55+
const uploadResult = await pluginInstance.kernelApi.putFile(uploadFilePath, file)
56+
if (uploadResult.code !== 0) {
57+
showMessage(`${pluginInstance.i18n.msgFileUploadError}${uploadResult.msg}`, 7000, "error")
58+
return
59+
}
60+
}
61+
62+
// 文件转换
63+
// 不是 md 才需要转换
64+
if (ext !== "md") {
65+
const convertResult = await pluginInstance.kernelApi.convertPandoc(
66+
"markdown_strict-raw_html",
67+
fromFilename,
68+
toFilename
69+
)
70+
if (convertResult.code !== 0) {
71+
showMessage(`${pluginInstance.i18n.msgFileConvertError}${convertResult.msg}`, 7000, "error")
72+
return
73+
}
74+
75+
// 读取文件
76+
let mdText = (await pluginInstance.kernelApi.getFile(toFilePath, "text")) ?? ""
77+
if (mdText === "") {
78+
showMessage(pluginInstance.i18n.msgFileConvertEmpty, 7000, "error")
79+
return
80+
}
81+
82+
// 文本处理
83+
// 删除目录中链接
84+
mdText = removeLinks(mdText)
85+
// 去除空行
86+
mdText = removeEmptyLines(mdText)
87+
// 资源路径
88+
mdText = replaceImagePath(mdText)
89+
// 去除脚注
90+
mdText = removeFootnotes(mdText)
91+
await pluginInstance.kernelApi.saveTextData(`${toFilename}`, mdText)
92+
}
93+
94+
return toFilePath
95+
}
96+
97+
public static async singleImport(pluginInstance: ImporterPlugin, toFilePath: string, toNotebookId: string) {
98+
const ext = toFilePath.split(".").pop().toLowerCase()
99+
const isMd = ext === "md"
100+
// 导入 MD 文档
101+
const localPath = isMd ? toFilePath : `${workspaceDir}${toFilePath}`
102+
const mdResult = await pluginInstance.kernelApi.importStdMd(localPath, toNotebookId, `/`)
103+
if (mdResult.code !== 0) {
104+
showMessage(`${pluginInstance.i18n.msgDocCreateFailed}=>${toFilePath}`, 7000, "error")
105+
}
106+
107+
// 打开笔记本
108+
await pluginInstance.kernelApi.openNotebook(toNotebookId)
109+
110+
showMessage(pluginInstance.i18n.msgImportSuccess, 5000, "info")
111+
}
112+
113+
public static async multiImport(pluginInstance: ImporterPlugin, toNotebookId: string) {
114+
// 导入 MD 文档
115+
const localPath = `${workspaceDir}/temp/convert/pandoc`
116+
const mdResult = await pluginInstance.kernelApi.importStdMd(localPath, toNotebookId, `/`)
117+
if (mdResult.code !== 0) {
118+
showMessage(`${pluginInstance.i18n.msgDocCreateFailed}=>${localPath}`, 7000, "error")
119+
}
120+
121+
// 打开笔记本
122+
await pluginInstance.kernelApi.openNotebook(toNotebookId)
28123

29-
public static async multiImport() {}
124+
showMessage(pluginInstance.i18n.msgImportSuccess, 5000, "info")
125+
}
30126

31127
//////////////////////////////////////////////////////////////////
32128
// private function

0 commit comments

Comments
 (0)
Please sign in to comment.