├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── admin
├── jsconfig.json
└── src
│ ├── components
│ ├── Initializer.jsx
│ ├── Input.jsx
│ └── styles
│ │ └── global.js
│ ├── index.js
│ ├── pages
│ ├── App.jsx
│ └── HomePage.jsx
│ ├── pluginId.js
│ ├── translations
│ └── en.json
│ └── utils
│ └── getTranslation.js
├── assets
├── banner.png
├── cta.png
├── demo.png
└── showcase.gif
├── package.json
├── server
└── src
│ ├── index.js
│ └── register.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .yarn
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | Thank you so much for your interest in contributing! All types of contributions are encouraged and valued. The Project Team looks forward to your contributions.
3 |
4 | ## Filing issues
5 | When in doubt, file an issue. We'd rather close a few duplicate issues than let a problem go unnoticed.
6 | Similarly, if you support a particular feature request, please let us know by commenting on the issue or [subscribing](https://help.github.com/articles/subscribing-to-conversations/) to the issue.
7 |
8 | If you are reporting a bug, please help speed up problem diagnosis by providing as much information as possible. Ideally, that would include a small sample project (or gist) that reproduces the problem.
9 |
10 |
11 | ## Contributing code
12 | We actively welcome your pull requests. You can find instructions on building the project in [README.md](https://github.com/canopas/strapi-plugin-tagsinput).
13 | 1. Fork the repo and create your branch from `master`.
14 | 2. If you've added code that should be tested, add tests
15 | 4. Make sure your code lints.
16 |
17 | ## Labels
18 | Labels on issues are managed by contributors, you don't have to worry about them. Here's a list of what they mean:
19 |
20 | * **bug**: feature that should work, but doesn't
21 | * **enhancement**: minor tweak/addition to existing behaviour
22 | * **feature**: new behaviour, bigger than enhancement
23 | * **question**: no need of any fix, usually a usage problem
24 | * **reproducible**: has enough information to very easily reproduce, mostly in the form of a small project in a GitHub repo
25 | * **repro-needed**: we need some code to be able to reproduce and debug locally, otherwise there's not much we can do
26 | * **duplicate**: there's another issue which already covers/tracks this
27 | * **wontfix**: working as intended, or won't be fixed due to compatibility or other reasons
28 | * **invalid**: there isn't enough information to make a verdict, or unrelated
29 | * **non-library**: issue is not in the core library code, but rather in documentation, samples, build process, releases
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Sumita Canopas
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 | Tagsinput plugin for strapi with suggestions
4 |
5 | 🚀Strapi 5 supported from 2.0.2 🚀
6 |
7 | This plugin is used to add tagsinput in your strapi admin panel.
8 | Read more about it at [tagsinput guidence](https://blog.canopas.com/the-simple-guidance-how-to-add-tagsinput-customfield-plugin-in-strapi-b5d2b5af7c3b).
9 |
10 |
11 |
12 | ## How to Install
13 |
14 | Using npm,
15 |
16 | ```
17 | npm i strapi-plugin-tagsinput
18 | ```
19 |
20 | Using yarn,
21 |
22 | ```
23 | yarn add strapi-plugin-tagsinput
24 | ```
25 |
26 | ## How to use
27 |
28 | After installation, you can add tagsinput as a custom field.
29 |
30 | #### Suggestions for tag
31 |
32 | While adding tagsInput, you will see the `API URL` field.
33 |
34 | If you want to use REST API for suggestions, then add your API url in this field.
35 |
36 | **Notes:**
37 |
38 | - If the API domain is different, then a full API URL is required. i.e. `http://localhost:1337/api/tags?fields[0]=name` (Make sure API CORS are enabled for your strapi domain in this case).
39 | - Otherwise, add only the path of API i.e `/api/tags?fields[0]=name`
40 | - You need to add custom logic for adding created tags in `Tags` collection.
41 |
42 | ## Showcase
43 |
44 | How to use tagsinput?
45 |
46 |
47 |
48 | ## Issues
49 |
50 | If you face any issues, feel free to submit them with detailed information.
51 |
52 | ## Contribution
53 |
54 | The Canopas team enthusiastically welcomes contributions and project participation! There are a bunch of things you can do if you want to contribute! The [Contributor Guide](CONTRIBUTING.md) has all the information you need for everything from reporting bugs to contributing entire new features. Please don't hesitate to jump in if you'd like to, or even ask us questions if something isn't clear.
55 |
56 | ## Show your support ⭐️
57 |
58 | Add a star if this project helped you.
59 |
60 | ## Credits
61 |
62 | This repository is owned and maintained by the [Canopas team](https://canopas.com/). If you are interested in building web apps, plugins or designing products, please let us know. We'd love to hear from you!
63 |
64 |
65 |
66 | ## Licence
67 |
68 | This repository is licenced under [MIT](https://github.com/canopas/strapi-plugin-tagsinput/blob/master/LICENSE).
69 |
--------------------------------------------------------------------------------
/admin/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "jsx": "react",
5 | "module": "esnext",
6 | "allowSyntheticDefaultImports": true,
7 | "esModuleInterop": true
8 | },
9 | "include": ["./src/**/*.js", "./src/**/*.jsx"]
10 | }
11 |
--------------------------------------------------------------------------------
/admin/src/components/Initializer.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 | import pluginId from "../pluginId";
3 |
4 | /**
5 | * @type {import('react').FC<{ setPlugin: (id: string) => void }>}
6 | */
7 | const Initializer = ({ setPlugin }) => {
8 | const ref = useRef(setPlugin);
9 |
10 | useEffect(() => {
11 | ref.current(pluginId);
12 | }, []);
13 |
14 | return null;
15 | };
16 |
17 | export { Initializer };
18 |
--------------------------------------------------------------------------------
/admin/src/components/Input.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from "react";
2 | import PropTypes from "prop-types";
3 | import axios from "axios";
4 | import { Flex, Field } from "@strapi/design-system";
5 | import { useIntl } from "react-intl";
6 | import TagsInput from "react-tagsinput";
7 | import Autosuggest from "react-autosuggest";
8 | import { getStyling } from "./styles/global";
9 |
10 | const ThemeStyle = getStyling(localStorage.getItem("STRAPI_THEME"));
11 |
12 | const Tags = ({
13 | attribute,
14 | description,
15 | error,
16 | label,
17 | labelAction,
18 | name,
19 | onChange,
20 | required,
21 | value,
22 | }) => {
23 | const { formatMessage } = useIntl();
24 | const apiUrl = attribute?.options?.apiUrl || "";
25 | const attrName = apiUrl.slice(apiUrl.lastIndexOf("=") + 1) || "name";
26 | const inputEle = useRef(null);
27 |
28 | const [tags, setTags] = useState(() => {
29 | try {
30 | const values = typeof value === "string" ? JSON.parse(value) : value;
31 | return Array.isArray(values) ? values.map((v) => v[attrName] || v.name || v) : [];
32 | } catch (e) {
33 | return [];
34 | }
35 | });
36 |
37 | const [suggestions, setSuggestions] = useState([]);
38 |
39 | useEffect(() => {
40 | const suggestionsContainer = document.querySelector(
41 | ".react-autosuggest__suggestions-container"
42 | );
43 | if (suggestionsContainer && inputEle.current) {
44 | suggestionsContainer.style.top = `${inputEle.current.offsetHeight + 5}px`;
45 | }
46 |
47 | const handleClickOutside = (event) => {
48 | const tagsInput = document.querySelector(".react-tagsinput");
49 | if (tagsInput) {
50 | tagsInput.classList.toggle(
51 | "react-tagsinput--focused",
52 | inputEle.current?.contains(event.target)
53 | );
54 | }
55 | };
56 |
57 | document.addEventListener("mousedown", handleClickOutside);
58 | return () => document.removeEventListener("mousedown", handleClickOutside);
59 | }, []);
60 |
61 | const handleTagsChange = async (newTags) => {
62 | const lastTag = newTags[newTags.length - 1];
63 | const suggestionsArray = suggestions.data || [];
64 | const existingTag = suggestionsArray.find(
65 | (s) => s[attrName]?.toLowerCase() === lastTag.toLowerCase()
66 | );
67 |
68 | if (!existingTag) {
69 | if (apiUrl) {
70 | try {
71 | const response = await axios.post(apiUrl, {
72 | data: {
73 | [attrName]: lastTag,
74 | },
75 | });
76 |
77 | setSuggestions((prevSuggestions) => {
78 | const newSuggestionsData = [...(prevSuggestions.data || []), response.data.data];
79 | return { ...prevSuggestions, data: newSuggestionsData };
80 | });
81 |
82 | newTags[newTags.length - 1] = response.data.data[attrName];
83 | } catch (error) {
84 | console.error("Error creating new tag:", error);
85 | // Don't remove the tag if API call fails, allow it to be added locally
86 | }
87 | } else {
88 | // If there's no apiUrl, just add the new tag to the suggestions
89 | setSuggestions((prevSuggestions) => {
90 | const newSuggestion = { id: Date.now(), [attrName]: lastTag };
91 | const newSuggestionsData = [...(prevSuggestions.data || []), newSuggestion];
92 | return { ...prevSuggestions, data: newSuggestionsData };
93 | });
94 | }
95 | }
96 |
97 | setTags(newTags);
98 |
99 | const value = JSON.stringify(newTags.map((tag) => ({ [attrName]: tag })));
100 |
101 | onChange({
102 | target: {
103 | name,
104 | value,
105 | type: attribute.type,
106 | },
107 | });
108 | };
109 |
110 | const getSuggestions = async () => {
111 | if (!apiUrl) return;
112 | try {
113 | const res = await axios.get(apiUrl);
114 | setSuggestions(res.data);
115 | } catch (err) {
116 | setSuggestions({ data: [] });
117 | }
118 | };
119 |
120 | const autocompleteRenderInput = (props) => {
121 | const handleOnChange = (e, { newValue, method }) => {
122 | if (method === "enter") {
123 | e.preventDefault();
124 | } else {
125 | props.onChange(e);
126 | }
127 | };
128 |
129 | const inputValue = (props.value && props.value.trim()) || "";
130 | const inputLength = inputValue.length;
131 |
132 | let s = suggestions.data || [];
133 | if (s.length <= 0) {
134 | getSuggestions();
135 | }
136 |
137 | if (inputLength > 0) {
138 | s = s
139 | .filter((state) => {
140 | const suggestionName = state[attrName] || "";
141 | return suggestionName.toLowerCase().slice(0, inputLength) === inputValue;
142 | })
143 | .map((state) => ({
144 | id: state.id,
145 | [attrName]: state[attrName] || "",
146 | }));
147 | }
148 |
149 | return (
150 | value && value.trim().length > 0}
154 | getSuggestionValue={(s) => s[attrName]}
155 | renderSuggestion={(s) => {s[attrName]}}
156 | inputProps={{ ...props, onChange: handleOnChange }}
157 | onSuggestionSelected={(_, { suggestion }) => props.addTag(suggestion[attrName])}
158 | onSuggestionsFetchRequested={() => {}}
159 | />
160 | );
161 | };
162 |
163 | return (
164 | <>
165 |
172 |
179 | {label && {formatMessage({ id: label, defaultMessage: "Tags" })}}
180 |
181 |
182 |
188 |
189 |
190 |
191 |
192 |
193 | >
194 | );
195 | };
196 |
197 | Tags.defaultProps = {
198 | description: null,
199 | disabled: false,
200 | error: null,
201 | labelAction: null,
202 | required: false,
203 | value: "",
204 | };
205 |
206 | Tags.propTypes = {
207 | label: PropTypes.object.isRequired,
208 | onChange: PropTypes.func.isRequired,
209 | attribute: PropTypes.object.isRequired,
210 | name: PropTypes.string.isRequired,
211 | description: PropTypes.object,
212 | error: PropTypes.string,
213 | labelAction: PropTypes.object,
214 | required: PropTypes.bool,
215 | value: PropTypes.string,
216 | };
217 |
218 | export default Tags;
219 |
--------------------------------------------------------------------------------
/admin/src/components/styles/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle, css } from "styled-components";
2 |
3 | const light = css`
4 | :root {
5 | --primary: #7b79ff;
6 | --secondary: rgb(255, 255, 255);
7 | --text: #32324d;
8 | --input-background: #ffffff;
9 | --input-border: #dcdce4;
10 | --tag-background: #f0f0ff;
11 | --tag-text: #4945ff;
12 | --suggestion-background: #ffffff;
13 | --suggestion-hover: #f6f6f9;
14 | }
15 | `;
16 |
17 | const dark = css`
18 | :root {
19 | --primary: #7b79ff;
20 | --secondary: rgb(33, 33, 52);
21 | --text: #ffffff;
22 | --input-background: #181826;
23 | --input-border: #4a4a6a;
24 | --tag-background: #7b79ff;
25 | --tag-text: #ffffff;
26 | --suggestion-background: #181826;
27 | --suggestion-hover: #212134;
28 | }
29 | `;
30 |
31 | const styles = css`
32 | .react-tagsinput {
33 | width: 100%;
34 | border: 1px solid var(--input-border);
35 | border-radius: 4px;
36 | overflow: hidden;
37 | padding-left: 5px;
38 | padding-top: 5px;
39 | }
40 |
41 | .react-tagsinput--focused {
42 | outline: 3px solid var(--primary);
43 | }
44 |
45 | .react-tagsinput-tag {
46 | background-color: var(--tag-background);
47 | border-radius: 2px;
48 | border: 1px solid var(--tag-background);
49 | color: var(--tag-text);
50 | display: inline-block;
51 | font-family: sans-serif;
52 | font-size: 13px;
53 | font-weight: 400;
54 | margin-bottom: 5px;
55 | margin-right: 5px;
56 | padding: 5px;
57 | }
58 |
59 | .react-tagsinput-remove {
60 | cursor: pointer;
61 | font-weight: bold;
62 | }
63 |
64 | .react-tagsinput-tag a::before {
65 | content: " ×";
66 | }
67 |
68 | .react-tagsinput-input {
69 | background: transparent;
70 | border: 0;
71 | color: var(--text);
72 | font-family: sans-serif;
73 | font-size: 13px;
74 | font-weight: 400;
75 | margin-bottom: 6px;
76 | margin-top: 1px;
77 | outline: none;
78 | padding: 5px;
79 | width: 100%;
80 | }
81 |
82 | .react-tagsinput > span {
83 | display: flex;
84 | flex-flow: wrap;
85 | }
86 |
87 | .react-autosuggest__container {
88 | display: flex;
89 | flex-direction: column;
90 | flex: auto;
91 | }
92 |
93 | .react-autosuggest__suggestions-container {
94 | position: absolute;
95 | z-index: 200;
96 | width: 280px;
97 | margin: 0;
98 | padding: 0;
99 | list-style-type: none;
100 | background-color: var(--suggestion-background);
101 | }
102 |
103 | .react-autosuggest__suggestions-container--open {
104 | border: 1px solid var(--input-border);
105 | }
106 |
107 | .react-autosuggest__suggestion {
108 | cursor: pointer;
109 | padding: 10px 20px;
110 | }
111 |
112 | .react-autosuggest__suggestion > span {
113 | font-size: 13px;
114 | font-weight: 400;
115 | }
116 |
117 | .react-autosuggest__suggestion--highlighted,
118 | .react-autosuggest__suggestion--focused {
119 | background-color: var(--suggestion-hover);
120 | }
121 | `;
122 |
123 | export const getStyling = (theme) => {
124 | let themeStyle = light;
125 |
126 | switch (theme) {
127 | case 'dark':
128 | themeStyle = dark;
129 | break;
130 | default:
131 | themeStyle = light;
132 | }
133 |
134 | return createGlobalStyle`
135 | ${themeStyle}
136 | ${styles}
137 | `;
138 | };
139 |
--------------------------------------------------------------------------------
/admin/src/index.js:
--------------------------------------------------------------------------------
1 | import pluginId from "./pluginId";
2 | import { getTranslation } from './utils/getTranslation';
3 |
4 | export default {
5 | register(app) {
6 | app.customFields.register({
7 | name: "tags",
8 | pluginId: pluginId,
9 | type: "text",
10 | intlLabel: {
11 | id: getTranslation('form.label'),
12 | defaultMessage: 'TagsInput',
13 | },
14 | intlDescription: {
15 | id: getTranslation('form.description'),
16 | defaultMessage: 'TagsInput to add custom tags',
17 | },
18 | components: {
19 | Input: async () =>
20 | import(
21 | /* webpackChunkName: "input-component" */ "./components/Input"
22 | ),
23 | },
24 | options: {
25 | base: [
26 | {
27 | sectionTitle: {
28 | id: "form.section.apiUrl",
29 | defaultMessage: "API Url",
30 | },
31 | items: [
32 | {
33 | intlLabel: {
34 | id: "form.apiUrl",
35 | defaultMessage: "Rest API URL for suggestions",
36 | },
37 | name: "options.apiUrl",
38 | type: "text",
39 | value: "",
40 | options: [],
41 | },
42 | ],
43 | },
44 | ],
45 | },
46 | });
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/admin/src/pages/App.jsx:
--------------------------------------------------------------------------------
1 | import { Page } from '@strapi/strapi/admin';
2 | import { Routes, Route } from 'react-router-dom';
3 |
4 | import { HomePage } from './HomePage';
5 |
6 | const App = () => {
7 | return (
8 |
9 | } />
10 | } />
11 |
12 | );
13 | };
14 |
15 | export { App };
16 |
--------------------------------------------------------------------------------
/admin/src/pages/HomePage.jsx:
--------------------------------------------------------------------------------
1 | import { Main } from '@strapi/design-system';
2 | import { useIntl } from 'react-intl';
3 | import pluginId from "../pluginId";
4 |
5 | const HomePage = () => {
6 | const { formatMessage } = useIntl();
7 |
8 | return (
9 |
10 | Welcome to {formatMessage({ id: pluginId })}
11 |
12 | );
13 | };
14 |
15 | export { HomePage };
16 |
--------------------------------------------------------------------------------
/admin/src/pluginId.js:
--------------------------------------------------------------------------------
1 | import pluginPkg from '../../package.json';
2 |
3 | const pluginId = pluginPkg.name.replace(/^(@[^-,.][\w,-]+\/|strapi-)plugin-/i, '');
4 |
5 | export default pluginId;
6 |
--------------------------------------------------------------------------------
/admin/src/translations/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "form.label": "TagsInput",
3 | "form.description": "TagsInput to add custom tags",
4 | "form.section.apiUrl": "API Url",
5 | "form.apiUrl": "Rest API URL for suggestions"
6 | }
--------------------------------------------------------------------------------
/admin/src/utils/getTranslation.js:
--------------------------------------------------------------------------------
1 | import pluginId from '../pluginId';
2 |
3 | const getTranslation = (id) => `${pluginId}.${id}`;
4 |
5 | export { getTranslation };
--------------------------------------------------------------------------------
/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/strapi-plugin-tagsinput/ea59389a5d7e41e77f29a55b3d429ff6a74e6287/assets/banner.png
--------------------------------------------------------------------------------
/assets/cta.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/strapi-plugin-tagsinput/ea59389a5d7e41e77f29a55b3d429ff6a74e6287/assets/cta.png
--------------------------------------------------------------------------------
/assets/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/strapi-plugin-tagsinput/ea59389a5d7e41e77f29a55b3d429ff6a74e6287/assets/demo.png
--------------------------------------------------------------------------------
/assets/showcase.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canopas/strapi-plugin-tagsinput/ea59389a5d7e41e77f29a55b3d429ff6a74e6287/assets/showcase.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "strapi-plugin-tagsinput",
3 | "version": "2.0.3",
4 | "description": "Tagsinput plugin for your strapi project",
5 | "strapi": {
6 | "name": "tagsinput",
7 | "description": "Tagsinput plugin for your strapi project",
8 | "kind": "plugin",
9 | "displayName": "Tagsinput"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/canopas/strapi-plugin-tagsinput.git"
14 | },
15 | "scripts": {
16 | "build": "strapi-plugin build",
17 | "verify": "strapi-plugin verify",
18 | "watch": "strapi-plugin watch",
19 | "watch:link": "strapi-plugin watch:link"
20 | },
21 | "exports": {
22 | "./package.json": "./package.json",
23 | "./strapi-admin": {
24 | "source": "./admin/src/index.js",
25 | "import": "./dist/admin/index.mjs",
26 | "require": "./dist/admin/index.js",
27 | "default": "./dist/admin/index.js"
28 | },
29 | "./strapi-server": {
30 | "source": "./server/src/index.js",
31 | "import": "./dist/server/index.mjs",
32 | "require": "./dist/server/index.js",
33 | "default": "./dist/server/index.js"
34 | }
35 | },
36 | "files": [
37 | "dist"
38 | ],
39 | "dependencies": {
40 | "@strapi/design-system": "^2.0.0-rc.11",
41 | "@strapi/icons": "^2.0.0-rc.11",
42 | "axios": "^1.7.7",
43 | "prop-types": "^15.8.1",
44 | "react-autosuggest": "^10.1.0",
45 | "react-intl": "^6.7.0",
46 | "react-tagsinput": "^3.20.3"
47 | },
48 | "devDependencies": {
49 | "@strapi/sdk-plugin": "^5.2.6",
50 | "@strapi/strapi": "^5.0.2",
51 | "react": "^18.3.1",
52 | "react-dom": "^18.3.1",
53 | "react-router-dom": "^6.26.2",
54 | "styled-components": "^6.1.13"
55 | },
56 | "peerDependencies": {
57 | "@strapi/sdk-plugin": "^5.2.6",
58 | "@strapi/strapi": "^5.0.2",
59 | "react": "^18.3.1",
60 | "react-dom": "^18.3.1",
61 | "react-router-dom": "^6.26.2",
62 | "styled-components": "^6.1.13"
63 | },
64 | "author": "Canopas",
65 | "maintainers": [
66 | "Canopas"
67 | ],
68 | "engines": {
69 | "node": ">=14.19.1 <=20.x.x",
70 | "npm": ">=6.0.0"
71 | },
72 | "license": "MIT",
73 | "main": "strapi-server.js",
74 | "keywords": [
75 | "strapi",
76 | "plugin",
77 | "tagsinput"
78 | ],
79 | "bugs": {
80 | "url": "https://github.com/canopas/strapi-plugin-tagsinput/issues"
81 | },
82 | "homepage": "https://github.com/canopas/strapi-plugin-tagsinput#readme",
83 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
84 | }
85 |
--------------------------------------------------------------------------------
/server/src/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const register = require("./register");
4 |
5 | module.exports = {
6 | register,
7 | };
8 |
--------------------------------------------------------------------------------
/server/src/register.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = ({ strapi }) => {
4 | strapi.customFields.register({
5 | name: "tags",
6 | plugin: "tagsinput",
7 | type: "json",
8 | });
9 | };
--------------------------------------------------------------------------------