servel 0.2.0 → 0.3.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.
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