mirror of
https://github.com/purocean/yn.git
synced 2026-09-24 15:46:02 +08:00
feat: 增加双击编辑表格单元格功能
This commit is contained in:
@@ -54,7 +54,7 @@
|
||||
+ HTML 解析:可以直接在文档里面使用 HTML 代码,也可以使用快捷键粘贴复制 HTML 为 Markdown
|
||||
+ docx 导出:后端使用 pandoc 做转换器
|
||||
+ TOC 支持:生成 TOC 在需要生成目录的地方写入 `[toc]{type: "ol", level: [1,2,3]}` 即可
|
||||
+ 编辑表格单元格:快捷编辑表格单元格
|
||||
+ 编辑表格单元格:双击表格单元格即可快速编辑
|
||||
+ 复制标题链接:复制标题链接路径到剪切板,便于插入到其他文件
|
||||
+ 嵌入小工具:文档支持内嵌 HTML 小工具
|
||||
+ 嵌入 Plantuml 图形:需要安装 Java,graphviz
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div :class="{'scroll-to-top': true, 'hide': scrollTop < 30}" :style="{top: (height - 40) + 'px'}" @click="scrollToTop">TOP</div>
|
||||
<article ref="refView" class="markdown-body" @click.capture="handleClick"></article>
|
||||
<article ref="refView" class="markdown-body" @dblclick.capture="handleDbClick" @click.capture="handleClick"></article>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user