dragon-editor 2.0.0-beta.2.1.2 → 2.1.0
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/README.md +29 -5
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -2
- package/dist/runtime/core/components/SvgIcon.d.ts +10 -0
- package/dist/runtime/core/components/SvgIcon.mjs +98 -0
- package/dist/runtime/core/components/editor/ImageBlock.vue +5 -5
- package/dist/runtime/core/components/editor/OlBlock.vue +93 -66
- package/dist/runtime/core/components/editor/TextBlock.vue +12 -6
- package/dist/runtime/core/components/editor/UlBlock.vue +162 -0
- package/dist/runtime/core/style/common.css +18 -1
- package/dist/runtime/core/style/viewer.css +14 -0
- package/dist/runtime/core/utils/converter.d.ts +2 -0
- package/dist/runtime/core/utils/converter.mjs +90 -0
- package/dist/runtime/core/utils/global.d.ts +3 -0
- package/dist/runtime/core/utils/global.mjs +81 -0
- package/dist/runtime/core/utils/index.d.ts +3 -2
- package/dist/runtime/core/utils/index.mjs +3 -79
- package/dist/runtime/core/utils/keyboard.mjs +22 -14
- package/dist/runtime/core/utils/ui.d.ts +4 -0
- package/dist/runtime/core/utils/ui.mjs +13 -0
- package/dist/runtime/shared/components/DragonEditor.d.ts +16 -0
- package/dist/runtime/shared/components/DragonEditor.mjs +62 -0
- package/dist/runtime/shared/components/DragonEditor.vue +17 -13
- package/dist/runtime/shared/components/DragonEditorComment.vue +60 -27
- package/dist/runtime/shared/components/DragonEditorViewer.d.ts +14 -0
- package/dist/runtime/shared/components/DragonEditorViewer.mjs +15 -0
- package/package.json +57 -57
- package/dist/runtime/core/components/SvgIcon.vue +0 -175
- package/dist/runtime/core/components/icon/Accept.vue +0 -5
- package/dist/runtime/core/components/icon/AlignCenter.vue +0 -6
- package/dist/runtime/core/components/icon/AlignLeft.vue +0 -6
- package/dist/runtime/core/components/icon/AlignRight.vue +0 -6
- package/dist/runtime/core/components/icon/ArrowDown.vue +0 -3
- package/dist/runtime/core/components/icon/ArrowUp.vue +0 -3
- package/dist/runtime/core/components/icon/Cancel.vue +0 -5
- package/dist/runtime/core/components/icon/CodeBlock.vue +0 -6
- package/dist/runtime/core/components/icon/DecorationBold.vue +0 -6
- package/dist/runtime/core/components/icon/DecorationItalic.vue +0 -6
- package/dist/runtime/core/components/icon/DecorationStrikethrough.vue +0 -6
- package/dist/runtime/core/components/icon/DecorationUnderline.vue +0 -6
- package/dist/runtime/core/components/icon/Delete.vue +0 -3
- package/dist/runtime/core/components/icon/ImageBlock.vue +0 -5
- package/dist/runtime/core/components/icon/LinkPath.vue +0 -6
- package/dist/runtime/core/components/icon/OlBlock.vue +0 -6
- package/dist/runtime/core/components/icon/QuotationBlock.vue +0 -6
- package/dist/runtime/core/components/icon/TableBlock.vue +0 -8
- package/dist/runtime/core/components/icon/TextBlock.vue +0 -5
- package/dist/runtime/core/components/icon/UlBlock.vue +0 -6
- package/dist/runtime/shared/components/DragonEditorViewer.vue +0 -31
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ul class="d-ul-block" @keydown="textKeyboardEvent" ref="$ul" :key="updateCount">
|
|
3
|
+
<li class="d-li-item" v-for="(row, i) in data.childList" :key="i" :class="row.classList" contenteditable ref="$item" v-html="row.content"></li>
|
|
4
|
+
</ul>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import { ref, unref } from "#imports";
|
|
10
|
+
import { cursorSelection, liItem, ListBlock, styleFunctionArgument } from "../../../../types";
|
|
11
|
+
import { getArrangementCursorData, setCursor, pasteText, styleSettings, keyboardEvent, getCursor, findEditableElement } from "../../utils";
|
|
12
|
+
|
|
13
|
+
const updateCount = ref<number>(0);
|
|
14
|
+
const $ul = ref();
|
|
15
|
+
const $item = ref();
|
|
16
|
+
const itemIdx = ref<number>(0);
|
|
17
|
+
const data = ref<ListBlock>({
|
|
18
|
+
type: "",
|
|
19
|
+
id: "",
|
|
20
|
+
childList: [
|
|
21
|
+
{
|
|
22
|
+
classList: [],
|
|
23
|
+
content: "",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
});
|
|
27
|
+
const props = defineProps<{ modelValue: ListBlock; cursorData: cursorSelection }>();
|
|
28
|
+
const emit = defineEmits<{
|
|
29
|
+
(e: "update:modelValue", modelValue: ListBlock): void;
|
|
30
|
+
(e: "addBlock", {}: { name: string; value: object }): void;
|
|
31
|
+
(e: "deleteBlockLocal", index?: number): void;
|
|
32
|
+
}>();
|
|
33
|
+
data.value = unref(props.modelValue) as ListBlock;
|
|
34
|
+
|
|
35
|
+
if (data.value.childList.length === 0) {
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 키보드 이벤트 할당
|
|
39
|
+
function textKeyboardEvent(e: KeyboardEvent) {
|
|
40
|
+
keyboardEvent("list", e, emit, updateBlockData);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 외부용 함수
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
// 데이터 정규화 및 검수
|
|
48
|
+
function updateBlockData() {
|
|
49
|
+
const $block = $ul.value;
|
|
50
|
+
const $childList = $block.querySelectorAll("li");
|
|
51
|
+
const childData: liItem[] = [];
|
|
52
|
+
const cursorData = getCursor();
|
|
53
|
+
|
|
54
|
+
$childList.forEach((row) => {
|
|
55
|
+
row.childNodes.forEach((child: ChildNode) => {
|
|
56
|
+
const $child = child as HTMLElement;
|
|
57
|
+
|
|
58
|
+
if (child.constructor.name !== "Text") {
|
|
59
|
+
// 텍스트가 아닐경우
|
|
60
|
+
if (child.constructor.name !== "HTMLBRElement") {
|
|
61
|
+
// br 태그 유지
|
|
62
|
+
if (child.textContent === "") {
|
|
63
|
+
// 빈 태그 삭제
|
|
64
|
+
child.remove();
|
|
65
|
+
} else if ($child.classList.length === 0) {
|
|
66
|
+
// 클레스 없는 엘리먼트 처리
|
|
67
|
+
$child.insertAdjacentHTML("afterend", $child.innerHTML);
|
|
68
|
+
child.remove();
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
$child.removeAttribute("class");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
childData.push({
|
|
77
|
+
classList: [...row.classList].splice(1),
|
|
78
|
+
content: row.innerHTML,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
data.value.childList = childData;
|
|
83
|
+
emit("update:modelValue", data.value);
|
|
84
|
+
updateCount.value += 1;
|
|
85
|
+
|
|
86
|
+
if (cursorData.startNode) {
|
|
87
|
+
const editableNode = findEditableElement(cursorData.startNode);
|
|
88
|
+
let childIdx = -1;
|
|
89
|
+
|
|
90
|
+
$childList.forEach((row, count) => {
|
|
91
|
+
if (row === editableNode) {
|
|
92
|
+
childIdx = count;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (childIdx > -1) {
|
|
97
|
+
// 기본 로직
|
|
98
|
+
itemIdx.value = childIdx;
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
const afterChildList = $ul.value.querySelectorAll("li");
|
|
101
|
+
setCursor(afterChildList[childIdx], cursorData.startOffset as number);
|
|
102
|
+
}, 100);
|
|
103
|
+
} else {
|
|
104
|
+
// 중간 엔터
|
|
105
|
+
$childList.forEach((row, count) => {
|
|
106
|
+
if (row === (cursorData.startNode as Node).parentNode) {
|
|
107
|
+
childIdx = count;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
const afterChildList = $ul.value.querySelectorAll("li");
|
|
113
|
+
setCursor(afterChildList[childIdx], cursorData.startOffset as number);
|
|
114
|
+
}, 100);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 포커스
|
|
120
|
+
function focus() {
|
|
121
|
+
const childList = $ul.value.querySelectorAll(".d-li-item");
|
|
122
|
+
setCursor(childList[itemIdx.value], 0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 블럭 위치 주기
|
|
126
|
+
function getBoundingClientRect() {
|
|
127
|
+
return $ul.value.parentNode.getBoundingClientRect();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 타입 전달
|
|
131
|
+
function getType() {
|
|
132
|
+
return data.value.type;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 붙여넣기 이벤트
|
|
136
|
+
function pasteEvent(text: string) {
|
|
137
|
+
pasteText("text", text);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 텍스트 스타일 지정
|
|
141
|
+
function setStyles({ type, url }: styleFunctionArgument) {
|
|
142
|
+
data.value = styleSettings({
|
|
143
|
+
kind: type,
|
|
144
|
+
blockData: data.value,
|
|
145
|
+
$target: $item[itemIdx.value],
|
|
146
|
+
url: url,
|
|
147
|
+
cursorData: props.cursorData,
|
|
148
|
+
});
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
updateBlockData();
|
|
151
|
+
}, 250);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
defineExpose({
|
|
155
|
+
updateBlockData,
|
|
156
|
+
focus,
|
|
157
|
+
getType,
|
|
158
|
+
pasteEvent,
|
|
159
|
+
setStyles,
|
|
160
|
+
getBoundingClientRect,
|
|
161
|
+
});
|
|
162
|
+
</script>
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
background: transparent;
|
|
137
137
|
transition: background 0.25s ease;
|
|
138
138
|
}
|
|
139
|
-
.dragon-editor .d-row-block:hover {
|
|
139
|
+
.dragon-editor .d-row-block:hover, .dragon-editor .d-row-block.--active {
|
|
140
140
|
background: #fafafa;
|
|
141
141
|
}
|
|
142
142
|
.dragon-editor .d-text-block {
|
|
@@ -168,6 +168,23 @@
|
|
|
168
168
|
content: "Write text";
|
|
169
169
|
color: #ccc;
|
|
170
170
|
}
|
|
171
|
+
.dragon-editor .d-ul-block {
|
|
172
|
+
padding-left: 30px;
|
|
173
|
+
cursor: text;
|
|
174
|
+
list-style: disc;
|
|
175
|
+
}
|
|
176
|
+
.dragon-editor .d-ul-block .d-li-item {
|
|
177
|
+
list-style: inherit;
|
|
178
|
+
outline: 0;
|
|
179
|
+
}
|
|
180
|
+
.dragon-editor .d-ul-block .d-li-item:empty {
|
|
181
|
+
min-height: 1.6em;
|
|
182
|
+
}
|
|
183
|
+
.dragon-editor .d-ul-block .d-li-item:empty::after {
|
|
184
|
+
display: inline;
|
|
185
|
+
content: "Write text";
|
|
186
|
+
color: #ccc;
|
|
187
|
+
}
|
|
171
188
|
.dragon-editor .d-image-block {
|
|
172
189
|
display: flex;
|
|
173
190
|
flex-direction: column;
|
|
@@ -189,3 +189,17 @@
|
|
|
189
189
|
color: #ccc;
|
|
190
190
|
font-size: 1rem;
|
|
191
191
|
}
|
|
192
|
+
.dragon-editor-viewer .d-ol-block {
|
|
193
|
+
padding-left: 30px;
|
|
194
|
+
list-style: decimal;
|
|
195
|
+
}
|
|
196
|
+
.dragon-editor-viewer .d-ol-block .d-li-item {
|
|
197
|
+
list-style: inherit;
|
|
198
|
+
}
|
|
199
|
+
.dragon-editor-viewer .d-ul-block {
|
|
200
|
+
padding-left: 30px;
|
|
201
|
+
list-style: disc;
|
|
202
|
+
}
|
|
203
|
+
.dragon-editor-viewer .d-ul-block .d-li-item {
|
|
204
|
+
list-style: inherit;
|
|
205
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { h } from "vue";
|
|
2
|
+
export function convertViewBlock(data, mediaUrl) {
|
|
3
|
+
const childList = [];
|
|
4
|
+
data.forEach((row) => {
|
|
5
|
+
let hObject;
|
|
6
|
+
switch (row.type) {
|
|
7
|
+
case "text":
|
|
8
|
+
const textBlockData = row;
|
|
9
|
+
hObject = h("p", {
|
|
10
|
+
class: ["d-text-block", ...textBlockData.classList],
|
|
11
|
+
innerHTML: textBlockData.content
|
|
12
|
+
});
|
|
13
|
+
break;
|
|
14
|
+
case "image":
|
|
15
|
+
const imageBlockData = row;
|
|
16
|
+
const imageChildList = [];
|
|
17
|
+
const imageUrl = mediaUrl ? mediaUrl + imageBlockData.src : imageBlockData.src;
|
|
18
|
+
imageChildList.push(
|
|
19
|
+
h(
|
|
20
|
+
"div",
|
|
21
|
+
{ class: ["d-image-area"] },
|
|
22
|
+
h("img", {
|
|
23
|
+
class: ["d-img"],
|
|
24
|
+
src: imageUrl,
|
|
25
|
+
width: imageBlockData.width,
|
|
26
|
+
height: imageBlockData.height,
|
|
27
|
+
alt: imageBlockData.caption,
|
|
28
|
+
loading: "lazy"
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
if (imageBlockData.caption !== "") {
|
|
33
|
+
imageChildList.push(
|
|
34
|
+
h("p", {
|
|
35
|
+
class: ["d-caption"],
|
|
36
|
+
innerHTML: imageBlockData.caption
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
hObject = h("div", { class: ["d-image-block", ...imageBlockData.classList] }, imageChildList);
|
|
41
|
+
break;
|
|
42
|
+
case "ol":
|
|
43
|
+
const olBlockData = row;
|
|
44
|
+
const olChildList = [];
|
|
45
|
+
olBlockData.childList.forEach((child) => {
|
|
46
|
+
olChildList.push(
|
|
47
|
+
h("li", {
|
|
48
|
+
class: ["d-li-item", ...child.classList],
|
|
49
|
+
innerHTML: child.content
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
hObject = h(
|
|
54
|
+
"ol",
|
|
55
|
+
{
|
|
56
|
+
class: ["d-ol-block", ...olBlockData.classList]
|
|
57
|
+
},
|
|
58
|
+
olChildList
|
|
59
|
+
);
|
|
60
|
+
break;
|
|
61
|
+
case "ul":
|
|
62
|
+
const ulBlockData = row;
|
|
63
|
+
const ulChildList = [];
|
|
64
|
+
ulBlockData.childList.forEach((child) => {
|
|
65
|
+
ulChildList.push(
|
|
66
|
+
h("li", {
|
|
67
|
+
class: ["d-li-item", ...child.classList],
|
|
68
|
+
innerHTML: child.content
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
hObject = h(
|
|
73
|
+
"ul",
|
|
74
|
+
{
|
|
75
|
+
class: ["d-ul-block", ...ulBlockData.classList]
|
|
76
|
+
},
|
|
77
|
+
ulChildList
|
|
78
|
+
);
|
|
79
|
+
break;
|
|
80
|
+
default:
|
|
81
|
+
const defaultData = row;
|
|
82
|
+
hObject = h("div", {
|
|
83
|
+
class: ["d-other-block"],
|
|
84
|
+
innerHTML: defaultData.innerHTML
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
childList.push(hObject);
|
|
88
|
+
});
|
|
89
|
+
return childList;
|
|
90
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export function generateId() {
|
|
2
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
3
|
+
let str = "";
|
|
4
|
+
for (let i = 0; i < 20; i++) {
|
|
5
|
+
str += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
6
|
+
}
|
|
7
|
+
return str;
|
|
8
|
+
}
|
|
9
|
+
function createTextBlock(data) {
|
|
10
|
+
if (data) {
|
|
11
|
+
return {
|
|
12
|
+
type: "text",
|
|
13
|
+
id: generateId(),
|
|
14
|
+
classList: data.classList,
|
|
15
|
+
content: data.content
|
|
16
|
+
};
|
|
17
|
+
} else {
|
|
18
|
+
return {
|
|
19
|
+
type: "text",
|
|
20
|
+
id: generateId(),
|
|
21
|
+
classList: [],
|
|
22
|
+
content: ""
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function createImageBlock(data) {
|
|
27
|
+
const totalSize = data.width + data.height;
|
|
28
|
+
const w = Math.round(100 / totalSize * data.width);
|
|
29
|
+
const h = Math.round(100 / totalSize * data.height);
|
|
30
|
+
const contrast = w - h;
|
|
31
|
+
let classList = ["d-align-center"];
|
|
32
|
+
switch (true) {
|
|
33
|
+
case contrast < -40:
|
|
34
|
+
classList.push("--5");
|
|
35
|
+
break;
|
|
36
|
+
case contrast < -15:
|
|
37
|
+
classList.push("--10");
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
classList.push("--20");
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
type: "image",
|
|
44
|
+
id: generateId(),
|
|
45
|
+
classList,
|
|
46
|
+
src: data.src,
|
|
47
|
+
width: data.width,
|
|
48
|
+
height: data.height,
|
|
49
|
+
caption: data.caption
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createListBlock(type = "ul") {
|
|
53
|
+
return {
|
|
54
|
+
type,
|
|
55
|
+
id: generateId(),
|
|
56
|
+
classList: [],
|
|
57
|
+
childList: [
|
|
58
|
+
{
|
|
59
|
+
classList: [],
|
|
60
|
+
content: ""
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function createBlock(name, value) {
|
|
66
|
+
let blockData;
|
|
67
|
+
switch (name) {
|
|
68
|
+
case "ul":
|
|
69
|
+
blockData = createListBlock();
|
|
70
|
+
break;
|
|
71
|
+
case "ol":
|
|
72
|
+
blockData = createListBlock("ol");
|
|
73
|
+
break;
|
|
74
|
+
case "image":
|
|
75
|
+
blockData = createImageBlock(value);
|
|
76
|
+
break;
|
|
77
|
+
default:
|
|
78
|
+
blockData = createTextBlock(value);
|
|
79
|
+
}
|
|
80
|
+
return blockData;
|
|
81
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function createBlock(name: string, value?: object): allBlock;
|
|
1
|
+
export * from "./global";
|
|
3
2
|
export * from "./keyboard";
|
|
4
3
|
export * from "./cursor";
|
|
5
4
|
export * from "./style";
|
|
6
5
|
export * from "./element";
|
|
6
|
+
export * from "./converter";
|
|
7
|
+
export * from "./ui";
|
|
@@ -1,83 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
3
|
-
let str = "";
|
|
4
|
-
for (let i = 0; i < 20; i++) {
|
|
5
|
-
str += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
6
|
-
}
|
|
7
|
-
return str;
|
|
8
|
-
}
|
|
9
|
-
function createTextBlock(data) {
|
|
10
|
-
if (data) {
|
|
11
|
-
return {
|
|
12
|
-
type: "text",
|
|
13
|
-
id: generateId(),
|
|
14
|
-
classList: data.classList,
|
|
15
|
-
content: data.content
|
|
16
|
-
};
|
|
17
|
-
} else {
|
|
18
|
-
return {
|
|
19
|
-
type: "text",
|
|
20
|
-
id: generateId(),
|
|
21
|
-
classList: [],
|
|
22
|
-
content: ""
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function createImageBlock(data) {
|
|
27
|
-
const totalSize = data.width + data.height;
|
|
28
|
-
const w = Math.round(100 / totalSize * data.width);
|
|
29
|
-
const h = Math.round(100 / totalSize * data.height);
|
|
30
|
-
const contrast = w - h;
|
|
31
|
-
let classList = ["d-align-center"];
|
|
32
|
-
switch (true) {
|
|
33
|
-
case contrast < -40:
|
|
34
|
-
classList.push("--5");
|
|
35
|
-
break;
|
|
36
|
-
case contrast < -15:
|
|
37
|
-
classList.push("--10");
|
|
38
|
-
break;
|
|
39
|
-
default:
|
|
40
|
-
classList.push("--20");
|
|
41
|
-
}
|
|
42
|
-
return {
|
|
43
|
-
type: "image",
|
|
44
|
-
id: generateId(),
|
|
45
|
-
classList,
|
|
46
|
-
src: data.src,
|
|
47
|
-
width: data.width,
|
|
48
|
-
height: data.height,
|
|
49
|
-
webp: data.webp,
|
|
50
|
-
caption: data.caption
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function createlistBlock(type = "ul") {
|
|
54
|
-
return {
|
|
55
|
-
type,
|
|
56
|
-
id: generateId(),
|
|
57
|
-
classList: [],
|
|
58
|
-
childList: [
|
|
59
|
-
{
|
|
60
|
-
classList: [],
|
|
61
|
-
content: ""
|
|
62
|
-
}
|
|
63
|
-
]
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
export function createBlock(name, value) {
|
|
67
|
-
let blockData;
|
|
68
|
-
switch (name) {
|
|
69
|
-
case "ol":
|
|
70
|
-
blockData = createlistBlock("ol");
|
|
71
|
-
break;
|
|
72
|
-
case "image":
|
|
73
|
-
blockData = createImageBlock(value);
|
|
74
|
-
break;
|
|
75
|
-
default:
|
|
76
|
-
blockData = createTextBlock(value);
|
|
77
|
-
}
|
|
78
|
-
return blockData;
|
|
79
|
-
}
|
|
1
|
+
export * from "./global.mjs";
|
|
80
2
|
export * from "./keyboard.mjs";
|
|
81
3
|
export * from "./cursor.mjs";
|
|
82
4
|
export * from "./style.mjs";
|
|
83
5
|
export * from "./element.mjs";
|
|
6
|
+
export * from "./converter.mjs";
|
|
7
|
+
export * from "./ui.mjs";
|
|
@@ -2,15 +2,15 @@ import { getCursor, setCursor } from "./cursor.mjs";
|
|
|
2
2
|
import { findEditableElement, findChildNumber, findLiElement } from "./element.mjs";
|
|
3
3
|
import { getTagName } from "./style.mjs";
|
|
4
4
|
let enterCount = 0;
|
|
5
|
-
function enterEvent(type, event, action) {
|
|
5
|
+
function enterEvent(type, event, action, update) {
|
|
6
6
|
if (event.code === "Enter") {
|
|
7
7
|
event.preventDefault();
|
|
8
8
|
const useShift = event.shiftKey;
|
|
9
9
|
switch (type) {
|
|
10
|
-
case "
|
|
10
|
+
case "list":
|
|
11
11
|
if (useShift === false) {
|
|
12
12
|
if (enterCount == 0) {
|
|
13
|
-
listEnterEvent(
|
|
13
|
+
listEnterEvent(action, update);
|
|
14
14
|
}
|
|
15
15
|
} else {
|
|
16
16
|
addBrEvent();
|
|
@@ -27,7 +27,7 @@ function enterEvent(type, event, action) {
|
|
|
27
27
|
default:
|
|
28
28
|
if (useShift === false) {
|
|
29
29
|
if (enterCount == 0) {
|
|
30
|
-
textEnterEvent(
|
|
30
|
+
textEnterEvent(action);
|
|
31
31
|
}
|
|
32
32
|
} else {
|
|
33
33
|
addBrEvent();
|
|
@@ -39,7 +39,7 @@ function enterEvent(type, event, action) {
|
|
|
39
39
|
}, 150);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
function listEnterEvent(
|
|
42
|
+
function listEnterEvent(action, update) {
|
|
43
43
|
const cursorData = getCursor();
|
|
44
44
|
if (cursorData.startNode) {
|
|
45
45
|
const editableElement = findEditableElement(cursorData.startNode);
|
|
@@ -74,9 +74,16 @@ function listEnterEvent(event, action) {
|
|
|
74
74
|
endOffset = cursorData.endOffset;
|
|
75
75
|
}
|
|
76
76
|
if (editableElement.childNodes.length === 0 || endChildIdx === editableElement.childNodes.length - 1 && editableElement.childNodes[endChildIdx].textContent.length === endOffset) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
if (editableElement.childNodes.length === 0) {
|
|
78
|
+
editableElement.remove();
|
|
79
|
+
action("addBlock", {
|
|
80
|
+
name: "text"
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
editableElement.insertAdjacentHTML("afterend", `<li class="d-li-item" contenteditable></li>`);
|
|
84
|
+
setCursor(editableElement.nextSibling, 0);
|
|
85
|
+
update();
|
|
86
|
+
}
|
|
80
87
|
} else {
|
|
81
88
|
editableElement.childNodes.forEach((child, count) => {
|
|
82
89
|
const text = child.textContent;
|
|
@@ -125,14 +132,15 @@ function listEnterEvent(event, action) {
|
|
|
125
132
|
}
|
|
126
133
|
});
|
|
127
134
|
editableElement.innerHTML = preHTMLStructor;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
editableElement.insertAdjacentHTML("afterend", `<li class="d-li-item ${editableElementClassList.join(" ")}">${nextHTMLStructor}</li>`);
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
setCursor(editableElement.nextSibling.childNodes[0], 0);
|
|
138
|
+
update();
|
|
139
|
+
}, 100);
|
|
132
140
|
}
|
|
133
141
|
}
|
|
134
142
|
}
|
|
135
|
-
function textEnterEvent(
|
|
143
|
+
function textEnterEvent(action) {
|
|
136
144
|
const cursorData = getCursor();
|
|
137
145
|
if (cursorData.startNode) {
|
|
138
146
|
const editableElement = findEditableElement(cursorData.startNode);
|
|
@@ -245,7 +253,7 @@ function backspaceEvent(type, event, action, update) {
|
|
|
245
253
|
}
|
|
246
254
|
}
|
|
247
255
|
export function keyboardEvent(type, event, action, update) {
|
|
248
|
-
enterEvent(type, event, action);
|
|
256
|
+
enterEvent(type, event, action, update);
|
|
249
257
|
backspaceEvent(type, event, action, update);
|
|
250
258
|
}
|
|
251
259
|
export function getClipboardData(data) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { EditorContentType } from "../../../types/index";
|
|
2
|
+
export declare function createLeftMenu(modelValue: EditorContentType, top: number, isActive: boolean): import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
3
|
+
[key: string]: any;
|
|
4
|
+
}>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { h } from "vue";
|
|
2
|
+
export function createLeftMenu(modelValue, top, isActive) {
|
|
3
|
+
console.log(modelValue);
|
|
4
|
+
return h("div", {
|
|
5
|
+
class: ["d-left-menu", { "--active": isActive }],
|
|
6
|
+
onClick: () => {
|
|
7
|
+
test();
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
function test() {
|
|
11
|
+
modelValue = [{ type: "text", classList: [], content: "123" }];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import "../../core/style/common.css";
|
|
2
|
+
declare const _default: import("vue").DefineComponent<Readonly<{
|
|
3
|
+
option?: any;
|
|
4
|
+
modelValue?: any;
|
|
5
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}>, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "update:modelValue"[], "update:modelValue", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<Readonly<{
|
|
8
|
+
option?: any;
|
|
9
|
+
modelValue?: any;
|
|
10
|
+
}>>> & {
|
|
11
|
+
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
readonly option?: any;
|
|
14
|
+
readonly modelValue?: any;
|
|
15
|
+
}, {}>;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { h, defineComponent, ref, unref, watch } from "vue";
|
|
2
|
+
import { createLeftMenu } from "../../core/utils/index.mjs";
|
|
3
|
+
import "../../core/style/common.css";
|
|
4
|
+
export default defineComponent({
|
|
5
|
+
name: "DragonEditor",
|
|
6
|
+
props: ["modelValue", "option"],
|
|
7
|
+
emits: ["update:modelValue"],
|
|
8
|
+
setup: (props, ctx) => {
|
|
9
|
+
const modelValue = ref([]);
|
|
10
|
+
const option = ref({
|
|
11
|
+
blockMenu: ["text", "ol", "ul"]
|
|
12
|
+
// TODO : 다른 블럭 만들기 "table", "quotation"
|
|
13
|
+
});
|
|
14
|
+
if (props.modelValue) {
|
|
15
|
+
modelValue.value = props.modelValue;
|
|
16
|
+
}
|
|
17
|
+
if (props.option !== void 0) {
|
|
18
|
+
option.value = Object.assign(option.value, props.option);
|
|
19
|
+
}
|
|
20
|
+
const activeLeftMenu = ref(false);
|
|
21
|
+
const leftMenuPosition = ref(0);
|
|
22
|
+
const leftMenuStructure = createLeftMenu(modelValue.value, leftMenuPosition.value, activeLeftMenu.value);
|
|
23
|
+
const customStyleMenu = ref([]);
|
|
24
|
+
if (option.value.customStyleMenu) {
|
|
25
|
+
customStyleMenu.value = unref(option.value.customStyleMenu);
|
|
26
|
+
}
|
|
27
|
+
init(modelValue.value);
|
|
28
|
+
function init(targetData) {
|
|
29
|
+
if (targetData && Array.isArray(targetData)) {
|
|
30
|
+
if (targetData.length == 0) {
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error("[DragonEditor] : You must set 'v-model' attribute and 'v-mode' type is must be Array(EditorContentType).");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function addImageBlock({ src, width, height, caption = "" }) {
|
|
37
|
+
console.log("addImage");
|
|
38
|
+
}
|
|
39
|
+
watch(
|
|
40
|
+
() => props.modelValue,
|
|
41
|
+
(newData, oldData) => {
|
|
42
|
+
init(newData);
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
ctx.expose({ addImageBlock });
|
|
46
|
+
return () => {
|
|
47
|
+
return h(
|
|
48
|
+
"div",
|
|
49
|
+
{
|
|
50
|
+
class: ["dragon-editor"],
|
|
51
|
+
onPaste: () => {
|
|
52
|
+
console.log("paste");
|
|
53
|
+
},
|
|
54
|
+
onCopy: () => {
|
|
55
|
+
console.log("copy");
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
[leftMenuStructure]
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
});
|