MDL-66539 atto_link: Non-intuitive action for atto link button

This commit is contained in:
Huong Nguyen 2021-04-07 14:33:13 +07:00
parent 30b8ad51f4
commit 6372d61f71
7 changed files with 355 additions and 51 deletions

View File

@ -29,3 +29,4 @@ $string['openinnewwindow'] = 'Open in new window';
$string['pluginname'] = 'Link'; $string['pluginname'] = 'Link';
$string['unlink'] = 'Unlink'; $string['unlink'] = 'Unlink';
$string['privacy:metadata'] = 'The atto_link plugin does not store any personal data.'; $string['privacy:metadata'] = 'The atto_link plugin does not store any personal data.';
$string['texttodisplay'] = 'Text to display';

View File

@ -35,7 +35,8 @@ function atto_link_strings_for_js() {
'unlink', 'unlink',
'enterurl', 'enterurl',
'browserepositories', 'browserepositories',
'openinnewwindow'), 'openinnewwindow',
'texttodisplay'),
'atto_link'); 'atto_link');
} }

View File

@ -12,6 +12,7 @@ Feature: Add links to Atto
And I set the field "Description" to "Super cool" And I set the field "Description" to "Super cool"
And I select the text in the "Description" Atto editor And I select the text in the "Description" Atto editor
And I click on "Link" "button" And I click on "Link" "button"
Then the field "Text to display" matches value "Super cool"
And I click on "Browse repositories..." "button" And I click on "Browse repositories..." "button"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element" And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "moodle-logo.png" "link" And I click on "moodle-logo.png" "link"
@ -22,4 +23,59 @@ Feature: Add links to Atto
And I set the field "Text editor" to "Plain text area" And I set the field "Text editor" to "Plain text area"
And I press "Save changes" And I press "Save changes"
And I click on "Edit profile" "link" in the "region-main" "region" And I click on "Edit profile" "link" in the "region-main" "region"
Then I should see "Super cool</a>" And I should see "Super cool</a>"
@javascript
Scenario: Insert a link without providing text to display
Given I log in as "admin"
When I open my profile in edit mode
And I click on "Link" "button"
And I set the field "Enter a URL" to "https://moodle.org/"
Then the field "Text to display" matches value "https://moodle.org/"
And I click on "Create link" "button"
And I should see "https://moodle.org/"
And I click on "Link" "button"
And the field "Text to display" matches value "https://moodle.org/"
And the field "Enter a URL" matches value "https://moodle.org/"
And I click on "Close" "button" in the "Create link" "dialogue"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
And I should see "https://moodle.org/</a>"
@javascript
Scenario: Insert a link with providing text to display
Given I log in as "admin"
When I open my profile in edit mode
And I click on "Link" "button"
And I set the field "Text to display" to "Moodle - Open-source learning platform"
And I set the field "Enter a URL" to "https://moodle.org/"
And I click on "Create link" "button"
Then I should see "Moodle - Open-source learning platform"
And I click on "Link" "button"
And the field "Text to display" matches value "Moodle - Open-source learning platform"
And the field "Enter a URL" matches value "https://moodle.org/"
And I click on "Close" "button" in the "Create link" "dialogue"
And I click on "Show more buttons" "button"
And I click on "HTML" "button"
And I should see "Moodle - Open-source learning platform</a>"
And I should not see "https://moodle.org/</a>"
@javascript
Scenario: Edit a link that already had a custom text to display
Given I log in as "admin"
And I follow "Preferences" in the user menu
And I follow "Editor preferences"
And I set the field "Text editor" to "Plain text area"
And I press "Save changes"
And I click on "Edit profile" "link" in the "region-main" "region"
And I set the field "Description" to "<a href=\"https://moodle.org/\">Moodle - Open-source learning platform</a>"
And I click on "Update profile" "button"
And I follow "Preferences" in the user menu
And I follow "Editor preferences"
And I set the field "Text editor" to "Atto HTML editor"
And I press "Save changes"
When I click on "Edit profile" "link" in the "region-main" "region"
Then I should see "Moodle - Open-source learning platform"
And I click on "Link" "button"
And the field "Text to display" matches value "Moodle - Open-source learning platform"
And the field "Enter a URL" matches value "https://moodle.org/"

