mirror of
https://github.com/Damillora/Shioriko.git
synced 2024-11-23 21:07:32 +00:00
feat: update
This commit is contained in:
parent
c978c6eb50
commit
dbff5649d7
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
Dockerfile
|
28
.github/workflows/workflow.yml
vendored
Normal file
28
.github/workflows/workflow.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
name: CI
|
||||
on: push
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: damillora/shioriko:latest
|
||||
-
|
||||
name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
20
Dockerfile
20
Dockerfile
@ -4,13 +4,21 @@ WORKDIR /go/src/shioriko
|
||||
COPY . .
|
||||
|
||||
RUN go get -d -v ./...
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o /shioriko
|
||||
RUN go build -o /shioriko
|
||||
RUN mkdir -p /web && cp -r web/static web/template /web
|
||||
|
||||
FROM scratch AS runtime
|
||||
FROM node:14-alpine AS node_build
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
WORKDIR /src/web/app
|
||||
RUN yarn install && yarn build
|
||||
|
||||
WORKDIR /
|
||||
COPY --from=build /shioriko /
|
||||
COPY --from=build /web /web
|
||||
FROM alpine AS runtime
|
||||
|
||||
ENTRYPOINT ["/shioriko"]
|
||||
RUN mkdir -p /app/web
|
||||
WORKDIR /app
|
||||
COPY --from=build /shioriko /app
|
||||
COPY --from=node_build /src/web/static /app/web/static
|
||||
COPY --from=node_build /src/web/template /app/web/template
|
||||
|
||||
ENTRYPOINT ["/app/shioriko"]
|
||||
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Damillora
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
30
README.md
Normal file
30
README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Shioriko
|
||||
|
||||
A booru-like software written in Go and Svelte.
|
||||
|
||||
## Installation
|
||||
|
||||
The easiest way to get started is to use Docker:
|
||||
```bash
|
||||
docker pull damillora/shioriko
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
* PostgreSQL database
|
||||
|
||||
## Configuration
|
||||
|
||||
Shioriko is configured using environment variables:
|
||||
|
||||
* `POSTGRES_DATABASE`: DSN string of Postgres Database, see [Gorm documentation](https://gorm.io/docs/connecting_to_the_database.html)
|
||||
* `AUTH_SECRET`: Secret used to sign JWTs
|
||||
* `DATA_DIR`: Data directory to store images
|
||||
* `BASE_URL`: Accesible URL of the instance
|
||||
* `DISABLE_REGISTRATION`: Optional, disable registration on the instance
|
||||
|
||||
## Contributing
|
||||
Shioriko is still in an early stage, but contributions are welcome!
|
||||
|
||||
## License
|
||||
[MIT](https://choosealicense.com/licenses/mit/)
|
@ -72,7 +72,7 @@ func postGetTag(c *gin.Context) {
|
||||
Tags: tagStrings,
|
||||
})
|
||||
}
|
||||
postPages := services.CountPostPages()
|
||||
postPages := services.CountPostPagesTag(tag)
|
||||
c.JSON(http.StatusOK, models.PostPaginationResponse{
|
||||
CurrentPage: page,
|
||||
TotalPage: postPages,
|
||||
|
@ -78,6 +78,16 @@ func CountPostPages() int {
|
||||
database.DB.Model(&database.Post{}).Count(&count)
|
||||
return int(count/perPage) + 1
|
||||
}
|
||||
func CountPostPagesTag(tagSyntax string) int {
|
||||
tag, err := GetTag(tagSyntax)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var count int64
|
||||
count = database.DB.Model(&tag).Joins("Blob").Preload("Tags").Preload("Tags.TagType").Association("Posts").Count()
|
||||
return int(count/perPage) + 1
|
||||
}
|
||||
|
||||
func DeletePost(id string) error {
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
<script>
|
||||
import { Router, Link, Route } from "svelte-routing";
|
||||
|
||||
import Navbar from "./Navbar.svelte";
|
||||
|
||||
import Home from "./routes/Home.svelte";
|
||||
import Posts from "./routes/Posts.svelte";
|
||||
import Post from "./routes/Post.svelte";
|
||||
@ -7,69 +10,21 @@
|
||||
import Logout from "./routes/Logout.svelte";
|
||||
import Tag from "./routes/Tag.svelte";
|
||||
|
||||
import { token } from "./stores.js"
|
||||
|
||||
let loggedIn = false;
|
||||
token.subscribe(value => {
|
||||
loggedIn = value !== ""
|
||||
});
|
||||
|
||||
export let url = "";
|
||||
let baseURL = window.BASE_URL;
|
||||
|
||||
</script>
|
||||
|
||||
<Router url="{url}">
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<Link class="navbar-item" to="/">
|
||||
Shioriko
|
||||
</Link>
|
||||
|
||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="navbarBasicExample" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<Link class="navbar-item" to="/posts">
|
||||
Posts
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
{#if loggedIn}
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<Link to="/auth/logout" class="button is-light">
|
||||
Log out
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<Link to="/auth/register" class="button is-primary">
|
||||
<strong>Register</strong>
|
||||
</Link>
|
||||
<Link to="/auth/login" class="button is-light">
|
||||
Log in
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div>
|
||||
<Route path="/" component="{Home}" />
|
||||
<Route path="/posts" component="{Posts}" />
|
||||
<Route path="/tag/:id" component="{Tag}" />
|
||||
<Route path="/post/:id" component="{Post}" />
|
||||
<Route path="/auth/login" component="{Login}" />
|
||||
<Route path="/auth/logout" component="{Logout}" />
|
||||
</div>
|
||||
</Router>
|
||||
<Router {url}>
|
||||
<Navbar />
|
||||
<div>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/posts" component={Posts} />
|
||||
<Route path="/tag/:id" component={Tag} />
|
||||
<Route path="/post/:id" component={Post} />
|
||||
<Route path="/auth/login" component={Login} />
|
||||
<Route path="/auth/logout" component={Logout} />
|
||||
</div>
|
||||
</Router>
|
||||
|
16
web/app/src/AuthRequired.svelte
Normal file
16
web/app/src/AuthRequired.svelte
Normal file
@ -0,0 +1,16 @@
|
||||
<script>
|
||||
import { token } from "./stores.js";
|
||||
import { navigate } from "svelte-routing";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let loggedIn = false;
|
||||
token.subscribe((value) => {
|
||||
loggedIn = value !== "";
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
if (loggedIn === false) {
|
||||
navigate("/auth/login");
|
||||
}
|
||||
});
|
||||
</script>
|
66
web/app/src/Navbar.svelte
Normal file
66
web/app/src/Navbar.svelte
Normal file
@ -0,0 +1,66 @@
|
||||
<script>
|
||||
import { Link } from "svelte-routing";
|
||||
import { token } from "./stores.js";
|
||||
|
||||
let menu_shown = false;
|
||||
|
||||
let loggedIn = false;
|
||||
token.subscribe((value) => {
|
||||
loggedIn = value !== "";
|
||||
});
|
||||
|
||||
const toggleMenu = () => {
|
||||
menu_shown = !menu_shown;
|
||||
};
|
||||
</script>
|
||||
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<Link class="navbar-item" to="/">Shioriko</Link>
|
||||
|
||||
<a
|
||||
href={"#"}
|
||||
on:click={toggleMenu}
|
||||
role="button"
|
||||
class="navbar-burger"
|
||||
aria-label="menu"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span aria-hidden="true" />
|
||||
<span aria-hidden="true" />
|
||||
<span aria-hidden="true" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-menu" class:is-active={menu_shown}>
|
||||
<div class="navbar-start">
|
||||
<Link class="navbar-item" to="/posts">Posts</Link>
|
||||
{#if loggedIn}
|
||||
<Link class="navbar-item" to="/upload">Upload</Link>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
{#if loggedIn}
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<Link to="/auth/logout" class="button is-light">
|
||||
Log out
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<Link to="/auth/register" class="button is-primary">
|
||||
<strong>Register</strong>
|
||||
</Link>
|
||||
<Link to="/auth/login" class="button is-light">
|
||||
Log in
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
@ -1,14 +1,9 @@
|
||||
<script>
|
||||
|
||||
</script>
|
||||
|
||||
<section class="hero is-primary is-medium">
|
||||
<div class="hero-body">
|
||||
<p class="title">
|
||||
Shioriko
|
||||
</p>
|
||||
<p class="subtitle">
|
||||
Booru-style gallery written in Go and Svelte
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<div class="hero-body">
|
||||
<p class="title">Shioriko</p>
|
||||
<p class="subtitle">Booru-style gallery written in Go and Svelte</p>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -1,33 +1,48 @@
|
||||
<script>
|
||||
import { login } from "../api.js"
|
||||
import { navigate } from "svelte-routing"
|
||||
import { login } from "../api.js";
|
||||
import { navigate } from "svelte-routing";
|
||||
|
||||
let username = "";
|
||||
let password = "";
|
||||
|
||||
const doLogin = async () => {
|
||||
const tokenData = await login({ username, password});
|
||||
const tokenData = await login({ username, password });
|
||||
navigate("/");
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<form on:submit|preventDefault={doLogin}>
|
||||
<div class="field">
|
||||
<label class="label">Username</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" placeholder="Username" bind:value={username} required>
|
||||
</div>
|
||||
<label for="username" class="label">Username</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="username"
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
bind:value={username}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Password</label>
|
||||
<div class="control">
|
||||
<input class="input" type="password" placeholder="Password" bind:value={password} required>
|
||||
</div>
|
||||
<label for="password" class="label">Password</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="password"
|
||||
class="input"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
bind:value={password}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button class="button is-link">Login</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-link">Login</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -1,11 +1,10 @@
|
||||
<script>
|
||||
import { token } from "../stores.js"
|
||||
import { navigate } from "svelte-routing"
|
||||
import {onMount } from "svelte"
|
||||
import { token } from "../stores.js";
|
||||
import { navigate } from "svelte-routing";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
onMount(() => {
|
||||
token.set("");
|
||||
navigate("/");
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@ -50,7 +50,7 @@
|
||||
</div>
|
||||
<div class="column">
|
||||
<figure class="image">
|
||||
<img src="{post.image_path}">
|
||||
<img alt="{post.id}" src="{post.image_path}">
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script>
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { getPosts } from "../api.js";
|
||||
import { Link} from "svelte-routing";
|
||||
import { Link } from "svelte-routing";
|
||||
import queryString from "query-string";
|
||||
|
||||
export let location;
|
||||
@ -10,34 +10,32 @@
|
||||
let totalPages = 1;
|
||||
let posts = [];
|
||||
const getData = async () => {
|
||||
const data = await getPosts({page});
|
||||
if(Array.isArray(data.posts)) {
|
||||
const data = await getPosts({ page });
|
||||
if (Array.isArray(data.posts)) {
|
||||
posts = data.posts;
|
||||
totalPages = data.totalPage;
|
||||
}
|
||||
}
|
||||
onMount(() => {
|
||||
let queryParams;
|
||||
$: queryParams = queryString.parse(location.search);
|
||||
if(queryParams.page) {
|
||||
page = parseInt(queryParams.page);
|
||||
}
|
||||
};
|
||||
onMount(() => {
|
||||
let queryParams;
|
||||
queryParams = queryString.parse(location.search);
|
||||
if (queryParams.page) {
|
||||
page = parseInt(queryParams.page);
|
||||
}
|
||||
getData();
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
const handlePage = (i) => {
|
||||
return () => {
|
||||
page = i;
|
||||
getData();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="hero is-primary">
|
||||
<div class="hero-body">
|
||||
<p class="title">
|
||||
Posts
|
||||
</p>
|
||||
<p class="title">Posts</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -45,63 +43,94 @@
|
||||
<div class="container">
|
||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||
{#if page > 1}
|
||||
<a class="pagination-previous">Previous</a>
|
||||
<Link
|
||||
on:click={handlePage(page - 1)}
|
||||
to="/posts?page={page - 1}"
|
||||
class="pagination-previous"
|
||||
aria-label="Previous">Previous</Link
|
||||
>
|
||||
{/if}
|
||||
{#if page < totalPages}
|
||||
<a class="pagination-next">Next page</a>
|
||||
<Link
|
||||
on:click={handlePage(page + 1)}
|
||||
to="/posts?page={page + 1}"
|
||||
class="pagination-next"
|
||||
aria-label="Next">Next</Link
|
||||
>
|
||||
{/if}
|
||||
<ul class="pagination-list">
|
||||
{#if page > 3}
|
||||
<li>
|
||||
<Link on:click="{handlePage(1)}" to="/posts?page={1}" class="pagination-link" aria-label="Goto page 1">1</Link>
|
||||
</li>
|
||||
<li>
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
</li>
|
||||
{/if}
|
||||
{#each [...Array(5).keys()].map(x => x + page - 2) as i }
|
||||
{#if i >= 1 && i <= totalPages}
|
||||
{#if i == page}
|
||||
<li>
|
||||
<Link on:click="{handlePage(i)}" to="/posts?page={i}" class="pagination-link is-current" aria-label="Goto page {i}">{i}</Link>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<Link on:click="{handlePage(i)}" to="/posts?page={i}" class="pagination-link" aria-label="Goto page {i}">{i}</Link>
|
||||
</li>
|
||||
{#if page > 3}
|
||||
<li>
|
||||
<Link
|
||||
on:click={handlePage(1)}
|
||||
to="/posts?page={1}"
|
||||
class="pagination-link"
|
||||
aria-label="Goto page 1">1</Link
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
</li>
|
||||
{/if}
|
||||
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
|
||||
{#if i >= 1 && i <= totalPages}
|
||||
{#if i == page}
|
||||
<li>
|
||||
<Link
|
||||
on:click={handlePage(i)}
|
||||
to="/posts?page={i}"
|
||||
class="pagination-link is-current"
|
||||
aria-label="Goto page {i}">{i}</Link
|
||||
>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<Link
|
||||
on:click={handlePage(i)}
|
||||
to="/posts?page={i}"
|
||||
class="pagination-link"
|
||||
aria-label="Goto page {i}">{i}</Link
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{#if totalPages - page > 2}
|
||||
<li>
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
on:click={handlePage(totalPages)}
|
||||
to="/posts?page={totalPages}"
|
||||
class="pagination-link"
|
||||
aria-label="Goto page {totalPages}"
|
||||
>{totalPages}</Link
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{#if (totalPages - page) > 3}
|
||||
<li>
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
</li>
|
||||
<li>
|
||||
<Link on:click="{handlePage(totalPages)}" to="/posts?page={totalPages}" class="pagination-link" aria-label="Goto page {totalPages}">{totalPages}</Link>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</nav>
|
||||
</nav>
|
||||
<div class="columns is-multiline">
|
||||
{#each posts as post (post.id)}
|
||||
<div class="column is-one-quarter card">
|
||||
<div class="card-image">
|
||||
<figure class="image">
|
||||
<Link to="/post/{post.id}">
|
||||
<img src="{post.image_path}">
|
||||
</Link>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
{#each post.tags as tag (tag)}
|
||||
<p>
|
||||
<Link to="/tag/{tag}">{tag}</Link>
|
||||
</p>
|
||||
{/each}
|
||||
<div class="column is-one-quarter card">
|
||||
<div class="card-image">
|
||||
<figure class="image">
|
||||
<Link to="/post/{post.id}">
|
||||
<img alt={post.id} src={post.image_path} />
|
||||
</Link>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
{#each post.tags as tag (tag)}
|
||||
<p>
|
||||
<Link to="/tag/{tag}">{tag}</Link>
|
||||
</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script>
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { getPostsTag } from "../api.js";
|
||||
import { Link} from "svelte-routing";
|
||||
import { Link } from "svelte-routing";
|
||||
import queryString from "query-string";
|
||||
|
||||
export let location;
|
||||
@ -12,37 +12,35 @@
|
||||
let totalPages = 1;
|
||||
let posts = [];
|
||||
const getData = async () => {
|
||||
const data = await getPostsTag({page, tag: id});
|
||||
if(Array.isArray(data.posts)) {
|
||||
const data = await getPostsTag({ page, tag: id });
|
||||
if (Array.isArray(data.posts)) {
|
||||
posts = data.posts;
|
||||
totalPages = data.totalPage;
|
||||
totalPages = data.totalPage;
|
||||
}
|
||||
};
|
||||
onMount(() => {
|
||||
let queryParams;
|
||||
queryParams = queryString.parse(location.search);
|
||||
if (queryParams.page) {
|
||||
page = parseInt(queryParams.page);
|
||||
}
|
||||
}
|
||||
onMount(() => {
|
||||
let queryParams;
|
||||
queryParams = queryString.parse(location.search);
|
||||
if(queryParams.page) {
|
||||
page = parseInt(queryParams.page);
|
||||
}
|
||||
getData();
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
const handlePage = (i) => {
|
||||
return () => {
|
||||
page = i;
|
||||
getData();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="hero is-primary">
|
||||
<div class="hero-body">
|
||||
<p class="title">
|
||||
{id}
|
||||
</p>
|
||||
<p class="subtitle">
|
||||
Tag
|
||||
</p>
|
||||
<p class="title">
|
||||
{id}
|
||||
</p>
|
||||
<p class="subtitle">Tag</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -50,63 +48,94 @@
|
||||
<div class="container">
|
||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||
{#if page > 1}
|
||||
<a class="pagination-previous">Previous</a>
|
||||
<Link
|
||||
on:click={handlePage(page - 1)}
|
||||
to="/tag/{id}?page={page - 1}"
|
||||
class="pagination-previous"
|
||||
aria-label="Previous">Previous</Link
|
||||
>
|
||||
{/if}
|
||||
{#if page < totalPages}
|
||||
<a class="pagination-next">Next page</a>
|
||||
<Link
|
||||
on:click={handlePage(page + 1)}
|
||||
to="/tag/{id}?page={page + 1}"
|
||||
class="pagination-next"
|
||||
aria-label="Next">Next</Link
|
||||
>
|
||||
{/if}
|
||||
<ul class="pagination-list">
|
||||
{#if page > 3}
|
||||
<li>
|
||||
<Link on:click="{handlePage(1)}" to="/tag/{id}?page={1}" class="pagination-link" aria-label="Goto page 1">1</Link>
|
||||
</li>
|
||||
<li>
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
</li>
|
||||
{/if}
|
||||
{#each [...Array(5).keys()].map(x => x + page - 2) as i }
|
||||
{#if i >= 1 && i <= totalPages}
|
||||
{#if i == page}
|
||||
<li>
|
||||
<Link on:click="{handlePage(i)}" to="/tag/{id}?page={i}" class="pagination-link is-current" aria-label="Goto page {i}">{i}</Link>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<Link on:click="{handlePage(i)}" to="/tag/{id}?page={i}" class="pagination-link" aria-label="Goto page {i}">{i}</Link>
|
||||
</li>
|
||||
{#if page > 3}
|
||||
<li>
|
||||
<Link
|
||||
on:click={handlePage(1)}
|
||||
to="/tag/{id}?page={1}"
|
||||
class="pagination-link"
|
||||
aria-label="Goto page 1">1</Link
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
</li>
|
||||
{/if}
|
||||
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
|
||||
{#if i >= 1 && i <= totalPages}
|
||||
{#if i == page}
|
||||
<li>
|
||||
<Link
|
||||
on:click={handlePage(i)}
|
||||
to="/tag/{id}?page={i}"
|
||||
class="pagination-link is-current"
|
||||
aria-label="Goto page {i}">{i}</Link
|
||||
>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<Link
|
||||
on:click={handlePage(i)}
|
||||
to="/tag/{id}?page={i}"
|
||||
class="pagination-link"
|
||||
aria-label="Goto page {i}">{i}</Link
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{#if totalPages - page > 2}
|
||||
<li>
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
on:click={handlePage(totalPages)}
|
||||
to="/tag/{id}?page={totalPages}"
|
||||
class="pagination-link"
|
||||
aria-label="Goto page {totalPages}"
|
||||
>{totalPages}</Link
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{#if (totalPages - page) > 3}
|
||||
<li>
|
||||
<span class="pagination-ellipsis">…</span>
|
||||
</li>
|
||||
<li>
|
||||
<Link on:click="{handlePage(totalPages)}" to="/tag/{id}?page={totalPages}" class="pagination-link" aria-label="Goto page {totalPages}">{totalPages}</Link>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</nav>
|
||||
</nav>
|
||||
<div class="columns is-multiline">
|
||||
{#each posts as post (post.id)}
|
||||
<div class="column is-one-quarter card">
|
||||
<div class="card-image">
|
||||
<figure class="image">
|
||||
<Link to="/post/{post.id}">
|
||||
<img src="{post.image_path}">
|
||||
</Link>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
{#each post.tags as tag (tag)}
|
||||
<p>
|
||||
<Link to="/tag/{tag}">{tag}</Link>
|
||||
</p>
|
||||
{/each}
|
||||
<div class="column is-one-quarter card">
|
||||
<div class="card-image">
|
||||
<figure class="image">
|
||||
<Link to="/post/{post.id}">
|
||||
<img alt={post.id} src={post.image_path} />
|
||||
</Link>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
{#each post.tags as tag (tag)}
|
||||
<p>
|
||||
<Link to="/tag/{tag}">{tag}</Link>
|
||||
</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
7568
web/static/bundle.js
7568
web/static/bundle.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user