mirror of
https://github.com/Damillora/Shioriko.git
synced 2024-11-24 21:27:31 +00:00
feat: refactor post browse api
This commit is contained in:
parent
79fe33496b
commit
a09058ceb2
@ -3,7 +3,9 @@ package app
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Damillora/Shioriko/pkg/database"
|
||||||
"github.com/Damillora/Shioriko/pkg/middleware"
|
"github.com/Damillora/Shioriko/pkg/middleware"
|
||||||
"github.com/Damillora/Shioriko/pkg/models"
|
"github.com/Damillora/Shioriko/pkg/models"
|
||||||
"github.com/Damillora/Shioriko/pkg/services"
|
"github.com/Damillora/Shioriko/pkg/services"
|
||||||
@ -16,7 +18,6 @@ func InitializePostRoutes(g *gin.Engine) {
|
|||||||
{
|
{
|
||||||
unprotected.GET("/", postGet)
|
unprotected.GET("/", postGet)
|
||||||
unprotected.GET("/:id", postGetOne)
|
unprotected.GET("/:id", postGetOne)
|
||||||
unprotected.GET("/tag/:id", postGetTag)
|
|
||||||
}
|
}
|
||||||
protected := g.Group("/api/post").Use(middleware.AuthMiddleware())
|
protected := g.Group("/api/post").Use(middleware.AuthMiddleware())
|
||||||
{
|
{
|
||||||
@ -30,7 +31,22 @@ func InitializePostRoutes(g *gin.Engine) {
|
|||||||
func postGet(c *gin.Context) {
|
func postGet(c *gin.Context) {
|
||||||
pageParam := c.Query("page")
|
pageParam := c.Query("page")
|
||||||
page, _ := strconv.Atoi(pageParam)
|
page, _ := strconv.Atoi(pageParam)
|
||||||
posts := services.GetPostAll(page)
|
|
||||||
|
tag := c.Query("tags")
|
||||||
|
|
||||||
|
tags := strings.Split(tag, " ")
|
||||||
|
|
||||||
|
var posts []database.Post
|
||||||
|
var postPages int
|
||||||
|
|
||||||
|
if tag != "" {
|
||||||
|
posts = services.GetPostTags(page, tags)
|
||||||
|
postPages = services.CountPostPagesTag(tags)
|
||||||
|
} else {
|
||||||
|
posts = services.GetPostAll(page)
|
||||||
|
postPages = services.CountPostPages()
|
||||||
|
}
|
||||||
|
|
||||||
var postResult []models.PostListItem
|
var postResult []models.PostListItem
|
||||||
for _, post := range posts {
|
for _, post := range posts {
|
||||||
var tagStrings []string
|
var tagStrings []string
|
||||||
@ -44,35 +60,6 @@ func postGet(c *gin.Context) {
|
|||||||
Tags: tagStrings,
|
Tags: tagStrings,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
postPages := services.CountPostPages()
|
|
||||||
c.JSON(http.StatusOK, models.PostPaginationResponse{
|
|
||||||
CurrentPage: page,
|
|
||||||
TotalPage: postPages,
|
|
||||||
Posts: postResult,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func postGetTag(c *gin.Context) {
|
|
||||||
pageParam := c.Query("page")
|
|
||||||
page, _ := strconv.Atoi(pageParam)
|
|
||||||
|
|
||||||
tag := c.Param("id")
|
|
||||||
|
|
||||||
posts := services.GetPostTag(page, tag)
|
|
||||||
var postResult []models.PostListItem
|
|
||||||
for _, post := range posts {
|
|
||||||
var tagStrings []string
|
|
||||||
for _, tag := range post.Tags {
|
|
||||||
tagStrings = append(tagStrings, tag.TagType.Name+":"+tag.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
postResult = append(postResult, models.PostListItem{
|
|
||||||
ID: post.ID,
|
|
||||||
ImagePath: "/data/" + post.Blob.FilePath,
|
|
||||||
Tags: tagStrings,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
postPages := services.CountPostPagesTag(tag)
|
|
||||||
c.JSON(http.StatusOK, models.PostPaginationResponse{
|
c.JSON(http.StatusOK, models.PostPaginationResponse{
|
||||||
CurrentPage: page,
|
CurrentPage: page,
|
||||||
TotalPage: postPages,
|
TotalPage: postPages,
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/Damillora/Shioriko/pkg/database"
|
"github.com/Damillora/Shioriko/pkg/database"
|
||||||
"github.com/Damillora/Shioriko/pkg/models"
|
"github.com/Damillora/Shioriko/pkg/models"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -10,17 +13,18 @@ const perPage = 20
|
|||||||
|
|
||||||
func GetPostAll(page int) []database.Post {
|
func GetPostAll(page int) []database.Post {
|
||||||
var posts []database.Post
|
var posts []database.Post
|
||||||
database.DB.Joins("Blob").Preload("Tags").Preload("Tags.TagType").Offset((page - 1) * perPage).Limit(20).Find(&posts)
|
database.DB.Joins("Blob").Preload("Tags").Preload("Tags.TagType").Order("created_at desc").Offset((page - 1) * perPage).Limit(20).Find(&posts)
|
||||||
return posts
|
return posts
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPostTag(page int, tagSyntax string) []database.Post {
|
func GetPostTags(page int, tagSyntax []string) []database.Post {
|
||||||
tag, err := GetTag(tagSyntax)
|
tags, err := ParseReadTags(tagSyntax)
|
||||||
|
log.Println(tags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []database.Post{}
|
return []database.Post{}
|
||||||
}
|
}
|
||||||
var posts []database.Post
|
var posts []database.Post
|
||||||
database.DB.Model(&tag).Joins("Blob").Preload("Tags").Preload("Tags.TagType").Offset((page - 1) * perPage).Limit(20).Association("Posts").Find(&posts)
|
database.DB.Model(&tags).Distinct().Joins("Blob").Preload("Tags").Preload("Tags.TagType").Order("created_at desc").Offset((page - 1) * perPage).Limit(20).Association("Posts").Find(&posts)
|
||||||
return posts
|
return posts
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,34 +63,38 @@ func UpdatePost(id string, model models.PostUpdateModel) (*database.Post, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var post database.Post
|
var post database.Post
|
||||||
result := database.DB.Where("id = ?", id).First(&post)
|
result := database.DB.Preload("Tags").Where("id = ?", id).First(&post)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, result.Error
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
database.DB.Model(&post).Association("Tags").Replace(tags)
|
||||||
|
|
||||||
post.SourceURL = model.SourceURL
|
post.SourceURL = model.SourceURL
|
||||||
post.Tags = tags
|
|
||||||
|
|
||||||
result = database.DB.Save(&post)
|
result = database.DB.Save(&post)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, result.Error
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
return &post, nil
|
return &post, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
func CountPostPages() int {
|
func CountPostPages() int {
|
||||||
var count int64
|
var count int64
|
||||||
database.DB.Model(&database.Post{}).Count(&count)
|
database.DB.Model(&database.Post{}).Count(&count)
|
||||||
return int(count/perPage) + 1
|
return int(math.Abs(float64(count-1))/perPage) + 1
|
||||||
}
|
}
|
||||||
func CountPostPagesTag(tagSyntax string) int {
|
func CountPostPagesTag(tagSyntax []string) int {
|
||||||
tag, err := GetTag(tagSyntax)
|
tags, err := ParseReadTags(tagSyntax)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var count int64
|
var count int64
|
||||||
count = database.DB.Model(&tag).Joins("Blob").Preload("Tags").Preload("Tags.TagType").Association("Posts").Count()
|
count = database.DB.Model(&tags).Distinct().Joins("Blob").Preload("Tags").Preload("Tags.TagType").Association("Posts").Count()
|
||||||
return int(count/perPage) + 1
|
|
||||||
|
return int(math.Abs(float64(count-1))/perPage) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeletePost(id string) error {
|
func DeletePost(id string) error {
|
||||||
|
@ -14,27 +14,15 @@ func GetTagAll() []database.Tag {
|
|||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateOrUpdateTagGeneric(tagName string) (*database.Tag, error) {
|
func FindTagGeneric(tagName string) (*database.Tag, error) {
|
||||||
var tag database.Tag
|
var tag database.Tag
|
||||||
result := database.DB.Where("name = ?", tagName).First(&tag)
|
result := database.DB.Where("name = ?", tagName).First(&tag)
|
||||||
if result.Error != nil {
|
|
||||||
var tagType database.TagType
|
|
||||||
database.DB.Where("name = ?", "general").First(&tagType)
|
|
||||||
|
|
||||||
tag = database.Tag{
|
|
||||||
ID: uuid.NewString(),
|
|
||||||
Name: tagName,
|
|
||||||
TagTypeID: tagType.ID,
|
|
||||||
}
|
|
||||||
result = database.DB.Create(&tag)
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, result.Error
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
return &tag, nil
|
return &tag, nil
|
||||||
}
|
}
|
||||||
func CreateOrUpdateTagComplex(tagName string, tagTypeString string) (*database.Tag, error) {
|
func FindTagComplex(tagName string, tagTypeString string) (*database.Tag, error) {
|
||||||
var tag database.Tag
|
var tag database.Tag
|
||||||
var tagType database.TagType
|
var tagType database.TagType
|
||||||
result := database.DB.Where("name = ?", tagTypeString).First(&tagType)
|
result := database.DB.Where("name = ?", tagTypeString).First(&tagType)
|
||||||
@ -45,7 +33,40 @@ func CreateOrUpdateTagComplex(tagName string, tagTypeString string) (*database.T
|
|||||||
result = database.DB.Where("name = ? AND tag_type_id = ? ", tagName, tagType.ID).First(&tag)
|
result = database.DB.Where("name = ? AND tag_type_id = ? ", tagName, tagType.ID).First(&tag)
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
tag = database.Tag{
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
return &tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateOrUpdateTagGeneric(tagName string) (*database.Tag, error) {
|
||||||
|
tag, err := FindTagGeneric(tagName)
|
||||||
|
if err != nil {
|
||||||
|
var tagType database.TagType
|
||||||
|
database.DB.Where("name = ?", "general").First(&tagType)
|
||||||
|
|
||||||
|
tag = &database.Tag{
|
||||||
|
ID: uuid.NewString(),
|
||||||
|
Name: tagName,
|
||||||
|
TagTypeID: tagType.ID,
|
||||||
|
}
|
||||||
|
result := database.DB.Create(&tag)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
func CreateOrUpdateTagComplex(tagName string, tagTypeString string) (*database.Tag, error) {
|
||||||
|
tag, err := FindTagComplex(tagName, tagTypeString)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
var tagType database.TagType
|
||||||
|
result := database.DB.Where("name = ?", tagTypeString).First(&tagType)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
tag = &database.Tag{
|
||||||
ID: uuid.NewString(),
|
ID: uuid.NewString(),
|
||||||
Name: tagName,
|
Name: tagName,
|
||||||
TagTypeID: tagType.ID,
|
TagTypeID: tagType.ID,
|
||||||
@ -56,7 +77,7 @@ func CreateOrUpdateTagComplex(tagName string, tagTypeString string) (*database.T
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return &tag, nil
|
return tag, nil
|
||||||
}
|
}
|
||||||
func CreateOrUpdateTag(tagSyntax string) (*database.Tag, error) {
|
func CreateOrUpdateTag(tagSyntax string) (*database.Tag, error) {
|
||||||
tagFields := strings.Split(tagSyntax, ":")
|
tagFields := strings.Split(tagSyntax, ":")
|
||||||
@ -73,30 +94,20 @@ func CreateOrUpdateTag(tagSyntax string) (*database.Tag, error) {
|
|||||||
return nil, errors.New("Malformed tag syntax")
|
return nil, errors.New("Malformed tag syntax")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func FindTag(tagSyntax string) (*database.Tag, error) {
|
||||||
func GetTag(tagSyntax string) (*database.Tag, error) {
|
|
||||||
tagFields := strings.Split(tagSyntax, ":")
|
tagFields := strings.Split(tagSyntax, ":")
|
||||||
var tagName string
|
var tagName string
|
||||||
var tagType database.TagType
|
var tagType string
|
||||||
if len(tagFields) == 1 {
|
if len(tagFields) == 1 {
|
||||||
tagName = tagFields[0]
|
tagName = tagFields[0]
|
||||||
database.DB.Where("name = ?", "general").First(&tagType)
|
return FindTagGeneric(tagName)
|
||||||
} else if len(tagFields) == 2 {
|
} else if len(tagFields) == 2 {
|
||||||
|
tagType = tagFields[0]
|
||||||
tagName = tagFields[1]
|
tagName = tagFields[1]
|
||||||
result := database.DB.Where("name = ?", tagFields[0]).First(&tagType)
|
return FindTagComplex(tagName, tagType)
|
||||||
if result.Error != nil {
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("Malformed tag syntax")
|
return nil, errors.New("Malformed tag syntax")
|
||||||
}
|
}
|
||||||
var tag database.Tag
|
|
||||||
result := database.DB.Preload("Posts").Where("name = ? AND tag_type_id = ? ", tagName, tagType.ID).First(&tag)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
return &tag, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseTags(tags []string) ([]database.Tag, error) {
|
func ParseTags(tags []string) ([]database.Tag, error) {
|
||||||
@ -110,3 +121,15 @@ func ParseTags(tags []string) ([]database.Tag, error) {
|
|||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseReadTags(tags []string) ([]database.Tag, error) {
|
||||||
|
var result []database.Tag
|
||||||
|
for _, tagSyntax := range tags {
|
||||||
|
tag, err := FindTag(tagSyntax)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = append(result, *tag)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
import Post from "./routes/Post.svelte";
|
import Post from "./routes/Post.svelte";
|
||||||
import Login from "./routes/Login.svelte";
|
import Login from "./routes/Login.svelte";
|
||||||
import Logout from "./routes/Logout.svelte";
|
import Logout from "./routes/Logout.svelte";
|
||||||
import Tag from "./routes/Tag.svelte";
|
|
||||||
import Upload from "./routes/Upload.svelte";
|
import Upload from "./routes/Upload.svelte";
|
||||||
|
import Edit from "./routes/Edit.svelte";
|
||||||
|
|
||||||
export let url = "";
|
export let url = "";
|
||||||
let baseURL = window.BASE_URL;
|
let baseURL = window.BASE_URL;
|
||||||
@ -20,8 +20,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<Route path="/" component={Home} />
|
<Route path="/" component={Home} />
|
||||||
<Route path="/posts" component={Posts} />
|
<Route path="/posts" component={Posts} />
|
||||||
<Route path="/tag/:id" component={Tag} />
|
|
||||||
<Route path="/post/:id" component={Post} />
|
<Route path="/post/:id" component={Post} />
|
||||||
|
<Route path="/post/edit/:id" component={Edit} />
|
||||||
<Route path="/auth/login" component={Login} />
|
<Route path="/auth/login" component={Login} />
|
||||||
<Route path="/auth/logout" component={Logout} />
|
<Route path="/auth/logout" component={Logout} />
|
||||||
<Route path="/upload" component={Upload} />
|
<Route path="/upload" component={Upload} />
|
||||||
|
@ -1,20 +1,32 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Link } from "svelte-routing";
|
import { Link } from "svelte-routing";
|
||||||
|
import TagLink from "./TagLink.svelte";
|
||||||
|
|
||||||
export let posts = [];
|
export let posts = [];
|
||||||
|
let postChunks = [];
|
||||||
|
// split posts into 4 columns
|
||||||
|
$: postChunks = Array(Math.min(posts.length, 4))
|
||||||
|
.fill()
|
||||||
|
.map(function (_, i) {
|
||||||
|
let chunkSize = Math.floor(posts.length / 4);
|
||||||
|
if (chunkSize % 4 > i + 1) {
|
||||||
|
chunkSize += 1;
|
||||||
|
}
|
||||||
|
chunkSize = Math.max(chunkSize, 1);
|
||||||
|
|
||||||
|
return posts.slice(i * chunkSize, i * chunkSize + chunkSize);
|
||||||
|
});
|
||||||
export let page = 1;
|
export let page = 1;
|
||||||
export let totalPages = 1;
|
export let totalPages = 1;
|
||||||
export let handlePage = (i) => { };
|
export let handlePage = (i) => {};
|
||||||
export let url = "/posts";
|
export let url = "/posts";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="section">
|
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||||
<div class="container">
|
|
||||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
|
||||||
{#if page > 1}
|
{#if page > 1}
|
||||||
<Link
|
<Link
|
||||||
on:click={handlePage(page - 1)}
|
on:click={handlePage(page - 1)}
|
||||||
to="{url}?page={page - 1}"
|
to="{url}page={page - 1}"
|
||||||
class="pagination-previous"
|
class="pagination-previous"
|
||||||
aria-label="Previous">Previous</Link
|
aria-label="Previous">Previous</Link
|
||||||
>
|
>
|
||||||
@ -22,7 +34,7 @@
|
|||||||
{#if page < totalPages}
|
{#if page < totalPages}
|
||||||
<Link
|
<Link
|
||||||
on:click={handlePage(page + 1)}
|
on:click={handlePage(page + 1)}
|
||||||
to="{url}?page={page + 1}"
|
to="{url}page={page + 1}"
|
||||||
class="pagination-next"
|
class="pagination-next"
|
||||||
aria-label="Next">Next</Link
|
aria-label="Next">Next</Link
|
||||||
>
|
>
|
||||||
@ -32,7 +44,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
on:click={handlePage(1)}
|
on:click={handlePage(1)}
|
||||||
to="{url}?page={1}"
|
to="{url}page={1}"
|
||||||
class="pagination-link"
|
class="pagination-link"
|
||||||
aria-label="Goto page 1">1</Link
|
aria-label="Goto page 1">1</Link
|
||||||
>
|
>
|
||||||
@ -47,7 +59,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
on:click={handlePage(i)}
|
on:click={handlePage(i)}
|
||||||
to="{url}?page={i}"
|
to="{url}page={i}"
|
||||||
class="pagination-link is-current"
|
class="pagination-link is-current"
|
||||||
aria-label="Goto page {i}">{i}</Link
|
aria-label="Goto page {i}">{i}</Link
|
||||||
>
|
>
|
||||||
@ -56,7 +68,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
on:click={handlePage(i)}
|
on:click={handlePage(i)}
|
||||||
to="{url}?page={i}"
|
to="{url}page={i}"
|
||||||
class="pagination-link"
|
class="pagination-link"
|
||||||
aria-label="Goto page {i}">{i}</Link
|
aria-label="Goto page {i}">{i}</Link
|
||||||
>
|
>
|
||||||
@ -71,18 +83,19 @@
|
|||||||
<li>
|
<li>
|
||||||
<Link
|
<Link
|
||||||
on:click={handlePage(totalPages)}
|
on:click={handlePage(totalPages)}
|
||||||
to="{url}?page={totalPages}"
|
to="{url}page={totalPages}"
|
||||||
class="pagination-link"
|
class="pagination-link"
|
||||||
aria-label="Goto page {totalPages}"
|
aria-label="Goto page {totalPages}">{totalPages}</Link
|
||||||
>{totalPages}</Link
|
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="columns is-multiline">
|
<div class="tile is-multiline is-ancestor">
|
||||||
{#each posts as post (post.id)}
|
{#each postChunks as postChunk}
|
||||||
<div class="column is-one-quarter card">
|
<div class="tile is-parent is-vertical is-3">
|
||||||
|
{#each postChunk as post, i (post.id)}
|
||||||
|
<div class="tile is-child is-vertical 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}">
|
||||||
@ -91,16 +104,16 @@
|
|||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="content">
|
{#if post.tags}
|
||||||
{#each post.tags as tag (tag)}
|
{#each post.tags as tag (tag)}
|
||||||
<p>
|
<TagLink {tag} />
|
||||||
<Link to="/tag/{tag}">{tag}</Link>
|
|
||||||
</p>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
{:else}
|
||||||
|
None
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/each}
|
||||||
</section>
|
</div>
|
||||||
|
10
web/app/src/TagLink.svelte
Normal file
10
web/app/src/TagLink.svelte
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
import { Link } from "svelte-routing";
|
||||||
|
|
||||||
|
export let tag;
|
||||||
|
|
||||||
|
let tagType = tag.split(":")[0] ?? "";
|
||||||
|
let tagName = tag.split(":")[1] ?? "";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Link class="button is-rounded is-primary is-small m-1" to="/posts?tags={tagName}">{tagName}</Link>
|
@ -27,10 +27,16 @@ export async function getPosts({ page }) {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPostsTag({ page, tag }) {
|
export async function getPostSearchTag({ page, q }) {
|
||||||
const endpoint = url + "/api/post/tag/" + tag + "?page=" + page;
|
if (q) {
|
||||||
|
const endpoint = url + "/api/post?tags=" + q + "&page=" + page;
|
||||||
const response = await axios(endpoint);
|
const response = await axios(endpoint);
|
||||||
return response.data;
|
return response.data;
|
||||||
|
} else {
|
||||||
|
const endpoint = url + "/api/post?page=" + page;
|
||||||
|
const response = await axios(endpoint);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPost({ id }) {
|
export async function getPost({ id }) {
|
||||||
@ -76,3 +82,19 @@ export async function postCreate({ blob_id, source_url, tags }) {
|
|||||||
})
|
})
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function postUpdate(id, { source_url, tags }) {
|
||||||
|
const endpoint = url + "/api/post/"+id;
|
||||||
|
const response = await axios({
|
||||||
|
url: endpoint,
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + current_token,
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
data: {
|
||||||
|
source_url, tags
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return response.data;
|
||||||
|
}
|
@ -1,9 +1,13 @@
|
|||||||
|
@import '../node_modules/bulma/sass/utilities/_all';
|
||||||
|
@import '../node_modules/bulma/sass/base/_all';
|
||||||
|
@import '../node_modules/bulma/sass/elements/_all';
|
||||||
|
@import '../node_modules/bulma/sass/form/_all';
|
||||||
|
@import '../node_modules/bulma/sass/components/_all';
|
||||||
|
@import '../node_modules/bulma/sass/grid/_all';
|
||||||
|
@import '../node_modules/bulma/sass/helpers/_all';
|
||||||
|
@import '../node_modules/bulma/sass/layout/_all';
|
||||||
|
|
||||||
|
.tile.is-multiline {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
@import "../node_modules/bulma/sass/utilities/_all";
|
|
||||||
@import "../node_modules/bulma/sass/base/_all";
|
|
||||||
@import "../node_modules/bulma/sass/elements/_all";
|
|
||||||
@import "../node_modules/bulma/sass/form/_all";
|
|
||||||
@import "../node_modules/bulma/sass/components/_all";
|
|
||||||
@import "../node_modules/bulma/sass/grid/_all";
|
|
||||||
@import "../node_modules/bulma/sass/helpers/_all";
|
|
||||||
@import "../node_modules/bulma/sass/layout/_all";
|
|
||||||
|
90
web/app/src/routes/Edit.svelte
Normal file
90
web/app/src/routes/Edit.svelte
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<script>
|
||||||
|
import { getPost, postUpdate } from "../api.js";
|
||||||
|
import { navigate } from "svelte-routing";
|
||||||
|
import Tags from "svelte-tags-input";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { Link } from "svelte-routing";
|
||||||
|
|
||||||
|
export let id;
|
||||||
|
|
||||||
|
let image_path = "";
|
||||||
|
|
||||||
|
let form = {
|
||||||
|
source_url: "",
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
const data = await getPost({ id });
|
||||||
|
form.source_url = data.source_url;
|
||||||
|
form.tags = data.tags;
|
||||||
|
image_path = data.image_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTagChange = (value) => {
|
||||||
|
form.tags = value.detail.tags;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
const response = await postUpdate(id, form);
|
||||||
|
navigate(`/post/${response.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<p class="title">Edit Post: {id}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<section class="section">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-one-third box">
|
||||||
|
<p>
|
||||||
|
<Link class="button is-primary" to="/post/{id}"
|
||||||
|
>Back</Link
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<form on:submit|preventDefault={onSubmit}>
|
||||||
|
<div class="field">
|
||||||
|
<label for="source" class="label">Source URL</label>
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
id="source"
|
||||||
|
class="input"
|
||||||
|
type="url"
|
||||||
|
placeholder="Source URL"
|
||||||
|
bind:value={form.source_url}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="tags" class="label">Tags</label>
|
||||||
|
<div class="control" id="tags">
|
||||||
|
<Tags
|
||||||
|
tags={form.tags}
|
||||||
|
addKeys={[9,32]}
|
||||||
|
on:tags={onTagChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button type="submit" class="button is-primary"
|
||||||
|
>Submit</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<figure class="image">
|
||||||
|
<img alt={id} src={image_path} />
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
@ -1,25 +1,27 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import TagLink from "../TagLink.svelte";
|
||||||
|
import { getPost, postCreate } from "../api.js";
|
||||||
import { Link } from "svelte-routing";
|
import { Link } from "svelte-routing";
|
||||||
import {getPost } from "../api.js";
|
|
||||||
export let id;
|
export let id;
|
||||||
let post;
|
let post;
|
||||||
const getData = async() => {
|
const getData = async () => {
|
||||||
const data = await getPost({id});
|
const data = await getPost({ id });
|
||||||
post = data;
|
post = data;
|
||||||
}
|
};
|
||||||
|
|
||||||
const trimUrl = (str) => {
|
const trimUrl = (str) => {
|
||||||
if(str.length > 30) {
|
if (str.length > 30) {
|
||||||
return str.substring(0,30) + "...";
|
return str.substring(0, 30) + "...";
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
};
|
||||||
|
|
||||||
onMount(() => {getData()});
|
onMount(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<section class="hero is-primary">
|
<section class="hero is-primary">
|
||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
{#if post}
|
{#if post}
|
||||||
@ -30,30 +32,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{#if post}
|
{#if post}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-one-third box">
|
<div class="column is-one-third box">
|
||||||
|
<div class="content">
|
||||||
<p>
|
<p>
|
||||||
Source URL: <a href="{post.source_url}">{trimUrl(post.source_url)}</a>
|
<Link
|
||||||
|
class="button is-primary"
|
||||||
|
to="/post/edit/{post.id}">Edit</Link
|
||||||
|
>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Tags:
|
Source URL: <a href={post.source_url}
|
||||||
|
>{trimUrl(post.source_url)}</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Tags:<br />
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{#if post.tags}
|
||||||
{#each post.tags as tag (tag)}
|
{#each post.tags as tag (tag)}
|
||||||
<ul>
|
<TagLink {tag} />
|
||||||
<li>
|
|
||||||
<Link to="/tag/{tag}">{tag}</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{/each}
|
{/each}
|
||||||
|
{:else}
|
||||||
|
None
|
||||||
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<figure class="image">
|
<figure class="image">
|
||||||
<img alt="{post.id}" src="{post.image_path}">
|
<img alt={post.id} src={post.image_path} />
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
@ -1,30 +1,45 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { getPosts } from "../api.js";
|
import { getPostSearchTag } from "../api.js";
|
||||||
import { Link } from "svelte-routing";
|
import { navigate } from "svelte-routing";
|
||||||
import queryString from "query-string";
|
|
||||||
import PostPaginator from "../PostPaginator.svelte";
|
import PostPaginator from "../PostPaginator.svelte";
|
||||||
|
import queryString from "query-string";
|
||||||
|
import Tags from "svelte-tags-input";
|
||||||
|
|
||||||
export let location;
|
export let location;
|
||||||
|
|
||||||
|
let searchTerms = [];
|
||||||
|
|
||||||
let page = 1;
|
let page = 1;
|
||||||
let totalPages = 1;
|
let totalPages = 1;
|
||||||
let posts = [];
|
let posts = [];
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
const data = await getPosts({ page });
|
const data = await getPostSearchTag({ page, q: searchTerms.join("+") });
|
||||||
if (Array.isArray(data.posts)) {
|
if (Array.isArray(data.posts)) {
|
||||||
posts = data.posts;
|
posts = data.posts;
|
||||||
totalPages = data.totalPage;
|
totalPages = data.totalPage;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
onMount(() => {
|
|
||||||
let queryParams;
|
let queryParams;
|
||||||
|
|
||||||
|
const onTagChange = (value) => {
|
||||||
|
searchTerms = value.detail.tags;
|
||||||
|
};
|
||||||
|
|
||||||
|
$: {
|
||||||
queryParams = queryString.parse(location.search);
|
queryParams = queryString.parse(location.search);
|
||||||
|
console.log(queryParams);
|
||||||
if (queryParams.page) {
|
if (queryParams.page) {
|
||||||
page = parseInt(queryParams.page);
|
page = parseInt(queryParams.page);
|
||||||
}
|
}
|
||||||
|
if (queryParams.tags) {
|
||||||
|
searchTerms = queryParams.tags.split(" ");
|
||||||
|
} else {
|
||||||
|
searchTerms = [];
|
||||||
|
}
|
||||||
getData();
|
getData();
|
||||||
});
|
}
|
||||||
|
|
||||||
const handlePage = (i) => {
|
const handlePage = (i) => {
|
||||||
return () => {
|
return () => {
|
||||||
@ -32,6 +47,14 @@
|
|||||||
getData();
|
getData();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSearch = (i) => {
|
||||||
|
if (searchTerms.length > 0) {
|
||||||
|
navigate(`/posts?tags=${searchTerms.join("+")}`);
|
||||||
|
} else {
|
||||||
|
navigate(`/posts`);
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="hero is-primary">
|
<section class="hero is-primary">
|
||||||
@ -40,4 +63,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<PostPaginator url="/posts" posts={posts} page={page} totalPages={totalPages} handlePage={handlePage} />
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="block">
|
||||||
|
<form on:submit|preventDefault={onSearch}>
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control is-expanded">
|
||||||
|
<div class="control" id="tags">
|
||||||
|
<Tags
|
||||||
|
tags={searchTerms}
|
||||||
|
addKeys={[9,32]}
|
||||||
|
on:tags={onTagChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button type="submit" class="button is-primary">
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<PostPaginator
|
||||||
|
url="/posts?tags={searchTerms.join('+')}&"
|
||||||
|
{posts}
|
||||||
|
{page}
|
||||||
|
{totalPages}
|
||||||
|
{handlePage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
import { getPostsTag } from "../api.js";
|
|
||||||
import PostPaginator from "../PostPaginator.svelte";
|
|
||||||
import queryString from "query-string";
|
|
||||||
|
|
||||||
export let location;
|
|
||||||
|
|
||||||
export let id;
|
|
||||||
|
|
||||||
let page = 1;
|
|
||||||
let totalPages = 1;
|
|
||||||
let posts = [];
|
|
||||||
const getData = async () => {
|
|
||||||
const data = await getPostsTag({ page, tag: id });
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
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>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<PostPaginator url="/tag/{id}" posts={posts} page={page} totalPages={totalPages} handlePage={handlePage} />
|
|
@ -88,7 +88,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="tags" class="label">Tags</label>
|
<label for="tags" class="label">Tags</label>
|
||||||
<div class="control" id="tags">
|
<div class="control" id="tags">
|
||||||
<Tags on:tags={onTagChange} />
|
<Tags addKeys={[9,32]} on:tags={onTagChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
6303
web/static/bundle.js
6303
web/static/bundle.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user