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
@@ -0,0 +1,229 @@
1
+ <script lang="ts" setup>
2
+ import type { Editor } from '@tiptap/core'
3
+ import tiptapifyCharMap from '@tiptapify/extensions/charmap'
4
+ import { PickerEventBus } from '@tiptapify/extensions/PickerEventBus'
5
+ import { computed, ref, watch } from 'vue'
6
+
7
+ const props = defineProps<{
8
+ editor: Editor,
9
+ t: any
10
+ }>()
11
+
12
+ const filter = ref('')
13
+ const activeTab = ref('punctuation')
14
+ const sorted = tiptapifyCharMap.sort((previous, current) => {
15
+ if (previous.order < current.order) {
16
+ return -1
17
+ }
18
+ if (previous.order > current.order) {
19
+ return 1
20
+ }
21
+ return 0
22
+ })
23
+
24
+ if (!activeTab.value && sorted.length > 0) {
25
+ activeTab.value = sorted[0].name
26
+ }
27
+
28
+ const chars = computed(() => sorted.map(item => ({ group: item.name, items: item.items })))
29
+ const filteredChars = ref(JSON.parse(JSON.stringify(chars.value)))
30
+
31
+ const handleCharacterClick = (charItem: any) => {
32
+ props.editor.chain().focus().insertContent(charItem.char).run()
33
+ PickerEventBus.emit('close', { type: 'charmap' })
34
+ }
35
+
36
+ const filterChars = () => {
37
+ if (!filter.value) {
38
+ filteredChars.value = JSON.parse(JSON.stringify(chars.value))
39
+ return
40
+ }
41
+
42
+ const filtered: any[] = []
43
+ filteredChars.value.forEach((group: any) => {
44
+ const matchingChars = group.items.filter((item: any) =>
45
+ item.name.toLowerCase().match(filter.value.toLowerCase())
46
+ )
47
+ if (matchingChars.length > 0) {
48
+ filtered.push({ ...group, items: matchingChars })
49
+ }
50
+ })
51
+ filteredChars.value = filtered
52
+ if (filtered.length > 0) {
53
+ activeTab.value = filtered[0].group
54
+ }
55
+ }
56
+
57
+ const resetFilter = () => {
58
+ filter.value = ''
59
+ filteredChars.value = JSON.parse(JSON.stringify(chars.value))
60
+ }
61
+
62
+ watch(filter, filterChars)
63
+ </script>
64
+
65
+ <template>
66
+ <div class="tiptapify-slash-picker">
67
+ <div class="tiptapify-slash-picker__sidebar">
68
+ <button
69
+ v-for="item in filteredChars"
70
+ :key="item.group"
71
+ :class="['tiptapify-slash-picker__tab', { 'tiptapify-slash-picker__tab--active': activeTab === item.group }]"
72
+ @click="activeTab = item.group"
73
+ >
74
+ <VTooltip activator="parent">
75
+ {{ t(`media.charmap.categories.${item.group}`) }}
76
+ </VTooltip>
77
+ {{ item.items[0]?.char }}
78
+ </button>
79
+ </div>
80
+
81
+ <div class="tiptapify-slash-picker__content">
82
+ <div class="tiptapify-slash-picker__search">
83
+ <input
84
+ v-model="filter"
85
+ type="text"
86
+ placeholder="Search characters..."
87
+ class="tiptapify-slash-picker__search-input"
88
+ />
89
+ <button v-if="filter" class="tiptapify-slash-picker__clear" @click="resetFilter">&times;</button>
90
+ </div>
91
+
92
+ <div class="tiptapify-slash-picker__grid-container">
93
+ <div
94
+ v-for="item in filteredChars"
95
+ :key="item.group"
96
+ class="tiptapify-slash-picker__group"
97
+ >
98
+ <div v-show="activeTab === item.group" class="tiptapify-slash-picker__grid">
99
+ <button
100
+ v-for="charItem in item.items"
101
+ :key="charItem.char"
102
+ class="tiptapify-slash-picker__item"
103
+ :title="charItem.name"
104
+ @click="handleCharacterClick(charItem)"
105
+ >
106
+ {{ charItem.char }}
107
+ </button>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </template>
114
+
115
+ <style scoped>
116
+ .tiptapify-slash-picker {
117
+ display: flex;
118
+ width: 450px;
119
+ height: 400px;
120
+ background: rgb(var(--v-theme-surface));
121
+ border-radius: 8px;
122
+ overflow: hidden;
123
+ }
124
+
125
+ .tiptapify-slash-picker__sidebar {
126
+ width: 48px;
127
+ background: rgba(var(--v-theme-on-surface), 0.04);
128
+ border-right: 1px solid rgba(var(--v-theme-on-surface), 0.12);
129
+ padding: 8px 4px;
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: 4px;
133
+ overflow-y: auto;
134
+ }
135
+
136
+ .tiptapify-slash-picker__tab {
137
+ width: 40px;
138
+ height: 32px;
139
+ font-size: 16px;
140
+ border: none;
141
+ background: transparent;
142
+ border-radius: 4px;
143
+ cursor: pointer;
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ }
148
+
149
+ .tiptapify-slash-picker__tab:hover {
150
+ background: rgba(var(--v-theme-on-surface), 0.08);
151
+ }
152
+
153
+ .tiptapify-slash-picker__tab--active {
154
+ background: rgba(var(--v-theme-primary), 0.12);
155
+ color: rgb(var(--v-theme-primary));
156
+ }
157
+
158
+ .tiptapify-slash-picker__content {
159
+ flex: 1;
160
+ display: flex;
161
+ flex-direction: column;
162
+ padding: 12px;
163
+ }
164
+
165
+ .tiptapify-slash-picker__search {
166
+ position: relative;
167
+ margin-bottom: 12px;
168
+ }
169
+
170
+ .tiptapify-slash-picker__search-input {
171
+ width: 100%;
172
+ padding: 8px 32px 8px 12px;
173
+ border: 1px solid rgba(var(--v-theme-on-surface), 0.24);
174
+ border-radius: 4px;
175
+ font-size: 14px;
176
+ background: rgb(var(--v-theme-surface));
177
+ color: rgb(var(--v-theme-on-surface));
178
+ }
179
+
180
+ .tiptapify-slash-picker__search-input:focus {
181
+ outline: none;
182
+ border-color: rgb(var(--v-theme-primary));
183
+ }
184
+
185
+ .tiptapify-slash-picker__clear {
186
+ position: absolute;
187
+ right: 8px;
188
+ top: 50%;
189
+ transform: translateY(-50%);
190
+ width: 20px;
191
+ height: 20px;
192
+ border: none;
193
+ background: transparent;
194
+ cursor: pointer;
195
+ font-size: 16px;
196
+ color: rgba(var(--v-theme-on-surface), 0.6);
197
+ }
198
+
199
+ .tiptapify-slash-picker__grid-container {
200
+ flex: 1;
201
+ overflow-y: auto;
202
+ border: 1px solid rgba(var(--v-theme-on-surface), 0.12);
203
+ border-radius: 4px;
204
+ }
205
+
206
+ .tiptapify-slash-picker__grid {
207
+ display: grid;
208
+ grid-template-columns: repeat(auto-fill, minmax(32px, 1fr));
209
+ gap: 2px;
210
+ padding: 4px;
211
+ }
212
+
213
+ .tiptapify-slash-picker__item {
214
+ width: 32px;
215
+ height: 32px;
216
+ font-size: 16px;
217
+ border: none;
218
+ background: transparent;
219
+ border-radius: 4px;
220
+ cursor: pointer;
221
+ display: flex;
222
+ align-items: center;
223
+ justify-content: center;
224
+ }
225
+
226
+ .tiptapify-slash-picker__item:hover {
227
+ background: rgba(var(--v-theme-on-surface), 0.08);
228
+ }
229
+ </style>
@@ -1,69 +1,17 @@
1
1
  <script lang="ts" setup>
