mirror of
https://github.com/Damillora/Shioriko.git
synced 2024-11-24 05:17:33 +00:00
feat: add related tags
This commit is contained in:
parent
e175d12ca7
commit
ea5ba769df
@ -19,6 +19,10 @@ func InitializeTagRoutes(g *gin.Engine) {
|
|||||||
unprotected.GET("/", tagGet)
|
unprotected.GET("/", tagGet)
|
||||||
unprotected.GET("/:tag", tagGetOne)
|
unprotected.GET("/:tag", tagGetOne)
|
||||||
}
|
}
|
||||||
|
related := g.Group("/api/tag-related")
|
||||||
|
{
|
||||||
|
related.GET("/:tag", tagGetRelated)
|
||||||
|
}
|
||||||
protected := g.Group("/api/tag").Use(middleware.AuthMiddleware())
|
protected := g.Group("/api/tag").Use(middleware.AuthMiddleware())
|
||||||
{
|
{
|
||||||
protected.PUT("/:tag/note", tagUpdateNote)
|
protected.PUT("/:tag/note", tagUpdateNote)
|
||||||
@ -51,6 +55,20 @@ func tagGetOne(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tagGetRelated(c *gin.Context) {
|
||||||
|
tag := c.Param("tag")
|
||||||
|
tagObj, err := services.GetRelatedTags(tag)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, tagObj)
|
||||||
|
}
|
||||||
|
|
||||||
func tagAutocomplete(c *gin.Context) {
|
func tagAutocomplete(c *gin.Context) {
|
||||||
tags := services.GetTagAutocomplete()
|
tags := services.GetTagAutocomplete()
|
||||||
c.JSON(http.StatusOK, tags)
|
c.JSON(http.StatusOK, tags)
|
||||||
|
@ -2,6 +2,7 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Damillora/Shioriko/pkg/database"
|
"github.com/Damillora/Shioriko/pkg/database"
|
||||||
@ -225,3 +226,41 @@ func UpdateTag(tagString string, model models.TagUpdateModel) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetRelatedTags(tagSyntax string) ([]models.TagListItem, error) {
|
||||||
|
tags, err := FindTag(tagSyntax)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var postIds []string
|
||||||
|
database.DB.
|
||||||
|
Model(&tags).
|
||||||
|
Joins("join post_tags on post_tags.tag_id = tags.id").
|
||||||
|
Select("post_tags.post_id").
|
||||||
|
Where("post_tags.tag_id = ?", tags.ID).
|
||||||
|
Find(&postIds)
|
||||||
|
|
||||||
|
var tagIds []string
|
||||||
|
database.DB.
|
||||||
|
Model(&tags).
|
||||||
|
Joins("join post_tags on post_tags.tag_id = tags.id").
|
||||||
|
Select("post_tags.tag_id").
|
||||||
|
Where("post_tags.post_id IN ?", postIds).
|
||||||
|
Find(&tagIds)
|
||||||
|
fmt.Printf("%+v", tags)
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("%v", tagIds)
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
var tagInfo []models.TagListItem
|
||||||
|
database.DB.Model(&tags).
|
||||||
|
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(&tagInfo, tagIds)
|
||||||
|
|
||||||
|
return tagInfo, nil
|
||||||
|
}
|
||||||
|
@ -48,6 +48,11 @@ export async function getTag({ tag }) {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getRelatedTags({ tag }) {
|
||||||
|
const endpoint = url + "/api/tag-related/" + tag;
|
||||||
|
const response = await axios.get(endpoint);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
export async function getTagAutocomplete() {
|
export async function getTagAutocomplete() {
|
||||||
const endpoint = url + "/api/tag-autocomplete";
|
const endpoint = url + "/api/tag-autocomplete";
|
||||||
const response = await axios.get(endpoint);
|
const response = await axios.get(endpoint);
|
||||||
|
24
web/app/src/components/Post/PostGallery.svelte
Normal file
24
web/app/src/components/Post/PostGallery.svelte
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { Link } from "svelte-routing";
|
||||||
|
|
||||||
|
export let posts = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
{#each posts as post, i (post.id)}
|
||||||
|
<div class="column is-one-quarter">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
@ -1,10 +1,24 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
import { Link } from "svelte-routing";
|
import { Link } from "svelte-routing";
|
||||||
|
import { getRelatedTags } from "../../api.js";
|
||||||
import AuthCheck from "../../AuthCheck.svelte";
|
import AuthCheck from "../../AuthCheck.svelte";
|
||||||
|
import TagLinkNumbered from "../../TagLinkNumbered.svelte";
|
||||||
|
|
||||||
export let tag;
|
export let tag;
|
||||||
export let data;
|
export let data;
|
||||||
export let toggleRenameMenu;
|
export let toggleRenameMenu;
|
||||||
|
let related_tags = [];
|
||||||
|
const getData = async () => {
|
||||||
|
related_tags = await getRelatedTags({ tag });
|
||||||
|
related_tags = related_tags
|
||||||
|
.filter((x) => x.tagName != tag)
|
||||||
|
.sort((a, b) => b.postCount - a.postCount);
|
||||||
|
};
|
||||||
|
onMount(() => {
|
||||||
|
getData();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="panel is-primary">
|
<div class="panel is-primary">
|
||||||
@ -29,6 +43,26 @@
|
|||||||
{data.postCount} (<Link to="/posts?tags={tag}">Browse</Link>)
|
{data.postCount} (<Link to="/posts?tags={tag}">Browse</Link>)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel-block column">
|
||||||
|
<div class="row">
|
||||||
|
<strong>Related Tags:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="menu">
|
||||||
|
<ul class="menu-list">
|
||||||
|
{#each related_tags as tag (tag)}
|
||||||
|
<li>
|
||||||
|
<TagLinkNumbered
|
||||||
|
class=""
|
||||||
|
tag={tag.tagType + ":" + tag.tagName}
|
||||||
|
num={tag.postCount}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<AuthCheck>
|
<AuthCheck>
|
||||||
<div class="panel-block column">
|
<div class="panel-block column">
|
||||||
<button
|
<button
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
@import '../node_modules/bulma/sass/utilities/_all';
|
@import "../node_modules/bulma/sass/utilities/_all";
|
||||||
@import '../node_modules/bulma/sass/base/_all';
|
@import "../node_modules/bulma/sass/base/_all";
|
||||||
@import '../node_modules/bulma/sass/elements/_all';
|
@import "../node_modules/bulma/sass/elements/_all";
|
||||||
@import '../node_modules/bulma/sass/form/_all';
|
@import "../node_modules/bulma/sass/form/_all";
|
||||||
@import '../node_modules/bulma/sass/components/_all';
|
@import "../node_modules/bulma/sass/components/_all";
|
||||||
@import '../node_modules/bulma/sass/grid/_all';
|
@import "../node_modules/bulma/sass/grid/_all";
|
||||||
@import '../node_modules/bulma/sass/helpers/_all';
|
@import "../node_modules/bulma/sass/helpers/_all";
|
||||||
@import '../node_modules/bulma/sass/layout/_all';
|
@import "../node_modules/bulma/sass/layout/_all";
|
||||||
@import '../node_modules/bytemd/dist/index.css';
|
@import "../node_modules/bytemd/dist/index.css";
|
||||||
|
|
||||||
.tile.is-multiline {
|
.tile.is-multiline {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pre-line {
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
.svelte-tags-input-matchs-parent {
|
.svelte-tags-input-matchs-parent {
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getPostSearchTag, getTagAutocomplete } from "../../api.js";
|
import { getPostSearchTag, getTag, getTagAutocomplete } from "../../api.js";
|
||||||
import { Link, navigate } from "svelte-routing";
|
import { Link, navigate } from "svelte-routing";
|
||||||
import TagLinkNumbered from "../../TagLinkNumbered.svelte";
|
import TagLinkNumbered from "../../TagLinkNumbered.svelte";
|
||||||
|
import PostGallery from "../../components/Post/PostGallery.svelte";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import Tags from "svelte-tags-input";
|
import Tags from "svelte-tags-input";
|
||||||
import { paginate } from "../../simple-pagination.js";
|
import { paginate } from "../../simple-pagination.js";
|
||||||
@ -16,6 +17,7 @@
|
|||||||
let posts = [];
|
let posts = [];
|
||||||
let postCount = 0;
|
let postCount = 0;
|
||||||
let tags = [];
|
let tags = [];
|
||||||
|
let tagInfo = null;
|
||||||
let categorizedTags = {};
|
let categorizedTags = {};
|
||||||
|
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
@ -33,6 +35,10 @@
|
|||||||
postCount = 0;
|
postCount = 0;
|
||||||
pagination = paginate(page, totalPages);
|
pagination = paginate(page, totalPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (searchTerms.length == 1) {
|
||||||
|
tagInfo = await getTag({ tag: searchTerms[0] });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let queryParams;
|
let queryParams;
|
||||||
|
|
||||||
@ -51,6 +57,7 @@
|
|||||||
searchTerms = queryParams.tags.split(" ");
|
searchTerms = queryParams.tags.split(" ");
|
||||||
} else {
|
} else {
|
||||||
searchTerms = [];
|
searchTerms = [];
|
||||||
|
tagInfo = null;
|
||||||
}
|
}
|
||||||
posts = [];
|
posts = [];
|
||||||
page = 1;
|
page = 1;
|
||||||
@ -103,6 +110,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-one-third">
|
<div class="column is-one-third">
|
||||||
|
{#if tagInfo}
|
||||||
|
<div class="panel is-info">
|
||||||
|
<p class="panel-heading">
|
||||||
|
Tag:
|
||||||
|
{tagInfo.tagName.split("_").join(" ")}
|
||||||
|
</p>
|
||||||
|
{#if tagInfo.tagNote}
|
||||||
|
<div class="panel-block column ">
|
||||||
|
<div class="content pre-line">
|
||||||
|
{tagInfo.tagNote}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="panel-block column">
|
||||||
|
<Link
|
||||||
|
class="button is-primary"
|
||||||
|
to="/tags/{tagInfo.tagName}">View Tag</Link
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="panel is-primary">
|
<div class="panel is-primary">
|
||||||
<div class="panel-heading">Tags</div>
|
<div class="panel-heading">Tags</div>
|
||||||
<div class="panel-block column">
|
<div class="panel-block column">
|
||||||
@ -127,28 +155,7 @@
|
|||||||
<div class="column is-two-thirds">
|
<div class="column is-two-thirds">
|
||||||
<div class="columns is-multiline">
|
<div class="columns is-multiline">
|
||||||
<div class="column is-full">
|
<div class="column is-full">
|
||||||
<div class="columns is-multiline">
|
<PostGallery {posts} />
|
||||||
{#each posts as post, i (post.id)}
|
|
||||||
<div class="column is-one-quarter">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column is-full">
|
<div class="column is-full">
|
||||||
|
@ -1,18 +1,29 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
import { getTag } from "../../api";
|
import { getTag, getPostSearchTag } from "../../api";
|
||||||
import EditTagNotesPanel from "../../components/TagNotes/EditTagNotesPanel.svelte";
|
import EditTagNotesPanel from "../../components/TagNotes/EditTagNotesPanel.svelte";
|
||||||
import ViewTagNotesPanel from "../../components/TagNotes/ViewTagNotesPanel.svelte";
|
import ViewTagNotesPanel from "../../components/TagNotes/ViewTagNotesPanel.svelte";
|
||||||
import ViewTagPanel from "../../components/Tag/ViewTagPanel.svelte";
|
import ViewTagPanel from "../../components/Tag/ViewTagPanel.svelte";
|
||||||
import EditTagPanel from "../../components/Tag/EditTagPanel.svelte";
|
import EditTagPanel from "../../components/Tag/EditTagPanel.svelte";
|
||||||
|
import PostGallery from "../../components/Post/PostGallery.svelte";
|
||||||
|
|
||||||
|
import { Link } from "svelte-routing";
|
||||||
|
|
||||||
export let tag;
|
export let tag;
|
||||||
let data;
|
let data;
|
||||||
|
let posts = [];
|
||||||
|
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
data = await getTag({ tag });
|
data = await getTag({ tag });
|
||||||
|
const response = await getPostSearchTag({
|
||||||
|
page: 1,
|
||||||
|
q: tag,
|
||||||
|
});
|
||||||
|
if (response.posts) {
|
||||||
|
posts = response.posts.slice(0, 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -63,6 +74,8 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<ViewTagNotesPanel {data} {toggleEditMenu} />
|
<ViewTagNotesPanel {data} {toggleEditMenu} />
|
||||||
{/if}
|
{/if}
|
||||||
|
<h1 class="title">Posts</h1>
|
||||||
|
<PostGallery {posts} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
Loading…
Reference in New Issue
Block a user