v0.3.5
new: danbooru account login with api is available now. new: class Client User adjust: [css] route element padding inline. new: [css] color variable. new: interface APIError. new: [page] /login route. new: [$nav] menu button and account button. new: class $Drawer, with user account basic detail, and login/logout button. new: function numberFormat. optimize: [$IconButton] $icon default is hiding, method icon will set $icon visible. new: [$IconButton] method link, set 'click' listener with open link.
This commit is contained in:
parent
e9f5e1808d
commit
a992af35c2
1
dist/assets/index-BkbpsU_v.css
vendored
Normal file
1
dist/assets/index-BkbpsU_v.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/index-DaDrA_5o.js
vendored
1
dist/assets/index-DaDrA_5o.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-DeBFHjT4.css
vendored
1
dist/assets/index-DeBFHjT4.css
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-La3x4wRW.js
vendored
Normal file
1
dist/assets/index-La3x4wRW.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
dist/index.html
vendored
4
dist/index.html
vendored
@ -7,8 +7,8 @@
|
||||
<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-DaDrA_5o.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DeBFHjT4.css">
|
||||
<script type="module" crossorigin src="/assets/index-La3x4wRW.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BkbpsU_v.css">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
@ -3,6 +3,7 @@
|
||||
@import '/src/component/PostTile/$PostTile';
|
||||
@import '/src/component/Searchbar/$Searchbar';
|
||||
@import '/src/component/IconButton/$IconButton';
|
||||
@import '/src/component/Drawer/$Drawer';
|
||||
// routes
|
||||
@import '/src/route/post/$post_route';
|
||||
@import '/src/route/login/$login_route';
|
||||
@ -11,12 +12,15 @@
|
||||
--background-color: #1e1e2c;
|
||||
--background-color-lighter: #3b3b66;
|
||||
--background-color-light: #24243b;
|
||||
--background-color-dark: #12121f;
|
||||
--background-color-darker: #07070c;
|
||||
--primary-color: #d1d1ee;
|
||||
--primary-color-dark: #9696b3;
|
||||
--primary-color-darker: #72728d;
|
||||
--secondary-color: #aeaeec;
|
||||
--secondary-color-dark: #6d6da1;
|
||||
--secondary-color-darker: #424268;
|
||||
--shadow-color: #09090e50;
|
||||
|
||||
--border-radius-small: 0.4rem;
|
||||
--border-radius-medium: 0.8rem;
|
||||
@ -154,7 +158,7 @@ router {
|
||||
route {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-inline: 10px;
|
||||
padding-inline: 1rem;
|
||||
padding-top: var(--nav-height);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
"name": "danbooru-viewer",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.5",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"vite": "^5.4.8"
|
||||
|
91
src/component/Drawer/$Drawer.ts
Normal file
91
src/component/Drawer/$Drawer.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { $Container } from "elexis";
|
||||
import { Booru } from "../../structure/Booru";
|
||||
import { numberFormat } from "../../modules";
|
||||
|
||||
export class $Drawer extends $Container {
|
||||
$filter = $('div').class('filter');
|
||||
$container = $('div').class('drawer-container')
|
||||
constructor() {
|
||||
super('drawer');
|
||||
this.hide(true);
|
||||
this.build();
|
||||
}
|
||||
|
||||
private build() {
|
||||
this.content([
|
||||
this.$container.content([
|
||||
$('div').class('user-info').hide(true).self(($div) => [
|
||||
Booru.events
|
||||
.on('login', (user) => {
|
||||
$div.content([
|
||||
$('div').content([
|
||||
$('h3').class('username').content(user.name$),
|
||||
$('div').class('user-detail').content([
|
||||
$('span').class('userid').content(`ID: ${user.id}`),
|
||||
$('span').class('level').content(['Level: ', user.level_string$])
|
||||
])
|
||||
]),//.on('click', () => $.replace(user.url)),
|
||||
$('div').class('user-nav').content([
|
||||
$('icon-button').title('Uploaded Posts').icon('image').content(user.post_upload_count$.convert(numberFormat)).link(`/posts?tags=user:${user.name}`, true),
|
||||
$('icon-button').title('Favorites').icon('heart').content(user.favorite_count$.convert(numberFormat)).link(`/posts?tags=ordfav:${user.name}`, true),
|
||||
$('icon-button').title('Forum Posts').icon('document-text').content(user.forum_post_count$.convert(numberFormat)).hide(true),
|
||||
])
|
||||
]).hide(false);
|
||||
})
|
||||
.on('logout', () => {
|
||||
$div.clear().hide(true);
|
||||
})
|
||||
]),
|
||||
$('div').class('nav').content([
|
||||
$('div').class('login')
|
||||
.content([ $('icon-button').icon('log-in-outline').content('Login').link('/login', true) ])
|
||||
.self(($div => Booru.events.on('login', () => $div.hide(true)).on('logout', () => $div.hide(false)))),
|
||||
$('div').class('logout').hide(true)
|
||||
.content([ $('icon-button').icon('log-in-outline').content('Logout').on('dblclick', () => Booru.used.logout()) ])
|
||||
.self(($div => Booru.events.on('login', () => $div.hide(false)).on('logout', () => $div.hide(true)))),
|
||||
])
|
||||
]),
|
||||
this.$filter.on('click', () => $.back())
|
||||
])
|
||||
}
|
||||
|
||||
open() {
|
||||
this.hide(false);
|
||||
this.$container.animate({
|
||||
transform: [`translateX(100%)`, `translateX(0%)`]
|
||||
}, {
|
||||
fill: 'both',
|
||||
duration: 300,
|
||||
easing: 'ease'
|
||||
})
|
||||
this.$filter.animate({
|
||||
opacity: [0, 1]
|
||||
}, {
|
||||
fill: 'both',
|
||||
duration: 300,
|
||||
easing: 'ease'
|
||||
})
|
||||
}
|
||||
|
||||
close() {
|
||||
this.$container.animate({
|
||||
transform: [`translateX(0%)`, `translateX(100%)`]
|
||||
}, {
|
||||
fill: 'both',
|
||||
duration: 300,
|
||||
easing: 'ease'
|
||||
}, () => this.hide(true))
|
||||
this.$filter.animate({
|
||||
opacity: [1, 0]
|
||||
}, {
|
||||
fill: 'both',
|
||||
duration: 300,
|
||||
easing: 'ease'
|
||||
})
|
||||
}
|
||||
|
||||
checkURL(beforeURL: URL | undefined, afterURL: URL) {
|
||||
if (beforeURL?.hash === '#drawer') this.close();
|
||||
if (afterURL.hash === '#drawer') this.open();
|
||||
}
|
||||
}
|
75
src/component/Drawer/_$Drawer.scss
Normal file
75
src/component/Drawer/_$Drawer.scss
Normal file
@ -0,0 +1,75 @@
|
||||
drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
z-index: 300;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
|
||||
div.drawer-container {
|
||||
width: 300px;
|
||||
max-width: 70%;
|
||||
height: 100%;
|
||||
background-color: var(--background-color);
|
||||
border-radius: var(--border-radius-large);
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
|
||||
div.user-info {
|
||||
background-color: var(--background-color-light);
|
||||
padding: 2rem;
|
||||
|
||||
.username {
|
||||
margin: 0;
|
||||
color: var(--secondary-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.user-detail {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.2rem;
|
||||
span {
|
||||
font-size: 0.8rem;
|
||||
color: var(--primary-color-dark);
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
div.user-nav {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
div.nav {
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-direction: column;
|
||||
button.icon {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.filter {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: linear-gradient(90deg,
|
||||
color-mix(in srgb, var(--background-color-dark) 50%, transparent) 0%,
|
||||
color-mix(in srgb, var(--background-color-darker) 70%, transparent) 100%
|
||||
);
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ export class $IconButton extends $Button {
|
||||
|
||||
private build() {
|
||||
super.content([
|
||||
this.$icon,
|
||||
this.$icon.hide(true),
|
||||
this.$label
|
||||
])
|
||||
}
|
||||
@ -22,7 +22,12 @@ export class $IconButton extends $Button {
|
||||
}
|
||||
|
||||
icon(name: string) {
|
||||
this.$icon.name(name);
|
||||
this.$icon.name(name).hide(false);
|
||||
return this;
|
||||
}
|
||||
|
||||
link(url: string, replace = false) {
|
||||
this.on('click', () => replace ? $.replace(url) : $.open(url));
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { $Container } from "elexis";
|
||||
import { Tag, TagCategory } from "../../structure/Tag";
|
||||
import { Booru } from "../../structure/Booru";
|
||||
import { User } from "../../structure/User";
|
||||
import { Autocomplete } from "../../structure/Autocomplete";
|
||||
import { numberFormat } from "../../modules";
|
||||
|
||||
export class $Searchbar extends $Container {
|
||||
$tagInput = new $TagInput(this);
|
||||
@ -143,7 +143,7 @@ export class $Searchbar extends $Container {
|
||||
])
|
||||
]),
|
||||
data.isTag() ? $('div').class('tag-detail').content([
|
||||
$('span').class('tag-post-count').content(new Intl.NumberFormat('en', {notation: 'compact'}).format(data.post_count)),
|
||||
$('span').class('tag-post-count').content(numberFormat(data.post_count)),
|
||||
$('span').class('tag-category').content(TagCategory[data.category])
|
||||
]) : null,
|
||||
data.isUser() ? $('span').class('user-level').content(data.level) : null
|
||||
@ -160,8 +160,8 @@ export class $Searchbar extends $Container {
|
||||
return this;
|
||||
}
|
||||
|
||||
checkURL(beforeURL: URL, afterURL: URL) {
|
||||
if (beforeURL.hash === '#search') this.inactivate();
|
||||
checkURL(beforeURL: URL | undefined, afterURL: URL) {
|
||||
if (beforeURL?.hash === '#search') this.inactivate();
|
||||
if (afterURL.hash === '#search') this.activate();
|
||||
}
|
||||
}
|
||||
|
7
src/index.d.ts
vendored
7
src/index.d.ts
vendored
@ -45,7 +45,10 @@ type TextSyntaxComparisons<T> =
|
||||
{ _lower_space: string }
|
||||
|
||||
type UserSyntax = { _id: id } | { _name: username };
|
||||
|
||||
type ChainingSyntax = {_id: id} | {has_: boolean};
|
||||
|
||||
type PostSyntax = {_id: id} | {_tags_match: string};
|
||||
interface APIError {
|
||||
success: false;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
30
src/main.ts
30
src/main.ts
@ -8,6 +8,8 @@ import { $Router, $RouterNavigationDirection } from '@elexis/router';
|
||||
import { $Searchbar } from './component/Searchbar/$Searchbar';
|
||||
import { $IonIcon } from './component/IonIcon/$IonIcon';
|
||||
import { $IconButton } from './component/IconButton/$IconButton';
|
||||
import { $login_route } from './route/login/$login_route';
|
||||
import { $Drawer } from './component/Drawer/$Drawer';
|
||||
// declare elexis module
|
||||
declare module 'elexis' {
|
||||
export namespace $ {
|
||||
@ -28,7 +30,8 @@ export const [danbooru, safebooru]: Booru[] = [
|
||||
]
|
||||
Booru.set(Booru.manager.get(Booru.storageAPI ?? '') ?? danbooru);
|
||||
const $searchbar = new $Searchbar().hide(true);
|
||||
if (location.hash === '#search') $searchbar.activate();
|
||||
const $drawer = new $Drawer();
|
||||
|
||||
// render
|
||||
$(document.body).content([
|
||||
// Navigation Bar
|
||||
@ -60,10 +63,24 @@ $(document.body).content([
|
||||
// Open Booru
|
||||
$('ion-icon').class('open').name('open-outline').title('Open in Original Site')
|
||||
.on('click', () => $.open(location.href.replace(location.origin, Booru.used.origin))),
|
||||
// Menu Button
|
||||
$('ion-icon').class('menu').name('menu-outline').title('Menu').hide(false)
|
||||
.self(($icon) => { Booru.events.on('login', () => $icon.hide(true)).on('logout', () => $icon.hide(false)) })
|
||||
.on('click', () => $.open(location.href + '#drawer')),
|
||||
// Account Menu
|
||||
$('div').class('account').hide(true).title('Menu')
|
||||
.self(($account) => {
|
||||
Booru.events
|
||||
.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'))
|
||||
])
|
||||
]),
|
||||
// Searchbar
|
||||
$searchbar,
|
||||
// Drawer
|
||||
$drawer,
|
||||
// Base Router
|
||||
$('router').base('/').map([
|
||||
// Home Page
|
||||
@ -71,7 +88,9 @@ $(document.body).content([
|
||||
// Posts Page
|
||||
$('route').id('posts').path('/posts?tags').builder(({query}) => new $PostGrid({tags: query.tags})),
|
||||
// Post Page
|
||||
post_route
|
||||
post_route,
|
||||
// Login Page
|
||||
$login_route
|
||||
]).on('beforeSwitch', (e) => {
|
||||
const DURATION = 300;
|
||||
const TX = 2;
|
||||
@ -123,4 +142,9 @@ $(document.body).content([
|
||||
})
|
||||
])
|
||||
|
||||
$Router.events.on('stateChange', ({beforeURL, afterURL}) => { $searchbar.checkURL(beforeURL, afterURL) })
|
||||
$Router.events.on('stateChange', ({beforeURL, afterURL}) => componentState(beforeURL, afterURL))
|
||||
componentState(undefined, new URL(location.href))
|
||||
|
||||
function componentState(beforeURL: URL | undefined, afterURL: URL) {
|
||||
$searchbar.checkURL(beforeURL, afterURL); $drawer.checkURL(beforeURL, afterURL)
|
||||
}
|
4
src/modules.ts
Normal file
4
src/modules.ts
Normal file
@ -0,0 +1,4 @@
|
||||
const NUMBER_FORMAT = new Intl.NumberFormat('en', {notation: 'compact'})
|
||||
export function numberFormat(number: number) {
|
||||
return NUMBER_FORMAT.format(number)
|
||||
}
|
30
src/route/login/$login_route.ts
Normal file
30
src/route/login/$login_route.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Booru } from "../../structure/Booru"
|
||||
import { ClientUser } from "../../structure/ClientUser";
|
||||
|
||||
export const $login_route = $('route').id('login').path('/login').builder(() => {
|
||||
const [username$, apiKey$] = [$.state(''), $.state('')]
|
||||
return [
|
||||
$('div').class('login-container').content([
|
||||
$('h1').content('Login'),
|
||||
$('div').class('username', 'input-container').content([
|
||||
$('label').for('username').content('Username'),
|
||||
$('input').type('text').id('username').value(username$)
|
||||
]),
|
||||
$('div').class('api-key', 'input-container').content([
|
||||
$('label').for('api-key').content('API Key'),
|
||||
$('input').type('password').id('api-key').value(apiKey$)
|
||||
]),
|
||||
$('icon-button').content('Login').on('click', async () => {
|
||||
await Booru.used.login(username$.value, apiKey$.value);
|
||||
if (Booru.used.user) {
|
||||
ClientUser.storageUserData = { apiKey: apiKey$.value, username: username$.value }
|
||||
// Clear input
|
||||
username$.set('');
|
||||
apiKey$.set('');
|
||||
$.replace('/');
|
||||
};
|
||||
}),
|
||||
$('icon-button').content('Create Account').icon('open-outline').on('click', () => $.open('https://danbooru.donmai.us/users/new', '_blank')),
|
||||
])
|
||||
]
|
||||
})
|
27
src/route/login/_$login_route.scss
Normal file
27
src/route/login/_$login_route.scss
Normal file
@ -0,0 +1,27 @@
|
||||
route#login {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 5rem;
|
||||
|
||||
.login-container {
|
||||
padding: 2rem;
|
||||
border: 1px solid var(--secondary-color);
|
||||
border-radius: var(--border-radius-large);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
.input-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
input {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import { Tag, TagCategory } from "../../structure/Tag";
|
||||
import { ArtistCommentary } from "../../structure/Commentary";
|
||||
import { Booru } from "../../structure/Booru";
|
||||
import type { $IonIcon } from "../../component/IonIcon/$IonIcon";
|
||||
import { numberFormat } from "../../modules";
|
||||
|
||||
export const post_route = $('route').path('/posts/:id').id('post').builder(({$route, params}) => {
|
||||
if (!Number(params.id)) return $('h1').content('404: POST NOT FOUND');
|
||||
@ -87,7 +88,7 @@ export const post_route = $('route').path('/posts/:id').id('post').builder(({$ro
|
||||
$('section').content([
|
||||
tags.map(tag => $('div').class('tag').content([
|
||||
$('a').class('tag-name').content(tag.name).href(`/posts?tags=${tag.name}`),
|
||||
$('span').class('tag-post-count').content(tag.post_count$)
|
||||
$('span').class('tag-post-count').content(tag.post_count$.convert(numberFormat))
|
||||
]))
|
||||
])
|
||||
] : null
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { $EventManager, type $EventMap } from "elexis";
|
||||
import type { Post } from "./Post";
|
||||
import type { Tag } from "./Tag";
|
||||
import { ClientUser, type ClientUserData } from "./ClientUser";
|
||||
|
||||
export interface BooruOptions {
|
||||
origin: string;
|
||||
@ -9,9 +10,10 @@ export interface BooruOptions {
|
||||
export interface Booru extends BooruOptions {}
|
||||
export class Booru {
|
||||
static used: Booru;
|
||||
static events = new $EventManager<BooruEventMap>();
|
||||
static events = new $EventManager<BooruStaticEventMap>();
|
||||
static name$ = $.state(this.name);
|
||||
static manager = new Map<string, Booru>()
|
||||
user?: ClientUser;
|
||||
posts = new Map<id, Post>();
|
||||
tags = new Map<id, Tag>();
|
||||
constructor(options: BooruOptions) {
|
||||
@ -24,6 +26,8 @@ export class Booru {
|
||||
this.used = booru;
|
||||
this.name$.set(booru.name);
|
||||
this.storageAPI = booru.name;
|
||||
const userdata = ClientUser.storageUserData;
|
||||
if (userdata) booru.login(userdata.username, userdata.apiKey);
|
||||
this.events.fire('set');
|
||||
return this;
|
||||
}
|
||||
@ -31,8 +35,33 @@ export class Booru {
|
||||
static get storageAPI() { return localStorage.getItem('booru_api'); }
|
||||
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;
|
||||
if (data.success === false) throw data.message;
|
||||
return data as T;
|
||||
}
|
||||
|
||||
async login(username: string, apiKey: string) {
|
||||
const data = await this.fetch<ClientUserData>(`/profile.json?login=${username}&api_key=${apiKey}`);
|
||||
this.user = new ClientUser(this, apiKey, data);
|
||||
Booru.events.fire('login', this.user);
|
||||
return this.user;
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.user = undefined;
|
||||
ClientUser.storageUserData = null;
|
||||
Booru.events.fire('logout');
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface BooruStaticEventMap extends $EventMap {
|
||||
set: [];
|
||||
login: [user: ClientUser];
|
||||
logout: [];
|
||||
}
|
||||
|
||||
interface BooruEventMap extends $EventMap {
|
||||
set: []
|
||||
}
|
71
src/structure/ClientUser.ts
Normal file
71
src/structure/ClientUser.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import type { Booru } from "./Booru";
|
||||
import { User, type UserData } from "./User";
|
||||
|
||||
export interface ClientUser extends ClientUserData {}
|
||||
export class ClientUser extends User {
|
||||
apiKey: string;
|
||||
favorite_count$ = $.state(0);
|
||||
forum_post_count$ = $.state(0);
|
||||
constructor(booru: Booru, apiKey: string, data: ClientUserData) {
|
||||
super(booru, data, false);
|
||||
this.apiKey = apiKey;
|
||||
this.update$();
|
||||
}
|
||||
|
||||
update$() {
|
||||
super.update$();
|
||||
this.forum_post_count$?.set(this.forum_post_count);
|
||||
this.favorite_count$?.set(this.favorite_count);
|
||||
}
|
||||
|
||||
static get storageUserData() { const data = localStorage.getItem('user_data'); return data ? JSON.parse(data) as ClientUserStoreData : null }
|
||||
static set storageUserData(data: ClientUserStoreData | null) { localStorage.setItem('user_data', JSON.stringify(data)) }
|
||||
}
|
||||
export interface ClientUserData extends UserData {
|
||||
"last_logged_in_at": ISOString,
|
||||
"last_forum_read_at": ISOString,
|
||||
"comment_threshold": number,
|
||||
"updated_at": ISOString,
|
||||
"default_image_size": "large" | "original",
|
||||
"favorite_tags": null | string,
|
||||
"blacklisted_tags": string,
|
||||
"time_zone": string,
|
||||
"favorite_count": number,
|
||||
"per_page": number,
|
||||
"custom_style": string,
|
||||
"theme": "auto" | "light" | "dark",
|
||||
"receive_email_notifications": boolean,
|
||||
"new_post_navigation_layout": boolean,
|
||||
"enable_private_favorites": boolean,
|
||||
"show_deleted_children": boolean,
|
||||
"disable_categorized_saved_searches": boolean,
|
||||
"disable_tagged_filenames": boolean,
|
||||
"disable_mobile_gestures": boolean,
|
||||
"enable_safe_mode": boolean,
|
||||
"enable_desktop_mode": boolean,
|
||||
"disable_post_tooltips": boolean,
|
||||
"requires_verification": boolean,
|
||||
"is_verified": boolean,
|
||||
"show_deleted_posts": boolean,
|
||||
"statement_timeout": number,
|
||||
"favorite_group_limit": 10 | 100,
|
||||
"tag_query_limit": 2 | 6,
|
||||
"max_saved_searches": 250,
|
||||
"wiki_page_version_count": number,
|
||||
"artist_version_count": number,
|
||||
"artist_commentary_version_count": number,
|
||||
"pool_version_count": number | null,
|
||||
"forum_post_count": number,
|
||||
"comment_count": number,
|
||||
"favorite_group_count": number,
|
||||
"appeal_count": number,
|
||||
"flag_count": number,
|
||||
"positive_feedback_count": number,
|
||||
"neutral_feedback_count": number,
|
||||
"negative_feedback_count": number
|
||||
}
|
||||
|
||||
export interface ClientUserStoreData {
|
||||
username: string;
|
||||
apiKey: string;
|
||||
}
|
@ -113,6 +113,7 @@ export class Post extends $EventManager<{update: []}> {
|
||||
}
|
||||
get previewURL() { return this.media_asset.variants?.find(variant => variant.file_ext === 'webp')?.url ?? this.large_file_url }
|
||||
get url() { return `${this.booru.origin}/posts/${this.id}` }
|
||||
get isFileSource() { return this.source.startsWith('file://') }
|
||||
}
|
||||
|
||||
export interface PostData extends PostOptions {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import type { Booru } from "./Booru";
|
||||
|
||||
const INTL_number = new Intl.NumberFormat('en', {notation: 'compact'})
|
||||
export interface TagOptions {}
|
||||
export interface Tag extends TagData {}
|
||||
export class Tag {
|
||||
post_count$ = $.state(0, {format: (value) => `${INTL_number.format(value)}`});
|
||||
post_count$ = $.state(0);
|
||||
name$ = $.state('');
|
||||
booru: Booru;
|
||||
constructor(booru: Booru, data: TagData) {
|
||||
|
@ -4,15 +4,20 @@ export class UserOptions {}
|
||||
export interface User extends UserOptions, UserData {}
|
||||
export class User {
|
||||
static manager = new Map<id, User>();
|
||||
name$ = $.state('loding...');
|
||||
constructor(data: UserData) {
|
||||
name$ = $.state('...');
|
||||
post_upload_count$ = $.state(0);
|
||||
level$ = $.state(10);
|
||||
level_string$ = $.state('...');
|
||||
booru: Booru;
|
||||
constructor(booru: Booru, data: UserData, update$: boolean = true) {
|
||||
this.booru = booru;
|
||||
Object.assign(this, data);
|
||||
this.update$();
|
||||
if (update$) this.update$();
|
||||
}
|
||||
|
||||
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 instance = this.manager.get(data.id)?.update(data) ?? new this(data);
|
||||
const instance = this.manager.get(data.id)?.update(data) ?? new this(booru, data);
|
||||
this.manager.set(instance.id, instance);
|
||||
return instance;
|
||||
}
|
||||
@ -33,7 +38,7 @@ export class User {
|
||||
const req = await fetch(`${booru.origin}/users.json?limit=${limit}${searchQuery}`);
|
||||
const dataArray: UserData[] = await req.json();
|
||||
const list = dataArray.map(data => {
|
||||
const instance = new this(data);
|
||||
const instance = new this(booru, data);
|
||||
this.manager.set(instance.id, instance);
|
||||
return instance;
|
||||
});
|
||||
@ -48,9 +53,26 @@ export class User {
|
||||
|
||||
update$() {
|
||||
this.name$.set(this.name);
|
||||
this.post_upload_count$.set(this.post_upload_count);
|
||||
this.level$.set(this.level);
|
||||
this.level_string$.set(this.level_string);
|
||||
}
|
||||
|
||||
get booruURL() { return `${this.booru.origin}/users/${this.id}`}
|
||||
get url() { return `/users/${this.id}`}
|
||||
}
|
||||
|
||||
export enum UserLevel {
|
||||
Restricted = 10,
|
||||
Member = 20,
|
||||
Gold = 30,
|
||||
Platinum = 31,
|
||||
Builder = 32,
|
||||
Contributor = 35,
|
||||
Approver = 37,
|
||||
Moderater = 40,
|
||||
Admin = 50
|
||||
}
|
||||
export interface UserData {
|
||||
"id": id,
|
||||
"name": username,
|
||||
@ -61,57 +83,10 @@ export interface UserData {
|
||||
"note_update_count": number,
|
||||
"post_upload_count": number,
|
||||
"is_deleted": boolean,
|
||||
"level_string": UserLevelString,
|
||||
"level_string": keyof UserLevel,
|
||||
"is_banned": boolean,
|
||||
}
|
||||
|
||||
export type UserLevel = 10 | 20 | 30 | 31 | 32 | 40 | 50;
|
||||
export type UserLevelString = "Member" | "Gold" | "Platinum" | "Admin" | "Contributor" | "Builder" | "Approver";
|
||||
|
||||
export interface UserProfileData extends UserData {
|
||||
"last_logged_in_at": ISOString,
|
||||
"last_forum_read_at": ISOString,
|
||||
"comment_threshold": number,
|
||||
"updated_at": ISOString,
|
||||
"default_image_size": "large" | "original",
|
||||
"favorite_tags": null | string,
|
||||
"blacklisted_tags": string,
|
||||
"time_zone": string,
|
||||
"favorite_count": number,
|
||||
"per_page": number,
|
||||
"custom_style": string,
|
||||
"theme": "auto" | "light" | "dark",
|
||||
"receive_email_notifications": boolean,
|
||||
"new_post_navigation_layout": boolean,
|
||||
"enable_private_favorites": boolean,
|
||||
"show_deleted_children": boolean,
|
||||
"disable_categorized_saved_searches": boolean,
|
||||
"disable_tagged_filenames": boolean,
|
||||
"disable_mobile_gestures": boolean,
|
||||
"enable_safe_mode": boolean,
|
||||
"enable_desktop_mode": boolean,
|
||||
"disable_post_tooltips": boolean,
|
||||
"requires_verification": boolean,
|
||||
"is_verified": boolean,
|
||||
"show_deleted_posts": boolean,
|
||||
"statement_timeout": number,
|
||||
"favorite_group_limit": 10 | 100,
|
||||
"tag_query_limit": 2 | 6,
|
||||
"max_saved_searches": 250,
|
||||
"wiki_page_version_count": number,
|
||||
"artist_version_count": number,
|
||||
"artist_commentary_version_count": number,
|
||||
"pool_version_count": number | null,
|
||||
"forum_post_count": number,
|
||||
"comment_count": number,
|
||||
"favorite_group_count": number,
|
||||
"appeal_count": number,
|
||||
"flag_count": number,
|
||||
"positive_feedback_count": number,
|
||||
"neutral_feedback_count": number,
|
||||
"negative_feedback_count": number
|
||||
}
|
||||
|
||||
export interface UserSearchParam {
|
||||
id: NumericSyntax<id>;
|
||||
level: NumericSyntax<UserLevel>;
|
||||
|
Loading…
Reference in New Issue
Block a user