written 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.babelrc +3 -0
  3. data/.gitignore +2 -0
  4. data/.ruby-version +1 -0
  5. data/README.md +18 -8
  6. data/Rakefile +1 -0
  7. data/compile-coffee.js +14 -0
  8. data/lib/written/app/assets/javascripts/written/attachments/image.coffee +4 -4
  9. data/lib/written/app/assets/javascripts/written/core/content.coffee +18 -29
  10. data/lib/written/app/assets/javascripts/written/core/cursor.coffee +8 -7
  11. data/lib/written/app/assets/javascripts/written/core/document.coffee +9 -8
  12. data/lib/written/app/assets/javascripts/written/core/{extensions/string.coffee → html.coffee} +3 -2
  13. data/lib/written/app/assets/javascripts/written/core/parsers.coffee +179 -0
  14. data/lib/written/app/assets/javascripts/written/parsers/block/code.coffee +11 -28
  15. data/lib/written/app/assets/javascripts/written/parsers/block/heading.coffee +37 -39
  16. data/lib/written/app/assets/javascripts/written/parsers/block/image.coffee +15 -39
  17. data/lib/written/app/assets/javascripts/written/parsers/block/olist.coffee +23 -20
  18. data/lib/written/app/assets/javascripts/written/parsers/block/paragraph.coffee +14 -34
  19. data/lib/written/app/assets/javascripts/written/parsers/block/quote.coffee +19 -38
  20. data/lib/written/app/assets/javascripts/written/parsers/block/ulist.coffee +20 -18
  21. data/lib/written/app/assets/javascripts/written/parsers/inline/bold.coffee +23 -0
  22. data/lib/written/app/assets/javascripts/written/parsers/inline/code.coffee +8 -27
  23. data/lib/written/app/assets/javascripts/written/parsers/inline/italic.coffee +8 -28
  24. data/lib/written/app/assets/javascripts/written/parsers/inline/link.coffee +8 -27
  25. data/lib/written/app/assets/stylesheets/written.scss +4 -0
  26. data/lib/written/version.rb +1 -1
  27. data/package.json +25 -0
  28. data/test/javascript/parsers/block/code.js +58 -0
  29. data/test/javascript/parsers/block/header.js +40 -0
  30. data/test/javascript/parsers/block/image.js +39 -0
  31. data/test/javascript/parsers/block/olist.js +41 -0
  32. data/test/javascript/parsers/block/paragraph.js +42 -0
  33. data/test/javascript/parsers/block/quote.js +41 -0
  34. data/test/javascript/parsers/block/ulist.js +40 -0
  35. data/test/javascript/parsers/inline/code.js +41 -0
  36. data/test/javascript/parsers/inline/italic.js +42 -0
  37. data/test/javascript/parsers/inline/link.js +42 -0
  38. data/test/javascript/parsers/inline/strong.js +41 -0
  39. data/test/server/app/assets/javascripts/application.coffee +8 -9
  40. data/test/server/app/assets/javascripts/secret.coffee +6 -0
  41. data/test/server/app/views/posts/show.html.erb +4 -4
  42. metadata +21 -12
  43. data/lib/written/app/assets/javascripts/written/parsers/inline/strong.coffee +0 -43
  44. data/lib/written/app/assets/javascripts/written/parsers/parsers.coffee +0 -95
  45. data/test/javascript/assertions/assert.coffee +0 -3
  46. data/test/javascript/polyfills.coffee +0 -2
  47. data/test/javascript/polyfills/HTMLULListElement.coffee +0 -0
  48. data/test/javascript/polyfills/Text.coffee +0 -0
  49. data/test/javascript/runner.coffee +0 -46
  50. data/test/javascript/tests/initialization.coffee +0 -16
  51. data/test/javascript/tests/parsing.coffee +0 -9
@@ -1,4 +1,5 @@
1
- class Quote
1
+ RULE = /^(>\s)(.*)/i
2
+ class Quote extends Written.Parsers.Block
2
3
  multiline: true
3
4
 
