Compare commits
3 Commits
master
...
feature/im
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
244ec7fc42 | ||
|
|
3046780f1b | ||
|
|
9a66134469 |
11
Dockerfile
Executable file → Normal file
11
Dockerfile
Executable file → Normal file
@@ -1,19 +1,20 @@
|
||||
FROM node:22
|
||||
FROM node:20
|
||||
|
||||
# Set working directory inside container
|
||||
WORKDIR /app
|
||||
WORKDIR /src
|
||||
|
||||
# Copy package files first (better layer caching)
|
||||
# Copy package files first (for better caching)
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy the rest of the project
|
||||
# Copy the rest of your project
|
||||
COPY . .
|
||||
|
||||
# Expose Vite dev server port
|
||||
# Expose Vite’s default dev port
|
||||
EXPOSE 5173
|
||||
|
||||
# Default command
|
||||
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
||||
|
||||
|
||||
34
README.md
Executable file → Normal file
34
README.md
Executable file → Normal file
@@ -1,34 +0,0 @@
|
||||
## BOOKMETER PLUS
|
||||
|
||||
|
||||
For a start let get the docker container going
|
||||
```bash
|
||||
docker compose up -d --remove-orphans
|
||||
```
|
||||
|
||||
### Vite
|
||||
Then for development purposes execute:
|
||||
```bash
|
||||
bin/dev
|
||||
```
|
||||
This command will start server on port 5173. http://localhost:5173
|
||||
|
||||
|
||||
### Tailwind
|
||||
If we want to rebuild tailwind styles then lets start this command in another terminal window:
|
||||
|
||||
```bash
|
||||
bin/tailwindbuild
|
||||
```
|
||||
|
||||
To download bookmeter json files there is a PHP script
|
||||
|
||||
```bash
|
||||
php download_posts.ph
|
||||
```
|
||||
|
||||
To compile production version execute this command:
|
||||
|
||||
```bash
|
||||
bin/build
|
||||
```
|
||||
|
||||
6
docker-compose.yml
Executable file → Normal file
6
docker-compose.yml
Executable file → Normal file
@@ -1,10 +1,10 @@
|
||||
services:
|
||||
node:
|
||||
build: .
|
||||
working_dir: /app
|
||||
working_dir: /src
|
||||
volumes:
|
||||
- .:/app
|
||||
- /app/node_modules
|
||||
- .:/src
|
||||
- /node_modules
|
||||
ports:
|
||||
- "5173:5173"
|
||||
command: npm run dev -- --host 0.0.0.0
|
||||
12
download_posts.php
Executable file → Normal file
12
download_posts.php
Executable file → Normal file
@@ -6,17 +6,7 @@ if (!file_exists('public/posts_2023.json')) {
|
||||
if (!file_exists('public/posts_2024.json')) {
|
||||
file_put_contents('public/posts_2024.json', download_posts('https://bookmeter.xyz/api/posts?edition=2024'));
|
||||
}
|
||||
if (!file_exists('public/posts_2025.json')) {
|
||||
file_put_contents('public/posts_2025.json', download_posts('https://bookmeter.xyz/api/posts?edition=2025'));
|
||||
}
|
||||
file_put_contents('public/posts_2026.json', download_posts('https://bookmeter.xyz/api/posts?edition=2026'));
|
||||
|
||||
if (is_dir('dist')) {
|
||||
copy('public/posts_2026.json', 'dist/posts_2026.json');
|
||||
copy('public/posts_2025.json', 'dist/posts_2025.json');
|
||||
copy('public/posts_2024.json', 'dist/posts_2024.json');
|
||||
copy('public/posts_2023.json', 'dist/posts_2023.json');
|
||||
}
|
||||
file_put_contents('public/posts_2025.json', download_posts('https://bookmeter.xyz/api/posts?edition=2025'));
|
||||
function download_posts(string $url): false|string
|
||||
{
|
||||
$posts = file_get_contents($url);
|
||||
|
||||
182
index.html
Executable file → Normal file
182
index.html
Executable file → Normal file
@@ -1,85 +1,59 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<!DOCTYPE html>
|
||||
<html lang="pl" x-data="{ dark: false }" x-init="
|
||||
dark = localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
$watch('dark', value => {
|
||||
document.documentElement.classList.toggle('dark', value)
|
||||
localStorage.theme = value ? 'dark' : 'light'
|
||||
})
|
||||
"
|
||||
:class="{ 'dark': dark }">
|
||||
<head>
|
||||
<script>
|
||||
(() => {
|
||||
const theme = localStorage.getItem('theme');
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
if (theme === 'dark' || (!theme && prefersDark)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
html {
|
||||
background-color: #030712;
|
||||
transition: background-color 0.7s ease;
|
||||
}
|
||||
|
||||
html:not(.dark) {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
[x-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<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 class="dark:text-gray-300 dark:bg-gray-950">
|
||||
|
||||
|
||||
<div x-data="bookmeterList">
|
||||
<!-- <div class="p-10">-->
|
||||
<!-- <div class="chart-container" style="position: relative; height:30vh; width:80vw">-->
|
||||
<!-- <canvas id="statsChart" height="100px"></canvas>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="chart-container" style="position: relative; height:50vh; width:80vw">-->
|
||||
<!-- <canvas id="chartPoints" height="100px"></canvas>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <button @click="updatechartnew()" class="text-red-500">click me</button>-->
|
||||
<!-- <div class="p-10">-->
|
||||
<!-- <div class="chart-container" style="position: relative; height:30vh; width:80vw">-->
|
||||
<!-- <canvas id="statsChart" height="100px"></canvas>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="chart-container" style="position: relative; height:50vh; width:80vw">-->
|
||||
<!-- <canvas id="chartPoints" height="100px"></canvas>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <button @click="updatechartnew()" class="text-red-500">click me</button>-->
|
||||
<div x-cloak>
|
||||
<form class="flex items-center m-2" x-show="!advancedSearch">
|
||||
<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"
|
||||
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 class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" 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 dark:bg-gray-900 dark:border-stone-700 dark:text-gray-100"
|
||||
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" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor">
|
||||
<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 dark:bg-gray-900 dark:border-stone-700 dark:text-gray-100" 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" 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="d-block max-w-xs mr-2">
|
||||
<label for="basic-year-2026" class="p-2.5 inline-block">
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="basic-year-2026"
|
||||
value="2026"/> 2026
|
||||
<label for="basic-year-2025" class="p-2.5 inline-block" >
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="basic-year-2025" value="2025" /> 2025
|
||||
</label>
|
||||
<label for="basic-year-2025" class="p-2.5 inline-block">
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="basic-year-2025"
|
||||
value="2025"/> 2025
|
||||
<label for="year-2024" class="p-2.5 inline-block" >
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="basic-year-2024" value="2024" /> 2024
|
||||
</label>
|
||||
<label for="year-2024" class="p-2.5 inline-block">
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="basic-year-2024"
|
||||
value="2024"/> 2024
|
||||
</label>
|
||||
<label for="year-2023" class="p-2.5 inline-block">
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="basic-year-2023"
|
||||
value="2023"/> 2023
|
||||
<label for="year-2023" class="p-2.5 inline-block" >
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="basic-year-2023" value="2023" /> 2023
|
||||
</label>
|
||||
</div>
|
||||
<div class="relative inline-block m-4 text-blue-400 inline cursor-pointer">
|
||||
@@ -88,63 +62,46 @@
|
||||
</form>
|
||||
<div x-show="advancedSearch" class="flex m-4">
|
||||
<div class="relative w-full d-block max-w-xs mr-2">
|
||||
<input @input.debounce="updateFilter" x-model.debounce.250ms="username" placeholder="użytkownik"
|
||||
type="text"
|
||||
class="peer 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 dark:bg-gray-900 dark:border-stone-700 dark:text-gray-100"/>
|
||||
<button x-show="username.length" @click="username=''" 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">
|
||||
<input @input.debounce="updateFilter" x-model="username" placeholder="użytkownik" type="text" class="peer 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 dark:bg-gray-900 dark:border-stone-700 dark:text-gray-100" />
|
||||
<button x-show="username.length" @click="username=''" 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 w-full d-block max-w-xs mr-2">
|
||||
<input @input.debounce="updateFilter" x-model.debounce.250ms="title" placeholder="tytuł" type="text"
|
||||
class="peer 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 dark:bg-gray-900 dark:border-stone-700 dark:text-gray-100"/>
|
||||
<button x-show="title.length" @click="title=''" 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">
|
||||
<input @input.debounce="updateFilter" x-model="title" placeholder="tytuł" type="text" class="peer 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 dark:bg-gray-900 dark:border-stone-700 dark:text-gray-100" />
|
||||
<button x-show="title.length" @click="title=''" 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 w-full d-block max-w-xs mr-2">
|
||||
<input @input.debounce="updateFilter" x-model.debounce.250ms="author" placeholder="autor" type="text"
|
||||
class="peer 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 dark:bg-gray-900 dark:border-stone-700 dark:text-gray-100"/>
|
||||
<button x-show="author.length" @click="author=''" 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">
|
||||
<input @input.debounce="updateFilter" x-model="author" placeholder="autor" type="text" class="peer 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 dark:bg-gray-900 dark:border-stone-700 dark:text-gray-100" />
|
||||
<button x-show="author.length" @click="author=''" 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 w-full d-block max-w-xs mr-2">
|
||||
<input @input.debounce="updateFilter" x-model.debounce.250ms="publisher" placeholder="wydawca"
|
||||
type="text"
|
||||
class="peer 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 dark:bg-gray-900 dark:border-stone-700 dark:text-gray-100"/>
|
||||
<button x-show="publisher.length" @click="publisher=''" 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">
|
||||
<input @input.debounce="updateFilter" x-model="publisher" placeholder="wydawca" type="text" class="peer 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 dark:bg-gray-900 dark:border-stone-700 dark:text-gray-100" />
|
||||
<button x-show="publisher.length" @click="publisher=''" 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="d-block max-w-xs mr-2">
|
||||
<label for="year-2026" class="p-2.5 inline-block">
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="year-2026" value="2026"/> 2026
|
||||
<label for="year-2025" class="p-2.5 inline-block" >
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="year-2025" value="2025" /> 2025
|
||||
</label>
|
||||
<label for="year-2025" class="p-2.5 inline-block">
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="year-2025" value="2025"/> 2025
|
||||
<label for="year-2024" class="p-2.5 inline-block" >
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="year-2024" value="2024" /> 2024
|
||||
</label>
|
||||
<label for="year-2024" class="p-2.5 inline-block">
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="year-2024" value="2024"/> 2024
|
||||
</label>
|
||||
<label for="year-2023" class="p-2.5 inline-block">
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="year-2023" value="2023"/> 2023
|
||||
<label for="year-2023" class="p-2.5 inline-block" >
|
||||
<input @input.debounce="updateFilter" x-model="year" type="checkbox" id="year-2023" value="2023" /> 2023
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -156,13 +113,11 @@
|
||||
|
||||
<template x-ref="sortIcons">
|
||||
<span>
|
||||
<svg x-show="orderColumn[1] === -1 && orderColumn[0] === columnName" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/>
|
||||
<svg x-show="orderColumn[1] === -1 && orderColumn[0] === columnName" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
<svg x-show="orderColumn[1] === 1 && orderColumn[0] === columnName" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5"/>
|
||||
<svg x-show="orderColumn[1] === 1 && orderColumn[0] === columnName" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5" />
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
@@ -226,31 +181,25 @@
|
||||
<tr class="border-b border-slate-100 dark:border-stone-900 m-3 w-auto hover:bg-gray-100 dark:hover:bg-gray-900">
|
||||
<td class="pl-1 text-right" x-text="filteredItems.length-key"></td>
|
||||
<td class="pl-1 w-3 py-2">
|
||||
<a @click="advancedSearch ? username=item.username : nameFilter='@'+item.username"
|
||||
class="cursor-pointer" x-text="item.username"></a>
|
||||
<a @click="advancedSearch ? username=item.username : nameFilter='@'+item.username" class="cursor-pointer" x-text="item.username"></a>
|
||||
</td>
|
||||
<td class="pl-1 w-12">
|
||||
<a @click="advancedSearch ? title=item.book.title : nameFilter=item.book.title"
|
||||
class="cursor-pointer" x-text="item.book.title"></a>
|
||||
<a @click="advancedSearch ? title=item.book.title : nameFilter=item.book.title" class="cursor-pointer" x-text="item.book.title"></a>
|
||||
</td>
|
||||
<td class="pl-1 w-8">
|
||||
<a @click="advancedSearch ? author=item.book.author : nameFilter=item.book.author"
|
||||
class="cursor-pointer" x-text="item.book.author"></a>
|
||||
<a @click="advancedSearch ? author=item.book.author : nameFilter=item.book.author" class="cursor-pointer" x-text="item.book.author"></a>
|
||||
</td>
|
||||
<td class="pl-1 w-8">
|
||||
<a @click="advancedSearch ? publisher=item.book.publisher : nameFilter=item.book.publisher"
|
||||
class="cursor-pointer" 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>
|
||||
<a @click="advancedSearch ? publisher=item.book.publisher : nameFilter=item.book.publisher" class="cursor-pointer" 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"><a :href="item.book.format" x-text="item.book.format"></a></td>
|
||||
<td class="text-right"><a :href="item.book.rating" x-text="item.book.rating"></a></td>
|
||||
<td class="pr-4 text-right" x-text="item.likes"></td>
|
||||
<td class="pl-1 py-2">
|
||||
<a :href="'https://www.hejto.pl/wpis/'+item.slug" target="_blank">
|
||||
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">
|
||||
<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>
|
||||
@@ -268,10 +217,7 @@
|
||||
</td>
|
||||
<td class="align-top">
|
||||
<template x-for="(item, index) in stats().formats">
|
||||
<div class="flex">
|
||||
<p x-text="index+':'"></p>
|
||||
<p x-text="item" class="flex-1 text-right"></p>
|
||||
</div>
|
||||
<p x-text="index+': '+item"></p>
|
||||
</template>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
@@ -286,7 +232,7 @@
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div x-data="darkmodetoggle" x-cloak="" class="flex m-5 border-t py-4 px-4 dark:bg-gray-800 dark:border-t-gray-500">
|
||||
<div x-data="darkmodetoggle" class="flex m-5 border-t py-4 px-4 dark:bg-gray-800 dark:border-t-gray-500">
|
||||
<div class="cursor-pointer text-yellow-500 px-4 dark:text-yellow-200" @click="mode('light')" title="Light mode">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||
<path d="M12 2.25a.75.75 0 0 1 .75.75v2.25a.75.75 0 0 1-1.5 0V3a.75.75 0 0 1 .75-.75ZM7.5 12a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM18.894 6.166a.75.75 0 0 0-1.06-1.06l-1.591 1.59a.75.75 0 1 0 1.06 1.061l1.591-1.59ZM21.75 12a.75.75 0 0 1-.75.75h-2.25a.75.75 0 0 1 0-1.5H21a.75.75 0 0 1 .75.75ZM17.834 18.894a.75.75 0 0 0 1.06-1.06l-1.59-1.591a.75.75 0 1 0-1.061 1.06l1.59 1.591ZM12 18a.75.75 0 0 1 .75.75V21a.75.75 0 0 1-1.5 0v-2.25A.75.75 0 0 1 12 18ZM7.758 17.303a.75.75 0 0 0-1.061-1.06l-1.591 1.59a.75.75 0 0 0 1.06 1.061l1.591-1.59ZM6 12a.75.75 0 0 1-.75.75H3a.75.75 0 0 1 0-1.5h2.25A.75.75 0 0 1 6 12ZM6.697 7.757a.75.75 0 0 0 1.06-1.06l-1.59-1.591a.75.75 0 0 0-1.061 1.06l1.59 1.591Z"/>
|
||||
|
||||
0
package-lock.json
generated
Executable file → Normal file
0
package-lock.json
generated
Executable file → Normal file
0
package.json
Executable file → Normal file
0
package.json
Executable file → Normal file
0
src/chart.js
Executable file → Normal file
0
src/chart.js
Executable file → Normal file
0
src/darkmodetoggle.js
Executable file → Normal file
0
src/darkmodetoggle.js
Executable file → Normal file
906
src/generated.css
Executable file → Normal file
906
src/generated.css
Executable file → Normal file
File diff suppressed because one or more lines are too long
74
src/main.js
Executable file → Normal file
74
src/main.js
Executable file → Normal file
@@ -20,48 +20,28 @@ Alpine.data('bookmeterList', function () {
|
||||
title: '',
|
||||
author: '',
|
||||
nameFilter: '',
|
||||
years: [2023, 2024, 2025, 2026],
|
||||
year: this.$persist(['2026']).as('year-filter'),
|
||||
years: [2023, 2024, 2025],
|
||||
year: this.$persist(['2025']).as('year-filter'),
|
||||
orderColumn: ['order', 0],
|
||||
advancedSearch: this.$persist(0).as('advanced-search'),
|
||||
async init() {
|
||||
|
||||
this.$watch(() => JSON.stringify([
|
||||
this.nameFilter,
|
||||
this.username,
|
||||
this.title,
|
||||
this.author,
|
||||
this.publisher,
|
||||
this.year,
|
||||
this.advancedSearch
|
||||
]), () => 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());
|
||||
this.$watch('year', () => this.updateFilter());
|
||||
|
||||
let qp = new URLSearchParams(window.location.search);
|
||||
if(qp.get('filter')) this.nameFilter = qp.get('filter');
|
||||
|
||||
const [data1, data2, data3, data4] = await Promise.all([
|
||||
fetch('/posts_2026.json').then(res => res.json()),
|
||||
const [data1, data2, data3] = await Promise.all([
|
||||
fetch('/posts_2025.json').then(res => res.json()),
|
||||
fetch('/posts_2024.json').then(res => res.json()),
|
||||
fetch('/posts_2023.json').then(res => res.json())
|
||||
])
|
||||
|
||||
this.items = [...data1, ...data2, ...data3, ...data4]
|
||||
.filter(i => i.book.title.length > 0)
|
||||
.map(i => ({
|
||||
...i,
|
||||
_year: i.created_at.substring(0, 4),
|
||||
_username: i.username.toLowerCase(),
|
||||
_title: i.book.title.toLowerCase(),
|
||||
_author: i.book.author.toLowerCase(),
|
||||
_publisher: i.book.publisher.toLowerCase(),
|
||||
_search: (
|
||||
i.username +
|
||||
' ' + i.book.title +
|
||||
' ' + i.book.author +
|
||||
' ' + i.book.publisher
|
||||
).toLowerCase()
|
||||
}));
|
||||
this.items = [...data1, ...data2, ...data3]
|
||||
this.updateFilter();
|
||||
// this.initChart();
|
||||
},
|
||||
@@ -188,10 +168,6 @@ Alpine.data('bookmeterList', function () {
|
||||
};
|
||||
},
|
||||
sort() {
|
||||
if (this.orderColumn[1] === 0) {
|
||||
this.initFilteredData();
|
||||
return;
|
||||
}
|
||||
let sortFunction = ({
|
||||
username: (a,b) => ((a.username < b.username) ? -1 : (a.username > b.username) ? 1 : 0) * this.orderColumn[1],
|
||||
title: (a,b) => ((a.book.title < b.book.title) ? -1 : (a.book.title > b.book.title) ? 1 : 0) * this.orderColumn[1],
|
||||
@@ -210,34 +186,30 @@ Alpine.data('bookmeterList', function () {
|
||||
initFilteredData(){
|
||||
let filter = this.nameFilter.toLowerCase();
|
||||
|
||||
let username = this.username.toLowerCase().trim();
|
||||
let author = this.author.toLowerCase().trim();
|
||||
let publisher = this.publisher.toLowerCase().trim();
|
||||
let title = this.title.toLowerCase().trim();
|
||||
|
||||
this.filteredItems = this.items
|
||||
.filter(i => this.year.includes(i._year))
|
||||
.filter(i => this.year.includes(i.created_at.substring(0,4)))
|
||||
.filter(i => {
|
||||
if (this.advancedSearch) {
|
||||
if(this.advancedSearch) {
|
||||
return (
|
||||
(!this.username || i._username.includes(username)) &&
|
||||
(!this.title || i._title.includes(title)) &&
|
||||
(!this.author || i._author.includes(author)) &&
|
||||
(!this.publisher || i._publisher.includes(publisher))
|
||||
(!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()))
|
||||
);
|
||||
} else {
|
||||
if (filter.startsWith('@')) {
|
||||
return i.username.toLowerCase().includes(filter.slice(1));
|
||||
}
|
||||
return i._search.includes(filter);
|
||||
if(filter.startsWith('@') && i.username.toLowerCase().includes(filter.substring(1))) 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;
|
||||
}
|
||||
});
|
||||
},
|
||||
updateFilter: Alpine.debounce(function () {
|
||||
updateFilter(){
|
||||
this.initFilteredData();
|
||||
this.sort();
|
||||
|
||||
this.updateURL();
|
||||
}, 250),
|
||||
},
|
||||
updateURL() {
|
||||
let qp = new URLSearchParams();
|
||||
if(this.nameFilter !== '') qp.set('filter', this.nameFilter);
|
||||
|
||||
0
src/style.css
Executable file → Normal file
0
src/style.css
Executable file → Normal file
0
tailwind.config.js
Executable file → Normal file
0
tailwind.config.js
Executable file → Normal file
Reference in New Issue
Block a user