tiptapify 0.3.3 → 0.3.5
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 -0
- package/dist/tiptapify.css +1 -1
- package/dist/tiptapify.mjs +15909 -15553
- package/dist/tiptapify.umd.js +55 -55
- package/index.d.ts +95 -0
- package/package.json +2 -1
- package/src/components/ImageBubbleMenu.vue +16 -7
- package/src/components/MenuBubble.vue +45 -7
- package/src/components/SkinScope.test.ts +74 -0
- package/src/components/SkinScope.vue +18 -0
- package/src/components/Tiptapify.skin.test.ts +186 -0
- package/src/components/Tiptapify.vue +115 -21
- package/src/components/Toolbar/Index.vue +43 -62
- package/src/components/Toolbar/Items.test.ts +58 -2
- package/src/components/Toolbar/Items.vue +93 -7
- package/src/components/UI/TiptapifyDialog.test.ts +132 -0
- package/src/components/UI/TiptapifyDialog.vue +90 -40
- package/src/composables/useTiptapifyConfig.test.ts +25 -2
- package/src/composables/useTiptapifyConfig.ts +21 -2
- package/src/constants/defaults.ts +2 -1
- package/src/extensions/components/alignment/menu/Button.vue +21 -17
- package/src/extensions/components/list/bullet/Button.vue +20 -16
- package/src/extensions/components/list/styles/ListStyleButton.vue +25 -25
- package/src/extensions/components/style/heading/Button.vue +33 -29
- package/src/index.test.ts +88 -0
- package/src/index.ts +15 -0
- package/src/skins/__fixtures__/skin-baseline.json +805 -0
- package/src/skins/builtins.ts +81 -0
- package/src/skins/menu.css +87 -0
- package/src/skins/menu.ts +10 -0
- package/src/skins/registry.test.ts +180 -0
- package/src/skins/registry.ts +132 -0
- package/src/skins/tokens.reachability.test.ts +43 -0
- package/src/skins/tokens.ts +92 -0
- package/src/types/editor.ts +99 -0
- package/src/utils/validateHref.test.ts +25 -0
- package/src/utils/validateHref.ts +17 -13
package/src/types/editor.ts
CHANGED
|
@@ -11,6 +11,105 @@ export type variantFieldTypes = 'outlined' | 'plain' | 'filled' | 'solo' | 'solo
|
|
|
11
11
|
|
|
12
12
|
export type TiptapifyFooterAlignment = 'start' | 'center' | 'end'
|
|
13
13
|
|
|
14
|
+
// The skin of the editor chrome: 'default' is the classic look, 'pastel' is the quiet 0.3.3 look
|
|
15
|
+
export type TiptapifySkin = 'default' | 'pastel'
|
|
16
|
+
|
|
17
|
+
// Structural variants a token cannot express (component markup, vuetify prop bindings).
|
|
18
|
+
// The shape decides which skin rule sets apply; the tokens paint them.
|
|
19
|
+
export type TiptapifyShape = {
|
|
20
|
+
// VToolbar elevation 1 + no inner hairline vs flat + tinted bar
|
|
21
|
+
toolbar?: 'raised' | 'flat'
|
|
22
|
+
// toolbar sections as raised boxes vs plain flex runs
|
|
23
|
+
groups?: 'box' | 'flat'
|
|
24
|
+
// native vuetify buttons vs ghost buttons with a neutral pressed state
|
|
25
|
+
buttons?: 'vuetify' | 'quiet'
|
|
26
|
+
// bubble menu: VCard + divided VBtnGroup vs flat row of quiet buttons
|
|
27
|
+
bubble?: 'card' | 'flat'
|
|
28
|
+
// divider under the title, elevated close, field defaults vs soft card, text close, outlined defaults
|
|
29
|
+
dialog?: 'classic' | 'soft'
|
|
30
|
+
// dropdown menus: vuetify list vs the light padded card
|
|
31
|
+
menu?: 'vuetify' | 'soft'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The shape after resolution: every key set (missing keys fall back to classic)
|
|
35
|
+
export type ResolvedShape = Required<TiptapifyShape>
|
|
36
|
+
|
|
37
|
+
export type TiptapifySkinColors = {
|
|
38
|
+
toolbarBg: string
|
|
39
|
+
toolbarBorderBottom: string
|
|
40
|
+
toolbarFg: string
|
|
41
|
+
groupBg: string
|
|
42
|
+
groupShadow: string
|
|
43
|
+
dividerColor: string
|
|
44
|
+
buttonFg: string
|
|
45
|
+
buttonHoverBg: string
|
|
46
|
+
buttonHoverFg: string
|
|
47
|
+
buttonActiveBg: string
|
|
48
|
+
buttonActiveHoverBg: string
|
|
49
|
+
bubbleBg: string
|
|
50
|
+
bubbleBorder: string
|
|
51
|
+
bubbleShadow: string
|
|
52
|
+
menuBorder: string
|
|
53
|
+
menuShadow: string
|
|
54
|
+
menuItemActiveBg: string
|
|
55
|
+
dialogShadow: string
|
|
56
|
+
frameBg: string
|
|
57
|
+
frameShadow: string
|
|
58
|
+
frameBorder: string
|
|
59
|
+
imageBg: string
|
|
60
|
+
imageOutline: string
|
|
61
|
+
imageOutlineHover: string
|
|
62
|
+
handleBg: string
|
|
63
|
+
handleBgHover: string
|
|
64
|
+
handleBorder: string
|
|
65
|
+
handleShadow: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type TiptapifySkinMetrics = {
|
|
69
|
+
itemsPadding: string
|
|
70
|
+
itemsGap: string
|
|
71
|
+
itemsRowGap: string
|
|
72
|
+
groupGap: string
|
|
73
|
+
groupHeight: string
|
|
74
|
+
groupRadius: string
|
|
75
|
+
dividerWidth: string
|
|
76
|
+
dividerHeight: string
|
|
77
|
+
buttonRadius: string
|
|
78
|
+
bubbleRadius: string
|
|
79
|
+
bubblePadding: string
|
|
80
|
+
bubbleGap: string
|
|
81
|
+
menuListPadding: string
|
|
82
|
+
menuRadius: string
|
|
83
|
+
menuItemRadius: string
|
|
84
|
+
menuItemPadding: string
|
|
85
|
+
menuItemMinHeight: string
|
|
86
|
+
dialogRadius: string
|
|
87
|
+
dialogHeaderPadding: string
|
|
88
|
+
dialogContentPadding: string
|
|
89
|
+
dialogActionsPadding: string
|
|
90
|
+
dialogTitleSize: string
|
|
91
|
+
dialogTitleWeight: string
|
|
92
|
+
dialogFieldRadius: string
|
|
93
|
+
dialogActionBtnMinHeight: string
|
|
94
|
+
dialogActionBtnRadius: string
|
|
95
|
+
dialogGridGap: string
|
|
96
|
+
imageRadius: string
|
|
97
|
+
handleRadius: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// A skin as data: token overrides + shape flags, inheriting from a registered skin
|
|
101
|
+
export type TiptapifySkinConfig = TiptapifyShape & {
|
|
102
|
+
name?: string
|
|
103
|
+
// registered skin name; implicit 'default'
|
|
104
|
+
extends?: string
|
|
105
|
+
colors?: Partial<TiptapifySkinColors>
|
|
106
|
+
metrics?: Partial<TiptapifySkinMetrics>
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// The `skin` prop: a registered name (string) or an inline object; inline objects
|
|
110
|
+
// resolve through the same chain and never enter the global registry
|
|
111
|
+
export type TiptapifySkinInput = string | TiptapifySkinConfig
|
|
112
|
+
|
|
14
113
|
export type TiptapifyAiMode = 'insert' | 'replace' | 'append'
|
|
15
114
|
|
|
16
115
|
export type TiptapifyAiPromptExample = {
|
|
@@ -30,6 +30,23 @@ describe('isValidHref', () => {
|
|
|
30
30
|
expect(isValidHref('https://example.com/a/b?q=1-2_3%40&r=99')).toBe(true)
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
+
it('accepts facebook post, photo and permalink URLs', () => {
|
|
34
|
+
expect(isValidHref('https://www.facebook.com/user.name/posts/pfbid02abcXYZ123')).toBe(true)
|
|
35
|
+
expect(isValidHref('https://www.facebook.com/photo/?fbid=123456&set=a.789')).toBe(true)
|
|
36
|
+
expect(isValidHref('https://m.facebook.com/permalink.php?story_fbid=pfbid0abc&id=100064')).toBe(true)
|
|
37
|
+
expect(isValidHref('https://fb.watch/abc_DEF12/')).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('accepts dots, underscores and other URL characters in the path', () => {
|
|
41
|
+
expect(isValidHref('https://example.com/docs/file_v2.pdf')).toBe(true)
|
|
42
|
+
expect(isValidHref('https://example.com/~user/a+b,c:d')).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('accepts a hyphenated or non-latin domain', () => {
|
|
46
|
+
expect(isValidHref('https://my-site.com')).toBe(true)
|
|
47
|
+
expect(isValidHref('https://ліга.укр/новини')).toBe(true)
|
|
48
|
+
})
|
|
49
|
+
|
|
33
50
|
it('accepts mailto links', () => {
|
|
34
51
|
expect(isValidHref('mailto:test@example.com')).toBe(true)
|
|
35
52
|
})
|
|
@@ -62,6 +79,14 @@ describe('isValidHref', () => {
|
|
|
62
79
|
expect(isValidHref('https://exa mple.com')).toBe(false)
|
|
63
80
|
})
|
|
64
81
|
|
|
82
|
+
it('rejects an http URL without slashes after the protocol', () => {
|
|
83
|
+
expect(isValidHref('https:example.com')).toBe(false)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('rejects a domain with an empty label', () => {
|
|
87
|
+
expect(isValidHref('https://example..com')).toBe(false)
|
|
88
|
+
})
|
|
89
|
+
|
|
65
90
|
it('rejects plain text', () => {
|
|
66
91
|
expect(isValidHref('not a url')).toBe(false)
|
|
67
92
|
})
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
const azAZ09 = 'a-zA-Z0-9'
|
|
2
2
|
|
|
3
|
-
const regexHrefProto = 'https?:\\/\\/'
|
|
4
|
-
const regexHrefAuth = `([${azAZ09}]+(:[${azAZ09}]+)?@)?`
|
|
5
|
-
const regexHrefDomain = `[${azAZ09}]+(\\.[${azAZ09}]+(-?[${azAZ09}]+)?)+`
|
|
6
|
-
const regexHrefPath = `(\\/[${azAZ09}\\-]+)*\\/?`
|
|
7
|
-
const regexHrefFragment = '(#[^\\s]*)?'
|
|
8
|
-
const regexHrefQueryParam = `(\\?[${azAZ09}\\-_]+((\\[[${azAZ09}]+\\])?=[${azAZ09}\\-_%]+)?)?`
|
|
9
|
-
const regexHrefQueryParamExtra = `(&[${azAZ09}\\-_]+((\\[[${azAZ09}]+\\])?=[${azAZ09}\\-_%]+)?)*`
|
|
10
|
-
const regexHref = `${regexHrefProto}${regexHrefAuth}${regexHrefDomain}${regexHrefPath}${regexHrefFragment}${regexHrefQueryParam}${regexHrefQueryParamExtra}`
|
|
11
|
-
|
|
12
3
|
const regexMailto = `mailto:\\w+@[${azAZ09}]+(\\.[${azAZ09}]+)*`
|
|
13
4
|
const regexTel = 'tel:\\+?[0-9]+'
|
|
14
5
|
|
|
15
|
-
const
|
|
6
|
+
const hrefRegex = new RegExp(`^(${regexMailto}|${regexTel})$`, 'i')
|
|
7
|
+
|
|
8
|
+
const httpPrefixRegex = /^https?:\/\//i
|
|
9
|
+
const hostnameRegex = /^[^.]+(\.[^.]+)+$/
|
|
10
|
+
|
|
11
|
+
function isValidHttpUrl(href: string): boolean {
|
|
12
|
+
if (!httpPrefixRegex.test(href) || /\s/.test(href)) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
try {
|
|
17
|
+
return hostnameRegex.test(new URL(href).hostname)
|
|
18
|
+
} catch {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
}
|
|
18
22
|
|
|
19
23
|
export function isValidHref(href: string): boolean {
|
|
20
|
-
return hrefRegex.test(href || '')
|
|
21
|
-
}
|
|
24
|
+
return isValidHttpUrl(href || '') || hrefRegex.test(href || '')
|
|
25
|
+
}
|