@live-change/wysiwyg-frontend 0.0.3
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/front/index.html +11 -0
- package/front/public/favicon.ico +0 -0
- package/front/public/images/empty-photo.svg +38 -0
- package/front/public/images/empty-user-photo.svg +33 -0
- package/front/public/images/logo.svg +34 -0
- package/front/public/images/logo128.png +0 -0
- package/front/src/App.vue +32 -0
- package/front/src/ContentView.js +23 -0
- package/front/src/DocumentEditor.vue +130 -0
- package/front/src/DocumentEditorTest.vue +55 -0
- package/front/src/Editor.vue +104 -0
- package/front/src/EditorMenu.vue +123 -0
- package/front/src/EditorWithPreview.vue +65 -0
- package/front/src/ImageComponent.vue +35 -0
- package/front/src/ImageNode.js +52 -0
- package/front/src/NavBar.vue +105 -0
- package/front/src/ProseMirrorCollab.js +21 -0
- package/front/src/contentConfig.js +50 -0
- package/front/src/contentConfigExtensions.js +58 -0
- package/front/src/entry-client.js +6 -0
- package/front/src/entry-server.js +6 -0
- package/front/src/router.js +44 -0
- package/front/src/schemaJson.js +28 -0
- package/front/vite.config.js +18 -0
- package/index.js +0 -0
- package/package.json +95 -0
- package/server/init.js +5 -0
- package/server/rich.documentType.js +87 -0
- package/server/services.config.js +24 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div class="grid w-full">
|
|
4
|
+
<div class="col relative">
|
|
5
|
+
<Editor v-model="content" :config="contentConfig" />
|
|
6
|
+
</div>
|
|
7
|
+
<div class="col relative">
|
|
8
|
+
<ContentView :content="content" :config="contentConfig" />
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<pre>{{ JSON.stringify(JSON.parse(content), null, ' ') }}</pre>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup>
|
|
18
|
+
import ContentView from "./ContentView.js"
|
|
19
|
+
import Editor from "./Editor.vue"
|
|
20
|
+
import { ref } from 'vue'
|
|
21
|
+
import { basicMarks, messageNodes, richEditorNodes } from "./contentConfig.js"
|
|
22
|
+
|
|
23
|
+
const contentConfig = {
|
|
24
|
+
marks: {
|
|
25
|
+
...basicMarks
|
|
26
|
+
},
|
|
27
|
+
nodes: {
|
|
28
|
+
//...messageNodes,
|
|
29
|
+
...richEditorNodes
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const content = ref(JSON.stringify({
|
|
34
|
+
"type": "doc",
|
|
35
|
+
"content": [ {
|
|
36
|
+
"type": "image",
|
|
37
|
+
"attrs": {
|
|
38
|
+
"image": "[Wc6GBWx5c.0@1V6ZJBwyF_1I98X_]"
|
|
39
|
+
}
|
|
40
|
+
}].concat(new Array(2).fill(
|
|
41
|
+
{
|
|
42
|
+
"type": "paragraph",
|
|
43
|
+
"content": [
|
|
44
|
+
{
|
|
45
|
+
"type": "text",
|
|
46
|
+
"text": "test"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"type": "text",
|
|
50
|
+
"marks": [
|
|
51
|
+
{
|
|
52
|
+
"type": "bold"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"text": "est"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
))
|
|
60
|
+
}))
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<style scoped>
|
|
64
|
+
|
|
65
|
+
</style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<node-view-wrapper>
|
|
3
|
+
<Image :image="image" width="100" dom-resize="width" />
|
|
4
|
+
</node-view-wrapper>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
|
|
9
|
+
import { inject, computed, onUnmounted } from 'vue'
|
|
10
|
+
import { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3'
|
|
11
|
+
import { imageUploads, Image } from "@live-change/image-frontend";
|
|
12
|
+
|
|
13
|
+
const upload = computed(() => {
|
|
14
|
+
return imageUploads.value.find(upload => upload.id == props.image)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const props = defineProps(nodeViewProps)
|
|
18
|
+
const image = computed(() => props.node.attrs.image )
|
|
19
|
+
|
|
20
|
+
const form = inject('form', null)
|
|
21
|
+
if(form) {
|
|
22
|
+
const barrier = async () => {
|
|
23
|
+
if(upload.value) await upload.value.upload()
|
|
24
|
+
}
|
|
25
|
+
form.addBarrier('', barrier)
|
|
26
|
+
onUnmounted(() => {
|
|
27
|
+
form.removeBarrier('', barrier)
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<style scoped>
|
|
34
|
+
|
|
35
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Node } from '@tiptap/core'
|
|
2
|
+
import { VueNodeViewRenderer } from '@tiptap/vue-3'
|
|
3
|
+
import ImageComponent from "./ImageComponent.vue"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export default Node.create({
|
|
7
|
+
name: 'image',
|
|
8
|
+
group: 'block',
|
|
9
|
+
atom: true,
|
|
10
|
+
inline: false,
|
|
11
|
+
selectable: true,
|
|
12
|
+
draggable: true,
|
|
13
|
+
content: '',
|
|
14
|
+
marks: '',
|
|
15
|
+
|
|
16
|
+
addAttributes() {
|
|
17
|
+
return {
|
|
18
|
+
image: {
|
|
19
|
+
rendered: false
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
/*parseHTML() {
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
tag: this.options.allowBase64
|
|
28
|
+
? 'img[src]'
|
|
29
|
+
: 'img[src]:not([src^="data:"])',
|
|
30
|
+
},
|
|
31
|
+
]
|
|
32
|
+
},*/
|
|
33
|
+
|
|
34
|
+
renderHTML({ }) {
|
|
35
|
+
return ['img', {}]
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
addNodeView() {
|
|
39
|
+
return VueNodeViewRenderer(ImageComponent)
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
addCommands() {
|
|
43
|
+
return {
|
|
44
|
+
setImage: options => ({ commands }) => {
|
|
45
|
+
return commands.insertContent({
|
|
46
|
+
type: this.name,
|
|
47
|
+
attrs: options
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="surface-overlay py-3 px-6 shadow-2 flex align-items-center justify-content-between
|
|
3
|
+
relative md:sticky top-0 z-5"
|
|
4
|
+
style="min-height: 80px" key="navbar">
|
|
5
|
+
<img src="/images/logo.svg" alt="Image" height="40" class="mr-0 lg:mr-6">
|
|
6
|
+
<a v-ripple class="cursor-pointer block lg:hidden text-700 p-ripple"
|
|
7
|
+
v-styleclass="{ selector: '@next', enterClass: 'hidden', leaveToClass: 'hidden', hideOnOutsideClick: true }">
|
|
8
|
+
<i class="pi pi-bars text-4xl"></i>
|
|
9
|
+
</a>
|
|
10
|
+
<div class="align-items-center flex-grow-1 justify-content-between hidden lg:flex absolute lg:static w-full surface-overlay left-0 top-100 z-1 shadow-2 lg:shadow-none">
|
|
11
|
+
<ul class="list-none p-0 m-0 flex lg:align-items-center select-none flex-column lg:flex-row">
|
|
12
|
+
<li>
|
|
13
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
14
|
+
<i class="pi pi-home mr-2"></i>
|
|
15
|
+
<span>Home</span>
|
|
16
|
+
</a>
|
|
17
|
+
</li>
|
|
18
|
+
<li class="lg:relative">
|
|
19
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple"
|
|
20
|
+
v-styleclass="{ selector: '@next', enterClass: 'hidden', enterActiveClass: 'scalein', leaveToClass: 'hidden', leaveActiveClass: 'fadeout', hideOnOutsideClick: true }">
|
|
21
|
+
<i class="pi pi-users mr-2"></i>
|
|
22
|
+
<span>Customers</span>
|
|
23
|
+
<i class="pi pi-angle-down ml-auto lg:ml-3"></i>
|
|
24
|
+
</a>
|
|
25
|
+
<ul class="list-none py-3 px-6 m-0 lg:px-0 lg:py-0 border-round shadow-0 lg:shadow-2 lg:border-1 border-50 lg:absolute surface-overlay hidden origin-top w-full lg:w-15rem cursor-pointer">
|
|
26
|
+
<li>
|
|
27
|
+
<a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple">
|
|
28
|
+
<i class="pi pi-user-plus mr-2"></i>
|
|
29
|
+
<span class="font-medium">Add New</span>
|
|
30
|
+
</a>
|
|
31
|
+
</li>
|
|
32
|
+
<li class="relative">
|
|
33
|
+
<a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple"
|
|
34
|
+
v-styleclass="{ selector: '@next', enterClass: 'hidden', enterActiveClass: 'scalein', leaveToClass: 'hidden', leaveActiveClass: 'fadeout', hideOnOutsideClick: true }">
|
|
35
|
+
<i class="pi pi-search mr-2"></i>
|
|
36
|
+
<span class="font-medium">Search</span>
|
|
37
|
+
<i class="pi pi-angle-down ml-auto lg:-rotate-90"></i>
|
|
38
|
+
</a>
|
|
39
|
+
<ul class="list-none py-3 pl-3 m-0 lg:px-0 lg:py-0 border-round shadow-0 lg:shadow-2 lg:border-1 border-50 lg:absolute surface-overlay hidden origin-top w-full lg:w-15rem cursor-pointer left-100 top-0">
|
|
40
|
+
<li>
|
|
41
|
+
<a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple">
|
|
42
|
+
<i class="pi pi-shopping-cart mr-2"></i>
|
|
43
|
+
<span class="font-medium">Purchases</span>
|
|
44
|
+
</a>
|
|
45
|
+
</li>
|
|
46
|
+
<li class="relative">
|
|
47
|
+
<a v-ripple class="flex p-3 align-items-center text-600 hover:text-900 hover:surface-100 transition-colors transition-duration-150 p-ripple">
|
|
48
|
+
<i class="pi pi-comments mr-2"></i>
|
|
49
|
+
<span class="font-medium">Messages</span>
|
|
50
|
+
</a>
|
|
51
|
+
</li>
|
|
52
|
+
</ul>
|
|
53
|
+
</li>
|
|
54
|
+
</ul>
|
|
55
|
+
</li>
|
|
56
|
+
<li>
|
|
57
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
58
|
+
<i class="pi pi-calendar mr-2"></i>
|
|
59
|
+
<span>Calendar</span>
|
|
60
|
+
</a>
|
|
61
|
+
</li>
|
|
62
|
+
<li>
|
|
63
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
64
|
+
<i class="pi pi-chart-line mr-2"></i>
|
|
65
|
+
<span>Stats</span>
|
|
66
|
+
</a>
|
|
67
|
+
</li>
|
|
68
|
+
</ul>
|
|
69
|
+
<ul class="list-none p-0 m-0 flex lg:align-items-center select-none flex-column lg:flex-row border-top-1 surface-border lg:border-top-none">
|
|
70
|
+
<li>
|
|
71
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center text-600 hover:text-900 hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
72
|
+
<i class="pi pi-inbox text-base lg:text-2xl mr-2 lg:mr-0"></i>
|
|
73
|
+
<span class="block lg:hidden font-medium">Inbox</span>
|
|
74
|
+
</a>
|
|
75
|
+
</li>
|
|
76
|
+
<li>
|
|
77
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-3 align-items-center text-600 hover:text-900 hover:surface-100
|
|
78
|
+
font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
79
|
+
<i class="pi pi-bell text-base lg:text-2xl mr-2 lg:mr-0 p-overlay-badge">
|
|
80
|
+
<Badge value="2"></Badge>
|
|
81
|
+
</i>
|
|
82
|
+
<span class="block lg:hidden font-medium">Notifications</span>
|
|
83
|
+
</a>
|
|
84
|
+
</li>
|
|
85
|
+
<li class="border-top-1 surface-border lg:border-top-none">
|
|
86
|
+
<a v-ripple class="flex px-6 p-3 lg:px-3 lg:py-2 align-items-center hover:surface-100 font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
|
|
87
|
+
<img src="/images/empty-user-photo.svg" class="mr-3 lg:mr-0 border-circle" style="width: 28px; height: 28px"/>
|
|
88
|
+
<div class="block lg:hidden">
|
|
89
|
+
<div class="text-900 font-medium">Josephine Lillard</div>
|
|
90
|
+
<span class="text-600 font-medium text-sm">Marketing Specialist</span>
|
|
91
|
+
</div>
|
|
92
|
+
</a>
|
|
93
|
+
</li>
|
|
94
|
+
</ul>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<script setup>
|
|
100
|
+
import Badge from "primevue/badge"
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<style scoped>
|
|
104
|
+
|
|
105
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core'
|
|
2
|
+
import { collab } from 'prosemirror-collab'
|
|
3
|
+
|
|
4
|
+
const ProseMirrorCollab = Extension.create({
|
|
5
|
+
addOptions() {
|
|
6
|
+
return {
|
|
7
|
+
version: 0,
|
|
8
|
+
clientID: ''
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
addProseMirrorPlugins() {
|
|
12
|
+
const version = this.options.version
|
|
13
|
+
const clientID = this.options.clientID
|
|
14
|
+
if(!clientID) throw new Error('prosemirror collab clientId is required')
|
|
15
|
+
return [
|
|
16
|
+
collab({ version, clientID }),
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export default ProseMirrorCollab
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { h } from 'vue'
|
|
2
|
+
import { Image } from "@live-change/image-frontend"
|
|
3
|
+
|
|
4
|
+
export const basicMarks = {
|
|
5
|
+
bold: (m, c) => h('strong', {}, [ c ]),
|
|
6
|
+
italic: (m, c) => h('em', {}, [ c ]),
|
|
7
|
+
underline: (m, c) => h('u', {}, [ c ]),
|
|
8
|
+
strike: (m, c) => h('s', {}, [ c ])
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const plainTextNodes = {
|
|
12
|
+
doc: ({ content }, r) => h('div', { 'class': 'content' }, r(content)),
|
|
13
|
+
|
|
14
|
+
paragraph: ({ content }, r) => h('p', { }, r(content)),
|
|
15
|
+
|
|
16
|
+
text({ text, marks: ms }, r, marks) {
|
|
17
|
+
let res = text
|
|
18
|
+
if(ms) for(const mark of ms) {
|
|
19
|
+
const renderer = marks[mark.type]
|
|
20
|
+
if(!renderer) {
|
|
21
|
+
res = h('span', { style: 'color: red' }, `[unknown mark type ${mark.type}]`)
|
|
22
|
+
} else {
|
|
23
|
+
res = renderer(mark, res)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return res
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const messageNodes = {
|
|
31
|
+
...plainTextNodes,
|
|
32
|
+
|
|
33
|
+
hardBreak: ({ content }) => h('br')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const richEditorNodes = {
|
|
37
|
+
horizontalRule: ({ content }) => h('hr'),
|
|
38
|
+
|
|
39
|
+
heading: ({ content, attrs }, r) => h('h'+(+attrs.level), { }, r(content)),
|
|
40
|
+
blockquote: ({ content }, r) => h('blockquote', { }, r(content)),
|
|
41
|
+
codeBlock: ({ content }, r) => h('pre', { }, h('code', { }, r(content))),
|
|
42
|
+
|
|
43
|
+
bulletList: ({ content }, r) => h('ul', { }, r(content)),
|
|
44
|
+
orderedList: ({ content }, r) => h('ol', { }, r(content)),
|
|
45
|
+
listItem: ({ content }, r) => h('li', { }, r(content)),
|
|
46
|
+
|
|
47
|
+
image: ({ attrs }) => h(Image, { domResize: 'width', width: 100, ...attrs }),
|
|
48
|
+
|
|
49
|
+
...messageNodes
|
|
50
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import Document from '@tiptap/extension-document'
|
|
2
|
+
|
|
3
|
+
import Paragraph from "@tiptap/extension-paragraph"
|
|
4
|
+
import HardBreak from "@tiptap/extension-hard-break"
|
|
5
|
+
import HorizontalRule from "@tiptap/extension-horizontal-rule"
|
|
6
|
+
import Heading from '@tiptap/extension-heading'
|
|
7
|
+
import Blockqoute from "@tiptap/extension-blockquote"
|
|
8
|
+
import CodeBlock from "@tiptap/extension-code-block"
|
|
9
|
+
import BulletList from "@tiptap/extension-bullet-list"
|
|
10
|
+
import OrderedList from "@tiptap/extension-ordered-list"
|
|
11
|
+
import ListItem from "@tiptap/extension-list-item"
|
|
12
|
+
|
|
13
|
+
import Text from '@tiptap/extension-text'
|
|
14
|
+
|
|
15
|
+
import Bold from '@tiptap/extension-bold'
|
|
16
|
+
import Italic from '@tiptap/extension-italic'
|
|
17
|
+
import Underline from "@tiptap/extension-underline"
|
|
18
|
+
import Strike from "@tiptap/extension-strike"
|
|
19
|
+
|
|
20
|
+
import ImageNode from "./ImageNode.js"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export const marksExtensions = {
|
|
24
|
+
bold: [Bold],
|
|
25
|
+
italic: [Italic],
|
|
26
|
+
underline: [Underline],
|
|
27
|
+
strike: [Strike]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const nodesExtensions = {
|
|
31
|
+
doc: [Document],
|
|
32
|
+
paragraph: [Paragraph],
|
|
33
|
+
text: [Text],
|
|
34
|
+
|
|
35
|
+
hardBreak: [HardBreak],
|
|
36
|
+
horizontalRule: [HorizontalRule],
|
|
37
|
+
|
|
38
|
+
heading: [Heading],
|
|
39
|
+
blockquote: [Blockqoute],
|
|
40
|
+
codeBlock: [CodeBlock],
|
|
41
|
+
|
|
42
|
+
bulletList: [BulletList],
|
|
43
|
+
orderedList: [OrderedList],
|
|
44
|
+
listItem: [ListItem],
|
|
45
|
+
|
|
46
|
+
image: [ImageNode]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getExtensions(contentConfig) {
|
|
50
|
+
const extensions = []
|
|
51
|
+
for(const mark in contentConfig.marks) {
|
|
52
|
+
extensions.push(...marksExtensions[mark])
|
|
53
|
+
}
|
|
54
|
+
for(const node in contentConfig.nodes) {
|
|
55
|
+
extensions.push(...nodesExtensions[node])
|
|
56
|
+
}
|
|
57
|
+
return extensions
|
|
58
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMemoryHistory,
|
|
3
|
+
createRouter as _createRouter,
|
|
4
|
+
createWebHistory
|
|
5
|
+
} from 'vue-router'
|
|
6
|
+
|
|
7
|
+
import { dbAdminRoutes } from "@live-change/db-admin"
|
|
8
|
+
|
|
9
|
+
export function wysiwygRoutes(config = {}) {
|
|
10
|
+
const { prefix = '/', route = (r) => r } = config
|
|
11
|
+
return [
|
|
12
|
+
|
|
13
|
+
route({
|
|
14
|
+
name: 'wysiwyg:editorWithPreview', path: prefix + '', meta: { },
|
|
15
|
+
component: () => import("./EditorWithPreview.vue"),
|
|
16
|
+
props: {
|
|
17
|
+
}
|
|
18
|
+
}),
|
|
19
|
+
|
|
20
|
+
route({
|
|
21
|
+
name: 'wysiwyg:documentEditor', path: prefix + 'collab', meta: { },
|
|
22
|
+
component: () => import("./DocumentEditorTest.vue"),
|
|
23
|
+
props: {
|
|
24
|
+
}
|
|
25
|
+
}),
|
|
26
|
+
|
|
27
|
+
...dbAdminRoutes({ prefix: '/_db', route: r => ({ ...r, meta: { ...r.meta, raw: true }}) })
|
|
28
|
+
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function sitemap(route, api) {
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
export function createRouter(app, config) {
|
|
38
|
+
const router = _createRouter({
|
|
39
|
+
history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
|
|
40
|
+
routes: wysiwygRoutes(config)
|
|
41
|
+
})
|
|
42
|
+
return router
|
|
43
|
+
}
|
|
44
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getSchema } from "@tiptap/core"
|
|
2
|
+
import {getExtensions} from "./contentConfigExtensions";
|
|
3
|
+
|
|
4
|
+
export function schemaTypesMapToObject(map) {
|
|
5
|
+
let obj = {}
|
|
6
|
+
for(let i = 0; i < map.content.length; i += 2) {
|
|
7
|
+
const value = map.content[i+1]
|
|
8
|
+
obj[map.content[i]] = {
|
|
9
|
+
...value,
|
|
10
|
+
parseDOM: undefined
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return obj
|
|
14
|
+
}
|
|
15
|
+
export function serializeSchema(schema) {
|
|
16
|
+
console.log("SERIALIZE", schema)
|
|
17
|
+
return JSON.parse(JSON.stringify({
|
|
18
|
+
marks: schemaTypesMapToObject(schema.marks),
|
|
19
|
+
nodes: schemaTypesMapToObject(schema.nodes),
|
|
20
|
+
topNode: schema.topNode
|
|
21
|
+
}, null, ' '))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getSchemaSpecFromConfig(config) {
|
|
25
|
+
const extensions = getExtensions(config)
|
|
26
|
+
const schema = getSchema(extensions)
|
|
27
|
+
return serializeSchema(schema.spec)
|
|
28
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
|
|
3
|
+
import baseViteConfig from '@live-change/frontend-base/vite-config.js'
|
|
4
|
+
|
|
5
|
+
export default defineConfig(async ({ command, mode }) => {
|
|
6
|
+
const baseConfig = (await baseViteConfig({ command, mode }))
|
|
7
|
+
return {
|
|
8
|
+
...baseConfig,
|
|
9
|
+
|
|
10
|
+
resolve: {
|
|
11
|
+
alias: [
|
|
12
|
+
...baseConfig.resolve.alias,
|
|
13
|
+
/* { find: 'vue', replacement: 'vue/dist/vue.esm-bundler.js' },
|
|
14
|
+
{ find: 'vue/server-renderer', replacement: 'vue/server-renderer' },*/
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
})
|
package/index.js
ADDED
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@live-change/wysiwyg-frontend",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"memDev": "lcli memDev --enableSessions --initScript ./init.js --dbAccess",
|
|
6
|
+
"localDevInit": "rm tmp.db; lcli localDev --enableSessions --initScript ./init.js",
|
|
7
|
+
"localDev": "lcli localDev --enableSessions",
|
|
8
|
+
"dev": "lcli dev --enableSessions",
|
|
9
|
+
"ssrDev": "lcli ssrDev --enableSessions",
|
|
10
|
+
"serveAllMem": "cross-env NODE_ENV=production lcli ssrServer --withApi --withServices --updateServices --enableSessions --withDb --dbBackend mem --createDb",
|
|
11
|
+
"serveAll": "cross-env NODE_ENV=production lcli ssrServer --withApi --withServices --updateServices --enableSessions",
|
|
12
|
+
"serve": "cross-env NODE_ENV=production lcli ssrServer --enableSessions",
|
|
13
|
+
"apiServer": "lcli apiServer --enableSessions",
|
|
14
|
+
"devApiServer": "lcli devApiServer --enableSessions",
|
|
15
|
+
"memApiServer": "lcli memApiServer --enableSessions",
|
|
16
|
+
"build": "cd front; yarn build:client && yarn build:server",
|
|
17
|
+
"build:client": "cd front; vite build --ssrManifest --outDir dist/client",
|
|
18
|
+
"build:server": "cd front; vite build --ssr src/entry-server.js --outDir dist/server",
|
|
19
|
+
"generate": "vite build --ssrManifest --outDir dist/static && yarn build:server && node prerender",
|
|
20
|
+
"debug": "node --inspect-brk server"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@live-change/framework": "0.6.5",
|
|
24
|
+
"@live-change/cli": "0.6.5",
|
|
25
|
+
"@live-change/dao": "0.4.13",
|
|
26
|
+
"@live-change/dao-vue3": "0.4.13",
|
|
27
|
+
"@live-change/dao-websocket": "0.4.13",
|
|
28
|
+
"@live-change/vue3-ssr": "0.2.14",
|
|
29
|
+
"@live-change/vue3-components": "0.2.12",
|
|
30
|
+
"@live-change/session-service": "0.2.39",
|
|
31
|
+
"@live-change/password-authentication-service": "0.2.39",
|
|
32
|
+
"@live-change/secret-link-service": "0.2.39",
|
|
33
|
+
"@live-change/secret-code-service": "0.2.39",
|
|
34
|
+
"@live-change/prosemirror-service": "0.2.39",
|
|
35
|
+
"@vitejs/plugin-vue": "^2.3.1",
|
|
36
|
+
"@vitejs/plugin-vue-jsx": "^1.3.10",
|
|
37
|
+
"@vue/compiler-sfc": "^3.2.33",
|
|
38
|
+
"@vueuse/core": "^8.3.1",
|
|
39
|
+
"v-shared-element": "3.1.0",
|
|
40
|
+
"vue3-scroll-border": "0.1.2",
|
|
41
|
+
"codeceptjs-assert": "^0.0.5",
|
|
42
|
+
"compression": "^1.7.4",
|
|
43
|
+
"cross-env": "^7.0.3",
|
|
44
|
+
"get-port-sync": "1.0.1",
|
|
45
|
+
"primeicons": "^5.0.0",
|
|
46
|
+
"primevue": "^3.15.0",
|
|
47
|
+
"primeflex": "^3.2.1",
|
|
48
|
+
"rollup-plugin-node-builtins": "^2.1.2",
|
|
49
|
+
"serialize-javascript": "^6.0.0",
|
|
50
|
+
"serve-static": "^1.15.0",
|
|
51
|
+
"vite": "^2.9.6",
|
|
52
|
+
"vue": "^3.2.33",
|
|
53
|
+
"vue-meta": "^3.0.0-alpha.9",
|
|
54
|
+
"vue-router": "^4.0.14",
|
|
55
|
+
"vite-plugin-compression": "0.5.1",
|
|
56
|
+
"vite-plugin-vue-images": "^0.6.1",
|
|
57
|
+
"rollup-plugin-visualizer": "5.6.0",
|
|
58
|
+
"@tiptap/vue-3": "2.0.0-beta.91",
|
|
59
|
+
"@tiptap/extension-blockquote": "^2.0.0-beta.29",
|
|
60
|
+
"@tiptap/extension-bold": "^2.0.0-beta.28",
|
|
61
|
+
"@tiptap/extension-bullet-list": "^2.0.0-beta.29",
|
|
62
|
+
"@tiptap/extension-code": "^2.0.0-beta.28",
|
|
63
|
+
"@tiptap/extension-code-block": "^2.0.0-beta.42",
|
|
64
|
+
"@tiptap/extension-document": "^2.0.0-beta.17",
|
|
65
|
+
"@tiptap/extension-dropcursor": "^2.0.0-beta.29",
|
|
66
|
+
"@tiptap/extension-gapcursor": "^2.0.0-beta.39",
|
|
67
|
+
"@tiptap/extension-hard-break": "^2.0.0-beta.33",
|
|
68
|
+
"@tiptap/extension-heading": "^2.0.0-beta.29",
|
|
69
|
+
"@tiptap/extension-history": "^2.0.0-beta.26",
|
|
70
|
+
"@tiptap/extension-horizontal-rule": "^2.0.0-beta.36",
|
|
71
|
+
"@tiptap/extension-italic": "^2.0.0-beta.28",
|
|
72
|
+
"@tiptap/extension-list-item": "^2.0.0-beta.23",
|
|
73
|
+
"@tiptap/extension-ordered-list": "^2.0.0-beta.30",
|
|
74
|
+
"@tiptap/extension-paragraph": "^2.0.0-beta.26",
|
|
75
|
+
"@tiptap/extension-strike": "^2.0.0-beta.29",
|
|
76
|
+
"@tiptap/extension-text": "^2.0.0-beta.17",
|
|
77
|
+
"@tiptap/extension-underline": "2.0.0-beta.23",
|
|
78
|
+
"@tiptap/extension-highlight": "^2.0.0-beta.33",
|
|
79
|
+
"prosemirror-collab": "^1.3.0",
|
|
80
|
+
"@fortawesome/fontawesome-free": "^6.1.1"
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@live-change/codeceptjs-helper": "0.6.5",
|
|
84
|
+
"@wdio/selenium-standalone-service": "^7.19.5",
|
|
85
|
+
"codeceptjs": "^3.3.1",
|
|
86
|
+
"playwright": "^1.21.1",
|
|
87
|
+
"random-profile-generator": "^2.3.0",
|
|
88
|
+
"txtgen": "^3.0.1",
|
|
89
|
+
"generate-password": "1.7.0",
|
|
90
|
+
"webdriverio": "^7.19.5"
|
|
91
|
+
},
|
|
92
|
+
"author": "",
|
|
93
|
+
"license": "ISC",
|
|
94
|
+
"description": ""
|
|
95
|
+
}
|