strapi-plugin-tags-custom-field 1.0.1 → 1.0.4
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.
- package/README.md +3 -4
- package/dist/admin/{TagsInput-BGUydL2t.js → TagsInput-Bed2AKfo.js} +78 -80
- package/dist/admin/{TagsInput-CCehyU5Y.mjs → TagsInput-C96IrAdH.mjs} +78 -80
- package/dist/admin/{en-C_FVtF2u.js → en-B10Zs0Lz.js} +2 -2
- package/dist/admin/{en-BhiXf-Vq.mjs → en-vnGnUwx5.mjs} +2 -2
- package/dist/admin/index.js +5 -5
- package/dist/admin/index.mjs +5 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,15 +25,14 @@ Restart your Strapi server after installation.
|
|
|
25
25
|
- `maxTags` (default: `20`): maximum number of tags.
|
|
26
26
|
- `maxTagLength` (default: `40`): maximum characters per tag.
|
|
27
27
|
- `allowDuplicates` (default: `false`): allow repeated tags.
|
|
28
|
-
- `separator` (
|
|
28
|
+
- `separator` (optional): character used to split typed/pasted values.
|
|
29
29
|
- `normalizeCase` (default: `none`): `none`, `lowercase`, or `UPPERCASE`.
|
|
30
30
|
|
|
31
31
|
## Input behavior
|
|
32
32
|
|
|
33
33
|
- `Enter` adds the current tag.
|
|
34
|
-
-
|
|
34
|
+
- If configured, the separator also adds the current tag.
|
|
35
35
|
- Paste supports multiple tags (newline or configured separator).
|
|
36
|
-
- `Backspace` on empty input removes the last tag.
|
|
37
36
|
|
|
38
37
|
## Data format
|
|
39
38
|
|
|
@@ -50,7 +49,7 @@ Example:
|
|
|
50
49
|
## Compatibility
|
|
51
50
|
|
|
52
51
|
- Strapi: `v5`
|
|
53
|
-
- Node.js: `>=18`
|
|
52
|
+
- Node.js: `>=18 <25` (18 to 24)
|
|
54
53
|
|
|
55
54
|
## Local development (plugin repo)
|
|
56
55
|
|
|
@@ -23,9 +23,8 @@ function _interopNamespace(e) {
|
|
|
23
23
|
return Object.freeze(n);
|
|
24
24
|
}
|
|
25
25
|
const React__namespace = /* @__PURE__ */ _interopNamespace(React);
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const DEFAULT_MAX_TAG_LENGTH = 40;
|
|
26
|
+
const DEFAULT_MAX_TAGS = 50;
|
|
27
|
+
const DEFAULT_MAX_TAG_LENGTH = 100;
|
|
29
28
|
const parseTagsValue = (value) => {
|
|
30
29
|
if (Array.isArray(value)) {
|
|
31
30
|
return value.map((item) => String(item).trim()).filter((item) => item.length > 0);
|
|
@@ -84,22 +83,24 @@ const parsePositiveInt = (value, fallbackValue) => {
|
|
|
84
83
|
};
|
|
85
84
|
const getSplitRegex = (separator) => {
|
|
86
85
|
const characters = Array.from(/* @__PURE__ */ new Set([separator, "\n", "\r"])).filter(
|
|
87
|
-
(character) => character.length > 0
|
|
86
|
+
(character) => typeof character === "string" && character.length > 0
|
|
88
87
|
);
|
|
89
88
|
const escapedCharacters = characters.map((character) => character.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("");
|
|
90
89
|
return new RegExp(`[${escapedCharacters}]+`);
|
|
91
90
|
};
|
|
92
91
|
const parseRawTags = (rawValue, separator, normalizeCase) => rawValue.split(getSplitRegex(separator)).map((tag) => normalizeTag(tag, normalizeCase)).filter((tag) => tag.length > 0);
|
|
93
92
|
const hasSplitCharacters = (value, separator) => [separator, "\n", "\r"].some(
|
|
94
|
-
(character) => character.length > 0 && value.includes(character)
|
|
93
|
+
(character) => typeof character === "string" && character.length > 0 && value.includes(character)
|
|
95
94
|
);
|
|
96
95
|
const TagsInput = React__namespace.forwardRef(
|
|
97
96
|
({
|
|
98
97
|
attribute,
|
|
98
|
+
description,
|
|
99
99
|
disabled = false,
|
|
100
100
|
error,
|
|
101
101
|
hint,
|
|
102
102
|
intlLabel,
|
|
103
|
+
label,
|
|
103
104
|
name,
|
|
104
105
|
onChange,
|
|
105
106
|
placeholder,
|
|
@@ -115,7 +116,7 @@ const TagsInput = React__namespace.forwardRef(
|
|
|
115
116
|
}, [value]);
|
|
116
117
|
const fieldType = attribute?.type ?? "json";
|
|
117
118
|
const options = attribute?.options ?? {};
|
|
118
|
-
const separator = typeof options.separator === "string" && options.separator.trim().length > 0 ? options.separator.trim().charAt(0) :
|
|
119
|
+
const separator = typeof options.separator === "string" && options.separator.trim().length > 0 ? options.separator.trim().charAt(0) : void 0;
|
|
119
120
|
const maxTags = parsePositiveInt(options.maxTags, DEFAULT_MAX_TAGS);
|
|
120
121
|
const maxTagLength = parsePositiveInt(
|
|
121
122
|
options.maxTagLength,
|
|
@@ -123,13 +124,24 @@ const TagsInput = React__namespace.forwardRef(
|
|
|
123
124
|
);
|
|
124
125
|
const allowDuplicates = parseBoolean(options.allowDuplicates, false);
|
|
125
126
|
const normalizeCase = normalizeCaseValue(options.normalizeCase);
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
const formatIntlMessage = React__namespace.useCallback(
|
|
128
|
+
(message) => {
|
|
129
|
+
if (!message?.id && !message?.defaultMessage) {
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
return formatMessage({
|
|
133
|
+
id: message.id ?? `${name}.label`,
|
|
134
|
+
defaultMessage: message.defaultMessage ?? "Tags"
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
[formatMessage, name]
|
|
138
|
+
);
|
|
139
|
+
const labelText = typeof label === "string" ? label : formatIntlMessage(label) ?? formatIntlMessage(intlLabel);
|
|
140
|
+
const labelMessage = labelText && labelText.trim().length > 0 ? labelText.trim() : "Tags";
|
|
141
|
+
const descriptionText = typeof description === "string" ? description : formatIntlMessage(description);
|
|
142
|
+
const hintMessage = descriptionText && descriptionText.trim().length > 0 ? descriptionText.trim() : hint ?? formatMessage({
|
|
131
143
|
id: "tags-input.hint",
|
|
132
|
-
defaultMessage: "Press Enter or type the separator to add tags. Paste multiple tags at once."
|
|
144
|
+
defaultMessage: separator ? "Press Enter or type the separator to add tags. Paste multiple tags at once." : "Press Enter to add tags. Paste multiple tags at once."
|
|
133
145
|
});
|
|
134
146
|
const shownError = error || localError;
|
|
135
147
|
const emitChange = React__namespace.useCallback(
|
|
@@ -149,58 +161,53 @@ const TagsInput = React__namespace.forwardRef(
|
|
|
149
161
|
if (rawTags.length === 0) {
|
|
150
162
|
return false;
|
|
151
163
|
}
|
|
164
|
+
const nextTags = [...tags];
|
|
152
165
|
let didChange = false;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
},
|
|
163
|
-
{ maxTags }
|
|
164
|
-
)
|
|
165
|
-
);
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
if (rawTag.length > maxTagLength) {
|
|
169
|
-
setLocalError(
|
|
170
|
-
formatMessage(
|
|
171
|
-
{
|
|
172
|
-
id: "tags-input.error.max-length",
|
|
173
|
-
defaultMessage: "Each tag must be at most {maxLength} characters."
|
|
174
|
-
},
|
|
175
|
-
{ maxLength: maxTagLength }
|
|
176
|
-
)
|
|
177
|
-
);
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
const duplicateIndex = nextTags.findIndex(
|
|
181
|
-
(tag) => tag.toLowerCase() === rawTag.toLowerCase()
|
|
166
|
+
let nextError;
|
|
167
|
+
for (const rawTag of rawTags) {
|
|
168
|
+
if (nextTags.length >= maxTags) {
|
|
169
|
+
nextError = formatMessage(
|
|
170
|
+
{
|
|
171
|
+
id: "tags-input.error.max-tags",
|
|
172
|
+
defaultMessage: "You can only add up to {maxTags} tags."
|
|
173
|
+
},
|
|
174
|
+
{ maxTags }
|
|
182
175
|
);
|
|
183
|
-
|
|
184
|
-
setLocalError(
|
|
185
|
-
formatMessage({
|
|
186
|
-
id: "tags-input.error.duplicate",
|
|
187
|
-
defaultMessage: "Duplicate tags are not allowed."
|
|
188
|
-
})
|
|
189
|
-
);
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
nextTags.push(rawTag);
|
|
193
|
-
didChange = true;
|
|
176
|
+
break;
|
|
194
177
|
}
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
|
|
178
|
+
if (rawTag.length > maxTagLength) {
|
|
179
|
+
nextError = formatMessage(
|
|
180
|
+
{
|
|
181
|
+
id: "tags-input.error.max-length",
|
|
182
|
+
defaultMessage: "Each tag must be at most {maxLength} characters."
|
|
183
|
+
},
|
|
184
|
+
{ maxLength: maxTagLength }
|
|
185
|
+
);
|
|
186
|
+
continue;
|
|
198
187
|
}
|
|
199
|
-
|
|
200
|
-
|
|
188
|
+
const duplicateIndex = nextTags.findIndex(
|
|
189
|
+
(tag) => tag.toLowerCase() === rawTag.toLowerCase()
|
|
190
|
+
);
|
|
191
|
+
if (duplicateIndex !== -1 && !allowDuplicates) {
|
|
192
|
+
nextError = formatMessage({
|
|
193
|
+
id: "tags-input.error.duplicate",
|
|
194
|
+
defaultMessage: "Duplicate tags are not allowed."
|
|
195
|
+
});
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
nextTags.push(rawTag);
|
|
199
|
+
didChange = true;
|
|
200
|
+
}
|
|
201
|
+
if (didChange) {
|
|
202
|
+
setTags(nextTags);
|
|
203
|
+
setLocalError(void 0);
|
|
204
|
+
emitChange(nextTags);
|
|
205
|
+
} else if (nextError) {
|
|
206
|
+
setLocalError(nextError);
|
|
207
|
+
}
|
|
201
208
|
return didChange;
|
|
202
209
|
},
|
|
203
|
-
[allowDuplicates, emitChange, formatMessage, maxTagLength, maxTags]
|
|
210
|
+
[allowDuplicates, emitChange, formatMessage, maxTagLength, maxTags, tags]
|
|
204
211
|
);
|
|
205
212
|
const commitDraft = React__namespace.useCallback(() => {
|
|
206
213
|
const parsedTags = parseRawTags(draft, separator, normalizeCase);
|
|
@@ -219,26 +226,11 @@ const TagsInput = React__namespace.forwardRef(
|
|
|
219
226
|
},
|
|
220
227
|
[emitChange]
|
|
221
228
|
);
|
|
222
|
-
const removeLastTag = React__namespace.useCallback(() => {
|
|
223
|
-
setTags((currentTags) => {
|
|
224
|
-
if (currentTags.length === 0) {
|
|
225
|
-
return currentTags;
|
|
226
|
-
}
|
|
227
|
-
const nextTags = currentTags.slice(0, currentTags.length - 1);
|
|
228
|
-
emitChange(nextTags);
|
|
229
|
-
setLocalError(void 0);
|
|
230
|
-
return nextTags;
|
|
231
|
-
});
|
|
232
|
-
}, [emitChange]);
|
|
233
229
|
const onKeyDown = (event) => {
|
|
234
|
-
if (event.key === "Enter" || event.key === separator) {
|
|
230
|
+
if (event.key === "Enter" || separator && event.key === separator) {
|
|
235
231
|
event.preventDefault();
|
|
236
232
|
commitDraft();
|
|
237
233
|
}
|
|
238
|
-
if (event.key === "Backspace" && draft.length === 0 && !disabled) {
|
|
239
|
-
event.preventDefault();
|
|
240
|
-
removeLastTag();
|
|
241
|
-
}
|
|
242
234
|
};
|
|
243
235
|
const onPaste = (event) => {
|
|
244
236
|
const pastedText = event.clipboardData.getData("text");
|
|
@@ -247,7 +239,10 @@ const TagsInput = React__namespace.forwardRef(
|
|
|
247
239
|
}
|
|
248
240
|
event.preventDefault();
|
|
249
241
|
const parsedTags = parseRawTags(pastedText, separator, normalizeCase);
|
|
250
|
-
addTags(parsedTags);
|
|
242
|
+
const added = addTags(parsedTags);
|
|
243
|
+
if (added) {
|
|
244
|
+
setDraft("");
|
|
245
|
+
}
|
|
251
246
|
};
|
|
252
247
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
253
248
|
designSystem.Field.Root,
|
|
@@ -258,7 +253,8 @@ const TagsInput = React__namespace.forwardRef(
|
|
|
258
253
|
hint: hintMessage,
|
|
259
254
|
error: shownError,
|
|
260
255
|
children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
|
|
261
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children:
|
|
256
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: labelMessage }),
|
|
257
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
|
|
262
258
|
tags.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
263
259
|
designSystem.Box,
|
|
264
260
|
{
|
|
@@ -311,13 +307,16 @@ const TagsInput = React__namespace.forwardRef(
|
|
|
311
307
|
onBlur: commitDraft,
|
|
312
308
|
onKeyDown,
|
|
313
309
|
onPaste,
|
|
314
|
-
placeholder: placeholder ?? formatMessage(
|
|
310
|
+
placeholder: placeholder ?? (separator ? formatMessage(
|
|
315
311
|
{
|
|
316
|
-
id: "tags-input.placeholder",
|
|
312
|
+
id: "tags-input.placeholder.with-separator",
|
|
317
313
|
defaultMessage: "Type a tag and press Enter or {separator} to add it"
|
|
318
314
|
},
|
|
319
315
|
{ separator }
|
|
320
|
-
)
|
|
316
|
+
) : formatMessage({
|
|
317
|
+
id: "tags-input.placeholder.without-separator",
|
|
318
|
+
defaultMessage: "Type a tag and press Enter to add it"
|
|
319
|
+
}))
|
|
321
320
|
}
|
|
322
321
|
),
|
|
323
322
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", gap: 2, children: [
|
|
@@ -343,7 +342,6 @@ const TagsInput = React__namespace.forwardRef(
|
|
|
343
342
|
}
|
|
344
343
|
)
|
|
345
344
|
] }),
|
|
346
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
|
|
347
345
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
|
|
348
346
|
] })
|
|
349
347
|
}
|
|
@@ -3,9 +3,8 @@ import * as React from "react";
|
|
|
3
3
|
import { Field, Flex, Box, Tag, TextInput, Typography } from "@strapi/design-system";
|
|
4
4
|
import { Cross } from "@strapi/icons";
|
|
5
5
|
import { useIntl } from "react-intl";
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const DEFAULT_MAX_TAG_LENGTH = 40;
|
|
6
|
+
const DEFAULT_MAX_TAGS = 50;
|
|
7
|
+
const DEFAULT_MAX_TAG_LENGTH = 100;
|
|
9
8
|
const parseTagsValue = (value) => {
|
|
10
9
|
if (Array.isArray(value)) {
|
|
11
10
|
return value.map((item) => String(item).trim()).filter((item) => item.length > 0);
|
|
@@ -64,22 +63,24 @@ const parsePositiveInt = (value, fallbackValue) => {
|
|
|
64
63
|
};
|
|
65
64
|
const getSplitRegex = (separator) => {
|
|
66
65
|
const characters = Array.from(/* @__PURE__ */ new Set([separator, "\n", "\r"])).filter(
|
|
67
|
-
(character) => character.length > 0
|
|
66
|
+
(character) => typeof character === "string" && character.length > 0
|
|
68
67
|
);
|
|
69
68
|
const escapedCharacters = characters.map((character) => character.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("");
|
|
70
69
|
return new RegExp(`[${escapedCharacters}]+`);
|
|
71
70
|
};
|
|
72
71
|
const parseRawTags = (rawValue, separator, normalizeCase) => rawValue.split(getSplitRegex(separator)).map((tag) => normalizeTag(tag, normalizeCase)).filter((tag) => tag.length > 0);
|
|
73
72
|
const hasSplitCharacters = (value, separator) => [separator, "\n", "\r"].some(
|
|
74
|
-
(character) => character.length > 0 && value.includes(character)
|
|
73
|
+
(character) => typeof character === "string" && character.length > 0 && value.includes(character)
|
|
75
74
|
);
|
|
76
75
|
const TagsInput = React.forwardRef(
|
|
77
76
|
({
|
|
78
77
|
attribute,
|
|
78
|
+
description,
|
|
79
79
|
disabled = false,
|
|
80
80
|
error,
|
|
81
81
|
hint,
|
|
82
82
|
intlLabel,
|
|
83
|
+
label,
|
|
83
84
|
name,
|
|
84
85
|
onChange,
|
|
85
86
|
placeholder,
|
|
@@ -95,7 +96,7 @@ const TagsInput = React.forwardRef(
|
|
|
95
96
|
}, [value]);
|
|
96
97
|
const fieldType = attribute?.type ?? "json";
|
|
97
98
|
const options = attribute?.options ?? {};
|
|
98
|
-
const separator = typeof options.separator === "string" && options.separator.trim().length > 0 ? options.separator.trim().charAt(0) :
|
|
99
|
+
const separator = typeof options.separator === "string" && options.separator.trim().length > 0 ? options.separator.trim().charAt(0) : void 0;
|
|
99
100
|
const maxTags = parsePositiveInt(options.maxTags, DEFAULT_MAX_TAGS);
|
|
100
101
|
const maxTagLength = parsePositiveInt(
|
|
101
102
|
options.maxTagLength,
|
|
@@ -103,13 +104,24 @@ const TagsInput = React.forwardRef(
|
|
|
103
104
|
);
|
|
104
105
|
const allowDuplicates = parseBoolean(options.allowDuplicates, false);
|
|
105
106
|
const normalizeCase = normalizeCaseValue(options.normalizeCase);
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
const formatIntlMessage = React.useCallback(
|
|
108
|
+
(message) => {
|
|
109
|
+
if (!message?.id && !message?.defaultMessage) {
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
return formatMessage({
|
|
113
|
+
id: message.id ?? `${name}.label`,
|
|
114
|
+
defaultMessage: message.defaultMessage ?? "Tags"
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
[formatMessage, name]
|
|
118
|
+
);
|
|
119
|
+
const labelText = typeof label === "string" ? label : formatIntlMessage(label) ?? formatIntlMessage(intlLabel);
|
|
120
|
+
const labelMessage = labelText && labelText.trim().length > 0 ? labelText.trim() : "Tags";
|
|
121
|
+
const descriptionText = typeof description === "string" ? description : formatIntlMessage(description);
|
|
122
|
+
const hintMessage = descriptionText && descriptionText.trim().length > 0 ? descriptionText.trim() : hint ?? formatMessage({
|
|
111
123
|
id: "tags-input.hint",
|
|
112
|
-
defaultMessage: "Press Enter or type the separator to add tags. Paste multiple tags at once."
|
|
124
|
+
defaultMessage: separator ? "Press Enter or type the separator to add tags. Paste multiple tags at once." : "Press Enter to add tags. Paste multiple tags at once."
|
|
113
125
|
});
|
|
114
126
|
const shownError = error || localError;
|
|
115
127
|
const emitChange = React.useCallback(
|
|
@@ -129,58 +141,53 @@ const TagsInput = React.forwardRef(
|
|
|
129
141
|
if (rawTags.length === 0) {
|
|
130
142
|
return false;
|
|
131
143
|
}
|
|
144
|
+
const nextTags = [...tags];
|
|
132
145
|
let didChange = false;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
},
|
|
143
|
-
{ maxTags }
|
|
144
|
-
)
|
|
145
|
-
);
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
if (rawTag.length > maxTagLength) {
|
|
149
|
-
setLocalError(
|
|
150
|
-
formatMessage(
|
|
151
|
-
{
|
|
152
|
-
id: "tags-input.error.max-length",
|
|
153
|
-
defaultMessage: "Each tag must be at most {maxLength} characters."
|
|
154
|
-
},
|
|
155
|
-
{ maxLength: maxTagLength }
|
|
156
|
-
)
|
|
157
|
-
);
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
const duplicateIndex = nextTags.findIndex(
|
|
161
|
-
(tag) => tag.toLowerCase() === rawTag.toLowerCase()
|
|
146
|
+
let nextError;
|
|
147
|
+
for (const rawTag of rawTags) {
|
|
148
|
+
if (nextTags.length >= maxTags) {
|
|
149
|
+
nextError = formatMessage(
|
|
150
|
+
{
|
|
151
|
+
id: "tags-input.error.max-tags",
|
|
152
|
+
defaultMessage: "You can only add up to {maxTags} tags."
|
|
153
|
+
},
|
|
154
|
+
{ maxTags }
|
|
162
155
|
);
|
|
163
|
-
|
|
164
|
-
setLocalError(
|
|
165
|
-
formatMessage({
|
|
166
|
-
id: "tags-input.error.duplicate",
|
|
167
|
-
defaultMessage: "Duplicate tags are not allowed."
|
|
168
|
-
})
|
|
169
|
-
);
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
nextTags.push(rawTag);
|
|
173
|
-
didChange = true;
|
|
156
|
+
break;
|
|
174
157
|
}
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
158
|
+
if (rawTag.length > maxTagLength) {
|
|
159
|
+
nextError = formatMessage(
|
|
160
|
+
{
|
|
161
|
+
id: "tags-input.error.max-length",
|
|
162
|
+
defaultMessage: "Each tag must be at most {maxLength} characters."
|
|
163
|
+
},
|
|
164
|
+
{ maxLength: maxTagLength }
|
|
165
|
+
);
|
|
166
|
+
continue;
|
|
178
167
|
}
|
|
179
|
-
|
|
180
|
-
|
|
168
|
+
const duplicateIndex = nextTags.findIndex(
|
|
169
|
+
(tag) => tag.toLowerCase() === rawTag.toLowerCase()
|
|
170
|
+
);
|
|
171
|
+
if (duplicateIndex !== -1 && !allowDuplicates) {
|
|
172
|
+
nextError = formatMessage({
|
|
173
|
+
id: "tags-input.error.duplicate",
|
|
174
|
+
defaultMessage: "Duplicate tags are not allowed."
|
|
175
|
+
});
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
nextTags.push(rawTag);
|
|
179
|
+
didChange = true;
|
|
180
|
+
}
|
|
181
|
+
if (didChange) {
|
|
182
|
+
setTags(nextTags);
|
|
183
|
+
setLocalError(void 0);
|
|
184
|
+
emitChange(nextTags);
|
|
185
|
+
} else if (nextError) {
|
|
186
|
+
setLocalError(nextError);
|
|
187
|
+
}
|
|
181
188
|
return didChange;
|
|
182
189
|
},
|
|
183
|
-
[allowDuplicates, emitChange, formatMessage, maxTagLength, maxTags]
|
|
190
|
+
[allowDuplicates, emitChange, formatMessage, maxTagLength, maxTags, tags]
|
|
184
191
|
);
|
|
185
192
|
const commitDraft = React.useCallback(() => {
|
|
186
193
|
const parsedTags = parseRawTags(draft, separator, normalizeCase);
|
|
@@ -199,26 +206,11 @@ const TagsInput = React.forwardRef(
|
|
|
199
206
|
},
|
|
200
207
|
[emitChange]
|
|
201
208
|
);
|
|
202
|
-
const removeLastTag = React.useCallback(() => {
|
|
203
|
-
setTags((currentTags) => {
|
|
204
|
-
if (currentTags.length === 0) {
|
|
205
|
-
return currentTags;
|
|
206
|
-
}
|
|
207
|
-
const nextTags = currentTags.slice(0, currentTags.length - 1);
|
|
208
|
-
emitChange(nextTags);
|
|
209
|
-
setLocalError(void 0);
|
|
210
|
-
return nextTags;
|
|
211
|
-
});
|
|
212
|
-
}, [emitChange]);
|
|
213
209
|
const onKeyDown = (event) => {
|
|
214
|
-
if (event.key === "Enter" || event.key === separator) {
|
|
210
|
+
if (event.key === "Enter" || separator && event.key === separator) {
|
|
215
211
|
event.preventDefault();
|
|
216
212
|
commitDraft();
|
|
217
213
|
}
|
|
218
|
-
if (event.key === "Backspace" && draft.length === 0 && !disabled) {
|
|
219
|
-
event.preventDefault();
|
|
220
|
-
removeLastTag();
|
|
221
|
-
}
|
|
222
214
|
};
|
|
223
215
|
const onPaste = (event) => {
|
|
224
216
|
const pastedText = event.clipboardData.getData("text");
|
|
@@ -227,7 +219,10 @@ const TagsInput = React.forwardRef(
|
|
|
227
219
|
}
|
|
228
220
|
event.preventDefault();
|
|
229
221
|
const parsedTags = parseRawTags(pastedText, separator, normalizeCase);
|
|
230
|
-
addTags(parsedTags);
|
|
222
|
+
const added = addTags(parsedTags);
|
|
223
|
+
if (added) {
|
|
224
|
+
setDraft("");
|
|
225
|
+
}
|
|
231
226
|
};
|
|
232
227
|
return /* @__PURE__ */ jsx(
|
|
233
228
|
Field.Root,
|
|
@@ -238,7 +233,8 @@ const TagsInput = React.forwardRef(
|
|
|
238
233
|
hint: hintMessage,
|
|
239
234
|
error: shownError,
|
|
240
235
|
children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
|
|
241
|
-
/* @__PURE__ */ jsx(Field.Label, { children:
|
|
236
|
+
/* @__PURE__ */ jsx(Field.Label, { children: labelMessage }),
|
|
237
|
+
/* @__PURE__ */ jsx(Field.Hint, {}),
|
|
242
238
|
tags.length > 0 ? /* @__PURE__ */ jsx(
|
|
243
239
|
Box,
|
|
244
240
|
{
|
|
@@ -291,13 +287,16 @@ const TagsInput = React.forwardRef(
|
|
|
291
287
|
onBlur: commitDraft,
|
|
292
288
|
onKeyDown,
|
|
293
289
|
onPaste,
|
|
294
|
-
placeholder: placeholder ?? formatMessage(
|
|
290
|
+
placeholder: placeholder ?? (separator ? formatMessage(
|
|
295
291
|
{
|
|
296
|
-
id: "tags-input.placeholder",
|
|
292
|
+
id: "tags-input.placeholder.with-separator",
|
|
297
293
|
defaultMessage: "Type a tag and press Enter or {separator} to add it"
|
|
298
294
|
},
|
|
299
295
|
{ separator }
|
|
300
|
-
)
|
|
296
|
+
) : formatMessage({
|
|
297
|
+
id: "tags-input.placeholder.without-separator",
|
|
298
|
+
defaultMessage: "Type a tag and press Enter to add it"
|
|
299
|
+
}))
|
|
301
300
|
}
|
|
302
301
|
),
|
|
303
302
|
/* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 2, children: [
|
|
@@ -323,7 +322,6 @@ const TagsInput = React.forwardRef(
|
|
|
323
322
|
}
|
|
324
323
|
)
|
|
325
324
|
] }),
|
|
326
|
-
/* @__PURE__ */ jsx(Field.Hint, {}),
|
|
327
325
|
/* @__PURE__ */ jsx(Field.Error, {})
|
|
328
326
|
] })
|
|
329
327
|
}
|
|
@@ -6,9 +6,9 @@ const en = {
|
|
|
6
6
|
"field.tags.description": "Store tags as a JSON array",
|
|
7
7
|
"field.tags.options.behavior.section": "Tag behavior",
|
|
8
8
|
"field.tags.options.maxTags.label": "Maximum tags",
|
|
9
|
-
"field.tags.options.maxTags.description": "Maximum number of tags allowed",
|
|
9
|
+
"field.tags.options.maxTags.description": "Maximum number of tags allowed (default 50)",
|
|
10
10
|
"field.tags.options.maxTagLength.label": "Maximum tag length",
|
|
11
|
-
"field.tags.options.maxTagLength.description": "Maximum number of characters per tag",
|
|
11
|
+
"field.tags.options.maxTagLength.description": "Maximum number of characters per tag (default 100)",
|
|
12
12
|
"field.tags.options.allowDuplicates.label": "Allow duplicates",
|
|
13
13
|
"field.tags.options.allowDuplicates.description": "Allow repeated tags in the same value",
|
|
14
14
|
"field.tags.options.input.section": "Input parsing",
|
|
@@ -4,9 +4,9 @@ const en = {
|
|
|
4
4
|
"field.tags.description": "Store tags as a JSON array",
|
|
5
5
|
"field.tags.options.behavior.section": "Tag behavior",
|
|
6
6
|
"field.tags.options.maxTags.label": "Maximum tags",
|
|
7
|
-
"field.tags.options.maxTags.description": "Maximum number of tags allowed",
|
|
7
|
+
"field.tags.options.maxTags.description": "Maximum number of tags allowed (default 50)",
|
|
8
8
|
"field.tags.options.maxTagLength.label": "Maximum tag length",
|
|
9
|
-
"field.tags.options.maxTagLength.description": "Maximum number of characters per tag",
|
|
9
|
+
"field.tags.options.maxTagLength.description": "Maximum number of characters per tag (default 100)",
|
|
10
10
|
"field.tags.options.allowDuplicates.label": "Allow duplicates",
|
|
11
11
|
"field.tags.options.allowDuplicates.description": "Allow repeated tags in the same value",
|
|
12
12
|
"field.tags.options.input.section": "Input parsing",
|
package/dist/admin/index.js
CHANGED
|
@@ -66,7 +66,7 @@ const index = {
|
|
|
66
66
|
},
|
|
67
67
|
icon: PluginIcon,
|
|
68
68
|
components: {
|
|
69
|
-
Input: async () => Promise.resolve().then(() => require("./TagsInput-
|
|
69
|
+
Input: async () => Promise.resolve().then(() => require("./TagsInput-Bed2AKfo.js")).then((module2) => ({
|
|
70
70
|
default: module2.TagsInput
|
|
71
71
|
}))
|
|
72
72
|
},
|
|
@@ -85,7 +85,7 @@ const index = {
|
|
|
85
85
|
},
|
|
86
86
|
description: {
|
|
87
87
|
id: getTranslation("field.tags.options.maxTags.description"),
|
|
88
|
-
defaultMessage: "Maximum number of tags allowed"
|
|
88
|
+
defaultMessage: "Maximum number of tags allowed (default 50)"
|
|
89
89
|
},
|
|
90
90
|
name: "options.maxTags",
|
|
91
91
|
type: "number",
|
|
@@ -100,7 +100,7 @@ const index = {
|
|
|
100
100
|
id: getTranslation(
|
|
101
101
|
"field.tags.options.maxTagLength.description"
|
|
102
102
|
),
|
|
103
|
-
defaultMessage: "Maximum number of characters per tag"
|
|
103
|
+
defaultMessage: "Maximum number of characters per tag (default 100)"
|
|
104
104
|
},
|
|
105
105
|
name: "options.maxTagLength",
|
|
106
106
|
type: "number",
|
|
@@ -140,7 +140,7 @@ const index = {
|
|
|
140
140
|
},
|
|
141
141
|
name: "options.separator",
|
|
142
142
|
type: "text",
|
|
143
|
-
value: "
|
|
143
|
+
value: ""
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
intlLabel: {
|
|
@@ -209,7 +209,7 @@ const index = {
|
|
|
209
209
|
return Promise.all(
|
|
210
210
|
locales.map(async (locale) => {
|
|
211
211
|
try {
|
|
212
|
-
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("./en-
|
|
212
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("./en-B10Zs0Lz.js")) }), `./translations/${locale}.json`, 3);
|
|
213
213
|
return { data, locale };
|
|
214
214
|
} catch {
|
|
215
215
|
return { data: {}, locale };
|
package/dist/admin/index.mjs
CHANGED
|
@@ -64,7 +64,7 @@ const index = {
|
|
|
64
64
|
},
|
|
65
65
|
icon: PluginIcon,
|
|
66
66
|
components: {
|
|
67
|
-
Input: async () => import("./TagsInput-
|
|
67
|
+
Input: async () => import("./TagsInput-C96IrAdH.mjs").then((module) => ({
|
|
68
68
|
default: module.TagsInput
|
|
69
69
|
}))
|
|
70
70
|
},
|
|
@@ -83,7 +83,7 @@ const index = {
|
|
|
83
83
|
},
|
|
84
84
|
description: {
|
|
85
85
|
id: getTranslation("field.tags.options.maxTags.description"),
|
|
86
|
-
defaultMessage: "Maximum number of tags allowed"
|
|
86
|
+
defaultMessage: "Maximum number of tags allowed (default 50)"
|
|
87
87
|
},
|
|
88
88
|
name: "options.maxTags",
|
|
89
89
|
type: "number",
|
|
@@ -98,7 +98,7 @@ const index = {
|
|
|
98
98
|
id: getTranslation(
|
|
99
99
|
"field.tags.options.maxTagLength.description"
|
|
100
100
|
),
|
|
101
|
-
defaultMessage: "Maximum number of characters per tag"
|
|
101
|
+
defaultMessage: "Maximum number of characters per tag (default 100)"
|
|
102
102
|
},
|
|
103
103
|
name: "options.maxTagLength",
|
|
104
104
|
type: "number",
|
|
@@ -138,7 +138,7 @@ const index = {
|
|
|
138
138
|
},
|
|
139
139
|
name: "options.separator",
|
|
140
140
|
type: "text",
|
|
141
|
-
value: "
|
|
141
|
+
value: ""
|
|
142
142
|
},
|
|
143
143
|
{
|
|
144
144
|
intlLabel: {
|
|
@@ -207,7 +207,7 @@ const index = {
|
|
|
207
207
|
return Promise.all(
|
|
208
208
|
locales.map(async (locale) => {
|
|
209
209
|
try {
|
|
210
|
-
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-
|
|
210
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-vnGnUwx5.mjs") }), `./translations/${locale}.json`, 3);
|
|
211
211
|
return { data, locale };
|
|
212
212
|
} catch {
|
|
213
213
|
return { data: {}, locale };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-tags-custom-field",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Custom field plugin for Strapi 5 to manage tags (array of strings) stored as JSON array.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"description": "Custom field for tags (array of strings) stored as JSON array"
|
|
75
75
|
},
|
|
76
76
|
"engines": {
|
|
77
|
-
"node": ">=18
|
|
77
|
+
"node": ">=18 <25"
|
|
78
78
|
},
|
|
79
79
|
"repository": {
|
|
80
80
|
"type": "git",
|