servel 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e91e5efe5fb7ebf0a803c7b8651d84188582b93c
4
- data.tar.gz: db7b14fbe9096a17722164c6b389fc324268b0c0
3
+ metadata.gz: f87f633c75cd01064baed1ec7f09e01b2ca44d5c
4
+ data.tar.gz: 91e1f72f5c9523c3a35ba001912ca1f61a728a9d
5
5
  SHA512:
6
- metadata.gz: b56fedb3ecc2caf4f023a58d5bec0d817256d268991d78051c9bdbed8d2f76e8ee40174c4f14c71cc5308a89cddacef9e7fabf4ba273bdabfb6c85ee947275a3
7
- data.tar.gz: fbb024ed53a80cb97f9d4d407f69dc9b5840709517b50a9454bc37076893363f3b4084c50a43ba15f70a20da403eed495a1f7c10098dff67c753a5ed7558d071
6
+ metadata.gz: b2f7b7d838120c588d4cabe93cd5e6afbd1782784ae9200bbeb0e3a6a9f7db5672dc4d99dc7b1a916ced99fc319ded52a5e54fe9303456e82c04a175afb1151d
7
+ data.tar.gz: 51e7058e7de4372b3eeebfccca89a9a39ffead7e4cb4a052e4cf3f3184d44b11ccaf3fb6f715a58b9f9edfbc600d79466ac65a2e2716712d80138d3371572bf6
data/lib/servel.rb CHANGED
@@ -6,13 +6,15 @@ require 'active_support/all'
6
6
 
7
7
  require 'json'
8
8
  require 'pathname'
9
+ require 'delegate'
9
10
 
10
11
  module Servel
11
12
  end
12
13
 
13
14
  require "servel/version"
14
15
  require "servel/core_ext/pathname"
16
+ require "servel/pathname_decorator"
15
17
  require "servel/haml_context"
16
- require "servel/index_view"
18
+ require "servel/locals"
17
19
  require "servel/middleware"
18
20
  require "servel/servel"
@@ -1,9 +1,5 @@
1
1
  class Pathname
2
- def image?
3
- file? && extname && %w(.jpg .jpeg .png .gif).include?(extname.downcase)
4
- end
5
-
6
- def video?
7
- file? && extname && %w(.webm).include?(extname.downcase)
2
+ def decorate(parent = false)
3
+ Servel::PathnameDecorator.new(self, parent)
8
4
  end
9
5
  end
@@ -0,0 +1,31 @@
1
+ class Servel::Locals
2
+ def initialize(url_path:, fs_path:, root:)
3
+ @url_path = url_path
4
+ @fs_path = fs_path
5
+ @root = root
6
+ end
7
+
8
+ def locals
9
+ {
10
+ url_path: @url_path,
11
+ directories: directories,
12
+ files: files
13
+ }
14
+ end
15
+
16
+ def directories
17
+ list = @fs_path.children.select { |child| child.directory? }
18
+ list = sort_paths(list)
19
+ list.unshift(@fs_path.decorate(true)) unless @fs_path == @root
20
+ list
21
+ end
22
+
23
+ def files
24
+ list = @fs_path.children.select { |child| child.file? }
25
+ sort_paths(list)
26
+ end
27
+
28
+ def sort_paths(paths)
29
+ Naturalsorter::Sorter.sort(paths.map(&:to_s), true).map { |path| Pathname.new(path) }
30
+ end
31
+ end
@@ -2,7 +2,7 @@ class Servel::Middleware
2
2
  def initialize(app, options = {})
3
3
  @app = app
4
4
  @root = Pathname.new(options[:root])
5
- @haml_context = Servel::HamlContext.new
5
+ @file_server = Rack::File.new(@root.to_s)
6
6
  end
7
7
 
8
8
  def call(env)
@@ -10,17 +10,13 @@ class Servel::Middleware
10
10
  url_path = url_path_for(path)
11
11
  fs_path = @root + url_path[1..-1]
12
12
 
13
- unless fs_path.directory?
14
- return @app.call(env)
15
- end
13
+ return @file_server.call(env) unless fs_path.directory?
16
14
 
17
- if path != "" && !path.end_with?("/")
18
- return [302, { "Location" => "#{url_path}/" }, []]
19
- end
15
+ url_path << "/" unless url_path.end_with?("/")
20
16
 
21
- url_path << "/" if url_path != "" && !url_path.end_with?("/")
17
+ return [302, { "Location" => url_path }, []] unless path == "" || path.end_with?("/")
22
18
 
