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/LICENSE +21 -0
- package/README.md +78 -0
- package/components.d.ts +20 -0
- package/dist/tiptapify.css +1 -0
- package/dist/tiptapify.es.js +53260 -0
- package/dist/tiptapify.umd.js +109 -0
- package/package.json +92 -0
- package/src/components/CodeBlockComponent.vue +52 -0
- package/src/components/Footer.vue +30 -0
- package/src/components/MenuBubble.vue +68 -0
- package/src/components/MenuFloating.vue +54 -0
- package/src/components/Tiptapify.vue +254 -0
- package/src/components/Toolbar.vue +651 -0
- package/src/components/editorExtensions.ts +98 -0
- package/src/components/extensions/components/LinkDialog.vue +98 -0
- package/src/components/extensions/components/slashCommands/CommandsList.vue +103 -0
- package/src/components/extensions/components/slashCommands/suggestion.ts +107 -0
- package/src/components/extensions/slash-commands.ts +28 -0
- package/src/composable/useEditor.ts +35 -0
- package/src/i18n/index.ts +22 -0
- package/src/i18n/locales/en.json +50 -0
- package/src/i18n/locales/ru.json +50 -0
- package/src/i18n/locales/ua.json +50 -0
- package/src/index.ts +19 -0
- package/tsconfig.json +22 -0
- package/vite.config.ts +86 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { VueNodeViewRenderer } from '@tiptap/vue-3'
|
|
2
|
+
import { TextStyleKit } from '@tiptap/extension-text-style'
|
|
3
|
+
import { BulletList, OrderedList, ListItem, ListKeymap, TaskList, TaskItem } from '@tiptap/extension-list'
|
|
4
|
+
import { Selection, Focus, Placeholder, UndoRedo, Dropcursor, CharacterCount } from '@tiptap/extensions'
|
|
5
|
+
import { Document } from '@tiptap/extension-document'
|
|
6
|
+
import { Text } from '@tiptap/extension-text'
|
|
7
|
+
import { Paragraph } from '@tiptap/extension-paragraph'
|
|
8
|
+
import { Heading } from '@tiptap/extension-heading'
|
|
9
|
+
import { Bold } from '@tiptap/extension-bold'
|
|
10
|
+
import { Italic } from '@tiptap/extension-italic'
|
|
11
|
+
import { Strike } from '@tiptap/extension-strike'
|
|
12
|
+
import { Code } from '@tiptap/extension-code'
|
|
13
|
+
import { Blockquote } from '@tiptap/extension-blockquote'
|
|
14
|
+
import { HardBreak } from '@tiptap/extension-hard-break'
|
|
15
|
+
import { HorizontalRule } from '@tiptap/extension-horizontal-rule'
|
|
16
|
+
import { Typography } from '@tiptap/extension-typography'
|
|
17
|
+
import { Highlight } from '@tiptap/extension-highlight'
|
|
18
|
+
import { Image } from '@tiptap/extension-image'
|
|
19
|
+
import { Superscript } from '@tiptap/extension-superscript'
|
|
20
|
+
import { Subscript } from '@tiptap/extension-subscript'
|
|
21
|
+
import { TextAlign } from '@tiptap/extension-text-align'
|
|
22
|
+
import { Underline } from '@tiptap/extension-underline'
|
|
23
|
+
import { TableKit } from '@tiptap/extension-table'
|
|
24
|
+
import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight'
|
|
25
|
+
|
|
26
|
+
import { Link } from '@tiptap/extension-link'
|
|
27
|
+
import CodeBlockComponent from '@tiptapify/components/CodeBlockComponent.vue'
|
|
28
|
+
import SlashCommands from '@tiptapify/components/extensions/slash-commands'
|
|
29
|
+
import suggestion from '@tiptapify/components/extensions/components/slashCommands/suggestion'
|
|
30
|
+
|
|
31
|
+
// load all languages with "all" or common languages with "common"
|
|
32
|
+
import { common, createLowlight } from 'lowlight'
|
|
33
|
+
|
|
34
|
+
// create a lowlight instance
|
|
35
|
+
// using all available languages
|
|
36
|
+
const lowlight = createLowlight(common)
|
|
37
|
+
|
|
38
|
+
// or you can register specific languages
|
|
39
|
+
// const lowlight = createLowlight()
|
|
40
|
+
//
|
|
41
|
+
// import language example
|
|
42
|
+
// import ts from 'highlight.js/lib/languages/typescript'
|
|
43
|
+
//
|
|
44
|
+
// register language example
|
|
45
|
+
// lowlight.register('ts', ts)
|
|
46
|
+
|
|
47
|
+
export const editorExtensions = (placeholder: string = '') => [
|
|
48
|
+
TextStyleKit,
|
|
49
|
+
Document,
|
|
50
|
+
Text,
|
|
51
|
+
Paragraph,
|
|
52
|
+
Heading,
|
|
53
|
+
Bold,
|
|
54
|
+
Italic,
|
|
55
|
+
Strike,
|
|
56
|
+
Blockquote,
|
|
57
|
+
OrderedList,
|
|
58
|
+
BulletList,
|
|
59
|
+
TaskList,
|
|
60
|
+
TaskItem,
|
|
61
|
+
ListItem,
|
|
62
|
+
ListKeymap,
|
|
63
|
+
HardBreak,
|
|
64
|
+
HorizontalRule,
|
|
65
|
+
Dropcursor,
|
|
66
|
+
Typography,
|
|
67
|
+
Underline,
|
|
68
|
+
Highlight,
|
|
69
|
+
Link.configure({
|
|
70
|
+
openOnClick: false,
|
|
71
|
+
defaultProtocol: 'https',
|
|
72
|
+
}),
|
|
73
|
+
Image,
|
|
74
|
+
Superscript,
|
|
75
|
+
Subscript,
|
|
76
|
+
TableKit,
|
|
77
|
+
Code,
|
|
78
|
+
UndoRedo,
|
|
79
|
+
Focus,
|
|
80
|
+
CodeBlockLowlight
|
|
81
|
+
.extend({
|
|
82
|
+
addNodeView() {
|
|
83
|
+
return VueNodeViewRenderer(CodeBlockComponent)
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
.configure({ lowlight }),
|
|
87
|
+
Selection.configure({
|
|
88
|
+
className: 'selection',
|
|
89
|
+
}),
|
|
90
|
+
TextAlign.configure({
|
|
91
|
+
types: ['heading', 'paragraph'],
|
|
92
|
+
}),
|
|
93
|
+
Placeholder.configure({ placeholder }),
|
|
94
|
+
SlashCommands.configure({
|
|
95
|
+
suggestion,
|
|
96
|
+
}),
|
|
97
|
+
CharacterCount
|
|
98
|
+
]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import * as mdi from '@mdi/js'
|
|
4
|
+
import { useEditor } from "@tiptapify/composable/useEditor";
|
|
5
|
+
|
|
6
|
+
import { useI18n } from 'vue-i18n'
|
|
7
|
+
import { computed, ref, watch } from 'vue'
|
|
8
|
+
|
|
9
|
+
defineExpose({ open })
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
value?: string
|
|
13
|
+
target?: '_self' | '_blank'
|
|
14
|
+
destroy?: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
+
value: undefined,
|
|
19
|
+
target: '_blank',
|
|
20
|
+
destroy: undefined
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const editor = useEditor().editor
|
|
24
|
+
const editorInstance = editor.getInstance()
|
|
25
|
+
|
|
26
|
+
const { t } = useI18n()
|
|
27
|
+
|
|
28
|
+
const generateLinkAttrs = () => ({
|
|
29
|
+
href: '',
|
|
30
|
+
target: '_blank'
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const attrs = ref(generateLinkAttrs())
|
|
34
|
+
|
|
35
|
+
const dialog = ref<boolean>(false)
|
|
36
|
+
|
|
37
|
+
const isDisabled = computed(() => {
|
|
38
|
+
const { href, target } = attrs.value
|
|
39
|
+
if (!href) return true
|
|
40
|
+
|
|
41
|
+
return props.value === href && props.target === target
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
function apply() {
|
|
45
|
+
const { href, target } = attrs.value
|
|
46
|
+
|
|
47
|
+
if (href) {
|
|
48
|
+
editorInstance.value.chain().focus().extendMarkRange('link').setLink({ href, target }).run()
|
|
49
|
+
}
|
|
50
|
+
close()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function open() {
|
|
54
|
+
dialog.value = true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function close() {
|
|
58
|
+
dialog.value = false
|
|
59
|
+
attrs.value = generateLinkAttrs()
|
|
60
|
+
|
|
61
|
+
setTimeout(() => props.destroy?.(), 300)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
watch(dialog, val => {
|
|
65
|
+
if (!val) return
|
|
66
|
+
|
|
67
|
+
attrs.value = {
|
|
68
|
+
href: props.value,
|
|
69
|
+
target: props.target
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<VDialog v-model="dialog" max-width="400" absolute @click:outside="close">
|
|
76
|
+
<VCard>
|
|
77
|
+
<VToolbar class="px-6" density="compact">
|
|
78
|
+
<span class="headline">{{ t('dialog.link.title') }}</span>
|
|
79
|
+
|
|
80
|
+
<VSpacer />
|
|
81
|
+
|
|
82
|
+
<VBtn class="mx-0" icon @click="close">
|
|
83
|
+
<VIcon :icon="mdi.mdiClose" />
|
|
84
|
+
</VBtn>
|
|
85
|
+
</VToolbar>
|
|
86
|
+
|
|
87
|
+
<VCardText>
|
|
88
|
+
<VTextField v-model="attrs.href" :label="t('dialog.link.placeholder')" autofocus />
|
|
89
|
+
</VCardText>
|
|
90
|
+
|
|
91
|
+
<VCardActions>
|
|
92
|
+
<VBtn :disabled="isDisabled" @click="apply">
|
|
93
|
+
{{ t('dialog.link.apply') }}
|
|
94
|
+
</VBtn>
|
|
95
|
+
</VCardActions>
|
|
96
|
+
</VCard>
|
|
97
|
+
</VDialog>
|
|
98
|
+
</template>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch } from 'vue'
|
|
3
|
+
import * as mdi from '@mdi/js'
|
|
4
|
+
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
items: {
|
|
7
|
+
type: Array,
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
command: {
|
|
11
|
+
type: Function,
|
|
12
|
+
required: true,
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const selectedIndex = ref(0)
|
|
17
|
+
|
|
18
|
+
watch(() => props.items, () => {
|
|
19
|
+
selectedIndex.value = 0
|
|
20
|
+
}, { deep: true })
|
|
21
|
+
|
|
22
|
+
function onKeyDown({ event }) {
|
|
23
|
+
if (event.key === 'ArrowUp') {
|
|
24
|
+
upHandler()
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (event.key === 'ArrowDown') {
|
|
29
|
+
downHandler()
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (event.key === 'Enter') {
|
|
34
|
+
enterHandler()
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function upHandler() {
|
|
42
|
+
selectedIndex.value = (selectedIndex.value + props.items.length - 1) % props.items.length
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function downHandler() {
|
|
46
|
+
selectedIndex.value = (selectedIndex.value + 1) % props.items.length
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function enterHandler() {
|
|
50
|
+
selectItem(selectedIndex.value)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function selectItem(index) {
|
|
54
|
+
const item = props.items[index]
|
|
55
|
+
|
|
56
|
+
if (item) {
|
|
57
|
+
props.command(item)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
defineExpose({
|
|
62
|
+
onKeyDown,
|
|
63
|
+
upHandler,
|
|
64
|
+
downHandler,
|
|
65
|
+
enterHandler,
|
|
66
|
+
selectItem
|
|
67
|
+
})
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<template>
|
|
71
|
+
<div class="dropdown-menu">
|
|
72
|
+
<template v-if="items.length">
|
|
73
|
+
<VBtn
|
|
74
|
+
variant="text"
|
|
75
|
+
v-for="(item, index) in items"
|
|
76
|
+
:key="index"
|
|
77
|
+
@click="selectItem(index)"
|
|
78
|
+
size="small"
|
|
79
|
+
>
|
|
80
|
+
<VIcon v-if="item.icon" :icon="mdi[`mdi${item.icon}`]" size="16" />
|
|
81
|
+
<span v-else>
|
|
82
|
+
{{ item.title }}
|
|
83
|
+
</span>
|
|
84
|
+
</VBtn>
|
|
85
|
+
</template>
|
|
86
|
+
<div class="item" v-else>No result</div>
|
|
87
|
+
</div>
|
|
88
|
+
</template>
|
|
89
|
+
|
|
90
|
+
<style scoped lang="scss">
|
|
91
|
+
.dropdown-menu {
|
|
92
|
+
background: var(--white);
|
|
93
|
+
border: 1px solid var(--gray-1);
|
|
94
|
+
border-radius: 0.7rem;
|
|
95
|
+
box-shadow: var(--shadow);
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
gap: 0.1rem;
|
|
99
|
+
overflow: auto;
|
|
100
|
+
padding: 0.4rem;
|
|
101
|
+
position: relative;
|
|
102
|
+
}
|
|
103
|
+
</style>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { computePosition, flip, shift } from '@floating-ui/dom'
|
|
2
|
+
import { posToDOMRect, VueRenderer } from '@tiptap/vue-3'
|
|
3
|
+
|
|
4
|
+
import CommandsList from './CommandsList.vue'
|
|
5
|
+
|
|
6
|
+
const updatePosition = (editor, element) => {
|
|
7
|
+
const virtualElement = {
|
|
8
|
+
getBoundingClientRect: () => posToDOMRect(editor.view, editor.state.selection.from, editor.state.selection.to),
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
computePosition(virtualElement, element, {
|
|
12
|
+
placement: 'bottom-start',
|
|
13
|
+
strategy: 'absolute',
|
|
14
|
+
middleware: [shift(), flip()],
|
|
15
|
+
}).then(({ x, y, strategy }) => {
|
|
16
|
+
element.style.width = 'max-content'
|
|
17
|
+
element.style.position = strategy
|
|
18
|
+
element.style.left = `${x}px`
|
|
19
|
+
element.style.top = `${y}px`
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
items: ({ query }) => {
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
title: 'H1',
|
|
28
|
+
icon: 'FormatHeader1',
|
|
29
|
+
command: ({ editor, range }) => {
|
|
30
|
+
editor.chain().focus().deleteRange(range).setNode('heading', { level: 1 }).run()
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
title: 'H2',
|
|
35
|
+
icon: 'FormatHeader2',
|
|
36
|
+
command: ({ editor, range }) => {
|
|
37
|
+
editor.chain().focus().deleteRange(range).setNode('heading', { level: 2 }).run()
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: 'Bold',
|
|
42
|
+
icon: 'FormatBold',
|
|
43
|
+
command: ({ editor, range }) => {
|
|
44
|
+
editor.chain().focus().deleteRange(range).setMark('bold').run()
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
title: 'Italic',
|
|
49
|
+
icon: 'FormatItalic',
|
|
50
|
+
command: ({ editor, range }) => {
|
|
51
|
+
editor.chain().focus().deleteRange(range).setMark('italic').run()
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
.filter(item => item.title.toLowerCase().startsWith(query.toLowerCase()))
|
|
56
|
+
.slice(0, 10)
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
render: () => {
|
|
60
|
+
let component
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
onStart: props => {
|
|
64
|
+
component = new VueRenderer(CommandsList, {
|
|
65
|
+
props,
|
|
66
|
+
editor: props.editor,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (!props.clientRect) {
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
component.element.style.position = 'absolute'
|
|
74
|
+
|
|
75
|
+
document.body.appendChild(component.element)
|
|
76
|
+
|
|
77
|
+
updatePosition(props.editor, component.element)
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
onUpdate(props) {
|
|
81
|
+
component.updateProps(props)
|
|
82
|
+
|
|
83
|
+
if (!props.clientRect) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
updatePosition(props.editor, component.element)
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
onKeyDown(props) {
|
|
91
|
+
if (props.event.key === 'Escape') {
|
|
92
|
+
component.destroy()
|
|
93
|
+
component.element.remove()
|
|
94
|
+
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return component.ref?.onKeyDown(props)
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
onExit() {
|
|
102
|
+
component.destroy()
|
|
103
|
+
component.element.remove()
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core'
|
|
2
|
+
import Suggestion from '@tiptap/suggestion'
|
|
3
|
+
|
|
4
|
+
export default Extension.create(
|
|
5
|
+
{
|
|
6
|
+
name: 'slash-commands',
|
|
7
|
+
|
|
8
|
+
addOptions() {
|
|
9
|
+
return {
|
|
10
|
+
suggestion: {
|
|
11
|
+
char: '/',
|
|
12
|
+
command: ({editor, range, props}) => {
|
|
13
|
+
props.command({editor, range})
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
addProseMirrorPlugins() {
|
|
20
|
+
return [
|
|
21
|
+
Suggestion(
|
|
22
|
+
{
|
|
23
|
+
editor: this.editor,
|
|
24
|
+
...this.options.suggestion,
|
|
25
|
+
}),
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useEditor as useEditorOriginal } from '@tiptap/vue-3'
|
|
2
|
+
|
|
3
|
+
import { editorExtensions } from '@tiptapify/components/editorExtensions'
|
|
4
|
+
|
|
5
|
+
let editorInstance: any = null
|
|
6
|
+
|
|
7
|
+
export function useEditor(content: any = '', placeholder: string = '') {
|
|
8
|
+
class TiptapifyEditor {
|
|
9
|
+
private editor
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
const extensions = editorExtensions(placeholder)
|
|
13
|
+
this.editor = useEditorOriginal({
|
|
14
|
+
content,
|
|
15
|
+
extensions,
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getInstance() {
|
|
20
|
+
return this.editor
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
destroy() {
|
|
24
|
+
this.editor.value.destroy()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!editorInstance) {
|
|
29
|
+
editorInstance = new TiptapifyEditor()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
editor: editorInstance
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createI18n } from 'vue-i18n'
|
|
2
|
+
|
|
3
|
+
const messages = Object.fromEntries(
|
|
4
|
+
Object.entries(
|
|
5
|
+
import.meta.glob<{ default: any }>('./locales/*.json', { eager: true }))
|
|
6
|
+
.map(([key, value]) => [key.slice(10, -5), value.default]),
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
let _i18n: any = null
|
|
10
|
+
|
|
11
|
+
export const getI18n = (locale: string) => {
|
|
12
|
+
if (_i18n === null) {
|
|
13
|
+
_i18n = createI18n({
|
|
14
|
+
legacy: false,
|
|
15
|
+
locale: locale,
|
|
16
|
+
fallbackLocale: 'en',
|
|
17
|
+
messages
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return _i18n
|
|
22
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"style": {
|
|
3
|
+
"heading": "heading",
|
|
4
|
+
"fontFamily": "font family",
|
|
5
|
+
"fontSize": "font size",
|
|
6
|
+
"lineHeight": "line height"
|
|
7
|
+
},
|
|
8
|
+
"format": {
|
|
9
|
+
"bold": "bold",
|
|
10
|
+
"italic": "italic",
|
|
11
|
+
"strike": "strikethrough",
|
|
12
|
+
"underline": "underline",
|
|
13
|
+
"sup": "superscript",
|
|
14
|
+
"sub": "subscript",
|
|
15
|
+
"break": "hard break",
|
|
16
|
+
"highlight": "highlight",
|
|
17
|
+
"line": "horizontal line",
|
|
18
|
+
"blockquote": "cite",
|
|
19
|
+
"code": "code",
|
|
20
|
+
"codeblock": "code block",
|
|
21
|
+
"codeblock_syntax": "code block w/ syntax highlighting",
|
|
22
|
+
"link": "external link"
|
|
23
|
+
},
|
|
24
|
+
"action": {
|
|
25
|
+
"undo": "undo",
|
|
26
|
+
"redo": "redo"
|
|
27
|
+
},
|
|
28
|
+
"alignment": "alignment",
|
|
29
|
+
"alignments": {
|
|
30
|
+
"left": "align left",
|
|
31
|
+
"center": "align center",
|
|
32
|
+
"right": "align right",
|
|
33
|
+
"justify": "justify"
|
|
34
|
+
},
|
|
35
|
+
"list": "list",
|
|
36
|
+
"lists": {
|
|
37
|
+
"bullet": "unordered list",
|
|
38
|
+
"numbered": "ordered list",
|
|
39
|
+
"task": "task list",
|
|
40
|
+
"indent": "list item indent",
|
|
41
|
+
"outdent": "list item outdent"
|
|
42
|
+
},
|
|
43
|
+
"dialog": {
|
|
44
|
+
"link": {
|
|
45
|
+
"title": "add/edit link",
|
|
46
|
+
"placeholder": "link address",
|
|
47
|
+
"apply": "apply"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"style": {
|
|
3
|
+
"heading": "заголовок",
|
|
4
|
+
"fontFamily": "шрифт",
|
|
5
|
+
"fontSize": "размер",
|
|
6
|
+
"lineHeight": "высота строки"
|
|
7
|
+
},
|
|
8
|
+
"format": {
|
|
9
|
+
"bold": "жирный",
|
|
10
|
+
"italic": "курсив",
|
|
11
|
+
"strike": "зачеркивание",
|
|
12
|
+
"underline": "подчеркивание",
|
|
13
|
+
"sup": "верхний индекс",
|
|
14
|
+
"sub": "нижний индекс",
|
|
15
|
+
"break": "разрыв строки",
|
|
16
|
+
"highlight": "выделение",
|
|
17
|
+
"line": "горизонтальная линия",
|
|
18
|
+
"blockquote": "цитата",
|
|
19
|
+
"code": "код",
|
|
20
|
+
"codeblock": "блок кода",
|
|
21
|
+
"codeblock_syntax": "блок кода с подсветкой синтаксиса",
|
|
22
|
+
"link": "внешняя ссылка"
|
|
23
|
+
},
|
|
24
|
+
"action": {
|
|
25
|
+
"undo": "отмена",
|
|
26
|
+
"redo": "повтор"
|
|
27
|
+
},
|
|
28
|
+
"alignment": "выравнивание",
|
|
29
|
+
"alignments": {
|
|
30
|
+
"left": "выравнивание по левому краю",
|
|
31
|
+
"center": "выравнивание по центру",
|
|
32
|
+
"right": "выравнивание по правому краю",
|
|
33
|
+
"justify": "выравнивание по ширине"
|
|
34
|
+
},
|
|
35
|
+
"list": "список",
|
|
36
|
+
"lists": {
|
|
37
|
+
"bullet": "ненумерованный список",
|
|
38
|
+
"numbered": "нумерованный список",
|
|
39
|
+
"task": "список задач",
|
|
40
|
+
"indent": "увеличить отступ элемента списка",
|
|
41
|
+
"outdent": "уменьшить отступ элемента списка"
|
|
42
|
+
},
|
|
43
|
+
"dialog": {
|
|
44
|
+
"link": {
|
|
45
|
+
"title": "добавление/изменение ссылки",
|
|
46
|
+
"placeholder": "адрес ссылки",
|
|
47
|
+
"apply": "применить"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"style": {
|
|
3
|
+
"heading": "заголовок",
|
|
4
|
+
"fontFamily": "шрифт",
|
|
5
|
+
"fontSize": "розмір",
|
|
6
|
+
"lineHeight": "висота строки"
|
|
7
|
+
},
|
|
8
|
+
"format": {
|
|
9
|
+
"bold": "жирний",
|
|
10
|
+
"italic": "курсив",
|
|
11
|
+
"strike": "закреслення",
|
|
12
|
+
"underline": "підкреслення",
|
|
13
|
+
"sup": "верхній індекс",
|
|
14
|
+
"sub": "нижній індекс",
|
|
15
|
+
"break": "розрив строки",
|
|
16
|
+
"highlight": "виділення",
|
|
17
|
+
"line": "горизонтальна лінія",
|
|
18
|
+
"blockquote": "цитата",
|
|
19
|
+
"code": "код",
|
|
20
|
+
"codeblock": "блок коду",
|
|
21
|
+
"codeblock_syntax": "блок коду з підсвіткою синтаксиса",
|
|
22
|
+
"link": "зовнішнє посилання"
|
|
23
|
+
},
|
|
24
|
+
"action": {
|
|
25
|
+
"undo": "відміна",
|
|
26
|
+
"redo": "повтор"
|
|
27
|
+
},
|
|
28
|
+
"alignment": "вирівнювання",
|
|
29
|
+
"alignments": {
|
|
30
|
+
"left": "вирівнювання по лівому краю",
|
|
31
|
+
"center": "вирівнювання по центру",
|
|
32
|
+
"right": "вирівнювання по правому краю",
|
|
33
|
+
"justify": "вирівнювання по ширині"
|
|
34
|
+
},
|
|
35
|
+
"list": "список",
|
|
36
|
+
"lists": {
|
|
37
|
+
"bullet": "ненумерований список",
|
|
38
|
+
"numbered": "нумерований список",
|
|
39
|
+
"task": "список задач",
|
|
40
|
+
"indent": "збільшити відступ елемента списку",
|
|
41
|
+
"outdent": "зменшити відступ елемента списку"
|
|
42
|
+
},
|
|
43
|
+
"dialog": {
|
|
44
|
+
"link": {
|
|
45
|
+
"title": "додавання/зміна посилання",
|
|
46
|
+
"placeholder": "адреса посилання",
|
|
47
|
+
"apply": "застосувати"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Plugin } from 'vue';
|
|
2
|
+
import Tiptapify from '@tiptapify/components/Tiptapify.vue';
|
|
3
|
+
import { getI18n } from "@tiptapify/i18n";
|
|
4
|
+
|
|
5
|
+
interface PackageOptions {
|
|
6
|
+
locale?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const TiptapifyPlugin: Plugin = {
|
|
10
|
+
install(app, options: PackageOptions = {}) {
|
|
11
|
+
const locale = options.locale || 'en'
|
|
12
|
+
app.use(getI18n(locale));
|
|
13
|
+
app.component('Tiptapify', Tiptapify);
|
|
14
|
+
app.component('v-tiptap', Tiptapify);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { Tiptapify };
|
|
19
|
+
export default TiptapifyPlugin;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Node",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"jsx": "preserve",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"lib": ["ESNext", "DOM"],
|
|
13
|
+
"types": ["vite/client", "node"],
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationDir": "lib",
|
|
16
|
+
"baseUrl": ".",
|
|
17
|
+
"paths": {
|
|
18
|
+
"@tiptapify/*": ["src/*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["src/**/*"]
|
|
22
|
+
}
|