written 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +128 -0
  5. data/Rakefile +83 -0
  6. data/lib/written/app/assets/javascripts/vendors/prism.js +1411 -0
  7. data/lib/written/app/assets/javascripts/written/core/content.coffee +106 -0
  8. data/lib/written/app/assets/javascripts/written/core/cursor.coffee +59 -0
  9. data/lib/written/app/assets/javascripts/written/core/document.coffee +19 -0
  10. data/lib/written/app/assets/javascripts/written/core/ext.coffee +109 -0
  11. data/lib/written/app/assets/javascripts/written/core/extensions.coffee +2 -0
  12. data/lib/written/app/assets/javascripts/written/core/history.coffee +16 -0
  13. data/lib/written/app/assets/javascripts/written/core/observer.coffee +29 -0
  14. data/lib/written/app/assets/javascripts/written/extensions/clipboard.coffee +114 -0
  15. data/lib/written/app/assets/javascripts/written/extensions/image.coffee +91 -0
  16. data/lib/written/app/assets/javascripts/written/parsers/block/code.coffee +25 -0
  17. data/lib/written/app/assets/javascripts/written/parsers/block/heading.coffee +10 -0
  18. data/lib/written/app/assets/javascripts/written/parsers/block/image.coffee +18 -0
  19. data/lib/written/app/assets/javascripts/written/parsers/block/olist.coffee +18 -0
  20. data/lib/written/app/assets/javascripts/written/parsers/block/paragraph.coffee +10 -0
  21. data/lib/written/app/assets/javascripts/written/parsers/block/ulist.coffee +17 -0
  22. data/lib/written/app/assets/javascripts/written/parsers/inline/italic.coffee +13 -0
  23. data/lib/written/app/assets/javascripts/written/parsers/inline/link.coffee +16 -0
  24. data/lib/written/app/assets/javascripts/written/parsers/inline/strong.coffee +12 -0
  25. data/lib/written/app/assets/javascripts/written/parsers/parsers.coffee +98 -0
  26. data/lib/written/app/assets/javascripts/written.coffee +4 -0
  27. data/lib/written/app/assets/stylesheets/vendors/prism.css +138 -0
  28. data/lib/written/app/assets/stylesheets/written.scss +21 -0
  29. data/lib/written/document.rb +42 -0
  30. data/lib/written/node.rb +21 -0
  31. data/lib/written/nodes/code.rb +65 -0
  32. data/lib/written/nodes/heading.rb +15 -0
  33. data/lib/written/nodes/image.rb +14 -0
  34. data/lib/written/nodes/ordered_list.rb +18 -0
  35. data/lib/written/nodes/unordered_list.rb +19 -0
  36. data/lib/written/parsers/base.rb +26 -0
  37. data/lib/written/parsers/code.rb +60 -0
  38. data/lib/written/parsers/heading.rb +19 -0
  39. data/lib/written/parsers/image.rb +19 -0
  40. data/lib/written/parsers/link.rb +12 -0
  41. data/lib/written/parsers/list.rb +33 -0
  42. data/lib/written/parsers/word.rb +16 -0
  43. data/lib/written/parsers.rb +11 -0
  44. data/lib/written/railtie.rb +20 -0
  45. data/lib/written/version.rb +3 -0
  46. data/lib/written.rb +14 -0
  47. data/test/javascript/assertions/assert.coffee +3 -0
  48. data/test/javascript/polyfills/HTMLULListElement.coffee +0 -0
  49. data/test/javascript/polyfills/Text.coffee +0 -0
  50. data/test/javascript/polyfills.coffee +2 -0
  51. data/test/javascript/runner.coffee +46 -0
  52. data/test/javascript/tests/initialization.coffee +16 -0
  53. data/test/javascript/tests/parsing.coffee +9 -0
  54. data/test/ruby/blank_test.rb +83 -0
  55. data/test/server/app/assets/javascripts/application.coffee +3 -0
  56. data/test/server/app/assets/stylesheets/application.scss +10 -0
  57. data/test/server/app/controllers/application_controller.rb +2 -0
  58. data/test/server/app/controllers/posts_controller.rb +4 -0
  59. data/test/server/app/views/layouts/application.html.erb +14 -0
  60. data/test/server/app/views/posts/show.html.erb +14 -0
  61. data/test/server/application.rb +12 -0
  62. data/test/server/config.ru +5 -0
  63. data/test/server/log/test.log +570 -0
  64. data/written.gemspec +17 -0
  65. metadata +106 -0
