Add gitignore and advanced filtering

This commit is contained in:
Krzysztof Płaczek
2025-07-28 20:30:37 +02:00
parent 4174396ca1
commit e836daa2b1
5 changed files with 140 additions and 3 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
dist
node_modules
.idea

109
index.html Normal file
View File

@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>bookmeterplus Project</title>
<style>
[x-cloak] {
display: none;
}
</style>
</head>
<body>
<div x-data="bookmeterList">
<div x-data="{ advancedSearch: $persist(0).as('advanced-search')}" x-cloak>
<form class="flex items-center m-4">
<div class="relative w-full d-block max-w-md ">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 21 21">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
</svg>
</div>
<input type="text" class="peer bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:outline focus:outline-sky-500 block w-full ps-10 p-2.5 pr-8"
x-model="nameFilter" placeholder="wyszukiwarka"/>
<button x-show="nameFilter.length" @click="nameFilter=''" type="button" class="absolute inset-y-0 end-0 flex items-center pe-3 invisible peer-valid:visible">
<svg class="w-4 h-4 text-gray-500 " xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="relative inline-block m-4 text-blue-400 inline cursor-pointer">
<div @click="advancedSearch = !advancedSearch">advanced search toggle</div>
</div>
</form>
<div x-show="advancedSearch" class="flex m-4">
<input x-model="username" placeholder="użytkownik" type="text" class="mr-2 max-w-xs bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:outline focus:outline-sky-500 block w-full p-2.5 px-4" />
<input x-model="title" placeholder="tytuł" type="text" class="mr-2 max-w-xs bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:outline focus:outline-sky-500 block w-full p-2.5 px-4" />
<input x-model="author" placeholder="autor" type="text" class="mr-2 max-w-xs bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:outline focus:outline-sky-500 block w-full p-2.5 px-4" />
<input x-model="publisher" placeholder="wydawca" type="text" class="mr-2 max-w-xs bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:outline focus:outline-sky-500 block w-full p-2.5 px-4" />
</div>
</div>
<table class="table table-fixed w-full">
<thead>
<tr>
<th class="text-right pl-1 w-[4rem]">#</th>
<th class="text-left pl-1">Uzytkownik</th>
<th class="text-left pl-1">Tytuł</th>
<th class="text-left pl-1">Autor</th>
<th class="text-left pl-1">Wydawca</th>
<th class="text-right pr-4 w-[5rem]">Stron</th>
<th class="text-left pl-1">Format</th>
<th class="text-right pr-4">Ocena</th>
<th class="text-right pr-4"></th>
<th class="text-left pl-1">Link</th>
</tr>
</thead>
<tbody>
<template x-for="(item, key) in filteredItems" :key="item.index">
<tr class="text-base border-b border-slate-100 m-3 w-auto hover:bg-gray-100">
<td class="pl-1 text-right" x-text="filteredItems.length-key"></td>
<td class="pl-1 w-3 py-2"><a :href="'?filter=@'+item.username" x-text="item.username"></a></td>
<td class="pl-1 w-12"><a :href="'?filter='+item.book.title" x-text="item.book.title"></a></td>
<td class="pl-1 w-8"><a :href="'?filter='+item.book.author" x-text="item.book.author"></a></td>
<td class="pl-1 w-8"><a :href="'?filter='+item.book.publisher" x-text="item.book.publisher"></a></td>
<td class="pr-4 text-right w-[5rem] w-[5rem]"><a :href="item.book.pages" x-text="item.book.pages"></a></td>
<td class="pl-1 w-1"><a :href="item.book.format" x-text="item.book.format"></a></td>
<td class="pr-4 w-1 text-right"><a :href="item.book.rating" x-text="item.book.rating"></a></td>
<td class="pr-4 w-1 text-right"><a :href="item.book.likes" x-text="item.likes"></a></td>
<td class="pl-1 w-1 py-2">
<a :href="'https://www.hejto.pl/wpis/'+item.slug">
link
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4 text-gray-500 inline">
<path stroke-linecap="round" stroke-linejoin="round"
d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"/>
</svg>
</a>
</td>
</tr>
</template>
</tbody>
<tfoot>
<tr x-show="filteredItems.length">
<td colspan="5"></td>
<td class="align-top">
<p class="text-right pr-4" x-text="'strony: '+stats().pages_total"></p>
<p class="text-right pr-4" x-text="'strony śr: '+stats().pages_avg"></p>
</td>
<td class="align-top">
<template x-for="(item, index) in stats().formats">
<p x-text="index+': '+item"></p>
</template>
</td>
<td class="align-top">
<p class="text-right pr-4" x-text="'ocena śr: '+stats().rating_avg"></p>
</td>
<td>
<p class="text-right pr-4" x-text="'⚡: '+stats().likes_total"></p>
<p class="text-right pr-4" x-text="'⚡ śr: '+stats().likes_avg"></p>
</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

