tiptapify 0.0.35 → 0.1.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.
Files changed (59) hide show
  1. package/README.md +12 -6
  2. package/package.json +7 -3
  3. package/src/components/Tiptapify.vue +95 -2
  4. package/src/components/Toolbar/Index.vue +10 -0
  5. package/src/components/Toolbar/Items.vue +17 -13
  6. package/src/components/Toolbar/media.ts +5 -0
  7. package/src/components/editorExtensions.ts +15 -6
  8. package/src/components/index.ts +2 -1
  9. package/src/extensions/PickerEventBus.ts +32 -0
  10. package/src/extensions/charmap/arrows.ts +1227 -0
  11. package/src/extensions/charmap/box_drawing.ts +324 -0
  12. package/src/extensions/charmap/currency.ts +157 -0
  13. package/src/extensions/charmap/cyrillic.ts +646 -0
  14. package/src/extensions/charmap/diacritics.ts +107 -0
  15. package/src/extensions/charmap/extended_letters.ts +1311 -0
  16. package/src/extensions/charmap/greek.ts +443 -0
  17. package/src/extensions/charmap/hebrew.ts +177 -0
  18. package/src/extensions/charmap/index.ts +75 -0
  19. package/src/extensions/charmap/math.ts +2949 -0
  20. package/src/extensions/charmap/punctuation.ts +121 -0
  21. package/src/extensions/charmap/symbols.ts +506 -0
  22. package/src/extensions/charmap/typography.ts +499 -0
  23. package/src/extensions/components/media/charmap/Button.vue +27 -0
  24. package/src/extensions/components/media/charmap/Picker.vue +229 -0
  25. package/src/extensions/components/media/emoji/Button.vue +6 -147
  26. package/src/extensions/components/media/emoji/Picker.vue +225 -0
  27. package/src/extensions/components/media/image/ImageDialog.vue +69 -27
  28. package/src/extensions/components/slashCommands/CommandsList.vue +65 -22
  29. package/src/extensions/components/slashCommands/PickerDialog.vue +44 -0
  30. package/src/extensions/components/slashCommands/suggestion.ts +152 -105
  31. package/src/extensions/slash-commands.ts +169 -9
  32. package/src/i18n/locales/ar.json +37 -14
  33. package/src/i18n/locales/ch.json +37 -14
  34. package/src/i18n/locales/cz.json +37 -14
  35. package/src/i18n/locales/de.json +37 -14
  36. package/src/i18n/locales/en.json +34 -12
  37. package/src/i18n/locales/es.json +37 -14
  38. package/src/i18n/locales/fi.json +37 -14
  39. package/src/i18n/locales/fr.json +37 -14
  40. package/src/i18n/locales/hu.json +37 -14
  41. package/src/i18n/locales/it.json +37 -14
  42. package/src/i18n/locales/ja.json +37 -14
  43. package/src/i18n/locales/ko.json +37 -14
  44. package/src/i18n/locales/la.json +37 -14
  45. package/src/i18n/locales/lt.json +37 -14
  46. package/src/i18n/locales/nl.json +37 -14
  47. package/src/i18n/locales/pl.json +37 -14
  48. package/src/i18n/locales/pt.json +37 -14
  49. package/src/i18n/locales/ru.json +37 -14
  50. package/src/i18n/locales/se.json +37 -14
  51. package/src/i18n/locales/th.json +37 -14
  52. package/src/i18n/locales/tr.json +37 -14
  53. package/src/i18n/locales/{ua.json → uk.json} +37 -14
  54. package/src/i18n/locales/vi.json +37 -14
  55. package/src/types/slashCommandsTypes.ts +19 -0
  56. package/src/types/toolbarTypes.ts +1 -1
  57. package/dist/tiptapify.css +0 -1
  58. package/dist/tiptapify.mjs +0 -82239
  59. package/dist/tiptapify.umd.js +0 -202
