Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to validate Link and Embedded url #1378

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ export default "{\n" +
" component: undefined,\n" +
" popupClassName: undefined,\n" +
" embedCallback: undefined,\n" +
" embedValidator: undefined,\n" +

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linkValidator field is missing in this file

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7f61272

" linkValidator: undefined,\n" +
" defaultSize: {\n" +
" height: 'auto',\n" +
" width: 'auto',\n" +
Expand Down
15 changes: 15 additions & 0 deletions docs/src/components/Docs/Props/CustomizingToolbarProp/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@ export default () => (
will be saved iin the link.
</span>
</li>
<li>
<b>link: linkValidator</b>
<span>
: This is a function to validate the link added by the user.
The validator is passed the link text input by the user and is
expected to return a boolean.
</span>
</li>
<li>
<b>emoji: emojis</b>
<span>
Expand All @@ -181,6 +189,13 @@ export default () => (
passed a url and should return url only.
</span>
</li>
<li>
<b>embedded: embedValidator</b>
<span>
: This is a function to validate the url added by the user. The validator
is passed the url input by the user and is expected to return a boolean.
</span>
</li>
<li>
<b>image: urlEnabled</b>
<span>
Expand Down
4 changes: 3 additions & 1 deletion src/config/defaultToolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ export default {
options: ["link", "unlink"],
link: { icon: link, className: undefined, title: undefined },
unlink: { icon: unlink, className: undefined, title: undefined },
linkCallback: undefined
linkCallback: undefined,
linkValidator: undefined
},
emoji: {
icon: emoji,
Expand Down Expand Up @@ -327,6 +328,7 @@ export default {
component: undefined,
popupClassName: undefined,
embedCallback: undefined,
embedValidator: undefined,
defaultSize: {
height: "auto",
width: "auto"
Expand Down
57 changes: 51 additions & 6 deletions src/controls/Embedded/Component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class LayoutComponent extends Component {
embeddedLink: '',
height: this.props.config.defaultSize.height,
width: this.props.config.defaultSize.width,
embeddedLinkValid: false,
heightValid: !!this.props.config.defaultSize.height,
widthValid: !!this.props.config.defaultSize.width,
};

componentDidUpdate(prevProps) {
Expand All @@ -30,24 +33,54 @@ class LayoutComponent extends Component {
embeddedLink: '',
height,
width,
embeddedLinkValid: false,
heightValid: !!height,
widthValid: !!width,
});
}
}

validateValue = (name, value) => {
if (name === 'embeddedLink') {
const { embedValidator } = this.props.config;
return !!value && (!embedValidator ? true : embedValidator(value));
}
return !!value;
}

isValid = () => {
const {
embeddedLinkValid,
heightValid,
widthValid,
} = this.state;
return embeddedLinkValid && heightValid && widthValid;
}

onChange = () => {
if (!this.isValid()) return;
const { onChange } = this.props;
const { embeddedLink, height, width } = this.state;
onChange(embeddedLink, height, width);
};

updateValue = event => {
const { name, value } = event.target;
this.setState({
[`${event.target.name}`]: event.target.value,
[`${name}`]: value,
[`${name}Valid`]: this.validateValue(name, value),
});
};

rendeEmbeddedLinkModal() {
const { embeddedLink, height, width } = this.state;
const {
embeddedLink,
height,
width,
embeddedLinkValid,
heightValid,
widthValid,
} = this.state;
const {
config: { popupClassName },
doCollapse,
Expand All @@ -67,7 +100,11 @@ class LayoutComponent extends Component {
<div className="rdw-embedded-modal-link-section">
<span className="rdw-embedded-modal-link-input-wrapper">
<input
className="rdw-embedded-modal-link-input"
className={classNames({
'rdw-embedded-modal-link-input': true,
valid: embeddedLinkValid,
invalid: !embeddedLinkValid,
})}
placeholder={
translations['components.controls.embedded.enterlink']
}
Expand All @@ -85,7 +122,11 @@ class LayoutComponent extends Component {
onBlur={this.updateValue}
value={height}
name="height"
className="rdw-embedded-modal-size-input"
className={classNames({
'rdw-embedded-modal-size-input': true,
valid: heightValid,
invalid: !heightValid,
})}
placeholder="Height"
/>
<span className="rdw-image-mandatory-sign">*</span>
Expand All @@ -96,7 +137,11 @@ class LayoutComponent extends Component {
onBlur={this.updateValue}
value={width}
name="width"
className="rdw-embedded-modal-size-input"
className={classNames({
'rdw-embedded-modal-size-input': true,
valid: widthValid,
invalid: !widthValid,
})}
placeholder="Width"
/>
<span className="rdw-image-mandatory-sign">*</span>
Expand All @@ -108,7 +153,7 @@ class LayoutComponent extends Component {
type="button"
className="rdw-embedded-modal-btn"
onClick={this.onChange}
disabled={!embeddedLink || !height || !width}
disabled={!this.isValid()}
>
{translations['generic.add']}
</button>
Expand Down
8 changes: 8 additions & 0 deletions src/controls/Embedded/Component/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
font-size: 15px;
padding: 0 5px;
}
.rdw-embedded-modal-link-input.invalid {
border: 1px solid #ffadad;
background: #fffafa;
}
.rdw-embedded-modal-link-input-wrapper {
display: flex;
align-items: center;
Expand Down Expand Up @@ -99,6 +103,10 @@
border-radius: 2px;
font-size: 12px;
}
.rdw-embedded-modal-size-input.invalid {
border: 1px solid #ffadad;
background: #fffafa;
}
.rdw-embedded-modal-size-input:focus {
outline: none;
}
60 changes: 51 additions & 9 deletions src/controls/Link/Component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class LayoutComponent extends Component {
linkTarget: '',
linkTitle: '',
linkTargetOption: this.props.config.defaultTargetOption,
linkTargetValid: false,
linkTitleValid: false,
};

componentDidUpdate(prevProps) {
Expand All @@ -34,24 +36,42 @@ class LayoutComponent extends Component {
linkTarget: '',
linkTitle: '',
linkTargetOption: this.props.config.defaultTargetOption,
linkTargetValid: false,
linkTitleValid: false,
});
}
}

validateValue = (name, value) => {
if (name === 'linkTarget') {
const { linkValidator } = this.props.config;
return !!value && (!linkValidator ? true : linkValidator(value));
}
return !!value
}

isValid = () => {
const { linkTitleValid, linkTargetValid } = this.state;
return linkTitleValid && linkTargetValid;
}

removeLink = () => {
const { onChange } = this.props;
onChange('unlink');
};

addLink = () => {
if (!this.isValid()) return;
const { onChange } = this.props;
const { linkTitle, linkTarget, linkTargetOption } = this.state;
onChange('link', linkTitle, linkTarget, linkTargetOption);
};

updateValue = event => {
const { name, value } = event.target;
this.setState({
[`${event.target.name}`]: event.target.value,
[`${name}`]: value,
[`${name}Valid`]: this.validateValue(name, value),
});
};

Expand All @@ -73,12 +93,16 @@ class LayoutComponent extends Component {
currentState: { link, selectionText },
} = this.props;
const { linkTargetOption } = this.state;
const linkTarget = (link && link.target) || '';
const linkTitle = (link && link.title) || selectionText;
onExpandEvent();
this.setState({
showModal: true,
linkTarget: (link && link.target) || '',
linkTarget,
linkTargetOption: (link && link.targetOption) || linkTargetOption,
linkTitle: (link && link.title) || selectionText,
linkTitle,
linkTargetValid: this.validateValue('linkTarget', linkTarget),
linkTitleValid: this.validateValue('linkTitle', linkTitle),
});
};

Expand All @@ -88,12 +112,16 @@ class LayoutComponent extends Component {
currentState: { link, selectionText },
} = this.props;
const { linkTargetOption } = this.state;
const linkTarget = (link && link.target);
const linkTitle = (link && link.title) || selectionText;
doExpand();
this.setState({
showModal: true,
linkTarget: link && link.target,
linkTarget,
linkTargetOption: (link && link.targetOption) || linkTargetOption,
linkTitle: (link && link.title) || selectionText,
linkTitle,
linkTargetValid: this.validateValue('linkTarget', linkTarget),
linkTitleValid: this.validateValue('linkTitle', linkTitle),
});
};

Expand All @@ -103,7 +131,13 @@ class LayoutComponent extends Component {
doCollapse,
translations,
} = this.props;
const { linkTitle, linkTarget, linkTargetOption } = this.state;
const {
linkTitle,
linkTarget,
linkTargetOption,
linkTitleValid,
linkTargetValid,
} = this.state;
return (
<div
className={classNames('rdw-link-modal', popupClassName)}
Expand All @@ -114,7 +148,11 @@ class LayoutComponent extends Component {
</label>
<input
id="linkTitle"
className="rdw-link-modal-input"
className={classNames({
'rdw-link-modal-input': true,
valid: linkTitleValid,
invalid: !linkTitleValid,
})}
onChange={this.updateValue}
onBlur={this.updateValue}
name="linkTitle"
Expand All @@ -125,7 +163,11 @@ class LayoutComponent extends Component {
</label>
<input
id="linkTarget"
className="rdw-link-modal-input"
className={classNames({
'rdw-link-modal-input': true,
valid: linkTargetValid,
invalid: !linkTargetValid,
})}
onChange={this.updateValue}
onBlur={this.updateValue}
name="linkTarget"
Expand All @@ -150,7 +192,7 @@ class LayoutComponent extends Component {
<button
className="rdw-link-modal-btn"
onClick={this.addLink}
disabled={!linkTarget || !linkTitle}
disabled={!this.isValid()}
>
{translations['generic.add']}
</button>
Expand Down
4 changes: 4 additions & 0 deletions src/controls/Link/Component/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
.rdw-link-modal-input:focus {
outline: none;
}
.rdw-link-modal-input.invalid {
border: 1px solid #ffadad;
background: #fffafa;
}
.rdw-link-modal-buttonsection {
margin: 0 auto;
}
Expand Down
17 changes: 17 additions & 0 deletions src/controls/Link/__test__/linkControlTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,23 @@ describe("LinkControl test suite", () => {
);
});

it("should use custom validator if one is set with linkValidator", () => {
const onChange = spy();
const control = mount(<LinkControl config={{ ...defaultToolbar.link, linkValidator: target => false }} onChange={onChange} editorState={editorState} translations={localeTranslations.en} modalHandler={new ModalHandler()} />);
control.setState({ expanded: true });
const buttons = control.find(".rdw-option-wrapper");
buttons.first().simulate("click");
const inputs = control.find(".rdw-link-modal-input");
inputs.last().simulate("change", {
target: { name: "linkTitle", value: "the google" }
});
inputs.first().simulate("change", {
target: { name: "linkTarget", value: "www.google.com" }
});
const addButton = control.find(".rdw-link-modal-btn").first();
assert.isTrue(addButton.prop('disabled'));
});

it("should return input value by default", () => {
const onChange = spy();
const control = mount(
Expand Down