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