package/README.md CHANGED
@@ -1,13 +1,19 @@
1
1
  # Tiptapify
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/tiptapify.svg)](https://www.npmjs.com/package/tiptapify)
4
+ [![npm](https://img.shields.io/npm/dw/tiptapify.svg)](https://www.npmjs.com/package/tiptapify)
5
+ [![GitHub](https://img.shields.io/github/license/ivoyt/tiptapify)](./LICENSE)
6
+
3
7
  ---
4
8
 
5
- [Tiptap](https://tiptap.dev) 3 editor for Vue3 with [Vuetify](https://vuetifyjs.com) toolbar implementation
9
+ [Tiptap 3 Editor](https://tiptap.dev) [Vuetify](https://vuetifyjs.com) toolbar implementation
6
10
 
7
11
  ## Status
8
- *Alpha*
12
+ *Beta*
13
+
14
+ ## Live Demo
9
15
 
10
- *Not production ready (yet) - may contain bugs and internal logic may change*
16
+ [View Documentation & Demo](https://ivoyt.github.io/tiptapify)
11
17
 
12
18
  ## Requirements
13
19
  - Vue 3.x
@@ -155,11 +161,11 @@ Found a bug or have ideas on improvement? Feel free to [create a ticket](https:/
155
161
  - [x] filter option in emoji extension
156
162
  - [x] option to provide custom extension
157
163
  - [x] iframe extension
158
- - [ ] charmap extension
164
+ - [x] charmap extension
165
+ - [x] demo
166
+ - [x] documentation
159
167
  - [ ] extended video extensions
160
168
  - [ ] print hotkey in a tooltip
161
- - [ ] demo
162
- - [ ] documentation
163
169
  - [ ] AI features
164
170
 
165
171
  ## Licence
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tiptapify",
3
3
  "types": "./index.d.ts",
4
- "version": "0.0.35",
4
+ "version": "0.1.0",
5
5
  "description": "Tiptap3 editor with Vuetify3 menu implementation",
6
6
  "exports": {
7
7
  ".": {
@@ -30,7 +30,11 @@
30
30
  "dev": "vite",
31
31
  "build": "vite build",
32
32
  "test": "echo \"Error: no test specified\" && exit 1",
33
- "build:emojis": "tsx build-emojis.ts"
33
+ "build:charmap": "tsx build-charmap.ts",
34
+ "build:emojis": "tsx build-emojis.ts",
35
+ "docs:dev": "pnpm --filter tiptapify-docs docs:dev",
36
+ "docs:build": "pnpm --filter tiptapify-docs docs:build",
37
+ "docs:preview": "pnpm --filter tiptapify-docs docs:preview"
34
38
  },
35
39
  "keywords": [
36
40
  "vue",
@@ -57,7 +61,7 @@
57
61
  "author": "Igor Voytovich",
58
62
  "license": "MIT",
59
63
  "repository": "https://github.com/IVoyt/tiptapify",
60
- "packageManager": "pnpm@10.32.1",
64
+ "packageManager": "pnpm@10.33.4",
61
65
  "dependencies": {
62
66
  "@tiptap/core": "^3.20.4",
63
67
  "@tiptap/extension-blockquote": "^3.20.4",
@@ -2,6 +2,7 @@
2
2
 
3
3
  import defaults from "@tiptapify/constants/defaults";
4
4
  import { itemsPropType, toolbarSections } from "@tiptapify/types/toolbarTypes";
5
+ import { SlashCommandsConfig } from "@tiptapify/types/slashCommandsTypes";
5
6
  import { computed, onBeforeUnmount, PropType, provide, ref, ShallowRef, watch } from "vue";
6
7
  import { default as Toolbar } from "@tiptapify/components/Toolbar/Index.vue";
7
8
  import { Editor, EditorContent } from '@tiptap/vue-3'
@@ -20,7 +21,7 @@ const { t, i18n, setLocale } = useLocale();
20
21
  const props = defineProps({
21
22
  locale: { type: String, default () { return 'en' } },
22
23
  content: String|Object,
23
- height: { type: Number, default () { return null } },
24
+ height: { type: [Number,String], default () { return null } },
24
25
  variantBtn: { type: String, default () { return defaults.variantBtn } },
25
26
  variantField: { type: String, default () { return defaults.variantField } },
26
27
  toolbar: { type: Boolean, default () { return true } },
@@ -28,7 +29,7 @@ const props = defineProps({
28
29
  itemsExclude: { type: Boolean, default() { return false } },
29
30
  bubbleMenu: { type: Boolean, default () { return true } },
30
31
  floatingMenu: { type: Boolean, default () { return true } },
31
- slashCommands: { type: Boolean, default () { return true } },
32
+ slashCommands: { type: [Boolean, Array] as PropType<SlashCommandsConfig>, default () { return true } },
32
33
  placeholder: { type: String, default () { return '' } },
33
34
  showWordsCount: { type: Boolean, default () { return true } },
34
35
  showCharactersCount: { type: Boolean, default () { return true } },
@@ -71,6 +72,7 @@ watch(() => editor.value, (editorInstance) => {
71
72
  editor.value.interactiveStyles = interactiveStyles.value
72
73
  emit('editor-ready', {
73
74
  editor: editorInstance,
75
+ setLocale,
74
76
  getHTML: () => editorInstance.getHTML(),
75
77
  getJSON: () => editorInstance.getJSON(),
76
78
  });
@@ -421,5 +423,96 @@ onBeforeUnmount(() => {
421
423
  ul.list-style-square {
422
424
  list-style-type: square !important;
423
425
  }
426
+
427
+ img {
428
+ display: block;
429
+ }
430
+
431
+ [data-node="image"].has-focus {
432
+ [data-resize-handle] {
433
+ position: absolute;
434
+ background: rgba(0, 0, 0, 0.5);
435
+ border: 1px solid rgba(255, 255, 255, 0.8);
436
+ border-radius: 2px;
437
+ z-index: 10;
438
+
439
+ &:hover {
440
+ background: rgba(0, 0, 0, 0.8);
441
+ }
442
+
443
+ /* Corner handles */
444
+ &[data-resize-handle='top-left'],
445
+ &[data-resize-handle='top-right'],
446
+ &[data-resize-handle='bottom-left'],
447
+ &[data-resize-handle='bottom-right'] {
448
+ width: 8px;
449
+ height: 8px;
450
+ }
451
+
452
+ &[data-resize-handle='top-left'] {
453
+ top: -4px;
454
+ left: -4px;
455
+ cursor: nwse-resize;
456
+ }
457
+
458
+ &[data-resize-handle='top-right'] {
459
+ top: -4px;
460
+ right: -4px;
461
+ cursor: nesw-resize;
462
+ }
463
+
464
+ &[data-resize-handle='bottom-left'] {
465
+ bottom: -4px;
466
+ left: -4px;
467
+ cursor: nesw-resize;
468
+ }
469
+
470
+ &[data-resize-handle='bottom-right'] {
471
+ bottom: -4px;
472
+ right: -4px;
473
+ cursor: nwse-resize;
474
+ }
475
+
476
+ /* Edge handles */
477
+ &[data-resize-handle='top'],
478
+ &[data-resize-handle='bottom'] {
479
+ height: 6px;
480
+ left: 8px;
481
+ right: 8px;
482
+ }
483
+
484
+ &[data-resize-handle='top'] {
485
+ top: -3px;
486
+ cursor: ns-resize;
487
+ }
488
+
489
+ &[data-resize-handle='bottom'] {
490
+ bottom: -3px;
491
+ cursor: ns-resize;
492
+ }
493
+
494
+ &[data-resize-handle='left'],
495
+ &[data-resize-handle='right'] {
496
+ width: 6px;
497
+ top: 8px;
498
+ bottom: 8px;
499
+ }
500
+
501
+ &[data-resize-handle='left'] {
502
+ left: -3px;
503
+ cursor: ew-resize;
504
+ }
505
+
506
+ &[data-resize-handle='right'] {
507
+ right: -3px;
508
+ cursor: ew-resize;
509
+ }
510
+ }
511
+
512
+ [data-resize-state='true'] [data-resize-wrapper] {
513
+ outline: 1px solid rgba(0, 0, 0, 0.25);
514
+ border-radius: 0.125rem;
515
+ }
516
+ }
424
517
  }
425
518
  </style>
@@ -54,6 +54,16 @@ function prepareToolbarItems() {
54
54
  }
55
55
  } else if (propsItems.value.length > 0) {
56
56
  for (const propsItem of propsItems.value as string[]) {
57
+ if (propsItem === '|') {
58
+ if (typeof _toolbarItems['__separator__'] === 'undefined') {
59
+ _toolbarItems['__separator__'] = {
60
+ section: '__separator__',
61
+ group: false,
62
+ components: []
63
+ }
64
+ }
65
+ continue
66
+ }
57
67
  const item = propsItem.split(':')
58
68
  if (!availableItemsKeys.includes(item[0])) {
59
69
  throw new Error(`The ${propsItem} is unknown extension. Please use one of the following: ${availableItemsKeys.join(', ')}`)
@@ -15,21 +15,25 @@ defineProps({
15
15
  <template>
16
16
  <VToolbarItems class="py-2">
17
17
  <template v-for="item in items" :key="item.section">
18
- <VBtnGroup v-if="item.group" elevation="4">
19
- <template v-for="sectionItem in item.components" :key="sectionItem.name">
20
- <component :is="sectionItem.component" v-bind="{ ...sectionItem.props ?? {} }" />
21
- </template>
22
- </VBtnGroup>
18
+ <template v-if="item.section === '__separator__'">
19
+ <div class="menu-divider"></div>
20
+ </template>
23
21
  <template v-else>
24
- <component
25
- v-for="sectionItem in item.components"
26
- :key="sectionItem.name"
27
- :is="sectionItem.component"
28
- v-bind="{ variantBtn, ...sectionItem.props ?? {} }"
29
- />
22
+ <VBtnGroup v-if="item.group" elevation="4">
23
+ <template v-for="sectionItem in item.components" :key="sectionItem.name">
24
+ <component :is="sectionItem.component" v-bind="{ ...sectionItem.props ?? {} }" />
25
+ </template>
26
+ </VBtnGroup>
27
+ <template v-else>
28
+ <component
29
+ v-for="sectionItem in item.components"
30
+ :key="sectionItem.name"
31
+ :is="sectionItem.component"
32
+ v-bind="{ variantBtn, ...sectionItem.props ?? {} }"
33
+ />
34
+ </template>
35
+ <div class="menu-divider"></div>
30
36
  </template>
31
-
32
- <div class="menu-divider"></div>
33
37
  </template>
34
38
  </VToolbarItems>
35
39
  </template>
@@ -1,4 +1,5 @@
1
1
  import { default as EmojiButton } from "@tiptapify/extensions/components/media/emoji/Button.vue";
2
+ import { default as CharmapButton } from "@tiptapify/extensions/components/media/charmap/Button.vue";
2
3
  import { default as LinkButton } from "@tiptapify/extensions/components/media/link/Button.vue";
3
4
  import { default as ImageButton } from "@tiptapify/extensions/components/media/image/Button.vue";
4
5
  import { default as IframeButton } from "@tiptapify/extensions/components/media/iframe/Button.vue";
@@ -30,6 +31,10 @@ export default {
30
31
  name: 'emoji',
31
32
  component: markRaw(EmojiButton),
32
33
  },
34
+ {
35
+ name: 'charmap',
36
+ component: markRaw(CharmapButton),
37
+ },
33
38
  {
34
39
  name: 'table',
35
40
  component: markRaw(TableButton),
@@ -31,8 +31,8 @@ import { TiptapifyImage } from '@tiptapify/extensions/components/media/image'
31
31
  import { TiptapifyLink } from '@tiptapify/extensions/components/media/link'
32
32
  import { TiptapifyVideo } from '@tiptapify/extensions/components/media/video'
33
33
  import CodeBlockComponent from '@tiptapify/extensions/components/CodeBlockComponent.vue'
34
- import SlashCommands from '@tiptapify/extensions/slash-commands'
35
- import suggestion from '@tiptapify/extensions/components/slashCommands/suggestion'
34
+ import SlashCommands, { SlashCommandsExtensionOptions } from '@tiptapify/extensions/slash-commands'
35
+ import { SlashCommandsConfig, SlashCommandId } from '@tiptapify/types/slashCommandsTypes'
36
36
  import { toolbarSections } from "@tiptapify/types/toolbarTypes";
37
37
 
38
38
  // load all languages with "all" or common languages with "common"
@@ -51,7 +51,7 @@ const lowlight = createLowlight(common)
51
51
  // register language example
52
52
  // lowlight.register('ts', ts)
53
53
 
54
- export function editorExtensions (placeholder: string, slashCommands: boolean, customExtensions: toolbarSections) {
54
+ export function editorExtensions (placeholder: string, slashCommands: SlashCommandsConfig, customExtensions: toolbarSections) {
55
55
  const extensions = [
56
56
  TextStyleKit,
57
57
  Document,
@@ -78,7 +78,12 @@ export function editorExtensions (placeholder: string, slashCommands: boolean, c
78
78
  openOnClick: false,
79
79
  defaultProtocol: 'https'
80
80
  }),
81
- Image,
81
+ Image.configure({
82
+ resize: {
83
+ enabled: true,
84
+ alwaysPreserveAspectRatio: true,
85
+ },
86
+ }),
82
87
  Youtube.configure({
83
88
  controls: true,
84
89
  nocookie: true,
@@ -110,8 +115,12 @@ export function editorExtensions (placeholder: string, slashCommands: boolean, c
110
115
  BulletListSquare
111
116
  ]
112
117
 
113
- if (slashCommands) {
114
- extensions.push(SlashCommands.configure({ suggestion }))
118
+ if (slashCommands !== false) {
119
+ const config: SlashCommandsExtensionOptions = {}
120
+ if (Array.isArray(slashCommands)) {
121
+ config.suggestion = { allowedCommands: slashCommands }
122
+ }
123
+ extensions.push(SlashCommands.configure(config))
115
124
  }
116
125
 
117
126
  if (customExtensions.length) {
@@ -1,12 +1,13 @@
1
1
  import { Editor, useEditor } from "@tiptap/vue-3";
2
2
  import { editorExtensions } from "@tiptapify/components/editorExtensions";
3
3
  import { toolbarSections } from "@tiptapify/types/toolbarTypes";
4
+ import { SlashCommandsConfig } from "@tiptapify/types/slashCommandsTypes";
4
5
  import { ShallowRef } from "vue";
5
6
 
6
7
  export function getTiptapEditor (
7
8
  content: any,
8
9
  placeholder: string,
9
- slashCommands: boolean = true,
10
+ slashCommands: SlashCommandsConfig = true,
10
11
  customExtensions: toolbarSections,
11
12
  onUpdate: Function = () => {}
12
13
  ): ShallowRef<Editor | undefined> {
@@ -0,0 +1,32 @@
1
+ import { reactive } from 'vue'
2
+
3
+ type PickerCloseEvent = {
4
+ type: 'emoji' | 'charmap'
5
+ }
6
+
7
+ type PickerEvents = {
8
+ close: PickerCloseEvent
9
+ }
10
+
11
+ type EventCallback<T> = (data: T) => void
12
+
13
+ class PickerEventBusClass {
14
+ private listeners: Map<keyof PickerEvents, Set<EventCallback<any>>> = new Map()
15
+
16
+ on<K extends keyof PickerEvents>(event: K, callback: EventCallback<PickerEvents[K]>) {
17
+ if (!this.listeners.has(event)) {
18
+ this.listeners.set(event, new Set())
19
+ }
20
+ this.listeners.get(event)!.add(callback)
21
+ }
22
+
23
+ off<K extends keyof PickerEvents>(event: K, callback: EventCallback<PickerEvents[K]>) {
24
+ this.listeners.get(event)?.delete(callback)
25
+ }
26
+
27
+ emit<K extends keyof PickerEvents>(event: K, data: PickerEvents[K]) {
28
+ this.listeners.get(event)?.forEach(callback => callback(data))
29
+ }
30
+ }
31
+
32
+ export const PickerEventBus = reactive(new PickerEventBusClass())