@drodil/backstage-plugin-qeta-react 3.59.5 → 3.59.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { Autocomplete } from '@material-ui/lab';
3
3
  import { Box, TextField, CircularProgress, Tooltip, Typography, Chip } from '@material-ui/core';
4
- import { forwardRef, useState, useEffect, useMemo } from 'react';
4
+ import { forwardRef, useState, useRef, useEffect, useMemo } from 'react';
5
5
  import { qetaApiRef } from '../../api.esm.js';
6
6
  import { useApi, configApiRef } from '@backstage/core-plugin-api';
7
7
  import { qetaCreateTagPermission, filterTags } from '@drodil/backstage-plugin-qeta-common';
@@ -12,6 +12,87 @@ import { permissionApiRef } from '@backstage/plugin-permission-react';
12
12
  import { AuthorizeResult } from '@backstage/plugin-permission-common';
13
13
  import { useDebounce } from 'react-use';
14
14
 
15
+ const TAG_SEARCH_LIMIT = 25;
16
+ const isCreateTagOption = (option) => typeof option !== "string";
17
+ const getTagOptionValue = (option) => isCreateTagOption(option) ? option.inputValue : option;
18
+ const getTagOptionLabel = (option) => isCreateTagOption(option) ? option.label : option;
19
+ const normalizeTagInput = (input) => filterTags([input])[0];
20
+ const getTagSearchRequest = (term) => ({
21
+ limit: TAG_SEARCH_LIMIT,
22
+ orderBy: "postsCount",
23
+ order: "desc",
24
+ ...term ? { searchQuery: term } : {}
25
+ });
26
+ const getMatchingAllowedTags = (allowedTags, term) => {
27
+ const normalizedTerm = term.trim().toLocaleLowerCase();
28
+ if (!normalizedTerm) {
29
+ return allowedTags;
30
+ }
31
+ return allowedTags.filter(
32
+ (tag) => tag.toLocaleLowerCase().includes(normalizedTerm)
33
+ );
34
+ };
35
+ const mergeTags = (...groups) => {
36
+ const seen = /* @__PURE__ */ new Set();
37
+ const merged = [];
38
+ for (const tag of groups.flat()) {
39
+ if (!seen.has(tag)) {
40
+ seen.add(tag);
41
+ merged.push(tag);
42
+ }
43
+ }
44
+ return merged;
45
+ };
46
+ const getTagDescriptions = (tags) => tags.reduce(
47
+ (acc, tag) => {
48
+ if (!tag.description) {
49
+ return acc;
50
+ }
51
+ acc[tag.tag] = tag.description;
52
+ return acc;
53
+ },
54
+ {}
55
+ );
56
+ const getFilteredTagOptions = ({
57
+ allowCreation,
58
+ getCreateOptionLabel,
59
+ inputValue,
60
+ maximumTags,
61
+ options,
62
+ selectedTags
63
+ }) => {
64
+ const trimmedInput = inputValue.trim();
65
+ const normalizedInput = trimmedInput.toLocaleLowerCase();
66
+ const filteredOptions = options.filter((option) => {
67
+ if (!normalizedInput) {
68
+ return true;
69
+ }
70
+ return option.toLocaleLowerCase().includes(normalizedInput);
71
+ });
72
+ if (!allowCreation || !trimmedInput || selectedTags.length >= maximumTags) {
73
+ return filteredOptions;
74
+ }
75
+ const normalizedTag = normalizeTagInput(trimmedInput);
76
+ if (!normalizedTag) {
77
+ return filteredOptions;
78
+ }
79
+ const tagAlreadyExists = options.some(
80
+ (option) => option.toLocaleLowerCase() === normalizedTag
81
+ );
82
+ const tagAlreadySelected = selectedTags.some(
83
+ (tag) => tag.toLocaleLowerCase() === normalizedTag
84
+ );
85
+ if (tagAlreadyExists || tagAlreadySelected) {
86
+ return filteredOptions;
87
+ }
88
+ return [
89
+ ...filteredOptions,
90
+ {
91
+ inputValue: trimmedInput,
92
+ label: getCreateOptionLabel(trimmedInput)
93
+ }
94
+ ];
95
+ };
15
96
  const TagInput = forwardRef((props, _ref) => {
16
97
  const {
17
98
  value,
@@ -35,7 +116,10 @@ const TagInput = forwardRef((props, _ref) => {
35
116
  );
36
117
  const [loading, setLoading] = useState(true);
37
118
  const [suggestedTags, setSuggestedTags] = useState([]);
38
- const [, setLoadingSuggestions] = useState(false);
119
+ const [loadingSuggestions, setLoadingSuggestions] = useState(false);
120
+ const [inputValue, setInputValue] = useState("");
121
+ const searchCache = useRef(/* @__PURE__ */ new Map());
122
+ const activeRequest = useRef(0);
39
123
  useEffect(() => {
40
124
  if (allowCreate !== void 0) {
41
125
  return;
@@ -75,37 +159,73 @@ const TagInput = forwardRef((props, _ref) => {
75
159
  () => config.getOptionalStringArray("qeta.tags.allowedTags") ?? [],
76
160
  [config]
77
161
  );
162
+ const selectedTags = useMemo(() => value ?? [], [value]);
78
163
  const maximumTags = useMemo(
79
164
  () => config.getOptionalNumber("qeta.tags.max") ?? 5,
80
165
  [config]
81
166
  );
82
167
  const [availableTags, setAvailableTags] = useState([]);
83
168
  const [tagDescriptions, setTagDescriptions] = useState({});
84
- useEffect(() => {
85
- qetaApi.getTags().catch((_) => setAvailableTags([])).then((data) => {
86
- setLoading(false);
87
- if (!data) {
169
+ const loadTags = useMemo(
170
+ () => async (term) => {
171
+ const trimmed = term.trim();
172
+ const cacheKey = trimmed.toLocaleLowerCase();
173
+ const matchingAllowedTags = getMatchingAllowedTags(allowedTags, trimmed);
174
+ const cached = searchCache.current.get(cacheKey);
175
+ if (cached) {
176
+ setLoading(false);
177
+ setAvailableTags(
178
+ (prev) => mergeTags(prev, cached.tags, matchingAllowedTags)
179
+ );
180
+ setTagDescriptions((prev) => ({
181
+ ...prev,
182
+ ...cached.descriptions
183
+ }));
88
184
  return;
89
185
  }
90
- const uniqueTags = [
91
- .../* @__PURE__ */ new Set([...allowedTags, ...data.tags.map((tag) => tag.tag)])
92
- ].sort((a, b) => a.localeCompare(b));
93
- setAvailableTags(uniqueTags);
94
- setTagDescriptions(
95
- data.tags.reduce(
96
- (acc, tag) => {
97
- if (!tag.description) {
98
- return acc;
99
- }
100
- acc[tag.tag] = tag.description;
101
- return acc;
102
- },
103
- {}
104
- )
105
- );
106
- });
107
- }, [qetaApi, allowCreation, allowedTags]);
108
- if (!allowCreation && availableTags.length === 0) {
186
+ const requestId = activeRequest.current + 1;
187
+ activeRequest.current = requestId;
188
+ setLoading(true);
189
+ try {
190
+ const data = await qetaApi.getTags(getTagSearchRequest(trimmed));
191
+ const remoteTags = data.tags.map((tag) => tag.tag);
192
+ const descriptions = getTagDescriptions(data.tags);
193
+ const nextTags = mergeTags(remoteTags, matchingAllowedTags);
194
+ if (activeRequest.current !== requestId) {
195
+ return;
196
+ }
197
+ searchCache.current.set(cacheKey, {
198
+ tags: remoteTags,
199
+ descriptions
200
+ });
201
+ setAvailableTags((prev) => mergeTags(prev, nextTags));
202
+ setTagDescriptions((prev) => ({ ...prev, ...descriptions }));
203
+ } catch {
204
+ if (activeRequest.current === requestId) {
205
+ setAvailableTags((prev) => mergeTags(prev, matchingAllowedTags));
206
+ }
207
+ } finally {
208
+ if (activeRequest.current === requestId) {
209
+ setLoading(false);
210
+ }
211
+ }
212
+ },
213
+ [allowedTags, qetaApi]
214
+ );
215
+ useEffect(() => {
216
+ searchCache.current.clear();
217
+ activeRequest.current += 1;
218
+ setLoading(true);
219
+ loadTags("");
220
+ }, [loadTags]);
221
+ useDebounce(
222
+ () => {
223
+ loadTags(inputValue);
224
+ },
225
+ 300,
226
+ [inputValue, loadTags]
227
+ );
228
+ if (allowCreation === false && !loading && availableTags.length === 0) {
109
229
  return null;
110
230
  }
111
231
  const getHelperText = () => {
@@ -132,18 +252,41 @@ const TagInput = forwardRef((props, _ref) => {
132
252
  multiple: true,
133
253
  id: "tags-select",
134
254
  className: "qetaTagInput",
135
- value: value || [],
255
+ value: selectedTags,
136
256
  loading,
137
257
  autoHighlight: true,
138
258
  autoComplete: true,
139
259
  loadingText: t("common.loading"),
140
- options: availableTags ?? [],
260
+ options: availableTags,
141
261
  freeSolo: allowCreation,
142
262
  handleHomeEndKeys: true,
263
+ limitTags: maximumTags,
264
+ getOptionLabel: getTagOptionLabel,
265
+ filterOptions: (options, state) => getFilteredTagOptions({
266
+ allowCreation: allowCreation === true,
267
+ getCreateOptionLabel: (tag) => t("tagsInput.createOption", { tag }),
268
+ inputValue: state.inputValue,
269
+ maximumTags,
270
+ options: options.filter(
271
+ (option) => typeof option === "string"
272
+ ),
273
+ selectedTags
274
+ }),
275
+ inputValue,
276
+ onInputChange: (_event, nextValue, reason) => {
277
+ if (reason === "reset") {
278
+ setInputValue("");
279
+ return;
280
+ }
281
+ setInputValue(nextValue);
282
+ },
143
283
  ListboxComponent: AutocompleteListboxComponent,
144
284
  disableListWrap: true,
145
285
  style,
146
286
  renderOption: (option) => {
287
+ if (isCreateTagOption(option)) {
288
+ return option.label;
289
+ }
147
290
  if (tagDescriptions[option]) {
148
291
  return /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(
149
292
  Tooltip,
@@ -158,8 +301,9 @@ const TagInput = forwardRef((props, _ref) => {
158
301
  return option;
159
302
  },
160
303
  onChange: (_e, newValue) => {
161
- const tags = filterTags(newValue);
162
- if (tags && tags.length <= maximumTags && tags.length === newValue.length) {
304
+ const nextValues = newValue.map(getTagOptionValue);
305
+ const tags = filterTags(nextValues);
306
+ if (tags && tags.length <= maximumTags && tags.length === nextValues.length) {
163
307
  onChange(tags);
164
308
  }
165
309
  },
@@ -171,7 +315,7 @@ const TagInput = forwardRef((props, _ref) => {
171
315
  margin: "normal",
172
316
  label: label ?? t("tagsInput.label"),
173
317
  placeholder: t("tagsInput.placeholder"),
174
- helperText: error !== void 0 ? error.message : getHelperText(),
318
+ helperText: error ? error.message : getHelperText(),
175
319
  FormHelperTextProps: {
176
320
  style: { marginLeft: "0.2em" }
177
321
  },
@@ -197,7 +341,7 @@ const TagInput = forwardRef((props, _ref) => {
197
341
  size: "small",
198
342
  onClick: () => handleSuggestedTagClick(tag),
199
343
  style: { margin: "0 4px 4px 0" },
200
- disabled: value?.includes(tag) || (value?.length ?? 0) >= maximumTags
344
+ disabled: value?.includes(tag) || (value?.length ?? 0) >= maximumTags || loadingSuggestions
201
345
  },
202
346
  tag
203
347
  )) })
@@ -1 +1 @@
1
- {"version":3,"file":"TagInput.esm.js","sources":["../../../src/components/PostForm/TagInput.tsx"],"sourcesContent":["import { Autocomplete } from '@material-ui/lab';\nimport {\n Box,\n Chip,\n CircularProgress,\n TextField,\n Tooltip,\n Typography,\n} from '@material-ui/core';\nimport {\n ComponentType,\n CSSProperties,\n forwardRef,\n HTMLAttributes,\n useEffect,\n useMemo,\n useState,\n} from 'react';\nimport { qetaApiRef } from '../../api';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport {\n filterTags,\n qetaCreateTagPermission,\n} from '@drodil/backstage-plugin-qeta-common';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport { qetaTranslationRef } from '../../translation.ts';\nimport { FieldError } from 'react-hook-form';\nimport { AutocompleteListboxComponent } from './AutocompleteListComponent';\nimport { permissionApiRef } from '@backstage/plugin-permission-react';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\nimport { useDebounce } from 'react-use';\n\nexport const TagInput = forwardRef<\n any,\n {\n value?: string[];\n onChange: (value: string[]) => void;\n error?: FieldError;\n allowCreate?: boolean;\n hideHelpText?: boolean;\n style?: CSSProperties;\n title?: string;\n name?: string;\n content?: string;\n entities?: string[];\n label?: string;\n }\n>((props, _ref) => {\n const {\n value,\n onChange,\n error,\n allowCreate,\n hideHelpText = false,\n style,\n name = 'tags',\n title,\n content,\n entities,\n label,\n } = props;\n const qetaApi = useApi(qetaApiRef);\n const config = useApi(configApiRef);\n const permissions = useApi(permissionApiRef);\n const { t } = useTranslationRef(qetaTranslationRef);\n const [allowCreation, setAllowCreation] = useState<boolean | undefined>(\n allowCreate,\n );\n const [loading, setLoading] = useState(true);\n const [suggestedTags, setSuggestedTags] = useState<string[]>([]);\n const [, setLoadingSuggestions] = useState(false);\n\n useEffect(() => {\n if (allowCreate !== undefined) {\n return;\n }\n\n if (config.getOptionalBoolean('qeta.permissions') === true) {\n permissions\n .authorize({\n permission: qetaCreateTagPermission,\n })\n .catch(_ => setAllowCreation(false))\n .then(res => {\n if (res && res.result === AuthorizeResult.ALLOW) {\n setAllowCreation(true);\n } else {\n setAllowCreation(false);\n }\n });\n } else {\n setAllowCreation(\n config.getOptionalBoolean('qeta.tags.allowCreation') ?? true,\n );\n }\n }, [config, permissions, allowCreate]);\n\n useDebounce(\n () => {\n if (title && content) {\n setLoadingSuggestions(true);\n qetaApi\n .getTagSuggestions({ title, content, entities, limit: 5 })\n .then(response => {\n setSuggestedTags(response.tags);\n })\n .catch(() => {\n // Ignore errors\n })\n .finally(() => {\n setLoadingSuggestions(false);\n });\n }\n },\n 2000,\n [title, content, entities, qetaApi],\n );\n\n const allowedTags = useMemo(\n () => config.getOptionalStringArray('qeta.tags.allowedTags') ?? [],\n [config],\n );\n const maximumTags = useMemo(\n () => config.getOptionalNumber('qeta.tags.max') ?? 5,\n [config],\n );\n\n const [availableTags, setAvailableTags] = useState<string[]>([]);\n const [tagDescriptions, setTagDescriptions] = useState<\n Record<string, string>\n >({});\n useEffect(() => {\n qetaApi\n .getTags()\n .catch(_ => setAvailableTags([]))\n .then(data => {\n setLoading(false);\n if (!data) {\n return;\n }\n\n const uniqueTags = [\n ...new Set([...allowedTags, ...data.tags.map(tag => tag.tag)]),\n ].sort((a, b) => a.localeCompare(b));\n setAvailableTags(uniqueTags);\n setTagDescriptions(\n data.tags.reduce(\n (acc, tag) => {\n if (!tag.description) {\n return acc;\n }\n acc[tag.tag] = tag.description;\n return acc;\n },\n {} as Record<string, string>,\n ),\n );\n });\n }, [qetaApi, allowCreation, allowedTags]);\n\n if (!allowCreation && availableTags.length === 0) {\n return null;\n }\n\n const getHelperText = () => {\n if (hideHelpText) {\n return '';\n }\n\n const baseText = t('tagsInput.helperText', {\n max: maximumTags.toString(10),\n });\n\n if (!allowCreation) {\n return baseText;\n }\n return `${baseText}. ${t('tagsInput.allowAddHelperText')}`;\n };\n\n const handleSuggestedTagClick = (tag: string) => {\n if (value && value.length < maximumTags && !value.includes(tag)) {\n onChange([...value, tag]);\n }\n };\n\n return (\n <Box>\n <Autocomplete\n multiple\n id=\"tags-select\"\n className=\"qetaTagInput\"\n value={value || []}\n loading={loading}\n autoHighlight\n autoComplete\n loadingText={t('common.loading')}\n options={availableTags ?? []}\n freeSolo={allowCreation}\n handleHomeEndKeys\n ListboxComponent={\n AutocompleteListboxComponent as ComponentType<\n HTMLAttributes<HTMLElement>\n >\n }\n disableListWrap\n style={style}\n renderOption={option => {\n if (tagDescriptions[option]) {\n return (\n <span key={option}>\n <Tooltip\n arrow\n placement=\"right\"\n title={<Typography>{tagDescriptions[option]}</Typography>}\n >\n <span>{option}</span>\n </Tooltip>\n </span>\n );\n }\n return option;\n }}\n onChange={(_e, newValue) => {\n const tags = filterTags(newValue);\n if (\n tags &&\n tags.length <= maximumTags &&\n tags.length === newValue.length\n ) {\n onChange(tags);\n }\n }}\n renderInput={params => (\n <TextField\n {...params}\n variant=\"outlined\"\n margin=\"normal\"\n label={label ?? t('tagsInput.label')}\n placeholder={t('tagsInput.placeholder')}\n helperText={error !== undefined ? error.message : getHelperText()}\n FormHelperTextProps={{\n style: { marginLeft: '0.2em' },\n }}\n name={name}\n InputProps={{\n ...params.InputProps,\n endAdornment: (\n <>\n {loading ? (\n <CircularProgress color=\"inherit\" size={20} />\n ) : null}\n {params.InputProps.endAdornment}\n </>\n ),\n }}\n error={error !== undefined}\n />\n )}\n />\n {suggestedTags?.length > 0 && (\n <Box style={{ marginLeft: '4px' }}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n {t('tagsInput.suggestedTags')}\n </Typography>\n <Box mt={0.5}>\n {suggestedTags.map(tag => (\n <Chip\n key={tag}\n label={tag}\n size=\"small\"\n onClick={() => handleSuggestedTagClick(tag)}\n style={{ margin: '0 4px 4px 0' }}\n disabled={\n value?.includes(tag) || (value?.length ?? 0) >= maximumTags\n }\n />\n ))}\n </Box>\n </Box>\n )}{' '}\n </Box>\n );\n});\n\nTagInput.displayName = 'TagInput';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TagInput.esm.js","sources":["../../../src/components/PostForm/TagInput.tsx"],"sourcesContent":["import { Autocomplete } from '@material-ui/lab';\nimport {\n Box,\n Chip,\n CircularProgress,\n TextField,\n Tooltip,\n Typography,\n} from '@material-ui/core';\nimport {\n ComponentType,\n CSSProperties,\n forwardRef,\n HTMLAttributes,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { qetaApiRef } from '../../api';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport {\n filterTags,\n qetaCreateTagPermission,\n type TagsQuery,\n} from '@drodil/backstage-plugin-qeta-common';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport { qetaTranslationRef } from '../../translation.ts';\nimport { FieldError } from 'react-hook-form';\nimport { AutocompleteListboxComponent } from './AutocompleteListComponent';\nimport { permissionApiRef } from '@backstage/plugin-permission-react';\nimport { AuthorizeResult } from '@backstage/plugin-permission-common';\nimport { useDebounce } from 'react-use';\n\nconst TAG_SEARCH_LIMIT = 25;\n\ntype CreateTagOption = {\n inputValue: string;\n label: string;\n};\n\ntype TagAutocompleteOption = string | CreateTagOption;\n\nconst isCreateTagOption = (\n option: TagAutocompleteOption,\n): option is CreateTagOption => typeof option !== 'string';\n\nconst getTagOptionValue = (option: TagAutocompleteOption) =>\n isCreateTagOption(option) ? option.inputValue : option;\n\nconst getTagOptionLabel = (option: TagAutocompleteOption) =>\n isCreateTagOption(option) ? option.label : option;\n\nconst normalizeTagInput = (input: string) => filterTags([input])[0];\n\nconst getTagSearchRequest = (term: string): TagsQuery => ({\n limit: TAG_SEARCH_LIMIT,\n orderBy: 'postsCount',\n order: 'desc',\n ...(term ? { searchQuery: term } : {}),\n});\n\nconst getMatchingAllowedTags = (allowedTags: string[], term: string) => {\n const normalizedTerm = term.trim().toLocaleLowerCase();\n\n if (!normalizedTerm) {\n return allowedTags;\n }\n\n return allowedTags.filter(tag =>\n tag.toLocaleLowerCase().includes(normalizedTerm),\n );\n};\n\nconst mergeTags = (...groups: string[][]) => {\n const seen = new Set<string>();\n const merged: string[] = [];\n\n for (const tag of groups.flat()) {\n if (!seen.has(tag)) {\n seen.add(tag);\n merged.push(tag);\n }\n }\n\n return merged;\n};\n\nconst getTagDescriptions = (\n tags: Array<{ tag: string; description?: string }>,\n) =>\n tags.reduce(\n (acc, tag) => {\n if (!tag.description) {\n return acc;\n }\n acc[tag.tag] = tag.description;\n return acc;\n },\n {} as Record<string, string>,\n );\n\nconst getFilteredTagOptions = ({\n allowCreation,\n getCreateOptionLabel,\n inputValue,\n maximumTags,\n options,\n selectedTags,\n}: {\n allowCreation: boolean;\n getCreateOptionLabel: (tag: string) => string;\n inputValue: string;\n maximumTags: number;\n options: string[];\n selectedTags: string[];\n}): TagAutocompleteOption[] => {\n const trimmedInput = inputValue.trim();\n const normalizedInput = trimmedInput.toLocaleLowerCase();\n const filteredOptions = options.filter(option => {\n if (!normalizedInput) {\n return true;\n }\n\n return option.toLocaleLowerCase().includes(normalizedInput);\n });\n\n if (!allowCreation || !trimmedInput || selectedTags.length >= maximumTags) {\n return filteredOptions;\n }\n\n const normalizedTag = normalizeTagInput(trimmedInput);\n if (!normalizedTag) {\n return filteredOptions;\n }\n\n const tagAlreadyExists = options.some(\n option => option.toLocaleLowerCase() === normalizedTag,\n );\n const tagAlreadySelected = selectedTags.some(\n tag => tag.toLocaleLowerCase() === normalizedTag,\n );\n\n if (tagAlreadyExists || tagAlreadySelected) {\n return filteredOptions;\n }\n\n return [\n ...filteredOptions,\n {\n inputValue: trimmedInput,\n label: getCreateOptionLabel(trimmedInput),\n },\n ];\n};\n\nexport const TagInput = forwardRef<\n any,\n {\n value?: string[];\n onChange: (value: string[]) => void;\n error?: FieldError;\n allowCreate?: boolean;\n hideHelpText?: boolean;\n style?: CSSProperties;\n title?: string;\n name?: string;\n content?: string;\n entities?: string[];\n label?: string;\n }\n>((props, _ref) => {\n const {\n value,\n onChange,\n error,\n allowCreate,\n hideHelpText = false,\n style,\n name = 'tags',\n title,\n content,\n entities,\n label,\n } = props;\n const qetaApi = useApi(qetaApiRef);\n const config = useApi(configApiRef);\n const permissions = useApi(permissionApiRef);\n const { t } = useTranslationRef(qetaTranslationRef);\n const [allowCreation, setAllowCreation] = useState<boolean | undefined>(\n allowCreate,\n );\n const [loading, setLoading] = useState(true);\n const [suggestedTags, setSuggestedTags] = useState<string[]>([]);\n const [loadingSuggestions, setLoadingSuggestions] = useState(false);\n const [inputValue, setInputValue] = useState('');\n const searchCache = useRef<\n Map<string, { tags: string[]; descriptions: Record<string, string> }>\n >(new Map());\n const activeRequest = useRef(0);\n\n useEffect(() => {\n if (allowCreate !== undefined) {\n return;\n }\n\n if (config.getOptionalBoolean('qeta.permissions') === true) {\n permissions\n .authorize({\n permission: qetaCreateTagPermission,\n })\n .catch(_ => setAllowCreation(false))\n .then(res => {\n if (res && res.result === AuthorizeResult.ALLOW) {\n setAllowCreation(true);\n } else {\n setAllowCreation(false);\n }\n });\n } else {\n setAllowCreation(\n config.getOptionalBoolean('qeta.tags.allowCreation') ?? true,\n );\n }\n }, [config, permissions, allowCreate]);\n\n useDebounce(\n () => {\n if (title && content) {\n setLoadingSuggestions(true);\n qetaApi\n .getTagSuggestions({ title, content, entities, limit: 5 })\n .then(response => {\n setSuggestedTags(response.tags);\n })\n .catch(() => {\n // Ignore errors\n })\n .finally(() => {\n setLoadingSuggestions(false);\n });\n }\n },\n 2000,\n [title, content, entities, qetaApi],\n );\n\n const allowedTags = useMemo(\n () => config.getOptionalStringArray('qeta.tags.allowedTags') ?? [],\n [config],\n );\n const selectedTags = useMemo(() => value ?? [], [value]);\n const maximumTags = useMemo(\n () => config.getOptionalNumber('qeta.tags.max') ?? 5,\n [config],\n );\n\n const [availableTags, setAvailableTags] = useState<string[]>([]);\n const [tagDescriptions, setTagDescriptions] = useState<\n Record<string, string>\n >({});\n\n const loadTags = useMemo(\n () => async (term: string) => {\n const trimmed = term.trim();\n const cacheKey = trimmed.toLocaleLowerCase();\n const matchingAllowedTags = getMatchingAllowedTags(allowedTags, trimmed);\n const cached = searchCache.current.get(cacheKey);\n\n if (cached) {\n setLoading(false);\n setAvailableTags(prev =>\n mergeTags(prev, cached.tags, matchingAllowedTags),\n );\n setTagDescriptions(prev => ({\n ...prev,\n ...cached.descriptions,\n }));\n return;\n }\n\n const requestId = activeRequest.current + 1;\n activeRequest.current = requestId;\n setLoading(true);\n\n try {\n const data = await qetaApi.getTags(getTagSearchRequest(trimmed));\n const remoteTags = data.tags.map(tag => tag.tag);\n const descriptions = getTagDescriptions(data.tags);\n const nextTags = mergeTags(remoteTags, matchingAllowedTags);\n\n if (activeRequest.current !== requestId) {\n return;\n }\n\n searchCache.current.set(cacheKey, {\n tags: remoteTags,\n descriptions,\n });\n setAvailableTags(prev => mergeTags(prev, nextTags));\n setTagDescriptions(prev => ({ ...prev, ...descriptions }));\n } catch {\n if (activeRequest.current === requestId) {\n setAvailableTags(prev => mergeTags(prev, matchingAllowedTags));\n }\n } finally {\n if (activeRequest.current === requestId) {\n setLoading(false);\n }\n }\n },\n [allowedTags, qetaApi],\n );\n\n useEffect(() => {\n searchCache.current.clear();\n activeRequest.current += 1;\n setLoading(true);\n loadTags('');\n }, [loadTags]);\n\n useDebounce(\n () => {\n loadTags(inputValue);\n },\n 300,\n [inputValue, loadTags],\n );\n\n if (allowCreation === false && !loading && availableTags.length === 0) {\n return null;\n }\n\n const getHelperText = () => {\n if (hideHelpText) {\n return '';\n }\n\n const baseText = t('tagsInput.helperText', {\n max: maximumTags.toString(10),\n });\n\n if (!allowCreation) {\n return baseText;\n }\n return `${baseText}. ${t('tagsInput.allowAddHelperText')}`;\n };\n\n const handleSuggestedTagClick = (tag: string) => {\n if (value && value.length < maximumTags && !value.includes(tag)) {\n onChange([...value, tag]);\n }\n };\n\n return (\n <Box>\n <Autocomplete\n multiple\n id=\"tags-select\"\n className=\"qetaTagInput\"\n value={selectedTags as TagAutocompleteOption[]}\n loading={loading}\n autoHighlight\n autoComplete\n loadingText={t('common.loading')}\n options={availableTags as TagAutocompleteOption[]}\n freeSolo={allowCreation}\n handleHomeEndKeys\n limitTags={maximumTags}\n getOptionLabel={getTagOptionLabel}\n filterOptions={(options, state) =>\n getFilteredTagOptions({\n allowCreation: allowCreation === true,\n getCreateOptionLabel: tag =>\n t('tagsInput.createOption' as never, { tag } as never) as string,\n inputValue: state.inputValue,\n maximumTags,\n options: options.filter(\n (option): option is string => typeof option === 'string',\n ),\n selectedTags,\n })\n }\n inputValue={inputValue}\n onInputChange={(_event, nextValue, reason) => {\n if (reason === 'reset') {\n setInputValue('');\n return;\n }\n\n setInputValue(nextValue);\n }}\n ListboxComponent={\n AutocompleteListboxComponent as ComponentType<\n HTMLAttributes<HTMLElement>\n >\n }\n disableListWrap\n style={style}\n renderOption={option => {\n if (isCreateTagOption(option)) {\n return option.label;\n }\n\n if (tagDescriptions[option]) {\n return (\n <span key={option}>\n <Tooltip\n arrow\n placement=\"right\"\n title={<Typography>{tagDescriptions[option]}</Typography>}\n >\n <span>{option}</span>\n </Tooltip>\n </span>\n );\n }\n return option;\n }}\n onChange={(_e, newValue) => {\n const nextValues = newValue.map(getTagOptionValue);\n const tags = filterTags(nextValues);\n if (\n tags &&\n tags.length <= maximumTags &&\n tags.length === nextValues.length\n ) {\n onChange(tags);\n }\n }}\n renderInput={params => (\n <TextField\n {...params}\n variant=\"outlined\"\n margin=\"normal\"\n label={label ?? t('tagsInput.label')}\n placeholder={t('tagsInput.placeholder')}\n helperText={error ? error.message : getHelperText()}\n FormHelperTextProps={{\n style: { marginLeft: '0.2em' },\n }}\n name={name}\n InputProps={{\n ...params.InputProps,\n endAdornment: (\n <>\n {loading ? (\n <CircularProgress color=\"inherit\" size={20} />\n ) : null}\n {params.InputProps.endAdornment}\n </>\n ),\n }}\n error={error !== undefined}\n />\n )}\n />\n {suggestedTags?.length > 0 && (\n <Box style={{ marginLeft: '4px' }}>\n <Typography variant=\"caption\" color=\"textSecondary\">\n {t('tagsInput.suggestedTags')}\n </Typography>\n <Box mt={0.5}>\n {suggestedTags.map(tag => (\n <Chip\n key={tag}\n label={tag}\n size=\"small\"\n onClick={() => handleSuggestedTagClick(tag)}\n style={{ margin: '0 4px 4px 0' }}\n disabled={\n value?.includes(tag) ||\n (value?.length ?? 0) >= maximumTags ||\n loadingSuggestions\n }\n />\n ))}\n </Box>\n </Box>\n )}{' '}\n </Box>\n );\n});\n\nTagInput.displayName = 'TagInput';\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1040,6 +1040,7 @@ declare const qetaTranslationRef: _backstage_frontend_plugin_api.TranslationRef<
1040
1040
  readonly "tagsInput.helperText": "Add up to {{max}} tags";
1041
1041
  readonly "tagsInput.minimumError": "Please add at least {{min}} tags";
1042
1042
  readonly "tagsInput.allowAddHelperText": "You can create new tags by typing the tag and pressing enter";
1043
+ readonly "tagsInput.createOption": "Add {{tag}}";
1043
1044
  readonly "tagsInput.suggestedTags": "Suggested tags";
1044
1045
  readonly "askPage.title.newQuestion": "Ask a question";
1045
1046
  readonly "askPage.title.existingQuestion": "Edit question";
@@ -241,6 +241,7 @@ const qetaTranslationRef = createTranslationRef({
241
241
  placeholder: "Type or select tags",
242
242
  helperText: "Add up to {{max}} tags",
243
243
  allowAddHelperText: "You can create new tags by typing the tag and pressing enter",
244
+ createOption: "Add {{tag}}",
244
245
  minimumError: "Please add at least {{min}} tags",
245
246
  suggestedTags: "Suggested tags"
246
247
  },