Finish search for MD

This commit is contained in:
Alex Ling 2021-02-26 10:32:50 +00:00
parent 011768ed1f
commit a9a2c9faa8
6 changed files with 175 additions and 34 deletions

View File

@ -34,9 +34,11 @@
.uk-card-body { .uk-card-body {
padding: 20px; padding: 20px;
.uk-card-title { .uk-card-title {
max-height: 3em;
font-size: 1rem; font-size: 1rem;
} }
.uk-card-title:not(.free-height) {
max-height: 3em;
}
} }
} }

View File

@ -3,9 +3,12 @@ const downloadComponent = () => {
chaptersLimit: 1000, chaptersLimit: 1000,
loading: false, loading: false,
addingToDownload: false, addingToDownload: false,
searchAvailable: false,
searchInput: '', searchInput: '',
data: {}, data: {},
chapters: [], chapters: [],
mangaAry: undefined, // undefined: not searching; []: searched but no result
candidateManga: {},
langChoice: 'All', langChoice: 'All',
groupChoice: 'All', groupChoice: 'All',
chapterRange: '', chapterRange: '',
@ -48,7 +51,21 @@ const downloadComponent = () => {
childList: true, childList: true,
subtree: true subtree: true
}); });
$.getJSON(`${base_url}api/admin/mangadex/expires`)
.done((data) => {
if (data.error) {
alert('danger', 'Failed to check MangaDex integration status. Error: ' + data.error);
return;
}
if (data.expires && data.expires > Math.floor(Date.now() / 1000))
this.searchAvailable = true;
})
.fail((jqXHR, status) => {
alert('danger', `Failed to check MangaDex integration status. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
})
}, },
filtersUpdated() { filtersUpdated() {
if (!this.data.chapters) if (!this.data.chapters)
this.chapters = []; this.chapters = [];
@ -90,10 +107,11 @@ const downloadComponent = () => {
console.log('filtered chapters:', _chapters); console.log('filtered chapters:', _chapters);
this.chapters = _chapters; this.chapters = _chapters;
}, },
search() { search() {
if (this.loading || this.searchInput === '') return; if (this.loading || this.searchInput === '') return;
this.loading = true;
this.data = {}; this.data = {};
this.mangaAry = undefined;
var int_id = -1; var int_id = -1;
try { try {
@ -103,29 +121,54 @@ const downloadComponent = () => {
} catch (e) { } catch (e) {
int_id = parseInt(this.searchInput); int_id = parseInt(this.searchInput);
} }
if (int_id <= 0 || isNaN(int_id)) {
alert('danger', 'Please make sure you are using a valid manga ID or manga URL from Mangadex.'); if (!isNaN(int_id) && int_id > 0) {
this.loading = false; // The input is a positive integer. We treat it as an ID.
return; this.loading = true;
$.getJSON(`${base_url}api/admin/mangadex/manga/${int_id}`)
.done((data) => {
if (data.error) {
alert('danger', 'Failed to get manga info. Error: ' + data.error);
return;
}
this.data = data;
this.chapters = data.chapters;
this.mangaAry = undefined;
})
.fail((jqXHR, status) => {
alert('danger', `Failed to get manga info. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
})
.always(() => {
this.loading = false;
});
} else {
if (!this.searchAvailable) {
alert('danger', 'Please make sure you are using a valid manga ID or manga URL from Mangadex. If you are trying to search MangaDex with a search term, please log in to MangaDex first by going to "Admin -> Connect to MangaDex"');
return;
}
// Search as a search term
this.loading = true;
$.getJSON(`${base_url}api/admin/mangadex/search?${$.param({
query: this.searchInput
})}`)
.done((data) => {
if (data.error) {
alert('danger', `Failed to search MangaDex. Error: ${data.error}`);
return;
}
this.mangaAry = data.manga;
this.data = {};
})
.fail((jqXHR, status) => {
alert('danger', `Failed to search MangaDex. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
})
.always(() => {
this.loading = false;
});
} }
$.getJSON(`${base_url}api/admin/mangadex/manga/${int_id}`)
.done((data) => {
if (data.error) {
alert('danger', 'Failed to get manga info. Error: ' + data.error);
return;
}
this.data = data;
this.chapters = data.chapters;
})
.fail((jqXHR, status) => {
alert('danger', `Failed to get manga info. Error: [${jqXHR.status}] ${jqXHR.statusText}`);
})
.always(() => {
this.loading = false;
});
}, },
parseRange(str) { parseRange(str) {
@ -228,6 +271,17 @@ const downloadComponent = () => {
this.addingToDownload = false; this.addingToDownload = false;
}); });
}); });
},
chooseManga(manga) {
this.candidateManga = manga;
UIkit.modal($('#modal').get(0)).show();
},
confirmManga(id) {
UIkit.modal($('#modal').get(0)).hide();
this.searchInput = id;
this.search();
} }
}; };
}; };

View File

@ -54,7 +54,7 @@ shards:
mangadex: mangadex:
git: https://github.com/hkalexling/mangadex.git git: https://github.com/hkalexling/mangadex.git
version: 0.6.0+git.commit.d87a1d7a6e84d122814b38618954dcc73fc5553b version: 0.8.0+git.commit.24e6fb51afd043721139355854e305b43bf98c43
mg: mg:
git: https://github.com/hkalexling/mg.git git: https://github.com/hkalexling/mg.git

View File

@ -57,14 +57,16 @@ struct APIRouter
} }
Koa.schema("mdChapter", { Koa.schema("mdChapter", {
"id" => Int64,
"group" => {} of String => String, "group" => {} of String => String,
}.merge(s %w(id title volume chapter language full_title time }.merge(s %w(title volume chapter language full_title time
manga_title manga_id)), manga_title manga_id)),
desc: "A MangaDex chapter") desc: "A MangaDex chapter")
Koa.schema "mdManga", { Koa.schema "mdManga", {
"id" => Int64,
"chapters" => ["mdChapter"], "chapters" => ["mdChapter"],
}.merge(s %w(id title description author artist cover_url)), }.merge(s %w(title description author artist cover_url)),
desc: "A MangaDex manga" desc: "A MangaDex manga"
Koa.describe "Returns a page in a manga entry" Koa.describe "Returns a page in a manga entry"
@ -944,6 +946,44 @@ struct APIRouter
end end
end end
Koa.describe "Searches MangaDex for manga matching `query`", <<-MD
Returns an empty list if the current user hasn't logged in to MangaDex.
MD
Koa.query "query"
Koa.response 200, schema: {
"success" => Bool,
"error" => String?,
"manga?" => [{
"id" => Int64,
"title" => String,
"description" => String,
"mainCover" => String,
}],
}
Koa.tags ["admin", "mangadex"]
get "/api/admin/mangadex/search" do |env|
begin
token, expires = Storage.default.get_md_token get_username env
client = MangaDex::Client.from_config
client.token = token
client.token_expires = expires
query = env.params.query["query"]
send_json env, {
"success" => true,
"error" => nil,
"manga" => client.partial_search query,
}.to_json
rescue e
Logger.error e
send_json env, {
"success" => false,
"error" => e.message,
}.to_json
end
end
doc = Koa.generate doc = Koa.generate
@@api_json = doc.to_json if doc @@api_json = doc.to_json if doc

View File

@ -530,9 +530,9 @@ class Storage
end end
end end
def get_md_token(username) : Tuple(String?, Time?) def get_md_token(username) : Tuple(String?, Time)
token = nil token = nil
expires = nil expires = Time.utc
MainFiber.run do MainFiber.run do
get_db do |db| get_db do |db|
db.query_one? "select token, expire from md_account where " \ db.query_one? "select token, expire from md_account where " \

View File

@ -1,17 +1,39 @@
<h2 class=uk-title>Download from MangaDex</h2> <h2 class=uk-title>Download from MangaDex</h2>
<div x-data="downloadComponent()" x-init="init()"> <div x-data="downloadComponent()" x-init="init()">
<div class="uk-grid-small" uk-grid> <div class="uk-grid-small" uk-grid style="margin-bottom:40px;">
<div class="uk-width-3-4"> <div class="uk-width-expand">
<input class="uk-input" type="text" placeholder="MangaDex manga ID or URL" x-model="searchInput" @keydown.enter.debounce="search()"> <input class="uk-input" type="text" :placeholder="searchAvailable ? 'Search MangaDex or enter a manga ID/URL' : 'MangaDex manga ID or URL'" x-model="searchInput" @keydown.enter.debounce="search()">
</div> </div>
<div class="uk-width-1-4"> <div class="uk-width-auto">
<div uk-spinner class="uk-align-center" x-show="loading" x-cloak></div> <div uk-spinner class="uk-align-center" x-show="loading" x-cloak></div>
<button class="uk-button uk-button-default" x-show="!loading" @click="search()">Search</button> <button class="uk-button uk-button-default" x-show="!loading" @click="search()">Search</button>
</div> </div>
</div> </div>
<template x-if="mangaAry">
<div>
<p x-show="mangaAry.length === 0">No matching manga found.</p>
<div class="uk-child-width-1-4@m uk-child-width-1-2" uk-grid>
<template x-for="manga in mangaAry" :key="manga.id">
<div class="item" :data-id="manga.id" @click="chooseManga(manga)">
<div class="uk-card uk-card-default">
<div class="uk-card-media-top uk-inline">
<img uk-img :data-src="manga.mainCover">
</div>
<div class="uk-card-body">
<h3 class="uk-card-title break-word uk-margin-remove-bottom free-height" x-text="manga.title"></h3>
<p class="uk-text-meta" x-text="`ID: ${manga.id}`"></p>
</div>
</div>
</div>
</template>
</div>
</div>
</template>
<div x-show="data && data.chapters" x-cloak> <div x-show="data && data.chapters" x-cloak>
<div class"uk-grid-small" uk-grid style="margin-top:40px"> <div class"uk-grid-small" uk-grid>
<div class="uk-width-1-4@s"> <div class="uk-width-1-4@s">
<img :src="data.mainCover"> <img :src="data.mainCover">
</div> </div>
@ -107,6 +129,29 @@
</template> </template>
</table> </table>
</div> </div>
<div id="modal" class="uk-flex-top" uk-modal="container: false">
<div class="uk-modal-dialog uk-margin-auto-vertical">
<button class="uk-modal-close-default" type="button" uk-close></button>
<div class="uk-modal-header">
<h3 class="uk-modal-title break-word" x-text="candidateManga.title"></h3>
</div>
<div class="uk-modal-body">
<div class="uk-grid">
<div class="uk-width-1-3@s">
<img uk-img data-width data-height :src="candidateManga.mainCover" style="width:100%;margin-bottom:10px;">
<a :href="`<%= mangadex_base_url %>/manga/${candidateManga.id}`" x-text="`ID: ${candidateManga.id}`" class="uk-link-muted"></a>
</div>
<div class="uk-width-2-3@s" uk-overflow-auto>
<p x-text="candidateManga.description"></p>
</div>
</div>
</div>
<div class="uk-modal-footer">
<button class="uk-button uk-button-primary" type="button" @click="confirmManga(candidateManga.id)">Choose</button>
</div>
</div>
</div>
</div> </div>
<% content_for "script" do %> <% content_for "script" do %>