Compare commits

...

6 Commits

12 changed files with 87 additions and 26 deletions

View File

@ -1 +1,2 @@
Dockerfile Dockerfile
web/app/node_modules

View File

@ -3,7 +3,6 @@ package app
import ( import (
"net/http" "net/http"
"github.com/Damillora/Shioriko/pkg/models"
"github.com/Damillora/Shioriko/pkg/services" "github.com/Damillora/Shioriko/pkg/services"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -12,18 +11,16 @@ func InitializeTagRoutes(g *gin.Engine) {
unprotected := g.Group("/api/tag") unprotected := g.Group("/api/tag")
{ {
unprotected.GET("/", tagGet) unprotected.GET("/", tagGet)
unprotected.GET("/autocomplete", tagAutocomplete)
} }
} }
func tagGet(c *gin.Context) { func tagGet(c *gin.Context) {
tags := services.GetTagAll() tags := services.GetTagAll()
var tagResult []models.TagListItem c.JSON(http.StatusOK, tags)
for _, tag := range tags { }
tagResult = append(tagResult, models.TagListItem{
ID: tag.ID, func tagAutocomplete(c *gin.Context) {
Name: tag.Name, tags := services.GetTagAutocomplete()
TagType: tag.TagType.Name, c.JSON(http.StatusOK, tags)
})
}
c.JSON(http.StatusOK, tagResult)
} }

View File

@ -6,9 +6,14 @@ type TagTypeListItem struct {
} }
type TagListItem struct { type TagListItem struct {
ID string `json:"id"` TagID string `json:"tagId"`
Name string `json:"name"` TagName string `json:"tagName"`
TagType string `json:"tagType"` TagType string `json:"tagType"`
PostCount int `json:"postCount"`
}
type TagAutocompleteListItem struct {
Name string `json:"name"`
} }
type PostListItem struct { type PostListItem struct {

View File

@ -5,12 +5,31 @@ import (
"strings" "strings"
"github.com/Damillora/Shioriko/pkg/database" "github.com/Damillora/Shioriko/pkg/database"
"github.com/Damillora/Shioriko/pkg/models"
"github.com/google/uuid" "github.com/google/uuid"
) )
func GetTagAll() []database.Tag { func GetTagAll() []models.TagListItem {
var tags []database.Tag var tags []models.TagListItem
database.DB.Joins("TagType").Find(&tags) database.DB.Model(&database.Tag{}).
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)
return tags
}
func GetTagAutocomplete() []string {
var tags []string
result := database.DB.Model(&database.Tag{}).
Joins("join tag_types on tag_types.id = tags.tag_type_id").
Select("concat(tag_types.name,':',tags.name) as name").
Find(&tags)
if result.Error != nil {
return []string{}
}
return tags return tags
} }

View File

@ -49,4 +49,13 @@
font-size: 13.3333px; font-size: 13.3333px;
} }
} }
#tags .svelte-tags-input-matchs {
z-index: 200;
&-parent {
z-index: 200;
}
& li:hover {
background: $primary;
}
}
</style> </style>

View File

