refactor: Classes for Plugins (#631)

* set target to ES5

* refactor types

* use .gitkeep

* use dot to make dist/js dirs on top

* add hako

* add Hako

* add madara

* fix: url path js -> .js

* fix: branch

* freestyle for using UA, cookie

* add parseNovels for Hako

* update readme

* fix: conflict types

* add Kolnovel, remove node-fetch

* add bestlightnovel

* add plugins

* WQMangaStreamScraper
* PandaMTL
* NobleMTL
* LightNovelBrasil
* WPMangaStream

* add filters for popularNovels

* fix undefined json

* Linovelib (#26)

* linovelib CN

* icon for linovelib

* fix undefined chapter

* add check

* fix: add artist

* TuNovelaLigera ES + Madara Fix (#28)

* app repo test

* POST is now working

* update madara template + run generator

* template fix didnt get pushed commited idk why

* template update POST

* generated

* fix: useNewChapterEndpoint update (#29)

* useNewChapterEndpoint update

* Update Selectors

---------

Co-authored-by: K1ngfish3r <trulyihu@gmail.com>
This commit is contained in:
nyagami
2023-11-04 17:15:49 +07:00
committed by GitHub
parent 538841ddee
commit 20efcf404c
295 changed files with 67636 additions and 57286 deletions
+10 -11
View File
@@ -24,9 +24,9 @@ export const all_plugins = (): PluginList => {
const requirePath = `@plugins/${languageEnglish.toLowerCase()}/${
plugin.split(".")[0]
}`;
const instance = require(requirePath);
const instance = require(requirePath).default;
const { id, name, version, icon } = instance;
const info: Plugin.Info = {
const info: Plugin.PluginItem = {
id,
name,
lang: languageNative,
@@ -42,21 +42,20 @@ export const all_plugins = (): PluginList => {
const getPlugin = async (
requirePath: string
): Promise<Plugin.instance | null> => {
const plugin = await require(requirePath);
): Promise<Plugin.PluginBase | null> => {
const plugin = await require(requirePath).default;
if (isPlugin(plugin)) return plugin;
return null;
};
export const getFilter = async (
pluginRequirePath: string
) => (await getPlugin(pluginRequirePath))?.filters;
export const popularNovels = async (
pluginRequirePath: string,
options: Plugin.Options
) => {
const plugin = await getPlugin(pluginRequirePath);
if (!plugin) return null;
const popularNovels = await plugin.popularNovels(1, options);
return popularNovels;
};
options: Plugin.PopularNovelsOptions
) => (await getPlugin(pluginRequirePath))?.popularNovels(1, options);
export const searchNovels = async (
pluginRequirePath: string,
+31 -6
View File
@@ -7,6 +7,7 @@
<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/mdb5.css">
<link rel="stylesheet" href="/static/css/index.css">
<title>Testing plugin</title>
</head>
@@ -30,9 +31,31 @@
</nav>
<div id="test-container">
<div class="test-wrapper" id="popularNovels">
<div class="modal-container">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#filtersModal">
Filters
</button>
<div class="modal fade" id="filtersModal" tabindex="-1" role="dialog" aria-labelledby="filtersModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="filterModalLabel">Filters</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<div class="title">
popularNovels
<button type="button" class="btn btn-primary" onclick="popularNovels()">fetch</button>
<button type="button" class="btn btn-primary popularNovels-btn">fetch</button>
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
@@ -46,7 +69,7 @@
<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>
<button class="btn btn-primary searchNovels-btn" type="button">search</button>
</div>
</div>
<div class="spinner-border" role="status">
@@ -62,7 +85,7 @@
<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>
<button class="btn btn-primary parseNovelAndChapters-btn" type="button">fetch</button>
</div>
</div>
<div class="spinner-border" role="status">
@@ -82,7 +105,7 @@
<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>
<button class="btn btn-primary parseChapter-btn" type="button">fetch</button>
</div>
</div>
<div class="spinner-border" role="status">
@@ -100,7 +123,7 @@
<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>
<button class="btn btn-primary fetchImage-btn" type="button">fetch</button>
</div>
</div>
<div class="spinner-border" role="status">
@@ -111,7 +134,9 @@
</div>
</div>
</body>
<script src="/static/js/jquery-3.6.4.min.js"></script>
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/mdb5.js"></script>
<script src="/static/js/index.js"></script>
</html>
+9
View File
@@ -1,3 +1,8 @@
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.navbar{
width: 1080px;
align-items: center;
@@ -119,3 +124,7 @@
grid-template-columns: 15rem 15rem 15rem 15rem;
grid-gap: 0, 0;
}
.select-option-text{
padding-left: 1rem;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+308 -211
View File
@@ -1,17 +1,21 @@
var all_plugins = undefined;
var plugin_arr = [];
var current_array = [];
var current_plugin_requirePath = undefined;
const state = {
all_plugins: {},
plugin_arr: [],
current_array: [],
current_plugin: undefined,
filters: [],
}
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;
state.all_plugins = all;
language_selection.html(`<option value=""></option>`);
for(let lang in all){
language_selection.append(`<option>${lang}</option>`);
plugin_arr = plugin_arr.concat(all[lang]);
state.plugin_arr = state.plugin_arr.concat(all[lang]);
}
});
@@ -32,236 +36,329 @@ const display_search = (arr) => {
}
plugin_search.focus(e => {
if(!all_plugins) return;
if(!state.all_plugins) return;
if(language_selection.val()){
current_array = all_plugins[language_selection.val()];
state.current_array = state.all_plugins[language_selection.val()];
}else{
current_array = plugin_arr;
state.current_array = state.plugin_arr;
}
display_search(current_array);
display_search(state.current_array);
});
plugin_search.keyup(e => {
display_search(current_array.filter(plg => plg.name.toLowerCase().includes(plugin_search.val().toLowerCase())));
display_search(state.current_array.filter(plg => plg.name.toLowerCase().includes(plugin_search.val().toLowerCase())));
});
$('#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_requirePath = ele.attr('data-require');
state.current_plugin = new Plugin(plugin_requirePath);
plugin_search.attr('data-require', plugin_requirePath);
plugin_search.val(ele.text().split('/')[1]);
search_results.hide();
state.current_plugin.getfilters();
}
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>
class Plugin {
constructor(requirePath){
this.requirePath = requirePath;
}
getfilters = () => {
const makeSelect = (filter) => {
const container = document.createElement('div');
const select = document.createElement('select');
select.classList.add('select');
if(filter.inputType === 2 || filter.inputType === "Checkbox"){
select.setAttribute("multiple", "");
}else{
select.innerHTML = `<option value=""></option>`;
}
select.setAttribute("key", filter.key);
filter.values.forEach((v) => {
const option = document.createElement('option');
option.setAttribute("value", v.value)
option.innerText = v.label;
select.append(option);
});
container.append(select);
const label = document.createElement('label');
label.classList.add("form-label");
label.classList.add("select-label");
label.innerText = filter.label;
container.append(label);
const hr = document.createElement('hr');
hr.classList.add("hr");
container.append(hr);
return container;
}
fetch("/filters", {
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({"pluginRequirePath": this.requirePath}),
}).then(res => res.json())
.then(filters => {
const filtersModal = $("#filtersModal .modal-body");
filtersModal.html("");
filters.forEach(filter => {
filtersModal.append(makeSelect(filter))
});
state.filters.length = 0;
document.querySelectorAll('select.select').forEach(e => {
const key = e.getAttribute("key");
const select = new mdb.Select(e);
select.key = key;
state.filters.push(select);
})
})
}
popularNovels = () => {
const filters = {};
state.filters.forEach(_f => {
filters[_f.key] = _f.value;
})
$('#popularNovels .spinner-border').show();
fetch(`/popularNovels/`,{
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(
{
"pluginRequirePath": this.requirePath,
"filters": filters
}
),
})
.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>
</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>
`);
})
}
$('#popularNovels .spinner-border').hide();
});
}
searchNovels = () => {
const searchTerm = $('#searchNovels input').val();
$('#searchNovels .spinner-border').show();
fetch(`/searchNovels/`,{
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({"pluginRequirePath": this.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();
});
}
parseNovelAndChapters = () => {
const novelUrl = $('#parseNovelAndChapters input').val();
$('#parseNovelAndChapters .spinner-border').show();
fetch(`/parseNovelAndChapters/`,{
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({"pluginRequirePath": this.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>
`);
})
}
$('#searchNovels .spinner-border').hide();
});
`);
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><br>
<samp class="info-value btn-light">${chapter.releaseTime}</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();
});
}
parseChapter = () => {
const chapterUrl = $('#parseChapter input').val();
$('#parseChapter .spinner-border').show();
fetch(`/parseChapter/`,{
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({"pluginRequirePath": this.requirePath, "chapterUrl": chapterUrl}),
})
.then(res => res.json())
.then(chapterText => {
$('#parseChapter textarea').text(chapterText);
$('#parseChapter .spinner-border').hide();
})
}
fetchImage = () => {
const url = $('#fetchImage input').val();
$('#fetchImage .spinner-border').show();
fetch(`/fetchImage/`,{
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({"pluginRequirePath": this.requirePath, "url": url}),
})
.then(res => res.json())
.then(base64 => {
$('#fetchImage img').attr("src", `data:image/jpg;base64,${base64}`);
$('#fetchImage .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><br>
<samp class="info-value btn-light">${chapter.releaseTime}</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();
});
}
$(".popularNovels-btn").click(e => {
if(state.current_plugin){
state.current_plugin.popularNovels();
}
});
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();
})
}
$(".searchNovels-btn").click(e => {
if(state.current_plugin){
state.current_plugin.searchNovels();
}
});
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();
})
}
$(".parseNovelAndChapters-btn").click(e => {
if(state.current_plugin){
state.current_plugin.parseNovelAndChapters();
}
});
$(".parseChapter-btn").click(e => {
if(state.current_plugin){
state.current_plugin.parseChapter();
}
});
$(".fetchImage-btn").click(e => {
if(state.current_plugin){
state.current_plugin.fetchImage();
}
});
File diff suppressed because it is too large Load Diff