@livingdocs/editable.js 3.2.0 → 3.2.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@livingdocs/editable.js",
3
3
  "description": "Friendly contenteditable API",
4
- "version": "3.2.0",
4
+ "version": "3.2.1",
5
5
  "dependencies": {
6
6
  "@babel/runtime": "^7.16.7",
7
7
  "bowser": "2.11.0",
@@ -75,18 +75,22 @@
75
75
  "test:watch": "npm run test:karma -s -- --single-run false",
76
76
  "lint": "eslint --ignore-path .gitignore ."
77
77
  },
78
+ "publishConfig": {
79
+ "tag": "3.x"
80
+ },
78
81
  "release": {
79
- "extends": "@livingdocs/semantic-release-presets/npm-github-verify"
82
+ "extends": "@livingdocs/semantic-release-presets/npm-github-verify",
83
+ "branch": "3.x"
80
84
  },
81
85
  "ci": {
82
86
  "isCi": true,
83
87
  "name": "Drone",
84
88
  "service": "drone",
85
- "commit": "82a67b054b04e557628b02fa4a1e9f5d605eae19",
86
- "build": "1218",
87
- "branch": "master",
89
+ "commit": "ca878f0f32cf4eaf0ebd53f2467973e5c59e8b97",
90
+ "build": "1407",
91
+ "branch": "3.x",
88
92
  "isPr": false,
89
93
  "slug": "livingdocsIO/editable.js",
90
- "date": "2023-09-19T06:19:31.745Z"
94
+ "date": "2024-01-10T22:49:33.014Z"
91
95
  }
92
96
  }
package/src/selection.js CHANGED
@@ -7,6 +7,10 @@ import * as block from './block'
7
7
  import config from './config'
8
8
  import highlightSupport from './highlight-support'
9
9
  import highlightText from './highlight-text'
10
+ import {
11
+ findStartExcludingWhitespace,
12
+ findEndExcludingWhitespace
13
+ } from './util/dom'
10
14
 
