trahald 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +16 -0
  3. data/Gemfile.lock +58 -0
  4. data/README.md +29 -8
  5. data/lib/public/lib/markdown/markdown.js +1616 -0
  6. data/lib/public/lib/masonry/jquery.masonry.min.js +10 -0
  7. data/lib/public/lib/reveal/markdown.js +151 -0
  8. data/lib/public/lib/reveal/showdown.js +62 -0
  9. data/lib/public/lib/reveal/theme/beige.css +142 -0
  10. data/lib/public/lib/reveal/theme/default.css +150 -0
  11. data/lib/public/lib/reveal/theme/moon.css +142 -0
  12. data/lib/public/lib/reveal/theme/night.css +130 -0
  13. data/lib/public/lib/reveal/theme/serif.css +130 -0
  14. data/lib/public/lib/reveal/theme/simple.css +132 -0
  15. data/lib/public/lib/reveal/theme/sky.css +136 -0
  16. data/lib/public/lib/reveal/theme/solarized.css +142 -0
  17. data/lib/trahald.rb +90 -11
  18. data/lib/trahald/.redis-client.rb.swn +0 -0
  19. data/lib/trahald/article.rb +34 -0
  20. data/lib/trahald/backend-base.rb +13 -0
  21. data/lib/trahald/git.rb +46 -1
  22. data/lib/trahald/markdown-body.rb +44 -0
  23. data/lib/trahald/redis-client.rb +33 -9
  24. data/lib/trahald/version.rb +1 -1
  25. data/lib/views/edit.slim +47 -10
  26. data/lib/views/fd.scss +22 -0
  27. data/lib/views/header.slim +13 -0
  28. data/lib/views/layout.slim +7 -4
  29. data/lib/views/list.slim +3 -1
  30. data/lib/views/page.slim +5 -8
  31. data/lib/views/slide.slim +37 -0
  32. data/lib/views/style.scss +3 -1
  33. data/lib/views/summary.slim +36 -0
  34. data/lib/views/tab.slim +10 -0
  35. data/lib/views/tab_edit.slim +10 -0
  36. data/spec/git_spec.rb +3 -23
  37. data/spec/redis-client_spec.rb +3 -25
  38. data/spec/spec_helper.rb +2 -1
  39. data/spec/support/shared_examples_for_backends.rb +38 -0
  40. data/trahald.gemspec +1 -0
  41. metadata +44 -4
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Trahald
4
4
  class BackendBase
5
+
5
6
  def initialize
6
7
  end
7
8
 
@@ -9,6 +10,10 @@ module Trahald
9
10
  raise "Called abstract method: add!"
10
11
  end
11
12
 
13
+ def article(name)
14
+ raise "Called abstract method: article"
15
+ end
16
+
12
17
  def body(name)
13
18
  raise "Called abstract method: body"
14
19
  end
@@ -17,10 +22,18 @@ module Trahald
17
22
  raise "Called abstract method: commit!"
18
23
  end
19
24
 
25
+ def last_modified
26
+ raise "Called abstract method: last_modified"
27
+ end
28
+
20
29
  def list
21
30
  raise "Called abstract method: list"
22
31
  end
23
32
 
33
+ def data
34
+ raise "Called abstract method: data"
35
+ end
36
+
24
37
  def self.init_repo_if_needed(dir)
25
38
  raise "Called abstract method: self.init_repo_if_needed"
26
39
  end
@@ -9,13 +9,25 @@ module Trahald
9
9
  @ext = ext
10
10
  end
11
11
 
12
+ def article(name)
13
+ commit = repo.commits('master', false).find{|c|
14
+ c.diffs.first.b_path.force_encoding("ASCII-8BIT") == "#{name}.#{@ext}".force_encoding("ASCII-8BIT")
15
+ }
16
+ return nil unless commit
17
+ Article.new(
18
+ name,
19
+ commit.diffs.first.b_blob.data.force_encoding("UTF-8"),
20
+ commit.date
21
+ )
22
+ end
23
+
12
24
  def add!(name, body)
13
25
  path = "#{@repo_dir}/#{name}.#{@ext}"
14
26
  FileUtils.mkdir_p File.dirname(path)
15
27
  begin
16
28
  File.open(path, 'w'){|f| f.write(body)}
17
29
  Dir.chdir(@repo_dir){
18
- repo.add "#{name}.#{@ext}"
30
+ repo.add "#{name}.#{@ext}".force_encoding("ASCII-8BIT")
19
31
  }
