tiptapify 0.0.36 → 0.1.1

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 (44) hide show
  1. package/README.md +11 -5
  2. package/dist/tiptapify.css +1 -1
  3. package/dist/tiptapify.mjs +21407 -21776
  4. package/dist/tiptapify.umd.js +49 -49
  5. package/package.json +6 -3
  6. package/src/components/Tiptapify.vue +95 -2
  7. package/src/components/Toolbar/Index.vue +10 -0
  8. package/src/components/Toolbar/Items.vue +17 -13
  9. package/src/components/editorExtensions.ts +15 -6
  10. package/src/components/index.ts +2 -1
  11. package/src/extensions/PickerEventBus.ts +32 -0
  12. package/src/extensions/components/media/charmap/Button.vue +5 -143
  13. package/src/extensions/components/media/charmap/Picker.vue +229 -0
  14. package/src/extensions/components/media/emoji/Button.vue +5 -141
  15. package/src/extensions/components/media/emoji/Picker.vue +225 -0
  16. package/src/extensions/components/media/image/ImageDialog.vue +69 -27
  17. package/src/extensions/components/slashCommands/CommandsList.vue +65 -22
  18. package/src/extensions/components/slashCommands/PickerDialog.vue +44 -0
  19. package/src/extensions/components/slashCommands/suggestion.ts +152 -105
  20. package/src/extensions/slash-commands.ts +169 -9
  21. package/src/i18n/locales/ar.json +3 -2
  22. package/src/i18n/locales/ch.json +3 -2
  23. package/src/i18n/locales/cz.json +3 -2
  24. package/src/i18n/locales/de.json +3 -2
  25. package/src/i18n/locales/es.json +3 -2
  26. package/src/i18n/locales/fi.json +3 -2
  27. package/src/i18n/locales/fr.json +3 -2
  28. package/src/i18n/locales/hu.json +3 -2
  29. package/src/i18n/locales/it.json +3 -2
  30. package/src/i18n/locales/ja.json +3 -2
  31. package/src/i18n/locales/ko.json +3 -2
  32. package/src/i18n/locales/la.json +3 -2
  33. package/src/i18n/locales/lt.json +3 -2
  34. package/src/i18n/locales/nl.json +3 -2
  35. package/src/i18n/locales/pl.json +3 -2
  36. package/src/i18n/locales/pt.json +3 -2
  37. package/src/i18n/locales/ru.json +3 -2
  38. package/src/i18n/locales/se.json +3 -2
  39. package/src/i18n/locales/th.json +3 -2
  40. package/src/i18n/locales/tr.json +3 -2
  41. package/src/i18n/locales/{ua.json → uk.json} +3 -2
  42. package/src/i18n/locales/vi.json +3 -2
  43. package/src/types/slashCommandsTypes.ts +19 -0
  44. package/src/types/toolbarTypes.ts +1 -1
@@ -1,7 +1,42 @@
1
1
  import { Extension } from '@tiptap/core'
2
+ import { Editor } from '@tiptap/core'
2
3
  import Suggestion from '@tiptap/suggestion'
4
+ import { VueRenderer, posToDOMRect } from '@tiptap/vue-3'
5
+ import { computePosition, flip, shift } from '@floating-ui/dom'
6
+ import { useLocale } from '@tiptapify/i18n'
7
+ import CommandsList from '@tiptapify/extensions/components/slashCommands/CommandsList.vue'
8
+ import PickerDialog from '@tiptapify/extensions/components/slashCommands/PickerDialog.vue'
9
+ import { PickerEventBus } from '@tiptapify/extensions/PickerEventBus'
10
+ import { defaultSlashCommandIds, slashCommandMap } from '@tiptapify/extensions/components/slashCommands/suggestion'
11
+ import { SlashCommandId } from '@tiptapify/types/slashCommandsTypes'
3
12
 
