feat: Plugins (#465)
* update * gitignore * base file * template * json generator * remove valid method * Update README.md README * update template * Update README.md * update template * not support async arrow function * remove valid method * basic plugin validation. * Update README.md * update template * Update README.md * add fetchFile lib. empy .language file * comment fetch * update * comment fetch * auto comment, update template, template.min * Update README.md * add option to test * reset version * Update README.md * small fix for LightNovelReader * comment fetch * minor: LL date format * refactor: rename project, remove html2text lib * fix: error if there's no plugin changed * implement cookies bypass * update template.min.js * add syosetu * fix: get last file in case removing. shorter id * shoter native language name * change app project name * testing with UI would be less painful :) * remove console.log * Update README.md for UI testing * fix Syosetu * remove try catch * add ranobehub * add json for ranobehub * wrong icon path :D * fix ranobehub * fix status ranobe * add french * fix css * add chireads * add indonesian * add sakura novel * add sakuranovel json * add spanish * add skynovels * fix skynovels * add agitoon * add turkish * add epiknovel * add portuguese * update template * cheerio_space * add novel Fullme * minor * lol, this source havent been finished before. cant help * cant bypass this * add hasutl * novelasligera not working properly * add novelpub, but no searching * add jaomix * fix jaomix * add ranoberf * add indowebnovel * add novelringan * add noveldeglace * search not work for this * add oasisTL, this should have better popularNovels * add tunovelaligera * this source need to fix search * add bestlightnovel * add comrade mao * minor fix * add divinedaolibrary * add freewebnovel * lol xD * minor * update json * fix icon novelawuxia * update site in for pluginItem * some changes for more convenient * remove try catch * Update README.md * remove * Update README.md * trying to use pre-commit * update README * feat: .env for repo config * add foxaholic (#2) * rebase fox * fox officially 1.0.0 --------- Co-authored-by: K1ngfish3r <trulyihu@gmail.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" sizes="128x128" type="image/png" href="/static/ico.png" defer />
|
||||
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/css/index.css">
|
||||
<title>Testing plugin</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-light bg-light">
|
||||
<form class="form-inline">
|
||||
<input id="plugin" class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
|
||||
<button class="btn btn-outline-success my-2 my-sm-0" type="button" id="clear-search">Clear</button>
|
||||
<div id="search-results">
|
||||
</div>
|
||||
</form>
|
||||
<div class="input-group">
|
||||
<select class="custom-select" id="language">
|
||||
<option value=""></option>
|
||||
</select>
|
||||
<div class="input-group-append">
|
||||
<label class="input-group-text" for="language">Language</label>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="test-container">
|
||||
<div class="test-wrapper" id="popularNovels">
|
||||
<div class="title">
|
||||
popularNovels
|
||||
<button type="button" class="btn btn-primary" onclick="popularNovels()">fetch</button>
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="novel-list">
|
||||
</div>
|
||||
</div>
|
||||
<div class="test-wrapper" id="searchNovels">
|
||||
<div class="title">
|
||||
searchNovels
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="searchTerm">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="button" onclick="searchNovels()">search</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="novel-list">
|
||||
</div>
|
||||
</div>
|
||||
<div class="test-wrapper" id="parseNovelAndChapters">
|
||||
<div class="title">
|
||||
parseNovelAndChapters
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="novel url">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="button" onclick="parseNovelAndChapters()">fetch</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="novel-list">
|
||||
<div class="novel-item">
|
||||
</div>
|
||||
<div class="chapter-list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="test-wrapper" id="parseChapter">
|
||||
<div class="title">
|
||||
parseChapter
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="chapter url">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="button" onclick="parseChapter()">fetch</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" rows="10">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="test-wrapper" id="fetchImage">
|
||||
<div class="title">
|
||||
fetchImage
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="image url (shoule be in chapterText)">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="button" onclick="fetchImage()">fetch</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<img src="" style="min-height: 300px; max-width: 450px;" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="/static/js/jquery-3.6.4.min.js"></script>
|
||||
<script src="/static/js/index.js"></script>
|
||||
|
||||
</html>
|
||||
+7
File diff suppressed because one or more lines are too long
@@ -0,0 +1,120 @@
|
||||
.navbar{
|
||||
width: 1080px;
|
||||
align-items: center;
|
||||
padding-left: 5%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.input-group{
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
#search-results{
|
||||
position: absolute;
|
||||
top: calc(100% - 0.5rem);
|
||||
width: 15rem;
|
||||
padding: 0.375rem 0;
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.btn.disabled, .btn:disabled{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#test-container{
|
||||
align-items: center;
|
||||
height: max-content;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
|
||||
.test-wrapper{
|
||||
margin: 1rem;
|
||||
padding: 0.5rem;
|
||||
background-color: aliceblue;
|
||||
}
|
||||
|
||||
#popularNovels, #searchNovels{
|
||||
height: 350px;
|
||||
min-width: 95%;
|
||||
max-width: 95%;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
#parseNovelAndChapters{
|
||||
height: 380px;
|
||||
min-width: 95%;
|
||||
max-width: 95%;
|
||||
}
|
||||
|
||||
.title{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
display: none;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
vertical-align: text-bottom;
|
||||
border: 0.25em solid currentColor;
|
||||
border-right-color: transparent;
|
||||
border-radius: 50%;
|
||||
-webkit-animation: spinner-border .75s linear infinite;
|
||||
animation: spinner-border .75s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spinner-border{
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.novel-list{
|
||||
display: flex;
|
||||
margin-top: 0.5rem;
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.novel-item{
|
||||
display: flex;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.novel-item img {
|
||||
max-height: 90%;
|
||||
}
|
||||
|
||||
.novel-info{
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
.info-item{
|
||||
display: flex;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.info-value{
|
||||
width: 10rem;
|
||||
padding-left: 0.25rem;
|
||||
font-size: 0.65rem;
|
||||
word-wrap:break-word;
|
||||
}
|
||||
|
||||
.btn{
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.25rem;
|
||||
}
|
||||
|
||||
.chapter-list{
|
||||
position: relative;
|
||||
display: grid;
|
||||
min-width: 50%;
|
||||
overflow-y: scroll;
|
||||
overflow-x: scroll;
|
||||
grid-template-columns: 15rem 15rem 15rem 15rem;
|
||||
grid-gap: 0, 0;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,263 @@
|
||||
var all_plugins = undefined;
|
||||
var plugin_arr = [];
|
||||
var current_array = [];
|
||||
var current_plugin_requirePath = undefined;
|
||||
|
||||
const language_selection = $('#language');
|
||||
const plugin_search = $('#plugin');
|
||||
const search_results = $('#search-results');
|
||||
|
||||
fetch('/all_plugins').then(res => res.json()).then(all => {
|
||||
all_plugins = all;
|
||||
for(let lang in all){
|
||||
language_selection.append(`<option>${lang}</option>`);
|
||||
plugin_arr = plugin_arr.concat(all[lang]);
|
||||
}
|
||||
});
|
||||
|
||||
const display_search = (arr) => {
|
||||
search_results.hide();
|
||||
search_results.html('');
|
||||
arr.slice(0, 15).forEach(plugin => {
|
||||
search_results.append(`
|
||||
<div>
|
||||
<button data-require="${plugin.requirePath}" onclick="test_plugin(this)" type="button" class="search-item btn btn-light btn-outline-primary btn-md btn-block">${plugin.lang}/${plugin.name}</button>
|
||||
</div>
|
||||
`)
|
||||
});
|
||||
search_results.show();
|
||||
}
|
||||
|
||||
plugin_search.focus(e => {
|
||||
if(!all_plugins) return;
|
||||
if(language_selection.val()){
|
||||
current_array = all_plugins[language_selection.val()];
|
||||
}else{
|
||||
current_array = plugin_arr;
|
||||
}
|
||||
display_search(current_array);
|
||||
});
|
||||
|
||||
plugin_search.keyup(e => {
|
||||
display_search(current_array.filter(plg => plg.name.toLowerCase().includes(plugin_search.val())));
|
||||
});
|
||||
|
||||
$('#clear-search').click(e => {
|
||||
search_results.hide();
|
||||
plugin_search.val('');
|
||||
plugin_search.attr('data-require', '');
|
||||
})
|
||||
|
||||
const test_plugin = (btn) => {
|
||||
const ele = $(btn);
|
||||
current_plugin_requirePath = ele.attr('data-require');
|
||||
plugin_search.attr('data-require', current_plugin_requirePath);
|
||||
plugin_search.val(ele.text().split('/')[1]);
|
||||
search_results.hide();
|
||||
}
|
||||
|
||||
const popularNovels = () => {
|
||||
if(!current_plugin_requirePath) return;
|
||||
$('#popularNovels .spinner-border').show();
|
||||
fetch(`/popularNovels/`,{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({"pluginRequirePath": current_plugin_requirePath}),
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(novels => {
|
||||
const novel_list = $('#popularNovels .novel-list');
|
||||
novel_list.html('');
|
||||
if(novels.error){
|
||||
novel_list.html(novels.error);
|
||||
}else{
|
||||
novels.forEach(novel => {
|
||||
novel_list.append(`
|
||||
<div class="novel-item">
|
||||
<img src="${novel.cover}" alt="No cover found">
|
||||
<div class="novel-info">
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">name</div>
|
||||
<samp class="info-value btn-light">${novel.name?.slice(0, 50)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${novel.name}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">url</div>
|
||||
<samp class="info-value btn-light">${novel.url?.slice(0, 50)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${novel.url}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">cover</div>
|
||||
<samp class="info-value btn-light">${novel.cover?.slice(0, 50)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${novel.cover}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
})
|
||||
}
|
||||
$('#popularNovels .spinner-border').hide();
|
||||
});
|
||||
}
|
||||
|
||||
const searchNovels = () => {
|
||||
const searchTerm = $('#searchNovels input').val();
|
||||
if(!current_plugin_requirePath || !searchTerm) return;
|
||||
$('#searchNovels .spinner-border').show();
|
||||
fetch(`/searchNovels/`,{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({"pluginRequirePath": current_plugin_requirePath, "searchTerm": searchTerm}),
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(novels => {
|
||||
const novel_list = $('#searchNovels .novel-list');
|
||||
novel_list.html('');
|
||||
if(novels.error){
|
||||
novel_list.html(novels.error);
|
||||
}else{
|
||||
novels.forEach(novel => {
|
||||
novel_list.append(`
|
||||
<div class="novel-item">
|
||||
<img src="${novel.cover}" alt="No cover found">
|
||||
<div class="novel-info">
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">name</div>
|
||||
<samp class="info-value btn-light">${novel.name?.slice(0, 50)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${novel.name}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">url</div>
|
||||
<samp class="info-value btn-light">${novel.url?.slice(0, 50)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${novel.url}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">cover</div>
|
||||
<samp class="info-value btn-light">${novel.cover?.slice(0, 50)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${novel.cover}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
})
|
||||
}
|
||||
$('#searchNovels .spinner-border').hide();
|
||||
});
|
||||
}
|
||||
|
||||
const parseNovelAndChapters = () => {
|
||||
const novelUrl = $('#parseNovelAndChapters input').val();
|
||||
if(!current_plugin_requirePath || !novelUrl) return;
|
||||
$('#parseNovelAndChapters .spinner-border').show();
|
||||
fetch(`/parseNovelAndChapters/`,{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({"pluginRequirePath": current_plugin_requirePath, "novelUrl": novelUrl}),
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(sourceNovel => {
|
||||
const novel_item = $('#parseNovelAndChapters .novel-list .novel-item');
|
||||
novel_item.html(`
|
||||
<img src="${sourceNovel.cover}" alt="No cover found">
|
||||
<div class="novel-info">
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">name</div>
|
||||
<samp class="info-value btn-light">${sourceNovel.name?.slice(0, 50)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${sourceNovel.name}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">url</div>
|
||||
<samp class="info-value btn-light">${sourceNovel.url?.slice(0, 50)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${sourceNovel.url}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">cover</div>
|
||||
<samp class="info-value btn-light">${sourceNovel.cover?.slice(0, 50)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${sourceNovel.cover}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">summary</div>
|
||||
<samp class="info-value btn-light">${sourceNovel.summary?.slice(0, 50)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${sourceNovel.summary}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">author</div>
|
||||
<samp class="info-value btn-light">${sourceNovel.author}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${sourceNovel.author}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">artist</div>
|
||||
<samp class="info-value btn-light">${sourceNovel.artist}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${sourceNovel.artist}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">status</div>
|
||||
<samp class="info-value btn-light">${sourceNovel.status}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${sourceNovel.status}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-name btn btn-info disabled">genres</div>
|
||||
<samp class="info-value btn-light">${sourceNovel.genres?.slice(0, 50)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${sourceNovel.genres}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
if(sourceNovel.error){
|
||||
novel_item.html(sourceNovel.error);
|
||||
}else{
|
||||
const chapter_list = $('#parseNovelAndChapters .chapter-list');
|
||||
chapter_list.html('');
|
||||
sourceNovel.chapters.forEach(chapter => {
|
||||
chapter_list.append(`
|
||||
<div class="chapter-item">
|
||||
<samp class="info-value btn-light">${chapter.name?.slice(0, 26)}</samp>
|
||||
<div class="info-copy btn btn-primary" data="${chapter.url}" onclick="navigator.clipboard.writeText(this.getAttribute('data'))">Copy url</div>
|
||||
</div>
|
||||
`);
|
||||
})
|
||||
}
|
||||
$('#parseNovelAndChapters .spinner-border').hide();
|
||||
});
|
||||
}
|
||||
|
||||
const parseChapter = () => {
|
||||
const chapterUrl = $('#parseChapter input').val();
|
||||
if(!current_plugin_requirePath || !chapterUrl) return;
|
||||
$('#parseChapter .spinner-border').show();
|
||||
fetch(`/parseChapter/`,{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({"pluginRequirePath": current_plugin_requirePath, "chapterUrl": chapterUrl}),
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(chapterText => {
|
||||
$('#parseChapter textarea').text(chapterText);
|
||||
$('#parseChapter .spinner-border').hide();
|
||||
})
|
||||
}
|
||||
|
||||
const fetchImage = () => {
|
||||
const url = $('#fetchImage input').val();
|
||||
if(!current_plugin_requirePath || !url) return;
|
||||
$('#fetchImage .spinner-border').show();
|
||||
fetch(`/fetchImage/`,{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({"pluginRequirePath": current_plugin_requirePath, "url": url}),
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(base64 => {
|
||||
$('#fetchImage img').attr("src", `data:image/jpg;base64,${base64}`);
|
||||
$('#fetchImage .spinner-border').hide();
|
||||
})
|
||||
}
|
||||
+2
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user