servel 0.9.0 → 0.10.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: 0bbb0f367b145394675e204c7a80df6418b6bbb2
4
- data.tar.gz: 6070307eafbfe804dd06539c736a95c0991c93b3
3
+ metadata.gz: 6b04f7ed035910d2308b11a3ca5cde78e3e5464f
4
+ data.tar.gz: aa266ba4e42980678226a9aad763ef23fef31f67
5
5
  SHA512:
6
- metadata.gz: 865cdba7e39632c9b242d6d7961f2a17940a9b509a158e9d120f9f15fe26c1a0986cd9bce0e6f3e40880d528aa96a50e36de67ae7f6d2619a4f1f20145809caf
7
- data.tar.gz: 07d88d0349d4e20e2eb42cad1003b91679a276c37b9105015c6b792b76b01688b8b75842c8626921d3a717465ba5bdb7451d49749b7e3a6243c38c505fc67f3d
6
+ metadata.gz: 61c466680fffedef13a2a2e9d996379a7c1d98d356b0d8eb044e358e89100bcce1baa1105a2d0c2acd5f463fa087e5b85a29e091552c26f0342ad365bda404e9
7
+ data.tar.gz: 4fa87e8bcdd2b192517f32d21272da99002e242b29a2e01fcb7f6b05aa59b333b44ba5d3940786722156d03a4e47f82f5652617b7f202c08b808bd99332f35f3
@@ -1,27 +1,26 @@
1
- require 'rack'
2
- require 'rack/handler/puma'
3
- require 'haml'
4
- require 'naturalsorter'
5
- require 'active_support/all'
6
-
7
- require 'json'
8
- require 'pathname'
9
- require 'delegate'
10
-
11
- module Servel
12
- def self.build_app(path_map)
13
- url_map = path_map.map { |root, url_root| [url_root, Servel::App.new(root)] }.to_h
14
- url_map["/"] = Servel::HomeApp.new(path_map.values) unless url_map.keys.include?("/")
15
-
16
- Rack::URLMap.new(url_map)
17
- end
18
- end
19
-
20
- require "servel/version"
21
- require "servel/path"
22
- require "servel/path_builder"
23
- require "servel/haml_context"
24
- require "servel/locals"
25
- require "servel/app"
26
- require "servel/home_app"
27
- require "servel/cli"
1
+ require 'rack'
2
+ require 'rack/handler/puma'
3
+ require 'haml'
4
+ require 'naturalsorter'
5
+ require 'active_support/all'
6
+
7
+ require 'thread'
8
+ require 'pathname'
9
+
10
+ module Servel
11
+ def self.build_app(path_map)
12
+ url_map = path_map.map { |root, url_root| [url_root, Servel::App.new(root)] }.to_h
13
+ url_map["/"] = Servel::HomeApp.new(path_map.values) unless url_map.keys.include?("/")
14
+
15
+ Rack::URLMap.new(url_map)
16
+ end
17
+ end
18
+
19
+ require "servel/version"
20
+ require "servel/entry"
21
+ require "servel/entry_factory"
22
+ require "servel/haml_context"
23
+ require "servel/index"
24
+ require "servel/app"
25
+ require "servel/home_app"
26
+ require "servel/cli"
@@ -18,7 +18,7 @@ class Servel::App
18
18
 
19
19
  return [404, {}, []] unless fs_path.exist?
20
20
 
21
- index(Servel::Locals.new(url_root: url_root, url_path: url_path, fs_path: fs_path))
21
+ Servel::Index.new(url_root: url_root, url_path: url_path, fs_path: fs_path).render
22
22
  end
23
23
 
24
24
  def redirect(location)
@@ -30,11 +30,4 @@ class Servel::App
30
30
  raise unless Rack::Utils.valid_path?(url_path)
31
31
  Rack::Utils.clean_path_info(url_path)
32
32
  end
33
-
34
- def index(locals)
35
- @haml_context ||= Servel::HamlContext.new
36
- body = @haml_context.render('index.haml', locals.resolve)
37
-
38
- [200, {}, [body]]
39
- end
40
33
  end
