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 CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "dragon-editor",
3
3
  "configKey": "dragon-editor",
4
- "version": "3.3.0"
4
+ "version": "3.4.1"
5
5
  }
@@ -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
- _setCursorData(editorStore);
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
- menuBarTop.value = scrollY - realElementY - 1;
439
+ value = scrollY - realElementY - 1;
373
440
  } else {
374
- menuBarTop.value = 0;
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: grid;
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: grid;
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
  }
@@ -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
- const childList = $element.childNodes;
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
- _setCursor($element.childNodes[targetIdx], cursorOffset);
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
- _setCursor($parentElement.childNodes[0], store.cursorData.startOffset);
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
- _setCursor($parentElement.nextSibling, store.cursorData.startOffset);
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
- _setCursor($parentElement.childNodes[0], store.cursorData.startOffset);
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
- _setCursor($element.childNodes[childNumber], cursorData.endOffset - cursorData.startOffset);
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
- _setCursor($nextElement, cursorData.endOffset - cursorData.startOffset);
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dragon-editor",
3
- "version": "3.3.0",
3
+ "version": "3.4.1",
4
4
  "description": "Javascript WYSIWYG editor in Nuxt3!",
5
5
  "repository": {
6
6
  "type": "git",