tiptapify 0.0.25 → 0.0.27

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,7 +1,7 @@
1
1
  {
2
2
  "name": "tiptapify",
3
3
  "types": "./index.d.ts",
4
- "version": "0.0.25",
4
+ "version": "0.0.27",
5
5
  "description": "Tiptap3 editor with Vuetify3 menu implementation",
6
6
  "exports": {
7
7
  ".": {
@@ -93,30 +93,34 @@ onBeforeUnmount(() => {
93
93
  </script>
94
94
 
95
95
  <template>
96
- <template v-if="toolbar">
97
- <Toolbar
98
- v-if="editor"
99
- :variant-btn="variantBtn"
100
- :variant-field="variantField"
101
- :font-measure="fontMeasure"
102
- :items="propsItems"
103
- :items-exclude="propsItemsExclude"
104
- :rounded="rounded"
105
- :custom-extensions="customExtensions"
106
- :theme="currentTheme"
107
- />
108
- </template>
109
-
110
- <div :class="`tiptapify-editor border border-t-0 rounded-b-${rounded}`">
111
- <div class="pa-2 tiptapify-container resizable" :style="`${height > 0 ? `height: ${height}px` : ''}`">
112
- <MenuFloating v-if="floatingMenu" :variant="variantBtn" :theme="currentTheme" />
113
-
114
- <MenuBubble v-if="bubbleMenu" :variant="variantBtn" :theme="currentTheme" />
115
-
116
- <EditorContent :editor="editor" class="tiptapify-editor" />
96
+ <div :id="`tiptapify-editor-${editor?.instanceId}`">
97
+ <div>
98
+ <template v-if="toolbar">
99
+ <Toolbar
100
+ v-if="editor"
101
+ :variant-btn="variantBtn"
102
+ :variant-field="variantField"
103
+ :font-measure="fontMeasure"
104
+ :items="propsItems"
105
+ :items-exclude="propsItemsExclude"
106
+ :rounded="rounded"
107
+ :custom-extensions="customExtensions"
108
+ :theme="currentTheme"
109
+ />
110
+ </template>
111
+
112
+ <div :class="`border border-t-0 rounded-b-${rounded}`">
113
+ <div class="pa-2 tiptapify-container resizable" :style="`${height > 0 ? `height: ${height}px` : ''}`">
114
+ <MenuFloating v-if="floatingMenu" :variant="variantBtn" :theme="currentTheme" />
115
+
116
+ <MenuBubble v-if="bubbleMenu" :variant="variantBtn" :theme="currentTheme" />
117
+
118
+ <EditorContent :editor="editor" class="tiptapify-editor" />
119
+ </div>
120
+
121
+ <Footer :show-words-count="showWordsCount" :show-characters-count="showCharactersCount" />
122
+ </div>
117
123
  </div>
118
-
119
- <Footer :show-words-count="showWordsCount" :show-characters-count="showCharactersCount" />
120
124
  </div>
121
125
  </template>
122
126
 
@@ -410,5 +414,12 @@ onBeforeUnmount(() => {
410
414
  cursor: col-resize;
411
415
  }
412
416
 
417
+ ul.list-style-circle {
418
+ list-style-type: circle !important;
419
+ }
420
+
421
+ ul.list-style-square {
422
+ list-style-type: square !important;
423
+ }
413
424
  }
414
425
  </style>
@@ -125,6 +125,20 @@ function addToolbarItem(_toolbarItems: toolbarItemsType, itemsList: any, itemTit
125
125
  }
126
126
  }
127
127
 
128
+ if (_itemTitle === 'bulletList' && _itemOptions.length > 0) {
129
+ const paragraphIndex = _itemOptions.findIndex((item: any) => item === 'p')
130
+ const withParagraph = paragraphIndex > -1
131
+ if (withParagraph) {
132
+ _itemOptions.splice(paragraphIndex, 1)
133
+ }
134
+
135
+ component.props = {
136
+ withDisc: _itemOptions.includes('disc'),
137
+ withCircle: _itemOptions.includes('circle'),
138
+ withSquare: _itemOptions.includes('square'),
139
+ }
140
+ }
141
+
128
142
  _toolbarItems[itemSection].components.push(component)
129
143
 
130
144
  return true
@@ -1,6 +1,7 @@
1
1
  import { default as LineButton } from "@tiptapify/extensions/components/misc/line/Button.vue";
2
2
  import { default as BreakButton } from "@tiptapify/extensions/components/misc/break/Button.vue";
3
3
  import { default as PreviewButton } from "@tiptapify/extensions/components/misc/preview/Button.vue";
4
+ import { default as FullscreenButton } from "@tiptapify/extensions/components/misc/fullscreen/Button.vue";
4
5
  import { default as SourceButton } from "@tiptapify/extensions/components/misc/source/Button.vue";
5
6
  import { default as InvisibleCharButton } from "@tiptapify/extensions/components/misc/invisibleChar/Button.vue";
6
7
  import { default as FormatClearButton } from "@tiptapify/extensions/components/misc/formatClear/Button.vue";
@@ -26,6 +27,10 @@ export default {
26
27
  name: 'preview',
27
28
  component: markRaw(PreviewButton),
28
29
  },
30
+ {
31
+ name: 'fullscreen',
32
+ component: markRaw(FullscreenButton),
33
+ },
29
34
  {
30
35
  name: 'formatClear',
31
36
  component: markRaw(FormatClearButton),
@@ -13,12 +13,12 @@ const isSvgString = (icon: any) => {
13
13
 
14
14
  <template>
15
15
  <div v-if="isSvgString(icon)" v-html="icon" class="v-icon tiptapify-btn-svg-icon"></div>
16
- <VIcon v-else :icon="icon || `mdiSvg:${mdi.mdiImageBrokenVariant}`" size="small" />
16
+ <VIcon v-else :icon="icon || `mdiSvg:${mdi.mdiImageBrokenVariant}`" size="20" />
17
17
  </template>
18
18
 
19
19
  <style lang="scss" scoped>
20
20
  .tiptapify-btn-svg-icon {
21
- width: 16px;
22
- height: 16px;
21
+ width: 20px;
22
+ height: 20px;
23
23
  }
24
24
  </style>
@@ -105,7 +105,11 @@ watch(() => dialog.value, async () => {
105
105
  <template>
106
106
  <VDialog v-model="dialog" :max-width="maxWidth" :fullscreen="fullscreen" @click:outside="emitClose">
107
107
  <VCard>
108
- <VCardTitle ref="movableHandler" :class="`d-flex ${!fullscreen ? 'tiptapify-movable-handler' : ''}`" style="user-select: none;">
108
+ <VCardTitle
109
+ ref="movableHandler"
110
+ :class="`d-flex ${!fullscreen ? 'tiptapify-movable-handler' : ''}`"
111
+ style="user-select: none;"
112
+ >
109
113
  <VLabel>
110
114
  {{ title ?? t(`dialog.${module}.title`) }}
111
115
  </VLabel>
@@ -25,12 +25,11 @@ import { TableKit } from '@tiptap/extension-table'
25
25
  import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
26
26
  import { InvisibleCharacters } from '@tiptap/extension-invisible-characters'
27
27
 
28
+ import { BulletListCircle, BulletListSquare } from '@tiptapify/extensions/components/list/bullet'
28
29
  import { TiptapifyLink } from '@tiptapify/extensions/components/media/link'
29
30
  import { TiptapifyImage } from '@tiptapify/extensions/components/media/image'
30
31
  import { TiptapifyVideo } from '@tiptapify/extensions/components/media/video'
31
32
  import CodeBlockComponent from '@tiptapify/extensions/components/CodeBlockComponent.vue'
32
- import { ViewSource } from '@tiptapify/extensions/components/misc/source'
33
- import { Preview } from '@tiptapify/extensions/components/misc/preview'
34
33
  import SlashCommands from '@tiptapify/extensions/slash-commands'
35
34
  import suggestion from '@tiptapify/extensions/components/slashCommands/suggestion'
36
35
  import { toolbarSections } from "@tiptapify/types/toolbarTypes";
@@ -102,11 +101,11 @@ export function editorExtensions (placeholder: string, slashCommands: boolean, c
102
101
  TextAlign.configure({ types: ['heading', 'paragraph'] }),
103
102
  Placeholder.configure({ placeholder }),
104
103
  CharacterCount,
105
- ViewSource,
106
104
  InvisibleCharacters.configure({
107
105
  visible: false,
108
106
  }),
109
- Preview
107
+ BulletListCircle,
108
+ BulletListSquare
110
109
  ]
111
110
 
112
111
  if (slashCommands) {
@@ -3,22 +3,117 @@
3
3
  import * as mdi from '@mdi/js'
4
4
  import { Editor } from "@tiptap/vue-3";
5
5
  import BtnIcon from "@tiptapify/components/UI/BtnIcon.vue";
6
- import { inject, Ref } from "vue";
6
+ import { computed, inject, ref, Ref, watch } from "vue";
7
7
 
8
8
  import defaults from '@tiptapify/constants/defaults'
9
9
 
10
- defineProps({
11
- variantBtn: { type: String, default: defaults.variantBtn }
10
+ const props = defineProps({
11
+ variantBtn: { type: String, default: defaults.variantBtn },
12
+ withDisc: { type: Boolean, default: true },
13
+ withCircle: { type: Boolean, default: true },
14
+ withSquare: { type: Boolean, default: true }
12
15
  })
13
16
 
14
17
  const editor = inject('tiptapifyEditor') as Ref<Editor>
15
18
 
16
19
  const { t } = inject('tiptapifyI18n') as any
17
20
 
21
+ const disc = computed(() => props.withDisc)
22
+ const circle = computed(() => props.withCircle)
23
+ const square = computed(() => props.withSquare)
24
+
25
+ const iconDisc = '<svg width="48" height="48" focusable="false" viewBox="5 5 32 32"><g fill-rule="evenodd"><circle cx="11" cy="14" r="3"></circle><circle cx="11" cy="24" r="3"></circle><circle cx="11" cy="34" r="3"></circle><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"></path></g></svg>'
26
+ const iconCircle = '<svg width="48" height="48" focusable="false" viewBox="5 5 32 32"><g fill-rule="evenodd"><path d="M11 16a2 2 0 1 0 0-4 2 2 0 0 0 0 4Zm0 1a3 3 0 1 1 0-6 3 3 0 0 1 0 6ZM11 26a2 2 0 1 0 0-4 2 2 0 0 0 0 4Zm0 1a3 3 0 1 1 0-6 3 3 0 0 1 0 6ZM11 36a2 2 0 1 0 0-4 2 2 0 0 0 0 4Zm0 1a3 3 0 1 1 0-6 3 3 0 0 1 0 6Z" fill-rule="nonzero"></path><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"></path></g></svg>'
27
+ const iconSquare = '<svg width="48" height="48" focusable="false" viewBox="5 5 32 32"><g fill-rule="evenodd"><path d="M8 11h6v6H8zM8 21h6v6H8zM8 31h6v6H8z"></path><path opacity=".2" d="M18 12h22v4H18zM18 22h22v4H18zM18 32h22v4H18z"></path>'
28
+
29
+ const bulletLists = ref({
30
+ disc: {
31
+ name: 'bulletList',
32
+ enabled: disc.value,
33
+ icon: iconDisc
34
+ },
35
+ circle: {
36
+ name: 'bulletListCircle',
37
+ enabled: circle.value,
38
+ icon: iconCircle
39
+ },
40
+ square: {
41
+ name: 'bulletListSquare',
42
+ enabled: square.value,
43
+ icon: iconSquare
44
+ }
45
+ })
46
+ const menuItems = ref({})
47
+ const isMenu = ref(false)
48
+
49
+ function checkButtonOrMenu() {
50
+ menuItems.value = Object.values(bulletLists.value).filter(item => item.enabled)
51
+ isMenu.value = menuItems.value.length > 1
52
+ }
53
+ checkButtonOrMenu()
54
+
55
+ function toggleList(listType: string) {
56
+ switch(listType) {
57
+ case 'disc': editor.value.commands.toggleBulletList(); break;
58
+ case 'circle': editor.value.commands.toggleBulletListCircle(); break;
59
+ case 'square': editor.value.commands.toggleBulletListSquare(); break;
60
+ }
61
+ }
62
+
63
+ const buttonActive = computed(() => {
64
+ return editor.value.isActive('bulletList') ||
65
+ editor.value.isActive('bulletListCircle') ||
66
+ editor.value.isActive('bulletListSquare')
67
+ })
68
+
69
+ const buttonDisabled = computed(() => {
70
+ return !editor.value.can().chain().focus().toggleBulletList().run() &&
71
+ !editor.value.can().chain().focus().toggleBulletListCircle().run() &&
72
+ !editor.value.can().chain().focus().toggleBulletListSquare().run()
73
+ })
74
+
75
+ watch(() => bulletLists.value, () => {
76
+ checkButtonOrMenu()
77
+ }, { deep: true, immediate: true })
18
78
  </script>
19
79
 
20
80
  <template>
81
+ <VMenu v-if="isMenu">
82
+ <template #activator="{ props: menuProps }">
83
+ <VBtn
84
+ :color="buttonActive ? 'primary' : ''"
85
+ :disabled="buttonDisabled"
86
+ :variant="variantBtn"
87
+ v-bind="menuProps"
88
+ size="32"
89
+ >
90
+ <VTooltip activator="parent">
91
+ {{ t('lists.bullet') }}
92
+ </VTooltip>
93
+ <BtnIcon :icon="`mdiSvg:${mdi.mdiFormatListBulleted}`" />
94
+ </VBtn>
95
+ </template>
96
+
97
+ <VList>
98
+ <VListItem
99
+ v-for="(bulletList, key) in menuItems"
100
+ :key="key"
101
+ link
102
+ :active="editor.isActive(bulletList.name)"
103
+ @click="toggleList(key)"
104
+ >
105
+ <VTooltip activator="parent">
106
+ {{ t('lists.bullet') }}
107
+ </VTooltip>
108
+ <VListItemTitle class="d-flex justify-center align-center">
109
+ <BtnIcon :icon="bulletList.icon" />
110
+ </VListItemTitle>
111
+ </VListItem>
112
+ </VList>
113
+ </VMenu>
114
+
21
115
  <VBtn
116
+ v-else
22
117
  :color="editor.isActive('bulletList') ? 'primary' : ''"
23
118
  :disabled="!editor.can().chain().focus().toggleBulletList().run()"
24
119
  :variant="variantBtn"
@@ -0,0 +1,101 @@
1
+ import { BulletList } from "@tiptap/extension-list"
2
+
3
+ export const BulletListCircle = BulletList.extend({
4
+ name: 'bulletListCircle',
5
+
6
+ addAttributes() {
7
+ return {
8
+ class: {
9
+ default: null,
10
+ renderHTML: (attributes: any) => {
11
+ return {
12
+ class: `${attributes.class}`,
13
+ }
14
+ },
15
+ parseHTML: (element: any) => element.getAttribute('class'),
16
+ }
17
+ }
18
+ },
19
+
20
+ addOptions() {
21
+ return {
22
+ itemTypeName: 'listItem',
23
+ HTMLAttributes: {
24
+ class: 'list-style-circle',
25
+ },
26
+ keepMarks: false,
27
+ keepAttributes: false,
28
+ }
29
+ },
30
+
31
+ parseHTML() {
32
+ return [
33
+ { tag: 'ul', class: 'list-style-circle' }
34
+ ]
35
+ },
36
+
37
+ addCommands() {
38
+ return {
39
+ toggleBulletListCircle: () =>
40
+ ({ commands, chain }) => {
41
+ if (this.options.keepAttributes) {
42
+ return chain()
43
+ .toggleList(this.name, this.options.itemTypeName, this.options.keepMarks)
44
+ .updateAttributes('listItem', this.editor.getAttributes('textStyle'))
45
+ .run()
46
+ }
47
+ return commands.toggleList(this.name, this.options.itemTypeName, this.options.keepMarks)
48
+ },
49
+ }
50
+ }
51
+ })
52
+
53
+ export const BulletListSquare = BulletList.extend({
54
+ name: 'bulletListSquare',
55
+
56
+ addAttributes() {
57
+ return {
58
+ class: {
59
+ default: null,
60
+ renderHTML: (attributes: any) => {
61
+ return {
62
+ class: `${attributes.class}`,
63
+ }
64
+ },
65
+ parseHTML: (element: any) => element.getAttribute('class'),
66
+ }
67
+ }
68
+ },
69
+
70
+ addOptions() {
71
+ return {
72
+ itemTypeName: 'listItem',
73
+ HTMLAttributes: {
74
+ class: 'list-style-square',
75
+ },
76
+ keepMarks: false,
77
+ keepAttributes: false,
78
+ }
79
+ },
80
+
81
+ parseHTML() {
82
+ return [
83
+ { tag: 'ul', class: 'list-style-square' }
84
+ ]
85
+ },
86
+
87
+ addCommands() {
88
+ return {
89
+ toggleBulletListSquare: () =>
90
+ ({ commands, chain }) => {
91
+ if (this.options.keepAttributes) {
92
+ return chain()
93
+ .toggleList(this.name, this.options.itemTypeName, this.options.keepMarks)
94
+ .updateAttributes('listItem', this.editor.getAttributes('textStyle'))
95
+ .run()
96
+ }
97
+ return commands.toggleList(this.name, this.options.itemTypeName, this.options.keepMarks)
98
+ },
99
+ }
100
+ }
101
+ })
@@ -0,0 +1,61 @@
1
+ <script lang="ts" setup>
2
+
3
+ import * as mdi from '@mdi/js'
4
+ import { Editor } from "@tiptap/vue-3";
5
+ import BtnIcon from "@tiptapify/components/UI/BtnIcon.vue";
6
+ import { inject, nextTick, ref, Ref } from "vue";
7
+
8
+ import defaults from '@tiptapify/constants/defaults'
9
+
10
+ defineProps({
11
+ variantBtn: { type: String, default: defaults.variantBtn }
12
+ })
13
+
14
+ const editor = inject('tiptapifyEditor') as Ref<Editor>
15
+
16
+ const { t } = inject('tiptapifyI18n') as any
17
+
18
+ const dialog = ref(false)
19
+
20
+ async function dialogOpen() {
21
+ dialog.value = true
22
+
23
+ await changeEditorContainer('tiptapify-editor', 'tiptapify-editor-fullscreen')
24
+ }
25
+
26
+ async function dialogClose() {
27
+ dialog.value = false
28
+
29
+ await changeEditorContainer('tiptapify-editor-fullscreen', 'tiptapify-editor')
30
+ }
31
+
32
+ async function changeEditorContainer(source: string, target: string) {
33
+ await nextTick()
34
+
35
+ const sourceElm = document.querySelector(`#${source}-${editor.value.instanceId} > div`);
36
+
37
+ const targetElm = document.querySelector(`#${target}-${editor.value.instanceId}`);
38
+
39
+ targetElm.appendChild(sourceElm);
40
+ }
41
+
42
+ </script>
43
+
44
+ <template>
45
+ <VBtn @click="dialog ? dialogClose() : dialogOpen()" size="32" :variant="variantBtn">
46
+ <VTooltip activator="parent">
47
+ {{ t('misc.preview') }}
48
+ </VTooltip>
49
+ <BtnIcon :icon="dialog ? `mdiSvg:${mdi.mdiFullscreenExit}` : `mdiSvg:${mdi.mdiFullscreen}`" />
50
+ </VBtn>
51
+
52
+ <VDialog v-model="dialog" fullscreen @close="dialogClose()" @update:modelValue="!dialog ? dialogClose() : ''">
53
+ <VCard>
54
+ <div :id="`tiptapify-editor-fullscreen-${editor?.instanceId}`"></div>
55
+ </VCard>
56
+ </VDialog>
57
+ </template>
58
+
59
+ <style lang="scss" scoped>
60
+
61
+ </style>
@@ -1,41 +0,0 @@
1
- import { Extension } from '@tiptap/core'
2
- import { Plugin, PluginKey } from '@tiptap/pm/state'
3
-
4
- const name: string = 'preview'
5
-
6
- declare module '@tiptap/core' {
7
- interface Commands<ReturnType> {
8
- preview: {
9
- showPreview: () => ReturnType
10
- }
11
- }
12
- }
13
-
14
- export const Preview = Extension.create({
15
- name,
16
-
17
- addCommands() {
18
- return {
19
- showPreview: () => ({ editor }) => {
20
- const event = new CustomEvent(`tiptapify-show-${name}`, {
21
- detail: {
22
- html: editor.getHTML(),
23
- editorId: editor.instanceId
24
- }
25
- })
26
-
27
- window.dispatchEvent(event)
28
-
29
- return true
30
- },
31
- }
32
- },
33
-
34
- addProseMirrorPlugins() {
35
- return [
36
- new Plugin({
37
- key: new PluginKey(name),
38
- }),
39
- ]
40
- },
41
- })
@@ -1,49 +0,0 @@
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
- showSource: () => ReturnType
12
- }
13
- }
14
- }
15
-
16
- export const ViewSource = Extension.create<ViewSourceOptions>({
17
- name: 'viewSource',
18
-
19
- addOptions() {
20
- return {
21
- HTMLAttributes: {},
22
- }
23
- },
24
-
25
- addCommands() {
26
- return {
27
- showSource: () => ({ editor }) => {
28
- const event = new CustomEvent('tiptapify-show-source', {
29
- detail: {
30
- html: editor.getHTML(),
31
- editorId: editor.instanceId
32
- }
33
- })
34
-
35
- window.dispatchEvent(event)
36
-
37
- return true
38
- },
39
- }
40
- },
41
-
42
- addProseMirrorPlugins() {
43
- return [
44
- new Plugin({
45
- key: new PluginKey('viewSource'),
46
- }),
47
- ]
48
- },
49
- })