mirror of
https://github.com/hkalexling/Mango.git
synced 2026-04-25 00:00:52 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a7d9eb3a1 |
@@ -1,11 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
parser: '@babel/eslint-parser',
|
|
||||||
parserOptions: { requireConfigFile: false },
|
|
||||||
plugins: ['prettier'],
|
|
||||||
rules: {
|
|
||||||
eqeqeq: ['error', 'always'],
|
|
||||||
'object-shorthand': ['error', 'always'],
|
|
||||||
'prettier/prettier': 'error',
|
|
||||||
'no-var': 'error',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "all",
|
|
||||||
"printWidth": 80,
|
|
||||||
"tabWidth": 2
|
|
||||||
}
|
|
||||||
@@ -29,7 +29,6 @@ test:
|
|||||||
check:
|
check:
|
||||||
crystal tool format --check
|
crystal tool format --check
|
||||||
./bin/ameba
|
./bin/ameba
|
||||||
yarn lint
|
|
||||||
|
|
||||||
arm32v7:
|
arm32v7:
|
||||||
crystal build src/mango.cr --release --progress --error-trace --cross-compile --target='arm-linux-gnueabihf' -o mango-arm32v7
|
crystal build src/mango.cr --release --progress --error-trace --cross-compile --target='arm-linux-gnueabihf' -o mango-arm32v7
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
[](https://www.patreon.com/hkalexling)  [](https://gitter.im/mango-cr/mango?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](http://discord.com/invite/ezKtacCp9Q)
|
[](https://www.patreon.com/hkalexling)  [](https://gitter.im/mango-cr/mango?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](http://discord.com/invite/ezKtacCp9Q)
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> As of March 2025, Mango is no longer maintained. We are incredibly grateful to everyone who used it, contributed, or gave feedback along the way - thank you! Unfortunately, we just don't have the time to keep it going right now. That said, it's open source, so you're more than welcome to fork it, build on it, or maintain your own version. If you're looking for alternatives, check out the wiki for similar projects. We might return to it someday, but for now, we don't recommend using it as-is - running unmaintained software can introduce security risks.
|
||||||
|
|
||||||
Mango is a self-hosted manga server and reader. Its features include
|
Mango is a self-hosted manga server and reader. Its features include
|
||||||
|
|
||||||
- Multi-user support
|
- Multi-user support
|
||||||
|
|||||||
+21
-36
@@ -5,16 +5,12 @@ const minifyCss = require('gulp-minify-css');
|
|||||||
const less = require('gulp-less');
|
const less = require('gulp-less');
|
||||||
|
|
||||||
gulp.task('copy-img', () => {
|
gulp.task('copy-img', () => {
|
||||||
return gulp
|
return gulp.src('node_modules/uikit/src/images/backgrounds/*.svg')
|
||||||
.src('node_modules/uikit/src/images/backgrounds/*.svg')
|
|
||||||
.pipe(gulp.dest('public/img'));
|
.pipe(gulp.dest('public/img'));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('copy-font', () => {
|
gulp.task('copy-font', () => {
|
||||||
return gulp
|
return gulp.src('node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff**')
|
||||||
.src(
|
|
||||||
'node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff**',
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest('public/webfonts'));
|
.pipe(gulp.dest('public/webfonts'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -23,59 +19,48 @@ gulp.task('node-modules-copy', gulp.parallel('copy-img', 'copy-font'));
|
|||||||
|
|
||||||
// Compile less
|
// Compile less
|
||||||
gulp.task('less', () => {
|
gulp.task('less', () => {
|
||||||
return gulp
|
return gulp.src([
|
||||||
.src(['public/css/mango.less', 'public/css/tags.less'])
|
'public/css/mango.less',
|
||||||
|
'public/css/tags.less'
|
||||||
|
])
|
||||||
.pipe(less())
|
.pipe(less())
|
||||||
.pipe(gulp.dest('public/css'));
|
.pipe(gulp.dest('public/css'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Transpile and minify JS files and output to dist
|
// Transpile and minify JS files and output to dist
|
||||||
gulp.task('babel', () => {
|
gulp.task('babel', () => {
|
||||||
return gulp
|
return gulp.src(['public/js/*.js', '!public/js/*.min.js'])
|
||||||
.src(['public/js/*.js', '!public/js/*.min.js'])
|
.pipe(babel({
|
||||||
.pipe(
|
|
||||||
babel({
|
|
||||||
presets: [
|
presets: [
|
||||||
[
|
['@babel/preset-env', {
|
||||||
'@babel/preset-env',
|
targets: '>0.25%, not dead, ios>=9'
|
||||||
{
|
}]
|
||||||
targets: '>0.25%, not dead, ios>=9',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
],
|
}))
|
||||||
}),
|
.pipe(minify({
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
minify({
|
|
||||||
removeConsole: true,
|
removeConsole: true,
|
||||||
builtIns: false,
|
builtIns: false
|
||||||
}),
|
}))
|
||||||
)
|
|
||||||
.pipe(gulp.dest('dist/js'));
|
.pipe(gulp.dest('dist/js'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Minify CSS and output to dist
|
// Minify CSS and output to dist
|
||||||
gulp.task('minify-css', () => {
|
gulp.task('minify-css', () => {
|
||||||
return gulp
|
return gulp.src('public/css/*.css')
|
||||||
.src('public/css/*.css')
|
|
||||||
.pipe(minifyCss())
|
.pipe(minifyCss())
|
||||||
.pipe(gulp.dest('dist/css'));
|
.pipe(gulp.dest('dist/css'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy static files (includeing images) to dist
|
// Copy static files (includeing images) to dist
|
||||||
gulp.task('copy-files', () => {
|
gulp.task('copy-files', () => {
|
||||||
return gulp
|
return gulp.src([
|
||||||
.src(
|
|
||||||
[
|
|
||||||
'public/*.*',
|
'public/*.*',
|
||||||
'public/img/**',
|
'public/img/**',
|
||||||
'public/webfonts/*',
|
'public/webfonts/*',
|
||||||
'public/js/*.min.js',
|
'public/js/*.min.js'
|
||||||
],
|
], {
|
||||||
{
|
base: 'public'
|
||||||
base: 'public',
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest('dist'));
|
.pipe(gulp.dest('dist'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+3
-8
@@ -6,25 +6,20 @@
|
|||||||
"author": "Alex Ling <hkalexling@gmail.com>",
|
"author": "Alex Ling <hkalexling@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.18.9",
|
|
||||||
"@babel/preset-env": "^7.11.5",
|
"@babel/preset-env": "^7.11.5",
|
||||||
"all-contributors-cli": "^6.19.0",
|
"all-contributors-cli": "^6.19.0",
|
||||||
"eslint": "^8.22.0",
|
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-babel": "^8.0.0",
|
"gulp-babel": "^8.0.0",
|
||||||
"gulp-babel-minify": "^0.5.1",
|
"gulp-babel-minify": "^0.5.1",
|
||||||
"gulp-less": "^4.0.1",
|
"gulp-less": "^4.0.1",
|
||||||
"gulp-minify-css": "^1.2.4",
|
"gulp-minify-css": "^1.2.4",
|
||||||
"less": "^3.11.3",
|
"less": "^3.11.3"
|
||||||
"prettier": "^2.7.1"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"uglify": "gulp",
|
"uglify": "gulp"
|
||||||
"lint": "eslint public/js *.js --ext .js"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.14.0",
|
"@fortawesome/fontawesome-free": "^5.14.0",
|
||||||
"uikit": "~3.14.0"
|
"uikit": "^3.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-5
@@ -27,11 +27,11 @@ const component = () => {
|
|||||||
this.scanMs = -1;
|
this.scanMs = -1;
|
||||||
this.scanTitles = 0;
|
this.scanTitles = 0;
|
||||||
$.post(`${base_url}api/admin/scan`)
|
$.post(`${base_url}api/admin/scan`)
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
this.scanMs = data.milliseconds;
|
this.scanMs = data.milliseconds;
|
||||||
this.scanTitles = data.titles;
|
this.scanTitles = data.titles;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch(e => {
|
||||||
alert('danger', `Failed to trigger a scan. Error: ${e}`);
|
alert('danger', `Failed to trigger a scan. Error: ${e}`);
|
||||||
})
|
})
|
||||||
.always(() => {
|
.always(() => {
|
||||||
@@ -42,12 +42,14 @@ const component = () => {
|
|||||||
if (this.generating) return;
|
if (this.generating) return;
|
||||||
this.generating = true;
|
this.generating = true;
|
||||||
this.progress = 0.0;
|
this.progress = 0.0;
|
||||||
$.post(`${base_url}api/admin/generate_thumbnails`).then(() => {
|
$.post(`${base_url}api/admin/generate_thumbnails`)
|
||||||
this.getProgress();
|
.then(() => {
|
||||||
|
this.getProgress()
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getProgress() {
|
getProgress() {
|
||||||
$.get(`${base_url}api/admin/thumbnail_progress`).then((data) => {
|
$.get(`${base_url}api/admin/thumbnail_progress`)
|
||||||
|
.then(data => {
|
||||||
this.progress = data.progress;
|
this.progress = data.progress;
|
||||||
this.generating = data.progress > 0;
|
this.generating = data.progress > 0;
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -2,5 +2,5 @@ const alert = (level, text) => {
|
|||||||
$('#alert').empty();
|
$('#alert').empty();
|
||||||
const html = `<div class="uk-alert-${level}" uk-alert><a class="uk-alert-close" uk-close></a><p>${text}</p></div>`;
|
const html = `<div class="uk-alert-${level}" uk-alert><a class="uk-alert-close" uk-close></a><p>${text}</p></div>`;
|
||||||
$('#alert').append(html);
|
$('#alert').append(html);
|
||||||
$('html, body').animate({ scrollTop: 0 });
|
$("html, body").animate({ scrollTop: 0 });
|
||||||
};
|
};
|
||||||
|
|||||||
+4
-8
@@ -41,10 +41,7 @@ const getProp = (key, selector = '#root') => {
|
|||||||
* @return {bool}
|
* @return {bool}
|
||||||
*/
|
*/
|
||||||
const preferDarkMode = () => {
|
const preferDarkMode = () => {
|
||||||
return (
|
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
window.matchMedia &&
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,7 +87,7 @@ const loadTheme = () => {
|
|||||||
* @function saveThemeSetting
|
* @function saveThemeSetting
|
||||||
* @param {string} setting - A theme setting
|
* @param {string} setting - A theme setting
|
||||||
*/
|
*/
|
||||||
const saveThemeSetting = (setting) => {
|
const saveThemeSetting = setting => {
|
||||||
if (!validThemeSetting(setting)) setting = 'system';
|
if (!validThemeSetting(setting)) setting = 'system';
|
||||||
localStorage.setItem('theme', setting);
|
localStorage.setItem('theme', setting);
|
||||||
};
|
};
|
||||||
@@ -137,9 +134,8 @@ $(() => {
|
|||||||
|
|
||||||
// on system dark mode setting change
|
// on system dark mode setting change
|
||||||
if (window.matchMedia) {
|
if (window.matchMedia) {
|
||||||
window
|
window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
.matchMedia('(prefers-color-scheme: dark)')
|
.addEventListener('change', event => {
|
||||||
.addEventListener('change', (event) => {
|
|
||||||
if (loadThemeSetting() === 'system')
|
if (loadThemeSetting() === 'system')
|
||||||
setTheme(event.matches ? 'dark' : 'light');
|
setTheme(event.matches ? 'dark' : 'light');
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ const truncate = (e) => {
|
|||||||
} else {
|
} else {
|
||||||
$(e).removeAttr('uk-tooltip');
|
$(e).removeAttr('uk-tooltip');
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,22 +7,22 @@ const component = () => {
|
|||||||
ws: undefined,
|
ws: undefined,
|
||||||
|
|
||||||
wsConnect(secure = true) {
|
wsConnect(secure = true) {
|
||||||
const url = `${secure ? 'wss' : 'ws'}://${
|
const url = `${secure ? 'wss' : 'ws'}://${location.host}${base_url}api/admin/mangadex/queue`;
|
||||||
location.host
|
|
||||||
}${base_url}api/admin/mangadex/queue`;
|
|
||||||
console.log(`Connecting to ${url}`);
|
console.log(`Connecting to ${url}`);
|
||||||
this.ws = new WebSocket(url);
|
this.ws = new WebSocket(url);
|
||||||
this.ws.onmessage = (event) => {
|
this.ws.onmessage = event => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
this.jobs = data.jobs;
|
this.jobs = data.jobs;
|
||||||
this.paused = data.paused;
|
this.paused = data.paused;
|
||||||
};
|
};
|
||||||
this.ws.onclose = () => {
|
this.ws.onclose = () => {
|
||||||
if (this.ws.failed) return this.wsConnect(false);
|
if (this.ws.failed)
|
||||||
|
return this.wsConnect(false);
|
||||||
alert('danger', 'Socket connection closed');
|
alert('danger', 'Socket connection closed');
|
||||||
};
|
};
|
||||||
this.ws.onerror = () => {
|
this.ws.onerror = () => {
|
||||||
if (secure) return (this.ws.failed = true);
|
if (secure)
|
||||||
|
return this.ws.failed = true;
|
||||||
alert('danger', 'Socket connection failed');
|
alert('danger', 'Socket connection failed');
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -35,24 +35,18 @@ const component = () => {
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: base_url + 'api/admin/mangadex/queue',
|
url: base_url + 'api/admin/mangadex/queue',
|
||||||
dataType: 'json',
|
dataType: 'json'
|
||||||
})
|
})
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (!data.success && data.error) {
|
if (!data.success && data.error) {
|
||||||
alert(
|
alert('danger', `Failed to fetch download queue. Error: ${data.error}`);
|
||||||
'danger',
|
|
||||||
`Failed to fetch download queue. Error: ${data.error}`,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.jobs = data.jobs;
|
this.jobs = data.jobs;
|
||||||
this.paused = data.paused;
|
this.paused = data.paused;
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to fetch download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
|
||||||
`Failed to fetch download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.always(() => {
|
.always(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
@@ -61,36 +55,26 @@ const component = () => {
|
|||||||
jobAction(action, event) {
|
jobAction(action, event) {
|
||||||
let url = `${base_url}api/admin/mangadex/queue/${action}`;
|
let url = `${base_url}api/admin/mangadex/queue/${action}`;
|
||||||
if (event) {
|
if (event) {
|
||||||
const id = event.currentTarget
|
const id = event.currentTarget.closest('tr').id.split('-').slice(1).join('-');
|
||||||
.closest('tr')
|
|
||||||
.id.split('-')
|
|
||||||
.slice(1)
|
|
||||||
.join('-');
|
|
||||||
url = `${url}?${$.param({
|
url = `${url}?${$.param({
|
||||||
id,
|
id: id
|
||||||
})}`;
|
})}`;
|
||||||
}
|
}
|
||||||
console.log(url);
|
console.log(url);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url,
|
url: url,
|
||||||
dataType: 'json',
|
dataType: 'json'
|
||||||
})
|
})
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (!data.success && data.error) {
|
if (!data.success && data.error) {
|
||||||
alert(
|
alert('danger', `Failed to ${action} job from download queue. Error: ${data.error}`);
|
||||||
'danger',
|
|
||||||
`Failed to ${action} job from download queue. Error: ${data.error}`,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.load();
|
this.load();
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to ${action} job from download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
|
||||||
`Failed to ${action} job from download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
toggle() {
|
toggle() {
|
||||||
@@ -99,14 +83,11 @@ const component = () => {
|
|||||||
const url = `${base_url}api/admin/mangadex/queue/${action}`;
|
const url = `${base_url}api/admin/mangadex/queue/${action}`;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url,
|
url: url,
|
||||||
dataType: 'json',
|
dataType: 'json'
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to ${action} download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
|
||||||
`Failed to ${action} download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.always(() => {
|
.always(() => {
|
||||||
this.load();
|
this.load();
|
||||||
@@ -130,6 +111,6 @@ const component = () => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return cls;
|
return cls;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+14
-28
@@ -7,9 +7,9 @@ const component = () => {
|
|||||||
|
|
||||||
load() {
|
load() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.request('GET', `${base_url}api/admin/titles/missing`, (data) => {
|
this.request('GET', `${base_url}api/admin/titles/missing`, data => {
|
||||||
this.titles = data.titles;
|
this.titles = data.titles;
|
||||||
this.request('GET', `${base_url}api/admin/entries/missing`, (data) => {
|
this.request('GET', `${base_url}api/admin/entries/missing`, data => {
|
||||||
this.entries = data.entries;
|
this.entries = data.entries;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.empty = this.entries.length === 0 && this.titles.length === 0;
|
this.empty = this.entries.length === 0 && this.titles.length === 0;
|
||||||
@@ -19,33 +19,22 @@ const component = () => {
|
|||||||
rm(event) {
|
rm(event) {
|
||||||
const rawID = event.currentTarget.closest('tr').id;
|
const rawID = event.currentTarget.closest('tr').id;
|
||||||
const [type, id] = rawID.split('-');
|
const [type, id] = rawID.split('-');
|
||||||
const url = `${base_url}api/admin/${
|
const url = `${base_url}api/admin/${type === 'title' ? 'titles' : 'entries'}/missing/${id}`;
|
||||||
type === 'title' ? 'titles' : 'entries'
|
|
||||||
}/missing/${id}`;
|
|
||||||
this.request('DELETE', url, () => {
|
this.request('DELETE', url, () => {
|
||||||
this.load();
|
this.load();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
rmAll() {
|
rmAll() {
|
||||||
UIkit.modal
|
UIkit.modal.confirm('Are you sure? All metadata associated with these items, including their tags and thumbnails, will be deleted from the database.', {
|
||||||
.confirm(
|
|
||||||
'Are you sure? All metadata associated with these items, including their tags and thumbnails, will be deleted from the database.',
|
|
||||||
{
|
|
||||||
labels: {
|
labels: {
|
||||||
ok: 'Yes, delete them',
|
ok: 'Yes, delete them',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel'
|
||||||
},
|
}
|
||||||
},
|
}).then(() => {
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
this.request('DELETE', `${base_url}api/admin/titles/missing`, () => {
|
this.request('DELETE', `${base_url}api/admin/titles/missing`, () => {
|
||||||
this.request(
|
this.request('DELETE', `${base_url}api/admin/entries/missing`, () => {
|
||||||
'DELETE',
|
|
||||||
`${base_url}api/admin/entries/missing`,
|
|
||||||
() => {
|
|
||||||
this.load();
|
this.load();
|
||||||
},
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -53,10 +42,10 @@ const component = () => {
|
|||||||
console.log(url);
|
console.log(url);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: method,
|
type: method,
|
||||||
url,
|
url: url,
|
||||||
contentType: 'application/json',
|
contentType: 'application/json'
|
||||||
})
|
})
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert('danger', `Failed to ${method} ${url}. Error: ${data.error}`);
|
alert('danger', `Failed to ${method} ${url}. Error: ${data.error}`);
|
||||||
return;
|
return;
|
||||||
@@ -64,11 +53,8 @@ const component = () => {
|
|||||||
if (cb) cb(data);
|
if (cb) cb(data);
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to ${method} ${url}. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
|
||||||
`Failed to ${method} ${url}. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+123
-106
@@ -8,8 +8,8 @@ const component = () => {
|
|||||||
manga: undefined, // undefined: not searched yet, []: empty
|
manga: undefined, // undefined: not searched yet, []: empty
|
||||||
mid: undefined, // id of the selected manga
|
mid: undefined, // id of the selected manga
|
||||||
allChapters: [],
|
allChapters: [],
|
||||||
query: '',
|
query: "",
|
||||||
mangaTitle: '',
|
mangaTitle: "",
|
||||||
searching: false,
|
searching: false,
|
||||||
adding: false,
|
adding: false,
|
||||||
sortOptions: [],
|
sortOptions: [],
|
||||||
@@ -18,16 +18,16 @@ const component = () => {
|
|||||||
chaptersLimit: 500,
|
chaptersLimit: 500,
|
||||||
listManga: false,
|
listManga: false,
|
||||||
subscribing: false,
|
subscribing: false,
|
||||||
subscriptionName: '',
|
subscriptionName: "",
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
const tableObserver = new MutationObserver(() => {
|
const tableObserver = new MutationObserver(() => {
|
||||||
console.log('table mutated');
|
console.log("table mutated");
|
||||||
$('#selectable').selectable({
|
$("#selectable").selectable({
|
||||||
filter: 'tr',
|
filter: "tr",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
tableObserver.observe($('table').get(0), {
|
tableObserver.observe($("table").get(0), {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
});
|
});
|
||||||
@@ -37,21 +37,25 @@ const component = () => {
|
|||||||
if (!data.success) throw new Error(data.error);
|
if (!data.success) throw new Error(data.error);
|
||||||
this.plugins = data.plugins;
|
this.plugins = data.plugins;
|
||||||
|
|
||||||
const pid = localStorage.getItem('plugin');
|
const pid = localStorage.getItem("plugin");
|
||||||
if (pid && this.plugins.map((p) => p.id).includes(pid))
|
if (pid && this.plugins.map((p) => p.id).includes(pid))
|
||||||
return this.loadPlugin(pid);
|
return this.loadPlugin(pid);
|
||||||
|
|
||||||
if (this.plugins.length > 0) this.loadPlugin(this.plugins[0].id);
|
if (this.plugins.length > 0)
|
||||||
|
this.loadPlugin(this.plugins[0].id);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert('danger', `Failed to list the available plugins. Error: ${e}`);
|
alert(
|
||||||
|
"danger",
|
||||||
|
`Failed to list the available plugins. Error: ${e}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
loadPlugin(pid) {
|
loadPlugin(pid) {
|
||||||
fetch(
|
fetch(
|
||||||
`${base_url}api/admin/plugin/info?${new URLSearchParams({
|
`${base_url}api/admin/plugin/info?${new URLSearchParams({
|
||||||
plugin: pid,
|
plugin: pid,
|
||||||
})}`,
|
})}`
|
||||||
)
|
)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@@ -61,7 +65,10 @@ const component = () => {
|
|||||||
this.pid = pid;
|
this.pid = pid;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert('danger', `Failed to get plugin metadata. Error: ${e}`);
|
alert(
|
||||||
|
"danger",
|
||||||
|
`Failed to get plugin metadata. Error: ${e}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
pluginChanged() {
|
pluginChanged() {
|
||||||
@@ -69,12 +76,12 @@ const component = () => {
|
|||||||
this.chapters = undefined;
|
this.chapters = undefined;
|
||||||
this.mid = undefined;
|
this.mid = undefined;
|
||||||
this.loadPlugin(this.pid);
|
this.loadPlugin(this.pid);
|
||||||
localStorage.setItem('plugin', this.pid);
|
localStorage.setItem("plugin", this.pid);
|
||||||
},
|
},
|
||||||
get chapterKeys() {
|
get chapterKeys() {
|
||||||
if (this.allChapters.length < 1) return [];
|
if (this.allChapters.length < 1) return [];
|
||||||
return Object.keys(this.allChapters[0]).filter(
|
return Object.keys(this.allChapters[0]).filter(
|
||||||
(k) => !['manga_title'].includes(k),
|
(k) => !["manga_title"].includes(k)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
searchChapters(query) {
|
searchChapters(query) {
|
||||||
@@ -86,8 +93,8 @@ const component = () => {
|
|||||||
fetch(
|
fetch(
|
||||||
`${base_url}api/admin/plugin/list?${new URLSearchParams({
|
`${base_url}api/admin/plugin/list?${new URLSearchParams({
|
||||||
plugin: this.pid,
|
plugin: this.pid,
|
||||||
query,
|
query: query,
|
||||||
})}`,
|
})}`
|
||||||
)
|
)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@@ -103,7 +110,7 @@ const component = () => {
|
|||||||
this.chapters = data.chapters;
|
this.chapters = data.chapters;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert('danger', `Failed to list chapters. Error: ${e}`);
|
alert("danger", `Failed to list chapters. Error: ${e}`);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.searching = false;
|
this.searching = false;
|
||||||
@@ -117,8 +124,8 @@ const component = () => {
|
|||||||
fetch(
|
fetch(
|
||||||
`${base_url}api/admin/plugin/search?${new URLSearchParams({
|
`${base_url}api/admin/plugin/search?${new URLSearchParams({
|
||||||
plugin: this.pid,
|
plugin: this.pid,
|
||||||
query,
|
query: query,
|
||||||
})}`,
|
})}`
|
||||||
)
|
)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@@ -127,7 +134,7 @@ const component = () => {
|
|||||||
this.listManga = true;
|
this.listManga = true;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert('danger', `Search failed. Error: ${e}`);
|
alert("danger", `Search failed. Error: ${e}`);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.searching = false;
|
this.searching = false;
|
||||||
@@ -146,35 +153,37 @@ const component = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectAll() {
|
selectAll() {
|
||||||
$('tbody#selectable > tr').each((i, e) => {
|
$("tbody > tr").each((i, e) => {
|
||||||
$(e).addClass('ui-selected');
|
$(e).addClass("ui-selected");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
$('tbody#selectable > tr').each((i, e) => {
|
$("tbody > tr").each((i, e) => {
|
||||||
$(e).removeClass('ui-selected');
|
$(e).removeClass("ui-selected");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
download() {
|
download() {
|
||||||
const selected = $('tbody#selectable > tr.ui-selected').get();
|
const selected = $("tbody > tr.ui-selected").get();
|
||||||
if (selected.length === 0) return;
|
if (selected.length === 0) return;
|
||||||
|
|
||||||
UIkit.modal
|
UIkit.modal
|
||||||
.confirm(`Download ${selected.length} selected chapters?`)
|
.confirm(`Download ${selected.length} selected chapters?`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const ids = selected.map((e) => e.id);
|
const ids = selected.map((e) => e.id);
|
||||||
const chapters = this.chapters.filter((c) => ids.includes(c.id));
|
const chapters = this.chapters.filter((c) =>
|
||||||
|
ids.includes(c.id)
|
||||||
|
);
|
||||||
console.log(chapters);
|
console.log(chapters);
|
||||||
this.adding = true;
|
this.adding = true;
|
||||||
fetch(`${base_url}api/admin/plugin/download`, {
|
fetch(`${base_url}api/admin/plugin/download`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
chapters,
|
chapters,
|
||||||
plugin: this.pid,
|
plugin: this.pid,
|
||||||
title: this.mangaTitle,
|
title: this.mangaTitle,
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
@@ -183,16 +192,16 @@ const component = () => {
|
|||||||
const successCount = parseInt(data.success);
|
const successCount = parseInt(data.success);
|
||||||
const failCount = parseInt(data.fail);
|
const failCount = parseInt(data.fail);
|
||||||
alert(
|
alert(
|
||||||
'success',
|
"success",
|
||||||
`${successCount} of ${
|
`${successCount} of ${
|
||||||
successCount + failCount
|
successCount + failCount
|
||||||
} chapters added to the download queue. You can view and manage your download queue on the <a href="${base_url}admin/downloads">download manager page</a>.`,
|
} chapters added to the download queue. You can view and manage your download queue on the <a href="${base_url}admin/downloads">download manager page</a>.`
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert(
|
alert(
|
||||||
'danger',
|
"danger",
|
||||||
`Failed to add chapters to the download queue. Error: ${e}`,
|
`Failed to add chapters to the download queue. Error: ${e}`
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -201,7 +210,7 @@ const component = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
thClicked(event) {
|
thClicked(event) {
|
||||||
const idx = parseInt(event.currentTarget.id.split('-')[1]);
|
const idx = parseInt(event.currentTarget.id.split("-")[1]);
|
||||||
if (idx === undefined || isNaN(idx)) return;
|
if (idx === undefined || isNaN(idx)) return;
|
||||||
const curOption = this.sortOptions[idx];
|
const curOption = this.sortOptions[idx];
|
||||||
let option;
|
let option;
|
||||||
@@ -224,46 +233,51 @@ const component = () => {
|
|||||||
get filteredChapters() {
|
get filteredChapters() {
|
||||||
let ary = this.allChapters.slice();
|
let ary = this.allChapters.slice();
|
||||||
|
|
||||||
console.log('initial size:', ary.length);
|
console.log("initial size:", ary.length);
|
||||||
for (let filter of this.appliedFilters) {
|
for (let filter of this.appliedFilters) {
|
||||||
if (!filter.value) continue;
|
if (!filter.value) continue;
|
||||||
if (filter.type === 'array' && filter.value === 'all') continue;
|
if (filter.type === "array" && filter.value === "all") continue;
|
||||||
if (filter.type.startsWith('number') && isNaN(filter.value)) continue;
|
if (filter.type.startsWith("number") && isNaN(filter.value))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (filter.type === 'string') {
|
if (filter.type === "string") {
|
||||||
ary = ary.filter((ch) =>
|
|
||||||
ch[filter.key].toLowerCase().includes(filter.value.toLowerCase()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (filter.type === 'number-min') {
|
|
||||||
ary = ary.filter(
|
|
||||||
(ch) => Number(ch[filter.key]) >= Number(filter.value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (filter.type === 'number-max') {
|
|
||||||
ary = ary.filter(
|
|
||||||
(ch) => Number(ch[filter.key]) <= Number(filter.value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (filter.type === 'date-min') {
|
|
||||||
ary = ary.filter(
|
|
||||||
(ch) => Number(ch[filter.key]) >= Number(filter.value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (filter.type === 'date-max') {
|
|
||||||
ary = ary.filter(
|
|
||||||
(ch) => Number(ch[filter.key]) <= Number(filter.value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (filter.type === 'array') {
|
|
||||||
ary = ary.filter((ch) =>
|
ary = ary.filter((ch) =>
|
||||||
ch[filter.key]
|
ch[filter.key]
|
||||||
.map((s) => (typeof s === 'string' ? s.toLowerCase() : s))
|
.toLowerCase()
|
||||||
.includes(filter.value.toLowerCase()),
|
.includes(filter.value.toLowerCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (filter.type === "number-min") {
|
||||||
|
ary = ary.filter(
|
||||||
|
(ch) => Number(ch[filter.key]) >= Number(filter.value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (filter.type === "number-max") {
|
||||||
|
ary = ary.filter(
|
||||||
|
(ch) => Number(ch[filter.key]) <= Number(filter.value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (filter.type === "date-min") {
|
||||||
|
ary = ary.filter(
|
||||||
|
(ch) => Number(ch[filter.key]) >= Number(filter.value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (filter.type === "date-max") {
|
||||||
|
ary = ary.filter(
|
||||||
|
(ch) => Number(ch[filter.key]) <= Number(filter.value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (filter.type === "array") {
|
||||||
|
ary = ary.filter((ch) =>
|
||||||
|
ch[filter.key]
|
||||||
|
.map((s) =>
|
||||||
|
typeof s === "string" ? s.toLowerCase() : s
|
||||||
|
)
|
||||||
|
.includes(filter.value.toLowerCase())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('filtered size:', ary.length);
|
console.log("filtered size:", ary.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ary;
|
return ary;
|
||||||
@@ -290,58 +304,59 @@ const component = () => {
|
|||||||
if (!isNaN(a) && !isNaN(b)) return Number(a) - Number(b);
|
if (!isNaN(a) && !isNaN(b)) return Number(a) - Number(b);
|
||||||
|
|
||||||
const preprocessString = (val) => {
|
const preprocessString = (val) => {
|
||||||
if (typeof val !== 'string') return val;
|
if (typeof val !== "string") return val;
|
||||||
return val.toLowerCase().replace(/\s\s/g, ' ').trim();
|
return val.toLowerCase().replace(/\s\s/g, " ").trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
return preprocessString(a) > preprocessString(b) ? 1 : -1;
|
return preprocessString(a) > preprocessString(b) ? 1 : -1;
|
||||||
},
|
},
|
||||||
fieldType(values) {
|
fieldType(values) {
|
||||||
if (values.every((v) => this.numIsDate(v))) return 'date';
|
if (values.every((v) => this.numIsDate(v))) return "date";
|
||||||
if (values.every((v) => !isNaN(v))) return 'number';
|
if (values.every((v) => !isNaN(v))) return "number";
|
||||||
if (values.every((v) => Array.isArray(v))) return 'array';
|
if (values.every((v) => Array.isArray(v))) return "array";
|
||||||
return 'string';
|
return "string";
|
||||||
},
|
},
|
||||||
get filters() {
|
get filters() {
|
||||||
if (this.allChapters.length < 1) return [];
|
if (this.allChapters.length < 1) return [];
|
||||||
const keys = Object.keys(this.allChapters[0]).filter(
|
const keys = Object.keys(this.allChapters[0]).filter(
|
||||||
(k) => !['manga_title', 'id'].includes(k),
|
(k) => !["manga_title", "id"].includes(k)
|
||||||
);
|
);
|
||||||
return keys.map((k) => {
|
return keys.map((k) => {
|
||||||
let values = this.allChapters.map((c) => c[k]);
|
let values = this.allChapters.map((c) => c[k]);
|
||||||
const type = this.fieldType(values);
|
const type = this.fieldType(values);
|
||||||
|
|
||||||
if (type === 'array') {
|
if (type === "array") {
|
||||||
// if the type is an array, return the list of available elements
|
// if the type is an array, return the list of available elements
|
||||||
// example: an array of groups or authors
|
// example: an array of groups or authors
|
||||||
values = Array.from(
|
values = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
values.flat().map((v) => {
|
values.flat().map((v) => {
|
||||||
if (typeof v === 'string') return v.toLowerCase();
|
if (typeof v === "string")
|
||||||
}),
|
return v.toLowerCase();
|
||||||
),
|
})
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: k,
|
key: k,
|
||||||
type,
|
type: type,
|
||||||
values,
|
values: values,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
get filterSettings() {
|
get filterSettings() {
|
||||||
return $('#filter-form input:visible, #filter-form select:visible')
|
return $("#filter-form input:visible, #filter-form select:visible")
|
||||||
.get()
|
.get()
|
||||||
.map((i) => {
|
.map((i) => {
|
||||||
const type = i.getAttribute('data-filter-type');
|
const type = i.getAttribute("data-filter-type");
|
||||||
let value = i.value.trim();
|
let value = i.value.trim();
|
||||||
if (type.startsWith('date'))
|
if (type.startsWith("date"))
|
||||||
value = value ? Date.parse(value).toString() : '';
|
value = value ? Date.parse(value).toString() : "";
|
||||||
return {
|
return {
|
||||||
key: i.getAttribute('data-filter-key'),
|
key: i.getAttribute("data-filter-key"),
|
||||||
value,
|
value: value,
|
||||||
type,
|
type: type,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -351,23 +366,23 @@ const component = () => {
|
|||||||
this.sortOptions = [];
|
this.sortOptions = [];
|
||||||
},
|
},
|
||||||
clearFilters() {
|
clearFilters() {
|
||||||
$('#filter-form input')
|
$("#filter-form input")
|
||||||
.get()
|
.get()
|
||||||
.forEach((i) => (i.value = ''));
|
.forEach((i) => (i.value = ""));
|
||||||
$('#filter-form select').val('all');
|
$("#filter-form select").val("all");
|
||||||
this.appliedFilters = [];
|
this.appliedFilters = [];
|
||||||
this.chapters = this.filteredChapters;
|
this.chapters = this.filteredChapters;
|
||||||
this.sortOptions = [];
|
this.sortOptions = [];
|
||||||
},
|
},
|
||||||
mangaSelected(event) {
|
mangaSelected(event) {
|
||||||
const mid = event.currentTarget.getAttribute('data-id');
|
const mid = event.currentTarget.getAttribute("data-id");
|
||||||
this.mid = mid;
|
this.mid = mid;
|
||||||
this.searchChapters(mid);
|
this.searchChapters(mid);
|
||||||
},
|
},
|
||||||
subscribe(modal) {
|
subscribe(modal) {
|
||||||
this.subscribing = true;
|
this.subscribing = true;
|
||||||
fetch(`${base_url}api/admin/plugin/subscriptions`, {
|
fetch(`${base_url}api/admin/plugin/subscriptions`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
filters: this.filterSettings,
|
filters: this.filterSettings,
|
||||||
plugin: this.pid,
|
plugin: this.pid,
|
||||||
@@ -376,16 +391,16 @@ const component = () => {
|
|||||||
manga_id: this.mid,
|
manga_id: this.mid,
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.success) throw new Error(data.error);
|
if (!data.success) throw new Error(data.error);
|
||||||
alert('success', 'Subscription created');
|
alert("success", "Subscription created");
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert('danger', `Failed to subscribe. Error: ${e}`);
|
alert("danger", `Failed to subscribe. Error: ${e}`);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.subscribing = false;
|
this.subscribing = false;
|
||||||
@@ -397,12 +412,14 @@ const component = () => {
|
|||||||
},
|
},
|
||||||
renderCell(value) {
|
renderCell(value) {
|
||||||
if (this.numIsDate(value))
|
if (this.numIsDate(value))
|
||||||
return `<span>${moment(Number(value)).format('MMM D, YYYY')}</span>`;
|
return `<span>${moment(Number(value)).format(
|
||||||
|
"MMM D, YYYY"
|
||||||
|
)}</span>`;
|
||||||
const maxLength = 40;
|
const maxLength = 40;
|
||||||
if (value && value.length > maxLength)
|
if (value && value.length > maxLength)
|
||||||
return `<span>${value.substr(
|
return `<span>${value.substr(
|
||||||
0,
|
0,
|
||||||
maxLength,
|
maxLength
|
||||||
)}...</span><div uk-dropdown>${value}</div>`;
|
)}...</span><div uk-dropdown>${value}</div>`;
|
||||||
return `<span>${value}</span>`;
|
return `<span>${value}</span>`;
|
||||||
},
|
},
|
||||||
@@ -410,24 +427,24 @@ const component = () => {
|
|||||||
const key = ft.key;
|
const key = ft.key;
|
||||||
let type = ft.type;
|
let type = ft.type;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'number-min':
|
case "number-min":
|
||||||
type = 'number (minimum value)';
|
type = "number (minimum value)";
|
||||||
break;
|
break;
|
||||||
case 'number-max':
|
case "number-max":
|
||||||
type = 'number (maximum value)';
|
type = "number (maximum value)";
|
||||||
break;
|
break;
|
||||||
case 'date-min':
|
case "date-min":
|
||||||
type = 'minimum date';
|
type = "minimum date";
|
||||||
break;
|
break;
|
||||||
case 'date-max':
|
case "date-max":
|
||||||
type = 'maximum date';
|
type = "maximum date";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let value = ft.value;
|
let value = ft.value;
|
||||||
|
|
||||||
if (ft.type.startsWith('number') && isNaN(value)) value = '';
|
if (ft.type.startsWith("number") && isNaN(value)) value = "";
|
||||||
else if (ft.type.startsWith('date') && value)
|
else if (ft.type.startsWith("date") && value)
|
||||||
value = moment(Number(value)).format('MMM D, YYYY');
|
value = moment(Number(value)).format("MMM D, YYYY");
|
||||||
|
|
||||||
return `<td>${key}</td><td>${type}</td><td>${value}</td>`;
|
return `<td>${key}</td><td>${type}</td><td>${value}</td>`;
|
||||||
},
|
},
|
||||||
|
|||||||
+31
-40
@@ -21,24 +21,24 @@ const readerComponent = () => {
|
|||||||
*/
|
*/
|
||||||
init(nextTick) {
|
init(nextTick) {
|
||||||
$.get(`${base_url}api/dimensions/${tid}/${eid}`)
|
$.get(`${base_url}api/dimensions/${tid}/${eid}`)
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
if (!data.success && data.error) throw new Error(resp.error);
|
if (!data.success && data.error)
|
||||||
|
throw new Error(resp.error);
|
||||||
const dimensions = data.dimensions;
|
const dimensions = data.dimensions;
|
||||||
|
|
||||||
this.items = dimensions.map((d, i) => {
|
this.items = dimensions.map((d, i) => {
|
||||||
return {
|
return {
|
||||||
id: i + 1,
|
id: i + 1,
|
||||||
url: `${base_url}api/page/${tid}/${eid}/${i + 1}`,
|
url: `${base_url}api/page/${tid}/${eid}/${i+1}`,
|
||||||
width: d.width === 0 ? '100%' : d.width,
|
width: d.width == 0 ? "100%" : d.width,
|
||||||
height: d.height === 0 ? '100%' : d.height,
|
height: d.height == 0 ? "100%" : d.height,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: for image types not supported by image_size.cr, the width and height will be 0, and so `avgRatio` will be `Infinity`.
|
// Note: for image types not supported by image_size.cr, the width and height will be 0, and so `avgRatio` will be `Infinity`.
|
||||||
// TODO: support more image types in image_size.cr
|
// TODO: support more image types in image_size.cr
|
||||||
const avgRatio =
|
const avgRatio = dimensions.reduce((acc, cur) => {
|
||||||
dimensions.reduce((acc, cur) => {
|
return acc + cur.height / cur.width
|
||||||
return acc + cur.height / cur.width;
|
|
||||||
}, 0) / dimensions.length;
|
}, 0) / dimensions.length;
|
||||||
|
|
||||||
console.log(avgRatio);
|
console.log(avgRatio);
|
||||||
@@ -60,13 +60,8 @@ const readerComponent = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Preload Images
|
// Preload Images
|
||||||
this.preloadLookahead = +(
|
this.preloadLookahead = +(localStorage.getItem('preloadLookahead') ?? 3);
|
||||||
localStorage.getItem('preloadLookahead') ?? 3
|
const limit = Math.min(page + this.preloadLookahead, this.items.length);
|
||||||
);
|
|
||||||
const limit = Math.min(
|
|
||||||
page + this.preloadLookahead,
|
|
||||||
this.items.length,
|
|
||||||
);
|
|
||||||
for (let idx = page + 1; idx <= limit; idx++) {
|
for (let idx = page + 1; idx <= limit; idx++) {
|
||||||
this.preloadImage(this.items[idx - 1].url);
|
this.preloadImage(this.items[idx - 1].url);
|
||||||
}
|
}
|
||||||
@@ -76,31 +71,28 @@ const readerComponent = () => {
|
|||||||
this.fitType = savedFitType;
|
this.fitType = savedFitType;
|
||||||
$('#fit-select').val(savedFitType);
|
$('#fit-select').val(savedFitType);
|
||||||
}
|
}
|
||||||
const savedFlipAnimation = localStorage.getItem(
|
const savedFlipAnimation = localStorage.getItem('enableFlipAnimation');
|
||||||
'enableFlipAnimation',
|
this.enableFlipAnimation = savedFlipAnimation === null || savedFlipAnimation === 'true';
|
||||||
);
|
|
||||||
this.enableFlipAnimation =
|
|
||||||
savedFlipAnimation === null || savedFlipAnimation === 'true';
|
|
||||||
|
|
||||||
const savedRightToLeft = localStorage.getItem('enableRightToLeft');
|
const savedRightToLeft = localStorage.getItem('enableRightToLeft');
|
||||||
if (savedRightToLeft === null) {
|
if (savedRightToLeft === null) {
|
||||||
this.enableRightToLeft = false;
|
this.enableRightToLeft = false;
|
||||||
} else {
|
} else {
|
||||||
this.enableRightToLeft = savedRightToLeft === 'true';
|
this.enableRightToLeft = (savedRightToLeft === 'true');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch(e => {
|
||||||
const errMsg = `Failed to get the page dimensions. ${e}`;
|
const errMsg = `Failed to get the page dimensions. ${e}`;
|
||||||
console.error(e);
|
console.error(e);
|
||||||
this.alertClass = 'uk-alert-danger';
|
this.alertClass = 'uk-alert-danger';
|
||||||
this.msg = errMsg;
|
this.msg = errMsg;
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Preload an image, which is expected to be cached
|
* Preload an image, which is expected to be cached
|
||||||
*/
|
*/
|
||||||
preloadImage(url) {
|
preloadImage(url) {
|
||||||
new Image().src = url;
|
(new Image()).src = url;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Handles the `change` event for the page selector
|
* Handles the `change` event for the page selector
|
||||||
@@ -164,8 +156,10 @@ const readerComponent = () => {
|
|||||||
this.toPage(newIdx);
|
this.toPage(newIdx);
|
||||||
|
|
||||||
if (this.enableFlipAnimation) {
|
if (this.enableFlipAnimation) {
|
||||||
if (isNext ^ this.enableRightToLeft) this.flipAnimation = 'right';
|
if (isNext ^ this.enableRightToLeft)
|
||||||
else this.flipAnimation = 'left';
|
this.flipAnimation = 'right';
|
||||||
|
else
|
||||||
|
this.flipAnimation = 'left';
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -202,7 +196,7 @@ const readerComponent = () => {
|
|||||||
ary.unshift(window.location.origin);
|
ary.unshift(window.location.origin);
|
||||||
const url = ary.join('/');
|
const url = ary.join('/');
|
||||||
this.saveProgress(idx);
|
this.saveProgress(idx);
|
||||||
history.replaceState(null, '', url);
|
history.replaceState(null, "", url);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Updates the backend reading progress if:
|
* Updates the backend reading progress if:
|
||||||
@@ -217,25 +211,22 @@ const readerComponent = () => {
|
|||||||
*/
|
*/
|
||||||
saveProgress(idx, cb) {
|
saveProgress(idx, cb) {
|
||||||
idx = parseInt(idx);
|
idx = parseInt(idx);
|
||||||
if (
|
if (Math.abs(idx - this.lastSavedPage) >= 5 ||
|
||||||
Math.abs(idx - this.lastSavedPage) >= 5 ||
|
|
||||||
this.longPages ||
|
this.longPages ||
|
||||||
idx === 1 ||
|
idx === 1 || idx === this.items.length
|
||||||
idx === this.items.length
|
|
||||||
) {
|
) {
|
||||||
this.lastSavedPage = idx;
|
this.lastSavedPage = idx;
|
||||||
console.log('saving progress', idx);
|
console.log('saving progress', idx);
|
||||||
|
|
||||||
const url = `${base_url}api/progress/${tid}/${idx}?${$.param({
|
const url = `${base_url}api/progress/${tid}/${idx}?${$.param({eid: eid})}`;
|
||||||
eid,
|
|
||||||
})}`;
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
url,
|
url: url,
|
||||||
dataType: 'json',
|
dataType: 'json'
|
||||||
})
|
})
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (data.error) alert('danger', data.error);
|
if (data.error)
|
||||||
|
alert('danger', data.error);
|
||||||
if (cb) cb();
|
if (cb) cb();
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
@@ -350,7 +341,7 @@ const readerComponent = () => {
|
|||||||
this.toPage(this.selectedIndex);
|
this.toPage(this.selectedIndex);
|
||||||
},
|
},
|
||||||
|
|
||||||
fitChanged() {
|
fitChanged(){
|
||||||
this.fitType = $('#fit-select').val();
|
this.fitType = $('#fit-select').val();
|
||||||
localStorage.setItem('fitType', this.fitType);
|
localStorage.setItem('fitType', this.fitType);
|
||||||
},
|
},
|
||||||
@@ -367,4 +358,4 @@ const readerComponent = () => {
|
|||||||
localStorage.setItem('enableRightToLeft', this.enableRightToLeft);
|
localStorage.setItem('enableRightToLeft', this.enableRightToLeft);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|||||||
+14
-12
@@ -1,25 +1,27 @@
|
|||||||
$(function () {
|
$(function(){
|
||||||
let filter = [];
|
var filter = [];
|
||||||
let result = [];
|
var result = [];
|
||||||
$('.uk-card-title').each(function () {
|
$('.uk-card-title').each(function(){
|
||||||
filter.push($(this).text());
|
filter.push($(this).text());
|
||||||
});
|
});
|
||||||
$('.uk-search-input').keyup(function () {
|
$('.uk-search-input').keyup(function(){
|
||||||
let input = $('.uk-search-input').val();
|
var input = $('.uk-search-input').val();
|
||||||
let regex = new RegExp(input, 'i');
|
var regex = new RegExp(input, 'i');
|
||||||
|
|
||||||
if (input === '') {
|
if (input === '') {
|
||||||
$('.item').each(function () {
|
$('.item').each(function(){
|
||||||
$(this).removeAttr('hidden');
|
$(this).removeAttr('hidden');
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
filter.forEach(function (text, i) {
|
else {
|
||||||
|
filter.forEach(function(text, i){
|
||||||
result[i] = text.match(regex);
|
result[i] = text.match(regex);
|
||||||
});
|
});
|
||||||
$('.item').each(function (i) {
|
$('.item').each(function(i){
|
||||||
if (result[i]) {
|
if (result[i]) {
|
||||||
$(this).removeAttr('hidden');
|
$(this).removeAttr('hidden');
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
$(this).attr('hidden', '');
|
$(this).attr('hidden', '');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ $(() => {
|
|||||||
const url = `${location.protocol}//${location.host}${location.pathname}`;
|
const url = `${location.protocol}//${location.host}${location.pathname}`;
|
||||||
const newURL = `${url}?${$.param({
|
const newURL = `${url}?${$.param({
|
||||||
sort: by,
|
sort: by,
|
||||||
ascend: dir === 'up' ? 1 : 0,
|
ascend: dir === 'up' ? 1 : 0
|
||||||
})}`;
|
})}`;
|
||||||
window.location.href = newURL;
|
window.location.href = newURL;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,31 +13,36 @@ const component = () => {
|
|||||||
if (!data.success) throw new Error(data.error);
|
if (!data.success) throw new Error(data.error);
|
||||||
this.plugins = data.plugins;
|
this.plugins = data.plugins;
|
||||||
|
|
||||||
let pid = localStorage.getItem('plugin');
|
const pid = localStorage.getItem("plugin");
|
||||||
if (!pid || !this.plugins.find((p) => p.id === pid)) {
|
if (pid && this.plugins.map((p) => p.id).includes(pid))
|
||||||
pid = this.plugins[0].id;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pid = pid;
|
this.pid = pid;
|
||||||
|
else if (this.plugins.length > 0)
|
||||||
|
this.pid = this.plugins[0].id;
|
||||||
|
|
||||||
this.list(pid);
|
this.list(pid);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert('danger', `Failed to list the available plugins. Error: ${e}`);
|
alert(
|
||||||
|
"danger",
|
||||||
|
`Failed to list the available plugins. Error: ${e}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
pluginChanged() {
|
pluginChanged() {
|
||||||
localStorage.setItem('plugin', this.pid);
|
localStorage.setItem("plugin", this.pid);
|
||||||
this.list(this.pid);
|
this.list(this.pid);
|
||||||
},
|
},
|
||||||
list(pid) {
|
list(pid) {
|
||||||
if (!pid) return;
|
if (!pid) return;
|
||||||
fetch(
|
fetch(
|
||||||
`${base_url}api/admin/plugin/subscriptions?${new URLSearchParams({
|
`${base_url}api/admin/plugin/subscriptions?${new URLSearchParams(
|
||||||
plugin: pid,
|
|
||||||
})}`,
|
|
||||||
{
|
{
|
||||||
method: 'GET',
|
plugin: pid,
|
||||||
},
|
}
|
||||||
|
)}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@@ -45,7 +50,10 @@ const component = () => {
|
|||||||
this.subscriptions = data.subscriptions;
|
this.subscriptions = data.subscriptions;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert('danger', `Failed to list subscriptions. Error: ${e}`);
|
alert(
|
||||||
|
"danger",
|
||||||
|
`Failed to list subscriptions. Error: ${e}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
renderStrCell(str) {
|
renderStrCell(str) {
|
||||||
@@ -53,7 +61,7 @@ const component = () => {
|
|||||||
if (str.length > maxLength)
|
if (str.length > maxLength)
|
||||||
return `<td><span>${str.substring(
|
return `<td><span>${str.substring(
|
||||||
0,
|
0,
|
||||||
maxLength,
|
maxLength
|
||||||
)}...</span><div uk-dropdown>${str}</div></td>`;
|
)}...</span><div uk-dropdown>${str}</div></td>`;
|
||||||
return `<td>${str}</td>`;
|
return `<td>${str}</td>`;
|
||||||
},
|
},
|
||||||
@@ -63,7 +71,7 @@ const component = () => {
|
|||||||
.humanize(true)}</td>`;
|
.humanize(true)}</td>`;
|
||||||
},
|
},
|
||||||
selected(event, modal) {
|
selected(event, modal) {
|
||||||
const id = event.currentTarget.getAttribute('sid');
|
const id = event.currentTarget.getAttribute("sid");
|
||||||
this.subscription = this.subscriptions.find((s) => s.id === id);
|
this.subscription = this.subscriptions.find((s) => s.id === id);
|
||||||
UIkit.modal(modal).show();
|
UIkit.modal(modal).show();
|
||||||
},
|
},
|
||||||
@@ -71,41 +79,36 @@ const component = () => {
|
|||||||
const key = ft.key;
|
const key = ft.key;
|
||||||
let type = ft.type;
|
let type = ft.type;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'number-min':
|
case "number-min":
|
||||||
type = 'number (minimum value)';
|
type = "number (minimum value)";
|
||||||
break;
|
break;
|
||||||
case 'number-max':
|
case "number-max":
|
||||||
type = 'number (maximum value)';
|
type = "number (maximum value)";
|
||||||
break;
|
break;
|
||||||
case 'date-min':
|
case "date-min":
|
||||||
type = 'minimum date';
|
type = "minimum date";
|
||||||
break;
|
break;
|
||||||
case 'date-max':
|
case "date-max":
|
||||||
type = 'maximum date';
|
type = "maximum date";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let value = ft.value;
|
let value = ft.value;
|
||||||
|
|
||||||
if (ft.type.startsWith('number') && isNaN(value)) value = '';
|
if (ft.type.startsWith("number") && isNaN(value)) value = "";
|
||||||
else if (ft.type.startsWith('date') && value)
|
else if (ft.type.startsWith("date") && value)
|
||||||
value = moment(Number(value)).format('MMM D, YYYY');
|
value = moment(Number(value)).format("MMM D, YYYY");
|
||||||
|
|
||||||
return `<td>${key}</td><td>${type}</td><td>${value}</td>`;
|
return `<td>${key}</td><td>${type}</td><td>${value}</td>`;
|
||||||
},
|
},
|
||||||
actionHandler(event, type) {
|
actionHandler(event, type) {
|
||||||
const id = $(event.currentTarget).closest('tr').attr('sid');
|
const id = $(event.currentTarget).closest("tr").attr("sid");
|
||||||
if (type !== 'delete') return this.action(id, type);
|
if (type !== 'delete') return this.action(id, type);
|
||||||
UIkit.modal
|
UIkit.modal.confirm('Are you sure you want to delete the subscription? This cannot be undone.', {
|
||||||
.confirm(
|
|
||||||
'Are you sure you want to delete the subscription? This cannot be undone.',
|
|
||||||
{
|
|
||||||
labels: {
|
labels: {
|
||||||
ok: 'Yes, delete it',
|
ok: 'Yes, delete it',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel'
|
||||||
},
|
}
|
||||||
},
|
}).then(() => {
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
this.action(id, type);
|
this.action(id, type);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -113,27 +116,27 @@ const component = () => {
|
|||||||
if (this.loading) return;
|
if (this.loading) return;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
fetch(
|
fetch(
|
||||||
`${base_url}api/admin/plugin/subscriptions${
|
`${base_url}api/admin/plugin/subscriptions${type === 'update' ? '/update' : ''}?${new URLSearchParams(
|
||||||
type === 'update' ? '/update' : ''
|
{
|
||||||
}?${new URLSearchParams({
|
|
||||||
plugin: this.pid,
|
plugin: this.pid,
|
||||||
subscription: id,
|
subscription: id,
|
||||||
})}`,
|
}
|
||||||
|
)}`,
|
||||||
{
|
{
|
||||||
method: type === 'delete' ? 'DELETE' : 'POST',
|
method: type === 'delete' ? "DELETE" : 'POST'
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.success) throw new Error(data.error);
|
if (!data.success) throw new Error(data.error);
|
||||||
if (type === 'update')
|
if (type === 'update')
|
||||||
alert(
|
alert("success", `Checking updates for subscription ${id}. Check the log for the progress or come back to this page later.`);
|
||||||
'success',
|
|
||||||
`Checking updates for subscription ${id}. Check the log for the progress or come back to this page later.`,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert('danger', `Failed to ${type} subscription. Error: ${e}`);
|
alert(
|
||||||
|
"danger",
|
||||||
|
`Failed to ${type} subscription. Error: ${e}`
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|||||||
+18
-48
@@ -7,45 +7,30 @@ const component = () => {
|
|||||||
$.getJSON(`${base_url}api/admin/mangadex/expires`)
|
$.getJSON(`${base_url}api/admin/mangadex/expires`)
|
||||||
.done((data) => {
|
.done((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert(
|
alert('danger', 'Failed to check MangaDex integration status. Error: ' + data.error);
|
||||||
'danger',
|
|
||||||
'Failed to check MangaDex integration status. Error: ' +
|
|
||||||
data.error,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.available = Boolean(
|
this.available = Boolean(data.expires && data.expires > Math.floor(Date.now() / 1000));
|
||||||
data.expires && data.expires > Math.floor(Date.now() / 1000),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.available) this.getSubscriptions();
|
if (this.available) this.getSubscriptions();
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to check MangaDex integration status. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
})
|
||||||
`Failed to check MangaDex integration status. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getSubscriptions() {
|
getSubscriptions() {
|
||||||
$.getJSON(`${base_url}api/admin/mangadex/subscriptions`)
|
$.getJSON(`${base_url}api/admin/mangadex/subscriptions`)
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert(
|
alert('danger', 'Failed to get subscriptions. Error: ' + data.error);
|
||||||
'danger',
|
|
||||||
'Failed to get subscriptions. Error: ' + data.error,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.subscriptions = data.subscriptions;
|
this.subscriptions = data.subscriptions;
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to get subscriptions. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
})
|
||||||
`Failed to get subscriptions. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
rm(event) {
|
rm(event) {
|
||||||
@@ -53,22 +38,16 @@ const component = () => {
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
url: `${base_url}api/admin/mangadex/subscriptions/${id}`,
|
url: `${base_url}api/admin/mangadex/subscriptions/${id}`,
|
||||||
contentType: 'application/json',
|
contentType: 'application/json'
|
||||||
})
|
})
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert(
|
alert('danger', `Failed to delete subscription. Error: ${data.error}`);
|
||||||
'danger',
|
|
||||||
`Failed to delete subscription. Error: ${data.error}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this.getSubscriptions();
|
this.getSubscriptions();
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to delete subscription. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
|
||||||
`Failed to delete subscription. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -77,26 +56,17 @@ const component = () => {
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: `${base_url}api/admin/mangadex/subscriptions/check/${id}`,
|
url: `${base_url}api/admin/mangadex/subscriptions/check/${id}`,
|
||||||
contentType: 'application/json',
|
contentType: 'application/json'
|
||||||
})
|
})
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert(
|
alert('danger', `Failed to check subscription. Error: ${data.error}`);
|
||||||
'danger',
|
|
||||||
`Failed to check subscription. Error: ${data.error}`,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
alert(
|
alert('success', 'Mango is now checking the subscription for updates. This might take a while, but you can safely leave the page.');
|
||||||
'success',
|
|
||||||
'Mango is now checking the subscription for updates. This might take a while, but you can safely leave the page.',
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to check subscription. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
|
||||||
`Failed to check subscription. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -107,6 +77,6 @@ const component = () => {
|
|||||||
|
|
||||||
if (min === max) return `= ${min}`;
|
if (min === max) return `= ${min}`;
|
||||||
return `${min} - ${max}`;
|
return `${min} - ${max}`;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+53
-82
@@ -14,24 +14,16 @@ const setupAcard = () => {
|
|||||||
$(card).attr('data-encoded-book-title'),
|
$(card).attr('data-encoded-book-title'),
|
||||||
$(card).attr('data-encoded-title'),
|
$(card).attr('data-encoded-title'),
|
||||||
$(card).attr('data-book-id'),
|
$(card).attr('data-book-id'),
|
||||||
$(card).attr('data-id'),
|
$(card).attr('data-id')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function showModal(
|
function showModal(encodedPath, pages, percentage, encodedeTitle, encodedEntryTitle, titleID, entryID) {
|
||||||
encodedPath,
|
|
||||||
pages,
|
|
||||||
percentage,
|
|
||||||
encodedeTitle,
|
|
||||||
encodedEntryTitle,
|
|
||||||
titleID,
|
|
||||||
entryID,
|
|
||||||
) {
|
|
||||||
const zipPath = decodeURIComponent(encodedPath);
|
const zipPath = decodeURIComponent(encodedPath);
|
||||||
const title = decodeURIComponent(encodedeTitle);
|
const title = decodeURIComponent(encodedeTitle);
|
||||||
const entry = decodeURIComponent(encodedEntryTitle);
|
const entry = decodeURIComponent(encodedEntryTitle);
|
||||||
$('#modal button, #modal a').each(function () {
|
$('#modal button, #modal a').each(function() {
|
||||||
$(this).removeAttr('hidden');
|
$(this).removeAttr('hidden');
|
||||||
});
|
});
|
||||||
if (percentage === 0) {
|
if (percentage === 0) {
|
||||||
@@ -54,19 +46,16 @@ function showModal(
|
|||||||
$('#beginning-btn').attr('href', `${base_url}reader/${titleID}/${entryID}/1`);
|
$('#beginning-btn').attr('href', `${base_url}reader/${titleID}/${entryID}/1`);
|
||||||
$('#continue-btn').attr('href', `${base_url}reader/${titleID}/${entryID}`);
|
$('#continue-btn').attr('href', `${base_url}reader/${titleID}/${entryID}`);
|
||||||
|
|
||||||
$('#read-btn').click(function () {
|
$('#read-btn').click(function() {
|
||||||
updateProgress(titleID, entryID, pages);
|
updateProgress(titleID, entryID, pages);
|
||||||
});
|
});
|
||||||
$('#unread-btn').click(function () {
|
$('#unread-btn').click(function() {
|
||||||
updateProgress(titleID, entryID, 0);
|
updateProgress(titleID, entryID, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#modal-edit-btn').attr('onclick', `edit("${entryID}")`);
|
$('#modal-edit-btn').attr('onclick', `edit("${entryID}")`);
|
||||||
|
|
||||||
$('#modal-download-btn').attr(
|
$('#modal-download-btn').attr('href', `${base_url}api/download/${titleID}/${entryID}`);
|
||||||
'href',
|
|
||||||
`${base_url}api/download/${titleID}/${entryID}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
UIkit.modal($('#modal')).show();
|
UIkit.modal($('#modal')).show();
|
||||||
}
|
}
|
||||||
@@ -77,18 +66,19 @@ UIkit.util.on(document, 'hidden', '#modal', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updateProgress = (tid, eid, page) => {
|
const updateProgress = (tid, eid, page) => {
|
||||||
let url = `${base_url}api/progress/${tid}/${page}`;
|
let url = `${base_url}api/progress/${tid}/${page}`
|
||||||
const query = $.param({
|
const query = $.param({
|
||||||
eid,
|
eid: eid
|
||||||
});
|
});
|
||||||
if (eid) url += `?${query}`;
|
if (eid)
|
||||||
|
url += `?${query}`;
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
url,
|
url: url,
|
||||||
dataType: 'json',
|
dataType: 'json'
|
||||||
})
|
})
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
location.reload();
|
location.reload();
|
||||||
} else {
|
} else {
|
||||||
@@ -111,18 +101,19 @@ const renameSubmit = (name, eid) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const query = $.param({
|
const query = $.param({
|
||||||
eid,
|
eid: eid
|
||||||
});
|
});
|
||||||
let url = `${base_url}api/admin/display_name/${titleId}/${name}`;
|
let url = `${base_url}api/admin/display_name/${titleId}/${name}`;
|
||||||
if (eid) url += `?${query}`;
|
if (eid)
|
||||||
|
url += `?${query}`;
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
url,
|
url: url,
|
||||||
contentType: 'application/json',
|
contentType: "application/json",
|
||||||
dataType: 'json',
|
dataType: 'json'
|
||||||
})
|
})
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert('danger', `Failed to update display name. Error: ${data.error}`);
|
alert('danger', `Failed to update display name. Error: ${data.error}`);
|
||||||
return;
|
return;
|
||||||
@@ -130,10 +121,7 @@ const renameSubmit = (name, eid) => {
|
|||||||
location.reload();
|
location.reload();
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to update display name. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
|
||||||
`Failed to update display name. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -151,9 +139,9 @@ const renameSortNameSubmit = (name, eid) => {
|
|||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
url,
|
url,
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
dataType: 'json',
|
dataType: 'json'
|
||||||
})
|
})
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert('danger', `Failed to update sort title. Error: ${data.error}`);
|
alert('danger', `Failed to update sort title. Error: ${data.error}`);
|
||||||
return;
|
return;
|
||||||
@@ -161,10 +149,7 @@ const renameSortNameSubmit = (name, eid) => {
|
|||||||
location.reload();
|
location.reload();
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to update sort title. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
|
||||||
`Failed to update sort title. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -191,7 +176,7 @@ const edit = (eid) => {
|
|||||||
const displayNameField = $('#display-name-field');
|
const displayNameField = $('#display-name-field');
|
||||||
displayNameField.attr('value', displayName);
|
displayNameField.attr('value', displayName);
|
||||||
displayNameField.attr('placeholder', fileTitle);
|
displayNameField.attr('placeholder', fileTitle);
|
||||||
displayNameField.keyup((event) => {
|
displayNameField.keyup(event => {
|
||||||
if (event.keyCode === 13) {
|
if (event.keyCode === 13) {
|
||||||
renameSubmit(displayNameField.val() || fileTitle, eid);
|
renameSubmit(displayNameField.val() || fileTitle, eid);
|
||||||
}
|
}
|
||||||
@@ -203,7 +188,7 @@ const edit = (eid) => {
|
|||||||
const sortTitleField = $('#sort-title-field');
|
const sortTitleField = $('#sort-title-field');
|
||||||
sortTitleField.val(sortTitle);
|
sortTitleField.val(sortTitle);
|
||||||
sortTitleField.attr('placeholder', fileTitle);
|
sortTitleField.attr('placeholder', fileTitle);
|
||||||
sortTitleField.keyup((event) => {
|
sortTitleField.keyup(event => {
|
||||||
if (event.keyCode === 13) {
|
if (event.keyCode === 13) {
|
||||||
renameSortNameSubmit(sortTitleField.val(), eid);
|
renameSortNameSubmit(sortTitleField.val(), eid);
|
||||||
}
|
}
|
||||||
@@ -232,13 +217,14 @@ const setupUpload = (eid) => {
|
|||||||
const bar = $('#upload-progress').get(0);
|
const bar = $('#upload-progress').get(0);
|
||||||
const titleId = upload.attr('data-title-id');
|
const titleId = upload.attr('data-title-id');
|
||||||
const queryObj = {
|
const queryObj = {
|
||||||
tid: titleId,
|
tid: titleId
|
||||||
};
|
};
|
||||||
if (eid) queryObj['eid'] = eid;
|
if (eid)
|
||||||
|
queryObj['eid'] = eid;
|
||||||
const query = $.param(queryObj);
|
const query = $.param(queryObj);
|
||||||
const url = `${base_url}api/admin/upload/cover?${query}`;
|
const url = `${base_url}api/admin/upload/cover?${query}`;
|
||||||
UIkit.upload('.upload-field', {
|
UIkit.upload('.upload-field', {
|
||||||
url,
|
url: url,
|
||||||
name: 'file',
|
name: 'file',
|
||||||
error: (e) => {
|
error: (e) => {
|
||||||
alert('danger', `Failed to upload cover image: ${e.toString()}`);
|
alert('danger', `Failed to upload cover image: ${e.toString()}`);
|
||||||
@@ -259,7 +245,7 @@ const setupUpload = (eid) => {
|
|||||||
completeAll: () => {
|
completeAll: () => {
|
||||||
$(bar).attr('hidden', '');
|
$(bar).attr('hidden', '');
|
||||||
location.reload();
|
location.reload();
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -301,28 +287,22 @@ const bulkProgress = (action, el) => {
|
|||||||
const url = `${base_url}api/bulk_progress/${action}/${tid}`;
|
const url = `${base_url}api/bulk_progress/${action}/${tid}`;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
url,
|
url: url,
|
||||||
contentType: 'application/json',
|
contentType: "application/json",
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
ids,
|
ids: ids
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
.done((data) => {
|
})
|
||||||
|
.done(data => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert(
|
alert('danger', `Failed to mark entries as ${action}. Error: ${data.error}`);
|
||||||
'danger',
|
|
||||||
`Failed to mark entries as ${action}. Error: ${data.error}`,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
location.reload();
|
location.reload();
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to mark entries as ${action}. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
|
||||||
`Failed to mark entries as ${action}. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.always(() => {
|
.always(() => {
|
||||||
deselectAll();
|
deselectAll();
|
||||||
@@ -345,32 +325,29 @@ const tagsComponent = () => {
|
|||||||
disabled: !this.isAdmin,
|
disabled: !this.isAdmin,
|
||||||
templateSelection(state) {
|
templateSelection(state) {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.setAttribute(
|
a.setAttribute('href', `${base_url}tags/${encodeURIComponent(state.text)}`);
|
||||||
'href',
|
|
||||||
`${base_url}tags/${encodeURIComponent(state.text)}`,
|
|
||||||
);
|
|
||||||
a.setAttribute('class', 'uk-link-reset');
|
a.setAttribute('class', 'uk-link-reset');
|
||||||
a.onclick = (event) => {
|
a.onclick = event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
};
|
};
|
||||||
a.innerText = state.text;
|
a.innerText = state.text;
|
||||||
return a;
|
return a;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.request(`${base_url}api/tags`, 'GET', (data) => {
|
this.request(`${base_url}api/tags`, 'GET', (data) => {
|
||||||
const allTags = data.tags;
|
const allTags = data.tags;
|
||||||
const url = `${base_url}api/tags/${this.tid}`;
|
const url = `${base_url}api/tags/${this.tid}`;
|
||||||
this.request(url, 'GET', (data) => {
|
this.request(url, 'GET', data => {
|
||||||
this.tags = data.tags;
|
this.tags = data.tags;
|
||||||
allTags.forEach((t) => {
|
allTags.forEach(t => {
|
||||||
const op = new Option(t, t, false, this.tags.indexOf(t) >= 0);
|
const op = new Option(t, t, false, this.tags.indexOf(t) >= 0);
|
||||||
$('.tag-select').append(op);
|
$('.tag-select').append(op);
|
||||||
});
|
});
|
||||||
$('.tag-select').on('select2:select', (e) => {
|
$('.tag-select').on('select2:select', e => {
|
||||||
this.onAdd(e);
|
this.onAdd(e);
|
||||||
});
|
});
|
||||||
$('.tag-select').on('select2:unselect', (e) => {
|
$('.tag-select').on('select2:unselect', e => {
|
||||||
this.onDelete(e);
|
this.onDelete(e);
|
||||||
});
|
});
|
||||||
$('.tag-select').on('change', () => {
|
$('.tag-select').on('change', () => {
|
||||||
@@ -382,31 +359,25 @@ const tagsComponent = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onChange() {
|
onChange() {
|
||||||
this.tags = $('.tag-select')
|
this.tags = $('.tag-select').select2('data').map(o => o.text);
|
||||||
.select2('data')
|
|
||||||
.map((o) => o.text);
|
|
||||||
},
|
},
|
||||||
onAdd(event) {
|
onAdd(event) {
|
||||||
const tag = event.params.data.text;
|
const tag = event.params.data.text;
|
||||||
const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(
|
const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(tag)}`;
|
||||||
tag,
|
|
||||||
)}`;
|
|
||||||
this.request(url, 'PUT');
|
this.request(url, 'PUT');
|
||||||
},
|
},
|
||||||
onDelete(event) {
|
onDelete(event) {
|
||||||
const tag = event.params.data.text;
|
const tag = event.params.data.text;
|
||||||
const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(
|
const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(tag)}`;
|
||||||
tag,
|
|
||||||
)}`;
|
|
||||||
this.request(url, 'DELETE');
|
this.request(url, 'DELETE');
|
||||||
},
|
},
|
||||||
request(url, method, cb) {
|
request(url, method, cb) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url,
|
url: url,
|
||||||
method,
|
method: method,
|
||||||
dataType: 'json',
|
dataType: 'json'
|
||||||
})
|
})
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
if (cb) cb(data);
|
if (cb) cb(data);
|
||||||
} else {
|
} else {
|
||||||
@@ -416,6 +387,6 @@ const tagsComponent = () => {
|
|||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert('danger', `Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
alert('danger', `Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$(() => {
|
$(() => {
|
||||||
let target = base_url + 'admin/user/edit';
|
var target = base_url + 'admin/user/edit';
|
||||||
if (username) target += username;
|
if (username) target += username;
|
||||||
$('form').attr('action', target);
|
$('form').attr('action', target);
|
||||||
if (error) alert('danger', error);
|
if (error) alert('danger', error);
|
||||||
|
|||||||
+7
-8
@@ -2,16 +2,15 @@ const remove = (username) => {
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
url: `${base_url}api/admin/user/delete/${username}`,
|
url: `${base_url}api/admin/user/delete/${username}`,
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
dataType: 'json',
|
dataType: 'json'
|
||||||
})
|
})
|
||||||
.done((data) => {
|
.done(data => {
|
||||||
if (data.success) location.reload();
|
if (data.success)
|
||||||
else alert('danger', data.error);
|
location.reload();
|
||||||
|
else
|
||||||
|
alert('danger', data.error);
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert(
|
alert('danger', `Failed to delete the user. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
'danger',
|
|
||||||
`Failed to delete the user. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ class Config
|
|||||||
"session_secret" => "mango-session-secret",
|
"session_secret" => "mango-session-secret",
|
||||||
"library_path" => "~/mango/library",
|
"library_path" => "~/mango/library",
|
||||||
"library_cache_path" => "~/mango/library.yml.gz",
|
"library_cache_path" => "~/mango/library.yml.gz",
|
||||||
"db_path" => "~/mango/mango.db",
|
"db_path" => "~/mango.db",
|
||||||
"queue_db_path" => "~/mango/queue.db",
|
"queue_db_path" => "~/mango/queue.db",
|
||||||
"scan_interval_minutes" => 5,
|
"scan_interval_minutes" => 5,
|
||||||
"thumbnail_generation_interval_hours" => 24,
|
"thumbnail_generation_interval_hours" => 24,
|
||||||
|
|||||||
+1
-1
@@ -184,7 +184,7 @@ def delete_cache_and_exit(path : String)
|
|||||||
File.delete path
|
File.delete path
|
||||||
Logger.fatal "Invalid library cache deleted. Mango needs to " \
|
Logger.fatal "Invalid library cache deleted. Mango needs to " \
|
||||||
"perform a full reset to recover from this. " \
|
"perform a full reset to recover from this. " \
|
||||||
"Please restart Mango. This is NOT a bug."
|
"Pleae restart Mango. This is NOT a bug."
|
||||||
Logger.fatal "Exiting"
|
Logger.fatal "Exiting"
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user