ecrire 0.26.3 → 0.27.0

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/bin/ecrire +4 -0
  4. data/lib/ecrire/app/assets/javascripts/admin/editor/content.coffee +20 -2
  5. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/code.coffee +78 -24
  6. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/heading.coffee +1 -1
  7. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/image.coffee +1 -1
  8. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/link.coffee +1 -1
  9. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/lists.coffee +1 -1
  10. data/lib/ecrire/app/assets/javascripts/admin/editor/parsers/word.coffee +1 -1
  11. data/lib/ecrire/app/assets/javascripts/admin/posts/header.coffee +1 -2
  12. data/lib/ecrire/app/assets/stylesheets/admin/base.css.scss +12 -13
  13. data/lib/ecrire/app/assets/stylesheets/admin/header.css.scss +5 -1
  14. data/lib/ecrire/app/assets/stylesheets/admin/navigation.css.scss +60 -49
  15. data/lib/ecrire/app/assets/stylesheets/admin/posts.css.scss +2 -2
  16. data/lib/ecrire/app/assets/stylesheets/editor/code.css.scss +21 -2
  17. data/lib/ecrire/app/assets/stylesheets/vendor/prism.scss +0 -13
  18. data/lib/ecrire/app/models/admin/post.rb +8 -1
  19. data/lib/ecrire/app/models/post.rb +1 -0
  20. data/lib/ecrire/app/views/admin/posts/titles/_title.html.erb +4 -2
  21. data/lib/ecrire/commands/server.rb +1 -0
  22. data/lib/ecrire/markdown/node.rb +1 -1
  23. data/lib/ecrire/markdown/nodes/code.rb +63 -0
  24. data/lib/ecrire/markdown/parsers/code.rb +39 -18
  25. data/lib/ecrire/onboarding/assets/stylesheets/browser/base.scss +0 -0
  26. data/lib/ecrire/onboarding/assets/stylesheets/browser.scss +3 -0
  27. data/lib/ecrire/onboarding/assets/stylesheets/mobile/base.scss +0 -0
  28. data/lib/ecrire/onboarding/assets/stylesheets/mobile.scss +3 -0
  29. data/lib/ecrire/onboarding/assets/stylesheets/{theme.css.scss → shared/base.scss} +1 -0
  30. data/lib/ecrire/onboarding/assets/stylesheets/{welcome.css.scss → shared/welcome.scss} +1 -0
  31. data/lib/ecrire/onboarding/assets/stylesheets/tablet/base.scss +0 -0
  32. data/lib/ecrire/onboarding/assets/stylesheets/tablet.scss +4 -0
  33. data/lib/ecrire/onboarding/views/layouts/application.html.erb +6 -4
  34. data/lib/ecrire/theme/template/Gemfile +1 -1
  35. data/lib/ecrire/theme/template/assets/javascripts/application.js.coffee +1 -0
  36. data/lib/ecrire/version.rb +1 -1
  37. data/test/editor/models/post_test.rb +65 -0
  38. data/test/markdown/markdown_test.rb +13 -0
  39. metadata +18 -11
  40. data/lib/ecrire/markdown/nodes/code_block.rb +0 -30
  41. /data/lib/ecrire/onboarding/assets/javascripts/{theme.js → application.js} +0 -0
  42. /data/lib/ecrire/onboarding/assets/stylesheets/{complete.css.scss → shared/complete.scss} +0 -0
  43. /data/lib/ecrire/onboarding/assets/stylesheets/{fonts.css.scss → shared/fonts.scss} +0 -0
  44. /data/lib/ecrire/onboarding/assets/stylesheets/{form.css.scss → shared/form.scss} +0 -0
  45. /data/lib/ecrire/onboarding/assets/stylesheets/{message.css.scss → shared/message.scss} +0 -0
  46. /data/lib/ecrire/onboarding/assets/stylesheets/{terminal.css.scss → shared/terminal.scss} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 42ebfbcd7b234a8ed0eced636045769471049b42
4
- data.tar.gz: fcf2461eb398ad52f848e08f4be8d2a8975ef354
3
+ metadata.gz: 6ba5385d6716080f7627c1fca5a2a6402f86a0d3
4
+ data.tar.gz: 8da73ff93cd99d67cce527fe12e826a19bdceb12
5
5
  SHA512:
6
- metadata.gz: 5fc4670039672091e34e0055e400063b5715c95f39375efc770f528d32d09435aff6cccc141be01e901d99cd9ba40aa05c4aebbc4ba9c76e04048eceace9c07d
7
- data.tar.gz: 633ca19d3ad1b9d88a3da8f8419c737cd89b0750ea423b19a92de5593f98c0ce48668b523db7b2ccf04d3afc609e33411caa3b9eb47ff83bd3ec407e42d62d4f
6
+ metadata.gz: dfe75295b9fd1430d0d5a6e35486c0f928c81f695da95dac37810386daa8db00a7077e88de1c6e0495ffa707d159f1bda1783dc1b9b6e76a759455c749fc6ed2
7
+ data.tar.gz: 89a2986375f70b026f629647ac4a2f8fa84400c37432bc4fe97fb9c48ef475753cc9d2d321ead18b634638001f2adaa1599d2d3067af3a17ce770bbcbda58748
data/.gitignore CHANGED
@@ -15,7 +15,9 @@
15
15
  log
16
16
  tmp
17
17
 
18
+ Dockerfile
19
+ docker-compose.yml
18
20
  .DS_Store
