written 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.babelrc +3 -0
- data/.gitignore +2 -0
- data/.ruby-version +1 -0
- data/README.md +18 -8
- data/Rakefile +1 -0
- data/compile-coffee.js +14 -0
- data/lib/written/app/assets/javascripts/written/attachments/image.coffee +4 -4
- data/lib/written/app/assets/javascripts/written/core/content.coffee +18 -29
- data/lib/written/app/assets/javascripts/written/core/cursor.coffee +8 -7
- data/lib/written/app/assets/javascripts/written/core/document.coffee +9 -8
- data/lib/written/app/assets/javascripts/written/core/{extensions/string.coffee → html.coffee} +3 -2
- data/lib/written/app/assets/javascripts/written/core/parsers.coffee +179 -0
- data/lib/written/app/assets/javascripts/written/parsers/block/code.coffee +11 -28
- data/lib/written/app/assets/javascripts/written/parsers/block/heading.coffee +37 -39
- data/lib/written/app/assets/javascripts/written/parsers/block/image.coffee +15 -39
- data/lib/written/app/assets/javascripts/written/parsers/block/olist.coffee +23 -20
- data/lib/written/app/assets/javascripts/written/parsers/block/paragraph.coffee +14 -34
- data/lib/written/app/assets/javascripts/written/parsers/block/quote.coffee +19 -38
- data/lib/written/app/assets/javascripts/written/parsers/block/ulist.coffee +20 -18
- data/lib/written/app/assets/javascripts/written/parsers/inline/bold.coffee +23 -0
- data/lib/written/app/assets/javascripts/written/parsers/inline/code.coffee +8 -27
- data/lib/written/app/assets/javascripts/written/parsers/inline/italic.coffee +8 -28
- data/lib/written/app/assets/javascripts/written/parsers/inline/link.coffee +8 -27
- data/lib/written/app/assets/stylesheets/written.scss +4 -0
- data/lib/written/version.rb +1 -1
- data/package.json +25 -0
- data/test/javascript/parsers/block/code.js +58 -0
- data/test/javascript/parsers/block/header.js +40 -0
- data/test/javascript/parsers/block/image.js +39 -0
- data/test/javascript/parsers/block/olist.js +41 -0
- data/test/javascript/parsers/block/paragraph.js +42 -0
- data/test/javascript/parsers/block/quote.js +41 -0
- data/test/javascript/parsers/block/ulist.js +40 -0
- data/test/javascript/parsers/inline/code.js +41 -0
- data/test/javascript/parsers/inline/italic.js +42 -0
- data/test/javascript/parsers/inline/link.js +42 -0
- data/test/javascript/parsers/inline/strong.js +41 -0
- data/test/server/app/assets/javascripts/application.coffee +8 -9
- data/test/server/app/assets/javascripts/secret.coffee +6 -0
- data/test/server/app/views/posts/show.html.erb +4 -4
- metadata +21 -12
- data/lib/written/app/assets/javascripts/written/parsers/inline/strong.coffee +0 -43
- data/lib/written/app/assets/javascripts/written/parsers/parsers.coffee +0 -95
- data/test/javascript/assertions/assert.coffee +0 -3
- data/test/javascript/polyfills.coffee +0 -2
- data/test/javascript/polyfills/HTMLULListElement.coffee +0 -0
- data/test/javascript/polyfills/Text.coffee +0 -0
- data/test/javascript/runner.coffee +0 -46
- data/test/javascript/tests/initialization.coffee +0 -16
- data/test/javascript/tests/parsing.coffee +0 -9
@@ -1,4 +1,4 @@
|
|
1
|
-
class Code
|
1
|
+
class Code extends Written.Parsers.Block
|
2
2
|
multiline: true
|
3
3
|
|
4
4
|
constructor: (match) ->
|
@@ -7,7 +7,7 @@ class Code
|
|
7
7
|
@opened = true
|
8
8
|
|
9
9
|
|
10
|
-
|
10
|
+
outerText: ->
|
11
11
|
texts = @matches.map (m) ->
|
12
12
|
m[0]
|
13
13
|
texts.join('\n')
|
@@ -16,7 +16,7 @@ class Code
|
|
16
16
|
@opened
|
17
17
|
|
18
18
|
append: (text) ->
|
19
|
-
match =
|
19
|
+
match = /^(~{3})$/i.exec(text)
|
20
20
|
if match?
|
21
21
|
@matches.push(match)
|
22
22
|
@opened = false
|
@@ -30,8 +30,8 @@ class Code
|
|
30
30
|
equals: (current, rendered) ->
|
31
31
|
current.outerHTML == rendered.outerHTML
|
32
32
|
|
33
|
-
|
34
|
-
node = "<pre><code></code></pre>"
|
33
|
+
toEditor: =>
|
34
|
+
node = Written.toHTML("<pre><code></code></pre>")
|
35
35
|
code = node.querySelector('code')
|
36
36
|
|
37
37
|
if @matches[0][3]?
|
@@ -47,8 +47,8 @@ class Code
|
|
47
47
|
|
48
48
|
node
|
49
49
|
|
50
|
-
|
51
|
-
node = "<pre><code></code></pre>"
|
50
|
+
toHTML: =>
|
51
|
+
node = Written.toHTML("<pre><code></code></pre>")
|
52
52
|
code = node.querySelector('code')
|
53
53
|
|
54
54
|
if @matches[0][3]?
|
@@ -57,33 +57,16 @@ class Code
|
|
57
57
|
if @matches[0][4]?
|
58
58
|
code.insertAdjacentHTML('beforebegin', "<header>#{@matches[0][4]}</header>")
|
59
59
|
|
60
|
-
code.appendChild(document.createTextNode(@content))
|
60
|
+
code.appendChild(document.createTextNode(@content.slice(1, -1)))
|
61
61
|
|
62
62
|
node
|
63
63
|
|
64
|
-
Code.rule = /^((~{3})([a-z]+)?)(?:\s(.*))?/i
|
65
|
-
|
66
64
|
Written.Parsers.register {
|
67
65
|
parser: Code
|
68
|
-
|
66
|
+
name: 'code'
|
67
|
+
nodes: ['pre']
|
69
68
|
type: 'block'
|
70
|
-
|
71
|
-
range = document.createRange()
|
72
|
-
|
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
|
80
|
-
|
81
|
-
range.setStart(walker.currentNode, offset)
|
82
|
-
break
|
83
|
-
|
84
|
-
range.collapse(true)
|
85
|
-
range
|
86
|
-
|
69
|
+
rule: /^((~{3})([a-z]+)?)(?:\s(.*))?/i
|
87
70
|
toString: (node) ->
|
88
71
|
if node.textContent[node.textContent.length - 1] == '\n'
|
89
72
|
node.textContent.substr(0, node.textContent.length - 1)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class Header
|
1
|
+
class Header extends Written.Parsers.Block
|
2
2
|
multiline: false
|
3
3
|
|
4
4
|
constructor: (match) ->
|
@@ -7,58 +7,56 @@ class Header
|
|
7
7
|
equals: (current, rendered) ->
|
8
8
|
current.outerHTML == rendered.outerHTML
|
9
9
|
|
10
|
-
|
10
|
+
innerText: ->
|
11
11
|
@match[3]
|
12
12
|
|
13
|
-
|
13
|
+
outerText: ->
|
14
14
|
@match[0]
|
15
15
|
|
16
|
-
|
17
|
-
node = "<h#{@match[2].length}>"
|
16
|
+
toEditor: =>
|
17
|
+
node = Written.toHTML("<h#{@match[2].length}>")
|
18
18
|
|
19
19
|
for text in @content
|
20
|
-
if text.
|
21
|
-
node.appendChild(text.
|
20
|
+
if text.toEditor?
|
21
|
+
node.appendChild(text.toEditor())
|
22
22
|
else
|
23
23
|
node.appendChild(document.createTextNode(text))
|
24
24
|
|
25
25
|
node.insertAdjacentHTML('afterbegin', @match[1])
|
26
26
|
node
|
27
27
|
|
28
|
-
|
29
|
-
node = "<h#{@match[2].length}>"
|
28
|
+
toHTML: =>
|
29
|
+
node = Written.toHTML("<h#{@match[2].length}>")
|
30
30
|
for text in @content
|
31
|
-
if text.
|
32
|
-
node.appendChild(text.
|
31
|
+
if text.toHTML?
|
32
|
+
node.appendChild(text.toHTML())
|
33
33
|
else
|
34
34
|
node.appendChild(document.createTextNode(text))
|
35
35
|
node
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
[
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
node.textContent
|
64
|
-
}
|
37
|
+
Written.Parsers.register {
|
38
|
+
parser: Header
|
39
|
+
name: 'header'
|
40
|
+
nodes: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
|
41
|
+
type: 'block'
|
42
|
+
rule: /^((#{1,6})\s)(.*)$/i
|
43
|
+
getRange: (node, offset, walker) ->
|
44
|
+
range = document.createRange()
|
45
|
+
|
46
|
+
if !node.firstChild?
|
47
|
+
range.setStart(node, 0)
|
48
|
+
else
|
49
|
+
while walker.nextNode()
|
50
|
+
if walker.currentNode.length < offset
|
51
|
+
offset -= walker.currentNode.length
|
52
|
+
continue
|
53
|
+
|
54
|
+
range.setStart(walker.currentNode, offset)
|
55
|
+
break
|
56
|
+
|
57
|
+
range.collapse(true)
|
58
|
+
range
|
59
|
+
|
60
|
+
toString: (node) ->
|
61
|
+
node.textContent
|
62
|
+
}
|
@@ -1,13 +1,13 @@
|
|
1
|
-
class Image
|
1
|
+
class Image extends Written.Parsers.Block
|
2
2
|
multiline: false
|
3
3
|
|
4
4
|
constructor: (match) ->
|
5
5
|
@match = match
|
6
6
|
|
7
|
-
|
7
|
+
outerText: ->
|
8
8
|
@match[0]
|
9
9
|
|
10
|
-
|
10
|
+
innerText: ->
|
11
11
|
@match[2]
|
12
12
|
|
13
13
|
equals: (current, rendered) ->
|
@@ -17,14 +17,14 @@ class Image
|
|
17
17
|
rendered.querySelector('figcaption').outerHTML == figcaption.outerHTML &&
|
18
18
|
rendered.querySelector('img').src == img.src
|
19
19
|
|
20
|
-
|
21
|
-
figure = "<figure><div contenteditable='false'><img/></div><figcaption /></figure>"
|
20
|
+
toEditor: =>
|
21
|
+
figure = Written.toHTML("<figure><div contenteditable='false'><img/></div><figcaption /></figure>")
|
22
22
|
caption = figure.querySelector('figcaption')
|
23
23
|
container = figure.querySelector('div')
|
24
24
|
|
25
25
|
for text in @content
|
26
|
-
if text.
|
27
|
-
caption.appendChild(text.
|
26
|
+
if text.toEditor?
|
27
|
+
caption.appendChild(text.toEditor())
|
28
28
|
else
|
29
29
|
caption.appendChild(document.createTextNode(text))
|
30
30
|
|
@@ -34,25 +34,22 @@ class Image
|
|
34
34
|
|
35
35
|
img = figure.querySelector('img')
|
36
36
|
if @match[4]?
|
37
|
-
img.src =
|
37
|
+
img.src = @match[4]
|
38
38
|
else
|
39
39
|
img.src = '/assets/written/placeholder.png'
|
40
40
|
|
41
|
-
if @configure?
|
42
|
-
@configure(figure)
|
43
|
-
|
44
41
|
figure
|
45
42
|
|
46
|
-
|
47
|
-
figure = "<figure><
|
43
|
+
toHTML: ->
|
44
|
+
figure = Written.toHTML("<figure><img/><figcaption /></figure>")
|
48
45
|
img = figure.querySelector('img')
|
49
46
|
caption = figure.querySelector('figcaption')
|
50
47
|
|
51
48
|
img.src = @match[4]
|
52
49
|
|
53
50
|
for text in @content
|
54
|
-
if text.
|
55
|
-
caption.appendChild(text.
|
51
|
+
if text.toHTML?
|
52
|
+
caption.appendChild(text.toHTML())
|
56
53
|
else
|
57
54
|
caption.appendChild(document.createTextNode(text))
|
58
55
|
|
@@ -62,33 +59,12 @@ class Image
|
|
62
59
|
img.src = '/assets/written/placeholder.png'
|
63
60
|
img.onerror = undefined
|
64
61
|
|
65
|
-
Image.rule = /^(!{1}\[([^\]]*)\])(\(([^\s]*)?\))$/i
|
66
|
-
|
67
|
-
Image.uploader = (uploader) ->
|
68
|
-
Image::configure = uploader.initialize
|
69
|
-
|
70
|
-
|
71
62
|
Written.Parsers.register {
|
72
63
|
parser: Image
|
73
|
-
|
64
|
+
name: 'image'
|
65
|
+
nodes: ['figure']
|
74
66
|
type: 'block'
|
75
|
-
|
76
|
-
range = document.createRange()
|
77
|
-
|
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
|
85
|
-
|
86
|
-
range.setStart(walker.currentNode, offset)
|
87
|
-
break
|
88
|
-
|
89
|
-
range.collapse(true)
|
90
|
-
range
|
91
|
-
|
67
|
+
rule: /^(!{1}\[([^\]]*)\])(\(([^\s]*)?\))$/i
|
92
68
|
toString: (node) ->
|
93
69
|
(node.querySelector('figcaption') || node).textContent
|
94
70
|
}
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
RULE = /^(\d+\.\s)(.*)/i
|
2
|
+
|
3
|
+
class OList extends Written.Parsers.Block
|
2
4
|
multiline: true
|
3
5
|
|
4
6
|
constructor: (match) ->
|
@@ -6,37 +8,38 @@ class OList
|
|
6
8
|
@opened = true
|
7
9
|
|
8
10
|
accepts: (text) ->
|
9
|
-
@opened =
|
11
|
+
@opened = RULE.test(text)
|
10
12
|
|
11
13
|
@opened
|
12
14
|
|
13
15
|
append: (text) ->
|
14
|
-
@matches.push(
|
16
|
+
@matches.push(RULE.exec(text))
|
15
17
|
|
16
18
|
equals: (current, rendered) ->
|
17
19
|
current.outerHTML == rendered.outerHTML
|
18
20
|
|
19
|
-
|
21
|
+
innerText: ->
|
20
22
|
texts = @matches.map (match) ->
|
21
|
-
match[
|
23
|
+
match[2]
|
22
24
|
|
23
25
|
texts.join('\n')
|
24
26
|
|
25
|
-
|
27
|
+
outerText: ->
|
26
28
|
texts = @matches.map (match) ->
|
27
|
-
match[
|
29
|
+
match[0]
|
28
30
|
|
29
31
|
texts.join('\n')
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
+
|
34
|
+
toEditor: =>
|
35
|
+
node = Written.toHTML("<ol></ol>")
|
33
36
|
for line, index in @content
|
34
|
-
li = "<li>"
|
37
|
+
li = Written.toHTML("<li>")
|
35
38
|
li.appendChild(document.createTextNode(@matches[index][1]))
|
36
39
|
|
37
40
|
for text in line
|
38
|
-
if text.
|
39
|
-
li.appendChild(text.
|
41
|
+
if text.toEditor?
|
42
|
+
li.appendChild(text.toEditor())
|
40
43
|
else
|
41
44
|
li.appendChild(document.createTextNode(text.toString()))
|
42
45
|
|
@@ -44,14 +47,14 @@ class OList
|
|
44
47
|
|
45
48
|
node
|
46
49
|
|
47
|
-
|
48
|
-
node = "<ol></ol>"
|
50
|
+
toHTML: =>
|
51
|
+
node = Written.toHTML("<ol></ol>")
|
49
52
|
for line, index in @content
|
50
|
-
li = "<li>"
|
53
|
+
li = Written.toHTML("<li>")
|
51
54
|
|
52
55
|
for text in line
|
53
|
-
if text.
|
54
|
-
li.appendChild(text.
|
56
|
+
if text.toHTML?
|
57
|
+
li.appendChild(text.toHTML())
|
55
58
|
else
|
56
59
|
li.appendChild(document.createTextNode(text.toString()))
|
57
60
|
|
@@ -60,12 +63,12 @@ class OList
|
|
60
63
|
node
|
61
64
|
|
62
65
|
|
63
|
-
OList.rule = /^(\d+\.\s)(.*)/i
|
64
|
-
|
65
66
|
Written.Parsers.register {
|
66
67
|
parser: OList
|
67
|
-
|
68
|
+
name: 'olist'
|
69
|
+
nodes: ['ol']
|
68
70
|
type: 'block'
|
71
|
+
rule: RULE
|
69
72
|
getRange: (node, offset, walker) ->
|
70
73
|
range = document.createRange()
|
71
74
|
if !node.firstChild?
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class Paragraph
|
1
|
+
class Paragraph extends Written.Parsers.Block
|
2
2
|
multiline: false
|
3
3
|
|
4
4
|
constructor: (match) ->
|
@@ -7,27 +7,27 @@ class Paragraph
|
|
7
7
|
equals: (current, rendered) ->
|
8
8
|
current.outerHTML == rendered.outerHTML
|
9
9
|
|
10
|
-
|
10
|
+
innerText: =>
|
11
11
|
@match[0]
|
12
12
|
|
13
|
-
|
13
|
+
outerText: =>
|
14
14
|
@match[0]
|
15
15
|
|
16
|
-
|
17
|
-
node = "<p>"
|
16
|
+
toEditor: =>
|
17
|
+
node = Written.toHTML("<p>")
|
18
18
|
for text in @content
|
19
|
-
if text.
|
20
|
-
node.appendChild(text.
|
19
|
+
if text.toEditor?
|
20
|
+
node.appendChild(text.toEditor())
|
21
21
|
else
|
22
22
|
node.appendChild(document.createTextNode(text))
|
23
23
|
|
24
24
|
node
|
25
25
|
|
26
|
-
|
27
|
-
node = "<p>"
|
26
|
+
toHTML: =>
|
27
|
+
node = Written.toHTML("<p>")
|
28
28
|
for text in @content
|
29
|
-
if text.
|
30
|
-
node.appendChild(text.
|
29
|
+
if text.toHTML?
|
30
|
+
node.appendChild(text.toHTML())
|
31
31
|
else
|
32
32
|
node.appendChild(document.createTextNode(text))
|
33
33
|
|
@@ -35,31 +35,11 @@ class Paragraph
|
|
35
35
|
|
36
36
|
|
37
37
|
|
38
|
-
Paragraph.rule = /.*/i
|
39
|
-
|
40
38
|
Written.Parsers.register {
|
41
39
|
parser: Paragraph
|
42
|
-
|
40
|
+
name: 'paragraph'
|
41
|
+
nodes: ['p']
|
43
42
|
type: 'block'
|
43
|
+
rule: /.*/i
|
44
44
|
default: true
|
45
|
-
getRange: (node, offset, walker) ->
|
46
|
-
range = document.createRange()
|
47
|
-
|
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
|
55
|
-
|
56
|
-
range.setStart(walker.currentNode, offset)
|
57
|
-
break
|
58
|
-
|
59
|
-
range.collapse(true)
|
60
|
-
range
|
61
|
-
|
62
|
-
toString: (node) ->
|
63
|
-
node.textContent
|
64
|
-
|
65
45
|
}
|