diff --git a/README.md b/README.md
index f1575d0..a63df4b 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,8 @@ var editor = EditorJS({
hyperlink: {
Save: 'Salvar',
'Select target': 'Seleziona destinazione',
- 'Select rel': 'Wählen rel'
+ 'Select rel': 'Wählen rel',
+ 'The URL is not valid.': 'The URL is not valid.'
}
}
}
@@ -83,7 +84,8 @@ var editor = EditorJS({
| availableTargets | `string[]` | Available link targets, defaults to all targets.
If empty array is provided, the control will be hidden and the default value applied. |
| availableRels | `string[]` | Available link rels, defaults to all rels.
If empty array is provided, the control will be hidden and the default value applied. |
| validate | `boolean` | Defines if an URL should be validated on saving |
+| configure | `function` | Function that accepts URL and returns attributes object with optional fields: `{href:"https://...",target:"_blank",rels:{nofollow: true}}` |
## License
-[MIT](https://tamit.info)
\ No newline at end of file
+[MIT](https://tamit.info)
diff --git a/src/Hyperlink.css b/src/Hyperlink.css
index 46ab9c6..0c49aae 100644
--- a/src/Hyperlink.css
+++ b/src/Hyperlink.css
@@ -5,7 +5,7 @@
margin: 0;
font-size: 13px;
padding: 10px;
- width: 100%;
+ width: 300px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
display: none;
@@ -18,8 +18,7 @@
}
.ce-inline-tool-hyperlink--input,
-.ce-inline-tool-hyperlink--select-target,
-.ce-inline-tool-hyperlink--select-rel {
+.ce-inline-tool-hyperlink--select-target {
border: 1px solid rgba(201,201,204,.48);
-webkit-box-shadow: inset 0 1px 2px 0 rgba(35,44,72,.06);
box-shadow: inset 0 1px 2px 0 rgba(35,44,72,.06);
@@ -32,16 +31,28 @@
box-sizing: border-box;
}
-.ce-inline-tool-hyperlink--select-target,
-.ce-inline-tool-hyperlink--select-rel {
- width: 48%;
+.ce-inline-tool-hyperlink--select-target {
+ width: 100%;
display: inline-block;
}
.ce-inline-tool-hyperlink--select-target {
margin-right: 2%;
}
+
.ce-inline-tool-hyperlink--select-rel {
- margin-left: 2%;
+ display: flex;
+ flex-wrap: wrap;
+ margin-bottom: 10px;
+}
+
+.ce-inline-tool-hyperlink--checkbox-label {
+ display: flex;
+ flex: 50%;
+ padding: 3px 10px;
+ align-items: center;
+}
+.ce-inline-tool-hyperlink--checkbox-input {
+ margin-right: 5px;
}
.ce-inline-tool-hyperlink--button {
diff --git a/src/Hyperlink.js b/src/Hyperlink.js
index a8d4142..9f1342e 100644
--- a/src/Hyperlink.js
+++ b/src/Hyperlink.js
@@ -1,5 +1,4 @@
-import SelectionUtils from "./SelectionUtils";
-import css from './Hyperlink.css';
+import SelectionUtils from './SelectionUtils';
export default class Hyperlink {
@@ -24,6 +23,8 @@ export default class Hyperlink {
input: 'ce-inline-tool-hyperlink--input',
selectTarget: 'ce-inline-tool-hyperlink--select-target',
selectRel: 'ce-inline-tool-hyperlink--select-rel',
+ checkboxLabel: 'ce-inline-tool-hyperlink--checkbox-label',
+ checkboxInput: 'ce-inline-tool-hyperlink--checkbox-input',
buttonSave: 'ce-inline-tool-hyperlink--button',
};
@@ -79,19 +80,18 @@ export default class Hyperlink {
this.nodes.input = document.createElement('input');
this.nodes.input.placeholder = 'https://...';
this.nodes.input.classList.add(this.CSS.input);
-
- let i;
+ this.nodes.input.addEventListener('blur', this.onBlur.bind(this));
// Target
this.nodes.selectTarget = document.createElement('select');
this.nodes.selectTarget.classList.add(this.CSS.selectTarget);
this.addOption(this.nodes.selectTarget, this.i18n.t('Select target'), '');
- for (i=0; i 0) {
+ if (!!this.targetAttributes && this.targetAttributes.length > 0) {
this.nodes.wrapper.appendChild(this.nodes.selectTarget);
}
- if(!!this.relAttributes && this.relAttributes.length > 0) {
+ if (!!this.relAttributes && this.relAttributes.length > 0) {
this.nodes.wrapper.appendChild(this.nodes.selectRel);
}
@@ -183,7 +178,7 @@ export default class Hyperlink {
};
}
- checkState(selection=null) {
+ checkState(selection = null) {
const anchorTag = this.selection.findParentTag('A');
if (anchorTag) {
this.nodes.button.classList.add(this.CSS.buttonUnlink);
@@ -191,10 +186,12 @@ export default class Hyperlink {
this.openActions();
const hrefAttr = anchorTag.getAttribute('href');
const targetAttr = anchorTag.getAttribute('target');
- const relAttr = anchorTag.getAttribute('rel');
+ const relAttr = (anchorTag.getAttribute('rel') || '').split(' ');
this.nodes.input.value = !!hrefAttr ? hrefAttr : '';
this.nodes.selectTarget.value = !!targetAttr ? targetAttr : '';
- this.nodes.selectRel.value = !!relAttr ? relAttr : '';
+ for (const checkbox of this.nodes.selectRel.getElementsByClassName(this.CSS.checkboxInput)) {
+ checkbox.checked = relAttr.indexOf(checkbox.dataset.rel) !== -1;
+ }
this.selection.save();
} else {
this.nodes.button.classList.remove(this.CSS.buttonUnlink);
@@ -223,6 +220,49 @@ export default class Hyperlink {
this.inputOpened = true;
}
+ onBlur() {
+ const value = this.nodes.input.value || '';
+
+ if (!!this.config.validate && !!this.config.validate === true && !this.validateURL(value)) {
+ this.tooltip.show(this.nodes.input, this.i18n.t('The URL is not valid.'), {
+ placement: 'top',
+ });
+ setTimeout(() => {
+ this.tooltip.hide();
+ }, 1000);
+ return;
+ }
+
+ if (typeof this.config.configure === 'function') {
+ Promise.resolve(this.config.configure(value)).then(data => {
+ if (data.href) {
+ this.nodes.input.value = data.href;
+ }
+
+ if (typeof data.target === 'string') {
+ this.nodes.selectTarget.value = data.target;
+ }
+
+ if (typeof data.rels === 'object') {
+ const rels = {};
+ if (data.rels instanceof Array) {
+ for (const rel of data.rels) {
+ rels[rel] = true;
+ }
+ } else {
+ Object.assign(rels, data.rels);
+ }
+
+ for (const checkbox of this.nodes.selectRel.getElementsByClassName(this.CSS.checkboxInput)) {
+ if (checkbox.dataset.rel in rels) {
+ checkbox.checked = rels[checkbox.dataset.rel];
+ }
+ }
+ }
+ });
+ }
+ }
+
closeActions(clearSavedSelection = true) {
if (this.selection.isFakeBackgroundEnabled) {
const currentSelection = new SelectionUtils();
@@ -234,7 +274,9 @@ export default class Hyperlink {
this.nodes.wrapper.classList.remove(this.CSS.wrapperShowed);
this.nodes.input.value = '';
this.nodes.selectTarget.value = '';
- this.nodes.selectRel.value = '';
+ for (const checkbox of this.nodes.selectRel.getElementsByClassName(this.CSS.checkboxInput)) {
+ checkbox.checked = false;
+ }
if (clearSavedSelection) {
this.selection.clearSaved();
@@ -249,17 +291,23 @@ export default class Hyperlink {
let value = this.nodes.input.value || '';
let target = this.nodes.selectTarget.value || '';
- let rel = this.nodes.selectRel.value || '';
+ const rels = [];
+ for (const checkbox of this.nodes.selectRel.getElementsByClassName(this.CSS.checkboxInput)) {
+ if (checkbox.checked) {
+ rels.push(checkbox.dataset.rel);
+ }
+ }
if (!value.trim()) {
this.selection.restore();
this.unlink();
event.preventDefault();
this.closeActions();
+ return;
}
if (!!this.config.validate && !!this.config.validate === true && !this.validateURL(value)) {
- this.tooltip.show(this.nodes.input, 'The URL is not valid.', {
+ this.tooltip.show(this.nodes.input, this.i18n.t('The URL is not valid.'), {
placement: 'top',
});
setTimeout(() => {
@@ -273,19 +321,19 @@ export default class Hyperlink {
this.selection.restore();
this.selection.removeFakeBackground();
- this.insertLink(value, target, rel);
+ this.insertLink(value, target, rels.join(' '));
this.selection.collapseToEnd();
this.inlineToolbar.close();
}
validateURL(str) {
- const pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
- '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
- '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
- '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
- '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
- '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
+ const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
+ '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
+ '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
+ '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator
return !!pattern.test(str);
}
@@ -311,23 +359,23 @@ export default class Hyperlink {
return link;
}
- insertLink(link, target='', rel='') {
+ insertLink(link, target = '', rel = '') {
let anchorTag = this.selection.findParentTag('A');
if (anchorTag) {
this.selection.expandToTag(anchorTag);
- }else{
+ } else {
document.execCommand(this.commandLink, false, link);
anchorTag = this.selection.findParentTag('A');
}
- if(anchorTag) {
- if(!!target) {
+ if (anchorTag) {
+ if (!!target) {
anchorTag['target'] = target;
- }else{
+ } else {
anchorTag.removeAttribute('target');
}
- if(!!rel) {
+ if (!!rel) {
anchorTag['rel'] = rel;
- }else{
+ } else {
anchorTag.removeAttribute('rel');
}
}
@@ -346,10 +394,26 @@ export default class Hyperlink {
return icon;
}
- addOption(element, text, value=null) {
+ addOption(element, text, value = null) {
let option = document.createElement('option');
option.text = text;
option.value = value;
element.add(option);
}
+
+ addCheckbox(element, text, value, checked = false) {
+ let input = document.createElement('input');
+ input.type = 'checkbox';
+ input.classList.add(this.CSS.checkboxInput);
+ input.dataset.rel = value;
+ input.checked = checked;
+
+ const label = document.createElement('label');
+ label.classList.add(this.CSS.checkboxLabel);
+
+ label.appendChild(input);
+ label.insertAdjacentText('beforeend', text);
+
+ element.appendChild(label);
+ }
}