written 0.1.2 → 0.1.3

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/written/app/assets/javascripts/written/core/content.coffee +10 -10
  3. data/lib/written/app/assets/javascripts/written/core/cursor.coffee +6 -6
  4. data/lib/written/app/assets/javascripts/written/core/document.coffee +39 -6
  5. data/lib/written/app/assets/javascripts/written/parsers/block/code.coffee +34 -30
  6. data/lib/written/app/assets/javascripts/written/parsers/block/heading.coffee +28 -30
  7. data/lib/written/app/assets/javascripts/written/parsers/block/image.coffee +30 -49
  8. data/lib/written/app/assets/javascripts/written/parsers/block/olist.coffee +46 -49
  9. data/lib/written/app/assets/javascripts/written/parsers/block/paragraph.coffee +28 -32
  10. data/lib/written/app/assets/javascripts/written/parsers/block/quote.coffee +30 -32
  11. data/lib/written/app/assets/javascripts/written/parsers/block/ulist.coffee +43 -45
  12. data/lib/written/app/assets/javascripts/written/parsers/inline/code.coffee +28 -30
  13. data/lib/written/app/assets/javascripts/written/parsers/inline/italic.coffee +21 -25
  14. data/lib/written/app/assets/javascripts/written/parsers/inline/link.coffee +21 -25
  15. data/lib/written/app/assets/javascripts/written/parsers/inline/strong.coffee +21 -25
  16. data/lib/written/app/assets/javascripts/written/parsers/parsers.coffee +87 -19
  17. data/lib/written/app/assets/javascripts/written.coffee +0 -1
  18. data/lib/written/version.rb +1 -1
  19. data/test/server/app/assets/javascripts/application.coffee +5 -15
  20. metadata +2 -18
  21. data/lib/written/app/assets/javascripts/written/core/extensions/text.coffee +0 -2
  22. data/lib/written/app/assets/javascripts/written/core/stringify.coffee +0 -15
  23. data/lib/written/app/assets/javascripts/written/parsers/block.coffee +0 -69
  24. data/lib/written/app/assets/javascripts/written/parsers/inline/list.coffee +0 -27
  25. data/lib/written/app/assets/javascripts/written/parsers/inline.coffee +0 -81
  26. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/CustomElements.js +0 -32
  27. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/base.js +0 -40
  28. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/boot.js +0 -124
  29. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/observe.js +0 -318
  30. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/register.js +0 -369
  31. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/traverse.js +0 -86
  32. data/lib/written/app/assets/javascripts/written/polyfills/CustomElements/upgrade.js +0 -130
  33. data/lib/written/app/assets/javascripts/written/polyfills/MutationObserver/MutationObserver.js +0 -575
  34. data/lib/written/app/assets/javascripts/written/polyfills/WeakMap/WeakMap.js +0 -49
  35. data/lib/written/app/assets/javascripts/written/polyfills/base.coffee +0 -10
  36. data/lib/written/app/assets/javascripts/written/polyfills/dom.js +0 -104
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e36c2f64be41a65d8bac7fdde2e86d3b54529cb6
4
- data.tar.gz: 570b11448dc1be526eda6a768294b5d8c6423eae
3
+ metadata.gz: baeba731f6551380429d586b4cf9ce6958e87cc9
4
+ data.tar.gz: e8cb3235cc64c398e64cf7d87d3deee94f5eea47
5
5
  SHA512:
6
- metadata.gz: 0159af44f886957579f72d2006b04338a1b525cd51f3b68c51e6b8dbdc7987e9d766ee1f679d66262c8d5fc032656cd26cccdcf33def2a633548bfec7f7d1831
7
- data.tar.gz: a2107de3631865aaf514307c08ea4d6e1deeee36352f47d345a2aca0e070e66745c927cb040c192587815a1e4a4f8c97a11310db7cf9bb9120bf672749c176b6
6
+ metadata.gz: 904f533a3ab8b865659b82f7e26a10028f98708150facb7b7f9b8c0665aeabf38b8427329d6719c1bcc43ded3653c3b19db76594f99f37c99b02997bef00dc8d
7
+ data.tar.gz: 3e4db620d0eac31e9cb4252104fffbcede4660aadcd3f8bb5ad784bb17505e36c944f9579a356cbe2ba7f7efe4c63f00f22f596806e85a9f70ba2da3b252d3ff
@@ -26,21 +26,19 @@ class @Written
26
26
  initialize: (text, parsers) ->