4
- export default Extension.create(
13
+ const updatePosition = (editor: Editor, element: HTMLElement) => {
14
+ const virtualElement = {
15
+ getBoundingClientRect: () => posToDOMRect(editor.view, editor.state.selection.from, editor.state.selection.to),
16
+ }
17
+
18
+ computePosition(virtualElement, element, {
19
+ placement: 'bottom-start',
20
+ strategy: 'absolute',
21
+ middleware: [shift(), flip()],
22
+ }).then(({ x, y, strategy }) => {
23
+ element.style.width = 'max-content'
24
+ element.style.position = strategy
25
+ element.style.left = `${x}px`
26
+ element.style.top = `${y}px`
27
+ })
28
+ }
29
+
30
+ export type SlashCommandsExtensionOptions = {
31
+ suggestion?: {
32
+ char?: string
33
+ command?: ({ editor, range, props }: { editor: Editor, range: any, props: any }) => void
34
+ allowedCommands?: SlashCommandId[]
35
+ render?: () => any
36
+ }
37
+ }
38
+
39
+ export default Extension.create<SlashCommandsExtensionOptions>(
5
40
  {
6
41
  name: 'slash-commands',
7
42
 
@@ -9,20 +44,145 @@ export default Extension.create(
9
44
  return {
10
45
  suggestion: {
11
46
  char: '/',
12
- command: ({ editor, range, props }) => {
13
- props.command({editor, range})
14
- },
47
+ allowedCommands: defaultSlashCommandIds,
15
48
  },
16
49
  }
17
50
  },
18
51
 
19
52
  addProseMirrorPlugins() {
53
+ const allowedCommands = this.options.suggestion?.allowedCommands ?? defaultSlashCommandIds
54
+ const customCommand = this.options.suggestion?.command
55
+ const char = this.options.suggestion?.char ?? '/'
56
+
57
+ let component: VueRenderer | null = null
58
+ let pickerComponent: VueRenderer | null = null
59
+
60
+ const items = ({ query }: { query: string }) => {
61
+ const lowerQuery = query.toLowerCase()
62
+ return allowedCommands
63
+ .map(id => {
64
+ const cmd = slashCommandMap[id]
65
+ if (cmd.isPicker) {
66
+ return {
67
+ ...cmd,
68
+ id,
69
+ pickerType: id === 'emoji' ? 'emoji' : 'charmap'
70
+ }
71
+ }
72
+ return { ...cmd, id }
73
+ })
74
+ .filter((cmd: any) => {
75
+ if (!query) return true
76
+ return cmd.title.toLowerCase().includes(lowerQuery) || cmd.id.toLowerCase().includes(lowerQuery)
77
+ })
78
+ .slice(0, 20)
79
+ }
80
+
81
+ const closePicker = () => {
82
+ if (pickerComponent) {
83
+ pickerComponent.element.remove()
84
+ pickerComponent.destroy()
85
+ pickerComponent = null
86
+ }
87
+ }
88
+
89
+ PickerEventBus.on('close', () => {
90
+ closePicker()
91
+ })
92
+
93
+ const command = ({ editor, range, props }: { editor: Editor, range: any, props: any }) => {
94
+ if (customCommand) {
95
+ customCommand({ editor, range, props })
96
+ } else if (props.command) {
97
+ props.command({ editor, range })
98
+ } else if (props.isPicker && props.pickerType) {
99
+ editor.chain().focus().deleteRange(range).run()
100
+ closePicker()
101
+
102
+ const { t } = useLocale()
103
+
104
+ pickerComponent = new VueRenderer(PickerDialog, {
105
+ props: {
106
+ editor,
107
+ t,
108
+ type: props.pickerType,
109
+ },
110
+ editor,
111
+ })
112
+
113
+ const element = pickerComponent.element as HTMLElement
114
+ element.style.position = 'fixed'
115
+ element.style.zIndex = '9999'
116
+ element.style.inset = '0'
117
+ element.style.display = 'flex'
118
+ element.style.alignItems = 'center'
119
+ element.style.justifyContent = 'center'
120
+
121
+ document.body.appendChild(element)
122
+ }
123
+ }
124
+
20
125
  return [
21
- Suggestion(
22
- {
23
- editor: this.editor,
24
- ...this.options.suggestion,
126
+ Suggestion({
127
+ editor: this.editor,
128
+ char,
129
+ command,
130
+ items,
131
+ render: () => ({
132
+ onStart: (props: any) => {
133
+ component = new VueRenderer(CommandsList, {
134
+ props,
135
+ editor: props.editor,
136
+ })
137
+
138
+ if (!props.clientRect) {
139
+ return
140
+ }
141
+
142
+ const element = component.element as HTMLElement
143
+ element.style.position = 'absolute'
144
+
145
+ document.body.appendChild(element)
146
+
147
+ updatePosition(props.editor, element)
148
+ },
149
+
150
+ onUpdate: (props: any) => {
151
+ if (component) {
152
+ component.updateProps(props)
153
+ }
154
+
155
+ if (!props.clientRect) {
156
+ return
157
+ }
158
+
159
+ const element = component?.element as HTMLElement | undefined
160
+ if (element) {
161
+ updatePosition(props.editor, element)
162
+ }
163
+ },
164
+
165
+ onKeyDown: (props: any) => {
166
+ if (props.event.key === 'Escape') {
167
+ closePicker()
168
+ return true
169
+ }
170
+ return component?.ref?.onKeyDown(props)
171
+ },
172
+
173
+ onExit: () => {
174
+ closePicker()
175
+ if (component) {
176
+ component.destroy()
177
+ const element = component.element as HTMLElement | null
178
+ if (element?.parentNode) {
179
+ element.parentNode.removeChild(element)
180
+ }
181
+ component = null
182
+ }
183
+ },
25
184
  }),
185
+ }),
26
186
  ]
27
187
  },
28
- })
188
+ })
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "عنوان URL المدخل غير صالح"
132
132
  },
