tiptapify 0.0.2 → 0.0.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/package.json CHANGED
@@ -1,16 +1,29 @@
1
1
  {
2
2
  "name": "tiptapify",
3
- "version": "0.0.2",
3
+ "version": "0.0.5",
4
4
  "description": "Tiptap3 editor with Vuetify3 menu implementation",
5
5
  "exports": {
6
6
  ".": {
7
- "import": "./dist/tiptapify.es.js",
8
- "require": "./dist/tiptapify.umd.js"
7
+ "import": {
8
+ "default": "./dist/tiptapify.es.js",
9
+ "development": "./src/index.ts"
10
+ },
11
+ "require": {
12
+ "default": "./dist/tiptapify.umd.js",
13
+ "development": "./src/index.ts"
14
+ }
9
15
  },
16
+ "./src/*": "./src/*",
17
+ "./dist/*": "./dist/*",
10
18
  "./style.css": "./dist/tiptapify.css"
11
19
  },
12
20
  "main": "./dist/tiptapify.umd.cjs",
13
21
  "module": "./dist/tiptapify.js",
22
+ "source": "./src/index.ts",
23
+ "files": [
24
+ "dist",
25
+ "src"
26
+ ],
14
27
  "scripts": {
15
28
  "dev": "vite",
16
29
  "build": "vite build",
@@ -38,7 +51,6 @@
38
51
  "repository": "https://github.com/IVoyt/tiptapify",
39
52
  "packageManager": "pnpm@10.11.0",
40
53
  "dependencies": {
41
- "@intlify/unplugin-vue-i18n": "^6.0.8",
42
54
  "@tiptap/core": "next",
43
55
  "@tiptap/extension-blockquote": "next",
44
56
  "@tiptap/extension-bold": "next",
@@ -77,15 +89,9 @@
77
89
  "@tiptap/starter-kit": "next",
78
90
  "@tiptap/suggestion": "next",
79
91
  "@tiptap/vue-3": "next",
80
- "@vitejs/plugin-vue": "^5.2.4",
81
- "@vitejs/plugin-vue-jsx": "^4.2.0",
82
92
  "highlight.js": "^11.11.1",
83
93
  "linkifyjs": "^4.3.1",
84
94
  "lowlight": "^3.3.0",
85
- "unplugin-vue-components": "^28.5.0",
86
- "vite": "^6.3.5",
87
- "vite-plugin-vuetify": "^2.1.1",
88
- "vite-svg-loader": "^5.1.0",
89
95
  "vue-i18n": "^11.1.4"
90
96
  },
91
97
  "peerDependencies": {
@@ -94,11 +100,18 @@
94
100
  "vuetify": "^3.8.5"
95
101
  },
96
102
  "devDependencies": {
103
+ "@intlify/unplugin-vue-i18n": "^6.0.8",
97
104
  "@rollup/plugin-alias": "^5.1.1",
98
105
  "@types/node": "^22.15.21",
106
+ "@vitejs/plugin-vue": "^5.2.4",
107
+ "@vitejs/plugin-vue-jsx": "^4.2.0",
99
108
  "rollup-plugin-tsconfig-paths": "^1.5.2",
100
109
  "sass-embedded": "^1.89.0",
101
110
  "typescript": "^5.8.3",
111
+ "unplugin-vue-components": "^28.5.0",
112
+ "vite": "^6.3.5",
113
+ "vite-plugin-vuetify": "^2.1.1",
114
+ "vite-svg-loader": "^5.1.0",
102
115
  "vite-tsconfig-paths": "^5.1.4",
103
116
  "vue-tsc": "^2.2.10"
104
117
  }
@@ -74,6 +74,14 @@ const items = ref([
74
74
  disabled: computed(() => editorInstance.value.isActive('code') || editorInstance.value.isActive('codeBlock')),
75
75
  },
76
76
  click: () => linkAction(),
77
+ },
78
+ {
79
+ name: 'format clear',
80
+ icon: mdi.mdiFormatClear,
81
+ props: {
82
+ active: false,
83
+ },
84
+ click: () => editorInstance.value.chain().focus().unsetAllMarks().clearNodes().run(),
77
85
  }
78
86
  ])
