mirror of
https://github.com/hkalexling/Mango.git
synced 2026-04-25 00:00:52 -04:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a612cc15fb | |||
| c9c0818069 | |||
| 2f8efc382f | |||
| a0fb1880bd | |||
| a408f14425 | |||
| 243b6c8927 | |||
| ff3a44d017 | |||
| 67ef1f7112 | |||
| 5d7b8a1ef9 | |||
| a68f3eea95 | |||
| 220fc42bf2 | |||
| a45e6ea3da | |||
| 88394d4636 |
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
lib
|
||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: apk add --no-cache yarn yaml sqlite-static libarchive-dev libarchive-static acl-static expat-static zstd-static lz4-static bzip2-static
|
run: apk add --no-cache yarn yaml sqlite-static libarchive-dev libarchive-static acl-static expat-static zstd-static lz4-static bzip2-static libjpeg-turbo-dev libpng-dev tiff-dev
|
||||||
- name: Build
|
- name: Build
|
||||||
run: make static || make static
|
run: make static || make static
|
||||||
- name: Linter
|
- name: Linter
|
||||||
|
|||||||
+2
-3
@@ -3,9 +3,8 @@ FROM crystallang/crystal:0.34.0-alpine AS builder
|
|||||||
WORKDIR /Mango
|
WORKDIR /Mango
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
COPY package*.json .
|
RUN apk add --no-cache yarn yaml sqlite-static libarchive-dev libarchive-static acl-static expat-static zstd-static lz4-static bzip2-static libjpeg-turbo-dev libpng-dev tiff-dev
|
||||||
RUN apk add --no-cache yarn yaml sqlite-static libarchive-dev libarchive-static acl-static expat-static zstd-static lz4-static bzip2-static \
|
RUN make static || make static
|
||||||
&& make static
|
|
||||||
|
|
||||||
FROM library/alpine
|
FROM library/alpine
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -1,13 +1,14 @@
|
|||||||
FROM arm32v7/ubuntu:18.04
|
FROM arm32v7/ubuntu:18.04
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y wget git make llvm-8 llvm-8-dev g++ libsqlite3-dev libyaml-dev libgc-dev libssl-dev libcrypto++-dev libevent-dev libgmp-dev zlib1g-dev libpcre++-dev pkg-config libarchive-dev libxml2-dev libacl1-dev nettle-dev liblzo2-dev liblzma-dev libbz2-dev
|
RUN apt-get update && apt-get install -y wget git make llvm-8 llvm-8-dev g++ libsqlite3-dev libyaml-dev libgc-dev libssl-dev libcrypto++-dev libevent-dev libgmp-dev zlib1g-dev libpcre++-dev pkg-config libarchive-dev libxml2-dev libacl1-dev nettle-dev liblzo2-dev liblzma-dev libbz2-dev libjpeg-turbo8-dev libpng-dev libtiff-dev
|
||||||
|
|
||||||
RUN git clone https://github.com/crystal-lang/crystal && cd crystal && git checkout 0.34.0 && make deps && cd ..
|
RUN git clone https://github.com/crystal-lang/crystal && cd crystal && git checkout 0.34.0 && make deps && cd ..
|
||||||
RUN git clone https://github.com/kostya/myhtml && cd myhtml/src/ext && git checkout v1.5.0 && make && cd ..
|
RUN git clone https://github.com/kostya/myhtml && cd myhtml/src/ext && git checkout v1.5.0 && make && cd ..
|
||||||
RUN git clone https://github.com/jessedoyle/duktape.cr && cd duktape.cr/ext && git checkout v0.20.0 && make && cd ..
|
RUN git clone https://github.com/jessedoyle/duktape.cr && cd duktape.cr/ext && git checkout v0.20.0 && make && cd ..
|
||||||
|
RUN git clone https://github.com/hkalexling/image_size.cr && cd image_size.cr/ext/libwebp && git checkout v0.1.1 && make && cd ../stbi && make
|
||||||
|
|
||||||
COPY mango-arm32v7.o .
|
COPY mango-arm32v7.o .
|
||||||
|
|
||||||
RUN cc 'mango-arm32v7.o' -o 'mango' -rdynamic -lxml2 /myhtml/src/ext/modest-c/lib/libmodest_static.a -L/duktape.cr/src/.build/lib -L/duktape.cr/src/.build/include -lduktape -lm `pkg-config libarchive --libs` -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lgmp -lsqlite3 -lyaml -lpcre -lm /usr/lib/arm-linux-gnueabihf/libgc.so -lpthread /crystal/src/ext/libcrystal.a -levent -lrt -ldl -L/usr/bin/../lib/crystal/lib -L/usr/bin/../lib/crystal/lib
|
RUN cc 'mango-arm32v7.o' -o 'mango' -rdynamic -lxml2 -L/image_size.cr/ext/libwebp -lwebp -L/image_size.cr/ext/stbi -lstbi /myhtml/src/ext/modest-c/lib/libmodest_static.a -L/duktape.cr/src/.build/lib -L/duktape.cr/src/.build/include -lduktape -lm `pkg-config libarchive --libs` -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lgmp -lsqlite3 -lyaml -lpcre -lm /usr/lib/arm-linux-gnueabihf/libgc.so -lpthread /crystal/src/ext/libcrystal.a -levent -lrt -ldl -L/usr/bin/../lib/crystal/lib -L/usr/bin/../lib/crystal/lib
|
||||||
|
|
||||||
CMD ["./mango"]
|
CMD ["./mango"]
|
||||||
|
|||||||
+3
-2
@@ -1,13 +1,14 @@
|
|||||||
FROM arm64v8/ubuntu:18.04
|
FROM arm64v8/ubuntu:18.04
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y wget git make llvm-8 llvm-8-dev g++ libsqlite3-dev libyaml-dev libgc-dev libssl-dev libcrypto++-dev libevent-dev libgmp-dev zlib1g-dev libpcre++-dev pkg-config libarchive-dev libxml2-dev libacl1-dev nettle-dev liblzo2-dev liblzma-dev libbz2-dev
|
RUN apt-get update && apt-get install -y wget git make llvm-8 llvm-8-dev g++ libsqlite3-dev libyaml-dev libgc-dev libssl-dev libcrypto++-dev libevent-dev libgmp-dev zlib1g-dev libpcre++-dev pkg-config libarchive-dev libxml2-dev libacl1-dev nettle-dev liblzo2-dev liblzma-dev libbz2-dev libjpeg-turbo8-dev libpng-dev libtiff-dev
|
||||||
|
|
||||||
RUN git clone https://github.com/crystal-lang/crystal && cd crystal && git checkout 0.34.0 && make deps && cd ..
|
RUN git clone https://github.com/crystal-lang/crystal && cd crystal && git checkout 0.34.0 && make deps && cd ..
|
||||||
RUN git clone https://github.com/kostya/myhtml && cd myhtml/src/ext && git checkout v1.5.0 && make && cd ..
|
RUN git clone https://github.com/kostya/myhtml && cd myhtml/src/ext && git checkout v1.5.0 && make && cd ..
|
||||||
RUN git clone https://github.com/jessedoyle/duktape.cr && cd duktape.cr/ext && git checkout v0.20.0 && make && cd ..
|
RUN git clone https://github.com/jessedoyle/duktape.cr && cd duktape.cr/ext && git checkout v0.20.0 && make && cd ..
|
||||||
|
RUN git clone https://github.com/hkalexling/image_size.cr && cd image_size.cr/ext/libwebp && git checkout v0.1.1 && make && cd ../stbi && make
|
||||||
|
|
||||||
COPY mango-arm64v8.o .
|
COPY mango-arm64v8.o .
|
||||||
|
|
||||||
RUN cc 'mango-arm64v8.o' -o 'mango' -rdynamic -lxml2 /myhtml/src/ext/modest-c/lib/libmodest_static.a -L/duktape.cr/src/.build/lib -L/duktape.cr/src/.build/include -lduktape -lm `pkg-config libarchive --libs` -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lgmp -lsqlite3 -lyaml -lpcre -lm /usr/lib/aarch64-linux-gnu/libgc.so -lpthread /crystal/src/ext/libcrystal.a -levent -lrt -ldl -L/usr/bin/../lib/crystal/lib -L/usr/bin/../lib/crystal/lib
|
RUN cc 'mango-arm64v8.o' -o 'mango' -rdynamic -lxml2 -L/image_size.cr/ext/libwebp -lwebp -L/image_size.cr/ext/stbi -lstbi /myhtml/src/ext/modest-c/lib/libmodest_static.a -L/duktape.cr/src/.build/lib -L/duktape.cr/src/.build/include -lduktape -lm `pkg-config libarchive --libs` -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lgmp -lsqlite3 -lyaml -lpcre -lm /usr/lib/arm-linux-gnueabihf/libgc.so -lpthread /crystal/src/ext/libcrystal.a -levent -lrt -ldl -L/usr/bin/../lib/crystal/lib -L/usr/bin/../lib/crystal/lib
|
||||||
|
|
||||||
CMD ["./mango"]
|
CMD ["./mango"]
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ The official docker images are available on [Dockerhub](https://hub.docker.com/r
|
|||||||
### CLI
|
### CLI
|
||||||
|
|
||||||
```
|
```
|
||||||
Mango - Manga Server and Web Reader. Version 0.11.0
|
Mango - Manga Server and Web Reader. Version 0.12.0
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
|
|||||||
+148
-71
@@ -1,64 +1,91 @@
|
|||||||
$(function() {
|
$(() => {
|
||||||
function bind() {
|
getPages();
|
||||||
var controller = new ScrollMagic.Controller();
|
|
||||||
|
|
||||||
// replace history on scroll
|
$('#page-select').change(() => {
|
||||||
$('img').each(function(idx) {
|
const p = parseInt($('#page-select').val());
|
||||||
var scene = new ScrollMagic.Scene({
|
toPage(p);
|
||||||
triggerElement: $(this).get(),
|
|
||||||
triggerHook: 'onEnter',
|
|
||||||
reverse: true
|
|
||||||
})
|
|
||||||
.addTo(controller)
|
|
||||||
.on('enter', function(event) {
|
|
||||||
current = $(event.target.triggerElement()).attr('id');
|
|
||||||
replaceHistory(current);
|
|
||||||
})
|
|
||||||
.on('leave', function(event) {
|
|
||||||
var prev = $(event.target.triggerElement()).prev();
|
|
||||||
current = $(prev).attr('id');
|
|
||||||
replaceHistory(current);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// poor man's infinite scroll
|
|
||||||
var scene = new ScrollMagic.Scene({
|
|
||||||
triggerElement: $('.next-url').get(),
|
|
||||||
triggerHook: 'onEnter',
|
|
||||||
offset: -500
|
|
||||||
})
|
|
||||||
.addTo(controller)
|
|
||||||
.on('enter', function() {
|
|
||||||
var nextURL = $('.next-url').attr('href');
|
|
||||||
$('.next-url').remove();
|
|
||||||
if (!nextURL) {
|
|
||||||
console.log('No .next-url found. Reached end of page');
|
|
||||||
var lastURL = $('img').last().attr('id');
|
|
||||||
// load the reader URL for the last page to update reading progrss to 100%
|
|
||||||
$.get(lastURL);
|
|
||||||
$('#next-btn').removeAttr('hidden');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$('#hidden').load(encodeURI(nextURL) + ' .uk-container', function(res, status, xhr) {
|
|
||||||
if (status === 'error') console.log(xhr.statusText);
|
|
||||||
if (status === 'success') {
|
|
||||||
console.log(nextURL + ' loaded');
|
|
||||||
// new page loaded to #hidden, we now append it
|
|
||||||
$('.uk-section > .uk-container').append($('#hidden .uk-container').children());
|
|
||||||
$('#hidden').empty();
|
|
||||||
bind();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bind();
|
|
||||||
});
|
|
||||||
$('#page-select').change(function() {
|
|
||||||
jumpTo(parseInt($('#page-select').val()));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function showControl(idx) {
|
/**
|
||||||
|
* Set an alpine.js property
|
||||||
|
*
|
||||||
|
* @function setProp
|
||||||
|
* @param {string} key - Key of the data property
|
||||||
|
* @param {*} prop - The data property
|
||||||
|
*/
|
||||||
|
const setProp = (key, prop) => {
|
||||||
|
$('#root').get(0).__x.$data[key] = prop;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get dimension of the pages in the entry from the API and update the view
|
||||||
|
*/
|
||||||
|
const getPages = () => {
|
||||||
|
$.get(`${base_url}api/dimensions/${tid}/${eid}`)
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success && data.error)
|
||||||
|
throw new Error(resp.error);
|
||||||
|
const dimensions = data.dimensions;
|
||||||
|
|
||||||
|
const items = dimensions.map((d, i) => {
|
||||||
|
return {
|
||||||
|
id: i + 1,
|
||||||
|
url: `${base_url}api/page/${tid}/${eid}/${i+1}`,
|
||||||
|
width: d.width,
|
||||||
|
height: d.height
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setProp('items', items);
|
||||||
|
setProp('loading', false);
|
||||||
|
|
||||||
|
waitForPage(items.length, () => {
|
||||||
|
toPage(page);
|
||||||
|
setupScroller();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
const errMsg = `Failed to get the page dimensions. ${e}`;
|
||||||
|
console.error(e);
|
||||||
|
setProp('alertClass', 'uk-alert-danger');
|
||||||
|
setProp('msg', errMsg);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jump to a specific page
|
||||||
|
*
|
||||||
|
* @function toPage
|
||||||
|
* @param {number} idx - One-based index of the page
|
||||||
|
*/
|
||||||
|
const toPage = (idx) => {
|
||||||
|
$(`#${idx}`).get(0).scrollIntoView(true);
|
||||||
|
UIkit.modal($('#modal-sections')).hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a page exists every 100ms. If so, invoke the callback function.
|
||||||
|
*
|
||||||
|
* @function waitForPage
|
||||||
|
* @param {number} idx - One-based index of the page
|
||||||
|
* @param {function} cb - Callback function
|
||||||
|
*/
|
||||||
|
const waitForPage = (idx, cb) => {
|
||||||
|
if ($(`#${idx}`).length > 0) return cb();
|
||||||
|
setTimeout(() => {
|
||||||
|
waitForPage(idx, cb)
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the control modal
|
||||||
|
*
|
||||||
|
* @function showControl
|
||||||
|
* @param {object} event - The onclick event that triggers the function
|
||||||
|
*/
|
||||||
|
const showControl = (event) => {
|
||||||
|
const idx = parseInt($(event.currentTarget).attr('id'));
|
||||||
const pageCount = $('#page-select > option').length;
|
const pageCount = $('#page-select > option').length;
|
||||||
const progressText = `Progress: ${idx}/${pageCount} (${(idx/pageCount * 100).toFixed(1)}%)`;
|
const progressText = `Progress: ${idx}/${pageCount} (${(idx/pageCount * 100).toFixed(1)}%)`;
|
||||||
$('#progress-label').text(progressText);
|
$('#progress-label').text(progressText);
|
||||||
@@ -66,19 +93,69 @@ function showControl(idx) {
|
|||||||
UIkit.modal($('#modal-sections')).show();
|
UIkit.modal($('#modal-sections')).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function jumpTo(page) {
|
/**
|
||||||
var ary = window.location.pathname.split('/');
|
* Redirect to a URL
|
||||||
ary[ary.length - 1] = page;
|
*
|
||||||
ary.shift(); // remove leading `/`
|
* @function redirect
|
||||||
ary.unshift(window.location.origin);
|
* @param {string} url - The target URL
|
||||||
window.location.replace(ary.join('/'));
|
*/
|
||||||
}
|
const redirect = (url) => {
|
||||||
|
|
||||||
function replaceHistory(url) {
|
|
||||||
history.replaceState(null, "", url);
|
|
||||||
console.log('reading ' + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
function redirect(url) {
|
|
||||||
window.location.replace(url);
|
window.location.replace(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the address bar history and save th ereading progress if necessary
|
||||||
|
*
|
||||||
|
* @function replaceHistory
|
||||||
|
* @param {number} idx - One-based index of the current page
|
||||||
|
*/
|
||||||
|
const replaceHistory = (idx) => {
|
||||||
|
const ary = window.location.pathname.split('/');
|
||||||
|
ary[ary.length - 1] = idx;
|
||||||
|
ary.shift(); // remove leading `/`
|
||||||
|
ary.unshift(window.location.origin);
|
||||||
|
const url = ary.join('/');
|
||||||
|
saveProgress(idx);
|
||||||
|
history.replaceState(null, "", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up the scroll handler that calls `replaceHistory` when an image
|
||||||
|
* enters the view port
|
||||||
|
*
|
||||||
|
* @function setupScroller
|
||||||
|
*/
|
||||||
|
const setupScroller = () => {
|
||||||
|
$('#root img').each((idx, el) => {
|
||||||
|
$(el).on('inview', (event, inView) => {
|
||||||
|
if (inView) {
|
||||||
|
const current = $(event.currentTarget).attr('id');
|
||||||
|
replaceHistory(current);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let lastSavedPage = page;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the backend reading progress if the current page is more than
|
||||||
|
* five pages away from the last saved page
|
||||||
|
*
|
||||||
|
* @function saveProgress
|
||||||
|
* @param {number} idx - One-based index of the page
|
||||||
|
*/
|
||||||
|
const saveProgress = (idx) => {
|
||||||
|
if (Math.abs(idx - lastSavedPage) < 5) return;
|
||||||
|
lastSavedPage = idx;
|
||||||
|
|
||||||
|
const url = `${base_url}api/progress/${tid}/${idx}?${$.param({entry: eid})}`;
|
||||||
|
$.post(url)
|
||||||
|
.then(data => {
|
||||||
|
if (data.error) throw new Error(data.error);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
alert('danger', e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ shards:
|
|||||||
github: mamantoha/http_proxy
|
github: mamantoha/http_proxy
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
|
|
||||||
|
image_size:
|
||||||
|
github: hkalexling/image_size.cr
|
||||||
|
version: 0.1.1
|
||||||
|
|
||||||
kemal:
|
kemal:
|
||||||
github: kemalcr/kemal
|
github: kemalcr/kemal
|
||||||
version: 0.26.1
|
version: 0.26.1
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: mango
|
name: mango
|
||||||
version: 0.11.0
|
version: 0.12.0
|
||||||
|
|
||||||
authors:
|
authors:
|
||||||
- Alex Ling <hkalexling@gmail.com>
|
- Alex Ling <hkalexling@gmail.com>
|
||||||
@@ -34,3 +34,5 @@ dependencies:
|
|||||||
github: kostya/myhtml
|
github: kostya/myhtml
|
||||||
http_proxy:
|
http_proxy:
|
||||||
github: mamantoha/http_proxy
|
github: mamantoha/http_proxy
|
||||||
|
image_size:
|
||||||
|
github: hkalexling/image_size.cr
|
||||||
|
|||||||
+33
-5
@@ -1,3 +1,5 @@
|
|||||||
|
require "image_size"
|
||||||
|
|
||||||
class Entry
|
class Entry
|
||||||
property zip_path : String, book : Title, title : String,
|
property zip_path : String, book : Title, title : String,
|
||||||
size : String, pages : Int32, id : String, encoded_path : String,
|
size : String, pages : Int32, id : String, encoded_path : String,
|
||||||
@@ -77,11 +79,9 @@ class Entry
|
|||||||
url
|
url
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_page(page_num)
|
private def sorted_archive_entries
|
||||||
raise "Unreadble archive. #{@err_msg}" if @err_msg
|
|
||||||
img = nil
|
|
||||||
ArchiveFile.open @zip_path do |file|
|
ArchiveFile.open @zip_path do |file|
|
||||||
page = file.entries
|
entries = file.entries
|
||||||
.select { |e|
|
.select { |e|
|
||||||
SUPPORTED_IMG_TYPES.includes? \
|
SUPPORTED_IMG_TYPES.includes? \
|
||||||
MIME.from_filename? e.filename
|
MIME.from_filename? e.filename
|
||||||
@@ -89,7 +89,15 @@ class Entry
|
|||||||
.sort { |a, b|
|
.sort { |a, b|
|
||||||
compare_numerically a.filename, b.filename
|
compare_numerically a.filename, b.filename
|
||||||
}
|
}
|
||||||
.[page_num - 1]
|
yield file, entries
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_page(page_num)
|
||||||
|
raise "Unreadble archive. #{@err_msg}" if @err_msg
|
||||||
|
img = nil
|
||||||
|
sorted_archive_entries do |file, entries|
|
||||||
|
page = entries[page_num - 1]
|
||||||
data = file.read_entry page
|
data = file.read_entry page
|
||||||
if data
|
if data
|
||||||
img = Image.new data, MIME.from_filename(page.filename), page.filename,
|
img = Image.new data, MIME.from_filename(page.filename), page.filename,
|
||||||
@@ -99,6 +107,26 @@ class Entry
|
|||||||
img
|
img
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def page_dimensions
|
||||||
|
sizes = [] of Hash(String, Int32)
|
||||||
|
sorted_archive_entries do |file, entries|
|
||||||
|
entries.each_with_index do |e, i|
|
||||||
|
begin
|
||||||
|
data = file.read_entry(e).not_nil!
|
||||||
|
size = ImageSize.get data
|
||||||
|
sizes << {
|
||||||
|
"width" => size.width,
|
||||||
|
"height" => size.height,
|
||||||
|
}
|
||||||
|
rescue
|
||||||
|
Logger.warn "Failed to read page #{i} of entry #{@id}"
|
||||||
|
sizes << {"width" => 1000_i32, "height" => 1000_i32}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
sizes
|
||||||
|
end
|
||||||
|
|
||||||
def next_entry(username)
|
def next_entry(username)
|
||||||
entries = @book.sorted_entries username
|
entries = @book.sorted_entries username
|
||||||
idx = entries.index self
|
idx = entries.index self
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ require "option_parser"
|
|||||||
require "clim"
|
require "clim"
|
||||||
require "./plugin/*"
|
require "./plugin/*"
|
||||||
|
|
||||||
MANGO_VERSION = "0.11.0"
|
MANGO_VERSION = "0.12.0"
|
||||||
|
|
||||||
# From http://www.network-science.de/ascii/
|
# From http://www.network-science.de/ascii/
|
||||||
BANNER = %{
|
BANNER = %{
|
||||||
|
|||||||
@@ -332,5 +332,28 @@ class APIRouter < Router
|
|||||||
}.to_json
|
}.to_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get "/api/dimensions/:tid/:eid" do |env|
|
||||||
|
begin
|
||||||
|
tid = env.params.url["tid"]
|
||||||
|
eid = env.params.url["eid"]
|
||||||
|
|
||||||
|
title = @context.library.get_title tid
|
||||||
|
raise "Title ID `#{tid}` not found" if title.nil?
|
||||||
|
entry = title.get_entry eid
|
||||||
|
raise "Entry ID `#{eid}` of `#{title.title}` not found" if entry.nil?
|
||||||
|
|
||||||
|
sizes = entry.page_dimensions
|
||||||
|
send_json env, {
|
||||||
|
"success" => true,
|
||||||
|
"dimensions" => sizes,
|
||||||
|
}.to_json
|
||||||
|
rescue e
|
||||||
|
send_json env, {
|
||||||
|
"success" => false,
|
||||||
|
"error" => e.message,
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+4
-20
@@ -13,10 +13,6 @@ class ReaderRouter < Router
|
|||||||
|
|
||||||
# load progress
|
# load progress
|
||||||
page = entry.load_progress username
|
page = entry.load_progress username
|
||||||
# we go back 2 * `IMGS_PER_PAGE` pages. the infinite scroll
|
|
||||||
# library perloads a few pages in advance, and the user
|
|
||||||
# might not have actually read them
|
|
||||||
page = [page - 2 * IMGS_PER_PAGE, 1].max
|
|
||||||
|
|
||||||
# start from page 1 if the user has finished reading the entry
|
# start from page 1 if the user has finished reading the entry
|
||||||
page = 1 if entry.finished? username
|
page = 1 if entry.finished? username
|
||||||
@@ -32,29 +28,17 @@ class ReaderRouter < Router
|
|||||||
begin
|
begin
|
||||||
base_url = Config.current.base_url
|
base_url = Config.current.base_url
|
||||||
|
|
||||||
|
username = get_username env
|
||||||
|
|
||||||
title = (@context.library.get_title env.params.url["title"]).not_nil!
|
title = (@context.library.get_title env.params.url["title"]).not_nil!
|
||||||
entry = (title.get_entry env.params.url["entry"]).not_nil!
|
entry = (title.get_entry env.params.url["entry"]).not_nil!
|
||||||
page = env.params.url["page"].to_i
|
page = env.params.url["page"].to_i
|
||||||
raise "" if page > entry.pages || page <= 0
|
raise "" if page > entry.pages || page <= 0
|
||||||
|
|
||||||
# save progress
|
|
||||||
username = get_username env
|
|
||||||
entry.save_progress username, page
|
|
||||||
|
|
||||||
pages = (page...[entry.pages + 1, page + IMGS_PER_PAGE].min)
|
|
||||||
urls = pages.map { |idx|
|
|
||||||
"#{base_url}api/page/#{title.id}/#{entry.id}/#{idx}"
|
|
||||||
}
|
|
||||||
reader_urls = pages.map { |idx|
|
|
||||||
"#{base_url}reader/#{title.id}/#{entry.id}/#{idx}"
|
|
||||||
}
|
|
||||||
next_page = page + IMGS_PER_PAGE
|
|
||||||
next_url = next_entry_url = nil
|
|
||||||
exit_url = "#{base_url}book/#{title.id}"
|
exit_url = "#{base_url}book/#{title.id}"
|
||||||
|
|
||||||
|
next_entry_url = nil
|
||||||
next_entry = entry.next_entry username
|
next_entry = entry.next_entry username
|
||||||
unless next_page > entry.pages
|
|
||||||
next_url = "#{base_url}reader/#{title.id}/#{entry.id}/#{next_page}"
|
|
||||||
end
|
|
||||||
unless next_entry.nil?
|
unless next_entry.nil?
|
||||||
next_entry_url = "#{base_url}reader/#{title.id}/#{next_entry.id}"
|
next_entry_url = "#{base_url}reader/#{title.id}/#{next_entry.id}"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ def validate_username(username)
|
|||||||
if username.size < 3
|
if username.size < 3
|
||||||
raise "Username should contain at least 3 characters"
|
raise "Username should contain at least 3 characters"
|
||||||
end
|
end
|
||||||
if (username =~ /^[A-Za-z0-9_]+$/).nil?
|
if (username =~ /^[a-zA-Z_][a-zA-Z0-9_\-]*$/).nil?
|
||||||
raise "Username should contain alphanumeric characters " \
|
raise "Username can only contain alphanumeric characters, " \
|
||||||
"and underscores only"
|
"underscores, and hyphens"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% title.entries.each do |e| %>
|
<% title.entries.each do |e| %>
|
||||||
|
<% next if e.err_msg %>
|
||||||
<entry>
|
<entry>
|
||||||
<title><%= HTML.escape(e.display_name) %></title>
|
<title><%= HTML.escape(e.display_name) %></title>
|
||||||
<id>urn:mango:<%= e.id %></id>
|
<id>urn:mango:<%= e.id %></id>
|
||||||
@@ -34,5 +35,4 @@
|
|||||||
<link type="text/html" rel="alternate" title="Open in Mango" href="<%= base_url %>book/<%= e.book.id %>" />
|
<link type="text/html" rel="alternate" title="Open in Mango" href="<%= base_url %>book/<%= e.book.id %>" />
|
||||||
</entry>
|
</entry>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</feed>
|
||||||
</feed>
|
|
||||||
|
|||||||
+40
-13
@@ -6,21 +6,39 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="uk-section uk-section-default uk-section-small reader-bg">
|
<div class="uk-section uk-section-default uk-section-small reader-bg">
|
||||||
<div class="uk-container uk-container-small">
|
<div class="uk-container uk-container-small">
|
||||||
<%- urls.each_with_index do |url, i| -%>
|
<div id="alert"></div>
|
||||||
<img class="uk-align-center" data-src="<%= url %>" src="<%= base_url %>img/loading.gif" data-width data-height uk-img id="<%= reader_urls[i] %>" onclick="showControl(<%= pages.to_a[i] %>);">
|
<div id="root" x-data="{
|
||||||
<%- end -%>
|
loading: true,
|
||||||
<%- if next_url -%>
|
msg: 'Loading the web reader. Please wait...',
|
||||||
<a class="next-url" href="<%= next_url %>"></a>
|
alertClass: 'uk-alert-primary',
|
||||||
<%- end -%>
|
items: []
|
||||||
|
}">
|
||||||
|
<div x-show="loading">
|
||||||
|
<div :class="alertClass" x-show="msg" uk-alert>
|
||||||
|
<p x-text="msg"></p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div x-show="!loading" x-cloak>
|
||||||
|
<template x-for="item in items">
|
||||||
|
<img
|
||||||
|
uk-img
|
||||||
|
class="uk-align-center"
|
||||||
|
:data-src="item.url"
|
||||||
|
:width="item.width"
|
||||||
|
:height="item.height"
|
||||||
|
:id="item.id"
|
||||||
|
@click="showControl($event)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<%- if next_entry_url -%>
|
<%- if next_entry_url -%>
|
||||||
<button id="next-btn" class="uk-align-center uk-button uk-button-primary" hidden onclick="redirect('<%= next_entry_url %>')">Next Entry</button>
|
<button id="next-btn" class="uk-align-center uk-button uk-button-primary" @click="redirect('<%= next_entry_url %>')">Next Entry</button>
|
||||||
<%- else -%>
|
<%- else -%>
|
||||||
<button id="next-btn" class="uk-align-center uk-button uk-button-primary" hidden onclick="redirect('<%= exit_url %>')">Exit Reader</button>
|
<button id="next-btn" class="uk-align-center uk-button uk-button-primary" @click="redirect('<%= exit_url %>')">Exit Reader</button>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div id="hidden" hidden></div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="modal-sections" class="uk-flex-top" uk-modal>
|
<div id="modal-sections" class="uk-flex-top" uk-modal>
|
||||||
<div class="uk-modal-dialog uk-margin-auto-vertical">
|
<div class="uk-modal-dialog uk-margin-auto-vertical">
|
||||||
@@ -49,14 +67,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const base_url = "<%= base_url %>"
|
const base_url = "<%= base_url %>";
|
||||||
|
const page = <%= page %>;
|
||||||
|
const tid = "<%= title.id %>";
|
||||||
|
const eid = "<%= entry.id %>";
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/protonet-jquery.inview/1.1.2/jquery.inview.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/ScrollMagic.min.js"></script>
|
<script src="<%= base_url %>js/alert.js"></script>
|
||||||
<script src="<%= base_url %>js/uikit.min.js"></script>
|
<script src="<%= base_url %>js/uikit.min.js"></script>
|
||||||
<script src="<%= base_url %>js/uikit-icons.min.js"></script>
|
<script src="<%= base_url %>js/uikit-icons.min.js"></script>
|
||||||
<script src="<%= base_url %>js/reader.js"></script>
|
<script src="<%= base_url %>js/reader.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
img[data-src][src*='data:image'] { background: white; }
|
||||||
|
#root img { width: 100%; }
|
||||||
|
</style>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user