Skip to content
This repository has been archived by the owner on Oct 3, 2022. It is now read-only.

server: extend tag completion #76

Merged
merged 3 commits into from
Aug 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions assets/assets.go

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions client/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const figureWidth = 200 + 4; // With margin
}, { passive: true });


search.addEventListener("input", async e => {
search.addEventListener("input", async () => {
let text = search.value;
if (!text.length || text[text.length - 1] == " ") {
sugg.innerHTML = "";
Expand All @@ -31,7 +31,8 @@ const figureWidth = 200 + 4; // With margin
try {
let i = text.lastIndexOf(" ");
const r = await fetch("/api/complete_tag/"
+ (i === -1 ? text : text.slice(i + 1)));
+ (i === -1 ?encodeURIComponent(text) :
encodeURIComponent(text.slice(i + 1))));
if (r.status !== 200) {
throw await r.text();
}
Expand Down
96 changes: 92 additions & 4 deletions db/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package db

import (
"database/sql"
"fmt"
"regexp"
"strings"

"github.com/Masterminds/squirrel"
Expand Down Expand Up @@ -96,17 +98,103 @@ func RemoveTags(imageID int64, tags []common.Tag) error {
})
}

// Attempt to complete a tag by suggesting up to 10 possible tags for a prefix
// Try to match an incomplete tag against a list of postfixes, return matches
func matchPost(re *regexp.Regexp, prefix string,
postfixes []string) (matches []string) {
for _, p := range postfixes {
matched := re.FindString(p)
if len(matched) != 0 {
matches = append(matches, prefix+matched)
}
}
return
}

// Attempt to complete a tag by suggesting up to 20 possible tags for a prefix
func CompleteTag(s string) (tags []string, err error) {
var re *regexp.Regexp
const maxCap = 20
tags = make([]string, 0, maxCap)
typeQ := ""
prefix := ""
if s[0] == '-' {
prefix = "-"
s = s[1:]
}

// TODO: Inject system tags into array
i := strings.IndexByte(s, ':')
if i != -1 {
// Complete postfix
re, err = regexp.Compile(`^` + regexp.QuoteMeta(s[i+1:]) + `.*`)
if err != nil {
return
}
completeWithPrefix := func(t common.TagType) {
prefix += s[:i+1]
s = s[i+1:]
typeQ = fmt.Sprintf(" and type = %d", t)
}
switch s[:i] {
case "undefined":
completeWithPrefix(common.Undefined)
case "artist":
fallthrough
case "author":
completeWithPrefix(common.Author)
case "character":
completeWithPrefix(common.Character)
case "copyright":
fallthrough
case "series":
completeWithPrefix(common.Series)
case "meta":
completeWithPrefix(common.Meta)
case "rating":
tags = matchPost(re, "rating:",
[]string{"safe", "questionable", "explicit"})
return
case "system":
tags = matchPost(re, "system:",
[]string{"size", "width", "height", "duration", "tag_count"})
return
case "order":
prefix = s[:i+1]
if s[i+1:] != "" {
if s[i+1] == '-' {
prefix = "order:-"
re, err = regexp.Compile(`^` + regexp.QuoteMeta(s[i+2:]) + `.*`)
if err != nil {
return
}
}
}
tags = matchPost(re, prefix,
[]string{"size", "width", "height", "duration", "tag_count", "random"})
return
case "limit":
return
default:
// Continue as regular tag
}
} else {
// Complete prefix
re, err = regexp.Compile(`^` + regexp.QuoteMeta(s) + `.*`)
if err != nil {
return
}
for _, t := range []string{"undefined", "artist", "author", "character",
"copyright", "series", "meta", "rating", "system", "order", "limit"} {
matched := re.FindString(t)
if matched != "" {
tags = append(tags, prefix+matched+":")
}
}
}

r, err := sq.Select("tag").
Distinct().
From("tags").
Where("tag like ? || '%' escape '$'", // Escape underscores
Where("tag like ? || '%' escape '$'"+typeQ, // Escape underscores
strings.Replace(s, "_", "$_", -1)).
OrderBy("tag").
Limit(maxCap).
Expand All @@ -122,7 +210,7 @@ func CompleteTag(s string) (tags []string, err error) {
if err != nil {
return
}
tags = append(tags, tag)
tags = append(tags, prefix+tag)
if len(tags) == maxCap {
break
}
Expand Down
8 changes: 7 additions & 1 deletion server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"runtime/debug"
Expand Down Expand Up @@ -252,7 +253,12 @@ func removeFileHTTP(w http.ResponseWriter, r *http.Request) {

// Complete a tag by prefix from an HTTP request
func completeTagHTTP(w http.ResponseWriter, r *http.Request) {
tags, err := db.CompleteTag(extractParam(r, "prefix"))
orig, err := url.QueryUnescape(extractParam(r, "prefix"))
if err != nil {
send500(w, r, err)
return
}
tags, err := db.CompleteTag(orig)
if err != nil {
send500(w, r, err)
return
Expand Down
5 changes: 3 additions & 2 deletions www/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const figureWidth = 200 + 4; // With margin
}, { passive: true });


search.addEventListener("input", async e => {
search.addEventListener("input", async () => {
let text = search.value;
if (!text.length || text[text.length - 1] == " ") {
sugg.innerHTML = "";
Expand All @@ -31,7 +31,8 @@ const figureWidth = 200 + 4; // With margin
try {
let i = text.lastIndexOf(" ");
const r = await fetch("/api/complete_tag/"
+ (i === -1 ? text : text.slice(i + 1)));
+ (i === -1 ?encodeURIComponent(text) :
encodeURIComponent(text.slice(i + 1))));
if (r.status !== 200) {
throw await r.text();
}
Expand Down