79
87
 
@@ -86,7 +94,7 @@ function linkAction() {
86
94
 
87
95
  <template>
88
96
  <!-- <BubbleMenu v-if="editorInstance" :editor="editorInstance" :tippy-options="{ duration: 100 }">-->
89
- <BubbleMenu v-if="editorInstance" :editor="editorInstance">
97
+ <BubbleMenu v-if="editorInstance" :editor="editorInstance" :options="{ placement: 'bottom' }">
90
98
  <div class="bubble-menu">
91
99
  <VBtnToggle divided density="compact" :variant="variant">
92
100
  <VBtn v-for="(item, key) in items" :key="key" v-bind="item.props" @click="item.click" size="small">
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import LinkDialog from "@tiptapify/components/extensions/components/LinkDialog.vue";
3
+ import ShowSource from "@tiptapify/components/extensions/components/ShowSource.vue";
3
4
  import { useEditor } from "@tiptapify/composable/useEditor";
4
5
  import { computed, defineProps, Ref, ref } from 'vue'
5
6
  import { useI18n } from "vue-i18n";
@@ -69,7 +70,7 @@ const toolbarItemsRef: Ref<ToolbarItemSections> = ref(items)
69
70
  </VBtn>
70
71
  </template>
71
72
 
72
- <VList v-model="toolbarItem.modelValue">
73
+ <VList v-model="toolbarItem.modelValue" max-height="430px">
73
74
  <VListItem
74
75
  v-for="(item, menuItemKey) in toolbarItem.children"
75
76
  :key="menuItemKey"
@@ -111,6 +112,7 @@ const toolbarItemsRef: Ref<ToolbarItemSections> = ref(items)
111
112
  </template>
112
113
 
113
114
  <LinkDialog ref="toolbarLinkButton" />
115
+ <ShowSource />
114
116
  </div>
115
117
  </template>
116
118
 
@@ -0,0 +1,118 @@
1
+ export const fonts = [
2
+ {
3
+ name: 'Arial',
4
+ fontFamily: 'arial'
5
+ },
6
+ {
7
+ name: 'Arial Black',
8
+ fontFamily: 'arial black'
9
+ },
10
+ {
11
+ name: 'Baskerville',
12
+ fontFamily: 'baskerville'
13
+ },
14
+ {
15
+ name: 'Bodoni MT',
16
+ fontFamily: 'bodoni mt'
17
+ },
18
+ {
19
+ name: 'Brush Script MT',
20
+ fontFamily: 'brush script mt'
21
+ },
22
+ {
23
+ name: 'Calibri',
24
+ fontFamily: 'calibri'
25
+ },
26
+ {
27
+ name: 'Calisto MT',
28
+ fontFamily: 'calisto mt'
29
+ },
30
+ {
31
+ name: 'Cambria',
32
+ fontFamily: 'cambria'
33
+ },
34
+ {
35
+ name: 'Century Gothic',
36
+ fontFamily: 'century gothic'
37
+ },
38
+ {
39
+ name: 'Consolas',
40
+ fontFamily: 'consolas'
41
+ },
42
+ {
43
+ name: 'Comic Sans',
44
+ fontFamily: 'comic sans ms, comic sans'
45
+ },
46
+ {
47
+ name: 'Courier',
48
+ fontFamily: 'Courier'
49
+ },
50
+ {
51
+ name: 'Courier New',
52
+ fontFamily: 'courier new'
53
+ },
54
+ {
55
+ name: 'Cursive',
56
+ fontFamily: 'cursive'
57
+ },
58
+ {
59
+ name: 'Dejavu Sans',
60
+ fontFamily: 'dejavu sans'
61
+ },
62
+ {
63
+ name: 'Franklin Gothic',
64
+ fontFamily: 'franklin gothic'
65
+ },
66
+ {
67
+ name: 'Garamond',
68
+ fontFamily: 'garamond'
69
+ },
70
+ {
71
+ name: 'Georgia',
72
+ fontFamily: 'georgia'
73
+ },
74
+ {
75
+ name: 'Helvetica',
76
+ fontFamily: 'helvetica'
77
+ },
78
+ {
79
+ name: 'Impact',
80
+ fontFamily: 'impact'
81
+ },
82
+ {
83
+ name: 'Inter',
84
+ fontFamily: 'inter'
85
+ },
86
+ {
87
+ name: 'Monospace',
88
+ fontFamily: 'monospace'
89
+ },
90
+ {
91
+ name: 'Optima',
92
+ fontFamily: 'optima'
93
+ },
94
+ {
95
+ name: 'Segoe UI',
96
+ fontFamily: 'segoe ui'
97
+ },
98
+ {
99
+ name: 'Serif',
100
+ fontFamily: 'serif'
101
+ },
102
+ {
103
+ name: 'Tahoma',
104
+ fontFamily: 'tahoma'
105
+ },
106
+ {
107
+ name: 'Time New Roman',
108
+ fontFamily: 'times new roman'
109
+ },
110
+ {
111
+ name: 'Trebuchet MS',
112
+ fontFamily: 'trebuchet ms'
113
+ },
114
+ {
115
+ name: 'Verdana',
116
+ fontFamily: 'verdana'
117
+ },
118
+ ]
@@ -1,28 +1,6 @@
1
1
  import { computed, ComputedRef, Ref, ref } from "vue";
2
2
  import * as mdi from '@mdi/js'
3
-
4
- const fonts = ref([
5
- {
6
- name: 'Inter',
7
- fontFamily: 'Inter'
8
- },
9
- {
10
- name: 'Comic Sans',
11
- fontFamily: 'Comic Sans MS, Comic Sans'
12
- },
13
- {
14
- name: 'Serif',
15
- fontFamily: 'serif'
16
- },
17
- {
18
- name: 'Monospace',
19
- fontFamily: 'monospace'
20
- },
21
- {
22
- name: 'Cursive',
23
- fontFamily: 'cursive'
24
- },
25
- ])
3
+ import { fonts } from './fonts'
26
4
 
27
5
  interface MDIIcons {
28
6
  [key: string]: string
@@ -88,7 +66,6 @@ export function toolbarItems(
88
66
  * font color, backgroundcolor
89
67
  * tables
90
68
  * media (image, video)
91
- * unsetmarks, clearnodes
92
69
  */
93
70
  heading: {
94
71
  name: 'heading',
@@ -103,23 +80,41 @@ export function toolbarItems(
103
80
  props: {
104
81
  color: computed(() => editor.value.isActive('heading') ? 'primary' : ''),
105
82
  },
106
- children: headingLevels.value.map(level => {
107
- return {
108
- name: `H${level}`,
109
- tooltip: `style.headings.h${level}`,
110
- icon: mdiIcons[`mdiFormatHeader${level}`],
83
+ children: [
84
+ {
85
+ name: `paragraph`,
86
+ tooltip: `style.paragraph`,
87
+ icon: mdiIcons[`mdiFormatParagraph`],
111
88
  noI18n: true,
112
89
  enabled: true,
113
90
  props: {
114
91
  color: computed(() => {
115
- return editor.value.isActive('heading', { level }) ? 'primary' : ''
92
+ return editor.value.isActive('paragraph') ? 'primary' : ''
116
93
  }),
117
94
  },
118
95
  attrs: {
119
- click: () => editor.value.chain().focus().toggleHeading({ level }).run()
96
+ click: () => editor.value.chain().focus().setParagraph().run()
120
97
  }
121
98
  }
122
- })
99
+ ].concat(
100
+ headingLevels.value.map(level => {
101
+ return {
102
+ name: `H${level}`,
103
+ tooltip: `style.headings.h${level}`,
104
+ icon: mdiIcons[`mdiFormatHeader${level}`],
105
+ noI18n: true,
106
+ enabled: true,
107
+ props: {
108
+ color: computed(() => {
109
+ return editor.value.isActive('heading', { level }) ? 'primary' : ''
110
+ }),
111
+ },
112
+ attrs: {
113
+ click: () => editor.value.chain().focus().toggleHeading({ level }).run()
114
+ }
115
+ }
116
+ })
117
+ )
123
118
  },
124
119
  fontFamily: {
125
120
  name: 'font-family',
@@ -131,7 +126,7 @@ export function toolbarItems(
131
126
  attrs: {
132
127
  click: () => editor.value.chain().focus().unsetFontFamily().run()
133
128
  },
134
- children: fonts.value.map((font) => {
129
+ children: fonts.map((font) => {
135
130
  return {
136
131
  name: font.name,
137
132
  tooltip: '',
@@ -271,6 +266,19 @@ export function toolbarItems(
271
266
  click: () => editor.value.chain().focus().toggleHighlight().run()
272
267
  }
273
268
  },
269
+ formatClear: {
270
+ name: 'format clear',
271
+ tooltip: 'format.formatClear',
272
+ icon: mdi.mdiFormatClear,
273
+ section: 'format',
274
+ enabled: true,
275
+ props: {
276
+ disabled: computed(() => !editor.value.can().chain().focus().unsetAllMarks().run()),
277
+ },
278
+ attrs: {
279
+ click: () => editor.value.chain().focus().unsetAllMarks().clearNodes().run()
280
+ }
281
+ },
274
282
 
275
283
  code: {
276
284
  name: 'code',
@@ -542,6 +550,17 @@ export function toolbarItems(
542
550
  click: () => editor.value.chain().focus().setHardBreak().run()
543
551
  }
544
552
  },
553
+ source: {
554
+ name: 'source',
555
+ tooltip: 'misc.source',
556
+ icon: mdi.mdiCodeTags,
557
+ section: 'misc',
558
+ enabled: true,
559
+ props: {},
560
+ attrs: {
561
+ click: () => editor.value.commands.showSource()
562
+ }
563
+ },
545
564
  }
546
565
 
547
566
 
@@ -25,6 +25,7 @@ import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
25
25
 
26
26
  import { Link } from '@tiptap/extension-link'
27
27
  import CodeBlockComponent from '@tiptapify/components/CodeBlockComponent.vue'
28
+ import { ViewSource } from '@tiptapify/components/extensions/view-source'
28
29
  import SlashCommands from '@tiptapify/components/extensions/slash-commands'
29
30
  import suggestion from '@tiptapify/components/extensions/components/slashCommands/suggestion'
30
31
 
@@ -92,7 +93,8 @@ export function editorExtensions (placeholder: string, slashCommands: boolean) {
92
93
  types: ['heading', 'paragraph'],
93
94
  }),
94
95
  Placeholder.configure({ placeholder }),
95
- CharacterCount
96
+ CharacterCount,
97
+ ViewSource
96
98
  ]
97
99
 
98
100
  if (slashCommands) {
@@ -90,7 +90,7 @@ watch(dialog, val => {
90
90
 
91
91
  <VCardActions>
92
92
  <VBtn :disabled="isDisabled" @click="apply">
93
- {{ t('dialog.link.apply') }}
93
+ {{ t('dialog.apply') }}
94
94
  </VBtn>
95
95
  </VCardActions>
96
96
  </VCard>
@@ -0,0 +1,124 @@
1
+ <script setup lang="ts">
2
+ import { useEditor } from "@tiptapify/composable/useEditor";
3
+ import { ref, onMounted, onUnmounted, watch } from 'vue'
4
+ import { useI18n } from "vue-i18n";
5
+
6
+ const props = defineProps({
7
+ indent: { type: Number, default: 2 },
8
+ })
9
+
10
+ const { t } = useI18n();
11
+
12
+ const editor = useEditor().editor.getInstance()
13
+
14
+ const dialog = ref(false)
15
+ const formatted = ref(false)
16
+ const sourceCode = ref('')
17
+
18
+ const formatHtml = (html: string): string => {
19
+ let formatted = html.replace(/>/g, '>\n');
20
+
21
+ formatted = formatted.replace(/\n</g, '\n<');
22
+ formatted = formatted.replace(/([^>\n])</g, '$1\n<');
23
+
24
+ const lines = formatted.split('\n');
25
+ let indentLevel = 0;
26
+
27
+ return lines
28
+ .map(line => {
29
+ if (line.match(/<\//)) {
30
+ indentLevel = Math.max(0, indentLevel - 1);
31
+ }
32
+
33
+ const indentedLine = ' '.repeat(indentLevel * props.indent) + line;
34
+
35
+ if (line.match(/<[^\/][^>]*>/) && !line.match(/<.*\/>/)) {
36
+ indentLevel++;
37
+ }
38
+
39
+ return indentedLine;
40
+ })
41
+ .filter(line => line.trim())
42
+ .join('\n');
43
+ }
44
+
45
+ const unformatHtml = (html: string): string => {
46
+ return html
47
+ .replace(/\n/g, '')
48
+ .replace(/\s+/g, ' ')
49
+ .replace(/>\s+</g, '><')
50
+ }
51
+
52
+ const showDialog = (event: CustomEvent) => {
53
+ sourceCode.value = event.detail.html
54
+ dialog.value = true;
55
+ }
56
+
57
+ const saveChanges = () => {
58
+ dialog.value = false
59
+
60
+ editor.value.commands.setContent(sourceCode.value, true)
61
+ }
62
+
63
+ onMounted(() => {
64
+ window.addEventListener('tiptapify-show-source', showDialog as EventListener)
65
+ })
66
+
67
+ onUnmounted(() => {
68
+ window.removeEventListener('tiptapify-show-source', showDialog as EventListener)
69
+ })
70
+
71
+ watch(() => formatted.value, () => {
72
+ sourceCode.value = formatted.value ? formatHtml(sourceCode.value) : unformatHtml(sourceCode.value)
73
+ })
74
+ </script>
75
+
76
+ <template>
77
+ <VDialog v-model="dialog" max-width="1500">
78
+ <VCard>
79
+ <VCardTitle>{{ t('dialog.source.title') }}</VCardTitle>
80
+
81
+ <VCardText>
82
+ <VContainer fluid class="pt-0 pl-0 pr-0">
83
+ <VRow>
84
+ <VCol>
85
+ <VBtn v-model="formatted" :color="`${formatted ? 'primary' : ''}`" @click="formatted = !formatted">
86
+ {{ t('dialog.source.prettify') }}
87
+ </VBtn>
88
+ </VCol>
89
+ </VRow>
90
+ </VContainer>
91
+
92
+ <VTextarea
93
+ v-model="sourceCode"
94
+ no-resize
95
+ rows="100"
96
+ variant="outlined"
97
+ class="source-code-area"
98
+ />
99
+ </VCardText>
100
+
101
+ <VCardActions>
102
+ <VSpacer></VSpacer>
103
+ <VBtn color="primary" @click="dialog = false">
104
+ {{ t('dialog.close') }}
105
+ </VBtn>
106
+ <VBtn color="primary" @click="saveChanges">
107
+ {{ t('dialog.apply') }}
108
+ </VBtn>
109
+ </VCardActions>
110
+ </VCard>
111
+ </VDialog>
112
+ </template>
113
+
114
+ <style scoped lang="scss">
115
+ .source-code-area {
116
+ font-family: monospace;
117
+ white-space: pre-wrap;
118
+ }
119
+
120
+ :deep(.source-code-area textarea) {
121
+ max-height: 900px;
122
+ overflow-y: auto;
123
+ }
124
+ </style>
@@ -0,0 +1,53 @@
1
+ import { Extension } from '@tiptap/core'
2
+ import { Plugin, PluginKey } from '@tiptap/pm/state'
3
+
4
+ export interface ViewSourceOptions {
5
+ HTMLAttributes: Record<string, any>
6
+ }
7
+
8
+ declare module '@tiptap/core' {
9
+ interface Commands<ReturnType> {
10
+ viewSource: {
11
+ /**
12
+ * Показать исходный HTML-код
13
+ */
14
+ showSource: () => ReturnType
15
+ }
16
+ }
17
+ }
18
+
19
+ export const ViewSource = Extension.create<ViewSourceOptions>({
20
+ name: 'viewSource',
21
+
22
+ addOptions() {
23
+ return {
24
+ HTMLAttributes: {},
25
+ }
26
+ },
27
+
28
+ addCommands() {
29
+ return {
30
+ showSource: () => ({ editor }) => {
31
+ const event = new CustomEvent('tiptapify-show-source', {
32
+ detail: {
33
+ // html: editor.getHTML()
34
+ html: editor.getHTML({ blockSeparator: '\n\n' })
35
+ // html: editor.getText({ blockSeparator: '\n\n' })
36
+ }
37
+ })
38
+
39
+ window.dispatchEvent(event)
40
+
41
+ return true
42
+ },
43
+ }
44
+ },
45
+
46
+ addProseMirrorPlugins() {
47
+ return [
48
+ new Plugin({
49
+ key: new PluginKey('viewSource'),
50
+ }),
51
+ ]
52
+ },
53
+ })
@@ -0,0 +1,67 @@
1
+ {
2
+ "style": {
3
+ "paragraph": "Absatz",
4
+ "heading": "Überschrift",
5
+ "headings": {
6
+ "h1": "Überschrift Ebene 1",
7
+ "h2": "Überschrift Ebene 2",
8
+ "h3": "Überschrift Ebene 3",
9
+ "h4": "Überschrift Ebene 4",
10
+ "h5": "Überschrift Ebene 5",
11
+ "h6": "Überschrift Ebene 6"
12
+ },
13
+ "fontFamily": "Schriftart",
14
+ "fontSize": "Schriftgröße",
15
+ "lineHeight": "Zeilenhöhe"
16
+ },
17
+ "format": {
18
+ "bold": "Fett",
19
+ "italic": "Kursiv",
20
+ "strike": "Durchgestrichen",
21
+ "underline": "Unterstrichen",
22
+ "sup": "Hochgestellt",
23
+ "sub": "Tiefgestellt",
24
+ "break": "Zeilenumbruch",
25
+ "highlight": "Hervorheben",
26
+ "line": "Horizontale Linie",
27
+ "blockquote": "Zitat",
28
+ "code": "Code",
29
+ "codeblock": "Codeblock",
30
+ "link": "Externer Link",
31
+ "formatClear": "Formatierung löschen"
32
+ },
33
+ "action": {
34
+ "undo": "Rückgängig",
35
+ "redo": "Wiederherstellen"
36
+ },
37
+ "alignment": "Ausrichtung",
38
+ "alignments": {
39
+ "left": "Linksbündig",
40
+ "center": "Zentriert",
41
+ "right": "Rechtsbündig",
42
+ "justify": "Blocksatz"
43
+ },
44
+ "list": "Liste",
45
+ "lists": {
46
+ "bullet": "Aufzählungsliste",
47
+ "numbered": "Nummerierte Liste",
48
+ "task": "Aufgabenliste",
49
+ "indent": "Einzug vergrößern",
50
+ "outdent": "Einzug verkleinern"
51
+ },
52
+ "dialog": {
53
+ "close": "Schließen",
54
+ "apply": "Anwenden",
55
+ "link": {
56
+ "title": "Link hinzufügen/bearbeiten",
57
+ "placeholder": "Linkadresse"
58
+ },
59
+ "source": {
60
+ "title": "Quellcode anzeigen",
61
+ "prettify": "prettify"
62
+ }
63
+ },
64
+ "misc": {
65
+ "source": "Quellcode anzeigen"
66
+ }
67
+ }
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "style": {
3
+ "paragraph": "paragraph",
3
4
  "heading": "heading",
4
5
  "headings": {
5
6
  "h1": "heading level 1",
@@ -26,7 +27,8 @@
26
27
  "blockquote": "cite",
27
28
  "code": "code",
28
29
  "codeblock": "code block",
29
- "link": "external link"
30
+ "link": "external link",
31
+ "formatClear": "format clear"
30
32
  },
31
33
  "action": {
32
34
  "undo": "undo",
@@ -48,10 +50,18 @@
48
50
  "outdent": "list item outdent"
49
51
  },
50
52
  "dialog": {
53
+ "close": "close",
54
+ "apply": "apply",
51
55
  "link": {
52
56
  "title": "add/edit link",
53
- "placeholder": "link address",
54
- "apply": "apply"
57
+ "placeholder": "link address"
58
+ },
59
+ "source": {
60
+ "title": "view source code",
61
+ "prettify": "prettify"
55
62
  }
63
+ },
64
+ "misc": {
65
+ "source": "view source code"
56
66
  }
57
67
  }