20
32
  true
21
33
  rescue => exception
@@ -24,7 +36,13 @@ module Trahald
24
36
  end
25
37
  end
26
38
 
39
+ #experimental
27
40
  def body(name)
41
+ a = article(name)
42
+ if a; a.body else nil end
43
+ end
44
+
45
+ def body_old(name)
28
46
  first = first_commit
29
47
  return nil unless first
30
48
 
@@ -39,6 +57,17 @@ module Trahald
39
57
  repo.commit_index(message)
40
58
  end
41
59
 
60
+ # experimental
61
+ def data
62
+ first = first_commit
63
+ return [] unless first
64
+ summary 50
65
+ end
66
+
67
+ def last_modified
68
+ first_commit.date
69
+ end
70
+
42
71
  def list
43
72
  first = first_commit
44
73
  return [] unless first
@@ -59,18 +88,34 @@ module Trahald
59
88
  end
60
89
 
61
90
  def first_commit
91
+ return Time.now unless repo.commits.any?
62
92
  repo.commits.first
63
93
  end
64
94
 
65
95
  def files(pos, tree, list)
66
96
  tree.blobs.each{|blob|
97
+ puts blob.name
67
98
  list.push pos + File.basename(blob.name.force_encoding("UTF-8"), ".#{@ext}")
68
99
  }
69
100
  tree.trees.each{|t|
101
+ puts t.name
70
102
  files "#{pos}#{t.name.force_encoding("UTF-8")}/", t, list
71
103
  }
72
104
  end
73
105
 
106
+ # args:
107
+ # max: number of commits gotten. if max is false, all commits are gotten.
108
+ def summary(max=false)
109
+ repo.commits('master', max).map{|commit|
110
+ path = commit.diffs.first.b_path.force_encoding("UTF-8")
111
+ MarkdownBody.new(
112
+ path.slice(0, path.size - (@ext.size+1)),
113
+ commit.diffs.first.b_blob.data.force_encoding("UTF-8"),
114
+ commit.date
115
+ ).summary
116
+ }.uniq{|i| i.name}
117
+ end
118
+
74
119
  def repo
75
120
  @repo ||= Grit::Repo.new @repo_dir
76
121
  end
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Trahald
4
+ require 'kramdown'
5
+ require 'sanitize'
6
+
7
+ class MarkdownBody
8
+ MAX_TEXT_SIZE = 160
9
+ Summary = Struct.new("Summary", :name, :imgs, :body, :date)
10
+
11
+ def initialize(name, body, date)
12
+ @name = name
13
+ @body = body
14
+ @date = date
15
+ end
16
+
17
+ def pre
18
+ raw = Sanitize.clean Kramdown::Document.new(@body).to_html
19
+ if raw.size > MAX_TEXT_SIZE
20
+ raw[0, MAX_TEXT_SIZE] + "..."
21
+ else
22
+ raw
23
+ end
24
+ end
25
+
26
+ def img_src
27
+ pattern = Regexp.new '!\[.+\]\((.+)\)'
28
+ raw = @body.split('\n')
29
+ raw.map{|r|
30
+ $1 if pattern =~ r
31
+ }.select{|i| i}
32
+ end
33
+
34
+ def summary
35
+ Summary.new(
36
+ @name,
37
+ img_src,
38
+ pre,
39
+ @date
40
+ )
41
+ end
42
+ end
43
+ end
44
+
@@ -5,14 +5,20 @@ module Trahald
5
5
  require 'uri'
6
6
 
7
7
  class RedisClient < BackendBase
8
+ # track all page names.
9
+ KEY_SET = ".keys" # TODO: this must not be collided with any page names.
10
+
11
+ # for using cache.
12
+ MODIFIED_DATE = ".modified" # TODO: same
13
+
8
14
  def initialize(url)
9
- uri = URI.parse url
10
- @redis = Redis.new(
11
- :host => uri.host,
12
- :port => uri.port
13
- )
15
+ @redis = Redis.new(:url => url)
16
+ @params = Hash.new
17
+ end
14
18
 
15
- @params = Hash::new
19
+ def article(name)
20
+ json = @redis.zrange(name, -1, -1).first # nil unless zrange(..).any?
21
+ if json; Article.from_json(json) else nil end
16
22
  end
17
23
 
18
24
  # This method does not set data to Redis DB. To confirm, use commit! after add!.
@@ -21,14 +27,20 @@ module Trahald
21
27
  end
22
28
 
23
29
  def body(name)