@@ -0,0 +1,18 @@
1
+ class Servel::Entry
2
+ attr_reader :type, :media_type, :listing_classes, :icon, :href, :name, :size, :mtime
3
+
4
+ def initialize(type:, media_type: nil, listing_classes:, icon:, href:, name:, size: nil, mtime: nil)
5
+ @type = type
6
+ @media_type = media_type
7
+ @listing_classes = listing_classes
8
+ @icon = icon
9
+ @href = href
10
+ @name = name
11
+ @size = size
12
+ @mtime = mtime
13
+ end
14
+
15
+ def media?
16
+ !@media_type.nil?
17
+ end
18
+ end
@@ -1,14 +1,39 @@
1
- class Servel::PathBuilder
1
+ class Servel::EntryFactory
2
2
  IMAGE_EXTS = %w(.jpg .jpeg .png .gif)
3
3
  VIDEO_EXTS = %w(.webm .mp4 .mkv)
4
4
  AUDIO_EXTS = %w(.mp3 .m4a .wav)
5
+ TEXT_EXTS = %w(.txt)
6
+
7
+ def self.top(href)
8
+ Servel::Entry.new(
9
+ type: "Dir",
10
+ listing_classes: "top directory",
11
+ icon: "🔝",
12
+ href: href,
13
+ name: "Top Directory"
14
+ )
15
+ end
16
+
17
+ def self.parent(href)
18
+ Servel::Entry.new(
19
+ type: "Dir",
20
+ listing_classes: "parent directory",
21
+ icon: "⬆️",
22
+ href: href,
23
+ name: "Parent Directory"
24
+ )
25
+ end
26
+
27
+ def self.for(path)
28
+ new(path).entry
29
+ end
5
30
 
6
31
  def initialize(path)
7
32
  @path = Pathname.new(path)
8
33
  end
9
34
 
