Compare commits

..

12 Commits

Author SHA1 Message Date
Alex Ling 6f39c2a74c Restrict table selection to id seletable
fixes #335
2022-09-09 13:47:30 +00:00
Alex Ling 61d6c2e1d9 Fix incorrect default DB path 2022-09-05 03:18:44 +00:00
Alex Ling ce559984e6 Fix uikit version to ~3.14.0 (fixes #334) 2022-09-05 03:17:36 +00:00
Alex Ling 76b4666708 Merge pull request #331 from getmango/feature/eslint
Add ESLint
2022-08-19 20:31:58 +08:00
Alex Ling 5bdeca94fe Merge dev 2022-08-19 12:12:45 +00:00
Alex Ling f8c569f204 Merge branch 'dev' into feature/eslint 2022-08-19 12:11:00 +00:00
Alex Ling 7ef2e4d162 Add eslint to make check 2022-08-19 12:06:40 +00:00
Alex Ling 28c098a56e Add eslint and style fix 2022-08-19 12:05:38 +00:00
Alex Ling 2597b4ce60 Merge pull request #330 from getmango/fix/subscription-manager-single-plugin
Correctly load subscriptions when there's only one plugin
2022-08-19 19:32:49 +08:00
Alex Ling cd3ee0728c Handle the case where only one plugin is installed
fixes #329
2022-08-19 11:07:45 +00:00
Alex Ling e4af194d0c Merge pull request #328 from dudeitssm/patch
Fix typo: "Pleae" should be "Please"
2022-08-10 20:54:11 +08:00
dudeitssm 586ebf8dc8 Fix typo: "Pleae" should be "Please" 2022-08-01 00:06:31 +00:00
23 changed files with 1800 additions and 1683 deletions
+11
View 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
View File
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80,
"tabWidth": 2
}
+1
View File
@@ -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
-3
View File
@@ -4,9 +4,6 @@
[![Patreon](https://img.shields.io/badge/support-patreon-brightgreen?link=https://www.patreon.com/hkalexling)](https://www.patreon.com/hkalexling) ![Build](https://github.com/hkalexling/Mango/workflows/Build/badge.svg) [![Gitter](https://badges.gitter.im/mango-cr/mango.svg)](https://gitter.im/mango-cr/mango?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Discord](https://img.shields.io/discord/855633663425118228?label=discord)](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
- Multi-user support
+36 -21
View File
@@ -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'));
});
+8 -3
View File
@@ -6,20 +6,25 @@
"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",
"uikit": "^3.5.4"
"uikit": "~3.14.0"
}
}
+5 -7
View File
@@ -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;
});
+1 -1
View File
@@ -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 });
};
+8 -4
View File
@@ -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');
});
+1 -1
View File
@@ -14,7 +14,7 @@ const truncate = (e) => {
} else {
$(e).removeAttr('uk-tooltip');
}
}
},
});
};
+40 -21
View File
@@ -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;
}
},
};
};
+28 -14
View File
@@ -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}`,
);
});
}
},
};
};
+106 -123
View File
@@ -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#selectable > tr').each((i, e) => {
$(e).addClass('ui-selected');
});
},
clearSelection() {
$("tbody > tr").each((i, e) => {
$(e).removeClass("ui-selected");
$('tbody#selectable > tr').each((i, e) => {
$(e).removeClass('ui-selected');
});
},
download() {
const selected = $("tbody > tr.ui-selected").get();
const selected = $('tbody#selectable > 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>`;
},
+40 -31
View File
@@ -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,
url: `${base_url}api/page/${tid}/${eid}/${i + 1}`,
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) => {
@@ -341,7 +350,7 @@ const readerComponent = () => {
this.toPage(this.selectedIndex);
},
fitChanged(){
fitChanged() {
this.fitType = $('#fit-select').val();
localStorage.setItem('fitType', this.fitType);
},
@@ -358,4 +367,4 @@ const readerComponent = () => {
localStorage.setItem('enableRightToLeft', this.enableRightToLeft);
},
};
}
};
+12 -14
View File
@@ -1,27 +1,25 @@
$(function(){
var filter = [];
var result = [];
$('.uk-card-title').each(function(){
$(function () {
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');
$('.uk-search-input').keyup(function () {
let input = $('.uk-search-input').val();
let regex = new RegExp(input, 'i');
if (input === '') {
$('.item').each(function(){
$('.item').each(function () {
$(this).removeAttr('hidden');
});
}
else {
filter.forEach(function(text, i){
} else {
filter.forEach(function (text, i) {
result[i] = text.match(regex);
});
$('.item').each(function(i){
$('.item').each(function (i) {
if (result[i]) {
$(this).removeAttr('hidden');
}
else {
} else {
$(this).attr('hidden', '');
}
});
+1 -1
View File
@@ -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;
});
+46 -49
View File
@@ -13,36 +13,31 @@ const component = () => {
if (!data.success) throw new Error(data.error);
this.plugins = data.plugins;
const pid = localStorage.getItem("plugin");
if (pid && this.plugins.map((p) => p.id).includes(pid))
this.pid = pid;
else if (this.plugins.length > 0)
this.pid = this.plugins[0].id;
let pid = localStorage.getItem('plugin');
if (!pid || !this.plugins.find((p) => p.id === pid)) {
pid = this.plugins[0].id;
}
this.pid = pid;
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;
+48 -18
View File
@@ -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}`;
}
},
};
};
+82 -53
View File
@@ -14,16 +14,24 @@ 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);
$('#modal button, #modal a').each(function() {
$('#modal button, #modal a').each(function () {
$(this).removeAttr('hidden');
});
if (percentage === 0) {
@@ -46,16 +54,19 @@ function showModal(encodedPath, pages, percentage, encodedeTitle, encodedEntryTi
$('#beginning-btn').attr('href', `${base_url}reader/${titleID}/${entryID}/1`);
$('#continue-btn').attr('href', `${base_url}reader/${titleID}/${entryID}`);
$('#read-btn').click(function() {
$('#read-btn').click(function () {
updateProgress(titleID, entryID, pages);
});
$('#unread-btn').click(function() {
$('#unread-btn').click(function () {
updateProgress(titleID, entryID, 0);
});
$('#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 -1
View File
@@ -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);
+8 -7
View File
@@ -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}`,
);
});
};
+1 -1
View File
@@ -8,7 +8,7 @@ class Config
"session_secret" => "mango-session-secret",
"library_path" => "~/mango/library",
"library_cache_path" => "~/mango/library.yml.gz",
"db_path" => "~/mango.db",
"db_path" => "~/mango/mango.db",
"queue_db_path" => "~/mango/queue.db",
"scan_interval_minutes" => 5,
"thumbnail_generation_interval_hours" => 24,
+1 -1
View File
@@ -184,7 +184,7 @@ def delete_cache_and_exit(path : String)
File.delete path
Logger.fatal "Invalid library cache deleted. Mango needs to " \
"perform a full reset to recover from this. " \
"Pleae restart Mango. This is NOT a bug."
"Please restart Mango. This is NOT a bug."
Logger.fatal "Exiting"
exit 1
end