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,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,4 @@
1
+ #= require_tree ./vendors
2
+ #= require_tree ./written/core
3
+ #= require_tree ./written/extensions
4
+ #= require_tree ./written/parsers
@@ -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,21 @@
1
+ [data-editor="written"] {
2
+ white-space: pre-wrap;
3
+
4
+ p {
5
+ min-height: 1.2em;
6
+ }
7
+
8
+ h1 {
9
+ font-size: 3em;
10
+ }
11
+
12
+ ul, li {
13
+ list-style-type: none;
14
+ }
15
+
16
+ figure {
17
+ background: gray;
18
+ img {
19
+ }
20
+ }
21
+ }
@@ -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
@@ -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,15 @@
1
+ module Written
2
+ module Nodes
3
+ class Heading < Node
4
+ def initialize(size, content)
5
+ @size = size
6
+ @content = content
7
+ end
8
+
9
+ def to_s
10
+ "<h#{@size}>#{@content}</h#{@size}>"
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,14 @@
1
+ module Written
2
+ module Nodes
3
+ class Image < Node
4
+ def initialize(title, src)
5
+ @title = title
6
+ @src = src
7
+ end
8
+
9
+ def to_s
10
+ "<figure><img src='#{@src}' /><figcaption>#{@title}</figcaption></figure>"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -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