133
133
  "image": {
134
- "title": "إضافة/تعديل صورة",
134
+ "title": "العنوان",
135
135
  "src": "المصدر",
136
136
  "keep_ratio": "الحفاظ على نسبة العرض إلى الارتفاع",
137
137
  "alt": "نص بديل",
138
138
  "width": "العرض",
139
- "height": "الارتفاع"
139
+ "height": "الارتفاع",
140
+ "dialog_title": "إضافة/تعديل صورة"
140
141
  },
141
142
  "link": {
142
143
  "title": "إضافة/تعديل رابط",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "输入的URL无效"
132
132
  },
133
133
  "image": {
134
- "title": "添加/修改图片",
134
+ "title": "标题",
135
135
  "src": "源",
136
136
  "keep_ratio": "保持比例",
137
137
  "alt": "替代文本",
138
138
  "width": "宽度",
139
- "height": "高度"
139
+ "height": "高度",
140
+ "dialog_title": "添加/修改图片"
140
141
  },
141
142
  "link": {
142
143
  "title": "添加/修改链接",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "Zadaná URL adresa je neplatná"
132
132
  },
133
133
  "image": {
134
- "title": "Přidání/úprava obrázku",
134
+ "title": "Název",
135
135
  "src": "Zdroj",
136
136
  "keep_ratio": "Udržet poměr stran",
137
137
  "alt": "alt",
138
138
  "width": "Šířka",
139
- "height": "Výška"
139
+ "height": "Výška",
140
+ "dialog_title": "Přidání/úprava obrázku"
140
141
  },
141
142
  "link": {
142
143
  "title": "Přidání/úprava odkazu",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "Die eingegebene URL ist ungültig"
132
132
  },
133
133
  "image": {
134
- "title": "Bild hinzufügen/ändern",
134
+ "title": "Titel",
135
135
  "src": "Quelle",
136
136
  "keep_ratio": "Seitenverhältnis beibehalten",
137
137
  "alt": "alt",
138
138
  "width": "Breite",
139
- "height": "Höhe"
139
+ "height": "Höhe",
140
+ "dialog_title": "Bild hinzufügen/ändern"
140
141
  },
