mirror of
https://github.com/Damillora/Shioriko.git
synced 2024-11-21 20:07:33 +00:00
feat: negative tag search
This commit is contained in:
parent
a0868e15a1
commit
a1da324f56
@ -70,7 +70,10 @@ func tagGetRelated(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func tagAutocomplete(c *gin.Context) {
|
func tagAutocomplete(c *gin.Context) {
|
||||||
tags := services.GetTagAutocomplete()
|
tagParam := c.Query("tag")
|
||||||
|
positiveParam := c.Query("positive")
|
||||||
|
|
||||||
|
tags := services.GetTagAutocomplete(tagParam, positiveParam != "")
|
||||||
c.JSON(http.StatusOK, tags)
|
c.JSON(http.StatusOK, tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/Damillora/Shioriko/pkg/database"
|
"github.com/Damillora/Shioriko/pkg/database"
|
||||||
"github.com/Damillora/Shioriko/pkg/models"
|
"github.com/Damillora/Shioriko/pkg/models"
|
||||||
@ -17,36 +18,74 @@ func GetPostAll(page int) []database.Post {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetPostTags(page int, tagSyntax []string) []database.Post {
|
func GetPostTags(page int, tagSyntax []string) []database.Post {
|
||||||
tags, err := ParseReadTags(tagSyntax)
|
positiveTagSyntax := []string{}
|
||||||
|
negativeTagSyntax := []string{}
|
||||||
|
|
||||||
|
for _, tag := range tagSyntax {
|
||||||
|
if strings.HasPrefix(tag, "-") {
|
||||||
|
negativeTagSyntax = append(negativeTagSyntax, strings.TrimPrefix(tag, "-"))
|
||||||
|
} else {
|
||||||
|
positiveTagSyntax = append(positiveTagSyntax, tag)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
positiveTags, err := ParseReadTags(positiveTagSyntax)
|
||||||
|
if err != nil {
|
||||||
|
return []database.Post{}
|
||||||
|
}
|
||||||
|
negativeTags, err := ParseReadTags(negativeTagSyntax)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []database.Post{}
|
return []database.Post{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tagIds []string
|
var positiveTagIds []string
|
||||||
for _, tag := range tags {
|
for _, tag := range positiveTags {
|
||||||
tagIds = append(tagIds, tag.ID)
|
positiveTagIds = append(positiveTagIds, tag.ID)
|
||||||
}
|
}
|
||||||
fmt.Printf("%v", tagIds)
|
|
||||||
|
|
||||||
var postIds []string
|
var negativeTagIds []string
|
||||||
|
for _, tag := range negativeTags {
|
||||||
|
negativeTagIds = append(negativeTagIds, tag.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var positivePostIds []string
|
||||||
database.DB.
|
database.DB.
|
||||||
Model(&tags).
|
Model(&positiveTags).
|
||||||
Joins("join post_tags on post_tags.tag_id = tags.id").
|
Joins("join post_tags on post_tags.tag_id = tags.id").
|
||||||
Select("post_tags.post_id").
|
Select("post_tags.post_id").
|
||||||
Where("post_tags.tag_id IN ?", tagIds).
|
Where("post_tags.tag_id IN ?", positiveTagIds).
|
||||||
Group("post_tags.post_id").
|
Group("post_tags.post_id").
|
||||||
Having("count(*) = ?", len(tagIds)).
|
Having("count(*) = ?", len(positiveTagIds)).
|
||||||
Distinct().
|
Distinct().
|
||||||
Find(&postIds)
|
Find(&positivePostIds)
|
||||||
|
var negativePostIds []string
|
||||||
|
database.DB.
|
||||||
|
Model(&positiveTags).
|
||||||
|
Joins("join post_tags on post_tags.tag_id = tags.id").
|
||||||
|
Select("post_tags.post_id").
|
||||||
|
Where("post_tags.tag_id IN ?", negativeTagIds).
|
||||||
|
Group("post_tags.post_id").
|
||||||
|
Having("count(*) = ?", len(negativeTagIds)).
|
||||||
|
Distinct().
|
||||||
|
Find(&negativePostIds)
|
||||||
|
|
||||||
var posts []database.Post
|
var posts []database.Post
|
||||||
database.DB.
|
query := database.DB.
|
||||||
Joins("Blob").
|
Joins("Blob").
|
||||||
Preload("Tags").
|
Preload("Tags").
|
||||||
Preload("Tags.TagType").
|
Preload("Tags.TagType")
|
||||||
Where("posts.id IN ?", postIds).
|
|
||||||
Order("created_at desc").
|
if len(positivePostIds) > 0 && len(negativePostIds) > 0 {
|
||||||
|
query = query.
|
||||||
|
Where("posts.id IN ? AND posts.id NOT IN ?", positivePostIds, negativePostIds)
|
||||||
|
} else if len(positivePostIds) > 0 {
|
||||||
|
query = query.
|
||||||
|
Where("posts.id IN ?", positivePostIds)
|
||||||
|
} else if len(negativePostIds) > 0 {
|
||||||
|
query = query.
|
||||||
|
Where("posts.id NOT IN ?", negativePostIds)
|
||||||
|
}
|
||||||
|
query.Order("created_at desc").
|
||||||
Offset((page - 1) * perPage).
|
Offset((page - 1) * perPage).
|
||||||
Limit(20).
|
Limit(20).
|
||||||
Find(&posts)
|
Find(&posts)
|
||||||
@ -118,28 +157,73 @@ func CountPostPages() int {
|
|||||||
return int(count)
|
return int(count)
|
||||||
}
|
}
|
||||||
func CountPostPagesTag(tagSyntax []string) int {
|
func CountPostPagesTag(tagSyntax []string) int {
|
||||||
tags, err := ParseReadTags(tagSyntax)
|
positiveTagSyntax := []string{}
|
||||||
|
negativeTagSyntax := []string{}
|
||||||
|
|
||||||
|
for _, tag := range tagSyntax {
|
||||||
|
if strings.HasPrefix(tag, "-") {
|
||||||
|
negativeTagSyntax = append(negativeTagSyntax, strings.TrimPrefix(tag, "-"))
|
||||||
|
} else {
|
||||||
|
positiveTagSyntax = append(positiveTagSyntax, tag)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
positiveTags, err := ParseReadTags(positiveTagSyntax)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
negativeTags, err := ParseReadTags(negativeTagSyntax)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var tagIds []string
|
var positiveTagIds []string
|
||||||
for _, tag := range tags {
|
for _, tag := range positiveTags {
|
||||||
tagIds = append(tagIds, tag.ID)
|
positiveTagIds = append(positiveTagIds, tag.ID)
|
||||||
}
|
}
|
||||||
fmt.Printf("%v", tagIds)
|
|
||||||
|
|
||||||
var count int64
|
var negativeTagIds []string
|
||||||
|
for _, tag := range negativeTags {
|
||||||
|
negativeTagIds = append(negativeTagIds, tag.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var positivePostIds []string
|
||||||
database.DB.
|
database.DB.
|
||||||
Model(&tags).
|
Model(&positiveTags).
|
||||||
Distinct().
|
|
||||||
Joins("join post_tags on post_tags.tag_id = tags.id").
|
Joins("join post_tags on post_tags.tag_id = tags.id").
|
||||||
Select("post_tags.post_id").
|
Select("post_tags.post_id").
|
||||||
Where("post_tags.tag_id IN ?", tagIds).
|
Where("post_tags.tag_id IN ?", positiveTagIds).
|
||||||
Group("post_tags.post_id").
|
Group("post_tags.post_id").
|
||||||
Having("count(*) = ?", len(tagIds)).
|
Having("count(*) = ?", len(positiveTagIds)).
|
||||||
Count(&count)
|
Distinct().
|
||||||
|
Find(&positivePostIds)
|
||||||
|
var negativePostIds []string
|
||||||
|
database.DB.
|
||||||
|
Model(&positiveTags).
|
||||||
|
Joins("join post_tags on post_tags.tag_id = tags.id").
|
||||||
|
Select("post_tags.post_id").
|
||||||
|
Where("post_tags.tag_id IN ?", negativeTagIds).
|
||||||
|
Group("post_tags.post_id").
|
||||||
|
Having("count(*) = ?", len(negativeTagIds)).
|
||||||
|
Distinct().
|
||||||
|
Find(&negativePostIds)
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
query := database.DB.
|
||||||
|
Model(&database.Post{})
|
||||||
|
if len(positivePostIds) > 0 && len(negativePostIds) > 0 {
|
||||||
|
query = query.
|
||||||
|
Where("posts.id IN ? AND posts.id NOT IN ?", positivePostIds, negativePostIds)
|
||||||
|
} else if len(positivePostIds) > 0 {
|
||||||
|
query = query.
|
||||||
|
Where("posts.id IN ?", positivePostIds)
|
||||||
|
} else if len(negativePostIds) > 0 {
|
||||||
|
query = query.
|
||||||
|
Where("posts.id NOT IN ?", negativePostIds)
|
||||||
|
}
|
||||||
|
query.
|
||||||
|
Count(&count)
|
||||||
|
fmt.Println(count)
|
||||||
return int(count)
|
return int(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,12 +61,56 @@ func GetTag(tagString string) (*models.TagReadModel, error) {
|
|||||||
return &tagModel, nil
|
return &tagModel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTagAutocomplete() []string {
|
func GetTagAutocomplete(searchValue string, forcePositive bool) []string {
|
||||||
|
if forcePositive {
|
||||||
|
return getPositiveTagAutocomplete(strings.TrimPrefix(searchValue, "-"))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(searchValue, "-") {
|
||||||
|
return getNegativeTagAutocomplete(strings.TrimPrefix(searchValue, "-"))
|
||||||
|
} else {
|
||||||
|
return getPositiveTagAutocomplete(searchValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPositiveTagAutocomplete(searchValue string) []string {
|
||||||
var tags []string
|
var tags []string
|
||||||
result := database.DB.Model(&database.Tag{}).
|
query := database.DB.Model(&database.Tag{}).
|
||||||
Joins("join tag_types on tag_types.id = tags.tag_type_id").
|
Joins("join tag_types on tag_types.id = tags.tag_type_id").
|
||||||
Select("concat(tag_types.name,':',tags.name) as name").
|
Select("concat(tag_types.name,':',tags.name) as name")
|
||||||
Find(&tags)
|
|
||||||
|
tagFields := strings.Split(searchValue, ":")
|
||||||
|
if len(tagFields) == 2 {
|
||||||
|
query = query.
|
||||||
|
Where("tags.name LIKE ?", "%"+tagFields[1]+"%").
|
||||||
|
Where("tag_types.name = ?", tagFields[0])
|
||||||
|
} else if len(tagFields) == 1 {
|
||||||
|
query = query.
|
||||||
|
Where("tags.name LIKE ?", "%"+tagFields[0]+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := query.Find(&tags)
|
||||||
|
if result.Error != nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
func getNegativeTagAutocomplete(searchValue string) []string {
|
||||||
|
var tags []string
|
||||||
|
query := 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")
|
||||||
|
|
||||||
|
tagFields := strings.Split(searchValue, ":")
|
||||||
|
if len(tagFields) == 2 {
|
||||||
|
query = query.
|
||||||
|
Where("tags.name LIKE ?", "%"+tagFields[1]+"%").
|
||||||
|
Where("tag_types.name = ?", tagFields[0])
|
||||||
|
} else if len(tagFields) == 1 {
|
||||||
|
query = query.
|
||||||
|
Where("tags.name LIKE ?", "%"+tagFields[0]+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := query.Find(&tags)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
@ -18,16 +18,13 @@
|
|||||||
"svelte": "^3.0.0"
|
"svelte": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@svelte-parts/editor": "^0.0.26",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
"@tiptap/core": "^2.0.0-beta.174",
|
|
||||||
"@tiptap/starter-kit": "^2.0.0-beta.183",
|
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"bulma": "^0.9.2",
|
"bulma": "^0.9.2",
|
||||||
"node-sass": "^6.0.0",
|
"node-sass": "^6.0.0",
|
||||||
"postcss": "^8.2.14",
|
"postcss": "^8.2.14",
|
||||||
"query-string": "^7.0.0",
|
"query-string": "^7.0.0",
|
||||||
"sirv-cli": "^1.0.0",
|
"sirv-cli": "^1.0.0",
|
||||||
"svelte-infinite-scroll": "^1.5.2",
|
|
||||||
"svelte-preprocess": "^4.7.3",
|
"svelte-preprocess": "^4.7.3",
|
||||||
"svelte-routing": "^1.6.0",
|
"svelte-routing": "^1.6.0",
|
||||||
"svelte-tags-input": "^2.7.1"
|
"svelte-tags-input": "^2.7.1"
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import json from '@rollup/plugin-json';
|
||||||
import svelte from 'rollup-plugin-svelte';
|
import svelte from 'rollup-plugin-svelte';
|
||||||
import commonjs from '@rollup/plugin-commonjs';
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
import resolve from '@rollup/plugin-node-resolve';
|
import resolve from '@rollup/plugin-node-resolve';
|
||||||
@ -40,6 +41,7 @@ export default {
|
|||||||
entryFileNames: 'bundle.js'
|
entryFileNames: 'bundle.js'
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
json(),
|
||||||
svelte({
|
svelte({
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
// enable run-time checks when not in production
|
// enable run-time checks when not in production
|
||||||
|
@ -25,8 +25,8 @@
|
|||||||
form.tags = value.detail.tags;
|
form.tags = value.detail.tags;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAutocomplete = async () => {
|
const onAutocomplete = async (tag) => {
|
||||||
const list = await getTagAutocomplete();
|
const list = await getTagAutocomplete({ tag, positive: true });
|
||||||
return list;
|
return list;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -97,6 +97,7 @@
|
|||||||
addKeys={[9, 32]}
|
addKeys={[9, 32]}
|
||||||
on:tags={onTagChange}
|
on:tags={onTagChange}
|
||||||
autoComplete={onAutocomplete}
|
autoComplete={onAutocomplete}
|
||||||
|
autoCompleteFilter={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,8 +53,11 @@ export async function getRelatedTags({ tag }) {
|
|||||||
const response = await axios.get(endpoint);
|
const response = await axios.get(endpoint);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
export async function getTagAutocomplete() {
|
export async function getTagAutocomplete({ tag, positive }) {
|
||||||
const endpoint = url + "/api/tag-autocomplete";
|
let endpoint = url + "/api/tag-autocomplete?tag=" + tag;
|
||||||
|
if (positive) {
|
||||||
|
endpoint = endpoint + "&positive=true";
|
||||||
|
}
|
||||||
const response = await axios.get(endpoint);
|
const response = await axios.get(endpoint);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</AuthCheck>
|
</AuthCheck>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
.pre-line {
|
|
||||||
white-space: pre-line;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -9,10 +9,10 @@
|
|||||||
searchTerms = value.detail.tags;
|
searchTerms = value.detail.tags;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAutocomplete = async () => {
|
const onAutocomplete = async (tag) => {
|
||||||
const list = await getTagAutocomplete();
|
const list = await getTagAutocomplete({ tag });
|
||||||
return list;
|
return list;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSearch = (i) => {
|
const onSearch = (i) => {
|
||||||
if (searchTerms.length > 0) {
|
if (searchTerms.length > 0) {
|
||||||
@ -41,6 +41,7 @@
|
|||||||
addKeys={[9, 32]}
|
addKeys={[9, 32]}
|
||||||
on:tags={onTagChange}
|
on:tags={onTagChange}
|
||||||
autoComplete={onAutocomplete}
|
autoComplete={onAutocomplete}
|
||||||
|
autoCompleteFilter={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
pagination = paginate(page, totalPages);
|
pagination = paginate(page, totalPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchTerms.length == 1) {
|
if (searchTerms.filter(x => !x.startsWith("-")).length == 1) {
|
||||||
tagInfo = await getTag({ tag: searchTerms[0] });
|
tagInfo = await getTag({ tag: searchTerms[0] });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -52,8 +52,8 @@
|
|||||||
searchTerms = value.detail.tags;
|
searchTerms = value.detail.tags;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAutocomplete = async () => {
|
const onAutocomplete = async (tag) => {
|
||||||
const list = await getTagAutocomplete();
|
const list = await getTagAutocomplete({ tag });
|
||||||
return list;
|
return list;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,6 +100,7 @@
|
|||||||
addKeys={[9, 32]}
|
addKeys={[9, 32]}
|
||||||
on:tags={onTagChange}
|
on:tags={onTagChange}
|
||||||
autoComplete={onAutocomplete}
|
autoComplete={onAutocomplete}
|
||||||
|
autoCompleteFilter={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -167,7 +168,6 @@
|
|||||||
<div class="column is-full">
|
<div class="column is-full">
|
||||||
<nav
|
<nav
|
||||||
class="pagination is-centered"
|
class="pagination is-centered"
|
||||||
role="navigation"
|
|
||||||
aria-label="pagination"
|
aria-label="pagination"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
@ -38,8 +38,8 @@
|
|||||||
form.tags = value.detail.tags;
|
form.tags = value.detail.tags;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAutocomplete = async () => {
|
const onAutocomplete = async (tag) => {
|
||||||
const list = await getTagAutocomplete();
|
const list = await getTagAutocomplete({ tag, positive: true });
|
||||||
return list;
|
return list;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -107,7 +107,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} autoComplete={onAutocomplete} />
|
<Tags addKeys={[9, 32]} on:tags={onTagChange} autoComplete={onAutocomplete} autoCompleteFilter={false}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
1341
web/app/yarn.lock
1341
web/app/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user