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:
|
check:
|
||||||
crystal tool format --check
|
crystal tool format --check
|
||||||
./bin/ameba
|
./bin/ameba
|
||||||
|
yarn lint
|
||||||
|
|
||||||
arm32v7:
|
arm32v7:
|
||||||
crystal build src/mango.cr --release --progress --error-trace --cross-compile --target='arm-linux-gnueabihf' -o mango-arm32v7
|
crystal build src/mango.cr --release --progress --error-trace --cross-compile --target='arm-linux-gnueabihf' -o mango-arm32v7
|
||||||
|
85
gulpfile.js
85
gulpfile.js
@ -5,13 +5,17 @@ const minifyCss = require('gulp-minify-css');
|
|||||||
const less = require('gulp-less');
|
const less = require('gulp-less');
|
||||||
|
|
||||||
gulp.task('copy-img', () => {
|
gulp.task('copy-img', () => {
|
||||||
return gulp.src('node_modules/uikit/src/images/backgrounds/*.svg')
|
return gulp
|
||||||
.pipe(gulp.dest('public/img'));
|
.src('node_modules/uikit/src/images/backgrounds/*.svg')
|
||||||
|
.pipe(gulp.dest('public/img'));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('copy-font', () => {
|
gulp.task('copy-font', () => {
|
||||||
return gulp.src('node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff**')
|
return gulp
|
||||||
.pipe(gulp.dest('public/webfonts'));
|
.src(
|
||||||
|
'node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff**',
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('public/webfonts'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy files from node_modules
|
// Copy files from node_modules
|
||||||
@ -19,49 +23,60 @@ gulp.task('node-modules-copy', gulp.parallel('copy-img', 'copy-font'));
|
|||||||
|
|
||||||
// Compile less
|
// Compile less
|
||||||
gulp.task('less', () => {
|
gulp.task('less', () => {
|
||||||
return gulp.src([
|
return gulp
|
||||||
'public/css/mango.less',
|
.src(['public/css/mango.less', 'public/css/tags.less'])
|
||||||
'public/css/tags.less'
|
.pipe(less())
|
||||||
])
|
.pipe(gulp.dest('public/css'));
|
||||||
.pipe(less())
|
|
||||||
.pipe(gulp.dest('public/css'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Transpile and minify JS files and output to dist
|
// Transpile and minify JS files and output to dist
|
||||||
gulp.task('babel', () => {
|
gulp.task('babel', () => {
|
||||||
return gulp.src(['public/js/*.js', '!public/js/*.min.js'])
|
return gulp
|
||||||
.pipe(babel({
|
.src(['public/js/*.js', '!public/js/*.min.js'])
|
||||||
presets: [
|
.pipe(
|
||||||
['@babel/preset-env', {
|
babel({
|
||||||
targets: '>0.25%, not dead, ios>=9'
|
presets: [
|
||||||
}]
|
[
|
||||||
],
|
'@babel/preset-env',
|
||||||
}))
|
{
|
||||||
.pipe(minify({
|
targets: '>0.25%, not dead, ios>=9',
|
||||||
removeConsole: true,
|
},
|
||||||
builtIns: false
|
],
|
||||||
}))
|
],
|
||||||
.pipe(gulp.dest('dist/js'));
|
}),
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
minify({
|
||||||
|
removeConsole: true,
|
||||||
|
builtIns: false,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist/js'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Minify CSS and output to dist
|
// Minify CSS and output to dist
|
||||||
gulp.task('minify-css', () => {
|
gulp.task('minify-css', () => {
|
||||||
return gulp.src('public/css/*.css')
|
return gulp
|
||||||
.pipe(minifyCss())
|
.src('public/css/*.css')
|
||||||
.pipe(gulp.dest('dist/css'));
|
.pipe(minifyCss())
|
||||||
|
.pipe(gulp.dest('dist/css'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy static files (includeing images) to dist
|
// Copy static files (includeing images) to dist
|
||||||
gulp.task('copy-files', () => {
|
gulp.task('copy-files', () => {
|
||||||
return gulp.src([
|
return gulp
|
||||||
'public/*.*',
|
.src(
|
||||||
'public/img/**',
|
[
|
||||||
'public/webfonts/*',
|
'public/*.*',
|
||||||
'public/js/*.min.js'
|
'public/img/**',
|
||||||
], {
|
'public/webfonts/*',
|
||||||
base: 'public'
|
'public/js/*.min.js',
|
||||||
})
|
],
|
||||||
.pipe(gulp.dest('dist'));
|
{
|
||||||
|
base: 'public',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest('dist'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up the public folder for development
|
// Set up the public folder for development
|
||||||
|
@ -6,17 +6,22 @@
|
|||||||
"author": "Alex Ling <hkalexling@gmail.com>",
|
"author": "Alex Ling <hkalexling@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/eslint-parser": "^7.18.9",
|
||||||
"@babel/preset-env": "^7.11.5",
|
"@babel/preset-env": "^7.11.5",
|
||||||
"all-contributors-cli": "^6.19.0",
|
"all-contributors-cli": "^6.19.0",
|
||||||
|
"eslint": "^8.22.0",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-babel": "^8.0.0",
|
"gulp-babel": "^8.0.0",
|
||||||
"gulp-babel-minify": "^0.5.1",
|
"gulp-babel-minify": "^0.5.1",
|
||||||
"gulp-less": "^4.0.1",
|
"gulp-less": "^4.0.1",
|
||||||
"gulp-minify-css": "^1.2.4",
|
"gulp-minify-css": "^1.2.4",
|
||||||
"less": "^3.11.3"
|
"less": "^3.11.3",
|
||||||
|
"prettier": "^2.7.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"uglify": "gulp"
|
"uglify": "gulp",
|
||||||
|
"lint": "eslint public/js *.js --ext .js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.14.0",
|
"@fortawesome/fontawesome-free": "^5.14.0",
|
||||||
|
@ -1,58 +1,56 @@
|
|||||||
const component = () => {
|
const component = () => {
|
||||||
return {
|
return {
|
||||||
progress: 1.0,
|
progress: 1.0,
|
||||||
generating: false,
|
generating: false,
|
||||||
scanning: false,
|
scanning: false,
|
||||||
scanTitles: 0,
|
scanTitles: 0,
|
||||||
scanMs: -1,
|
scanMs: -1,
|
||||||
themeSetting: '',
|
themeSetting: '',
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.getProgress();
|
this.getProgress();
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.getProgress();
|
this.getProgress();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
const setting = loadThemeSetting();
|
const setting = loadThemeSetting();
|
||||||
this.themeSetting = setting.charAt(0).toUpperCase() + setting.slice(1);
|
this.themeSetting = setting.charAt(0).toUpperCase() + setting.slice(1);
|
||||||
},
|
},
|
||||||
themeChanged(event) {
|
themeChanged(event) {
|
||||||
const newSetting = $(event.currentTarget).val().toLowerCase();
|
const newSetting = $(event.currentTarget).val().toLowerCase();
|
||||||
saveThemeSetting(newSetting);
|
saveThemeSetting(newSetting);
|
||||||
setTheme();
|
setTheme();
|
||||||
},
|
},
|
||||||
scan() {
|
scan() {
|
||||||
if (this.scanning) return;
|
if (this.scanning) return;
|
||||||
this.scanning = true;
|
this.scanning = true;
|
||||||
this.scanMs = -1;
|
this.scanMs = -1;
|
||||||
this.scanTitles = 0;
|
this.scanTitles = 0;
|
||||||
$.post(`${base_url}api/admin/scan`)
|
$.post(`${base_url}api/admin/scan`)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
this.scanMs = data.milliseconds;
|
this.scanMs = data.milliseconds;
|
||||||
this.scanTitles = data.titles;
|
this.scanTitles = data.titles;
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
alert('danger', `Failed to trigger a scan. Error: ${e}`);
|
alert('danger', `Failed to trigger a scan. Error: ${e}`);
|
||||||
})
|
})
|
||||||
.always(() => {
|
.always(() => {
|
||||||
this.scanning = false;
|
this.scanning = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
generateThumbnails() {
|
generateThumbnails() {
|
||||||
if (this.generating) return;
|
if (this.generating) return;
|
||||||
this.generating = true;
|
this.generating = true;
|
||||||
this.progress = 0.0;
|
this.progress = 0.0;
|
||||||
$.post(`${base_url}api/admin/generate_thumbnails`)
|
$.post(`${base_url}api/admin/generate_thumbnails`).then(() => {
|
||||||
.then(() => {
|
this.getProgress();
|
||||||
this.getProgress()
|
});
|
||||||
});
|
},
|
||||||
},
|
getProgress() {
|
||||||
getProgress() {
|
$.get(`${base_url}api/admin/thumbnail_progress`).then((data) => {
|
||||||
$.get(`${base_url}api/admin/thumbnail_progress`)
|
this.progress = data.progress;
|
||||||
.then(data => {
|
this.generating = data.progress > 0;
|
||||||
this.progress = data.progress;
|
});
|
||||||
this.generating = data.progress > 0;
|
},
|
||||||
});
|
};
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const alert = (level, text) => {
|
const alert = (level, text) => {
|
||||||
$('#alert').empty();
|
$('#alert').empty();
|
||||||
const html = `<div class="uk-alert-${level}" uk-alert><a class="uk-alert-close" uk-close></a><p>${text}</p></div>`;
|
const html = `<div class="uk-alert-${level}" uk-alert><a class="uk-alert-close" uk-close></a><p>${text}</p></div>`;
|
||||||
$('#alert').append(html);
|
$('#alert').append(html);
|
||||||
$("html, body").animate({ scrollTop: 0 });
|
$('html, body').animate({ scrollTop: 0 });
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
* @param {string} selector - The jQuery selector to the root element
|
* @param {string} selector - The jQuery selector to the root element
|
||||||
*/
|
*/
|
||||||
const setProp = (key, prop, selector = '#root') => {
|
const setProp = (key, prop, selector = '#root') => {
|
||||||
$(selector).get(0).__x.$data[key] = prop;
|
$(selector).get(0).__x.$data[key] = prop;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,7 +23,7 @@ const setProp = (key, prop, selector = '#root') => {
|
|||||||
* @return {*} The data property
|
* @return {*} The data property
|
||||||
*/
|
*/
|
||||||
const getProp = (key, selector = '#root') => {
|
const getProp = (key, selector = '#root') => {
|
||||||
return $(selector).get(0).__x.$data[key];
|
return $(selector).get(0).__x.$data[key];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,7 +41,10 @@ const getProp = (key, selector = '#root') => {
|
|||||||
* @return {bool}
|
* @return {bool}
|
||||||
*/
|
*/
|
||||||
const preferDarkMode = () => {
|
const preferDarkMode = () => {
|
||||||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
return (
|
||||||
|
window.matchMedia &&
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,7 +55,7 @@ const preferDarkMode = () => {
|
|||||||
* @return {bool}
|
* @return {bool}
|
||||||
*/
|
*/
|
||||||
const validThemeSetting = (theme) => {
|
const validThemeSetting = (theme) => {
|
||||||
return ['dark', 'light', 'system'].indexOf(theme) >= 0;
|
return ['dark', 'light', 'system'].indexOf(theme) >= 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,9 +65,9 @@ const validThemeSetting = (theme) => {
|
|||||||
* @return {string} A theme setting ('dark', 'light', or 'system')
|
* @return {string} A theme setting ('dark', 'light', or 'system')
|
||||||
*/
|
*/
|
||||||
const loadThemeSetting = () => {
|
const loadThemeSetting = () => {
|
||||||
let str = localStorage.getItem('theme');
|
let str = localStorage.getItem('theme');
|
||||||
if (!str || !validThemeSetting(str)) str = 'system';
|
if (!str || !validThemeSetting(str)) str = 'system';
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,11 +77,11 @@ const loadThemeSetting = () => {
|
|||||||
* @return {string} The current theme to use ('dark' or 'light')
|
* @return {string} The current theme to use ('dark' or 'light')
|
||||||
*/
|
*/
|
||||||
const loadTheme = () => {
|
const loadTheme = () => {
|
||||||
let setting = loadThemeSetting();
|
let setting = loadThemeSetting();
|
||||||
if (setting === 'system') {
|
if (setting === 'system') {
|
||||||
setting = preferDarkMode() ? 'dark' : 'light';
|
setting = preferDarkMode() ? 'dark' : 'light';
|
||||||
}
|
}
|
||||||
return setting;
|
return setting;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,9 +90,9 @@ const loadTheme = () => {
|
|||||||
* @function saveThemeSetting
|
* @function saveThemeSetting
|
||||||
* @param {string} setting - A theme setting
|
* @param {string} setting - A theme setting
|
||||||
*/
|
*/
|
||||||
const saveThemeSetting = setting => {
|
const saveThemeSetting = (setting) => {
|
||||||
if (!validThemeSetting(setting)) setting = 'system';
|
if (!validThemeSetting(setting)) setting = 'system';
|
||||||
localStorage.setItem('theme', setting);
|
localStorage.setItem('theme', setting);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,10 +102,10 @@ const saveThemeSetting = setting => {
|
|||||||
* @function toggleTheme
|
* @function toggleTheme
|
||||||
*/
|
*/
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
const theme = loadTheme();
|
const theme = loadTheme();
|
||||||
const newTheme = theme === 'dark' ? 'light' : 'dark';
|
const newTheme = theme === 'dark' ? 'light' : 'dark';
|
||||||
saveThemeSetting(newTheme);
|
saveThemeSetting(newTheme);
|
||||||
setTheme(newTheme);
|
setTheme(newTheme);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,31 +116,32 @@ const toggleTheme = () => {
|
|||||||
* `loadTheme` to get a theme and apply it.
|
* `loadTheme` to get a theme and apply it.
|
||||||
*/
|
*/
|
||||||
const setTheme = (theme) => {
|
const setTheme = (theme) => {
|
||||||
if (!theme) theme = loadTheme();
|
if (!theme) theme = loadTheme();
|
||||||
if (theme === 'dark') {
|
if (theme === 'dark') {
|
||||||
$('html').css('background', 'rgb(20, 20, 20)');
|
$('html').css('background', 'rgb(20, 20, 20)');
|
||||||
$('body').addClass('uk-light');
|
$('body').addClass('uk-light');
|
||||||
$('.ui-widget-content').addClass('dark');
|
$('.ui-widget-content').addClass('dark');
|
||||||
} else {
|
} else {
|
||||||
$('html').css('background', '');
|
$('html').css('background', '');
|
||||||
$('body').removeClass('uk-light');
|
$('body').removeClass('uk-light');
|
||||||
$('.ui-widget-content').removeClass('dark');
|
$('.ui-widget-content').removeClass('dark');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// do it before document is ready to prevent the initial flash of white on
|
// do it before document is ready to prevent the initial flash of white on
|
||||||
// most pages
|
// most pages
|
||||||
setTheme();
|
setTheme();
|
||||||
$(() => {
|
$(() => {
|
||||||
// hack for the reader page
|
// hack for the reader page
|
||||||
setTheme();
|
setTheme();
|
||||||
|
|
||||||
// on system dark mode setting change
|
// on system dark mode setting change
|
||||||
if (window.matchMedia) {
|
if (window.matchMedia) {
|
||||||
window.matchMedia('(prefers-color-scheme: dark)')
|
window
|
||||||
.addEventListener('change', event => {
|
.matchMedia('(prefers-color-scheme: dark)')
|
||||||
if (loadThemeSetting() === 'system')
|
.addEventListener('change', (event) => {
|
||||||
setTheme(event.matches ? 'dark' : 'light');
|
if (loadThemeSetting() === 'system')
|
||||||
});
|
setTheme(event.matches ? 'dark' : 'light');
|
||||||
}
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -5,22 +5,22 @@
|
|||||||
* @param {object} e - The title element to truncate
|
* @param {object} e - The title element to truncate
|
||||||
*/
|
*/
|
||||||
const truncate = (e) => {
|
const truncate = (e) => {
|
||||||
$(e).dotdotdot({
|
$(e).dotdotdot({
|
||||||
truncate: 'letter',
|
truncate: 'letter',
|
||||||
watch: true,
|
watch: true,
|
||||||
callback: (truncated) => {
|
callback: (truncated) => {
|
||||||
if (truncated) {
|
if (truncated) {
|
||||||
$(e).attr('uk-tooltip', $(e).attr('data-title'));
|
$(e).attr('uk-tooltip', $(e).attr('data-title'));
|
||||||
} else {
|
} else {
|
||||||
$(e).removeAttr('uk-tooltip');
|
$(e).removeAttr('uk-tooltip');
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$('.uk-card-title').each((i, e) => {
|
$('.uk-card-title').each((i, e) => {
|
||||||
// Truncate the title when it first enters the view
|
// Truncate the title when it first enters the view
|
||||||
$(e).one('inview', () => {
|
$(e).one('inview', () => {
|
||||||
truncate(e);
|
truncate(e);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,116 +1,135 @@
|
|||||||
const component = () => {
|
const component = () => {
|
||||||
return {
|
return {
|
||||||
jobs: [],
|
jobs: [],
|
||||||
paused: undefined,
|
paused: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
toggling: false,
|
toggling: false,
|
||||||
ws: undefined,
|
ws: undefined,
|
||||||
|
|
||||||
wsConnect(secure = true) {
|
wsConnect(secure = true) {
|
||||||
const url = `${secure ? 'wss' : 'ws'}://${location.host}${base_url}api/admin/mangadex/queue`;
|
const url = `${secure ? 'wss' : 'ws'}://${
|
||||||
console.log(`Connecting to ${url}`);
|
location.host
|
||||||
this.ws = new WebSocket(url);
|
}${base_url}api/admin/mangadex/queue`;
|
||||||
this.ws.onmessage = event => {
|
console.log(`Connecting to ${url}`);
|
||||||
const data = JSON.parse(event.data);
|
this.ws = new WebSocket(url);
|
||||||
this.jobs = data.jobs;
|
this.ws.onmessage = (event) => {
|
||||||
this.paused = data.paused;
|
const data = JSON.parse(event.data);
|
||||||
};
|
this.jobs = data.jobs;
|
||||||
this.ws.onclose = () => {
|
this.paused = data.paused;
|
||||||
if (this.ws.failed)
|
};
|
||||||
return this.wsConnect(false);
|
this.ws.onclose = () => {
|
||||||
alert('danger', 'Socket connection closed');
|
if (this.ws.failed) return this.wsConnect(false);
|
||||||
};
|
alert('danger', 'Socket connection closed');
|
||||||
this.ws.onerror = () => {
|
};
|
||||||
if (secure)
|
this.ws.onerror = () => {
|
||||||
return this.ws.failed = true;
|
if (secure) return (this.ws.failed = true);
|
||||||
alert('danger', 'Socket connection failed');
|
alert('danger', 'Socket connection failed');
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.wsConnect();
|
this.wsConnect();
|
||||||
this.load();
|
this.load();
|
||||||
},
|
},
|
||||||
load() {
|
load() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: base_url + 'api/admin/mangadex/queue',
|
url: base_url + 'api/admin/mangadex/queue',
|
||||||
dataType: 'json'
|
dataType: 'json',
|
||||||
})
|
})
|
||||||
.done(data => {
|
.done((data) => {
|
||||||
if (!data.success && data.error) {
|
if (!data.success && data.error) {
|
||||||
alert('danger', `Failed to fetch download queue. Error: ${data.error}`);
|
alert(
|
||||||
return;
|
'danger',
|
||||||
}
|
`Failed to fetch download queue. Error: ${data.error}`,
|
||||||
this.jobs = data.jobs;
|
);
|
||||||
this.paused = data.paused;
|
return;
|
||||||
})
|
}
|
||||||
.fail((jqXHR, status) => {
|
this.jobs = data.jobs;
|
||||||
alert('danger', `Failed to fetch download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
this.paused = data.paused;
|
||||||
})
|
})
|
||||||
.always(() => {
|
.fail((jqXHR, status) => {
|
||||||
this.loading = false;
|
alert(
|
||||||
});
|
'danger',
|
||||||
},
|
`Failed to fetch download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
||||||
jobAction(action, event) {
|
);
|
||||||
let url = `${base_url}api/admin/mangadex/queue/${action}`;
|
})
|
||||||
if (event) {
|
.always(() => {
|
||||||
const id = event.currentTarget.closest('tr').id.split('-').slice(1).join('-');
|
this.loading = false;
|
||||||
url = `${url}?${$.param({
|
});
|
||||||
id: id
|
},
|
||||||
})}`;
|
jobAction(action, event) {
|
||||||
}
|
let url = `${base_url}api/admin/mangadex/queue/${action}`;
|
||||||
console.log(url);
|
if (event) {
|
||||||
$.ajax({
|
const id = event.currentTarget
|
||||||
type: 'POST',
|
.closest('tr')
|
||||||
url: url,
|
.id.split('-')
|
||||||
dataType: 'json'
|
.slice(1)
|
||||||
})
|
.join('-');
|
||||||
.done(data => {
|
url = `${url}?${$.param({
|
||||||
if (!data.success && data.error) {
|
id,
|
||||||
alert('danger', `Failed to ${action} job from download queue. Error: ${data.error}`);
|
})}`;
|
||||||
return;
|
}
|
||||||
}
|
console.log(url);
|
||||||
this.load();
|
$.ajax({
|
||||||
})
|
type: 'POST',
|
||||||
.fail((jqXHR, status) => {
|
url,
|
||||||
alert('danger', `Failed to ${action} job from download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
dataType: 'json',
|
||||||
});
|
})
|
||||||
},
|
.done((data) => {
|
||||||
toggle() {
|
if (!data.success && data.error) {
|
||||||
this.toggling = true;
|
alert(
|
||||||
const action = this.paused ? 'resume' : 'pause';
|
'danger',
|
||||||
const url = `${base_url}api/admin/mangadex/queue/${action}`;
|
`Failed to ${action} job from download queue. Error: ${data.error}`,
|
||||||
$.ajax({
|
);
|
||||||
type: 'POST',
|
return;
|
||||||
url: url,
|
}
|
||||||
dataType: 'json'
|
this.load();
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert('danger', `Failed to ${action} download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
alert(
|
||||||
})
|
'danger',
|
||||||
.always(() => {
|
`Failed to ${action} job from download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
||||||
this.load();
|
);
|
||||||
this.toggling = false;
|
});
|
||||||
});
|
},
|
||||||
},
|
toggle() {
|
||||||
statusClass(status) {
|
this.toggling = true;
|
||||||
let cls = 'label ';
|
const action = this.paused ? 'resume' : 'pause';
|
||||||
switch (status) {
|
const url = `${base_url}api/admin/mangadex/queue/${action}`;
|
||||||
case 'Pending':
|
$.ajax({
|
||||||
cls += 'label-pending';
|
type: 'POST',
|
||||||
break;
|
url,
|
||||||
case 'Completed':
|
dataType: 'json',
|
||||||
cls += 'label-success';
|
})
|
||||||
break;
|
.fail((jqXHR, status) => {
|
||||||
case 'Error':
|
alert(
|
||||||
cls += 'label-danger';
|
'danger',
|
||||||
break;
|
`Failed to ${action} download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
||||||
case 'MissingPages':
|
);
|
||||||
cls += 'label-warning';
|
})
|
||||||
break;
|
.always(() => {
|
||||||
}
|
this.load();
|
||||||
return cls;
|
this.toggling = false;
|
||||||
}
|
});
|
||||||
};
|
},
|
||||||
|
statusClass(status) {
|
||||||
|
let cls = 'label ';
|
||||||
|
switch (status) {
|
||||||
|
case 'Pending':
|
||||||
|
cls += 'label-pending';
|
||||||
|
break;
|
||||||
|
case 'Completed':
|
||||||
|
cls += 'label-success';
|
||||||
|
break;
|
||||||
|
case 'Error':
|
||||||
|
cls += 'label-danger';
|
||||||
|
break;
|
||||||
|
case 'MissingPages':
|
||||||
|
cls += 'label-warning';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return cls;
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,60 +1,74 @@
|
|||||||
const component = () => {
|
const component = () => {
|
||||||
return {
|
return {
|
||||||
empty: true,
|
empty: true,
|
||||||
titles: [],
|
titles: [],
|
||||||
entries: [],
|
entries: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.request('GET', `${base_url}api/admin/titles/missing`, data => {
|
this.request('GET', `${base_url}api/admin/titles/missing`, (data) => {
|
||||||
this.titles = data.titles;
|
this.titles = data.titles;
|
||||||
this.request('GET', `${base_url}api/admin/entries/missing`, data => {
|
this.request('GET', `${base_url}api/admin/entries/missing`, (data) => {
|
||||||
this.entries = data.entries;
|
this.entries = data.entries;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.empty = this.entries.length === 0 && this.titles.length === 0;
|
this.empty = this.entries.length === 0 && this.titles.length === 0;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
rm(event) {
|
rm(event) {
|
||||||
const rawID = event.currentTarget.closest('tr').id;
|
const rawID = event.currentTarget.closest('tr').id;
|
||||||
const [type, id] = rawID.split('-');
|
const [type, id] = rawID.split('-');
|
||||||
const url = `${base_url}api/admin/${type === 'title' ? 'titles' : 'entries'}/missing/${id}`;
|
const url = `${base_url}api/admin/${
|
||||||
this.request('DELETE', url, () => {
|
type === 'title' ? 'titles' : 'entries'
|
||||||
this.load();
|
}/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.', {
|
},
|
||||||
labels: {
|
rmAll() {
|
||||||
ok: 'Yes, delete them',
|
UIkit.modal
|
||||||
cancel: 'Cancel'
|
.confirm(
|
||||||
}
|
'Are you sure? All metadata associated with these items, including their tags and thumbnails, will be deleted from the database.',
|
||||||
}).then(() => {
|
{
|
||||||
this.request('DELETE', `${base_url}api/admin/titles/missing`, () => {
|
labels: {
|
||||||
this.request('DELETE', `${base_url}api/admin/entries/missing`, () => {
|
ok: 'Yes, delete them',
|
||||||
this.load();
|
cancel: 'Cancel',
|
||||||
});
|
},
|
||||||
});
|
},
|
||||||
});
|
)
|
||||||
},
|
.then(() => {
|
||||||
request(method, url, cb) {
|
this.request('DELETE', `${base_url}api/admin/titles/missing`, () => {
|
||||||
console.log(url);
|
this.request(
|
||||||
$.ajax({
|
'DELETE',
|
||||||
type: method,
|
`${base_url}api/admin/entries/missing`,
|
||||||
url: url,
|
() => {
|
||||||
contentType: 'application/json'
|
this.load();
|
||||||
})
|
},
|
||||||
.done(data => {
|
);
|
||||||
if (data.error) {
|
});
|
||||||
alert('danger', `Failed to ${method} ${url}. Error: ${data.error}`);
|
});
|
||||||
return;
|
},
|
||||||
}
|
request(method, url, cb) {
|
||||||
if (cb) cb(data);
|
console.log(url);
|
||||||
})
|
$.ajax({
|
||||||
.fail((jqXHR, status) => {
|
type: method,
|
||||||
alert('danger', `Failed to ${method} ${url}. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
url,
|
||||||
});
|
contentType: 'application/json',
|
||||||
}
|
})
|
||||||
};
|
.done((data) => {
|
||||||
|
if (data.error) {
|
||||||
|
alert('danger', `Failed to ${method} ${url}. Error: ${data.error}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cb) cb(data);
|
||||||
|
})
|
||||||
|
.fail((jqXHR, status) => {
|
||||||
|
alert(
|
||||||
|
'danger',
|
||||||
|
`Failed to ${method} ${url}. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,452 +1,435 @@
|
|||||||
const component = () => {
|
const component = () => {
|
||||||
return {
|
return {
|
||||||
plugins: [],
|
plugins: [],
|
||||||
subscribable: false,
|
subscribable: false,
|
||||||
info: undefined,
|
info: undefined,
|
||||||
pid: undefined,
|
pid: undefined,
|
||||||
chapters: undefined, // undefined: not searched yet, []: empty
|
chapters: undefined, // undefined: not searched yet, []: empty
|
||||||
manga: undefined, // undefined: not searched yet, []: empty
|
manga: undefined, // undefined: not searched yet, []: empty
|
||||||
mid: undefined, // id of the selected manga
|
mid: undefined, // id of the selected manga
|
||||||
allChapters: [],
|
allChapters: [],
|
||||||
query: "",
|
query: '',
|
||||||
mangaTitle: "",
|
mangaTitle: '',
|
||||||
searching: false,
|
searching: false,
|
||||||
adding: false,
|
adding: false,
|
||||||
sortOptions: [],
|
sortOptions: [],
|
||||||
showFilters: false,
|
showFilters: false,
|
||||||
appliedFilters: [],
|
appliedFilters: [],
|
||||||
chaptersLimit: 500,
|
chaptersLimit: 500,
|
||||||
listManga: false,
|
listManga: false,
|
||||||
subscribing: false,
|
subscribing: false,
|
||||||
subscriptionName: "",
|
subscriptionName: '',
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
const tableObserver = new MutationObserver(() => {
|
const tableObserver = new MutationObserver(() => {
|
||||||
console.log("table mutated");
|
console.log('table mutated');
|
||||||
$("#selectable").selectable({
|
$('#selectable').selectable({
|
||||||
filter: "tr",
|
filter: 'tr',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
tableObserver.observe($("table").get(0), {
|
tableObserver.observe($('table').get(0), {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true,
|
subtree: true,
|
||||||
});
|
});
|
||||||
fetch(`${base_url}api/admin/plugin`)
|
fetch(`${base_url}api/admin/plugin`)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.success) throw new Error(data.error);
|
if (!data.success) throw new Error(data.error);
|
||||||
this.plugins = data.plugins;
|
this.plugins = data.plugins;
|
||||||
|
|
||||||
const pid = localStorage.getItem("plugin");
|
const pid = localStorage.getItem('plugin');
|
||||||
if (pid && this.plugins.map((p) => p.id).includes(pid))
|
if (pid && this.plugins.map((p) => p.id).includes(pid))
|
||||||
return this.loadPlugin(pid);
|
return this.loadPlugin(pid);
|
||||||
|
|
||||||
if (this.plugins.length > 0)
|
if (this.plugins.length > 0) this.loadPlugin(this.plugins[0].id);
|
||||||
this.loadPlugin(this.plugins[0].id);
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
.catch((e) => {
|
alert('danger', `Failed to list the available plugins. Error: ${e}`);
|
||||||
alert(
|
});
|
||||||
"danger",
|
},
|
||||||
`Failed to list the available plugins. Error: ${e}`
|
loadPlugin(pid) {
|
||||||
);
|
fetch(
|
||||||
});
|
`${base_url}api/admin/plugin/info?${new URLSearchParams({
|
||||||
},
|
plugin: pid,
|
||||||
loadPlugin(pid) {
|
})}`,
|
||||||
fetch(
|
)
|
||||||
`${base_url}api/admin/plugin/info?${new URLSearchParams({
|
.then((res) => res.json())
|
||||||
plugin: pid,
|
.then((data) => {
|
||||||
})}`
|
if (!data.success) throw new Error(data.error);
|
||||||
)
|
this.info = data.info;
|
||||||
.then((res) => res.json())
|
this.subscribable = data.subscribable;
|
||||||
.then((data) => {
|
this.pid = pid;
|
||||||
if (!data.success) throw new Error(data.error);
|
})
|
||||||
this.info = data.info;
|
.catch((e) => {
|
||||||
this.subscribable = data.subscribable;
|
alert('danger', `Failed to get plugin metadata. Error: ${e}`);
|
||||||
this.pid = pid;
|
});
|
||||||
})
|
},
|
||||||
.catch((e) => {
|
pluginChanged() {
|
||||||
alert(
|
this.manga = undefined;
|
||||||
"danger",
|
this.chapters = undefined;
|
||||||
`Failed to get plugin metadata. Error: ${e}`
|
this.mid = undefined;
|
||||||
);
|
this.loadPlugin(this.pid);
|
||||||
});
|
localStorage.setItem('plugin', this.pid);
|
||||||
},
|
},
|
||||||
pluginChanged() {
|
get chapterKeys() {
|
||||||
this.manga = undefined;
|
if (this.allChapters.length < 1) return [];
|
||||||
this.chapters = undefined;
|
return Object.keys(this.allChapters[0]).filter(
|
||||||
this.mid = undefined;
|
(k) => !['manga_title'].includes(k),
|
||||||
this.loadPlugin(this.pid);
|
);
|
||||||
localStorage.setItem("plugin", this.pid);
|
},
|
||||||
},
|
searchChapters(query) {
|
||||||
get chapterKeys() {
|
this.searching = true;
|
||||||
if (this.allChapters.length < 1) return [];
|
this.allChapters = [];
|
||||||
return Object.keys(this.allChapters[0]).filter(
|
this.sortOptions = [];
|
||||||
(k) => !["manga_title"].includes(k)
|
this.chapters = undefined;
|
||||||
);
|
this.listManga = false;
|
||||||
},
|
fetch(
|
||||||
searchChapters(query) {
|
`${base_url}api/admin/plugin/list?${new URLSearchParams({
|
||||||
this.searching = true;
|
plugin: this.pid,
|
||||||
this.allChapters = [];
|
query,
|
||||||
this.sortOptions = [];
|
})}`,
|
||||||
this.chapters = undefined;
|
)
|
||||||
this.listManga = false;
|
.then((res) => res.json())
|
||||||
fetch(
|
.then((data) => {
|
||||||
`${base_url}api/admin/plugin/list?${new URLSearchParams({
|
if (!data.success) throw new Error(data.error);
|
||||||
plugin: this.pid,
|
try {
|
||||||
query: query,
|
this.mangaTitle = data.chapters[0].manga_title;
|
||||||
})}`
|
if (!this.mangaTitle) throw new Error();
|
||||||
)
|
} catch (e) {
|
||||||
.then((res) => res.json())
|
this.mangaTitle = data.title;
|
||||||
.then((data) => {
|
}
|
||||||
if (!data.success) throw new Error(data.error);
|
|
||||||
try {
|
|
||||||
this.mangaTitle = data.chapters[0].manga_title;
|
|
||||||
if (!this.mangaTitle) throw new Error();
|
|
||||||
} catch (e) {
|
|
||||||
this.mangaTitle = data.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.allChapters = data.chapters;
|
this.allChapters = data.chapters;
|
||||||
this.chapters = data.chapters;
|
this.chapters = data.chapters;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert("danger", `Failed to list chapters. Error: ${e}`);
|
alert('danger', `Failed to list chapters. Error: ${e}`);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.searching = false;
|
this.searching = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
searchManga(query) {
|
searchManga(query) {
|
||||||
this.searching = true;
|
this.searching = true;
|
||||||
this.allChapters = [];
|
this.allChapters = [];
|
||||||
this.chapters = undefined;
|
this.chapters = undefined;
|
||||||
this.manga = undefined;
|
this.manga = undefined;
|
||||||
fetch(
|
fetch(
|
||||||
`${base_url}api/admin/plugin/search?${new URLSearchParams({
|
`${base_url}api/admin/plugin/search?${new URLSearchParams({
|
||||||
plugin: this.pid,
|
plugin: this.pid,
|
||||||
query: query,
|
query,
|
||||||
})}`
|
})}`,
|
||||||
)
|
)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.success) throw new Error(data.error);
|
if (!data.success) throw new Error(data.error);
|
||||||
this.manga = data.manga;
|
this.manga = data.manga;
|
||||||
this.listManga = true;
|
this.listManga = true;
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert("danger", `Search failed. Error: ${e}`);
|
alert('danger', `Search failed. Error: ${e}`);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.searching = false;
|
this.searching = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
search() {
|
search() {
|
||||||
const query = this.query.trim();
|
const query = this.query.trim();
|
||||||
if (!query) return;
|
if (!query) return;
|
||||||
|
|
||||||
this.manga = undefined;
|
this.manga = undefined;
|
||||||
this.mid = undefined;
|
this.mid = undefined;
|
||||||
if (this.info.version === 1) {
|
if (this.info.version === 1) {
|
||||||
this.searchChapters(query);
|
this.searchChapters(query);
|
||||||
} else {
|
} else {
|
||||||
this.searchManga(query);
|
this.searchManga(query);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectAll() {
|
selectAll() {
|
||||||
$("tbody > tr").each((i, e) => {
|
$('tbody > tr').each((i, e) => {
|
||||||
$(e).addClass("ui-selected");
|
$(e).addClass('ui-selected');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
$("tbody > tr").each((i, e) => {
|
$('tbody > tr').each((i, e) => {
|
||||||
$(e).removeClass("ui-selected");
|
$(e).removeClass('ui-selected');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
download() {
|
download() {
|
||||||
const selected = $("tbody > tr.ui-selected").get();
|
const selected = $('tbody > tr.ui-selected').get();
|
||||||
if (selected.length === 0) return;
|
if (selected.length === 0) return;
|
||||||
|
|
||||||
UIkit.modal
|
UIkit.modal
|
||||||
.confirm(`Download ${selected.length} selected chapters?`)
|
.confirm(`Download ${selected.length} selected chapters?`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const ids = selected.map((e) => e.id);
|
const ids = selected.map((e) => e.id);
|
||||||
const chapters = this.chapters.filter((c) =>
|
const chapters = this.chapters.filter((c) => ids.includes(c.id));
|
||||||
ids.includes(c.id)
|
console.log(chapters);
|
||||||
);
|
this.adding = true;
|
||||||
console.log(chapters);
|
fetch(`${base_url}api/admin/plugin/download`, {
|
||||||
this.adding = true;
|
method: 'POST',
|
||||||
fetch(`${base_url}api/admin/plugin/download`, {
|
body: JSON.stringify({
|
||||||
method: "POST",
|
chapters,
|
||||||
body: JSON.stringify({
|
plugin: this.pid,
|
||||||
chapters,
|
title: this.mangaTitle,
|
||||||
plugin: this.pid,
|
}),
|
||||||
title: this.mangaTitle,
|
headers: {
|
||||||
}),
|
'Content-Type': 'application/json',
|
||||||
headers: {
|
},
|
||||||
"Content-Type": "application/json",
|
})
|
||||||
},
|
.then((res) => res.json())
|
||||||
})
|
.then((data) => {
|
||||||
.then((res) => res.json())
|
if (!data.success) throw new Error(data.error);
|
||||||
.then((data) => {
|
const successCount = parseInt(data.success);
|
||||||
if (!data.success) throw new Error(data.error);
|
const failCount = parseInt(data.fail);
|
||||||
const successCount = parseInt(data.success);
|
alert(
|
||||||
const failCount = parseInt(data.fail);
|
'success',
|
||||||
alert(
|
`${successCount} of ${
|
||||||
"success",
|
successCount + failCount
|
||||||
`${successCount} of ${
|
} 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>.`,
|
||||||
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>.`
|
})
|
||||||
);
|
.catch((e) => {
|
||||||
})
|
alert(
|
||||||
.catch((e) => {
|
'danger',
|
||||||
alert(
|
`Failed to add chapters to the download queue. Error: ${e}`,
|
||||||
"danger",
|
);
|
||||||
`Failed to add chapters to the download queue. Error: ${e}`
|
})
|
||||||
);
|
.finally(() => {
|
||||||
})
|
this.adding = false;
|
||||||
.finally(() => {
|
});
|
||||||
this.adding = false;
|
});
|
||||||
});
|
},
|
||||||
});
|
thClicked(event) {
|
||||||
},
|
const idx = parseInt(event.currentTarget.id.split('-')[1]);
|
||||||
thClicked(event) {
|
if (idx === undefined || isNaN(idx)) return;
|
||||||
const idx = parseInt(event.currentTarget.id.split("-")[1]);
|
const curOption = this.sortOptions[idx];
|
||||||
if (idx === undefined || isNaN(idx)) return;
|
let option;
|
||||||
const curOption = this.sortOptions[idx];
|
this.sortOptions = [];
|
||||||
let option;
|
switch (curOption) {
|
||||||
this.sortOptions = [];
|
case 1:
|
||||||
switch (curOption) {
|
option = -1;
|
||||||
case 1:
|
break;
|
||||||
option = -1;
|
case -1:
|
||||||
break;
|
option = 0;
|
||||||
case -1:
|
break;
|
||||||
option = 0;
|
default:
|
||||||
break;
|
option = 1;
|
||||||
default:
|
}
|
||||||
option = 1;
|
this.sortOptions[idx] = option;
|
||||||
}
|
this.sort(this.chapterKeys[idx], option);
|
||||||
this.sortOptions[idx] = option;
|
},
|
||||||
this.sort(this.chapterKeys[idx], option);
|
// Returns an array of filtered but unsorted chapters. Useful when
|
||||||
},
|
// reseting the sort options.
|
||||||
// Returns an array of filtered but unsorted chapters. Useful when
|
get filteredChapters() {
|
||||||
// reseting the sort options.
|
let ary = this.allChapters.slice();
|
||||||
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) {
|
for (let filter of this.appliedFilters) {
|
||||||
if (!filter.value) continue;
|
if (!filter.value) continue;
|
||||||
if (filter.type === "array" && filter.value === "all") continue;
|
if (filter.type === 'array' && filter.value === 'all') continue;
|
||||||
if (filter.type.startsWith("number") && isNaN(filter.value))
|
if (filter.type.startsWith('number') && isNaN(filter.value)) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (filter.type === "string") {
|
if (filter.type === 'string') {
|
||||||
ary = ary.filter((ch) =>
|
ary = ary.filter((ch) =>
|
||||||
ch[filter.key]
|
ch[filter.key].toLowerCase().includes(filter.value.toLowerCase()),
|
||||||
.toLowerCase()
|
);
|
||||||
.includes(filter.value.toLowerCase())
|
}
|
||||||
);
|
if (filter.type === 'number-min') {
|
||||||
}
|
ary = ary.filter(
|
||||||
if (filter.type === "number-min") {
|
(ch) => Number(ch[filter.key]) >= Number(filter.value),
|
||||||
ary = ary.filter(
|
);
|
||||||
(ch) => Number(ch[filter.key]) >= Number(filter.value)
|
}
|
||||||
);
|
if (filter.type === 'number-max') {
|
||||||
}
|
ary = ary.filter(
|
||||||
if (filter.type === "number-max") {
|
(ch) => Number(ch[filter.key]) <= Number(filter.value),
|
||||||
ary = ary.filter(
|
);
|
||||||
(ch) => Number(ch[filter.key]) <= Number(filter.value)
|
}
|
||||||
);
|
if (filter.type === 'date-min') {
|
||||||
}
|
ary = ary.filter(
|
||||||
if (filter.type === "date-min") {
|
(ch) => Number(ch[filter.key]) >= Number(filter.value),
|
||||||
ary = ary.filter(
|
);
|
||||||
(ch) => Number(ch[filter.key]) >= Number(filter.value)
|
}
|
||||||
);
|
if (filter.type === 'date-max') {
|
||||||
}
|
ary = ary.filter(
|
||||||
if (filter.type === "date-max") {
|
(ch) => Number(ch[filter.key]) <= Number(filter.value),
|
||||||
ary = ary.filter(
|
);
|
||||||
(ch) => Number(ch[filter.key]) <= Number(filter.value)
|
}
|
||||||
);
|
if (filter.type === 'array') {
|
||||||
}
|
ary = ary.filter((ch) =>
|
||||||
if (filter.type === "array") {
|
ch[filter.key]
|
||||||
ary = ary.filter((ch) =>
|
.map((s) => (typeof s === 'string' ? s.toLowerCase() : s))
|
||||||
ch[filter.key]
|
.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;
|
return ary;
|
||||||
},
|
},
|
||||||
// option:
|
// option:
|
||||||
// - 1: asending
|
// - 1: asending
|
||||||
// - -1: desending
|
// - -1: desending
|
||||||
// - 0: unsorted
|
// - 0: unsorted
|
||||||
sort(key, option) {
|
sort(key, option) {
|
||||||
if (option === 0) {
|
if (option === 0) {
|
||||||
this.chapters = this.filteredChapters;
|
this.chapters = this.filteredChapters;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.chapters = this.filteredChapters.sort((a, b) => {
|
this.chapters = this.filteredChapters.sort((a, b) => {
|
||||||
const comp = this.compare(a[key], b[key]);
|
const comp = this.compare(a[key], b[key]);
|
||||||
return option < 0 ? comp * -1 : comp;
|
return option < 0 ? comp * -1 : comp;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
compare(a, b) {
|
compare(a, b) {
|
||||||
if (a === b) return 0;
|
if (a === b) return 0;
|
||||||
|
|
||||||
// try numbers (also covers dates)
|
// try numbers (also covers dates)
|
||||||
if (!isNaN(a) && !isNaN(b)) return Number(a) - Number(b);
|
if (!isNaN(a) && !isNaN(b)) return Number(a) - Number(b);
|
||||||
|
|
||||||
const preprocessString = (val) => {
|
const preprocessString = (val) => {
|
||||||
if (typeof val !== "string") return val;
|
if (typeof val !== 'string') return val;
|
||||||
return val.toLowerCase().replace(/\s\s/g, " ").trim();
|
return val.toLowerCase().replace(/\s\s/g, ' ').trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
return preprocessString(a) > preprocessString(b) ? 1 : -1;
|
return preprocessString(a) > preprocessString(b) ? 1 : -1;
|
||||||
},
|
},
|
||||||
fieldType(values) {
|
fieldType(values) {
|
||||||
if (values.every((v) => this.numIsDate(v))) return "date";
|
if (values.every((v) => this.numIsDate(v))) return 'date';
|
||||||
if (values.every((v) => !isNaN(v))) return "number";
|
if (values.every((v) => !isNaN(v))) return 'number';
|
||||||
if (values.every((v) => Array.isArray(v))) return "array";
|
if (values.every((v) => Array.isArray(v))) return 'array';
|
||||||
return "string";
|
return 'string';
|
||||||
},
|
},
|
||||||
get filters() {
|
get filters() {
|
||||||
if (this.allChapters.length < 1) return [];
|
if (this.allChapters.length < 1) return [];
|
||||||
const keys = Object.keys(this.allChapters[0]).filter(
|
const keys = Object.keys(this.allChapters[0]).filter(
|
||||||
(k) => !["manga_title", "id"].includes(k)
|
(k) => !['manga_title', 'id'].includes(k),
|
||||||
);
|
);
|
||||||
return keys.map((k) => {
|
return keys.map((k) => {
|
||||||
let values = this.allChapters.map((c) => c[k]);
|
let values = this.allChapters.map((c) => c[k]);
|
||||||
const type = this.fieldType(values);
|
const type = this.fieldType(values);
|
||||||
|
|
||||||
if (type === "array") {
|
if (type === 'array') {
|
||||||
// if the type is an array, return the list of available elements
|
// if the type is an array, return the list of available elements
|
||||||
// example: an array of groups or authors
|
// example: an array of groups or authors
|
||||||
values = Array.from(
|
values = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
values.flat().map((v) => {
|
values.flat().map((v) => {
|
||||||
if (typeof v === "string")
|
if (typeof v === 'string') return v.toLowerCase();
|
||||||
return v.toLowerCase();
|
}),
|
||||||
})
|
),
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: k,
|
key: k,
|
||||||
type: type,
|
type,
|
||||||
values: values,
|
values,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
get filterSettings() {
|
get filterSettings() {
|
||||||
return $("#filter-form input:visible, #filter-form select:visible")
|
return $('#filter-form input:visible, #filter-form select:visible')
|
||||||
.get()
|
.get()
|
||||||
.map((i) => {
|
.map((i) => {
|
||||||
const type = i.getAttribute("data-filter-type");
|
const type = i.getAttribute('data-filter-type');
|
||||||
let value = i.value.trim();
|
let value = i.value.trim();
|
||||||
if (type.startsWith("date"))
|
if (type.startsWith('date'))
|
||||||
value = value ? Date.parse(value).toString() : "";
|
value = value ? Date.parse(value).toString() : '';
|
||||||
return {
|
return {
|
||||||
key: i.getAttribute("data-filter-key"),
|
key: i.getAttribute('data-filter-key'),
|
||||||
value: value,
|
value,
|
||||||
type: type,
|
type,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
applyFilters() {
|
applyFilters() {
|
||||||
this.appliedFilters = this.filterSettings;
|
this.appliedFilters = this.filterSettings;
|
||||||
this.chapters = this.filteredChapters;
|
this.chapters = this.filteredChapters;
|
||||||
this.sortOptions = [];
|
this.sortOptions = [];
|
||||||
},
|
},
|
||||||
clearFilters() {
|
clearFilters() {
|
||||||
$("#filter-form input")
|
$('#filter-form input')
|
||||||
.get()
|
.get()
|
||||||
.forEach((i) => (i.value = ""));
|
.forEach((i) => (i.value = ''));
|
||||||
$("#filter-form select").val("all");
|
$('#filter-form select').val('all');
|
||||||
this.appliedFilters = [];
|
this.appliedFilters = [];
|
||||||
this.chapters = this.filteredChapters;
|
this.chapters = this.filteredChapters;
|
||||||
this.sortOptions = [];
|
this.sortOptions = [];
|
||||||
},
|
},
|
||||||
mangaSelected(event) {
|
mangaSelected(event) {
|
||||||
const mid = event.currentTarget.getAttribute("data-id");
|
const mid = event.currentTarget.getAttribute('data-id');
|
||||||
this.mid = mid;
|
this.mid = mid;
|
||||||
this.searchChapters(mid);
|
this.searchChapters(mid);
|
||||||
},
|
},
|
||||||
subscribe(modal) {
|
subscribe(modal) {
|
||||||
this.subscribing = true;
|
this.subscribing = true;
|
||||||
fetch(`${base_url}api/admin/plugin/subscriptions`, {
|
fetch(`${base_url}api/admin/plugin/subscriptions`, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
filters: this.filterSettings,
|
filters: this.filterSettings,
|
||||||
plugin: this.pid,
|
plugin: this.pid,
|
||||||
name: this.subscriptionName.trim(),
|
name: this.subscriptionName.trim(),
|
||||||
manga: this.mangaTitle,
|
manga: this.mangaTitle,
|
||||||
manga_id: this.mid,
|
manga_id: this.mid,
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.success) throw new Error(data.error);
|
if (!data.success) throw new Error(data.error);
|
||||||
alert("success", "Subscription created");
|
alert('success', 'Subscription created');
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert("danger", `Failed to subscribe. Error: ${e}`);
|
alert('danger', `Failed to subscribe. Error: ${e}`);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.subscribing = false;
|
this.subscribing = false;
|
||||||
UIkit.modal(modal).hide();
|
UIkit.modal(modal).hide();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
numIsDate(num) {
|
numIsDate(num) {
|
||||||
return !isNaN(num) && Number(num) > 328896000000; // 328896000000 => 1 Jan, 1980
|
return !isNaN(num) && Number(num) > 328896000000; // 328896000000 => 1 Jan, 1980
|
||||||
},
|
},
|
||||||
renderCell(value) {
|
renderCell(value) {
|
||||||
if (this.numIsDate(value))
|
if (this.numIsDate(value))
|
||||||
return `<span>${moment(Number(value)).format(
|
return `<span>${moment(Number(value)).format('MMM D, YYYY')}</span>`;
|
||||||
"MMM D, YYYY"
|
const maxLength = 40;
|
||||||
)}</span>`;
|
if (value && value.length > maxLength)
|
||||||
const maxLength = 40;
|
return `<span>${value.substr(
|
||||||
if (value && value.length > maxLength)
|
0,
|
||||||
return `<span>${value.substr(
|
maxLength,
|
||||||
0,
|
)}...</span><div uk-dropdown>${value}</div>`;
|
||||||
maxLength
|
return `<span>${value}</span>`;
|
||||||
)}...</span><div uk-dropdown>${value}</div>`;
|
},
|
||||||
return `<span>${value}</span>`;
|
renderFilterRow(ft) {
|
||||||
},
|
const key = ft.key;
|
||||||
renderFilterRow(ft) {
|
let type = ft.type;
|
||||||
const key = ft.key;
|
switch (type) {
|
||||||
let type = ft.type;
|
case 'number-min':
|
||||||
switch (type) {
|
type = 'number (minimum value)';
|
||||||
case "number-min":
|
break;
|
||||||
type = "number (minimum value)";
|
case 'number-max':
|
||||||
break;
|
type = 'number (maximum value)';
|
||||||
case "number-max":
|
break;
|
||||||
type = "number (maximum value)";
|
case 'date-min':
|
||||||
break;
|
type = 'minimum date';
|
||||||
case "date-min":
|
break;
|
||||||
type = "minimum date";
|
case 'date-max':
|
||||||
break;
|
type = 'maximum date';
|
||||||
case "date-max":
|
break;
|
||||||
type = "maximum date";
|
}
|
||||||
break;
|
let value = ft.value;
|
||||||
}
|
|
||||||
let value = ft.value;
|
|
||||||
|
|
||||||
if (ft.type.startsWith("number") && isNaN(value)) value = "";
|
if (ft.type.startsWith('number') && isNaN(value)) value = '';
|
||||||
else if (ft.type.startsWith("date") && value)
|
else if (ft.type.startsWith('date') && value)
|
||||||
value = moment(Number(value)).format("MMM D, YYYY");
|
value = moment(Number(value)).format('MMM D, YYYY');
|
||||||
|
|
||||||
return `<td>${key}</td><td>${type}</td><td>${value}</td>`;
|
return `<td>${key}</td><td>${type}</td><td>${value}</td>`;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,361 +1,370 @@
|
|||||||
const readerComponent = () => {
|
const readerComponent = () => {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: true,
|
||||||
mode: 'continuous', // Can be 'continuous', 'height' or 'width'
|
mode: 'continuous', // Can be 'continuous', 'height' or 'width'
|
||||||
msg: 'Loading the web reader. Please wait...',
|
msg: 'Loading the web reader. Please wait...',
|
||||||
alertClass: 'uk-alert-primary',
|
alertClass: 'uk-alert-primary',
|
||||||
items: [],
|
items: [],
|
||||||
curItem: {},
|
curItem: {},
|
||||||
enableFlipAnimation: true,
|
enableFlipAnimation: true,
|
||||||
flipAnimation: null,
|
flipAnimation: null,
|
||||||
longPages: false,
|
longPages: false,
|
||||||
lastSavedPage: page,
|
lastSavedPage: page,
|
||||||
selectedIndex: 0, // 0: not selected; 1: the first page
|
selectedIndex: 0, // 0: not selected; 1: the first page
|
||||||
margin: 30,
|
margin: 30,
|
||||||
preloadLookahead: 3,
|
preloadLookahead: 3,
|
||||||
enableRightToLeft: false,
|
enableRightToLeft: false,
|
||||||
fitType: 'vert',
|
fitType: 'vert',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the component by fetching the page dimensions
|
* Initialize the component by fetching the page dimensions
|
||||||
*/
|
*/
|
||||||
init(nextTick) {
|
init(nextTick) {
|
||||||
$.get(`${base_url}api/dimensions/${tid}/${eid}`)
|
$.get(`${base_url}api/dimensions/${tid}/${eid}`)
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
if (!data.success && data.error)
|
if (!data.success && data.error) throw new Error(resp.error);
|
||||||
throw new Error(resp.error);
|
const dimensions = data.dimensions;
|
||||||
const dimensions = data.dimensions;
|
|
||||||
|
|
||||||
this.items = dimensions.map((d, i) => {
|
this.items = dimensions.map((d, i) => {
|
||||||
return {
|
return {
|
||||||
id: i + 1,
|
id: i + 1,
|
||||||
url: `${base_url}api/page/${tid}/${eid}/${i+1}`,
|
url: `${base_url}api/page/${tid}/${eid}/${i + 1}`,
|
||||||
width: d.width == 0 ? "100%" : d.width,
|
width: d.width === 0 ? '100%' : d.width,
|
||||||
height: d.height == 0 ? "100%" : d.height,
|
height: d.height === 0 ? '100%' : d.height,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: for image types not supported by image_size.cr, the width and height will be 0, and so `avgRatio` will be `Infinity`.
|
// Note: for image types not supported by image_size.cr, the width and height will be 0, and so `avgRatio` will be `Infinity`.
|
||||||
// TODO: support more image types in image_size.cr
|
// TODO: support more image types in image_size.cr
|
||||||
const avgRatio = dimensions.reduce((acc, cur) => {
|
const avgRatio =
|
||||||
return acc + cur.height / cur.width
|
dimensions.reduce((acc, cur) => {
|
||||||
}, 0) / dimensions.length;
|
return acc + cur.height / cur.width;
|
||||||
|
}, 0) / dimensions.length;
|
||||||
|
|
||||||
console.log(avgRatio);
|
console.log(avgRatio);
|
||||||
this.longPages = avgRatio > 2;
|
this.longPages = avgRatio > 2;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.mode = localStorage.getItem('mode') || 'continuous';
|
this.mode = localStorage.getItem('mode') || 'continuous';
|
||||||
|
|
||||||
// Here we save a copy of this.mode, and use the copy as
|
// Here we save a copy of this.mode, and use the copy as
|
||||||
// the model-select value. This is because `updateMode`
|
// the model-select value. This is because `updateMode`
|
||||||
// might change this.mode and make it `height` or `width`,
|
// might change this.mode and make it `height` or `width`,
|
||||||
// which are not available in mode-select
|
// which are not available in mode-select
|
||||||
const mode = this.mode;
|
const mode = this.mode;
|
||||||
this.updateMode(this.mode, page, nextTick);
|
this.updateMode(this.mode, page, nextTick);
|
||||||
$('#mode-select').val(mode);
|
$('#mode-select').val(mode);
|
||||||
|
|
||||||
const savedMargin = localStorage.getItem('margin');
|
const savedMargin = localStorage.getItem('margin');
|
||||||
if (savedMargin) {
|
if (savedMargin) {
|
||||||
this.margin = savedMargin;
|
this.margin = savedMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preload Images
|
// Preload Images
|
||||||
this.preloadLookahead = +(localStorage.getItem('preloadLookahead') ?? 3);
|
this.preloadLookahead = +(
|
||||||
const limit = Math.min(page + this.preloadLookahead, this.items.length);
|
localStorage.getItem('preloadLookahead') ?? 3
|
||||||
for (let idx = page + 1; idx <= limit; idx++) {
|
);
|
||||||
this.preloadImage(this.items[idx - 1].url);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
const savedFitType = localStorage.getItem('fitType');
|
const savedFitType = localStorage.getItem('fitType');
|
||||||
if (savedFitType) {
|
if (savedFitType) {
|
||||||
this.fitType = savedFitType;
|
this.fitType = savedFitType;
|
||||||
$('#fit-select').val(savedFitType);
|
$('#fit-select').val(savedFitType);
|
||||||
}
|
}
|
||||||
const savedFlipAnimation = localStorage.getItem('enableFlipAnimation');
|
const savedFlipAnimation = localStorage.getItem(
|
||||||
this.enableFlipAnimation = savedFlipAnimation === null || savedFlipAnimation === 'true';
|
'enableFlipAnimation',
|
||||||
|
);
|
||||||
|
this.enableFlipAnimation =
|
||||||
|
savedFlipAnimation === null || savedFlipAnimation === 'true';
|
||||||
|
|
||||||
const savedRightToLeft = localStorage.getItem('enableRightToLeft');
|
const savedRightToLeft = localStorage.getItem('enableRightToLeft');
|
||||||
if (savedRightToLeft === null) {
|
if (savedRightToLeft === null) {
|
||||||
this.enableRightToLeft = false;
|
this.enableRightToLeft = false;
|
||||||
} else {
|
} else {
|
||||||
this.enableRightToLeft = (savedRightToLeft === 'true');
|
this.enableRightToLeft = savedRightToLeft === 'true';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
const errMsg = `Failed to get the page dimensions. ${e}`;
|
const errMsg = `Failed to get the page dimensions. ${e}`;
|
||||||
console.error(e);
|
console.error(e);
|
||||||
this.alertClass = 'uk-alert-danger';
|
this.alertClass = 'uk-alert-danger';
|
||||||
this.msg = errMsg;
|
this.msg = errMsg;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Preload an image, which is expected to be cached
|
* Preload an image, which is expected to be cached
|
||||||
*/
|
*/
|
||||||
preloadImage(url) {
|
preloadImage(url) {
|
||||||
(new Image()).src = url;
|
new Image().src = url;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Handles the `change` event for the page selector
|
* Handles the `change` event for the page selector
|
||||||
*/
|
*/
|
||||||
pageChanged() {
|
pageChanged() {
|
||||||
const p = parseInt($('#page-select').val());
|
const p = parseInt($('#page-select').val());
|
||||||
this.toPage(p);
|
this.toPage(p);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Handles the `change` event for the mode selector
|
* Handles the `change` event for the mode selector
|
||||||
*
|
*
|
||||||
* @param {function} nextTick - Alpine $nextTick magic property
|
* @param {function} nextTick - Alpine $nextTick magic property
|
||||||
*/
|
*/
|
||||||
modeChanged(nextTick) {
|
modeChanged(nextTick) {
|
||||||
const mode = $('#mode-select').val();
|
const mode = $('#mode-select').val();
|
||||||
const curIdx = parseInt($('#page-select').val());
|
const curIdx = parseInt($('#page-select').val());
|
||||||
|
|
||||||
this.updateMode(mode, curIdx, nextTick);
|
this.updateMode(mode, curIdx, nextTick);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Handles the window `resize` event
|
* Handles the window `resize` event
|
||||||
*/
|
*/
|
||||||
resized() {
|
resized() {
|
||||||
if (this.mode === 'continuous') return;
|
if (this.mode === 'continuous') return;
|
||||||
|
|
||||||
const wideScreen = $(window).width() > $(window).height();
|
const wideScreen = $(window).width() > $(window).height();
|
||||||
this.mode = wideScreen ? 'height' : 'width';
|
this.mode = wideScreen ? 'height' : 'width';
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Handles the window `keydown` event
|
* Handles the window `keydown` event
|
||||||
*
|
*
|
||||||
* @param {Event} event - The triggering event
|
* @param {Event} event - The triggering event
|
||||||
*/
|
*/
|
||||||
keyHandler(event) {
|
keyHandler(event) {
|
||||||
if (this.mode === 'continuous') return;
|
if (this.mode === 'continuous') return;
|
||||||
|
|
||||||
if (event.key === 'ArrowLeft' || event.key === 'k')
|
if (event.key === 'ArrowLeft' || event.key === 'k')
|
||||||
this.flipPage(false ^ this.enableRightToLeft);
|
this.flipPage(false ^ this.enableRightToLeft);
|
||||||
if (event.key === 'ArrowRight' || event.key === 'j')
|
if (event.key === 'ArrowRight' || event.key === 'j')
|
||||||
this.flipPage(true ^ this.enableRightToLeft);
|
this.flipPage(true ^ this.enableRightToLeft);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Flips to the next or the previous page
|
* Flips to the next or the previous page
|
||||||
*
|
*
|
||||||
* @param {bool} isNext - Whether we are going to the next page
|
* @param {bool} isNext - Whether we are going to the next page
|
||||||
*/
|
*/
|
||||||
flipPage(isNext) {
|
flipPage(isNext) {
|
||||||
const idx = parseInt(this.curItem.id);
|
const idx = parseInt(this.curItem.id);
|
||||||
const newIdx = idx + (isNext ? 1 : -1);
|
const newIdx = idx + (isNext ? 1 : -1);
|
||||||
|
|
||||||
if (newIdx <= 0) return;
|
if (newIdx <= 0) return;
|
||||||
if (newIdx > this.items.length) {
|
if (newIdx > this.items.length) {
|
||||||
this.showControl(idx);
|
this.showControl(idx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newIdx + this.preloadLookahead < this.items.length + 1) {
|
if (newIdx + this.preloadLookahead < this.items.length + 1) {
|
||||||
this.preloadImage(this.items[newIdx + this.preloadLookahead - 1].url);
|
this.preloadImage(this.items[newIdx + this.preloadLookahead - 1].url);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toPage(newIdx);
|
this.toPage(newIdx);
|
||||||
|
|
||||||
if (this.enableFlipAnimation) {
|
if (this.enableFlipAnimation) {
|
||||||
if (isNext ^ this.enableRightToLeft)
|
if (isNext ^ this.enableRightToLeft) this.flipAnimation = 'right';
|
||||||
this.flipAnimation = 'right';
|
else this.flipAnimation = 'left';
|
||||||
else
|
}
|
||||||
this.flipAnimation = 'left';
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.flipAnimation = null;
|
this.flipAnimation = null;
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
this.replaceHistory(newIdx);
|
this.replaceHistory(newIdx);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Jumps to a specific page
|
* Jumps to a specific page
|
||||||
*
|
*
|
||||||
* @param {number} idx - One-based index of the page
|
* @param {number} idx - One-based index of the page
|
||||||
*/
|
*/
|
||||||
toPage(idx) {
|
toPage(idx) {
|
||||||
if (this.mode === 'continuous') {
|
if (this.mode === 'continuous') {
|
||||||
$(`#${idx}`).get(0).scrollIntoView(true);
|
$(`#${idx}`).get(0).scrollIntoView(true);
|
||||||
} else {
|
} else {
|
||||||
if (idx >= 1 && idx <= this.items.length) {
|
if (idx >= 1 && idx <= this.items.length) {
|
||||||
this.curItem = this.items[idx - 1];
|
this.curItem = this.items[idx - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.replaceHistory(idx);
|
this.replaceHistory(idx);
|
||||||
UIkit.modal($('#modal-sections')).hide();
|
UIkit.modal($('#modal-sections')).hide();
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Replace the address bar history and save the reading progress if necessary
|
* Replace the address bar history and save the reading progress if necessary
|
||||||
*
|
*
|
||||||
* @param {number} idx - One-based index of the page
|
* @param {number} idx - One-based index of the page
|
||||||
*/
|
*/
|
||||||
replaceHistory(idx) {
|
replaceHistory(idx) {
|
||||||
const ary = window.location.pathname.split('/');
|
const ary = window.location.pathname.split('/');
|
||||||
ary[ary.length - 1] = idx;
|
ary[ary.length - 1] = idx;
|
||||||
ary.shift(); // remove leading `/`
|
ary.shift(); // remove leading `/`
|
||||||
ary.unshift(window.location.origin);
|
ary.unshift(window.location.origin);
|
||||||
const url = ary.join('/');
|
const url = ary.join('/');
|
||||||
this.saveProgress(idx);
|
this.saveProgress(idx);
|
||||||
history.replaceState(null, "", url);
|
history.replaceState(null, '', url);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Updates the backend reading progress if:
|
* Updates the backend reading progress if:
|
||||||
* 1) the current page is more than five pages away from the last
|
* 1) the current page is more than five pages away from the last
|
||||||
* saved page, or
|
* saved page, or
|
||||||
* 2) the average height/width ratio of the pages is over 2, or
|
* 2) the average height/width ratio of the pages is over 2, or
|
||||||
* 3) the current page is the first page, or
|
* 3) the current page is the first page, or
|
||||||
* 4) the current page is the last page
|
* 4) the current page is the last page
|
||||||
*
|
*
|
||||||
* @param {number} idx - One-based index of the page
|
* @param {number} idx - One-based index of the page
|
||||||
* @param {function} cb - Callback
|
* @param {function} cb - Callback
|
||||||
*/
|
*/
|
||||||
saveProgress(idx, cb) {
|
saveProgress(idx, cb) {
|
||||||
idx = parseInt(idx);
|
idx = parseInt(idx);
|
||||||
if (Math.abs(idx - this.lastSavedPage) >= 5 ||
|
if (
|
||||||
this.longPages ||
|
Math.abs(idx - this.lastSavedPage) >= 5 ||
|
||||||
idx === 1 || idx === this.items.length
|
this.longPages ||
|
||||||
) {
|
idx === 1 ||
|
||||||
this.lastSavedPage = idx;
|
idx === this.items.length
|
||||||
console.log('saving progress', idx);
|
) {
|
||||||
|
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({
|
||||||
$.ajax({
|
eid,
|
||||||
method: 'PUT',
|
})}`;
|
||||||
url: url,
|
$.ajax({
|
||||||
dataType: 'json'
|
method: 'PUT',
|
||||||
})
|
url,
|
||||||
.done(data => {
|
dataType: 'json',
|
||||||
if (data.error)
|
})
|
||||||
alert('danger', data.error);
|
.done((data) => {
|
||||||
if (cb) cb();
|
if (data.error) alert('danger', data.error);
|
||||||
})
|
if (cb) cb();
|
||||||
.fail((jqXHR, status) => {
|
})
|
||||||
alert('danger', `Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
.fail((jqXHR, status) => {
|
||||||
});
|
alert('danger', `Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
}
|
});
|
||||||
},
|
}
|
||||||
/**
|
},
|
||||||
* Updates the reader mode
|
/**
|
||||||
*
|
* Updates the reader mode
|
||||||
* @param {string} mode - Either `continuous` or `paged`
|
*
|
||||||
* @param {number} targetPage - The one-based index of the target page
|
* @param {string} mode - Either `continuous` or `paged`
|
||||||
* @param {function} nextTick - Alpine $nextTick magic property
|
* @param {number} targetPage - The one-based index of the target page
|
||||||
*/
|
* @param {function} nextTick - Alpine $nextTick magic property
|
||||||
updateMode(mode, targetPage, nextTick) {
|
*/
|
||||||
localStorage.setItem('mode', mode);
|
updateMode(mode, targetPage, nextTick) {
|
||||||
|
localStorage.setItem('mode', mode);
|
||||||
|
|
||||||
// The mode to be put into the `mode` prop. It can't be `screen`
|
// The mode to be put into the `mode` prop. It can't be `screen`
|
||||||
let propMode = mode;
|
let propMode = mode;
|
||||||
|
|
||||||
if (mode === 'paged') {
|
if (mode === 'paged') {
|
||||||
const wideScreen = $(window).width() > $(window).height();
|
const wideScreen = $(window).width() > $(window).height();
|
||||||
propMode = wideScreen ? 'height' : 'width';
|
propMode = wideScreen ? 'height' : 'width';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mode = propMode;
|
this.mode = propMode;
|
||||||
|
|
||||||
if (mode === 'continuous') {
|
if (mode === 'continuous') {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
this.setupScroller();
|
this.setupScroller();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
this.toPage(targetPage);
|
this.toPage(targetPage);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Handles clicked image
|
* Handles clicked image
|
||||||
*
|
*
|
||||||
* @param {Event} event - The triggering event
|
* @param {Event} event - The triggering event
|
||||||
*/
|
*/
|
||||||
clickImage(event) {
|
clickImage(event) {
|
||||||
const idx = event.currentTarget.id;
|
const idx = event.currentTarget.id;
|
||||||
this.showControl(idx);
|
this.showControl(idx);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Shows the control modal
|
* Shows the control modal
|
||||||
*
|
*
|
||||||
* @param {number} idx - selected page index
|
* @param {number} idx - selected page index
|
||||||
*/
|
*/
|
||||||
showControl(idx) {
|
showControl(idx) {
|
||||||
this.selectedIndex = idx;
|
this.selectedIndex = idx;
|
||||||
UIkit.modal($('#modal-sections')).show();
|
UIkit.modal($('#modal-sections')).show();
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Redirects to a URL
|
* Redirects to a URL
|
||||||
*
|
*
|
||||||
* @param {string} url - The target URL
|
* @param {string} url - The target URL
|
||||||
*/
|
*/
|
||||||
redirect(url) {
|
redirect(url) {
|
||||||
window.location.replace(url);
|
window.location.replace(url);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Set up the scroll handler that calls `replaceHistory` when an image
|
* Set up the scroll handler that calls `replaceHistory` when an image
|
||||||
* enters the view port
|
* enters the view port
|
||||||
*/
|
*/
|
||||||
setupScroller() {
|
setupScroller() {
|
||||||
if (this.mode !== 'continuous') return;
|
if (this.mode !== 'continuous') return;
|
||||||
$('img').each((idx, el) => {
|
$('img').each((idx, el) => {
|
||||||
$(el).on('inview', (event, inView) => {
|
$(el).on('inview', (event, inView) => {
|
||||||
if (inView) {
|
if (inView) {
|
||||||
const current = $(event.currentTarget).attr('id');
|
const current = $(event.currentTarget).attr('id');
|
||||||
|
|
||||||
this.curItem = this.items[current - 1];
|
this.curItem = this.items[current - 1];
|
||||||
this.replaceHistory(current);
|
this.replaceHistory(current);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Marks progress as 100% and jumps to the next entry
|
* Marks progress as 100% and jumps to the next entry
|
||||||
*
|
*
|
||||||
* @param {string} nextUrl - URL of the next entry
|
* @param {string} nextUrl - URL of the next entry
|
||||||
*/
|
*/
|
||||||
nextEntry(nextUrl) {
|
nextEntry(nextUrl) {
|
||||||
this.saveProgress(this.items.length, () => {
|
this.saveProgress(this.items.length, () => {
|
||||||
this.redirect(nextUrl);
|
this.redirect(nextUrl);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Exits the reader, and sets the reading progress tp 100%
|
* Exits the reader, and sets the reading progress tp 100%
|
||||||
*
|
*
|
||||||
* @param {string} exitUrl - The Exit URL
|
* @param {string} exitUrl - The Exit URL
|
||||||
*/
|
*/
|
||||||
exitReader(exitUrl) {
|
exitReader(exitUrl) {
|
||||||
this.saveProgress(this.items.length, () => {
|
this.saveProgress(this.items.length, () => {
|
||||||
this.redirect(exitUrl);
|
this.redirect(exitUrl);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the `change` event for the entry selector
|
* Handles the `change` event for the entry selector
|
||||||
*/
|
*/
|
||||||
entryChanged() {
|
entryChanged() {
|
||||||
const id = $('#entry-select').val();
|
const id = $('#entry-select').val();
|
||||||
this.redirect(`${base_url}reader/${tid}/${id}`);
|
this.redirect(`${base_url}reader/${tid}/${id}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
marginChanged() {
|
marginChanged() {
|
||||||
localStorage.setItem('margin', this.margin);
|
localStorage.setItem('margin', this.margin);
|
||||||
this.toPage(this.selectedIndex);
|
this.toPage(this.selectedIndex);
|
||||||
},
|
},
|
||||||
|
|
||||||
fitChanged(){
|
fitChanged() {
|
||||||
this.fitType = $('#fit-select').val();
|
this.fitType = $('#fit-select').val();
|
||||||
localStorage.setItem('fitType', this.fitType);
|
localStorage.setItem('fitType', this.fitType);
|
||||||
},
|
},
|
||||||
|
|
||||||
preloadLookaheadChanged() {
|
preloadLookaheadChanged() {
|
||||||
localStorage.setItem('preloadLookahead', this.preloadLookahead);
|
localStorage.setItem('preloadLookahead', this.preloadLookahead);
|
||||||
},
|
},
|
||||||
|
|
||||||
enableFlipAnimationChanged() {
|
enableFlipAnimationChanged() {
|
||||||
localStorage.setItem('enableFlipAnimation', this.enableFlipAnimation);
|
localStorage.setItem('enableFlipAnimation', this.enableFlipAnimation);
|
||||||
},
|
},
|
||||||
|
|
||||||
enableRightToLeftChanged() {
|
enableRightToLeftChanged() {
|
||||||
localStorage.setItem('enableRightToLeft', this.enableRightToLeft);
|
localStorage.setItem('enableRightToLeft', this.enableRightToLeft);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
@ -1,30 +1,28 @@
|
|||||||
$(function(){
|
$(function () {
|
||||||
var filter = [];
|
let filter = [];
|
||||||
var result = [];
|
let result = [];
|
||||||
$('.uk-card-title').each(function(){
|
$('.uk-card-title').each(function () {
|
||||||
filter.push($(this).text());
|
filter.push($(this).text());
|
||||||
});
|
});
|
||||||
$('.uk-search-input').keyup(function(){
|
$('.uk-search-input').keyup(function () {
|
||||||
var input = $('.uk-search-input').val();
|
let input = $('.uk-search-input').val();
|
||||||
var regex = new RegExp(input, 'i');
|
let regex = new RegExp(input, 'i');
|
||||||
|
|
||||||
if (input === '') {
|
if (input === '') {
|
||||||
$('.item').each(function(){
|
$('.item').each(function () {
|
||||||
$(this).removeAttr('hidden');
|
$(this).removeAttr('hidden');
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
filter.forEach(function (text, i) {
|
||||||
filter.forEach(function(text, i){
|
result[i] = text.match(regex);
|
||||||
result[i] = text.match(regex);
|
});
|
||||||
});
|
$('.item').each(function (i) {
|
||||||
$('.item').each(function(i){
|
if (result[i]) {
|
||||||
if (result[i]) {
|
$(this).removeAttr('hidden');
|
||||||
$(this).removeAttr('hidden');
|
} else {
|
||||||
}
|
$(this).attr('hidden', '');
|
||||||
else {
|
}
|
||||||
$(this).attr('hidden', '');
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
$(() => {
|
$(() => {
|
||||||
$('#sort-select').change(() => {
|
$('#sort-select').change(() => {
|
||||||
const sort = $('#sort-select').find(':selected').attr('id');
|
const sort = $('#sort-select').find(':selected').attr('id');
|
||||||
const ary = sort.split('-');
|
const ary = sort.split('-');
|
||||||
const by = ary[0];
|
const by = ary[0];
|
||||||
const dir = ary[1];
|
const dir = ary[1];
|
||||||
|
|
||||||
const url = `${location.protocol}//${location.host}${location.pathname}`;
|
const url = `${location.protocol}//${location.host}${location.pathname}`;
|
||||||
const newURL = `${url}?${$.param({
|
const newURL = `${url}?${$.param({
|
||||||
sort: by,
|
sort: by,
|
||||||
ascend: dir === 'up' ? 1 : 0
|
ascend: dir === 'up' ? 1 : 0,
|
||||||
})}`;
|
})}`;
|
||||||
window.location.href = newURL;
|
window.location.href = newURL;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,147 +1,144 @@
|
|||||||
const component = () => {
|
const component = () => {
|
||||||
return {
|
return {
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
plugins: [],
|
plugins: [],
|
||||||
pid: undefined,
|
pid: undefined,
|
||||||
subscription: undefined, // selected subscription
|
subscription: undefined, // selected subscription
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
fetch(`${base_url}api/admin/plugin`)
|
fetch(`${base_url}api/admin/plugin`)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.success) throw new Error(data.error);
|
if (!data.success) throw new Error(data.error);
|
||||||
this.plugins = data.plugins;
|
this.plugins = data.plugins;
|
||||||
|
|
||||||
let pid = localStorage.getItem("plugin");
|
let pid = localStorage.getItem('plugin');
|
||||||
if (!pid || !this.plugins.find((p) => p.id === pid)) {
|
if (!pid || !this.plugins.find((p) => p.id === pid)) {
|
||||||
pid = this.plugins[0].id;
|
pid = this.plugins[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pid = pid;
|
this.pid = pid;
|
||||||
this.list(pid);
|
this.list(pid);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
alert(
|
alert('danger', `Failed to list the available plugins. Error: ${e}`);
|
||||||
"danger",
|
});
|
||||||
`Failed to list the available plugins. Error: ${e}`
|
},
|
||||||
);
|
pluginChanged() {
|
||||||
});
|
localStorage.setItem('plugin', this.pid);
|
||||||
},
|
this.list(this.pid);
|
||||||
pluginChanged() {
|
},
|
||||||
localStorage.setItem("plugin", this.pid);
|
list(pid) {
|
||||||
this.list(this.pid);
|
if (!pid) return;
|
||||||
},
|
fetch(
|
||||||
list(pid) {
|
`${base_url}api/admin/plugin/subscriptions?${new URLSearchParams({
|
||||||
if (!pid) return;
|
plugin: pid,
|
||||||
fetch(
|
})}`,
|
||||||
`${base_url}api/admin/plugin/subscriptions?${new URLSearchParams(
|
{
|
||||||
{
|
method: 'GET',
|
||||||
plugin: pid,
|
},
|
||||||
}
|
)
|
||||||
)}`,
|
.then((response) => response.json())
|
||||||
{
|
.then((data) => {
|
||||||
method: "GET",
|
if (!data.success) throw new Error(data.error);
|
||||||
}
|
this.subscriptions = data.subscriptions;
|
||||||
)
|
})
|
||||||
.then((response) => response.json())
|
.catch((e) => {
|
||||||
.then((data) => {
|
alert('danger', `Failed to list subscriptions. Error: ${e}`);
|
||||||
if (!data.success) throw new Error(data.error);
|
});
|
||||||
this.subscriptions = data.subscriptions;
|
},
|
||||||
})
|
renderStrCell(str) {
|
||||||
.catch((e) => {
|
const maxLength = 40;
|
||||||
alert(
|
if (str.length > maxLength)
|
||||||
"danger",
|
return `<td><span>${str.substring(
|
||||||
`Failed to list subscriptions. Error: ${e}`
|
0,
|
||||||
);
|
maxLength,
|
||||||
});
|
)}...</span><div uk-dropdown>${str}</div></td>`;
|
||||||
},
|
return `<td>${str}</td>`;
|
||||||
renderStrCell(str) {
|
},
|
||||||
const maxLength = 40;
|
renderDateCell(timestamp) {
|
||||||
if (str.length > maxLength)
|
return `<td>${moment
|
||||||
return `<td><span>${str.substring(
|
.duration(moment.unix(timestamp).diff(moment()))
|
||||||
0,
|
.humanize(true)}</td>`;
|
||||||
maxLength
|
},
|
||||||
)}...</span><div uk-dropdown>${str}</div></td>`;
|
selected(event, modal) {
|
||||||
return `<td>${str}</td>`;
|
const id = event.currentTarget.getAttribute('sid');
|
||||||
},
|
this.subscription = this.subscriptions.find((s) => s.id === id);
|
||||||
renderDateCell(timestamp) {
|
UIkit.modal(modal).show();
|
||||||
return `<td>${moment
|
},
|
||||||
.duration(moment.unix(timestamp).diff(moment()))
|
renderFilterRow(ft) {
|
||||||
.humanize(true)}</td>`;
|
const key = ft.key;
|
||||||
},
|
let type = ft.type;
|
||||||
selected(event, modal) {
|
switch (type) {
|
||||||
const id = event.currentTarget.getAttribute("sid");
|
case 'number-min':
|
||||||
this.subscription = this.subscriptions.find((s) => s.id === id);
|
type = 'number (minimum value)';
|
||||||
UIkit.modal(modal).show();
|
break;
|
||||||
},
|
case 'number-max':
|
||||||
renderFilterRow(ft) {
|
type = 'number (maximum value)';
|
||||||
const key = ft.key;
|
break;
|
||||||
let type = ft.type;
|
case 'date-min':
|
||||||
switch (type) {
|
type = 'minimum date';
|
||||||
case "number-min":
|
break;
|
||||||
type = "number (minimum value)";
|
case 'date-max':
|
||||||
break;
|
type = 'maximum date';
|
||||||
case "number-max":
|
break;
|
||||||
type = "number (maximum value)";
|
}
|
||||||
break;
|
let value = ft.value;
|
||||||
case "date-min":
|
|
||||||
type = "minimum date";
|
|
||||||
break;
|
|
||||||
case "date-max":
|
|
||||||
type = "maximum date";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let value = ft.value;
|
|
||||||
|
|
||||||
if (ft.type.startsWith("number") && isNaN(value)) value = "";
|
if (ft.type.startsWith('number') && isNaN(value)) value = '';
|
||||||
else if (ft.type.startsWith("date") && value)
|
else if (ft.type.startsWith('date') && value)
|
||||||
value = moment(Number(value)).format("MMM D, YYYY");
|
value = moment(Number(value)).format('MMM D, YYYY');
|
||||||
|
|
||||||
return `<td>${key}</td><td>${type}</td><td>${value}</td>`;
|
return `<td>${key}</td><td>${type}</td><td>${value}</td>`;
|
||||||
},
|
},
|
||||||
actionHandler(event, type) {
|
actionHandler(event, type) {
|
||||||
const id = $(event.currentTarget).closest("tr").attr("sid");
|
const id = $(event.currentTarget).closest('tr').attr('sid');
|
||||||
if (type !== 'delete') return this.action(id, type);
|
if (type !== 'delete') return this.action(id, type);
|
||||||
UIkit.modal.confirm('Are you sure you want to delete the subscription? This cannot be undone.', {
|
UIkit.modal
|
||||||
labels: {
|
.confirm(
|
||||||
ok: 'Yes, delete it',
|
'Are you sure you want to delete the subscription? This cannot be undone.',
|
||||||
cancel: 'Cancel'
|
{
|
||||||
}
|
labels: {
|
||||||
}).then(() => {
|
ok: 'Yes, delete it',
|
||||||
this.action(id, type);
|
cancel: 'Cancel',
|
||||||
});
|
},
|
||||||
},
|
},
|
||||||
action(id, type) {
|
)
|
||||||
if (this.loading) return;
|
.then(() => {
|
||||||
this.loading = true;
|
this.action(id, type);
|
||||||
fetch(
|
});
|
||||||
`${base_url}api/admin/plugin/subscriptions${type === 'update' ? '/update' : ''}?${new URLSearchParams(
|
},
|
||||||
{
|
action(id, type) {
|
||||||
plugin: this.pid,
|
if (this.loading) return;
|
||||||
subscription: id,
|
this.loading = true;
|
||||||
}
|
fetch(
|
||||||
)}`,
|
`${base_url}api/admin/plugin/subscriptions${
|
||||||
{
|
type === 'update' ? '/update' : ''
|
||||||
method: type === 'delete' ? "DELETE" : 'POST'
|
}?${new URLSearchParams({
|
||||||
}
|
plugin: this.pid,
|
||||||
)
|
subscription: id,
|
||||||
.then((response) => response.json())
|
})}`,
|
||||||
.then((data) => {
|
{
|
||||||
if (!data.success) throw new Error(data.error);
|
method: type === 'delete' ? 'DELETE' : 'POST',
|
||||||
if (type === 'update')
|
},
|
||||||
alert("success", `Checking updates for subscription ${id}. Check the log for the progress or come back to this page later.`);
|
)
|
||||||
})
|
.then((response) => response.json())
|
||||||
.catch((e) => {
|
.then((data) => {
|
||||||
alert(
|
if (!data.success) throw new Error(data.error);
|
||||||
"danger",
|
if (type === 'update')
|
||||||
`Failed to ${type} subscription. Error: ${e}`
|
alert(
|
||||||
);
|
'success',
|
||||||
})
|
`Checking updates for subscription ${id}. Check the log for the progress or come back to this page later.`,
|
||||||
.finally(() => {
|
);
|
||||||
this.loading = false;
|
})
|
||||||
this.list(this.pid);
|
.catch((e) => {
|
||||||
});
|
alert('danger', `Failed to ${type} subscription. Error: ${e}`);
|
||||||
},
|
})
|
||||||
};
|
.finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
this.list(this.pid);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,82 +1,112 @@
|
|||||||
const component = () => {
|
const component = () => {
|
||||||
return {
|
return {
|
||||||
available: undefined,
|
available: undefined,
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
$.getJSON(`${base_url}api/admin/mangadex/expires`)
|
$.getJSON(`${base_url}api/admin/mangadex/expires`)
|
||||||
.done((data) => {
|
.done((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert('danger', 'Failed to check MangaDex integration status. Error: ' + data.error);
|
alert(
|
||||||
return;
|
'danger',
|
||||||
}
|
'Failed to check MangaDex integration status. Error: ' +
|
||||||
this.available = Boolean(data.expires && data.expires > Math.floor(Date.now() / 1000));
|
data.error,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.available = Boolean(
|
||||||
|
data.expires && data.expires > Math.floor(Date.now() / 1000),
|
||||||
|
);
|
||||||
|
|
||||||
if (this.available) this.getSubscriptions();
|
if (this.available) this.getSubscriptions();
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert('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() {
|
getSubscriptions() {
|
||||||
$.getJSON(`${base_url}api/admin/mangadex/subscriptions`)
|
$.getJSON(`${base_url}api/admin/mangadex/subscriptions`)
|
||||||
.done(data => {
|
.done((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert('danger', 'Failed to get subscriptions. Error: ' + data.error);
|
alert(
|
||||||
return;
|
'danger',
|
||||||
}
|
'Failed to get subscriptions. Error: ' + data.error,
|
||||||
this.subscriptions = data.subscriptions;
|
);
|
||||||
})
|
return;
|
||||||
.fail((jqXHR, status) => {
|
}
|
||||||
alert('danger', `Failed to get subscriptions. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
this.subscriptions = data.subscriptions;
|
||||||
})
|
})
|
||||||
},
|
.fail((jqXHR, status) => {
|
||||||
|
alert(
|
||||||
|
'danger',
|
||||||
|
`Failed to get subscriptions. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
rm(event) {
|
rm(event) {
|
||||||
const id = event.currentTarget.parentNode.getAttribute('data-id');
|
const id = event.currentTarget.parentNode.getAttribute('data-id');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
url: `${base_url}api/admin/mangadex/subscriptions/${id}`,
|
url: `${base_url}api/admin/mangadex/subscriptions/${id}`,
|
||||||
contentType: 'application/json'
|
contentType: 'application/json',
|
||||||
})
|
})
|
||||||
.done(data => {
|
.done((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert('danger', `Failed to delete subscription. Error: ${data.error}`);
|
alert(
|
||||||
}
|
'danger',
|
||||||
this.getSubscriptions();
|
`Failed to delete subscription. Error: ${data.error}`,
|
||||||
})
|
);
|
||||||
.fail((jqXHR, status) => {
|
}
|
||||||
alert('danger', `Failed to delete subscription. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
this.getSubscriptions();
|
||||||
});
|
})
|
||||||
},
|
.fail((jqXHR, status) => {
|
||||||
|
alert(
|
||||||
|
'danger',
|
||||||
|
`Failed to delete subscription. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
check(event) {
|
check(event) {
|
||||||
const id = event.currentTarget.parentNode.getAttribute('data-id');
|
const id = event.currentTarget.parentNode.getAttribute('data-id');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: `${base_url}api/admin/mangadex/subscriptions/check/${id}`,
|
url: `${base_url}api/admin/mangadex/subscriptions/check/${id}`,
|
||||||
contentType: 'application/json'
|
contentType: 'application/json',
|
||||||
})
|
})
|
||||||
.done(data => {
|
.done((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert('danger', `Failed to check subscription. Error: ${data.error}`);
|
alert(
|
||||||
return;
|
'danger',
|
||||||
}
|
`Failed to check subscription. Error: ${data.error}`,
|
||||||
alert('success', 'Mango is now checking the subscription for updates. This might take a while, but you can safely leave the page.');
|
);
|
||||||
})
|
return;
|
||||||
.fail((jqXHR, status) => {
|
}
|
||||||
alert('danger', `Failed to check subscription. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
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}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
formatRange(min, max) {
|
formatRange(min, max) {
|
||||||
if (!isNaN(min) && isNaN(max)) return `≥ ${min}`;
|
if (!isNaN(min) && isNaN(max)) return `≥ ${min}`;
|
||||||
if (isNaN(min) && !isNaN(max)) return `≤ ${max}`;
|
if (isNaN(min) && !isNaN(max)) return `≤ ${max}`;
|
||||||
if (isNaN(min) && isNaN(max)) return 'All';
|
if (isNaN(min) && isNaN(max)) return 'All';
|
||||||
|
|
||||||
if (min === max) return `= ${min}`;
|
if (min === max) return `= ${min}`;
|
||||||
return `${min} - ${max}`;
|
return `${min} - ${max}`;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,392 +1,421 @@
|
|||||||
$(() => {
|
$(() => {
|
||||||
setupAcard();
|
setupAcard();
|
||||||
});
|
});
|
||||||
|
|
||||||
const setupAcard = () => {
|
const setupAcard = () => {
|
||||||
$('.acard.is_entry').click((e) => {
|
$('.acard.is_entry').click((e) => {
|
||||||
if ($(e.target).hasClass('no-modal')) return;
|
if ($(e.target).hasClass('no-modal')) return;
|
||||||
const card = $(e.target).closest('.acard');
|
const card = $(e.target).closest('.acard');
|
||||||
|
|
||||||
showModal(
|
showModal(
|
||||||
$(card).attr('data-encoded-path'),
|
$(card).attr('data-encoded-path'),
|
||||||
parseInt($(card).attr('data-pages')),
|
parseInt($(card).attr('data-pages')),
|
||||||
parseFloat($(card).attr('data-progress')),
|
parseFloat($(card).attr('data-progress')),
|
||||||
$(card).attr('data-encoded-book-title'),
|
$(card).attr('data-encoded-book-title'),
|
||||||
$(card).attr('data-encoded-title'),
|
$(card).attr('data-encoded-title'),
|
||||||
$(card).attr('data-book-id'),
|
$(card).attr('data-book-id'),
|
||||||
$(card).attr('data-id')
|
$(card).attr('data-id'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function showModal(encodedPath, pages, percentage, encodedeTitle, encodedEntryTitle, titleID, entryID) {
|
function showModal(
|
||||||
const zipPath = decodeURIComponent(encodedPath);
|
encodedPath,
|
||||||
const title = decodeURIComponent(encodedeTitle);
|
pages,
|
||||||
const entry = decodeURIComponent(encodedEntryTitle);
|
percentage,
|
||||||
$('#modal button, #modal a').each(function() {
|
encodedeTitle,
|
||||||
$(this).removeAttr('hidden');
|
encodedEntryTitle,
|
||||||
});
|
titleID,
|
||||||
if (percentage === 0) {
|
entryID,
|
||||||
$('#continue-btn').attr('hidden', '');
|
) {
|
||||||
$('#unread-btn').attr('hidden', '');
|
const zipPath = decodeURIComponent(encodedPath);
|
||||||
} else if (percentage === 100) {
|
const title = decodeURIComponent(encodedeTitle);
|
||||||
$('#read-btn').attr('hidden', '');
|
const entry = decodeURIComponent(encodedEntryTitle);
|
||||||
$('#continue-btn').attr('hidden', '');
|
$('#modal button, #modal a').each(function () {
|
||||||
} else {
|
$(this).removeAttr('hidden');
|
||||||
$('#continue-btn').text('Continue from ' + percentage + '%');
|
});
|
||||||
}
|
if (percentage === 0) {
|
||||||
|
$('#continue-btn').attr('hidden', '');
|
||||||
|
$('#unread-btn').attr('hidden', '');
|
||||||
|
} else if (percentage === 100) {
|
||||||
|
$('#read-btn').attr('hidden', '');
|
||||||
|
$('#continue-btn').attr('hidden', '');
|
||||||
|
} else {
|
||||||
|
$('#continue-btn').text('Continue from ' + percentage + '%');
|
||||||
|
}
|
||||||
|
|
||||||
$('#modal-entry-title').find('span').text(entry);
|
$('#modal-entry-title').find('span').text(entry);
|
||||||
$('#modal-entry-title').next().attr('data-id', titleID);
|
$('#modal-entry-title').next().attr('data-id', titleID);
|
||||||
$('#modal-entry-title').next().attr('data-entry-id', entryID);
|
$('#modal-entry-title').next().attr('data-entry-id', entryID);
|
||||||
$('#modal-entry-title').next().find('.title-rename-field').val(entry);
|
$('#modal-entry-title').next().find('.title-rename-field').val(entry);
|
||||||
$('#path-text').text(zipPath);
|
$('#path-text').text(zipPath);
|
||||||
$('#pages-text').text(pages + ' pages');
|
$('#pages-text').text(pages + ' pages');
|
||||||
|
|
||||||
$('#beginning-btn').attr('href', `${base_url}reader/${titleID}/${entryID}/1`);
|
$('#beginning-btn').attr('href', `${base_url}reader/${titleID}/${entryID}/1`);
|
||||||
$('#continue-btn').attr('href', `${base_url}reader/${titleID}/${entryID}`);
|
$('#continue-btn').attr('href', `${base_url}reader/${titleID}/${entryID}`);
|
||||||
|
|
||||||
$('#read-btn').click(function() {
|
$('#read-btn').click(function () {
|
||||||
updateProgress(titleID, entryID, pages);
|
updateProgress(titleID, entryID, pages);
|
||||||
});
|
});
|
||||||
$('#unread-btn').click(function() {
|
$('#unread-btn').click(function () {
|
||||||
updateProgress(titleID, entryID, 0);
|
updateProgress(titleID, entryID, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#modal-edit-btn').attr('onclick', `edit("${entryID}")`);
|
$('#modal-edit-btn').attr('onclick', `edit("${entryID}")`);
|
||||||
|
|
||||||
$('#modal-download-btn').attr('href', `${base_url}api/download/${titleID}/${entryID}`);
|
$('#modal-download-btn').attr(
|
||||||
|
'href',
|
||||||
|
`${base_url}api/download/${titleID}/${entryID}`,
|
||||||
|
);
|
||||||
|
|
||||||
UIkit.modal($('#modal')).show();
|
UIkit.modal($('#modal')).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
UIkit.util.on(document, 'hidden', '#modal', () => {
|
UIkit.util.on(document, 'hidden', '#modal', () => {
|
||||||
$('#read-btn').off('click');
|
$('#read-btn').off('click');
|
||||||
$('#unread-btn').off('click');
|
$('#unread-btn').off('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateProgress = (tid, eid, page) => {
|
const updateProgress = (tid, eid, page) => {
|
||||||
let url = `${base_url}api/progress/${tid}/${page}`
|
let url = `${base_url}api/progress/${tid}/${page}`;
|
||||||
const query = $.param({
|
const query = $.param({
|
||||||
eid: eid
|
eid,
|
||||||
});
|
});
|
||||||
if (eid)
|
if (eid) url += `?${query}`;
|
||||||
url += `?${query}`;
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
url: url,
|
url,
|
||||||
dataType: 'json'
|
dataType: 'json',
|
||||||
})
|
})
|
||||||
.done(data => {
|
.done((data) => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
location.reload();
|
location.reload();
|
||||||
} else {
|
} else {
|
||||||
error = data.error;
|
error = data.error;
|
||||||
alert('danger', error);
|
alert('danger', error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.fail((jqXHR, status) => {
|
||||||
alert('danger', `Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
alert('danger', `Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renameSubmit = (name, eid) => {
|
const renameSubmit = (name, eid) => {
|
||||||
const upload = $('.upload-field');
|
const upload = $('.upload-field');
|
||||||
const titleId = upload.attr('data-title-id');
|
const titleId = upload.attr('data-title-id');
|
||||||
|
|
||||||
if (name.length === 0) {
|
if (name.length === 0) {
|
||||||
alert('danger', 'The display name should not be empty');
|
alert('danger', 'The display name should not be empty');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = $.param({
|
const query = $.param({
|
||||||
eid: eid
|
eid,
|
||||||
});
|
});
|
||||||
let url = `${base_url}api/admin/display_name/${titleId}/${name}`;
|
let url = `${base_url}api/admin/display_name/${titleId}/${name}`;
|
||||||
if (eid)
|
if (eid) url += `?${query}`;
|
||||||
url += `?${query}`;
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
url: url,
|
url,
|
||||||
contentType: "application/json",
|
contentType: 'application/json',
|
||||||
dataType: 'json'
|
dataType: 'json',
|
||||||
})
|
})
|
||||||
.done(data => {
|
.done((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert('danger', `Failed to update display name. Error: ${data.error}`);
|
alert('danger', `Failed to update display name. Error: ${data.error}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
location.reload();
|
location.reload();
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.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}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renameSortNameSubmit = (name, eid) => {
|
const renameSortNameSubmit = (name, eid) => {
|
||||||
const upload = $('.upload-field');
|
const upload = $('.upload-field');
|
||||||
const titleId = upload.attr('data-title-id');
|
const titleId = upload.attr('data-title-id');
|
||||||
|
|
||||||
const params = {};
|
const params = {};
|
||||||
if (eid) params.eid = eid;
|
if (eid) params.eid = eid;
|
||||||
if (name) params.name = name;
|
if (name) params.name = name;
|
||||||
const query = $.param(params);
|
const query = $.param(params);
|
||||||
let url = `${base_url}api/admin/sort_title/${titleId}?${query}`;
|
let url = `${base_url}api/admin/sort_title/${titleId}?${query}`;
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
url,
|
url,
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
dataType: 'json'
|
dataType: 'json',
|
||||||
})
|
})
|
||||||
.done(data => {
|
.done((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert('danger', `Failed to update sort title. Error: ${data.error}`);
|
alert('danger', `Failed to update sort title. Error: ${data.error}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
location.reload();
|
location.reload();
|
||||||
})
|
})
|
||||||
.fail((jqXHR, status) => {
|
.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}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const edit = (eid) => {
|
const edit = (eid) => {
|
||||||
const cover = $('#edit-modal #cover');
|
const cover = $('#edit-modal #cover');
|
||||||
let url = cover.attr('data-title-cover');
|
let url = cover.attr('data-title-cover');
|
||||||
let displayName = $('h2.uk-title > span').text();
|
let displayName = $('h2.uk-title > span').text();
|
||||||
let fileTitle = $('h2.uk-title').attr('data-file-title');
|
let fileTitle = $('h2.uk-title').attr('data-file-title');
|
||||||
let sortTitle = $('h2.uk-title').attr('data-sort-title');
|
let sortTitle = $('h2.uk-title').attr('data-sort-title');
|
||||||
|
|
||||||
if (eid) {
|
if (eid) {
|
||||||
const item = $(`#${eid}`);
|
const item = $(`#${eid}`);
|
||||||
url = item.find('img').attr('data-src');
|
url = item.find('img').attr('data-src');
|
||||||
displayName = item.find('.uk-card-title').attr('data-title');
|
displayName = item.find('.uk-card-title').attr('data-title');
|
||||||
fileTitle = item.find('.uk-card-title').attr('data-file-title');
|
fileTitle = item.find('.uk-card-title').attr('data-file-title');
|
||||||
sortTitle = item.find('.uk-card-title').attr('data-sort-title');
|
sortTitle = item.find('.uk-card-title').attr('data-sort-title');
|
||||||
$('#title-progress-control').attr('hidden', '');
|
$('#title-progress-control').attr('hidden', '');
|
||||||
} else {
|
} else {
|
||||||
$('#title-progress-control').removeAttr('hidden');
|
$('#title-progress-control').removeAttr('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
cover.attr('data-src', url);
|
cover.attr('data-src', url);
|
||||||
|
|
||||||
const displayNameField = $('#display-name-field');
|
const displayNameField = $('#display-name-field');
|
||||||
displayNameField.attr('value', displayName);
|
displayNameField.attr('value', displayName);
|
||||||
displayNameField.attr('placeholder', fileTitle);
|
displayNameField.attr('placeholder', fileTitle);
|
||||||
displayNameField.keyup(event => {
|
displayNameField.keyup((event) => {
|
||||||
if (event.keyCode === 13) {
|
if (event.keyCode === 13) {
|
||||||
renameSubmit(displayNameField.val() || fileTitle, eid);
|
renameSubmit(displayNameField.val() || fileTitle, eid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
displayNameField.siblings('a.uk-form-icon').click(() => {
|
displayNameField.siblings('a.uk-form-icon').click(() => {
|
||||||
renameSubmit(displayNameField.val() || fileTitle, eid);
|
renameSubmit(displayNameField.val() || fileTitle, eid);
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortTitleField = $('#sort-title-field');
|
const sortTitleField = $('#sort-title-field');
|
||||||
sortTitleField.val(sortTitle);
|
sortTitleField.val(sortTitle);
|
||||||
sortTitleField.attr('placeholder', fileTitle);
|
sortTitleField.attr('placeholder', fileTitle);
|
||||||
sortTitleField.keyup(event => {
|
sortTitleField.keyup((event) => {
|
||||||
if (event.keyCode === 13) {
|
if (event.keyCode === 13) {
|
||||||
renameSortNameSubmit(sortTitleField.val(), eid);
|
renameSortNameSubmit(sortTitleField.val(), eid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
sortTitleField.siblings('a.uk-form-icon').click(() => {
|
sortTitleField.siblings('a.uk-form-icon').click(() => {
|
||||||
renameSortNameSubmit(sortTitleField.val(), eid);
|
renameSortNameSubmit(sortTitleField.val(), eid);
|
||||||
});
|
});
|
||||||
|
|
||||||
setupUpload(eid);
|
setupUpload(eid);
|
||||||
|
|
||||||
UIkit.modal($('#edit-modal')).show();
|
UIkit.modal($('#edit-modal')).show();
|
||||||
};
|
};
|
||||||
|
|
||||||
UIkit.util.on(document, 'hidden', '#edit-modal', () => {
|
UIkit.util.on(document, 'hidden', '#edit-modal', () => {
|
||||||
const displayNameField = $('#display-name-field');
|
const displayNameField = $('#display-name-field');
|
||||||
displayNameField.off('keyup');
|
displayNameField.off('keyup');
|
||||||
displayNameField.off('click');
|
displayNameField.off('click');
|
||||||
|
|
||||||
const sortTitleField = $('#sort-title-field');
|
const sortTitleField = $('#sort-title-field');
|
||||||
sortTitleField.off('keyup');
|
sortTitleField.off('keyup');
|
||||||
sortTitleField.off('click');
|
sortTitleField.off('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
const setupUpload = (eid) => {
|
const setupUpload = (eid) => {
|
||||||
const upload = $('.upload-field');
|
const upload = $('.upload-field');
|
||||||
const bar = $('#upload-progress').get(0);
|
const bar = $('#upload-progress').get(0);
|
||||||
const titleId = upload.attr('data-title-id');
|
const titleId = upload.attr('data-title-id');
|
||||||
const queryObj = {
|
const queryObj = {
|
||||||
tid: titleId
|
tid: titleId,
|
||||||
};
|
};
|
||||||
if (eid)
|
if (eid) queryObj['eid'] = eid;
|
||||||
queryObj['eid'] = eid;
|
const query = $.param(queryObj);
|
||||||
const query = $.param(queryObj);
|
const url = `${base_url}api/admin/upload/cover?${query}`;
|
||||||
const url = `${base_url}api/admin/upload/cover?${query}`;
|
UIkit.upload('.upload-field', {
|
||||||
UIkit.upload('.upload-field', {
|
url,
|
||||||
url: url,
|
name: 'file',
|
||||||
name: 'file',
|
error: (e) => {
|
||||||
error: (e) => {
|
alert('danger', `Failed to upload cover image: ${e.toString()}`);
|
||||||
alert('danger', `Failed to upload cover image: ${e.toString()}`);
|
},
|
||||||
},
|
loadStart: (e) => {
|
||||||
loadStart: (e) => {
|
$(bar).removeAttr('hidden');
|
||||||
$(bar).removeAttr('hidden');
|
bar.max = e.total;
|
||||||
bar.max = e.total;
|
bar.value = e.loaded;
|
||||||
bar.value = e.loaded;
|
},
|
||||||
},
|
progress: (e) => {
|
||||||
progress: (e) => {
|
bar.max = e.total;
|
||||||
bar.max = e.total;
|
bar.value = e.loaded;
|
||||||
bar.value = e.loaded;
|
},
|
||||||
},
|
loadEnd: (e) => {
|
||||||
loadEnd: (e) => {
|
bar.max = e.total;
|
||||||
bar.max = e.total;
|
bar.value = e.loaded;
|
||||||
bar.value = e.loaded;
|
},
|
||||||
},
|
completeAll: () => {
|
||||||
completeAll: () => {
|
$(bar).attr('hidden', '');
|
||||||
$(bar).attr('hidden', '');
|
location.reload();
|
||||||
location.reload();
|
},
|
||||||
}
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const deselectAll = () => {
|
const deselectAll = () => {
|
||||||
$('.item .uk-card').each((i, e) => {
|
$('.item .uk-card').each((i, e) => {
|
||||||
const data = e.__x.$data;
|
const data = e.__x.$data;
|
||||||
data['selected'] = false;
|
data['selected'] = false;
|
||||||
});
|
});
|
||||||
$('#select-bar')[0].__x.$data['count'] = 0;
|
$('#select-bar')[0].__x.$data['count'] = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectAll = () => {
|
const selectAll = () => {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
$('.item .uk-card').each((i, e) => {
|
$('.item .uk-card').each((i, e) => {
|
||||||
const data = e.__x.$data;
|
const data = e.__x.$data;
|
||||||
if (!data['disabled']) {
|
if (!data['disabled']) {
|
||||||
data['selected'] = true;
|
data['selected'] = true;
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$('#select-bar')[0].__x.$data['count'] = count;
|
$('#select-bar')[0].__x.$data['count'] = count;
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedIDs = () => {
|
const selectedIDs = () => {
|
||||||
const ary = [];
|
const ary = [];
|
||||||
$('.item .uk-card').each((i, e) => {
|
$('.item .uk-card').each((i, e) => {
|
||||||
const data = e.__x.$data;
|
const data = e.__x.$data;
|
||||||
if (!data['disabled'] && data['selected']) {
|
if (!data['disabled'] && data['selected']) {
|
||||||
const item = $(e).closest('.item');
|
const item = $(e).closest('.item');
|
||||||
ary.push($(item).attr('id'));
|
ary.push($(item).attr('id'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return ary;
|
return ary;
|
||||||
};
|
};
|
||||||
|
|
||||||
const bulkProgress = (action, el) => {
|
const bulkProgress = (action, el) => {
|
||||||
const tid = $(el).attr('data-id');
|
const tid = $(el).attr('data-id');
|
||||||
const ids = selectedIDs();
|
const ids = selectedIDs();
|
||||||
const url = `${base_url}api/bulk_progress/${action}/${tid}`;
|
const url = `${base_url}api/bulk_progress/${action}/${tid}`;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'PUT',
|
type: 'PUT',
|
||||||
url: url,
|
url,
|
||||||
contentType: "application/json",
|
contentType: 'application/json',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
ids: ids
|
ids,
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
.done(data => {
|
.done((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
alert('danger', `Failed to mark entries as ${action}. Error: ${data.error}`);
|
alert(
|
||||||
return;
|
'danger',
|
||||||
}
|
`Failed to mark entries as ${action}. Error: ${data.error}`,
|
||||||
location.reload();
|
);
|
||||||
})
|
return;
|
||||||
.fail((jqXHR, status) => {
|
}
|
||||||
alert('danger', `Failed to mark entries as ${action}. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
location.reload();
|
||||||
})
|
})
|
||||||
.always(() => {
|
.fail((jqXHR, status) => {
|
||||||
deselectAll();
|
alert(
|
||||||
});
|
'danger',
|
||||||
|
`Failed to mark entries as ${action}. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.always(() => {
|
||||||
|
deselectAll();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const tagsComponent = () => {
|
const tagsComponent = () => {
|
||||||
return {
|
return {
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
tags: [],
|
tags: [],
|
||||||
tid: $('.upload-field').attr('data-title-id'),
|
tid: $('.upload-field').attr('data-title-id'),
|
||||||
loading: true,
|
loading: true,
|
||||||
|
|
||||||
load(admin) {
|
load(admin) {
|
||||||
this.isAdmin = admin;
|
this.isAdmin = admin;
|
||||||
|
|
||||||
$('.tag-select').select2({
|
$('.tag-select').select2({
|
||||||
tags: true,
|
tags: true,
|
||||||
placeholder: this.isAdmin ? 'Tag the title' : 'No tags found',
|
placeholder: this.isAdmin ? 'Tag the title' : 'No tags found',
|
||||||
disabled: !this.isAdmin,
|
disabled: !this.isAdmin,
|
||||||
templateSelection(state) {
|
templateSelection(state) {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.setAttribute('href', `${base_url}tags/${encodeURIComponent(state.text)}`);
|
a.setAttribute(
|
||||||
a.setAttribute('class', 'uk-link-reset');
|
'href',
|
||||||
a.onclick = event => {
|
`${base_url}tags/${encodeURIComponent(state.text)}`,
|
||||||
event.stopPropagation();
|
);
|
||||||
};
|
a.setAttribute('class', 'uk-link-reset');
|
||||||
a.innerText = state.text;
|
a.onclick = (event) => {
|
||||||
return a;
|
event.stopPropagation();
|
||||||
}
|
};
|
||||||
});
|
a.innerText = state.text;
|
||||||
|
return a;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.request(`${base_url}api/tags`, 'GET', (data) => {
|
this.request(`${base_url}api/tags`, 'GET', (data) => {
|
||||||
const allTags = data.tags;
|
const allTags = data.tags;
|
||||||
const url = `${base_url}api/tags/${this.tid}`;
|
const url = `${base_url}api/tags/${this.tid}`;
|
||||||
this.request(url, 'GET', data => {
|
this.request(url, 'GET', (data) => {
|
||||||
this.tags = data.tags;
|
this.tags = data.tags;
|
||||||
allTags.forEach(t => {
|
allTags.forEach((t) => {
|
||||||
const op = new Option(t, t, false, this.tags.indexOf(t) >= 0);
|
const op = new Option(t, t, false, this.tags.indexOf(t) >= 0);
|
||||||
$('.tag-select').append(op);
|
$('.tag-select').append(op);
|
||||||
});
|
});
|
||||||
$('.tag-select').on('select2:select', e => {
|
$('.tag-select').on('select2:select', (e) => {
|
||||||
this.onAdd(e);
|
this.onAdd(e);
|
||||||
});
|
});
|
||||||
$('.tag-select').on('select2:unselect', e => {
|
$('.tag-select').on('select2:unselect', (e) => {
|
||||||
this.onDelete(e);
|
this.onDelete(e);
|
||||||
});
|
});
|
||||||
$('.tag-select').on('change', () => {
|
$('.tag-select').on('change', () => {
|
||||||
this.onChange();
|
this.onChange();
|
||||||
});
|
});
|
||||||
$('.tag-select').trigger('change');
|
$('.tag-select').trigger('change');
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onChange() {
|
onChange() {
|
||||||
this.tags = $('.tag-select').select2('data').map(o => o.text);
|
this.tags = $('.tag-select')
|
||||||
},
|
.select2('data')
|
||||||
onAdd(event) {
|
.map((o) => o.text);
|
||||||
const tag = event.params.data.text;
|
},
|
||||||
const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(tag)}`;
|
onAdd(event) {
|
||||||
this.request(url, 'PUT');
|
const tag = event.params.data.text;
|
||||||
},
|
const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(
|
||||||
onDelete(event) {
|
tag,
|
||||||
const tag = event.params.data.text;
|
)}`;
|
||||||
const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(tag)}`;
|
this.request(url, 'PUT');
|
||||||
this.request(url, 'DELETE');
|
},
|
||||||
},
|
onDelete(event) {
|
||||||
request(url, method, cb) {
|
const tag = event.params.data.text;
|
||||||
$.ajax({
|
const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(
|
||||||
url: url,
|
tag,
|
||||||
method: method,
|
)}`;
|
||||||
dataType: 'json'
|
this.request(url, 'DELETE');
|
||||||
})
|
},
|
||||||
.done(data => {
|
request(url, method, cb) {
|
||||||
if (data.success) {
|
$.ajax({
|
||||||
if (cb) cb(data);
|
url,
|
||||||
} else {
|
method,
|
||||||
alert('danger', data.error);
|
dataType: 'json',
|
||||||
}
|
})
|
||||||
})
|
.done((data) => {
|
||||||
.fail((jqXHR, status) => {
|
if (data.success) {
|
||||||
alert('danger', `Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
if (cb) cb(data);
|
||||||
});
|
} else {
|
||||||
}
|
alert('danger', data.error);
|
||||||
};
|
}
|
||||||
|
})
|
||||||
|
.fail((jqXHR, status) => {
|
||||||
|
alert('danger', `Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
$(() => {
|
$(() => {
|
||||||
var target = base_url + 'admin/user/edit';
|
let target = base_url + 'admin/user/edit';
|
||||||
if (username) target += username;
|
if (username) target += username;
|
||||||
$('form').attr('action', target);
|
$('form').attr('action', target);
|
||||||
if (error) alert('danger', error);
|
if (error) alert('danger', error);
|
||||||
});
|
});
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
const remove = (username) => {
|
const remove = (username) => {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `${base_url}api/admin/user/delete/${username}`,
|
url: `${base_url}api/admin/user/delete/${username}`,
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
dataType: 'json'
|
dataType: 'json',
|
||||||
})
|
})
|
||||||
.done(data => {
|
.done((data) => {
|
||||||
if (data.success)
|
if (data.success) location.reload();
|
||||||
location.reload();
|
else alert('danger', data.error);
|
||||||
else
|
})
|
||||||
alert('danger', data.error);
|
.fail((jqXHR, status) => {
|
||||||
})
|
alert(
|
||||||
.fail((jqXHR, status) => {
|
'danger',
|
||||||
alert('danger', `Failed to delete the user. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
|
`Failed to delete the user. Error: [${jqXHR.status}] ${jqXHR.statusText}`,
|
||||||
});
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user