@@ -0,0 +1,106 @@
1
+ class @Written
2
+ constructor: (el) ->
3
+ el.instance = this
4
+ el.dataset.editor = "written"
5
+ @element = ->
6
+ return el
7
+
8
+ @observer = new Written.Observer(@element(), @changed)
9
+ @observer.pause @initialize
10
+
11
+ @element().addEventListener('keypress', @linefeed)
12
+
13
+ cursor = new Written.Cursor(@element(), window.getSelection())
14
+ cursor.focus(0, @element())
15
+
16
+ initialize: =>
17
+ if @parsers?
18
+ return
19
+
20
+ Written.Parsers.freeze()
21
+
22
+ @history = new Written.History(new Written.Document(@toString(), Written.Parsers))
23
+
24
+ node = @history.current().head
25
+
26
+ @element().textContent = ''
27
+
28
+ while node
29
+ @element().appendChild(node)
30
+ node = node.nextDocumentNode
31
+
32
+
33
+ changed: (e) =>
34
+ oldDocument = @history.current()
35
+ newDocument = new Written.Document(@toString())
36
+ if oldDocument.toString().localeCompare(newDocument.toString()) == 0
37
+ return
38
+
39
+ @update(newDocument, new Written.Cursor(@element(), window.getSelection()))
40
+ @history.push(newDocument)
41
+
42
+ update: (document, cursor) =>
43
+ node = document.head
44
+ current = @element().firstElementChild
45
+
46
+ while node
47
+ if !current?
48
+ @element().appendChild(node.cloneNode(true))
49
+ else if node.outerHTML.localeCompare(current.outerHTML) != 0
50
+ clonedNodeDocument = node.cloneNode(true)
51
+ @element().replaceChild(clonedNodeDocument, current)
52
+ current = clonedNodeDocument
53
+
54
+ node = node.nextDocumentNode
55
+ if current?
56
+ current = current.nextElementSibling
57
+
58
+ if current?
59
+ node = current.nextElementSibling
60
+ while node
61
+ nextNode = node.nextElementSibling
62
+ node.remove()
63
+ node = nextNode
64
+
65
+ cursor.focus()
66
+
67
+
68
+ linefeed: (e) =>
69
+ return unless e.which == 13
70
+ e.preventDefault()
71
+ e.stopPropagation()
72
+
73
+ cursor = new Written.Cursor(@element(), window.getSelection())
74
+ @observer.pause =>
75
+
76
+ offset = cursor.offset
77
+ lines = @history.current().toString().split('\n').map (line) ->
78
+ if line.length < offset
79
+ offset -= line.length
80
+ else if offset >= 0
81
+ line = [line.slice(0, offset), '\n', line.slice(offset)].join('')
82
+ offset -= line.length
83
+ offset -= 1
84
+ line
85
+
86
+ if offset == 0
87
+ lines.push('')
88
+ cursor.offset += 1
89
+
90
+ document = new Written.Document(lines.join('\n'))
91
+ if cursor.offset < document.toString().length
92
+ cursor.offset += 1
93
+
94
+ @update(document, cursor)
95
+ @history.push(document)
96
+
97
+
98
+ toString: =>
99
+ texts = []
100
+ for node in @element().childNodes
101
+ content = node.toString().split('\n')
102
+ texts.push content.join('\n')
103
+
104
+ texts.join '\n'
105
+
106
+
@@ -0,0 +1,59 @@
1
+ class Written.Cursor
2
+ constructor: (element, selection) ->
3
+ @element = ->
4
+ element
5
+ @selection = selection
6
+ children = Array.prototype.slice.call(@element().children, 0)
7
+ @offset = selection.focusOffset
8
+
9
+ node = selection.focusNode
10
+
11
+ while node && !children.includes(node)
12
+ parent = node.parentElement
13
+
14
+ if parent
15
+ for child in (parent.childNodes || [])
16
+ if child == node
17
+ break
18
+ else
19
+ @offset += child.textContent.length
20
+
21
+ node = parent
22
+
23
+
24
+ for child in @element().children
25
+ if child == node
26
+ break
27
+ @offset += child.textContent.length
28
+ @offset += 1
29
+
30
+ @currentNode = ->
31
+ node
32
+
33
+
34
+ offsetAt: (node) ->
35
+ offset = @offset
36
+
37
+ element = @element().firstElementChild
38
+ while element && element != node
39
+ offset -= element.textContent.length
40
+ element = element.nextElementSibling
41
+
42
+ offset
43
+
44
+ focus: (offset, node) =>
45
+ if offset is undefined
46
+ offset = @offset
47
+
48
+ if node is undefined
49
+ node = @element().firstElementChild
50
+
51
+ while node.nextElementSibling && node.toString().length < offset
52
+ offset -= node.textContent.length + 1
53
+ node = node.nextElementSibling
54
+
55
+
56
+ range = node.getRange(offset, document.createTreeWalker(node, NodeFilter.SHOW_TEXT))
57
+ @selection.removeAllRanges()
58
+ @selection.addRange(range)
59
+
@@ -0,0 +1,19 @@
1
+ #= require ../parsers/parsers
2
+
3
+ Written.Document = class
4
+ constructor: (textContent, parsers) ->
5
+ @texts = new Array()
6
+
7
+ @head = Written.Parsers.Block.parse(textContent.split('\n').reverse())
8
+
9
+ node = @head
10
+
11
+ while node
12
+ Written.Parsers.Inline.parse(node)
13
+ text = node.toString()
14
+ @texts.push(text)
15
+ node = node.nextDocumentNode
16
+
17
+
18
+ toString: =>
19
+ @texts.join('\n')
@@ -0,0 +1,109 @@
1
+ String::toHTML = ->
2
+ el = document.createElement('div')
3
+ el.innerHTML = this
4
+ if el.children.length > 1
5
+ el.children
6
+ else
7
+ el.children[0]
8
+
9
+ Text::toString = ->
10
+ @textContent
11
+
12
+ HTMLOListElement::toString = HTMLUListElement::toString = ->
13
+ texts = Array.prototype.slice.call(@children).map (li) ->
14
+ li.textContent
15
+ texts.join("\n")
16
+
17
+ HTMLDivElement::toString = ->
18
+ if @classList.contains('image')
19
+ @lastChild.textContent
20
+ else
21
+ @textContent
22
+
23
+ HTMLPreElement::toString = ->
24
+ if @textContent[@textContent.length - 1] == '\n'
25
+ @textContent.substr(0, @textContent.length - 1)
26
+ else
27
+ @textContent
28
+
29
+ HTMLElement::toString = ->
30
+ if @nodeName == 'FIGURE'
31
+ (@querySelector('figcaption') || this).textContent
32
+ else
33
+ @textContent
34
+
35
+ HTMLHeadingElement::toString = HTMLParagraphElement::toString = ->
36
+ @textContent
37
+
38
+ HTMLElement::offset = (node, walker) ->
39
+ offset = 0
40
+
41
+ while walker.nextNode()
42
+ if walker.currentNode != node
43
+ offset += walker.currentNode.length
44
+ else
45
+ break
46
+
47
+ offset
48
+
49
+ HTMLOListElement::offset = HTMLUListElement::offset = (node, walker) ->
50
+ offset = 0
51
+ li = this.firstElementChild
52
+
53
+ while walker.nextNode()
54
+ if !li.contains(walker.currentNode)
55
+ newList = walker.currentNode
56
+ while newList? && !(newList instanceof HTMLLIElement)
57
+ newList = newList.parentElement
58
+ li = newList
59
+ offset++
60
+
61
+ if walker.currentNode != node
62
+ offset += walker.currentNode.length
63
+ else
64
+ break
65
+
66
+ offset
67
+
68
+ HTMLElement::getRange = (offset, walker) ->
69
+ range = document.createRange()
70
+
71
+ if !@firstChild?
72
+ range.setStart(this, 0)
73
+ else
74
+ while walker.nextNode()
75
+ if walker.currentNode.length < offset
76
+ offset -= walker.currentNode.length
77
+ continue
78
+
79
+ range.setStart(walker.currentNode, offset)
80
+ break
81
+
82
+ range.collapse(true)
83
+ range
84
+
85
+
86
+ HTMLOListElement::getRange = HTMLUListElement::getRange = (offset, walker) ->
87
+ range = document.createRange()
88
+ if !@firstChild?
89
+ range.setStart(this, 0)
90
+ return
91
+
92
+ li = this.firstElementChild
93
+
94
+ while walker.nextNode()
95
+ if !li.contains(walker.currentNode)
96
+ newList = walker.currentNode
97
+ while newList? && !(newList instanceof HTMLLIElement)
98
+ newList = newList.parentElement
99
+ li = newList
100
+ offset--
101
+
102
+ if walker.currentNode.length < offset
103
+ offset -= walker.currentNode.length
104
+ continue
105
+ range.setStart(walker.currentNode, offset)
106
+ break
107
+
108
+ range.collapse(true)
109
+ range
@@ -0,0 +1,2 @@
1
+ Written.Extensions = []
2
+
@@ -0,0 +1,16 @@
1
+ Written.History = class History
2
+ constructor: (currentDocument) ->
3
+ @documents = [currentDocument]
4
+
5
+ current: =>
6
+ @documents[0]
7
+
8
+ push: (document) =>
9
+ @documents.splice(0, 0, document)
10
+ while @documents.length > 50
11
+ @documents.pop()
12
+
13
+ pop: =>
14
+ documents.pop()
15
+
16
+
@@ -0,0 +1,29 @@
1
+ class Written.Observer
2
+ constructor: (element, callback) ->
3
+ @element = ->
4
+ element
5
+
6
+ @settings = ->
7
+ {
8
+ childList: true
9
+ subtree: true
10
+ characterData: true
11
+ }
12
+
13
+
14
+ @mutations = new MutationObserver(@normalize.bind(this, callback))
15
+ @mutations.observe @element(), @settings()
16
+
17
+ normalize: (callback, events) =>
18
+ @pause =>
19
+ for event in events
20
+ if event.target instanceof HTMLElement
21
+ for br in event.target.querySelectorAll('br')
22
+ br.remove()
23
+
24
+ @pause(callback)
25
+
26
+ pause: (callback) =>
27
+ @mutations.disconnect()
28
+ callback()
29
+ @mutations.observe @element(), @settings()
@@ -0,0 +1,114 @@
1
+ class ClipBoard
2
+ constructor: (editor, hooks) ->
3
+ @editor = editor
4
+ @editor.element().addEventListener 'paste', @paste
5
+
6
+ paste: (e) =>
7
+ e.preventDefault()
8
+ e.stopPropagation()
9
+
10
+ data = e.clipboardData.getData('text/plain')
11
+
12
+ texts = data.split('\n').map (text) ->
13
+ document.createTextNode(text)
14
+ return unless texts.length > 0
15
+
16
+ _sel = window.getSelection()
17
+ sel = {
18
+ type: _sel.type
19
+ }
20
+ position = _sel.anchorNode.compareDocumentPosition(_sel.extentNode)
21
+ if _sel.anchorOffset <= _sel.extentOffset || position == document.DOCUMENT_POSITION_PRECEDING
22
+ sel.anchorNode = _sel.anchorNode
23
+ sel.anchorOffset = _sel.anchorOffset
24
+ sel.extentNode = _sel.extentNode
25
+ sel.extentOffset = _sel.extentOffset
26
+ else
27
+ sel.anchorNode = _sel.extentNode
28
+ sel.anchorOffset = _sel.extentOffset
29
+ sel.extentNode = _sel.anchorNode
30
+ sel.extentOffset = _sel.anchorOffset
31
+
32
+ if sel.anchorNode == @editor.element()
33
+ line = @editor.line()
34
+ @editor.element().appendChild(line)
35
+ sel.anchorNode = sel.extentNode = line
36
+
37
+ @editor.observer.hold =>
38
+ if sel.type == 'Range'
39
+ @replace(texts, sel)
40
+ else
41
+ @insert(texts, sel)
42
+
43
+
44
+ replace: (texts, sel) =>
45
+
46
+ node = sel.extentNode
47
+ line = node
48
+ nodes = [node]
49
+
50
+ while line.parentElement != @editor.element()
51
+ line = line.parentElement
52
+
53
+ offsets = {
54
+ start: sel.anchorOffset
55
+ end: sel.extentOffset
56
+ }
57
+
58
+ offsets.start = @editor.lineOffset(line, sel.anchorNode, offsets.start)
59
+ offsets.end = @editor.lineOffset(line, sel.extentNode, offsets.end)
60
+
61
+ text = texts.map((t) ->
62
+ t.textContent
63
+ ).join()
64
+
65
+
66
+ line.textContent = line.textContent.substr(0, offsets.start) + text + line.textContent.substr(offsets.end)
67
+
68
+ fragment = @editor.parse(@editor.cloneNodesFrom(line))
69
+
70
+ @editor.updateDOM(line, fragment)
71
+
72
+ cursor = new Written.Cursor(offsets.start + text.length)
73
+ cursor.update(@editor.walker(cursor.focus(line)), true)
74
+
75
+
76
+ insert: (texts, sel) =>
77
+ node = sel.anchorNode
78
+ str = node.textContent
79
+ text = texts.map((t) ->
80
+ t.textContent
81
+ ).join("\n")
82
+
83
+ node.textContent = str.substr(0, sel.anchorOffset)
84
+ node.textContent += text
85
+ node.textContent += str.substr(sel.anchorOffset)
86
+
87
+ line = node
88
+ while line.parentElement != @editor.element()
89
+ line = line.parentElement
90
+
91
+ offset = sel.anchorOffset + text.length
92
+ if node != line
93
+ offset += @nodeOffset(node, line)
94
+
95
+ fragment = @editor.parse(@editor.cloneNodesFrom(line))
96
+
97
+ lines = @editor.updateDOM(line, fragment)
98
+
99
+ cursor = new Written.Cursor(offset)
100
+ cursor.update(@editor.walker(cursor.focus(lines[0])), true)
101
+
102
+ nodeOffset: (node, line) ->
103
+ offset = 0
104
+
105
+ for n in line.childNodes
106
+ if n == node
107
+ break
108
+ else if n.contains(node)
109
+ offset += @nodeOffset(node, n)
110
+ break
111
+ else
112
+ offset += n.textContent.length
113
+
114
+ offset
@@ -0,0 +1,91 @@
1
+ class
2
+ loaded: =>
3
+ @element().addEventListener 'dragover', @over
4
+ @element().addEventListener 'dragleave', @cancel
5
+ @element().addEventListener 'drop', @drop
6
+ @on 'click', @container(), @open
7
+ @on 'change', @element().querySelector('input'), @upload
8
+ if @element().querySelector('figcaption').hasAttribute('name')
9
+ @show()
10
+ else
11
+ @placehold()
12
+
13
+ over: (e) =>
14
+ e.preventDefault()
15
+ @element().classList.add 'dropping'
16
+
17
+ cancel: (e) =>
18
+ e.preventDefault()
19
+ @element().classList.remove 'dropping'
20
+
21
+ drop: (e) =>
22
+ e.preventDefault()
23
+ @element().classList.remove 'dropping'
24
+ e.file = e.dataTransfer.files[0]
25
+ @upload(e)
26
+
27
+ show: =>
28
+ img = @retrieve('img')
29
+ img.src = @element().querySelector('figcaption').getAttribute('name')
30
+ @container().appendChild(img)
31
+
32
+ placehold: =>
33
+ placeholder = @retrieve('.placeholder')
34
+ @container().appendChild(placeholder)
35
+
36
+ container: =>
37
+ @container.element ||= @element().querySelector('div[contenteditable=false]')
38
+
39
+ open: =>
40
+ @container().querySelector('input').click()
41
+
42
+ uploaded: (e) =>
43
+ xml = new DOMParser().parseFromString(e.target.response, 'text/xml')
44
+ url = xml.querySelector('Location').textContent
45
+ img = @retrieve('img')
46
+ img.src = url
47
+ progressBar = @retrieve('.progressbar')
48
+ @container().insertBefore(img, progressBar)
49
+ progressBar.remove()
50
+ @retrieve('.placeholder').remove()
51
+ @element().querySelector('figcaption').lastChild.textContent = "(#{url})"
52
+
53
+ upload: (e) =>
54
+ @container().appendChild(@retrieve('.progressbar'))
55
+ unless e.file?
56
+ e.file = e.target.files[0]
57
+ id = PostBody.getAttribute('postid')
58
+ policy = PostBody.getAttribute('policy')
59
+ signature = PostBody.getAttribute('signature')
60
+ bucket = PostBody.getAttribute('bucket')
61
+ namespace = PostBody.getAttribute('namespace')
62
+ access_key = PostBody.getAttribute('access_key')
63
+
64
+ url = "https://s3.amazonaws.com/#{bucket}/"
65
+ dir = [id]
66
+ if namespace?
67
+ dir.splice(0,0, namespace)
68
+
69
+ dir = dir.join '/'
70
+
71
+ data = new FormData()
72
+ data.append 'AWSAccessKeyId', access_key
73
+ data.append 'success_action_status', 201
74
+ data.append 'acl', 'private'
75
+ data.append 'policy', policy
76
+ data.append 'signature', signature
77
+ data.append 'key', "#{dir}/#{e.file.name}"
78
+ data.append 'Content-Type', e.file.type
79
+ data.append 'file', e.file
80
+
81
+ xhr = new XMLHttpRequest()
82
+ xhr.open('POST', url, true)
83
+ xhr.onload = @uploaded
84
+ xhr.onprogress = @progress
85
+ xhr.send(data)
86
+
87
+ progress: (e) =>
88
+ return unless e.lengthComputable
89
+ percentComplete = e.loaded / e.total * 100.0;
90
+ progressBar = @retrieve('.progressbar')
91
+ progressBar.firstElementChild.style.width = "#{percentComplete}%";
@@ -0,0 +1,25 @@
1
+ class Code
2
+ constructor: (match) ->
3
+ @match = match
4
+ @node = "<pre data-status='opened' data-multiline='true'><code as='Written.Code'></code></pre>".toHTML()
5
+
6
+
7
+ valid: ->
8
+ true
9
+
10
+ render: (text) =>
11
+ if @match[3]?
12
+ @node.querySelector('code').classList.add("language-#{@match[3]}")
13
+
14
+ code = @node.querySelector('code')
15
+ code.appendChild(document.createTextNode(text + "\n"))
16
+
17
+ if /(~{3})$/i.test(text)
18
+ @node.dataset.status = 'closed'
19
+
20
+ Prism.highlightElement(code, false)
21
+
22
+ @node
23
+
24
+
25
+ Written.Parsers.Block.register Code, /((~{3})([a-z]+)?)(.+)?/i
@@ -0,0 +1,10 @@
1
+ class Header
2
+ constructor: (match) ->
3
+ @match = match
4
+
5
+ render: (text) =>
6
+ size = @match[1].length
7
+ return "<h#{size}>#{text}</h#{size}>".toHTML()
8
+
9
+
10
+ Written.Parsers.Block.register Header, /^(#{1,6}) /i
@@ -0,0 +1,18 @@
1
+ class Image
2
+ constructor: (match) ->
3
+ @match = match
4
+ @figure = "<figure><div contenteditable='false'><img/></div><figcaption /></figure>".toHTML()
5
+
6
+ render: =>
7
+ caption = @figure.querySelector('figcaption')
8
+ caption.appendChild document.createTextNode(@match[1])
9
+ caption.appendChild document.createTextNode(@match[3])
10
+ if @match[4]?
11
+ caption.setAttribute('name', @match[4])
12
+
13
+ img = @figure.querySelector('img')
14
+ img.src = @match[4]
15
+
16
+ @figure
17
+
18
+ Written.Parsers.Block.register Image, /^(!{1}\[([^\]]+)\])(\(([^\s]+)?\))$/i
@@ -0,0 +1,18 @@
1
+ class OList
2
+ rule: /^(\d\.\s)(.+)/i
3
+ constructor: (match) ->
4
+ @match = match
5
+ @node = "<ol data-status='opened'></ol>".toHTML()
6
+
7
+ valid: (text) ->
8
+ valid = OList::rule.test(text)
9
+ if !valid
10
+ @node.dataset.status = false
11
+ valid
12
+
13
+ render: (text) =>
14
+ @node.appendChild("<li>#{text}</li>".toHTML())
15
+ @node
16
+
17
+ Written.Parsers.Block.register OList, OList::rule
18
+
@@ -0,0 +1,10 @@
1
+ class Paragraph
2
+ constructor: (match) ->
3
+ @match = match
4
+ @node = "<p>".toHTML()
5
+
6
+ render: (str) =>
7
+ @node.textContent = str
8
+ @node
9
+
10
+ Written.Parsers.Block.register Paragraph, /.*/i, true
@@ -0,0 +1,17 @@
1
+ class UList
2
+ rule: /^(-\s)(.+)/i
3
+ constructor: (match) ->
4
+ @match = match
5
+ @node = "<ul data-status='opened'></ul>".toHTML()
6
+
7
+ valid: (text) ->
8
+ valid = UList::rule.test(text)
9
+ if !valid
10
+ @node.dataset.status = false
11
+ valid
12
+
13
+ render: (text) =>
14
+ @node.appendChild("<li>#{text}</li>".toHTML())
15
+ @node
16
+
17
+ Written.Parsers.Block.register UList, UList::rule
@@ -0,0 +1,13 @@
1
+ class Italic
2
+ constructor: (match) ->
3
+ @match = match
4
+ @node = "<em>".toHTML()
5
+
6
+ render: (textNode) =>
7
+ italic = textNode.splitText(textNode.textContent.indexOf(@match[2]))
8
+ italic.splitText(@match[2].length)
9
+ @node.appendChild(document.createTextNode(@match[2]))
10
+ textNode.parentElement.replaceChild(@node, italic)
11
+
12
+ Written.Parsers.Inline.register Italic, /(\s|^|\d)(\*{1}([^\*]+)\*{1})/gi
13
+
@@ -0,0 +1,16 @@
1
+ class Link
2
+ constructor: (match) ->
3
+ @match = match
4
+ @node = "<a>".toHTML()
5
+
6
+ render: (textNode) =>
7
+ @node.href = @match[4]
8
+ name = "<strong>".toHTML()
9
+ anchor = textNode.splitText(textNode.textContent.indexOf(@match[0]))
10
+ anchor.splitText(@match[0].length)
11
+ name.textContent = @match[1]
12
+ @node.appendChild(name)
13
+ @node.appendChild(document.createTextNode(@match[3]))
14
+ textNode.parentElement.replaceChild(@node, anchor)
15
+
16
+ Written.Parsers.Inline.register Link, /!{0}(\[([^\]]+)\])(\(([^\)]+)\))/gi