27
27
  @observer.pause()
28
28
  if !parsers?
29
- parsers = Written.Parsers.default()
29
+ parsers = Written.Parsers
30
30
 
31
31
  @parsers = parsers
32
32
 
33
33
  if @element().contentEditable != 'true'
34
34
  @element().contentEditable = 'true'
35
35
 
36
- @parsers.freeze()
37
-
38
36
  document = new Written.Document(text, @parsers)
39
37
  cursor = new Written.Cursor(@element(), window.getSelection())
40
38
  document.cursor = cursor
41
39
 
42
40
 
43
- @update(document)
41
+ document.applyTo(@element())
44
42
  document.cursor.focus(document.toString().length)
45
43
 
46
44
  @history = new Written.History(document)
@@ -54,7 +52,9 @@ class @Written
54
52
  @element().dispatchEvent(event)
55
53
 
56
54
  changeTo: (text) =>
57
- @update(new Written.Document(text, @parsers))
55
+ document = new Written.Document(text, @parsers)
56
+ @history.push(document)
57
+ document.applyTo(@element())
58
58
 
59
59
  changed: (e) =>
60
60
  oldDocument = @history.current
@@ -63,7 +63,7 @@ class @Written
63
63
  if @element().children.length > 0 && oldDocument.toString().localeCompare(newDocument.toString()) == 0
64
64
  return
65
65
 
66
- @update(newDocument)
66
+ newDocument.applyTo(@element())
67
67
  @history.push(newDocument)
68
68
  @dispatch('written:changed', document: newDocument)
69
69
 
@@ -117,7 +117,7 @@ class @Written
117
117
  if cursor.offset < document.toString().length
118
118
  cursor.offset += 1
119
119
 
120
- @update(document)
120
+ document.applyTo(@element())
121
121
  @history.push(document)
122
122
 
123
123
 
@@ -130,7 +130,7 @@ class @Written
130
130
 
131
131
  if document = @history.previous()
132
132
  @history.current = document
133
- @update(@history.current)
133
+ document.applyTo(@element())
134
134
 
135
135
  redo: (e) =>
136
136
  if e.code == 'KeyZ' && e.metaKey && e.shiftKey
@@ -141,12 +141,12 @@ class @Written
141
141
 
142
142
  if document = @history.next()
143
143
  @history.current = document
144
- @update(@history.current)
144
+ @history.current.applyTo(@element())
145
145
 
146
146
  toString: =>
147
147
  texts = []
148
148
  for node in @element().childNodes
149
- content = node.toString().split('\n')
149
+ content = Written.Parsers.toString(node).split('\n')
150
150
  texts.push content.join('\n')
151
151
 
152
152
  texts.join '\n'
@@ -15,7 +15,7 @@ class Written.Cursor
15
15
  child = node.previousSibling
16
16
 
17
17
  while child
18
- @offset += Written.Stringify(child).length
18
+ @offset += Written.Parsers.toString(child).length
19
19
  child = child.previousSibling
20
20
 
21
21
  if node instanceof HTMLLIElement
@@ -27,7 +27,7 @@ class Written.Cursor
27
27
  for child in @element().children
28
28
  if child == node
29
29
  break
30
- @offset += Written.Stringify(child).length
30
+ @offset += Written.Parsers.toString(child).length
31
31
  @offset += 1
32
32
 
33
33
  @currentNode = ->
@@ -39,7 +39,7 @@ class Written.Cursor
39
39
 
40
40
  element = @element().firstElementChild
41
41
  while element && element != node
42
- offset -= Written.Stringify(element).length
42
+ offset -= Written.Parsers.toString(element).length
43
43
  element = element.nextElementSibling
44
44
 
45
45
  offset
@@ -53,12 +53,12 @@ class Written.Cursor
53
53
  if node is undefined
54
54
  node = @element().firstElementChild
55
55
 