10
- def build
11
- Servel::Path.new(
35
+ def entry
36
+ Servel::Entry.new(
12
37
  type: type,
13
38
  media_type: media_type,
14
39
  listing_classes: listing_classes,
@@ -40,6 +65,8 @@ class Servel::PathBuilder
40
65
  :video
41
66
  when *AUDIO_EXTS
42
67
  :audio
68
+ when *TEXT_EXTS
69
+ :text
43
70
  else
44
71
  nil
45
72
  end
@@ -2,10 +2,14 @@ class Servel::HamlContext
2
2
  include ActiveSupport::NumberHelper
3
3
 
4
4
  ENGINE_OPTIONS = { remove_whitespace: true, escape_html: true, ugly: true }
5
+ LOCK = Mutex.new
5
6
 
6
- def initialize()
7
+ def self.render(template, locals)
8
+ [200, {}, [new.render(template, locals)]]
9
+ end
10
+
11
+ def initialize
7
12
  @build_path = Pathname.new(__FILE__).dirname.realpath + 'templates'
8
- @haml_engine_cache = {}
9
13
  end
10
14
 
11
15
  def render(template, locals = {})
@@ -21,9 +25,12 @@ class Servel::HamlContext
21
25
  end
22
26
 
23
27
  def haml_engine(path)
24
- unless @haml_engine_cache.key?(path)
25
- @haml_engine_cache[path] = Haml::Engine.new(include(path), ENGINE_OPTIONS.merge(filename: path))
28
+ LOCK.synchronize do
29
+ @@haml_engine_cache ||= {}
30
+ unless @@haml_engine_cache.key?(path)
31
+ @@haml_engine_cache[path] = Haml::Engine.new(include(path), ENGINE_OPTIONS.merge(filename: path))
32
+ end
33
+ @@haml_engine_cache[path]
26
34
  end
27
- @haml_engine_cache[path]
28
35
  end
29
36
  end
@@ -1,12 +1,12 @@
1
1
  class Servel::HomeApp
2
+ FAVICON_PATH = "/favicon.ico"
3
+
2
4
  def initialize(roots)
3
5
  @roots = roots
4
6
  end
5
7
 
6
8
  def call(env)
7
- @haml_context ||= Servel::HamlContext.new
8
- body = @haml_context.render('home.haml', { roots: @roots })
9
-
10
- [200, {}, [body]]
9
+ return [404, {}, []] if env["PATH_INFO"] == FAVICON_PATH
10
+ Servel::HamlContext.render('home.haml', { roots: @roots })
11
11
  end
12
12
  end
@@ -1,41 +1,41 @@
1
- class Servel::Locals
2
- def initialize(url_root:, url_path:, fs_path:)
3
- @url_root = url_root
4
- @url_path = url_path
5
- @fs_path = fs_path
6
- end
7
-
8
- def resolve
9
- {
10
- url_root: @url_root,
11
- url_path: @url_path,
12
- directories: directories,
13
- files: files
14
- }
15
- end
16
-
17
- def directories
18
- list = @fs_path.children.select { |child| child.directory? }
19
- list = sort_paths(list).map { |path| build(path) }
20
-
21
- unless @url_path == "/"
22
- list.unshift(Servel::Path.parent("../"))
23
- list.unshift(Servel::Path.top(@url_root == "" ? "/" : @url_root))
24
- end
25
-
26
- list
27
- end
28
-
29
- def build(path)
30
- Servel::PathBuilder.new(path).build
31
- end
32
-
33
- def files
34
- list = @fs_path.children.select { |child| child.file? }
35
- sort_paths(list).map { |path| build(path) }
36
- end
37
-
38
- def sort_paths(paths)
39
- Naturalsorter::Sorter.sort(paths.map(&:to_s), true).map { |path| Pathname.new(path) }
40
- end
1
+ class Servel::Index
2
+ def initialize(url_root:, url_path:, fs_path:)
3
+ @url_root = url_root
4
+ @url_path = url_path
5
+ @fs_path = fs_path
6
+ end
7
+
8
+ def render
9
+ Servel::HamlContext.render('index.haml', locals)
10
+ end
11
+
12
+ def locals
13
+ {
14
+ url_root: @url_root,
15
+ url_path: @url_path,
16
+ directories: directories,
17
+ files: files
18
+ }
19
+ end
20
+
21
+ def directories
22
+ list = @fs_path.children.select { |child| child.directory? }
23
+ list = sort_paths(list).map { |path| Servel::EntryFactory.for(path) }
24
+
25
+ unless @url_path == "/"
26
+ list.unshift(Servel::EntryFactory.parent("../"))
27
+ list.unshift(Servel::EntryFactory.top(@url_root == "" ? "/" : @url_root))
28
+ end
29
+
30
+ list
31
+ end
32
+
33
+ def files
34
+ list = @fs_path.children.select { |child| child.file? }
35
+ sort_paths(list).map { |path| Servel::EntryFactory.for(path) }
36
+ end
37
+
38
+ def sort_paths(paths)
39
+ Naturalsorter::Sorter.sort(paths.map(&:to_s), true).map { |path| Pathname.new(path) }
40
+ end
41
41
  end
@@ -1,6 +1,9 @@
1
1
  %img#image
2
2
  %video#video{autoplay: true, controls: true}
3
3
  %audio#audio{autoplay: true, controls: true}
4
+ #text
5
+ %a#text-anchor{href: '#'}
6
+ #text-content
4
7
  #page-back.paginator ◀
5
8
  #page-next.paginator ▶
6
9
  #page-back-10.paginator ◀◀
@@ -0,0 +1,39 @@
1
+ #text-content {
2
+ max-width: 1000px;
3
+ margin: 0 auto 0 auto;
4
+ padding: 30px;
5
+ }
6
+
7
+ #text-content p {
8
+ margin-top: 0;
9
+ margin-bottom: 1.2em;
10
+ }
11
+
12
+ #text-content h1, #text-content h2, #text-content h3, #text-content h4, #text-content h5, #text-content h6 {
13
+ margin-top: 0;
14
+ margin-bottom: 0.6em;
15
+ }
16
+
17
+ #text-content h1 {
18
+ font-size: 36px;
19
+ }
20
+
21
+ #text-content h2 {
22
+ font-size: 26px;
23
+ }
24
+
25
+ #text-content h3 {
26
+ font-size: 22px;
27
+ }
28
+
29
+ #text-content h4 {
30
+ font-size: 18px;
31
+ }
32
+
33
+ #text-content h5 {
34
+ font-size: 16px;
35
+ }
36
+
37
+ #text-content h6 {
38
+ font-size: 16px;
39
+ }
@@ -3,7 +3,7 @@
3
3
  transform: translateZ(0px);
