MDL-49107 atto_link: Insert link even when selection is empty

This commit is contained in:
Frederic Massart 2015-02-25 15:19:18 +01:00
parent 95751e81ac
commit 39c6f62d03
8 changed files with 137 additions and 47 deletions

View File

@ -111,7 +111,7 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false || this._currentSelection.collapsed) {
if (this._currentSelection === false) {
return;
}
@ -167,11 +167,11 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
},
/**
* Update the dialogue after an image was selected in the File Picker.
* Update the dialogue after a link was selected in the File Picker.
*
* @method _filepickerCallback
* @param {object} params The parameters provided by the filepicker
* containing information about the image.
* containing information about the link.
* @private
*/
_filepickerCallback: function(params) {
@ -180,9 +180,9 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
.hide();
if (params.url !== '') {
this.get('host').setSelection(this._currentSelection);
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, params.url);
// Add the link.
this._setLinkOnSelection(params.url);
// And mark the text area as updated.
this.markUpdated();
}
@ -202,8 +202,6 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
anchornodes,
value;
var host = this.get('host');
e.preventDefault();
this.getDialogue({
focusAfterHide: null
@ -213,8 +211,6 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
value = input.get('value');
if (value !== '') {
this.editor.focus();
host.setSelection(this._currentSelection);
// We add a prefix if it is not already prefixed.
value = value.trim();
@ -223,11 +219,8 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
value = 'http://' + value;
}
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, value);
// Now set the target.
selectednode = host.getSelectionParentNode();
// Add the link.
selectednode = this._setLinkOnSelection(value);
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
@ -248,6 +241,41 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
}
},
/**
* Final step setting the anchor on the selection.
*
* @private
* @method _setLinkOnSelection
* @param {String} url URL the link will point to.
* @return {Node} The added Node.
*/
_setLinkOnSelection: function(url) {
var host = this.get('host'),
link,
selectednode;
this.editor.focus();
host.setSelection(this._currentSelection);
if (this._currentSelection[0].collapsed) {
// Firefox cannot add links when the selection is empty so we will add it manually.
link = Y.Node.create('<a>' + url + '</a>');
link.setAttribute('href', url);
// Add the node and select it to replicate the behaviour of execCommand.
selectednode = host.insertContentAtFocusPoint(link.get('outerHTML'));
host.setSelection(host.getSelectionFromNode(selectednode));
} else {
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, url);
// Now set the target.
selectednode = host.getSelectionParentNode();
}
return selectednode;
},
/**
* Look up and down for the nearest anchor tags that are least partly contained in the selection.
*

View File

@ -1 +1 @@
YUI.add("moodle-atto_link-button",function(e,t){var n="atto_link",r={NEWWINDOW:"atto_link_openinnewwindow",URLINPUT:"atto_link_urlentry"},i={URLINPUT:".atto_link_urlentry"},s='<form class="atto_form"><label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label><input class="fullwidth url {{CSS.URLINPUT}}" type="url" id="{{elementid}}_atto_link_urlentry" size="32"/><br/>{{#if showFilepicker}}<button class="openlinkbrowser">{{get_string "browserepositories" component}}</button><br/>{{/if}}<input type="checkbox" class="newwindow" id="{{elementid}}_{{CSS.NEWWINDOW}}"/><label class="sameline" for="{{elementid}}_{{CSS.NEWWINDOW}}">{{get_string "openinnewwindow" component}}</label><br/><div class="mdl-align"><br/><button type="submit" class="submit">{{get_string "createlink" component}}</button></div></form>';e.namespace("M.atto_link").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_content:null,initializer:function(){this.addButton({icon:"e/insert_edit_link",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(){this._currentSelection=this.get("host").getSelection();if(this._currentSelection===!1||this._currentSelection.collapsed)return;var e=this.getDialogue({headerContent:M.util.get_string("createlink",n),focusAfterHide:!0,focusOnShowSelector:i.URLINPUT});e.set("bodyContent",this._getDialogueContent()),this._resolveAnchors(),e.show()},_resolveAnchors:function(){var t=this.get("host").getSelectionParentNode(),n,r,i,s;if(!t)return;n=this._findSelectedAnchors(e.one(t)),n.length>0&&(r=n[0],this._currentSelection=this.get("host").getSelectionFromNode(r),i=r.getAttribute("href"),s=r.getAttribute("target"),i!==""&&this._content.one(".url").setAttribute("value",i),s==="_blank"?this._content.one(".newwindow").setAttribute("checked","checked"):this._content.one(".newwindow").removeAttribute("checked"))},_filepickerCallback:function(e){this.getDialogue().set("focusAfterHide",null).hide(),e.url!==""&&(this.get("host").setSelection(this._currentSelection),document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,e.url),this.markUpdated())},_setLink:function(t){var n,r,i,s,o,u=this.get("host");t.preventDefault(),this.getDialogue({focusAfterHide:null}).hide(),n=this._content.one(".url"),o=n.get("value");if(o!==""){this.editor.focus(),u.setSelection(this._currentSelection),o=o.trim();var a=new RegExp(/^[a-zA-Z]*\.*\/|^#|^[a-zA-Z]*:/);a.test(o)||(o="http://"+o),document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,o),i=u.getSelectionParentNode();if(!i)return;s=this._findSelectedAnchors(e.one(i)),e.Array.each(s,function(e){r=this._content.one(".newwindow"),r.get("checked")?e.setAttribute("target","_blank"):e.removeAttribute("target")},this),this.markUpdated()}},_findSelectedAnchors:function(e){var t=e.get("tagName"),n,r;return t&&t.toLowerCase()==="a"?[e]:(r=[],e.all("a").each(function(e){!n&&this.get("host").selectionContainsNode(e)&&r.push(e)},this),r.length>0?r:(n=e.ancestor("a"),n?[n]:[]))},_getDialogueContent:function(){var t=this.get("host").canShowFilepicker("link"),i=e.Handlebars.compile(s);return this._content=e.Node.create(i({showFilepicker:t,component:n,CSS:r})),this._content.one(".submit").on("click",this._setLink,this),t&&this._content.one(".openlinkbrowser").on("click",function(e){e.preventDefault(),this.get("host").showFilepicker("link",this._filepickerCallback,this)},this),this._content},_unlink:function(){var e=this.get("host"),t=e.getSelection();if(t&&t.length)if(t[0].startOffset===t[0].endOffset){var n=e.getSelectedNodes();n&&(n.each(function(t){var n=t.ancestor("a",!0);n&&(e.setSelection(e.getSelectionFromNode(n)),document.execCommand("unlink",!1,null))},this),this.markUpdated())}else document.execCommand("unlink",!1,null),this.markUpdated()}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
YUI.add("moodle-atto_link-button",function(e,t){var n="atto_link",r={NEWWINDOW:"atto_link_openinnewwindow",URLINPUT:"atto_link_urlentry"},i={URLINPUT:".atto_link_urlentry"},s='<form class="atto_form"><label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label><input class="fullwidth url {{CSS.URLINPUT}}" type="url" id="{{elementid}}_atto_link_urlentry" size="32"/><br/>{{#if showFilepicker}}<button class="openlinkbrowser">{{get_string "browserepositories" component}}</button><br/>{{/if}}<input type="checkbox" class="newwindow" id="{{elementid}}_{{CSS.NEWWINDOW}}"/><label class="sameline" for="{{elementid}}_{{CSS.NEWWINDOW}}">{{get_string "openinnewwindow" component}}</label><br/><div class="mdl-align"><br/><button type="submit" class="submit">{{get_string "createlink" component}}</button></div></form>';e.namespace("M.atto_link").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{_currentSelection:null,_content:null,initializer:function(){this.addButton({icon:"e/insert_edit_link",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(){this._currentSelection=this.get("host").getSelection();if(this._currentSelection===!1)return;var e=this.getDialogue({headerContent:M.util.get_string("createlink",n),focusAfterHide:!0,focusOnShowSelector:i.URLINPUT});e.set("bodyContent",this._getDialogueContent()),this._resolveAnchors(),e.show()},_resolveAnchors:function(){var t=this.get("host").getSelectionParentNode(),n,r,i,s;if(!t)return;n=this._findSelectedAnchors(e.one(t)),n.length>0&&(r=n[0],this._currentSelection=this.get("host").getSelectionFromNode(r),i=r.getAttribute("href"),s=r.getAttribute("target"),i!==""&&this._content.one(".url").setAttribute("value",i),s==="_blank"?this._content.one(".newwindow").setAttribute("checked","checked"):this._content.one(".newwindow").removeAttribute("checked"))},_filepickerCallback:function(e){this.getDialogue().set("focusAfterHide",null).hide(),e.url!==""&&(this._setLinkOnSelection(e.url),this.markUpdated())},_setLink:function(t){var n,r,i,s,o;t.preventDefault(),this.getDialogue({focusAfterHide:null}).hide(),n=this._content.one(".url"),o=n.get("value");if(o!==""){o=o.trim();var u=new RegExp(/^[a-zA-Z]*\.*\/|^#|^[a-zA-Z]*:/);u.test(o)||(o="http://"+o),i=this._setLinkOnSelection(o);if(!i)return;s=this._findSelectedAnchors(e.one(i)),e.Array.each(s,function(e){r=this._content.one(".newwindow"),r.get("checked")?e.setAttribute("target","_blank"):e.removeAttribute("target")},this),this.markUpdated()}},_setLinkOnSelection:function(t){var n=this.get("host"),r,i;return this.editor.focus(),n.setSelection(this._currentSelection),this._currentSelection[0].collapsed?(r=e.Node.create("<a>"+t+"</a>"),r.setAttribute("href",t),i=n.insertContentAtFocusPoint(r.get("outerHTML")),n.setSelection(n.getSelectionFromNode(i))):(document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,t),i=n.getSelectionParentNode()),i},_findSelectedAnchors:function(e){var t=e.get("tagName"),n,r;return t&&t.toLowerCase()==="a"?[e]:(r=[],e.all("a").each(function(e){!n&&this.get("host").selectionContainsNode(e)&&r.push(e)},this),r.length>0?r:(n=e.ancestor("a"),n?[n]:[]))},_getDialogueContent:function(){var t=this.get("host").canShowFilepicker("link"),i=e.Handlebars.compile(s);return this._content=e.Node.create(i({showFilepicker:t,component:n,CSS:r})),this._content.one(".submit").on("click",this._setLink,this),t&&this._content.one(".openlinkbrowser").on("click",function(e){e.preventDefault(),this.get("host").showFilepicker("link",this._filepickerCallback,this)},this),this._content},_unlink:function(){var e=this.get("host"),t=e.getSelection();if(t&&t.length)if(t[0].startOffset===t[0].endOffset){var n=e.getSelectedNodes();n&&(n.each(function(t){var n=t.ancestor("a",!0);n&&(e.setSelection(e.getSelectionFromNode(n)),document.execCommand("unlink",!1,null))},this),this.markUpdated())}else document.execCommand("unlink",!1,null),this.markUpdated()}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});

View File

@ -111,7 +111,7 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false || this._currentSelection.collapsed) {
if (this._currentSelection === false) {
return;
}
@ -167,11 +167,11 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
},
/**
* Update the dialogue after an image was selected in the File Picker.
* Update the dialogue after a link was selected in the File Picker.
*
* @method _filepickerCallback
* @param {object} params The parameters provided by the filepicker
* containing information about the image.
* containing information about the link.
* @private
*/
_filepickerCallback: function(params) {
@ -180,9 +180,9 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
.hide();
if (params.url !== '') {
this.get('host').setSelection(this._currentSelection);
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, params.url);
// Add the link.
this._setLinkOnSelection(params.url);
// And mark the text area as updated.
this.markUpdated();
}
@ -202,8 +202,6 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
anchornodes,
value;
var host = this.get('host');
e.preventDefault();
this.getDialogue({
focusAfterHide: null
@ -213,8 +211,6 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
value = input.get('value');
if (value !== '') {
this.editor.focus();
host.setSelection(this._currentSelection);
// We add a prefix if it is not already prefixed.
value = value.trim();
@ -223,11 +219,8 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
value = 'http://' + value;
}
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, value);
// Now set the target.
selectednode = host.getSelectionParentNode();
// Add the link.
selectednode = this._setLinkOnSelection(value);
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
@ -248,6 +241,41 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
}
},
/**
* Final step setting the anchor on the selection.
*
* @private
* @method _setLinkOnSelection
* @param {String} url URL the link will point to.
* @return {Node} The added Node.
*/
_setLinkOnSelection: function(url) {
var host = this.get('host'),
link,
selectednode;
this.editor.focus();
host.setSelection(this._currentSelection);
if (this._currentSelection[0].collapsed) {
// Firefox cannot add links when the selection is empty so we will add it manually.
link = Y.Node.create('<a>' + url + '</a>');
link.setAttribute('href', url);
// Add the node and select it to replicate the behaviour of execCommand.
selectednode = host.insertContentAtFocusPoint(link.get('outerHTML'));
host.setSelection(host.getSelectionFromNode(selectednode));
} else {
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, url);
// Now set the target.
selectednode = host.getSelectionParentNode();
}
return selectednode;
},
/**
* Look up and down for the nearest anchor tags that are least partly contained in the selection.
*

View File

@ -109,7 +109,7 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
_displayDialogue: function() {
// Store the current selection.
this._currentSelection = this.get('host').getSelection();
if (this._currentSelection === false || this._currentSelection.collapsed) {
if (this._currentSelection === false) {
return;
}
@ -165,11 +165,11 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
},
/**
* Update the dialogue after an image was selected in the File Picker.
* Update the dialogue after a link was selected in the File Picker.
*
* @method _filepickerCallback
* @param {object} params The parameters provided by the filepicker
* containing information about the image.
* containing information about the link.
* @private
*/
_filepickerCallback: function(params) {
@ -178,9 +178,9 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
.hide();
if (params.url !== '') {
this.get('host').setSelection(this._currentSelection);
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, params.url);
// Add the link.
this._setLinkOnSelection(params.url);
// And mark the text area as updated.
this.markUpdated();
}
@ -200,8 +200,6 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
anchornodes,
value;
var host = this.get('host');
e.preventDefault();
this.getDialogue({
focusAfterHide: null
@ -211,8 +209,6 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
value = input.get('value');
if (value !== '') {
this.editor.focus();
host.setSelection(this._currentSelection);
// We add a prefix if it is not already prefixed.
value = value.trim();
@ -221,11 +217,8 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
value = 'http://' + value;
}
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, value);
// Now set the target.
selectednode = host.getSelectionParentNode();
// Add the link.
selectednode = this._setLinkOnSelection(value);
// Note this is a document fragment and YUI doesn't like them.
if (!selectednode) {
@ -246,6 +239,41 @@ Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.Edit
}
},
/**
* Final step setting the anchor on the selection.
*
* @private
* @method _setLinkOnSelection
* @param {String} url URL the link will point to.
* @return {Node} The added Node.
*/
_setLinkOnSelection: function(url) {
var host = this.get('host'),
link,
selectednode;
this.editor.focus();
host.setSelection(this._currentSelection);
if (this._currentSelection[0].collapsed) {
// Firefox cannot add links when the selection is empty so we will add it manually.
link = Y.Node.create('<a>' + url + '</a>');
link.setAttribute('href', url);
// Add the node and select it to replicate the behaviour of execCommand.
selectednode = host.insertContentAtFocusPoint(link.get('outerHTML'));
host.setSelection(host.getSelectionFromNode(selectednode));
} else {
document.execCommand('unlink', false, null);
document.execCommand('createLink', false, url);
// Now set the target.
selectednode = host.getSelectionParentNode();
}
return selectednode;
},
/**
* Look up and down for the nearest anchor tags that are least partly contained in the selection.
*

View File

@ -1817,6 +1817,7 @@ EditorSelection.prototype = {
*
* @method insertContentAtFocusPoint
* @param {String} html
* @return {Node} The YUI Node object added to the DOM.
*/
insertContentAtFocusPoint: function(html) {
var selection = rangy.getSelection(),
@ -1829,6 +1830,7 @@ EditorSelection.prototype = {
range.deleteContents();
range.insertNode(node.getDOMNode());
}
return node;
}
};

File diff suppressed because one or more lines are too long

View File

@ -1805,6 +1805,7 @@ EditorSelection.prototype = {
*
* @method insertContentAtFocusPoint
* @param {String} html
* @return {Node} The YUI Node object added to the DOM.
*/
insertContentAtFocusPoint: function(html) {
var selection = rangy.getSelection(),
@ -1817,6 +1818,7 @@ EditorSelection.prototype = {
range.deleteContents();
range.insertNode(node.getDOMNode());
}
return node;
}
};

View File

@ -378,6 +378,7 @@ EditorSelection.prototype = {
*
* @method insertContentAtFocusPoint
* @param {String} html
* @return {Node} The YUI Node object added to the DOM.
*/
insertContentAtFocusPoint: function(html) {
var selection = rangy.getSelection(),
@ -390,6 +391,7 @@ EditorSelection.prototype = {
range.deleteContents();
range.insertNode(node.getDOMNode());
}
return node;
}
};