19
21
  Gemfile.lock
20
22
 
21
- secrets.yml
23
+ /lib/ecrire/template/secrets.yml
data/bin/ecrire CHANGED
@@ -21,6 +21,10 @@ parser = OptionParser.new do |opts|
21
21
  options[:Port] = port
22
22
  end
23
23
 
24
+ opts.on '-b IP', '--binding=IP', "Binds Rails to the specified IP.", "Default: localhost" do |ip|
25
+ options[:Host] = ip
26
+ end
27
+
24
28
  opts.separator ''
25
29
  opts.separator 'ecrire console'
26
30
 
@@ -3,7 +3,7 @@ ObserveJS.bind 'Editor.Content', class @Editor
3
3
  loaded: =>
4
4
  @on 'keydown', @linefeed
5
5
 
6
- @parsers = Editor.Parsers
6
+ @parsers = Editor.Parsers.sort()
7
7
 
8
8
  @extensions = Editor.Extensions.map (ext) =>
9
9
  new ext(this)
@@ -357,5 +357,23 @@ class Mutations
357
357
  else
358
358
  'none'
359
359
 
360
- Editor.Parsers = []
360
+ class Parsers
361
+ constructor: ->
362
+ @store = {}
363
+
364
+ add: (name, kls) =>
365
+ @store[name] = kls
366
+
367
+ sort: =>
368
+ [
369
+ @store.lists
370
+ @store.code
371
+ @store.image
372
+ @store.headers
373
+ @store.link
374
+ @store.word
375
+ ]
376
+
377
+ Editor.Parsers = new Parsers()
378
+
361
379
  Editor.Extensions = []
@@ -1,43 +1,97 @@
1
- Editor.Parsers.push class
2
- rule: /^((~{3,})([a-z]+)?)(.+)?(~{3,}$)?/mi
1
+ Editor.Parsers.add 'code', class
2
+ rules:
3
+ start: /((~{3,})([a-z]+)?)(.+)?/i
3
4
 
4
5
  constructor: (node) ->
5
- @nodes = [node]
6
- @tildeCount = 0
6
+ @walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT)
7
7
 
8
8
  isMatched: =>
9
- @match = @rule.exec(@nodes[0].textContent)
10
- if @match? && @match[0]?
11
- @tildeCount = @match[2].length
12
- @collectSiblingsUntilMatched(@tildeCount, @nodes[0])
9
+ @matches().length > 0
13
10
 
14
- collectSiblingsUntilMatched: (count, node) =>
15
- node = node.nextSibling
11
+ matches: =>
12
+ matches = []
13
+ while node = @walker.nextNode()
14
+ match = @extract(node)
15
+ if match?
16
+ matches.push match
17
+ @walker.currentNode = match.contentNodes[match.contentNodes.length - 1]
18
+
19
+ @matches = ->
20
+ matches
21
+ @matches()
22
+
23
+ extract: (node) =>
24
+ match = @rules.start.exec(node.textContent)
25
+
26
+ if !match?
27
+ return null
28
+
29
+ return @split(match, node)
30
+
31
+ split: (match, node) ->
32
+ node = node.splitText(match.index)
33
+ match.contentNodes = [node]
34
+
35
+ if match.index > 0
36
+ match.inline = true
37
+
38
+ match.node = node
39
+ match.tildeCount = match[2].length
40
+ match.contentNodes.push node.splitText(match.tildeCount)
41
+
42
+ if m = @findCloseTag(match.contentNodes[1], match.tildeCount)
43
+ match.contentNodes[1].splitText(m.index + m[0].length)
44
+ match.closeNode = match.contentNodes[match.contentNodes.length - 1]
45
+
46
+ if match.closeNode?
47
+ match.inline = true
48
+ return match
49
+
50
+ node = @walker.root.nextSibling
16
51
  while node
17
- @nodes.push node
18
- nextNode = node.nextSibling
52
+
53
+ sibling = node.nextSibling
19
54
  node.remove()
20
55
 
21
- match = @rule.exec(node.textContent)
56
+ textNode = document.createTextNode('\n' + node.textContent)
57
+ match.contentNodes.push textNode
22
58
 
23
- if match? && match[2]? && match[2].length == count
59
+ if m = @findCloseTag(textNode, match.tildeCount)
60
+ match.closeNode = node
24
61
  break
25
62
 
26
- node = nextNode
63
+ node = sibling
64
+
65
+ return match
66
+
67
+ findCloseTag: (node, tildeCount) =>
68
+ rule = new RegExp("(~{#{tildeCount},})", 'i')
69
+ rule.exec(node.textContent)
70
+
27
71
 
28
72
 
29
73
  render: =>
30
- pre = "<pre>".toHTML()
31
- code = "<code as='Editor.Code'>".toHTML()
74
+ root = @walker.root
75
+
76
+ for match in @matches()
77
+
78
+ code = "<code as='Editor.Code'>".toHTML()
79
+ match.node.parentElement.replaceChild(code, match.node)
80
+
81
+ if match.inline?
82
+ parent = match.node.parentElement
83
+
84
+ code.appendChild(line) for line in match.contentNodes
32
85
 
33
- if @match[3]?
34
- code.classList.add("language-#{@match[3]}")
35
86
 
36
- texts = @nodes.map (n) -> n.textContent
87
+ if match[3]?
88
+ code.classList.add("language-#{match[3]}")
37
89
 
