Skip to content

Commit

Permalink
feat: 支持通过全局关键字导入.md文件
Browse files Browse the repository at this point in the history
- 解耦文件导入、解析、读取的逻辑到utils和hooks中
- 通过全局关键字导入.md文件
  • Loading branch information
ZiuChen committed Apr 9, 2023
1 parent a4f3e11 commit 816f451
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 91 deletions.
20 changes: 20 additions & 0 deletions public/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@
"code": "新建Markdown笔记",
"explain": "新建 Markdown 笔记",
"cmds": ["New Note", "新建笔记"]
},
{
"code": "导入Markdown文档",
"explain": "将Markdown文档导入到超级Markdown",
"cmds": [
{
"type": "files",
"label": "从文件创建笔记",
"fileType": "file",
"match": "/.md$/i"
}
]
},
{
"code": "创建Markdown笔记",
"explain": "从文本或图片或文件创建笔记",
"cmds": [
{ "type": "over", "label": "从文本创建笔记" },
{ "type": "img", "label": "从图片创建笔记" }
]
}
]
}
44 changes: 25 additions & 19 deletions src/common/registerCallback.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,35 @@
import { Message } from '@arco-design/web-vue'
import { $emit } from '@/hooks/useEventBus'
import { ENTER_FILE, ENTER_CREATE } from '@/common/symbol'
import { ENTER_FILE, ENTER_CREATE, ENTER_IMPORT, ENTER_CONTENT } from '@/common/symbol'
import { isElectron } from '@/utils'
import { useMainStore } from '@/store'
import type { IPayloadFile } from '@/types'

export function registerCallback() {
if (!isElectron) return

const store = useMainStore()

utools.onPluginEnter(({ code, type, payload }) => {
// 通过全局关键字打开文章
if (code.startsWith('note/')) {
const docId = code.split('/')[1]

if (!store.isReady) {
watch(
() => store.isReady,
(val) => (val ? $emit(ENTER_FILE, docId) : null)
)
} else {
$emit(ENTER_FILE, docId)
}
emitWithWatch(ENTER_FILE, docId)
}

// 通过关键字创建文章
if (code === '新建Markdown笔记') {
if (!store.isReady) {
watch(
() => store.isReady,
(val) => (val ? $emit(ENTER_CREATE) : null)
)
} else {
$emit(ENTER_CREATE)
}
emitWithWatch(ENTER_CREATE)
}

// 从.md文件导入文档
if (code === '导入Markdown文档') {
const files = payload as IPayloadFile[]
emitWithWatch(ENTER_IMPORT, files)
}

// 从文本/图片匹配创建文档
if (code === '创建Markdown笔记') {
emitWithWatch(ENTER_CONTENT, payload)
}
})

Expand All @@ -43,3 +39,13 @@ export function registerCallback() {
})
}
}

function emitWithWatch(event: symbol, payload?: any) {
const store = useMainStore()
if (store.isReady) $emit(event, payload)
else
watch(
() => store.isReady,
(val) => (val ? $emit(event, payload) : null)
)
}
10 changes: 8 additions & 2 deletions src/common/symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ export const SWITCH_FILE = Symbol('SWITCH_FILE') // 切换侧栏目录树 当前
export const EDITOR_LOADED = Symbol('EDITOR_LOADED') // 编辑器挂载完毕
export const FOCUS_EDITOR = Symbol('FOCUS_EDITOR') // 主动聚焦编辑器

/**
* 全局关键字
*/
export const ENTER_FILE = Symbol('ENTER_FILE') // 由关键字进入插件
export const ENTER_CREATE = Symbol('ENTER_CREATE') // 新建空的笔记
export const ENTER_IMPORT = Symbol('ENTER_IMPORT') // 导入MD文档
export const ENTER_CONTENT = Symbol('ENTER_CONTENT') // 新建有内容的笔记