24
- @redis.get name
30
+ a = article name
31
+ if a; a.body else nil end
25
32
  end
26
33
 
27
34
  # message is not used.
28
35
  def commit!(message)
36
+ date = Time.now
29
37
  @params.each{|name, body|
30
- @redis.set(name, body)
38
+ json = Article.new(name, body, date).to_json
39
+ zcard = @redis.zcard name
40
+ @redis.zadd name, zcard+1, json
41
+ @redis.sadd KEY_SET, name
31
42
  }
43
+ @redis.set MODIFIED_DATE, date.to_s
32
44
  end
33
45
 
34
46
  # CAUTION! This method flush data on current db.
@@ -36,8 +48,20 @@ module Trahald
36
48
  @redis.flushdb
37
49
  end
38
50
 
51
+ def data
52
+ @redis.smembers(KEY_SET).map do |name|
53
+ a = article name
54
+ MarkdownBody.new(name, a.body, a.date).summary
55
+ end
56
+ end
57
+
58
+ def last_modified
59
+ date = @redis.get(MODIFIED_DATE)
60
+ if date; Time.parse date else Time.now end
61
+ end
62
+
39
63
  def list
40
- @redis.keys.sort
64
+ @redis.smembers(KEY_SET).sort
41
65
  end
42
66
 
43
67
  def self.init_repo_if_needed(dir)
@@ -1,3 +1,3 @@
1
1
  module Trahald
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -1,10 +1,47 @@
1
- form action="/edit" method="post"
2
- input type="text" name="name" value="#{@name}"
3
- br
4
- textarea name="body" #{@body}
5
- br
6
- input type="submit" value="作成/更新"
7
-
8
- ul.nav
9
- li
10
- a href='/list' 一覧
1
+ script src="/lib/markdown/markdown.js"
2
+ script src="http://proger.i-forge.net/filedrop-min.js"
3
+
4
+ javascript:
5
+ $(function(){
6
+ var updatePreview = function(){
7
+ $("#preview").html(markdown.toHTML($("#text-input").val()));
8
+ };
9
+ $(document).ready(updatePreview());
10
+ $("#text-input").on("keyup", function(event){
11
+ updatePreview();
12
+ });
13
+
14
+ var options = {iframe: {url: '/upload'}};
15
+ var zone = new FileDrop('zone', options);
16
+ zone.on.send.push(function(files){
17
+ var unix_t = parseInt(new Date / 1000);
18
+ for(var i=0; i<files.length;++i){
19
+ name = unix_t + "." + files[i].name.split('.').pop().toLowerCase();
20
+ files[i].name = name
21
+ files[i].on.done.push(function(xhr,e){
22
+ var input = $("#text-input").val() + "\n![" + name + "](/#{UPLOAD}/" + name + ")\n";
23
+ $("#text-input").val(input);
24
+ updatePreview();
25
+ });
26
+ files[i].on.error.push(function(e, xhr){
27
+ alert(xhr.response);
28
+ });
29
+ files[i].SendTo('/upload');
30
+ }
31
+ });
32
+ });
33
+
34
+ == slim :header
35
+
36
+ div.row-fluid
37
+ div.span6
38
+ form action="/edit" method="post" style="margin-top:20px;"
39
+ div.row-fluid
40
+ input type="text" name="name" value="#{@name}" class="span12 input" style="font-size: 24px;"
41
+ fieldset#zone.row-fluid
42
+ textarea name="body" id="text-input" style="border:0; box-shadow: none; height: 500px;" class="span12" #{@body}
43
+ div.row-fluid
44
+ input type="submit" value="作成/更新" class="btn btn-primary"
45
+ div.span6#preview.well style="background: #fdfdfd; margin-top: 20px;"
46
+
47
+ == slim :tab_edit
@@ -0,0 +1,22 @@
1
+ .fd-zone {
2
+ position: relative;
3
+ overflow: hidden;
4
+ //width: 15em;
5
+ text-align:center;
6
+ }
7
+
8
+ .fd-file {
9
+ opacity: 0;
10
+ font-size:118px;
11
+ position: absolute;
12
+ right: 0;
13
+ top: 0;
14
+ z-index: 1;
15
+ padding: 0;
16
+ margin: 0;
17
+ cursor: pointer;
18
+ filter: alpha(opacity=0);
19
+ font-family: sans-serif;
20
+ }
21
+
22
+ .fd-zone.over {border-color: maroon;}
@@ -0,0 +1,13 @@
1
+ ul.breadcrumb
2
+ li.active
3
+ a href="/#{@name}" #{@name}
4
+ span.divider = "/"
5
+ li
6
+ a href="/" trahald
7
+ span.divider = "/"
8
+ li
9
+ a href="/list" 一覧
10
+ span.divider = "/"
11
+ li
12
+ a href="/uploads" 添付
13
+
@@ -1,10 +1,13 @@
1
1
  html
