diff --git a/README.md b/README.md index 4745393..b336702 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Markdown 文档自动即时渲染为微信图文,让你不再为微信文章 - [x] 支持自定义 CSS 样式 - [x] 支持 Markdown 所有基础语法 - [x] 支持浅色、暗黑两种主题模式 -- [x] 支持 Ctrl + F 快速格式化文档 +- [x] 支持 Alt + Shift + F 快速格式化文档 - [x] 支持色盘取色,快速替换文章整体色调 - [x] 支持多图上传,可自定义配置图床 - [x] 支持自定义上传逻辑 diff --git a/src/assets/example/theme-css.txt b/src/assets/example/theme-css.txt index 4e2a287..cadc8c2 100644 --- a/src/assets/example/theme-css.txt +++ b/src/assets/example/theme-css.txt @@ -1,5 +1,5 @@ /* - 按Ctrl/Command+F可格式化 + 按 Alt/Option + Shift + F 可格式化 */ /* 一级标题样式 */ h1 { diff --git a/src/components/CodemirrorEditor/EditorHeader/index.vue b/src/components/CodemirrorEditor/EditorHeader/index.vue index 2eb412a..b1f8c23 100644 --- a/src/components/CodemirrorEditor/EditorHeader/index.vue +++ b/src/components/CodemirrorEditor/EditorHeader/index.vue @@ -20,6 +20,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' +import { altSign, ctrlKey, ctrlSign, shiftSign } from '@/config' import { mergeCss, solveWeChatImage } from '@/utils' import { useStore } from '@/stores' @@ -29,42 +30,36 @@ const emit = defineEmits([ `startCopy`, `endCopy`, ]) -const defaultKeyMap = CodeMirror.keyMap.default -const modPrefix - = defaultKeyMap === CodeMirror.keyMap.macDefault ? `Cmd` : `Ctrl` - -const kbdIcon - = defaultKeyMap === CodeMirror.keyMap.macDefault ? `⌘` : `Ctrl` const formatItems = [ { label: `加粗`, - kbd: [kbdIcon, `B`], - emitArgs: [`addFormat`, `${modPrefix}-B`], + kbd: [ctrlSign, `B`], + emitArgs: [`addFormat`, `${ctrlKey}-B`], }, { label: `斜体`, - kbd: [kbdIcon, `I`], - emitArgs: [`addFormat`, `${modPrefix}-I`], + kbd: [ctrlSign, `I`], + emitArgs: [`addFormat`, `${ctrlKey}-I`], }, { label: `删除线`, - kbd: [kbdIcon, `D`], - emitArgs: [`addFormat`, `${modPrefix}-D`], + kbd: [ctrlSign, `D`], + emitArgs: [`addFormat`, `${ctrlKey}-D`], }, { label: `超链接`, - kbd: [kbdIcon, `K`], - emitArgs: [`addFormat`, `${modPrefix}-K`], + kbd: [ctrlSign, `K`], + emitArgs: [`addFormat`, `${ctrlKey}-K`], }, { label: `行内代码`, - kbd: [kbdIcon, `E`], - emitArgs: [`addFormat`, `${modPrefix}-E`], + kbd: [ctrlSign, `E`], + emitArgs: [`addFormat`, `${ctrlKey}-E`], }, { label: `格式化`, - kbd: [kbdIcon, `F`], + kbd: [altSign, shiftSign, `F`], emitArgs: [`formatContent`], }, ] @@ -124,9 +119,9 @@ function copy() { /class="base"( style="display: inline")*/g, `class="base" style="display: inline"`, ) - // 公众号不支持 position, 转换为等价的 translateY + // 公众号不支持 position, 转换为等价的 translateY .replace(/top:(.*?)em/g, `transform: translateY($1em)`) - // 适配主题中的颜色变量 + // 适配主题中的颜色变量 .replaceAll(`var(--el-text-color-regular)`, `#3f3f3f`) clipboardDiv.focus() window.getSelection().removeAllRanges() @@ -182,21 +177,16 @@ function updateOpen(isOpen) {
diff --git a/src/components/CodemirrorEditor/RightClickMenu.vue b/src/components/CodemirrorEditor/RightClickMenu.vue deleted file mode 100644 index 46e22f6..0000000 --- a/src/components/CodemirrorEditor/RightClickMenu.vue +++ /dev/null @@ -1,125 +0,0 @@ - - - - - diff --git a/src/components/ui/context-menu/ContextMenu.vue b/src/components/ui/context-menu/ContextMenu.vue new file mode 100644 index 0000000..a888b59 --- /dev/null +++ b/src/components/ui/context-menu/ContextMenu.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuCheckboxItem.vue b/src/components/ui/context-menu/ContextMenuCheckboxItem.vue new file mode 100644 index 0000000..3025e5a --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuCheckboxItem.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuContent.vue b/src/components/ui/context-menu/ContextMenuContent.vue new file mode 100644 index 0000000..4651fb3 --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuContent.vue @@ -0,0 +1,36 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuGroup.vue b/src/components/ui/context-menu/ContextMenuGroup.vue new file mode 100644 index 0000000..b7458d7 --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuGroup.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuItem.vue b/src/components/ui/context-menu/ContextMenuItem.vue new file mode 100644 index 0000000..dc2903a --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuItem.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuLabel.vue b/src/components/ui/context-menu/ContextMenuLabel.vue new file mode 100644 index 0000000..e9a9439 --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuLabel.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuPortal.vue b/src/components/ui/context-menu/ContextMenuPortal.vue new file mode 100644 index 0000000..73dc714 --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuPortal.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuRadioGroup.vue b/src/components/ui/context-menu/ContextMenuRadioGroup.vue new file mode 100644 index 0000000..33273a7 --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuRadioGroup.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuRadioItem.vue b/src/components/ui/context-menu/ContextMenuRadioItem.vue new file mode 100644 index 0000000..37a39a4 --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuRadioItem.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuSeparator.vue b/src/components/ui/context-menu/ContextMenuSeparator.vue new file mode 100644 index 0000000..ae55f3a --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuSeparator.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuShortcut.vue b/src/components/ui/context-menu/ContextMenuShortcut.vue new file mode 100644 index 0000000..0d4da92 --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuShortcut.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuSub.vue b/src/components/ui/context-menu/ContextMenuSub.vue new file mode 100644 index 0000000..7abc360 --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuSub.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuSubContent.vue b/src/components/ui/context-menu/ContextMenuSubContent.vue new file mode 100644 index 0000000..744388b --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuSubContent.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuSubTrigger.vue b/src/components/ui/context-menu/ContextMenuSubTrigger.vue new file mode 100644 index 0000000..94400a6 --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuSubTrigger.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/components/ui/context-menu/ContextMenuTrigger.vue b/src/components/ui/context-menu/ContextMenuTrigger.vue new file mode 100644 index 0000000..22e417b --- /dev/null +++ b/src/components/ui/context-menu/ContextMenuTrigger.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/components/ui/context-menu/index.ts b/src/components/ui/context-menu/index.ts new file mode 100644 index 0000000..157f7b1 --- /dev/null +++ b/src/components/ui/context-menu/index.ts @@ -0,0 +1,14 @@ +export { default as ContextMenu } from './ContextMenu.vue' +export { default as ContextMenuTrigger } from './ContextMenuTrigger.vue' +export { default as ContextMenuContent } from './ContextMenuContent.vue' +export { default as ContextMenuGroup } from './ContextMenuGroup.vue' +export { default as ContextMenuRadioGroup } from './ContextMenuRadioGroup.vue' +export { default as ContextMenuItem } from './ContextMenuItem.vue' +export { default as ContextMenuCheckboxItem } from './ContextMenuCheckboxItem.vue' +export { default as ContextMenuRadioItem } from './ContextMenuRadioItem.vue' +export { default as ContextMenuShortcut } from './ContextMenuShortcut.vue' +export { default as ContextMenuSeparator } from './ContextMenuSeparator.vue' +export { default as ContextMenuLabel } from './ContextMenuLabel.vue' +export { default as ContextMenuSub } from './ContextMenuSub.vue' +export { default as ContextMenuSubTrigger } from './ContextMenuSubTrigger.vue' +export { default as ContextMenuSubContent } from './ContextMenuSubContent.vue' diff --git a/src/config/index.js b/src/config/index.js index c11ce92..bde782c 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -1,5 +1,17 @@ +import CodeMirror from 'codemirror' + export * from './api' export * from './style' export * from './theme' export const prefix = `MD` + +const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault + +export const ctrlKey = isMac ? `Cmd` : `Ctrl` +export const altKey = `Alt` +export const shiftKey = `Shift` + +export const ctrlSign = isMac ? `⌘` : `Ctrl` +export const altSign = isMac ? `⌥` : `Alt` +export const shiftSign = isMac ? `⇧` : `Shift` diff --git a/src/stores/index.js b/src/stores/index.js index a25744b..e785862 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -5,16 +5,12 @@ import CodeMirror from 'codemirror' import { useDark, useStorage, useToggle } from '@vueuse/core' import { ElMessage, ElMessageBox } from 'element-plus' -import { codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, legendOptions, themeMap, themeOptions } from '@/config' +import { altKey, codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, legendOptions, shiftKey, themeMap, themeOptions } from '@/config' import WxRenderer from '@/utils/wx-renderer' import DEFAULT_CONTENT from '@/assets/example/markdown.md?raw' import DEFAULT_CSS_CONTENT from '@/assets/example/theme-css.txt?raw' import { addPrefix, css2json, customCssWithTemplate, downloadMD, exportHTML, formatCss, formatDoc, setColorWithCustomTemplate, setFontSizeWithTemplate, setTheme } from '@/utils' -const defaultKeyMap = CodeMirror.keyMap.default -const modPrefix - = defaultKeyMap === CodeMirror.keyMap.macDefault ? `Cmd` : `Ctrl` - export const useStore = defineStore(`store`, () => { // 是否开启深色模式 const isDark = useDark() @@ -209,7 +205,7 @@ export const useStore = defineStore(`store`, () => { matchBrackets: true, autofocus: true, extraKeys: { - [`${modPrefix}-F`]: function autoFormat(editor) { + [`${shiftKey}-${altKey}-F`]: function autoFormat(editor) { const doc = formatCss(editor.getValue()) getCurrentTab().content = doc editor.setValue(doc) diff --git a/src/views/CodemirrorEditor.vue b/src/views/CodemirrorEditor.vue index 8b59675..123ce95 100644 --- a/src/views/CodemirrorEditor.vue +++ b/src/views/CodemirrorEditor.vue @@ -9,11 +9,28 @@ import { useStore } from '@/stores' import EditorHeader from '@/components/CodemirrorEditor/EditorHeader/index.vue' import InsertFormDialog from '@/components/CodemirrorEditor/InsertFormDialog.vue' -import RightClickMenu from '@/components/CodemirrorEditor/RightClickMenu.vue' import UploadImgDialog from '@/components/CodemirrorEditor/UploadImgDialog.vue' import CssEditor from '@/components/CodemirrorEditor/CssEditor.vue' import RunLoading from '@/components/RunLoading.vue' +import { + ContextMenu, + ContextMenuCheckboxItem, + ContextMenuContent, + ContextMenuItem, + ContextMenuLabel, + ContextMenuRadioGroup, + ContextMenuRadioItem, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuTrigger, +} from '@/components/ui/context-menu' + +import { altKey, altSign, ctrlKey, ctrlSign, shiftKey, shiftSign } from '@/config' + import { checkImage, formatDoc, @@ -22,10 +39,6 @@ import { import 'codemirror/mode/javascript/javascript' -const defaultKeyMap = CodeMirror.keyMap.default -const modPrefix - = defaultKeyMap === CodeMirror.keyMap.macDefault ? `Cmd` : `Ctrl` - const store = useStore() const { output, editor, editorContent, isShowCssEditor } = storeToRefs(store) @@ -42,9 +55,6 @@ const { const isImgLoading = ref(false) const timeout = ref(0) -const mouseLeft = ref(0) -const mouseTop = ref(0) -const rightClickMenuVisible = ref(false) const preview = ref(null) @@ -119,34 +129,6 @@ function endCopy() { }, 800) } -function onMenuEvent(type) { - switch (type) { - case `insertPic`: - toggleShowUploadImgDialog() - break - case `insertTable`: - toggleShowInsertFormDialog() - break - case `resetStyle`: - resetStyleConfirm() - break - case `importMarkdown`: - importMarkdownContent() - break - case `exportMarkdown`: - exportEditorContent2MD() - break - case `exportHtml`: - exportEditorContent2HTML() - break - case `formatMarkdown`: - formatContent() - break - default: - break - } -} - function beforeUpload(file) { // validate image const checkResult = checkImage(file) @@ -221,32 +203,32 @@ function initEditor() { styleActiveLine: true, autoCloseBrackets: true, extraKeys: { - [`${modPrefix}-F`]: function autoFormat(editor) { + [`${shiftKey}-${altKey}-F`]: function autoFormat(editor) { const doc = formatDoc(editor.getValue(0)) editor.setValue(doc) }, - [`${modPrefix}-B`]: function bold(editor) { + [`${ctrlKey}-B`]: function bold(editor) { const selected = editor.getSelection() editor.replaceSelection(`**${selected}**`) }, - [`${modPrefix}-I`]: function italic(editor) { + [`${ctrlKey}-I`]: function italic(editor) { const selected = editor.getSelection() editor.replaceSelection(`*${selected}*`) }, - [`${modPrefix}-D`]: function del(editor) { + [`${ctrlKey}-D`]: function del(editor) { const selected = editor.getSelection() editor.replaceSelection(`~~${selected}~~`) }, - [`${modPrefix}-K`]: function italic(editor) { + [`${ctrlKey}-K`]: function italic(editor) { const selected = editor.getSelection() editor.replaceSelection(`[${selected}]()`) }, - [`${modPrefix}-E`]: function code(editor) { + [`${ctrlKey}-E`]: function code(editor) { const selected = editor.getSelection() editor.replaceSelection(`\`${selected}\``) }, // 预备弃用 - [`${modPrefix}-L`]: function code(editor) { + [`${ctrlKey}-L`]: function code(editor) { const selected = editor.getSelection() editor.replaceSelection(`\`${selected}\``) }, @@ -279,33 +261,10 @@ function initEditor() { } } }) - - editor.value.on(`mousedown`, () => { - rightClickMenuVisible.value = false - }) - editor.value.on(`blur`, () => { - // !影响到右键菜单的点击事件,右键菜单的点击事件在组件内通过mousedown触发 - rightClickMenuVisible.value = false - }) - editor.value.on(`scroll`, () => { - rightClickMenuVisible.value = false - }) } const container = ref(null) -// 右键菜单 -function openMenu(e) { - const menuMinWidth = 105 - const offsetLeft = container.value.getBoundingClientRect().left - const offsetWidth = container.value.offsetWidth - const maxLeft = offsetWidth - menuMinWidth - const left = e.clientX - offsetLeft - mouseLeft.value = Math.min(maxLeft, left) - mouseTop.value = e.clientY + 10 - rightClickMenuVisible.value = true -} - // 工具函数,添加格式 function addFormat(cmd) { editor.value.options.extraKeys[cmd](editor.value) @@ -440,13 +399,41 @@ onMounted(() => { :class="{ 'order-1': !store.isEditOnLeft, }" - @contextmenu.prevent="openMenu" > -