dragon-editor 2.1.1 → 3.0.0-beta
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 +40 -115
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -8
- package/dist/runtime/components/DragonEditor.vue +441 -0
- package/dist/runtime/plugin.d.ts +2 -0
- package/dist/runtime/plugin.mjs +10 -0
- package/dist/runtime/scss/editor.css +262 -0
- package/dist/runtime/scss/viewer.css +129 -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/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.d.ts +0 -16
- package/dist/runtime/shared/components/DragonEditor.mjs +0 -62
- package/dist/runtime/shared/components/DragonEditor.vue +0 -695
- package/dist/runtime/shared/components/DragonEditorComment.vue +0 -172
- package/dist/runtime/shared/components/DragonEditorViewer.d.ts +0 -14
- package/dist/runtime/shared/components/DragonEditorViewer.mjs +0 -15
|
@@ -1,695 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="dragon-editor" @paste="pasteEvent" ref="$wrap" @mouseleave="deactiveMenuEvent" @keydown="deactiveMenuEvent">
|
|
3
|
-
<div class="d-left-menu" :class="{ '--active': activeMenu }" :style="{ top: `${leftMenuPosition}px` }">
|
|
4
|
-
<div class="d-add-block">
|
|
5
|
-
<button class="d-btn-menu-pop" @click="toggleBlockAddMenu"></button>
|
|
6
|
-
|
|
7
|
-
<div class="d-block-list" :class="{ '--active': activeBlockAddMenu }">
|
|
8
|
-
<button v-for="(row, count) in blockMenu" :key="count" class="d-btn-block" @click="row.action">
|
|
9
|
-
<SvgIcon v-if="row.hasIcon" :kind="row.icon" />
|
|
10
|
-
<div v-else class="d-icon" v-html="row.icon"></div>
|
|
11
|
-
<p class="d-name">{{ row.name }}</p>
|
|
12
|
-
</button>
|
|
13
|
-
</div>
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
|
-
<div class="d-control-block">
|
|
17
|
-
<button class="d-btn-block-pop" @click="toggleBlockControlMenu"></button>
|
|
18
|
-
|
|
19
|
-
<div class="d-block-list" :class="{ '--active': activeBlockColtrolMenu }">
|
|
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>
|
|
23
|
-
</div>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
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
|
-
>
|
|
35
|
-
<template v-if="styleButtonList[2][0].active">
|
|
36
|
-
<p class="d-input">{{ linkValue }}</p>
|
|
37
|
-
<button class="d-btn-link" @click="decoLinkControl">
|
|
38
|
-
<SvgIcon kind="cancel" />
|
|
39
|
-
</button>
|
|
40
|
-
</template>
|
|
41
|
-
<template v-else>
|
|
42
|
-
<input type="url" class="d-input" v-model="linkValue" />
|
|
43
|
-
<button class="d-btn-link" @click="decoLinkControl">
|
|
44
|
-
<SvgIcon kind="accept" />
|
|
45
|
-
</button>
|
|
46
|
-
</template>
|
|
47
|
-
</div>
|
|
48
|
-
|
|
49
|
-
<div class="d-style-menu" :class="{ '--active': activeMenu }" :style="{ top: `${styleMenuPosition}px` }">
|
|
50
|
-
<div v-for="(column, count) in styleButtonList" :key="count" class="d-column">
|
|
51
|
-
<template v-for="(item, j) in column">
|
|
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 v-if="item.target.indexOf(content[activeIdx].type) > -1" 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>
|
|
65
|
-
</template>
|
|
66
|
-
</div>
|
|
67
|
-
<div v-if="customStyleMenu.length > 0" class="d-column"></div>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
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" />
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
</template>
|
|
75
|
-
|
|
76
|
-
<script setup lang="ts">
|
|
77
|
-
// @ts-ignore
|
|
78
|
-
import { ref, unref, onMounted } from "#imports";
|
|
79
|
-
import { createBlock, getClipboardData, getCursor } from "../../core/utils";
|
|
80
|
-
import type { editorOptions, editorMenu, EditorContentType, userCustomMenu, userStyleMenu, cursorSelection } from "../../../types/index";
|
|
81
|
-
|
|
82
|
-
// components
|
|
83
|
-
import SvgIcon from "../../core/components/SvgIcon.vue";
|
|
84
|
-
import TextBlock from "../../core/components/editor/TextBlock.vue";
|
|
85
|
-
import ImageBlock from "../../core/components/editor/ImageBlock.vue";
|
|
86
|
-
import olBlock from "../../core/components/editor/OlBlock.vue";
|
|
87
|
-
import ulBlock from "../../core/components/editor/UlBlock.vue";
|
|
88
|
-
|
|
89
|
-
// 기본 정보
|
|
90
|
-
const props = defineProps<{
|
|
91
|
-
modelValue: EditorContentType;
|
|
92
|
-
option?: editorOptions;
|
|
93
|
-
}>();
|
|
94
|
-
const modelValue = ref<EditorContentType>([]);
|
|
95
|
-
const option = ref<editorOptions>({
|
|
96
|
-
blockMenu: ["text", "ol", "ul"],
|
|
97
|
-
// blockMenu: ["text", "ol", "ul", "table", "quotation"], // TODO : 다른 블럭 만들기
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
if (props.modelValue) {
|
|
101
|
-
modelValue.value = props.modelValue;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (props.option) {
|
|
105
|
-
option.value = Object.assign(option.value, props.option);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const emit = defineEmits<{
|
|
109
|
-
(e: "update:modelValue", modelValue: EditorContentType): void;
|
|
110
|
-
}>();
|
|
111
|
-
|
|
112
|
-
// 내부 데이터
|
|
113
|
-
const $wrap = ref();
|
|
114
|
-
const $child = ref();
|
|
115
|
-
const activeMenu = ref<boolean>(false);
|
|
116
|
-
const activeLinkBox = ref<boolean>(false);
|
|
117
|
-
const activeBlockAddMenu = ref<boolean>(false);
|
|
118
|
-
const activeBlockColtrolMenu = ref<boolean>(false);
|
|
119
|
-
const leftMenuPosition = ref<number>(0);
|
|
120
|
-
const styleMenuPosition = ref<number>(0);
|
|
121
|
-
const linkBoxPosition = ref({
|
|
122
|
-
top: 0,
|
|
123
|
-
left: 0,
|
|
124
|
-
});
|
|
125
|
-
const iconList = ["TextBlock", "ImageBlock", "ulBlock", "olBlock", "quotationBlock", "tableBlock"];
|
|
126
|
-
const blockMenu = ref<editorMenu[]>([]);
|
|
127
|
-
const customStyleMenu = ref<userStyleMenu[]>([]);
|
|
128
|
-
const content = ref<EditorContentType>([]);
|
|
129
|
-
const activeIdx = ref<number>(0);
|
|
130
|
-
const focusIdx = ref<number>(0);
|
|
131
|
-
const linkValue = ref<string>("");
|
|
132
|
-
const cursorData = ref<cursorSelection>({
|
|
133
|
-
type: "",
|
|
134
|
-
startNode: null,
|
|
135
|
-
startOffset: null,
|
|
136
|
-
endNode: null,
|
|
137
|
-
endOffset: null,
|
|
138
|
-
});
|
|
139
|
-
const styleButtonList = ref([
|
|
140
|
-
[
|
|
141
|
-
{
|
|
142
|
-
type: "single",
|
|
143
|
-
name: "Align Left",
|
|
144
|
-
icon: "alignLeft",
|
|
145
|
-
active: false,
|
|
146
|
-
target: ["text", "image", "table", "ul", "ol"],
|
|
147
|
-
action: () => {
|
|
148
|
-
setBlockDecoEvent("alignLeft");
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
type: "single",
|
|
153
|
-
name: "Align Center",
|
|
154
|
-
icon: "alignCenter",
|
|
155
|
-
target: ["text", "image", "table", "ul", "ol"],
|
|
156
|
-
active: false,
|
|
157
|
-
action: () => {
|
|
158
|
-
setBlockDecoEvent("alignCenter");
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
type: "single",
|
|
163
|
-
name: "Align right",
|
|
164
|
-
icon: "alignRight",
|
|
165
|
-
target: ["text", "image", "table", "ul", "ol"],
|
|
166
|
-
active: false,
|
|
167
|
-
action: () => {
|
|
168
|
-
setBlockDecoEvent("alignRight");
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
[
|
|
173
|
-
{
|
|
174
|
-
type: "single",
|
|
175
|
-
name: "Decoration Bold",
|
|
176
|
-
icon: "decorationBold",
|
|
177
|
-
target: ["text", "table", "ul", "ol"],
|
|
178
|
-
active: false,
|
|
179
|
-
action: () => {
|
|
180
|
-
setBlockDecoEvent("decorationBold");
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
type: "single",
|
|
185
|
-
name: "Decoration Italic",
|
|
186
|
-
icon: "decorationItalic",
|
|
187
|
-
target: ["text", "table", "ul", "ol"],
|
|
188
|
-
active: false,
|
|
189
|
-
action: () => {
|
|
190
|
-
setBlockDecoEvent("decorationItalic");
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
type: "single",
|
|
195
|
-
name: "Decoration Underline",
|
|
196
|
-
icon: "decorationUnderline",
|
|
197
|
-
target: ["text", "table", "ul", "ol"],
|
|
198
|
-
active: false,
|
|
199
|
-
action: () => {
|
|
200
|
-
setBlockDecoEvent("decorationUnderline");
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
type: "single",
|
|
205
|
-
name: "Decoration Strikethrough",
|
|
206
|
-
icon: "decorationStrikethrough",
|
|
207
|
-
target: ["text", "table", "ul", "ol"],
|
|
208
|
-
active: false,
|
|
209
|
-
action: () => {
|
|
210
|
-
setBlockDecoEvent("decorationStrikethrough");
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
[
|
|
215
|
-
{
|
|
216
|
-
type: "single",
|
|
217
|
-
name: "Link",
|
|
218
|
-
icon: "link",
|
|
219
|
-
active: false,
|
|
220
|
-
target: ["text", "table", "ul", "ol"],
|
|
221
|
-
action: () => {
|
|
222
|
-
activeLinkBox.value = !activeLinkBox.value;
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
],
|
|
226
|
-
[
|
|
227
|
-
{
|
|
228
|
-
type: "single",
|
|
229
|
-
name: "Decoration Code",
|
|
230
|
-
icon: "codeBlock",
|
|
231
|
-
target: ["text", "table", "ul", "ol"],
|
|
232
|
-
active: false,
|
|
233
|
-
action: () => {
|
|
234
|
-
setBlockDecoEvent("decorationCode");
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
[
|
|
239
|
-
{
|
|
240
|
-
type: "list",
|
|
241
|
-
name: "Font Size",
|
|
242
|
-
value: "default",
|
|
243
|
-
target: ["text"],
|
|
244
|
-
active: false,
|
|
245
|
-
action: (count, j) => {
|
|
246
|
-
styleButtonList.value[count][j].active = !styleButtonList.value[count][j].active;
|
|
247
|
-
},
|
|
248
|
-
childList: [
|
|
249
|
-
{
|
|
250
|
-
name: "default",
|
|
251
|
-
action: (count, j) => {
|
|
252
|
-
setBlockDecoEvent("heading-4");
|
|
253
|
-
styleButtonList.value[count][j].value = "default";
|
|
254
|
-
styleButtonList.value[count][j].active = false;
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
name: "h1",
|
|
259
|
-
action: (count, j) => {
|
|
260
|
-
setBlockDecoEvent("heading-1");
|
|
261
|
-
styleButtonList.value[count][j].value = "h1";
|
|
262
|
-
styleButtonList.value[count][j].active = false;
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
name: "h2",
|
|
267
|
-
action: (count, j) => {
|
|
268
|
-
setBlockDecoEvent("heading-2");
|
|
269
|
-
styleButtonList.value[count][j].value = "h2";
|
|
270
|
-
styleButtonList.value[count][j].active = false;
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
name: "h3",
|
|
275
|
-
action: (count, j) => {
|
|
276
|
-
setBlockDecoEvent("heading-3");
|
|
277
|
-
styleButtonList.value[count][j].value = "h3";
|
|
278
|
-
styleButtonList.value[count][j].active = false;
|
|
279
|
-
},
|
|
280
|
-
},
|
|
281
|
-
{
|
|
282
|
-
name: "h5",
|
|
283
|
-
action: (count, j) => {
|
|
284
|
-
setBlockDecoEvent("heading-5");
|
|
285
|
-
styleButtonList.value[count][j].value = "h5";
|
|
286
|
-
styleButtonList.value[count][j].active = false;
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
name: "h6",
|
|
291
|
-
action: (count, j) => {
|
|
292
|
-
setBlockDecoEvent("heading-6");
|
|
293
|
-
styleButtonList.value[count][j].value = "h6";
|
|
294
|
-
styleButtonList.value[count][j].active = false;
|
|
295
|
-
},
|
|
296
|
-
},
|
|
297
|
-
],
|
|
298
|
-
},
|
|
299
|
-
],
|
|
300
|
-
]);
|
|
301
|
-
|
|
302
|
-
// 블럭 추가 메뉴 설정
|
|
303
|
-
blockMenu.value = setEditorMenu(option.value.blockMenu as string[], unref(option.value.customBlockMenu) as userCustomMenu[]);
|
|
304
|
-
|
|
305
|
-
// 유저 커스텀 스타일 메뉴
|
|
306
|
-
if (option.value.customStyleMenu) {
|
|
307
|
-
customStyleMenu.value = unref(option.value.customStyleMenu);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// 컨텐츠 데이터 설정
|
|
311
|
-
if (modelValue.value && Array.isArray(modelValue.value)) {
|
|
312
|
-
if (modelValue.value.length == 0) {
|
|
313
|
-
addBlockLocal({ name: "text", time: true });
|
|
314
|
-
} else {
|
|
315
|
-
content.value = modelValue.value;
|
|
316
|
-
}
|
|
317
|
-
} else {
|
|
318
|
-
throw new Error("[DragonEditor]ERROR : You must set 'v-model' attribute and 'v-mode' type is must be Array.");
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// local logic
|
|
322
|
-
function checkAlignActive(className: string) {
|
|
323
|
-
const data = content.value[activeIdx.value];
|
|
324
|
-
let value = false;
|
|
325
|
-
|
|
326
|
-
switch (data.type) {
|
|
327
|
-
case "text":
|
|
328
|
-
value = data.classList.indexOf(className) > -1;
|
|
329
|
-
break;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return value;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// 스타일 메뉴 엑티브 상황
|
|
336
|
-
function checkDecoActive() {
|
|
337
|
-
styleButtonList.value[0][0].active = checkAlignActive("d-align-left");
|
|
338
|
-
styleButtonList.value[0][1].active = checkAlignActive("d-align-center");
|
|
339
|
-
styleButtonList.value[0][2].active = checkAlignActive("d-align-right");
|
|
340
|
-
styleButtonList.value[1][0].active = hasClassNameCheckLogic("d-deco-bold");
|
|
341
|
-
styleButtonList.value[1][1].active = hasClassNameCheckLogic("d-deco-italic");
|
|
342
|
-
styleButtonList.value[1][2].active = hasClassNameCheckLogic("d-deco-underline");
|
|
343
|
-
styleButtonList.value[1][3].active = hasClassNameCheckLogic("d-deco-through");
|
|
344
|
-
styleButtonList.value[2][0].active = hasClassNameCheckLogic("d-deco-link");
|
|
345
|
-
styleButtonList.value[3][0].active = hasClassNameCheckLogic("d-deco-code");
|
|
346
|
-
styleButtonList.value[4][0].value = checkHeadingClass();
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// 텍스트 스타일 확인용 함수
|
|
350
|
-
function hasClassNameCheckLogic(className: string) {
|
|
351
|
-
const cursorData = getCursor();
|
|
352
|
-
let value = false;
|
|
353
|
-
|
|
354
|
-
if (cursorData.type === "Caret") {
|
|
355
|
-
const type = (cursorData.startNode as Node).constructor.name;
|
|
356
|
-
let $target = cursorData.startNode as HTMLElement;
|
|
357
|
-
|
|
358
|
-
if (type === "Text") {
|
|
359
|
-
$target = (cursorData.startNode as HTMLElement).parentNode as HTMLElement;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
if ($target) {
|
|
363
|
-
const classList = [...$target.classList];
|
|
364
|
-
|
|
365
|
-
if (classList.indexOf(className) > -1) {
|
|
366
|
-
value = true;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (className === "d-deco-link") {
|
|
371
|
-
if (value) {
|
|
372
|
-
linkValue.value = $target.getAttribute("href");
|
|
373
|
-
} else {
|
|
374
|
-
linkValue.value = "";
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return value;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// 글자크기 클레스 확인
|
|
383
|
-
function checkHeadingClass(): string {
|
|
384
|
-
let value: string = "";
|
|
385
|
-
|
|
386
|
-
switch (true) {
|
|
387
|
-
case hasClassNameCheckLogic("d-h1"):
|
|
388
|
-
value = "h1";
|
|
389
|
-
break;
|
|
390
|
-
case hasClassNameCheckLogic("d-h2"):
|
|
391
|
-
value = "h2";
|
|
392
|
-
break;
|
|
393
|
-
case hasClassNameCheckLogic("d-h3"):
|
|
394
|
-
value = "h3";
|
|
395
|
-
break;
|
|
396
|
-
case hasClassNameCheckLogic("d-h5"):
|
|
397
|
-
value = "h5";
|
|
398
|
-
break;
|
|
399
|
-
case hasClassNameCheckLogic("d-h6"):
|
|
400
|
-
value = "h6";
|
|
401
|
-
break;
|
|
402
|
-
default:
|
|
403
|
-
value = "default";
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return value;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function setEditorMenu(vanillaData: string[], customData?: userCustomMenu[]) {
|
|
410
|
-
const dataList: editorMenu[] = [];
|
|
411
|
-
|
|
412
|
-
vanillaData.forEach((name) => {
|
|
413
|
-
dataList.push({
|
|
414
|
-
name: name,
|
|
415
|
-
hasIcon: true,
|
|
416
|
-
icon: `${name}Block`,
|
|
417
|
-
action: () => {
|
|
418
|
-
addBlockLocal({ name: name });
|
|
419
|
-
},
|
|
420
|
-
});
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
if (customData) {
|
|
424
|
-
customData.forEach((row) => {
|
|
425
|
-
dataList.push({
|
|
426
|
-
name: row.name,
|
|
427
|
-
hasIcon: iconList.indexOf(row.icon) > -1,
|
|
428
|
-
icon: row.icon,
|
|
429
|
-
action: row.action,
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
return dataList;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* 내부용 이벤트 함수
|
|
439
|
-
*/
|
|
440
|
-
// 관련 메뉴 열기
|
|
441
|
-
function activeMenuEvent(count: number, e?: MouseEvent) {
|
|
442
|
-
let $target: HTMLElement;
|
|
443
|
-
|
|
444
|
-
focusIdx.value = count;
|
|
445
|
-
|
|
446
|
-
cursorData.value = getCursor();
|
|
447
|
-
|
|
448
|
-
if (e) {
|
|
449
|
-
$target = e.currentTarget as HTMLElement;
|
|
450
|
-
|
|
451
|
-
if (e.type === "mouseup") {
|
|
452
|
-
const wrap = $target.parentElement as HTMLElement;
|
|
453
|
-
const child = wrap.querySelectorAll(".d-row-block");
|
|
454
|
-
let idx: number = -1;
|
|
455
|
-
|
|
456
|
-
[...child].filter((item, count) => {
|
|
457
|
-
if (item === $target) {
|
|
458
|
-
idx = count;
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
if (idx > -1) {
|
|
463
|
-
activeIdx.value = idx;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
} else {
|
|
467
|
-
$target = $child.value[activeIdx.value];
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
setMenuPosition($target);
|
|
471
|
-
checkDecoActive();
|
|
472
|
-
activeMenu.value = true;
|
|
473
|
-
styleButtonList.value[4][0].active = false;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// 관련 메뉴 닫기
|
|
477
|
-
function deactiveMenuEvent(e?: MouseEvent | KeyboardEvent) {
|
|
478
|
-
activeMenu.value = false;
|
|
479
|
-
activeBlockAddMenu.value = false;
|
|
480
|
-
activeBlockColtrolMenu.value = false;
|
|
481
|
-
styleButtonList.value[4][0].active = false;
|
|
482
|
-
|
|
483
|
-
if (e && e.type === "mouseleave") {
|
|
484
|
-
activeLinkBox.value = false;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function dataUpdateAction() {
|
|
489
|
-
$child.value.forEach((row: any) => {
|
|
490
|
-
row.updateBlockData();
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
emit("update:modelValue", content.value);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// 블럭 추가 로직
|
|
497
|
-
function addBlockLocal({ name, value, time = false }: { name: string; value?: object; time?: boolean }) {
|
|
498
|
-
const block = createBlock(name, value);
|
|
499
|
-
|
|
500
|
-
content.value.splice(activeIdx.value + 1, 0, block);
|
|
501
|
-
|
|
502
|
-
if (time === false) {
|
|
503
|
-
activeIdx.value += 1;
|
|
504
|
-
|
|
505
|
-
setTimeout(() => {
|
|
506
|
-
activeBlockAddMenu.value = false;
|
|
507
|
-
$child.value[activeIdx.value].focus();
|
|
508
|
-
dataUpdateAction();
|
|
509
|
-
activeMenuEvent(activeIdx.value);
|
|
510
|
-
}, 100);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// 블럭 삭제 이벤트
|
|
515
|
-
function deleteBlockLocal(index?: number) {
|
|
516
|
-
if (content.value.length > 1) {
|
|
517
|
-
if (index === undefined) {
|
|
518
|
-
index = activeIdx.value as number;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
if (index - 1 !== -1) {
|
|
522
|
-
const $targetData = content.value[index - 1];
|
|
523
|
-
const $thisData = content.value[index];
|
|
524
|
-
|
|
525
|
-
if ($targetData.type === "text") {
|
|
526
|
-
activeIdx.value -= 1;
|
|
527
|
-
content.value[index - 1].content += `<span class="${$thisData.classList.join(" ")}">${$thisData.content}</span>`;
|
|
528
|
-
content.value.splice(index, 1);
|
|
529
|
-
|
|
530
|
-
setTimeout(() => {
|
|
531
|
-
dataUpdateAction();
|
|
532
|
-
$child.value[activeIdx.value].focus("last");
|
|
533
|
-
}, 150);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// 붙여넣기 이벤트
|
|
540
|
-
function pasteEvent(e: ClipboardEvent) {
|
|
541
|
-
e.preventDefault();
|
|
542
|
-
const data = getClipboardData(e.clipboardData as DataTransfer);
|
|
543
|
-
|
|
544
|
-
if (data.type === "text") {
|
|
545
|
-
const targetComponent = $child.value[activeIdx.value];
|
|
546
|
-
const componentType = targetComponent.getType();
|
|
547
|
-
|
|
548
|
-
if (componentType === "other") {
|
|
549
|
-
// TODO : add block
|
|
550
|
-
} else {
|
|
551
|
-
targetComponent.pasteEvent(data.value);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// 블럭 종류 정의
|
|
557
|
-
function setComponentKind(kind: string) {
|
|
558
|
-
let componentData: any;
|
|
559
|
-
|
|
560
|
-
switch (kind) {
|
|
561
|
-
case "ul":
|
|
562
|
-
componentData = ulBlock;
|
|
563
|
-
break;
|
|
564
|
-
case "ol":
|
|
565
|
-
componentData = olBlock;
|
|
566
|
-
break;
|
|
567
|
-
case "image":
|
|
568
|
-
componentData = ImageBlock;
|
|
569
|
-
break;
|
|
570
|
-
case "text":
|
|
571
|
-
componentData = TextBlock;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
return componentData;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function setMenuPosition($target: HTMLElement) {
|
|
578
|
-
const parentNode = $wrap.value.parentNode;
|
|
579
|
-
const bodyRect = document.body.getBoundingClientRect();
|
|
580
|
-
const wrapRect = $wrap.value.getBoundingClientRect();
|
|
581
|
-
const targetRect = $target.getBoundingClientRect();
|
|
582
|
-
const parentNodeScrollY = parentNode.scrollTop;
|
|
583
|
-
const wrapTop = wrapRect.top - bodyRect.top;
|
|
584
|
-
const targetTop = targetRect.top - bodyRect.top;
|
|
585
|
-
const targetBottom = targetRect.bottom - bodyRect.top;
|
|
586
|
-
const top = targetTop - (wrapTop + 10) - parentNodeScrollY + 13;
|
|
587
|
-
const bottom = targetBottom - (wrapTop + 10) - parentNodeScrollY + 10;
|
|
588
|
-
let startNode = cursorData.value.startNode;
|
|
589
|
-
|
|
590
|
-
if (startNode !== null) {
|
|
591
|
-
if (startNode.constructor.name === "Text") {
|
|
592
|
-
startNode = startNode.parentNode;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const startNodeRect = startNode.getBoundingClientRect();
|
|
596
|
-
const wrapleft = startNodeRect.left - bodyRect.left;
|
|
597
|
-
|
|
598
|
-
linkBoxPosition.value = {
|
|
599
|
-
top: top - 32,
|
|
600
|
-
left: wrapleft,
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
styleMenuPosition.value = bottom;
|
|
605
|
-
leftMenuPosition.value = top;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// 블럭 스타일 이벤트
|
|
609
|
-
function setBlockDecoEvent(type: string, url?: string) {
|
|
610
|
-
$child.value[activeIdx.value].setStyles({
|
|
611
|
-
type: type,
|
|
612
|
-
url: url,
|
|
613
|
-
});
|
|
614
|
-
setTimeout(() => {
|
|
615
|
-
checkDecoActive();
|
|
616
|
-
}, 100);
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// 링크 스타일컨트롤
|
|
620
|
-
function decoLinkControl() {
|
|
621
|
-
setBlockDecoEvent("decorationLink", linkValue.value);
|
|
622
|
-
activeLinkBox.value = false;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// 블럭 위치 조정
|
|
626
|
-
async function moveBlock(type: string) {
|
|
627
|
-
let targetIdx = 0;
|
|
628
|
-
dataUpdateAction();
|
|
629
|
-
|
|
630
|
-
if (type === "up") {
|
|
631
|
-
targetIdx = activeIdx.value - 1;
|
|
632
|
-
} else {
|
|
633
|
-
targetIdx = activeIdx.value + 1;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
if (targetIdx >= 0 && targetIdx < content.value.length) {
|
|
637
|
-
const targetData = content.value[targetIdx];
|
|
638
|
-
const thisData = content.value[activeIdx.value];
|
|
639
|
-
|
|
640
|
-
content.value.splice(targetIdx, 1, thisData);
|
|
641
|
-
content.value.splice(activeIdx.value, 1, targetData);
|
|
642
|
-
activeIdx.value = targetIdx;
|
|
643
|
-
deactiveMenuEvent();
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// 블럭 추가 메뉴 열기
|
|
648
|
-
function toggleBlockAddMenu() {
|
|
649
|
-
activeIdx.value = focusIdx.value;
|
|
650
|
-
activeBlockAddMenu.value = !activeBlockAddMenu.value;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// 블럭 컨트롤 메뉴 열기
|
|
654
|
-
function toggleBlockControlMenu() {
|
|
655
|
-
activeIdx.value = focusIdx.value;
|
|
656
|
-
activeBlockColtrolMenu.value = !activeBlockColtrolMenu.value;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
/**
|
|
660
|
-
* 외부용 함수
|
|
661
|
-
*/
|
|
662
|
-
// function checkStyleActive(className: string) {
|
|
663
|
-
// return hasClassNameCheckLogic(className);
|
|
664
|
-
// }
|
|
665
|
-
|
|
666
|
-
function addImageBlock({ src, width, height, webp, caption }: { src: string; width: number; height: number; webp: boolean; caption?: string }) {
|
|
667
|
-
addBlockLocal({
|
|
668
|
-
name: "image",
|
|
669
|
-
value: {
|
|
670
|
-
src: src,
|
|
671
|
-
width: width,
|
|
672
|
-
height: height,
|
|
673
|
-
webp: webp,
|
|
674
|
-
caption: caption,
|
|
675
|
-
},
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// 함수 내보내기
|
|
680
|
-
defineExpose({
|
|
681
|
-
addImageBlock,
|
|
682
|
-
dataUpdateAction,
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* 초기 데이터 확인용 로직
|
|
687
|
-
*/
|
|
688
|
-
onMounted(() => {
|
|
689
|
-
dataUpdateAction();
|
|
690
|
-
});
|
|
691
|
-
</script>
|
|
692
|
-
|
|
693
|
-
<style>
|
|
694
|
-
@import "../../core/style/common.css";
|
|
695
|
-
</style>
|