2
2
  head
3
3
  meta charset='UTF-8'
4
+ meta name="viewport" content="width=device-width, initial-scale=1.0"
4
5
  title Trahald
5
- link href='/style.css' rel='stylesheet' type='text/css'
6
+ link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet"
7
+ link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-responsive.min.css" rel="stylesheet"
8
+ link href="/css/fd.css" rel="stylesheet"
9
+ script src="//code.jquery.com/jquery-1.9.1.min.js"
10
+ script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"
6
11
  body
7
12
  div.container
8
- h1
9
- a href='/' Trahald
10
- == yield
13
+ == yield
@@ -1,4 +1,6 @@
1
- h1 ページ一覧
1
+ == slim :header
2
+
3
+ h1 =@title
2
4
  - if @keys.any?
3
5
  ul#menu
4
6
  - for key in @keys do
@@ -1,12 +1,9 @@
1
- h1 #{@name}
1
+ == slim :header
2
2
 
3
3
  == @body
4
4
 
5
- ul.nav
6
- li
7
- a href="/#{@name}/edit" 編集
8
- li
9
- a href='/#{@name}.md' Raw
10
- li
11
- a href='/list' 一覧
5
+ == slim :tab
12
6
 
7
+ p
8
+ small
9
+ em="更新日時: #{@date.to_s}"
@@ -0,0 +1,37 @@
1
+ html
2
+ head
3
+ meta charset='UTF-8'
4
+ meta content="yes" name="apple-mobile-web-app-capable"
5
+ meta content="black-translucent" name="apple-mobile-web-app-status-bar-style"
6
+ meta name="viewport" content="width=device-width, initial-scale=1.0"
7
+ title Trahald
8
+ link rel="stylesheet" href="//cdn.jsdelivr.net/reveal.js/2.2/reveal.min.css"
9
+ link rel="stylesheet" href="/lib/reveal/theme/serif.css" id="theme"
10
+ style
11
+ body{}
12
+ body
13
+ .reveal
14
+ .slides
15
+ section data-markdown="" data-separator="---"
16
+ script type="text/template"
17
+ == @body
18
+ script src="//cdn.jsdelivr.net/headjs/0.99/head.min.js"
19
+ script src="//cdn.jsdelivr.net/reveal.js/2.2/reveal.min.js"
20
+ javascript:
21
+ Reveal.initialize({
22
+ controls: true,
23
+ progress: true,
24
+ history: true,
25
+ center: true,
26
+
27
+ theme: Reveal.getQueryHash().theme,
28
+ transition: Reveal.getQueryHash().transition || 'default',
29
+
30
+ // Optional libraries used to extend on reveal.js
31
+ dependencies: [
32
+ { src: '/lib/reveal/showdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
33
+ { src: '/lib/reveal/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } }
34
+ ]
35
+ });
36
+
37
+
@@ -13,4 +13,6 @@ ul.nav {
13
13
  text-decoration: none;
14
14
  color: #99D;
15
15
  }
16
- }
16
+ }
17
+
18
+
@@ -0,0 +1,36 @@
1
+ == slim :header
2
+ script src="/lib/masonry/jquery.masonry.min.js"
3
+
4
+ css:
5
+ .item{
6
+ width: 300px;
7
+ margin: 10px;
8
+ float: center;
9
+ }
10
+
11
+ javascript:
12
+ $(function(){
13
+ $('#ultarget').masonry({
14
+ itemSelector: '.item',
15
+ columnWidth: 350
16
+ });
17
+ });
18
+
19
+ /h1 =@title
20
+ - if @data.any?
21
+ ul#ultarget.thumbnails
22
+ - for summary in @data do
23
+ li.thumbnail.item.span4
24
+ a href="#{summary.name}"
25
+ h3 =summary.name
26
+ - if summary.imgs.any?
27
+ a href="#{summary.name}"
28
+ img src="#{summary.imgs.first}" alt=""/
29
+ p=summary.body
30
+ p
31
+ small
32
+ em="更新日時: #{summary.date.to_s}"
33
+ - else
34
+ ul
35
+ li まだページが作成されていません。
36
+