tiptapify 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/tiptapify.css +1 -1
- package/dist/tiptapify.mjs +23435 -25368
- package/dist/tiptapify.umd.js +70 -73
- package/package.json +1 -1
- package/src/components/Footer.vue +1 -1
- package/src/components/Tiptapify.vue +6 -2
- package/src/components/Toolbar/Index.vue +3 -4
- package/src/components/Toolbar/Items.vue +3 -3
- package/src/components/UI/HotkeyTooltip.test.ts +65 -0
- package/src/components/UI/HotkeyTooltip.vue +54 -0
- package/src/components/UI/Tooltip.vue +20 -0
- package/src/composables/useTiptapifyConfig.test.ts +66 -0
- package/src/composables/useTiptapifyConfig.ts +17 -0
- package/src/constants/defaults.ts +4 -2
- package/src/extensions/components/actions/redo/Button.vue +5 -9
- package/src/extensions/components/actions/undo/Button.vue +5 -9
- package/src/extensions/components/ai/Button.vue +7 -10
- package/src/extensions/components/ai/Dialog.vue +4 -9
- package/src/extensions/components/alignment/center/Button.vue +5 -9
- package/src/extensions/components/alignment/justify/Button.vue +5 -9
- package/src/extensions/components/alignment/left/Button.vue +5 -9
- package/src/extensions/components/alignment/right/Button.vue +5 -9
- package/src/extensions/components/format/bold/Button.vue +5 -9
- package/src/extensions/components/format/italic/Button.vue +5 -9
- package/src/extensions/components/format/strike/Button.vue +5 -9
- package/src/extensions/components/format/underline/Button.vue +5 -9
- package/src/extensions/components/formatExtra/code/Button.vue +5 -9
- package/src/extensions/components/formatExtra/codeBlock/Button.vue +5 -9
- package/src/extensions/components/formatExtra/quote/Button.vue +5 -9
- package/src/extensions/components/formatExtra/sub/Button.vue +5 -9
- package/src/extensions/components/formatExtra/sup/Button.vue +5 -9
- package/src/extensions/components/list/bullet/Button.vue +8 -13
- package/src/extensions/components/list/indent/Button.vue +5 -9
- package/src/extensions/components/list/numbered/Button.vue +5 -9
- package/src/extensions/components/list/outdent/Button.vue +5 -9
- package/src/extensions/components/list/task/Button.vue +5 -9
- package/src/extensions/components/media/charmap/Button.vue +5 -8
- package/src/extensions/components/media/charmap/Picker.vue +2 -3
- package/src/extensions/components/media/emoji/Button.vue +6 -9
- package/src/extensions/components/media/emoji/Picker.vue +2 -3
- package/src/extensions/components/media/iframe/Button.vue +6 -10
- package/src/extensions/components/media/iframe/IframeDialog.vue +3 -7
- package/src/extensions/components/media/image/Button.vue +6 -10
- package/src/extensions/components/media/image/ImageDialog.vue +5 -10
- package/src/extensions/components/media/link/Button.vue +7 -10
- package/src/extensions/components/media/link/LinkDialog.vue +3 -7
- package/src/extensions/components/media/table/Button.vue +10 -24
- package/src/extensions/components/media/table/ColumnActions.vue +4 -9
- package/src/extensions/components/media/table/RowActions.vue +4 -9
- package/src/extensions/components/media/video/Button.vue +6 -10
- package/src/extensions/components/media/video/VideoDialog.vue +3 -7
- package/src/extensions/components/misc/break/Button.vue +5 -9
- package/src/extensions/components/misc/formatClear/Button.vue +5 -9
- package/src/extensions/components/misc/fullscreen/Button.vue +5 -9
- package/src/extensions/components/misc/invisibleChar/Button.vue +5 -9
- package/src/extensions/components/misc/line/Button.vue +5 -9
- package/src/extensions/components/misc/preview/Button.vue +5 -9
- package/src/extensions/components/misc/source/Button.vue +6 -10
- package/src/extensions/components/misc/source/ShowSourceDialog.vue +4 -5
- package/src/extensions/components/style/color/Button.vue +5 -9
- package/src/extensions/components/style/fontFamily/Button.vue +5 -9
- package/src/extensions/components/style/fontSize/Button.vue +6 -7
- package/src/extensions/components/style/heading/Button.vue +6 -8
- package/src/extensions/components/style/highlight/Button.vue +5 -9
- package/src/extensions/components/style/lineHeight/Button.vue +5 -9
- package/src/index.ts +3 -0
- package/src/utils/hotkeys.test.ts +146 -0
- package/src/utils/hotkeys.ts +65 -0
|
@@ -3,19 +3,20 @@
|
|
|
3
3
|
import * as mdi from '@mdi/js'
|
|
4
4
|
import { Editor } from '@tiptap/vue-3'
|
|
5
5
|
import BtnIcon from '@tiptapify/components/UI/BtnIcon.vue'
|
|
6
|
+
import Tooltip from '@tiptapify/components/UI/Tooltip.vue'
|
|
6
7
|
import FontSize from '@tiptapify/extensions/components/style/fontSize/FontSize.vue'
|
|
7
8
|
import { fontSizes } from '@tiptapify/constants/style'
|
|
8
|
-
import {
|
|
9
|
-
import { computed, inject,
|
|
9
|
+
import { useTiptapifyConfig } from '@tiptapify/composables/useTiptapifyConfig'
|
|
10
|
+
import { computed, inject, Ref } from 'vue'
|
|
10
11
|
|
|
11
|
-
import defaults from '@tiptapify/constants/defaults'
|
|
12
12
|
import { ComposerTranslation } from 'vue-i18n'
|
|
13
13
|
|
|
14
14
|
const props = defineProps({
|
|
15
15
|
fontMeasure: { type: String, default () { return 'px' } },
|
|
16
|
-
variantBtn: { type: String as PropType<variantBtnTypes>, default() { return defaults.variantBtn } },
|
|
17
16
|
})
|
|
18
17
|
|
|
18
|
+
const { variantBtn } = useTiptapifyConfig()
|
|
19
|
+
|
|
19
20
|
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
20
21
|
|
|
21
22
|
const { t } = inject('tiptapifyI18n') as { t: ComposerTranslation }
|
|
@@ -54,9 +55,7 @@ function getColor() {
|
|
|
54
55
|
:variant="variantBtn"
|
|
55
56
|
size="32"
|
|
56
57
|
>
|
|
57
|
-
<
|
|
58
|
-
{{ t('style.fontSize') }}
|
|
59
|
-
</VTooltip>
|
|
58
|
+
<Tooltip :label="t('style.fontSize')" />
|
|
60
59
|
<BtnIcon :icon="`mdiSvg:${mdi.mdiFormatSize}`" />
|
|
61
60
|
</VBtn>
|
|
62
61
|
|
|
@@ -4,17 +4,17 @@ import * as mdi from '@mdi/js'
|
|
|
4
4
|
import { Level } from '@tiptap/extension-heading'
|
|
5
5
|
import { Editor } from '@tiptap/vue-3'
|
|
6
6
|
import BtnIcon from '@tiptapify/components/UI/BtnIcon.vue'
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
import defaults from '@tiptapify/constants/defaults'
|
|
7
|
+
import Tooltip from '@tiptapify/components/UI/Tooltip.vue'
|
|
8
|
+
import { useTiptapifyConfig } from '@tiptapify/composables/useTiptapifyConfig'
|
|
9
|
+
import { inject, Ref } from 'vue'
|
|
11
10
|
|
|
12
11
|
const props = defineProps({
|
|
13
12
|
customHeadingLevels: { type: Array<Level>, default: () => [] },
|
|
14
13
|
withParagraph: { type: Boolean, default: () => true },
|
|
15
|
-
variantBtn: { type: String as PropType<variantBtnTypes>, default() { return defaults.variantBtn } },
|
|
16
14
|
})
|
|
17
15
|
|
|
16
|
+
const { variantBtn } = useTiptapifyConfig()
|
|
17
|
+
|
|
18
18
|
interface MDIIcons { [key: string]: string }
|
|
19
19
|
const mdiIcons = mdi as MDIIcons
|
|
20
20
|
|
|
@@ -35,9 +35,7 @@ setHeadingLevels(props.customHeadingLevels)
|
|
|
35
35
|
size="32"
|
|
36
36
|
@click="editor.chain().focus().redo().run()"
|
|
37
37
|
>
|
|
38
|
-
<
|
|
39
|
-
{{ t('style.heading') }}
|
|
40
|
-
</VTooltip>
|
|
38
|
+
<Tooltip :label="t('style.heading')" hotkey="Mod-Alt-1..6" />
|
|
41
39
|
<BtnIcon :icon="`mdiSvg:${mdi.mdiFormatHeaderPound}`" />
|
|
42
40
|
</VBtn>
|
|
43
41
|
|
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
import * as mdi from '@mdi/js'
|
|
4
4
|
import { Editor } from '@tiptap/vue-3'
|
|
5
5
|
import BtnIcon from '@tiptapify/components/UI/BtnIcon.vue'
|
|
6
|
+
import Tooltip from '@tiptapify/components/UI/Tooltip.vue'
|
|
6
7
|
import StyleColor from '@tiptapify/extensions/components/style/StyleColor.vue'
|
|
7
|
-
import {
|
|
8
|
-
import { computed, inject,
|
|
8
|
+
import { useTiptapifyConfig } from '@tiptapify/composables/useTiptapifyConfig'
|
|
9
|
+
import { computed, inject, Ref } from 'vue'
|
|
9
10
|
import { ComposerTranslation } from 'vue-i18n'
|
|
10
11
|
import { useTheme } from 'vuetify/framework'
|
|
11
12
|
|
|
12
|
-
import defaults from '@tiptapify/constants/defaults'
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
variantBtn: { type: String as PropType<variantBtnTypes>, default() { return defaults.variantBtn } },
|
|
16
|
-
})
|
|
14
|
+
const { variantBtn } = useTiptapifyConfig()
|
|
17
15
|
|
|
18
16
|
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
19
17
|
|
|
@@ -37,9 +35,7 @@ const selectedColor = computed(() => editor.value.getAttributes('highlight').col
|
|
|
37
35
|
:variant="variantBtn"
|
|
38
36
|
size="32"
|
|
39
37
|
>
|
|
40
|
-
<
|
|
41
|
-
{{ t('style.color.highlight') }}
|
|
42
|
-
</VTooltip>
|
|
38
|
+
<Tooltip :label="t('style.color.highlight')" hotkey="Mod-Shift-h" />
|
|
43
39
|
|
|
44
40
|
<BtnIcon :icon="`mdiSvg:${mdi.mdiFormatColorFill}`" />
|
|
45
41
|
<VIcon
|
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
import * as mdi from '@mdi/js'
|
|
4
4
|
import { Editor } from '@tiptap/vue-3'
|
|
5
5
|
import BtnIcon from '@tiptapify/components/UI/BtnIcon.vue'
|
|
6
|
+
import Tooltip from '@tiptapify/components/UI/Tooltip.vue'
|
|
6
7
|
import LineHeight from '@tiptapify/extensions/components/style/lineHeight/LineHeight.vue'
|
|
7
8
|
import { lineHeights } from '@tiptapify/constants/style'
|
|
8
|
-
import {
|
|
9
|
-
import { computed, inject,
|
|
9
|
+
import { useTiptapifyConfig } from '@tiptapify/composables/useTiptapifyConfig'
|
|
10
|
+
import { computed, inject, Ref } from 'vue'
|
|
10
11
|
|
|
11
|
-
import defaults from '@tiptapify/constants/defaults'
|
|
12
12
|
import { ComposerTranslation } from 'vue-i18n'
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
variantBtn: { type: String as PropType<variantBtnTypes>, default() { return defaults.variantBtn } },
|
|
16
|
-
})
|
|
14
|
+
const { variantBtn } = useTiptapifyConfig()
|
|
17
15
|
|
|
18
16
|
const editor = inject('tiptapifyEditor') as Ref<Editor>
|
|
19
17
|
|
|
@@ -53,9 +51,7 @@ function getColor() {
|
|
|
53
51
|
:variant="variantBtn"
|
|
54
52
|
size="32"
|
|
55
53
|
>
|
|
56
|
-
<
|
|
57
|
-
{{ t('style.lineHeight') }}
|
|
58
|
-
</VTooltip>
|
|
54
|
+
<Tooltip :label="t('style.lineHeight')" />
|
|
59
55
|
<BtnIcon :icon="`mdiSvg:${mdi.mdiFormatLineHeight}`" />
|
|
60
56
|
</VBtn>
|
|
61
57
|
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Plugin } from 'vue'
|
|
2
2
|
import Tiptapify from '@tiptapify/components/Tiptapify.vue'
|
|
3
3
|
import TiptapifyDialog from '@tiptapify/components/UI/TiptapifyDialog.vue'
|
|
4
|
+
import { useTiptapifyConfig } from '@tiptapify/composables/useTiptapifyConfig'
|
|
4
5
|
import { TiptapifyFooterAlignment } from '@tiptapify/types/editor'
|
|
5
6
|
import { createAiBackendProvider } from '@tiptapify/extensions/components/ai/backend'
|
|
6
7
|
|
|
@@ -38,6 +39,7 @@ export {
|
|
|
38
39
|
VueNodeViewRenderer,
|
|
39
40
|
Tiptapify,
|
|
40
41
|
TiptapifyDialog,
|
|
42
|
+
useTiptapifyConfig,
|
|
41
43
|
createAiBackendProvider,
|
|
42
44
|
Node,
|
|
43
45
|
Mark,
|
|
@@ -46,6 +48,7 @@ export {
|
|
|
46
48
|
mergeAttributes,
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
export type { TiptapifyConfig } from '@tiptapify/composables/useTiptapifyConfig'
|
|
49
52
|
export type { TiptapifyFooterAlignment }
|
|
50
53
|
export type { CommandProps, InputRuleMatch, PasteRuleMatch }
|
|
51
54
|
export type {
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { hotkeyParts, isMac } from './hotkeys'
|
|
3
|
+
|
|
4
|
+
const MAC_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
|
|
5
|
+
const IPHONE_UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1'
|
|
6
|
+
const WIN_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
|
|
7
|
+
const LINUX_UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
|
|
8
|
+
|
|
9
|
+
function mockUserAgent (userAgent: string) {
|
|
10
|
+
Object.defineProperty(window.navigator, 'userAgent', {
|
|
11
|
+
value: userAgent,
|
|
12
|
+
configurable: true,
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function expectIcon (part: { icon?: string }): void {
|
|
17
|
+
expect(part.icon).toBeDefined()
|
|
18
|
+
expect(part.icon).toContain('mdiSvg:')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('isMac', () => {
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
vi.unstubAllGlobals()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('returns true for macOS user agent', () => {
|
|
27
|
+
mockUserAgent(MAC_UA)
|
|
28
|
+
expect(isMac()).toBe(true)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('returns true for Apple mobile user agents', () => {
|
|
32
|
+
mockUserAgent(IPHONE_UA)
|
|
33
|
+
expect(isMac()).toBe(true)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('returns false for Windows user agent', () => {
|
|
37
|
+
mockUserAgent(WIN_UA)
|
|
38
|
+
expect(isMac()).toBe(false)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('returns false for Linux user agent', () => {
|
|
42
|
+
mockUserAgent(LINUX_UA)
|
|
43
|
+
expect(isMac()).toBe(false)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('returns false when navigator is unavailable', () => {
|
|
47
|
+
vi.stubGlobal('navigator', undefined)
|
|
48
|
+
expect(isMac()).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('hotkeyParts', () => {
|
|
53
|
+
it('renders Mod as Ctrl text on Windows', () => {
|
|
54
|
+
mockUserAgent(WIN_UA)
|
|
55
|
+
expect(hotkeyParts('Mod-b')).toEqual([
|
|
56
|
+
{ label: 'Ctrl' },
|
|
57
|
+
{ label: 'B' },
|
|
58
|
+
])
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('renders Mod as the Command icon on macOS', () => {
|
|
62
|
+
mockUserAgent(MAC_UA)
|
|
63
|
+
const parts = hotkeyParts('Mod-Shift-b')
|
|
64
|
+
expect(parts).toHaveLength(3)
|
|
65
|
+
expect(parts[0].label).toBe('Command')
|
|
66
|
+
expectIcon(parts[0])
|
|
67
|
+
expect(parts[1].label).toBe('Shift')
|
|
68
|
+
expectIcon(parts[1])
|
|
69
|
+
expect(parts[2]).toEqual({ label: 'B' })
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('renders Alt as text on Windows and Linux', () => {
|
|
73
|
+
mockUserAgent(WIN_UA)
|
|
74
|
+
expect(hotkeyParts('Mod-Alt-c')).toEqual([
|
|
75
|
+
{ label: 'Ctrl' },
|
|
76
|
+
{ label: 'Alt' },
|
|
77
|
+
{ label: 'C' },
|
|
78
|
+
])
|
|
79
|
+
mockUserAgent(LINUX_UA)
|
|
80
|
+
expect(hotkeyParts('Mod-Alt-c')).toEqual([
|
|
81
|
+
{ label: 'Ctrl' },
|
|
82
|
+
{ label: 'Alt' },
|
|
83
|
+
{ label: 'C' },
|
|
84
|
+
])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('renders Alt as the Option icon on macOS', () => {
|
|
88
|
+
mockUserAgent(MAC_UA)
|
|
89
|
+
const parts = hotkeyParts('Mod-Alt-c')
|
|
90
|
+
expect(parts[0].label).toBe('Command')
|
|
91
|
+
expectIcon(parts[0])
|
|
92
|
+
expect(parts[1].label).toBe('Option')
|
|
93
|
+
expectIcon(parts[1])
|
|
94
|
+
expect(parts[2]).toEqual({ label: 'C' })
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('renders Enter as the return icon on macOS', () => {
|
|
98
|
+
mockUserAgent(MAC_UA)
|
|
99
|
+
const parts = hotkeyParts('Mod-Enter')
|
|
100
|
+
expect(parts[0].label).toBe('Command')
|
|
101
|
+
expectIcon(parts[0])
|
|
102
|
+
expect(parts[1].label).toBe('Enter')
|
|
103
|
+
expectIcon(parts[1])
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('renders Enter as text on Windows', () => {
|
|
107
|
+
mockUserAgent(WIN_UA)
|
|
108
|
+
expect(hotkeyParts('Mod-Enter')).toEqual([
|
|
109
|
+
{ label: 'Ctrl' },
|
|
110
|
+
{ label: 'Enter' },
|
|
111
|
+
])
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('preserves modifier order', () => {
|
|
115
|
+
mockUserAgent(WIN_UA)
|
|
116
|
+
expect(hotkeyParts('Mod-Shift-s')).toEqual([
|
|
117
|
+
{ label: 'Ctrl' },
|
|
118
|
+
{ label: 'Shift' },
|
|
119
|
+
{ label: 'S' },
|
|
120
|
+
])
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('keeps punctuation keys as is', () => {
|
|
124
|
+
mockUserAgent(WIN_UA)
|
|
125
|
+
expect(hotkeyParts('Mod-,').map(part => part.label)).toEqual(['Ctrl', ','])
|
|
126
|
+
expect(hotkeyParts('Mod-.').map(part => part.label)).toEqual(['Ctrl', '.'])
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('renders numeric ranges with a dash', () => {
|
|
130
|
+
mockUserAgent(WIN_UA)
|
|
131
|
+
expect(hotkeyParts('Mod-Alt-1..6')).toEqual([
|
|
132
|
+
{ label: 'Ctrl' },
|
|
133
|
+
{ label: 'Alt' },
|
|
134
|
+
{ label: '1–6' },
|
|
135
|
+
])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('renders digits as is', () => {
|
|
139
|
+
mockUserAgent(WIN_UA)
|
|
140
|
+
expect(hotkeyParts('Mod-Shift-8')).toEqual([
|
|
141
|
+
{ label: 'Ctrl' },
|
|
142
|
+
{ label: 'Shift' },
|
|
143
|
+
{ label: '8' },
|
|
144
|
+
])
|
|
145
|
+
})
|
|
146
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as mdi from '@mdi/js'
|
|
2
|
+
|
|
3
|
+
export interface HotkeyPart {
|
|
4
|
+
icon?: string
|
|
5
|
+
label: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detects whether the current platform is macOS (or an Apple mobile device).
|
|
10
|
+
* Returns false in non-browser environments (SSR, tests).
|
|
11
|
+
*/
|
|
12
|
+
export function isMac (): boolean {
|
|
13
|
+
if (typeof navigator === 'undefined') {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return /mac|iphone|ipad|ipod/i.test(navigator.userAgent)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function partForKey (key: string, mac: boolean): HotkeyPart {
|
|
21
|
+
const normalized = key.toLowerCase()
|
|
22
|
+
|
|
23
|
+
if (normalized === 'mod') {
|
|
24
|
+
return mac
|
|
25
|
+
? { label: 'Command', icon: `mdiSvg:${mdi.mdiAppleKeyboardCommand}` }
|
|
26
|
+
: { label: 'Ctrl' }
|
|
27
|
+
}
|
|
28
|
+
if (normalized === 'shift') {
|
|
29
|
+
return mac
|
|
30
|
+
? { label: 'Shift', icon: `mdiSvg:${mdi.mdiAppleKeyboardShift}` }
|
|
31
|
+
: { label: 'Shift' }
|
|
32
|
+
}
|
|
33
|
+
if (normalized === 'alt') {
|
|
34
|
+
return mac
|
|
35
|
+
? { label: 'Option', icon: `mdiSvg:${mdi.mdiAppleKeyboardOption}` }
|
|
36
|
+
: { label: 'Alt' }
|
|
37
|
+
}
|
|
38
|
+
if (normalized === 'enter') {
|
|
39
|
+
return mac
|
|
40
|
+
? { label: 'Enter', icon: `mdiSvg:${mdi.mdiKeyboardReturn}` }
|
|
41
|
+
: { label: 'Enter' }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// numeric ranges in "1..6" format are rendered as "1–6"
|
|
45
|
+
const range = normalized.match(/^(\d+)\.\.(\d+)$/)
|
|
46
|
+
if (range) {
|
|
47
|
+
return { label: `${range[1]}–${range[2]}` }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { label: key.toUpperCase() }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Converts a Tiptap-style shortcut string ("Mod-Shift-b", "Mod-Alt-1..6")
|
|
55
|
+
* into display parts for the current platform. On macOS modifier keys
|
|
56
|
+
* get their Apple keyboard glyphs (⌘ ⌥ ⇧ ⏎), on other platforms
|
|
57
|
+
* everything is rendered as text.
|
|
58
|
+
*/
|
|
59
|
+
export function hotkeyParts (shortcut: string): HotkeyPart[] {
|
|
60
|
+
const mac = isMac()
|
|
61
|
+
|
|
62
|
+
return shortcut
|
|
63
|
+
.split('-')
|
|
64
|
+
.map(key => partForKey(key, mac))
|
|
65
|
+
}
|