dragon-editor 2.0.0-beta.2 → 2.0.0-beta.2.1.1
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 +27 -17
- package/dist/module.json +1 -1
- package/dist/runtime/core/components/editor/ImageBlock.vue +4 -3
- package/dist/runtime/core/components/editor/OlBlock.vue +135 -0
- package/dist/runtime/core/components/editor/TextBlock.vue +39 -10
- package/dist/runtime/core/style/common.css +60 -0
- package/dist/runtime/core/utils/element.d.ts +2 -1
- package/dist/runtime/core/utils/element.mjs +11 -0
- package/dist/runtime/core/utils/index.mjs +16 -0
- package/dist/runtime/core/utils/keyboard.mjs +236 -1
- package/dist/runtime/core/utils/style.mjs +62 -47
- package/dist/runtime/shared/components/DragonEditor.vue +157 -27
- package/dist/runtime/shared/components/DragonEditorComment.vue +22 -34
- package/dist/runtime/shared/components/DragonEditorViewer.vue +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,35 @@
|
|
|
1
|
-
[
|
|
2
|
-
[
|
|
3
|
-
[
|
|
4
|
-
[
|
|
5
|
-
[
|
|
1
|
+
[stars-src]: https://img.shields.io/github/stars/lovefields/dragonEditor
|
|
2
|
+
[stars-href]: https://github.com/lovefields/dragonEditor/stargazers
|
|
3
|
+
[issues-src]: https://img.shields.io/github/issues/lovefields/dragonEditor
|
|
4
|
+
[issues-href]: https://github.com/lovefields/dragonEditor/issues
|
|
5
|
+
[forks-src]: https://img.shields.io/github/forks/lovefields/dragonEditor
|
|
6
|
+
[forks-href]: https://github.com/lovefields/dragonEditor/network/members
|
|
7
|
+
[language-src]: https://img.shields.io/github/languages/top/lovefields/dragonEditor
|
|
8
|
+
[language-href]: https://github.com/lovefields/dragonEditor/
|
|
9
|
+
[hits-src]: https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Flovefields%2FdragonEditor&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false
|
|
10
|
+
[hits-href]: https://hits.seeyoufarm.com
|
|
11
|
+
[npm-version-src]: https://img.shields.io/npm/v/my-module/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
12
|
+
[npm-version-href]: https://www.npmjs.com/package/dragon-editor
|
|
13
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/my-module.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
14
|
+
[npm-downloads-href]: https://www.npmjs.com/package/dragon-editor
|
|
15
|
+
[license-src]: https://img.shields.io/npm/l/my-module.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
16
|
+
[license-href]: https://www.npmjs.com/package/dragon-editor
|
|
17
|
+
[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js
|
|
18
|
+
[nuxt-href]: https://nuxt.com
|
|
19
|
+
[readme-ko]: https://github.com/lovefields/dragonEditor/blob/main/README.md
|
|
20
|
+
[readme-en]: https://github.com/lovefields/dragonEditor/blob/main/README_en.md
|
|
21
|
+
|
|
22
|
+
[](stars-href)
|
|
23
|
+
[](issues-href)
|
|
24
|
+
[](forks-href)
|
|
25
|
+
[](language-href)
|
|
26
|
+
[](hits-href)
|
|
6
27
|
[![npm version][npm-version-src]][npm-version-href]
|
|
7
28
|
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
8
29
|
[![License][license-src]][license-href]
|
|
9
30
|
[![Nuxt][nuxt-src]][nuxt-href]
|
|
10
31
|
|
|
11
|
-
[KO](
|
|
32
|
+
[KO](readme-ko) / [EN](readme-en)
|
|
12
33
|
|
|
13
34
|
# DragonEditor
|
|
14
35
|
|
|
@@ -102,14 +123,3 @@ const contentData = ref([]); // 에디터로 저장한 데이터
|
|
|
102
123
|
## 문서
|
|
103
124
|
|
|
104
125
|
- [DragonEditor Document](https://lovefields.github.io/dragonEditor-doc/) -->
|
|
105
|
-
|
|
106
|
-
<!-- Badges -->
|
|
107
|
-
|
|
108
|
-
[npm-version-src]: https://img.shields.io/npm/v/my-module/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
109
|
-
[npm-version-href]: https://www.npmjs.com/package/dragon-edito
|
|
110
|
-
[npm-downloads-src]: https://img.shields.io/npm/dm/my-module.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
111
|
-
[npm-downloads-href]: https://www.npmjs.com/package/dragon-edito
|
|
112
|
-
[license-src]: https://img.shields.io/npm/l/my-module.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
113
|
-
[license-href]: https://www.npmjs.com/package/dragon-edito
|
|
114
|
-
[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js
|
|
115
|
-
[nuxt-href]: https://nuxt.com
|
package/dist/module.json
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
<template v-if="data.webp">
|
|
8
8
|
<picture>
|
|
9
|
-
<source :srcset="data.src.replace(/\.(jpg|png)/g, '.webp')"
|
|
10
|
-
<img class="d-img" :src="data.src" :width="data.width" :height="data.height" :alt="data.caption" loading="lazy"
|
|
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
11
|
</picture>
|
|
12
12
|
</template>
|
|
13
13
|
|
|
14
14
|
<template v-else>
|
|
15
|
-
<img class="d-img" :src="data.src" :width="data.width" :height="data.height" :alt="data.caption" loading="lazy"
|
|
15
|
+
<img class="d-img" :src="data.src" :width="data.width" :height="data.height" :alt="data.caption" loading="lazy" />
|
|
16
16
|
</template>
|
|
17
17
|
</div>
|
|
18
18
|
<p class="d-caption" v-html="data.caption" @keydown="textKeyboardEvent" ref="$caption" contenteditable></p>
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
</template>
|
|
21
21
|
|
|
22
22
|
<script setup lang="ts">
|
|
23
|
+
// @ts-ignore
|
|
23
24
|
import { ref, unref } from "#imports";
|
|
24
25
|
import { keyboardEvent, pasteText, styleSettings } from "../../utils/index";
|
|
25
26
|
import { imageBlock, cursorSelection, styleFunctionArgument } from "../../../../types/index";
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ol class="d-ol-block" :class="data.classList" @keydown="textKeyboardEvent">
|
|
3
|
+
<li class="d-li-item" contenteditable ref="$olItem"></li>
|
|
4
|
+
</ol>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import { ref, unref } from "#imports";
|
|
10
|
+
import { cursorSelection, listBlock, styleFunctionArgument } from "../../../../types";
|
|
11
|
+
import { getArrangementCursorData, setCursor, pasteText, styleSettings, keyboardEvent } from "../../utils";
|
|
12
|
+
|
|
13
|
+
const $olItem = ref();
|
|
14
|
+
const data = ref<listBlock>({
|
|
15
|
+
type: "",
|
|
16
|
+
id: "",
|
|
17
|
+
classList: [],
|
|
18
|
+
childList: [],
|
|
19
|
+
});
|
|
20
|
+
const props = defineProps<{ modelValue: listBlock; cursorData: cursorSelection }>();
|
|
21
|
+
const emit = defineEmits<{
|
|
22
|
+
(e: "update:modelValue", modelValue: listBlock): void;
|
|
23
|
+
(e: "addBlock", {}: { name: string; value: object }): void;
|
|
24
|
+
(e: "deleteBlockLocal", index?: number): void;
|
|
25
|
+
}>();
|
|
26
|
+
data.value = unref(props.modelValue) as listBlock;
|
|
27
|
+
|
|
28
|
+
if(data.value.childList.length === 0){
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 키보드 이벤트 할당
|
|
33
|
+
function textKeyboardEvent(e: KeyboardEvent) {
|
|
34
|
+
keyboardEvent("ol", e, emit, updateBlockData);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 외부용 함수
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
// 데이터 정규화 및 검수
|
|
42
|
+
function updateBlockData() {
|
|
43
|
+
// const blockClassList = [...$block.value.classList];
|
|
44
|
+
// blockClassList.splice(0, 1);
|
|
45
|
+
// const pushList = blockClassList.filter((item) => data.value.classList.indexOf(item) === -1);
|
|
46
|
+
// data.value.classList = data.value.classList.concat(pushList);
|
|
47
|
+
// // 클레스 검수
|
|
48
|
+
// const checkClassIdx = data.value.classList.indexOf("d-text-block");
|
|
49
|
+
// if (checkClassIdx > -1) {
|
|
50
|
+
// data.value.classList.splice(checkClassIdx, 1);
|
|
51
|
+
// }
|
|
52
|
+
// // 커서위치 재지정
|
|
53
|
+
// if ($block.value.innerHTML.length > 0) {
|
|
54
|
+
// const cursorData = getArrangementCursorData(props.cursorData);
|
|
55
|
+
// data.value.content = $block.value.innerHTML;
|
|
56
|
+
// emit("update:modelValue", data.value);
|
|
57
|
+
// setTimeout(() => {
|
|
58
|
+
// if ($block.value) {
|
|
59
|
+
// setCursor($block.value.childNodes[cursorData.childCount], cursorData.length);
|
|
60
|
+
// // 구조 검수
|
|
61
|
+
// $block.value.childNodes.forEach((child: ChildNode) => {
|
|
62
|
+
// const $child = child as HTMLElement;
|
|
63
|
+
// if (child.constructor.name !== "Text") {
|
|
64
|
+
// // 텍스트가 아닐경우
|
|
65
|
+
// if (child.constructor.name !== "HTMLBRElement") {
|
|
66
|
+
// // br 태그 유지
|
|
67
|
+
// if (child.textContent === "") {
|
|
68
|
+
// // 빈 태그 삭제
|
|
69
|
+
// child.remove();
|
|
70
|
+
// } else if ($child.classList.length === 0) {
|
|
71
|
+
// // 클레스 없는 엘리먼트 처리
|
|
72
|
+
// $child.insertAdjacentHTML("afterend", $child.innerHTML);
|
|
73
|
+
// child.remove();
|
|
74
|
+
// }
|
|
75
|
+
// } else {
|
|
76
|
+
// $child.removeAttribute("class");
|
|
77
|
+
// }
|
|
78
|
+
// }
|
|
79
|
+
// });
|
|
80
|
+
// }
|
|
81
|
+
// }, 100);
|
|
82
|
+
// } else {
|
|
83
|
+
// emit("update:modelValue", data.value);
|
|
84
|
+
// }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 포커스
|
|
88
|
+
function focus(type: string = "first") {
|
|
89
|
+
// if (type === "first") {
|
|
90
|
+
// setCursor($block.value, 0);
|
|
91
|
+
// } else {
|
|
92
|
+
// const childCount = $block.value.childNodes.length;
|
|
93
|
+
// const targetChild = $block.value.childNodes[childCount - 1];
|
|
94
|
+
// setCursor(targetChild, 0);
|
|
95
|
+
// }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 블럭 위치 주기
|
|
99
|
+
function getBoundingClientRect() {
|
|
100
|
+
// return $block.value.parentNode.getBoundingClientRect();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 타입 전달
|
|
104
|
+
function getType() {
|
|
105
|
+
return data.value.type;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 붙여넣기 이벤트
|
|
109
|
+
function pasteEvent(text: string) {
|
|
110
|
+
pasteText("text", text);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 텍스트 스타일 지정
|
|
114
|
+
function setStyles({ type, url }: styleFunctionArgument) {
|
|
115
|
+
data.value = styleSettings({
|
|
116
|
+
kind: type,
|
|
117
|
+
blockData: data.value,
|
|
118
|
+
$target: undefined,
|
|
119
|
+
url: url,
|
|
120
|
+
cursorData: props.cursorData,
|
|
121
|
+
});
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
updateBlockData();
|
|
124
|
+
}, 250);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
defineExpose({
|
|
128
|
+
updateBlockData,
|
|
129
|
+
focus,
|
|
130
|
+
getType,
|
|
131
|
+
pasteEvent,
|
|
132
|
+
setStyles,
|
|
133
|
+
getBoundingClientRect,
|
|
134
|
+
});
|
|
135
|
+
</script>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
5
|
<script setup lang="ts">
|
|
6
|
+
// @ts-ignore
|
|
6
7
|
import { ref, unref } from "#imports";
|
|
7
8
|
import { keyboardEvent, setCursor, pasteText, styleSettings, getArrangementCursorData } from "../../utils/index";
|
|
8
9
|
import { textBlock, styleFunctionArgument, cursorSelection } from "../../../../types/index";
|
|
@@ -48,16 +49,41 @@ function updateBlockData() {
|
|
|
48
49
|
|
|
49
50
|
// 커서위치 재지정
|
|
50
51
|
if ($block.value.innerHTML.length > 0) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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 {
|
|
57
86
|
if ($block.value) {
|
|
58
|
-
setCursor($block.value.childNodes[cursorData.childCount], cursorData.length);
|
|
59
|
-
|
|
60
|
-
// 구조 검수
|
|
61
87
|
$block.value.childNodes.forEach((child: ChildNode) => {
|
|
62
88
|
const $child = child as HTMLElement;
|
|
63
89
|
|
|
@@ -78,8 +104,11 @@ function updateBlockData() {
|
|
|
78
104
|
}
|
|
79
105
|
}
|
|
80
106
|
});
|
|
107
|
+
|
|
108
|
+
data.value.content = $block.value.innerHTML;
|
|
109
|
+
emit("update:modelValue", data.value);
|
|
81
110
|
}
|
|
82
|
-
}
|
|
111
|
+
}
|
|
83
112
|
} else {
|
|
84
113
|
emit("update:modelValue", data.value);
|
|
85
114
|
}
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
position: relative;
|
|
3
3
|
width: 100%;
|
|
4
4
|
padding: 10px 10px 50px;
|
|
5
|
+
background: #fff;
|
|
6
|
+
border-radius: 10px;
|
|
5
7
|
line-height: 1.6;
|
|
8
|
+
box-sizing: border-box;
|
|
6
9
|
}
|
|
7
10
|
.dragon-editor.--comment {
|
|
8
11
|
padding: 10px;
|
|
@@ -109,6 +112,24 @@
|
|
|
109
112
|
.dragon-editor .d-deco-link {
|
|
110
113
|
color: inherit;
|
|
111
114
|
}
|
|
115
|
+
.dragon-editor .d-h1 {
|
|
116
|
+
font-size: 2em;
|
|
117
|
+
}
|
|
118
|
+
.dragon-editor .d-h2 {
|
|
119
|
+
font-size: 1.5em;
|
|
120
|
+
}
|
|
121
|
+
.dragon-editor .d-h3 {
|
|
122
|
+
font-size: 1.17em;
|
|
123
|
+
}
|
|
124
|
+
.dragon-editor .d-h4 {
|
|
125
|
+
font-size: 1em;
|
|
126
|
+
}
|
|
127
|
+
.dragon-editor .d-h5 {
|
|
128
|
+
font-size: 0.83em;
|
|
129
|
+
}
|
|
130
|
+
.dragon-editor .d-h6 {
|
|
131
|
+
font-size: 0.67em;
|
|
132
|
+
}
|
|
112
133
|
.dragon-editor .d-row-block {
|
|
113
134
|
padding-left: 50px;
|
|
114
135
|
border-radius: 5px;
|
|
@@ -130,6 +151,23 @@
|
|
|
130
151
|
content: "Write text";
|
|
131
152
|
color: #ccc;
|
|
132
153
|
}
|
|
154
|
+
.dragon-editor .d-ol-block {
|
|
155
|
+
padding-left: 30px;
|
|
156
|
+
cursor: text;
|
|
157
|
+
list-style: decimal;
|
|
158
|
+
}
|
|
159
|
+
.dragon-editor .d-ol-block .d-li-item {
|
|
160
|
+
list-style: inherit;
|
|
161
|
+
outline: 0;
|
|
162
|
+
}
|
|
163
|
+
.dragon-editor .d-ol-block .d-li-item:empty {
|
|
164
|
+
min-height: 1.6em;
|
|
165
|
+
}
|
|
166
|
+
.dragon-editor .d-ol-block .d-li-item:empty::after {
|
|
167
|
+
display: inline;
|
|
168
|
+
content: "Write text";
|
|
169
|
+
color: #ccc;
|
|
170
|
+
}
|
|
133
171
|
.dragon-editor .d-image-block {
|
|
134
172
|
display: flex;
|
|
135
173
|
flex-direction: column;
|
|
@@ -362,6 +400,7 @@
|
|
|
362
400
|
.dragon-editor .d-style-menu .d-column {
|
|
363
401
|
display: flex;
|
|
364
402
|
column-gap: 2px;
|
|
403
|
+
position: relative;
|
|
365
404
|
padding: 5px;
|
|
366
405
|
border-right: 1px solid #eee;
|
|
367
406
|
box-sizing: border-box;
|
|
@@ -380,6 +419,27 @@
|
|
|
380
419
|
.dragon-editor .d-style-menu .d-btn.--active {
|
|
381
420
|
fill: blue;
|
|
382
421
|
}
|
|
422
|
+
.dragon-editor .d-style-menu .d-btn.--hug {
|
|
423
|
+
width: auto;
|
|
424
|
+
min-width: 60px;
|
|
425
|
+
}
|
|
426
|
+
.dragon-editor .d-style-menu .d-child-list {
|
|
427
|
+
display: none;
|
|
428
|
+
flex-direction: column;
|
|
429
|
+
row-gap: 4px;
|
|
430
|
+
position: absolute;
|
|
431
|
+
top: 100%;
|
|
432
|
+
left: 0;
|
|
433
|
+
right: 0;
|
|
434
|
+
padding: 4px 0;
|
|
435
|
+
background: #fff;
|
|
436
|
+
border: 1px solid #f1f1f1;
|
|
437
|
+
border-top: 0;
|
|
438
|
+
border-radius: 0 0 5px 5px;
|
|
439
|
+
}
|
|
440
|
+
.dragon-editor .d-style-menu .d-child-list.--active {
|
|
441
|
+
display: flex;
|
|
442
|
+
}
|
|
383
443
|
.dragon-editor .d-link-box {
|
|
384
444
|
display: flex;
|
|
385
445
|
justify-content: space-between;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export declare function findEditableElement(node: Node):
|
|
1
|
+
export declare function findEditableElement(node: Node): HTMLElement | null;
|
|
2
|
+
export declare function findLiElement(node: Node): HTMLElement | null;
|
|
2
3
|
export declare function findChildNumber(parent: HTMLElement, child: Node): number;
|
|
@@ -18,6 +18,17 @@ export function findEditableElement(node) {
|
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
export function findLiElement(node) {
|
|
22
|
+
if (node) {
|
|
23
|
+
if (node.constructor.name !== "HTMLLIElement") {
|
|
24
|
+
return findLiElement(node.parentNode);
|
|
25
|
+
} else {
|
|
26
|
+
return node;
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
21
32
|
export function findChildNumber(parent, child) {
|
|
22
33
|
let idx = 0;
|
|
23
34
|
parent.childNodes.forEach((item, count) => {
|
|
@@ -50,9 +50,25 @@ function createImageBlock(data) {
|
|
|
50
50
|
caption: data.caption
|
|
51
51
|
};
|
|
52
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
|
+
}
|
|
53
66
|
export function createBlock(name, value) {
|
|
54
67
|
let blockData;
|
|
55
68
|
switch (name) {
|
|
69
|
+
case "ol":
|
|
70
|
+
blockData = createlistBlock("ol");
|
|
71
|
+
break;
|
|
56
72
|
case "image":
|
|
57
73
|
blockData = createImageBlock(value);
|
|
58
74
|
break;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getCursor, setCursor } from "./cursor.mjs";
|
|
2
|
-
import { findEditableElement, findChildNumber } from "./element.mjs";
|
|
2
|
+
import { findEditableElement, findChildNumber, findLiElement } from "./element.mjs";
|
|
3
3
|
import { getTagName } from "./style.mjs";
|
|
4
4
|
let enterCount = 0;
|
|
5
5
|
function enterEvent(type, event, action) {
|
|
@@ -7,6 +7,15 @@ function enterEvent(type, event, action) {
|
|
|
7
7
|
event.preventDefault();
|
|
8
8
|
const useShift = event.shiftKey;
|
|
9
9
|
switch (type) {
|
|
10
|
+
case "ol":
|
|
11
|
+
if (useShift === false) {
|
|
12
|
+
if (enterCount == 0) {
|
|
13
|
+
listEnterEvent(event, action);
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
addBrEvent();
|
|
17
|
+
}
|
|
18
|
+
break;
|
|
10
19
|
case "image":
|
|
11
20
|
action("addBlock", {
|
|
12
21
|
name: "text"
|
|
@@ -30,6 +39,99 @@ function enterEvent(type, event, action) {
|
|
|
30
39
|
}, 150);
|
|
31
40
|
}
|
|
32
41
|
}
|
|
42
|
+
function listEnterEvent(event, action) {
|
|
43
|
+
const cursorData = getCursor();
|
|
44
|
+
if (cursorData.startNode) {
|
|
45
|
+
const editableElement = findEditableElement(cursorData.startNode);
|
|
46
|
+
const editableElementClassList = [...editableElement.classList].slice(0, 1);
|
|
47
|
+
const startNode = cursorData.startNode;
|
|
48
|
+
const endNode = cursorData.endNode;
|
|
49
|
+
let startChild = startNode;
|
|
50
|
+
let endChild = endNode;
|
|
51
|
+
if (startNode.parentNode !== editableElement) {
|
|
52
|
+
startChild = startNode.parentNode;
|
|
53
|
+
}
|
|
54
|
+
if (endNode.parentNode !== editableElement) {
|
|
55
|
+
endChild = endNode.parentNode;
|
|
56
|
+
}
|
|
57
|
+
const startChildIdx = findChildNumber(editableElement, startChild);
|
|
58
|
+
const endChildIdx = findChildNumber(editableElement, endChild);
|
|
59
|
+
let startIdx = 0;
|
|
60
|
+
let endIdx = 0;
|
|
61
|
+
let startOffset = 0;
|
|
62
|
+
let endOffset = 0;
|
|
63
|
+
let preHTMLStructor = "";
|
|
64
|
+
let nextHTMLStructor = "";
|
|
65
|
+
if (startChildIdx > endChildIdx) {
|
|
66
|
+
startIdx = endChildIdx;
|
|
67
|
+
endIdx = startChildIdx;
|
|
68
|
+
startOffset = cursorData.endOffset;
|
|
69
|
+
endOffset = cursorData.startOffset;
|
|
70
|
+
} else {
|
|
71
|
+
startIdx = startChildIdx;
|
|
72
|
+
endIdx = endChildIdx;
|
|
73
|
+
startOffset = cursorData.startOffset;
|
|
74
|
+
endOffset = cursorData.endOffset;
|
|
75
|
+
}
|
|
76
|
+
if (editableElement.childNodes.length === 0 || endChildIdx === editableElement.childNodes.length - 1 && editableElement.childNodes[endChildIdx].textContent.length === endOffset) {
|
|
77
|
+
action("addBlock", {
|
|
78
|
+
name: "text"
|
|
79
|
+
});
|
|
80
|
+
} else {
|
|
81
|
+
editableElement.childNodes.forEach((child, count) => {
|
|
82
|
+
const text = child.textContent;
|
|
83
|
+
if (count < startChildIdx) {
|
|
84
|
+
if (child.constructor.name === "Text") {
|
|
85
|
+
preHTMLStructor += child.textContent;
|
|
86
|
+
} else {
|
|
87
|
+
preHTMLStructor += child.outerHTML;
|
|
88
|
+
}
|
|
89
|
+
} else if (count > endChildIdx) {
|
|
90
|
+
if (child.constructor.name === "Text") {
|
|
91
|
+
nextHTMLStructor += child.textContent;
|
|
92
|
+
} else {
|
|
93
|
+
nextHTMLStructor += child.outerHTML;
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
if (startChildIdx === endChildIdx) {
|
|
97
|
+
if (child.constructor.name === "Text") {
|
|
98
|
+
preHTMLStructor += text.substring(0, startOffset);
|
|
99
|
+
nextHTMLStructor += text.substring(endOffset, text.length);
|
|
100
|
+
} else {
|
|
101
|
+
const childClassList = [...child.classList];
|
|
102
|
+
const originTag = getTagName(child);
|
|
103
|
+
preHTMLStructor += `<${originTag.name} ${originTag.href ? `href="${originTag.href}" rel="nofollow"` : ""} class="${childClassList.join(" ")}">${text.substring(0, startOffset)}</${originTag.name}>`;
|
|
104
|
+
nextHTMLStructor += `<${originTag.name} ${originTag.href ? `href="${originTag.href}" rel="nofollow"` : ""} class="${childClassList.join(" ")}">${text.substring(endOffset, text.length)}</${originTag.name}>`;
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
if (count === startChildIdx) {
|
|
108
|
+
if (child.constructor.name === "Text") {
|
|
109
|
+
preHTMLStructor += text.substring(0, startOffset);
|
|
110
|
+
} else {
|
|
111
|
+
const childClassList = [...child.classList];
|
|
112
|
+
const originTag = getTagName(child);
|
|
113
|
+
preHTMLStructor += `<${originTag.name} ${originTag.href ? `href="${originTag.href}" rel="nofollow"` : ""} class="${childClassList.join(" ")}">${text.substring(0, startOffset)}</${originTag.name}>`;
|
|
114
|
+
}
|
|
115
|
+
} else if (count === endChildIdx) {
|
|
116
|
+
if (child.constructor.name === "Text") {
|
|
117
|
+
nextHTMLStructor += text.substring(endOffset, text.length);
|
|
118
|
+
} else {
|
|
119
|
+
const childClassList = [...child.classList];
|
|
120
|
+
const originTag = getTagName(child);
|
|
121
|
+
nextHTMLStructor += `<${originTag.name} ${originTag.href ? `href="${originTag.href}" rel="nofollow"` : ""} class="${childClassList.join(" ")}">${text.substring(endOffset, text.length)}</${originTag.name}>`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
editableElement.innerHTML = preHTMLStructor;
|
|
128
|
+
action("addBlock", {
|
|
129
|
+
name: "text",
|
|
130
|
+
value: { classList: editableElementClassList, content: nextHTMLStructor }
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
33
135
|
function textEnterEvent(event, action) {
|
|
34
136
|
const cursorData = getCursor();
|
|
35
137
|
if (cursorData.startNode) {
|
|
@@ -320,3 +422,136 @@ function addBrEvent() {
|
|
|
320
422
|
}
|
|
321
423
|
}
|
|
322
424
|
}
|
|
425
|
+
function addBrEventWithList() {
|
|
426
|
+
const cursorData = getCursor();
|
|
427
|
+
if (cursorData.startNode) {
|
|
428
|
+
let $target = cursorData.startNode;
|
|
429
|
+
const preEditableElement = findLiElement($target);
|
|
430
|
+
if ($target.constructor.name === "Text") {
|
|
431
|
+
$target = $target.parentNode;
|
|
432
|
+
}
|
|
433
|
+
if (preEditableElement !== $target && $target.constructor.name !== "HTMLBRElement") {
|
|
434
|
+
let startNode = cursorData.startNode;
|
|
435
|
+
let endNode = cursorData.endNode;
|
|
436
|
+
let startChild = startNode;
|
|
437
|
+
let endChild = endNode;
|
|
438
|
+
if (startNode.parentNode !== preEditableElement) {
|
|
439
|
+
startChild = startNode.parentNode;
|
|
440
|
+
}
|
|
441
|
+
if (endNode.parentNode !== preEditableElement) {
|
|
442
|
+
endChild = endNode.parentNode;
|
|
443
|
+
}
|
|
444
|
+
const startChildIdx = findChildNumber(preEditableElement, startChild);
|
|
445
|
+
const endChildIdx = findChildNumber(preEditableElement, endChild);
|
|
446
|
+
let startIdx = 0;
|
|
447
|
+
let endIdx = 0;
|
|
448
|
+
let startOffset = 0;
|
|
449
|
+
let endOffset = 0;
|
|
450
|
+
let htmlStructure = "";
|
|
451
|
+
if (startChildIdx > endChildIdx) {
|
|
452
|
+
startIdx = endChildIdx;
|
|
453
|
+
endIdx = startChildIdx;
|
|
454
|
+
startOffset = cursorData.endOffset;
|
|
455
|
+
endOffset = cursorData.startOffset;
|
|
456
|
+
} else {
|
|
457
|
+
startIdx = startChildIdx;
|
|
458
|
+
endIdx = endChildIdx;
|
|
459
|
+
startOffset = cursorData.startOffset;
|
|
460
|
+
endOffset = cursorData.endOffset;
|
|
461
|
+
}
|
|
462
|
+
if (enterCount === 1) {
|
|
463
|
+
const text = startChild.textContent;
|
|
464
|
+
startChild.textContent = text.substring(1, text.length);
|
|
465
|
+
setCursor(startChild, 0);
|
|
466
|
+
} else {
|
|
467
|
+
if (startNode === endNode) {
|
|
468
|
+
const text = startChild.textContent;
|
|
469
|
+
const childClassList = [...startChild.classList];
|
|
470
|
+
const tagData = getTagName(startChild);
|
|
471
|
+
htmlStructure += `<${tagData.name} ${tagData.href ? `href="${tagData.href}" rel="nofollow"` : ""} class="${childClassList.join(" ")}">${text.substring(0, startOffset)}</${tagData.name}>`;
|
|
472
|
+
htmlStructure += `<br>`;
|
|
473
|
+
htmlStructure += `<${tagData.name} ${tagData.href ? `href="${tagData.href}" rel="nofollow"` : ""} class="${childClassList.join(" ")}">${text.substring(endOffset, text.length)}</${tagData.name}>`;
|
|
474
|
+
startChild.insertAdjacentHTML("beforebegin", htmlStructure);
|
|
475
|
+
setCursor(preEditableElement.childNodes[startChildIdx + 2], 0);
|
|
476
|
+
startChild.remove();
|
|
477
|
+
} else {
|
|
478
|
+
preEditableElement.childNodes.forEach((child, count) => {
|
|
479
|
+
const type = child.constructor.name;
|
|
480
|
+
const text = child.textContent;
|
|
481
|
+
if (count > startIdx && count < endIdx) {
|
|
482
|
+
} else if (count === startIdx) {
|
|
483
|
+
if (type === "Text") {
|
|
484
|
+
htmlStructure += text.substring(0, startOffset);
|
|
485
|
+
} else {
|
|
486
|
+
const childClassList = [...child.classList];
|
|
487
|
+
const tagData = getTagName(child);
|
|
488
|
+
htmlStructure += `<${tagData.name} ${tagData.href ? `href="${tagData.href}" rel="nofollow"` : ""} class="${childClassList.join(" ")}">${text.substring(0, startOffset)}</${tagData.name}><br>`;
|
|
489
|
+
}
|
|
490
|
+
} else if (count === endIdx) {
|
|
491
|
+
if (type === "Text") {
|
|
492
|
+
htmlStructure += text.substring(endOffset, text.length);
|
|
493
|
+
} else {
|
|
494
|
+
const childClassList = [...child.classList];
|
|
495
|
+
const tagData = getTagName(child);
|
|
496
|
+
htmlStructure += `<${tagData.name} ${tagData.href ? `href="${tagData.href}" rel="nofollow"` : ""} class="${childClassList.join(" ")}">${text.substring(endOffset, text.length)}</${tagData.name}><br>`;
|
|
497
|
+
}
|
|
498
|
+
} else {
|
|
499
|
+
if (type === "Text") {
|
|
500
|
+
htmlStructure += child.textContent;
|
|
501
|
+
} else {
|
|
502
|
+
htmlStructure += child.outerHTML;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
preEditableElement.innerHTML = htmlStructure;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
const brTag = document.createElement("br");
|
|
511
|
+
const selection = window.getSelection();
|
|
512
|
+
const range = document.createRange();
|
|
513
|
+
console.log("br");
|
|
514
|
+
if (enterCount === 1) {
|
|
515
|
+
const nextCursorData = getCursor();
|
|
516
|
+
const editableElement = findEditableElement(nextCursorData.startNode);
|
|
517
|
+
const childList = editableElement.childNodes;
|
|
518
|
+
const childIdx = findChildNumber(editableElement, nextCursorData.startNode);
|
|
519
|
+
setCursor(childList[childIdx + 2], 0);
|
|
520
|
+
} else {
|
|
521
|
+
selection.deleteFromDocument();
|
|
522
|
+
selection.getRangeAt(0).insertNode(brTag);
|
|
523
|
+
range.setStart(brTag, 0);
|
|
524
|
+
range.collapse(true);
|
|
525
|
+
selection.removeAllRanges();
|
|
526
|
+
selection.addRange(range);
|
|
527
|
+
const nextCursorData = getCursor();
|
|
528
|
+
const editableElement = findEditableElement(nextCursorData.startNode);
|
|
529
|
+
const childList = editableElement.childNodes;
|
|
530
|
+
const childIdx = findChildNumber(editableElement, nextCursorData.startNode);
|
|
531
|
+
let hasText = false;
|
|
532
|
+
childList.forEach((row) => {
|
|
533
|
+
if (row.constructor.name === "Text") {
|
|
534
|
+
hasText = true;
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
if (hasText) {
|
|
538
|
+
if (childList[childIdx + 1].textContent?.length === 0) {
|
|
539
|
+
if (childList[childIdx + 2]) {
|
|
540
|
+
if (childList[childIdx + 2].constructor.name == "HTMLBRElement") {
|
|
541
|
+
setCursor(childList[childIdx + 1], 0);
|
|
542
|
+
} else {
|
|
543
|
+
childList[childIdx].insertAdjacentHTML("beforebegin", "<br>");
|
|
544
|
+
}
|
|
545
|
+
} else {
|
|
546
|
+
childList[childIdx].insertAdjacentHTML("beforebegin", "<br>");
|
|
547
|
+
}
|
|
548
|
+
} else {
|
|
549
|
+
setCursor(childList[childIdx + 1], 0);
|
|
550
|
+
}
|
|
551
|
+
} else {
|
|
552
|
+
childList[childIdx].insertAdjacentHTML("beforebegin", "<br>");
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
@@ -13,6 +13,19 @@ function arrangementAlignClass(originList, className) {
|
|
|
13
13
|
}
|
|
14
14
|
return array;
|
|
15
15
|
}
|
|
16
|
+
const headingClassList = ["d-h1", "d-h2", "d-h3", "d-h4", "d-h5", "d-h6"];
|
|
17
|
+
function arrangementHeadingClass(originList, className) {
|
|
18
|
+
const hasClass = originList.indexOf(className) > -1;
|
|
19
|
+
let array = originList;
|
|
20
|
+
if (hasClass) {
|
|
21
|
+
originList.splice(originList.indexOf(className), 1);
|
|
22
|
+
array = originList;
|
|
23
|
+
} else {
|
|
24
|
+
array = originList.filter((item) => headingClassList.indexOf(item) === -1);
|
|
25
|
+
array.push(className);
|
|
26
|
+
}
|
|
27
|
+
return array;
|
|
28
|
+
}
|
|
16
29
|
function getNextNode($target, node) {
|
|
17
30
|
const childNode = $target.childNodes;
|
|
18
31
|
let idx = -1;
|
|
@@ -252,13 +265,7 @@ function defaultDecorationMake({ originData, $target, className, tagName = "span
|
|
|
252
265
|
}
|
|
253
266
|
return originData;
|
|
254
267
|
}
|
|
255
|
-
export function styleSettings({
|
|
256
|
-
kind,
|
|
257
|
-
blockData,
|
|
258
|
-
$target,
|
|
259
|
-
url,
|
|
260
|
-
cursorData
|
|
261
|
-
}) {
|
|
268
|
+
export function styleSettings({ kind, blockData, $target, url, cursorData }) {
|
|
262
269
|
let rawData = blockData;
|
|
263
270
|
switch (kind) {
|
|
264
271
|
case "alignLeft":
|
|
@@ -271,44 +278,36 @@ export function styleSettings({
|
|
|
271
278
|
rawData.classList = arrangementAlignClass(rawData.classList, "d-align-right");
|
|
272
279
|
break;
|
|
273
280
|
case "decorationBold":
|
|
274
|
-
rawData = defaultDecorationMake(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
);
|
|
281
|
+
rawData = defaultDecorationMake({
|
|
282
|
+
originData: rawData,
|
|
283
|
+
$target,
|
|
284
|
+
className: "d-deco-bold",
|
|
285
|
+
parentCursorData: cursorData
|
|
286
|
+
});
|
|
282
287
|
break;
|
|
283
288
|
case "decorationItalic":
|
|
284
|
-
rawData = defaultDecorationMake(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
);
|
|
289
|
+
rawData = defaultDecorationMake({
|
|
290
|
+
originData: rawData,
|
|
291
|
+
$target,
|
|
292
|
+
className: "d-deco-italic",
|
|
293
|
+
parentCursorData: cursorData
|
|
294
|
+
});
|
|
292
295
|
break;
|
|
293
296
|
case "decorationUnderline":
|
|
294
|
-
rawData = defaultDecorationMake(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
);
|
|
297
|
+
rawData = defaultDecorationMake({
|
|
298
|
+
originData: rawData,
|
|
299
|
+
$target,
|
|
300
|
+
className: "d-deco-underline",
|
|
301
|
+
parentCursorData: cursorData
|
|
302
|
+
});
|
|
302
303
|
break;
|
|
303
304
|
case "decorationStrikethrough":
|
|
304
|
-
rawData = defaultDecorationMake(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
);
|
|
305
|
+
rawData = defaultDecorationMake({
|
|
306
|
+
originData: rawData,
|
|
307
|
+
$target,
|
|
308
|
+
className: "d-deco-through",
|
|
309
|
+
parentCursorData: cursorData
|
|
310
|
+
});
|
|
312
311
|
break;
|
|
313
312
|
case "decorationCode":
|
|
314
313
|
rawData = defaultDecorationMake({
|
|
@@ -329,15 +328,31 @@ export function styleSettings({
|
|
|
329
328
|
parentCursorData: cursorData
|
|
330
329
|
});
|
|
331
330
|
break;
|
|
331
|
+
case "heading-1":
|
|
332
|
+
rawData.classList = arrangementHeadingClass(rawData.classList, "d-h1");
|
|
333
|
+
break;
|
|
334
|
+
case "heading-2":
|
|
335
|
+
rawData.classList = arrangementHeadingClass(rawData.classList, "d-h2");
|
|
336
|
+
break;
|
|
337
|
+
case "heading-3":
|
|
338
|
+
rawData.classList = arrangementHeadingClass(rawData.classList, "d-h3");
|
|
339
|
+
break;
|
|
340
|
+
case "heading-4":
|
|
341
|
+
rawData.classList = arrangementHeadingClass(rawData.classList, "d-h4");
|
|
342
|
+
break;
|
|
343
|
+
case "heading-5":
|
|
344
|
+
rawData.classList = arrangementHeadingClass(rawData.classList, "d-h5");
|
|
345
|
+
break;
|
|
346
|
+
case "heading-6":
|
|
347
|
+
rawData.classList = arrangementHeadingClass(rawData.classList, "d-h6");
|
|
348
|
+
break;
|
|
332
349
|
default:
|
|
333
|
-
rawData = defaultDecorationMake(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
);
|
|
350
|
+
rawData = defaultDecorationMake({
|
|
351
|
+
originData: rawData,
|
|
352
|
+
$target,
|
|
353
|
+
className: kind,
|
|
354
|
+
parentCursorData: cursorData
|
|
355
|
+
});
|
|
341
356
|
}
|
|
342
357
|
return rawData;
|
|
343
358
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="dragon-editor" @paste="pasteEvent" ref="$wrap" @mouseleave="deactiveMenuEvent" @keydown="deactiveMenuEvent"
|
|
2
|
+
<div class="dragon-editor" @paste="pasteEvent" ref="$wrap" @mouseleave="deactiveMenuEvent" @keydown="deactiveMenuEvent">
|
|
3
3
|
<div class="d-left-menu" :class="{ '--active': activeMenu }" :style="{ top: `${leftMenuPosition}px` }">
|
|
4
4
|
<div class="d-add-block">
|
|
5
5
|
<button class="d-btn-menu-pop" @click="toggleBlockAddMenu"></button>
|
|
@@ -17,23 +17,21 @@
|
|
|
17
17
|
<button class="d-btn-block-pop" @click="toggleBlockControlMenu"></button>
|
|
18
18
|
|
|
19
19
|
<div class="d-block-list" :class="{ '--active': activeBlockColtrolMenu }">
|
|
20
|
-
<button class="d-btn-block" @click="moveBlock('up')">
|
|
21
|
-
|
|
22
|
-
</button>
|
|
23
|
-
<button class="d-btn-block" @click="moveBlock('down')">
|
|
24
|
-
<SvgIcon kind="arrowDown" />Down
|
|
25
|
-
</button>
|
|
26
|
-
<button class="d-btn-block">
|
|
27
|
-
<SvgIcon kind="delete" />Delete
|
|
28
|
-
</button>
|
|
20
|
+
<button class="d-btn-block" @click="moveBlock('up')"> <SvgIcon kind="arrowUp" />Up </button>
|
|
21
|
+
<button class="d-btn-block" @click="moveBlock('down')"> <SvgIcon kind="arrowDown" />Down </button>
|
|
22
|
+
<button class="d-btn-block"> <SvgIcon kind="delete" />Delete </button>
|
|
29
23
|
</div>
|
|
30
24
|
</div>
|
|
31
25
|
</div>
|
|
32
26
|
|
|
33
|
-
<div
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
<div
|
|
28
|
+
class="d-link-box"
|
|
29
|
+
:class="{ '--active': activeLinkBox }"
|
|
30
|
+
:style="{
|
|
31
|
+
top: `${linkBoxPosition.top}px`,
|
|
32
|
+
left: `${linkBoxPosition.left}px`,
|
|
33
|
+
}"
|
|
34
|
+
>
|
|
37
35
|
<template v-if="styleButtonList[2][0].active">
|
|
38
36
|
<p class="d-input">{{ linkValue }}</p>
|
|
39
37
|
<button class="d-btn-link" @click="decoLinkControl">
|
|
@@ -51,23 +49,32 @@
|
|
|
51
49
|
<div class="d-style-menu" :class="{ '--active': activeMenu }" :style="{ top: `${styleMenuPosition}px` }">
|
|
52
50
|
<div v-for="(column, count) in styleButtonList" :key="count" class="d-column">
|
|
53
51
|
<template v-for="(item, j) in column">
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
|
|
52
|
+
<template v-if="item.type === 'single'">
|
|
53
|
+
<button v-if="item.target.indexOf(content[activeIdx].type) > -1" class="d-btn" :class="{ '--active': item.active }" @click="item.action">
|
|
54
|
+
<SvgIcon :kind="item.icon" />
|
|
55
|
+
</button>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<template v-else>
|
|
59
|
+
<button v-if="item.target.indexOf(content[activeIdx].type) > -1" class="d-btn --hug" @click="item.action(count, j)">{{ `${item.name} : ${item.value}` }}</button>
|
|
60
|
+
|
|
61
|
+
<div class="d-child-list" :class="{ '--active': item.active }">
|
|
62
|
+
<button class="d-child-btn" v-for="(child, k) in item.childList" :key="k" @click="child.action(count, j)">{{ child.name }}</button>
|
|
63
|
+
</div>
|
|
64
|
+
</template>
|
|
57
65
|
</template>
|
|
58
66
|
</div>
|
|
59
|
-
<div v-if="customStyleMenu.length > 0" class="d-column">
|
|
60
|
-
<!-- customStyleMenu-->
|
|
61
|
-
</div>
|
|
67
|
+
<div v-if="customStyleMenu.length > 0" class="d-column"></div>
|
|
62
68
|
</div>
|
|
63
69
|
|
|
64
|
-
<div class="d-row-block" v-for="(row, count) in content" :key="
|
|
65
|
-
<component ref="$child" v-model="content[count]" :key="
|
|
70
|
+
<div class="d-row-block" v-for="(row, count) in content" :key="`${row.id}-wrap`" @click="activeIdx = count" @mouseenter="activeMenuEvent(count, $event)" @mousemove="activeMenuEvent(count, $event)" @mouseup="activeMenuEvent(count, $event)">
|
|
71
|
+
<component ref="$child" v-model="content[count]" :key="row.id" :is="setComponentKind(row.type)" :cursorData="cursorData" @addBlock="addBlockLocal" @deleteBlockLocal="deleteBlockLocal" />
|
|
66
72
|
</div>
|
|
67
73
|
</div>
|
|
68
74
|
</template>
|
|
69
75
|
|
|
70
76
|
<script setup lang="ts">
|
|
77
|
+
// @ts-ignore
|
|
71
78
|
import { ref, unref, onMounted } from "#imports";
|
|
72
79
|
import { createBlock, getClipboardData, getCursor } from "../../core/utils";
|
|
73
80
|
import type { editorOptions, editorMenu, editorContentType, userCustomMenu, userStyleMenu, cursorSelection } from "../../../types/index";
|
|
@@ -76,6 +83,7 @@ import type { editorOptions, editorMenu, editorContentType, userCustomMenu, user
|
|
|
76
83
|
import SvgIcon from "../../core/components/SvgIcon.vue";
|
|
77
84
|
import textBlock from "../../core/components/editor/TextBlock.vue";
|
|
78
85
|
import imageBlock from "../../core/components/editor/ImageBlock.vue";
|
|
86
|
+
import olBlock from "../../core/components/editor/OlBlock.vue";
|
|
79
87
|
|
|
80
88
|
// 기본 정보
|
|
81
89
|
const props = defineProps<{
|
|
@@ -130,6 +138,7 @@ const cursorData = ref<cursorSelection>({
|
|
|
130
138
|
const styleButtonList = ref([
|
|
131
139
|
[
|
|
132
140
|
{
|
|
141
|
+
type: "single",
|
|
133
142
|
name: "Align Left",
|
|
134
143
|
icon: "alignLeft",
|
|
135
144
|
active: false,
|
|
@@ -139,6 +148,7 @@ const styleButtonList = ref([
|
|
|
139
148
|
},
|
|
140
149
|
},
|
|
141
150
|
{
|
|
151
|
+
type: "single",
|
|
142
152
|
name: "Align Center",
|
|
143
153
|
icon: "alignCenter",
|
|
144
154
|
target: ["text", "image", "table", "ul", "ol"],
|
|
@@ -148,6 +158,7 @@ const styleButtonList = ref([
|
|
|
148
158
|
},
|
|
149
159
|
},
|
|
150
160
|
{
|
|
161
|
+
type: "single",
|
|
151
162
|
name: "Align right",
|
|
152
163
|
icon: "alignRight",
|
|
153
164
|
target: ["text", "image", "table", "ul", "ol"],
|
|
@@ -159,6 +170,7 @@ const styleButtonList = ref([
|
|
|
159
170
|
],
|
|
160
171
|
[
|
|
161
172
|
{
|
|
173
|
+
type: "single",
|
|
162
174
|
name: "Decoration Bold",
|
|
163
175
|
icon: "decorationBold",
|
|
164
176
|
target: ["text", "table", "ul", "ol"],
|
|
@@ -168,6 +180,7 @@ const styleButtonList = ref([
|
|
|
168
180
|
},
|
|
169
181
|
},
|
|
170
182
|
{
|
|
183
|
+
type: "single",
|
|
171
184
|
name: "Decoration Italic",
|
|
172
185
|
icon: "decorationItalic",
|
|
173
186
|
target: ["text", "table", "ul", "ol"],
|
|
@@ -177,6 +190,7 @@ const styleButtonList = ref([
|
|
|
177
190
|
},
|
|
178
191
|
},
|
|
179
192
|
{
|
|
193
|
+
type: "single",
|
|
180
194
|
name: "Decoration Underline",
|
|
181
195
|
icon: "decorationUnderline",
|
|
182
196
|
target: ["text", "table", "ul", "ol"],
|
|
@@ -186,6 +200,7 @@ const styleButtonList = ref([
|
|
|
186
200
|
},
|
|
187
201
|
},
|
|
188
202
|
{
|
|
203
|
+
type: "single",
|
|
189
204
|
name: "Decoration Strikethrough",
|
|
190
205
|
icon: "decorationStrikethrough",
|
|
191
206
|
target: ["text", "table", "ul", "ol"],
|
|
@@ -197,6 +212,7 @@ const styleButtonList = ref([
|
|
|
197
212
|
],
|
|
198
213
|
[
|
|
199
214
|
{
|
|
215
|
+
type: "single",
|
|
200
216
|
name: "Link",
|
|
201
217
|
icon: "link",
|
|
202
218
|
active: false,
|
|
@@ -208,6 +224,7 @@ const styleButtonList = ref([
|
|
|
208
224
|
],
|
|
209
225
|
[
|
|
210
226
|
{
|
|
227
|
+
type: "single",
|
|
211
228
|
name: "Decoration Code",
|
|
212
229
|
icon: "codeBlock",
|
|
213
230
|
target: ["text", "table", "ul", "ol"],
|
|
@@ -217,8 +234,69 @@ const styleButtonList = ref([
|
|
|
217
234
|
},
|
|
218
235
|
},
|
|
219
236
|
],
|
|
237
|
+
[
|
|
238
|
+
{
|
|
239
|
+
type: "list",
|
|
240
|
+
name: "Font Size",
|
|
241
|
+
value: "default",
|
|
242
|
+
target: ["text"],
|
|
243
|
+
active: false,
|
|
244
|
+
action: (count, j) => {
|
|
245
|
+
styleButtonList.value[count][j].active = !styleButtonList.value[count][j].active;
|
|
246
|
+
},
|
|
247
|
+
childList: [
|
|
248
|
+
{
|
|
249
|
+
name: "default",
|
|
250
|
+
action: (count, j) => {
|
|
251
|
+
setBlockDecoEvent("heading-4");
|
|
252
|
+
styleButtonList.value[count][j].value = "default";
|
|
253
|
+
styleButtonList.value[count][j].active = false;
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "h1",
|
|
258
|
+
action: (count, j) => {
|
|
259
|
+
setBlockDecoEvent("heading-1");
|
|
260
|
+
styleButtonList.value[count][j].value = "h1";
|
|
261
|
+
styleButtonList.value[count][j].active = false;
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: "h2",
|
|
266
|
+
action: (count, j) => {
|
|
267
|
+
setBlockDecoEvent("heading-2");
|
|
268
|
+
styleButtonList.value[count][j].value = "h2";
|
|
269
|
+
styleButtonList.value[count][j].active = false;
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
name: "h3",
|
|
274
|
+
action: (count, j) => {
|
|
275
|
+
setBlockDecoEvent("heading-3");
|
|
276
|
+
styleButtonList.value[count][j].value = "h3";
|
|
277
|
+
styleButtonList.value[count][j].active = false;
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "h5",
|
|
282
|
+
action: (count, j) => {
|
|
283
|
+
setBlockDecoEvent("heading-5");
|
|
284
|
+
styleButtonList.value[count][j].value = "h5";
|
|
285
|
+
styleButtonList.value[count][j].active = false;
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "h6",
|
|
290
|
+
action: (count, j) => {
|
|
291
|
+
setBlockDecoEvent("heading-6");
|
|
292
|
+
styleButtonList.value[count][j].value = "h6";
|
|
293
|
+
styleButtonList.value[count][j].active = false;
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
},
|
|
298
|
+
],
|
|
220
299
|
]);
|
|
221
|
-
const totalKey = ref<number>(1);
|
|
222
300
|
|
|
223
301
|
// 블럭 추가 메뉴 설정
|
|
224
302
|
blockMenu.value = setEditorMenu(option.value.blockMenu as string[], unref(option.value.customBlockMenu) as userCustomMenu[]);
|
|
@@ -253,6 +331,7 @@ function checkAlignActive(className: string) {
|
|
|
253
331
|
return value;
|
|
254
332
|
}
|
|
255
333
|
|
|
334
|
+
// 스타일 메뉴 엑티브 상황
|
|
256
335
|
function checkDecoActive() {
|
|
257
336
|
styleButtonList.value[0][0].active = checkAlignActive("d-align-left");
|
|
258
337
|
styleButtonList.value[0][1].active = checkAlignActive("d-align-center");
|
|
@@ -263,8 +342,10 @@ function checkDecoActive() {
|
|
|
263
342
|
styleButtonList.value[1][3].active = hasClassNameCheckLogic("d-deco-through");
|
|
264
343
|
styleButtonList.value[2][0].active = hasClassNameCheckLogic("d-deco-link");
|
|
265
344
|
styleButtonList.value[3][0].active = hasClassNameCheckLogic("d-deco-code");
|
|
345
|
+
styleButtonList.value[4][0].value = checkHeadingClass();
|
|
266
346
|
}
|
|
267
347
|
|
|
348
|
+
// 텍스트 스타일 확인용 함수
|
|
268
349
|
function hasClassNameCheckLogic(className: string) {
|
|
269
350
|
const cursorData = getCursor();
|
|
270
351
|
let value = false;
|
|
@@ -297,6 +378,33 @@ function hasClassNameCheckLogic(className: string) {
|
|
|
297
378
|
return value;
|
|
298
379
|
}
|
|
299
380
|
|
|
381
|
+
// 글자크기 클레스 확인
|
|
382
|
+
function checkHeadingClass(): string {
|
|
383
|
+
let value: string = "";
|
|
384
|
+
|
|
385
|
+
switch (true) {
|
|
386
|
+
case hasClassNameCheckLogic("d-h1"):
|
|
387
|
+
value = "h1";
|
|
388
|
+
break;
|
|
389
|
+
case hasClassNameCheckLogic("d-h2"):
|
|
390
|
+
value = "h2";
|
|
391
|
+
break;
|
|
392
|
+
case hasClassNameCheckLogic("d-h3"):
|
|
393
|
+
value = "h3";
|
|
394
|
+
break;
|
|
395
|
+
case hasClassNameCheckLogic("d-h5"):
|
|
396
|
+
value = "h5";
|
|
397
|
+
break;
|
|
398
|
+
case hasClassNameCheckLogic("d-h6"):
|
|
399
|
+
value = "h6";
|
|
400
|
+
break;
|
|
401
|
+
default:
|
|
402
|
+
value = "default";
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return value;
|
|
406
|
+
}
|
|
407
|
+
|
|
300
408
|
function setEditorMenu(vanillaData: string[], customData?: userCustomMenu[]) {
|
|
301
409
|
const dataList: editorMenu[] = [];
|
|
302
410
|
|
|
@@ -338,6 +446,22 @@ function activeMenuEvent(count: number, e?: MouseEvent) {
|
|
|
338
446
|
|
|
339
447
|
if (e) {
|
|
340
448
|
$target = e.currentTarget as HTMLElement;
|
|
449
|
+
|
|
450
|
+
if (e.type === "mouseup") {
|
|
451
|
+
const wrap = $target.parentElement as HTMLElement;
|
|
452
|
+
const child = wrap.querySelectorAll(".d-row-block");
|
|
453
|
+
let idx: number = -1;
|
|
454
|
+
|
|
455
|
+
[...child].filter((item, count) => {
|
|
456
|
+
if (item === $target) {
|
|
457
|
+
idx = count;
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
if (idx > -1) {
|
|
462
|
+
activeIdx.value = idx;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
341
465
|
} else {
|
|
342
466
|
$target = $child.value[activeIdx.value];
|
|
343
467
|
}
|
|
@@ -345,13 +469,15 @@ function activeMenuEvent(count: number, e?: MouseEvent) {
|
|
|
345
469
|
setMenuPosition($target);
|
|
346
470
|
checkDecoActive();
|
|
347
471
|
activeMenu.value = true;
|
|
472
|
+
styleButtonList.value[4][0].active = false;
|
|
348
473
|
}
|
|
349
474
|
|
|
350
475
|
// 관련 메뉴 닫기
|
|
351
|
-
function deactiveMenuEvent(e?:
|
|
476
|
+
function deactiveMenuEvent(e?: MouseEvent | KeyboardEvent) {
|
|
352
477
|
activeMenu.value = false;
|
|
353
478
|
activeBlockAddMenu.value = false;
|
|
354
479
|
activeBlockColtrolMenu.value = false;
|
|
480
|
+
styleButtonList.value[4][0].active = false;
|
|
355
481
|
|
|
356
482
|
if (e && e.type === "mouseleave") {
|
|
357
483
|
activeLinkBox.value = false;
|
|
@@ -418,7 +544,9 @@ function pasteEvent(e: ClipboardEvent) {
|
|
|
418
544
|
const targetComponent = $child.value[activeIdx.value];
|
|
419
545
|
const componentType = targetComponent.getType();
|
|
420
546
|
|
|
421
|
-
if (componentType
|
|
547
|
+
if (componentType === "other") {
|
|
548
|
+
// TODO : add block
|
|
549
|
+
} else {
|
|
422
550
|
targetComponent.pasteEvent(data.value);
|
|
423
551
|
}
|
|
424
552
|
}
|
|
@@ -427,7 +555,11 @@ function pasteEvent(e: ClipboardEvent) {
|
|
|
427
555
|
// 블럭 종류 정의
|
|
428
556
|
function setComponentKind(kind: string) {
|
|
429
557
|
let componentData: any;
|
|
558
|
+
|
|
430
559
|
switch (kind) {
|
|
560
|
+
case "ol":
|
|
561
|
+
componentData = olBlock;
|
|
562
|
+
break;
|
|
431
563
|
case "image":
|
|
432
564
|
componentData = imageBlock;
|
|
433
565
|
break;
|
|
@@ -504,8 +636,6 @@ async function moveBlock(type: string) {
|
|
|
504
636
|
content.value.splice(targetIdx, 1, thisData);
|
|
505
637
|
content.value.splice(activeIdx.value, 1, targetData);
|
|
506
638
|
activeIdx.value = targetIdx;
|
|
507
|
-
|
|
508
|
-
totalKey.value += 1;
|
|
509
639
|
deactiveMenuEvent();
|
|
510
640
|
}
|
|
511
641
|
}
|
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="dragon-editor --comment">
|
|
3
|
-
<p class="d-text-block" :class="data.classList" contenteditable v-html="data.content" @keydown="textKeyboardEvent"
|
|
4
|
-
@paste="pasteEvent" ref="$block"></p>
|
|
3
|
+
<p class="d-text-block" :class="data.classList" contenteditable v-html="data.content" @keydown="textKeyboardEvent" @paste="pasteEvent" ref="$block"></p>
|
|
5
4
|
</div>
|
|
6
5
|
</template>
|
|
7
6
|
|
|
8
7
|
<script setup lang="ts">
|
|
8
|
+
// @ts-ignore
|
|
9
9
|
import { ref, unref } from "#imports";
|
|
10
|
-
import {
|
|
11
|
-
keyboardEvent,
|
|
12
|
-
setCursor,
|
|
13
|
-
pasteText,
|
|
14
|
-
styleSettings,
|
|
15
|
-
getArrangementCursorData,
|
|
16
|
-
getClipboardData,
|
|
17
|
-
getCursor,
|
|
18
|
-
findEditableElement
|
|
19
|
-
} from "../../core/utils/index";
|
|
10
|
+
import { keyboardEvent, setCursor, pasteText, styleSettings, getArrangementCursorData, getClipboardData, getCursor, findEditableElement } from "../../core/utils/index";
|
|
20
11
|
import { commentBlock, cursorSelection } from "../../../types/index";
|
|
21
12
|
|
|
22
13
|
const $block = ref();
|
|
@@ -40,7 +31,7 @@ const blockCursorData = ref<cursorSelection>({
|
|
|
40
31
|
data.value = unref(props.modelValue) as commentBlock;
|
|
41
32
|
|
|
42
33
|
function textKeyboardEvent(e: KeyboardEvent) {
|
|
43
|
-
keyboardEvent("comment", e, emit);
|
|
34
|
+
keyboardEvent("comment", e, emit, updateBlockData);
|
|
44
35
|
}
|
|
45
36
|
|
|
46
37
|
function pasteEvent(e: ClipboardEvent) {
|
|
@@ -57,9 +48,7 @@ function updateBlockData() {
|
|
|
57
48
|
// 데이터 정규화 및 검수
|
|
58
49
|
const blockClassList = [...$block.value.classList];
|
|
59
50
|
blockClassList.splice(0, 1);
|
|
60
|
-
const pushList = blockClassList.filter(
|
|
61
|
-
(item) => data.value.classList.indexOf(item) === -1
|
|
62
|
-
);
|
|
51
|
+
const pushList = blockClassList.filter((item) => data.value.classList.indexOf(item) === -1);
|
|
63
52
|
|
|
64
53
|
data.value.classList = data.value.classList.concat(pushList);
|
|
65
54
|
|
|
@@ -71,18 +60,19 @@ function updateBlockData() {
|
|
|
71
60
|
emit("update:modelValue", data.value);
|
|
72
61
|
|
|
73
62
|
setTimeout(() => {
|
|
74
|
-
setCursor(
|
|
75
|
-
$block.value.childNodes[cursorData.childCount],
|
|
76
|
-
cursorData.length
|
|
77
|
-
);
|
|
63
|
+
setCursor($block.value.childNodes[cursorData.childCount], cursorData.length);
|
|
78
64
|
|
|
79
65
|
// 구조 검수
|
|
80
66
|
$block.value.childNodes.forEach((child: ChildNode) => {
|
|
81
|
-
if (child.constructor.name !== "Text") {
|
|
82
|
-
|
|
83
|
-
|
|
67
|
+
if (child.constructor.name !== "Text") {
|
|
68
|
+
// 텍스트가 아닐경우
|
|
69
|
+
if (child.constructor.name !== "HTMLBRElement") {
|
|
70
|
+
// br 태그 유지
|
|
71
|
+
if (child.textContent === "") {
|
|
72
|
+
// 빈 태그 삭제
|
|
84
73
|
child.remove();
|
|
85
|
-
} else if ((child as HTMLElement).classList.length === 0) {
|
|
74
|
+
} else if ((child as HTMLElement).classList.length === 0) {
|
|
75
|
+
// 클레스 없는 엘리먼트 처리
|
|
86
76
|
(child as HTMLElement).insertAdjacentHTML("afterend", child.textContent as string);
|
|
87
77
|
child.remove();
|
|
88
78
|
}
|
|
@@ -103,15 +93,13 @@ function focus() {
|
|
|
103
93
|
}
|
|
104
94
|
|
|
105
95
|
function setStyles(kind: string, url?: string) {
|
|
106
|
-
data.value = styleSettings(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
);
|
|
96
|
+
data.value = styleSettings({
|
|
97
|
+
kind: kind,
|
|
98
|
+
blockData: data.value,
|
|
99
|
+
$target: $block.value,
|
|
100
|
+
url: url,
|
|
101
|
+
cursorData: blockCursorData.value,
|
|
102
|
+
});
|
|
115
103
|
setTimeout(() => {
|
|
116
104
|
updateBlockData();
|
|
117
105
|
}, 250);
|
|
@@ -142,7 +130,7 @@ defineExpose({
|
|
|
142
130
|
updateBlockData,
|
|
143
131
|
focus,
|
|
144
132
|
setStyles,
|
|
145
|
-
getCursorClassList
|
|
133
|
+
getCursorClassList,
|
|
146
134
|
});
|
|
147
135
|
</script>
|
|
148
136
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<template v-else>
|
|
9
9
|
<div class="d-image-block" :class="row.classList">
|
|
10
10
|
<div class="d-image-area">
|
|
11
|
-
<img class="d-img" :src="row.src" :width="row.width" :height="row.height" :alt="row.caption" loading="lazy"
|
|
11
|
+
<img class="d-img" :src="row.src" :width="row.width" :height="row.height" :alt="row.caption" loading="lazy" />
|
|
12
12
|
</div>
|
|
13
13
|
<p class="d-caption" v-if="row.caption" v-html="row.caption"></p>
|
|
14
14
|
</div>
|
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
</template>
|
|
20
20
|
|
|
21
21
|
<script setup lang="ts">
|
|
22
|
+
import { editorContentType } from "../../../types";
|
|
23
|
+
|
|
22
24
|
const props = defineProps<{
|
|
23
|
-
content:
|
|
25
|
+
content: editorContentType;
|
|
24
26
|
}>();
|
|
25
27
|
</script>
|
|
26
28
|
|