@ -35,6 +35,7 @@
<div class="navbar-menu" class:is-active={menu_shown}> <div class="navbar-menu" class:is-active={menu_shown}>
<div class="navbar-start"> <div class="navbar-start">
<Link class="navbar-item" to="/posts">Posts</Link> <Link class="navbar-item" to="/posts">Posts</Link>
<Link class="navbar-item" to="/tags">Tags</Link>
{#if loggedIn} {#if loggedIn}
<Link class="navbar-item" to="/upload">Upload</Link> <Link class="navbar-item" to="/upload">Upload</Link>
{/if} {/if}

View File

@ -41,6 +41,11 @@ export async function getTags() {
const response = await axios.get(endpoint); const response = await axios.get(endpoint);
return response.data; return response.data;
} }
export async function getTagAutocomplete() {
const endpoint = url + "/api/tag/autocomplete";
const response = await axios.get(endpoint);
return response.data;
}
export async function getPosts({ page }) { export async function getPosts({ page }) {
const endpoint = url + "/api/post?page=" + page; const endpoint = url + "/api/post?page=" + page;
const response = await axios.get(endpoint); const response = await axios.get(endpoint);

View File

@ -10,4 +10,7 @@
.tile.is-multiline { .tile.is-multiline {
flex-wrap: wrap; flex-wrap: wrap;
} }
.svelte-tags-input-matchs-parent {
z-index: 200;
}

View File

@ -1,5 +1,5 @@
<script> <script>
import { getPost, postUpdate } from "../api.js"; import { getPost, postUpdate, getTagAutocomplete } from "../api.js";
import { navigate } from "svelte-routing"; import { navigate } from "svelte-routing";
import Tags from "svelte-tags-input"; import Tags from "svelte-tags-input";
import { onMount } from "svelte"; import { onMount } from "svelte";
@ -26,6 +26,11 @@
form.tags = value.detail.tags; form.tags = value.detail.tags;
}; };
const onAutocomplete = async () => {
const list = await getTagAutocomplete();
return list;
};
const onSubmit = async () => { const onSubmit = async () => {
const response = await postUpdate(id, form); const response = await postUpdate(id, form);
navigate(`/post/${response.id}`); navigate(`/post/${response.id}`);
@ -71,6 +76,7 @@
tags={form.tags} tags={form.tags}
addKeys={[9, 32]} addKeys={[9, 32]}
on:tags={onTagChange} on:tags={onTagChange}
autoComplete={onAutocomplete}
/> />
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
<script> <script>
import { onMount } from "svelte"; import { onMount } from "svelte";
import { getPostSearchTag } 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 TagLink from "../TagLink.svelte";
@ -51,6 +51,11 @@
searchTerms = value.detail.tags; searchTerms = value.detail.tags;
}; };
const onAutocomplete = async () => {
const list = await getTagAutocomplete();
return list;
};
$: { $: {
queryParams = queryString.parse(location.search); queryParams = queryString.parse(location.search);
if (queryParams.tags) { if (queryParams.tags) {
@ -88,6 +93,7 @@
tags={searchTerms} tags={searchTerms}
addKeys={[9, 32]} addKeys={[9, 32]}
on:tags={onTagChange} on:tags={onTagChange}
autoComplete={onAutocomplete}
/> />
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
<script> <script>
import { getTags } from "../api"; import { getTags } from "../api";
import { Link } from "svelte-routing";
let tags = []; let tags = [];
@ -25,15 +25,19 @@ import { getTags } from "../api";
<table class="table is-fullwidth"> <table class="table is-fullwidth">
<thead> <thead>
<tr> <tr>
<th>Tag</th> <th >Tag</th>
<th>Tag Type</th> <th style="width: 30%;">Tag Type</th>
<th style="width: 10%;">Post Count</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each tags as tag} {#each tags as tag}
<tr> <tr>
<td>{tag.name}</td> <td>
<Link to="/posts?tags={tag.tagType}:{tag.tagName}">{tag.tagName}</Link>
</td>
<td>{tag.tagType}</td> <td>{tag.tagType}</td>
<td>{tag.postCount}</td>
</tr> </tr>
{/each} {/each}
</tbody> </tbody>

View File

@ -1,5 +1,5 @@
<script> <script>
import { uploadBlob, postCreate } from "../api.js"; import { uploadBlob, postCreate, getTagAutocomplete } from "../api.js";
import { navigate, Link } from "svelte-routing"; import { navigate, Link } from "svelte-routing";
import Tags from "svelte-tags-input"; import Tags from "svelte-tags-input";
import AuthRequired from "../AuthRequired.svelte"; import AuthRequired from "../AuthRequired.svelte";
@ -38,6 +38,11 @@
form.tags = value.detail.tags; form.tags = value.detail.tags;
}; };
const onAutocomplete = async () => {
const list = await getTagAutocomplete();
return list;
};
const onSubmit = async () => { const onSubmit = async () => {
const response = await postCreate(form); const response = await postCreate(form);
navigate(`/post/${response.id}`); navigate(`/post/${response.id}`);
@ -107,7 +112,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 addKeys={[9, 32]} on:tags={onTagChange} /> <Tags addKeys={[9, 32]} on:tags={onTagChange} autoComplete={onAutocomplete} />
</div> </div>
</div> </div>
<div class="control"> <div class="control">