23
- [200, {}, StringIO.new(Servel::IndexView.new(url_path, fs_path).render(@haml_context))]
19
+ index(url_path, fs_path)
24
20
  end
25
21
 
26
22
  def url_path_for(url_path)
@@ -29,4 +25,12 @@ class Servel::Middleware
29
25
 
30
26
  Rack::Utils.clean_path_info(url_path)
31
27
  end
28
+
29
+ def index(url_path, fs_path)
30
+ @haml_context ||= Servel::HamlContext.new
31
+ locals = Servel::Locals.new(url_path: url_path, fs_path: fs_path, root: @root).locals
32
+ body = @haml_context.render('index.haml', locals)
33
+
34
+ [200, {}, [body]]
35
+ end
32
36
  end
@@ -0,0 +1,91 @@
1
+ class Servel::PathnameDecorator < SimpleDelegator
2
+ def initialize(pathname, parent)
3
+ super(pathname)
4
+ @parent = parent
5
+ end
6
+
7
+ def decorate
8
+ self
9
+ end
10
+
11
+ def image?
12
+ file? && extname && %w(.jpg .jpeg .png .gif).include?(extname.downcase)
13
+ end
14
+
15
+ def video?
16
+ file? && extname && %w(.webm).include?(extname.downcase)
17
+ end
18
+
19
+ def audio?
20
+ file? && extname && %w(.mp3 .m4a .wav).include?(extname.downcase)
21
+ end
22
+
23
+ def media?
24
+ image? || video? || audio?
25
+ end
26
+
27
+ def type
28
+ if directory?
29
+ "Dir"
30
+ elsif file?
31
+ extname.sub(/^\./, "")
32
+ else
33
+ ""
34
+ end
35
+ end
36
+
37
+ def media_type
38
+ return "video" if video?
39
+ return "image" if image?
40
+ return "audio" if audio?
41
+ "unknown"
42
+ end
43
+
44
+ def listing_classes
45
+ klasses = []
46
+ klasses << "media" if media?
47
+ klasses << "image" if image?
48
+ klasses << "video" if video?
49
+ klasses << "audio" if audio?
50
+ klasses.join(" ")
51
+ end
52
+
53
+ def listing_attrs
54
+ {
55
+ class: listing_classes,
56
+ data: {
57
+ type: media_type
58
+ }
59
+ }
60
+ end
61
+
62
+ def parent?
63
+ @parent
64
+ end
65
+
66
+ def icon
67
+ if @parent
68
+ "🔝"
69
+ elsif directory?
70
+ "📁"
71
+ else
72
+ ""
73
+ end
74
+ end
75
+
76
+ def href
77
+ if @parent
78
+ "../"
79
+ else
80
+ basename
81
+ end
82
+ end
83
+
84
+ def name
85
+ if @parent
86
+ "(Parent Directory)"
87
+ else
88
+ basename
89
+ end
90
+ end
91
+ end
data/lib/servel/servel.rb CHANGED
@@ -1,18 +1,17 @@
1
1
  class Servel::Servel
2
- def initialize(server_root)
3
- @server_root = server_root
2
+ def initialize(root)
3
+ @root = root
4
4
  end
5
-
5
+
6
6
  def start
7
7
  Rack::Handler::Puma.run(build_app)
8
8
  end
9
-
9
+
10
10
  def build_app
11
- server_root = @server_root
11
+ root = @root
12
12
 
13
13
  Rack::Builder.new do
14
- use(Servel::Middleware, root: server_root)
15
- use Rack::Static, urls: [""], root: server_root.to_s
14
+ use(Servel::Middleware, root: root)
16
15
 
17
16
  run ->(env) do
18
17
  [404, {}, []]
@@ -1,5 +1,6 @@
1
- %img#image.hidden
2
- %video#video.hidden{autoplay: true, controls: true}
1
+ %img#image
2
+ %video#video{autoplay: true, controls: true}
3
+ %audio#audio{autoplay: true, controls: true}
3
4
  #page-back.paginator ◀
4
5
  #page-next.paginator ▶
5
6
  #page-back-10.paginator ◀◀
@@ -1,27 +1,21 @@
1
- %h1 Directory Listing of #{url_path}
1
+ %h1 Listing of #{url_path}
2
2
  %table
3
3
  %thead
4
4
  %tr
5
5
  %th Name
6
- %th Size
7
- %th Modified
6
+ %th.new-tab
7
+ %th.type Type
8
+ %th.size Size
9
+ %th.modified Modified
8
10
  %tbody
9
- - unless url_path == "/"
10
- %tr
11
- %td
12
- (Dir)&nbsp;
13
- %a{href: "../"} ../
14
- %td
15
- %td
16
11
  - (directories + files).each do |file|
