mirror of
https://github.com/hkalexling/Mango.git
synced 2026-04-25 00:00:52 -04:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5645f272df | |||
| dc3bbd10d6 | |||
| c89c74c71b | |||
| cb76a96126 | |||
| 73b38492ba | |||
| bf37c4aa10 | |||
| f837be0718 | |||
| 98baf63b0c |
@@ -1,7 +1,12 @@
|
|||||||
# Mango
|
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
# Mango
|
||||||
|
|
||||||
|
[](https://gitter.im/mango-cr/mango?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
|
|
||||||
Mango is a self-hosted manga server and reader. Its features include
|
Mango is a self-hosted manga server and reader. Its features include
|
||||||
|
|
||||||
- Multi-user support
|
- Multi-user support
|
||||||
|
|||||||
+3
-1
@@ -4,7 +4,9 @@ const minifyCss = require('gulp-minify-css');
|
|||||||
|
|
||||||
gulp.task('minify-js', () => {
|
gulp.task('minify-js', () => {
|
||||||
return gulp.src('public/js/*.js')
|
return gulp.src('public/js/*.js')
|
||||||
.pipe(minify())
|
.pipe(minify({
|
||||||
|
removeConsole: true
|
||||||
|
}))
|
||||||
.pipe(gulp.dest('dist/js'));
|
.pipe(gulp.dest('dist/js'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
$(() => {
|
||||||
|
$('option#name-up').attr('selected', '');
|
||||||
|
$('#sort-select').change(() => {
|
||||||
|
const sort = $('#sort-select').find(':selected').attr('id');
|
||||||
|
const ary = sort.split('-');
|
||||||
|
const by = ary[0];
|
||||||
|
const dir = ary[1];
|
||||||
|
|
||||||
|
const items = $('.item');
|
||||||
|
items.remove();
|
||||||
|
|
||||||
|
items.sort((a, b) => {
|
||||||
|
var res;
|
||||||
|
if (by === 'name')
|
||||||
|
res = $(a).find('.uk-card-title').text() > $(b).find('.uk-card-title').text();
|
||||||
|
else if (by === 'date')
|
||||||
|
res = $(a).attr('data-mtime') > $(b).attr('data-mtime');
|
||||||
|
else {
|
||||||
|
const ap = $(a).attr('data-progress');
|
||||||
|
const bp = $(b).attr('data-progress');
|
||||||
|
if (ap === bp)
|
||||||
|
// if progress is the same, we compare by name
|
||||||
|
res = $(a).find('.uk-card-title').text() > $(b).find('.uk-card-title').text();
|
||||||
|
else
|
||||||
|
res = ap > bp;
|
||||||
|
}
|
||||||
|
if (dir === 'up')
|
||||||
|
return res;
|
||||||
|
else
|
||||||
|
return !res;
|
||||||
|
});
|
||||||
|
var html = '';
|
||||||
|
$('#item-container').append(items);
|
||||||
|
});
|
||||||
|
});
|
||||||
+30
-7
@@ -16,7 +16,8 @@ end
|
|||||||
class Entry
|
class Entry
|
||||||
JSON.mapping zip_path: String, book_title: String, title: String, \
|
JSON.mapping zip_path: String, book_title: String, title: String, \
|
||||||
size: String, pages: Int32, cover_url: String, id: String, \
|
size: String, pages: Int32, cover_url: String, id: String, \
|
||||||
title_id: String, encoded_path: String, encoded_title: String
|
title_id: String, encoded_path: String, encoded_title: String,
|
||||||
|
mtime: Time
|
||||||
|
|
||||||
def initialize(path, @book_title, @title_id, storage)
|
def initialize(path, @book_title, @title_id, storage)
|
||||||
@zip_path = path
|
@zip_path = path
|
||||||
@@ -24,14 +25,17 @@ class Entry
|
|||||||
@title = File.basename path, File.extname path
|
@title = File.basename path, File.extname path
|
||||||
@encoded_title = URI.encode @title
|
@encoded_title = URI.encode @title
|
||||||
@size = (File.size path).humanize_bytes
|
@size = (File.size path).humanize_bytes
|
||||||
@pages = Zip::File.new(path).entries
|
file = Zip::File.new path
|
||||||
|
@pages = file.entries
|
||||||
.select { |e|
|
.select { |e|
|
||||||
["image/jpeg", "image/png"].includes? \
|
["image/jpeg", "image/png"].includes? \
|
||||||
MIME.from_filename? e.filename
|
MIME.from_filename? e.filename
|
||||||
}
|
}
|
||||||
.size
|
.size
|
||||||
|
file.close
|
||||||
@id = storage.get_id @zip_path, false
|
@id = storage.get_id @zip_path, false
|
||||||
@cover_url = "/api/page/#{@title_id}/#{@id}/1"
|
@cover_url = "/api/page/#{@title_id}/#{@id}/1"
|
||||||
|
@mtime = File.info(@zip_path).modification_time
|
||||||
end
|
end
|
||||||
def read_page(page_num)
|
def read_page(page_num)
|
||||||
Zip::File.open @zip_path do |file|
|
Zip::File.open @zip_path do |file|
|
||||||
@@ -57,20 +61,40 @@ end
|
|||||||
|
|
||||||
class Title
|
class Title
|
||||||
JSON.mapping dir: String, entries: Array(Entry), title: String,
|
JSON.mapping dir: String, entries: Array(Entry), title: String,
|
||||||
id: String, encoded_title: String
|
id: String, encoded_title: String, mtime: Time, logger: MLogger
|
||||||
|
|
||||||
def initialize(dir : String, storage)
|
def initialize(dir : String, storage, @logger : MLogger)
|
||||||
@dir = dir
|
@dir = dir
|
||||||
@id = storage.get_id @dir, true
|
@id = storage.get_id @dir, true
|
||||||
@title = File.basename dir
|
@title = File.basename dir
|
||||||
@encoded_title = URI.encode @title
|
@encoded_title = URI.encode @title
|
||||||
@entries = (Dir.entries dir)
|
@entries = (Dir.entries dir)
|
||||||
.select { |path| [".zip", ".cbz"].includes? File.extname path }
|
.select { |path| [".zip", ".cbz"].includes? File.extname path }
|
||||||
|
.map { |path| File.join dir, path }
|
||||||
|
.select { |path| valid_zip path }
|
||||||
.map { |path|
|
.map { |path|
|
||||||
Entry.new File.join(dir, path), @title, @id, storage
|
Entry.new path, @title, @id, storage
|
||||||
}
|
}
|
||||||
.select { |e| e.pages > 0 }
|
.select { |e| e.pages > 0 }
|
||||||
.sort { |a, b| a.title <=> b.title }
|
.sort { |a, b| a.title <=> b.title }
|
||||||
|
mtimes = [File.info(dir).modification_time]
|
||||||
|
mtimes += @entries.map{|e| e.mtime}
|
||||||
|
@mtime = mtimes.max
|
||||||
|
end
|
||||||
|
# When downloading from MangaDex, the zip/cbz file would not be valid
|
||||||
|
# before the download is completed. If we scan the zip file,
|
||||||
|
# Entry.new would throw, so we use this method to check before
|
||||||
|
# constructing Entry
|
||||||
|
private def valid_zip(path : String)
|
||||||
|
begin
|
||||||
|
file = Zip::File.new path
|
||||||
|
file.close
|
||||||
|
return true
|
||||||
|
rescue
|
||||||
|
@logger.warn "File #{path} is corrupted or is not a valid zip "\
|
||||||
|
"archive. Ignoring it."
|
||||||
|
return false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
def get_entry(eid)
|
def get_entry(eid)
|
||||||
@entries.find { |e| e.id == eid }
|
@entries.find { |e| e.id == eid }
|
||||||
@@ -178,10 +202,9 @@ class Library
|
|||||||
end
|
end
|
||||||
@titles = (Dir.entries @dir)
|
@titles = (Dir.entries @dir)
|
||||||
.select { |path| File.directory? File.join @dir, path }
|
.select { |path| File.directory? File.join @dir, path }
|
||||||
.map { |path| Title.new File.join(@dir, path), @storage }
|
.map { |path| Title.new File.join(@dir, path), @storage, @logger }
|
||||||
.select { |title| !title.entries.empty? }
|
.select { |title| !title.entries.empty? }
|
||||||
.sort { |a, b| a.title <=> b.title }
|
.sort { |a, b| a.title <=> b.title }
|
||||||
@logger.debug "Scan completed"
|
@logger.debug "Scan completed"
|
||||||
@logger.debug "Scanned library: \n#{self.to_pretty_json}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ require "./server"
|
|||||||
require "./context"
|
require "./context"
|
||||||
require "option_parser"
|
require "option_parser"
|
||||||
|
|
||||||
VERSION = "0.1.0"
|
VERSION = "0.1.2"
|
||||||
|
|
||||||
config_path = nil
|
config_path = nil
|
||||||
|
|
||||||
|
|||||||
+18
-3
@@ -1,14 +1,28 @@
|
|||||||
<h2 class=uk-title>Library</h2>
|
<h2 class=uk-title>Library</h2>
|
||||||
<p class="uk-text-meta"><%= titles.size %> titles found</p>
|
<p class="uk-text-meta"><%= titles.size %> titles found</p>
|
||||||
<div class="uk-margin">
|
<div class="uk-grid-small" uk-grid>
|
||||||
|
<div class="uk-margin-bottom uk-width-3-4@s">
|
||||||
<form class="uk-search uk-search-default">
|
<form class="uk-search uk-search-default">
|
||||||
<span uk-search-icon></span>
|
<span uk-search-icon></span>
|
||||||
<input class="uk-search-input" type="search" placeholder="Search">
|
<input class="uk-search-input" type="search" placeholder="Search">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
|
<div class="uk-margin-bottom uk-width-1-4@s">
|
||||||
|
<div class="uk-form-horizontal">
|
||||||
|
<select class="uk-select" id="sort-select">
|
||||||
|
<option id="name-up">▲ Name</option>
|
||||||
|
<option id="name-down">▼ Name</option>
|
||||||
|
<option id="date-up">▲ Date Modified</option>
|
||||||
|
<option id="date-down">▼ Date Modified</option>
|
||||||
|
<option id="progress-up">▲ Progress</option>
|
||||||
|
<option id="progress-down">▼ Progress</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="item-container" class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
|
||||||
<%- titles.each_with_index do |t, i| -%>
|
<%- titles.each_with_index do |t, i| -%>
|
||||||
<div class="item">
|
<div class="item" data-mtime="<%= t.mtime.to_unix %>" data-progress="<%= percentage[i] %>">
|
||||||
<a class="acard" href="/book/<%= t.id %>">
|
<a class="acard" href="/book/<%= t.id %>">
|
||||||
<div class="uk-card uk-card-default">
|
<div class="uk-card uk-card-default">
|
||||||
<div class="uk-card-media-top">
|
<div class="uk-card-media-top">
|
||||||
@@ -27,4 +41,5 @@
|
|||||||
|
|
||||||
<% content_for "script" do %>
|
<% content_for "script" do %>
|
||||||
<script src="/js/search.js"></script>
|
<script src="/js/search.js"></script>
|
||||||
|
<script src="/js/sort-items.js"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
+18
-3
@@ -1,15 +1,29 @@
|
|||||||
<div id="alert"></div>
|
<div id="alert"></div>
|
||||||
<h2 class=uk-title><%= title.title %></h2>
|
<h2 class=uk-title><%= title.title %></h2>
|
||||||
<p class="uk-text-meta"><%= title.entries.size %> entries found</p>
|
<p class="uk-text-meta"><%= title.entries.size %> entries found</p>
|
||||||
<div class="uk-margin">
|
<div class="uk-grid-small" uk-grid>
|
||||||
|
<div class="uk-margin-bottom uk-width-3-4@s">
|
||||||
<form class="uk-search uk-search-default">
|
<form class="uk-search uk-search-default">
|
||||||
<span uk-search-icon></span>
|
<span uk-search-icon></span>
|
||||||
<input class="uk-search-input" type="search" placeholder="Search">
|
<input class="uk-search-input" type="search" placeholder="Search">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
|
<div class="uk-margin-bottom uk-width-1-4@s">
|
||||||
|
<div class="uk-form-horizontal">
|
||||||
|
<select class="uk-select" id="sort-select">
|
||||||
|
<option id="name-up">▲ Name</option>
|
||||||
|
<option id="name-down">▼ Name</option>
|
||||||
|
<option id="date-up">▲ Date Modified</option>
|
||||||
|
<option id="date-down">▼ Date Modified</option>
|
||||||
|
<option id="progress-up">▲ Progress</option>
|
||||||
|
<option id="progress-down">▼ Progress</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="item-container" class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
|
||||||
<%- title.entries.each_with_index do |e, i| -%>
|
<%- title.entries.each_with_index do |e, i| -%>
|
||||||
<div class="item">
|
<div class="item" data-mtime="<%= e.mtime.to_unix %>" data-progress="<%= percentage[i] %>">
|
||||||
<a class="acard">
|
<a class="acard">
|
||||||
<div class="uk-card uk-card-default" onclick="showModal("<%= e.encoded_path %>", '<%= e.pages %>', <%= (percentage[i] * 100).round(1) %>, "<%= title.encoded_title %>", "<%= e.encoded_title %>", '<%= e.title_id %>', '<%= e.id %>')">
|
<div class="uk-card uk-card-default" onclick="showModal("<%= e.encoded_path %>", '<%= e.pages %>', <%= (percentage[i] * 100).round(1) %>, "<%= title.encoded_title %>", "<%= e.encoded_title %>", '<%= e.title_id %>', '<%= e.id %>')">
|
||||||
<div class="uk-card-media-top">
|
<div class="uk-card-media-top">
|
||||||
@@ -51,4 +65,5 @@
|
|||||||
<% content_for "script" do %>
|
<% content_for "script" do %>
|
||||||
<script src="/js/title.js"></script>
|
<script src="/js/title.js"></script>
|
||||||
<script src="/js/search.js"></script>
|
<script src="/js/search.js"></script>
|
||||||
|
<script src="/js/sort-items.js"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
Reference in New Issue
Block a user