56
- while node.nextElementSibling && Written.Stringify(node).length < offset
57
- offset -= Written.Stringify(node).length + 1
56
+ while node.nextElementSibling && Written.Parsers.toString(node).length < offset
57
+ offset -= Written.Parsers.toString(node).length + 1
58
58
  node = node.nextElementSibling
59
59
 
60
60
 
61
- range = node.getRange(Math.min(offset, Written.Stringify(node).length), document.createTreeWalker(node, NodeFilter.SHOW_TEXT))
61
+ range = Written.Parsers.getRange(node, Math.min(offset, Written.Parsers.toString(node).length), document.createTreeWalker(node, NodeFilter.SHOW_TEXT))
62
62
 
63
63
  if @offsetDiffersBetween(@selection, range)
64
64
  @selection.removeAllRanges()
@@ -1,13 +1,16 @@
1
1
  #= require ../parsers/parsers
2
2
 
3
3
  class Written.Document
4
- constructor: (textContent, parsers) ->
5
- @markdown = textContent
6
-
7
- @blocks = parsers.block.parse(textContent.split('\n').reverse())
4
+ constructor: (text, parsers) ->
5
+ @blocks = parsers.parse(parsers.blocks, text)
8
6
 
9
7
  @blocks.forEach (block) =>
10
- block.processContent(parsers.inline.parse.bind(parsers.inline))
8
+ if block.text?
9
+ if block.multiline
10
+ block.content = block.text().split('\n').map (text) ->
11
+ parsers.parse(parsers.inlines, text)
12
+ else
13
+ block.content = parsers.parse(parsers.inlines, block.text())
11
14
 
12
15
  freeze: =>
13
16
  Object.freeze(@blocks)
@@ -21,5 +24,35 @@ class Written.Document
21
24
 
22
25
  text
23
26
 
27
+ applyTo: (content) =>
28
+
29
+ for block, index in @blocks
30
+ remaining = Array.prototype.slice.call(content.children, index)
31
+ element = remaining[0]
32
+ node = @findNodeFor(block, remaining)
33
+
34
+ content.insertBefore(node, element)
35
+
36
+ elements = Array.prototype.slice.call(content.children, index)
37
+ for element in elements
38
+ element.remove()
39
+
40
+ @cursor.focus()
41
+
42
+ findNodeFor: (block, remaining) ->
43
+ node = block.markdown()
44
+
45
+ found = remaining.find (existing) ->
46
+ block.equals(existing, node)
47
+
48
+ found || node
49
+
24
50
  toString: =>
25
- @markdown
51
+ if @toString.cache?
52
+ return @toString.cache
53
+
54
+ texts = @blocks.map (block) ->
55
+ block.raw()
56
+
57
+ texts.join('\n')
58
+
@@ -1,5 +1,4 @@
1
1
  class Code
2
- @parserName: 'Code'
3
2
  multiline: true
4
3
 
5
4
  constructor: (match) ->
@@ -8,6 +7,11 @@ class Code
8
7
  @opened = true
9
8
 
10
9
 
10
+ raw: ->
11
+ texts = @matches.map (m) ->
12
+ m[0]
13
+ texts.join('\n')
14
+
11
15
  accepts: (text) ->
12
16
  @opened
13
17
 
@@ -17,17 +21,17 @@ class Code
17
21
  @matches.push(match)
18
22
  @opened = false
19
23
  else
24
+ @matches.push([text])
20
25
  @content += text + "\n"
21
26
 
22
27
  @content
23
28
 
24
- processContent: (callback) =>
25
29
 
26
- identical: (current, rendered) ->
30
+ equals: (current, rendered) ->
27
31
  current.outerHTML == rendered.outerHTML
28
32
 
29
33
  markdown: =>
30
- node = "<pre is='written-pre'><code></code></pre>".toHTML()
34
+ node = "<pre><code></code></pre>".toHTML()
31
35
  code = node.querySelector('code')
32
36
 
33
37
  if @matches[0][3]?
@@ -39,7 +43,7 @@ class Code
39
43
 
40
44
  code.insertAdjacentHTML('afterbegin', @matches[0][0])
41
45
  if !@opened
42
- code.insertAdjacentHTML('beforeend', @matches[1][0])
46
+ code.insertAdjacentHTML('beforeend', @matches[@matches.length - 1][0])
43
47
 
44
48
  node
45
49
 
