mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-03 03:15:31 -04:00
Merge branch 'cover' into dev
This commit is contained in:
commit
b449d906ec
@ -56,6 +56,18 @@
|
||||
td > .uk-dropdown {
|
||||
white-space: pre-line;
|
||||
}
|
||||
.title-rename > .uk-inline {
|
||||
#edit-modal .uk-grid > div {
|
||||
height: 300px;
|
||||
}
|
||||
#edit-modal #cover {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
#edit-modal #cover-upload {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#edit-modal .uk-modal-body .uk-inline {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -32,9 +32,12 @@ function showModal(encodedPath, pages, percentage, encodedeTitle, encodedEntryTi
|
||||
updateProgress(titleID, entryID, 0);
|
||||
});
|
||||
|
||||
$('.uk-modal-title.break-word > a').attr('onclick', `edit("${entryID}")`);
|
||||
|
||||
UIkit.modal($('#modal')).show();
|
||||
styleModal();
|
||||
}
|
||||
|
||||
function updateProgress(titleID, entryID, page) {
|
||||
$.post('/api/progress/' + titleID + '/' + entryID + '/' + page, function(data) {
|
||||
if (data.success) {
|
||||
@ -47,33 +50,19 @@ function updateProgress(titleID, entryID, page) {
|
||||
});
|
||||
}
|
||||
|
||||
const rename = ele => {
|
||||
const h2 = $(ele).parent();
|
||||
|
||||
$(h2).attr('hidden', true);
|
||||
$(h2).next().removeAttr('hidden');
|
||||
};
|
||||
|
||||
const renameSubmit = ele => {
|
||||
const group = $(ele).closest('.title-rename');
|
||||
const id = $(group).attr('data-id');
|
||||
const eid = $(group).attr('data-entry-id');
|
||||
const name = $(ele).next().val();
|
||||
const renameSubmit = (name, eid) => {
|
||||
const upload = $('.upload-field');
|
||||
const titleId = upload.attr('data-title-id');
|
||||
|
||||
console.log(name);
|
||||
|
||||
$(group).attr('hidden', true);
|
||||
$(group).prev().removeAttr('hidden');
|
||||
|
||||
if (name.length === 0) {
|
||||
alert('danger', 'The display name should not be empty');
|
||||
return;
|
||||
}
|
||||
|
||||
$(group).prev().find('span').text(name);
|
||||
|
||||
const query = $.param({ entry: eid });
|
||||
let url = `/api/admin/display_name/${id}/${name}`;
|
||||
let url = `/api/admin/display_name/${titleId}/${name}`;
|
||||
if (eid)
|
||||
url += `?${query}`;
|
||||
|
||||
@ -84,7 +73,6 @@ const renameSubmit = ele => {
|
||||
dataType: 'json'
|
||||
})
|
||||
.done(data => {
|
||||
console.log(data);
|
||||
if (data.error) {
|
||||
alert('danger', `Failed to update display name. Error: ${data.error}`);
|
||||
return;
|
||||
@ -96,10 +84,68 @@ const renameSubmit = ele => {
|
||||
});
|
||||
};
|
||||
|
||||
$(() => {
|
||||
$('.uk-input.title-rename-field').keyup(event => {
|
||||
const edit = (eid) => {
|
||||
const cover = $('#edit-modal #cover');
|
||||
let url = cover.attr('data-title-cover');
|
||||
let displayName = $('h2.uk-title > span').text();
|
||||
|
||||
if (eid) {
|
||||
const item = $(`#${eid}`);
|
||||
url = item.find('img').attr('data-src');
|
||||
displayName = item.find('.uk-card-title').attr('data-title');
|
||||
}
|
||||
|
||||
cover.attr('data-src', url);
|
||||
|
||||
const displayNameField = $('#display-name-field');
|
||||
displayNameField.attr('value', displayName);
|
||||
displayNameField.keyup(event => {
|
||||
if (event.keyCode === 13) {
|
||||
renameSubmit($(event.currentTarget).prev());
|
||||
renameSubmit(displayNameField.val(), eid);
|
||||
}
|
||||
});
|
||||
});
|
||||
displayNameField.siblings('a.uk-form-icon').click(() => {
|
||||
renameSubmit(displayNameField.val(), eid);
|
||||
});
|
||||
|
||||
setupUpload(eid);
|
||||
|
||||
UIkit.modal($('#edit-modal')).show();
|
||||
styleModal();
|
||||
};
|
||||
|
||||
const setupUpload = (eid) => {
|
||||
const upload = $('.upload-field');
|
||||
const bar = $('#upload-progress').get(0);
|
||||
const titleId = upload.attr('data-title-id');
|
||||
const queryObj = {title: titleId};
|
||||
if (eid)
|
||||
queryObj['entry'] = eid;
|
||||
const query = $.param(queryObj);
|
||||
const url = `/api/admin/upload/cover?${query}`;
|
||||
console.log(url);
|
||||
UIkit.upload('.upload-field', {
|
||||
url: url,
|
||||
name: 'file',
|
||||
error: (e) => {
|
||||
alert('danger', `Failed to upload cover image: ${e.toString()}`);
|
||||
},
|
||||
loadStart: (e) => {
|
||||
$(bar).removeAttr('hidden');
|
||||
bar.max = e.total;
|
||||
bar.value = e.loaded;
|
||||
},
|
||||
progress: (e) => {
|
||||
bar.max = e.total;
|
||||
bar.value = e.loaded;
|
||||
},
|
||||
loadEnd: (e) => {
|
||||
bar.max = e.total;
|
||||
bar.value = e.loaded;
|
||||
},
|
||||
completeAll: () => {
|
||||
$(bar).attr('hidden', '');
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -4,11 +4,14 @@ class Config
|
||||
include YAML::Serializable
|
||||
|
||||
property port : Int32 = 9000
|
||||
property library_path : String = File.expand_path "~/mango/library", home: true
|
||||
property library_path : String = File.expand_path "~/mango/library",
|
||||
home: true
|
||||
property db_path : String = File.expand_path "~/mango/mango.db", home: true
|
||||
@[YAML::Field(key: "scan_interval_minutes")]
|
||||
property scan_interval : Int32 = 5
|
||||
property log_level : String = "info"
|
||||
property upload_path : String = File.expand_path "~/mango/uploads",
|
||||
home: true
|
||||
property mangadex = Hash(String, String | Int32).new
|
||||
|
||||
@[YAML::Field(ignore: true)]
|
||||
|
@ -1,6 +1,6 @@
|
||||
require "kemal"
|
||||
require "./storage"
|
||||
require "./util"
|
||||
require "../storage"
|
||||
require "../util"
|
||||
|
||||
class AuthHandler < Kemal::Handler
|
||||
def initialize(@storage : Storage)
|
@ -1,5 +1,5 @@
|
||||
require "kemal"
|
||||
require "./logger"
|
||||
require "../logger"
|
||||
|
||||
class LogHandler < Kemal::BaseLogHandler
|
||||
def initialize(@logger : Logger)
|
@ -1,16 +1,16 @@
|
||||
require "baked_file_system"
|
||||
require "kemal"
|
||||
require "./util"
|
||||
require "../util"
|
||||
|
||||
class FS
|
||||
extend BakedFileSystem
|
||||
{% if flag?(:release) %}
|
||||
{% if read_file? "#{__DIR__}/../dist/favicon.ico" %}
|
||||
{% puts "baking ../dist" %}
|
||||
bake_folder "../dist"
|
||||
{% if read_file? "#{__DIR__}/../../dist/favicon.ico" %}
|
||||
{% puts "baking ../../dist" %}
|
||||
bake_folder "../../dist"
|
||||
{% else %}
|
||||
{% puts "baking ../public" %}
|
||||
bake_folder "../public"
|
||||
{% puts "baking ../../public" %}
|
||||
bake_folder "../../public"
|
||||
{% end %}
|
||||
{% end %}
|
||||
end
|
24
src/handlers/upload_handler.cr
Normal file
24
src/handlers/upload_handler.cr
Normal file
@ -0,0 +1,24 @@
|
||||
require "kemal"
|
||||
require "../util"
|
||||
|
||||
class UploadHandler < Kemal::Handler
|
||||
def initialize(@upload_dir : String)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
unless request_path_startswith(env, [UPLOAD_URL_PREFIX]) &&
|
||||
env.request.method == "GET"
|
||||
return call_next env
|
||||
end
|
||||
|
||||
ary = env.request.path.split(File::SEPARATOR).select { |part| !part.empty? }
|
||||
ary[0] = @upload_dir
|
||||
path = File.join ary
|
||||
|
||||
if File.exists? path
|
||||
send_file env, path
|
||||
else
|
||||
env.response.status_code = 404
|
||||
end
|
||||
end
|
||||
end
|
140
src/library.cr
140
src/library.cr
@ -16,9 +16,8 @@ end
|
||||
|
||||
class Entry
|
||||
property zip_path : String, book : Title, title : String,
|
||||
size : String, pages : Int32, cover_url : String, id : String,
|
||||
title_id : String, encoded_path : String, encoded_title : String,
|
||||
mtime : Time
|
||||
size : String, pages : Int32, id : String, title_id : String,
|
||||
encoded_path : String, encoded_title : String, mtime : Time
|
||||
|
||||
def initialize(path, @book, @title_id, storage)
|
||||
@zip_path = path
|
||||
@ -33,17 +32,17 @@ class Entry
|
||||
end
|
||||
file.close
|
||||
@id = storage.get_id @zip_path, false
|
||||
@cover_url = "/api/page/#{@title_id}/#{@id}/1"
|
||||
@mtime = File.info(@zip_path).modification_time
|
||||
end
|
||||
|
||||
def to_json(json : JSON::Builder)
|
||||
json.object do
|
||||
{% for str in ["zip_path", "title", "size", "cover_url", "id",
|
||||
"title_id", "encoded_path", "encoded_title"] %}
|
||||
{% for str in ["zip_path", "title", "size", "id", "title_id",
|
||||
"encoded_path", "encoded_title"] %}
|
||||
json.field {{str}}, @{{str.id}}
|
||||
{% end %}
|
||||
json.field "display_name", @book.display_name @title
|
||||
json.field "cover_url", cover_url
|
||||
json.field "pages" { json.number @pages }
|
||||
json.field "mtime" { json.number @mtime.to_unix }
|
||||
end
|
||||
@ -57,6 +56,17 @@ class Entry
|
||||
URI.encode display_name
|
||||
end
|
||||
|
||||
def cover_url
|
||||
url = "/api/page/#{@title_id}/#{@id}/1"
|
||||
TitleInfo.new @book.dir do |info|
|
||||
info_url = info.entry_cover_url[@title]?
|
||||
unless info_url.nil? || info_url.empty?
|
||||
url = info_url
|
||||
end
|
||||
end
|
||||
url
|
||||
end
|
||||
|
||||
def read_page(page_num)
|
||||
Zip::File.open @zip_path do |file|
|
||||
page = file.entries
|
||||
@ -138,6 +148,7 @@ class Title
|
||||
json.field {{str}}, @{{str.id}}
|
||||
{% end %}
|
||||
json.field "display_name", display_name
|
||||
json.field "cover_url", cover_url
|
||||
json.field "mtime" { json.number @mtime.to_unix }
|
||||
json.field "titles" do
|
||||
json.raw self.titles.to_json
|
||||
@ -182,9 +193,12 @@ class Title
|
||||
end
|
||||
|
||||
def display_name
|
||||
info = TitleInfo.new @dir
|
||||
dn = info.display_name
|
||||
dn.empty? ? @title : dn
|
||||
dn = @title
|
||||
TitleInfo.new @dir do |info|
|
||||
info_dn = info.display_name
|
||||
dn = info_dn unless info_dn.empty?
|
||||
end
|
||||
dn
|
||||
end
|
||||
|
||||
def encoded_display_name
|
||||
@ -192,48 +206,80 @@ class Title
|
||||
end
|
||||
|
||||
def display_name(entry_name)
|
||||
info = TitleInfo.new @dir
|
||||
dn = info.entry_display_name[entry_name]?
|
||||
unless dn.nil? || dn.empty?
|
||||
return dn
|
||||
dn = entry_name
|
||||
TitleInfo.new @dir do |info|
|
||||
info_dn = info.entry_display_name[entry_name]?
|
||||
unless info_dn.nil? || info_dn.empty?
|
||||
dn = info_dn
|
||||
end
|
||||
end
|
||||
entry_name
|
||||
dn
|
||||
end
|
||||
|
||||
def set_display_name(dn)
|
||||
info = TitleInfo.new @dir
|
||||
info.display_name = dn
|
||||
info.save
|
||||
TitleInfo.new @dir do |info|
|
||||
info.display_name = dn
|
||||
info.save
|
||||
end
|
||||
end
|
||||
|
||||
def set_display_name(entry_name : String, dn)
|
||||
info = TitleInfo.new @dir
|
||||
info.entry_display_name[entry_name] = dn
|
||||
info.save
|
||||
TitleInfo.new @dir do |info|
|
||||
info.entry_display_name[entry_name] = dn
|
||||
info.save
|
||||
end
|
||||
end
|
||||
|
||||
def cover_url
|
||||
url = "img/icon.png"
|
||||
if @entries.size > 0
|
||||
url = @entries[0].cover_url
|
||||
end
|
||||
TitleInfo.new @dir do |info|
|
||||
info_url = info.cover_url
|
||||
unless info_url.nil? || info_url.empty?
|
||||
url = info_url
|
||||
end
|
||||
end
|
||||
url
|
||||
end
|
||||
|
||||
def set_cover_url(url : String)
|
||||
TitleInfo.new @dir do |info|
|
||||
info.cover_url = url
|
||||
info.save
|
||||
end
|
||||
end
|
||||
|
||||
def set_cover_url(entry_name : String, url : String)
|
||||
TitleInfo.new @dir do |info|
|
||||
info.entry_cover_url[entry_name] = url
|
||||
info.save
|
||||
end
|
||||
end
|
||||
|
||||
# For backward backward compatibility with v0.1.0, we save entry titles
|
||||
# instead of IDs in info.json
|
||||
def save_progress(username, entry, page)
|
||||
info = TitleInfo.new @dir
|
||||
if info.progress[username]?.nil?
|
||||
info.progress[username] = {entry => page}
|
||||
TitleInfo.new @dir do |info|
|
||||
if info.progress[username]?.nil?
|
||||
info.progress[username] = {entry => page}
|
||||
else
|
||||
info.progress[username][entry] = page
|
||||
end
|
||||
info.save
|
||||
return
|
||||
end
|
||||
info.progress[username][entry] = page
|
||||
info.save
|
||||
end
|
||||
|
||||
def load_progress(username, entry)
|
||||
info = TitleInfo.new @dir
|
||||
if info.progress[username]?.nil?
|
||||
return 0
|
||||
progress = 0
|
||||
TitleInfo.new @dir do |info|
|
||||
unless info.progress[username]?.nil? ||
|
||||
info.progress[username][entry]?.nil?
|
||||
progress = info.progress[username][entry]
|
||||
end
|
||||
end
|
||||
if info.progress[username][entry]?.nil?
|
||||
return 0
|
||||
end
|
||||
info.progress[username][entry]
|
||||
progress
|
||||
end
|
||||
|
||||
def load_percetage(username, entry)
|
||||
@ -264,22 +310,32 @@ class TitleInfo
|
||||
include JSON::Serializable
|
||||
|
||||
property comment = "Generated by Mango. DO NOT EDIT!"
|
||||
# { user1: { entry1: 10, entry2: 0 } }
|
||||
property progress = {} of String => Hash(String, Int32)
|
||||
property display_name = ""
|
||||
# { entry1 : "display name" }
|
||||
property entry_display_name = {} of String => String
|
||||
property cover_url = ""
|
||||
property entry_cover_url = {} of String => String
|
||||
|
||||
@[JSON::Field(ignore: true)]
|
||||
property dir : String = ""
|
||||
|
||||
def initialize(@dir)
|
||||
json_path = File.join @dir, "info.json"
|
||||
if File.exists? json_path
|
||||
info = TitleInfo.from_json File.read json_path
|
||||
@progress = info.progress.clone
|
||||
@display_name = info.display_name
|
||||
@entry_display_name = info.entry_display_name.clone
|
||||
@@mutex_hash = {} of String => Mutex
|
||||
|
||||
def self.new(dir, &)
|
||||
if @@mutex_hash[dir]?
|
||||
mutex = @@mutex_hash[dir]
|
||||
else
|
||||
mutex = Mutex.new
|
||||
@@mutex_hash[dir] = mutex
|
||||
end
|
||||
mutex.synchronize do
|
||||
instance = TitleInfo.allocate
|
||||
json_path = File.join dir, "info.json"
|
||||
if File.exists? json_path
|
||||
instance = TitleInfo.from_json File.read json_path
|
||||
end
|
||||
instance.dir = dir
|
||||
yield instance
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
require "./router"
|
||||
require "../mangadex/*"
|
||||
require "../upload"
|
||||
|
||||
class APIRouter < Router
|
||||
def setup
|
||||
@ -197,5 +198,59 @@ class APIRouter < Router
|
||||
}.to_json
|
||||
end
|
||||
end
|
||||
|
||||
post "/api/admin/upload/:target" do |env|
|
||||
begin
|
||||
target = env.params.url["target"]
|
||||
|
||||
HTTP::FormData.parse env.request do |part|
|
||||
next if part.name != "file"
|
||||
|
||||
filename = part.filename
|
||||
if filename.nil?
|
||||
raise "No file uploaded"
|
||||
end
|
||||
|
||||
case target
|
||||
when "cover"
|
||||
title_id = env.params.query["title"]
|
||||
entry_id = env.params.query["entry"]?
|
||||
title = @context.library.get_title(title_id).not_nil!
|
||||
|
||||
unless ["image/jpeg", "image/png"].includes? \
|
||||
MIME.from_filename? filename
|
||||
raise "The uploaded image must be either JPEG or PNG"
|
||||
end
|
||||
|
||||
ext = File.extname filename
|
||||
upload = Upload.new @context.config.upload_path, @context.logger
|
||||
url = upload.path_to_url upload.save "img", ext, part.body
|
||||
|
||||
if url.nil?
|
||||
raise "Failed to generate a public URL for the uploaded file"
|
||||
end
|
||||
|
||||
if entry_id.nil?
|
||||
title.set_cover_url url
|
||||
else
|
||||
entry_name = title.get_entry(entry_id).not_nil!.title
|
||||
title.set_cover_url entry_name, url
|
||||
end
|
||||
else
|
||||
raise "Unkown upload target #{target}"
|
||||
end
|
||||
|
||||
send_json env, {"success" => true}.to_json
|
||||
env.response.close
|
||||
end
|
||||
|
||||
raise "No part with name `file` found"
|
||||
rescue e
|
||||
send_json env, {
|
||||
"success" => false,
|
||||
"error" => e.message,
|
||||
}.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -33,10 +33,15 @@ class MainRouter < Router
|
||||
end
|
||||
|
||||
get "/" do |env|
|
||||
titles = @context.library.titles
|
||||
username = get_username env
|
||||
percentage = titles.map &.load_percetage username
|
||||
layout "index"
|
||||
begin
|
||||
titles = @context.library.titles
|
||||
username = get_username env
|
||||
percentage = titles.map &.load_percetage username
|
||||
layout "index"
|
||||
rescue e
|
||||
@context.error e
|
||||
env.response.status_code = 500
|
||||
end
|
||||
end
|
||||
|
||||
get "/book/:title" do |env|
|
||||
|
@ -1,8 +1,6 @@
|
||||
require "kemal"
|
||||
require "./context"
|
||||
require "./auth_handler"
|
||||
require "./static_handler"
|
||||
require "./log_handler"
|
||||
require "./handlers/*"
|
||||
require "./util"
|
||||
require "./routes/*"
|
||||
|
||||
@ -29,6 +27,7 @@ class Server
|
||||
Kemal.config.logging = false
|
||||
add_handler LogHandler.new @context.logger
|
||||
add_handler AuthHandler.new @context.storage
|
||||
add_handler UploadHandler.new @context.config.upload_path
|
||||
{% if flag?(:release) %}
|
||||
# when building for relase, embed the static files in binary
|
||||
@context.debug "We are in release mode. Using embedded static files."
|
||||
|
@ -2,6 +2,7 @@ require "sqlite3"
|
||||
require "crypto/bcrypt"
|
||||
require "uuid"
|
||||
require "base64"
|
||||
require "./util"
|
||||
|
||||
def hash_password(pw)
|
||||
Crypto::Bcrypt::Password.create(pw).to_s
|
||||
@ -11,10 +12,6 @@ def verify_password(hash, pw)
|
||||
(Crypto::Bcrypt::Password.new hash).verify pw
|
||||
end
|
||||
|
||||
def random_str
|
||||
UUID.random.to_s.gsub "-", ""
|
||||
end
|
||||
|
||||
class Storage
|
||||
def initialize(@path : String, @logger : Logger)
|
||||
dir = File.dirname path
|
||||
|
60
src/upload.cr
Normal file
60
src/upload.cr
Normal file
@ -0,0 +1,60 @@
|
||||
require "./util"
|
||||
|
||||
class Upload
|
||||
def initialize(@dir : String, @logger : Logger)
|
||||
unless Dir.exists? @dir
|
||||
@logger.info "The uploads directory #{@dir} does not exist. " \
|
||||
"Attempting to create it"
|
||||
Dir.mkdir_p @dir
|
||||
end
|
||||
end
|
||||
|
||||
# Writes IO to a file with random filename in the uploads directory and
|
||||
# returns the full path of created file
|
||||
# e.g., save("image", ".png", <io>)
|
||||
# ==> "~/mango/uploads/image/<random string>.png"
|
||||
def save(sub_dir : String, ext : String, io : IO)
|
||||
full_dir = File.join @dir, sub_dir
|
||||
filename = random_str + ext
|
||||
file_path = File.join full_dir, filename
|
||||
|
||||
unless Dir.exists? full_dir
|
||||
@logger.debug "creating directory #{full_dir}"
|
||||
Dir.mkdir_p full_dir
|
||||
end
|
||||
|
||||
File.open file_path, "w" do |f|
|
||||
IO.copy io, f
|
||||
end
|
||||
|
||||
file_path
|
||||
end
|
||||
|
||||
# Converts path to a file in the uploads directory to the URL path for
|
||||
# accessing the file.
|
||||
def path_to_url(path : String)
|
||||
dir_mathed = false
|
||||
ary = [] of String
|
||||
# We fill it with parts until it equals to @upload_dir
|
||||
dir_ary = [] of String
|
||||
|
||||
Path.new(path).each_part do |part|
|
||||
if dir_mathed
|
||||
ary << part
|
||||
else
|
||||
dir_ary << part
|
||||
if File.same? @dir, File.join dir_ary
|
||||
dir_mathed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ary.empty?
|
||||
@logger.warn "File #{path} is not in the upload directory #{@dir}"
|
||||
return
|
||||
end
|
||||
|
||||
ary.unshift UPLOAD_URL_PREFIX
|
||||
File.join(ary).to_s
|
||||
end
|
||||
end
|
16
src/util.cr
16
src/util.cr
@ -1,6 +1,7 @@
|
||||
require "big"
|
||||
|
||||
IMGS_PER_PAGE = 5
|
||||
IMGS_PER_PAGE = 5
|
||||
UPLOAD_URL_PREFIX = "/uploads"
|
||||
|
||||
macro layout(name)
|
||||
begin
|
||||
@ -12,7 +13,8 @@ macro layout(name)
|
||||
render "src/views/#{{{name}}}.ecr", "src/views/layout.ecr"
|
||||
rescue e
|
||||
message = e.to_s
|
||||
render "message"
|
||||
@context.error message
|
||||
render "src/views/message.ecr", "src/views/layout.ecr"
|
||||
end
|
||||
end
|
||||
|
||||
@ -27,9 +29,9 @@ macro get_username(env)
|
||||
(@context.storage.verify_token cookie.value).not_nil!
|
||||
end
|
||||
|
||||
macro send_json(env, json)
|
||||
{{env}}.response.content_type = "application/json"
|
||||
{{json}}
|
||||
def send_json(env, json)
|
||||
env.response.content_type = "application/json"
|
||||
env.response.print json
|
||||
end
|
||||
|
||||
def hash_to_query(hash)
|
||||
@ -93,3 +95,7 @@ def validate_zip(path : String) : Exception?
|
||||
rescue e
|
||||
e
|
||||
end
|
||||
|
||||
def random_str
|
||||
UUID.random.to_s.gsub "-", ""
|
||||
end
|
||||
|
@ -26,11 +26,7 @@
|
||||
<a class="acard" href="/book/<%= t.id %>">
|
||||
<div class="uk-card uk-card-default">
|
||||
<div class="uk-card-media-top">
|
||||
<%- if t.entries.size > 0 -%>
|
||||
<img data-src="<%= t.entries[0].cover_url %>" data-width data-height alt="" uk-img>
|
||||
<%- else -%>
|
||||
<img data-src="/img/icon.png" data-width data-height alt="" uk-img>
|
||||
<%- end -%>
|
||||
<img data-src="<%= t.cover_url %>" data-width data-height alt="" uk-img>
|
||||
</div>
|
||||
<div class="uk-card-body">
|
||||
<%- if t.entries.size > 0 -%>
|
||||
|
@ -2,15 +2,9 @@
|
||||
<h2 class=uk-title><span><%= title.display_name %></span>
|
||||
|
||||
<% if is_admin %>
|
||||
<a onclick="rename(this)" class="uk-icon-button" uk-icon="icon:pencil"></a>
|
||||
<a onclick="edit()" class="uk-icon-button" uk-icon="icon:pencil"></a>
|
||||
<% end %>
|
||||
</h2>
|
||||
<div class="uk-margin title-rename" data-id="<%= title.id %>" hidden>
|
||||
<div class="uk-inline">
|
||||
<a class="uk-form-icon uk-form-icon-flip" onclick="renameSubmit(this)" uk-icon="icon:check"></a>
|
||||
<input class="uk-input title-rename-field" type="text" value="<%= title.display_name.gsub("\"", """) %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="uk-breadcrumb">
|
||||
<li><a href="/">Library</a></li>
|
||||
@ -48,11 +42,7 @@
|
||||
<a class="acard" href="/book/<%= t.id %>">
|
||||
<div class="uk-card uk-card-default">
|
||||
<div class="uk-card-media-top">
|
||||
<%- if t.entries.size > 0 -%>
|
||||
<img data-src="<%= t.entries[0].cover_url %>" data-width data-height alt="" uk-img>
|
||||
<%- else -%>
|
||||
<img data-src="/img/icon.png" data-width data-height alt="" uk-img>
|
||||
<%- end -%>
|
||||
<img data-src="<%= t.cover_url %>" data-width data-height alt="" uk-img>
|
||||
</div>
|
||||
<div class="uk-card-body">
|
||||
<h3 class="uk-card-title break-word" data-title="<%= t.display_name.gsub("\"", """) %>"><%= t.display_name %></h3>
|
||||
@ -63,7 +53,7 @@
|
||||
</div>
|
||||
<%- end -%>
|
||||
<%- title.entries.each_with_index do |e, i| -%>
|
||||
<div class="item" data-mtime="<%= e.mtime.to_unix %>" data-progress="<%= percentage[i] %>">
|
||||
<div class="item" data-mtime="<%= e.mtime.to_unix %>" data-progress="<%= percentage[i] %>" id="<%= e.id %>">
|
||||
<a class="acard">
|
||||
<div class="uk-card uk-card-default" onclick="showModal("<%= e.encoded_path %>", '<%= e.pages %>', <%= (percentage[i] * 100).round(1) %>, "<%= title.encoded_display_name %>", "<%= e.encoded_display_name %>", '<%= e.title_id %>', '<%= e.id %>')">
|
||||
<div class="uk-card-media-top">
|
||||
@ -88,15 +78,9 @@
|
||||
<h3 class="uk-modal-title break-word" id="modal-title"><span></span>
|
||||
|
||||
<% if is_admin %>
|
||||
<a onclick="rename(this)" class="uk-icon-button" uk-icon="icon:pencil"></a>
|
||||
<a class="uk-icon-button" uk-icon="icon:pencil"></a>
|
||||
<% end %>
|
||||
</h3>
|
||||
<div class="uk-margin title-rename" data-id="" data-entry-id="" hidden>
|
||||
<div class="uk-inline">
|
||||
<a class="uk-form-icon uk-form-icon-flip" onclick="renameSubmit(this)" uk-icon="icon:check"></a>
|
||||
<input class="uk-input title-rename-field" type="text" value="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="uk-text-meta uk-margin-remove-bottom break-word" id="path-text"></p>
|
||||
<p class="uk-text-meta uk-margin-remove-top" id="pages-text"></p>
|
||||
@ -116,6 +100,46 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="edit-modal" class="uk-flex-top" uk-modal>
|
||||
<div class="uk-modal-dialog uk-margin-auto-vertical">
|
||||
<button class="uk-modal-close-default" type="button" uk-close></button>
|
||||
<div class="uk-modal-header">
|
||||
<div>
|
||||
<h3 class="uk-modal-title break-word" id="modal-title">Edit</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uk-modal-body">
|
||||
<div class="uk-margin">
|
||||
<label class="uk-form-label" for="display-name">Display Name</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="display-name" id="display-name-field">
|
||||
</div>
|
||||
</div>
|
||||
<label class="uk-form-label">Cover Image</label>
|
||||
<div class="uk-grid">
|
||||
<div class="uk-width-1-2@s">
|
||||
<img id="cover" data-title-cover="<%= title.cover_url %>" alt="" data-width data-height uk-img>
|
||||
</div>
|
||||
<div class="uk-width-1-2@s">
|
||||
<div id="cover-upload" class="upload-field uk-placeholder uk-text-center uk-flex uk-flex-middle" data-title-id="<%= title.id %>">
|
||||
<div>
|
||||
<span uk-icon="icon: cloud-upload"></span>
|
||||
<span class="uk-text-middle">Upload a cover image by dropping it here or</span>
|
||||
<div uk-form-custom>
|
||||
<input type="file" accept="image/*">
|
||||
<span class="uk-link">selecting one</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<progress id="upload-progress" class="uk-progress" value="0" max="100" hidden></progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% 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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user