11
15
  /**
12
16
  * The Selection module provides a cross-browser abstraction layer for range
@@ -98,9 +102,25 @@ export default class Selection extends Cursor {
98
102
  const textToTrim = this.range.toString()
99
103
  const whitespacesOnTheLeft = textToTrim.search(/\S|$/)
100
104
  const lastNonWhitespace = textToTrim.search(/\S[\s]+$/)
101
- const whitespacesOnTheRight = lastNonWhitespace === -1 ? 0 : textToTrim.length - (lastNonWhitespace + 1)
102
- this.range.setStart(this.range.startContainer, this.range.startOffset + whitespacesOnTheLeft)
103
- this.range.setEnd(this.range.endContainer, this.range.endOffset - whitespacesOnTheRight)
105
+ const whitespacesOnTheRight = lastNonWhitespace === -1
106
+ ? 0
107
+ : textToTrim.length - (lastNonWhitespace + 1)
108
+
109
+ const [startContainer, startOffset] = findStartExcludingWhitespace({
110
+ root: this.range.commonAncestorContainer,
111
+ startContainer: this.range.startContainer,
112
+ startOffset: this.range.startOffset,
113
+ whitespacesOnTheLeft
114
+ })
115
+ this.range.setStart(startContainer, startOffset)
116
+
117
+ const [endContainer, endOffset] = findEndExcludingWhitespace({
118
+ root: this.range.commonAncestorContainer,
119
+ endContainer: this.range.endContainer,
120
+ endOffset: this.range.endOffset,
121
+ whitespacesOnTheRight
122
+ })
123
+ this.range.setEnd(endContainer, endOffset)
104
124
  }
105
125
 
106
126
  unlink () {
@@ -146,6 +166,7 @@ export default class Selection extends Cursor {
146
166
  // toggle('<em>')
147
167
  toggle (elem) {
148
168
  if (block.isPlainTextBlock(this.host)) return
169
+ if (this.range.collapsed) return
149
170
  elem = this.adoptElement(elem)
150
171
  this.range = content.toggleTag(this.host, this.range, elem)
151
172
  this.setSelection()
@@ -310,6 +331,7 @@ export default class Selection extends Cursor {
310
331
  // remove first.
311
332
  forceWrap (elem) {
312
333
  if (block.isPlainTextBlock(this.host)) return
334
+ if (this.range.collapsed) return
313
335
  elem = this.adoptElement(elem)
314
336
  this.range = content.forceWrap(this.host, this.range, elem)
315
337
  this.setSelection()
package/src/util/dom.js CHANGED
@@ -1,3 +1,6 @@
1
+ import NodeIterator from '../node-iterator'
2
+ import {textNode} from '../node-type'
3
+
1
4
  /**
2
5
  * @param {HTMLElement | Array | String} target
3
6
  * @param {Document} document
@@ -42,3 +45,88 @@ export const closest = (elem, selector) => {
42
45
  if (!elem.closest) elem = elem.parentNode
43
46
  if (elem && elem.closest) return elem.closest(selector)
44
47
  }
48
+
49
+ export function findStartExcludingWhitespace ({root, startContainer, startOffset, whitespacesOnTheLeft}) {
50
+ const isTextNode = startContainer.nodeType === textNode
51
+ if (!isTextNode) {
52
+ return findStartExcludingWhitespace({
53
+ root,
54
+ startContainer: startContainer.childNodes[startOffset],
55
+ startOffset: 0,
56
+ whitespacesOnTheLeft
57
+ })
58
+ }
59
+
60
+ const offsetAfterWhitespace = startOffset + whitespacesOnTheLeft
61
+ if (startContainer.length > offsetAfterWhitespace) {
62
+ return [startContainer, offsetAfterWhitespace]
63
+ }
64
+
65
+ // Pass the root so that the iterator can traverse to siblings
66
+ const iterator = new NodeIterator(root)
67
+ // Set the position to the node which is selected
68
+ iterator.nextNode = startContainer
69
+ // Iterate once to avoid returning self
70
+ iterator.getNextTextNode()
71
+
72
+ const container = iterator.getNextTextNode()
73
+ if (!container) {
74
+ // No more text nodes - use the end of the last text node
75
+ const previousTextNode = iterator.getPreviousTextNode()
76
+ return [previousTextNode, previousTextNode.length]
77
+ }
78
+
79
+ return findStartExcludingWhitespace({
80
+ root,
81
+ startContainer: container,
82
+ startOffset: 0,
83
+ whitespacesOnTheLeft: offsetAfterWhitespace - startContainer.length
84
+ })
85
+ }
86
+
87
+ export function findEndExcludingWhitespace ({root, endContainer, endOffset, whitespacesOnTheRight}) {
88
+ const isTextNode = endContainer.nodeType === textNode
89
+ if (!isTextNode) {
90
+ const isFirstNode = !endContainer.childNodes[endOffset - 1]
91
+ const container = isFirstNode
92
+ ? endContainer.childNodes[endOffset]
93
+ : endContainer.childNodes[endOffset - 1]
94
+ let offset = 0
95
+ if (!isFirstNode) {
96
+ offset = container.nodeType === textNode
97
+ ? container.length
98
+ : container.childNodes.length
99
+ }
100
+ return findEndExcludingWhitespace({
101
+ root,
102
+ endContainer: container,
103
+ endOffset: offset,
104
+ whitespacesOnTheRight
105
+ })
106
+ }
107
+
108
+ const offsetBeforeWhitespace = endOffset - whitespacesOnTheRight
109
+ if (offsetBeforeWhitespace > 0) {
110
+ return [endContainer, offsetBeforeWhitespace]
111
+ }
112
+
113
+ // Pass the root so that the iterator can traverse to siblings
114
+ const iterator = new NodeIterator(root)
115
+ // Set the position to the node which is selected
116
+ iterator.previous = endContainer
117
+ // Iterate once to avoid returning self
118
+ iterator.getPreviousTextNode()
119
+
120
+ const container = iterator.getPreviousTextNode()
121
+ if (!container) {
122
+ // No more text nodes - use the start of the first text node
123
+ return [iterator.getNextTextNode(), 0]
124
+ }
125
+
126
+ return findEndExcludingWhitespace({
127
+ root,
128
+ endContainer: container,
129
+ endOffset: container.length,
130
+ whitespacesOnTheRight: whitespacesOnTheRight - endOffset
131
+ })
132
+ }