modules/cms/client/js/galleryhref.js
/* eslint no-cond-assign:0, no-new:0 */
/* jslint browser:true, node:true, esnext:true */
'use strict';
var MediumEditor = require('medium-editor');
module.exports = MediumEditor.extensions.form.extend({
/* Anchor Form Options */
/* customClassOption: [string] (previously options.anchorButton + options.anchorButtonClass)
* Custom class name the user can optionally have added to their created links (ie 'button').
* If passed as a non-empty string, a checkbox will be displayed allowing the user to choose
* whether to have the class added to the created link or not.
*/
customClassOption: null,
/* customClassOptionText: [string]
* text to be shown in the checkbox when the __customClassOption__ is being used.
*/
customClassOptionText: 'Button',
/* linkValidation: [boolean] (previously options.checkLinkFormat)
* enables/disables check for common URL protocols on anchor links.
*/
linkValidation: false,
/* placeholderText: [string] (previously options.anchorInputPlaceholder)
* text to be shown as placeholder of the anchor input.
*/
placeholderText: 'Paste or type a link',
/* targetCheckbox: [boolean] (previously options.anchorTarget)
* enables/disables displaying a "Open in new window" checkbox, which when checked
* changes the `target` attribute of the created link.
*/
targetCheckbox: false,
/* targetCheckboxText: [string] (previously options.anchorInputCheckboxLabel)
* text to be shown in the checkbox enabled via the __targetCheckbox__ option.
*/
targetCheckboxText: 'Open in new window',
// Options for the Button base class
name: 'galleryhref',
action: 'createLink',
init: function () {
this.button = this.document.createElement('button');
this.button.classList.add('medium-editor-action');
this.button.innerHTML = '<img src="img/cms/cms/svg/ui/editor/insert-attachment.svg" alt="Insert Attachment" />';
this.on(this.button, 'click', this.handleClick.bind(this));
},
// Called when the button the toolbar is clicked
// Overrides ButtonExtension.handleClick
handleClick: function (event) {
event.preventDefault();
event.stopPropagation();
var range = MediumEditor.selection.getSelectionRange(this.document);
if (range.startContainer.nodeName.toLowerCase() === 'a' ||
range.endContainer.nodeName.toLowerCase() === 'a' ||
MediumEditor.util.getClosestTag(MediumEditor.selection.getSelectedParentElement(range), 'a')) {
return this.execAction('unlink');
}
if (!this.isDisplayed()) {
top.LackeyManager
.stack
.inspectMedia({}, null)
.then(result => {
if (result) {
this.execAction(this.action, {value: this.checkLinkFormat(result.source), target: '_self'});
}
});
}
return false;
},
// Called by medium-editor to append form to the toolbar
getForm: function () {
if (!this.form) {
this.form = this.createForm();
}
return this.form;
},
getTemplate: function () {
var template = [
'<input type="text" class="medium-editor-toolbar-input" placeholder="', this.placeholderText, '">'
];
template.push(
'<a href="#" class="medium-editor-toolbar-save">',
this.getEditorOption('buttonLabels') === 'fontawesome' ? '<i class="fa fa-check"></i>' : this.formSaveLabel,
'</a>'
);
template.push('<a href="#" class="medium-editor-toolbar-close">',
this.getEditorOption('buttonLabels') === 'fontawesome' ? '<i class="fa fa-times"></i>' : this.formCloseLabel,
'</a>');
// both of these options are slightly moot with the ability to
// override the various form buildup/serialize functions.
if (this.targetCheckbox) {
// fixme: ideally, this targetCheckboxText would be a formLabel too,
// figure out how to deprecate? also consider `fa-` icon default implcations.
template.push(
'<div class="medium-editor-toolbar-form-row">',
'<input type="checkbox" class="medium-editor-toolbar-anchor-target">',
'<label>',
this.targetCheckboxText,
'</label>',
'</div>'
);
}
if (this.customClassOption) {
// fixme: expose this `Button` text as a formLabel property, too
// and provide similar access to a `fa-` icon default.
template.push(
'<div class="medium-editor-toolbar-form-row">',
'<input type="checkbox" class="medium-editor-toolbar-anchor-button">',
'<label>',
this.customClassOptionText,
'</label>',
'</div>'
);
}
return template.join('');
},
// Used by medium-editor when the default toolbar is to be displayed
isDisplayed: function () {
return MediumEditor.extensions.form.prototype.isDisplayed.apply(this);
},
showForm: function (opts) {
var input = this.getInput(),
targetCheckbox = this.getAnchorTargetCheckbox(),
buttonCheckbox = this.getAnchorButtonCheckbox();
opts = opts || { value: '' };
// TODO: This is for backwards compatability
// We don't need to support the 'string' argument in 6.0.0
if (typeof opts === 'string') {
opts = {
value: opts
};
}
this.base.saveSelection();
this.hideToolbarDefaultActions();
MediumEditor.extensions.form.prototype.showForm.apply(this);
this.setToolbarPosition();
input.value = opts.value;
input.focus();
// If we have a target checkbox, we want it to be checked/unchecked
// based on whether the existing link has target=_blank
if (targetCheckbox) {
targetCheckbox.checked = opts.target === '_blank';
}
// If we have a custom class checkbox, we want it to be checked/unchecked
// based on whether an existing link already has the class
if (buttonCheckbox) {
var classList = opts.buttonClass ? opts.buttonClass.split(' ') : [];
buttonCheckbox.checked = (classList.indexOf(this.customClassOption) !== -1);
}
},
// Called by core when tearing down medium-editor (destroy)
destroy: function () {
if (!this.form) {
return false;
}
if (this.form.parentNode) {
this.form.parentNode.removeChild(this.form);
}
delete this.form;
},
// core methods
ensureEncodedUri: function (str) {
return str === decodeURI(str) ? encodeURI(str) : str;
},
ensureEncodedUriComponent: function (str) {
return str === decodeURIComponent(str) ? encodeURIComponent(str) : str;
},
ensureEncodedParam: function (param) {
var split = param.split('='),
key = split[0],
val = split[1];
return key + (val === undefined ? '' : '=' + this.ensureEncodedUriComponent(val));
},
ensureEncodedQuery: function (queryString) {
return queryString.split('&').map(this.ensureEncodedParam.bind(this)).join('&');
},
checkLinkFormat: function (value) {
// Matches any alphabetical characters followed by ://
// Matches protocol relative "//"
// Matches common external protocols "mailto:" "tel:" "maps:"
// Matches relative hash link, begins with "#"
var urlSchemeRegex = /^([a-z]+:)?\/\/|^(mailto|tel|maps):|^\#/i,
hasScheme = urlSchemeRegex.test(value),
scheme = '',
// telRegex is a regex for checking if the string is a telephone number
telRegex = /^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/,
urlParts = value.match(/^(.*?)(?:\?(.*?))?(?:#(.*))?$/),
path = urlParts[1],
query = urlParts[2],
fragment = urlParts[3];
if (telRegex.test(value)) {
return 'tel:' + value;
}
if (!hasScheme) {
var host = path.split('/')[0];
// if the host part of the path looks like a hostname
if (host.match(/.+(\.|:).+/) || host === 'localhost') {
scheme = 'http://';
}
}
return scheme +
// Ensure path is encoded
this.ensureEncodedUri(path) +
// Ensure query is encoded
(query === undefined ? '' : '?' + this.ensureEncodedQuery(query)) +
// Include fragment unencoded as encodeUriComponent is too
// heavy handed for the many characters allowed in a fragment
(fragment === undefined ? '' : '#' + fragment);
},
doFormCancel: function () {
this.base.restoreSelection();
this.base.checkSelection();
},
createForm: function () {
var doc = this.document,
form = doc.createElement('div');
// Anchor Form (div)
form.className = 'medium-editor-toolbar-form';
form.id = 'medium-editor-toolbar-form-anchor-' + this.getEditorId();
form.innerHTML = this.getTemplate();
return form;
}
});