tiptapify 0.1.5 → 0.1.6
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/dist/tiptapify.mjs +11119 -11108
- package/dist/tiptapify.umd.js +46 -46
- package/index.d.ts +1 -0
- package/package.json +51 -44
- package/src/components/editorExtensions.ts +2 -0
- package/src/constants/style.test.ts +49 -0
- package/src/extensions/PickerEventBus.test.ts +38 -0
- package/src/extensions/components/media/link/LinkDialog.vue +5 -5
- package/src/utils/helpers.test.ts +32 -0
package/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ import '@tiptap/extension-heading'
|
|
|
42
42
|
import '@tiptap/extension-bold'
|
|
43
43
|
import '@tiptap/extension-italic'
|
|
44
44
|
import '@tiptap/extension-strike'
|
|
45
|
+
import '@tiptap/extension-link'
|
|
45
46
|
import '@tiptap/extension-code'
|
|
46
47
|
import '@tiptap/extension-blockquote'
|
|
47
48
|
import '@tiptap/extension-hard-break'
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tiptapify",
|
|
3
3
|
"types": "./index.d.ts",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6",
|
|
5
5
|
"description": "Tiptap3 editor with Vuetify3 menu implementation",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"dev": "vite",
|
|
31
31
|
"build": "vite build",
|
|
32
32
|
"lint": "eslint resources/ts --fix --ext .ts,.js,.cjs,.vue,.tsx,.jsx",
|
|
33
|
-
"test": "
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest",
|
|
34
35
|
"build:charmap": "tsx build-charmap.ts",
|
|
35
36
|
"build:emojis": "tsx build-emojis.ts",
|
|
36
37
|
"docs:dev": "pnpm --filter tiptapify-docs docs:dev",
|
|
@@ -55,6 +56,10 @@
|
|
|
55
56
|
"material design icons",
|
|
56
57
|
"wysiwyg",
|
|
57
58
|
"wysiwyg toolbar",
|
|
59
|
+
"rich text editor",
|
|
60
|
+
"vue editor",
|
|
61
|
+
"vue rich text",
|
|
62
|
+
"vue wysiwyg",
|
|
58
63
|
"markdown",
|
|
59
64
|
"emoji",
|
|
60
65
|
"emoticons"
|
|
@@ -65,45 +70,45 @@
|
|
|
65
70
|
"packageManager": "pnpm@11.8.0",
|
|
66
71
|
"dependencies": {
|
|
67
72
|
"@floating-ui/dom": "^1.7.6",
|
|
68
|
-
"@tiptap/core": "^3.
|
|
69
|
-
"@tiptap/extension-blockquote": "^3.
|
|
70
|
-
"@tiptap/extension-bold": "^3.
|
|
71
|
-
"@tiptap/extension-bubble-menu": "^3.
|
|
72
|
-
"@tiptap/extension-code": "^3.
|
|
73
|
-
"@tiptap/extension-code-block": "^3.
|
|
74
|
-
"@tiptap/extension-code-block-lowlight": "^3.
|
|
75
|
-
"@tiptap/extension-color": "^3.
|
|
76
|
-
"@tiptap/extension-document": "^3.
|
|
77
|
-
"@tiptap/extension-floating-menu": "^3.
|
|
78
|
-
"@tiptap/extension-font-family": "^3.
|
|
79
|
-
"@tiptap/extension-hard-break": "^3.
|
|
80
|
-
"@tiptap/extension-heading": "^3.
|
|
81
|
-
"@tiptap/extension-highlight": "^3.
|
|
82
|
-
"@tiptap/extension-horizontal-rule": "^3.
|
|
83
|
-
"@tiptap/extension-image": "^3.
|
|
84
|
-
"@tiptap/extension-invisible-characters": "^3.
|
|
85
|
-
"@tiptap/extension-italic": "^3.
|
|
86
|
-
"@tiptap/extension-link": "^3.
|
|
87
|
-
"@tiptap/extension-list": "^3.
|
|
88
|
-
"@tiptap/extension-list-item": "^3.
|
|
89
|
-
"@tiptap/extension-paragraph": "^3.
|
|
90
|
-
"@tiptap/extension-placeholder": "^3.
|
|
91
|
-
"@tiptap/extension-strike": "^3.
|
|
92
|
-
"@tiptap/extension-subscript": "^3.
|
|
93
|
-
"@tiptap/extension-superscript": "^3.
|
|
94
|
-
"@tiptap/extension-table": "^3.
|
|
95
|
-
"@tiptap/extension-task-item": "^3.
|
|
96
|
-
"@tiptap/extension-task-list": "^3.
|
|
97
|
-
"@tiptap/extension-text": "^3.
|
|
98
|
-
"@tiptap/extension-text-align": "^3.
|
|
99
|
-
"@tiptap/extension-text-style": "^3.
|
|
100
|
-
"@tiptap/extension-typography": "^3.
|
|
101
|
-
"@tiptap/extension-underline": "^3.
|
|
102
|
-
"@tiptap/extension-youtube": "^3.
|
|
103
|
-
"@tiptap/extensions": "^3.
|
|
104
|
-
"@tiptap/pm": "^3.
|
|
105
|
-
"@tiptap/suggestion": "^3.
|
|
106
|
-
"@tiptap/vue-3": "^3.
|
|
73
|
+
"@tiptap/core": "^3.27.1",
|
|
74
|
+
"@tiptap/extension-blockquote": "^3.27.1",
|
|
75
|
+
"@tiptap/extension-bold": "^3.27.1",
|
|
76
|
+
"@tiptap/extension-bubble-menu": "^3.27.1",
|
|
77
|
+
"@tiptap/extension-code": "^3.27.1",
|
|
78
|
+
"@tiptap/extension-code-block": "^3.27.1",
|
|
79
|
+
"@tiptap/extension-code-block-lowlight": "^3.27.1",
|
|
80
|
+
"@tiptap/extension-color": "^3.27.1",
|
|
81
|
+
"@tiptap/extension-document": "^3.27.1",
|
|
82
|
+
"@tiptap/extension-floating-menu": "^3.27.1",
|
|
83
|
+
"@tiptap/extension-font-family": "^3.27.1",
|
|
84
|
+
"@tiptap/extension-hard-break": "^3.27.1",
|
|
85
|
+
"@tiptap/extension-heading": "^3.27.1",
|
|
86
|
+
"@tiptap/extension-highlight": "^3.27.1",
|
|
87
|
+
"@tiptap/extension-horizontal-rule": "^3.27.1",
|
|
88
|
+
"@tiptap/extension-image": "^3.27.1",
|
|
89
|
+
"@tiptap/extension-invisible-characters": "^3.27.1",
|
|
90
|
+
"@tiptap/extension-italic": "^3.27.1",
|
|
91
|
+
"@tiptap/extension-link": "^3.27.1",
|
|
92
|
+
"@tiptap/extension-list": "^3.27.1",
|
|
93
|
+
"@tiptap/extension-list-item": "^3.27.1",
|
|
94
|
+
"@tiptap/extension-paragraph": "^3.27.1",
|
|
95
|
+
"@tiptap/extension-placeholder": "^3.27.1",
|
|
96
|
+
"@tiptap/extension-strike": "^3.27.1",
|
|
97
|
+
"@tiptap/extension-subscript": "^3.27.1",
|
|
98
|
+
"@tiptap/extension-superscript": "^3.27.1",
|
|
99
|
+
"@tiptap/extension-table": "^3.27.1",
|
|
100
|
+
"@tiptap/extension-task-item": "^3.27.1",
|
|
101
|
+
"@tiptap/extension-task-list": "^3.27.1",
|
|
102
|
+
"@tiptap/extension-text": "^3.27.1",
|
|
103
|
+
"@tiptap/extension-text-align": "^3.27.1",
|
|
104
|
+
"@tiptap/extension-text-style": "^3.27.1",
|
|
105
|
+
"@tiptap/extension-typography": "^3.27.1",
|
|
106
|
+
"@tiptap/extension-underline": "^3.27.1",
|
|
107
|
+
"@tiptap/extension-youtube": "^3.27.1",
|
|
108
|
+
"@tiptap/extensions": "^3.27.1",
|
|
109
|
+
"@tiptap/pm": "^3.27.1",
|
|
110
|
+
"@tiptap/suggestion": "^3.27.1",
|
|
111
|
+
"@tiptap/vue-3": "^3.27.1",
|
|
107
112
|
"emoji.json": "^16.0.0",
|
|
108
113
|
"highlight.js": "^11.11.1",
|
|
109
114
|
"linkifyjs": "^4.3.3",
|
|
@@ -116,8 +121,8 @@
|
|
|
116
121
|
"vuetify": "^3.8.5 || ^4.0.0"
|
|
117
122
|
},
|
|
118
123
|
"devDependencies": {
|
|
119
|
-
"@intlify/unplugin-vue-i18n": "^11.2.
|
|
120
|
-
"@types/node": "^
|
|
124
|
+
"@intlify/unplugin-vue-i18n": "^11.2.4",
|
|
125
|
+
"@types/node": "^26.0.0",
|
|
121
126
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
122
127
|
"@vitejs/plugin-vue-jsx": "^5.1.5",
|
|
123
128
|
"eslint": "^10.5.0",
|
|
@@ -127,8 +132,9 @@
|
|
|
127
132
|
"eslint-plugin-promise": "^7.3.0",
|
|
128
133
|
"eslint-plugin-regexp": "3.1.0",
|
|
129
134
|
"eslint-plugin-sonarjs": "2.0.4",
|
|
130
|
-
"eslint-plugin-unicorn": "^
|
|
135
|
+
"eslint-plugin-unicorn": "^68.0.0",
|
|
131
136
|
"eslint-plugin-vue": "^10.9.2",
|
|
137
|
+
"jsdom": "^29.1.1",
|
|
132
138
|
"sass-embedded": "^1.100.0",
|
|
133
139
|
"stylelint": "^17.13.0",
|
|
134
140
|
"stylelint-config-standard-scss": "17.0.0",
|
|
@@ -139,6 +145,7 @@
|
|
|
139
145
|
"vite": "^8.0.16",
|
|
140
146
|
"vite-plugin-vuetify": "^2.1.3",
|
|
141
147
|
"vite-svg-loader": "^5.1.1",
|
|
148
|
+
"vitest": "^4.1.9",
|
|
142
149
|
"vue-eslint-parser": "^10.4.1",
|
|
143
150
|
"vue-tsc": "^3.3.5"
|
|
144
151
|
}
|
|
@@ -9,6 +9,7 @@ import { Heading } from '@tiptap/extension-heading'
|
|
|
9
9
|
import { Bold } from '@tiptap/extension-bold'
|
|
10
10
|
import { Italic } from '@tiptap/extension-italic'
|
|
11
11
|
import { Strike } from '@tiptap/extension-strike'
|
|
12
|
+
import { Link } from '@tiptap/extension-link'
|
|
12
13
|
import { Code } from '@tiptap/extension-code'
|
|
13
14
|
import { Blockquote } from '@tiptap/extension-blockquote'
|
|
14
15
|
import { HardBreak } from '@tiptap/extension-hard-break'
|
|
@@ -61,6 +62,7 @@ export function editorExtensions (placeholder: string, slashCommands: SlashComma
|
|
|
61
62
|
Bold,
|
|
62
63
|
Italic,
|
|
63
64
|
Strike,
|
|
65
|
+
Link,
|
|
64
66
|
Blockquote,
|
|
65
67
|
OrderedList,
|
|
66
68
|
BulletList,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { setHeadingLevels, headingLevels, fontSizes, defaultFontSize, lineHeights, defaultLineHeight } from './style'
|
|
3
|
+
|
|
4
|
+
describe('style constants', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
headingLevels.value = [1, 2, 3, 4, 5, 6]
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('sets custom heading levels', () => {
|
|
10
|
+
setHeadingLevels([2, 3, 4])
|
|
11
|
+
expect(headingLevels.value).toEqual([2, 3, 4])
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('does not change heading levels on empty array', () => {
|
|
15
|
+
setHeadingLevels([])
|
|
16
|
+
expect(headingLevels.value).toEqual([1, 2, 3, 4, 5, 6])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('rejects levels below 1', () => {
|
|
20
|
+
expect(() => setHeadingLevels([0])).toThrow('customHeadingLevels must be between 1 and 6')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('rejects levels above 6', () => {
|
|
24
|
+
expect(() => setHeadingLevels([7])).toThrow('customHeadingLevels must be between 1 and 6')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('rejects any invalid level in a mixed array', () => {
|
|
28
|
+
expect(() => setHeadingLevels([1, 2, 7])).toThrow('customHeadingLevels must be between 1 and 6')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('has valid fontSizes constant', () => {
|
|
32
|
+
expect(fontSizes).toContain(12)
|
|
33
|
+
expect(fontSizes).toContain(6)
|
|
34
|
+
expect(fontSizes).toContain(96)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('has valid defaultFontSize', () => {
|
|
38
|
+
expect(defaultFontSize).toBe(12)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('has valid lineHeights constant', () => {
|
|
42
|
+
expect(lineHeights).toContain(1)
|
|
43
|
+
expect(lineHeights).toContain(4)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('has valid defaultLineHeight', () => {
|
|
47
|
+
expect(defaultLineHeight).toBe(1)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { PickerEventBus } from './PickerEventBus'
|
|
3
|
+
|
|
4
|
+
describe('PickerEventBus', () => {
|
|
5
|
+
it('calls registered callback on emit', () => {
|
|
6
|
+
const callback = vi.fn()
|
|
7
|
+
PickerEventBus.on('close', callback)
|
|
8
|
+
PickerEventBus.emit('close', { type: 'emoji' })
|
|
9
|
+
expect(callback).toHaveBeenCalledWith({ type: 'emoji' })
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('does not call callback after off', () => {
|
|
13
|
+
const callback = vi.fn()
|
|
14
|
+
PickerEventBus.on('close', callback)
|
|
15
|
+
PickerEventBus.off('close', callback)
|
|
16
|
+
PickerEventBus.emit('close', { type: 'charmap' })
|
|
17
|
+
expect(callback).not.toHaveBeenCalled()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('supports multiple listeners', () => {
|
|
21
|
+
const callback1 = vi.fn()
|
|
22
|
+
const callback2 = vi.fn()
|
|
23
|
+
PickerEventBus.on('close', callback1)
|
|
24
|
+
PickerEventBus.on('close', callback2)
|
|
25
|
+
PickerEventBus.emit('close', { type: 'emoji' })
|
|
26
|
+
expect(callback1).toHaveBeenCalledTimes(1)
|
|
27
|
+
expect(callback2).toHaveBeenCalledTimes(1)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('does not throw when emitting with no listeners', () => {
|
|
31
|
+
expect(() => PickerEventBus.emit('close', { type: 'emoji' })).not.toThrow()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('does not throw when removing unregistered listener', () => {
|
|
35
|
+
const callback = vi.fn()
|
|
36
|
+
expect(() => PickerEventBus.off('close', callback)).not.toThrow()
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -21,7 +21,7 @@ const generateLinkAttrs = () => ({
|
|
|
21
21
|
href: '',
|
|
22
22
|
target: targetAttrs.value[0],
|
|
23
23
|
cssClass: '',
|
|
24
|
-
rel:
|
|
24
|
+
rel: []
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
const relAttrs = ['alternate', 'author', 'bookmark', 'external', 'help', 'license', 'me', 'next', 'nofollow', 'noopener', 'noreferrer', 'opener', 'prev', 'privacy-policy', 'search', 'tag', 'terms-of-service']
|
|
@@ -43,10 +43,10 @@ const isDisabled = computed(() => {
|
|
|
43
43
|
|
|
44
44
|
function apply() {
|
|
45
45
|
let { href, target, rel, cssClass } = attrs.value
|
|
46
|
-
|
|
46
|
+
const relStr = rel?.length ? rel.join(' ') : null
|
|
47
47
|
|
|
48
48
|
if (href) {
|
|
49
|
-
editor.value.chain().focus().extendMarkRange('link').setLink({ href, target: target.value, rel, class: cssClass }).run()
|
|
49
|
+
editor.value.chain().focus().extendMarkRange('link').setLink({ href, target: target.value, rel: relStr, class: cssClass }).run()
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
close()
|
|
@@ -61,7 +61,7 @@ function clear() {
|
|
|
61
61
|
function close() {
|
|
62
62
|
attrs.value = generateLinkAttrs()
|
|
63
63
|
|
|
64
|
-
dialog.value
|
|
64
|
+
dialog.value?.close()
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
const showLink = (event: CustomEvent) => {
|
|
@@ -74,7 +74,7 @@ const showLink = (event: CustomEvent) => {
|
|
|
74
74
|
attrs.value.rel = event.detail.link?.rel?.split(' ')
|
|
75
75
|
attrs.value.cssClass = event.detail.link?.class
|
|
76
76
|
|
|
77
|
-
dialog.value
|
|
77
|
+
dialog.value?.open()
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
onMounted(() => {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import helpers from './helpers'
|
|
3
|
+
|
|
4
|
+
describe('ucFirst', () => {
|
|
5
|
+
it('capitalizes the first character of a string', () => {
|
|
6
|
+
expect(helpers.ucFirst('hello')).toBe('Hello')
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('returns empty string for empty input', () => {
|
|
10
|
+
expect(helpers.ucFirst('')).toBe('')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('converts snake_case to words and capitalizes', () => {
|
|
14
|
+
expect(helpers.ucFirst('hello_world', true)).toBe('Hello world')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('converts kebab-case to words and capitalizes', () => {
|
|
18
|
+
expect(helpers.ucFirst('hello-world', false, true)).toBe('Hello world')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('converts both snake_case and kebab-case', () => {
|
|
22
|
+
expect(helpers.ucFirst('hello_world-test', true, true)).toBe('Hello world test')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('handles already capitalized string', () => {
|
|
26
|
+
expect(helpers.ucFirst('Hello')).toBe('Hello')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('handles single character', () => {
|
|
30
|
+
expect(helpers.ucFirst('a')).toBe('A')
|
|
31
|
+
})
|
|
32
|
+
})
|