141
142
  "link": {
142
143
  "title": "Link hinzufügen/ändern",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "La URL ingresada es inválida"
132
132
  },
133
133
  "image": {
134
- "title": "Agregar/modificar imagen",
134
+ "title": "Título",
135
135
  "src": "Fuente",
136
136
  "keep_ratio": "Mantener proporción",
137
137
  "alt": "alt",
138
138
  "width": "Ancho",
139
- "height": "Alto"
139
+ "height": "Alto",
140
+ "dialog_title": "Agregar/modificar imagen"
140
141
  },
141
142
  "link": {
142
143
  "title": "Agregar/modificar enlace",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "Syötetty URL ei ole kelvollinen"
132
132
  },
133
133
  "image": {
134
- "title": "Lisää/muokkaa kuvaa",
134
+ "title": "Otsikko",
135
135
  "src": "Lähde",
136
136
  "keep_ratio": "Säilytä suhde",
137
137
  "alt": "Vaihtoehtoinen teksti",
138
138
  "width": "Leveys",
139
- "height": "Korkeus"
139
+ "height": "Korkeus",
140
+ "dialog_title": "Lisää/muokkaa kuvaa"
140
141
  },
141
142
  "link": {
142
143
  "title": "Lisää/muokkaa linkkiä",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "L'URL saisie est invalide"
132
132
  },
133
133
  "image": {
134
- "title": "Ajouter/modifier l'image",
134
+ "title": "Titre",
135
135
  "src": "Source",
136
136
  "keep_ratio": "Conserver le ratio d'aspect",
137
137
  "alt": "alt",
138
138
  "width": "Largeur",
139
- "height": "Hauteur"
139
+ "height": "Hauteur",
140
+ "dialog_title": "Ajouter/modifier l'image"
140
141
  },
141
142
  "link": {
142
143
  "title": "Ajouter/modifier le lien",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "A megadott URL érvénytelen"
132
132
  },
133
133
  "image": {
134
- "title": "Kép hozzáadása/szerkesztése",
134
+ "title": "Cím",
135
135
  "src": "Forrás",
136
136
  "keep_ratio": "Arány megtartása",
137
137
  "alt": "Alternatív szöveg",
138
138
  "width": "Szélesség",
139
- "height": "Magasság"
139
+ "height": "Magasság",
140
+ "dialog_title": "Kép hozzáadása/szerkesztése"
140
141
  },
141
142
  "link": {
142
143
  "title": "Hivatkozás hozzáadása/szerkesztése",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "L'URL inserito non è valido"
132
132
  },
133
133
  "image": {
134
- "title": "Aggiungi/modifica immagine",
134
+ "title": "Titolo",
135
135
  "src": "Origine",
136
136
  "keep_ratio": "Mantieni rapporto d'aspetto",
137
137
  "alt": "alt",
138
138
  "width": "Larghezza",
139
- "height": "Altezza"
139
+ "height": "Altezza",
140
+ "dialog_title": "Aggiungi/modifica immagine"
140
141
  },
141
142
  "link": {
142
143
  "title": "Aggiungi/modifica collegamento",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "入力された URL は無効です"
132
132
  },
133
133
  "image": {
134
- "title": "画像を追加/編集",
134
+ "title": "タイトル",
135
135
  "src": "ソース",
136
136
  "keep_ratio": "アスペクト比を維持",
137
137
  "alt": "代替テキスト",
138
138
  "width": "幅",
139
- "height": "高さ"
139
+ "height": "高さ",
140
+ "dialog_title": "画像を追加/編集"
140
141
  },
141
142
  "link": {
142
143
  "title": "リンクを追加/編集",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "입력한 URL 이 유효하지 않습니다"
132
132
  },
133
133
  "image": {
134
- "title": "이미지 추가/편집",
134
+ "title": "제목",
135
135
  "src": "소스",
136
136
  "keep_ratio": "종횡비 유지",
137
137
  "alt": "대체 텍스트",
138
138
  "width": "너비",
139
- "height": "높이"
139
+ "height": "높이",
140
+ "dialog_title": "이미지 추가/편집"
140
141
  },
