Compare commits
5 Commits
2cc9705845
...
36db313d33
Author | SHA1 | Date |
---|---|---|
Damillora | 36db313d33 | |
Damillora | e73d3d0dda | |
Damillora | 3ed594ab88 | |
Damillora | fdb4250652 | |
Damillora | ba86dd3c01 |
|
@ -32,12 +32,16 @@ func postGet(c *gin.Context) {
|
||||||
pageParam := c.Query("page")
|
pageParam := c.Query("page")
|
||||||
page, _ := strconv.Atoi(pageParam)
|
page, _ := strconv.Atoi(pageParam)
|
||||||
|
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
tag := c.Query("tags")
|
tag := c.Query("tags")
|
||||||
|
|
||||||
tags := strings.Split(tag, " ")
|
tags := strings.Split(tag, " ")
|
||||||
|
|
||||||
var posts []database.Post
|
var posts []database.Post
|
||||||
var postPages int
|
var postPages int
|
||||||
|
var perPage = 20
|
||||||
|
|
||||||
if tag != "" {
|
if tag != "" {
|
||||||
posts = services.GetPostTags(page, tags)
|
posts = services.GetPostTags(page, tags)
|
||||||
|
@ -47,9 +51,15 @@ func postGet(c *gin.Context) {
|
||||||
postPages = services.CountPostPages()
|
postPages = services.CountPostPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var totalPage = postPages / perPage
|
||||||
|
if postPages%perPage > 0 {
|
||||||
|
totalPage++
|
||||||
|
}
|
||||||
|
|
||||||
var postResult []models.PostListItem
|
var postResult []models.PostListItem
|
||||||
|
var tagStrings []string
|
||||||
for _, post := range posts {
|
for _, post := range posts {
|
||||||
var tagStrings []string
|
|
||||||
for _, tag := range post.Tags {
|
for _, tag := range post.Tags {
|
||||||
tagStrings = append(tagStrings, tag.TagType.Name+":"+tag.Name)
|
tagStrings = append(tagStrings, tag.TagType.Name+":"+tag.Name)
|
||||||
}
|
}
|
||||||
|
@ -58,13 +68,16 @@ func postGet(c *gin.Context) {
|
||||||
ID: post.ID,
|
ID: post.ID,
|
||||||
ImageThumbnailPath: "/data/" + post.Blob.ThumbnailFilePath,
|
ImageThumbnailPath: "/data/" + post.Blob.ThumbnailFilePath,
|
||||||
ImagePath: "/data/" + post.Blob.FilePath,
|
ImagePath: "/data/" + post.Blob.FilePath,
|
||||||
Tags: tagStrings,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
tagObjs := services.GetTagFilterString(tagStrings)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, models.PostPaginationResponse{
|
c.JSON(http.StatusOK, models.PostPaginationResponse{
|
||||||
CurrentPage: page,
|
CurrentPage: page,
|
||||||
|
TotalPage: totalPage,
|
||||||
PostCount: postPages,
|
PostCount: postPages,
|
||||||
Posts: postResult,
|
Posts: postResult,
|
||||||
|
Tags: tagObjs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,17 +90,15 @@ func postGetOne(c *gin.Context) {
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
var tagStrings []string
|
|
||||||
for _, tag := range post.Tags {
|
tagObjs := services.GetTagFilter(post.Tags)
|
||||||
tagStrings = append(tagStrings, tag.TagType.Name+":"+tag.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, models.PostReadModel{
|
c.JSON(http.StatusOK, models.PostReadModel{
|
||||||
ID: post.ID,
|
ID: post.ID,
|
||||||
ImagePreviewPath: "/data/" + post.Blob.PreviewFilePath,
|
ImagePreviewPath: "/data/" + post.Blob.PreviewFilePath,
|
||||||
ImagePath: "/data/" + post.Blob.FilePath,
|
ImagePath: "/data/" + post.Blob.FilePath,
|
||||||
SourceURL: post.SourceURL,
|
SourceURL: post.SourceURL,
|
||||||
Tags: tagStrings,
|
Tags: tagObjs,
|
||||||
Width: post.Blob.Width,
|
Width: post.Blob.Width,
|
||||||
Height: post.Blob.Height,
|
Height: post.Blob.Height,
|
||||||
Uploader: post.User.Username,
|
Uploader: post.User.Username,
|
||||||
|
|
|
@ -17,10 +17,9 @@ type TagAutocompleteListItem struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostListItem struct {
|
type PostListItem struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
ImagePath string `json:"image_path"`
|
ImagePath string `json:"image_path"`
|
||||||
ImageThumbnailPath string `json:"thumbnail_path"`
|
ImageThumbnailPath string `json:"thumbnail_path"`
|
||||||
Tags []string `json:"tags"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostSimilarityListItem struct {
|
type PostSimilarityListItem struct {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
type PostReadModel struct {
|
type PostReadModel struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
ImagePreviewPath string `json:"preview_path"`
|
ImagePreviewPath string `json:"preview_path"`
|
||||||
ImagePath string `json:"image_path"`
|
ImagePath string `json:"image_path"`
|
||||||
SourceURL string `json:"source_url"`
|
SourceURL string `json:"source_url"`
|
||||||
Tags []string `json:"tags"`
|
Tags []TagListItem `json:"tags"`
|
||||||
Width int `json:"width"`
|
Width int `json:"width"`
|
||||||
Height int `json:"height"`
|
Height int `json:"height"`
|
||||||
Uploader string `json:"uploader"`
|
Uploader string `json:"uploader"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ type BlobSimilarResponse struct {
|
||||||
}
|
}
|
||||||
type PostPaginationResponse struct {
|
type PostPaginationResponse struct {
|
||||||
CurrentPage int `json:"currentPage"`
|
CurrentPage int `json:"currentPage"`
|
||||||
|
TotalPage int `json:"totalPage"`
|
||||||
PostCount int `json:"postCount"`
|
PostCount int `json:"postCount"`
|
||||||
Posts []PostListItem `json:"posts"`
|
Posts []PostListItem `json:"posts"`
|
||||||
|
Tags []TagListItem `json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,27 @@ func GetTagAll() []models.TagListItem {
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTagFilterString(tagString []string) []models.TagListItem {
|
||||||
|
tagObjs, _ := ParseReadTags(tagString)
|
||||||
|
return GetTagFilter(tagObjs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTagFilter(tagObjs []database.Tag) []models.TagListItem {
|
||||||
|
var tagIds []string
|
||||||
|
for _, val := range tagObjs {
|
||||||
|
tagIds = append(tagIds, val.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tags []models.TagListItem
|
||||||
|
database.DB.Model(&tagObjs).
|
||||||
|
Joins("join tag_types on tag_types.id = tags.tag_type_id").
|
||||||
|
Joins("left join post_tags on post_tags.tag_id = tags.id").
|
||||||
|
Select("tags.id as tag_id, tags.name as tag_name, tag_types.name as tag_type, count(post_tags.post_id) as post_count").
|
||||||
|
Group("tags.id, tags.name, tag_types.name").
|
||||||
|
Order("post_count DESC").
|
||||||
|
Find(&tags, tagIds)
|
||||||
|
return tags
|
||||||
|
}
|
||||||
func GetTagAutocomplete() []string {
|
func GetTagAutocomplete() []string {
|
||||||
var tags []string
|
var tags []string
|
||||||
result := database.DB.Model(&database.Tag{}).
|
result := database.DB.Model(&database.Tag{}).
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script>
|
||||||
|
import { token } from "./stores.js";
|
||||||
|
|
||||||
|
let loggedIn = false;
|
||||||
|
token.subscribe((value) => {
|
||||||
|
loggedIn = value !== "";
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if loggedIn == true}
|
||||||
|
<slot />
|
||||||
|
{/if}
|
|
@ -0,0 +1,90 @@
|
||||||
|
<script>
|
||||||
|
import Tags from "svelte-tags-input";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { getPost, postUpdate, getTagAutocomplete } from "./api.js";
|
||||||
|
|
||||||
|
export let isActive = false;
|
||||||
|
export let post;
|
||||||
|
export let onSubmit;
|
||||||
|
|
||||||
|
const toggleEditModal = () => {
|
||||||
|
isActive = !isActive;
|
||||||
|
};
|
||||||
|
|
||||||
|
let form = {
|
||||||
|
source_url: "",
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = async () => {
|
||||||
|
form.source_url = post.source_url;
|
||||||
|
form.tags = post.tags;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTagChange = (value) => {
|
||||||
|
form.tags = value.detail.tags;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAutocomplete = async () => {
|
||||||
|
const list = await getTagAutocomplete();
|
||||||
|
return list;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFormSubmit = async () => {
|
||||||
|
const response = await postUpdate(post.id, form);
|
||||||
|
toggleEditModal();
|
||||||
|
|
||||||
|
onSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={onFormSubmit}>
|
||||||
|
<div class="panel is-warning">
|
||||||
|
<p class="panel-heading">Edit Post</p>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<label for="source" class="label">Source URL</label>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
id="source"
|
||||||
|
class="input"
|
||||||
|
type="url"
|
||||||
|
placeholder="Source URL"
|
||||||
|
bind:value={form.source_url}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<label for="tags" class="label">Tags</label>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control" id="tags">
|
||||||
|
<Tags
|
||||||
|
tags={form.tags}
|
||||||
|
addKeys={[9, 32]}
|
||||||
|
on:tags={onTagChange}
|
||||||
|
autoComplete={onAutocomplete}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<button class="button is-primary" type="submit">Save</button>
|
||||||
|
<button class="button" on:click|preventDefault={toggleEditModal}
|
||||||
|
>Cancel</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script>
|
||||||
|
import { Link } from "svelte-routing";
|
||||||
|
|
||||||
|
export let tag;
|
||||||
|
export let num;
|
||||||
|
|
||||||
|
let tagType = tag.split(":")[0] ?? "";
|
||||||
|
let tagName = tag.split(":")[1] ?? "";
|
||||||
|
let tagDisplay = tagName.split("_").join(" ");
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Link to="/posts?tags={tagName}">{tagDisplay} <span class="is-pulled-right">{num}</span></Link>
|
|
@ -0,0 +1,82 @@
|
||||||
|
<script>
|
||||||
|
import AuthCheck from "./AuthCheck.svelte";
|
||||||
|
import TagLinkNumbered from "./TagLinkNumbered.svelte";
|
||||||
|
export let post;
|
||||||
|
export let toggleEditMenu;
|
||||||
|
export let toggleDeleteMenu;
|
||||||
|
|
||||||
|
const trimUrl = (str) => {
|
||||||
|
if (str.length > 30) {
|
||||||
|
return str.substring(0, 30) + "...";
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="panel is-primary">
|
||||||
|
<p class="panel-heading">Post</p>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<strong>Uploader:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">{post.uploader}</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<strong>Source URL:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<a href={post.source_url}>{trimUrl(post.source_url)}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<strong>Original:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<a href={post.image_path}>Image</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<strong>Dimensions:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{post.width}x{post.height}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<p><strong>Tags:</strong></p>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="menu">
|
||||||
|
<ul class="menu-list">
|
||||||
|
{#if post.tags}
|
||||||
|
{#each post.tags as tag (tag)}
|
||||||
|
<li>
|
||||||
|
<TagLinkNumbered
|
||||||
|
class=""
|
||||||
|
tag={tag.tagType + ":" + tag.tagName}
|
||||||
|
num={tag.postCount}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<AuthCheck>
|
||||||
|
<p class="panel-block column">
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={toggleEditMenu}
|
||||||
|
class="button is-primary">Edit</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={toggleDeleteMenu}
|
||||||
|
class="button is-danger">Delete</button
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</AuthCheck>
|
||||||
|
</div>
|
|
@ -12,5 +12,5 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.svelte-tags-input-matchs-parent {
|
.svelte-tags-input-matchs-parent {
|
||||||
z-index: 200;
|
z-index: 2000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import TagLink from "../TagLink.svelte";
|
import { getPost, postDelete } from "../api.js";
|
||||||
import { getPost, postCreate, postDelete } from "../api.js";
|
import { navigate } from "svelte-routing";
|
||||||
import { Link, navigate } from "svelte-routing";
|
import EditPostPanel from "../EditPostPanel.svelte";
|
||||||
|
import ViewPostPanel from "../ViewPostPanel.svelte";
|
||||||
export let id;
|
export let id;
|
||||||
let post;
|
let post;
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
|
@ -21,70 +22,62 @@
|
||||||
getData();
|
getData();
|
||||||
});
|
});
|
||||||
|
|
||||||
let modal_shown = false;
|
let deleteMenuShown = false;
|
||||||
|
|
||||||
const deletePost = async () => {
|
const deletePost = async () => {
|
||||||
toggleModal();
|
toggleDeleteMenu();
|
||||||
const success = await postDelete({ id });
|
const success = await postDelete({ id });
|
||||||
if (success) {
|
if (success) {
|
||||||
navigate("/posts");
|
navigate("/posts");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const toggleModal = () => {
|
const toggleDeleteMenu = () => {
|
||||||
modal_shown = !modal_shown;
|
deleteMenuShown = !deleteMenuShown;
|
||||||
|
};
|
||||||
|
|
||||||
|
let editMenuShown = false;
|
||||||
|
|
||||||
|
const toggleEditMenu = () => {
|
||||||
|
editMenuShown = !editMenuShown;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="hero is-primary">
|
|
||||||
<div class="hero-body">
|
|
||||||
{#if post}
|
|
||||||
<p class="title">
|
|
||||||
Post ID: {post.id}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</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">
|
||||||
<div class="content">
|
{#if editMenuShown == false && deleteMenuShown == false}
|
||||||
<p>
|
<ViewPostPanel
|
||||||
<Link
|
{post}
|
||||||
class="button is-primary"
|
{toggleDeleteMenu}
|
||||||
to="/post/edit/{post.id}">Edit</Link
|
{toggleEditMenu}
|
||||||
>
|
/>
|
||||||
<button
|
{:else if editMenuShown == true}
|
||||||
on:click|preventDefault={toggleModal}
|
<EditPostPanel
|
||||||
class="button is-danger">Delete</button
|
bind:isActive={editMenuShown}
|
||||||
>
|
{post}
|
||||||
</p>
|
onSubmit={getData}
|
||||||
<p>
|
/>
|
||||||
Uploader: {post.uploader}
|
{:else if deleteMenuShown == true}
|
||||||
</p>
|
<div class="panel is-danger">
|
||||||
<p>
|
<p class="panel-heading">Delete Post</p>
|
||||||
Source URL: <a href={post.source_url}
|
<div class="panel-block">
|
||||||
>{trimUrl(post.source_url)}</a
|
Are you sure to delete post {post.id}?
|
||||||
>
|
</div>
|
||||||
</p>
|
<div class="panel-block column">
|
||||||
<p>
|
<button
|
||||||
Original: <a href={post.image_path}>Image</a>
|
on:click|preventDefault={deletePost}
|
||||||
</p>
|
class="button is-danger">Delete</button
|
||||||
<p>
|
>
|
||||||
Dimensions: {post.width}x{post.height}
|
<button
|
||||||
</p>
|
class="button"
|
||||||
<p>
|
on:click|preventDefault={toggleDeleteMenu}
|
||||||
Tags:<br />
|
>Cancel</button
|
||||||
</p>
|
>
|
||||||
<p>
|
</div>
|
||||||
{#if post.tags}
|
</div>
|
||||||
{#each post.tags as tag (tag)}
|
{/if}
|
||||||
<TagLink {tag} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="column box">
|
<div class="column box">
|
||||||
{#if post.width > 1000}
|
{#if post.width > 1000}
|
||||||
|
@ -105,26 +98,4 @@
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal" class:is-active={modal_shown}>
|
|
||||||
<div class="modal-background" />
|
|
||||||
<div class="modal-content">
|
|
||||||
Are you sure to delete post {post.id}?
|
|
||||||
</div>
|
|
||||||
<div class="modal-card">
|
|
||||||
<header class="modal-card-head">
|
|
||||||
<p class="modal-card-title">Delete post?</p>
|
|
||||||
<button class="delete" aria-label="close" />
|
|
||||||
</header>
|
|
||||||
<section class="modal-card-body" />
|
|
||||||
<footer class="modal-card-foot">
|
|
||||||
<button
|
|
||||||
on:click|preventDefault={deletePost}
|
|
||||||
class="button is-danger">Delete</button
|
|
||||||
>
|
|
||||||
<button class="button" on:click|preventDefault={toggleModal}
|
|
||||||
>Cancel</button
|
|
||||||
>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -3,48 +3,37 @@
|
||||||
import { getPostSearchTag, getTagAutocomplete } from "../api.js";
|
import { getPostSearchTag, getTagAutocomplete } from "../api.js";
|
||||||
import { Link, navigate } from "svelte-routing";
|
import { Link, navigate } from "svelte-routing";
|
||||||
import InfiniteScroll from "svelte-infinite-scroll";
|
import InfiniteScroll from "svelte-infinite-scroll";
|
||||||
import TagLink from "../TagLink.svelte";
|
import TagLinkNumbered from "../TagLinkNumbered.svelte";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import Tags from "svelte-tags-input";
|
import Tags from "svelte-tags-input";
|
||||||
import { add_attribute } from "svelte/internal";
|
import { add_attribute } from "svelte/internal";
|
||||||
|
import { paginate } from "../simple-pagination.js";
|
||||||
|
|
||||||
export let location;
|
export let location;
|
||||||
|
|
||||||
let searchTerms = [];
|
let searchTerms = [];
|
||||||
|
|
||||||
let page = 1;
|
let page = 1;
|
||||||
|
let totalPages = 1;
|
||||||
|
let pagination = [];
|
||||||
let posts = [];
|
let posts = [];
|
||||||
let newBatch = [];
|
let tags = [];
|
||||||
|
let categorizedTags = {};
|
||||||
const splitToChunks = (array, parts) => {
|
|
||||||
let result = [];
|
|
||||||
for (let i = 0; i < parts; i++) {
|
|
||||||
let currentColumn = [];
|
|
||||||
for (let j = i; j < array.length; j += parts) {
|
|
||||||
currentColumn.push(array[j]);
|
|
||||||
}
|
|
||||||
result.push(currentColumn);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
let postChunks = [];
|
|
||||||
// split posts into 4 columns
|
|
||||||
$: {
|
|
||||||
postChunks = splitToChunks(posts, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
const data = await getPostSearchTag({ page, q: searchTerms.join("+") });
|
const data = await getPostSearchTag({ page, q: searchTerms.join("+") });
|
||||||
if (data.posts) {
|
if (data.posts) {
|
||||||
newBatch = data.posts;
|
posts = data.posts;
|
||||||
|
tags = data.tags.sort((a, b) => b.postCount - a.postCount);
|
||||||
|
totalPages = data.totalPage;
|
||||||
|
pagination = paginate(page, totalPages);
|
||||||
} else {
|
} else {
|
||||||
newBatch = [];
|
posts = [];
|
||||||
|
tags = [];
|
||||||
|
totalPages = 0;
|
||||||
|
pagination = paginate(page, totalPages);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
$: {
|
|
||||||
posts = [...posts, ...newBatch];
|
|
||||||
}
|
|
||||||
let queryParams;
|
let queryParams;
|
||||||
|
|
||||||
const onTagChange = (value) => {
|
const onTagChange = (value) => {
|
||||||
|
@ -64,9 +53,9 @@
|
||||||
searchTerms = [];
|
searchTerms = [];
|
||||||
}
|
}
|
||||||
posts = [];
|
posts = [];
|
||||||
|
page = 1;
|
||||||
getData();
|
getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSearch = (i) => {
|
const onSearch = (i) => {
|
||||||
if (searchTerms.length > 0) {
|
if (searchTerms.length > 0) {
|
||||||
navigate(`/posts?tags=${searchTerms.join("+")}`);
|
navigate(`/posts?tags=${searchTerms.join("+")}`);
|
||||||
|
@ -74,13 +63,12 @@
|
||||||
navigate(`/posts`);
|
navigate(`/posts`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
|
||||||
|
|
||||||
<section class="hero is-primary">
|
const changePage = (i) => {
|
||||||
<div class="hero-body">
|
page = i;
|
||||||
<p class="title">Posts</p>
|
getData();
|
||||||
</div>
|
}
|
||||||
</section>
|
</script>
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -107,46 +95,101 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
{#each postChunks as postChunk}
|
<div class="column is-one-third">
|
||||||
<div class="column is-one-fifth">
|
<div class="panel is-primary">
|
||||||
{#each postChunk as post, i (post.id)}
|
<div class="panel-heading">Tags</div>
|
||||||
<div class="block">
|
<div class="panel-block column">
|
||||||
<div class="card">
|
<div class="menu">
|
||||||
<div class="card-image">
|
<ul class="menu-list">
|
||||||
<figure class="image">
|
{#each tags as tag (tag)}
|
||||||
<Link to="/post/{post.id}">
|
<li>
|
||||||
<img
|
<TagLinkNumbered
|
||||||
alt={post.id}
|
class=""
|
||||||
src={post.thumbnail_path}
|
tag={tag.tagType+":"+tag.tagName}
|
||||||
/>
|
num={tag.postCount}
|
||||||
</Link>
|
/>
|
||||||
</figure>
|
</li>
|
||||||
</div>
|
{/each}
|
||||||
<div class="card-content">
|
</ul>
|
||||||
{#if post.tags}
|
|
||||||
{#each post.tags as tag (tag)}
|
|
||||||
<TagLink {tag} />
|
|
||||||
{/each}
|
|
||||||
{:else}
|
|
||||||
<TagLink tag="tagme" />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
</div>
|
||||||
|
<div class="column is-two-thirds">
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
<div class="column is-full">
|
||||||
|
<nav
|
||||||
|
class="pagination is-centered"
|
||||||
|
role="navigation"
|
||||||
|
aria-label="pagination"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={null}
|
||||||
|
on:click={changePage(page - 1)}
|
||||||
|
class="pagination-previous">Previous</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={null}
|
||||||
|
on:click={changePage(page + 1)}
|
||||||
|
class="pagination-next">Next page</a
|
||||||
|
>
|
||||||
|
<ul class="pagination-list">
|
||||||
|
{#each pagination as pageEntry}
|
||||||
|
{#if pageEntry == "..."}
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
class="pagination-ellipsis"
|
||||||
|
>…</span
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{:else}
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href={null}
|
||||||
|
on:click={() =>
|
||||||
|
(changePage(pageEntry))}
|
||||||
|
class="pagination-link"
|
||||||
|
class:is-current={page ==
|
||||||
|
pageEntry}
|
||||||
|
aria-label="Goto page {pageEntry}"
|
||||||
|
>{pageEntry}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="column is-full">
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
{#each posts as post, i (post.id)}
|
||||||
|
<div class="column is-one-third">
|
||||||
|
<div class="block">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-image">
|
||||||
|
<figure class="image">
|
||||||
|
<Link
|
||||||
|
to="/post/{post.id}"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt={post.id}
|
||||||
|
src={post.thumbnail_path}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
<div class="card-content" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<InfiniteScroll
|
|
||||||
hasMore={newBatch.length}
|
|
||||||
elementScroll={document}
|
|
||||||
on:loadMore={() => {
|
|
||||||
page++;
|
|
||||||
getData();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{#if newBatch.length == 0}
|
{#if page >= totalPages}
|
||||||
<div class="notification is-primary">
|
<div class="notification is-primary">
|
||||||
<p class="has-text-centered">End of posts</p>
|
<p class="has-text-centered">End of posts</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,12 +14,6 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<section class="hero is-primary">
|
|
||||||
<div class="hero-body">
|
|
||||||
<p class="title">Tags</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<table class="table is-fullwidth">
|
<table class="table is-fullwidth">
|
||||||
|
|
|
@ -51,12 +51,6 @@
|
||||||
|
|
||||||
<AuthRequired />
|
<AuthRequired />
|
||||||
|
|
||||||
<section class="hero is-primary">
|
|
||||||
<div class="hero-body">
|
|
||||||
<p class="title">Upload</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<form on:submit|preventDefault={onSubmit}>
|
<form on:submit|preventDefault={onSubmit}>
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Implementation in ES6
|
||||||
|
// https://gist.github.com/kottenator/9d936eb3e4e3c3e02598
|
||||||
|
|
||||||
|
const paginate = (c, m) => {
|
||||||
|
let current = c,
|
||||||
|
last = m,
|
||||||
|
delta = 2,
|
||||||
|
left = current - delta,
|
||||||
|
right = current + delta + 1,
|
||||||
|
range = [],
|
||||||
|
rangeWithDots = [],
|
||||||
|
l;
|
||||||
|
|
||||||
|
for (let i = 1; i <= last; i++) {
|
||||||
|
if (i == 1 || i == last || i >= left && i < right) {
|
||||||
|
range.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i of range) {
|
||||||
|
if (l) {
|
||||||
|
if (i - l === 2) {
|
||||||
|
rangeWithDots.push(l + 1);
|
||||||
|
} else if (i - l !== 1) {
|
||||||
|
rangeWithDots.push('...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rangeWithDots.push(i);
|
||||||
|
l = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rangeWithDots;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { paginate };
|
||||||
|
/*
|
||||||
|
Test it:
|
||||||
|
|
||||||
|
for (let i = 1, l = 20; i <= l; i++)
|
||||||
|
console.log(`Selected page ${i}:`, pagination(i, l));
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
|
||||||
|
Selected page 1: [1, 2, 3, "...", 20]
|
||||||
|
Selected page 2: [1, 2, 3, 4, "...", 20]
|
||||||
|
Selected page 3: [1, 2, 3, 4, 5, "...", 20]
|
||||||
|
Selected page 4: [1, 2, 3, 4, 5, 6, "...", 20]
|
||||||
|
Selected page 5: [1, 2, 3, 4, 5, 6, 7, "...", 20]
|
||||||
|
Selected page 6: [1, "...", 4, 5, 6, 7, 8, "...", 20]
|
||||||
|
Selected page 7: [1, "...", 5, 6, 7, 8, 9, "...", 20]
|
||||||
|
Selected page 8: [1, "...", 6, 7, 8, 9, 10, "...", 20]
|
||||||
|
Selected page 9: [1, "...", 7, 8, 9, 10, 11, "...", 20]
|
||||||
|
Selected page 10: [1, "...", 8, 9, 10, 11, 12, "...", 20]
|
||||||
|
Selected page 11: [1, "...", 9, 10, 11, 12, 13, "...", 20]
|
||||||
|
Selected page 12: [1, "...", 10, 11, 12, 13, 14, "...", 20]
|
||||||
|
Selected page 13: [1, "...", 11, 12, 13, 14, 15, "...", 20]
|
||||||
|
Selected page 14: [1, "...", 12, 13, 14, 15, 16, "...", 20]
|
||||||
|
Selected page 15: [1, "...", 13, 14, 15, 16, 17, "...", 20]
|
||||||
|
Selected page 16: [1, "...", 14, 15, 16, 17, 18, 19, 20]
|
||||||
|
Selected page 17: [1, "...", 15, 16, 17, 18, 19, 20]
|
||||||
|
Selected page 18: [1, "...", 16, 17, 18, 19, 20]
|
||||||
|
Selected page 19: [1, "...", 17, 18, 19, 20]
|
||||||
|
Selected page 20: [1, "...", 18, 19, 20]
|
||||||
|
*/
|
Loading…
Reference in New Issue