38
- code.textContent = texts.join('\n')
90
+ Prism.highlightElement(code)
39
91
 
40
- Prism.highlightElement(code)
41
- pre.appendChild(code)
42
- pre
92
+ if !match.inline?
93
+ pre = "<pre>".toHTML()
94
+ pre.appendChild(code)
95
+ root = pre
43
96
 
97
+ root
@@ -1,4 +1,4 @@
1
- Editor.Parsers.push class
1
+ Editor.Parsers.add 'headers', class
2
2
  rule: /^(#{1,6}) /i
3
3
 
4
4
  constructor: (node) ->
@@ -1,4 +1,4 @@
1
- Editor.Parsers.push class
1
+ Editor.Parsers.add 'image', class
2
2
  rule: /^(!{1}\[([^\]]+)\])(\(([^\s]+)?\))$/i
3
3
 
4
4
  constructor: (node) ->
@@ -1,4 +1,4 @@
1
- Editor.Parsers.push class
1
+ Editor.Parsers.add 'link', class
2
2
  rule: /!{0}(\[([^\]]+)\])(\(([^\)]+)\))/gi
3
3
 
4
4
  constructor: (node, el) ->
@@ -1,4 +1,4 @@
1
- Editor.Parsers.push class
1
+ Editor.Parsers.add 'lists', class
2
2
  rules: [
3
3
  {
4
4
  type: 'ul'
@@ -1,4 +1,4 @@
1
- Editor.Parsers.push class
1
+ Editor.Parsers.add 'word', class
2
2
  rule: /((\*{1,2})[^\*]+(\*{1,2}))/gi
3
3
 
4
4
  constructor: (node, el) ->
@@ -9,8 +9,6 @@ ObserveJS.bind 'Post.Header', class
9
9
 
10
10
  @on 'ObserveJS:XHR:Failed', @failed
11
11
 
12
- @on 'click', @retrieve('div.error.status').querySelector('button'), @clear
13
-
14
12
  @on 'images:create', @refresh
15
13
  @on 'images:destroy', @refresh
16
14
  @on 'titles:index', @popup
@@ -50,6 +48,7 @@ ObserveJS.bind 'Post.Header', class
50
48
  @hide(@retrieve('div.status.uploading'))
51
49
  @show(@retrieve('div.error.status'))
52
50
  ul = @retrieve('div.error.status').querySelector('ul')
51
+ @on 'click', @retrieve('div.error.status').querySelector('button'), @clear
53
52
  for error in errors
54
53
  ul.insertAdjacentHTML('beforeend', "<li>#{error}</li>")
55
54
 
@@ -101,8 +101,8 @@ main {
101
101
  }
102
102
 
103
103
  .button {
104
- @include transition(text-shadow 0.1s ease-in, border-color 0.1s, background-color 0.1s, box-shadow 0.1s, color 0.1s);
105
- border: 1px solid darken($bright-blue, 15%);
104
+ @include transition(border-color 0.1s, background-color 0.1s, box-shadow 0.2s, color 0.1s);
105
+ border: 1px solid rgba(black, 0.25);
106
106
  border-radius: 2px;
107
107
  text-decoration: none;
108
108
  padding: 4px 8px;
@@ -110,26 +110,25 @@ main {
110
110
  font-size: 0.8em;
111
111
  text-transform: uppercase;
112
112
  font-weight: bold;
113
- background-color: $bright-blue;
114
-
113
+ background-color: rgba(white, 0.05);
115
114
  color: lighten($navy-blue, 100%);
116
- box-shadow: inset 0 1px 0 0 lighten($bright-blue, 15%);
115
+ box-shadow: inset 0 1px 0 0 transparent, 0 0 2px 0 transparent;
117
116
 
118
117
  &.selected {
119
- background-color: darken($bright-blue, 15%);
120
- border-color: darken($bright-blue, 25%);
118
+ background-color: rgba(black, 0.3);
119
+ border-color: rgba(black, 0.3);
121
120
  box-shadow: none;
122
121
  }
123
122
 
124
123
  &:hover:not(.selected) {
125
- background-color: lighten($bright-blue, 5%);
126
- color: lighten($navy-blue, 100%);
127
- border-color: darken($bright-blue, 25%);
128
- box-shadow: inset 0 1px 0 0 lighten($bright-blue, 15%), 0 0 2px 0 darken($bright-blue, 15%);
124
+ background-color: rgba(white, 0.05);
125
+ color: white;
126
+ border-color: rgba(black, 0.3);
127
+ box-shadow: inset 0 1px 0 0 rgba(white, 0.35), 0 0 2px 0 rgba(black, 0.2);
129
128
  }
130
129
 
131
130
  &:active:not(.selected) {
132
- box-shadow: inset 0 1px 2px 0 darken($bright-blue, 15%), 0 0 2px 0 transparent;
133
- background-color: darken($bright-blue, 5%);
131
+ box-shadow: inset 0 1px 2px 0 rgba(black, 0.3), 0 0 2px 0 transparent;
132
+ background-color: rgba(black, 0.3);
134
133
  }
135
134
  }
@@ -4,11 +4,15 @@ main.posts > header {
4
4
  @include align-items(center);
5
5
  @include justify-content(center);
6
6
  @include flex-direction(column);
7
+
7
8
  background-color: $bright-blue;
8
9
  background-size: cover;
10
+ background-position: center left;
11
+
9
12
  color: lighten($bright-blue, 50%);
10
- max-height: 200px;
13
+
11
14
  height: 30vh;
15
+ min-height: 200px;
12
16
  }
13
17
 
14
18
  main.edit.posts > header > div.title {
@@ -1,3 +1,8 @@
1
+ nav#Menu.admin section {
2
+ @include display(flex);
3
+ height: 100%;
4
+ }
5
+
1
6
  nav#Menu.admin {
2
7
  @include display(flex);
3
8
  @include align-content(flex-start);
@@ -5,87 +10,93 @@ nav#Menu.admin {
5
10
  @include justify-content(space-between);
6
11
  @include flex-wrap(wrap);
7
12
 
8
- height: 4em;
13
+ height: 3em;
9
14
  font-family: $fonts;
10
15
  font-size: 16px;
11
16
  font-weight: bold;
12
- padding: 0 0.5em;
17
+ padding: 0 2em;
13
18
  z-index: 10;
14
19
 
15
- background-color: darken($gray-blue, 25%);
16
- color: lighten($gray-blue, 15%);
20
+ background-color: lighten($gray-blue, 25%);
21
+ color: darken($gray-blue, 30%);
17
22
  border-bottom: 1px solid darken($gray-blue, 75%);
18
- box-shadow: inset 0 -1px 0 0 darken($gray-blue, 15%);
19
- text-shadow: 0 -1px darken($light-blue, 75%);
20
23
 
21
24
  .button {
22
- @include transition(text-shadow 0.2s ease-in, border-color 0.2s, background-color 0.2s, box-shadow 0.2s, color 0.2s);
23
- border: 1px solid darken($navy-blue, 5%);
24
- border-radius: 2px;
25
+ @include display(flex);
26
+ @include flex(0 0 auto);
27
+ @include align-items(center);
28
+ @include justify-content(center);
29
+
30
+ border-radius: 0;
31
+ border: none;
32
+ background: none;
33
+
34
+ height: 100%;
35
+
25
36
  text-decoration: none;
26
- padding: 4px 8px;
27
- margin: 0 0.5em;
37
+ padding: 4px 2em;
38
+ margin: 0;
28
39
  font-size: 0.8em;
29
40
  text-transform: uppercase;
30
- font-weight: bolder;
31
- background-color: lighten($navy-blue, 25%);
32
- box-shadow: inset 0 1px 0 lighten($navy-blue, 35%), 0 1px 1px darken($navy-blue, 15%);
33
- color: lighten($navy-blue, 60%);
34
- text-shadow: 0 1px lighten($navy-blue, 25%);
41
+ font-weight: lighter;
42
+ color: inherit;
43
+ text-shadow: none;
35
44
 
36
45
  &.selected {
37
- background-color: lighten($navy-blue, 25%);
38
- border-color: lighten($navy-blue, 25%);
39
46
  color: lighten($navy-blue, 100%);
40
47
  }
41
48
 
42
49
  &:hover:not(.selected) {
43
- border-color: darken($navy-blue, 25%);
50
+ text-decoration: underline;
51
+ box-shadow: none;
44
52
  }
45
53
 
46
54
  &:active:not(.selected) {
47
55
  color: lighten($navy-blue, 100%);
48
- text-shadow: 0 1px lighten($navy-blue, 25%);
49
- box-shadow: inset 0 0 2px darken($navy-blue, 15%), 0 1px 1px transparent;
50
- border-color: darken($navy-blue, 15%);
51
- }
52
-
53
- &.home {
54
- margin: 0 2em;
56
+ text-decoration: underline;
57
+ box-shadow: none;
55
58
  }
56
59
  }
57
60
  }
58
61
 
59
62
  nav#Menu.admin form.logout {
60
- @include transition(color 0.3s,background-color 0.3s, border 0.3s, box-shadow 0.3s);
61
- border: 1px solid lighten($red, 5%);
62
- border-radius: 2px;
63
- margin: 0;
64
- padding: 4px 8px;
65
- cursor: pointer;
66
- outline: none;
67
- background-color: lighten($red, 5%);
68
- color: lighten($red, 30%);
69
- box-shadow: inset 0 1px 0 0 lighten($red, 10%), 0 1px 1px darken($red, 20%);
70
-
71
- &:hover {
72
- border-color: darken($red, 10%);
73
- }
63
+ @include display(flex);
64
+ @include flex(0 0 auto);
65
+ @include align-items(center);
66
+ @include justify-content(center);
74
67
 
75
- &:active {
76
- box-shadow: none;
77
- }
68
+ margin: 0;
69
+ height: 100%;
78
70
 
79
71
  input {
80
- padding: 0;
72
+ @include transition(box-shadow 0.1s ease, background-color 0.1s ease);
73
+
74
+ background-color: lighten($red, 15%);
75
+ color: darken($red, 25%);
76
+
77
+ cursor: pointer;
78
+ outline: none;
79
+
80
+ padding: 6px 10px;
81
81
  margin: 0;
82
- background: none;
82
+
83
83
  border: none;
84
- font-size:inherit;
85
- color: inherit;
86
- font-weight: 800;
84
+ border-radius: 2px;
85
+
86
+ font-size: 0.9em;
87
87
  font-family: inherit;
88
- outline: none;
88
+ font-weight: bold;
89
+
90
+ box-shadow: 0 1px 1px 0 transparent;
91
+
92
+ &:hover {
93
+ box-shadow: 0 0.5px 0.5px 0 darken($red, 15%);
94
+ }
95
+
96
+ &:active {
97
+ box-shadow: 0 1px 1px 0 transparent;
98
+ background-color: darken($red, 5%);
99
+ }
89
100
  }
90
101
 
91
102
  }
@@ -51,7 +51,7 @@ main.posts > header > nav > div.publish.state {
51
51
  margin: 0 2em;
52
52
  width: 10em;
53
53
  height: 30px;
54
- background-color: #dddddd;
54
+ background-color: rgba(black, 0.2);
55
55
  color: darken($bright-blue, 25%);
56
56
  border-radius: 60px;
57
57
  cursor: pointer;
@@ -93,7 +93,7 @@ main.posts > header > nav > div.publish.state {
93
93
  }
94
94
 
95
95
  &[published='true'] {
96
- background-color: darken($bright-blue, 15%);
96
+ background-color: rgba(black, 0.5);
97
97
 
98
98
  label:first-child {
99
99
  background-color: darken($bright-blue, 25%);
@@ -1,4 +1,23 @@
1
- article.content pre {
1
+ article.content code, article.content pre {
2
+ border-radius: .3em;
3
+ }
4
+
5
+ article.content code {
2
6
  font-size: 0.7em;
3
- background: $light-blue;
7
+ }
8
+
9
+ article.content pre {
10
+ background: lighten($light-blue, 5%);
11
+ border-top: 1px solid $light-blue;
12
+ padding: 0.7em;
13
+ }
14
+
15
+ article.content pre > code {
16
+ background: none;
17
+ }
18
+
19
+ article.content :not(pre) > code {
20
+ background: lighten($light-blue, 5%);
21
+ border-top: 1px solid $light-blue;
22
+ padding: 0.2em 0.5em;
4
23
  }
@@ -37,19 +37,6 @@ code::selection, code ::selection {
37
37
  }
38
38
  }
39
39
 
40
- /* Code blocks */
41
- pre {
42
- padding: 1em;
43
- margin: .5em 0;
44
- overflow: auto;
45
- }
46
-
47
- /* Inline code */
48
- :not(pre) > code {
49
- padding: .1em;
50
- border-radius: .3em;
51
- }
52
-
53
40
  .token.comment,
54
41
  .token.prolog,
55
42
  .token.doctype,
@@ -42,7 +42,14 @@ module Admin
42
42
  html.xpath("//img").each do |img|
43
43
  img.remove
44
44
  end
45
- self.compiled_excerpt = html.xpath('//body').children[0..20].to_s
45
+
46
+ valid_elements = %w(p ul ol li).freeze
47
+
48
+ elements = html.xpath('//body').children[0..4].take_while do |el|
49
+ valid_elements.include?(el.name)
50
+ end
51
+
52
+ self.compiled_excerpt = elements.map(&:to_s).join
46
53
  end
47
54
 
48
55
  end
@@ -157,6 +157,7 @@ class Post < ActiveRecord::Base
157
157
 
158
158
  protected
159
159
 
160
+ #:nodoc:
160
161
  def update_tags
161
162
  ids = tags.map(&:id)
162
163
  unless ids == read_attribute(:tags)
@@ -1,5 +1,7 @@
1
- <% post = Post.new(titles: [title], published_at: title.post.published_at) %>
2
1
  <li>
3
2
  <%= content_tag :p, title.name %>
4
- <%= link_to url(Ecrire::Theme::Engine.post_path, post: post, absolute_path: true), url(Ecrire::Theme::Engine.post_path, post: post, absolute_path: true) %>
3
+ <% if title.post.published? %>
4
+ <% post = Post.new(titles: [title], published_at: title.post.published_at) %>
5
+ <%= link_to url(Ecrire::Theme::Engine.post_path, post: post, absolute_path: true), url(Ecrire::Theme::Engine.post_path, post: post, absolute_path: true) %>
6
+ <% end %>
5
7
  </li>
@@ -13,6 +13,7 @@ module Ecrire
13
13
 
14
14
  @server = Rails::Server.new
15
15
  @server.options[:Port] = options[:Port]
16
+ @server.options[:Host] = options[:Host]
16
17
  end
17
18
 
18
19
  def run!
@@ -3,7 +3,7 @@ module Ecrire::Markdown
3
3
  autoload :Image, 'ecrire/markdown/nodes/image'
4
4
  autoload :UnorderedList, 'ecrire/markdown/nodes/unordered_list'
5
5
  autoload :OrderedList, 'ecrire/markdown/nodes/ordered_list'
6
- autoload :CodeBlock, 'ecrire/markdown/nodes/code_block'
6
+ autoload :Code, 'ecrire/markdown/nodes/code'
7
7
  autoload :Heading, 'ecrire/markdown/nodes/heading'
8
8
  end
9
9
 
@@ -0,0 +1,63 @@
1
+ require 'active_support/core_ext/string'
2
+
3
+ module Ecrire::Markdown
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
+
16
+ @content = data[4]
17
+
18
+ end
19
+
20
+ def block?
21
+ @block == true
22
+ end
23
+
24
+ def content=(new_content)
25
+ if new_content.is_a?(Array)
26
+ @content = ERB::Util.html_escape(new_content.join("\n"))
27
+ else
28
+ @content = new_content
29
+ end
30
+ end
31
+
32
+ def code_element
33
+ str = "<code"
34
+
35
+ unless @language.nil?
36
+ str << " class='language-#{@language}'>"
37
+ end
38
+
39
+ str << @content
40
+ str << "</code>"
41
+ str
42
+ end
43
+
44
+ def to_s
45
+
46
+ str = String.new
47
+
48
+ unless @title.nil?
49
+ str << "<header>#{@title}</header>"
50
+ end
51
+
52
+ str << code_element
53
+
54
+ if block?
55
+ return "<pre>#{str}</pre>"
56
+ else
57
+ return str
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
@@ -1,6 +1,9 @@
1
+ require 'byebug'
2
+
1
3
  module Ecrire::Markdown::Parsers
2
4
  class Code < Base
3
- OPENING_TAG = /^((~{3,})([a-z]+)?)( (.*))?$/i
5
+ OPENING_TAG = /((~{3,})([a-z]+)?)(.+)?/i
6
+ attr_accessor :open, :close
4
7
 
5
8
  def parse!
6
9
 
@@ -8,33 +11,51 @@ module Ecrire::Markdown::Parsers
8
11
  return @node
9
12
  end
10
13
 
11
- if match = OPENING_TAG.match(@node.content)
12
- count = match[2].size
13
- language = match[3]
14
- title = match[5]
15
- nodes = extract_code_nodes!(count)
16
- @node = Ecrire::Markdown::Nodes::CodeBlock.new(language, title, nodes)
17
- @document.nodes[@index] = @node
14
+ while m = OPENING_TAG.match(@node.content)
15
+ node = Ecrire::Markdown::Nodes::Code.new(m)
16
+ @node.content.slice!(m.offset(1)[0], m[1].size)
17
+ close(node, m[2].size)
18
18
  end
19
+
19
20
  return @node
20
21
  end
21
22
 
22
- def extract_code_nodes!(count)
23
- regex = Regexp.new("^(~{#{count}}$)")
24
- found_closing_tag = false
23
+ def close(node, tildeCount)
24
+ regex = Regexp.new("(~{#{tildeCount},})", Regexp::IGNORECASE)
25
+
26
+ if match = regex.match(node.content)
27
+ @node.content.slice!(node.offset, match.offset(1)[1])
28
+ node.content.slice!(match.offset(0)[0], node.content.length - match.offset(0)[0])
29
+ node.content.lstrip!
30
+ @node.content.insert(node.offset, node.to_s)
31
+
32
+ elsif node.offset == 0
33
+ extract_siblings(node, regex)
34
+ @node = node
35
+ end
36
+ end
37
+
38
+ def extract_siblings(node, regex)
25
39
  i = @index + 1
26
40
  nodes = []
41
+ node.title = (node.content || "").strip
27
42
 
28
- while !found_closing_tag
29
- break if @document.nodes[i].nil?
30
- node = @document.nodes.delete_at(i)
31
- if regex.match(node.content)
32
- found_closing_tag = true
43
+ while !(n = @document.nodes[i]).nil?
44
+ match = regex.match(n.content)
45
+ @document.nodes.delete_at(i)
46
+ if match.nil?
47
+ nodes.push n
33
48
  else
34
- nodes.push node
49
+ break
35
50
  end
36
51
  end
37
- nodes
52
+
53
+ node.content = nodes
54
+ node.block = true
55
+
56
+ @document.nodes[@index] = node
57
+
58
+ return node
38
59
  end
39
60
 
40
61
  end
@@ -0,0 +1,3 @@
1
+ @import 'variables';
2
+ @import 'shared/**/*';
3
+ @import 'browser/**/*';
@@ -0,0 +1,3 @@
1
+ @import 'variables';
2
+ @import 'shared/**/*';
3
+ @import 'mobile/**/*';
@@ -67,6 +67,7 @@ body {
67
67
  }
68
68
 
69
69
  h1 {
70
+ text-align: center;
70
71
  font-size: 6em;
71
72
  font-family: courier;
72
73
  font-weight: normal;
@@ -5,6 +5,7 @@
5
5
  }
6
6
 
7
7
  div.slogan {
8
+ text-align: center;
8
9
  font-size: 1.4em;
9
10
  letter-spacing: 2px;
10
11
 
@@ -0,0 +1,4 @@
1
+ @import 'variables';
2
+ @import 'shared/**/*';
3
+ @import 'tablet/**/*';
4
+
@@ -3,11 +3,13 @@
3
3
 
4
4
  <head>
5
5
  <%= title_tag 'Before you start blogging...' %>
6
- <%= stylesheet_link_tag "ecrire", media: "all", "data-turbolinks-track" => true %>
7
- <%= javascript_include_tag "ecrire", "base", "data-turbolinks-track" => true %>
8
6
 
9
- <%= stylesheet_link_tag "theme", media: "all", "data-turbolinks-track" => true %>
10
- <%= javascript_include_tag "theme", "base", "data-turbolinks-track" => true %>
7
+ <%= javascript_include_tag "ecrire", "application", "data-turbolinks-track" => true %>
8
+
9
+ <%= stylesheet_link_tag "browser", media: "(min-width: 1025px)", "data-turbolinks-track" => true %>
10
+ <%= stylesheet_link_tag "tablet", media: "(min-width: 641px) and (max-width: 1024px)", "data-turbolinks-track" => true %>
11
+ <%= stylesheet_link_tag "mobile", media: "(max-width: 640px)", "data-turbolinks-track" => true %>
12
+
11
13
  <%= csrf_meta_tags %>
12
14
  <%= favicon_tag %>
13
15
  </head>
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'ecrire', git: 'https://github.com/pothibo/ecrire'
3
+ gem 'ecrire', path: '/ecrire'
4
4
 
5
5
  group :required do
6
6
  gem 'rails', '~> 4.2'
@@ -1,3 +1,3 @@
1
1
  module Ecrire
2
- VERSION = '0.26.3'
2
+ VERSION = '0.27.0'
3
3
  end
@@ -89,4 +89,69 @@ class PostTest < ActiveSupport::TestCase
89
89
 
90
90
  end
91
91
 
92
+ test "excerpt is generated until it reaches 5 elements" do
93
+ post = Admin::Post.new({
94
+ content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.
95
+ Nunc malesuada diam id fringilla varius.
96
+ Suspendisse ultricies sem ac enim pulvinar luctus.
97
+ Pellentesque a nunc in libero convallis fringilla.
98
+ Praesent nec ipsum ut turpis feugiat semper.
99
+ Integer quis magna quis nisi porta hendrerit in a lorem.
100
+ Nam dignissim sapien nec feugiat cursus.
101
+ In tincidunt orci eget est scelerisque, a consequat massa consectetur.
102
+ Donec et nunc at justo facilisis congue.
103
+ Cras mollis orci ac arcu consectetur, quis bibendum leo gravida.
104
+ Vivamus dapibus dolor eu tortor molestie, non pulvinar risus dignissim.
105
+ Aliquam vitae lectus vehicula, euismod ex at, accumsan quam.
106
+ Ut vulputate mauris hendrerit mauris placerat tempus quis eget diam.
107
+ Sed vestibulum lacus eu vehicula finibus.
108
+ Praesent non diam vitae eros congue pretium.
109
+ Nulla tincidunt justo sit amet aliquet porta.
110
+ Nam vel elit vitae diam mattis mattis.
111
+ Phasellus in lacus eget sem tempus elementum.
112
+ Integer gravida diam sit amet massa egestas convallis.
113
+ Vestibulum dignissim odio sit amet sem condimentum, ut pharetra erat bibendum.
114
+ Cras molestie tellus id convallis lobortis."
115
+ })
116
+
117
+ post.titles << Admin::Title.new(name: "Post Excerpt test.", post: post)
118
+
119
+ assert post.save, post.errors.full_messages.to_sentence
120
+
121
+ html = Nokogiri::HTML(post.compiled_excerpt)
122
+
123
+ assert html.css('body > *').length == 5
124
+
125
+ end
126
+
127
+ test "excerpt generation stops when it reaches an element that can't be part of it" do
128
+ post = Admin::Post.new({
129
+ content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.
130
+ [Nunc](http://google.com) malesuada diam id fringilla varius.
131
+ Nam dignissim sapien nec feugiat cursus.
132
+ - In tincidunt orci eget est scelerisque, a consequat massa consectetur.
133
+ - Donec et nunc at justo facilisis congue.
134
+ - Cras mollis orci ac arcu consectetur, quis bibendum leo gravida.
135
+ Vivamus dapibus dolor eu tortor molestie, non pulvinar risus dignissim.
136
+ Aliquam vitae lectus vehicula, euismod ex at, accumsan quam.
137
+ # Ut vulputate mauris hendrerit mauris placerat tempus quis eget diam.
138
+ Sed vestibulum lacus eu vehicula finibus.
139
+ Praesent non diam vitae eros congue pretium.
140
+ Nulla tincidunt justo sit amet aliquet porta.
141
+ Nam vel elit vitae diam mattis mattis.
142
+ Phasellus in lacus eget sem tempus elementum.
143
+ Integer gravida diam sit amet massa egestas convallis.
144
+ Vestibulum dignissim odio sit amet sem condimentum, ut pharetra erat bibendum.
145
+ Cras molestie tellus id convallis lobortis."
146
+ })
147
+
148
+ post.titles << Admin::Title.new(name: "Post Excerpt test.", post: post)
149
+
150
+ assert post.save, post.errors.full_messages.to_sentence
151
+
152
+ html = Nokogiri::HTML(post.compiled_excerpt)
153
+
154
+ assert html.css('body > *').length == 5
155
+ end
156
+
92
157
  end
@@ -35,6 +35,14 @@ class MarkdownTest < Minitest::Test
35
35
  assert_equal "<p><strong>bold</strong> and <em>italic</em></p>", document.to_html
36
36
  end
37
37
 
38
+ def test_code_inline
39
+ document = Ecrire::Markdown.parse("This is a ~~~ruby inline code~~~ snippet")
40
+ assert_equal "<p>This is a <code class='language-ruby'>inline code</code> snippet</p>", document.to_html
41
+
42
+ document = Ecrire::Markdown.parse("This is a ~~~ruby inline code~~~ snippet. Multiple ~~~ruby instance~~~ can coexist on the same line")
43
+ assert_equal "<p>This is a <code class='language-ruby'>inline code</code> snippet. Multiple <code class='language-ruby'>instance</code> can coexist on the same line</p>", document.to_html
44
+ end
45
+
38
46
  def test_code_blocks
39
47
  document = Ecrire::Markdown.parse("~~~ruby\n# A comment\nRails.application\n~~~")
40
48
  assert_equal "<pre><header></header><code class='language-ruby'># A comment\nRails.application</code></pre>", document.to_html
@@ -58,6 +66,11 @@ class MarkdownTest < Minitest::Test
58
66
  assert_equal '<ol><li>Ruby</li><li><strong>Go</strong></li></ol><ul><li>Test</li><li>123</li></ul>', document.to_html
59
67
  end
60
68
 
69
+ def test_lists_with_links
70
+ document = Ecrire::Markdown.parse("1. [Ruby](http://ruby.com)\n")
71
+ assert_equal "<ol><li><a href='http://ruby.com'>Ruby</a></li></ol>", document.to_html
72
+ end
73
+
61
74
  def test_image
62
75
  document = Ecrire::Markdown.parse('![An Image](http://bla.com)')
63
76
  assert_equal "<figure><img src='http://bla.com' /><figcaption>An Image</figcaption></figure>", document.to_html
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecrire
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.26.3
4
+ version: 0.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pier-Olivier Thibault
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-20 00:00:00.000000000 Z
11
+ date: 2015-06-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Blog engine on Rails
14
14
  email: pothibo@gmail.com
@@ -181,7 +181,7 @@ files:
181
181
  - lib/ecrire/markdown.rb
182
182
  - lib/ecrire/markdown/document.rb
183
183
  - lib/ecrire/markdown/node.rb
184
- - lib/ecrire/markdown/nodes/code_block.rb
184
+ - lib/ecrire/markdown/nodes/code.rb
185
185
  - lib/ecrire/markdown/nodes/heading.rb
186
186
  - lib/ecrire/markdown/nodes/image.rb
187
187
  - lib/ecrire/markdown/nodes/ordered_list.rb
@@ -199,17 +199,23 @@ files:
199
199
  - lib/ecrire/onboarding/assets/images/github-logo.png
200
200
  - lib/ecrire/onboarding/assets/images/profile.png
201
201
  - lib/ecrire/onboarding/assets/images/twitter-logo.png
202
+ - lib/ecrire/onboarding/assets/javascripts/application.js
202
203
  - lib/ecrire/onboarding/assets/javascripts/databases/information.js.coffee
203
204
  - lib/ecrire/onboarding/assets/javascripts/message.js.coffee
204
- - lib/ecrire/onboarding/assets/javascripts/theme.js
205
- - lib/ecrire/onboarding/assets/stylesheets/complete.css.scss
206
- - lib/ecrire/onboarding/assets/stylesheets/fonts.css.scss
207
- - lib/ecrire/onboarding/assets/stylesheets/form.css.scss
208
- - lib/ecrire/onboarding/assets/stylesheets/message.css.scss
209
- - lib/ecrire/onboarding/assets/stylesheets/terminal.css.scss
210
- - lib/ecrire/onboarding/assets/stylesheets/theme.css.scss
205
+ - lib/ecrire/onboarding/assets/stylesheets/browser.scss
206
+ - lib/ecrire/onboarding/assets/stylesheets/browser/base.scss
207
+ - lib/ecrire/onboarding/assets/stylesheets/mobile.scss
208
+ - lib/ecrire/onboarding/assets/stylesheets/mobile/base.scss
209
+ - lib/ecrire/onboarding/assets/stylesheets/shared/base.scss
210
+ - lib/ecrire/onboarding/assets/stylesheets/shared/complete.scss
211
+ - lib/ecrire/onboarding/assets/stylesheets/shared/fonts.scss
212
+ - lib/ecrire/onboarding/assets/stylesheets/shared/form.scss
213
+ - lib/ecrire/onboarding/assets/stylesheets/shared/message.scss
214
+ - lib/ecrire/onboarding/assets/stylesheets/shared/terminal.scss
215
+ - lib/ecrire/onboarding/assets/stylesheets/shared/welcome.scss
216
+ - lib/ecrire/onboarding/assets/stylesheets/tablet.scss
217
+ - lib/ecrire/onboarding/assets/stylesheets/tablet/base.scss
211
218
  - lib/ecrire/onboarding/assets/stylesheets/variables.css.scss
212
- - lib/ecrire/onboarding/assets/stylesheets/welcome.css.scss
213
219
  - lib/ecrire/onboarding/controllers/databases_controller.rb
214
220
  - lib/ecrire/onboarding/controllers/onboarding_controller.rb
215
221
  - lib/ecrire/onboarding/controllers/users_controller.rb
@@ -228,6 +234,7 @@ files:
228
234
  - lib/ecrire/theme/template/Procfile
229
235
  - lib/ecrire/theme/template/Rakefile
230
236
  - lib/ecrire/theme/template/assets/images/.keep
237
+ - lib/ecrire/theme/template/assets/javascripts/application.js.coffee
231
238
  - lib/ecrire/theme/template/assets/stylesheets/browser.css.scss
232
239
  - lib/ecrire/theme/template/assets/stylesheets/browser/base.css.scss
233
240
  - lib/ecrire/theme/template/assets/stylesheets/mobile.css.scss
@@ -1,30 +0,0 @@
1
- require 'active_support/core_ext/string'
2
-
3
- module Ecrire::Markdown
4
- module Nodes
5
- class CodeBlock < Node
6
- def initialize(language, title, nodes)
7
- @content = ERB::Util.html_escape(nodes.join("\n"))
8
- @title = title
9
- @language = language
10
- end
11
-
12
- def to_s
13
- str = "<pre>"
14
- str << "<header>#{@title}</header>"
15
-
16
- str << "<code"
17
-
18
- unless @language.nil?
19
- str << " class='language-#{@language}'>"
20
- end
21
-
22
-
23
- str << @content
24
- str << "</code></pre>"
25
- str
26
- end
27
- end
28
- end
29
- end
30
-