diff --git a/lib/editor/atto/plugins/link/tests/behat/link.feature b/lib/editor/atto/plugins/link/tests/behat/link.feature index c61ef69cd25..39dab8c1158 100644 --- a/lib/editor/atto/plugins/link/tests/behat/link.feature +++ b/lib/editor/atto/plugins/link/tests/behat/link.feature @@ -82,3 +82,37 @@ Feature: Add links to Atto 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/" + + @javascript + Scenario: Insert a link for an image + Given I log in as "admin" + And I follow "Private files" in the user menu + And I upload "lib/editor/atto/tests/fixtures/moodle-logo.png" file to "Files" filemanager + And I click on "Save changes" "button" + And I open my profile in edit mode + And I click on "Insert or edit image" "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 "moodle-logo.png" "link" + And I click on "Select this file" "button" + And I set the field "Describe this image for someone who cannot see it" to "It's the Moodle" + And I press "Save image" + And I select the text in the "Description" Atto editor + And I press the right key + And I press the shift left key + And I click on "Link" "button" + And I set the field "Enter a URL" to "https://moodle.org/" + And I set the field "Text to display" to "Moodle - Open-source learning platform" + And I click on "Create link" "button" + When I click on "Show more buttons" "button" + And I click on "HTML" "button" + Then I should see "Some text here + * window.getSelection() will return XYZSome text here + * + * @returns {string} Selected text + * @private + */ + _getTextSelection: function() { + var selText = ''; + var sel = window.getSelection(); + var rangeCount = sel.rangeCount; + if (rangeCount) { + var rangeTexts = []; + for (var i = 0; i < rangeCount; ++i) { + rangeTexts.push('' + sel.getRangeAt(i)); + } + selText = rangeTexts.join(''); + } + return selText; } }); diff --git a/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-min.js b/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-min.js index cefb3af85c0..f585967fa68 100644 --- a/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-min.js +++ b/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-min.js @@ -1 +1 @@ -YUI.add("moodle-atto_link-button",function(s,t){var n="atto_link",i={NEWWINDOW:"atto_link_openinnewwindow",URLINPUT:"atto_link_urlentry",URLTEXT:"atto_link_urltext"},a=".atto_link_openinnewwindow",o=".atto_link_urlentry",r=".atto_link_urltext",l=".submit",c=".openlinkbrowser";s.namespace("M.atto_link").Button=s.Base.create("button",s.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_content:null,_hasTextToDisplay:!1,initializer:function(){this.addButton({icon:"e/insert_edit_link",keys:"75",callback:this._displayDialogue,tags:"a",tagMatchRequiresAll:!1}),this.addButton({buttonName:"unlink",callback:this._unlink,icon:"e/remove_link",title:"unlink",tags:"a",tagMatchRequiresAll:!1})},_displayDialogue:function(){var t;this._currentSelection=this.get("host").getSelection(),!1!==this._currentSelection&&((t=this.getDialogue({headerContent:M.util.get_string("createlink",n),width:"auto",focusAfterHide:!0,focusOnShowSelector:o})).set("bodyContent",this._getDialogueContent()),this._resolveAnchors(),t.show())},_resolveAnchors:function(){var t,e,n,i=this.get("host").getSelectionParentNode();i&&(0<(i=this._findSelectedAnchors(s.one(i))).length?(i=i[0],this._currentSelection=this.get("host").getSelectionFromNode(i),t=i.getAttribute("href"),e=i.getAttribute("target"),n=i.get("innerText"),""!==t&&this._content.one(o).setAttribute("value",t),""!==n&&this._content.one(r).set("value",n),"_blank"===e?this._content.one(a).setAttribute("checked","checked"):this._content.one(a).removeAttribute("checked")):""!==(n=window.getSelection().toString())&&(this._hasTextToDisplay=!0,this._content.one(r).set("value",n)))},_filepickerCallback:function(t){this.getDialogue().set("focusAfterHide",null).hide(),""!==t.url&&(this._setLinkOnSelection(t.url),this.markUpdated())},_setLink:function(t){t.preventDefault(),this.getDialogue({focusAfterHide:null}).hide(),""!==(t=this._content.one(o).get("value"))&&(t=t.trim(),new RegExp(/^[a-zA-Z]*\.*\/|^#|^[a-zA-Z]*:/).test(t)||(t="http://"+t),this._setLinkOnSelection(t),this.markUpdated())},_setLinkOnSelection:function(t){var e,n,i,o,l=this.get("host");if(this.editor.focus(),l.setSelection(this._currentSelection),n=!this._currentSelection[0].collapsed,i=this._content.one(r),""===(o=i.get("value").replace(/(<([^>]+)>)/gi,"").trim())&&(o=t),n?(document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,t),e=l.getSelectionParentNode()):((i=s.Node.create(""+o+"")).setAttribute("href",t),e=l.insertContentAtFocusPoint(i.get("outerHTML")),l.setSelection(l.getSelectionFromNode(e))),e)return t=this._findSelectedAnchors(s.one(e)),s.Array.each(t,function(t){this._content.one(a).get("checked")?t.setAttribute("target","_blank"):t.removeAttribute("target"),n&&t.set("innerText",o)},this),e},_findSelectedAnchors:function(t){var e,n,i=t.get("tagName");return i&&"a"===i.toLowerCase()?[t]:(n=[],t.all("a").each(function(t){!e&&this.get("host").selectionContainsNode(t)&&n.push(t)},this),0
{{#if showFilepicker}}
{{else}}
{{/if}}

');return this._content=s.Node.create(e({showFilepicker:t,component:n,CSS:i})),this._content.one(o).on("keyup",this._updateTextToDisplay,this),this._content.one(r).on("keyup",this._setTextToDisplayState,this),this._content.one(l).on("click",this._setLink,this),t&&this._content.one(c).on("click",function(t){t.preventDefault(),this.get("host").showFilepicker("link",this._filepickerCallback,this)},this),this._content},_unlink:function(){var e=this.get("host"),t=e.getSelection();t&&t.length&&(t[0].startOffset===t[0].endOffset?(t=e.getSelectedNodes())&&(t.each(function(t){t=t.ancestor("a",!0);t&&(e.setSelection(e.getSelectionFromNode(t)),document.execCommand("unlink",!1,null))},this),this.markUpdated()):(document.execCommand("unlink",!1,null),this.markUpdated()))},_setTextToDisplayState:function(){var t=this._content.one(r).get("value");this._hasTextToDisplay=""!==t},_updateTextToDisplay:function(){var t=this._content.one(o),e=this._content.one(r),t=t.get("value");this._hasTextToDisplay||e.set("value",t)}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]}); \ No newline at end of file +YUI.add("moodle-atto_link-button",function(s,t){var n="atto_link",i={NEWWINDOW:"atto_link_openinnewwindow",URLINPUT:"atto_link_urlentry",URLTEXT:"atto_link_urltext"},a=".atto_link_openinnewwindow",o=".atto_link_urlentry",c=".atto_link_urltext",l=".submit",r=".openlinkbrowser";s.namespace("M.atto_link").Button=s.Base.create("button",s.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_content:null,_hasTextToDisplay:!1,_hasPlainTextSelected:!1,initializer:function(){this.addButton({icon:"e/insert_edit_link",keys:"75",callback:this._displayDialogue,tags:"a",tagMatchRequiresAll:!1}),this.addButton({buttonName:"unlink",callback:this._unlink,icon:"e/remove_link",title:"unlink",tags:"a",tagMatchRequiresAll:!1})},_displayDialogue:function(){var t;this._currentSelection=this.get("host").getSelection(),!1!==this._currentSelection&&((t=this.getDialogue({headerContent:M.util.get_string("createlink",n),width:"auto",focusAfterHide:!0,focusOnShowSelector:o})).set("bodyContent",this._getDialogueContent()),this._resolveAnchors(),t.show())},_resolveAnchors:function(){var t,e,n,i=this.get("host").getSelectionParentNode();i&&(0<(i=this._findSelectedAnchors(s.one(i))).length?(i=i[0],this._currentSelection=this.get("host").getSelectionFromNode(i),t=i.getAttribute("href"),e=i.getAttribute("target"),n=i.get("innerText"),i=i.getAttribute("title"),""!==t&&this._content.one(o).setAttribute("value",t),""!==n?this._content.one(c).set("value",n):""!==i&&this._content.one(c).set("value",i),"_blank"===e?this._content.one(a).setAttribute("checked","checked"):this._content.one(a).removeAttribute("checked")):""!==(n=this._getTextSelection())&&(this._hasTextToDisplay=!0,this._hasPlainTextSelected=!0,this._content.one(c).set("value",n)))},_filepickerCallback:function(t){this.getDialogue().set("focusAfterHide",null).hide(),""!==t.url&&(this._setLinkOnSelection(t.url),this.markUpdated())},_setLink:function(t){t.preventDefault(),this.getDialogue({focusAfterHide:null}).hide(),""!==(t=this._content.one(o).get("value"))&&(t=t.trim(),new RegExp(/^[a-zA-Z]*\.*\/|^#|^[a-zA-Z]*:/).test(t)||(t="http://"+t),this._setLinkOnSelection(t),this.markUpdated())},_setLinkOnSelection:function(t){var e,n,i,o,l=this.get("host");if(this.editor.focus(),l.setSelection(this._currentSelection),n=!this._currentSelection[0].collapsed,i=this._content.one(c),""===(o=i.get("value").replace(/(<([^>]+)>)/gi,"").trim())&&(o=t),n?(document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,t),e=l.getSelectionParentNode()):((i=s.Node.create(""+o+"")).setAttribute("href",t),e=l.insertContentAtFocusPoint(i.get("outerHTML")),l.setSelection(l.getSelectionFromNode(e))),e)return t=this._findSelectedAnchors(s.one(e)),s.Array.each(t,function(t){this._content.one(a).get("checked")?t.setAttribute("target","_blank"):t.removeAttribute("target"),n&&o&&(this._hasPlainTextSelected?t.set("innerText",o):t.setAttribute("title",o))},this),e},_findSelectedAnchors:function(t){var e,n,i=t.get("tagName");return i&&"a"===i.toLowerCase()?[t]:(n=[],t.all("a").each(function(t){!e&&this.get("host").selectionContainsNode(t)&&n.push(t)},this),0
{{#if showFilepicker}}
{{else}}
{{/if}}

');return this._content=s.Node.create(e({showFilepicker:t,component:n,CSS:i})),this._content.one(o).on("keyup",this._updateTextToDisplay,this),this._content.one(o).on("change",this._updateTextToDisplay,this),this._content.one(c).on("keyup",this._setTextToDisplayState,this),this._content.one(l).on("click",this._setLink,this),t&&this._content.one(r).on("click",function(t){t.preventDefault(),this.get("host").showFilepicker("link",this._filepickerCallback,this)},this),this._content},_unlink:function(){var e=this.get("host"),t=e.getSelection();t&&t.length&&(t[0].startOffset===t[0].endOffset?(t=e.getSelectedNodes())&&(t.each(function(t){t=t.ancestor("a",!0);t&&(e.setSelection(e.getSelectionFromNode(t)),document.execCommand("unlink",!1,null))},this),this.markUpdated()):(document.execCommand("unlink",!1,null),this.markUpdated()))},_setTextToDisplayState:function(){var t=this._content.one(c).get("value");this._hasTextToDisplay=""!==t},_updateTextToDisplay:function(){var t=this._content.one(o),e=this._content.one(c),t=t.get("value");this._hasTextToDisplay||e.set("value",t)},_getTextSelection:function(){var t,e,n="",i=window.getSelection(),o=i.rangeCount;if(o){for(t=[],e=0;eSome text here + * window.getSelection() will return XYZSome text here + * + * @returns {string} Selected text + * @private + */ + _getTextSelection: function() { + var selText = ''; + var sel = window.getSelection(); + var rangeCount = sel.rangeCount; + if (rangeCount) { + var rangeTexts = []; + for (var i = 0; i < rangeCount; ++i) { + rangeTexts.push('' + sel.getRangeAt(i)); + } + selText = rangeTexts.join(''); + } + return selText; } }); diff --git a/lib/editor/atto/plugins/link/yui/src/button/js/button.js b/lib/editor/atto/plugins/link/yui/src/button/js/button.js index e4b698bca3c..db182c031b7 100644 --- a/lib/editor/atto/plugins/link/yui/src/button/js/button.js +++ b/lib/editor/atto/plugins/link/yui/src/button/js/button.js @@ -109,6 +109,14 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit */ _hasTextToDisplay: false, + /** + * User has selected plain text or not. + * @property _hasPlainTextSelected + * @type Boolean + * @private + */ + _hasPlainTextSelected: false, + initializer: function() { // Add the link button first. this.addButton({ @@ -174,7 +182,8 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit anchornode, url, target, - textToDisplay; + textToDisplay, + title; // Note this is a document fragment and YUI doesn't like them. if (!selectednode) { @@ -188,11 +197,14 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit url = anchornode.getAttribute('href'); target = anchornode.getAttribute('target'); textToDisplay = anchornode.get('innerText'); + title = anchornode.getAttribute('title'); if (url !== '') { this._content.one(SELECTORS.URLINPUT).setAttribute('value', url); } if (textToDisplay !== '') { this._content.one(SELECTORS.URLTEXT).set('value', textToDisplay); + } else if (title !== '') { + this._content.one(SELECTORS.URLTEXT).set('value', title); } if (target === '_blank') { this._content.one(SELECTORS.NEWWINDOW).setAttribute('checked', 'checked'); @@ -201,9 +213,10 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit } } else { // User is selecting some text before clicking on the Link button. - textToDisplay = window.getSelection().toString(); + textToDisplay = this._getTextSelection(); if (textToDisplay !== '') { this._hasTextToDisplay = true; + this._hasPlainTextSelected = true; this._content.one(SELECTORS.URLTEXT).set('value', textToDisplay); } } @@ -323,9 +336,16 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit } else { anchornode.removeAttribute('target'); } - if (isUpdating) { + if (isUpdating && textToDisplay) { // The 'createLink' command do not allow to set the custom text to display. So we need to do it here. - anchornode.set('innerText', textToDisplay); + if (this._hasPlainTextSelected) { + // Only replace the innerText if the user has not selected any element or just the plain text. + anchornode.set('innerText', textToDisplay); + } else { + // The user has selected another element to add the hyperlink, like an image. + // We should add the title attribute instead of replacing the innerText of the hyperlink. + anchornode.setAttribute('title', textToDisplay); + } } }, this); @@ -385,6 +405,7 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit })); this._content.one(SELECTORS.URLINPUT).on('keyup', this._updateTextToDisplay, this); + this._content.one(SELECTORS.URLINPUT).on('change', 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) { @@ -477,5 +498,28 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit if (!this._hasTextToDisplay) { urlText.set('value', urlEntryVal); } + }, + + /** + * Get only the selected text. + * In some cases, window.getSelection() is not run as expected. We should only get the text value + * For ex: XYZSome text here + * window.getSelection() will return XYZSome text here + * + * @returns {string} Selected text + * @private + */ + _getTextSelection: function() { + var selText = ''; + var sel = window.getSelection(); + var rangeCount = sel.rangeCount; + if (rangeCount) { + var rangeTexts = []; + for (var i = 0; i < rangeCount; ++i) { + rangeTexts.push('' + sel.getRangeAt(i)); + } + selText = rangeTexts.join(''); + } + return selText; } });