4
5
  constructor: (match) ->
@@ -6,18 +7,18 @@ class Quote
6
7
  @opened = true
7
8
 
8
9
  accepts: (text) ->
9
- @opened = Quote.rule.test(text)
10
+ @opened = RULE.test(text)
10
11
 
11
12
  append: (text) ->
12
- @matches.push(Quote.rule.exec(text))
13
+ @matches.push(RULE.exec(text))
13
14
 
14
- raw: ->
15
+ outerText: ->
15
16
  lines = @matches.map (match) ->
16
17
  match[0]
17
18
 
18
19
  lines.join('\n')
19
20
 
20
- text: =>
21
+ innerText: =>
21
22
  lines = @matches.map (match) ->
22
23
  match[2]
23
24
 
@@ -26,15 +27,15 @@ class Quote
26
27
  equals: (current, rendered) ->
27
28
  current.outerHTML == rendered.outerHTML
28
29
 
29
- markdown: =>
30
- node = "<blockquote></blockquote>".toHTML()
30
+ toEditor: =>
31
+ node = Written.toHTML("<blockquote></blockquote>")
31
32
  for line, index in @content
32
- p = "<p>".toHTML()
33
+ p = Written.toHTML("<p>")
33
34
  p.appendChild(document.createTextNode(@matches[index][1]))
34
35
 
35
36
  for text in line
36
- if text.markdown?
37
- p.appendChild(text.markdown())
37
+ if text.toEditor?
38
+ p.appendChild(text.toEditor())
38
39
  else
39
40
  p.appendChild(document.createTextNode(text.toString()))
40
41
 
@@ -44,14 +45,14 @@ class Quote
44
45
 
45
46
  node
46
47
 
47
- html: =>
48
- node = "<blockquote></blockquote>".toHTML()
48
+ toHTML: =>
49
+ node = Written.toHTML("<blockquote></blockquote>")
49
50
  for line, index in @content
50
- p = "<p>".toHTML()
51
+ p = Written.toHTML("<p>")
51
52
 
52
53
  for text in line
53
- if text.html?
54
- p.appendChild(text.html())
54
+ if text.toHTML?
55
+ p.appendChild(text.toHTML())
55
56
  else
56
57
  p.appendChild(document.createTextNode(text.toString()))
57
58
 
@@ -62,30 +63,10 @@ class Quote
62
63
  node
63
64
 
64
65
 
65
- Quote.rule = /^(>\s)(.*)/i
66
-
67
66
  Written.Parsers.register {
68
67
  parser: Quote
69
- node: 'blockquote'
68
+ name: 'quote'
69
+ nodes: ['blockquote']
70
70
  type: 'block'
71
- getRange: (node, offset, walker) ->
72
- range = document.createRange()
73
-
74
- if !node.firstChild?
75
- range.setStart(this, 0)
76
- else
77
- while walker.nextNode()
78
- if walker.currentNode.length < offset
79
- offset -= walker.currentNode.length
80
- continue
81
-
82
- range.setStart(walker.currentNode, offset)
83
- break
84
-
85
- range.collapse(true)
86
- range
87
-
88
- toString: (node) ->
89
- node.textContent
90
-
71
+ rule: RULE
91
72
  }
@@ -1,41 +1,43 @@
1
- class UList
1
+ RULE = /^(-\s)(.*)/i
2
+
3
+ class UList extends Written.Parsers.Block
2
4
  multiline: true
3
5
 
4
6
  constructor: (match) ->
5
7
  @matches = [match]
6
8
 
7
9
  accepts: (text) ->
8
- @opened = UList.rule.test(text)
10
+ @opened = RULE.test(text)
9
11
 
10
12
  @opened
11
13
 
12
- raw: ->
14
+ outerText: ->
13
15
  texts = @matches.map (match) ->
14
16
  match[0]
15
17
 
16
18
  texts.join('\n')
17
19
 
18
- text: ->
20
+ innerText: ->
19
21
  texts = @matches.map (match) ->