17
- - relative_path = file.relative_path_from(fs_path)
12
+ - file = file.decorate
18
13
  %tr
19
14
  %td
20
- - if file.directory?
21
- (Dir)&nbsp;
22
- %a{href: relative_path, class: "#{"viewable" if file.image? || file.video?} #{"image" if file.image?} #{"video" if file.video?}", target: "_blank"}
23
- - extname = relative_path.extname
24
- - basename = relative_path.basename(extname)
25
- #{basename.to_s.truncate(50)}#{extname}
26
- %td= number_to_human_size(file.size)
15
+ %span.icon= file.icon
16
+ %a.default{href: file.href, **file.listing_attrs}= file.name
17
+ %td
18
+ %a.new-tab{href: file.href, target: "_blank", **file.listing_attrs} (New tab)
19
+ %td= file.type
20
+ %td= file.directory? ? "-" : number_to_human_size(file.size)
27
21
  %td= file.mtime.strftime("%e %b %Y %l:%M %p")
@@ -1,25 +1,42 @@
1
1
  #gallery {
2
- padding: 20px;
2
+ padding: 0 20px;
3
3
  background: #333;
4
4
  transform: translateZ(0px);
5
5
  }
6
6
 
7
- .hidden {
8
- display: none !important;
9
- }
10
-
11
- #image, #video {
12
- display: block;
7
+ #image, #video, #audio {
8
+ display: none;
13
9
  max-width: 100%;
14
10
  margin: 0 auto 0 auto;
15
11
  }
16
12
 
13
+ #audio {
14
+ width: 100%;
15
+ padding: 0 100px;
16
+ }
17
+
17
18
  @media (min-width: 992px) {
18
- #image, #video {
19
+ #image, #video, #audio {
19
20
  max-height: 100%;
20
21
  }
21
22
  }
22
23
 