View File

@ -36,13 +36,23 @@ YUI.add('moodle-atto_link-button', function (Y, NAME) {
var COMPONENTNAME = 'atto_link', var COMPONENTNAME = 'atto_link',
CSS = { CSS = {
NEWWINDOW: 'atto_link_openinnewwindow', NEWWINDOW: 'atto_link_openinnewwindow',
URLINPUT: 'atto_link_urlentry' URLINPUT: 'atto_link_urlentry',
URLTEXT: 'atto_link_urltext'
}, },
SELECTORS = { SELECTORS = {
URLINPUT: '.atto_link_urlentry' NEWWINDOW: '.atto_link_openinnewwindow',
URLINPUT: '.atto_link_urlentry',
URLTEXT: '.atto_link_urltext',
SUBMIT: '.submit',
LINKBROWSER: '.openlinkbrowser'
}, },
TEMPLATE = '' + TEMPLATE = '' +
'<form class="atto_form">' + '<form class="atto_form">' +
'<div class="mb-1">' +
'<label for="{{elementid}}_atto_link_urltext">{{get_string "texttodisplay" component}}</label>' +
'<input class="form-control fullwidth {{CSS.URLTEXT}}" type="text" ' +
'id="{{elementid}}_atto_link_urltext" size="32"/>' +
'</div>' +
'{{#if showFilepicker}}' + '{{#if showFilepicker}}' +
'<label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label>' + '<label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label>' +
'<div class="input-group input-append w-100 mb-1">' + '<div class="input-group input-append w-100 mb-1">' +
@ -61,7 +71,8 @@ var COMPONENTNAME = 'atto_link',
'</div>' + '</div>' +
'{{/if}}' + '{{/if}}' +
'<div class="form-check">' + '<div class="form-check">' +
'<input type="checkbox" class="form-check-input newwindow" id="{{elementid}}_{{CSS.NEWWINDOW}}"/>' + '<input type="checkbox" class="form-check-input newwindow {{CSS.NEWWINDOW}}" ' +
'id="{{elementid}}_{{CSS.NEWWINDOW}}"/>' +
'<label class="form-check-label" for="{{elementid}}_{{CSS.NEWWINDOW}}">' + '<label class="form-check-label" for="{{elementid}}_{{CSS.NEWWINDOW}}">' +
'{{get_string "openinnewwindow" component}}' + '{{get_string "openinnewwindow" component}}' +
'</label>' + '</label>' +
@ -92,6 +103,14 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
*/ */
_content: null, _content: null,
/**
* Text to display has value or not.
* @property _hasTextToDisplay
* @type Boolean
* @private
*/
_hasTextToDisplay: false,
initializer: function() { initializer: function() {
// Add the link button first. // Add the link button first.
this.addButton({ this.addButton({
@ -156,7 +175,8 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
anchornodes, anchornodes,
anchornode, anchornode,
url, url,
target; target,
textToDisplay;
// Note this is a document fragment and YUI doesn't like them. // Note this is a document fragment and YUI doesn't like them.
if (!selectednode) { if (!selectednode) {
@ -169,13 +189,24 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
this._currentSelection = this.get('host').getSelectionFromNode(anchornode); this._currentSelection = this.get('host').getSelectionFromNode(anchornode);
url = anchornode.getAttribute('href'); url = anchornode.getAttribute('href');
target = anchornode.getAttribute('target'); target = anchornode.getAttribute('target');
textToDisplay = anchornode.get('innerText');
if (url !== '') { if (url !== '') {
this._content.one('.url').setAttribute('value', url); this._content.one(SELECTORS.URLINPUT).setAttribute('value', url);
}
if (textToDisplay !== '') {
this._content.one(SELECTORS.URLTEXT).set('value', textToDisplay);
} }
if (target === '_blank') { if (target === '_blank') {
this._content.one('.newwindow').setAttribute('checked', 'checked'); this._content.one(SELECTORS.NEWWINDOW).setAttribute('checked', 'checked');
} else { } else {
this._content.one('.newwindow').removeAttribute('checked'); this._content.one(SELECTORS.NEWWINDOW).removeAttribute('checked');
}
} else {
// User is selecting some text before clicking on the Link button.
textToDisplay = Y.one(selectednode).get('textContent');
if (textToDisplay !== '') {
this._hasTextToDisplay = true;
this._content.one(SELECTORS.URLTEXT).set('value', textToDisplay);
} }
} }
}, },
@ -218,7 +249,7 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
focusAfterHide: null focusAfterHide: null
}).hide(); }).hide();
input = this._content.one('.url'); input = this._content.one(SELECTORS.URLINPUT);
value = input.get('value'); value = input.get('value');
if (value !== '') { if (value !== '') {
@ -250,14 +281,23 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
link, link,
selectednode, selectednode,
target, target,
anchornodes; anchornodes,
isUpdating,
urlText,
textToDisplay;
this.editor.focus(); this.editor.focus();
host.setSelection(this._currentSelection); host.setSelection(this._currentSelection);
isUpdating = !this._currentSelection[0].collapsed;
urlText = this._content.one(SELECTORS.URLTEXT);
textToDisplay = urlText.get('value').replace(/(<([^>]+)>)/gi, "").trim();
if (textToDisplay === '') {
textToDisplay = url;
}
if (this._currentSelection[0].collapsed) { if (!isUpdating) {
// Firefox cannot add links when the selection is empty so we will add it manually. // Firefox cannot add links when the selection is empty so we will add it manually.
link = Y.Node.create('<a>' + url + '</a>'); link = Y.Node.create('<a>' + textToDisplay + '</a>');
link.setAttribute('href', url); link.setAttribute('href', url);
// Add the node and select it to replicate the behaviour of execCommand. // Add the node and select it to replicate the behaviour of execCommand.
@ -279,12 +319,16 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
anchornodes = this._findSelectedAnchors(Y.one(selectednode)); anchornodes = this._findSelectedAnchors(Y.one(selectednode));
// Add new window attributes if requested. // Add new window attributes if requested.
Y.Array.each(anchornodes, function(anchornode) { Y.Array.each(anchornodes, function(anchornode) {
target = this._content.one('.newwindow'); target = this._content.one(SELECTORS.NEWWINDOW);
if (target.get('checked')) { if (target.get('checked')) {
anchornode.setAttribute('target', '_blank'); anchornode.setAttribute('target', '_blank');
} else { } else {
anchornode.removeAttribute('target'); anchornode.removeAttribute('target');
} }
if (isUpdating) {
// The 'createLink' command do not allow to set the custom text to display. So we need to do it here.
anchornode.set('innerText', textToDisplay);
}
}, this); }, this);
return selectednode; return selectednode;
@ -342,9 +386,11 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
CSS: CSS CSS: CSS
})); }));
this._content.one('.submit').on('click', this._setLink, this); this._content.one(SELECTORS.URLINPUT).on('keyup', this._updateTextToDisplay, this);
this._content.one(SELECTORS.URLTEXT).on('keyup', this._setTextToDisplayState, this);
this._content.one(SELECTORS.SUBMIT).on('click', this._setLink, this);
if (canShowFilepicker) { if (canShowFilepicker) {
this._content.one('.openlinkbrowser').on('click', function(e) { this._content.one(SELECTORS.LINKBROWSER).on('click', function(e) {
e.preventDefault(); e.preventDefault();
this.get('host').showFilepicker('link', this._filepickerCallback, this); this.get('host').showFilepicker('link', this._filepickerCallback, this);
}, this); }, this);
@ -375,10 +421,10 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
// would then be unlinked too. // would then be unlinked too.
nodes.each(function(node) { nodes.each(function(node) {
// We need to select the whole anchor node for this to work in some browsers. // We need to select the whole anchor node for this to work in some browsers.
// We only need to search up because getSeletedNodes returns all Nodes in the selection. // We only need to search up because getSelectedNodes returns all Nodes in the selection.
var anchor = node.ancestor('a', true); var anchor = node.ancestor('a', true);
if (anchor) { if (anchor) {
// Set the selection to the whole of the first anchro. // Set the selection to the whole of the first anchor.
host.setSelection(host.getSelectionFromNode(anchor)); host.setSelection(host.getSelectionFromNode(anchor));
// Call the browser unlink. // Call the browser unlink.
@ -397,6 +443,42 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
this.markUpdated(); this.markUpdated();
} }
} }
},
/**
* Set the current text to display state.
*
* @method _setTextToDisplayState
* @private
*/
_setTextToDisplayState: function() {
var urlText,
urlTextVal;
urlText = this._content.one(SELECTORS.URLTEXT);
urlTextVal = urlText.get('value');
if (urlTextVal !== '') {
this._hasTextToDisplay = true;
} else {
this._hasTextToDisplay = false;
}
},
/**
* Update the text to display if the user does not provide the custom text.
*
* @method _updateTextToDisplay
* @private
*/
_updateTextToDisplay: function() {
var urlEntry,
urlText,
urlEntryVal;
urlEntry = this._content.one(SELECTORS.URLINPUT);
urlText = this._content.one(SELECTORS.URLTEXT);
urlEntryVal = urlEntry.get('value');
if (!this._hasTextToDisplay) {
urlText.set('value', urlEntryVal);
}
} }
}); });

