Compare commits

...

20 Commits

Author SHA1 Message Date
Alex Ling c9b8770b9f Bump version to v0.2.5 2020-04-02 09:12:35 +00:00
Alex Ling e568ec8878 Fix the unexpected sorting behavior on Chrome 2020-04-02 09:06:16 +00:00
Alex Ling ac3df03d88 Show page counts on chapter cards 2020-04-02 05:44:29 +00:00
Alex Ling 7c9728683c On the title page, hide progress label of nested titles 2020-04-02 00:16:19 +00:00
Alex Ling d921d04abf Bump version to v0.2.4 2020-04-01 23:32:16 +00:00
Alex Ling 5400c8c8ef Fix a UI bug that shows "resume download" button on download manager even when the downloading process is not paused 2020-04-01 23:21:32 +00:00
Alex Ling 58e96cd4fe Watch the title element size for change 2020-04-01 06:13:03 +00:00
Alex Ling aa09f3a86f Only show tooltips for truncated titles 2020-04-01 05:59:46 +00:00
Alex Ling a5daded453 Fix the width and height of cover images (#23) 2020-04-01 04:51:57 +00:00
Alex Ling 4968cb8e18 Add tooltips to show un-truncated titles 2020-04-01 04:49:53 +00:00
Alex Ling 27c6e02da8 Run the truncate function after DOM is ready 2020-04-01 04:48:53 +00:00
Alex Ling 68d1b55aea Limit title text height in CSS 2020-04-01 04:47:55 +00:00
Alex Ling 32dc3e84b9 Lazy load images in library/title page to improve page load time 2020-03-31 08:44:07 +00:00
Alex Ling 460fcdf2f5 Limit the number of lines to display in card titles 2020-03-30 20:36:27 +00:00
Alex Ling c6369f9f26 Prevent flash of white in cards 2020-03-30 20:35:30 +00:00
Alex Ling aa147602fc Bump version number 0.2.2 -> 0.2.3 2020-03-27 05:00:14 +00:00
Alex Ling d58c83fbd8 Use BigInt when sorting filenames (#22) 2020-03-27 04:45:03 +00:00
Alex Ling 1a0c3d81ce Add Patreon 2020-03-21 05:18:53 +00:00
Alex Ling 33c61fd8c1 Add build badge 2020-03-19 16:04:06 -04:00
Alex Ling 6eba3fe351 Create build.yml 2020-03-19 19:58:59 +00:00
17 changed files with 115 additions and 79 deletions
+3
View File
@@ -0,0 +1,3 @@
# These are supported funding model platforms
patreon: hkalexling
+24
View File
@@ -0,0 +1,24 @@
name: Build
on:
push:
branches: [ master, dev ]
pull_request:
branches: [ master, dev ]
jobs:
build:
runs-on: ubuntu-latest
container:
image: crystallang/crystal:0.32.1-alpine
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: apk add --no-cache yarn yaml sqlite-static
- name: Build
run: make
- name: Run tests
run: make test
+1 -1
View File
@@ -5,7 +5,7 @@
# Mango # Mango
[![Gitter](https://badges.gitter.im/mango-cr/mango.svg)](https://gitter.im/mango-cr/mango?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Patreon](https://img.shields.io/badge/support-patreon-brightgreen?link=https://www.patreon.com/hkalexling)](https://www.patreon.com/hkalexling) ![Build](https://github.com/hkalexling/Mango/workflows/Build/badge.svg) [![Gitter](https://badges.gitter.im/mango-cr/mango.svg)](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
+14 -2
View File
@@ -5,8 +5,20 @@
padding: 20px; padding: 20px;
} }
.uk-card-media-top { .uk-card-media-top {
max-height: 350px; height: 250px;
overflow: hidden; }
@media (min-width: 600px) {
.uk-card-media-top {
height: 300px;
}
}
.uk-card-media-top > img {
height: 100%;
width: 100%;
object-fit: cover;
}
.uk-card-title {
height: 3em;
} }
.acard:hover { .acard:hover {
text-decoration: none; text-decoration: none;
+18
View File
@@ -0,0 +1,18 @@
const truncate = () => {
$('.acard .uk-card-title').each((i, e) => {
$(e).dotdotdot({
truncate: 'letter',
watch: true,
callback: (truncated) => {
if (truncated) {
$(e).attr('uk-tooltip', $(e).attr('data-title'));
}
else {
$(e).removeAttr('uk-tooltip');
}
}
});
});
};
truncate();
+5 -6
View File
@@ -50,10 +50,10 @@ $(() => {
sortedKeys.sort((a, b) => { sortedKeys.sort((a, b) => {
// sort by frequency of the key first // sort by frequency of the key first
if (keyRange[a][2] !== keyRange[b][2]) { if (keyRange[a][2] !== keyRange[b][2]) {
return keyRange[a][2] < keyRange[b][2]; return (keyRange[a][2] < keyRange[b][2]) ? 1 : -1;
} }
// then sort by range of the key // then sort by range of the key
return (keyRange[a][1] - keyRange[a][0]) < (keyRange[b][1] - keyRange[b][0]); return ((keyRange[a][1] - keyRange[a][0]) < (keyRange[b][1] - keyRange[b][0])) ? 1 : -1;
}); });
console.log(sortedKeys); console.log(sortedKeys);
@@ -70,7 +70,7 @@ $(() => {
return -1; return -1;
if (a.numbers[key] === b.numbers[key]) if (a.numbers[key] === b.numbers[key])
continue; continue;
return a.numbers[key] > b.numbers[key]; return (a.numbers[key] > b.numbers[key]) ? 1 : -1;
} }
return 0; return 0;
}); });
@@ -102,12 +102,11 @@ $(() => {
res = ap > bp; res = ap > bp;
} }
if (dir === 'up') if (dir === 'up')
return res; return res ? 1 : -1;
else else
return !res; return !res ? 1 : -1;
}); });
} }
var html = '';
$('#item-container').append(items); $('#item-container').append(items);
}; };
+10 -45
View File
@@ -15,48 +15,20 @@ const toggleTheme = () => {
saveTheme(newTheme); saveTheme(newTheme);
}; };
// https://stackoverflow.com/a/28344281
const hasClass = (ele,cls) => {
return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
};
const addClass = (ele,cls) => {
if (!hasClass(ele,cls)) ele.className += " "+cls;
};
const removeClass = (ele,cls) => {
if (hasClass(ele,cls)) {
var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
ele.className=ele.className.replace(reg,' ');
}
};
const addClassToClass = (targetCls, newCls) => {
const elements = document.getElementsByClassName(targetCls);
for (let i = 0; i < elements.length; i++) {
addClass(elements[i], newCls);
}
};
const removeClassFromClass = (targetCls, newCls) => {
const elements = document.getElementsByClassName(targetCls);
for (let i = 0; i < elements.length; i++) {
removeClass(elements[i], newCls);
}
};
const setTheme = themeStr => { const setTheme = themeStr => {
if (themeStr === 'dark') { if (themeStr === 'dark') {
document.getElementsByTagName('html')[0].style.background = 'rgb(20, 20, 20)'; $('html').css('background', 'rgb(20, 20, 20)');
addClass(document.getElementsByTagName('body')[0], 'uk-light'); $('body').addClass('uk-light');
addClassToClass('uk-card', 'uk-card-secondary'); $('.uk-card').addClass('uk-card-secondary');
removeClassFromClass('uk-card', 'uk-card-default'); $('.uk-card').removeClass('uk-card-default');
addClassToClass('ui-widget-content', 'dark'); $('.ui-widget-content').addClass('dark');
} }
else { else {
document.getElementsByTagName('html')[0].style.background = ''; $('html').css('background', '');
removeClass(document.getElementsByTagName('body')[0], 'uk-light'); $('body').removeClass('uk-light');
removeClassFromClass('uk-card', 'uk-card-secondary'); $('.uk-card').removeClass('uk-card-secondary');
addClassToClass('uk-card', 'uk-card-default'); $('.uk-card').addClass('uk-card-default');
removeClassFromClass('ui-widget-content', 'dark'); $('.ui-widget-content').removeClass('dark');
} }
}; };
@@ -69,10 +41,3 @@ const styleModal = () => {
// do it before document is ready to prevent the initial flash of white // do it before document is ready to prevent the initial flash of white
setTheme(getTheme()); setTheme(getTheme());
document.addEventListener('DOMContentLoaded', () => {
// because this script is attached at the top of HTML, the style on uk-card
// won't be applied because the elements are not available yet. We have to
// apply the theme again for it to take effect
setTheme(getTheme());
}, false);
+1 -1
View File
@@ -1,5 +1,5 @@
name: mango name: mango
version: 0.2.1 version: 0.2.5
authors: authors:
- Alex Ling <hkalexling@gmail.com> - Alex Ling <hkalexling@gmail.com>
+8
View File
@@ -25,4 +25,12 @@ describe "compare_alphanumerically" do
compare_alphanumerically a, b compare_alphanumerically a, b
}.should eq ary }.should eq ary
end end
# https://github.com/hkalexling/Mango/issues/22
it "handles numbers larger than Int32" do
ary = ["14410155591588.jpg", "21410155591588.png", "104410155591588.jpg"]
ary.reverse.sort {|a, b|
compare_alphanumerically a, b
}.should eq ary
end
end end
+2 -2
View File
@@ -208,11 +208,11 @@ class Title
info = TitleInfo.new @dir info = TitleInfo.new @dir
page = load_progress username, entry page = load_progress username, entry
entry_obj = @entries.find{|e| e.title == entry} entry_obj = @entries.find{|e| e.title == entry}
return 0 if entry_obj.nil? return 0.0 if entry_obj.nil?
page / entry_obj.pages page / entry_obj.pages
end end
def load_percetage(username) def load_percetage(username)
return 0 if @entries.empty? return 0.0 if @entries.empty?
read_pages = total_pages = 0 read_pages = total_pages = 0
@entries.each do |e| @entries.each do |e|
read_pages += load_progress username, e.title read_pages += load_progress username, e.title
+5 -4
View File
@@ -249,6 +249,7 @@ module MangaDex
class Downloader class Downloader
property stopped = false property stopped = false
@downloading = false
def initialize(@queue : Queue, @api : API, @library_path : String, def initialize(@queue : Queue, @api : API, @library_path : String,
@wait_seconds : Int32, @retries : Int32, @wait_seconds : Int32, @retries : Int32,
@@ -258,7 +259,7 @@ module MangaDex
spawn do spawn do
loop do loop do
sleep 1.second sleep 1.second
next if @stopped next if @stopped || @downloading
begin begin
job = @queue.pop job = @queue.pop
next if job.nil? next if job.nil?
@@ -271,7 +272,7 @@ module MangaDex
end end
private def download(job : Job) private def download(job : Job)
@stopped = true @downloading = true
@queue.set_status JobStatus::Downloading, job @queue.set_status JobStatus::Downloading, job
begin begin
chapter = @api.get_chapter(job.id) chapter = @api.get_chapter(job.id)
@@ -281,7 +282,7 @@ module MangaDex
unless e.message.nil? unless e.message.nil?
@queue.add_message e.message.not_nil!, job @queue.add_message e.message.not_nil!, job
end end
@stopped = false @downloading = false
return return
end end
@queue.set_pages chapter.pages.size, job @queue.set_pages chapter.pages.size, job
@@ -346,7 +347,7 @@ module MangaDex
else else
@queue.set_status JobStatus::MissingPages, job @queue.set_status JobStatus::MissingPages, job
end end
@stopped = false @downloading = false
end end
end end
+1 -1
View File
@@ -3,7 +3,7 @@ require "./context"
require "./mangadex/*" require "./mangadex/*"
require "option_parser" require "option_parser"
VERSION = "0.2.1" VERSION = "0.2.5"
config_path = nil config_path = nil
+1 -2
View File
@@ -33,6 +33,7 @@ class MainRouter < Router
env.redirect "/login" env.redirect "/login"
end end
end end
get "/" do |env| get "/" do |env|
titles = @context.library.titles titles = @context.library.titles
username = get_username env username = get_username env
@@ -47,8 +48,6 @@ class MainRouter < Router
username = get_username env username = get_username env
percentage = title.entries.map { |e| percentage = title.entries.map { |e|
title.load_percetage username, e.title } title.load_percetage username, e.title }
titles_percentage = title.titles.map { |t|
title.load_percetage username, t.title }
layout "title" layout "title"
rescue e rescue e
@context.error e @context.error e
+3 -1
View File
@@ -1,3 +1,5 @@
require "big"
IMGS_PER_PAGE = 5 IMGS_PER_PAGE = 5
macro layout(name) macro layout(name)
@@ -56,7 +58,7 @@ def compare_alphanumerically(c, d)
return -1 if a.nil? return -1 if a.nil?
return 1 if b.nil? return 1 if b.nil?
if is_numeric(a) && is_numeric(b) if is_numeric(a) && is_numeric(b)
compare = a.to_i <=> b.to_i compare = a.to_big_i <=> b.to_big_i
return compare if compare != 0 return compare if compare != 0
else else
compare = a <=> b compare = a <=> b
+5 -3
View File
@@ -27,16 +27,16 @@
<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">
<%- if t.entries.size > 0 -%> <%- if t.entries.size > 0 -%>
<img src="<%= t.entries[0].cover_url %>" alt=""> <img data-src="<%= t.entries[0].cover_url %>" data-width data-height alt="" uk-img>
<%- else -%> <%- else -%>
<img src="/img/icon.png" alt=""> <img data-src="/img/icon.png" data-width data-height alt="" uk-img>
<%- end -%> <%- end -%>
</div> </div>
<div class="uk-card-body"> <div class="uk-card-body">
<%- if t.entries.size > 0 -%> <%- if t.entries.size > 0 -%>
<div class="uk-card-badge uk-label"><%= (percentage[i] * 100).round(1) %>%</div> <div class="uk-card-badge uk-label"><%= (percentage[i] * 100).round(1) %>%</div>
<%- end -%> <%- end -%>
<h3 class="uk-card-title break-word"><%= t.title %></h3> <h3 class="uk-card-title break-word" data-title="<%= t.title.gsub("\"", "&quot;") %>"><%= t.title %></h3>
<p><%= t.size %> entries</p> <p><%= t.size %> entries</p>
</div> </div>
</div> </div>
@@ -46,6 +46,8 @@
</div> </div>
<% content_for "script" do %> <% content_for "script" do %>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery.dotdotdot/4.0.11/dotdotdot.js"></script>
<script src="/js/dots.js"></script>
<script src="/js/search.js"></script> <script src="/js/search.js"></script>
<script src="/js/sort-items.js"></script> <script src="/js/sort-items.js"></script>
<% end %> <% end %>
+5 -2
View File
@@ -10,10 +10,11 @@
<link rel="stylesheet" href="/css/mango.css" /> <link rel="stylesheet" href="/css/mango.css" />
<script defer src="/js/fontawesome.min.js"></script> <script defer src="/js/fontawesome.min.js"></script>
<script defer src="/js/solid.min.js"></script> <script defer src="/js/solid.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="/js/theme.js"></script>
</head> </head>
<body> <body>
<script src="/js/theme.js"></script>
<div class="uk-offcanvas-content"> <div class="uk-offcanvas-content">
<div class="uk-navbar-container uk-navbar-transparent" uk-navbar="uk-navbar"> <div class="uk-navbar-container uk-navbar-transparent" uk-navbar="uk-navbar">
<div id="mobile-nav" uk-offcanvas="overlay: true"> <div id="mobile-nav" uk-offcanvas="overlay: true">
@@ -59,7 +60,9 @@
<%= content %> <%= content %>
</div> </div>
</div> </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script>
setTheme(getTheme());
</script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit-icons.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit-icons.min.js"></script>
+9 -9
View File
@@ -31,21 +31,18 @@
</div> </div>
<div id="item-container" class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid> <div id="item-container" class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
<%- title.titles.each_with_index do |t, i| -%> <%- title.titles.each_with_index do |t, i| -%>
<div class="item" data-mtime="<%= t.mtime.to_unix %>" data-progress="<%= titles_percentage[i] %>"> <div class="item" data-mtime="<%= t.mtime.to_unix %>" data-progress="0.0">
<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">
<%- if t.entries.size > 0 -%> <%- if t.entries.size > 0 -%>
<img src="<%= t.entries[0].cover_url %>" alt=""> <img data-src="<%= t.entries[0].cover_url %>" data-width data-height alt="" uk-img>
<%- else -%> <%- else -%>
<img src="/img/icon.png" alt=""> <img data-src="/img/icon.png" data-width data-height alt="" uk-img>
<%- end -%> <%- end -%>
</div> </div>
<div class="uk-card-body"> <div class="uk-card-body">
<%- if t.entries.size > 0 -%> <h3 class="uk-card-title break-word" data-title="<%= t.title.gsub("\"", "&quot;") %>"><%= t.title %></h3>
<div class="uk-card-badge uk-label"><%= (titles_percentage[i] * 100).round(1) %>%</div>
<%- end -%>
<h3 class="uk-card-title break-word"><%= t.title %></h3>
<p><%= t.size %> entries</p> <p><%= t.size %> entries</p>
</div> </div>
</div> </div>
@@ -57,11 +54,12 @@
<a class="acard"> <a class="acard">
<div class="uk-card uk-card-default" onclick="showModal(&quot;<%= e.encoded_path %>&quot;, '<%= e.pages %>', <%= (percentage[i] * 100).round(1) %>, &quot;<%= title.encoded_title %>&quot;, &quot;<%= e.encoded_title %>&quot;, '<%= e.title_id %>', '<%= e.id %>')"> <div class="uk-card uk-card-default" onclick="showModal(&quot;<%= e.encoded_path %>&quot;, '<%= e.pages %>', <%= (percentage[i] * 100).round(1) %>, &quot;<%= title.encoded_title %>&quot;, &quot;<%= e.encoded_title %>&quot;, '<%= e.title_id %>', '<%= e.id %>')">
<div class="uk-card-media-top"> <div class="uk-card-media-top">
<img src="<%= e.cover_url %>" alt=""> <img data-src="<%= e.cover_url %>" alt="" data-width data-height uk-img>
</div> </div>
<div class="uk-card-body"> <div class="uk-card-body">
<div class="uk-card-badge uk-label"><%= (percentage[i] * 100).round(1) %>%</div> <div class="uk-card-badge uk-label"><%= (percentage[i] * 100).round(1) %>%</div>
<h3 class="uk-card-title"><%= e.title %></h3> <h3 class="uk-card-title break-word" data-title="<%= e.title.gsub("\"", "&quot;") %>"><%= e.title %></h3>
<p><%= e.pages %> pages</p>
</div> </div>
</div> </div>
</a> </a>
@@ -93,6 +91,8 @@
</div> </div>
<% content_for "script" do %> <% content_for "script" do %>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery.dotdotdot/4.0.11/dotdotdot.js"></script>
<script src="/js/dots.js"></script>
<script src="/js/alert.js"></script> <script src="/js/alert.js"></script>
<script src="/js/title.js"></script> <script src="/js/title.js"></script>
<script src="/js/search.js"></script> <script src="/js/search.js"></script>