dragon-editor 3.3.0 → 3.4.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/dist/module.json +1 -1
- package/dist/runtime/components/DragonEditor.vue +198 -6
- package/dist/runtime/components/DragonEditorViewer.vue +7 -1
- package/dist/runtime/scss/editor.css +63 -0
- package/dist/runtime/scss/viewer.css +7 -1
- package/dist/runtime/type.d.ts +5 -0
- package/dist/runtime/utils/style.d.ts +3 -0
- package/dist/runtime/utils/style.mjs +261 -9
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -38,6 +38,26 @@
|
|
|
38
38
|
</svg>
|
|
39
39
|
</button>
|
|
40
40
|
|
|
41
|
+
<button
|
|
42
|
+
class="de-menu de-link-add"
|
|
43
|
+
@click="
|
|
44
|
+
() => {
|
|
45
|
+
isActiveLinkArea = !isActiveLinkArea;
|
|
46
|
+
openLinkArea();
|
|
47
|
+
}
|
|
48
|
+
"
|
|
49
|
+
>
|
|
50
|
+
<svg class="de-icon" viewBox="0 0 64 64">
|
|
51
|
+
<path class="de-path" d="M45.3334 18.6665H34.6667V23.9998H45.3334C49.7334 23.9998 53.3334 27.5998 53.3334 31.9998C53.3334 36.3998 49.7334 39.9998 45.3334 39.9998H34.6667V45.3332H45.3334C52.6934 45.3332 58.6667 39.3598 58.6667 31.9998C58.6667 24.6398 52.6934 18.6665 45.3334 18.6665ZM29.3334 39.9998H18.6667C14.2667 39.9998 10.6667 36.3998 10.6667 31.9998C10.6667 27.5998 14.2667 23.9998 18.6667 23.9998H29.3334V18.6665H18.6667C11.3067 18.6665 5.33337 24.6398 5.33337 31.9998C5.33337 39.3598 11.3067 45.3332 18.6667 45.3332H29.3334V39.9998ZM21.3334 29.3332H42.6667V34.6665H21.3334V29.3332Z"></path>
|
|
52
|
+
</svg>
|
|
53
|
+
</button>
|
|
54
|
+
|
|
55
|
+
<button class="de-menu" @click="removeLink">
|
|
56
|
+
<svg class="de-icon" viewBox="0 0 64 64">
|
|
57
|
+
<path class="de-path" d="M38.3734 29.5065L42.6667 33.7998V29.5065H38.3734ZM45.3334 18.8398H34.6667V23.9065H45.3334C49.8934 23.9065 53.6 27.6131 53.6 32.1731C53.6 35.5598 51.5467 38.4931 48.6134 39.7465L52.3467 43.4798C56.1334 41.1331 58.6667 36.9465 58.6667 32.1731C58.6667 24.8131 52.6934 18.8398 45.3334 18.8398ZM5.33337 11.5598L13.6267 19.8531C8.77337 21.8265 5.33337 26.5998 5.33337 32.1731C5.33337 39.5331 11.3067 45.5065 18.6667 45.5065H29.3334V40.4398H18.6667C14.1067 40.4398 10.4 36.7331 10.4 32.1731C10.4 27.9331 13.6267 24.4398 17.76 23.9865L23.28 29.5065H21.3334V34.8398H28.6134L34.6667 40.8931V45.5065H39.28L49.9734 56.1998L53.7334 52.4398L9.09337 7.7998L5.33337 11.5598Z"></path>
|
|
58
|
+
</svg>
|
|
59
|
+
</button>
|
|
60
|
+
|
|
41
61
|
<label class="de-menu">
|
|
42
62
|
<input type="file" hidden accept=".jpg,.jpeg,.png,.webp,.gif" @change="chooseMediaEvent" />
|
|
43
63
|
<svg class="de-icon" viewBox="0 0 64 64">
|
|
@@ -101,6 +121,22 @@
|
|
|
101
121
|
<!-- <button class="de-add-block" @click="addBlock('video')">Video</button> youtube | vimeo -->
|
|
102
122
|
</div>
|
|
103
123
|
</div>
|
|
124
|
+
|
|
125
|
+
<div class="de-link-exit-area" :class="{ '--active': isActiveLinkArea }">
|
|
126
|
+
<div class="de-btn-area">
|
|
127
|
+
<button class="de-btn" :class="{ '--active': activeLinkTabType === 'url' }" @click="openLinkArea"> Text </button>
|
|
128
|
+
<button class="de-btn" :class="{ '--active': activeLinkTabType === 'heading' }" @click="listUpHeading">Heading</button>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div v-if="activeLinkTabType === 'url'" class="de-link-text-area">
|
|
132
|
+
<input v-model="anchorTagValue" class="de-input" :class="{ '--red': anchorValueError }" type="url" ref="$linkInput" />
|
|
133
|
+
<button class="de-btn" @click="setLink">Add</button>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div v-if="activeLinkTabType === 'heading'" class="de-link-heading-area">
|
|
137
|
+
<button v-for="item in anchorHeadingList" class="de-btn" @click="setHeadingLink(item.id)">{{ item.name }}</button>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
104
140
|
</div>
|
|
105
141
|
|
|
106
142
|
<div v-if="editorStore.$currentBlock !== null" class="de-control-bar" :class="{ '--active': editorStore.controlBar.active === true }" :style="{ top: `${editorStore.controlBar.y}px`, left: `${editorStore.controlBar.x}px` }" ref="$controlBar">
|
|
@@ -150,7 +186,7 @@ import { _getCodeBlockTheme, _getCodeBlockLanguage, _setCodeBlockTheme, _setCode
|
|
|
150
186
|
import { _findScrollingElement, _findContentEditableElement } from "../utils/element";
|
|
151
187
|
import { _elementKeyEvent, _hotKeyEvent, _copyEvent, _pasteEvent } from "../utils/keyboardEvent";
|
|
152
188
|
import { _getBlockType, _createTextBlock, _createHeadingBlock, _createListBlock, _createImageBlock, _createCustomBlock, _createCodeBlock } from "../utils/block";
|
|
153
|
-
import { _setNodeStyle, _setTextAlign } from "../utils/style";
|
|
189
|
+
import { _setNodeStyle, _setTextAlign, _setAnchorTag, _unsetAnchorTag, _getAnchorTagValue } from "../utils/style";
|
|
154
190
|
import { _setCursor, _setCursorData, _clenupCursor } from "../utils/cursor";
|
|
155
191
|
import { _getContentData, _setContentData } from "../utils/convertor";
|
|
156
192
|
import { _addBlockToContent } from "../utils/content";
|
|
@@ -178,10 +214,15 @@ const curruntType = ref<string>("");
|
|
|
178
214
|
const codeBlockTheme = ref<string>("github");
|
|
179
215
|
const codeblockLanguage = ref<string>("text");
|
|
180
216
|
const listBlockStyle = ref<DEListStyle>("disc");
|
|
217
|
+
const isActiveLinkArea = ref<boolean>(false);
|
|
218
|
+
const anchorValueError = ref<boolean>(false);
|
|
219
|
+
const activeLinkTabType = ref<"url" | "heading">("url");
|
|
220
|
+
const anchorHeadingList = ref<DEHeadingItem[]>([]);
|
|
181
221
|
const anchorTagValue = ref<string>("");
|
|
182
222
|
const $editor = ref<HTMLDivElement>();
|
|
183
223
|
const $content = ref<HTMLDivElement>();
|
|
184
224
|
const $controlBar = ref<HTMLDivElement>();
|
|
225
|
+
const $linkInput = ref<HTMLInputElement>();
|
|
185
226
|
let resizeEventActive: boolean = false;
|
|
186
227
|
let resizeStartX: number = 0;
|
|
187
228
|
let resizeType: string = "right";
|
|
@@ -200,7 +241,7 @@ function contentKeyboardEvent(e: KeyboardEvent) {
|
|
|
200
241
|
function updateCursorData(e: MouseEvent) {
|
|
201
242
|
const originalCursorData = editorStore.cursorData;
|
|
202
243
|
|
|
203
|
-
|
|
244
|
+
_clenupCursor(editorStore);
|
|
204
245
|
|
|
205
246
|
if (editorStore.cursorData !== null && _findContentEditableElement(editorStore.cursorData.startNode) === null) {
|
|
206
247
|
// 비정상 커서 값일 경우 초기화
|
|
@@ -217,7 +258,9 @@ function updateCursorData(e: MouseEvent) {
|
|
|
217
258
|
}
|
|
218
259
|
|
|
219
260
|
controlBarStatusUpdate();
|
|
261
|
+
anchorTagValueUpdate();
|
|
220
262
|
}
|
|
263
|
+
|
|
221
264
|
// 컨트롤 바 상태 업데이트
|
|
222
265
|
function controlBarStatusUpdate() {
|
|
223
266
|
if (editorStore.$currentBlock !== null) {
|
|
@@ -319,10 +362,34 @@ function resizeEventEnd() {
|
|
|
319
362
|
function checkOthersideClick(event: MouseEvent) {
|
|
320
363
|
if (event.target !== null) {
|
|
321
364
|
const $controlBar = (event.target as HTMLElement).closest(".de-menu-bar");
|
|
365
|
+
const $btnMenu = (event.target as HTMLElement).closest(".de-menu-add");
|
|
366
|
+
const $menuArea = (event.target as HTMLElement).closest(".de-block-menu-area");
|
|
367
|
+
const $btnLink = (event.target as HTMLElement).closest(".de-link-add");
|
|
368
|
+
const $linkArea = (event.target as HTMLElement).closest(".de-link-exit-area");
|
|
369
|
+
let closeMenu: boolean = false;
|
|
370
|
+
let closeLink: boolean = false;
|
|
322
371
|
|
|
323
372
|
if ($controlBar === null) {
|
|
373
|
+
closeMenu = true;
|
|
374
|
+
closeLink = true;
|
|
375
|
+
} else {
|
|
376
|
+
if ($btnMenu === null && $menuArea === null) {
|
|
377
|
+
closeMenu = true;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if ($btnLink === null && $linkArea === null) {
|
|
381
|
+
closeLink = true;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (closeMenu === true) {
|
|
324
386
|
isActiveAddBlockMenu.value = false;
|
|
325
387
|
}
|
|
388
|
+
|
|
389
|
+
if (closeLink === true) {
|
|
390
|
+
isActiveLinkArea.value = false;
|
|
391
|
+
anchorTagValue.value = "";
|
|
392
|
+
}
|
|
326
393
|
}
|
|
327
394
|
}
|
|
328
395
|
|
|
@@ -346,8 +413,6 @@ function deleteBlock() {
|
|
|
346
413
|
|
|
347
414
|
// 부모 요소 스크롤 이벤트 발생시 컨트롤 바 고정
|
|
348
415
|
function parentWrapScollEvent() {
|
|
349
|
-
editorStore.setParentWrapElement(_findScrollingElement($editor.value as HTMLElement));
|
|
350
|
-
|
|
351
416
|
if (props.useMenuBar === true && editorStore.$parentWrap !== null && editorStore.$editor !== null) {
|
|
352
417
|
// 메뉴바를 사용하는 경우만
|
|
353
418
|
|
|
@@ -368,11 +433,19 @@ function parentWrapScollEvent() {
|
|
|
368
433
|
realElementY -= parentRect.y;
|
|
369
434
|
}
|
|
370
435
|
|
|
436
|
+
let value: number = 0;
|
|
437
|
+
|
|
371
438
|
if (scrollY > realElementY) {
|
|
372
|
-
|
|
439
|
+
value = scrollY - realElementY - 1;
|
|
373
440
|
} else {
|
|
374
|
-
|
|
441
|
+
value = 0;
|
|
375
442
|
}
|
|
443
|
+
|
|
444
|
+
if (value > editorReac.height - 39) {
|
|
445
|
+
value = editorReac.height - 39;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
menuBarTop.value = Math.floor(value);
|
|
376
449
|
}
|
|
377
450
|
}
|
|
378
451
|
|
|
@@ -381,6 +454,13 @@ function contentPasteEvent(event: ClipboardEvent) {
|
|
|
381
454
|
_pasteEvent(event, editorStore, emit);
|
|
382
455
|
}
|
|
383
456
|
|
|
457
|
+
function anchorTagValueUpdate() {
|
|
458
|
+
// 다른 이벤트 순서에 의한 딜레이
|
|
459
|
+
setTimeout(() => {
|
|
460
|
+
anchorTagValue.value = _getAnchorTagValue(editorStore);
|
|
461
|
+
}, 500);
|
|
462
|
+
}
|
|
463
|
+
|
|
384
464
|
/**
|
|
385
465
|
* 이벤트 관련 영역 종료
|
|
386
466
|
*/
|
|
@@ -550,6 +630,11 @@ function moveBlock(type: "up" | "down") {
|
|
|
550
630
|
}
|
|
551
631
|
}
|
|
552
632
|
|
|
633
|
+
function openLinkArea() {
|
|
634
|
+
activeLinkTabType.value = "url";
|
|
635
|
+
anchorValueError.value = false;
|
|
636
|
+
}
|
|
637
|
+
|
|
553
638
|
function chooseMediaEvent(event: Event) {
|
|
554
639
|
const $target = event.target as HTMLInputElement;
|
|
555
640
|
const file = $target.files![0];
|
|
@@ -558,6 +643,50 @@ function chooseMediaEvent(event: Event) {
|
|
|
558
643
|
$target.value = "";
|
|
559
644
|
}
|
|
560
645
|
|
|
646
|
+
// 링크 삽입
|
|
647
|
+
function setLink() {
|
|
648
|
+
if ($linkInput.value !== null && $linkInput.value?.checkValidity() === true && anchorTagValue.value !== "") {
|
|
649
|
+
_setAnchorTag(anchorTagValue.value, true, editorStore);
|
|
650
|
+
isActiveLinkArea.value = false;
|
|
651
|
+
anchorValueError.value = false;
|
|
652
|
+
anchorTagValue.value = "";
|
|
653
|
+
} else {
|
|
654
|
+
anchorValueError.value = true;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function setHeadingLink(id: string) {
|
|
659
|
+
_setAnchorTag(id, false, editorStore);
|
|
660
|
+
isActiveLinkArea.value = false;
|
|
661
|
+
anchorValueError.value = false;
|
|
662
|
+
anchorTagValue.value = "";
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// 헤딩 리스트 업데이트
|
|
666
|
+
function listUpHeading() {
|
|
667
|
+
activeLinkTabType.value = "heading";
|
|
668
|
+
|
|
669
|
+
if (editorStore.$content !== null) {
|
|
670
|
+
const $blockList = editorStore.$content.querySelectorAll(".de-heading-block");
|
|
671
|
+
let headingList: DEHeadingItem[] = [];
|
|
672
|
+
|
|
673
|
+
$blockList.forEach(($headingTag) => {
|
|
674
|
+
if ($headingTag.textContent !== null) {
|
|
675
|
+
headingList.push({
|
|
676
|
+
name: $headingTag.textContent,
|
|
677
|
+
id: $headingTag.id,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
anchorHeadingList.value = headingList;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function removeLink() {
|
|
687
|
+
_unsetAnchorTag(editorStore);
|
|
688
|
+
}
|
|
689
|
+
|
|
561
690
|
/**
|
|
562
691
|
* 메뉴 이벤트 관련 영역 종료
|
|
563
692
|
*/
|
|
@@ -801,6 +930,65 @@ defineExpose({
|
|
|
801
930
|
.dragon-editor .de-menu-bar .de-block-menu-area .de-add-block {
|
|
802
931
|
line-height: 1.6;
|
|
803
932
|
}
|
|
933
|
+
.dragon-editor .de-menu-bar .de-link-exit-area {
|
|
934
|
+
display: none;
|
|
935
|
+
position: absolute;
|
|
936
|
+
top: 39px;
|
|
937
|
+
left: 228px;
|
|
938
|
+
width: 200px;
|
|
939
|
+
background: #fff;
|
|
940
|
+
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.25);
|
|
941
|
+
z-index: 1000;
|
|
942
|
+
}
|
|
943
|
+
.dragon-editor .de-menu-bar .de-link-exit-area.--active {
|
|
944
|
+
display: block;
|
|
945
|
+
}
|
|
946
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-btn-area {
|
|
947
|
+
display: flex;
|
|
948
|
+
border-bottom: 1px solid #ccc;
|
|
949
|
+
}
|
|
950
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-btn-area .de-btn {
|
|
951
|
+
flex: 1;
|
|
952
|
+
height: 24px;
|
|
953
|
+
}
|
|
954
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-btn-area .de-btn.--active {
|
|
955
|
+
background: #f1f1f1;
|
|
956
|
+
}
|
|
957
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-text-area {
|
|
958
|
+
display: flex;
|
|
959
|
+
column-gap: 10px;
|
|
960
|
+
padding: 4px;
|
|
961
|
+
}
|
|
962
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-text-area .de-input {
|
|
963
|
+
flex: 1;
|
|
964
|
+
min-width: 0;
|
|
965
|
+
height: 20px;
|
|
966
|
+
padding: 0 8px;
|
|
967
|
+
border: 1px solid #ccc;
|
|
968
|
+
box-sizing: border-box;
|
|
969
|
+
}
|
|
970
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-text-area .de-input.--red {
|
|
971
|
+
border-color: red;
|
|
972
|
+
}
|
|
973
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-text-area .de-btn {
|
|
974
|
+
min-width: 50px;
|
|
975
|
+
height: 20px;
|
|
976
|
+
}
|
|
977
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-heading-area {
|
|
978
|
+
display: flex;
|
|
979
|
+
flex-direction: column;
|
|
980
|
+
max-height: 150px;
|
|
981
|
+
padding: 8px;
|
|
982
|
+
box-sizing: border-box;
|
|
983
|
+
overflow-y: auto;
|
|
984
|
+
}
|
|
985
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-heading-area .de-btn {
|
|
986
|
+
min-height: 20px;
|
|
987
|
+
text-align: left;
|
|
988
|
+
}
|
|
989
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-heading-area .de-btn:hover {
|
|
990
|
+
background: #f1f1f1;
|
|
991
|
+
}
|
|
804
992
|
.dragon-editor .de-control-bar {
|
|
805
993
|
display: none;
|
|
806
994
|
position: fixed;
|
|
@@ -1476,4 +1664,8 @@ defineExpose({
|
|
|
1476
1664
|
.dragon-editor .de-align-justify {
|
|
1477
1665
|
text-align: justify;
|
|
1478
1666
|
}
|
|
1667
|
+
.dragon-editor .de-link {
|
|
1668
|
+
color: #2c95fe;
|
|
1669
|
+
text-decoration: none;
|
|
1670
|
+
}
|
|
1479
1671
|
</style>
|
|
@@ -181,12 +181,14 @@ const props = defineProps<{
|
|
|
181
181
|
* 노드 스타일
|
|
182
182
|
*/
|
|
183
183
|
.dragon-editor-viewer {
|
|
184
|
-
display:
|
|
184
|
+
display: flex;
|
|
185
|
+
flex-direction: column;
|
|
185
186
|
gap: 4px;
|
|
186
187
|
width: 100%;
|
|
187
188
|
height: 100%;
|
|
188
189
|
padding: 20px;
|
|
189
190
|
line-height: 1.6;
|
|
191
|
+
box-sizing: border-box;
|
|
190
192
|
}
|
|
191
193
|
.dragon-editor-viewer .de-block {
|
|
192
194
|
width: 100%;
|
|
@@ -783,4 +785,8 @@ const props = defineProps<{
|
|
|
783
785
|
.dragon-editor-viewer .de-align-justify {
|
|
784
786
|
text-align: justify;
|
|
785
787
|
}
|
|
788
|
+
.dragon-editor-viewer .de-link {
|
|
789
|
+
color: #2c95fe;
|
|
790
|
+
text-decoration: none;
|
|
791
|
+
}
|
|
786
792
|
</style>
|
|
@@ -202,6 +202,65 @@
|
|
|
202
202
|
.dragon-editor .de-menu-bar .de-block-menu-area .de-add-block {
|
|
203
203
|
line-height: 1.6;
|
|
204
204
|
}
|
|
205
|
+
.dragon-editor .de-menu-bar .de-link-exit-area {
|
|
206
|
+
display: none;
|
|
207
|
+
position: absolute;
|
|
208
|
+
top: 39px;
|
|
209
|
+
left: 228px;
|
|
210
|
+
width: 200px;
|
|
211
|
+
background: #fff;
|
|
212
|
+
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.25);
|
|
213
|
+
z-index: 1000;
|
|
214
|
+
}
|
|
215
|
+
.dragon-editor .de-menu-bar .de-link-exit-area.--active {
|
|
216
|
+
display: block;
|
|
217
|
+
}
|
|
218
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-btn-area {
|
|
219
|
+
display: flex;
|
|
220
|
+
border-bottom: 1px solid #ccc;
|
|
221
|
+
}
|
|
222
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-btn-area .de-btn {
|
|
223
|
+
flex: 1;
|
|
224
|
+
height: 24px;
|
|
225
|
+
}
|
|
226
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-btn-area .de-btn.--active {
|
|
227
|
+
background: #f1f1f1;
|
|
228
|
+
}
|
|
229
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-text-area {
|
|
230
|
+
display: flex;
|
|
231
|
+
column-gap: 10px;
|
|
232
|
+
padding: 4px;
|
|
233
|
+
}
|
|
234
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-text-area .de-input {
|
|
235
|
+
flex: 1;
|
|
236
|
+
min-width: 0;
|
|
237
|
+
height: 20px;
|
|
238
|
+
padding: 0 8px;
|
|
239
|
+
border: 1px solid #ccc;
|
|
240
|
+
box-sizing: border-box;
|
|
241
|
+
}
|
|
242
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-text-area .de-input.--red {
|
|
243
|
+
border-color: red;
|
|
244
|
+
}
|
|
245
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-text-area .de-btn {
|
|
246
|
+
min-width: 50px;
|
|
247
|
+
height: 20px;
|
|
248
|
+
}
|
|
249
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-heading-area {
|
|
250
|
+
display: flex;
|
|
251
|
+
flex-direction: column;
|
|
252
|
+
max-height: 150px;
|
|
253
|
+
padding: 8px;
|
|
254
|
+
box-sizing: border-box;
|
|
255
|
+
overflow-y: auto;
|
|
256
|
+
}
|
|
257
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-heading-area .de-btn {
|
|
258
|
+
min-height: 20px;
|
|
259
|
+
text-align: left;
|
|
260
|
+
}
|
|
261
|
+
.dragon-editor .de-menu-bar .de-link-exit-area .de-link-heading-area .de-btn:hover {
|
|
262
|
+
background: #f1f1f1;
|
|
263
|
+
}
|
|
205
264
|
.dragon-editor .de-control-bar {
|
|
206
265
|
display: none;
|
|
207
266
|
position: fixed;
|
|
@@ -876,4 +935,8 @@
|
|
|
876
935
|
}
|
|
877
936
|
.dragon-editor .de-align-justify {
|
|
878
937
|
text-align: justify;
|
|
938
|
+
}
|
|
939
|
+
.dragon-editor .de-link {
|
|
940
|
+
color: #2c95fe;
|
|
941
|
+
text-decoration: none;
|
|
879
942
|
}
|
|
@@ -133,12 +133,14 @@
|
|
|
133
133
|
* 노드 스타일
|
|
134
134
|
*/
|
|
135
135
|
.dragon-editor-viewer {
|
|
136
|
-
display:
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
137
138
|
gap: 4px;
|
|
138
139
|
width: 100%;
|
|
139
140
|
height: 100%;
|
|
140
141
|
padding: 20px;
|
|
141
142
|
line-height: 1.6;
|
|
143
|
+
box-sizing: border-box;
|
|
142
144
|
}
|
|
143
145
|
.dragon-editor-viewer .de-block {
|
|
144
146
|
width: 100%;
|
|
@@ -734,4 +736,8 @@
|
|
|
734
736
|
}
|
|
735
737
|
.dragon-editor-viewer .de-align-justify {
|
|
736
738
|
text-align: justify;
|
|
739
|
+
}
|
|
740
|
+
.dragon-editor-viewer .de-link {
|
|
741
|
+
color: #2c95fe;
|
|
742
|
+
text-decoration: none;
|
|
737
743
|
}
|
package/dist/runtime/type.d.ts
CHANGED
|
@@ -43,6 +43,11 @@ interface DECodeItem {
|
|
|
43
43
|
code: string;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
interface DEHeadingItem {
|
|
47
|
+
name: string;
|
|
48
|
+
id: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
type DEDecoration = "bold" | "italic" | "underline" | "strikethrough" | "code";
|
|
47
52
|
|
|
48
53
|
type DETextalign = "left" | "right" | "center" | "justify";
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export declare function _setNodeStyle(className: string, store: any): void;
|
|
2
2
|
export declare function _setTextAlign(type: DETextalign, store: any): void;
|
|
3
|
+
export declare function _setAnchorTag(url: string, isOutsideLink: boolean, store: any): void;
|
|
4
|
+
export declare function _unsetAnchorTag(store: any): false | undefined;
|
|
5
|
+
export declare function _getAnchorTagValue(store: any): string;
|
|
@@ -14,20 +14,21 @@ export function _setNodeStyle(className, store) {
|
|
|
14
14
|
if ($target.constructor.name === "Text") {
|
|
15
15
|
const $parentElement = $target.parentElement;
|
|
16
16
|
if ($parentElement === $element) {
|
|
17
|
-
|
|
17
|
+
let childList = $element.childNodes;
|
|
18
18
|
let targetIdx = -1;
|
|
19
19
|
let structure = "";
|
|
20
|
-
let cursorOffset = 0;
|
|
21
20
|
for (let i = 0; childList.length > i; i += 1) {
|
|
22
21
|
if ($target === childList[i]) {
|
|
23
22
|
targetIdx = i;
|
|
24
23
|
break;
|
|
25
24
|
}
|
|
26
25
|
}
|
|
26
|
+
targetIdx = findPoverTextNode(childList[targetIdx], targetIdx);
|
|
27
|
+
$element.innerHTML = $element.innerHTML;
|
|
28
|
+
childList = $element.childNodes;
|
|
27
29
|
childList.forEach((node, i) => {
|
|
28
30
|
if (i === targetIdx) {
|
|
29
31
|
structure += `<span class="${className}">${node.textContent}</span>`;
|
|
30
|
-
cursorOffset = node.textContent.length;
|
|
31
32
|
} else {
|
|
32
33
|
if (node.constructor.name === "Text") {
|
|
33
34
|
structure += node.textContent;
|
|
@@ -37,22 +38,26 @@ export function _setNodeStyle(className, store) {
|
|
|
37
38
|
}
|
|
38
39
|
});
|
|
39
40
|
$element.innerHTML = structure;
|
|
40
|
-
|
|
41
|
+
const targetElement = $element.childNodes[targetIdx];
|
|
42
|
+
_setRangeCursor(targetElement, targetElement, 0, targetElement.textContent?.length ?? 0);
|
|
41
43
|
} else {
|
|
42
44
|
if ($parentElement.tagName === "SPAN") {
|
|
43
45
|
const classList = $parentElement.classList.value.split(" ");
|
|
44
46
|
const classIdx = classList.indexOf(className);
|
|
45
47
|
if (classIdx === -1) {
|
|
46
48
|
$parentElement.classList.add(className);
|
|
47
|
-
|
|
49
|
+
const targetNode = $parentElement.childNodes[0];
|
|
50
|
+
_setRangeCursor(targetNode, targetNode, 0, targetNode.textContent?.length ?? 0);
|
|
48
51
|
} else {
|
|
49
52
|
if (classList.length === 1) {
|
|
50
53
|
$parentElement.insertAdjacentText("afterend", $parentElement.textContent);
|
|
51
|
-
|
|
54
|
+
const targetNode = $parentElement.nextSibling;
|
|
52
55
|
$parentElement.remove();
|
|
56
|
+
_setRangeCursor(targetNode, targetNode, 0, targetNode.textContent?.length ?? 0);
|
|
53
57
|
} else {
|
|
54
58
|
$parentElement.classList.remove(className);
|
|
55
|
-
|
|
59
|
+
const targetNode = $parentElement.childNodes[0];
|
|
60
|
+
_setRangeCursor(targetNode, targetNode, 0, targetNode.textContent?.length ?? 0);
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
}
|
|
@@ -88,7 +93,8 @@ export function _setNodeStyle(className, store) {
|
|
|
88
93
|
childNumber += 1;
|
|
89
94
|
}
|
|
90
95
|
$element.innerHTML = structure;
|
|
91
|
-
|
|
96
|
+
const targetNode = $element.childNodes[childNumber];
|
|
97
|
+
_setRangeCursor(targetNode, targetNode, 0, targetNode.textContent?.length ?? 0);
|
|
92
98
|
} else {
|
|
93
99
|
const $target = cursorData.startNode;
|
|
94
100
|
if ($target.tagName !== "A") {
|
|
@@ -125,7 +131,8 @@ export function _setNodeStyle(className, store) {
|
|
|
125
131
|
$nextElement = $nextElement.nextSibling;
|
|
126
132
|
}
|
|
127
133
|
$target.remove();
|
|
128
|
-
|
|
134
|
+
const targetNode = $nextElement;
|
|
135
|
+
_setRangeCursor(targetNode, targetNode, 0, targetNode.textContent?.length ?? 0);
|
|
129
136
|
}
|
|
130
137
|
}
|
|
131
138
|
} else {
|
|
@@ -363,3 +370,248 @@ export function _setTextAlign(type, store) {
|
|
|
363
370
|
}
|
|
364
371
|
}
|
|
365
372
|
}
|
|
373
|
+
export function _setAnchorTag(url, isOutsideLink, store) {
|
|
374
|
+
if (store.cursorData !== null) {
|
|
375
|
+
const { type } = _getBlockType(store.$currentBlock);
|
|
376
|
+
const typeIgnoreList = ["image", "code", "other"];
|
|
377
|
+
const hrefValue = isOutsideLink === true ? url : `#${url}`;
|
|
378
|
+
if (typeIgnoreList.includes(type) === false) {
|
|
379
|
+
const $element = _findContentEditableElement(store.cursorData.startNode);
|
|
380
|
+
if ($element !== null) {
|
|
381
|
+
if (store.cursorData.type === "Caret") {
|
|
382
|
+
if ($element.hasChildNodes() === true) {
|
|
383
|
+
let $target = store.cursorData.startNode;
|
|
384
|
+
if ($target.constructor.name === "Text") {
|
|
385
|
+
const $parentElement = $target.parentElement;
|
|
386
|
+
if ($parentElement === $element) {
|
|
387
|
+
let childList = $element.childNodes;
|
|
388
|
+
let targetIdx = -1;
|
|
389
|
+
let structure = "";
|
|
390
|
+
for (let i = 0; childList.length > i; i += 1) {
|
|
391
|
+
if ($target === childList[i]) {
|
|
392
|
+
targetIdx = i;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
targetIdx = findPoverTextNode(childList[targetIdx], targetIdx);
|
|
397
|
+
$element.innerHTML = $element.innerHTML;
|
|
398
|
+
childList = $element.childNodes;
|
|
399
|
+
childList.forEach((node, i) => {
|
|
400
|
+
if (i === targetIdx) {
|
|
401
|
+
structure += `<a class="de-link"`;
|
|
402
|
+
structure += `href="${hrefValue}"`;
|
|
403
|
+
if (isOutsideLink === true) {
|
|
404
|
+
structure += `target="_blank"`;
|
|
405
|
+
}
|
|
406
|
+
structure += `>`;
|
|
407
|
+
structure += `${node.textContent}</a>`;
|
|
408
|
+
} else {
|
|
409
|
+
if (node.constructor.name === "Text") {
|
|
410
|
+
structure += node.textContent;
|
|
411
|
+
} else {
|
|
412
|
+
structure += node.outerHTML;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
$element.innerHTML = structure;
|
|
417
|
+
const targetElement = $element.childNodes[targetIdx];
|
|
418
|
+
_setRangeCursor(targetElement, targetElement, 0, targetElement.textContent?.length ?? 0);
|
|
419
|
+
} else {
|
|
420
|
+
if ($parentElement.tagName === "A") {
|
|
421
|
+
$parentElement.href = hrefValue;
|
|
422
|
+
if (isOutsideLink === true) {
|
|
423
|
+
$parentElement.target = "_blank";
|
|
424
|
+
} else {
|
|
425
|
+
$parentElement.removeAttribute("target");
|
|
426
|
+
}
|
|
427
|
+
_setRangeCursor($parentElement, $parentElement, 0, $parentElement.textContent?.length ?? 0);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
const cursorData = _soltingCursorDataOnElement(store.cursorData, $element);
|
|
434
|
+
let structure = "";
|
|
435
|
+
let isDuble = false;
|
|
436
|
+
if (cursorData.startNodeIdx === cursorData.endNodeIdx) {
|
|
437
|
+
if (cursorData.startNode.constructor.name === "Text") {
|
|
438
|
+
$element.childNodes.forEach((childNode, i) => {
|
|
439
|
+
if (cursorData.startNodeIdx === i) {
|
|
440
|
+
if (cursorData.startOffset !== 0) {
|
|
441
|
+
structure += childNode.textContent.slice(0, cursorData.startOffset);
|
|
442
|
+
isDuble = true;
|
|
443
|
+
}
|
|
444
|
+
structure += `<a class="de-link"`;
|
|
445
|
+
structure += `href="${hrefValue}"`;
|
|
446
|
+
if (isOutsideLink === true) {
|
|
447
|
+
structure += `target="_blank"`;
|
|
448
|
+
}
|
|
449
|
+
structure += `>`;
|
|
450
|
+
structure += `${childNode.textContent.slice(cursorData.startOffset, cursorData.endOffset)}</a>`;
|
|
451
|
+
if (cursorData.endOffset !== childNode.textContent.length) {
|
|
452
|
+
structure += childNode.textContent.slice(cursorData.endOffset);
|
|
453
|
+
}
|
|
454
|
+
} else {
|
|
455
|
+
if (childNode.constructor.name === "Text") {
|
|
456
|
+
structure += childNode.textContent;
|
|
457
|
+
} else {
|
|
458
|
+
structure += childNode.outerHTML;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
let childNumber = cursorData.startNodeIdx;
|
|
463
|
+
if (isDuble === true) {
|
|
464
|
+
childNumber += 1;
|
|
465
|
+
}
|
|
466
|
+
$element.innerHTML = structure;
|
|
467
|
+
const targetNode = $element.childNodes[childNumber];
|
|
468
|
+
_setRangeCursor(targetNode, targetNode, 0, targetNode.textContent?.length ?? 0);
|
|
469
|
+
} else {
|
|
470
|
+
const $target = cursorData.startNode;
|
|
471
|
+
if ($target.tagName !== "A") {
|
|
472
|
+
const classList = $target.classList.value.split(" ");
|
|
473
|
+
if (cursorData.startOffset !== 0) {
|
|
474
|
+
structure += `<span class="${classList.join(" ")}">${$target.textContent.slice(0, cursorData.startOffset)}</span>`;
|
|
475
|
+
isDuble = true;
|
|
476
|
+
}
|
|
477
|
+
structure += `<a class="de-link"`;
|
|
478
|
+
structure += `href="${hrefValue}"`;
|
|
479
|
+
if (isOutsideLink === true) {
|
|
480
|
+
structure += `target="_blank"`;
|
|
481
|
+
}
|
|
482
|
+
structure += `>`;
|
|
483
|
+
structure += `${$target.textContent.slice(cursorData.startOffset, cursorData.endOffset)}</a>`;
|
|
484
|
+
if (cursorData.endOffset !== $target.textContent.length) {
|
|
485
|
+
structure += `<span class="${classList.join(" ")}">${$target.textContent.slice(cursorData.endOffset)}</span>`;
|
|
486
|
+
}
|
|
487
|
+
$target.insertAdjacentHTML("afterend", structure);
|
|
488
|
+
let $nextElement = $target.nextSibling;
|
|
489
|
+
if (isDuble === true) {
|
|
490
|
+
$nextElement = $nextElement.nextSibling;
|
|
491
|
+
}
|
|
492
|
+
$target.remove();
|
|
493
|
+
const targetNode = $nextElement;
|
|
494
|
+
_setRangeCursor(targetNode, targetNode, 0, targetNode.textContent?.length ?? 0);
|
|
495
|
+
} else {
|
|
496
|
+
$target.href = hrefValue;
|
|
497
|
+
if (isOutsideLink === true) {
|
|
498
|
+
$target.target = "_blank";
|
|
499
|
+
} else {
|
|
500
|
+
$target.removeAttribute("target");
|
|
501
|
+
}
|
|
502
|
+
_setRangeCursor($target, $target, 0, $target.textContent?.length ?? 0);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
let preStructure = "";
|
|
507
|
+
let anchorTextContent = "";
|
|
508
|
+
let nextStructure = "";
|
|
509
|
+
$element.childNodes.forEach((childNode, i) => {
|
|
510
|
+
const $elementNode = childNode;
|
|
511
|
+
let isText = childNode.constructor.name === "Text";
|
|
512
|
+
if (cursorData.startNodeIdx > i) {
|
|
513
|
+
if (isText === true) {
|
|
514
|
+
preStructure += childNode.textContent;
|
|
515
|
+
} else {
|
|
516
|
+
preStructure += $elementNode.outerHTML;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (cursorData.startNodeIdx === i) {
|
|
520
|
+
if (isText === true) {
|
|
521
|
+
preStructure += childNode.textContent.slice(0, cursorData.startOffset);
|
|
522
|
+
} else {
|
|
523
|
+
preStructure += `<span class="${childNode.classList.value}">${childNode.textContent.slice(0, cursorData.startOffset)}</span>`;
|
|
524
|
+
}
|
|
525
|
+
anchorTextContent += childNode.textContent.slice(cursorData.startOffset);
|
|
526
|
+
}
|
|
527
|
+
if (cursorData.startNodeIdx < i && cursorData.endNodeIdx > i) {
|
|
528
|
+
anchorTextContent += childNode.textContent;
|
|
529
|
+
}
|
|
530
|
+
if (cursorData.endNodeIdx === i) {
|
|
531
|
+
anchorTextContent += childNode.textContent.slice(0, cursorData.endOffset);
|
|
532
|
+
if (isText === true) {
|
|
533
|
+
nextStructure += childNode.textContent.slice(cursorData.startOffset);
|
|
534
|
+
} else {
|
|
535
|
+
nextStructure += `<span class="${childNode.classList.value}">${childNode.textContent.slice(cursorData.startOffset)}</span>`;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (cursorData.endNodeIdx < i) {
|
|
539
|
+
if (isText === true) {
|
|
540
|
+
nextStructure += childNode.textContent;
|
|
541
|
+
} else {
|
|
542
|
+
nextStructure += $elementNode.outerHTML;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
let anchorTag = "";
|
|
547
|
+
anchorTag += `<a class="de-link"`;
|
|
548
|
+
anchorTag += `href="${hrefValue}"`;
|
|
549
|
+
if (isOutsideLink === true) {
|
|
550
|
+
anchorTag += `target="_blank"`;
|
|
551
|
+
}
|
|
552
|
+
anchorTag += `>`;
|
|
553
|
+
anchorTag += anchorTextContent;
|
|
554
|
+
anchorTag += `</a>`;
|
|
555
|
+
$element.innerHTML = preStructure + anchorTag + nextStructure;
|
|
556
|
+
let targetNode = $element.childNodes[cursorData.startNodeIdx];
|
|
557
|
+
if (cursorData.startOffset !== 0) {
|
|
558
|
+
targetNode = $element.childNodes[cursorData.startNodeIdx + 1];
|
|
559
|
+
}
|
|
560
|
+
_setRangeCursor(targetNode, targetNode, 0, targetNode.textContent?.length ?? 0);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
_clenupCursor(store);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
export function _unsetAnchorTag(store) {
|
|
569
|
+
if (store.cursorData !== null) {
|
|
570
|
+
const $element = _findContentEditableElement(store.cursorData.startNode);
|
|
571
|
+
if ($element !== null) {
|
|
572
|
+
const cursorData = _soltingCursorDataOnElement(store.cursorData, $element);
|
|
573
|
+
if (cursorData.startNode.constructor.name === "HTMLAnchorElement") {
|
|
574
|
+
if (store.cursorData.type === "Range") {
|
|
575
|
+
if (cursorData.startNodeIdx !== cursorData.endNodeIdx) {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const $anchorTag = cursorData.startNode;
|
|
580
|
+
$anchorTag.insertAdjacentText("afterend", cursorData.startNode.textContent ?? "");
|
|
581
|
+
const $targetNode = $anchorTag.nextSibling;
|
|
582
|
+
$anchorTag.remove();
|
|
583
|
+
_setRangeCursor($targetNode, $targetNode, 0, $targetNode.textContent?.length ?? 0);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
export function _getAnchorTagValue(store) {
|
|
589
|
+
let href = "";
|
|
590
|
+
if (store.cursorData !== null) {
|
|
591
|
+
const $element = _findContentEditableElement(store.cursorData.startNode);
|
|
592
|
+
if ($element !== null) {
|
|
593
|
+
const cursorData = _soltingCursorDataOnElement(store.cursorData, $element);
|
|
594
|
+
if (cursorData.startNode.constructor.name === "HTMLAnchorElement") {
|
|
595
|
+
if (store.cursorData.type === "Range") {
|
|
596
|
+
if (cursorData.startNodeIdx !== cursorData.endNodeIdx) {
|
|
597
|
+
return href;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
const $anchorTag = cursorData.startNode;
|
|
601
|
+
href = $anchorTag.href;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return href;
|
|
606
|
+
}
|
|
607
|
+
function findPoverTextNode(node, idx) {
|
|
608
|
+
if (node.previousSibling !== null) {
|
|
609
|
+
if (node.previousSibling.constructor.name === "Text") {
|
|
610
|
+
return findPoverTextNode(node.previousSibling, idx -= 1);
|
|
611
|
+
} else {
|
|
612
|
+
return idx;
|
|
613
|
+
}
|
|
614
|
+
} else {
|
|
615
|
+
return idx;
|
|
616
|
+
}
|
|
617
|
+
}
|