2
-
3
2
  import * as mdi from '@mdi/js'
4
- import { Editor } from "@tiptap/vue-3";
5
- import tiptapifyEmojis from "@tiptapify/extensions/emoji"
6
- import { computed, inject, Ref, ref, watch } from 'vue'
3
+ import { inject, Ref } from 'vue'
7
4
  import BtnIcon from "@tiptapify/components/UI/BtnIcon.vue";
8
-
5
+ import EmojiPicker from './Picker.vue';
9
6
  import defaults from '@tiptapify/constants/defaults'
10
7
 
11
8
  defineProps({
12
9
  variantBtn: { type: String, default: defaults.variantBtn }
13
10
  })
14
11
 
15
- const editor = inject('tiptapifyEditor') as Ref<Editor>
12
+ const editor = inject('tiptapifyEditor') as Ref<any>
16
13
 
17
14
  const { t } = inject('tiptapifyI18n') as any
18
-
19
- const filter = ref(null)
20
- const tab = ref('smileys_and_emotion')
21
- const sorted = tiptapifyEmojis.sort((previous, current) => {
22
- if (previous.order < current.order) {
23
- return -1
24
- }
25
- if (previous.order > current.order) {
26
- return 1
27
- }
28
-
29
- return 0
30
- })
31
- const emojis = computed(() => sorted.map(item => { return { group: item.name, emojis: item.items } }))
32
- const emojisRef = ref(JSON.parse(JSON.stringify(emojis.value)))
33
-
34
- const handleEmojiClick = (emoji: any) => {
35
- editor.value.chain().focus().insertContent(emoji.char).run()
36
- }
37
-
38
- const filterEmoji = (filterValue: string) => {
39
- resetFilter()
40
- if (!filterValue) {
41
- return
42
- }
43
-
44
- const filtered: any[] = []
45
-
46
- const groupItems = emojisRef.value.find((item: any) => item.group === tab.value)
47
- if (groupItems?.emojis) {
48
- groupItems.emojis.forEach((item: any) => {
49
- if (item.name.toLowerCase().match(filterValue)) {
50
- filtered.push(item)
51
- }
52
- })
53
-
54
- groupItems.emojis = filtered
55
- }
56
- }
57
-
58
- const resetFilter = () => {
59
- emojisRef.value = JSON.parse(JSON.stringify(emojis.value))
60
- }
61
-
62
- watch(() => tab.value, () => {
63
- resetFilter()
64
- filter.value = null
65
- })
66
-
67
15
  </script>
