trahald 0.0.4 → 0.0.5

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