File diff suppressed because one or more lines are too long

View File

@ -36,13 +36,23 @@ YUI.add('moodle-atto_link-button', function (Y, NAME) {
var COMPONENTNAME = 'atto_link', var COMPONENTNAME = 'atto_link',
CSS = { CSS = {
NEWWINDOW: 'atto_link_openinnewwindow', NEWWINDOW: 'atto_link_openinnewwindow',
URLINPUT: 'atto_link_urlentry' URLINPUT: 'atto_link_urlentry',
URLTEXT: 'atto_link_urltext'
}, },
SELECTORS = { SELECTORS = {
URLINPUT: '.atto_link_urlentry' NEWWINDOW: '.atto_link_openinnewwindow',
URLINPUT: '.atto_link_urlentry',
URLTEXT: '.atto_link_urltext',
SUBMIT: '.submit',
LINKBROWSER: '.openlinkbrowser'
}, },
TEMPLATE = '' + TEMPLATE = '' +
'<form class="atto_form">' + '<form class="atto_form">' +
'<div class="mb-1">' +
'<label for="{{elementid}}_atto_link_urltext">{{get_string "texttodisplay" component}}</label>' +
'<input class="form-control fullwidth {{CSS.URLTEXT}}" type="text" ' +
'id="{{elementid}}_atto_link_urltext" size="32"/>' +
'</div>' +
'{{#if showFilepicker}}' + '{{#if showFilepicker}}' +
'<label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label>' + '<label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label>' +
'<div class="input-group input-append w-100 mb-1">' + '<div class="input-group input-append w-100 mb-1">' +
@ -61,7 +71,8 @@ var COMPONENTNAME = 'atto_link',
'</div>' + '</div>' +
'{{/if}}' + '{{/if}}' +
'<div class="form-check">' + '<div class="form-check">' +
'<input type="checkbox" class="form-check-input newwindow" id="{{elementid}}_{{CSS.NEWWINDOW}}"/>' + '<input type="checkbox" class="form-check-input newwindow {{CSS.NEWWINDOW}}" ' +
'id="{{elementid}}_{{CSS.NEWWINDOW}}"/>' +
'<label class="form-check-label" for="{{elementid}}_{{CSS.NEWWINDOW}}">' + '<label class="form-check-label" for="{{elementid}}_{{CSS.NEWWINDOW}}">' +
'{{get_string "openinnewwindow" component}}' + '{{get_string "openinnewwindow" component}}' +
'</label>' + '</label>' +
@ -92,6 +103,14 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
*/ */
_content: null, _content: null,
/**
* Text to display has value or not.
* @property _hasTextToDisplay
* @type Boolean
* @private
*/
_hasTextToDisplay: false,
initializer: function() { initializer: function() {
// Add the link button first. // Add the link button first.
this.addButton({ this.addButton({
@ -156,7 +175,8 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
anchornodes, anchornodes,
anchornode, anchornode,
url, url,
target; target,
textToDisplay;
// Note this is a document fragment and YUI doesn't like them. // Note this is a document fragment and YUI doesn't like them.
if (!selectednode) { if (!selectednode) {
@ -169,13 +189,24 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
this._currentSelection = this.get('host').getSelectionFromNode(anchornode); this._currentSelection = this.get('host').getSelectionFromNode(anchornode);
url = anchornode.getAttribute('href'); url = anchornode.getAttribute('href');
target = anchornode.getAttribute('target'); target = anchornode.getAttribute('target');
textToDisplay = anchornode.get('innerText');
if (url !== '') { if (url !== '') {
this._content.one('.url').setAttribute('value', url); this._content.one(SELECTORS.URLINPUT).setAttribute('value', url);
}
if (textToDisplay !== '') {
this._content.one(SELECTORS.URLTEXT).set('value', textToDisplay);
} }
if (target === '_blank') { if (target === '_blank') {
this._content.one('.newwindow').setAttribute('checked', 'checked'); this._content.one(SELECTORS.NEWWINDOW).setAttribute('checked', 'checked');
} else { } else {
this._content.one('.newwindow').removeAttribute('checked'); this._content.one(SELECTORS.NEWWINDOW).removeAttribute('checked');
}
} else {
// User is selecting some text before clicking on the Link button.
textToDisplay = Y.one(selectednode).get('textContent');
if (textToDisplay !== '') {
this._hasTextToDisplay = true;
this._content.one(SELECTORS.URLTEXT).set('value', textToDisplay);
} }
} }
}, },
@ -218,7 +249,7 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
focusAfterHide: null focusAfterHide: null
}).hide(); }).hide();
input = this._content.one('.url'); input = this._content.one(SELECTORS.URLINPUT);
value = input.get('value'); value = input.get('value');
if (value !== '') { if (value !== '') {
@ -250,14 +281,23 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
link, link,
selectednode, selectednode,
target, target,
anchornodes; anchornodes,
isUpdating,
urlText,
textToDisplay;
this.editor.focus(); this.editor.focus();
host.setSelection(this._currentSelection); host.setSelection(this._currentSelection);
isUpdating = !this._currentSelection[0].collapsed;
urlText = this._content.one(SELECTORS.URLTEXT);
textToDisplay = urlText.get('value').replace(/(<([^>]+)>)/gi, "").trim();
if (textToDisplay === '') {
textToDisplay = url;
}
if (this._currentSelection[0].collapsed) { if (!isUpdating) {
// Firefox cannot add links when the selection is empty so we will add it manually. // Firefox cannot add links when the selection is empty so we will add it manually.
link = Y.Node.create('<a>' + url + '</a>'); link = Y.Node.create('<a>' + textToDisplay + '</a>');
link.setAttribute('href', url); link.setAttribute('href', url);
// Add the node and select it to replicate the behaviour of execCommand. // Add the node and select it to replicate the behaviour of execCommand.
@ -279,12 +319,16 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
anchornodes = this._findSelectedAnchors(Y.one(selectednode)); anchornodes = this._findSelectedAnchors(Y.one(selectednode));
// Add new window attributes if requested. // Add new window attributes if requested.
Y.Array.each(anchornodes, function(anchornode) { Y.Array.each(anchornodes, function(anchornode) {
target = this._content.one('.newwindow'); target = this._content.one(SELECTORS.NEWWINDOW);
if (target.get('checked')) { if (target.get('checked')) {
anchornode.setAttribute('target', '_blank'); anchornode.setAttribute('target', '_blank');
} else { } else {
anchornode.removeAttribute('target'); anchornode.removeAttribute('target');
} }
if (isUpdating) {
// The 'createLink' command do not allow to set the custom text to display. So we need to do it here.
anchornode.set('innerText', textToDisplay);
}
}, this); }, this);
return selectednode; return selectednode;
@ -342,9 +386,11 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
CSS: CSS CSS: CSS
})); }));
this._content.one('.submit').on('click', this._setLink, this); this._content.one(SELECTORS.URLINPUT).on('keyup', this._updateTextToDisplay, this);
this._content.one(SELECTORS.URLTEXT).on('keyup', this._setTextToDisplayState, this);
this._content.one(SELECTORS.SUBMIT).on('click', this._setLink, this);
if (canShowFilepicker) { if (canShowFilepicker) {
this._content.one('.openlinkbrowser').on('click', function(e) { this._content.one(SELECTORS.LINKBROWSER).on('click', function(e) {
e.preventDefault(); e.preventDefault();
this.get('host').showFilepicker('link', this._filepickerCallback, this); this.get('host').showFilepicker('link', this._filepickerCallback, this);
}, this); }, this);
@ -375,10 +421,10 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
// would then be unlinked too. // would then be unlinked too.
nodes.each(function(node) { nodes.each(function(node) {
// We need to select the whole anchor node for this to work in some browsers. // We need to select the whole anchor node for this to work in some browsers.
// We only need to search up because getSeletedNodes returns all Nodes in the selection. // We only need to search up because getSelectedNodes returns all Nodes in the selection.
var anchor = node.ancestor('a', true); var anchor = node.ancestor('a', true);
if (anchor) { if (anchor) {
// Set the selection to the whole of the first anchro. // Set the selection to the whole of the first anchor.
host.setSelection(host.getSelectionFromNode(anchor)); host.setSelection(host.getSelectionFromNode(anchor));
// Call the browser unlink. // Call the browser unlink.
@ -397,6 +443,42 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
this.markUpdated(); this.markUpdated();
} }
} }
},
/**
* Set the current text to display state.
*
* @method _setTextToDisplayState
* @private
*/
_setTextToDisplayState: function() {
var urlText,
urlTextVal;
urlText = this._content.one(SELECTORS.URLTEXT);
urlTextVal = urlText.get('value');
if (urlTextVal !== '') {
this._hasTextToDisplay = true;
} else {
this._hasTextToDisplay = false;
}
},
/**
* Update the text to display if the user does not provide the custom text.
*
* @method _updateTextToDisplay
* @private
*/
_updateTextToDisplay: function() {
var urlEntry,
urlText,
urlEntryVal;
urlEntry = this._content.one(SELECTORS.URLINPUT);
urlText = this._content.one(SELECTORS.URLTEXT);
urlEntryVal = urlEntry.get('value');
if (!this._hasTextToDisplay) {
urlText.set('value', urlEntryVal);
}
} }
}); });