20
22
  match[2]
21
23
 
22
24
  texts.join('\n')
23
25
 
24
26
  append: (text) ->
25
- @matches.push(UList.rule.exec(text))
27
+ @matches.push(RULE.exec(text))
26
28
 
27
29
  equals: (current, rendered) ->
28
30
  current.outerHTML == rendered.outerHTML
29
31
 
30
- markdown: =>
31
- node = "<ul></ul>".toHTML()
32
+ toEditor: =>
33
+ node = Written.toHTML("<ul></ul>")
32
34
  for line, index in @content
33
- li = "<li>".toHTML()
35
+ li = Written.toHTML("<li>")
34
36
  li.appendChild(document.createTextNode(@matches[index][1]))
35
37
 
36
38
  for text in line
37
- if text.markdown?
38
- li.appendChild(text.markdown())
39
+ if text.toEditor?
40
+ li.appendChild(text.toEditor())
39
41
  else
40
42
  li.appendChild(document.createTextNode(text.toString()))
41
43
 
@@ -43,14 +45,14 @@ class UList
43
45
 
44
46
  node
45
47
 
46
- html: =>
47
- node = "<ul></ul>".toHTML()
48
+ toHTML: =>
49
+ node = Written.toHTML("<ul></ul>")
48
50
  for line, index in @content
49
- li = "<li>".toHTML()
51
+ li = Written.toHTML("<li>")
50
52
 
51
53
  for text in line
52
- if text.html?
53
- li.appendChild(text.html())
54
+ if text.toHTML?
55
+ li.appendChild(text.toHTML())
54
56
  else
55
57
  li.appendChild(document.createTextNode(text.toString()))
56
58
 
@@ -58,12 +60,12 @@ class UList
58
60
 
59
61
  node
60
62
 
