@live-change/wysiwyg-frontend 0.2.9 → 0.2.11
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/front/src/DocumentEditorTest.vue +72 -9
- package/front/src/DocumentPreviewTest.vue +35 -5
- package/front/src/components/ComponentComponent.vue +81 -0
- package/front/src/components/ComponentNode.js +74 -0
- package/front/src/components/ComponentsMenu.vue +108 -0
- package/front/src/components/ContentView.js +2 -2
- package/front/src/components/DocumentEditor.vue +55 -4
- package/front/src/components/Editor.vue +8 -1
- package/front/src/components/EditorMenu.vue +35 -4
- package/front/src/components/EmptyComponent.vue +11 -0
- package/front/src/components/ImageNode.js +0 -1
- package/front/src/components/PaddingEditor.vue +49 -0
- package/front/src/components/RemoteAuthority.js +1 -0
- package/front/src/components/SettingsEditor.vue +10 -0
- package/front/src/components/StyleEditor.vue +179 -0
- package/front/src/components/components.js +17 -0
- package/front/src/components/contentConfig.js +10 -0
- package/front/src/components/contentConfigExtensions.js +4 -1
- package/front/src/router.js +8 -3
- package/index.js +4 -0
- package/package.json +12 -11
- package/server/page.documentType.js +103 -0
- package/server/services.config.js +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<DocumentEditor targetType="Example" :target="target" :type="'
|
|
2
|
+
<div class="wysiwyg">
|
|
3
|
+
<DocumentEditor targetType="Example" :target="target" :type="'page'" :purpose="'test'"
|
|
4
4
|
:initialContent="emptyContent"
|
|
5
5
|
:config="contentConfig" />
|
|
6
6
|
</div>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import DocumentEditor from "./components/DocumentEditor.vue"
|
|
12
12
|
import { ref } from 'vue'
|
|
13
|
-
import { basicMarks, messageNodes, richEditorNodes } from "./components/contentConfig.js"
|
|
13
|
+
import { basicMarks, messageNodes, richEditorNodes, pageNodes } from "./components/contentConfig.js"
|
|
14
14
|
import { toRefs } from '@vueuse/core'
|
|
15
15
|
|
|
16
16
|
const props = defineProps({
|
|
@@ -28,15 +28,14 @@
|
|
|
28
28
|
},
|
|
29
29
|
nodes: {
|
|
30
30
|
//...messageNodes,
|
|
31
|
-
|
|
31
|
+
//...richEditorNodes
|
|
32
|
+
...pageNodes
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
const document = 'testDocument'
|
|
36
|
-
|
|
37
36
|
const emptyContent = {
|
|
38
37
|
"type": "doc",
|
|
39
|
-
"content": [
|
|
38
|
+
"content": [
|
|
40
39
|
{
|
|
41
40
|
"type": "paragraph",
|
|
42
41
|
"content": [
|
|
@@ -54,12 +53,76 @@
|
|
|
54
53
|
"text": "est"
|
|
55
54
|
}
|
|
56
55
|
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"type": "component",
|
|
59
|
+
"attrs": {
|
|
60
|
+
"is": "card",
|
|
61
|
+
"attrs": {
|
|
62
|
+
"class": "surface-card px-3 shadow-2 w-full f p-2"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"content": [
|
|
66
|
+
{
|
|
67
|
+
"type": "paragraph",
|
|
68
|
+
"content": [
|
|
69
|
+
{
|
|
70
|
+
"type": "text",
|
|
71
|
+
"text": "test"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"type": "paragraph",
|
|
79
|
+
"content": [
|
|
80
|
+
{
|
|
81
|
+
"type": "text",
|
|
82
|
+
"text": "test"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"type": "text",
|
|
86
|
+
"marks": [
|
|
87
|
+
{
|
|
88
|
+
"type": "bold"
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"text": "est"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"type": "component",
|
|
97
|
+
"attrs": {
|
|
98
|
+
"is": "card",
|
|
99
|
+
"attrs": {
|
|
100
|
+
"class": "surface-card py-1 px-3 shadow-2 w-full"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"content": [
|
|
104
|
+
{
|
|
105
|
+
"type": "paragraph",
|
|
106
|
+
"content": [
|
|
107
|
+
{
|
|
108
|
+
"type": "text",
|
|
109
|
+
"text": "test"
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
]
|
|
57
114
|
}
|
|
58
|
-
|
|
115
|
+
]
|
|
59
116
|
}
|
|
60
117
|
|
|
61
118
|
</script>
|
|
62
119
|
|
|
63
|
-
<style
|
|
120
|
+
<style lang="scss">
|
|
121
|
+
.wysiwyg {
|
|
122
|
+
|
|
123
|
+
.ProseMirror:focus {
|
|
124
|
+
outline: none;
|
|
125
|
+
}
|
|
64
126
|
|
|
127
|
+
}
|
|
65
128
|
</style>
|
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
2
|
+
<ContentView :content="document.content" :config="contentConfig" class="w-full" />
|
|
3
|
+
<pre>{{ JSON.stringify(document.content, null, " ") }}</pre>
|
|
3
4
|
</template>
|
|
4
5
|
|
|
5
|
-
<script>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
<script setup>
|
|
7
|
+
import ContentView from "./components/ContentView.js"
|
|
8
|
+
import { ref } from 'vue'
|
|
9
|
+
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
target: {
|
|
12
|
+
type: String,
|
|
13
|
+
default: 'one'
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
import { basicMarks, messageNodes, richEditorNodes, pageNodes } from "./components/contentConfig.js"
|
|
18
|
+
|
|
19
|
+
const contentConfig = {
|
|
20
|
+
marks: {
|
|
21
|
+
...basicMarks
|
|
22
|
+
},
|
|
23
|
+
nodes: {
|
|
24
|
+
//...messageNodes,
|
|
25
|
+
//...richEditorNodes,
|
|
26
|
+
...pageNodes
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
import { path, live, useApi } from '@live-change/vue3-ssr'
|
|
31
|
+
const api = useApi()
|
|
32
|
+
const p = path()
|
|
33
|
+
|
|
34
|
+
const document = await live(
|
|
35
|
+
p.prosemirror.document({ targetType: 'Example', target: props.target })
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
9
39
|
</script>
|
|
10
40
|
|
|
11
41
|
<style scoped>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<node-view-wrapper>
|
|
3
|
+
<ComponentEditor v-bind="{ ...attrs, class: ['relative', attrs.class] }">
|
|
4
|
+
<node-view-content class="content" />
|
|
5
|
+
<div class="absolute top-0 right-0 pr-4 max-h-0 align-items-center z-5 edit-buttons ">
|
|
6
|
+
<Button icon="pi pi-eye"
|
|
7
|
+
:class="[
|
|
8
|
+
'p-button p-button-icon-only p-button-rounded mr-0',
|
|
9
|
+
selectedEditor != 'style' ? 'p-button-success' : 'p-button-secondary'
|
|
10
|
+
]"
|
|
11
|
+
style="transform: scale(0.8)"
|
|
12
|
+
@click="(event) => toggleEditor('style', event)" />
|
|
13
|
+
<Button icon="pi pi-cog"
|
|
14
|
+
:class="[
|
|
15
|
+
'p-button p-button-icon-only p-button-rounded mr-0',
|
|
16
|
+
selectedEditor != 'settings' ? 'p-button-warning' : 'p-button-secondary'
|
|
17
|
+
]"
|
|
18
|
+
style="transform: scale(0.8)"
|
|
19
|
+
@click="(event) => toggleEditor('settings', event)"/>
|
|
20
|
+
<Button icon="pi pi-trash"
|
|
21
|
+
class="p-button p-button-icon-only p-button-rounded p-button-danger"
|
|
22
|
+
style="transform: scale(0.8)"
|
|
23
|
+
@click="() => props.deleteNode()" />
|
|
24
|
+
</div>
|
|
25
|
+
<div class="absolute pointer-events-none border-2 border-dashed z-9"
|
|
26
|
+
:class="selectedEditor == 'style' ? 'border-blue-400' : 'border-yellow-400'"
|
|
27
|
+
style="top: -10px; left: -10px; width: auto; height: auto; right: -10px; bottom: -10px"
|
|
28
|
+
v-if="selectedEditor"></div>
|
|
29
|
+
</ComponentEditor>
|
|
30
|
+
</node-view-wrapper>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup>
|
|
34
|
+
import Button from "primevue/button"
|
|
35
|
+
|
|
36
|
+
import { NodeViewContent, nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
|
|
37
|
+
import components from "./components.js";
|
|
38
|
+
|
|
39
|
+
import { inject, computed, getCurrentInstance } from "vue"
|
|
40
|
+
|
|
41
|
+
const props = defineProps(nodeViewProps)
|
|
42
|
+
|
|
43
|
+
const is = computed(() => props.node.attrs.is)
|
|
44
|
+
const attrs = computed(() => props.node.attrs.attrs)
|
|
45
|
+
|
|
46
|
+
const ComponentEditor = components[props.node.attrs.is].editor
|
|
47
|
+
|
|
48
|
+
const uid = getCurrentInstance().uid
|
|
49
|
+
|
|
50
|
+
const nodeControl = computed(() => ({
|
|
51
|
+
uid,
|
|
52
|
+
updateAttributes: props.updateAttributes,
|
|
53
|
+
deleteNode: props.deleteNode,
|
|
54
|
+
is: props.node.attrs.is,
|
|
55
|
+
attrs: props.node.attrs.attrs
|
|
56
|
+
}))
|
|
57
|
+
|
|
58
|
+
const editorControl = inject("componentEditorControl")
|
|
59
|
+
|
|
60
|
+
function toggleEditor(type, event) {
|
|
61
|
+
editorControl.toggleEditor(type, nodeControl, event)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const selectedEditor = computed(() =>{
|
|
65
|
+
console.log('EC', editorControl.componentControl.value?.nodeControl?.value.uid, uid)
|
|
66
|
+
return editorControl.componentControl.value?.nodeControl?.value?.uid === uid
|
|
67
|
+
? editorControl.componentControl.value?.type
|
|
68
|
+
: null
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<style lang="scss">
|
|
74
|
+
.edit-buttons {
|
|
75
|
+
display: none;
|
|
76
|
+
}
|
|
77
|
+
.show-edit-buttons .edit-buttons {
|
|
78
|
+
display: flex;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Node, mergeAttributes } from '@tiptap/core'
|
|
2
|
+
import { VueNodeViewRenderer } from '@tiptap/vue-3'
|
|
3
|
+
import ComponentComponent from "./ComponentComponent.vue"
|
|
4
|
+
|
|
5
|
+
export default Node.create({
|
|
6
|
+
name: 'component',
|
|
7
|
+
group: 'block',
|
|
8
|
+
//atom: true,
|
|
9
|
+
inline: false,
|
|
10
|
+
selectable: true,
|
|
11
|
+
draggable: true,
|
|
12
|
+
content: 'block*',
|
|
13
|
+
marks: '',
|
|
14
|
+
|
|
15
|
+
addAttributes() {
|
|
16
|
+
return {
|
|
17
|
+
is: {
|
|
18
|
+
default: 'card',
|
|
19
|
+
renderHTML: attributes => {
|
|
20
|
+
return {
|
|
21
|
+
'data-is': attributes.is
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
attrs: {
|
|
26
|
+
default: {},
|
|
27
|
+
renderHTML: attributes => {
|
|
28
|
+
const htmlAttrs = {}
|
|
29
|
+
const attrs = attributes.attrs
|
|
30
|
+
for(let key in this.options) {
|
|
31
|
+
htmlAttrs[`data-attr-${key}`] = attrs[key]
|
|
32
|
+
}
|
|
33
|
+
return htmlAttrs
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/*parseHTML() {
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
tag: this.options.allowBase64
|
|
43
|
+
? 'img[src]'
|
|
44
|
+
: 'img[src]:not([src^="data:"])',
|
|
45
|
+
},
|
|
46
|
+
]
|
|
47
|
+
},*/
|
|
48
|
+
|
|
49
|
+
renderHTML({ HTMLAttributes }) {
|
|
50
|
+
/*const htmlAttrs = {}
|
|
51
|
+
const attrs = this.options.attrs
|
|
52
|
+
for(let key in this.options) {
|
|
53
|
+
htmlAttrs[`data-attr-${key}`] = attrs[key]
|
|
54
|
+
}
|
|
55
|
+
htmlAttrs['data-is'] = this.options.is
|
|
56
|
+
return ['component', mergeAttributes(HTMLAttributes, htmlAttrs)]*/
|
|
57
|
+
return ['component', HTMLAttributes]
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
addNodeView() {
|
|
61
|
+
return VueNodeViewRenderer(ComponentComponent)
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
addCommands() {
|
|
65
|
+
return {
|
|
66
|
+
setImage: options => ({ commands }) => {
|
|
67
|
+
return commands.insertContent({
|
|
68
|
+
type: this.name,
|
|
69
|
+
attrs: options
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div class="px-2 pb-2">
|
|
4
|
+
<span class="p-input-icon-left w-full flex">
|
|
5
|
+
<i class="pi pi-search flex-shrink-0" />
|
|
6
|
+
<InputText type="text" v-model="searchQuery" placeholder="Search" class="flex-grow-1" v-focus />
|
|
7
|
+
</span>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="overflow-auto" style="max-height: 75vh">
|
|
10
|
+
<!-- <div v-for="i in [1,2,3,4,5,6,7,8,9,10]">-->
|
|
11
|
+
<div v-for="([ name, component ], index) in searchResults" :key="name"
|
|
12
|
+
class="flex flex-wrap relative cursor-pointer hover:surface-100"
|
|
13
|
+
@click="() => emit('selected', name, component)" >
|
|
14
|
+
<div class="w-3 ml-2 mr-2 my-1" style="aspect-ratio: 1/1">
|
|
15
|
+
<div class="pointer-events-none w-6 absolute p-1"
|
|
16
|
+
style="aspect-ratio: 1/1; transform: scale(0.5); transform-origin: 0 0;">
|
|
17
|
+
<ContentView :config="config" :content="getTestContent(name, component)" style="aspect-ratio: 1/1" />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="ml-2">
|
|
21
|
+
<h3>{{ name[0].toUpperCase() + name.slice(1) }}</h3>
|
|
22
|
+
<p>{{ component.description }}</p>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<!-- </div>-->
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup>
|
|
31
|
+
import InputText from "primevue/inputtext"
|
|
32
|
+
|
|
33
|
+
import ContentView from "./ContentView.js"
|
|
34
|
+
import components from "./components.js"
|
|
35
|
+
|
|
36
|
+
import { ref, computed } from 'vue'
|
|
37
|
+
|
|
38
|
+
const props = defineProps({
|
|
39
|
+
config: {
|
|
40
|
+
type: Object,
|
|
41
|
+
required: true
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const { config } = props
|
|
46
|
+
|
|
47
|
+
const emit = defineEmits(['selected'])
|
|
48
|
+
|
|
49
|
+
const searchQuery = ref('')
|
|
50
|
+
|
|
51
|
+
const searchResults = computed(() => {
|
|
52
|
+
const query = searchQuery.value.toLowerCase()
|
|
53
|
+
if(!query) return Object.entries(components)
|
|
54
|
+
const byName = Object.entries(components).filter(([name, component]) => {
|
|
55
|
+
return name.toLowerCase().includes(query)
|
|
56
|
+
})
|
|
57
|
+
const byDescription = Object.entries(components).filter(([name, component]) => {
|
|
58
|
+
return (component.description || '').toLowerCase().includes(query)
|
|
59
|
+
}).filter(([name, component]) => !byName.find(([name2]) => name == name2))
|
|
60
|
+
return [...byName, ...byDescription]
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
function getTestContent(name, component) {
|
|
64
|
+
return {
|
|
65
|
+
"type": 'component',
|
|
66
|
+
"attrs": {
|
|
67
|
+
"is": name,
|
|
68
|
+
"attrs": {
|
|
69
|
+
...component.initialAttrs,
|
|
70
|
+
...component.previewAttrs
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"content": component.previewContent ? [
|
|
74
|
+
{
|
|
75
|
+
"type": "heading",
|
|
76
|
+
"attrs": {
|
|
77
|
+
"level": 3
|
|
78
|
+
},
|
|
79
|
+
"content": [
|
|
80
|
+
{
|
|
81
|
+
"type": "text",
|
|
82
|
+
"text": name[0].toUpperCase() + name.slice(1)
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"type": "paragraph",
|
|
88
|
+
"content": [
|
|
89
|
+
{
|
|
90
|
+
"type": "text",
|
|
91
|
+
"text": component.description
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
] : [
|
|
96
|
+
{
|
|
97
|
+
"type": "text",
|
|
98
|
+
"text": component.description
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<style scoped>
|
|
107
|
+
|
|
108
|
+
</style>
|
|
@@ -9,9 +9,9 @@ function renderNode(node, r, marks, nodes) {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function ContentView(props, context) {
|
|
12
|
-
|
|
12
|
+
let { content, config } = props
|
|
13
13
|
if(content) {
|
|
14
|
-
const doc = JSON.parse(content)
|
|
14
|
+
const doc = typeof content == 'string' ? JSON.parse(content) : content
|
|
15
15
|
const r = nodes => nodes?.map(n => renderNode(n, r, config.marks, config.nodes))
|
|
16
16
|
return renderNode(doc, r, config.marks, config.nodes)
|
|
17
17
|
}
|
|
@@ -3,20 +3,42 @@
|
|
|
3
3
|
<slot v-if="editor" name="menu" :editor="editor" :saveState="saveState">
|
|
4
4
|
<EditorMenu :editor="editor" :config="config" :saveState="saveState">
|
|
5
5
|
<template #before="scope"><slot name="beforeMenu" v-bind="{ ...scope, editor, saveState }" /></template>
|
|
6
|
-
<template #after="scope"><slot name="afterMenu" v-bind="{ ...scope, editor, saveState }" /></template>
|
|
7
6
|
<template #begin="scope"><slot name="menuBegin" v-bind="{ ...scope, editor, saveState }" /></template>
|
|
8
|
-
<template #end="scope"
|
|
7
|
+
<template #end="scope">
|
|
8
|
+
<slot name="menuEnd" v-bind="{ ...scope, editor, saveState }" />
|
|
9
|
+
<Button v-if="config.nodes.component" label="Edit Buttons"
|
|
10
|
+
:class="[
|
|
11
|
+
'p-button-sm p-button-primary inline-block mr-1 mb-1', { 'p-button-outlined': !editButtons }
|
|
12
|
+
]"
|
|
13
|
+
@click="toggleEditButtons" />
|
|
14
|
+
</template>
|
|
15
|
+
<template #after="scope">
|
|
16
|
+
<div v-if="componentControl"
|
|
17
|
+
class="surface-card p-1 shadow-2 border-round">
|
|
18
|
+
<StyleEditor v-if="componentControl.type == 'style'" :nodeControl="componentControl.nodeControl"
|
|
19
|
+
:key="componentControl.uid" />
|
|
20
|
+
<SettingsEditor v-if="componentControl.type == 'settings'" :nodeControl="componentControl.nodeControl"
|
|
21
|
+
:key="componentControl.uid" />
|
|
22
|
+
</div>
|
|
23
|
+
<slot name="afterMenu" v-bind="{ ...scope, editor, saveState }" />
|
|
24
|
+
</template>
|
|
9
25
|
</EditorMenu>
|
|
10
26
|
</slot>
|
|
11
|
-
<editor-content :editor="editor" class="content" />
|
|
27
|
+
<editor-content :editor="editor" :class="[content, { 'show-edit-buttons': editButtons }]" />
|
|
12
28
|
</div>
|
|
13
29
|
</template>
|
|
14
30
|
|
|
15
31
|
<script setup>
|
|
32
|
+
import Button from "primevue/button"
|
|
33
|
+
|
|
34
|
+
import StyleEditor from "./StyleEditor.vue";
|
|
35
|
+
import SettingsEditor from "./SettingsEditor.vue";
|
|
16
36
|
|
|
17
37
|
import { useEditor, EditorContent } from '@tiptap/vue-3'
|
|
38
|
+
import { History } from '@tiptap/extension-history'
|
|
18
39
|
import {
|
|
19
|
-
ref, computed, watch, provide, defineEmits, defineProps, getCurrentInstance, onUnmounted, inject, onMounted
|
|
40
|
+
ref, computed, watch, provide, defineEmits, defineProps, getCurrentInstance, onUnmounted, inject, onMounted,
|
|
41
|
+
shallowRef
|
|
20
42
|
} from 'vue'
|
|
21
43
|
import { toRefs, useDebounceFn } from '@vueuse/core'
|
|
22
44
|
import EditorMenu from "./EditorMenu.vue"
|
|
@@ -119,6 +141,10 @@
|
|
|
119
141
|
ProseMirrorCollab.configure({
|
|
120
142
|
version: documentData.version,
|
|
121
143
|
clientID
|
|
144
|
+
}),
|
|
145
|
+
History.configure({
|
|
146
|
+
depth: 100,
|
|
147
|
+
newGroupDelay: 500
|
|
122
148
|
})
|
|
123
149
|
],
|
|
124
150
|
onTransaction: ({ editor, transaction }) => {
|
|
@@ -134,6 +160,16 @@
|
|
|
134
160
|
? serializeSchema(editor.view.state.schema.spec)
|
|
135
161
|
: getSchemaSpecFromConfig(props.config)
|
|
136
162
|
|
|
163
|
+
const editButtons = ref(true)
|
|
164
|
+
function toggleEditButtons() {
|
|
165
|
+
editButtons.value = !editButtons.value
|
|
166
|
+
if(!editButtons.value) {
|
|
167
|
+
componentControl.value = null
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if(typeof window != 'undefined') window.schemaSpecJson = JSON.stringify(schemaSpec, null, " ")
|
|
172
|
+
|
|
137
173
|
async function save() {
|
|
138
174
|
const state = editor.value.reactiveState.value
|
|
139
175
|
const sendable = sendableSteps(state)
|
|
@@ -155,6 +191,21 @@
|
|
|
155
191
|
transaction.apply(editor.value.view.state)
|
|
156
192
|
}
|
|
157
193
|
}
|
|
194
|
+
|
|
195
|
+
const componentControl = shallowRef()
|
|
196
|
+
provide('componentEditorControl', {
|
|
197
|
+
componentControl,
|
|
198
|
+
toggleEditor(type, nodeControl, event) {
|
|
199
|
+
const uid = nodeControl.value.uid
|
|
200
|
+
if(componentControl.value && componentControl.value.uid == uid && componentControl.value.type == type) {
|
|
201
|
+
componentControl.value = null
|
|
202
|
+
} else {
|
|
203
|
+
console.log("SET COMPONENT CONTROL", nodeControl.value)
|
|
204
|
+
componentControl.value = { type, nodeControl, uid }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
158
209
|
</script>
|
|
159
210
|
|
|
160
211
|
<style scoped>
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
<script setup>
|
|
16
16
|
|
|
17
17
|
import { useEditor, EditorContent } from '@tiptap/vue-3'
|
|
18
|
+
import { History } from '@tiptap/extension-history'
|
|
18
19
|
|
|
19
20
|
import { ref, computed, watch, provide, defineEmits, defineProps } from 'vue'
|
|
20
21
|
import { toRefs } from '@vueuse/core'
|
|
@@ -39,7 +40,13 @@
|
|
|
39
40
|
|
|
40
41
|
const editor = useEditor({
|
|
41
42
|
content: JSON.parse(props.modelValue),
|
|
42
|
-
extensions
|
|
43
|
+
extensions: [
|
|
44
|
+
...extensions,
|
|
45
|
+
History.configure({
|
|
46
|
+
depth: 100,
|
|
47
|
+
newGroupDelay: 500
|
|
48
|
+
})
|
|
49
|
+
],
|
|
43
50
|
onUpdate: ({ editor, transaction }) => {
|
|
44
51
|
console.log("EDITOR UPDATE", editor, transaction)
|
|
45
52
|
const editorJson = JSON.stringify(editor.getJSON())
|
|
@@ -76,8 +76,9 @@
|
|
|
76
76
|
</FileInput>
|
|
77
77
|
<!-- </div>-->
|
|
78
78
|
|
|
79
|
-
<Button v-if="config.nodes.component" label="
|
|
80
|
-
|
|
79
|
+
<Button v-if="config.nodes.component" label="Component"
|
|
80
|
+
class="p-button-sm p-button-primary p-button-outlined inline-block mr-1 mb-1"
|
|
81
|
+
@click="openComponentMenu" />
|
|
81
82
|
|
|
82
83
|
<span v-if="saveState"
|
|
83
84
|
class="p-button p-component p-button-sm p-button-outlined p-button-secondary cursor-auto
|
|
@@ -98,14 +99,23 @@
|
|
|
98
99
|
|
|
99
100
|
</div>
|
|
100
101
|
<slot name="after" />
|
|
102
|
+
<OverlayPanel ref="insertComponentOverlay" style="width: 450px" :breakpoints="{'960px': '75vw'}">
|
|
103
|
+
<ComponentsMenu :config="config" @selected="(n,c) => handleComponentSelected(n,c)" />
|
|
104
|
+
</OverlayPanel>
|
|
105
|
+
<!-- <div class="surface-card p-1 shadow-2" style="width: 450px">
|
|
106
|
+
<h3>components:</h3>
|
|
107
|
+
<ComponentsMenu :config="config" />
|
|
108
|
+
</div>-->
|
|
101
109
|
</div>
|
|
102
110
|
</template>
|
|
103
111
|
|
|
104
112
|
<script setup>
|
|
105
113
|
import Button from "primevue/button"
|
|
106
114
|
import ProgressSpinner from "primevue/progressspinner"
|
|
115
|
+
import OverlayPanel from 'primevue/overlaypanel'
|
|
107
116
|
|
|
108
|
-
import
|
|
117
|
+
import ComponentsMenu from "./ComponentsMenu.vue"
|
|
118
|
+
import { inject, getCurrentInstance, ref } from "vue"
|
|
109
119
|
import { toRefs } from "@vueuse/core"
|
|
110
120
|
import { uploadImage } from "@live-change/image-frontend"
|
|
111
121
|
import { FileInput } from "@live-change/upload-frontend"
|
|
@@ -145,7 +155,28 @@
|
|
|
145
155
|
await upload.upload()
|
|
146
156
|
}
|
|
147
157
|
|
|
148
|
-
|
|
158
|
+
const insertComponentOverlay = ref()
|
|
159
|
+
function openComponentMenu(event) {
|
|
160
|
+
insertComponentOverlay.value.show(event)
|
|
161
|
+
}
|
|
162
|
+
function handleComponentSelected(name, component) {
|
|
163
|
+
insertComponentOverlay.value.hide()
|
|
164
|
+
editor.value.chain().focus().insertContent({
|
|
165
|
+
type: 'component',
|
|
166
|
+
attrs: {
|
|
167
|
+
is: name,
|
|
168
|
+
attrs: component.initialAttrs
|
|
169
|
+
},
|
|
170
|
+
content: [
|
|
171
|
+
{ type: 'paragraph', content: [
|
|
172
|
+
{ type: 'text', text: 'test' }
|
|
173
|
+
] }
|
|
174
|
+
]
|
|
175
|
+
}).run()
|
|
176
|
+
//editor.value.chain().focus().setNode('component', { is: component }).run()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if(typeof window != 'undefined') window.editor = editor.value
|
|
149
180
|
</script>
|
|
150
181
|
|
|
151
182
|
<style scoped>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-row flex-wrap ">
|
|
3
|
+
<div v-for="type in paddingTypes"
|
|
4
|
+
class="flex flex-row align-items-center justify-content-start my-1"
|
|
5
|
+
style="width: 5.6em">
|
|
6
|
+
<label :for="'padding-'+type" class="mb-0 mr-1">{{ type }}</label>
|
|
7
|
+
<InputNumber v-if="props.modelValue[type] !== undefined"
|
|
8
|
+
:inputId="'padding-'+type"
|
|
9
|
+
class="p-inputtext-sm"
|
|
10
|
+
inputStyle="max-width: 1.7em;"
|
|
11
|
+
:modelValue="props.modelValue[type]"
|
|
12
|
+
@update:modelValue="value => updatePadding(type, value)"
|
|
13
|
+
mode="decimal"
|
|
14
|
+
showButtons :min="-1" :max="8" />
|
|
15
|
+
<Button v-else class="p-button p-button-icon-only p-button-rounded mr-0" icon="pi pi-plus"
|
|
16
|
+
@click="ev => updatePadding(type, 1)" />
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup>
|
|
22
|
+
import InputNumber from "primevue/inputnumber"
|
|
23
|
+
import Button from "primevue/button"
|
|
24
|
+
|
|
25
|
+
const props = defineProps({
|
|
26
|
+
modelValue: {
|
|
27
|
+
type: Object,
|
|
28
|
+
default: () => ({})
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
const emit = defineEmits(['update:modelValue'])
|
|
32
|
+
|
|
33
|
+
const paddingTypes = ['all', 'x', 'y', 'l', 'r', 't', 'b']
|
|
34
|
+
|
|
35
|
+
function updatePadding(type, value) {
|
|
36
|
+
console.log("UPDATE PADDING", type, value)
|
|
37
|
+
const newValue = { ...props.modelValue }
|
|
38
|
+
if(value < 0) {
|
|
39
|
+
delete newValue[type]
|
|
40
|
+
} else {
|
|
41
|
+
newValue[type] = value
|
|
42
|
+
}
|
|
43
|
+
emit('update:modelValue', newValue)
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<style scoped>
|
|
48
|
+
|
|
49
|
+
</style>
|
|
@@ -117,6 +117,7 @@ class RemoteAuthority{
|
|
|
117
117
|
debug("LOAD DOCUMENT!")
|
|
118
118
|
const identifier = { targetType: this.targetType, target: this.target }
|
|
119
119
|
let documentData = await this.api.get(['prosemirror', 'document', identifier])
|
|
120
|
+
console.log("DOCUMENT DATA", documentData)
|
|
120
121
|
if(!documentData) {
|
|
121
122
|
documentData = {
|
|
122
123
|
...identifier,
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- <pre>{{ JSON.stringify(classInfo, null, " ") }}</pre>-->
|
|
3
|
+
<div class="flex flex-column flex-wrap w-full">
|
|
4
|
+
<div v-if="editingPadding">
|
|
5
|
+
<div class="flex flex-row align-items-center mb-2">
|
|
6
|
+
<label class="mr-3" style="width: 4em">Padding:</label>
|
|
7
|
+
<PaddingEditor :modelValue="classInfo.padding"
|
|
8
|
+
@update:modelValue="p => handleClassInfoUpdate({ padding: p })" />
|
|
9
|
+
</div>
|
|
10
|
+
<div class="flex flex-row align-items-center">
|
|
11
|
+
<label class="mr-3" style="width: 4em">Margin:</label>
|
|
12
|
+
<PaddingEditor :modelValue="classInfo.margin"
|
|
13
|
+
@update:modelValue="p => handleClassInfoUpdate({ margin: p })" />
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="flex flex-column md:flex-row">
|
|
17
|
+
<div class="flex-1 mr-2 flex flex-column">
|
|
18
|
+
<div class="flex flex-row align-items-center flex-shrink-0">
|
|
19
|
+
<label class="my-2 flex-grow-1">Classes:</label>
|
|
20
|
+
<div class="flex flex-row align-items-center mr-4">
|
|
21
|
+
<i class="pi pi-arrows-alt" style="font-size: 1.2rem"></i>
|
|
22
|
+
<InputSwitch v-model="editingPadding" class="ml-2" />
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<AutoComplete :modelValue="classInfo.list"
|
|
26
|
+
@update:modelValue="l => handleClassInfoUpdate({ list: l })"
|
|
27
|
+
:separator="/ /" :multiple="true" :suggestions="classSuggestions"
|
|
28
|
+
@complete="ev => searchClasses(ev)"
|
|
29
|
+
class="autocomplete-w-full flex-grow-1" />
|
|
30
|
+
</div>
|
|
31
|
+
<div class="flex-1 flex flex-column">
|
|
32
|
+
<div class="flex flex-row flex-shrink-0">
|
|
33
|
+
<label class="my-2 flex-grow-1">Style:</label>
|
|
34
|
+
</div>
|
|
35
|
+
<prism-editor v-if="isMounted"
|
|
36
|
+
class="style-editor p-inputtext flex-grow-1" :highlight="highlightCss"
|
|
37
|
+
:style="{ height: (nodeStyleLines * 1.35 + 1.1) + 'em' }"
|
|
38
|
+
:modelValue="nodeStyle" @update:modelValue="handleStyleUpdate"
|
|
39
|
+
:readonly="false" :line-numbers="nodeStyleLines > 1" />
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script setup>
|
|
46
|
+
import AutoComplete from "primevue/autocomplete"
|
|
47
|
+
import InputSwitch from 'primevue/inputswitch';
|
|
48
|
+
|
|
49
|
+
import PaddingEditor from "./PaddingEditor.vue"
|
|
50
|
+
|
|
51
|
+
import 'vue-prism-editor/dist/prismeditor.min.css'
|
|
52
|
+
import 'prismjs/themes/prism-coy.css'
|
|
53
|
+
import * as Prism from 'prismjs/components/prism-core'
|
|
54
|
+
import 'prismjs/components/prism-css'
|
|
55
|
+
|
|
56
|
+
import { PrismEditor } from 'vue-prism-editor'
|
|
57
|
+
|
|
58
|
+
import {ref, computed, watch, provide, defineEmits, defineProps, onMounted} from 'vue'
|
|
59
|
+
import { toRefs } from '@vueuse/core'
|
|
60
|
+
|
|
61
|
+
const isMounted = ref(false)
|
|
62
|
+
onMounted(() => isMounted.value = true)
|
|
63
|
+
|
|
64
|
+
const props = defineProps({
|
|
65
|
+
nodeControl: {
|
|
66
|
+
type: Object,
|
|
67
|
+
required: true
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const { nodeControl } = toRefs(props)
|
|
72
|
+
|
|
73
|
+
const editingPadding = ref(false)
|
|
74
|
+
|
|
75
|
+
function extract(info, regex, setter) {
|
|
76
|
+
const rest = []
|
|
77
|
+
const list = info.list
|
|
78
|
+
for(let i = 0; i < list.length; i++) {
|
|
79
|
+
const match = list[i].match(regex)
|
|
80
|
+
if(match) setter(...match.slice(1))
|
|
81
|
+
else rest.push(list[i])
|
|
82
|
+
}
|
|
83
|
+
info.list = rest
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function extractClasses(info) {
|
|
87
|
+
info.padding = { }
|
|
88
|
+
if(editingPadding.value) {
|
|
89
|
+
extract(info, /^p([xyltrb])?-(\d+)$/, (dir, size) => {
|
|
90
|
+
if (dir) info.padding[dir] = size
|
|
91
|
+
else info.padding.all = size
|
|
92
|
+
})
|
|
93
|
+
info.margin = {}
|
|
94
|
+
extract(info, /^m([xyltrb])?-(\d+)$/, (dir, size) => {
|
|
95
|
+
if (dir) info.margin[dir] = size
|
|
96
|
+
else info.margin.all = size
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function recombinePadding(list, padding, prefix = 'p') {
|
|
101
|
+
if(padding.all !== undefined) list.push(`${prefix}-${padding.all}`)
|
|
102
|
+
if(padding.x !== undefined) list.push(`${prefix}x-${padding.x}`)
|
|
103
|
+
if(padding.y !== undefined) list.push(`${prefix}y-${padding.y}`)
|
|
104
|
+
if(padding.t !== undefined) list.push(`${prefix}t-${padding.t}`)
|
|
105
|
+
if(padding.r !== undefined) list.push(`${prefix}r-${padding.r}`)
|
|
106
|
+
if(padding.b !== undefined) list.push(`${prefix}b-${padding.b}`)
|
|
107
|
+
if(padding.l !== undefined) list.push(`${prefix}l-${padding.l}`)
|
|
108
|
+
}
|
|
109
|
+
function recombineClasses(info) {
|
|
110
|
+
const { padding, margin, list } = info
|
|
111
|
+
if(editingPadding.value) {
|
|
112
|
+
recombinePadding(list, padding, 'p')
|
|
113
|
+
recombinePadding(list, margin, 'm')
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const insertableClasses = ['flex', 'flex-grow-1']
|
|
118
|
+
const nodeClass = computed(() => {
|
|
119
|
+
console.log("nodeControl", nodeControl.value)
|
|
120
|
+
return nodeControl.value.attrs.class
|
|
121
|
+
})
|
|
122
|
+
const classInfo = computed(() => {
|
|
123
|
+
const list = nodeClass.value ? nodeClass.value.split(' ') : []
|
|
124
|
+
const info = { list }
|
|
125
|
+
extractClasses(info)
|
|
126
|
+
return info
|
|
127
|
+
})
|
|
128
|
+
const classSuggestions = ref()
|
|
129
|
+
function searchClasses(event) {
|
|
130
|
+
const query = event.query.toLowerCase()
|
|
131
|
+
console.log("SEARCH CLASSES", query)
|
|
132
|
+
classSuggestions.value = [
|
|
133
|
+
...(insertableClasses.find(c => c == event.query) ? [] : [event.query]),
|
|
134
|
+
...(insertableClasses.filter((c) => {
|
|
135
|
+
return c.toLowerCase().startsWith(query)
|
|
136
|
+
}))
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
function handleClassInfoUpdate(change) {
|
|
140
|
+
let info = JSON.parse(JSON.stringify(classInfo.value))
|
|
141
|
+
info = { ...info, ...change }
|
|
142
|
+
recombineClasses(info)
|
|
143
|
+
console.log("INFO", info)
|
|
144
|
+
nodeControl.value.updateAttributes({ attrs: {
|
|
145
|
+
...nodeControl.value.attrs,
|
|
146
|
+
class: info.list.join(' ')
|
|
147
|
+
}})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const nodeStyle = computed(() => {
|
|
151
|
+
return nodeControl.value.attrs.style
|
|
152
|
+
})
|
|
153
|
+
const nodeStyleLines = computed(() => (nodeStyle.value || '').split('\n').length)
|
|
154
|
+
function highlightCss(code) {
|
|
155
|
+
return Prism.highlight(code, Prism.languages.css, "css")
|
|
156
|
+
}
|
|
157
|
+
function handleStyleUpdate(style) {
|
|
158
|
+
console.log("NC", nodeControl.value)
|
|
159
|
+
nodeControl.value.updateAttributes({ attrs: {
|
|
160
|
+
...nodeControl.value.attrs,
|
|
161
|
+
style
|
|
162
|
+
}})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
</script>
|
|
166
|
+
|
|
167
|
+
<style lang="scss">
|
|
168
|
+
.autocomplete-w-full > ul {
|
|
169
|
+
width: 100%;
|
|
170
|
+
}
|
|
171
|
+
.prism-editor__textarea {
|
|
172
|
+
outline: 0 none;
|
|
173
|
+
outline-offset: 0;
|
|
174
|
+
}
|
|
175
|
+
.style-editor:focus-within {
|
|
176
|
+
box-shadow: 0 0 0 0.2rem #b7e0b8;
|
|
177
|
+
border-color: #4CAF50;
|
|
178
|
+
}
|
|
179
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { h } from 'vue'
|
|
2
|
+
|
|
3
|
+
const components = {
|
|
4
|
+
card: {
|
|
5
|
+
description: 'PrimeVUE card',
|
|
6
|
+
previewContent: true,
|
|
7
|
+
attrs: [],
|
|
8
|
+
initialAttrs: {
|
|
9
|
+
class: 'surface-card py-1 px-3 shadow-2'
|
|
10
|
+
},
|
|
11
|
+
render: ({ content, attrs }, r) => h('div', { ...attrs.attrs }, r(content)),
|
|
12
|
+
editor: (attrs, { slots }) => h('div', { ...attrs }, slots.default()),
|
|
13
|
+
async: true
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default components
|
|
@@ -48,3 +48,13 @@ export const richEditorNodes = {
|
|
|
48
48
|
|
|
49
49
|
...messageNodes
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
import components from './components.js'
|
|
53
|
+
|
|
54
|
+
export const pageNodes = {
|
|
55
|
+
...richEditorNodes,
|
|
56
|
+
|
|
57
|
+
component: (params, r) => {
|
|
58
|
+
return components[params.attrs.is].render(params, r)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -19,6 +19,7 @@ import Strike from "@tiptap/extension-strike"
|
|
|
19
19
|
|
|
20
20
|
import ImageNode from "./ImageNode.js"
|
|
21
21
|
|
|
22
|
+
import ComponentNode from "./ComponentNode.js"
|
|
22
23
|
|
|
23
24
|
export const marksExtensions = {
|
|
24
25
|
bold: [Bold],
|
|
@@ -43,7 +44,9 @@ export const nodesExtensions = {
|
|
|
43
44
|
orderedList: [OrderedList],
|
|
44
45
|
listItem: [ListItem],
|
|
45
46
|
|
|
46
|
-
image: [ImageNode]
|
|
47
|
+
image: [ImageNode],
|
|
48
|
+
|
|
49
|
+
component: [ComponentNode]
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
export function getExtensions(contentConfig) {
|
package/front/src/router.js
CHANGED
|
@@ -18,10 +18,15 @@ export function wysiwygRoutes(config = {}) {
|
|
|
18
18
|
}),
|
|
19
19
|
|
|
20
20
|
route({
|
|
21
|
-
name: 'wysiwyg:documentEditor', path: prefix + '
|
|
21
|
+
name: 'wysiwyg:documentEditor', path: prefix + 'doc/:target', meta: { },
|
|
22
22
|
component: () => import("./DocumentEditorTest.vue"),
|
|
23
|
-
props:
|
|
24
|
-
|
|
23
|
+
props: true
|
|
24
|
+
}),
|
|
25
|
+
|
|
26
|
+
route({
|
|
27
|
+
name: 'wysiwyg:documentPreview', path: prefix + 'view/:target', meta: { },
|
|
28
|
+
component: () => import("./DocumentPreviewTest.vue"),
|
|
29
|
+
props: true
|
|
25
30
|
}),
|
|
26
31
|
|
|
27
32
|
...dbAdminRoutes({ prefix: '/_db', route: r => ({ ...r, meta: { ...r.meta, raw: true }}) })
|
package/index.js
CHANGED
|
@@ -11,3 +11,7 @@ import EditorMenu from "./front/src/components/EditorMenu.vue"
|
|
|
11
11
|
import ImageComponent from "./front/src/components/ImageComponent.vue"
|
|
12
12
|
import ImageNode from "./front/src/components/ImageNode.js"
|
|
13
13
|
export { DocumentEditor, Editor, EditorMenu, ImageComponent, ImageNode }
|
|
14
|
+
|
|
15
|
+
import components from "./front/src/components/components.js"
|
|
16
|
+
|
|
17
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/wysiwyg-frontend",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"memDev": "lcli memDev --enableSessions --initScript ./init.js --dbAccess",
|
|
6
6
|
"localDevInit": "rm tmp.db; lcli localDev --enableSessions --initScript ./init.js",
|
|
@@ -21,16 +21,16 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@fortawesome/fontawesome-free": "^6.2.0",
|
|
24
|
-
"@live-change/cli": "0.7.
|
|
24
|
+
"@live-change/cli": "0.7.5",
|
|
25
25
|
"@live-change/dao": "0.5.8",
|
|
26
26
|
"@live-change/dao-vue3": "0.5.8",
|
|
27
27
|
"@live-change/dao-websocket": "0.5.8",
|
|
28
|
-
"@live-change/framework": "0.7.
|
|
29
|
-
"@live-change/password-authentication-service": "0.3.
|
|
30
|
-
"@live-change/prosemirror-service": "0.3.
|
|
31
|
-
"@live-change/secret-code-service": "0.3.
|
|
32
|
-
"@live-change/secret-link-service": "0.3.
|
|
33
|
-
"@live-change/session-service": "0.3.
|
|
28
|
+
"@live-change/framework": "0.7.5",
|
|
29
|
+
"@live-change/password-authentication-service": "0.3.4",
|
|
30
|
+
"@live-change/prosemirror-service": "0.3.4",
|
|
31
|
+
"@live-change/secret-code-service": "0.3.4",
|
|
32
|
+
"@live-change/secret-link-service": "0.3.4",
|
|
33
|
+
"@live-change/session-service": "0.3.4",
|
|
34
34
|
"@live-change/vue3-components": "0.2.16",
|
|
35
35
|
"@live-change/vue3-ssr": "0.2.16",
|
|
36
36
|
"@tiptap/extension-blockquote": "^2.0.0-beta.29",
|
|
@@ -62,18 +62,19 @@
|
|
|
62
62
|
"primeflex": "^3.2.1",
|
|
63
63
|
"primeicons": "^6.0.1",
|
|
64
64
|
"primevue": "^3.18.1",
|
|
65
|
+
"prismjs": "^1.28.0",
|
|
65
66
|
"prosemirror-collab": "^1.3.0",
|
|
66
67
|
"rollup-plugin-node-builtins": "^2.1.2",
|
|
67
68
|
"rollup-plugin-visualizer": "5.6.0",
|
|
68
|
-
"serialize-javascript": "^6.0.0",
|
|
69
69
|
"serve-static": "^1.15.0",
|
|
70
70
|
"v-shared-element": "3.1.0",
|
|
71
71
|
"vue-meta": "^3.0.0-alpha.9",
|
|
72
|
+
"vue-prism-editor": "2.0.0-alpha.2",
|
|
72
73
|
"vue-router": "^4.1.3",
|
|
73
74
|
"vue3-scroll-border": "0.1.2"
|
|
74
75
|
},
|
|
75
76
|
"devDependencies": {
|
|
76
|
-
"@live-change/codeceptjs-helper": "0.7.
|
|
77
|
+
"@live-change/codeceptjs-helper": "0.7.5",
|
|
77
78
|
"@wdio/selenium-standalone-service": "^7.20.8",
|
|
78
79
|
"codeceptjs": "^3.3.4",
|
|
79
80
|
"generate-password": "1.7.0",
|
|
@@ -85,5 +86,5 @@
|
|
|
85
86
|
"author": "",
|
|
86
87
|
"license": "ISC",
|
|
87
88
|
"description": "",
|
|
88
|
-
"gitHead": "
|
|
89
|
+
"gitHead": "7b0e7fc149a3d419aa125b45314cf4eac2fc9410"
|
|
89
90
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
"marks": {
|
|
3
|
+
"bold": {},
|
|
4
|
+
"italic": {},
|
|
5
|
+
"underline": {},
|
|
6
|
+
"strike": {}
|
|
7
|
+
},
|
|
8
|
+
"nodes": {
|
|
9
|
+
"paragraph": {
|
|
10
|
+
"content": "inline*",
|
|
11
|
+
"group": "block"
|
|
12
|
+
},
|
|
13
|
+
"horizontalRule": {
|
|
14
|
+
"group": "block"
|
|
15
|
+
},
|
|
16
|
+
"heading": {
|
|
17
|
+
"content": "inline*",
|
|
18
|
+
"group": "block",
|
|
19
|
+
"defining": true,
|
|
20
|
+
"attrs": {
|
|
21
|
+
"level": {
|
|
22
|
+
"default": 1
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"blockquote": {
|
|
27
|
+
"content": "block+",
|
|
28
|
+
"group": "block",
|
|
29
|
+
"defining": true
|
|
30
|
+
},
|
|
31
|
+
"codeBlock": {
|
|
32
|
+
"content": "text*",
|
|
33
|
+
"marks": "",
|
|
34
|
+
"group": "block",
|
|
35
|
+
"code": true,
|
|
36
|
+
"defining": true,
|
|
37
|
+
"attrs": {
|
|
38
|
+
"language": {
|
|
39
|
+
"default": null
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"bulletList": {
|
|
44
|
+
"content": "listItem+",
|
|
45
|
+
"group": "block list"
|
|
46
|
+
},
|
|
47
|
+
"orderedList": {
|
|
48
|
+
"content": "listItem+",
|
|
49
|
+
"group": "block list",
|
|
50
|
+
"attrs": {
|
|
51
|
+
"start": {
|
|
52
|
+
"default": 1
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"listItem": {
|
|
57
|
+
"content": "paragraph block*",
|
|
58
|
+
"defining": true
|
|
59
|
+
},
|
|
60
|
+
"image": {
|
|
61
|
+
"content": "",
|
|
62
|
+
"marks": "",
|
|
63
|
+
"group": "block",
|
|
64
|
+
"inline": false,
|
|
65
|
+
"atom": true,
|
|
66
|
+
"selectable": true,
|
|
67
|
+
"draggable": true,
|
|
68
|
+
"attrs": {
|
|
69
|
+
"image": {
|
|
70
|
+
"default": null
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"doc": {
|
|
75
|
+
"content": "block+"
|
|
76
|
+
},
|
|
77
|
+
"text": {
|
|
78
|
+
"group": "inline"
|
|
79
|
+
},
|
|
80
|
+
"hardBreak": {
|
|
81
|
+
"group": "inline",
|
|
82
|
+
"inline": true,
|
|
83
|
+
"selectable": false
|
|
84
|
+
},
|
|
85
|
+
"component": {
|
|
86
|
+
"content": "block*",
|
|
87
|
+
"marks": "",
|
|
88
|
+
"group": "block",
|
|
89
|
+
"inline": false,
|
|
90
|
+
"selectable": true,
|
|
91
|
+
"draggable": true,
|
|
92
|
+
"attrs": {
|
|
93
|
+
"is": {
|
|
94
|
+
"default": "card"
|
|
95
|
+
},
|
|
96
|
+
"attrs": {
|
|
97
|
+
"default": {}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"topNode": "doc"
|
|
103
|
+
}
|
|
@@ -17,7 +17,8 @@ module.exports = {
|
|
|
17
17
|
name: 'prosemirror',
|
|
18
18
|
path: '@live-change/prosemirror-service',
|
|
19
19
|
documentTypes: {
|
|
20
|
-
rich: require('./rich.documentType.js')
|
|
20
|
+
rich: require('./rich.documentType.js'),
|
|
21
|
+
page: require('./page.documentType.js')
|
|
21
22
|
},
|
|
22
23
|
testLatency: 1000
|
|
23
24
|
}
|