ecrire 0.26.3 → 0.27.0

Sign up to get free protection for your applications and to get access to all the features.
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
-