v0.5.0
new: add search query suggestion, change: fetch api optimize with api key. enhance: searchbar will auto add tags from url.
This commit is contained in:
parent
77d4f78cc2
commit
73894739d0
1
dist/assets/index-BM2d6uNq.js
vendored
Normal file
1
dist/assets/index-BM2d6uNq.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/index-Djy_8N6Q.js
vendored
1
dist/assets/index-Djy_8N6Q.js
vendored
File diff suppressed because one or more lines are too long
2
dist/index.html
vendored
2
dist/index.html
vendored
@ -7,7 +7,7 @@
|
||||
<title>Danbooru Viewer v0.2.5</title>
|
||||
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
|
||||
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-Djy_8N6Q.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BM2d6uNq.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BW2KEYV0.css">
|
||||
</head>
|
||||
<body>
|
||||
|
@ -2,7 +2,7 @@
|
||||
"name": "danbooru-viewer",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"vite": "^5.4.8"
|
||||
|
@ -62,6 +62,7 @@ export class $Searchbar extends $Container {
|
||||
const addTag = () => {e.preventDefault(); this.$tagInput.addTag().input()}
|
||||
const addSelectedTag = ($selection: $Selection) => {
|
||||
const inputIndex = this.$tagInput.children.indexOf(this.$tagInput.$inputor);
|
||||
if (this.$tagInput.$input.value().at(-1) === ':') return this.getSearchSuggestions();
|
||||
const nextTag = this.$tagInput.children.array.at(inputIndex + 1) as $Tag;
|
||||
this.$tagInput.addTag($selection.value());
|
||||
if (nextTag) this.$tagInput.editTag(nextTag);
|
||||
@ -94,7 +95,7 @@ export class $Searchbar extends $Container {
|
||||
e.preventDefault();
|
||||
const inputIndex = this.$tagInput.children.indexOf(this.$tagInput.$inputor)
|
||||
if (e.shiftKey) {
|
||||
this.$tagInput.editTag(this.$tagInput.children.array.at(inputIndex - 1) as $Tag)
|
||||
if (inputIndex - 1 >= 0) this.$tagInput.editTag(this.$tagInput.children.array.at(inputIndex - 1) as $Tag)
|
||||
break;
|
||||
}
|
||||
if (this.$selectionList.focused) addSelectedTag(this.$selectionList.focused);
|
||||
@ -128,8 +129,7 @@ export class $Searchbar extends $Container {
|
||||
}
|
||||
|
||||
async getSearchSuggestions() {
|
||||
const input = this.$tagInput.$input.value()
|
||||
if (!input.length) return this.$selectionList.clearSelections();
|
||||
const input = this.$tagInput.$input.value();
|
||||
const results = await Autocomplete.fetch(Booru.used, input, 20);
|
||||
this.$selectionList
|
||||
.clearSelections()
|
||||
@ -148,9 +148,8 @@ export class $Searchbar extends $Container {
|
||||
]) : null,
|
||||
data.isUser() ? $('span').class('user-level').content(data.level) : null
|
||||
])
|
||||
.on('click', () => {this.$tagInput.addTag(data.label).input()})
|
||||
.on('click', () => {this.$tagInput.addTag(data.value).input()})
|
||||
))
|
||||
if (!this.$tagInput.$input.value().length) this.$selectionList.clearSelections();
|
||||
}
|
||||
|
||||
search() {
|
||||
@ -163,6 +162,10 @@ export class $Searchbar extends $Container {
|
||||
checkURL(beforeURL: URL | undefined, afterURL: URL) {
|
||||
if (beforeURL?.hash === '#search') this.inactivate();
|
||||
if (afterURL.hash === '#search') this.activate();
|
||||
if (`${beforeURL?.pathname}${beforeURL?.search}` === `${afterURL.pathname}${afterURL.search}`) return;
|
||||
const tags_string = afterURL.searchParams.get('tags');
|
||||
this.$tagInput.clearAll();
|
||||
tags_string?.split(' ').forEach(tag => this.$tagInput.addTag(tag));
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,8 +272,8 @@ class $TagInput extends $Container {
|
||||
input() {
|
||||
this.insert(this.$inputor);
|
||||
this.$input.focus();
|
||||
if (this.$input.value()) this.$seachbar.getSearchSuggestions();
|
||||
else this.$seachbar.$selectionList.clearSelections();
|
||||
this.$seachbar.$selectionList.clearSelections();
|
||||
this.$seachbar.getSearchSuggestions();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -281,7 +284,8 @@ class $TagInput extends $Container {
|
||||
$tag.on('click', () => this.editTag($tag))
|
||||
this.tags.add($tag);
|
||||
this.value('');
|
||||
this.$inputor.replace($tag);
|
||||
if (this.$input.inDOM()) this.$inputor.replace($tag);
|
||||
else this.insert($tag);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -309,6 +313,11 @@ class $TagInput extends $Container {
|
||||
return this;
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.$input.focus();
|
||||
return this;
|
||||
}
|
||||
|
||||
get query() { return this.tags.array.map(tag => tag.name).toString().replace(',', '+') }
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,70 @@
|
||||
import type { Booru } from "./Booru";
|
||||
import type { TagCategory } from "./Tag";
|
||||
import type { UserLevelString } from "./User";
|
||||
import type { UserLevel } from "./User";
|
||||
|
||||
export class Autocomplete {
|
||||
static async fetch(booru: Booru, query: string, limit: number = 20) {
|
||||
const res = await fetch(`${booru.origin}/autocomplete.json?search[query]=${query}&search[type]=tag_query&version=1&limit=${limit}`).then(res => res.json()) as AutocompleteData[];
|
||||
return res.map(data => new AutocompleteResult(data));
|
||||
if (!query.length) return this.searchQuery.map(data => new AutocompleteResult(data))
|
||||
const res = await booru.fetch<AutocompleteData[]>(`/autocomplete.json?search[query]=${query}&search[type]=tag_query&version=1&limit=${limit}`);
|
||||
const searchQueryResult = query.length ? this.searchQuery.filter(sq => sq.value.startsWith(query) && sq.value !== query) : this.searchQuery
|
||||
return [...searchQueryResult, ...res].map(data => new AutocompleteResult(data));
|
||||
}
|
||||
|
||||
static searchQuery: AutocompleteSearchQueryData[] = [
|
||||
{value: 'user:', label: 'user:'},
|
||||
{value: 'approver:', label: 'approver:'},
|
||||
{value: '-approver:', label: '-approver:'},
|
||||
{value: 'order:', label: 'order:'},
|
||||
{value: 'ordfav:', label: 'ordfav:'},
|
||||
{value: 'ordfavgroup:', label: 'ordfavgroup:'},
|
||||
{value: 'search:', label: 'search:'},
|
||||
{value: 'favgroup:', label: 'favgroup:'},
|
||||
{value: '-favgroup:', label: '-favgroup:'},
|
||||
{value: 'favcount:', label: 'favcount:'},
|
||||
{value: 'id:', label: 'id:'},
|
||||
{value: 'tagcount:', label: 'tagcount:'},
|
||||
{value: 'gentags:', label: 'gentags:'},
|
||||
{value: 'arttags:', label: 'arttags:'},
|
||||
{value: 'chartags:', label: 'chartags:'},
|
||||
{value: 'copytags:', label: 'copytags:'},
|
||||
{value: 'metatags:', label: 'metatags:'},
|
||||
{value: 'score:', label: 'score:'},
|
||||
{value: 'upvote:', label: 'upvote:'},
|
||||
{value: 'downvote:', label: 'downvote:'},
|
||||
{value: 'disapproved:', label: 'disapproved:'},
|
||||
{value: 'md5:', label: 'md5:'},
|
||||
{value: 'width:', label: 'width:'},
|
||||
{value: 'height:', label: 'height:'},
|
||||
{value: 'ratio:', label: 'ratio:'},
|
||||
{value: 'mpixels:', label: 'mpixels:'},
|
||||
{value: 'filesize:', label: 'filesize:'},
|
||||
{value: 'duration:', label: 'duration:'},
|
||||
{value: 'is:', label: 'is:'},
|
||||
{value: 'has:', label: 'has:'},
|
||||
{value: 'pool:', label: 'pool:'},
|
||||
{value: '-pool:', label: '-pool:'},
|
||||
{value: 'ordpool:', label: 'ordpool:'},
|
||||
{value: 'random:', label: 'random:'},
|
||||
{value: 'limit:', label: 'limit:'},
|
||||
{value: 'date:', label: 'date:'},
|
||||
{value: 'commenter:', label: 'commenter:'},
|
||||
{value: 'note:', label: 'note:'},
|
||||
{value: 'noter:', label: 'noter:'},
|
||||
{value: 'noteupdater:', label: 'noteupdater:'},
|
||||
{value: 'status:', label: 'status:'},
|
||||
{value: '-status:', label: '-status:'},
|
||||
{value: 'rating:', label: 'rating:'},
|
||||
{value: '-rating:', label: '-rating:'},
|
||||
{value: 'source:', label: 'source:'},
|
||||
{value: '-source:', label: '-source:'},
|
||||
{value: 'pixiv:', label: 'pixiv:'},
|
||||
{value: 'parent:', label: 'parent:'},
|
||||
{value: 'child:', label: 'child:'},
|
||||
{value: 'flagger:', label: 'flagger:'},
|
||||
{value: 'appealer:', label: 'appealer:'},
|
||||
{value: 'commentary:', label: 'commentary:'},
|
||||
{value: 'commentaryupdater:', label: 'commentaryupdater:'},
|
||||
].map(data => ({type: 'query', ...data}))
|
||||
}
|
||||
|
||||
export interface AutocompleteResult extends AutocompleteBaseData {}
|
||||
@ -37,10 +95,10 @@ export class AutocompleteResult {
|
||||
}
|
||||
}
|
||||
|
||||
type AutocompleteData = AutocompleteBaseData & (AutocompleteUserData | AutocompleteTagData | AutocompleteTagAutocorrectData | AutocompleteTagAliasData);
|
||||
type AutocompleteData = AutocompleteBaseData & (AutocompleteUserData | AutocompleteTagData | AutocompleteTagAutocorrectData | AutocompleteTagAliasData | AutocompleteSearchQueryData);
|
||||
|
||||
interface AutocompleteBaseData {
|
||||
type: 'user' | 'tag' | 'tag-autocorrect' | 'tag-alias' | 'tag-word';
|
||||
type: 'user' | 'tag' | 'tag-autocorrect' | 'tag-alias' | 'tag-word' | 'query';
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
@ -48,7 +106,7 @@ interface AutocompleteBaseData {
|
||||
interface AutocompleteUserData {
|
||||
type: 'user';
|
||||
id: number;
|
||||
level: Lowercase<UserLevelString>;
|
||||
level: Lowercase<keyof UserLevel>;
|
||||
}
|
||||
interface AutocompleteTagData {
|
||||
type: 'tag';
|
||||
@ -73,3 +131,5 @@ interface AutocompleteTagWordData{
|
||||
post_count: number;
|
||||
antecedent: string;
|
||||
}
|
||||
|
||||
interface AutocompleteSearchQueryData {type: 'query', value: string, label: string}
|
@ -36,7 +36,8 @@ export class Booru {
|
||||
static set storageAPI(name: string | null) { if (name) localStorage.setItem('booru_api', name); else localStorage.removeItem('booru_api') }
|
||||
|
||||
async fetch<T>(endpoint: string) {
|
||||
const data = await fetch(`${this.origin}${endpoint}`).then(res => res.json()) as any;
|
||||
const auth = this.user ? `${endpoint.includes('?') ? '&' : '?'}login=${this.user.name}&api_key=${this.user.apiKey}` : '';
|
||||
const data = await fetch(`${this.origin}${endpoint}${auth}`).then(res => res.json()) as any;
|
||||
if (data.success === false) throw data.message;
|
||||
return data as T;
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ export class ArtistCommentary {
|
||||
}
|
||||
|
||||
static async fetch(booru: Booru, id: id) {
|
||||
const req = await fetch(`${booru.origin}/artist_commentaries/${id}.json`);
|
||||
const post = new this(await req.json());
|
||||
const data = await booru.fetch<ArtistCommentaryData>(`/artist_commentaries/${id}.json`);
|
||||
const post = new this(data);
|
||||
return post;
|
||||
}
|
||||
|
||||
@ -26,8 +26,7 @@ export class ArtistCommentary {
|
||||
else searchQuery += `&search[${key}]=${val}`
|
||||
}
|
||||
}
|
||||
const req = await fetch(`${booru.origin}/artist_commentaries.json?limit=${limit}${searchQuery}`);
|
||||
const dataArray: ArtistCommentaryData[] = await req.json();
|
||||
const dataArray = await booru.fetch<ArtistCommentaryData[]>(`/artist_commentaries.json?limit=${limit}${searchQuery}`);
|
||||
const list = dataArray.map(data => {
|
||||
const instance = new this(data);
|
||||
this.manager.set(instance.id, instance);
|
||||
|
@ -39,7 +39,7 @@ export class Post extends $EventManager<{update: []}> {
|
||||
}
|
||||
|
||||
async fetch() {
|
||||
const data = await fetch(`${this.booru.origin}/posts/${this.id}.json`).then(async data => await data.json()) as PostData;
|
||||
const data = await this.booru.fetch<PostData>(`/posts/${this.id}.json`);
|
||||
this.update(data);
|
||||
User.fetchMultiple(this.booru, {id: [this.uploader_id, this.approver_id].detype(null)}).then(() => this.update$());
|
||||
return this;
|
||||
@ -58,8 +58,7 @@ export class Post extends $EventManager<{update: []}> {
|
||||
}
|
||||
}
|
||||
}
|
||||
const req = await fetch(`${booru.origin}/posts.json?limit=${limit}&tags=${tagsQuery}&_method=get`);
|
||||
const dataArray: PostData[] = await req.json();
|
||||
const dataArray = await booru.fetch<PostData[]>(`/posts.json?limit=${limit}&tags=${tagsQuery}&_method=get`);
|
||||
if (dataArray instanceof Array === false) return [];
|
||||
const list = dataArray.map(data => {
|
||||
const instance = booru.posts.get(data.id)?.update(data) ?? new this(booru, data.id, data);
|
||||
|
@ -13,7 +13,7 @@ export class Tag {
|
||||
}
|
||||
|
||||
static async fetch(booru: Booru, id: id) {
|
||||
const data = await fetch(`${booru.origin}/tags/${id}.json`).then(async data => await data.json()) as TagData;
|
||||
const data = await booru.fetch<TagData>(`/tags/${id}.json`);
|
||||
const instance = booru.tags.get(data.id)?.update(data) ?? new this(booru, data);
|
||||
booru.tags.set(instance.id, instance);
|
||||
return instance;
|
||||
@ -32,8 +32,7 @@ export class Tag {
|
||||
else searchQuery += `&search[${key}]=${val}`
|
||||
}
|
||||
}
|
||||
const req = await fetch(`${booru.origin}/tags.json?limit=${limit}${searchQuery}`);
|
||||
const dataArray: TagData[] = await req.json();
|
||||
const dataArray = await booru.fetch<TagData[]>(`/tags.json?limit=${limit}${searchQuery}`);
|
||||
const list = dataArray.map(data => {
|
||||
const instance = booru.tags.get(data.id)?.update(data) ?? new this(booru, data);
|
||||
booru.tags.set(instance.id, instance);
|
||||
|
@ -16,7 +16,7 @@ export class User {
|
||||
}
|
||||
|
||||
static async fetch(booru: Booru, id: id) {
|
||||
const data = await fetch(`${booru.origin}/users/${id}.json`).then(async data => await data.json()) as UserData;
|
||||
const data = await booru.fetch<UserData>(`/users/${id}.json`);
|
||||
const instance = this.manager.get(data.id)?.update(data) ?? new this(booru, data);
|
||||
this.manager.set(instance.id, instance);
|
||||
return instance;
|
||||
@ -35,8 +35,7 @@ export class User {
|
||||
else searchQuery += `&search[${key}]=${val}`
|
||||
}
|
||||
}
|
||||
const req = await fetch(`${booru.origin}/users.json?limit=${limit}${searchQuery}`);
|
||||
const dataArray: UserData[] = await req.json();
|
||||
const dataArray = await booru.fetch<UserData[]>(`/users.json?limit=${limit}${searchQuery}`);
|
||||
const list = dataArray.map(data => {
|
||||
const instance = new this(booru, data);
|
||||
this.manager.set(instance.id, instance);
|
||||
|
Loading…
Reference in New Issue
Block a user