141
142
  "link": {
142
143
  "title": "링크 추가/편집",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "Ievadītais URL nav derīgs"
132
132
  },
133
133
  "image": {
134
- "title": "Pievienot/mainīt attēlu",
134
+ "title": "Titulus",
135
135
  "src": "Avots",
136
136
  "keep_ratio": "Saglabāt attiecības",
137
137
  "alt": "Alt",
138
138
  "width": "Platums",
139
- "height": "Augstums"
139
+ "height": "Augstums",
140
+ "dialog_title": "Pievienot/mainīt attēlu"
140
141
  },
141
142
  "link": {
142
143
  "title": "Pievienot/mainīt saiti",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "Įvestas URL netinkamas"
132
132
  },
133
133
  "image": {
134
- "title": "Pridėti/keisti paveikslą",
134
+ "title": "Pavadinimas",
135
135
  "src": "Šaltinis",
136
136
  "keep_ratio": "Išlaikyti proporciją",
137
137
  "alt": "alt",
138
138
  "width": "Plotis",
139
- "height": "Aukštis"
139
+ "height": "Aukštis",
140
+ "dialog_title": "Pridėti/keisti paveikslą"
140
141
  },
141
142
  "link": {
142
143
  "title": "Pridėti/keisti nuorodą",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "De ingevoerde URL is ongeldig"
132
132
  },
133
133
  "image": {
134
- "title": "Afbeelding toevoegen/wijzigen",
134
+ "title": "Titel",
135
135
  "src": "Bron",
136
136
  "keep_ratio": "Verhouding behouden",
137
137
  "alt": "alt",
138
138
  "width": "Breedte",
139
- "height": "Hoogte"
139
+ "height": "Hoogte",
140
+ "dialog_title": "Afbeelding toevoegen/wijzigen"
140
141
  },
141
142
  "link": {
142
143
  "title": "Link toevoegen/wijzigen",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "Wprowadzony URL jest nieprawidłowy"
132
132
  },
133
133
  "image": {
134
- "title": "Dodaj/zmień obraz",
134
+ "title": "Tytuł",
135
135
  "src": "Źródło",
136
136
  "keep_ratio": "Zachowaj proporcję",
137
137
  "alt": "alt",
138
138
  "width": "Szerokość",
139
- "height": "Wysokość"
139
+ "height": "Wysokość",
140
+ "dialog_title": "Dodaj/zmień obraz"
140
141
  },
141
142
  "link": {
142
143
  "title": "Dodaj/zmień link",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "A URL inserida é inválida"
132
132
  },
133
133
  "image": {
134
- "title": "Adicionar/modificar imagem",
134
+ "title": "Título",
135
135
  "src": "Fonte",
136
136
  "keep_ratio": "Manter proporção",
137
137
  "alt": "alt",
138
138
  "width": "Largura",
139
- "height": "Altura"
139
+ "height": "Altura",
140
+ "dialog_title": "Adicionar/modificar imagem"
140
141
  },
141
142
  "link": {
142
143
  "title": "Adicionar/modificar link",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "Введён некорректный URL"
132
132
  },
133
133
  "image": {
134
- "title": "Добавление/изменение изображения",
134
+ "title": "Заголовок",
135
135
  "src": "Источник",
136
136
  "keep_ratio": "Сохранять пропорции",
137
137
  "alt": "alt",
138
138
  "width": "Ширина",
139
- "height": "Высота"
139
+ "height": "Высота",
140
+ "dialog_title": "Добавление/изменение изображения"
140
141
  },
141
142
  "link": {
142
143
  "title": "Добавление/изменение ссылки",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "Den angivna URL:en är ogiltig"
132
132
  },
