mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-02 10:55:30 -04:00
Merge pull request #259 from Leeingnyo/feature/custom-sort-title-and-sorting-titles
Implement custom sort title and sorting titles
This commit is contained in:
commit
2007f13ed6
94
migration/sort_title.12.cr
Normal file
94
migration/sort_title.12.cr
Normal file
@ -0,0 +1,94 @@
|
||||
class SortTitle < MG::Base
|
||||
def up : String
|
||||
<<-SQL
|
||||
-- add sort_title column to ids and titles
|
||||
ALTER TABLE ids ADD COLUMN sort_title TEXT;
|
||||
ALTER TABLE titles ADD COLUMN sort_title TEXT;
|
||||
SQL
|
||||
end
|
||||
|
||||
def down : String
|
||||
<<-SQL
|
||||
-- remove sort_title column from ids
|
||||
ALTER TABLE ids RENAME TO tmp;
|
||||
|
||||
CREATE TABLE ids (
|
||||
path TEXT NOT NULL,
|
||||
id TEXT NOT NULL,
|
||||
signature TEXT,
|
||||
unavailable INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
INSERT INTO ids
|
||||
SELECT path, id, signature, unavailable
|
||||
FROM tmp;
|
||||
|
||||
DROP TABLE tmp;
|
||||
|
||||
-- recreate the indices
|
||||
CREATE UNIQUE INDEX path_idx ON ids (path);
|
||||
CREATE UNIQUE INDEX id_idx ON ids (id);
|
||||
|
||||
-- recreate the foreign key constraint on thumbnails
|
||||
ALTER TABLE thumbnails RENAME TO tmp;
|
||||
|
||||
CREATE TABLE thumbnails (
|
||||
id TEXT NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
mime TEXT NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
FOREIGN KEY (id) REFERENCES ids (id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO thumbnails
|
||||
SELECT * FROM tmp;
|
||||
|
||||
DROP TABLE tmp;
|
||||
|
||||
CREATE UNIQUE INDEX tn_index ON thumbnails (id);
|
||||
|
||||
-- remove sort_title column from titles
|
||||
ALTER TABLE titles RENAME TO tmp;
|
||||
|
||||
CREATE TABLE titles (
|
||||
id TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
signature TEXT,
|
||||
unavailable INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
INSERT INTO titles
|
||||
SELECT id, path, signature, unavailable
|
||||
FROM tmp;
|
||||
|
||||
DROP TABLE tmp;
|
||||
|
||||
-- recreate the indices
|
||||
CREATE UNIQUE INDEX titles_id_idx on titles (id);
|
||||
CREATE UNIQUE INDEX titles_path_idx on titles (path);
|
||||
|
||||
-- recreate the foreign key constraint on tags
|
||||
ALTER TABLE tags RENAME TO tmp;
|
||||
|
||||
CREATE TABLE tags (
|
||||
id TEXT NOT NULL,
|
||||
tag TEXT NOT NULL,
|
||||
UNIQUE (id, tag),
|
||||
FOREIGN KEY (id) REFERENCES titles (id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO tags
|
||||
SELECT * FROM tmp;
|
||||
|
||||
DROP TABLE tmp;
|
||||
|
||||
CREATE INDEX tags_id_idx ON tags (id);
|
||||
CREATE INDEX tags_tag_idx ON tags (tag);
|
||||
SQL
|
||||
end
|
||||
end
|
@ -60,6 +60,11 @@ function showModal(encodedPath, pages, percentage, encodedeTitle, encodedEntryTi
|
||||
UIkit.modal($('#modal')).show();
|
||||
}
|
||||
|
||||
UIkit.util.on(document, 'hidden', '#modal', () => {
|
||||
$('#read-btn').off('click');
|
||||
$('#unread-btn').off('click');
|
||||
});
|
||||
|
||||
const updateProgress = (tid, eid, page) => {
|
||||
let url = `${base_url}api/progress/${tid}/${page}`
|
||||
const query = $.param({
|
||||
@ -90,8 +95,6 @@ const renameSubmit = (name, eid) => {
|
||||
const upload = $('.upload-field');
|
||||
const titleId = upload.attr('data-title-id');
|
||||
|
||||
console.log(name);
|
||||
|
||||
if (name.length === 0) {
|
||||
alert('danger', 'The display name should not be empty');
|
||||
return;
|
||||
@ -122,15 +125,47 @@ const renameSubmit = (name, eid) => {
|
||||
});
|
||||
};
|
||||
|
||||
const renameSortNameSubmit = (name, eid) => {
|
||||
const upload = $('.upload-field');
|
||||
const titleId = upload.attr('data-title-id');
|
||||
|
||||
const params = {};
|
||||
if (eid) params.eid = eid;
|
||||
if (name) params.name = name;
|
||||
const query = $.param(params);
|
||||
let url = `${base_url}api/admin/sort_title/${titleId}?${query}`;
|
||||
|
||||
$.ajax({
|
||||
type: 'PUT',
|
||||
url,
|
||||
contentType: 'application/json',
|
||||
dataType: 'json'
|
||||
})
|
||||
.done(data => {
|
||||
if (data.error) {
|
||||
alert('danger', `Failed to update sort title. Error: ${data.error}`);
|
||||
return;
|
||||
}
|
||||
location.reload();
|
||||
})
|
||||
.fail((jqXHR, status) => {
|
||||
alert('danger', `Failed to update sort title. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||
});
|
||||
};
|
||||
|
||||
const edit = (eid) => {
|
||||
const cover = $('#edit-modal #cover');
|
||||
let url = cover.attr('data-title-cover');
|
||||
let displayName = $('h2.uk-title > span').text();
|
||||
let fileTitle = $('h2.uk-title').attr('data-file-title');
|
||||
let sortTitle = $('h2.uk-title').attr('data-sort-title');
|
||||
|
||||
if (eid) {
|
||||
const item = $(`#${eid}`);
|
||||
url = item.find('img').attr('data-src');
|
||||
displayName = item.find('.uk-card-title').attr('data-title');
|
||||
fileTitle = item.find('.uk-card-title').attr('data-file-title');
|
||||
sortTitle = item.find('.uk-card-title').attr('data-sort-title');
|
||||
$('#title-progress-control').attr('hidden', '');
|
||||
} else {
|
||||
$('#title-progress-control').removeAttr('hidden');
|
||||
@ -140,14 +175,26 @@ const edit = (eid) => {
|
||||
|
||||
const displayNameField = $('#display-name-field');
|
||||
displayNameField.attr('value', displayName);
|
||||
console.log(displayNameField);
|
||||
displayNameField.attr('placeholder', fileTitle);
|
||||
displayNameField.keyup(event => {
|
||||
if (event.keyCode === 13) {
|
||||
renameSubmit(displayNameField.val(), eid);
|
||||
renameSubmit(displayNameField.val() || fileTitle, eid);
|
||||
}
|
||||
});
|
||||
displayNameField.siblings('a.uk-form-icon').click(() => {
|
||||
renameSubmit(displayNameField.val(), eid);
|
||||
renameSubmit(displayNameField.val() || fileTitle, eid);
|
||||
});
|
||||
|
||||
const sortTitleField = $('#sort-title-field');
|
||||
sortTitleField.val(sortTitle);
|
||||
sortTitleField.attr('placeholder', fileTitle);
|
||||
sortTitleField.keyup(event => {
|
||||
if (event.keyCode === 13) {
|
||||
renameSortNameSubmit(sortTitleField.val(), eid);
|
||||
}
|
||||
});
|
||||
sortTitleField.siblings('a.uk-form-icon').click(() => {
|
||||
renameSortNameSubmit(sortTitleField.val(), eid);
|
||||
});
|
||||
|
||||
setupUpload(eid);
|
||||
@ -155,6 +202,16 @@ const edit = (eid) => {
|
||||
UIkit.modal($('#edit-modal')).show();
|
||||
};
|
||||
|
||||
UIkit.util.on(document, 'hidden', '#edit-modal', () => {
|
||||
const displayNameField = $('#display-name-field');
|
||||
displayNameField.off('keyup');
|
||||
displayNameField.off('click');
|
||||
|
||||
const sortTitleField = $('#sort-title-field');
|
||||
sortTitleField.off('keyup');
|
||||
sortTitleField.off('click');
|
||||
});
|
||||
|
||||
const setupUpload = (eid) => {
|
||||
const upload = $('.upload-field');
|
||||
const bar = $('#upload-progress').get(0);
|
||||
@ -166,7 +223,6 @@ const setupUpload = (eid) => {
|
||||
queryObj['eid'] = eid;
|
||||
const query = $.param(queryObj);
|
||||
const url = `${base_url}api/admin/upload/cover?${query}`;
|
||||
console.log(url);
|
||||
UIkit.upload('.upload-field', {
|
||||
url: url,
|
||||
name: 'file',
|
||||
|
@ -1,6 +1,7 @@
|
||||
require "digest"
|
||||
|
||||
require "./entry"
|
||||
require "./title"
|
||||
require "./types"
|
||||
|
||||
# Base class for an entry in the LRU cache.
|
||||
@ -81,6 +82,31 @@ class SortedEntriesCacheEntry < CacheEntry(Array(String), Array(Entry))
|
||||
end
|
||||
end
|
||||
|
||||
class SortedTitlesCacheEntry < CacheEntry(Array(String), Array(Title))
|
||||
def self.to_save_t(value : Array(Title))
|
||||
value.map &.id
|
||||
end
|
||||
|
||||
def self.to_return_t(value : Array(String))
|
||||
value.map { |title_id| Library.default.title_hash[title_id].not_nil! }
|
||||
end
|
||||
|
||||
def instance_size
|
||||
instance_sizeof(SortedTitlesCacheEntry) + # sizeof itself
|
||||
instance_sizeof(String) + @key.bytesize + # allocated memory for @key
|
||||
@value.size * (instance_sizeof(String) + sizeof(String)) +
|
||||
@value.sum(&.bytesize) # elements in Array(String)
|
||||
end
|
||||
|
||||
def self.gen_key(username : String, titles : Array(Title), opt : SortOptions?)
|
||||
titles_sig = Digest::SHA1.hexdigest (titles.map &.id).to_s
|
||||
user_context = opt && opt.method == SortMethod::Progress ? username : ""
|
||||
sig = Digest::SHA1.hexdigest (titles_sig + user_context +
|
||||
(opt ? opt.to_tuple.to_s : "nil"))
|
||||
"#{sig}:sorted_titles"
|
||||
end
|
||||
end
|
||||
|
||||
class String
|
||||
def instance_size
|
||||
instance_sizeof(String) + bytesize
|
||||
@ -101,14 +127,18 @@ struct Tuple(*T)
|
||||
end
|
||||
end
|
||||
|
||||
alias CacheableType = Array(Entry) | String | Tuple(String, Int32)
|
||||
alias CacheableType = Array(Entry) | Array(Title) | String |
|
||||
Tuple(String, Int32)
|
||||
alias CacheEntryType = SortedEntriesCacheEntry |
|
||||
SortedTitlesCacheEntry |
|
||||
CacheEntry(String, String) |
|
||||
CacheEntry(Tuple(String, Int32), Tuple(String, Int32))
|
||||
|
||||
def generate_cache_entry(key : String, value : CacheableType)
|
||||
if value.is_a? Array(Entry)
|
||||
SortedEntriesCacheEntry.new key, value
|
||||
elsif value.is_a? Array(Title)
|
||||
SortedTitlesCacheEntry.new key, value
|
||||
else
|
||||
CacheEntry(typeof(value), typeof(value)).new key, value
|
||||
end
|
||||
|
@ -8,6 +8,9 @@ class Entry
|
||||
size : String, pages : Int32, id : String, encoded_path : String,
|
||||
encoded_title : String, mtime : Time, err_msg : String?
|
||||
|
||||
@[YAML::Field(ignore: true)]
|
||||
@sort_title : String?
|
||||
|
||||
def initialize(@zip_path, @book)
|
||||
storage = Storage.default
|
||||
@encoded_path = URI.encode @zip_path
|
||||
@ -56,6 +59,7 @@ class Entry
|
||||
json.field {{str}}, @{{str.id}}
|
||||
{% end %}
|
||||
json.field "title_id", @book.id
|
||||
json.field "sort_title", sort_title
|
||||
json.field "pages" { json.number @pages }
|
||||
unless slim
|
||||
json.field "display_name", @book.display_name @title
|
||||
@ -66,6 +70,35 @@ class Entry
|
||||
end
|
||||
end
|
||||
|
||||
def sort_title
|
||||
sort_title_cached = @sort_title
|
||||
return sort_title_cached if sort_title_cached
|
||||
sort_title = @book.entry_sort_title_db id
|
||||
if sort_title
|
||||
@sort_title = sort_title
|
||||
return sort_title
|
||||
end
|
||||
@sort_title = @title
|
||||
@title
|
||||
end
|
||||
|
||||
def set_sort_title(sort_title : String | Nil, username : String)
|
||||
Storage.default.set_entry_sort_title id, sort_title
|
||||
if sort_title == "" || sort_title.nil?
|
||||
@sort_title = nil
|
||||
else
|
||||
@sort_title = sort_title
|
||||
end
|
||||
|
||||
@book.entry_sort_title_cache = nil
|
||||
@book.remove_sorted_entries_cache [SortMethod::Auto, SortMethod::Title],
|
||||
username
|
||||
end
|
||||
|
||||
def sort_title_db
|
||||
@book.entry_sort_title_db @id
|
||||
end
|
||||
|
||||
def display_name
|
||||
@book.display_name @title
|
||||
end
|
||||
@ -177,11 +210,7 @@ class Entry
|
||||
@book.parents.each do |parent|
|
||||
LRUCache.invalidate "#{parent.id}:#{username}:progress_sum"
|
||||
end
|
||||
[false, true].each do |ascend|
|
||||
sorted_entries_cache_key = SortedEntriesCacheEntry.gen_key @book.id,
|
||||
username, @book.entries, SortOptions.new(SortMethod::Progress, ascend)
|
||||
LRUCache.invalidate sorted_entries_cache_key
|
||||
end
|
||||
@book.remove_sorted_caches [SortMethod::Progress], username
|
||||
|
||||
TitleInfo.new @book.dir do |info|
|
||||
if info.progress[username]?.nil?
|
||||
|
@ -161,7 +161,7 @@ class Library
|
||||
.select { |path| File.directory? path }
|
||||
.map { |path| Title.new path, "", cache }
|
||||
.select { |title| !(title.entries.empty? && title.titles.empty?) }
|
||||
.sort! { |a, b| a.title <=> b.title }
|
||||
.sort! { |a, b| a.sort_title <=> b.sort_title }
|
||||
.each do |title|
|
||||
@title_hash[title.id] = title
|
||||
@title_ids << title.id
|
||||
|
@ -8,8 +8,13 @@ class Title
|
||||
entries : Array(Entry), title : String, id : String,
|
||||
encoded_title : String, mtime : Time, signature : UInt64,
|
||||
entry_cover_url_cache : Hash(String, String)?
|
||||
setter entry_cover_url_cache : Hash(String, String)?
|
||||
setter entry_cover_url_cache : Hash(String, String)?,
|
||||
entry_sort_title_cache : Hash(String, String | Nil)?
|
||||
|
||||
@[YAML::Field(ignore: true)]
|
||||
@sort_title : String?
|
||||
@[YAML::Field(ignore: true)]
|
||||
@entry_sort_title_cache : Hash(String, String | Nil)?
|
||||
@[YAML::Field(ignore: true)]
|
||||
@entry_display_name_cache : Hash(String, String)?
|
||||
@[YAML::Field(ignore: true)]
|
||||
@ -66,7 +71,7 @@ class Title
|
||||
end
|
||||
sorter = ChapterSorter.new @entries.map &.title
|
||||
@entries.sort! do |a, b|
|
||||
sorter.compare a.title, b.title
|
||||
sorter.compare a.sort_title, b.sort_title
|
||||
end
|
||||
end
|
||||
|
||||
@ -180,9 +185,9 @@ class Title
|
||||
end
|
||||
end
|
||||
if is_entries_added || previous_entries_size != @entries.size
|
||||
sorter = ChapterSorter.new @entries.map &.title
|
||||
sorter = ChapterSorter.new @entries.map &.sort_title
|
||||
@entries.sort! do |a, b|
|
||||
sorter.compare a.title, b.title
|
||||
sorter.compare a.sort_title, b.sort_title
|
||||
end
|
||||
end
|
||||
|
||||
@ -204,6 +209,7 @@ class Title
|
||||
json.field {{str}}, @{{str.id}}
|
||||
{% end %}
|
||||
json.field "signature" { json.number @signature }
|
||||
json.field "sort_title", sort_title
|
||||
unless slim
|
||||
json.field "display_name", display_name
|
||||
json.field "cover_url", cover_url
|
||||
@ -250,6 +256,15 @@ class Title
|
||||
@title_ids.map { |tid| Library.default.get_title! tid }
|
||||
end
|
||||
|
||||
def sorted_titles(username, opt : SortOptions? = nil)
|
||||
if opt.nil?
|
||||
opt = SortOptions.from_info_json @dir, username
|
||||
end
|
||||
|
||||
# Helper function from src/util/util.cr
|
||||
sort_titles titles, opt.not_nil!, username
|
||||
end
|
||||
|
||||
# Get all entries, including entries in nested titles
|
||||
def deep_entries
|
||||
return @entries if title_ids.empty?
|
||||
@ -286,6 +301,48 @@ class Title
|
||||
ary.join " and "
|
||||
end
|
||||
|
||||
def sort_title
|
||||
sort_title_cached = @sort_title
|
||||
return sort_title_cached if sort_title_cached
|
||||
sort_title = Storage.default.get_title_sort_title id
|
||||
if sort_title
|
||||
@sort_title = sort_title
|
||||
return sort_title
|
||||
end
|
||||
@sort_title = @title
|
||||
@title
|
||||
end
|
||||
|
||||
def set_sort_title(sort_title : String | Nil, username : String)
|
||||
Storage.default.set_title_sort_title id, sort_title
|
||||
if sort_title == "" || sort_title.nil?
|
||||
@sort_title = nil
|
||||
else
|
||||
@sort_title = sort_title
|
||||
end
|
||||
|
||||
if parents.size > 0
|
||||
target = parents[-1].titles
|
||||
else
|
||||
target = Library.default.titles
|
||||
end
|
||||
remove_sorted_titles_cache target,
|
||||
[SortMethod::Auto, SortMethod::Title], username
|
||||
end
|
||||
|
||||
def sort_title_db
|
||||
Storage.default.get_title_sort_title id
|
||||
end
|
||||
|
||||
def entry_sort_title_db(entry_id)
|
||||
unless @entry_sort_title_cache
|
||||
@entry_sort_title_cache =
|
||||
Storage.default.get_entries_sort_title @entries.map &.id
|
||||
end
|
||||
|
||||
@entry_sort_title_cache.not_nil![entry_id]?
|
||||
end
|
||||
|
||||
def tags
|
||||
Storage.default.get_title_tags @id
|
||||
end
|
||||
@ -472,28 +529,30 @@ class Title
|
||||
|
||||
case opt.not_nil!.method
|
||||
when .title?
|
||||
ary = @entries.sort { |a, b| compare_numerically a.title, b.title }
|
||||
ary = @entries.sort do |a, b|
|
||||
compare_numerically a.sort_title, b.sort_title
|
||||
end
|
||||
when .time_modified?
|
||||
ary = @entries.sort { |a, b| (a.mtime <=> b.mtime).or \
|
||||
compare_numerically a.title, b.title }
|
||||
compare_numerically a.sort_title, b.sort_title }
|
||||
when .time_added?
|
||||
ary = @entries.sort { |a, b| (a.date_added <=> b.date_added).or \
|
||||
compare_numerically a.title, b.title }
|
||||
compare_numerically a.sort_title, b.sort_title }
|
||||
when .progress?
|
||||
percentage_ary = load_percentage_for_all_entries username, opt, true
|
||||
ary = @entries.zip(percentage_ary)
|
||||
.sort { |a_tp, b_tp| (a_tp[1] <=> b_tp[1]).or \
|
||||
compare_numerically a_tp[0].title, b_tp[0].title }
|
||||
compare_numerically a_tp[0].sort_title, b_tp[0].sort_title }
|
||||
.map &.[0]
|
||||
else
|
||||
unless opt.method.auto?
|
||||
Logger.warn "Unknown sorting method #{opt.not_nil!.method}. Using " \
|
||||
"Auto instead"
|
||||
end
|
||||
sorter = ChapterSorter.new @entries.map &.title
|
||||
sorter = ChapterSorter.new @entries.map &.sort_title
|
||||
ary = @entries.sort do |a, b|
|
||||
sorter.compare(a.title, b.title).or \
|
||||
compare_numerically a.title, b.title
|
||||
sorter.compare(a.sort_title, b.sort_title).or \
|
||||
compare_numerically a.sort_title, b.sort_title
|
||||
end
|
||||
end
|
||||
|
||||
@ -560,17 +619,32 @@ class Title
|
||||
zip + titles.flat_map &.deep_entries_with_date_added
|
||||
end
|
||||
|
||||
def remove_sorted_entries_cache(sort_methods : Array(SortMethod),
|
||||
username : String)
|
||||
[false, true].each do |ascend|
|
||||
sort_methods.each do |sort_method|
|
||||
sorted_entries_cache_key =
|
||||
SortedEntriesCacheEntry.gen_key @id, username, @entries,
|
||||
SortOptions.new(sort_method, ascend)
|
||||
LRUCache.invalidate sorted_entries_cache_key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def remove_sorted_caches(sort_methods : Array(SortMethod), username : String)
|
||||
remove_sorted_entries_cache sort_methods, username
|
||||
parents.each do |parent|
|
||||
remove_sorted_titles_cache parent.titles, sort_methods, username
|
||||
end
|
||||
remove_sorted_titles_cache Library.default.titles, sort_methods, username
|
||||
end
|
||||
|
||||
def bulk_progress(action, ids : Array(String), username)
|
||||
LRUCache.invalidate "#{@id}:#{username}:progress_sum"
|
||||
parents.each do |parent|
|
||||
LRUCache.invalidate "#{parent.id}:#{username}:progress_sum"
|
||||
end
|
||||
[false, true].each do |ascend|
|
||||
sorted_entries_cache_key =
|
||||
SortedEntriesCacheEntry.gen_key @id, username, @entries,
|
||||
SortOptions.new(SortMethod::Progress, ascend)
|
||||
LRUCache.invalidate sorted_entries_cache_key
|
||||
end
|
||||
remove_sorted_caches [SortMethod::Progress], username
|
||||
|
||||
selected_entries = ids
|
||||
.map { |id|
|
||||
|
@ -371,6 +371,38 @@ struct APIRouter
|
||||
end
|
||||
end
|
||||
|
||||
Koa.describe "Sets the sort title of a title or an entry", <<-MD
|
||||
When `eid` is provided, apply the sort title to the entry. Otherwise, apply the sort title to the title identified by `tid`.
|
||||
MD
|
||||
Koa.tags ["admin", "library"]
|
||||
Koa.path "tid", desc: "Title ID"
|
||||
Koa.query "eid", desc: "Entry ID", required: false
|
||||
Koa.query "name", desc: "The new sort title"
|
||||
Koa.response 200, schema: "result"
|
||||
put "/api/admin/sort_title/:tid" do |env|
|
||||
username = get_username env
|
||||
begin
|
||||
title = (Library.default.get_title env.params.url["tid"])
|
||||
.not_nil!
|
||||
name = env.params.query["name"]?
|
||||
entry = env.params.query["eid"]?
|
||||
if entry.nil?
|
||||
title.set_sort_title name, username
|
||||
else
|
||||
eobj = title.get_entry entry
|
||||
eobj.set_sort_title name, username unless eobj.nil?
|
||||
end
|
||||
rescue e
|
||||
Logger.error e
|
||||
send_json env, {
|
||||
"success" => false,
|
||||
"error" => e.message,
|
||||
}.to_json
|
||||
else
|
||||
send_json env, {"success" => true}.to_json
|
||||
end
|
||||
end
|
||||
|
||||
ws "/api/admin/mangadex/queue" do |socket, env|
|
||||
interval_raw = env.params.query["interval"]?
|
||||
interval = (interval_raw.to_i? if interval_raw) || 5
|
||||
|
@ -61,9 +61,15 @@ struct MainRouter
|
||||
sort_opt = SortOptions.from_info_json title.dir, username
|
||||
get_and_save_sort_opt title.dir
|
||||
|
||||
sorted_titles = title.sorted_titles username, sort_opt
|
||||
entries = title.sorted_entries username, sort_opt
|
||||
percentage = title.load_percentage_for_all_entries username, sort_opt
|
||||
title_percentage = title.titles.map &.load_percentage username
|
||||
title_percentage_map = {} of String => Float64
|
||||
title_percentage.each_with_index do |tp, i|
|
||||
t = title.titles[i]
|
||||
title_percentage_map[t.id] = tp
|
||||
end
|
||||
|
||||
layout "title"
|
||||
rescue e
|
||||
|
@ -342,6 +342,67 @@ class Storage
|
||||
end
|
||||
end
|
||||
|
||||
def get_title_sort_title(title_id : String)
|
||||
sort_title = nil
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
sort_title =
|
||||
db.query_one? "Select sort_title from titles where id = (?)",
|
||||
title_id, as: String | Nil
|
||||
end
|
||||
end
|
||||
sort_title
|
||||
end
|
||||
|
||||
def set_title_sort_title(title_id : String, sort_title : String | Nil)
|
||||
sort_title = nil if sort_title == ""
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
db.exec "update titles set sort_title = (?) where id = (?)",
|
||||
sort_title, title_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_entry_sort_title(entry_id : String)
|
||||
sort_title = nil
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
sort_title =
|
||||
db.query_one? "Select sort_title from ids where id = (?)",
|
||||
entry_id, as: String | Nil
|
||||
end
|
||||
end
|
||||
sort_title
|
||||
end
|
||||
|
||||
def get_entries_sort_title(ids : Array(String))
|
||||
results = Hash(String, String | Nil).new
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
db.query "select id, sort_title from ids where id in " \
|
||||
"(#{ids.join "," { |id| "'#{id}'" }})" do |rs|
|
||||
rs.each do
|
||||
id = rs.read String
|
||||
sort_title = rs.read String | Nil
|
||||
results[id] = sort_title
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
results
|
||||
end
|
||||
|
||||
def set_entry_sort_title(entry_id : String, sort_title : String | Nil)
|
||||
sort_title = nil if sort_title == ""
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
db.exec "update ids set sort_title = (?) where id = (?)",
|
||||
sort_title, entry_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def save_thumbnail(id : String, img : Image)
|
||||
MainFiber.run do
|
||||
get_db do |db|
|
||||
|
@ -87,30 +87,49 @@ def env_is_true?(key : String) : Bool
|
||||
end
|
||||
|
||||
def sort_titles(titles : Array(Title), opt : SortOptions, username : String)
|
||||
ary = titles
|
||||
cache_key = SortedTitlesCacheEntry.gen_key username, titles, opt
|
||||
cached_titles = LRUCache.get cache_key
|
||||
return cached_titles if cached_titles.is_a? Array(Title)
|
||||
|
||||
case opt.method
|
||||
when .time_modified?
|
||||
ary.sort! { |a, b| (a.mtime <=> b.mtime).or \
|
||||
compare_numerically a.title, b.title }
|
||||
ary = titles.sort { |a, b| (a.mtime <=> b.mtime).or \
|
||||
compare_numerically a.sort_title, b.sort_title }
|
||||
when .progress?
|
||||
ary.sort! do |a, b|
|
||||
ary = titles.sort do |a, b|
|
||||
(a.load_percentage(username) <=> b.load_percentage(username)).or \
|
||||
compare_numerically a.title, b.title
|
||||
compare_numerically a.sort_title, b.sort_title
|
||||
end
|
||||
when .title?
|
||||
ary = titles.sort do |a, b|
|
||||
compare_numerically a.sort_title, b.sort_title
|
||||
end
|
||||
else
|
||||
unless opt.method.auto?
|
||||
Logger.warn "Unknown sorting method #{opt.not_nil!.method}. Using " \
|
||||
"Auto instead"
|
||||
end
|
||||
ary.sort! { |a, b| compare_numerically a.title, b.title }
|
||||
ary = titles.sort { |a, b| compare_numerically a.sort_title, b.sort_title }
|
||||
end
|
||||
|
||||
ary.reverse! unless opt.not_nil!.ascend
|
||||
|
||||
LRUCache.set generate_cache_entry cache_key, ary
|
||||
ary
|
||||
end
|
||||
|
||||
def remove_sorted_titles_cache(titles : Array(Title),
|
||||
sort_methods : Array(SortMethod),
|
||||
username : String)
|
||||
[false, true].each do |ascend|
|
||||
sort_methods.each do |sort_method|
|
||||
sorted_titles_cache_key = SortedTitlesCacheEntry.gen_key username,
|
||||
titles, SortOptions.new(sort_method, ascend)
|
||||
LRUCache.invalidate sorted_titles_cache_key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class String
|
||||
# Returns the similarity (in [0, 1]) of two paths.
|
||||
# For the two paths, separate them into arrays of components, count the
|
||||
|
@ -61,7 +61,9 @@
|
||||
<% if page == "home" && item.is_a? Entry %>
|
||||
<%= "uk-margin-remove-bottom" %>
|
||||
<% end %>
|
||||
" data-title="<%= HTML.escape(item.display_name) %>"><%= HTML.escape(item.display_name) %>
|
||||
" data-title="<%= HTML.escape(item.display_name) %>"
|
||||
data-file-title="<%= HTML.escape(item.title || "") %>"
|
||||
data-sort-title="<%= HTML.escape(item.sort_title_db || "") %>"><%= HTML.escape(item.display_name) %>
|
||||
</h3>
|
||||
<% if page == "home" && item.is_a? Entry %>
|
||||
<a class="uk-card-title break-word uk-margin-remove-top uk-text-meta uk-display-inline-block no-modal" data-title="<%= HTML.escape(item.book.display_name) %>" href="<%= base_url %>book/<%= item.book.id %>"><%= HTML.escape(item.book.display_name) %></a>
|
||||
|
@ -10,6 +10,7 @@
|
||||
<div class="uk-margin-bottom uk-width-1-4@s">
|
||||
<% hash = {
|
||||
"auto" => "Auto",
|
||||
"title" => "Name",
|
||||
"time_modified" => "Date Modified",
|
||||
"progress" => "Progress"
|
||||
} %>
|
||||
|
@ -18,7 +18,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class=uk-title><span><%= title.display_name %></span>
|
||||
<h2 class=uk-title data-file-title="<%= HTML.escape(title.title) %>" data-sort-title="<%= HTML.escape(title.sort_title_db || "") %>">
|
||||
<span><%= title.display_name %></span>
|
||||
|
||||
<% if is_admin %>
|
||||
<a onclick="edit()" class="uk-icon-button" uk-icon="icon:pencil"></a>
|
||||
@ -59,8 +60,8 @@
|
||||
</div>
|
||||
|
||||
<div class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
|
||||
<% title.titles.each_with_index do |item, i| %>
|
||||
<% progress = title_percentage[i] %>
|
||||
<% sorted_titles.each do |item| %>
|
||||
<% progress = title_percentage_map[item.id] %>
|
||||
<%= render_component "card" %>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -89,6 +90,13 @@
|
||||
<input class="uk-input" type="text" name="display-name" id="display-name-field">
|
||||
</div>
|
||||
</div>
|
||||
<div class="uk-margin">
|
||||
<label class="uk-form-label" for="sort-title">Sort Title</label>
|
||||
<div class="uk-inline">
|
||||
<a class="uk-form-icon uk-form-icon-flip" uk-icon="icon:check"></a>
|
||||
<input class="uk-input" type="text" name="sort-title" id="sort-title-field">
|
||||
</div>
|
||||
</div>
|
||||
<div class="uk-margin">
|
||||
<label class="uk-form-label">Cover Image</label>
|
||||
<div class="uk-grid">
|
||||
|
Loading…
x
Reference in New Issue
Block a user