@@ -59,33 +63,33 @@ class Code
59
63
 
60
64
  Code.rule = /^((~{3})([a-z]+)?)(?:\s(.*))?/i
61
65
 
62
- Written.Parsers.Block.register Code
63
-
64
- prototype = Object.create(HTMLPreElement.prototype)
66
+ Written.Parsers.register {
67
+ parser: Code
68
+ node: 'pre'
69
+ type: 'block'
70
+ getRange: (node, offset, walker) ->
71
+ range = document.createRange()
65
72
 
66
- prototype.getRange = (offset, walker) ->
67
- range = document.createRange()
73
+ if !node.firstChild?
74
+ range.setStart(node, 0)
75
+ else
76
+ while walker.nextNode()
77
+ if walker.currentNode.length < offset
78
+ offset -= walker.currentNode.length
79
+ continue
68
80
 
69
- if !@firstChild?
70
- range.setStart(this, 0)
71
- else
72
- while walker.nextNode()
73
- if walker.currentNode.length < offset
74
- offset -= walker.currentNode.length
75
- continue
81
+ range.setStart(walker.currentNode, offset)
82
+ break
76
83
 
77
- range.setStart(walker.currentNode, offset)
78
- break
84
+ range.collapse(true)
85
+ range
79
86
 
80
- range.collapse(true)
81
- range
82
- prototype.toString = ->
83
- if @textContent[@textContent.length - 1] == '\n'
84
- @textContent.substr(0, @textContent.length - 1)
85
- else
86
- @textContent
87
+ toString: (node) ->
88
+ if node.textContent[node.textContent.length - 1] == '\n'
89
+ node.textContent.substr(0, node.textContent.length - 1)
90
+ else
91
+ node.textContent
87
92
 
88
- document.registerElement('written-pre', {
89
- prototype: prototype
90
- extends: 'pre'
91
- })
93
+ highlightWith: (callback) ->
94
+ Code.prototype.highlight = callback
95
+ }
@@ -1,22 +1,21 @@
1
1
  class Header
2
- @parserName: 'Header'
3
2
  multiline: false
4
3
 
5
4
  constructor: (match) ->
6
5
  @match = match
7
6
 
8
- processContent: (callback) =>
9
- if @content?
10
- throw "Content Error: The content was already processed"
11
- return
7
+ equals: (current, rendered) ->
8
+ current.outerHTML == rendered.outerHTML
12
9
 
13
- @content = callback(@match[3])
10
+ text: ->
11
+ @match[3]
14
12
 
15
- identical: (current, rendered) ->
16
- current.outerHTML == rendered.outerHTML
13
+ raw: ->
14
+ @match[0]
17
15
 
18
16
  markdown: =>
19
- node = "<h#{@match[2].length} is='written-h#{@match[2].length}'>".toHTML()
17
+ node = "<h#{@match[2].length}>".toHTML()
18
+
20
19
  for text in @content
21
20
  if text.markdown?
22
21
  node.appendChild(text.markdown())
@@ -37,30 +36,29 @@ class Header
37
36
 
