written 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +128 -0
- data/Rakefile +83 -0
- data/lib/written/app/assets/javascripts/vendors/prism.js +1411 -0
- data/lib/written/app/assets/javascripts/written/core/content.coffee +106 -0
- data/lib/written/app/assets/javascripts/written/core/cursor.coffee +59 -0
- data/lib/written/app/assets/javascripts/written/core/document.coffee +19 -0
- data/lib/written/app/assets/javascripts/written/core/ext.coffee +109 -0
- data/lib/written/app/assets/javascripts/written/core/extensions.coffee +2 -0
- data/lib/written/app/assets/javascripts/written/core/history.coffee +16 -0
- data/lib/written/app/assets/javascripts/written/core/observer.coffee +29 -0
- data/lib/written/app/assets/javascripts/written/extensions/clipboard.coffee +114 -0
- data/lib/written/app/assets/javascripts/written/extensions/image.coffee +91 -0
- data/lib/written/app/assets/javascripts/written/parsers/block/code.coffee +25 -0
- data/lib/written/app/assets/javascripts/written/parsers/block/heading.coffee +10 -0
- data/lib/written/app/assets/javascripts/written/parsers/block/image.coffee +18 -0
- data/lib/written/app/assets/javascripts/written/parsers/block/olist.coffee +18 -0
- data/lib/written/app/assets/javascripts/written/parsers/block/paragraph.coffee +10 -0
- data/lib/written/app/assets/javascripts/written/parsers/block/ulist.coffee +17 -0
- data/lib/written/app/assets/javascripts/written/parsers/inline/italic.coffee +13 -0
- data/lib/written/app/assets/javascripts/written/parsers/inline/link.coffee +16 -0
- data/lib/written/app/assets/javascripts/written/parsers/inline/strong.coffee +12 -0
- data/lib/written/app/assets/javascripts/written/parsers/parsers.coffee +98 -0
- data/lib/written/app/assets/javascripts/written.coffee +4 -0
- data/lib/written/app/assets/stylesheets/vendors/prism.css +138 -0
- data/lib/written/app/assets/stylesheets/written.scss +21 -0
- data/lib/written/document.rb +42 -0
- data/lib/written/node.rb +21 -0
- data/lib/written/nodes/code.rb +65 -0
- data/lib/written/nodes/heading.rb +15 -0
- data/lib/written/nodes/image.rb +14 -0
- data/lib/written/nodes/ordered_list.rb +18 -0
- data/lib/written/nodes/unordered_list.rb +19 -0
- data/lib/written/parsers/base.rb +26 -0
- data/lib/written/parsers/code.rb +60 -0
- data/lib/written/parsers/heading.rb +19 -0
- data/lib/written/parsers/image.rb +19 -0
- data/lib/written/parsers/link.rb +12 -0
- data/lib/written/parsers/list.rb +33 -0
- data/lib/written/parsers/word.rb +16 -0
- data/lib/written/parsers.rb +11 -0
- data/lib/written/railtie.rb +20 -0
- data/lib/written/version.rb +3 -0
- data/lib/written.rb +14 -0
- data/test/javascript/assertions/assert.coffee +3 -0
- data/test/javascript/polyfills/HTMLULListElement.coffee +0 -0
- data/test/javascript/polyfills/Text.coffee +0 -0
- data/test/javascript/polyfills.coffee +2 -0
- data/test/javascript/runner.coffee +46 -0
- data/test/javascript/tests/initialization.coffee +16 -0
- data/test/javascript/tests/parsing.coffee +9 -0
- data/test/ruby/blank_test.rb +83 -0
- data/test/server/app/assets/javascripts/application.coffee +3 -0
- data/test/server/app/assets/stylesheets/application.scss +10 -0
- data/test/server/app/controllers/application_controller.rb +2 -0
- data/test/server/app/controllers/posts_controller.rb +4 -0
- data/test/server/app/views/layouts/application.html.erb +14 -0
- data/test/server/app/views/posts/show.html.erb +14 -0
- data/test/server/application.rb +12 -0
- data/test/server/config.ru +5 -0
- data/test/server/log/test.log +570 -0
- data/written.gemspec +17 -0
- 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,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,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,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
|