mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Sort tags by name while scraping or merging scenes (#5752)
* Sort tags by name while scraping scenes * TagStore.All should sort by sort_name first * Sort tag by sort name/name in TagIDSelect --------- Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
parent
0f2bc3e01d
commit
db06eae7cb
7 changed files with 42 additions and 5 deletions
|
|
@ -62,7 +62,11 @@ func (r *queryResolver) FindTags(ctx context.Context, tagFilter *models.TagFilte
|
||||||
func (r *queryResolver) AllTags(ctx context.Context) (ret []*models.Tag, err error) {
|
func (r *queryResolver) AllTags(ctx context.Context) (ret []*models.Tag, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.Tag.All(ctx)
|
ret, err = r.repository.Tag.All(ctx)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/match"
|
"github.com/stashapp/stash/pkg/match"
|
||||||
|
|
@ -207,6 +208,10 @@ func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source scraper.So
|
||||||
return nil, fmt.Errorf("%w: scraper_id or stash_box_index must be set", ErrInput)
|
return nil, fmt.Errorf("%w: scraper_id or stash_box_index must be set", ErrInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := range ret {
|
||||||
|
slices.SortFunc(ret[i].Tags, models.ScrapedTagSortFunction)
|
||||||
|
}
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package models
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
|
|
@ -400,6 +401,10 @@ type ScrapedTag struct {
|
||||||
|
|
||||||
func (ScrapedTag) IsScrapedContent() {}
|
func (ScrapedTag) IsScrapedContent() {}
|
||||||
|
|
||||||
|
func ScrapedTagSortFunction(a, b *ScrapedTag) int {
|
||||||
|
return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name))
|
||||||
|
}
|
||||||
|
|
||||||
// A movie from a scraping operation...
|
// A movie from a scraping operation...
|
||||||
type ScrapedMovie struct {
|
type ScrapedMovie struct {
|
||||||
StoredID *string `json:"stored_id"`
|
StoredID *string `json:"stored_id"`
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ func FilterTags(excludeRegexps []*regexp.Regexp, tags []*models.ScrapedTag) (new
|
||||||
return tags, nil
|
return tags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newTags = make([]*models.ScrapedTag, 0, len(tags))
|
||||||
|
|
||||||
for _, t := range tags {
|
for _, t := range tags {
|
||||||
ignore := false
|
ignore := false
|
||||||
for _, reg := range excludeRegexps {
|
for _, reg := range excludeRegexps {
|
||||||
|
|
|
||||||
|
|
@ -562,7 +562,7 @@ func (qb *TagStore) All(ctx context.Context) ([]*models.Tag, error) {
|
||||||
table := qb.table()
|
table := qb.table()
|
||||||
|
|
||||||
return qb.getMany(ctx, qb.selectDataset().Order(
|
return qb.getMany(ctx, qb.selectDataset().Order(
|
||||||
table.Col("name").Asc(),
|
goqu.L("COALESCE(tags.sort_name, tags.name) COLLATE NATURAL_CI").Asc(),
|
||||||
table.Col(idColumn).Asc(),
|
table.Col(idColumn).Asc(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1018,6 +1018,7 @@ type StashBoxConfig struct {
|
||||||
GuidelinesURL string `json:"guidelines_url"`
|
GuidelinesURL string `json:"guidelines_url"`
|
||||||
RequireSceneDraft bool `json:"require_scene_draft"`
|
RequireSceneDraft bool `json:"require_scene_draft"`
|
||||||
EditUpdateLimit int `json:"edit_update_limit"`
|
EditUpdateLimit int `json:"edit_update_limit"`
|
||||||
|
RequireTagRole bool `json:"require_tag_role"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StringCriterionInput struct {
|
type StringCriterionInput struct {
|
||||||
|
|
@ -2143,6 +2144,8 @@ const (
|
||||||
// May grant and rescind invite tokens and resind invite keys
|
// May grant and rescind invite tokens and resind invite keys
|
||||||
RoleEnumManageInvites RoleEnum = "MANAGE_INVITES"
|
RoleEnumManageInvites RoleEnum = "MANAGE_INVITES"
|
||||||
RoleEnumBot RoleEnum = "BOT"
|
RoleEnumBot RoleEnum = "BOT"
|
||||||
|
RoleEnumReadOnly RoleEnum = "READ_ONLY"
|
||||||
|
RoleEnumEditTags RoleEnum = "EDIT_TAGS"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AllRoleEnum = []RoleEnum{
|
var AllRoleEnum = []RoleEnum{
|
||||||
|
|
@ -2154,11 +2157,13 @@ var AllRoleEnum = []RoleEnum{
|
||||||
RoleEnumInvite,
|
RoleEnumInvite,
|
||||||
RoleEnumManageInvites,
|
RoleEnumManageInvites,
|
||||||
RoleEnumBot,
|
RoleEnumBot,
|
||||||
|
RoleEnumReadOnly,
|
||||||
|
RoleEnumEditTags,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e RoleEnum) IsValid() bool {
|
func (e RoleEnum) IsValid() bool {
|
||||||
switch e {
|
switch e {
|
||||||
case RoleEnumRead, RoleEnumVote, RoleEnumEdit, RoleEnumModify, RoleEnumAdmin, RoleEnumInvite, RoleEnumManageInvites, RoleEnumBot:
|
case RoleEnumRead, RoleEnumVote, RoleEnumEdit, RoleEnumModify, RoleEnumAdmin, RoleEnumInvite, RoleEnumManageInvites, RoleEnumBot, RoleEnumReadOnly, RoleEnumEditTags:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,10 @@ export type SelectObject = {
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Tag = Pick<GQL.Tag, "id" | "name" | "aliases" | "image_path">;
|
export type Tag = Pick<
|
||||||
|
GQL.Tag,
|
||||||
|
"id" | "name" | "sort_name" | "aliases" | "image_path"
|
||||||
|
>;
|
||||||
type Option = SelectOption<Tag>;
|
type Option = SelectOption<Tag>;
|
||||||
|
|
||||||
type FindTagsResult = Awaited<
|
type FindTagsResult = Awaited<
|
||||||
|
|
@ -293,7 +296,20 @@ const _TagIDSelect: React.FC<IFilterProps & IFilterIDProps<Tag>> = (props) => {
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
const items = await loadObjectsByID(ids);
|
const items = await loadObjectsByID(ids);
|
||||||
setValues(items);
|
|
||||||
|
// #4684 - sort items by sort name/name
|
||||||
|
const sortedItems = [...items];
|
||||||
|
sortedItems.sort((a, b) => {
|
||||||
|
const aName = a.sort_name || a.name;
|
||||||
|
const bName = b.sort_name || b.name;
|
||||||
|
|
||||||
|
if (aName && bName) {
|
||||||
|
return aName.localeCompare(bName);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
setValues(sortedItems);
|
||||||
};
|
};
|
||||||
|
|
||||||
load();
|
load();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue