Include tag counts in tag filter & tag input autocomplete (#2711)
This commit is contained in:
parent
80a225c677
commit
74c72ce551
@ -25,6 +25,7 @@ from .models import (
|
|||||||
ConfigRevision,
|
ConfigRevision,
|
||||||
CrawlConfig,
|
CrawlConfig,
|
||||||
CrawlConfigOut,
|
CrawlConfigOut,
|
||||||
|
CrawlConfigTags,
|
||||||
CrawlOut,
|
CrawlOut,
|
||||||
UpdateCrawlConfig,
|
UpdateCrawlConfig,
|
||||||
Organization,
|
Organization,
|
||||||
@ -976,8 +977,20 @@ class CrawlConfigOps:
|
|||||||
|
|
||||||
async def get_crawl_config_tags(self, org):
|
async def get_crawl_config_tags(self, org):
|
||||||
"""get distinct tags from all crawl configs for this org"""
|
"""get distinct tags from all crawl configs for this org"""
|
||||||
tags = await self.crawl_configs.distinct("tags", {"oid": org.id})
|
return await self.crawl_configs.distinct("tags", {"oid": org.id})
|
||||||
return list(tags)
|
|
||||||
|
async def get_crawl_config_tag_counts(self, org):
|
||||||
|
"""get distinct tags from all crawl configs for this org"""
|
||||||
|
tags = await self.crawl_configs.aggregate(
|
||||||
|
[
|
||||||
|
{"$match": {"oid": org.id}},
|
||||||
|
{"$unwind": "$tags"},
|
||||||
|
{"$group": {"_id": "$tags", "count": {"$sum": 1}}},
|
||||||
|
{"$project": {"tag": "$_id", "count": "$count", "_id": 0}},
|
||||||
|
{"$sort": {"count": -1, "tag": 1}},
|
||||||
|
]
|
||||||
|
).to_list()
|
||||||
|
return tags
|
||||||
|
|
||||||
async def get_crawl_config_search_values(self, org):
|
async def get_crawl_config_search_values(self, org):
|
||||||
"""List unique names, first seeds, and descriptions from all workflows in org"""
|
"""List unique names, first seeds, and descriptions from all workflows in org"""
|
||||||
@ -1399,10 +1412,17 @@ def init_crawl_config_api(
|
|||||||
)
|
)
|
||||||
return paginated_format(crawl_configs, total, page, pageSize)
|
return paginated_format(crawl_configs, total, page, pageSize)
|
||||||
|
|
||||||
@router.get("/tags", response_model=List[str])
|
@router.get("/tags", response_model=List[str], deprecated=True)
|
||||||
async def get_crawl_config_tags(org: Organization = Depends(org_viewer_dep)):
|
async def get_crawl_config_tags(org: Organization = Depends(org_viewer_dep)):
|
||||||
|
"""
|
||||||
|
Deprecated - prefer /api/orgs/{oid}/crawlconfigs/tagCounts instead.
|
||||||
|
"""
|
||||||
return await ops.get_crawl_config_tags(org)
|
return await ops.get_crawl_config_tags(org)
|
||||||
|
|
||||||
|
@router.get("/tagCounts", response_model=CrawlConfigTags)
|
||||||
|
async def get_crawl_config_tag_counts(org: Organization = Depends(org_viewer_dep)):
|
||||||
|
return {"tags": await ops.get_crawl_config_tag_counts(org)}
|
||||||
|
|
||||||
@router.get("/search-values", response_model=CrawlConfigSearchValues)
|
@router.get("/search-values", response_model=CrawlConfigSearchValues)
|
||||||
async def get_crawl_config_search_values(
|
async def get_crawl_config_search_values(
|
||||||
org: Organization = Depends(org_viewer_dep),
|
org: Organization = Depends(org_viewer_dep),
|
||||||
|
|||||||
@ -577,11 +577,19 @@ class CrawlConfigAddedResponse(BaseModel):
|
|||||||
execMinutesQuotaReached: bool
|
execMinutesQuotaReached: bool
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
class CrawlConfigTagCount(BaseModel):
|
||||||
|
"""Response model for crawlconfig tag count"""
|
||||||
|
|
||||||
|
tag: str
|
||||||
|
count: int
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
class CrawlConfigTags(BaseModel):
|
class CrawlConfigTags(BaseModel):
|
||||||
"""Response model for crawlconfig tags"""
|
"""Response model for crawlconfig tags"""
|
||||||
|
|
||||||
tags: List[str]
|
tags: List[CrawlConfigTagCount]
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@ -50,6 +50,22 @@ def test_get_config_by_tag_1(admin_auth_headers, default_org_id):
|
|||||||
assert sorted(data) == ["tag-1", "tag-2", "wr-test-1", "wr-test-2"]
|
assert sorted(data) == ["tag-1", "tag-2", "wr-test-1", "wr-test-2"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_config_by_tag_counts_1(admin_auth_headers, default_org_id):
|
||||||
|
r = requests.get(
|
||||||
|
f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs/tagCounts",
|
||||||
|
headers=admin_auth_headers,
|
||||||
|
)
|
||||||
|
data = r.json()
|
||||||
|
assert data == {
|
||||||
|
"tags": [
|
||||||
|
{"tag": "wr-test-2", "count": 2},
|
||||||
|
{"tag": "tag-1", "count": 1},
|
||||||
|
{"tag": "tag-2", "count": 1},
|
||||||
|
{"tag": "wr-test-1", "count": 1},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_create_new_config_2(admin_auth_headers, default_org_id):
|
def test_create_new_config_2(admin_auth_headers, default_org_id):
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs/",
|
f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs/",
|
||||||
@ -84,6 +100,24 @@ def test_get_config_by_tag_2(admin_auth_headers, default_org_id):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_config_by_tag_counts_2(admin_auth_headers, default_org_id):
|
||||||
|
r = requests.get(
|
||||||
|
f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs/tagCounts",
|
||||||
|
headers=admin_auth_headers,
|
||||||
|
)
|
||||||
|
data = r.json()
|
||||||
|
assert data == {
|
||||||
|
"tags": [
|
||||||
|
{"tag": "wr-test-2", "count": 2},
|
||||||
|
{"tag": "tag-0", "count": 1},
|
||||||
|
{"tag": "tag-1", "count": 1},
|
||||||
|
{"tag": "tag-2", "count": 1},
|
||||||
|
{"tag": "tag-3", "count": 1},
|
||||||
|
{"tag": "wr-test-1", "count": 1},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_get_config_2(admin_auth_headers, default_org_id):
|
def test_get_config_2(admin_auth_headers, default_org_id):
|
||||||
r = requests.get(
|
r = requests.get(
|
||||||
f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs/{new_cid_2}",
|
f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs/{new_cid_2}",
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export type BadgeVariant =
|
|||||||
| "danger"
|
| "danger"
|
||||||
| "neutral"
|
| "neutral"
|
||||||
| "primary"
|
| "primary"
|
||||||
| "blue"
|
| "cyan"
|
||||||
| "high-contrast";
|
| "high-contrast";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,6 +27,12 @@ export class Badge extends TailwindElement {
|
|||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
variant: BadgeVariant = "neutral";
|
variant: BadgeVariant = "neutral";
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
outline = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
pill = false;
|
||||||
|
|
||||||
@property({ type: String, reflect: true })
|
@property({ type: String, reflect: true })
|
||||||
role: string | null = "status";
|
role: string | null = "status";
|
||||||
|
|
||||||
@ -40,16 +46,32 @@ export class Badge extends TailwindElement {
|
|||||||
return html`
|
return html`
|
||||||
<span
|
<span
|
||||||
class=${clsx(
|
class=${clsx(
|
||||||
tw`h-4.5 inline-flex items-center justify-center rounded-sm px-2 align-[1px] text-xs`,
|
tw`inline-flex h-[1.125rem] items-center justify-center align-[1px] text-xs`,
|
||||||
{
|
this.outline
|
||||||
success: tw`bg-success-500 text-neutral-0`,
|
? [
|
||||||
warning: tw`bg-warning-600 text-neutral-0`,
|
tw`ring-1`,
|
||||||
danger: tw`bg-danger-500 text-neutral-0`,
|
{
|
||||||
neutral: tw`bg-neutral-100 text-neutral-600`,
|
success: tw`bg-success-500 text-success-500 ring-success-500`,
|
||||||
"high-contrast": tw`bg-neutral-600 text-neutral-0`,
|
warning: tw`bg-warning-600 text-warning-600 ring-warning-600`,
|
||||||
primary: tw`bg-primary text-neutral-0`,
|
danger: tw`bg-danger-500 text-danger-500 ring-danger-500`,
|
||||||
blue: tw`bg-cyan-50 text-cyan-600`,
|
neutral: tw`g-neutral-100 text-neutral-600 ring-neutral-600`,
|
||||||
}[this.variant],
|
"high-contrast": tw`bg-neutral-600 text-neutral-0 ring-neutral-0`,
|
||||||
|
primary: tw`bg-white text-primary ring-primary`,
|
||||||
|
cyan: tw`bg-cyan-50 text-cyan-600 ring-cyan-600`,
|
||||||
|
blue: tw`bg-blue-50 text-blue-600 ring-blue-600`,
|
||||||
|
}[this.variant],
|
||||||
|
]
|
||||||
|
: {
|
||||||
|
success: tw`bg-success-500 text-neutral-0`,
|
||||||
|
warning: tw`bg-warning-600 text-neutral-0`,
|
||||||
|
danger: tw`bg-danger-500 text-neutral-0`,
|
||||||
|
neutral: tw`bg-neutral-100 text-neutral-600`,
|
||||||
|
"high-contrast": tw`bg-neutral-600 text-neutral-0`,
|
||||||
|
primary: tw`bg-primary text-neutral-0`,
|
||||||
|
cyan: tw`bg-cyan-50 text-cyan-600`,
|
||||||
|
blue: tw`bg-blue-50 text-blue-600`,
|
||||||
|
}[this.variant],
|
||||||
|
this.pill ? tw`min-w-[1.125rem] rounded-full px-1` : tw`rounded px-2`,
|
||||||
)}
|
)}
|
||||||
part="base"
|
part="base"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { customElement, property, query, state } from "lit/decorators.js";
|
|||||||
import debounce from "lodash/fp/debounce";
|
import debounce from "lodash/fp/debounce";
|
||||||
|
|
||||||
import type { UnderlyingFunction } from "@/types/utils";
|
import type { UnderlyingFunction } from "@/types/utils";
|
||||||
|
import { type WorkflowTag } from "@/types/workflow";
|
||||||
import { dropdown } from "@/utils/css";
|
import { dropdown } from "@/utils/css";
|
||||||
|
|
||||||
export type Tags = string[];
|
export type Tags = string[];
|
||||||
@ -80,7 +81,7 @@ export class TagInput extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sl-popup::part(popup) {
|
sl-popup::part(popup) {
|
||||||
z-index: 3;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shake {
|
.shake {
|
||||||
@ -116,7 +117,7 @@ export class TagInput extends LitElement {
|
|||||||
initialTags?: Tags;
|
initialTags?: Tags;
|
||||||
|
|
||||||
@property({ type: Array })
|
@property({ type: Array })
|
||||||
tagOptions: Tags = [];
|
tagOptions: WorkflowTag[] = [];
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
disabled = false;
|
disabled = false;
|
||||||
@ -224,6 +225,7 @@ export class TagInput extends LitElement {
|
|||||||
@paste=${this.onPaste}
|
@paste=${this.onPaste}
|
||||||
?required=${this.required && !this.tags.length}
|
?required=${this.required && !this.tags.length}
|
||||||
placeholder=${placeholder}
|
placeholder=${placeholder}
|
||||||
|
autocomplete="off"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
aria-controls="dropdown"
|
aria-controls="dropdown"
|
||||||
aria-expanded="${this.dropdownIsOpen === true}"
|
aria-expanded="${this.dropdownIsOpen === true}"
|
||||||
@ -258,10 +260,14 @@ export class TagInput extends LitElement {
|
|||||||
>
|
>
|
||||||
${this.tagOptions
|
${this.tagOptions
|
||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
|
.filter(({ tag }) => !this.tags.includes(tag))
|
||||||
.map(
|
.map(
|
||||||
(tag) => html`
|
({ tag, count }) => html`
|
||||||
<sl-menu-item role="option" value=${tag}
|
<sl-menu-item role="option" value=${tag}
|
||||||
>${tag}</sl-menu-item
|
>${tag}
|
||||||
|
<btrix-badge pill variant="cyan" slot="suffix"
|
||||||
|
>${count}</btrix-badge
|
||||||
|
></sl-menu-item
|
||||||
>
|
>
|
||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import type {
|
|||||||
TagsChangeEvent,
|
TagsChangeEvent,
|
||||||
} from "@/components/ui/tag-input";
|
} from "@/components/ui/tag-input";
|
||||||
import { type CollectionsChangeEvent } from "@/features/collections/collections-add";
|
import { type CollectionsChangeEvent } from "@/features/collections/collections-add";
|
||||||
|
import { type WorkflowTag, type WorkflowTags } from "@/types/workflow";
|
||||||
import { APIError } from "@/utils/api";
|
import { APIError } from "@/utils/api";
|
||||||
import { maxLengthValidator } from "@/utils/form";
|
import { maxLengthValidator } from "@/utils/form";
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ export class FileUploader extends BtrixElement {
|
|||||||
private collectionIds: string[] = [];
|
private collectionIds: string[] = [];
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private tagOptions: Tags = [];
|
private tagOptions: WorkflowTag[] = [];
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private tagsToSave: Tags = [];
|
private tagsToSave: Tags = [];
|
||||||
@ -85,7 +86,8 @@ export class FileUploader extends BtrixElement {
|
|||||||
private readonly form!: Promise<HTMLFormElement>;
|
private readonly form!: Promise<HTMLFormElement>;
|
||||||
|
|
||||||
// For fuzzy search:
|
// For fuzzy search:
|
||||||
private readonly fuse = new Fuse([], {
|
private readonly fuse = new Fuse<WorkflowTag>([], {
|
||||||
|
keys: ["tag"],
|
||||||
shouldSort: false,
|
shouldSort: false,
|
||||||
threshold: 0.2, // stricter; default is 0.6
|
threshold: 0.2, // stricter; default is 0.6
|
||||||
});
|
});
|
||||||
@ -361,8 +363,8 @@ export class FileUploader extends BtrixElement {
|
|||||||
|
|
||||||
private async fetchTags() {
|
private async fetchTags() {
|
||||||
try {
|
try {
|
||||||
const tags = await this.api.fetch<never>(
|
const { tags } = await this.api.fetch<WorkflowTags>(
|
||||||
`/orgs/${this.orgId}/crawlconfigs/tags`,
|
`/orgs/${this.orgId}/crawlconfigs/tagCounts`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update search/filter collection
|
// Update search/filter collection
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import type {
|
|||||||
} from "@/components/ui/tag-input";
|
} from "@/components/ui/tag-input";
|
||||||
import { type CollectionsChangeEvent } from "@/features/collections/collections-add";
|
import { type CollectionsChangeEvent } from "@/features/collections/collections-add";
|
||||||
import type { ArchivedItem } from "@/types/crawler";
|
import type { ArchivedItem } from "@/types/crawler";
|
||||||
|
import { type WorkflowTag, type WorkflowTags } from "@/types/workflow";
|
||||||
import { maxLengthValidator } from "@/utils/form";
|
import { maxLengthValidator } from "@/utils/form";
|
||||||
import LiteElement, { html } from "@/utils/LiteElement";
|
import LiteElement, { html } from "@/utils/LiteElement";
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ export class CrawlMetadataEditor extends LiteElement {
|
|||||||
private includeName = false;
|
private includeName = false;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private tagOptions: Tags = [];
|
private tagOptions: WorkflowTag[] = [];
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private tagsToSave: Tags = [];
|
private tagsToSave: Tags = [];
|
||||||
@ -55,7 +56,8 @@ export class CrawlMetadataEditor extends LiteElement {
|
|||||||
private collectionsToSave: string[] = [];
|
private collectionsToSave: string[] = [];
|
||||||
|
|
||||||
// For fuzzy search:
|
// For fuzzy search:
|
||||||
private readonly fuse = new Fuse<string>([], {
|
private readonly fuse = new Fuse<WorkflowTag>([], {
|
||||||
|
keys: ["tag"],
|
||||||
shouldSort: false,
|
shouldSort: false,
|
||||||
threshold: 0.2, // stricter; default is 0.6
|
threshold: 0.2, // stricter; default is 0.6
|
||||||
});
|
});
|
||||||
@ -164,8 +166,8 @@ export class CrawlMetadataEditor extends LiteElement {
|
|||||||
private async fetchTags() {
|
private async fetchTags() {
|
||||||
if (!this.crawl) return;
|
if (!this.crawl) return;
|
||||||
try {
|
try {
|
||||||
const tags = await this.apiFetch<string[]>(
|
const { tags } = await this.apiFetch<WorkflowTags>(
|
||||||
`/orgs/${this.crawl.oid}/crawlconfigs/tags`,
|
`/orgs/${this.crawl.oid}/crawlconfigs/tagCounts`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update search/filter collection
|
// Update search/filter collection
|
||||||
|
|||||||
@ -88,7 +88,11 @@ import {
|
|||||||
type WorkflowParams,
|
type WorkflowParams,
|
||||||
} from "@/types/crawler";
|
} from "@/types/crawler";
|
||||||
import type { UnderlyingFunction } from "@/types/utils";
|
import type { UnderlyingFunction } from "@/types/utils";
|
||||||
import { NewWorkflowOnlyScopeType } from "@/types/workflow";
|
import {
|
||||||
|
NewWorkflowOnlyScopeType,
|
||||||
|
type WorkflowTag,
|
||||||
|
type WorkflowTags,
|
||||||
|
} from "@/types/workflow";
|
||||||
import { track } from "@/utils/analytics";
|
import { track } from "@/utils/analytics";
|
||||||
import { isApiError, isApiErrorDetail } from "@/utils/api";
|
import { isApiError, isApiErrorDetail } from "@/utils/api";
|
||||||
import { DEPTH_SUPPORTED_SCOPES, isPageScopeType } from "@/utils/crawler";
|
import { DEPTH_SUPPORTED_SCOPES, isPageScopeType } from "@/utils/crawler";
|
||||||
@ -258,7 +262,7 @@ export class WorkflowEditor extends BtrixElement {
|
|||||||
private showCrawlerChannels = false;
|
private showCrawlerChannels = false;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private tagOptions: string[] = [];
|
private tagOptions: WorkflowTag[] = [];
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private isSubmitting = false;
|
private isSubmitting = false;
|
||||||
@ -293,7 +297,8 @@ export class WorkflowEditor extends BtrixElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// For fuzzy search:
|
// For fuzzy search:
|
||||||
private readonly fuse = new Fuse<string>([], {
|
private readonly fuse = new Fuse<WorkflowTag>([], {
|
||||||
|
keys: ["tag"],
|
||||||
shouldSort: false,
|
shouldSort: false,
|
||||||
threshold: 0.2, // stricter; default is 0.6
|
threshold: 0.2, // stricter; default is 0.6
|
||||||
});
|
});
|
||||||
@ -2532,8 +2537,8 @@ https://archiveweb.page/images/${"logo.svg"}`}
|
|||||||
private async fetchTags() {
|
private async fetchTags() {
|
||||||
this.tagOptions = [];
|
this.tagOptions = [];
|
||||||
try {
|
try {
|
||||||
const tags = await this.api.fetch<string[]>(
|
const { tags } = await this.api.fetch<WorkflowTags>(
|
||||||
`/orgs/${this.orgId}/crawlconfigs/tags`,
|
`/orgs/${this.orgId}/crawlconfigs/tagCounts`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update search/filter collection
|
// Update search/filter collection
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import { isFocusable } from "tabbable";
|
|||||||
|
|
||||||
import { BtrixElement } from "@/classes/BtrixElement";
|
import { BtrixElement } from "@/classes/BtrixElement";
|
||||||
import type { BtrixChangeEvent } from "@/events/btrix-change";
|
import type { BtrixChangeEvent } from "@/events/btrix-change";
|
||||||
|
import { type WorkflowTag, type WorkflowTags } from "@/types/workflow";
|
||||||
import { tw } from "@/utils/tailwind";
|
import { tw } from "@/utils/tailwind";
|
||||||
|
|
||||||
const MAX_TAGS_IN_LABEL = 5;
|
const MAX_TAGS_IN_LABEL = 5;
|
||||||
@ -47,7 +48,9 @@ export class WorkflowTagFilter extends BtrixElement {
|
|||||||
@queryAll("sl-checkbox")
|
@queryAll("sl-checkbox")
|
||||||
private readonly checkboxes!: NodeListOf<SlCheckbox>;
|
private readonly checkboxes!: NodeListOf<SlCheckbox>;
|
||||||
|
|
||||||
private readonly fuse = new Fuse<string>([]);
|
private readonly fuse = new Fuse<WorkflowTag>([], {
|
||||||
|
keys: ["tag"],
|
||||||
|
});
|
||||||
|
|
||||||
private selected = new Map<string, boolean>();
|
private selected = new Map<string, boolean>();
|
||||||
|
|
||||||
@ -63,8 +66,8 @@ export class WorkflowTagFilter extends BtrixElement {
|
|||||||
|
|
||||||
private readonly orgTagsTask = new Task(this, {
|
private readonly orgTagsTask = new Task(this, {
|
||||||
task: async () => {
|
task: async () => {
|
||||||
const tags = await this.api.fetch<string[]>(
|
const { tags } = await this.api.fetch<WorkflowTags>(
|
||||||
`/orgs/${this.orgId}/crawlconfigs/tags`,
|
`/orgs/${this.orgId}/crawlconfigs/tagCounts`,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.fuse.setCollection(tags);
|
this.fuse.setCollection(tags);
|
||||||
@ -235,18 +238,18 @@ export class WorkflowTagFilter extends BtrixElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderList(opts: { item: string }[]) {
|
private renderList(opts: { item: WorkflowTag }[]) {
|
||||||
const tag = (tag: string) => {
|
const tag = (tag: WorkflowTag) => {
|
||||||
const checked = this.selected.get(tag) === true;
|
const checked = this.selected.get(tag.tag) === true;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<li role="option" aria-checked=${checked}>
|
<li role="option" aria-checked=${checked}>
|
||||||
<sl-checkbox
|
<sl-checkbox
|
||||||
class="w-full part-[base]:w-full part-[base]:rounded part-[base]:p-2 part-[base]:hover:bg-primary-50 part-[base]:focus:bg-primary-50"
|
class="w-full part-[label]:flex part-[base]:w-full part-[label]:w-full part-[label]:items-center part-[label]:justify-between part-[base]:rounded part-[base]:p-2 part-[base]:hover:bg-primary-50"
|
||||||
value=${tag}
|
value=${tag.tag}
|
||||||
?checked=${checked}
|
?checked=${checked}
|
||||||
tabindex="0"
|
>${tag.tag}
|
||||||
>${tag}
|
<btrix-badge pill variant="cyan">${tag.count}</btrix-badge>
|
||||||
</sl-checkbox>
|
</sl-checkbox>
|
||||||
</li>
|
</li>
|
||||||
`;
|
`;
|
||||||
@ -264,36 +267,6 @@ export class WorkflowTagFilter extends BtrixElement {
|
|||||||
|
|
||||||
this.selected.set(value, checked);
|
this.selected.set(value, checked);
|
||||||
}}
|
}}
|
||||||
@keydown=${(e: KeyboardEvent) => {
|
|
||||||
if (!this.checkboxes.length) return;
|
|
||||||
|
|
||||||
// Enable focus trapping
|
|
||||||
const options = Array.from(this.checkboxes);
|
|
||||||
const focused = options.findIndex((opt) => opt.matches(":focus"));
|
|
||||||
|
|
||||||
switch (e.key) {
|
|
||||||
case "ArrowDown": {
|
|
||||||
e.preventDefault();
|
|
||||||
options[
|
|
||||||
focused === -1 || focused === options.length - 1
|
|
||||||
? 0
|
|
||||||
: focused + 1
|
|
||||||
].focus();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "ArrowUp": {
|
|
||||||
e.preventDefault();
|
|
||||||
options[
|
|
||||||
focused === -1 || focused === 0
|
|
||||||
? options.length - 1
|
|
||||||
: focused - 1
|
|
||||||
].focus();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
${repeat(
|
${repeat(
|
||||||
opts,
|
opts,
|
||||||
|
|||||||
@ -5,3 +5,12 @@ export enum NewWorkflowOnlyScopeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const WorkflowScopeType = { ...ScopeType, ...NewWorkflowOnlyScopeType };
|
export const WorkflowScopeType = { ...ScopeType, ...NewWorkflowOnlyScopeType };
|
||||||
|
|
||||||
|
export type WorkflowTag = {
|
||||||
|
tag: string;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkflowTags = {
|
||||||
|
tags: WorkflowTag[];
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user