View File

@ -34,13 +34,23 @@
var COMPONENTNAME = 'atto_link', var COMPONENTNAME = 'atto_link',
CSS = { CSS = {
NEWWINDOW: 'atto_link_openinnewwindow', NEWWINDOW: 'atto_link_openinnewwindow',
URLINPUT: 'atto_link_urlentry' URLINPUT: 'atto_link_urlentry',
URLTEXT: 'atto_link_urltext'
}, },
SELECTORS = { SELECTORS = {
URLINPUT: '.atto_link_urlentry' NEWWINDOW: '.atto_link_openinnewwindow',
URLINPUT: '.atto_link_urlentry',
URLTEXT: '.atto_link_urltext',
SUBMIT: '.submit',
LINKBROWSER: '.openlinkbrowser'
}, },
TEMPLATE = '' + TEMPLATE = '' +
'<form class="atto_form">' + '<form class="atto_form">' +
'<div class="mb-1">' +
'<label for="{{elementid}}_atto_link_urltext">{{get_string "texttodisplay" component}}</label>' +
'<input class="form-control fullwidth {{CSS.URLTEXT}}" type="text" ' +
'id="{{elementid}}_atto_link_urltext" size="32"/>' +
'</div>' +
'{{#if showFilepicker}}' + '{{#if showFilepicker}}' +
'<label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label>' + '<label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label>' +
'<div class="input-group input-append w-100 mb-1">' + '<div class="input-group input-append w-100 mb-1">' +
@ -59,7 +69,8 @@ var COMPONENTNAME = 'atto_link',
'</div>' + '</div>' +
'{{/if}}' + '{{/if}}' +
'<div class="form-check">' + '<div class="form-check">' +
'<input type="checkbox" class="form-check-input newwindow" id="{{elementid}}_{{CSS.NEWWINDOW}}"/>' + '<input type="checkbox" class="form-check-input newwindow {{CSS.NEWWINDOW}}" ' +
'id="{{elementid}}_{{CSS.NEWWINDOW}}"/>' +
'<label class="form-check-label" for="{{elementid}}_{{CSS.NEWWINDOW}}">' + '<label class="form-check-label" for="{{elementid}}_{{CSS.NEWWINDOW}}">' +
'{{get_string "openinnewwindow" component}}' + '{{get_string "openinnewwindow" component}}' +
'</label>' + '</label>' +
@ -90,6 +101,14 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
*/ */
_content: null, _content: null,
/**
* Text to display has value or not.
* @property _hasTextToDisplay
* @type Boolean
* @private
*/
_hasTextToDisplay: false,
initializer: function() { initializer: function() {
// Add the link button first. // Add the link button first.
this.addButton({ this.addButton({
@ -154,7 +173,8 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
anchornodes, anchornodes,
anchornode, anchornode,
url, url,
target; target,
textToDisplay;
// Note this is a document fragment and YUI doesn't like them. // Note this is a document fragment and YUI doesn't like them.
if (!selectednode) { if (!selectednode) {
@ -167,13 +187,24 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
this._currentSelection = this.get('host').getSelectionFromNode(anchornode); this._currentSelection = this.get('host').getSelectionFromNode(anchornode);
url = anchornode.getAttribute('href'); url = anchornode.getAttribute('href');
target = anchornode.getAttribute('target'); target = anchornode.getAttribute('target');
textToDisplay = anchornode.get('innerText');
if (url !== '') { if (url !== '') {
this._content.one('.url').setAttribute('value', url); this._content.one(SELECTORS.URLINPUT).setAttribute('value', url);
}
if (textToDisplay !== '') {
this._content.one(SELECTORS.URLTEXT).set('value', textToDisplay);
} }
if (target === '_blank') { if (target === '_blank') {
this._content.one('.newwindow').setAttribute('checked', 'checked'); this._content.one(SELECTORS.NEWWINDOW).setAttribute('checked', 'checked');
} else { } else {
this._content.one('.newwindow').removeAttribute('checked'); this._content.one(SELECTORS.NEWWINDOW).removeAttribute('checked');
}
} else {
// User is selecting some text before clicking on the Link button.
textToDisplay = Y.one(selectednode).get('textContent');
if (textToDisplay !== '') {
this._hasTextToDisplay = true;
this._content.one(SELECTORS.URLTEXT).set('value', textToDisplay);
} }
} }
}, },
@ -216,7 +247,7 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
focusAfterHide: null focusAfterHide: null
}).hide(); }).hide();
input = this._content.one('.url'); input = this._content.one(SELECTORS.URLINPUT);
value = input.get('value'); value = input.get('value');
if (value !== '') { if (value !== '') {
@ -248,14 +279,23 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
link, link,
selectednode, selectednode,
target, target,
anchornodes; anchornodes,
isUpdating,
urlText,
textToDisplay;
this.editor.focus(); this.editor.focus();
host.setSelection(this._currentSelection); host.setSelection(this._currentSelection);
isUpdating = !this._currentSelection[0].collapsed;
urlText = this._content.one(SELECTORS.URLTEXT);
textToDisplay = urlText.get('value').replace(/(<([^>]+)>)/gi, "").trim();
if (textToDisplay === '') {
textToDisplay = url;
}
if (this._currentSelection[0].collapsed) { if (!isUpdating) {
// Firefox cannot add links when the selection is empty so we will add it manually. // Firefox cannot add links when the selection is empty so we will add it manually.
link = Y.Node.create('<a>' + url + '</a>'); link = Y.Node.create('<a>' + textToDisplay + '</a>');
link.setAttribute('href', url); link.setAttribute('href', url);
// Add the node and select it to replicate the behaviour of execCommand. // Add the node and select it to replicate the behaviour of execCommand.
@ -277,12 +317,16 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
anchornodes = this._findSelectedAnchors(Y.one(selectednode)); anchornodes = this._findSelectedAnchors(Y.one(selectednode));
// Add new window attributes if requested. // Add new window attributes if requested.
Y.Array.each(anchornodes, function(anchornode) { Y.Array.each(anchornodes, function(anchornode) {
target = this._content.one('.newwindow'); target = this._content.one(SELECTORS.NEWWINDOW);
if (target.get('checked')) { if (target.get('checked')) {
anchornode.setAttribute('target', '_blank'); anchornode.setAttribute('target', '_blank');
} else { } else {
anchornode.removeAttribute('target'); anchornode.removeAttribute('target');
} }
if (isUpdating) {
// The 'createLink' command do not allow to set the custom text to display. So we need to do it here.
anchornode.set('innerText', textToDisplay);
}
}, this); }, this);
return selectednode; return selectednode;
@ -340,9 +384,11 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
CSS: CSS CSS: CSS
})); }));
this._content.one('.submit').on('click', this._setLink, this); this._content.one(SELECTORS.URLINPUT).on('keyup', this._updateTextToDisplay, this);
this._content.one(SELECTORS.URLTEXT).on('keyup', this._setTextToDisplayState, this);
this._content.one(SELECTORS.SUBMIT).on('click', this._setLink, this);
if (canShowFilepicker) { if (canShowFilepicker) {
this._content.one('.openlinkbrowser').on('click', function(e) { this._content.one(SELECTORS.LINKBROWSER).on('click', function(e) {
e.preventDefault(); e.preventDefault();
this.get('host').showFilepicker('link', this._filepickerCallback, this); this.get('host').showFilepicker('link', this._filepickerCallback, this);
}, this); }, this);
@ -373,10 +419,10 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
// would then be unlinked too. // would then be unlinked too.
nodes.each(function(node) { nodes.each(function(node) {
// We need to select the whole anchor node for this to work in some browsers. // We need to select the whole anchor node for this to work in some browsers.
// We only need to search up because getSeletedNodes returns all Nodes in the selection. // We only need to search up because getSelectedNodes returns all Nodes in the selection.
var anchor = node.ancestor('a', true); var anchor = node.ancestor('a', true);
if (anchor) { if (anchor) {
// Set the selection to the whole of the first anchro. // Set the selection to the whole of the first anchor.
host.setSelection(host.getSelectionFromNode(anchor)); host.setSelection(host.getSelectionFromNode(anchor));
// Call the browser unlink. // Call the browser unlink.
@ -395,5 +441,41 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
this.markUpdated(); this.markUpdated();
} }
} }
},
/**
* Set the current text to display state.
*
* @method _setTextToDisplayState
* @private
*/
_setTextToDisplayState: function() {
var urlText,
urlTextVal;
urlText = this._content.one(SELECTORS.URLTEXT);
urlTextVal = urlText.get('value');
if (urlTextVal !== '') {
this._hasTextToDisplay = true;
} else {
this._hasTextToDisplay = false;
}
},
/**
* Update the text to display if the user does not provide the custom text.
*
* @method _updateTextToDisplay
* @private
*/
_updateTextToDisplay: function() {
var urlEntry,
urlText,
urlEntryVal;
urlEntry = this._content.one(SELECTORS.URLINPUT);
urlText = this._content.one(SELECTORS.URLTEXT);
urlEntryVal = urlEntry.get('value');
if (!this._hasTextToDisplay) {
urlText.set('value', urlEntryVal);
}
} }
}); });