7
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@alpinejs/persist": "^3.14.9",
"@tailwindcss/cli": "^4.1.11", "@tailwindcss/cli": "^4.1.11",
"alpinejs": "^3.14.9" "alpinejs": "^3.14.9"
}, },
@@ -32,6 +33,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@alpinejs/persist": {
"version": "3.14.9",
"resolved": "https://registry.npmjs.org/@alpinejs/persist/-/persist-3.14.9.tgz",
"integrity": "sha512-Td9hWEUtFFguYCRjXhq06sDultW21wXUnVro7YWJm+vjGu/nV/v3hepOXyYVjkGvH/F+zjx5UkzWXXzr3UiQ/w==",
"license": "MIT"
},
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",

View File

@@ -17,6 +17,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@alpinejs/persist": "^3.14.9",
"@tailwindcss/cli": "^4.1.11", "@tailwindcss/cli": "^4.1.11",
"alpinejs": "^3.14.9" "alpinejs": "^3.14.9"
}, },

View File

@@ -1,16 +1,25 @@
import './generated.css' import './generated.css'
import Alpine from 'alpinejs' import Alpine from 'alpinejs'
import persist from '@alpinejs/persist'
window.Alpine = Alpine; window.Alpine = Alpine;
Alpine.plugin(persist)
Alpine.data('bookmeterList', () => ({ Alpine.data('bookmeterList', () => ({
items: [], items: [],
filteredItems: [], filteredItems: [],
username: '',
publisher: '',
title: '',
author: '',
nameFilter: '', nameFilter: '',
async init() { async init() {
this.$watch('nameFilter', () => this.updateFilter()); this.$watch('nameFilter', () => this.updateFilter());
this.$watch('username', () => this.updateFilter());
this.$watch('title', () => this.updateFilter());
this.$watch('author', () => this.updateFilter());
this.$watch('publisher', () => this.updateFilter());
let qp = new URLSearchParams(window.location.search); let qp = new URLSearchParams(window.location.search);
if(qp.get('filter')) this.nameFilter = qp.get('filter'); if(qp.get('filter')) this.nameFilter = qp.get('filter');
@@ -19,7 +28,7 @@ Alpine.data('bookmeterList', () => ({
this.items = await res.json(); this.items = await res.json();
this.updateFilter(); this.updateFilter();
}, },
test(){ stats(){
return { return {
'pages_total': this.filteredItems.reduce((a, b) => a + b.book.pages, 0) || '-', 'pages_total': this.filteredItems.reduce((a, b) => a + b.book.pages, 0) || '-',
'likes_total': this.filteredItems.reduce((a, b) => a + b.likes, 0) || '-', 'likes_total': this.filteredItems.reduce((a, b) => a + b.likes, 0) || '-',
@@ -37,8 +46,16 @@ Alpine.data('bookmeterList', () => ({
let filter = this.nameFilter.toLowerCase(); let filter = this.nameFilter.toLowerCase();
this.filteredItems = this.items.filter(i => { this.filteredItems = this.items.filter(i => {
return (
(!this.username || i.username.toLowerCase().includes(this.username.toLowerCase())) &&
(!this.title || i.book.title.toLowerCase().includes(this.title.toLowerCase())) &&
(!this.author || i.book.author.toLowerCase().includes(this.author.toLowerCase())) &&
(!this.publisher || i.book.publisher.toLowerCase().includes(this.publisher.toLowerCase()))
);
if(filter.startsWith('@') && i.username.toLowerCase().includes(filter.substring(1))) return true; if(filter.startsWith('@') && i.username.toLowerCase().includes(filter.substring(1))) return true;
if (i.book.title.toLowerCase().includes(this.nameFilter.toLowerCase()) || i.book.author.toLowerCase().includes(this.nameFilter.toLowerCase())) return true; if (i.username.toLowerCase().includes(filter) ||
i.book.title.toLowerCase().includes(filter) || i.book.author.toLowerCase().includes(filter) || i.book.publisher.toLowerCase().includes(filter)) return true;
return false; return false;
}); });