61
- UList.rule = /^(-\s)(.*)/i
62
-
63
63
  Written.Parsers.register {
64
64
  parser: UList
65
- node: 'ul'
65
+ name: 'ulist'
66
+ nodes: ['ul']
66
67
  type: 'block'
68
+ rule: RULE
67
69
  getRange: (node, offset, walker) ->
68
70
  range = document.createRange()
69
71
  if !node.firstChild?
@@ -0,0 +1,23 @@
1
+ class Bold extends Written.Parsers.Inline
2
+ constructor: (match) ->
3
+ @match = match
4
+
5
+ index: =>
6
+ @match.index
7
+
8
+ length: =>
9
+ @match[0].length
10
+
11
+ toEditor: =>
12
+ Written.toHTML("<strong>#{@match[0]}</strong>")
13
+
14
+ toHTML: =>
15
+ Written.toHTML("<strong>#{@match[3]}</strong>")
16
+
17
+ Written.Parsers.register {
18
+ parser: Bold
19
+ name: 'bold'
20
+ nodes: ['strong']
21
+ type: 'inline'
22
+ rule: /((\*{1})([^\*]+)(\*{1}))/gi
23
+ }
@@ -1,4 +1,4 @@
1
- class Code
1
+ class Code extends Written.Parsers.Inline
2
2
  constructor: (match) ->
3
3
  @match = match
4
4
 
@@ -8,8 +8,8 @@ class Code
8
8
  length: =>
9
9
  @match[0].length
10
10
 
11
- markdown: =>
12
- node = "<code>#{this.match[0]}</code>".toHTML()
11
+ toEditor: =>
12
+ node = Written.toHTML("<code>#{this.match[0]}</code>")
13
13
  if @match[3]?
14
14
  node.classList.add("language-#{@match[3]}")
15
15
 
@@ -17,40 +17,21 @@ class Code
17
17
  @highlight(node)
18
18
  node
19
19
 
20
- html: =>
21
- node = "<code>#{this.match[4]}</code>".toHTML()
20
+ toHTML: =>
21
+ node = Written.toHTML("<code>#{this.match[4]}</code>")
22
22
  if @match[3]?
23
23
  node.classList.add("language-#{@match[3]}")
24
24
 
25
25
  node
26
26
 
27
27
 
28
- Code.rule = /((~{3})([a-z]+)?)\s(.+)?(~{3})/gi
29
28
 
30
29
  Written.Parsers.register {
31
30
  parser: Code
32
- node: 'code'
31
+ name: 'code'
32
+ nodes: ['code']
33
33
  type: 'inline'
34
- getRange: (node, offset, walker) ->
35
- range = document.createRange()
36
-
37
- if !@firstChild?
38
- range.setStart(this, 0)
39
- else
40
- while walker.nextNode()
41
- if walker.currentNode.length < offset
42
- offset -= walker.currentNode.length
43
- continue
44
-
45
- range.setStart(walker.currentNode, offset)
46
- break
47
-
48
- range.collapse(true)
49
- range
50
-
51
- toString: (node) ->
52
- node.textContent
53
-
34
+ rule: /((~{3})([a-z]+)?)\s(.+)?(~{3})/gi
54
35
  highlightWith: (callback) ->
55
36
  Code.prototype.highlight = callback
56
37
  }
@@ -1,4 +1,4 @@
1
- class Italic
1
+ class Italic extends Written.Parsers.Inline
2
2
  constructor: (match) ->
3
3
  @match = match
4
4
 
@@ -8,36 +8,16 @@ class Italic
8
8
  length: =>
9
9
  @match[0].length
10
10
 
11
- markdown: =>
12
- "<em>#{this.match[1]}</em>".toHTML()
13
-
14
- html: ->
15
- "<em>#{this.match[3]}</em>".toHTML()
16
-
17
- Italic.rule = /((_{1})([^_]+)(_{1}))/gi
11
+ toEditor: =>
12
+ Written.toHTML("<em>#{this.match[1]}</em>")
18
13
 
14
+ toHTML: ->
15
+ Written.toHTML("<em>#{this.match[3]}</em>")
19
16
 
20
17
  Written.Parsers.register {
21
18
  parser: Italic
22
- node: 'em'
19
+ name: 'italic'
20
+ nodes: ['em']
23
21
  type: 'inline'
24
- getRange: (node, offset, walker) ->
25
- range = document.createRange()
26
-
27
- if !node.firstChild?
28
- range.setStart(this, 0)
29
- else
30
- while walker.nextNode()
31
- if walker.currentNode.length < offset
32
- offset -= walker.currentNode.length
33
- continue
34
-
35
- range.setStart(walker.currentNode, offset)
36
- break
37
-
38
- range.collapse(true)
39
- range
40
-
41
- toString: (node) ->
42
- node.textContent
22
+ rule: /((_{1})([^_]+)(_{1}))/gi
43
23
  }
@@ -1,4 +1,4 @@
1
- class Link
1
+ class Link extends Written.Parsers.Inline
2
2
  constructor: (match) ->
3
3
  @match = match
4
4
 
@@ -8,36 +8,17 @@ class Link
8
8
  length: =>
9
9
  @match[0].length
10
10
 
11
- markdown: =>
12
- "<a href='javascript:void(0)'><strong>#{@match[1]}</strong>#{@match[3]}</a>".toHTML()
11
+ toEditor: =>
12
+ Written.toHTML("<a><strong>#{@match[1]}</strong>#{@match[3]}</a>")
13
13
 
14
- html: =>
15
- "<a href='#{@match[4]}'>#{@match[2]}</a>".toHTML()
16
-
17
- Link.rule = /!{0}(\[([^\]]+)\])(\(([^\)]+)\))/gi
14
+ toHTML: =>
15
+ Written.toHTML("<a href='#{@match[4]}'>#{@match[2]}</a>")
18
16
 
19
17
 
