v0.5.1
fix: $PostGrid get/update post order not perform ascending. remove: window title remove version string. fix: $Searchbar open and close with key bugs. change: User.manager move to instance Booru.users.
This commit is contained in:
parent
73894739d0
commit
1aa20c00b2
1
dist/assets/index-8i15BK3r.js
vendored
Normal file
1
dist/assets/index-8i15BK3r.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/index-BM2d6uNq.js
vendored
1
dist/assets/index-BM2d6uNq.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
dist/index.html
vendored
6
dist/index.html
vendored
@ -4,11 +4,11 @@
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Danbooru Viewer v0.2.5</title>
|
||||
<title>Danbooru Viewer</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-BM2d6uNq.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BW2KEYV0.css">
|
||||
<script type="module" crossorigin src="/assets/index-8i15BK3r.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C3nfTvuP.css">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Danbooru Viewer v0.2.5</title>
|
||||
<title>Danbooru Viewer</title>
|
||||
<link rel="stylesheet" href="/index.scss">
|
||||
<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>
|
||||
|
@ -172,6 +172,11 @@ route#posts {
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
div.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"name": "danbooru-viewer",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"vite": "^5.4.8"
|
||||
|
@ -2,15 +2,18 @@ import { $Layout, type $LayoutEventMap } from "@elexis/layout";
|
||||
import { Booru } from "../../structure/Booru";
|
||||
import { Post } from "../../structure/Post";
|
||||
import { $PostTile } from "../PostTile/$PostTile";
|
||||
import { User } from "../../structure/User";
|
||||
|
||||
interface $PostGridOptions {
|
||||
tags?: string
|
||||
}
|
||||
export class $PostGrid extends $Layout<$PostGridEventMap> {
|
||||
posts = new Set<Post>();
|
||||
$posts = new Set<$PostTile>();
|
||||
$posts = new Map<Post, $PostTile>();
|
||||
orderMap = new Map<id, Post>();
|
||||
tags?: string;
|
||||
finished = false;
|
||||
limit = 100;
|
||||
constructor(options?: $PostGridOptions) {
|
||||
super();
|
||||
this.tags = options?.tags;
|
||||
@ -20,7 +23,7 @@ export class $PostGrid extends $Layout<$PostGridEventMap> {
|
||||
}
|
||||
|
||||
protected async init() {
|
||||
setInterval(() => { if (this.inDOM() && document.documentElement.scrollTop === 0) this.updateNewest(); }, 10000);
|
||||
setInterval(() => { if (this.inDOM() && document.documentElement.scrollTop === 0) this.getPost('newer'); }, 10000);
|
||||
Booru.events.on('set', () => {
|
||||
this.removeAll();
|
||||
if (this.finished) {
|
||||
@ -37,20 +40,12 @@ export class $PostGrid extends $Layout<$PostGridEventMap> {
|
||||
protected async loader() {
|
||||
if (!this.inDOM()) return setTimeout(() => this.loader(), 100);;
|
||||
while (this.inDOM() && document.documentElement.scrollHeight <= innerHeight * 2) {
|
||||
const posts = await this.getPosts();
|
||||
if (!posts.length) {
|
||||
this.finished = true;
|
||||
if (!this.posts.size) this.events.fire('noPost');
|
||||
return
|
||||
}
|
||||
const posts = await this.getPost('older');
|
||||
if (!posts.length) return;
|
||||
}
|
||||
if (document.documentElement.scrollTop + innerHeight > document.documentElement.scrollHeight - innerHeight * 2) {
|
||||
const posts = await this.getPosts();
|
||||
if (!posts.length) {
|
||||
this.finished = true;
|
||||
this.events.fire('endPost');
|
||||
return
|
||||
}
|
||||
const posts = await this.getPost('older');
|
||||
if (!posts.length) return;
|
||||
}
|
||||
setTimeout(() => this.loader(), 100);
|
||||
}
|
||||
@ -66,10 +61,10 @@ export class $PostGrid extends $Layout<$PostGridEventMap> {
|
||||
if (!post.file_url) continue;
|
||||
if (this.posts.has(post)) continue;
|
||||
const $post = new $PostTile(post);
|
||||
this.$posts.add($post);
|
||||
this.$posts.set(post, $post);
|
||||
this.posts.add(post);
|
||||
}
|
||||
const $posts = [...this.$posts.values()].sort((a, b) => +b.post.createdDate - +a.post.createdDate);
|
||||
const $posts = [...this.orderMap.values()].map(post => this.$posts.get(post));
|
||||
this.content($posts).render();
|
||||
return this;
|
||||
}
|
||||
@ -81,26 +76,78 @@ export class $PostGrid extends $Layout<$PostGridEventMap> {
|
||||
return this;
|
||||
}
|
||||
|
||||
async updateNewest() {
|
||||
const latestPost = this.sortedPosts.at(0);
|
||||
const posts = await Post.fetchMultiple(Booru.used, {tags: this.tags, id: latestPost ? `>${latestPost.id}` : undefined}, 100);
|
||||
async getPost(direction: 'newer' | 'older'): Promise<Post[]> {
|
||||
const tags = this.tags ? decodeURIComponent(this.tags).split('+') : undefined;
|
||||
const generalTags: string[] = [];
|
||||
const orderTags: string[] = [];
|
||||
let limit: number = this.limit;
|
||||
if (tags) for (const tag of tags) {
|
||||
if (tag.startsWith('ordfav:')) orderTags.push(tag);
|
||||
else if (tag.startsWith('order:')) orderTags.push(tag);
|
||||
else if (tag.startsWith('limit:')) limit = Number(tag.split(':')[1]);
|
||||
else generalTags.push(tag);
|
||||
}
|
||||
if (orderTags.length) {
|
||||
if (orderTags.length > 1) {
|
||||
this.events.fire('post_error', `Error: These query can't be used together [${orderTags}].`)
|
||||
return [];
|
||||
}
|
||||
const orderTag = orderTags[0];
|
||||
if (orderTag.startsWith('ordfav:')) {
|
||||
const username = orderTag.split(':')[1];
|
||||
const match_tags = generalTags.length ? `&search[post_tags_match]=${generalTags.toString().replaceAll(',', '+')}` : '';
|
||||
const beforeAfter = this.orderKeyList.length ? direction === 'newer' ? `&search[id]=>${this.orderKeyList.at(0)}` : `&search[id]=<${this.orderKeyList.at(-1)}` : undefined;
|
||||
const favoritesDataList = await Booru.used.fetch<FavoritesData[]>(`/favorites.json?search[user_name]=${username}${beforeAfter ?? ''}${match_tags}&limit=${limit}`);
|
||||
const posts = await Post.fetchMultiple(Booru.used, {tags: `id:${favoritesDataList.map(data => data.post_id).toString()}`});
|
||||
const newPostOrderMap = new Map();
|
||||
for (const fav of favoritesDataList) {
|
||||
const post = posts.find(post => post.id === fav.post_id);
|
||||
if (!post) continue;
|
||||
newPostOrderMap.set(fav.id, post);
|
||||
}
|
||||
this.orderMap = new Map(direction === 'newer' ? [...newPostOrderMap, ...this.orderMap] : [...this.orderMap, ...newPostOrderMap]);
|
||||
this.addPost(posts);
|
||||
return posts;
|
||||
}
|
||||
|
||||
async getPosts() {
|
||||
const oldestPost = this.sortedPosts.at(-1);
|
||||
const posts = await Post.fetchMultiple(Booru.used, {tags: this.tags, id: oldestPost ? `<${oldestPost.id}` : undefined}, 100);
|
||||
if (orderTag.startsWith('order:')) {
|
||||
const page = this.orderKeyList.length ? direction === 'newer' ? 1 : (this.orderMap.size / limit) + 1 : undefined;
|
||||
const posts = await Post.fetchMultiple(Booru.used, {tags: this.tags}, limit, page);
|
||||
const newPostOrderMap = new Map(posts.map(post => [post.id, post]));
|
||||
newPostOrderMap.forEach((post, id) => { if (this.orderMap.has(id)) newPostOrderMap.delete(id) });
|
||||
this.orderMap = new Map(direction === 'newer' ? [...newPostOrderMap, ...this.orderMap] : [...this.orderMap, ...newPostOrderMap])
|
||||
this.addPost(posts);
|
||||
return posts;
|
||||
return posts
|
||||
}
|
||||
}
|
||||
|
||||
get sortedPosts() { return this.posts.array.sort((a, b) => +b.createdDate - +a.createdDate); }
|
||||
const beforeAfter = this.orderKeyList.length ? direction === 'newer' ? `a${this.orderKeyList.at(0)}` : `b${this.orderKeyList.at(-1)}` : undefined;
|
||||
const posts = await Post.fetchMultiple(Booru.used, {tags: this.tags}, limit, beforeAfter);
|
||||
const newPostOrderMap = new Map(posts.map(post => [post.id, post]));
|
||||
this.orderMap = new Map(direction === 'newer' ? [...newPostOrderMap, ...this.orderMap] : [...this.orderMap, ...newPostOrderMap])
|
||||
this.addPost(posts);
|
||||
|
||||
if (!posts.length) {
|
||||
this.finished = true;
|
||||
if (!this.posts.size) this.events.fire('noPost');
|
||||
else this.events.fire('endPost')
|
||||
}
|
||||
|
||||
return posts
|
||||
}
|
||||
|
||||
get orderKeyList() { return [...this.orderMap.keys()]}
|
||||
}
|
||||
|
||||
interface $PostGridEventMap extends $LayoutEventMap {
|
||||
startLoad: [];
|
||||
noPost: [];
|
||||
endPost: [];
|
||||
post_error: [message: string]
|
||||
}
|
||||
|
||||
interface FavoritesData {
|
||||
id: id;
|
||||
post_id: id;
|
||||
user_id: id;
|
||||
}
|
@ -13,8 +13,8 @@ export class $Searchbar extends $Container {
|
||||
super('searchbar');
|
||||
this.build();
|
||||
window.addEventListener('keyup', (e) => {
|
||||
if (!this.inDOM() && e.key === '/') this.activate();
|
||||
if (this.inDOM() && e.key === 'Escape') this.inactivate();
|
||||
if (!this.inDOM() && e.key === '/') this.open();
|
||||
if (this.inDOM() && e.key === 'Escape') this.close();
|
||||
})
|
||||
}
|
||||
|
||||
@ -36,11 +36,14 @@ export class $Searchbar extends $Container {
|
||||
this.$selectionList
|
||||
]),
|
||||
this.$filter.on('click', () => {
|
||||
if (location.hash === '#search') $.back();
|
||||
if (location.hash === '#search') this.close();
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
open() { $.open(location.href + '#search'); return this; }
|
||||
close() { $.back(); return this; }
|
||||
|
||||
activate() {
|
||||
this.hide(false);
|
||||
this.$filter
|
||||
|
13
src/main.ts
13
src/main.ts
@ -47,13 +47,13 @@ $(document.body).content([
|
||||
// Searchbar
|
||||
$('div').class('searchbar').content(['Search in ', Booru.name$])
|
||||
.self($self => $Router.events.on('stateChange', ({beforeURL, afterURL}) => {if (beforeURL.hash === '#search') $self.hide(false); if (afterURL.hash === '#search') $self.hide(true)}))
|
||||
.on('click', () => $.open(location.href + '#search')),
|
||||
.on('click', () => $searchbar.open()),
|
||||
// Buttons
|
||||
$('div').class('buttons').content([
|
||||
// Search Icon
|
||||
$('ion-icon').class('search').name('search-outline').title('Search')
|
||||
.self($self => $Router.events.on('stateChange', ({beforeURL, afterURL}) => {if (beforeURL.hash === '#search') $self.hide(false); if (afterURL.hash === '#search') $self.hide(true)}))
|
||||
.on('click', () => $.open(location.href + '#search')),
|
||||
.on('click', () => $searchbar.open()),
|
||||
// Switch Booru
|
||||
$('ion-icon').class('switch').name('swap-horizontal').title('Switch Booru')
|
||||
.on('click', () => {
|
||||
@ -74,7 +74,7 @@ $(document.body).content([
|
||||
.on('login', user => { $account.content(user.name$.convert(value => value.at(0)?.toUpperCase() ?? '')).hide(false); })
|
||||
.on('logout', () => $account.hide(true))
|
||||
})
|
||||
.on('click', () => $.open(location.href + '#drawer'))
|
||||
.on('click', () => $drawer.open())
|
||||
])
|
||||
]),
|
||||
// Searchbar
|
||||
@ -97,7 +97,12 @@ $(document.body).content([
|
||||
})
|
||||
})
|
||||
]),
|
||||
$('div').class('no-post').content('No Posts').hide(true).self($div => $postGrid.on('noPost', () => $div.hide(false)).on('startLoad', () => $div.hide(true))),
|
||||
$('div').class('no-post').hide(true).self($div => {
|
||||
$div.on('startLoad', () => $div.hide(true))
|
||||
$postGrid
|
||||
.on('noPost', () => $div.hide(false).content('No Posts'))
|
||||
.on('post_error', message => $div.hide(false).content(message))
|
||||
}),
|
||||
$postGrid
|
||||
]
|
||||
}),
|
||||
|
@ -2,6 +2,7 @@ import { $EventManager, type $EventMap } from "elexis";
|
||||
import type { Post } from "./Post";
|
||||
import type { Tag } from "./Tag";
|
||||
import { ClientUser, type ClientUserData } from "./ClientUser";
|
||||
import type { User } from "./User";
|
||||
|
||||
export interface BooruOptions {
|
||||
origin: string;
|
||||
@ -16,6 +17,7 @@ export class Booru {
|
||||
user?: ClientUser;
|
||||
posts = new Map<id, Post>();
|
||||
tags = new Map<id, Tag>();
|
||||
users = new Map<id, User>();
|
||||
constructor(options: BooruOptions) {
|
||||
Object.assign(this, options);
|
||||
if (this.origin.endsWith('/')) this.origin = this.origin.slice(0, -1);
|
||||
|
@ -45,7 +45,7 @@ export class Post extends $EventManager<{update: []}> {
|
||||
return this;
|
||||
}
|
||||
|
||||
static async fetchMultiple(booru: Booru, tags?: Partial<MetaTags> | string, limit = 20) {
|
||||
static async fetchMultiple(booru: Booru, tags?: Partial<MetaTags> | string, limit = 20, page?: string | number) {
|
||||
let tagsQuery = '';
|
||||
if (tags) {
|
||||
if (typeof tags === 'string') tagsQuery = tags;
|
||||
@ -58,7 +58,7 @@ export class Post extends $EventManager<{update: []}> {
|
||||
}
|
||||
}
|
||||
}
|
||||
const dataArray = await booru.fetch<PostData[]>(`/posts.json?limit=${limit}&tags=${tagsQuery}&_method=get`);
|
||||
const dataArray = await booru.fetch<PostData[]>(`/posts.json?limit=${limit}&tags=${tagsQuery}${page ? `&page=${page}` : ''}&_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);
|
||||
@ -100,8 +100,8 @@ export class Post extends $EventManager<{update: []}> {
|
||||
}
|
||||
|
||||
get pathname() { return `/posts/${this.id}` }
|
||||
get uploader() { return User.manager.get(this.uploader_id); }
|
||||
get approver() { if (this.approver_id) return User.manager.get(this.approver_id); else return null }
|
||||
get uploader() { return this.booru.users.get(this.uploader_id); }
|
||||
get approver() { if (this.approver_id) return this.booru.users.get(this.approver_id); else return null }
|
||||
get isVideo() { return this.file_ext === 'mp4' || this.file_ext === 'webm' || this.file_ext === 'zip' }
|
||||
get isGif() { return this.file_ext === 'gif' }
|
||||
get isUgoria() { return this.file_ext === 'zip' }
|
||||
|
@ -3,7 +3,6 @@ import type { Booru } from "./Booru";
|
||||
export class UserOptions {}
|
||||
export interface User extends UserOptions, UserData {}
|
||||
export class User {
|
||||
static manager = new Map<id, User>();
|
||||
name$ = $.state('...');
|
||||
post_upload_count$ = $.state(0);
|
||||
level$ = $.state(10);
|
||||
@ -15,10 +14,17 @@ export class User {
|
||||
if (update$) this.update$();
|
||||
}
|
||||
|
||||
static async fetch(booru: Booru, id: id) {
|
||||
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);
|
||||
static async fetch(booru: Booru, id: username): Promise<User>;
|
||||
static async fetch(booru: Booru, id: id): Promise<User>;
|
||||
static async fetch(booru: Booru, id: id | username) {
|
||||
let data: UserData;
|
||||
if (typeof id === 'string') {
|
||||
const res = (await booru.fetch<UserData[]>(`/users.json?search[name]=${id}`)).at(0);
|
||||
if (!res) throw 'User Not Found';
|
||||
return data = res;
|
||||
} else data = await booru.fetch<UserData>(`/users/${id}.json`);
|
||||
const instance = booru.users.get(data.id)?.update(data) ?? new this(booru, data);
|
||||
booru.users.set(instance.id, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
@ -38,7 +44,7 @@ export class User {
|
||||
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);
|
||||
booru.users.set(instance.id, instance);
|
||||
return instance;
|
||||
});
|
||||
return list;
|
||||
|
Loading…
Reference in New Issue
Block a user