tiptapify 0.0.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.
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "tiptapify",
3
+ "version": "0.0.1",
4
+ "description": "Tiptap3 editor with Vuetify3 menu implementation",
5
+ "exports": {
6
+ ".": {
7
+ "import": "./dist/tiptapify.es.js",
8
+ "require": "./dist/tiptapify.umd.js"
9
+ },
10
+ "./style.css": "./dist/tiptapify.css"
11
+ },
12
+ "main": "./dist/tiptapify.umd.cjs",
13
+ "module": "./dist/tiptapify.js",
14
+ "scripts": {
15
+ "dev": "vite",
16
+ "build": "vite build",
17
+ "test": "echo \"Error: no test specified\" && exit 1"
18
+ },
19
+ "keywords": [
20
+ "vue", "vue3", "vue.js", "typescript", "vuetify", "vuetify3", "tiptap", "tiptap3", "editor", "material design",
21
+ "wysiwyg", "markdown"
22
+ ],
23
+ "author": "Igor Voytovich",
24
+ "license": "MIT",
25
+ "packageManager": "pnpm@10.11.0",
26
+ "dependencies": {
27
+ "@intlify/unplugin-vue-i18n": "^6.0.8",
28
+ "@tiptap/core": "next",
29
+ "@tiptap/extension-blockquote": "next",
30
+ "@tiptap/extension-bold": "next",
31
+ "@tiptap/extension-bubble-menu": "next",
32
+ "@tiptap/extension-code": "next",
33
+ "@tiptap/extension-code-block": "next",
34
+ "@tiptap/extension-code-block-lowlight": "next",
35
+ "@tiptap/extension-color": "next",
36
+ "@tiptap/extension-document": "next",
37
+ "@tiptap/extension-floating-menu": "next",
38
+ "@tiptap/extension-font-family": "next",
39
+ "@tiptap/extension-hard-break": "next",
40
+ "@tiptap/extension-heading": "next",
41
+ "@tiptap/extension-highlight": "next",
42
+ "@tiptap/extension-horizontal-rule": "next",
43
+ "@tiptap/extension-image": "next",
44
+ "@tiptap/extension-italic": "next",
45
+ "@tiptap/extension-link": "next",
46
+ "@tiptap/extension-list": "next",
47
+ "@tiptap/extension-list-item": "next",
48
+ "@tiptap/extension-paragraph": "next",
49
+ "@tiptap/extension-placeholder": "next",
50
+ "@tiptap/extension-strike": "next",
51
+ "@tiptap/extension-subscript": "next",
52
+ "@tiptap/extension-superscript": "next",
53
+ "@tiptap/extension-table": "next",
54
+ "@tiptap/extension-task-item": "next",
55
+ "@tiptap/extension-task-list": "next",
56
+ "@tiptap/extension-text": "next",
57
+ "@tiptap/extension-text-align": "next",
58
+ "@tiptap/extension-text-style": "next",
59
+ "@tiptap/extension-typography": "next",
60
+ "@tiptap/extension-underline": "next",
61
+ "@tiptap/extensions": "next",
62
+ "@tiptap/pm": "next",
63
+ "@tiptap/starter-kit": "next",
64
+ "@tiptap/suggestion": "next",
65
+ "@tiptap/vue-3": "next",
66
+ "@vitejs/plugin-vue": "^5.2.4",
67
+ "@vitejs/plugin-vue-jsx": "^4.2.0",
68
+ "highlight.js": "^11.11.1",
69
+ "linkifyjs": "^4.3.1",
70
+ "lowlight": "^3.3.0",
71
+ "tippy.js": "^6.3.7",
72
+ "unplugin-vue-components": "^28.5.0",
73
+ "vite": "^6.3.5",
74
+ "vite-plugin-vuetify": "^2.1.1",
75
+ "vite-svg-loader": "^5.1.0",
76
+ "vue-i18n": "^11.1.3"
77
+ },
78
+ "peerDependencies": {
79
+ "@mdi/js": "^7.4.47",
80
+ "vue": "^3.5.14",
81
+ "vuetify": "^3.8.5"
82
+ },
83
+ "devDependencies": {
84
+ "@rollup/plugin-alias": "^5.1.1",
85
+ "@types/node": "^22.15.21",
86
+ "rollup-plugin-tsconfig-paths": "^1.5.2",
87
+ "sass-embedded": "^1.89.0",
88
+ "typescript": "^5.8.3",
89
+ "vite-tsconfig-paths": "^5.1.4",
90
+ "vue-tsc": "^2.2.10"
91
+ }
92
+ }
@@ -0,0 +1,52 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { NodeViewContent, nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
4
+
5
+ const props = defineProps({
6
+ ...nodeViewProps
7
+ })
8
+
9
+ const languages = props.extension.options.lowlight.listLanguages()
10
+
11
+ const selectedLanguage = computed({
12
+ get() {
13
+ return props.node.attrs.language
14
+ },
15
+ set(language) {
16
+ props.updateAttributes({ language })
17
+ }
18
+ })
19
+ </script>
20
+
21
+ <template>
22
+ <NodeViewWrapper class="code-block">
23
+ <select contenteditable="false" v-model="selectedLanguage">
24
+ <option :value="null">
25
+ auto
26
+ </option>
27
+ <option disabled>
28
+
29
+ </option>
30
+ <option v-for="(language, index) in languages" :value="language" :key="index">
31
+ {{ language }}
32
+ </option>
33
+ </select>
34
+ <pre><code><NodeViewContent /></code></pre>
35
+ </NodeViewWrapper>
36
+ </template>
37
+
38
+ <style scoped lang="scss">
39
+ .tiptap {
40
+ .code-block {
41
+ position: relative;
42
+
43
+ select {
44
+ position: absolute;
45
+ background-color: var(--white);
46
+ background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="Black" d="M7 10l5 5 5-5z"/></svg>');
47
+ right: 0.5rem;
48
+ top: 0.5rem;
49
+ }
50
+ }
51
+ }
52
+ </style>
@@ -0,0 +1,30 @@
1
+ <script setup lang="ts">
2
+ import { useEditor } from "@tiptapify/composable/useEditor";
3
+ import { ref } from 'vue'
4
+
5
+ const { editor } = useEditor()
6
+ const editorInstance = ref(editor.getInstance())
7
+ </script>
8
+
9
+ <template>
10
+ <div v-if="editorInstance" class="tiptapify-footer">
11
+ <VRow>
12
+ <VCol class="d-flex justify-end">
13
+ <span class="words-count">
14
+ {{ editorInstance.storage.characterCount.words() }} words
15
+ </span>
16
+ </VCol>
17
+ </VRow>
18
+ </div>
19
+ </template>
20
+
21
+ <style scoped lang="scss">
22
+ .tiptapify-footer {
23
+ padding: 8px;
24
+ border-top: var(--border);
25
+ }
26
+
27
+ .words-count {
28
+ color: #999;
29
+ }
30
+ </style>
@@ -0,0 +1,68 @@
1
+ <script setup lang="ts">
2
+ import LinkDialog from "@tiptapify/components/extensions/components/LinkDialog.vue";
3
+ import { useEditor } from "@tiptapify/composable/useEditor";
4
+ import { defineProps, ref } from "vue";
5
+ import { BubbleMenu } from '@tiptap/vue-3/menus'
6
+ import * as mdi from '@mdi/js'
7
+
8
+ defineProps({
9
+ variant: { type: String, default () { return 'flat' }},
10
+ })
11
+
12
+ const { editor } = useEditor()
13
+ const editorInstance = ref(editor.getInstance())
14
+
15
+ const bubbleMenuLinkButton = ref(null)
16
+
17
+ function linkAction() {
18
+ return editorInstance.value.isActive('link')
19
+ ? editorInstance.value.chain().focus().unsetLink().run()
20
+ : bubbleMenuLinkButton.value?.open()
21
+ }
22
+ </script>
23
+
24
+ <template>
25
+ <!-- <BubbleMenu v-if="editorInstance" :editor="editorInstance" :tippy-options="{ duration: 100 }">-->
26
+ <BubbleMenu v-if="editorInstance" :editor="editorInstance">
27
+ <div class="bubble-menu">
28
+ <VBtnToggle divided density="compact" :variant="variant">
29
+ <VBtn
30
+ @click="editorInstance.chain().focus().toggleBold().run()"
31
+ :active="editorInstance.isActive('bold')"
32
+ size="small"
33
+ >
34
+ <VIcon :icon="mdi.mdiFormatBold" size="16" />
35
+ </VBtn>
36
+ <VBtn
37
+ @click="editorInstance.chain().focus().toggleItalic().run()"
38
+ :active="editorInstance.isActive('italic')"
39
+ size="small"
40
+ >
41
+ <VIcon :icon="mdi.mdiFormatItalic" size="16" />
42
+ </VBtn>
43
+ <VBtn
44
+ @click="editorInstance.chain().focus().toggleStrike().run()"
45
+ :active="editorInstance.isActive('strike')"
46
+ size="small"
47
+ >
48
+ <VIcon :icon="mdi.mdiFormatStrikethroughVariant" size="16" />
49
+ </VBtn>
50
+ <VBtn @click="linkAction" :active="editorInstance.isActive('link')" size="small">
51
+ <VIcon :icon="`${ editorInstance.isActive('link') ? mdi.mdiLinkOff : mdi.mdiLink}`" size="16" />
52
+ </VBtn>
53
+ </VBtnToggle>
54
+ </div>
55
+ </BubbleMenu>
56
+
57
+ <LinkDialog ref="bubbleMenuLinkButton" />
58
+ </template>
59
+
60
+ <style scoped lang="scss">
61
+ .bubble-menu {
62
+ border-radius: 4px;
63
+ border-color: var(--gray-3);
64
+ border-style: solid;
65
+ border-width: 1px;
66
+ box-shadow: var(--shadow);
67
+ }
68
+ </style>
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ import { useEditor } from "@tiptapify/composable/useEditor";
3
+ import { defineProps, ref } from "vue";
4
+ import { FloatingMenu } from '@tiptap/vue-3/menus'
5
+ import * as mdi from '@mdi/js'
6
+
7
+ defineProps({
8
+ variant: { type: String, default () { return '' }},
9
+ })
10
+
11
+ const { editor } = useEditor()
12
+ const editorInstance = ref(editor.getInstance())
13
+ </script>
14
+
15
+ <template>
16
+ <!-- <FloatingMenu v-if="editorInstance" :editor="editorInstance" :tippy-options="{ duration: 100 }" >-->
17
+ <FloatingMenu v-if="editorInstance" :editor="editorInstance">
18
+ <div class="floating-menu">
19
+ <VBtnToggle divided density="compact" :variant="`${variant || 'plain'}`">
20
+ <VBtn
21
+ @click="editorInstance.chain().focus().toggleHeading({ level: 1 }).run()"
22
+ :color="`${editorInstance.isActive('heading', { level: 1 }) ? 'primary' : ''}`"
23
+ size="small"
24
+ >
25
+ <VIcon :icon="mdi['mdiFormatHeader1']" size="16" />
26
+ </VBtn>
27
+ <VBtn
28
+ @click="editorInstance.chain().focus().toggleHeading({ level: 2 }).run()"
29
+ :color="`${editorInstance.isActive('heading', { level: 2 }) ? 'primary' : ''}`"
30
+ size="small"
31
+ >
32
+ <VIcon :icon="mdi['mdiFormatHeader2']" size="16" />
33
+ </VBtn>
34
+ <VBtn
35
+ @click="editorInstance.chain().focus().toggleBulletList().run()"
36
+ :color="`${editorInstance.isActive('bulletList') ? 'primary' : ''}`"
37
+ size="small"
38
+ >
39
+ <VIcon :icon="mdi['mdiFormatListBulleted']" size="16" />
40
+ </VBtn>
41
+ </VBtnToggle>
42
+ </div>
43
+ </FloatingMenu>
44
+ </template>
45
+
46
+ <style scoped lang="scss">
47
+ .floating-menu {
48
+ border-radius: 4px;
49
+ border-color: var(--gray-3);
50
+ border-style: solid;
51
+ border-width: 1px;
52
+ box-shadow: var(--shadow);
53
+ }
54
+ </style>
@@ -0,0 +1,254 @@
1
+ <script setup lang="ts">
2
+
3
+ import { onBeforeUnmount, ref } from "vue";
4
+ import Toolbar from "@tiptapify/components/Toolbar.vue";
5
+ import { EditorContent } from '@tiptap/vue-3'
6
+ import { useEditor } from '@tiptapify/composable/useEditor'
7
+ import MenuBubble from '@tiptapify/components/MenuBubble.vue'
8
+ import MenuFloating from '@tiptapify/components/MenuFloating.vue'
9
+
10
+ import Footer from '@tiptapify/components/Footer.vue'
11
+
12
+ const props = defineProps({
13
+ content: String|Object,
14
+ variant: { type: String, default () { return 'outline' } },
15
+ menu: { type: Boolean, default () { return true } },
16
+ bubbleMenu: { type: Boolean, default () { return true } },
17
+ floatingMenu: { type: Boolean, default () { return true } },
18
+ placeholder: { type: String, default () { return 'Write something here...' } },
19
+ showCharacterCount: { type: Boolean, default () { return true } },
20
+ defaultFontFamily: { type: String, default () { return 'Inter' } },
21
+ })
22
+
23
+ const editor = useEditor(props.content, props.placeholder).editor
24
+ const editorInstance = ref(editor.getInstance())
25
+ editorInstance?.value?.chain().setFontFamily(props.defaultFontFamily).run()
26
+
27
+ onBeforeUnmount(() => {
28
+ editor.destroy()
29
+ })
30
+
31
+ </script>
32
+
33
+ <template>
34
+ <VContainer>
35
+ <VRow>
36
+ <VCol>
37
+ <div class="border rounded">
38
+ <template v-if="menu">
39
+ <Toolbar v-if="editorInstance" :variant="variant" />
40
+ </template>
41
+
42
+ <div class="pa-2 tiptapify-container">
43
+ <MenuFloating v-if="floatingMenu" />
44
+
45
+ <MenuBubble v-if="bubbleMenu" />
46
+
47
+ <EditorContent :editor="editorInstance" class="tiptapify-editor" />
48
+ </div>
49
+
50
+ <template v-if="showCharacterCount">
51
+ <Footer />
52
+ </template>
53
+ </div>
54
+ </VCol>
55
+ </VRow>
56
+ </VContainer>
57
+ </template>
58
+
59
+ <style scoped lang="scss">
60
+ .tiptapify-editor::v-deep(.ProseMirror:focus) {
61
+ outline: none;
62
+ }
63
+ </style>
64
+
65
+ <style lang="scss">
66
+ :root {
67
+ --white: #FFF;
68
+ --black: #2E2B29;
69
+ --black-contrast: #110F0E;
70
+ --gray-1: rgba(61, 37, 20, .05);
71
+ --gray-2: rgba(61, 37, 20, .08);
72
+ --gray-3: rgba(61, 37, 20, .12);
73
+ --gray-4: rgba(53, 38, 28, .3);
74
+ --gray-5: rgba(28, 25, 23, .6);
75
+ --green: #22C55E;
76
+ --purple: #6A00F5;
77
+ --purple-contrast: #5800CC;
78
+ --purple-light: rgba(88, 5, 255, .05);
79
+ --yellow-contrast: #FACC15;
80
+ --yellow: rgba(250, 204, 21, .4);
81
+ --yellow-light: #FFFAE5;
82
+ --red: #FF5C33;
83
+ --red-light: #FFEBE5;
84
+ --shadow: 0px 12px 33px 0px rgba(0, 0, 0, .06), 0px 3.618px 9.949px 0px rgba(0, 0, 0, .04);
85
+ --border: 1px solid var(--gray-2);
86
+ }
87
+
88
+ /* Basic editor styles */
89
+ .tiptap {
90
+ :first-child {
91
+ margin-top: 0;
92
+ }
93
+
94
+ select {
95
+ appearance: none;
96
+ -webkit-appearance: none;
97
+ -moz-appearance: none;
98
+ background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="Gray" d="M7 10l5 5 5-5z"/></svg>');
99
+ background-repeat: no-repeat;
100
+ background-position: right .1rem center;
101
+ background-size: 1.25rem 1.25rem;
102
+ /* padding-right: 1.25rem; */
103
+
104
+ border-radius: .5rem;
105
+ border: none;
106
+ color: var(--black);
107
+ font-family: inherit;
108
+ font-size: .875rem;
109
+ font-weight: 500;
110
+ line-height: 1.15;
111
+ margin: none;
112
+ padding: .375rem .625rem;
113
+ transition: all .2s cubic-bezier(.65,.05,.36,1);
114
+ }
115
+
116
+ /* List styles */
117
+ ul,
118
+ ol {
119
+ padding: 0 1rem;
120
+ margin: 1.25rem 1rem 1.25rem 0.4rem;
121
+
122
+ li p {
123
+ margin-top: 0.25em;
124
+ margin-bottom: 0.25em;
125
+ }
126
+ }
127
+
128
+ /* Heading styles */
129
+ h1,
130
+ h2,
131
+ h3,
132
+ h4,
133
+ h5,
134
+ h6 {
135
+ line-height: 1.1;
136
+ margin-top: 2.5rem;
137
+ text-wrap: pretty;
138
+ }
139
+
140
+ h1,
141
+ h2 {
142
+ margin-top: 3.5rem;
143
+ margin-bottom: 1.5rem;
144
+ }
145
+
146
+ h1 {
147
+ font-size: 1.4rem;
148
+ }
149
+
150
+ h2 {
151
+ font-size: 1.2rem;
152
+ }
153
+
154
+ h3 {
155
+ font-size: 1.1rem;
156
+ }
157
+
158
+ h4,
159
+ h5,
160
+ h6 {
161
+ font-size: 1rem;
162
+ }
163
+
164
+ /* Code and preformatted text styles */
165
+ code {
166
+ background-color: var(--purple-light);
167
+ border-radius: 0.4rem;
168
+ color: var(--black);
169
+ font-size: 0.85rem;
170
+ padding: 0.25em 0.3em;
171
+ }
172
+
173
+ pre {
174
+ background: var(--black);
175
+ border-radius: 0.5rem;
176
+ color: var(--white);
177
+ font-family: 'JetBrainsMono', monospace;
178
+ margin: 1.5rem 0;
179
+ padding: 0.75rem 1rem;
180
+
181
+ code {
182
+ background: none;
183
+ color: inherit;
184
+ font-size: 0.8rem;
185
+ padding: 0;
186
+ }
187
+
188
+ /* Code styling */
189
+ .hljs-comment,
190
+ .hljs-quote {
191
+ color: #616161;
192
+ }
193
+
194
+ .hljs-variable,
195
+ .hljs-template-variable,
196
+ .hljs-attribute,
197
+ .hljs-tag,
198
+ .hljs-name,
199
+ .hljs-regexp,
200
+ .hljs-link,
201
+ .hljs-name,
202
+ .hljs-selector-id,
203
+ .hljs-selector-class {
204
+ color: #f98181;
205
+ }
206
+
207
+ .hljs-number,
208
+ .hljs-meta,
209
+ .hljs-built_in,
210
+ .hljs-builtin-name,
211
+ .hljs-literal,
212
+ .hljs-type,
213
+ .hljs-params {
214
+ color: #fbbc88;
215
+ }
216
+
217
+ .hljs-string,
218
+ .hljs-symbol,
219
+ .hljs-bullet {
220
+ color: #b9f18d;
221
+ }
222
+
223
+ .hljs-title,
224
+ .hljs-section {
225
+ color: #faf594;
226
+ }
227
+
228
+ .hljs-keyword,
229
+ .hljs-selector-tag {
230
+ color: #70cff8;
231
+ }
232
+
233
+ .hljs-emphasis {
234
+ font-style: italic;
235
+ }
236
+
237
+ .hljs-strong {
238
+ font-weight: 700;
239
+ }
240
+ }
241
+
242
+ blockquote {
243
+ border-left: 3px solid var(--gray-3);
244
+ margin: 1.5rem 0;
245
+ padding-left: 1rem;
246
+ }
247
+
248
+ hr {
249
+ border: none;
250
+ border-top: 1px solid var(--gray-2);
251
+ margin: 2rem 0;
252
+ }
253
+ }
254
+ </style>