133
133
  "image": {
134
- "title": "Lägg till/ändra bild",
134
+ "title": "Rubrik",
135
135
  "src": "Källa",
136
136
  "keep_ratio": "Behåll proportioner",
137
137
  "alt": "alt",
138
138
  "width": "Bredd",
139
- "height": "Höjd"
139
+ "height": "Höjd",
140
+ "dialog_title": "Lägg till/ändra bild"
140
141
  },
141
142
  "link": {
142
143
  "title": "Lägg till/ändra länk",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "URL ที่ป้อนไม่ถูกต้อง"
132
132
  },
133
133
  "image": {
134
- "title": "เพิ่ม/แก้ไขรูปภาพ",
134
+ "title": "หัวข้อ",
135
135
  "src": "แหล่งที่มา",
136
136
  "keep_ratio": "รักษาสัดส่วน",
137
137
  "alt": "ข้อความทางเลือก",
138
138
  "width": "ความกว้าง",
139
- "height": "ความสูง"
139
+ "height": "ความสูง",
140
+ "dialog_title": "เพิ่ม/แก้ไขรูปภาพ"
140
141
  },
141
142
  "link": {
142
143
  "title": "เพิ่ม/แก้ไขลิงก์",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "Girilen URL geçersiz"
132
132
  },
133
133
  "image": {
134
- "title": "Görsel ekle/düzenle",
134
+ "title": "Başlık",
135
135
  "src": "Kaynak",
136
136
  "keep_ratio": "En boy oranını koru",
137
137
  "alt": "Alternatif metin",
138
138
  "width": "Genişlik",
139
- "height": "Yükseklik"
139
+ "height": "Yükseklik",
140
+ "dialog_title": "Görsel ekle/düzenle"
140
141
  },
141
142
  "link": {
142
143
  "title": "Bağlantı ekle/düzenle",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "Введений некоректний URL"
132
132
  },
133
133
  "image": {
134
- "title": "Додавання/зміна зображення",
134
+ "title": "Заголовок",
135
135
  "src": "Джерело",
136
136
  "keep_ratio": "Зберігати пропорції",
137
137
  "alt": "alt",
138
138
  "width": "Ширина",
139
- "height": "Висота"
139
+ "height": "Висота",
140
+ "dialog_title": "Додавання/зміна зображення"
140
141
  },
141
142
  "link": {
142
143
  "title": "Додавання/зміна посилання",
@@ -131,12 +131,13 @@
131
131
  "src_invalid": "URL đã nhập không hợp lệ"
132
132
  },
133
133
  "image": {
134
- "title": "Thêm/sửa hình ảnh",
134
+ "title": "Tiêu đề",
135
135
  "src": "Nguồn",
136
136
  "keep_ratio": "Giữ tỷ lệ khung hình",
137
137
  "alt": "Văn bản thay thế",
138
138
  "width": "Chiều rộng",
139
- "height": "Chiều cao"
139
+ "height": "Chiều cao",
140
+ "dialog_title": "Thêm/sửa hình ảnh"
140
141
  },
141
142
  "link": {
142
143
  "title": "Thêm/sửa liên kết",
@@ -0,0 +1,19 @@
1
+ import { Editor } from '@tiptap/core'
2
+
3
+ export type SlashCommandCallback = (params: { editor: Editor, range: any }) => void
4
+
5
+ export type SlashCommand = {
6
+ title: string
7
+ icon: string
8
+ command?: SlashCommandCallback
9
+ isPicker?: boolean
10
+ }
11
+
12
+ export type SlashCommandId =
13
+ | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
14
+ | 'bulletList' | 'numberedList' | 'taskList'
15
+ | 'code' | 'codeBlock' | 'quote'
16
+ | 'image' | 'video' | 'table' | 'link'
17
+ | 'divider' | 'emoji' | 'specialChars'
18
+
19
+ export type SlashCommandsConfig = SlashCommandId[] | true | false
@@ -25,4 +25,4 @@ export type section = {
25
25
 
26
26
  export type toolbarSections = Array<section>
27
27
 
28
- export type itemsPropType = { [key: string]: Array<string> } | Array<string>
28
+ export type itemsPropType = { [key: string]: string[] } | string[]