written 0.0.2

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 (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