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,12 @@
|
|
1
|
+
class Strong
|
2
|
+
constructor: (match) ->
|
3
|
+
@match = match
|
4
|
+
@node = "<strong>".toHTML()
|
5
|
+
|
6
|
+
render: (textNode) =>
|
7
|
+
strong = textNode.splitText(textNode.textContent.indexOf(@match[0]))
|
8
|
+
strong.splitText(@match[0].length)
|
9
|
+
@node.appendChild(document.createTextNode(@match[0]))
|
10
|
+
textNode.parentElement.replaceChild(@node, strong)
|
11
|
+
|
12
|
+
Written.Parsers.Inline.register Strong, /((\*{2})[^\*]+(\*{2}))/gi
|
@@ -0,0 +1,98 @@
|
|
1
|
+
Written.Parsers = {
|
2
|
+
freeze: ->
|
3
|
+
Written.Parsers.Block.freeze()
|
4
|
+
Written.Parsers.Inline.freeze()
|
5
|
+
}
|
6
|
+
|
7
|
+
Written.Parsers.Block = new class
|
8
|
+
constructor: ->
|
9
|
+
@parsers = []
|
10
|
+
|
11
|
+
freeze: ->
|
12
|
+
@parsers.push this.defaultParser
|
13
|
+
|
14
|
+
Object.freeze(this)
|
15
|
+
Object.freeze(@parsers)
|
16
|
+
|
17
|
+
|
18
|
+
register: (parser, rule, defaultParser = false) ->
|
19
|
+
if defaultParser
|
20
|
+
this.defaultParser = {
|
21
|
+
rule: rule,
|
22
|
+
parser: parser
|
23
|
+
}
|
24
|
+
else
|
25
|
+
@parsers.push {
|
26
|
+
rule: rule,
|
27
|
+
parser: parser
|
28
|
+
}
|
29
|
+
|
30
|
+
parse: (lines) =>
|
31
|
+
elements = []
|
32
|
+
currentNode = undefined
|
33
|
+
while (line = lines.pop()) != undefined
|
34
|
+
str = line.toString()
|
35
|
+
if !currentNode
|
36
|
+
parser = @find(str)
|
37
|
+
currentNode = parser.render(str)
|
38
|
+
elements.push(currentNode)
|
39
|
+
continue
|
40
|
+
|
41
|
+
if currentNode.dataset.status != 'opened'
|
42
|
+
parser = @find(str)
|
43
|
+
currentNode.nextDocumentNode = parser.render(str)
|
44
|
+
currentNode = currentNode.nextDocumentNode
|
45
|
+
currentNode.writtenNodeParser = parser
|
46
|
+
elements.push(currentNode)
|
47
|
+
continue
|
48
|
+
else if currentNode.writtenNodeParser.valid(str)
|
49
|
+
currentNode.writtenNodeParser.render(str)
|
50
|
+
continue
|
51
|
+
else
|
52
|
+
parser = @find(str)
|
53
|
+
currentNode.nextDocumentNode = parser.render(str)
|
54
|
+
currentNode = currentNode.nextDocumentNode
|
55
|
+
currentNode.writtenNodeParser = parser
|
56
|
+
elements.push(currentNode)
|
57
|
+
|
58
|
+
elements[0]
|
59
|
+
|
60
|
+
find: (str) ->
|
61
|
+
parser = undefined
|
62
|
+
for p in @parsers
|
63
|
+
if match = p.rule.exec(str)
|
64
|
+
parser = new p.parser(match)
|
65
|
+
break
|
66
|
+
|
67
|
+
return parser
|
68
|
+
|
69
|
+
|
70
|
+
Written.Parsers.Inline = new class
|
71
|
+
constructor: ->
|
72
|
+
@parsers = []
|
73
|
+
|
74
|
+
freeze: ->
|
75
|
+
Object.freeze(this)
|
76
|
+
Object.freeze(@parsers)
|
77
|
+
|
78
|
+
|
79
|
+
register: (parser, rule) ->
|
80
|
+
@parsers.push {
|
81
|
+
rule: rule,
|
82
|
+
parser: parser
|
83
|
+
}
|
84
|
+
|
85
|
+
parse: (block) =>
|
86
|
+
walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT)
|
87
|
+
for p in @parsers
|
88
|
+
walker.currentNode = walker.root
|
89
|
+
|
90
|
+
while walker.nextNode()
|
91
|
+
if match = p.rule.exec(walker.currentNode.textContent)
|
92
|
+
new p.parser(match).render(walker.currentNode)
|
93
|
+
|
94
|
+
|
95
|
+
isLeafNode: (node) ->
|
96
|
+
if node.children.length == 0
|
97
|
+
return NodeFilter.FILTER_ACCEPT
|
98
|
+
|
@@ -0,0 +1,138 @@
|
|
1
|
+
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+apacheconf+bash+c+csharp+cpp+ruby+git+go+haml+http+java+nginx+objectivec+php+python+scss */
|
2
|
+
/**
|
3
|
+
* prism.js default theme for JavaScript, CSS and HTML
|
4
|
+
* Based on dabblet (http://dabblet.com)
|
5
|
+
* @author Lea Verou
|
6
|
+
*/
|
7
|
+
|
8
|
+
code[class*="language-"],
|
9
|
+
pre[class*="language-"] {
|
10
|
+
color: black;
|
11
|
+
text-shadow: 0 1px white;
|
12
|
+
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
13
|
+
direction: ltr;
|
14
|
+
text-align: left;
|
15
|
+
white-space: pre;
|
16
|
+
word-spacing: normal;
|
17
|
+
word-break: normal;
|
18
|
+
word-wrap: normal;
|
19
|
+
line-height: 1.5;
|
20
|
+
|
21
|
+
-moz-tab-size: 4;
|
22
|
+
-o-tab-size: 4;
|
23
|
+
tab-size: 4;
|
24
|
+
|
25
|
+
-webkit-hyphens: none;
|
26
|
+
-moz-hyphens: none;
|
27
|
+
-ms-hyphens: none;
|
28
|
+
hyphens: none;
|
29
|
+
}
|
30
|
+
|
31
|
+
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
32
|
+
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
33
|
+
text-shadow: none;
|
34
|
+
background: #b3d4fc;
|
35
|
+
}
|
36
|
+
|
37
|
+
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
38
|
+
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
39
|
+
text-shadow: none;
|
40
|
+
background: #b3d4fc;
|
41
|
+
}
|
42
|
+
|
43
|
+
@media print {
|
44
|
+
code[class*="language-"],
|
45
|
+
pre[class*="language-"] {
|
46
|
+
text-shadow: none;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
/* Code blocks */
|
51
|
+
pre[class*="language-"] {
|
52
|
+
padding: 1em;
|
53
|
+
margin: .5em 0;
|
54
|
+
overflow: auto;
|
55
|
+
}
|
56
|
+
|
57
|
+
:not(pre) > code[class*="language-"],
|
58
|
+
pre[class*="language-"] {
|
59
|
+
background: #f5f2f0;
|
60
|
+
}
|
61
|
+
|
62
|
+
/* Inline code */
|
63
|
+
:not(pre) > code[class*="language-"] {
|
64
|
+
padding: .1em;
|
65
|
+
border-radius: .3em;
|
66
|
+
}
|
67
|
+
|
68
|
+
.token.comment,
|
69
|
+
.token.prolog,
|
70
|
+
.token.doctype,
|
71
|
+
.token.cdata {
|
72
|
+
color: slategray;
|
73
|
+
}
|
74
|
+
|
75
|
+
.token.punctuation {
|
76
|
+
color: #999;
|
77
|
+
}
|
78
|
+
|
79
|
+
.namespace {
|
80
|
+
opacity: .7;
|
81
|
+
}
|
82
|
+
|
83
|
+
.token.property,
|
84
|
+
.token.tag,
|
85
|
+
.token.boolean,
|
86
|
+
.token.number,
|
87
|
+
.token.constant,
|
88
|
+
.token.symbol,
|
89
|
+
.token.deleted {
|
90
|
+
color: #905;
|
91
|
+
}
|
92
|
+
|
93
|
+
.token.selector,
|
94
|
+
.token.attr-name,
|
95
|
+
.token.string,
|
96
|
+
.token.char,
|
97
|
+
.token.builtin,
|
98
|
+
.token.inserted {
|
99
|
+
color: #690;
|
100
|
+
}
|
101
|
+
|
102
|
+
.token.operator,
|
103
|
+
.token.entity,
|
104
|
+
.token.url,
|
105
|
+
.language-css .token.string,
|
106
|
+
.style .token.string {
|
107
|
+
color: #a67f59;
|
108
|
+
background: hsla(0, 0%, 100%, .5);
|
109
|
+
}
|
110
|
+
|
111
|
+
.token.atrule,
|
112
|
+
.token.attr-value,
|
113
|
+
.token.keyword {
|
114
|
+
color: #07a;
|
115
|
+
}
|
116
|
+
|
117
|
+
.token.function {
|
118
|
+
color: #DD4A68;
|
119
|
+
}
|
120
|
+
|
121
|
+
.token.regex,
|
122
|
+
.token.important,
|
123
|
+
.token.variable {
|
124
|
+
color: #e90;
|
125
|
+
}
|
126
|
+
|
127
|
+
.token.important,
|
128
|
+
.token.bold {
|
129
|
+
font-weight: bold;
|
130
|
+
}
|
131
|
+
.token.italic {
|
132
|
+
font-style: italic;
|
133
|
+
}
|
134
|
+
|
135
|
+
.token.entity {
|
136
|
+
cursor: help;
|
137
|
+
}
|
138
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'written/parsers'
|
2
|
+
require 'written/node'
|
3
|
+
|
4
|
+
module Written
|
5
|
+
class Document
|
6
|
+
|
7
|
+
attr_reader :nodes
|
8
|
+
|
9
|
+
def initialize(content)
|
10
|
+
@nodes = content.lines.map do |text|
|
11
|
+
Node.new(text.chomp)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def parsers
|
16
|
+
[
|
17
|
+
Written::Parsers::Code,
|
18
|
+
Written::Parsers::Heading,
|
19
|
+
Written::Parsers::Image,
|
20
|
+
Written::Parsers::List,
|
21
|
+
Written::Parsers::Word,
|
22
|
+
Written::Parsers::Link
|
23
|
+
]
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse!
|
27
|
+
parsers.each do |parser|
|
28
|
+
parser.parse!(self)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_html
|
33
|
+
@nodes.map do |node|
|
34
|
+
if node.instance_of?(Written::Node)
|
35
|
+
"<p>#{node.to_s}</p>"
|
36
|
+
else
|
37
|
+
node.to_s
|
38
|
+
end
|
39
|
+
end.join
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/written/node.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Written
|
2
|
+
module Nodes
|
3
|
+
autoload :Image, 'written/nodes/image'
|
4
|
+
autoload :UnorderedList, 'written/nodes/unordered_list'
|
5
|
+
autoload :OrderedList, 'written/nodes/ordered_list'
|
6
|
+
autoload :Code, 'written/nodes/code'
|
7
|
+
autoload :Heading, 'written/nodes/heading'
|
8
|
+
end
|
9
|
+
|
10
|
+
class Node
|
11
|
+
attr_accessor :content
|
12
|
+
|
13
|
+
def initialize(content)
|
14
|
+
@content = content || String.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@content
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Written
|
4
|
+
module Nodes
|
5
|
+
class Code < Node
|
6
|
+
|
7
|
+
attr_reader :offset
|
8
|
+
|
9
|
+
attr_accessor :title, :block
|
10
|
+
|
11
|
+
def initialize(data)
|
12
|
+
|
13
|
+
@offset = data.offset(0)[0]
|
14
|
+
@language = (data[3] || "").strip
|
15
|
+
@block = nil
|
16
|
+
@title = nil
|
17
|
+
|
18
|
+
@content = data[4]
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def block?
|
23
|
+
@block == true
|
24
|
+
end
|
25
|
+
|
26
|
+
def content=(new_content)
|
27
|
+
if new_content.is_a?(Array)
|
28
|
+
@content = CGI::escapeHTML(new_content.join("\n"))
|
29
|
+
else
|
30
|
+
@content = new_content
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def code_element
|
35
|
+
str = "<code"
|
36
|
+
|
37
|
+
unless @language.nil?
|
38
|
+
str << " class='language-#{@language}'>"
|
39
|
+
end
|
40
|
+
|
41
|
+
str << @content
|
42
|
+
str << "</code>"
|
43
|
+
str
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
|
48
|
+
str = String.new
|
49
|
+
|
50
|
+
unless @title.nil?
|
51
|
+
str << "<header>#{@title}</header>"
|
52
|
+
end
|
53
|
+
|
54
|
+
str << code_element
|
55
|
+
|
56
|
+
if block?
|
57
|
+
return "<pre>#{str}</pre>"
|
58
|
+
else
|
59
|
+
return str
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Written
|
2
|
+
module Nodes
|
3
|
+
class OrderedList < Node
|
4
|
+
|
5
|
+
def initialize(text)
|
6
|
+
@content = "<li>#{text}</li>"
|
7
|
+
end
|
8
|
+
|
9
|
+
def append(text)
|
10
|
+
@content << "<li>#{text}</li>"
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"<ol>#{@content}</ol>"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Written
|
2
|
+
module Nodes
|
3
|
+
class UnorderedList < Node
|
4
|
+
|
5
|
+
def initialize(text)
|
6
|
+
@content = "<li>#{text}</li>"
|
7
|
+
end
|
8
|
+
|
9
|
+
def append(text)
|
10
|
+
@content << "<li>#{text}</li>"
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"<ul>#{@content}</ul>"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Written::Parsers
|
2
|
+
class Base
|
3
|
+
class << self
|
4
|
+
private :new
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.parse!(document)
|
8
|
+
i = 0
|
9
|
+
while !document.nodes[i].nil? do
|
10
|
+
node = new(document, document.nodes[i], i).parse!
|
11
|
+
i = document.nodes.index(node) + 1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(document, node, index)
|
16
|
+
@document = document
|
17
|
+
@node = node
|
18
|
+
@index = index
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse!
|
22
|
+
return @node
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Written::Parsers
|
2
|
+
class Code < Base
|
3
|
+
OPENING_TAG = /((~{3,})([a-z]+)?)(.+)?/i
|
4
|
+
attr_accessor :open
|
5
|
+
|
6
|
+
def parse!
|
7
|
+
|
8
|
+
unless @node.instance_of?(Written::Node)
|
9
|
+
return @node
|
10
|
+
end
|
11
|
+
|
12
|
+
while m = OPENING_TAG.match(@node.content)
|
13
|
+
node = Written::Nodes::Code.new(m)
|
14
|
+
@node.content.slice!(m.offset(1)[0], m[1].size)
|
15
|
+
close(node, m[2].size)
|
16
|
+
end
|
17
|
+
|
18
|
+
return @node
|
19
|
+
end
|
20
|
+
|
21
|
+
def close(node, tildeCount)
|
22
|
+
regex = Regexp.new("(~{#{tildeCount},})", Regexp::IGNORECASE)
|
23
|
+
|
24
|
+
if match = regex.match(node.content)
|
25
|
+
@node.content.slice!(node.offset, match.offset(1)[1])
|
26
|
+
node.content.slice!(match.offset(0)[0], node.content.length - match.offset(0)[0])
|
27
|
+
node.content.lstrip!
|
28
|
+
@node.content.insert(node.offset, node.to_s)
|
29
|
+
|
30
|
+
elsif node.offset == 0
|
31
|
+
extract_siblings(node, regex)
|
32
|
+
@node = node
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_siblings(node, regex)
|
37
|
+
i = @index + 1
|
38
|
+
nodes = []
|
39
|
+
node.title = (node.content || "").strip
|
40
|
+
|
41
|
+
while !(n = @document.nodes[i]).nil?
|
42
|
+
match = regex.match(n.content)
|
43
|
+
@document.nodes.delete_at(i)
|
44
|
+
if match.nil?
|
45
|
+
nodes.push n
|
46
|
+
else
|
47
|
+
break
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
node.content = nodes
|
52
|
+
node.block = true
|
53
|
+
|
54
|
+
@document.nodes[@index] = node
|
55
|
+
|
56
|
+
return node
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Written::Parsers
|
2
|
+
class Heading < Base
|
3
|
+
RULE = /^(\#{1,6} )(.+)/i
|
4
|
+
|
5
|
+
def parse!
|
6
|
+
|
7
|
+
unless @node.instance_of?(Written::Node)
|
8
|
+
return @node
|
9
|
+
end
|
10
|
+
|
11
|
+
if match = RULE.match(@node.content)
|
12
|
+
size = match[1].length - 1
|
13
|
+
@node = Written::Nodes::Heading.new(size, match[2])
|
14
|
+
@document.nodes[@index] = @node
|
15
|
+
end
|
16
|
+
return @node
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Written::Parsers
|
2
|
+
class Image < Base
|
3
|
+
RULE = /^(!{1}\[([^\]]+)\])(\(([^\s]+)?\))$/i
|
4
|
+
|
5
|
+
def parse!
|
6
|
+
|
7
|
+
unless @node.instance_of?(Written::Node)
|
8
|
+
return @node
|
9
|
+
end
|
10
|
+
|
11
|
+
if match = RULE.match(@node.content)
|
12
|
+
@node = Written::Nodes::Image.new(match[2], match[4])
|
13
|
+
@document.nodes[@index] = @node
|
14
|
+
end
|
15
|
+
return @node
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Written::Parsers
|
2
|
+
class Link < Base
|
3
|
+
RULE = /!{0}(\[([^\]]+)\])(\(([^\)]+)\))/i
|
4
|
+
|
5
|
+
def parse!
|
6
|
+
while match = RULE.match(@node.content) do
|
7
|
+
@node.content.gsub! match[0], "<a href='#{match[4]}'>#{match[2]}</a>"
|
8
|
+
end
|
9
|
+
return @node
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Written::Parsers
|
2
|
+
class List < Base
|
3
|
+
UL = /^(-\s)(.+)?$/i
|
4
|
+
OL = /^(\d+\.\s)(.+)?$/i
|
5
|
+
|
6
|
+
def parse!
|
7
|
+
|
8
|
+
unless @node.instance_of?(Written::Node)
|
9
|
+
return @node
|
10
|
+
end
|
11
|
+
|
12
|
+
if match = UL.match(@node.content)
|
13
|
+
list! match, Written::Nodes::UnorderedList
|
14
|
+
elsif match = OL.match(@node.content)
|
15
|
+
list! match, Written::Nodes::OrderedList
|
16
|
+
end
|
17
|
+
return @node
|
18
|
+
end
|
19
|
+
|
20
|
+
def list!(match, tag)
|
21
|
+
previous_node = @document.nodes[@index - 1]
|
22
|
+
if previous_node.nil? || !previous_node.instance_of?(tag)
|
23
|
+
@node = tag.new(match[2])
|
24
|
+
@document.nodes[@index] = @node
|
25
|
+
else
|
26
|
+
@index -= 1
|
27
|
+
@document.nodes.delete(@node)
|
28
|
+
previous_node.append(match[2])
|
29
|
+
@node = previous_node
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Written::Parsers
|
2
|
+
class Word < Base
|
3
|
+
RULE = /((\*{1,2})([^\*]+)(\*{1,2}))/i
|
4
|
+
|
5
|
+
def parse!
|
6
|
+
while match = RULE.match(@node.content) do
|
7
|
+
if match[2].length == 1
|
8
|
+
@node.content.gsub! match[0], "<em>#{match[3]}</em>"
|
9
|
+
elsif match[2].length == 2
|
10
|
+
@node.content.gsub! match[0], "<strong>#{match[3]}</strong>"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
return @node
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Written
|
2
|
+
module Parsers
|
3
|
+
autoload :Base, 'written/parsers/base'
|
4
|
+
autoload :Word, 'written/parsers/word'
|
5
|
+
autoload :List, 'written/parsers/list'
|
6
|
+
autoload :Image, 'written/parsers/image'
|
7
|
+
autoload :Code, 'written/parsers/code'
|
8
|
+
autoload :Heading, 'written/parsers/heading'
|
9
|
+
autoload :Link, 'written/parsers/link'
|
10
|
+
end
|
11
|
+
end
|