@live-change/wysiwyg-frontend 0.2.10 → 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 +60 -2
- package/front/src/components/ComponentComponent.vue +63 -4
- package/front/src/components/ComponentsMenu.vue +2 -1
- package/front/src/components/DocumentEditor.vue +47 -4
- package/front/src/components/EditorMenu.vue +6 -5
- package/front/src/components/PaddingEditor.vue +49 -0
- package/front/src/components/SettingsEditor.vue +10 -0
- package/front/src/components/StyleEditor.vue +179 -0
- package/front/src/components/components.js +5 -3
- package/package.json +9 -8
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
const emptyContent = {
|
|
37
37
|
"type": "doc",
|
|
38
|
-
"content": [
|
|
38
|
+
"content": [
|
|
39
39
|
{
|
|
40
40
|
"type": "paragraph",
|
|
41
41
|
"content": [
|
|
@@ -53,8 +53,66 @@
|
|
|
53
53
|
"text": "est"
|
|
54
54
|
}
|
|
55
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
|
+
]
|
|
56
114
|
}
|
|
57
|
-
|
|
115
|
+
]
|
|
58
116
|
}
|
|
59
117
|
|
|
60
118
|
</script>
|
|
@@ -1,22 +1,81 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<node-view-wrapper>
|
|
3
|
-
<ComponentEditor>
|
|
3
|
+
<ComponentEditor v-bind="{ ...attrs, class: ['relative', attrs.class] }">
|
|
4
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>
|
|
5
29
|
</ComponentEditor>
|
|
6
30
|
</node-view-wrapper>
|
|
7
31
|
</template>
|
|
8
32
|
|
|
9
33
|
<script setup>
|
|
34
|
+
import Button from "primevue/button"
|
|
35
|
+
|
|
10
36
|
import { NodeViewContent, nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
|
|
11
37
|
import components from "./components.js";
|
|
12
38
|
|
|
39
|
+
import { inject, computed, getCurrentInstance } from "vue"
|
|
40
|
+
|
|
13
41
|
const props = defineProps(nodeViewProps)
|
|
14
42
|
|
|
15
|
-
const
|
|
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
|
+
})
|
|
16
70
|
|
|
17
|
-
const ComponentEditor = components[is].editor
|
|
18
71
|
</script>
|
|
19
72
|
|
|
20
|
-
<style
|
|
73
|
+
<style lang="scss">
|
|
74
|
+
.edit-buttons {
|
|
75
|
+
display: none;
|
|
76
|
+
}
|
|
77
|
+
.show-edit-buttons .edit-buttons {
|
|
78
|
+
display: flex;
|
|
79
|
+
}
|
|
21
80
|
|
|
22
81
|
</style>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<!-- <div v-for="i in [1,2,3,4,5,6,7,8,9,10]">-->
|
|
11
11
|
<div v-for="([ name, component ], index) in searchResults" :key="name"
|
|
12
12
|
class="flex flex-wrap relative cursor-pointer hover:surface-100"
|
|
13
|
-
@click="() => emit('selected', name)" >
|
|
13
|
+
@click="() => emit('selected', name, component)" >
|
|
14
14
|
<div class="w-3 ml-2 mr-2 my-1" style="aspect-ratio: 1/1">
|
|
15
15
|
<div class="pointer-events-none w-6 absolute p-1"
|
|
16
16
|
style="aspect-ratio: 1/1; transform: scale(0.5); transform-origin: 0 0;">
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"attrs": {
|
|
67
67
|
"is": name,
|
|
68
68
|
"attrs": {
|
|
69
|
+
...component.initialAttrs,
|
|
69
70
|
...component.previewAttrs
|
|
70
71
|
}
|
|
71
72
|
},
|
|
@@ -3,21 +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'
|
|
18
38
|
import { History } from '@tiptap/extension-history'
|
|
19
39
|
import {
|
|
20
|
-
ref, computed, watch, provide, defineEmits, defineProps, getCurrentInstance, onUnmounted, inject, onMounted
|
|
40
|
+
ref, computed, watch, provide, defineEmits, defineProps, getCurrentInstance, onUnmounted, inject, onMounted,
|
|
41
|
+
shallowRef
|
|
21
42
|
} from 'vue'
|
|
22
43
|
import { toRefs, useDebounceFn } from '@vueuse/core'
|
|
23
44
|
import EditorMenu from "./EditorMenu.vue"
|
|
@@ -139,6 +160,14 @@
|
|
|
139
160
|
? serializeSchema(editor.view.state.schema.spec)
|
|
140
161
|
: getSchemaSpecFromConfig(props.config)
|
|
141
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
|
+
|
|
142
171
|
if(typeof window != 'undefined') window.schemaSpecJson = JSON.stringify(schemaSpec, null, " ")
|
|
143
172
|
|
|
144
173
|
async function save() {
|
|
@@ -163,6 +192,20 @@
|
|
|
163
192
|
}
|
|
164
193
|
}
|
|
165
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
|
+
|
|
166
209
|
</script>
|
|
167
210
|
|
|
168
211
|
<style scoped>
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
</FileInput>
|
|
77
77
|
<!-- </div>-->
|
|
78
78
|
|
|
79
|
-
<Button v-if="config.nodes.component" label="
|
|
80
|
-
class="p-button-sm p-button-primary inline-block mr-1 mb-1"
|
|
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
81
|
@click="openComponentMenu" />
|
|
82
82
|
|
|
83
83
|
<span v-if="saveState"
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
</div>
|
|
101
101
|
<slot name="after" />
|
|
102
102
|
<OverlayPanel ref="insertComponentOverlay" style="width: 450px" :breakpoints="{'960px': '75vw'}">
|
|
103
|
-
<ComponentsMenu :config="config" @selected="c => handleComponentSelected(c)" />
|
|
103
|
+
<ComponentsMenu :config="config" @selected="(n,c) => handleComponentSelected(n,c)" />
|
|
104
104
|
</OverlayPanel>
|
|
105
105
|
<!-- <div class="surface-card p-1 shadow-2" style="width: 450px">
|
|
106
106
|
<h3>components:</h3>
|
|
@@ -159,12 +159,13 @@
|
|
|
159
159
|
function openComponentMenu(event) {
|
|
160
160
|
insertComponentOverlay.value.show(event)
|
|
161
161
|
}
|
|
162
|
-
function handleComponentSelected(component) {
|
|
162
|
+
function handleComponentSelected(name, component) {
|
|
163
163
|
insertComponentOverlay.value.hide()
|
|
164
164
|
editor.value.chain().focus().insertContent({
|
|
165
165
|
type: 'component',
|
|
166
166
|
attrs: {
|
|
167
|
-
is:
|
|
167
|
+
is: name,
|
|
168
|
+
attrs: component.initialAttrs
|
|
168
169
|
},
|
|
169
170
|
content: [
|
|
170
171
|
{ type: 'paragraph', content: [
|
|
@@ -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>
|
|
@@ -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>
|
|
@@ -5,11 +5,13 @@ const components = {
|
|
|
5
5
|
description: 'PrimeVUE card',
|
|
6
6
|
previewContent: true,
|
|
7
7
|
attrs: [],
|
|
8
|
-
|
|
9
|
-
|
|
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()),
|
|
10
13
|
async: true
|
|
11
14
|
}
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
|
|
15
17
|
export default components
|
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",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"@live-change/dao-vue3": "0.5.8",
|
|
27
27
|
"@live-change/dao-websocket": "0.5.8",
|
|
28
28
|
"@live-change/framework": "0.7.5",
|
|
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.
|
|
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,13 +62,14 @@
|
|
|
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
|
},
|
|
@@ -85,5 +86,5 @@
|
|
|
85
86
|
"author": "",
|
|
86
87
|
"license": "ISC",
|
|
87
88
|
"description": "",
|
|
88
|
-
"gitHead": "
|
|
89
|
+
"gitHead": "7b0e7fc149a3d419aa125b45314cf4eac2fc9410"
|
|
89
90
|
}
|