diff --git a/.eslintrc b/.eslintrc
index 8839bdef..7207fac3 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -35,6 +35,7 @@
"react/jsx-first-prop-new-line": ["error", "multiline-multiprop"],
"react/jsx-closing-bracket-location": ["error", "tag-aligned"],
"react/no-array-index-key": "off",
- "react/no-did-update-set-state": "off"
+ "react/no-did-update-set-state": "off",
+ "react/display-name": "off"
}
}
diff --git a/README.md b/README.md
index 88727204..3b2b1940 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,7 @@
[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/shlinkio/shlink-web-client.svg?style=flat-square)](https://scrutinizer-ci.com/g/shlinkio/shlink-web-client/?branch=master)
[![GitHub release](https://img.shields.io/github/release/shlinkio/shlink-web-client.svg?style=flat-square)](https://github.com/shlinkio/shlink-web-client/releases/latest)
[![GitHub license](https://img.shields.io/github/license/shlinkio/shlink-web-client.svg?style=flat-square)](https://github.com/shlinkio/shlink-web-client/blob/master/LICENSE)
+[![Paypal Donate](https://img.shields.io/badge/Donate-paypal-blue.svg?style=flat-square&logo=paypal&colorA=cccccc)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=alejandrocelaya%40gmail.com¤cy_code=EUR)
A ReactJS-based progressive web application for [Shlink](https://shlink.io).
diff --git a/package.json b/package.json
index 9280a206..55c84580 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"qs": "^6.5.2",
"ramda": "^0.25.0",
"react": "^16.3.2",
+ "react-autosuggest": "^9.4.0",
"react-chartjs-2": "^2.7.4",
"react-color": "^2.14.1",
"react-copy-to-clipboard": "^5.0.1",
diff --git a/src/tags/TagCard.js b/src/tags/TagCard.js
index b4e65959..3222a37d 100644
--- a/src/tags/TagCard.js
+++ b/src/tags/TagCard.js
@@ -5,7 +5,7 @@ import editIcon from '@fortawesome/fontawesome-free-solid/faPencilAlt';
import PropTypes from 'prop-types';
import React from 'react';
import { Link } from 'react-router-dom';
-import colorGenerator, { colorGeneratorType } from '../utils/ColorGenerator';
+import TagBullet from '../utils/TagBullet';
import './TagCard.scss';
import DeleteTagConfirmModal from './helpers/DeleteTagConfirmModal';
import EditTagModal from './helpers/EditTagModal';
@@ -14,16 +14,12 @@ export default class TagCard extends React.Component {
static propTypes = {
tag: PropTypes.string,
currentServerId: PropTypes.string,
- colorGenerator: colorGeneratorType,
- };
- static defaultProps = {
- colorGenerator,
};
state = { isDeleteModalOpen: false, isEditModalOpen: false };
render() {
- const { tag, colorGenerator, currentServerId } = this.props;
+ const { tag, currentServerId } = this.props;
const toggleDelete = () =>
this.setState(({ isDeleteModalOpen }) => ({ isDeleteModalOpen: !isDeleteModalOpen }));
const toggleEdit = () =>
@@ -45,10 +41,7 @@ export default class TagCard extends React.Component {
-
+
{tag}
diff --git a/src/tags/TagCard.scss b/src/tags/TagCard.scss
index 6e2b56d7..c30300c3 100644
--- a/src/tags/TagCard.scss
+++ b/src/tags/TagCard.scss
@@ -16,17 +16,6 @@
padding-right: 5px;
}
-.tag-card__tag-bullet {
- $width: 20px;
-
- border-radius: 50%;
- width: $width;
- height: $width;
- display: inline-block;
- vertical-align: -4px;
- margin-right: 7px;
-}
-
.tag-card__btn {
float: right;
}
diff --git a/src/tags/reducers/tagsList.js b/src/tags/reducers/tagsList.js
index 5cd229a0..cb415902 100644
--- a/src/tags/reducers/tagsList.js
+++ b/src/tags/reducers/tagsList.js
@@ -59,9 +59,7 @@ export default function reducer(state = defaultState, action) {
case FILTER_TAGS:
return {
...state,
- filteredTags: state.tags.filter(
- (tag) => tag.toLowerCase().match(action.searchTerm),
- ),
+ filteredTags: state.tags.filter((tag) => tag.toLowerCase().match(action.searchTerm)),
};
default:
return state;
diff --git a/src/utils/TagBullet.js b/src/utils/TagBullet.js
new file mode 100644
index 00000000..ccdc4801
--- /dev/null
+++ b/src/utils/TagBullet.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import * as PropTypes from 'prop-types';
+import colorGenerator, { colorGeneratorType } from './ColorGenerator';
+import './TagBullet.scss';
+
+const propTypes = {
+ tag: PropTypes.string.isRequired,
+ colorGenerator: colorGeneratorType,
+};
+const defaultProps = {
+ colorGenerator,
+};
+
+export default function TagBullet({ tag, colorGenerator }) {
+ return (
+
+ );
+}
+
+TagBullet.propTypes = propTypes;
+TagBullet.defaultProps = defaultProps;
diff --git a/src/utils/TagBullet.scss b/src/utils/TagBullet.scss
new file mode 100644
index 00000000..ad795a9d
--- /dev/null
+++ b/src/utils/TagBullet.scss
@@ -0,0 +1,10 @@
+.tag-bullet {
+ $width: 20px;
+
+ border-radius: 50%;
+ width: $width;
+ height: $width;
+ display: inline-block;
+ vertical-align: -4px;
+ margin-right: 7px;
+}
diff --git a/src/utils/TagsSelector.js b/src/utils/TagsSelector.js
index 0630912d..5b8eeb00 100644
--- a/src/utils/TagsSelector.js
+++ b/src/utils/TagsSelector.js
@@ -1,40 +1,102 @@
import React from 'react';
+import { connect } from 'react-redux';
import TagsInput from 'react-tagsinput';
import PropTypes from 'prop-types';
+import Autosuggest from 'react-autosuggest';
+import { pick, identity } from 'ramda';
+import { listTags } from '../tags/reducers/tagsList';
import colorGenerator, { colorGeneratorType } from './ColorGenerator';
+import './TagsSelector.scss';
+import TagBullet from './TagBullet';
-const defaultProps = {
- colorGenerator,
- placeholder: 'Add tags to the URL',
-};
-const propTypes = {
- tags: PropTypes.arrayOf(PropTypes.string).isRequired,
- onChange: PropTypes.func.isRequired,
- placeholder: PropTypes.string,
- colorGenerator: colorGeneratorType,
-};
+export class TagsSelectorComponent extends React.Component {
+ static propTypes = {
+ tags: PropTypes.arrayOf(PropTypes.string).isRequired,
+ onChange: PropTypes.func.isRequired,
+ placeholder: PropTypes.string,
+ colorGenerator: colorGeneratorType,
+ tagsList: PropTypes.shape({
+ tags: PropTypes.arrayOf(PropTypes.string),
+ }),
+ };
+ static defaultProps = {
+ colorGenerator,
+ placeholder: 'Add tags to the URL',
+ };
-export default function TagsSelector({ tags, onChange, placeholder, colorGenerator }) {
- const renderTag = ({ tag, key, disabled, onRemove, classNameRemove, getTagDisplayValue, ...other }) => (
-
- {getTagDisplayValue(tag)}
- {!disabled && onRemove(key)} />}
-
- );
+ componentDidMount() {
+ const { listTags } = this.props;
- return (
-
- );
+ render() {
+ const { tags, onChange, placeholder, colorGenerator, tagsList } = this.props;
+ const renderTag = ({ tag, key, disabled, onRemove, classNameRemove, getTagDisplayValue, ...other }) => (
+
+ {getTagDisplayValue(tag)}
+ {!disabled && onRemove(key)} />}
+
+ );
+ const renderAutocompleteInput = (data) => {
+ const { addTag, ...rest } = data;
+
+ const handleOnChange = (e, { method }) => {
+ if (method === 'enter') {
+ e.preventDefault();
+ } else {
+ rest.onChange(e);
+ }
+ };
+
+ // eslint-disable-next-line no-extra-parens
+ const inputValue = (rest.value && rest.value.trim().toLowerCase()) || '';
+ const inputLength = inputValue.length;
+ const suggestions = tagsList.tags.filter((state) => state.toLowerCase().slice(0, inputLength) === inputValue);
+
+ return (
+ value && value.trim().length > 0}
+ getSuggestionValue={(suggestion) => {
+ console.log(suggestion);
+
+ return suggestion;
+ }}
+ renderSuggestion={(suggestion) => (
+
+
+ {suggestion}
+
+ )}
+ onSuggestionSelected={(e, { suggestion }) => {
+ addTag(suggestion);
+ }}
+ onSuggestionsClearRequested={identity}
+ onSuggestionsFetchRequested={identity}
+ />
+ );
+ };
+
+ return (
+
+ );
+ }
}
-TagsSelector.defaultProps = defaultProps;
-TagsSelector.propTypes = propTypes;
+const TagsSelector = connect(pick([ 'tagsList' ]), { listTags })(TagsSelectorComponent);
+
+export default TagsSelector;
diff --git a/src/utils/TagsSelector.scss b/src/utils/TagsSelector.scss
new file mode 100644
index 00000000..24a68c8e
--- /dev/null
+++ b/src/utils/TagsSelector.scss
@@ -0,0 +1,16 @@
+@import './base';
+
+.react-autosuggest__suggestions-list {
+ list-style-type: none;
+ padding: 0;
+ margin-bottom: 6px;
+}
+
+.react-autosuggest__suggestion {
+ margin-left: -6px;
+ padding: 5px 8px;
+}
+
+.react-autosuggest__suggestion--highlighted {
+ background-color: $lightGrey;
+}
diff --git a/yarn.lock b/yarn.lock
index f3deac78..a2b0dc21 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5691,6 +5691,10 @@ object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+object-assign@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
+
object-copy@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
@@ -6717,6 +6721,22 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
+react-autosuggest@^9.4.0:
+ version "9.4.0"
+ resolved "https://registry.yarnpkg.com/react-autosuggest/-/react-autosuggest-9.4.0.tgz#3146bc9afa4f171bed067c542421edec5ca94294"
+ dependencies:
+ prop-types "^15.5.10"
+ react-autowhatever "^10.1.2"
+ shallow-equal "^1.0.0"
+
+react-autowhatever@^10.1.2:
+ version "10.1.2"
+ resolved "https://registry.yarnpkg.com/react-autowhatever/-/react-autowhatever-10.1.2.tgz#200ffc41373b2189e3f6140ac7bdb82363a79fd3"
+ dependencies:
+ prop-types "^15.5.8"
+ react-themeable "^1.1.0"
+ section-iterator "^2.0.0"
+
react-chartjs-2@^2.7.4:
version "2.7.4"
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-2.7.4.tgz#e41ea4e81491dc78347111126a48e96ee57db1a6"
@@ -6879,6 +6899,12 @@ react-test-renderer@^16.0.0-0:
prop-types "^15.6.0"
react-is "^16.4.2"
+react-themeable@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/react-themeable/-/react-themeable-1.1.0.tgz#7d4466dd9b2b5fa75058727825e9f152ba379a0e"
+ dependencies:
+ object-assign "^3.0.0"
+
react-transition-group@^2.3.1:
version "2.4.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.4.0.tgz#1d9391fabfd82e016f26fabd1eec329dbd922b5a"
@@ -7439,6 +7465,10 @@ scss-tokenizer@^0.2.3:
js-base64 "^2.1.8"
source-map "^0.4.2"
+section-iterator@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a"
+
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -7586,6 +7616,10 @@ shallow-clone@^1.0.0:
kind-of "^5.0.0"
mixin-object "^2.0.1"
+shallow-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.0.0.tgz#508d1838b3de590ab8757b011b25e430900945f7"
+
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"