4
4
  }
5
5
 
6
- #image, #video, #audio {
6
+ #image, #video, #audio, #text {
7
7
  display: none;
8
8
  max-width: 100%;
9
9
  margin: 0 auto 0 auto;
@@ -13,8 +13,14 @@
13
13
  width: 100%;
14
14
  }
15
15
 
16
+ #text {
17
+ overflow: auto;
18
+ color: #000000;
19
+ background-color: #ffffff;
20
+ }
21
+
16
22
  @media (min-width: 992px) {
17
- #image, #video, #audio {
23
+ #image, #video, #audio, #text {
18
24
  max-height: 100%;
19
25
  }
20
26
  }
@@ -39,6 +45,14 @@
39
45
  display: block;
40
46
  }
41
47
 
48
+ #gallery.text {
49
+ padding: 0 100px;
50
+ }
51
+
52
+ #gallery.text #text {
53
+ display: block;
54
+ }
55
+
42
56
  .paginator {
43
57
  position: fixed;
44
58
  width: 100px;
@@ -5,9 +5,9 @@
5
5
  %title Browse Listings
6
6
  %meta{name: 'viewport', content: 'width=device-width, height=device-height, user-scalable=no'}
7
7
  :css
8
- #{include('normalize.css')}
9
- #{include('common.css')}
10
- #{include('home.css')}
8
+ #{include('css/normalize.css')}
9
+ #{include('css/common.css')}
10
+ #{include('css/home.css')}
11
11
  %body
12
12
  #home
13
13
  %h1 Browse Listings
@@ -5,14 +5,16 @@
5
5
  %title Listing of #{url_root}#{url_path}
6
6
  %meta{name: 'viewport', content: 'width=device-width, height=device-height, user-scalable=no'}
7
7
  :css
8
- #{include('normalize.css')}
9
- #{include('common.css')}
10
- #{include('index.css')}
11
- #{include('listing.css')}
12
- #{include('gallery.css')}
8
+ #{include('css/normalize.css')}
9
+ #{include('css/common.css')}
10
+ #{include('css/index.css')}
11
+ #{include('css/listing.css')}
12
+ #{include('css/gallery.css')}
13
+ #{include('css/gallery-text.css')}
13
14
 
14
15
  :javascript
15
- #{include('gallery.js')}
16
+ #{include('js/ume.js')}
17
+ #{include('js/gallery.js')}
16
18
  %body
17
19
  - if files.any? { |f| f.media? }
18
20
  #gallery!= partial('gallery')
@@ -1,3 +1,5 @@
1
+ var $;
2
+
1
3
  var Gallery = (function() {
2
4
  var urls = [];
3
5
  var types = [];
@@ -13,15 +15,33 @@ var Gallery = (function() {
13
15
  }
14
16
  }
15
17
 
18
+ function renderText(url) {
19
+ var http = new XMLHttpRequest();
20
+ http.open("GET", url, true);
21
+ http.onload = function() {
22
+ $("#text-content").innerHTML = ume(http.responseText);
23
+ $("#text").scrollTop = 0;
24
+ $("#text-anchor").focus();
25
+ }
26
+ http.send();
27
+ }
28
+
16
29
  function render() {
17
30
  var url = urls[currentIndex];
18
31
  var type = types[currentIndex];
19
32
 
20
- var galleryElement = document.querySelector("#gallery");
21
- galleryElement.classList.remove("image", "video", "audio");
22
- galleryElement.classList.add(type);
33
+ var $gallery = $("#gallery");
34
+ $gallery.classList.remove("image", "video", "audio", "text");
35
+ $gallery.classList.add(type);
23
36
 
24
- document.getElementById(type).src = url;
37
+ if(type == "text") {
38
+ renderText(url);
39
+ }
40
+ else {
41
+ var $element = $("#" + type);
42
+ $element.src = url;
43
+ $element.focus();
44
+ }
25
45
 
26
46
  window.scrollTo(0, 0);
27
47
 
@@ -128,7 +148,8 @@ var Gallery = (function() {
128
148
  })();
129
149
 
130
150
  function initGallery() {
131
- if(!document.querySelector("#gallery")) return;
151
+ $ = document.querySelector.bind(document);
152
+ if(!$("#gallery")) return;
132
153
  Gallery.init();
133
154
  }
134
155
 
@@ -0,0 +1,63 @@
1
+ var ume = (function() {
2
+ function normalise(text) {
3
+ return text.replace(/\s+/g, " ").trim();
4
+ }
5
+
6
+ function escapeEntities(text) {
7
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
8
+ }
9
+
10
+ const BOLD_ITALIC_REGEX = /(?:(\W|^)(\*)|(\*)(\W|$)|(\W|^)(_)|(_)(\W|$))/;
11
+
12
+ function parseInlines(text) {
13
+ const parts = text.split(BOLD_ITALIC_REGEX);
14
+
15
+ if(parts.filter(function(part) { return part == "*"; }).length % 2 != 0) return text;
16
+ if(parts.filter(function(part) { return part == "_"; }).length % 2 != 0) return text;
17
+
18
+ var inBold = false;
19
+ var inItalic = false;
20
+ var html = [];
21
+
22
+ for(const part of parts) {
23
+ if(part == "*") {
24
+ inBold = !inBold;
25
+ html.push(inBold ? "<b>" : "</b>"); //Note that in_bold has been inverted, so this is inverted as well
26
+ }
27
+ else if(part == "_") {
28
+ inItalic = !inItalic;
29
+ html.push(inItalic ? "<i>" : "</i>"); //Note that in_italic has been inverted, so this is inverted as well
30
+ }
31
+ else {
32
+ html.push(part);
33
+ }
34
+ }
35
+
36
+ return html.join("");
37
+ }
38
+
39
+ function parseHeading(text) {
40
+ const parts = text.match(/^(#+) (.*)$/);
41
+ return "<h" + parts[1].length + ">" + parseInlines(parts[2]) + "</h" + parts[1].length + ">";
42
+ }
43
+
44
+ function parseParagraph(text) {
45
+ return "<p>" + parseInlines(text) + "</p>";
46
+ }
47
+
48
+ return function(text) {
49
+ const nodes = text.split(/(\r?\n){2,}/g);
50
+ var html = [];
51
+
52
+ for(const node of nodes) {
53
+ const cleanedNode = escapeEntities(normalise(node));
54
+
55
+ if(cleanedNode.startsWith("#")) html.push(parseHeading(cleanedNode));
56
+ else html.push(parseParagraph(cleanedNode));
57
+ }
58
+
59
+ return html.join("");
60
+ }
61
+ })();
62
+
63
+ if(typeof module === "object" && typeof module.exports === "object") module.exports = ume;
@@ -1,3 +1,3 @@
1
1
  module Servel
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
@@ -1,31 +1,31 @@
1
-
2
- lib = File.expand_path("../lib", __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "servel/version"
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "servel"
8
- spec.version = Servel::VERSION
9
- spec.authors = ["Brenton \"B-Train\" Fletcher"]
10
- spec.email = ["i@bloople.net"]
11
-
12
- spec.summary = %q{Serves files and directories over HTTP.}
13
- spec.description = %q{Serves files and directories over HTTP.}
14
- spec.homepage = "http://bloople.net/"
15
- spec.license = "MIT"
16
-
17
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
- f.match(%r{^(test|spec|features)/})
19
- end
20
- spec.bindir = "bin"
21
- spec.executables = "servel"
22
- spec.require_paths = ["lib"]
23
-
24
- spec.add_development_dependency "bundler", "~> 1.16"
25
- spec.add_development_dependency "rake", "~> 12.0"
26
- spec.add_dependency "rack", "~> 2.0"
27
- spec.add_dependency "puma"
28
- spec.add_dependency "naturalsorter"
29
- spec.add_dependency "haml", "~> 4"
30
- spec.add_dependency "activesupport"
31
- end
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "servel/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "servel"
8
+ spec.version = Servel::VERSION
9
+ spec.authors = ["Brenton \"B-Train\" Fletcher"]
10
+ spec.email = ["i@bloople.net"]
11
+
12
+ spec.summary = %q{Serves files and directories over HTTP.}
13
+ spec.description = %q{Serves files and directories over HTTP.}
14
+ spec.homepage = "http://bloople.net/"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "bin"
21
+ spec.executables = "servel"
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.16"
25
+ spec.add_development_dependency "rake", "~> 12.0"
26
+ spec.add_dependency "rack", "~> 2.0"
27
+ spec.add_dependency "puma"
28
+ spec.add_dependency "naturalsorter"
29
+ spec.add_dependency "haml", "~> 4"
30
+ spec.add_dependency "activesupport"
31
+ end
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.9.0
4
+ version: 0.10.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: 2018-03-12 00:00:00.000000000 Z
11
+ date: 2018-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -127,22 +127,24 @@ files:
127
127
  - lib/servel.rb
128
128
  - lib/servel/app.rb
129
129
  - lib/servel/cli.rb
130
+ - lib/servel/entry.rb
131
+ - lib/servel/entry_factory.rb
130
132
  - lib/servel/haml_context.rb
131
133
  - lib/servel/home_app.rb
132
- - lib/servel/locals.rb
133
- - lib/servel/path.rb
134
- - lib/servel/path_builder.rb
134
+ - lib/servel/index.rb
135
135
  - lib/servel/templates/_gallery.haml
136
136
  - lib/servel/templates/_listing.haml
137
- - lib/servel/templates/common.css
138
- - lib/servel/templates/gallery.css
139
- - lib/servel/templates/gallery.js
140
- - lib/servel/templates/home.css
137
+ - lib/servel/templates/css/common.css
138
+ - lib/servel/templates/css/gallery-text.css
139
+ - lib/servel/templates/css/gallery.css
140
+ - lib/servel/templates/css/home.css
141
+ - lib/servel/templates/css/index.css
142
+ - lib/servel/templates/css/listing.css
143
+ - lib/servel/templates/css/normalize.css
141
144
  - lib/servel/templates/home.haml
142
- - lib/servel/templates/index.css
143
145
  - lib/servel/templates/index.haml
144
- - lib/servel/templates/listing.css
145
- - lib/servel/templates/normalize.css
146
+ - lib/servel/templates/js/gallery.js
147
+ - lib/servel/templates/js/ume.js
146
148
  - lib/servel/version.rb
147
149
  - servel.gemspec
148
150
  homepage: http://bloople.net/
@@ -1,38 +0,0 @@
1
- class Servel::Path
2
- attr_reader :type, :media_type, :listing_classes, :icon, :href, :name, :size, :mtime
3
-
4
- def initialize(type:, media_type: nil, listing_classes:, icon:, href:, name:, size: nil, mtime: nil)
5
- @type = type
6
- @media_type = media_type
7
- @listing_classes = listing_classes
8
- @icon = icon
9
- @href = href
10
- @name = name
11
- @size = size
12
- @mtime = mtime
13
- end
14
-
15
- def media?
16
- !@media_type.nil?
17
- end
18
-
19
- def self.top(href)
20
- Servel::Path.new(
21
- type: "Dir",
22
- listing_classes: "top directory",
23
- icon: "🔝",
24
- href: href,
25
- name: "Top Directory"
26
- )
27
- end
28
-
29
- def self.parent(href)
30
- Servel::Path.new(
31
- type: "Dir",
32
- listing_classes: "parent directory",
33
- icon: "⬆️",
34
- href: href,
35
- name: "Parent Directory"
36
- )
37
- end
38
- end