/**
* 其他
*/
export const CHANGE_TITLE = Symbol('CHANGE_TITLE') // 标题变化
export const IS_DARK = Symbol('IS_DARK') // 当前主题色
export const ENTER_FILE = Symbol('ENTER_FILE') // 由关键字进入插件
export const ENTER_CREATE = Symbol('ENTER_CREATE') // 新建笔记
82 changes: 63 additions & 19 deletions src/components/SideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,24 @@
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue'
import { useRouter } from 'vue-router'
import { sidebar, SidebarItem } from '@/data/sidebar'
import { useArticleImport } from '@/hooks/useArticleImport'
import { useEventListener } from '@/hooks/useEventListener'
import { $emit, useEventBus } from '@/hooks/useEventBus'
import { useTreeData, findNodeByKey, collectAllParentKeys, filterNode } from '@/hooks/useTreeData'
import { useTreeDrag } from '@/hooks/useTreeDrag'
import { useArticleStore } from '@/store'
import { setItem, getItem, removeItem, getFeatures, removeFeature, saveArticle } from '@/utils'
import {
setItem,
getItem,
removeItem,
getFeatures,
removeFeature,
readFilesData,
saveArticle
} from '@/utils'
import {
SWITCH_FILE,
CATEGORY_CHANGE,
Expand All @@ -129,10 +139,11 @@ import {
DELETE_FOLDER,
ENTER_FILE,
ENTER_CREATE,
ENTER_IMPORT,
ENTER_CONTENT,
RENAME_NODE
} from '@/common/symbol'
import { Message } from '@arco-design/web-vue'
import { useRouter } from 'vue-router'
import { IPayloadFile } from '@/types'
const localTreeData = getItem('category') || sidebar
Expand Down Expand Up @@ -230,6 +241,28 @@ useEventBus(ENTER_CREATE, () => {
addFile()
})
// 通过全局关键字导入.md文件
// 此操作只会在Electron环境触发
useEventBus(ENTER_IMPORT, async (payload: IPayloadFile[]) => {
return Promise.resolve(payload)
.then((payload) => {
const files = payload.map((item) => item.path)
if (!files || !files.length) return []
return files
})
.then((files) => readFilesData(files))
.then((fileList) => {
if (!fileList || !fileList.length) return
return handleArticleImport(fileList)
})
})
// 从文本/图片创建带内容的文档
useEventBus(ENTER_CONTENT, (payload: string) => {
// TODO: console.log(payload)
})
// 处理文件删除事件
// 删除完成后直接失焦 提醒用户自己切换文章
useEventBus(DELETE_FILE, (key: string) => {
Expand Down Expand Up @@ -325,36 +358,47 @@ function handleSelect(_: any, data: any) {
selectedNode.value = data.node
}
/**
* 切换侧栏展开状态
*/
function toggleExpanded() {
expandedKeys.value = expandedKeys?.value.length ? [] : allExpandedKeys.value
}
/**
* 批量导入按钮点击
*/
function handleImportClick() {
// 弹窗进行文件选择
const promise = useArticleImport()
promise
.then((fileList) => {
if (!fileList || !fileList.length) return
.then((fileList) => handleArticleImport(fileList))
.catch((err) => Message.error(err.message))
}
// 将fileList添加到侧栏 并获取到带key的nodeList
const nodeList = patchFileExternal(fileList)
/**
* 将文件patch到侧栏 并保存到本地存储
*/
function handleArticleImport(fileList: { title: string; data: string }[]) {
if (!fileList || !fileList.length) return
if (!nodeList || !nodeList.length) return Message.error('导入出错')
// 将fileList添加到侧栏 并获取到带key的nodeList
const nodeList = patchFileExternal(fileList)
for (const node of nodeList) {
saveArticle({
id: node.key,
title: node.title,
code: node.data,
lastSavedAt: parseInt(node.key),
createAt: parseInt(node.key)
})
}
if (!nodeList || !nodeList.length) return Message.error('导入出错')
return Message.success('导入成功')
for (const node of nodeList) {
saveArticle({
id: node.key,
title: node.title,
code: node.data,
lastSavedAt: parseInt(node.key),
createAt: parseInt(node.key)
})
.catch((err) => Message.error(err.message))
}
return Message.success('导入成功')
}
</script>
Expand Down
61 changes: 12 additions & 49 deletions src/hooks/useArticleImport.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { isElectron } from '@/utils'
import { isElectron, readFilesData } from '@/utils'
import { Message } from '@arco-design/web-vue'
import { basename, extname, readFileSync } from '@/preload'

const FILE_NOT_NAMED = '导入的文章'
const FILE_NOT_SELECTED = '操作已取消'
const FILE_SELECT_TITLE = '选择要导入的 Markdown 文件'
const FILE_IMPORTING = '导入中'
Expand All @@ -13,14 +11,15 @@ export interface IFileImport {
}

export function useArticleImport() {
return isElectron ? readFilesInElectron() : readFilesInBrowser()
return isElectron ? getFileListInElectron() : getFileListInBrowser()
}

/**
* Electron环境下批量读取文件
* Electron环境下获取文件列表
* 弹出文件选框 选择文件并批量读取文件 组织为带数据的fileList
*/
async function readFilesInElectron(): Promise<IFileImport[]> {
return new Promise((resolve) => {
async function getFileListInElectron(): Promise<IFileImport[]> {
return new Promise(async (resolve) => {
const files = utools.showOpenDialog({
title: FILE_SELECT_TITLE,
filters: [{ name: 'Markdown', extensions: ['md'] }],
Expand All @@ -37,18 +36,7 @@ async function readFilesInElectron(): Promise<IFileImport[]> {
duration: 0
})

const res: IFileImport[] = []

for (let i = 0; i < files.length; i++) {
const fpath = files[i]
const title = basename(fpath, extname(fpath))
const data = readFileSync(fpath, 'utf-8')

res.push({
title,
data
})
}
const res = await readFilesData(files)

msgHandler.close()

Expand All @@ -57,10 +45,11 @@ async function readFilesInElectron(): Promise<IFileImport[]> {
}

/**
* 浏览器环境下批量读取文件
* 浏览器环境下获取文件列表
* 弹出文件选框 选择文件并批量读取文件 组织为带数据的fileList
*/
async function readFilesInBrowser(): Promise<IFileImport[]> {
return new Promise((resolve) => {
async function getFileListInBrowser(): Promise<IFileImport[]> {
return new Promise(async (resolve) => {
const input = document.createElement('input')
input.type = 'file'
input.multiple = true
Expand All @@ -79,18 +68,7 @@ async function readFilesInBrowser(): Promise<IFileImport[]> {
duration: 0
})

const res: IFileImport[] = []

for (let i = 0; i < files.length; i++) {
const file = files[i]
const title = file.name.slice(0, -3) || FILE_NOT_NAMED
const data = await readAsText(file)

res.push({
title,
data
})
}
const res = await readFilesData(files)

msgHandler.close()

Expand All @@ -100,18 +78,3 @@ async function readFilesInBrowser(): Promise<IFileImport[]> {
input.click()
})
}

/**
* 浏览器环境下将Blob读取为文本
* 本质上是FileReader的Promise封装
*/
async function readAsText(blob: Blob): Promise<string> {
const reader = new FileReader()
return new Promise((resolve, reject) => {
reader.onload = () => {
resolve(reader.result as string)
}
reader.onerror = reject
reader.readAsText(blob)
})
}
8 changes: 8 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@ export interface IArticleSaveExtend extends IArticle {
isFeature?: boolean
isReadonly?: boolean
}

// uTools onPluginEnter files payload
export interface IPayloadFile {
isFile: boolean
isDirectory: boolean
name: string
path: string
}
Loading

0 comments on commit 816f451

Please sign in to comment.