written 0.2.0 → 0.4.0

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 (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
+ })