dragon-editor 2.1.2 → 3.0.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 +82 -96
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -12
- package/dist/runtime/components/DragonEditor.vue +457 -0
- package/dist/runtime/components/DragonEditorViewer.vue +228 -0
- package/dist/runtime/plugin.d.ts +2 -0
- package/dist/runtime/plugin.mjs +10 -0
- package/dist/runtime/scss/editor.css +261 -0
- package/dist/runtime/scss/viewer.css +198 -0
- package/dist/runtime/store.d.ts +7 -0
- package/dist/runtime/store.mjs +27 -0
- package/dist/runtime/type.d.ts +24 -0
- package/dist/runtime/utils/block.d.ts +9 -0
- package/dist/runtime/utils/block.mjs +70 -0
- package/dist/runtime/utils/convertor.d.ts +2 -0
- package/dist/runtime/utils/convertor.mjs +86 -0
- package/dist/runtime/utils/cursor.d.ts +6 -0
- package/dist/runtime/utils/cursor.mjs +132 -0
- package/dist/runtime/utils/element.d.ts +3 -0
- package/dist/runtime/utils/element.mjs +39 -0
- package/dist/runtime/utils/keyboardEvent.d.ts +10 -0
- package/dist/runtime/utils/keyboardEvent.mjs +781 -0
- package/dist/runtime/utils/style.d.ts +1 -0
- package/dist/runtime/utils/style.mjs +330 -0
- package/dist/runtime/utils/ui.d.ts +1 -0
- package/dist/runtime/utils/ui.mjs +35 -0
- package/package.json +10 -4
- package/README_en.md +0 -30
- package/dist/runtime/core/components/SvgIcon.d.ts +0 -10
- package/dist/runtime/core/components/SvgIcon.mjs +0 -98
- package/dist/runtime/core/components/editor/ImageBlock.vue +0 -175
- package/dist/runtime/core/components/editor/OlBlock.vue +0 -162
- package/dist/runtime/core/components/editor/TextBlock.vue +0 -172
- package/dist/runtime/core/components/editor/UlBlock.vue +0 -162
- package/dist/runtime/core/style/common.css +0 -496
- package/dist/runtime/core/style/viewer.css +0 -205
- package/dist/runtime/core/utils/converter.d.ts +0 -2
- package/dist/runtime/core/utils/converter.mjs +0 -90
- package/dist/runtime/core/utils/cursor.d.ts +0 -4
- package/dist/runtime/core/utils/cursor.mjs +0 -84
- package/dist/runtime/core/utils/element.d.ts +0 -3
- package/dist/runtime/core/utils/element.mjs +0 -40
- package/dist/runtime/core/utils/global.d.ts +0 -3
- package/dist/runtime/core/utils/global.mjs +0 -81
- package/dist/runtime/core/utils/index.d.ts +0 -7
- package/dist/runtime/core/utils/index.mjs +0 -7
- package/dist/runtime/core/utils/keyboard.d.ts +0 -6
- package/dist/runtime/core/utils/keyboard.mjs +0 -565
- package/dist/runtime/core/utils/style.d.ts +0 -6
- package/dist/runtime/core/utils/style.mjs +0 -374
- package/dist/runtime/core/utils/ui.d.ts +0 -4
- package/dist/runtime/core/utils/ui.mjs +0 -13
- package/dist/runtime/shared/components/DragonEditor.vue +0 -695
- package/dist/runtime/shared/components/DragonEditorComment.vue +0 -172
- package/dist/runtime/shared/components/DragonEditorNew.d.ts +0 -16
- package/dist/runtime/shared/components/DragonEditorNew.mjs +0 -62
- package/dist/runtime/shared/components/DragonEditorViewer.d.ts +0 -14
- package/dist/runtime/shared/components/DragonEditorViewer.mjs +0 -15
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="d-image-block" :class="data.classList" ref="$block" @mousemove="resizeMouseEvent" @touchmove="resizeTouchEvent" @mouseup="doneResizeStatus" @touchend="doneResizeStatus">
|
|
3
|
-
<div class="d-image-area">
|
|
4
|
-
<button class="d-btn-size-left" @mousedown="startResizeEvent" @touchstart="startResizeEvent"></button>
|
|
5
|
-
<button class="d-btn-size-right" @mousedown="startResizeEvent" @touchstart="startResizeEvent"></button>
|
|
6
|
-
|
|
7
|
-
<template v-if="data.webp">
|
|
8
|
-
<picture>
|
|
9
|
-
<source :srcset="data.src.replace(/\.(jpg|png|jpeg|apng|gif)/g, '.webp')" />
|
|
10
|
-
<img class="d-img" :src="data.src" :width="data.width" :height="data.height" :alt="data.caption" loading="lazy" />
|
|
11
|
-
</picture>
|
|
12
|
-
</template>
|
|
13
|
-
|
|
14
|
-
<template v-else>
|
|
15
|
-
<img class="d-img" :src="data.src" :width="data.width" :height="data.height" :alt="data.caption" loading="lazy" />
|
|
16
|
-
</template>
|
|
17
|
-
</div>
|
|
18
|
-
<p class="d-caption" v-html="data.caption" @keydown="textKeyboardEvent" ref="$caption" contenteditable></p>
|
|
19
|
-
</div>
|
|
20
|
-
</template>
|
|
21
|
-
|
|
22
|
-
<script setup lang="ts">
|
|
23
|
-
// @ts-ignore
|
|
24
|
-
import { ref, unref } from "#imports";
|
|
25
|
-
import { keyboardEvent, pasteText, styleSettings } from "../../utils/index";
|
|
26
|
-
import { ImageBlock, cursorSelection, styleFunctionArgument } from "../../../../types/index";
|
|
27
|
-
|
|
28
|
-
const $block = ref();
|
|
29
|
-
const $caption = ref();
|
|
30
|
-
const data = ref<ImageBlock>({
|
|
31
|
-
type: "",
|
|
32
|
-
id: "",
|
|
33
|
-
classList: [],
|
|
34
|
-
src: "",
|
|
35
|
-
width: 0,
|
|
36
|
-
height: 0,
|
|
37
|
-
webp: false,
|
|
38
|
-
caption: "",
|
|
39
|
-
});
|
|
40
|
-
const props = defineProps<{ modelValue: ImageBlock; cursorData: cursorSelection }>();
|
|
41
|
-
const emit = defineEmits<{
|
|
42
|
-
(e: "update:modelValue", modelValue: ImageBlock): void;
|
|
43
|
-
(e: "addBlock", name: string): void;
|
|
44
|
-
(e: "deleteBlockLocal", index?: number): void;
|
|
45
|
-
}>();
|
|
46
|
-
|
|
47
|
-
data.value = unref(props.modelValue) as ImageBlock;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* 내부 상수
|
|
51
|
-
*/
|
|
52
|
-
let activeResize = false;
|
|
53
|
-
let startX = 0;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 내부 이벤트
|
|
57
|
-
*/
|
|
58
|
-
// 키보드 이벤트 할당
|
|
59
|
-
function textKeyboardEvent(e: KeyboardEvent) {
|
|
60
|
-
keyboardEvent("image", e, emit, updateBlockData);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 리사이즈 시작
|
|
64
|
-
const startResizeStatus = (value) => {
|
|
65
|
-
activeResize = true;
|
|
66
|
-
startX = value;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
function startResizeEvent() {
|
|
70
|
-
// startResizeStatus(e.clientX);
|
|
71
|
-
activeResize = true;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function activeResizeTouchEvent(e: TouchEvent) {
|
|
75
|
-
// startResizeStatus(Math.floor(e.touches[0].clientX));
|
|
76
|
-
activeResize = true;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// 리사이즈 이벤트
|
|
80
|
-
const resizeEvent = (value) => {
|
|
81
|
-
if (activeResize) {
|
|
82
|
-
const blockRect = $block.value.getBoundingClientRect();
|
|
83
|
-
const bodyRect = document.body.getBoundingClientRect();
|
|
84
|
-
const blockLeft = blockRect.left - bodyRect.left;
|
|
85
|
-
const centerPoint: number = blockRect.width / 2 + blockLeft;
|
|
86
|
-
const blockRight = window.innerWidth - (centerPoint + blockRect.width / 2);
|
|
87
|
-
let percent: number = 0;
|
|
88
|
-
|
|
89
|
-
// console.log(value);
|
|
90
|
-
|
|
91
|
-
if (centerPoint > value) {
|
|
92
|
-
// 왼쪽
|
|
93
|
-
const leftPercent = (100 / (centerPoint - blockLeft)) * (value - blockLeft);
|
|
94
|
-
|
|
95
|
-
percent = Math.floor(100 - leftPercent) + 2;
|
|
96
|
-
} else {
|
|
97
|
-
// 오른쪽
|
|
98
|
-
const right = window.innerWidth - (centerPoint + blockRight);
|
|
99
|
-
const valueData = value - centerPoint;
|
|
100
|
-
const rightPercent = (100 / right) * valueData;
|
|
101
|
-
|
|
102
|
-
percent = Math.floor(rightPercent);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
percent = percent - (percent % 5);
|
|
106
|
-
percent = percent / 5;
|
|
107
|
-
|
|
108
|
-
const classList = data.value.classList.filter((item) => /--\d{1,2}/g.test(item) === false);
|
|
109
|
-
classList.push(`--${percent}`);
|
|
110
|
-
data.value.classList = classList;
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
function resizeMouseEvent(e: MouseEvent) {
|
|
115
|
-
resizeEvent(e.clientX);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function resizeTouchEvent(e: TouchEvent) {
|
|
119
|
-
resizeEvent(Math.floor(e.touches[0].clientX));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// 리사이즈 종료
|
|
123
|
-
function doneResizeStatus() {
|
|
124
|
-
activeResize = false;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* 외부용 함수
|
|
129
|
-
*/
|
|
130
|
-
function focus() {
|
|
131
|
-
$caption.value.focus();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 블럭 위치 주기
|
|
135
|
-
function getBoundingClientRect() {
|
|
136
|
-
return $block.value.parentNode.getBoundingClientRect();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 타입 전달
|
|
140
|
-
function getType() {
|
|
141
|
-
return data.value.type;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// 붙여넣기 이벤트
|
|
145
|
-
function pasteEvent(text: string) {
|
|
146
|
-
pasteText("text", text);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// 데이터 정규화 및 검수
|
|
150
|
-
function updateBlockData() {
|
|
151
|
-
emit("update:modelValue", data.value);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// 텍스트 스타일 지정
|
|
155
|
-
function setStyles({ type, url }: styleFunctionArgument) {
|
|
156
|
-
data.value = styleSettings({
|
|
157
|
-
kind: type,
|
|
158
|
-
blockData: data.value,
|
|
159
|
-
$target: $block.value,
|
|
160
|
-
cursorData: props.cursorData,
|
|
161
|
-
});
|
|
162
|
-
setTimeout(() => {
|
|
163
|
-
updateBlockData();
|
|
164
|
-
}, 250);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
defineExpose({
|
|
168
|
-
updateBlockData,
|
|
169
|
-
getType,
|
|
170
|
-
focus,
|
|
171
|
-
pasteEvent,
|
|
172
|
-
setStyles,
|
|
173
|
-
getBoundingClientRect,
|
|
174
|
-
});
|
|
175
|
-
</script>
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<ol class="d-ol-block" @keydown="textKeyboardEvent" ref="$ol" :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
|
-
</ol>
|
|
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 $ol = 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 = $ol.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 = $ol.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 = $ol.value.querySelectorAll("li");
|
|
113
|
-
setCursor(afterChildList[childIdx], cursorData.startOffset as number);
|
|
114
|
-
}, 100);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// 포커스
|
|
120
|
-
function focus() {
|
|
121
|
-
const childList = $ol.value.querySelectorAll(".d-li-item");
|
|
122
|
-
setCursor(childList[itemIdx.value], 0);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 블럭 위치 주기
|
|
126
|
-
function getBoundingClientRect() {
|
|
127
|
-
return $ol.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>
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<p class="d-text-block" :class="data.classList" contenteditable v-html="data.content" @keydown="textKeyboardEvent" ref="$block"></p>
|
|
3
|
-
</template>
|
|
4
|
-
|
|
5
|
-
<script setup lang="ts">
|
|
6
|
-
// @ts-ignore
|
|
7
|
-
import { ref, unref } from "#imports";
|
|
8
|
-
import { keyboardEvent, setCursor, pasteText, styleSettings, getArrangementCursorData } from "../../utils/index";
|
|
9
|
-
import { TextBlock, styleFunctionArgument, cursorSelection } from "../../../../types/index";
|
|
10
|
-
|
|
11
|
-
const $block = ref();
|
|
12
|
-
const data = ref<TextBlock>({
|
|
13
|
-
type: "",
|
|
14
|
-
id: "",
|
|
15
|
-
classList: [],
|
|
16
|
-
content: "",
|
|
17
|
-
});
|
|
18
|
-
const props = defineProps<{ modelValue: TextBlock; cursorData: cursorSelection }>();
|
|
19
|
-
const emit = defineEmits<{
|
|
20
|
-
(e: "update:modelValue", modelValue: TextBlock): void;
|
|
21
|
-
(e: "addBlock", {}: { name: string; value: object }): void;
|
|
22
|
-
(e: "deleteBlockLocal", index?: number): void;
|
|
23
|
-
}>();
|
|
24
|
-
|
|
25
|
-
data.value = unref(props.modelValue) as TextBlock;
|
|
26
|
-
|
|
27
|
-
// 키보드 이벤트 할당
|
|
28
|
-
function textKeyboardEvent(e: KeyboardEvent) {
|
|
29
|
-
keyboardEvent("text", e, emit, updateBlockData);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 외부용 함수
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
// 데이터 정규화 및 검수
|
|
37
|
-
function updateBlockData() {
|
|
38
|
-
const blockClassList = [...$block.value.classList];
|
|
39
|
-
blockClassList.splice(0, 1);
|
|
40
|
-
const pushList = blockClassList.filter((item) => data.value.classList.indexOf(item) === -1);
|
|
41
|
-
|
|
42
|
-
data.value.classList = data.value.classList.concat(pushList);
|
|
43
|
-
|
|
44
|
-
// 클레스 검수
|
|
45
|
-
const checkClassIdx = data.value.classList.indexOf("d-text-block");
|
|
46
|
-
if (checkClassIdx > -1) {
|
|
47
|
-
data.value.classList.splice(checkClassIdx, 1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 커서위치 재지정
|
|
51
|
-
if ($block.value.innerHTML.length > 0) {
|
|
52
|
-
if (props.cursorData.endOffset !== null) {
|
|
53
|
-
const cursorData = getArrangementCursorData(props.cursorData);
|
|
54
|
-
|
|
55
|
-
data.value.content = $block.value.innerHTML;
|
|
56
|
-
emit("update:modelValue", data.value);
|
|
57
|
-
|
|
58
|
-
setTimeout(() => {
|
|
59
|
-
if ($block.value) {
|
|
60
|
-
setCursor($block.value.childNodes[cursorData.childCount], cursorData.length);
|
|
61
|
-
|
|
62
|
-
// 구조 검수
|
|
63
|
-
$block.value.childNodes.forEach((child: ChildNode) => {
|
|
64
|
-
const $child = child as HTMLElement;
|
|
65
|
-
|
|
66
|
-
if (child.constructor.name !== "Text") {
|
|
67
|
-
// 텍스트가 아닐경우
|
|
68
|
-
if (child.constructor.name !== "HTMLBRElement") {
|
|
69
|
-
// br 태그 유지
|
|
70
|
-
if (child.textContent === "") {
|
|
71
|
-
// 빈 태그 삭제
|
|
72
|
-
child.remove();
|
|
73
|
-
} else if ($child.classList.length === 0) {
|
|
74
|
-
// 클레스 없는 엘리먼트 처리
|
|
75
|
-
$child.insertAdjacentHTML("afterend", $child.innerHTML);
|
|
76
|
-
child.remove();
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
$child.removeAttribute("class");
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}, 100);
|
|
85
|
-
} else {
|
|
86
|
-
if ($block.value) {
|
|
87
|
-
$block.value.childNodes.forEach((child: ChildNode) => {
|
|
88
|
-
const $child = child as HTMLElement;
|
|
89
|
-
|
|
90
|
-
if (child.constructor.name !== "Text") {
|
|
91
|
-
// 텍스트가 아닐경우
|
|
92
|
-
if (child.constructor.name !== "HTMLBRElement") {
|
|
93
|
-
// br 태그 유지
|
|
94
|
-
if (child.textContent === "") {
|
|
95
|
-
// 빈 태그 삭제
|
|
96
|
-
child.remove();
|
|
97
|
-
} else if ($child.classList.length === 0) {
|
|
98
|
-
// 클레스 없는 엘리먼트 처리
|
|
99
|
-
$child.insertAdjacentHTML("afterend", $child.innerHTML);
|
|
100
|
-
child.remove();
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
$child.removeAttribute("class");
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
data.value.content = $block.value.innerHTML;
|
|
109
|
-
emit("update:modelValue", data.value);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
} else {
|
|
113
|
-
emit("update:modelValue", data.value);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 포커스
|
|
118
|
-
function focus(type: string = "first") {
|
|
119
|
-
if (type === "first") {
|
|
120
|
-
if ($block.value.childNodes.length > 0) {
|
|
121
|
-
setTimeout(() => {
|
|
122
|
-
setCursor($block.value.childNodes[0], 0);
|
|
123
|
-
}, 100);
|
|
124
|
-
} else {
|
|
125
|
-
setCursor($block.value, 0);
|
|
126
|
-
}
|
|
127
|
-
} else {
|
|
128
|
-
const childCount = $block.value.childNodes.length;
|
|
129
|
-
const targetChild = $block.value.childNodes[childCount - 1];
|
|
130
|
-
|
|
131
|
-
setCursor(targetChild, 0);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// 블럭 위치 주기
|
|
136
|
-
function getBoundingClientRect() {
|
|
137
|
-
return $block.value.parentNode.getBoundingClientRect();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 타입 전달
|
|
141
|
-
function getType() {
|
|
142
|
-
return data.value.type;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// 붙여넣기 이벤트
|
|
146
|
-
function pasteEvent(text: string) {
|
|
147
|
-
pasteText("text", text);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 텍스트 스타일 지정
|
|
151
|
-
function setStyles({ type, url }: styleFunctionArgument) {
|
|
152
|
-
data.value = styleSettings({
|
|
153
|
-
kind: type,
|
|
154
|
-
blockData: data.value,
|
|
155
|
-
$target: $block.value,
|
|
156
|
-
url: url,
|
|
157
|
-
cursorData: props.cursorData,
|
|
158
|
-
});
|
|
159
|
-
setTimeout(() => {
|
|
160
|
-
updateBlockData();
|
|
161
|
-
}, 250);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
defineExpose({
|
|
165
|
-
updateBlockData,
|
|
166
|
-
focus,
|
|
167
|
-
getType,
|
|
168
|
-
pasteEvent,
|
|
169
|
-
setStyles,
|
|
170
|
-
getBoundingClientRect,
|
|
171
|
-
});
|
|
172
|
-
</script>
|
|
@@ -1,162 +0,0 @@
|
|
|
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>
|