38
37
  Header.rule = /^((#{1,6})\s)(.*)$/i
39
38
 
40
- Written.Parsers.Block.register Header
41
39
 
42
40
  [1,2,3,4,5,6].forEach (size) ->
43
- prototype = Object.create(HTMLHeadingElement.prototype)
44
- prototype.getRange = (offset, walker) ->
45
- range = document.createRange()
41
+ Written.Parsers.register {
42
+ parser: Header
43
+ node: "h#{size}"
44
+ type: 'block'
45
+ getRange: (node, offset, walker) ->
46
+ range = document.createRange()
46
47
 
47
- if !@firstChild?
48
- range.setStart(this, 0)
49
- else
50
- while walker.nextNode()
51
- if walker.currentNode.length < offset
52
- offset -= walker.currentNode.length
53
- continue
48
+ if !node.firstChild?
49
+ range.setStart(node, 0)
50
+ else
51
+ while walker.nextNode()
52
+ if walker.currentNode.length < offset
53
+ offset -= walker.currentNode.length
54
+ continue
54
55
 
55
- range.setStart(walker.currentNode, offset)
56
- break
56
+ range.setStart(walker.currentNode, offset)
57
+ break
57
58
 
58
- range.collapse(true)
59
- range
60
- prototype.toString = ->
61
- @textContent
59
+ range.collapse(true)
60
+ range
62
61
 
63
- document.registerElement("written-h#{size}", {
64
- prototype: prototype
65
- extends: "h#{size}"
66
- })
62
+ toString: (node) ->
63
+ node.textContent
64
+ }
@@ -1,36 +1,24 @@
1
1
  class Image
2
- @parserName: 'Image'
3
2
  multiline: false
4
3
 
5
4
  constructor: (match) ->
6
5
  @match = match
7
6
 
8
- processContent: (callback) =>
9
- if @content?
10
- throw "Content Error: The content was already processed"
11
- return
7
+ raw: ->
8
+ @match[0]
12
9
 
13
- @content = callback(@match[2])
10
+ text: ->
11
+ @match[2]
14
12
 
15
- identical: (current, rendered) ->
16
-
17
- sameNode = (current, rendered) ->
18
- current.nodeName == rendered.nodeName
19
-
20
- sameImages = (current, rendered) ->
21
- (current? && current.dataset.image) == (rendered? && rendered.dataset.image)
22
-
23
- sameTexts = (current, rendered) ->
24
- (current? && current.innerHTML) == (rendered? && rendered.innerHTML)
25
-
26
-
27
- sameNode(current, rendered) &&
28
- sameImages(current.querySelector('img'), rendered.querySelector('img')) &&
29
- sameTexts(current.querySelector('figcaption'), rendered.querySelector('figcaption'))
13
+ equals: (current, rendered) ->
14
+ figcaption = current.querySelector('figcaption') || {}
15
+ img = current.querySelector('img') || {}
30
16
 
17
+ rendered.querySelector('figcaption').outerHTML == figcaption.outerHTML &&
18
+ rendered.querySelector('img').src == img.src
31
19
 
32
20
  markdown: =>
33
- figure = "<figure is='written-figure'><div contenteditable='false'><div class='progress'></div><input type='file' /><img/></div><figcaption /></figure>".toHTML()
21
+ figure = "<figure><div contenteditable='false'><input type='file' /><img/></div><figcaption /></figure>".toHTML()
34
22
  caption = figure.querySelector('figcaption')
35
23
  container = figure.querySelector('div')
36
24
 
@@ -47,7 +35,6 @@ class Image
47
35
  img = figure.querySelector('img')
48
36
  if @match[4]?
49
37
  img.src = img.dataset.image = @match[4]
50
- img.onerror = @placeholder.bind(this, img, true)
51
38
  else
52
39
  img.src = '/assets/written/placeholder.png'
53
40
 
@@ -74,40 +61,34 @@ class Image
74
61
  placeholder: (img, event, onerror = false) =>
75
62
  img.src = '/assets/written/placeholder.png'
76
63
  img.onerror = undefined
77
- if onerror
78
- img.classList.add 'error'
79
64
 
80
65
  Image.rule = /^(!{1}\[([^\]]*)\])(\(([^\s]*)?\))$/i
81
66
 
82
67
  Image.uploader = (uploader) ->
83
68
  Image::configure = uploader.initialize
84
69
 
85
- Written.Parsers.Block.register Image
86
70
 
87
- prototype = Object.create(HTMLElement.prototype)
71
+ Written.Parsers.register {
72
+ parser: Image
73
+ node: 'figure'
74
+ type: 'block'
75
+ getRange: (node, offset, walker) ->
76
+ range = document.createRange()
88
77
 
89
- prototype.getRange = (offset, walker) ->
90
- range = document.createRange()
91
-
92
- if !@firstChild?
93
- range.setStart(this, 0)
94
- else
95
- while walker.nextNode()
96
- if walker.currentNode.length < offset
97
- offset -= walker.currentNode.length
98
- continue
99
-
100
- range.setStart(walker.currentNode, offset)
101
- break
102
-
103
- range.collapse(true)
104
- range
78
+ if !node.firstChild?
79
+ range.setStart(this, 0)
80
+ else
81
+ while walker.nextNode()
82
+ if walker.currentNode.length < offset
83
+ offset -= walker.currentNode.length
84
+ continue
105
85
 
106
- prototype.toString = ->
107
- (@querySelector('figcaption') || this).textContent
86
+ range.setStart(walker.currentNode, offset)
87
+ break
108
88
 
109
- document.registerElement('written-figure', {
110
- prototype: prototype
111
- extends: 'figure'
112
- })
89
+ range.collapse(true)
90
+ range
113
91
 
92
+ toString: (node) ->
93
+ (node.querySelector('figcaption') || node).textContent
94
+ }
@@ -1,5 +1,4 @@
1
1
  class OList
2
- @parserName: 'OList'
3
2
  multiline: true
4
3
 
5
4
  constructor: (match) ->
@@ -14,23 +13,25 @@ class OList
14
13
  append: (text) ->
15
14
  @matches.push(OList.rule.exec(text))
16
15
 
17
- processContent: (callback) =>
18
- if @content?
19
- throw "Content Error: The content was already processed"
20
- return
16
+ equals: (current, rendered) ->
17
+ current.outerHTML == rendered.outerHTML
18
+
19
+ raw: ->
20
+ texts = @matches.map (match) ->
21
+ match[0]
22
+
23
+ texts.join('\n')
21
24
 
22
- lines = @matches.map (match) ->
25
+ text: ->
26
+ texts = @matches.map (match) ->
23
27
  match[2]
24
-
25
- @content = callback(lines)
26
28
 
27
- identical: (current, rendered) ->
28
- current.outerHTML == rendered.outerHTML
29
+ texts.join('\n')
29
30
 
30
31
  markdown: =>
31
- node = "<ol is='written-ol'></ol>".toHTML()
32
+ node = "<ol></ol>".toHTML()
32
33
  for line, index in @content
33
- li = "<li is='written-li'>".toHTML()
34
+ li = "<li>".toHTML()
34
35
  li.appendChild(document.createTextNode(@matches[index][1]))
35
36
 
36
37
  for text in line
@@ -61,41 +62,37 @@ class OList
61
62
 
62
63
  OList.rule = /^(\d+\.\s)(.*)/i
63
64
 
64
- Written.Parsers.Block.register OList
65
-
66
- prototype = Object.create(HTMLOListElement.prototype)
67
-
68
- prototype.toString = ->
69
- texts = Array.prototype.slice.call(@children).map (li) ->
70
- li.toString()
71
- texts.join("\n")
72
-
73
- prototype.getRange = (offset, walker) ->
74
- range = document.createRange()
75
- if !@firstChild?
76
- range.setStart(this, 0)
77
- return
78
-
79
- li = this.firstElementChild
80
-
81
- while walker.nextNode()
82
- if !li.contains(walker.currentNode)
83
- newList = walker.currentNode
84
- while newList? && !(newList instanceof HTMLLIElement)
85
- newList = newList.parentElement
86
- li = newList
87
- offset--
88
-
89
- if walker.currentNode.length < offset
90
- offset -= walker.currentNode.length
91
- continue
92
- range.setStart(walker.currentNode, offset)
93
- break
94
-
95
- range.collapse(true)
96
- range
65
+ Written.Parsers.register {
66
+ parser: OList
67
+ node: 'ol'
68
+ type: 'block'
69
+ getRange: (node, offset, walker) ->
70
+ range = document.createRange()
71
+ if !node.firstChild?
72
+ range.setStart(node, 0)
73
+ return
97
74
 
98
- document.registerElement('written-ol', {
99
- prototype: prototype
100
- extends: 'ol'
101
- })
75
+ li = node.firstElementChild
76
+
77
+ while walker.nextNode()
78
+ if !li.contains(walker.currentNode)
79
+ newList = walker.currentNode
80
+ while newList? && !(newList instanceof HTMLLIElement)
81
+ newList = newList.parentElement
82
+ li = newList
83
+ offset--
84
+
85
+ if walker.currentNode.length < offset
86
+ offset -= walker.currentNode.length
87
+ continue
88
+ range.setStart(walker.currentNode, offset)
89
+ break
90
+
91
+ range.collapse(true)
92
+ range
93
+
94
+ toString: (node) ->
95
+ texts = Array.prototype.slice.call(node.children).map (li) ->
96
+ li.textContent
97
+ texts.join("\n")
98
+ }