24
+ #gallery.video #video {
25
+ display: block;
26
+ }
27
+
28
+ #gallery.image #image {
29
+ display: block;
30
+ }
31
+
32
+ #gallery.audio {
33
+ padding-top: 20px;
34
+ }
35
+
36
+ #gallery.audio #audio {
37
+ display: block;
38
+ }
39
+
23
40
  .paginator {
24
41
  position: fixed;
25
42
  width: 100px;
@@ -1,112 +1,132 @@
1
- var pages = null;
2
- var currentPage = 1;
1
+ var Gallery = (function() {
2
+ var urls = [];
3
+ var types = [];
4
+ var currentIndex = 0;
3
5
 
4
- function loadPages() {
5
- pages = [];
6
+ function initItems() {
7
+ var links = document.querySelectorAll("#listing a.default.media");
8
+ for(var i = 0; i < links.length; i++) {
9
+ var link = links[i];
6
10
 
7
- var links = document.querySelectorAll("#listing a.viewable");
8
- for(var i = 0; i < links.length; i++) {
9
- var link = links[i];
10
- var type = link.classList.contains("image") ? "image" : "video";
11
- pages.push({ url: link.href, type: type });
11
+ urls.push(link.href);
12
+ types.push(link.dataset.type);
13
+ }
12
14
  }
13
- }
14
15
 
15
- function page(index) {
16
- if(arguments.length == 1) {
17
- if(isNaN(index) || index < 1) index = 1;
18
- if(index > pages.length) index = pages.length;
19
-
20
- currentPage = index;
21
- showCurrentPage();
16
+ function render() {
17
+ var url = urls[currentIndex];
18
+ var type = types[currentIndex];
19
+
20
+ var galleryElement = document.querySelector("#gallery");
21
+ galleryElement.classList.remove("image", "video", "audio");
22
+ galleryElement.classList.add(type);
23
+
24
+ document.getElementById(type).src = url;
25
+
26
+ window.scrollTo(0, 0);
27
+
28
+ //if(currentPage < imageUrls.length) (new Image()).src = imageUrls[currentPage];
22
29
  }
23
- else {
24
- var index = currentPage;
25
- if(isNaN(index) || index < 1) index = 1;
30
+
31
+ function clamp(index) {
32
+ if(index == null || isNaN(index) || index < 0) return 0;
33
+ if(index >= urls.length) return urls.length - 1;
26
34
  return index;
27
35
  }
28
- }
29
36
 
30
- function atBottom() {
31
- return (window.scrollY + window.innerHeight) == document.body.scrollHeight;
32
- }
37
+ function go(index) {
38
+ currentIndex = clamp(index);
39
+ render();
40
+ }
33
41
 
34
- function initPaginator() {
35
- document.querySelector("#page-back").addEventListener("click", function(e) {
36
- e.stopPropagation();
37
- page(page() - 1);
38
- });
39
- document.querySelector("#page-back-10").addEventListener("click", function(e) {
40
- e.stopPropagation();
41
- page(page() - 10);
42
- });
43
- document.querySelector("#page-next").addEventListener("click", function(e) {
44
- e.stopPropagation();
45
- page(page() + 1);
46
- });
47
- document.querySelector("#page-next-10").addEventListener("click", function(e) {
48
- e.stopPropagation();
49
- page(page() + 10);
50
- });
51
- document.querySelector("#page-toggle-size").addEventListener("click", function(e) {
52
- e.stopPropagation();
53
- document.body.classList.toggle("split-screen");
54
- });
55
-
56
- window.addEventListener("keydown", function(event) {
57
- if(event.keyCode == 39 || ((event.keyCode == 32 || event.keyCode == 13) && atBottom())) {
58
- event.preventDefault();
59
- page(page() + 1);
60
- }
61
- else if(event.keyCode == 8 || event.keyCode == 37) {
62
- event.preventDefault();
63
- page(page() - 1);
64
- }
65
- });
66
- }
42
+ function prev() {
43
+ go(currentIndex - 1);
44
+ }
67
45
 
68
- function showCurrentPage() {
69
- window.scrollTo(0, 0);
70
- var page = pages[currentPage - 1];
71
- if(!page) return;
72
-
73
- if(page.type == "image") {
74
- document.querySelector("#gallery #video").classList.add("hidden");
75
- document.querySelector("#gallery #image").classList.remove("hidden");
76
- document.querySelector("#gallery #image").src = page.url;
46
+ function next() {
47
+ go(currentIndex + 1);
77
48
  }
78
- else {
79
- document.querySelector("#gallery #image").classList.add("hidden");
80
- document.querySelector("#gallery #video").classList.remove("hidden");
81
- document.querySelector("#gallery #video").src = page.url;
49
+
50
+ function rewind() {
51
+ go(currentIndex - 10);
82
52
  }
83
53
 
84
- //if(currentPage < imageUrls.length) (new Image()).src = imageUrls[currentPage];
85
- }
54
+ function fastForward() {
55
+ go(currentIndex + 10);
56
+ }
86
57
 
87
- function openLink(event) {
88
- event.preventDefault();
58
+ function jump(url) {
59
+ go(urls.indexOf(url));
60
+ }
89
61
 
90
- for(var i = 0; i < pages.length; i++) {
91
- if(pages[i].url == this.href) {
92
- page(i + 1);
93
- return;
94
- }
62
+ function atBottom() {
63
+ return (window.scrollY + window.innerHeight) == document.body.scrollHeight;
95
64
  }
96
- }
97
65
 
98
- function initGallery() {
99
- if(!document.querySelector("#gallery")) return;
66
+ function initEvents() {
67
+ document.body.addEventListener("click", function(e) {
68
+ if(!e.target) return;
69
+
70
+ if(e.target.matches("a.media")) {
71
+ e.preventDefault();
72
+ jump(e.target.href);
73
+ }
74
+ else if(e.target.matches("#page-back")) {
75
+ e.stopPropagation();
76
+ prev();
77
+ }
78
+ else if(e.target.matches("#page-back-10")) {
79
+ e.stopPropagation();
80
+ rewind();
81
+ }
82
+ else if(e.target.matches("#page-next")) {
83
+ e.stopPropagation();
84
+ next();
85
+ }
86
+ else if(e.target.matches("#page-next-10")) {
87
+ e.stopPropagation();
88
+ fastForward();
89
+ }
90
+ else if(e.target.matches("#page-toggle-size")) {
91
+ e.stopPropagation();
92
+ document.body.classList.toggle("split-screen");
93
+ }
94
+ });
100
95
 
101
- initPaginator();
102
- loadPages();
96
+ window.addEventListener("keydown", function(e) {
97
+ if(e.keyCode == 39 || ((e.keyCode == 32 || e.keyCode == 13) && atBottom())) {
98
+ e.preventDefault();
99
+ next();
100
+ }
101
+ else if(e.keyCode == 8 || e.keyCode == 37) {
102
+ e.preventDefault();
103
+ prev();
104
+ }
105
+ });
106
+ }
103
107
 
104
- document.body.addEventListener("click", function(e) {
105
- console.log("clicked, e: ", e);
106
- if(e.target && e.target.matches("a.viewable")) openLink.call(e.target, e);
107
- });
108
+ function init() {
109
+ initItems();
110
+ initEvents();
111
+ render();
112
+ }
108
113
 
109
- page(1);
114
+ return {
115
+ init: init,
116
+ render: render,
117
+ clamp: clamp,
118
+ go: go,
119
+ prev: prev,
120
+ next: next,
121
+ rewind: rewind,
122
+ fastForward: fastForward,
123
+ jump: jump
124
+ };
125
+ })();
126
+
127
+ function initGallery() {
128
+ if(!document.querySelector("#gallery")) return;
129
+ Gallery.init();
110
130
  }
111
131
 
112
132
  window.addEventListener("DOMContentLoaded", initGallery);
@@ -2,7 +2,8 @@
2
2
  %html
3
3
  %head
4
4
  %meta{charset: 'utf-8'}
5
- %title Directory Listing of #{url_path}
5
+ %title Listing of #{url_path}
6
+ %meta{name: 'viewport', content: 'width=device-width, height=device-height, user-scalable=no'}
6
7
  :css
7
8
  #{include('normalize.css')}
8
9
  #{include('common.css')}
@@ -13,6 +14,6 @@
13
14
  :javascript
14
15
  #{include('gallery.js')}
15
16
  %body
16
- - if show_gallery
17
+ - if files.any? { |f| f.decorate.media? }
17
18
  #gallery!= partial('gallery')
18
- #listing!= partial('listing', { url_path: url_path, fs_path: fs_path, directories: directories, files: files })
19
+ #listing!= partial('listing', { url_path: url_path, directories: directories, files: files })
@@ -10,6 +10,7 @@ table {
10
10
  width: 100%;
11
11
  border-collapse: collapse;
12
12
  border: 1px solid #ddd;
13
+ table-layout: fixed;
13
14
  }
14
15
 
15
16
  th, td {
@@ -19,4 +20,24 @@ th, td {
19
20
 
20
21
  tbody > tr:nth-of-type(odd) {
21
22
  background-color: #f9f9f9;
23
+ }
24
+
25
+ th.new-tab {
26
+ width: 6em;
27
+ }
28
+
29
+ th.type {
30
+ width: 4em;
31
+ }
32
+
33
+ th.size {
34
+ width: 6em;
35
+ }
36
+
37
+ th.modified {
38
+ width: 12em;
39
+ }
40
+
41
+ span.icon {
42
+ margin-right: 0.2em;
22
43
  }
@@ -1,3 +1,3 @@
1
1
  module Servel
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/servel.gemspec CHANGED
@@ -22,8 +22,8 @@ Gem::Specification.new do |spec|
22
22
  spec.require_paths = ["lib"]
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 1.16"
25
- spec.add_development_dependency "rake", "~> 10.0"
26
- spec.add_dependency "rack"
25
+ spec.add_development_dependency "rake", "~> 12.0"
26
+ spec.add_dependency "rack", "~> 2.0"
27
27
  spec.add_dependency "puma"
28
28
  spec.add_dependency "naturalsorter"
29
29
  spec.add_dependency "haml", "~> 4"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: servel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brenton "B-Train" Fletcher
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-22 00:00:00.000000000 Z
11
+ date: 2018-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '12.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '12.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rack
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '2.0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '2.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: puma
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -127,8 +127,9 @@ files:
127
127
  - lib/servel.rb
128
128
  - lib/servel/core_ext/pathname.rb
129
129
  - lib/servel/haml_context.rb
130
- - lib/servel/index_view.rb
130
+ - lib/servel/locals.rb
131
131
  - lib/servel/middleware.rb
132
+ - lib/servel/pathname_decorator.rb
132
133
  - lib/servel/servel.rb
133
134
  - lib/servel/templates/_gallery.haml
134
135
  - lib/servel/templates/_listing.haml
@@ -1,26 +0,0 @@
1
- class Servel::IndexView
2
- def initialize(url_path, fs_path)
3
- @url_path = url_path
4
- @fs_path = fs_path
5
- end
6
-
7
- def render(haml_context)
8
- haml_context.render('index.haml', locals)
9
- end
10
-
11
- def locals
12
- directories, files = @fs_path.children.partition { |child| child.directory? }
13
-
14
- {
15
- url_path: @url_path,
16
- fs_path: @fs_path,
17
- directories: sort_paths(directories),
18
- files: sort_paths(files),
19
- show_gallery: files.any? { |file| file.image? }
20
- }
21
- end
22
-
23
- def sort_paths(paths)
24
- Naturalsorter::Sorter.sort(paths.map(&:to_s), true).map { |path| Pathname.new(path) }
25
- end
26
- end