20
18
  Written.Parsers.register {
21
19
  parser: Link
22
- node: 'a'
20
+ name: 'link'
21
+ nodes: ['a']
23
22
  type: 'inline'
24
- getRange: (node, offset, walker) ->
25
- range = document.createRange()
26
-
27
- if !node.firstChild?
28
- range.setStart(this, 0)
29
- else
30
- while walker.nextNode()
31
- if walker.currentNode.length < offset
32
- offset -= walker.currentNode.length
33
- continue
34
-
35
- range.setStart(walker.currentNode, offset)
36
- break
37
-
38
- range.collapse(true)
39
- range
40
-
41
- toString: (node) ->
42
- node.textContent
23
+ rule: /!{0}(\[([^\]]+)\])(\(([^\)]+)\))/gi
43
24
  }
@@ -3,6 +3,10 @@
3
3
  white-space: pre-wrap;
4
4
  padding: 1em;
5
5
 
6
+ a {
7
+ color: steelblue;
8
+ }
9
+
6
10
  p {
7
11
  min-height: 1.2em;
8
12
  }
@@ -1,3 +1,3 @@
1
1
  module Written
2
- VERSION = '0.2.0'
2
+ VERSION = '0.4.0'
3
3
  end
data/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "written",
3
+ "description": "Markdown Editor",
4
+ "version": "0.0.1",
5
+ "private": true,
6
+ "scripts": {
7
+ "test": "jest"
8
+ },
9
+ "devDependencies": {
10
+ "babel-jest": "^12.1.0",
11
+ "babel-polyfill": "^6.9.0",
12
+ "babel-preset-es2015": "^6.9.0",
13
+ "coffee-script": "^1.10.0",
14
+ "jest": "^12.1.1",
15
+ "jest-cli": "^12.1.0"
16
+ },
17
+ "jest": {
18
+ "testDirectoryName": "test/javascript",
19
+ "scriptPreprocessor": "<rootDir>/compile-coffee",
20
+ "moduleFileExtensions": [
21
+ "coffee",
22
+ "js"
23
+ ]
24
+ }
25
+ }
@@ -0,0 +1,58 @@
1
+ let struct = undefined;
2
+
3
+ window.Written = {
4
+ Parsers: {
5
+ Block: {},
6
+ register: function(s) {
7
+ struct = s
8
+ }
9
+ }
10
+ }
11
+
12
+ require('../../../../lib/written/app/assets/javascripts/written/core/html')
13
+ require('../../../../lib/written/app/assets/javascripts/written/parsers/block/code')
14
+
15
+ describe('code', () => {
16
+ it('detects a match', () => {
17
+ let match = struct.rule.exec('~~~')
18
+ expect(match).not.toBeNull()
19
+ })
20
+
21
+ it('can add lines until it finds the closing tag', () => {
22
+ let match = struct.rule.exec('~~~')
23
+ let parser = new struct.parser(match)
24
+
25
+ expect(match).not.toBeNull()
26
+ expect(parser.accepts('let var = true')).toBeTruthy()
27
+ parser.append('let var = true')
28
+
29
+ expect(parser.accepts('~~~')).toBeTruthy()
30
+ parser.append('~~~')
31
+
32
+ expect(parser.opened).toBeFalsy()
33
+ })
34
+
35
+ it('returns the content for the editor', () => {
36
+ let match = struct.rule.exec('~~~')
37
+ let html = Written.toHTML('<pre><code>~~~\nlet var = true\n~~~</code></pre>')
38
+
39
+ let parser = new struct.parser(match)
40
+ parser.append('let var = true')
41
+ parser.append('~~~')
42
+
43
+ expect(parser.opened).toBeFalsy()
44
+ expect(parser.toEditor().outerHTML).toBe(html.outerHTML)
45
+ })
46
+
47
+ it('HTML content should not include the markdown syntax', () => {
48
+ let match = struct.rule.exec('~~~')
49
+ let html = Written.toHTML('<pre><code>\nlet var = true\n</code></pre>')
50
+
51
+ let parser = new struct.parser(match)
52
+ parser.append('let var = true')
53
+ parser.append('~~~')
54
+
55
+ expect(parser.opened).toBeFalsy()
56
+ expect(parser.toHTML().outerHTML).toBe(html.outerHTML)
57
+ })
58
+ })