68
16
 
69
17
  <template>
@@ -76,91 +24,7 @@ watch(() => tab.value, () => {
76
24
 
77
25
  <VMenu :activator="`#tiptapify-emoji-button-${editor.instanceId}`" :close-on-content-click="false">
78
26
  <VSheet class="pa-2" max-width="570">
79
- <div class="d-flex flex-row">
80
- <VTabs v-model="tab" mandatory direction="vertical" color="primary" density="compact">
81
- <VTab
82
- v-for="item of emojisRef"
83
- :text="item.group"
84
- :value="item.group"
85
- :key="item.group"
86
- density="compact"
87
- rounded
88
- size="small"
89
- >
90
- {{ t(`media.emoji.categories.${item.group}`) }}
91
- </VTab>
92
- </VTabs>
93
-
94
- <div>
95
- <VTextField
96
- v-model="filter"
97
- :label="t('media.emoji.search')"
98
- density="compact"
99
- variant="solo"
100
- :prepend-inner-icon="`mdiSvg:${mdi.mdiMagnify}`"
101
- class="mb-2"
102
- hide-details
103
- clearable
104
- @input="filterEmoji($event.target.value)"
105
- @click:clear="resetFilter"
106
- />
107
- <div class="tiptapify-emoji-container">
108
- <VWindow v-model="tab" direction="vertical">
109
- <VWindowItem v-for="item of emojisRef" :value="item.group" :transition="false" :reverse-transition="false">
110
- <div
111
- v-for="emojiItem in item.emojis"
112
- class="tiptapify-emoji-container-item"
113
- @click="handleEmojiClick(emojiItem)"
114
- :title="emojiItem.name"
115
- >
116
- <div class="tiptapify-emoji-container-item__overlay">
117
- <span>
118
- {{ emojiItem.char }}
119
- </span>
120
- </div>
121
- </div>
122
- </VWindowItem>
123
- </VWindow>
124
- </div>
125
- </div>
126
- </div>
27
+ <EmojiPicker :editor="editor" :t="t" />
127
28
  </VSheet>
128
29
  </VMenu>
129
- </template>
130
-
131
- <style lang="scss" scoped>
132
- .tiptapify-emoji-container {
133
- width: 360px;
134
- height: 500px;
135
- overflow-y: auto;
136
-
137
- border: 1px solid rgba(var(--v-theme-on-background), calc(var(--v-border-opacity)));
138
- border-radius: 8px;
139
- box-shadow: 0 0 5px rgba(var(--v-theme-on-background), calc(var(--v-border-opacity))) inset;
140
- }
141
-
142
- .tiptapify-emoji-container-item__overlay {
143
- display: flex;
144
- justify-content: center;
145
- align-items: center;
146
- width: 32px;
147
- height: 32px;
148
- border-radius: 5px;
149
- z-index: 1;
150
- }
151
-
152
- .tiptapify-emoji-container-item {
153
- position: relative;
154
- display: inline-flex;
155
- justify-content: center;
156
- align-items: center;
157
- cursor: pointer;
158
- width: 32px;
159
- height: 32px;
160
- transition: background-color 0.2s ease-in-out;
161
- }
162
-
163
- .tiptapify-emoji-container-item:hover .tiptapify-emoji-container-item__overlay {
164
- background-color: rgba(var(--v-theme-on-background), calc(var(--v-hover-opacity) * var(--v-theme-overlay-multiplier)));
165
- }
166
- </style>
30
+ </template>
@@ -0,0 +1,225 @@
1
+ <script lang="ts" setup>
2
+ import type { Editor } from '@tiptap/core'
3
+ import tiptapifyEmojis from '@tiptapify/extensions/emoji'
4
+ import { PickerEventBus } from '@tiptapify/extensions/PickerEventBus'
5
+ import { computed, ref, watch } from 'vue'
6
+
7
+ const props = defineProps<{
8
+ editor: Editor,
9
+ t: any
10
+ }>()
11
+
12
+ const filter = ref('')
13
+ const activeTab = ref('smileys_and_emotion')
14
+ const sorted = tiptapifyEmojis.sort((previous, current) => {
15
+ if (previous.order < current.order) {
16
+ return -1
17
+ }
18
+ if (previous.order > current.order) {
19
+ return 1
20
+ }
21
+ return 0
22
+ })
23
+
24
+ const emojis = computed(() => sorted.map(item => ({ group: item.name, emojis: item.items })))
25
+ const filteredEmojis = ref(JSON.parse(JSON.stringify(emojis.value)))
26
+
27
+ const handleEmojiClick = (emoji: any) => {
28
+ props.editor.chain().focus().insertContent(emoji.char).run()
29
+ PickerEventBus.emit('close', { type: 'emoji' })
30
+ }
31
+
32
+ const filterEmoji = () => {
33
+ if (!filter.value) {
34
+ filteredEmojis.value = JSON.parse(JSON.stringify(emojis.value))
35
+ return
36
+ }
37
+
38
+ const filtered: any[] = []
39
+ filteredEmojis.value.forEach((group: any) => {
40
+ const matchingEmojis = group.emojis.filter((emoji: any) =>
41
+ emoji.name.toLowerCase().match(filter.value.toLowerCase())
42
+ )
43
+ if (matchingEmojis.length > 0) {
44
+ filtered.push({ ...group, emojis: matchingEmojis })
45
+ }
46
+ })
47
+ filteredEmojis.value = filtered
48
+ if (filtered.length > 0) {
49
+ activeTab.value = filtered[0].group
50
+ }
51
+ }
52
+
53
+ const resetFilter = () => {
54
+ filter.value = ''
55
+ filteredEmojis.value = JSON.parse(JSON.stringify(emojis.value))
56
+ }
57
+
58
+ watch(filter, filterEmoji)
59
+ </script>
60
+
61
+ <template>
62
+ <div class="tiptapify-slash-picker">
63
+ <div class="tiptapify-slash-picker__sidebar">
64
+ <button
65
+ v-for="item in filteredEmojis"
66
+ :key="item.group"
67
+ :class="['tiptapify-slash-picker__tab', { 'tiptapify-slash-picker__tab--active': activeTab === item.group }]"
68
+ @click="activeTab = item.group"
69
+ >
70
+ <VTooltip activator="parent">
71
+ {{ t(`media.emoji.categories.${item.group}`) }}
72
+ </VTooltip>
73
+ {{ item.emojis[0]?.char }}
74
+ </button>
75
+ </div>
76
+
77
+ <div class="tiptapify-slash-picker__content">
78
+ <div class="tiptapify-slash-picker__search">
79
+ <input
80
+ v-model="filter"
81
+ type="text"
82
+ placeholder="Search emoji..."
83
+ class="tiptapify-slash-picker__search-input"
84
+ />
85
+ <button v-if="filter" class="tiptapify-slash-picker__clear" @click="resetFilter">&times;</button>
86
+ </div>
87
+
88
+ <div class="tiptapify-slash-picker__grid-container">
89
+ <div
90
+ v-for="item in filteredEmojis"
91
+ :key="item.group"
92
+ class="tiptapify-slash-picker__group"
93
+ >
94
+ <div v-show="activeTab === item.group" class="tiptapify-slash-picker__grid">
95
+ <button
96
+ v-for="emojiItem in item.emojis"
97
+ :key="emojiItem.char"
98
+ class="tiptapify-slash-picker__item"
99
+ :title="emojiItem.name"
100
+ @click="handleEmojiClick(emojiItem)"
101
+ >
102
+ {{ emojiItem.char }}
103
+ </button>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </template>
110
+
111
+ <style scoped>
112
+ .tiptapify-slash-picker {
113
+ display: flex;
114
+ width: 450px;
115
+ height: 400px;
116
+ background: rgb(var(--v-theme-surface));
117
+ border-radius: 8px;
118
+ overflow: hidden;
119
+ }
120
+
121
+ .tiptapify-slash-picker__sidebar {
122
+ width: 48px;
123
+ background: rgba(var(--v-theme-on-surface), 0.04);
124
+ border-right: 1px solid rgba(var(--v-theme-on-surface), 0.12);
125
+ padding: 8px 4px;
126
+ display: flex;
127
+ flex-direction: column;
128
+ gap: 4px;
129
+ overflow-y: auto;
130
+ }
131
+
132
+ .tiptapify-slash-picker__tab {
133
+ width: 40px;
134
+ height: 32px;
135
+ font-size: 18px;
136
+ border: none;
137
+ background: transparent;
138
+ border-radius: 4px;
139
+ cursor: pointer;
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ }
144
+
145
+ .tiptapify-slash-picker__tab:hover {
146
+ background: rgba(var(--v-theme-on-surface), 0.08);
147
+ }
148
+
149
+ .tiptapify-slash-picker__tab--active {
150
+ background: rgba(var(--v-theme-primary), 0.12);
151
+ color: rgb(var(--v-theme-primary));
152
+ }
153
+
154
+ .tiptapify-slash-picker__content {
155
+ flex: 1;
156
+ display: flex;
157
+ flex-direction: column;
158
+ padding: 12px;
159
+ }
160
+
161
+ .tiptapify-slash-picker__search {
162
+ position: relative;
163
+ margin-bottom: 12px;
164
+ }
165
+
166
+ .tiptapify-slash-picker__search-input {
167
+ width: 100%;
168
+ padding: 8px 32px 8px 12px;
169
+ border: 1px solid rgba(var(--v-theme-on-surface), 0.24);
170
+ border-radius: 4px;
171
+ font-size: 14px;
172
+ background: rgb(var(--v-theme-surface));
173
+ color: rgb(var(--v-theme-on-surface));
174
+ }
175
+
176
+ .tiptapify-slash-picker__search-input:focus {
177
+ outline: none;
178
+ border-color: rgb(var(--v-theme-primary));
179
+ }
180
+
181
+ .tiptapify-slash-picker__clear {
182
+ position: absolute;
183
+ right: 8px;
184
+ top: 50%;
185
+ transform: translateY(-50%);
186
+ width: 20px;
187
+ height: 20px;
188
+ border: none;
189
+ background: transparent;
190
+ cursor: pointer;
191
+ font-size: 16px;
192
+ color: rgba(var(--v-theme-on-surface), 0.6);
193
+ }
194
+
195
+ .tiptapify-slash-picker__grid-container {
196
+ flex: 1;
197
+ overflow-y: auto;
198
+ border: 1px solid rgba(var(--v-theme-on-surface), 0.12);
199
+ border-radius: 4px;
200
+ }
201
+
202
+ .tiptapify-slash-picker__grid {
203
+ display: grid;
204
+ grid-template-columns: repeat(auto-fill, minmax(32px, 1fr));
205
+ gap: 2px;
206
+ padding: 4px;
207
+ }
208
+
209
+ .tiptapify-slash-picker__item {
210
+ width: 32px;
211
+ height: 32px;
212
+ font-size: 20px;
213
+ border: none;
214
+ background: transparent;
215
+ border-radius: 4px;
216
+ cursor: pointer;
217
+ display: flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ }
221
+
222
+ .tiptapify-slash-picker__item:hover {
223
+ background: rgba(var(--v-theme-on-surface), 0.08);
224
+ }
225
+ </style>