mirror of
https://github.com/Damillora/Shioriko.git
synced 2024-11-21 20:07:33 +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 . .
|
COPY . .
|
||||||
|
|
||||||
RUN go get -d -v ./...
|
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
|
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 /
|
FROM alpine AS runtime
|
||||||
COPY --from=build /shioriko /
|
|
||||||
COPY --from=build /web /web
|
|
||||||
|
|
||||||
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,
|
Tags: tagStrings,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
postPages := services.CountPostPages()
|
postPages := services.CountPostPagesTag(tag)
|
||||||
c.JSON(http.StatusOK, models.PostPaginationResponse{
|
c.JSON(http.StatusOK, models.PostPaginationResponse{
|
||||||
CurrentPage: page,
|
CurrentPage: page,
|
||||||
TotalPage: postPages,
|
TotalPage: postPages,
|
||||||
|
@ -78,6 +78,16 @@ func CountPostPages() int {
|
|||||||
database.DB.Model(&database.Post{}).Count(&count)
|
database.DB.Model(&database.Post{}).Count(&count)
|
||||||
return int(count/perPage) + 1
|
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 {
|
func DeletePost(id string) error {
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Router, Link, Route } from "svelte-routing";
|
import { Router, Link, Route } from "svelte-routing";
|
||||||
|
|
||||||
|
import Navbar from "./Navbar.svelte";
|
||||||
|
|
||||||
import Home from "./routes/Home.svelte";
|
import Home from "./routes/Home.svelte";
|
||||||
import Posts from "./routes/Posts.svelte";
|
import Posts from "./routes/Posts.svelte";
|
||||||
import Post from "./routes/Post.svelte";
|
import Post from "./routes/Post.svelte";
|
||||||
@ -7,69 +10,21 @@
|
|||||||
import Logout from "./routes/Logout.svelte";
|
import Logout from "./routes/Logout.svelte";
|
||||||
import Tag from "./routes/Tag.svelte";
|
import Tag from "./routes/Tag.svelte";
|
||||||
|
|
||||||
import { token } from "./stores.js"
|
|
||||||
|
|
||||||
let loggedIn = false;
|
|
||||||
token.subscribe(value => {
|
|
||||||
loggedIn = value !== ""
|
|
||||||
});
|
|
||||||
|
|
||||||
export let url = "";
|
export let url = "";
|
||||||
let baseURL = window.BASE_URL;
|
let baseURL = window.BASE_URL;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Router url="{url}">
|
<Router {url}>
|
||||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
<Navbar />
|
||||||
<div class="navbar-brand">
|
<div>
|
||||||
<Link class="navbar-item" to="/">
|
<Route path="/" component={Home} />
|
||||||
Shioriko
|
<Route path="/posts" component={Posts} />
|
||||||
</Link>
|
<Route path="/tag/:id" component={Tag} />
|
||||||
|
<Route path="/post/:id" component={Post} />
|
||||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
|
<Route path="/auth/login" component={Login} />
|
||||||
<span aria-hidden="true"></span>
|
<Route path="/auth/logout" component={Logout} />
|
||||||
<span aria-hidden="true"></span>
|
</div>
|
||||||
<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>
|
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>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="hero is-primary is-medium">
|
<section class="hero is-primary is-medium">
|
||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
<p class="title">
|
<p class="title">Shioriko</p>
|
||||||
Shioriko
|
<p class="subtitle">Booru-style gallery written in Go and Svelte</p>
|
||||||
</p>
|
</div>
|
||||||
<p class="subtitle">
|
</section>
|
||||||
Booru-style gallery written in Go and Svelte
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
@ -1,33 +1,48 @@
|
|||||||
<script>
|
<script>
|
||||||
import { login } from "../api.js"
|
import { login } from "../api.js";
|
||||||
import { navigate } from "svelte-routing"
|
import { navigate } from "svelte-routing";
|
||||||
|
|
||||||
let username = "";
|
let username = "";
|
||||||
let password = "";
|
let password = "";
|
||||||
|
|
||||||
const doLogin = async () => {
|
const doLogin = async () => {
|
||||||
const tokenData = await login({ username, password});
|
const tokenData = await login({ username, password });
|
||||||
navigate("/");
|
navigate("/");
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<form on:submit|preventDefault={doLogin}>
|
<form on:submit|preventDefault={doLogin}>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Username</label>
|
<label for="username" class="label">Username</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" type="text" placeholder="Username" bind:value={username} required>
|
<input
|
||||||
</div>
|
id="username"
|
||||||
|
class="input"
|
||||||
|
type="text"
|
||||||
|
placeholder="Username"
|
||||||
|
bind:value={username}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Password</label>
|
<label for="password" class="label">Password</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" type="password" placeholder="Password" bind:value={password} required>
|
<input
|
||||||
</div>
|
id="password"
|
||||||
|
class="input"
|
||||||
|
type="password"
|
||||||
|
placeholder="Password"
|
||||||
|
bind:value={password}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button class="button is-link">Login</button>
|
<button class="button is-link">Login</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { token } from "../stores.js"
|
import { token } from "../stores.js";
|
||||||
import { navigate } from "svelte-routing"
|
import { navigate } from "svelte-routing";
|
||||||
import {onMount } from "svelte"
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
token.set("");
|
token.set("");
|
||||||
navigate("/");
|
navigate("/");
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<figure class="image">
|
<figure class="image">
|
||||||
<img src="{post.image_path}">
|
<img alt="{post.id}" src="{post.image_path}">
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { getPosts } from "../api.js";
|
import { getPosts } from "../api.js";
|
||||||
import { Link} from "svelte-routing";
|
import { Link } from "svelte-routing";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
export let location;
|
export let location;
|
||||||
@ -10,34 +10,32 @@
|
|||||||
let totalPages = 1;
|
let totalPages = 1;
|
||||||
let posts = [];
|
let posts = [];
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
const data = await getPosts({page});
|
const data = await getPosts({ page });
|
||||||
if(Array.isArray(data.posts)) {
|
if (Array.isArray(data.posts)) {
|
||||||
posts = data.posts;
|
posts = data.posts;
|
||||||
totalPages = data.totalPage;
|
totalPages = data.totalPage;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
let queryParams;
|
let queryParams;
|
||||||
$: queryParams = queryString.parse(location.search);
|
queryParams = queryString.parse(location.search);
|
||||||
if(queryParams.page) {
|
if (queryParams.page) {
|
||||||
page = parseInt(queryParams.page);
|
page = parseInt(queryParams.page);
|
||||||
}
|
}
|
||||||
getData();
|
getData();
|
||||||
})
|
});
|
||||||
|
|
||||||
const handlePage = (i) => {
|
const handlePage = (i) => {
|
||||||
return () => {
|
return () => {
|
||||||
page = i;
|
page = i;
|
||||||
getData();
|
getData();
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="hero is-primary">
|
<section class="hero is-primary">
|
||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
<p class="title">
|
<p class="title">Posts</p>
|
||||||
Posts
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -45,63 +43,94 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||||
{#if page > 1}
|
{#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}
|
||||||
{#if page < totalPages}
|
{#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}
|
{/if}
|
||||||
<ul class="pagination-list">
|
<ul class="pagination-list">
|
||||||
{#if page > 3}
|
{#if page > 3}
|
||||||
<li>
|
<li>
|
||||||
<Link on:click="{handlePage(1)}" to="/posts?page={1}" class="pagination-link" aria-label="Goto page 1">1</Link>
|
<Link
|
||||||
</li>
|
on:click={handlePage(1)}
|
||||||
<li>
|
to="/posts?page={1}"
|
||||||
<span class="pagination-ellipsis">…</span>
|
class="pagination-link"
|
||||||
</li>
|
aria-label="Goto page 1">1</Link
|
||||||
{/if}
|
>
|
||||||
{#each [...Array(5).keys()].map(x => x + page - 2) as i }
|
</li>
|
||||||
{#if i >= 1 && i <= totalPages}
|
<li>
|
||||||
{#if i == page}
|
<span class="pagination-ellipsis">…</span>
|
||||||
<li>
|
</li>
|
||||||
<Link on:click="{handlePage(i)}" to="/posts?page={i}" class="pagination-link is-current" aria-label="Goto page {i}">{i}</Link>
|
{/if}
|
||||||
</li>
|
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
|
||||||
{:else}
|
{#if i >= 1 && i <= totalPages}
|
||||||
<li>
|
{#if i == page}
|
||||||
<Link on:click="{handlePage(i)}" to="/posts?page={i}" class="pagination-link" aria-label="Goto page {i}">{i}</Link>
|
<li>
|
||||||
</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}
|
||||||
{/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>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="columns is-multiline">
|
<div class="columns is-multiline">
|
||||||
{#each posts as post (post.id)}
|
{#each posts as post (post.id)}
|
||||||
<div class="column is-one-quarter card">
|
<div class="column is-one-quarter card">
|
||||||
<div class="card-image">
|
<div class="card-image">
|
||||||
<figure class="image">
|
<figure class="image">
|
||||||
<Link to="/post/{post.id}">
|
<Link to="/post/{post.id}">
|
||||||
<img src="{post.image_path}">
|
<img alt={post.id} src={post.image_path} />
|
||||||
</Link>
|
</Link>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{#each post.tags as tag (tag)}
|
{#each post.tags as tag (tag)}
|
||||||
<p>
|
<p>
|
||||||
<Link to="/tag/{tag}">{tag}</Link>
|
<Link to="/tag/{tag}">{tag}</Link>
|
||||||
</p>
|
</p>
|
||||||
{/each}
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { getPostsTag } from "../api.js";
|
import { getPostsTag } from "../api.js";
|
||||||
import { Link} from "svelte-routing";
|
import { Link } from "svelte-routing";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
|
|
||||||
export let location;
|
export let location;
|
||||||
@ -12,37 +12,35 @@
|
|||||||
let totalPages = 1;
|
let totalPages = 1;
|
||||||
let posts = [];
|
let posts = [];
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
const data = await getPostsTag({page, tag: id});
|
const data = await getPostsTag({ page, tag: id });
|
||||||
if(Array.isArray(data.posts)) {
|
if (Array.isArray(data.posts)) {
|
||||||
posts = data.posts;
|
posts = data.posts;
|
||||||
totalPages = data.totalPage;
|
totalPages = data.totalPage;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
let queryParams;
|
let queryParams;
|
||||||
queryParams = queryString.parse(location.search);
|
queryParams = queryString.parse(location.search);
|
||||||
if(queryParams.page) {
|
if (queryParams.page) {
|
||||||
page = parseInt(queryParams.page);
|
page = parseInt(queryParams.page);
|
||||||
}
|
}
|
||||||
getData();
|
getData();
|
||||||
})
|
});
|
||||||
|
|
||||||
const handlePage = (i) => {
|
const handlePage = (i) => {
|
||||||
return () => {
|
return () => {
|
||||||
page = i;
|
page = i;
|
||||||
getData();
|
getData();
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="hero is-primary">
|
<section class="hero is-primary">
|
||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
<p class="title">
|
<p class="title">
|
||||||
{id}
|
{id}
|
||||||
</p>
|
</p>
|
||||||
<p class="subtitle">
|
<p class="subtitle">Tag</p>
|
||||||
Tag
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -50,63 +48,94 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||||
{#if page > 1}
|
{#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}
|
||||||
{#if page < totalPages}
|
{#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}
|
{/if}
|
||||||
<ul class="pagination-list">
|
<ul class="pagination-list">
|
||||||
{#if page > 3}
|
{#if page > 3}
|
||||||
<li>
|
<li>
|
||||||
<Link on:click="{handlePage(1)}" to="/tag/{id}?page={1}" class="pagination-link" aria-label="Goto page 1">1</Link>
|
<Link
|
||||||
</li>
|
on:click={handlePage(1)}
|
||||||
<li>
|
to="/tag/{id}?page={1}"
|
||||||
<span class="pagination-ellipsis">…</span>
|
class="pagination-link"
|
||||||
</li>
|
aria-label="Goto page 1">1</Link
|
||||||
{/if}
|
>
|
||||||
{#each [...Array(5).keys()].map(x => x + page - 2) as i }
|
</li>
|
||||||
{#if i >= 1 && i <= totalPages}
|
<li>
|
||||||
{#if i == page}
|
<span class="pagination-ellipsis">…</span>
|
||||||
<li>
|
</li>
|
||||||
<Link on:click="{handlePage(i)}" to="/tag/{id}?page={i}" class="pagination-link is-current" aria-label="Goto page {i}">{i}</Link>
|
{/if}
|
||||||
</li>
|
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
|
||||||
{:else}
|
{#if i >= 1 && i <= totalPages}
|
||||||
<li>
|
{#if i == page}
|
||||||
<Link on:click="{handlePage(i)}" to="/tag/{id}?page={i}" class="pagination-link" aria-label="Goto page {i}">{i}</Link>
|
<li>
|
||||||
</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}
|
||||||
{/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>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="columns is-multiline">
|
<div class="columns is-multiline">
|
||||||
{#each posts as post (post.id)}
|
{#each posts as post (post.id)}
|
||||||
<div class="column is-one-quarter card">
|
<div class="column is-one-quarter card">
|
||||||
<div class="card-image">
|
<div class="card-image">
|
||||||
<figure class="image">
|
<figure class="image">
|
||||||
<Link to="/post/{post.id}">
|
<Link to="/post/{post.id}">
|
||||||
<img src="{post.image_path}">
|
<img alt={post.id} src={post.image_path} />
|
||||||
</Link>
|
</Link>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{#each post.tags as tag (tag)}
|
{#each post.tags as tag (tag)}
|
||||||
<p>
|
<p>
|
||||||
<Link to="/tag/{tag}">{tag}</Link>
|
<Link to="/tag/{tag}">{tag}</Link>
|
||||||
</p>
|
</p>
|
||||||
{/each}
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</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