diff --git a/README.md b/README.md
index 4be2ab5e..bd7d26f8 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@
+ HTML 解析:可以直接在文档里面使用 HTML 代码,也可以使用快捷键粘贴复制 HTML 为 Markdown
+ docx 导出:后端使用 pandoc 做转换器
+ TOC 支持:生成 TOC 在需要生成目录的地方写入 `[toc]{type: "ol", level: [1,2,3]}` 即可
-+ 编辑表格单元格:快捷编辑表格单元格
++ 编辑表格单元格:双击表格单元格即可快速编辑
+ 复制标题链接:复制标题链接路径到剪切板,便于插入到其他文件
+ 嵌入小工具:文档支持内嵌 HTML 小工具
+ 嵌入 Plantuml 图形:需要安装 Java,graphviz
diff --git a/frontend/src/components/Preview.vue b/frontend/src/components/Preview.vue
index c16687e3..dd911cb6 100644
--- a/frontend/src/components/Preview.vue
+++ b/frontend/src/components/Preview.vue
@@ -33,7 +33,7 @@
TOP
-
+
@@ -176,7 +176,7 @@ export default defineComponent({
todoDoneCount.value = refView.value!!.querySelectorAll('input[type=checkbox][checked]').length
}
- const render = lodash.debounce(() => {
+ const render = () => {
// 编辑非 markdown 文件预览直接显示代码
const content = (filePath.value || '').endsWith('.md')
? currentContent.value
@@ -189,7 +189,9 @@ export default defineComponent({
triggerHook('ON_VIEW_RENDER', { getViewDom })
nextTick(() => triggerHook('ON_VIEW_RENDERED', { getViewDom }))
- }, 500, { leading: true })
+ }
+
+ const renderDebonce = lodash.debounce(render, 500, { leading: true })
async function keydownHandler (e: KeyboardEvent) {
triggerHook('ON_VIEW_KEY_DOWN', e, getViewDom())
@@ -208,6 +210,10 @@ export default defineComponent({
syncScroll(1)
}
+ function handleDbClick (e: MouseEvent) {
+ triggerHook('ON_VIEW_ELEMENT_DBCLICK', e)
+ }
+
async function handleClick (e: MouseEvent) {
triggerHook('ON_VIEW_ELEMENT_CLICK', e)
@@ -261,7 +267,7 @@ export default defineComponent({
}
onMounted(() => {
- nextTick(render)
+ nextTick(renderDebonce)
triggerHook('ON_VIEW_MOUNTED', { getViewDom })
window.addEventListener('keydown', keydownHandler, true)
bus.on('resize', resizeHandler)
@@ -275,7 +281,7 @@ export default defineComponent({
bus.off('view-rerender', render)
})
- watch(currentContent, () => autoPreview.value && render())
+ watch(currentContent, () => autoPreview.value && renderDebonce())
watch(filePath, () => {
// 切换文件后,开启自动预览
store.commit('setAutoPreview', true)
@@ -298,6 +304,7 @@ export default defineComponent({
handleScroll,
scrollToTop,
handleClick,
+ handleDbClick,
revealLine,
syncScroll,
}
diff --git a/frontend/src/plugins/markdown-source-line.ts b/frontend/src/plugins/markdown-source-line.ts
index 3eb27fbe..09245e69 100644
--- a/frontend/src/plugins/markdown-source-line.ts
+++ b/frontend/src/plugins/markdown-source-line.ts
@@ -24,7 +24,8 @@ export default {
md.renderer.rules.heading_open = injectLineNumbers
md.renderer.rules.list_item_open = injectLineNumbers
md.renderer.rules.table_open = injectLineNumbers
- md.renderer.rules.td_open = buildFunction('yank-td')
+ md.renderer.rules.td_open = buildFunction('yank-table-cell')
+ md.renderer.rules.th_open = buildFunction('yank-table-cell')
})
}
} as Plugin
diff --git a/frontend/src/plugins/table-cell-edit.ts b/frontend/src/plugins/table-cell-edit.ts
index 401ae819..693593a3 100644
--- a/frontend/src/plugins/table-cell-edit.ts
+++ b/frontend/src/plugins/table-cell-edit.ts
@@ -4,13 +4,14 @@ import { Plugin, Ctx } from '@/useful/plugin'
import { hasCtrlCmd } from '@/useful/shortcut'
import { useToast } from '@/useful/toast'
-const editTableCell = async (start: number, end: number, cellIndex: number) => {
+const editTableCell = async (start: number, end: number, cellIndex: number, input: HTMLTextAreaElement | null) => {
const toast = useToast()
const modal = useModal()
const bus = useBus()
if (end - start !== 1) {
toast.show('warning', '暂只支持编辑单行文本')
+ input && input.remove()
return
}
@@ -55,23 +56,58 @@ const editTableCell = async (start: number, end: number, cellIndex: number) => {
bus.emit('editor-get-line', { line: start, callback: (val: string) => { text = val.trim() } })
const columns = escapedSplit(text)
- const cellText = columns[cellIndex]
+ const cellText = columns[cellIndex].trim()
if (typeof cellText !== 'string') {
toast.show('warning', '编辑错误')
return
}
- let value = await modal.input({
- title: '编辑单元格',
- type: 'textarea',
- value: cellText,
- modalWidth: '600px',
- hint: '单元格内容',
- })
- if (typeof value !== 'string') {
- toast.show('warning', '取消编辑')
- return
+ let value = cellText
+ if (input) {
+ input.value = cellText
+ value = await (new Promise((resolve) => {
+ const cancel = () => {
+ input.onblur = () => undefined
+ input.remove()
+ }
+
+ const ok = () => {
+ if (input.value === cellText) {
+ cancel()
+ } else {
+ resolve(input.value)
+ bus.emit('view-rerender')
+ }
+ }
+
+ input.onblur = ok
+
+ input.onkeydown = e => {
+ if (e.key === 'Escape') {
+ cancel()
+ }
+
+ if (e.key === 'Enter' && hasCtrlCmd(e)) {
+ ok()
+ }
+ }
+ }))
+ } else {
+ const inputVal = await modal.input({
+ title: '编辑单元格',
+ type: 'textarea',
+ value: cellText,
+ modalWidth: '600px',
+ hint: '单元格内容',
+ })
+
+ if (typeof inputVal !== 'string') {
+ toast.show('warning', '取消编辑')
+ return
+ }
+
+ value = inputVal
}
if (!value.startsWith(' ') && cellIndex > 0) value = ' ' + value
@@ -88,7 +124,7 @@ const editTableCell = async (start: number, end: number, cellIndex: number) => {
export default {
name: 'table-cell-edit',
register: (ctx: Ctx) => {
- ctx.registerHook('ON_VIEW_ELEMENT_CLICK', async (e: MouseEvent) => {
+ const handleClick = async (e: MouseEvent, modal: boolean) => {
const target = e.target as HTMLElement
const preventEvent = () => {
@@ -97,7 +133,11 @@ export default {
return true
}
- if (target.tagName === 'TD' && target.classList.contains('yank-td') && hasCtrlCmd(e)) {
+ if (['TD', 'TH'].includes(target.tagName) && target.classList.contains('yank-table-cell')) {
+ if ((hasCtrlCmd(e) && !modal) || (!hasCtrlCmd(e) && modal)) {
+ return false
+ }
+
const start = parseInt(target.dataset.sourceLine || '0')
const end = parseInt(target.dataset.sourceLineEnd || '0')
const td = target as HTMLTableDataCellElement
@@ -105,12 +145,45 @@ export default {
.slice(0, td.cellIndex)
.reduce((prev, current) => prev + current.colSpan, 0)
- editTableCell(start, end, cellIndex)
+ const input = modal ? null : document.createElement('textarea')
+
+ if (input) {
+ target.style.position = 'relative'
+ input.style.left = '0'
+ input.style.top = '0'
+ input.style.height = '100%'
+ input.style.width = '100%'
+ input.style.boxSizing = 'border-box'
+ input.style.color = '#fff'
+ input.style.borderColor = '#93e632'
+ input.style.backgroundColor = '#272a2b'
+ input.style.position = 'absolute'
+ input.style.display = 'block'
+ input.style.padding = '4px'
+ input.autofocus = true
+ input.placeholder = 'ESC 取消'
+ setTimeout(() => {
+ input.focus()
+ }, 0)
+
+ target.appendChild(input)
+ }
+
+ editTableCell(start, end, cellIndex, input)
return preventEvent()
}
return false
+ }
+
+ ctx.registerHook('ON_VIEW_ELEMENT_DBCLICK', e => handleClick(e, false))
+ ctx.registerHook('ON_VIEW_ELEMENT_CLICK', e => handleClick(e, true))
+ ctx.registerHook('ON_VIEW_RENDERED', ({ getViewDom }) => {
+ const view: HTMLElement = getViewDom()
+ view.querySelectorAll('.yank-table-cell').forEach(td => {
+ (td as HTMLElement).title = '双击编辑'
+ })
})
}
} as Plugin
diff --git a/frontend/src/useful/plugin/view.ts b/frontend/src/useful/plugin/view.ts
index f09e1502..ba54608b 100644
--- a/frontend/src/useful/plugin/view.ts
+++ b/frontend/src/useful/plugin/view.ts
@@ -1,4 +1,5 @@
export type HookType = 'ON_VIEW_ELEMENT_CLICK'
+ | 'ON_VIEW_ELEMENT_DBCLICK'
| 'ON_VIEW_KEY_DOWN'
| 'ON_VIEW_RENDER'
| 'ON_VIEW_RENDERED'
diff --git a/help/FEATURES.md b/help/FEATURES.md
index 1664517e..ad9f013e 100644
--- a/help/FEATURES.md
+++ b/help/FEATURES.md
@@ -354,7 +354,8 @@ xml 代码块 第一行注释需要有 `--drawio--` 文字
粘贴图片为 base64 链接 | Ctrl + B + V | Cmd + B + V
转换单个外链图片到本地 | Ctrl + Shift + 单击图片 | Cmd + Shift + 单击图片
转换所有外链图片到本地 | Ctrl + Shift + L | Cmd + Shift + L
-编辑表格单元格 | Ctrl + 单击单元格 | Cmd + 单击单元格
+编辑表格单元格 | 双击单元格 | 双击单元格
+编辑表格单元格(弹出框) | Ctrl + 单击单元格 | Cmd + 单击单元格 或
复制文档标题链接 | Ctrl + 单击标题 | Cmd + 单击标题
切换编辑器 Tab | Ctrl + Alt + Left/Right | Ctrl + Option + Left/Rig
切换侧栏 | Alt + E | Option + E