mirror of
https://github.com/moodle/moodle.git
synced 2025-03-14 12:40:01 +01:00
MDL-44219 editor_atto: reinvent the event wheel for the atto editor
This commit is contained in:
parent
86a83e3a9f
commit
67d3fe45bb
@ -50,8 +50,7 @@ M.atto_bold = M.atto_bold || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'bold');
|
||||
} else {
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-atto_bold-button",function(e,t){var n={TAGS:"b,strong"};M.atto_bold=M.atto_bold||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("bold",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/bold","core");M.editor_atto.add_toolbar_button(e.elementid,"bold",r,e.group,t),M.editor_atto.add_button_shortcut({action:"bold",keys:66});var i=M.editor_atto.get_editable_node(e.elementid);i.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"bold"):M.editor_atto.remove_widget_highlight(e.elementid,"bold")})}}},"@VERSION@",{requires:["node","moodle-editor_atto-editor-shortcut"]});
|
||||
YUI.add("moodle-atto_bold-button",function(e,t){var n={TAGS:"b,strong"};M.atto_bold=M.atto_bold||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("bold",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/bold","core");M.editor_atto.add_toolbar_button(e.elementid,"bold",r,e.group,t),M.editor_atto.add_button_shortcut({action:"bold",keys:66}),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"bold"):M.editor_atto.remove_widget_highlight(e.elementid,"bold")})}}},"@VERSION@",{requires:["node","moodle-editor_atto-editor-shortcut"]});
|
||||
|
@ -50,8 +50,7 @@ M.atto_bold = M.atto_bold || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'bold');
|
||||
} else {
|
||||
|
@ -48,8 +48,7 @@ M.atto_bold = M.atto_bold || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'bold');
|
||||
} else {
|
||||
|
@ -134,9 +134,8 @@ M.atto_equation = M.atto_equation || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
if (M.atto_equation.resolve_equation() !== false) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.atto_equation.resolve_equation()) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'equation');
|
||||
} else {
|
||||
M.editor_atto.remove_widget_highlight(e.elementid, 'equation');
|
||||
|
File diff suppressed because one or more lines are too long
@ -134,9 +134,8 @@ M.atto_equation = M.atto_equation || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
if (M.atto_equation.resolve_equation() !== false) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.atto_equation.resolve_equation()) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'equation');
|
||||
} else {
|
||||
M.editor_atto.remove_widget_highlight(e.elementid, 'equation');
|
||||
|
@ -132,9 +132,8 @@ M.atto_equation = M.atto_equation || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
if (M.atto_equation.resolve_equation() !== false) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.atto_equation.resolve_equation()) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'equation');
|
||||
} else {
|
||||
M.editor_atto.remove_widget_highlight(e.elementid, 'equation');
|
||||
|
@ -144,6 +144,7 @@ ALIGNMENTS = [
|
||||
M.atto_image = M.atto_image || {
|
||||
dialogue: null,
|
||||
selection: null,
|
||||
currentlyselected : {},
|
||||
init: function(params) {
|
||||
var display_chooser = function(e, elementid) {
|
||||
e.preventDefault();
|
||||
@ -175,16 +176,19 @@ M.atto_image = M.atto_image || {
|
||||
|
||||
var iconurl = M.util.image_url('e/insert_edit_image', 'core');
|
||||
M.editor_atto.add_toolbar_button(params.elementid, 'image', iconurl, params.group, display_chooser);
|
||||
M.editor_atto.currentlyselected = M.editor_atto.currentlyselected || {};
|
||||
M.editor_atto.currentlyselected[params.elementid] = null;
|
||||
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
if (e.selectedNodes.filter(SELECTORS.TAGS).size() > 0) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'image');
|
||||
M.editor_atto.currentlyselected[e.elementid] = e.selectedNodes;
|
||||
} else {
|
||||
M.editor_atto.remove_widget_highlight(e.elementid, 'image');
|
||||
M.editor_atto.currentlyselected[e.elementid] = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -303,11 +307,17 @@ M.atto_image = M.atto_image || {
|
||||
display: 'inline',
|
||||
presentation: false
|
||||
},
|
||||
images = M.editor_atto.get_selected_nodes(elementid).filter(SELECTORS.TAGS),
|
||||
images = M.editor_atto.currentlyselected[elementid],
|
||||
i, image, width, height, style;
|
||||
|
||||
if (images.size() > 0) {
|
||||
if (images !== null && images.size() > 0) {
|
||||
image = images.item(0);
|
||||
for (i in images) {
|
||||
if (images[i].test('img')) {
|
||||
image = images[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
style = image.getAttribute('style');
|
||||
width = parseInt(image.getAttribute('width'), 10);
|
||||
height = parseInt(image.getAttribute('height'), 10);
|
||||
|
File diff suppressed because one or more lines are too long
@ -144,6 +144,7 @@ ALIGNMENTS = [
|
||||
M.atto_image = M.atto_image || {
|
||||
dialogue: null,
|
||||
selection: null,
|
||||
currentlyselected : {},
|
||||
init: function(params) {
|
||||
var display_chooser = function(e, elementid) {
|
||||
e.preventDefault();
|
||||
@ -175,16 +176,19 @@ M.atto_image = M.atto_image || {
|
||||
|
||||
var iconurl = M.util.image_url('e/insert_edit_image', 'core');
|
||||
M.editor_atto.add_toolbar_button(params.elementid, 'image', iconurl, params.group, display_chooser);
|
||||
M.editor_atto.currentlyselected = M.editor_atto.currentlyselected || {};
|
||||
M.editor_atto.currentlyselected[params.elementid] = null;
|
||||
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
if (e.selectedNodes.filter(SELECTORS.TAGS).size() > 0) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'image');
|
||||
M.editor_atto.currentlyselected[e.elementid] = e.selectedNodes;
|
||||
} else {
|
||||
M.editor_atto.remove_widget_highlight(e.elementid, 'image');
|
||||
M.editor_atto.currentlyselected[e.elementid] = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -303,11 +307,17 @@ M.atto_image = M.atto_image || {
|
||||
display: 'inline',
|
||||
presentation: false
|
||||
},
|
||||
images = M.editor_atto.get_selected_nodes(elementid).filter(SELECTORS.TAGS),
|
||||
images = M.editor_atto.currentlyselected[elementid],
|
||||
i, image, width, height, style;
|
||||
|
||||
if (images.size() > 0) {
|
||||
if (images !== null && images.size() > 0) {
|
||||
image = images.item(0);
|
||||
for (i in images) {
|
||||
if (images[i].test('img')) {
|
||||
image = images[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
style = image.getAttribute('style');
|
||||
width = parseInt(image.getAttribute('width'), 10);
|
||||
height = parseInt(image.getAttribute('height'), 10);
|
||||
|
@ -142,6 +142,7 @@ ALIGNMENTS = [
|
||||
M.atto_image = M.atto_image || {
|
||||
dialogue: null,
|
||||
selection: null,
|
||||
currentlyselected : {},
|
||||
init: function(params) {
|
||||
var display_chooser = function(e, elementid) {
|
||||
e.preventDefault();
|
||||
@ -173,16 +174,19 @@ M.atto_image = M.atto_image || {
|
||||
|
||||
var iconurl = M.util.image_url('e/insert_edit_image', 'core');
|
||||
M.editor_atto.add_toolbar_button(params.elementid, 'image', iconurl, params.group, display_chooser);
|
||||
M.editor_atto.currentlyselected = M.editor_atto.currentlyselected || {};
|
||||
M.editor_atto.currentlyselected[params.elementid] = null;
|
||||
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
if (e.selectedNodes.filter(SELECTORS.TAGS).size() > 0) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'image');
|
||||
M.editor_atto.currentlyselected[e.elementid] = e.selectedNodes;
|
||||
} else {
|
||||
M.editor_atto.remove_widget_highlight(e.elementid, 'image');
|
||||
M.editor_atto.currentlyselected[e.elementid] = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -301,11 +305,17 @@ M.atto_image = M.atto_image || {
|
||||
display: 'inline',
|
||||
presentation: false
|
||||
},
|
||||
images = M.editor_atto.get_selected_nodes(elementid).filter(SELECTORS.TAGS),
|
||||
images = M.editor_atto.currentlyselected[elementid],
|
||||
i, image, width, height, style;
|
||||
|
||||
if (images.size() > 0) {
|
||||
if (images !== null && images.size() > 0) {
|
||||
image = images.item(0);
|
||||
for (i in images) {
|
||||
if (images[i].test('img')) {
|
||||
image = images[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
style = image.getAttribute('style');
|
||||
width = parseInt(image.getAttribute('width'), 10);
|
||||
height = parseInt(image.getAttribute('height'), 10);
|
||||
|
@ -50,8 +50,7 @@ M.atto_italic = M.atto_italic || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'italic');
|
||||
} else {
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-atto_italic-button",function(e,t){var n={TAGS:"i"};M.atto_italic=M.atto_italic||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("italic",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/italic","core");M.editor_atto.add_toolbar_button(e.elementid,"italic",r,e.group,t),M.editor_atto.add_button_shortcut({action:"italic",keys:73});var i=M.editor_atto.get_editable_node(e.elementid);i.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"italic"):M.editor_atto.remove_widget_highlight(e.elementid,"italic")})}}},"@VERSION@",{requires:["node","moodle-editor_atto-editor-shortcut"]});
|
||||
YUI.add("moodle-atto_italic-button",function(e,t){var n={TAGS:"i"};M.atto_italic=M.atto_italic||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("italic",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/italic","core");M.editor_atto.add_toolbar_button(e.elementid,"italic",r,e.group,t),M.editor_atto.add_button_shortcut({action:"italic",keys:73}),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"italic"):M.editor_atto.remove_widget_highlight(e.elementid,"italic")})}}},"@VERSION@",{requires:["node","moodle-editor_atto-editor-shortcut"]});
|
||||
|
@ -50,8 +50,7 @@ M.atto_italic = M.atto_italic || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'italic');
|
||||
} else {
|
||||
|
@ -48,8 +48,7 @@ M.atto_italic = M.atto_italic || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'italic');
|
||||
} else {
|
||||
|
@ -99,8 +99,7 @@ M.atto_link = M.atto_link || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'link');
|
||||
} else {
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-atto_link-button",function(e,t){var n={TAGS:"a"};M.atto_link=M.atto_link||{dialogue:null,selection:null,display_chooser:function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),M.atto_link.selection=M.editor_atto.get_selection();if(M.atto_link.selection!==!1&&!M.atto_link.selection.collapsed){var n;M.atto_link.dialogue?n=M.atto_link.dialogue:n=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0}),n.render(),n.set("bodyContent",M.atto_link.get_form_content(t)),n.set("headerContent",M.util.get_string("createlink","atto_link")),M.atto_link.resolve_anchors(),n.show(),M.atto_link.dialogue=n}},init:function(e){var t=M.util.image_url("e/insert_edit_link","core");M.editor_atto.add_toolbar_button(e.elementid,"link",t,e.group,this.display_chooser);var r=M.editor_atto.get_editable_node(e.elementid);r.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"link"):M.editor_atto.remove_widget_highlight(e.elementid,"link")})},resolve_anchors:function(){var t=M.editor_atto.get_selection_parent_node(),n,r,i;if(!t)return;n=M.atto_link.find_selected_anchors(e.one(t)),n.length>0&&(r=n[0],M.atto_link.selection=M.editor_atto.get_selection_from_node(r),i=r.getAttribute("href"),target=r.getAttribute("target"),i!==""&&e.one("#atto_link_urlentry").set("value",i),target==="_blank"?e.one("#atto_link_openinnewwindow").set("checked","checked"):e.one("#atto_link_openinnewwindow").set("checked",""))},open_filepicker:function(e){var t=this.getAttribute("data-editor");e.preventDefault(),M.editor_atto.show_filepicker(t,"link",M.atto_link.filepicker_callback)},filepicker_callback:function(e){M.atto_link.dialogue.hide(),e.url!==""&&(M.editor_atto.set_selection(M.atto_link.selection),document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,e.url))},set_link:function(t,n){var r,i,s,o,u;t.preventDefault(),M.atto_link.dialogue.hide(),r=t.currentTarget.ancestor(".atto_form").one("input[type=url]"),u=r.get("value");if(u!==""){M.editor_atto.set_selection(M.atto_link.selection),document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,u),s=M.editor_atto.get_selection_parent_node();if(!s)return;o=M.atto_link.find_selected_anchors(e.one(s)),e.Array.each(o,function(e){i=t.currentTarget.ancestor(".atto_form").one("input[type=checkbox]"),i.get("checked")?e.setAttribute("target","_blank"):e.removeAttribute("target")}),M.editor_atto.text_updated(n)}},find_selected_anchors:function(e){var t=e.get("tagName"),n,r;return t&&t.toLowerCase()==="a"?[e]:(r=[],e.all("a").each(function(e){!n&&M.editor_atto.selection_contains_node(e)&&r.push(e)}),r.length>0?r:(n=e.ancestor("a"),n?[n]:[]))},get_form_content:function(t){var n='<form class="atto_form"><label for="atto_link_urlentry">'+M.util.get_string("enterurl","atto_link")+"</label>"+'<input class="fullwidth" type="url" value="" id="atto_link_urlentry" size="32"/><br/>';M.editor_atto.can_show_filepicker(t,"link")&&(n+='<button id="openlinkbrowser" data-editor="'+e.Escape.html(t)+'" type="button" >'+M.util.get_string("browserepositories","atto_link")+"</button>"+"<br/>"),n+='<input type="checkbox" id="atto_link_openinnewwindow"/><label class="sameline" for="atto_link_openinnewwindow">'+M.util.get_string("openinnewwindow","atto_link")+"</label>"+"<br/>"+'<div class="mdl-align">'+"<br/>"+'<button type="submit" id="atto_link_urlentrysubmit">'+M.util.get_string("createlink","atto_link")+"</button>"+"</div>"+"</form>";var r=e.Node.create(n);return r.one("#atto_link_urlentrysubmit").on("click",M.atto_link.set_link,this,t),M.editor_atto.can_show_filepicker(t,"link")&&r.one("#openlinkbrowser").on("click",M.atto_link.open_filepicker),r}}},"@VERSION@",{requires:["node","escape"]});
|
||||
YUI.add("moodle-atto_link-button",function(e,t){var n={TAGS:"a"};M.atto_link=M.atto_link||{dialogue:null,selection:null,display_chooser:function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),M.atto_link.selection=M.editor_atto.get_selection();if(M.atto_link.selection!==!1&&!M.atto_link.selection.collapsed){var n;M.atto_link.dialogue?n=M.atto_link.dialogue:n=new M.core.dialogue({visible:!1,modal:!0,close:!0,draggable:!0}),n.render(),n.set("bodyContent",M.atto_link.get_form_content(t)),n.set("headerContent",M.util.get_string("createlink","atto_link")),M.atto_link.resolve_anchors(),n.show(),M.atto_link.dialogue=n}},init:function(e){var t=M.util.image_url("e/insert_edit_link","core");M.editor_atto.add_toolbar_button(e.elementid,"link",t,e.group,this.display_chooser),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"link"):M.editor_atto.remove_widget_highlight(e.elementid,"link")})},resolve_anchors:function(){var t=M.editor_atto.get_selection_parent_node(),n,r,i;if(!t)return;n=M.atto_link.find_selected_anchors(e.one(t)),n.length>0&&(r=n[0],M.atto_link.selection=M.editor_atto.get_selection_from_node(r),i=r.getAttribute("href"),target=r.getAttribute("target"),i!==""&&e.one("#atto_link_urlentry").set("value",i),target==="_blank"?e.one("#atto_link_openinnewwindow").set("checked","checked"):e.one("#atto_link_openinnewwindow").set("checked",""))},open_filepicker:function(e){var t=this.getAttribute("data-editor");e.preventDefault(),M.editor_atto.show_filepicker(t,"link",M.atto_link.filepicker_callback)},filepicker_callback:function(e){M.atto_link.dialogue.hide(),e.url!==""&&(M.editor_atto.set_selection(M.atto_link.selection),document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,e.url))},set_link:function(t,n){var r,i,s,o,u;t.preventDefault(),M.atto_link.dialogue.hide(),r=t.currentTarget.ancestor(".atto_form").one("input[type=url]"),u=r.get("value");if(u!==""){M.editor_atto.set_selection(M.atto_link.selection),document.execCommand("unlink",!1,null),document.execCommand("createLink",!1,u),s=M.editor_atto.get_selection_parent_node();if(!s)return;o=M.atto_link.find_selected_anchors(e.one(s)),e.Array.each(o,function(e){i=t.currentTarget.ancestor(".atto_form").one("input[type=checkbox]"),i.get("checked")?e.setAttribute("target","_blank"):e.removeAttribute("target")}),M.editor_atto.text_updated(n)}},find_selected_anchors:function(e){var t=e.get("tagName"),n,r;return t&&t.toLowerCase()==="a"?[e]:(r=[],e.all("a").each(function(e){!n&&M.editor_atto.selection_contains_node(e)&&r.push(e)}),r.length>0?r:(n=e.ancestor("a"),n?[n]:[]))},get_form_content:function(t){var n='<form class="atto_form"><label for="atto_link_urlentry">'+M.util.get_string("enterurl","atto_link")+"</label>"+'<input class="fullwidth" type="url" value="" id="atto_link_urlentry" size="32"/><br/>';M.editor_atto.can_show_filepicker(t,"link")&&(n+='<button id="openlinkbrowser" data-editor="'+e.Escape.html(t)+'" type="button" >'+M.util.get_string("browserepositories","atto_link")+"</button>"+"<br/>"),n+='<input type="checkbox" id="atto_link_openinnewwindow"/><label class="sameline" for="atto_link_openinnewwindow">'+M.util.get_string("openinnewwindow","atto_link")+"</label>"+"<br/>"+'<div class="mdl-align">'+"<br/>"+'<button type="submit" id="atto_link_urlentrysubmit">'+M.util.get_string("createlink","atto_link")+"</button>"+"</div>"+"</form>";var r=e.Node.create(n);return r.one("#atto_link_urlentrysubmit").on("click",M.atto_link.set_link,this,t),M.editor_atto.can_show_filepicker(t,"link")&&r.one("#openlinkbrowser").on("click",M.atto_link.open_filepicker),r}}},"@VERSION@",{requires:["node","escape"]});
|
||||
|
@ -99,8 +99,7 @@ M.atto_link = M.atto_link || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'link');
|
||||
} else {
|
||||
|
@ -97,8 +97,7 @@ M.atto_link = M.atto_link || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'link');
|
||||
} else {
|
||||
|
@ -49,8 +49,7 @@ M.atto_orderedlist = M.atto_orderedlist || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'orderedlist');
|
||||
} else {
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-atto_orderedlist-button",function(e,t){var n={TAGS:"ol"};M.atto_orderedlist=M.atto_orderedlist||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("insertOrderedList",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/numbered_list","core");M.editor_atto.add_toolbar_button(e.elementid,"orderedlist",r,e.group,t);var i=M.editor_atto.get_editable_node(e.elementid);i.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"orderedlist"):M.editor_atto.remove_widget_highlight(e.elementid,"orderedlist")})}}},"@VERSION@",{requires:["node"]});
|
||||
YUI.add("moodle-atto_orderedlist-button",function(e,t){var n={TAGS:"ol"};M.atto_orderedlist=M.atto_orderedlist||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("insertOrderedList",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/numbered_list","core");M.editor_atto.add_toolbar_button(e.elementid,"orderedlist",r,e.group,t),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"orderedlist"):M.editor_atto.remove_widget_highlight(e.elementid,"orderedlist")})}}},"@VERSION@",{requires:["node"]});
|
||||
|
@ -49,8 +49,7 @@ M.atto_orderedlist = M.atto_orderedlist || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'orderedlist');
|
||||
} else {
|
||||
|
@ -47,8 +47,7 @@ M.atto_orderedlist = M.atto_orderedlist || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'orderedlist');
|
||||
} else {
|
||||
|
@ -49,8 +49,7 @@ M.atto_strike = M.atto_strike || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'strike');
|
||||
} else {
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-atto_strike-button",function(e,t){var n={TAGS:"strike"};M.atto_strike=M.atto_strike||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("strikeThrough",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/strikethrough","core");M.editor_atto.add_toolbar_button(e.elementid,"strike",r,e.group,t);var i=M.editor_atto.get_editable_node(e.elementid);i.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"strike"):M.editor_atto.remove_widget_highlight(e.elementid,"strike")})}}},"@VERSION@",{requires:["node"]});
|
||||
YUI.add("moodle-atto_strike-button",function(e,t){var n={TAGS:"strike"};M.atto_strike=M.atto_strike||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("strikeThrough",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/strikethrough","core");M.editor_atto.add_toolbar_button(e.elementid,"strike",r,e.group,t),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"strike"):M.editor_atto.remove_widget_highlight(e.elementid,"strike")})}}},"@VERSION@",{requires:["node"]});
|
||||
|
@ -49,8 +49,7 @@ M.atto_strike = M.atto_strike || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'strike');
|
||||
} else {
|
||||
|
@ -47,8 +47,7 @@ M.atto_strike = M.atto_strike || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'strike');
|
||||
} else {
|
||||
|
@ -49,8 +49,7 @@ M.atto_subscript = M.atto_subscript || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'sub');
|
||||
} else {
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-atto_subscript-button",function(e,t){var n={TAGS:"sub"};M.atto_subscript=M.atto_subscript||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("subscript",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/subscript","core");M.editor_atto.add_toolbar_button(e.elementid,"subscript",r,e.group,t);var i=M.editor_atto.get_editable_node(e.elementid);i.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"sub"):M.editor_atto.remove_widget_highlight(e.elementid,"sub")})}}},"@VERSION@",{requires:["node"]});
|
||||
YUI.add("moodle-atto_subscript-button",function(e,t){var n={TAGS:"sub"};M.atto_subscript=M.atto_subscript||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("subscript",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/subscript","core");M.editor_atto.add_toolbar_button(e.elementid,"subscript",r,e.group,t),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"sub"):M.editor_atto.remove_widget_highlight(e.elementid,"sub")})}}},"@VERSION@",{requires:["node"]});
|
||||
|
@ -49,8 +49,7 @@ M.atto_subscript = M.atto_subscript || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'sub');
|
||||
} else {
|
||||
|
@ -47,8 +47,7 @@ M.atto_subscript = M.atto_subscript || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'sub');
|
||||
} else {
|
||||
|
@ -49,8 +49,7 @@ M.atto_superscript = M.atto_superscript || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'sup');
|
||||
} else {
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-atto_superscript-button",function(e,t){var n={TAGS:"sup"};M.atto_superscript=M.atto_superscript||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("superscript",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/superscript","core");M.editor_atto.add_toolbar_button(e.elementid,"superscript",r,e.group,t);var i=M.editor_atto.get_editable_node(e.elementid);i.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"sup"):M.editor_atto.remove_widget_highlight(e.elementid,"sup")})}}},"@VERSION@",{requires:["node"]});
|
||||
YUI.add("moodle-atto_superscript-button",function(e,t){var n={TAGS:"sup"};M.atto_superscript=M.atto_superscript||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("superscript",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/superscript","core");M.editor_atto.add_toolbar_button(e.elementid,"superscript",r,e.group,t),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"sup"):M.editor_atto.remove_widget_highlight(e.elementid,"sup")})}}},"@VERSION@",{requires:["node"]});
|
||||
|
@ -49,8 +49,7 @@ M.atto_superscript = M.atto_superscript || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'sup');
|
||||
} else {
|
||||
|
@ -47,8 +47,7 @@ M.atto_superscript = M.atto_superscript || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'sup');
|
||||
} else {
|
||||
|
@ -531,8 +531,7 @@ M.atto_table = M.atto_table || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'table');
|
||||
} else {
|
||||
|
File diff suppressed because one or more lines are too long
@ -531,8 +531,7 @@ M.atto_table = M.atto_table || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'table');
|
||||
} else {
|
||||
|
@ -529,8 +529,7 @@ M.atto_table = M.atto_table || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'table');
|
||||
} else {
|
||||
|
@ -50,8 +50,7 @@ M.atto_underline = M.atto_underline || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'underline');
|
||||
} else {
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-atto_underline-button",function(e,t){var n={TAGS:"u"};M.atto_underline=M.atto_underline||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("underline",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/underline","core");M.editor_atto.add_toolbar_button(e.elementid,"underline",r,e.group,t),M.editor_atto.add_button_shortcut({action:"underline",keys:85});var i=M.editor_atto.get_editable_node(e.elementid);i.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"underline"):M.editor_atto.remove_widget_highlight(e.elementid,"underline")})}}},"@VERSION@",{requires:["node","moodle-editor_atto-editor-shortcut"]});
|
||||
YUI.add("moodle-atto_underline-button",function(e,t){var n={TAGS:"u"};M.atto_underline=M.atto_underline||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("underline",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/underline","core");M.editor_atto.add_toolbar_button(e.elementid,"underline",r,e.group,t),M.editor_atto.add_button_shortcut({action:"underline",keys:85}),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"underline"):M.editor_atto.remove_widget_highlight(e.elementid,"underline")})}}},"@VERSION@",{requires:["node","moodle-editor_atto-editor-shortcut"]});
|
||||
|
@ -50,8 +50,7 @@ M.atto_underline = M.atto_underline || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'underline');
|
||||
} else {
|
||||
|
@ -48,8 +48,7 @@ M.atto_underline = M.atto_underline || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'underline');
|
||||
} else {
|
||||
|
@ -49,8 +49,7 @@ M.atto_unlink = M.atto_unlink || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'unlink');
|
||||
} else {
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-atto_unlink-button",function(e,t){var n={TAGS:"a"};M.atto_unlink=M.atto_unlink||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("unlink",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/remove_link","core");M.editor_atto.add_toolbar_button(e.elementid,"unlink",r,e.group,t);var i=M.editor_atto.get_editable_node(e.elementid);i.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"unlink"):M.editor_atto.remove_widget_highlight(e.elementid,"unlink")})}}},"@VERSION@",{requires:["node"]});
|
||||
YUI.add("moodle-atto_unlink-button",function(e,t){var n={TAGS:"a"};M.atto_unlink=M.atto_unlink||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("unlink",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/remove_link","core");M.editor_atto.add_toolbar_button(e.elementid,"unlink",r,e.group,t),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"unlink"):M.editor_atto.remove_widget_highlight(e.elementid,"unlink")})}}},"@VERSION@",{requires:["node"]});
|
||||
|
@ -49,8 +49,7 @@ M.atto_unlink = M.atto_unlink || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'unlink');
|
||||
} else {
|
||||
|
@ -47,8 +47,7 @@ M.atto_unlink = M.atto_unlink || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'unlink');
|
||||
} else {
|
||||
|
@ -49,8 +49,7 @@ M.atto_unorderedlist = M.atto_unorderedlist || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'unorderedlist');
|
||||
} else {
|
||||
|
@ -1 +1 @@
|
||||
YUI.add("moodle-atto_unorderedlist-button",function(e,t){var n={TAGS:"ul"};M.atto_unorderedlist=M.atto_unorderedlist||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("insertUnorderedList",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/bullet_list","core");M.editor_atto.add_toolbar_button(e.elementid,"unorderedlist",r,e.group,t);var i=M.editor_atto.get_editable_node(e.elementid);i.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"unorderedlist"):M.editor_atto.remove_widget_highlight(e.elementid,"unorderedlist")})}}},"@VERSION@",{requires:["node"]});
|
||||
YUI.add("moodle-atto_unorderedlist-button",function(e,t){var n={TAGS:"ul"};M.atto_unorderedlist=M.atto_unorderedlist||{init:function(e){var t=function(e,t){e.preventDefault(),M.editor_atto.is_active(t)||M.editor_atto.focus(t),document.execCommand("insertUnorderedList",!1,null),M.editor_atto.text_updated(t)},r=M.util.image_url("e/bullet_list","core");M.editor_atto.add_toolbar_button(e.elementid,"unorderedlist",r,e.group,t),M.editor_atto.on("atto:selectionchanged",function(e){M.editor_atto.selection_filter_matches(e.elementid,n.TAGS,e.selectedNodes)?M.editor_atto.add_widget_highlight(e.elementid,"unorderedlist"):M.editor_atto.remove_widget_highlight(e.elementid,"unorderedlist")})}}},"@VERSION@",{requires:["node"]});
|
||||
|
@ -49,8 +49,7 @@ M.atto_unorderedlist = M.atto_unorderedlist || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'unorderedlist');
|
||||
} else {
|
||||
|
@ -47,8 +47,7 @@ M.atto_unorderedlist = M.atto_unorderedlist || {
|
||||
// Attach an event listner to watch for "changes" in the contenteditable.
|
||||
// This includes cursor changes, we check if the button should be active or not, based
|
||||
// on the text selection.
|
||||
var editable = M.editor_atto.get_editable_node(params.elementid);
|
||||
editable.on('atto:selectionchanged', function(e) {
|
||||
M.editor_atto.on('atto:selectionchanged', function(e) {
|
||||
if (M.editor_atto.selection_filter_matches(e.elementid, SELECTORS.TAGS, e.selectedNodes)) {
|
||||
M.editor_atto.add_widget_highlight(e.elementid, 'unorderedlist');
|
||||
} else {
|
||||
|
@ -126,6 +126,38 @@ M.editor_atto = M.editor_atto || {
|
||||
|
||||
focusfromclick : false,
|
||||
|
||||
/**
|
||||
* Set to true after events have been published for M.editor_atto.
|
||||
* @param eventspublished
|
||||
* @type boolean
|
||||
*/
|
||||
eventspublished : false,
|
||||
|
||||
/**
|
||||
* A unique identifier for the last selection recorded.
|
||||
* @param lastselection
|
||||
* @type string
|
||||
*/
|
||||
lastselection : null,
|
||||
|
||||
/**
|
||||
* The number of selection ranges when the selection was last recorded.
|
||||
* @param lastselectioncount
|
||||
* @type integer
|
||||
*/
|
||||
lastselectioncount : 0,
|
||||
|
||||
/**
|
||||
* Set to true after the first editor has been initialised.
|
||||
*
|
||||
* "The first transport is away. The first transport is away."
|
||||
* ―Lieutenant Romas Navander, during the Battle of Hoth
|
||||
*
|
||||
* @param firstinit
|
||||
* @type boolean
|
||||
*/
|
||||
firstinit : true,
|
||||
|
||||
/**
|
||||
* Toggle a menu.
|
||||
* @param event e
|
||||
@ -597,6 +629,9 @@ M.editor_atto = M.editor_atto || {
|
||||
* @param object params for this editor instance.
|
||||
*/
|
||||
init : function(params) {
|
||||
// Some things we only want to do on the first init.
|
||||
var firstinit = this.firstinit;
|
||||
this.firstinit = false;
|
||||
var wrapper = Y.Node.create('<div class="' + CSS.WRAPPER + '" />');
|
||||
var atto = Y.Node.create('<div id="' + params.elementid + 'editable" ' +
|
||||
'contenteditable="true" ' +
|
||||
@ -644,6 +679,14 @@ M.editor_atto = M.editor_atto || {
|
||||
|
||||
this.publish_events();
|
||||
atto.on('atto:selectionchanged', this.save_selection, this, params.elementid);
|
||||
if (firstinit) {
|
||||
// Aha this is the first initialisation.
|
||||
// Publish our custom events.
|
||||
this.publish_events();
|
||||
// Start tracking selections.
|
||||
this.on('atto:selectionchanged', this.save_selection, this);
|
||||
}
|
||||
|
||||
atto.on('focus', this.restore_selection, this, params.elementid);
|
||||
// Do not restore selection when focus is from a click event.
|
||||
atto.on('mousedown', function() { this.focusfromclick = true; }, this);
|
||||
@ -704,78 +747,11 @@ M.editor_atto = M.editor_atto || {
|
||||
* @param string elementid - the id of the textarea we created this editor from.
|
||||
*/
|
||||
publish_events : function() {
|
||||
Y.Event.define("atto:selectionchanged", {
|
||||
/**
|
||||
* Catch the keydown/mouseup events, and fire a synthetic event for the change event.
|
||||
* @param {Y.Event} e - The real event that triggers this synthetic one.
|
||||
* @param {Object} params - Object containing the subscription object and the notifier.
|
||||
*/
|
||||
changeHandler: function (e, params) {
|
||||
// Add 3 properties to the event.
|
||||
// 1. add the elementid.
|
||||
var elementid = params.sub.node.getAttribute('id');
|
||||
// Strip 'editable' from the end of the id.
|
||||
elementid = elementid.substring(0, elementid.length - 8);
|
||||
e.elementid = elementid;
|
||||
|
||||
// 2. The list of leaf nodes contained in the selection.
|
||||
e.selectedNodes = M.editor_atto.get_selected_nodes(elementid);
|
||||
// 3. The current selection (Range)
|
||||
e.selection = M.editor_atto.get_selection();
|
||||
params.notifier.fire(e);
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribe to the atto:selectionchanged event.
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
* @param {Y.Event.Notifier} notifier - YUI notifier object.
|
||||
*/
|
||||
on: function (node, sub, notifier) {
|
||||
var params = { notifier: notifier, sub: sub };
|
||||
sub.attoKeyDownDetach = node.on('keydown', Y.throttle(this.changeHandler, 100), this, params);
|
||||
sub.attoMouseUpDetach = node.on('mouseup', Y.throttle(this.changeHandler, 100), this, params);
|
||||
sub.attoFocusDetach = node.on('focus', Y.throttle(this.changeHandler, 100), this, params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Detach the atto:selectionchanged event.
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
*/
|
||||
detach: function (node, sub) {
|
||||
sub.attoKeyDownDetach.detach();
|
||||
sub.attoMouseUpDetach.detach();
|
||||
sub.attoFocusDetach.detach();
|
||||
},
|
||||
|
||||
/**
|
||||
* Delegate the atto:selectionchanged event.
|
||||
*
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
* @param {Y.Event.Notifier} notifier - YUI notifier object.
|
||||
* @param {String} filter - CSS selector for the filter.
|
||||
*/
|
||||
delegate: function(node, sub, notifier, filter) {
|
||||
var params = { notifier: notifier, sub: sub };
|
||||
sub.attoKeyDownDetachDelegate = node.delegate('keydown', Y.throttle(this.changeHandler, 100), filter, this, params);
|
||||
sub.attoMouseUpDetachDelegate = node.delegate('mouseup', Y.throttle(this.changeHandler, 100), filter, this, params);
|
||||
sub.attoFocusDetachDelegate = node.delegate('focus', Y.throttle(this.changeHandler, 100), filter, this, params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Detach a delegated atto:selectionchanged event.
|
||||
*
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
*/
|
||||
detachDelegate: function(node, sub) {
|
||||
sub.attoKeyDownDetachDelegate.detach();
|
||||
sub.attoMouseUpDetachDelegate.detach();
|
||||
sub.attoFocusDetachDelegate.detach();
|
||||
}
|
||||
});
|
||||
if (!this.eventspublished) {
|
||||
Y.publish('atto:selectionchanged', {prefix: 'atto'});
|
||||
Y.delegate(['mouseup', 'keyup', 'focus'], this._has_selection_changed, document.body, '.' + CSS.CONTENT, this);
|
||||
this.eventspublished = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -934,8 +910,9 @@ M.editor_atto = M.editor_atto || {
|
||||
* @param Y.Event event
|
||||
* @param string elementid
|
||||
*/
|
||||
save_selection : function(event, elementid) {
|
||||
if (this.is_active(elementid)) {
|
||||
save_selection : function(event) {
|
||||
var elementid = event.elementid;
|
||||
if (elementid && this.is_active(elementid)) {
|
||||
var sel = this.get_selection();
|
||||
if (sel.length > 0) {
|
||||
this.selections[elementid] = sel;
|
||||
@ -959,7 +936,7 @@ M.editor_atto = M.editor_atto || {
|
||||
|
||||
/**
|
||||
* Get the selection object that can be passed back to set_selection.
|
||||
* @return range (browser dependent)
|
||||
* @return Range[]|TextRange[] All browsers except IE8 and less will get Range[], IE8 and less will get TextRange[]
|
||||
*/
|
||||
get_selection : function() {
|
||||
if (window.getSelection) {
|
||||
@ -972,7 +949,7 @@ M.editor_atto = M.editor_atto || {
|
||||
} else if (document.selection) {
|
||||
// IE < 9
|
||||
if (document.selection.createRange) {
|
||||
return document.selection.createRange();
|
||||
return [document.selection.createRange()];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -984,10 +961,10 @@ M.editor_atto = M.editor_atto || {
|
||||
* @return boolean
|
||||
*/
|
||||
selection_contains_node : function(node) {
|
||||
// TODO: This will break in IE11 . IE11 doesn't provide containsNode and document.selection has been removed.
|
||||
var range, sel;
|
||||
if (window.getSelection) {
|
||||
sel = window.getSelection();
|
||||
|
||||
if (sel.containsNode) {
|
||||
return sel.containsNode(node.getDOMNode(), true);
|
||||
}
|
||||
@ -1008,159 +985,390 @@ M.editor_atto = M.editor_atto || {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
selection_filter_matches : function(elementid, selector, selectednodes) {
|
||||
var result = true;
|
||||
|
||||
if (!selectednodes) {
|
||||
// Find this because it was not passed as a param.
|
||||
selectednodes = M.editor_atto.get_selected_nodes(elementid);
|
||||
}
|
||||
selector = '.' + CSS.CONTENT + ' ' + selector;
|
||||
|
||||
if (selectednodes.size() === 0) {
|
||||
return false;
|
||||
}
|
||||
selectednodes.each(function(node) {
|
||||
if (!node.ancestor(selector, true)) {
|
||||
result = false;
|
||||
var allmatch = selectednodes.size() > 0;
|
||||
selectednodes.each(function(node){
|
||||
// Check each node, if it doesn't match the tags AND is not within the specified tags then fail this thing.
|
||||
if (!allmatch || !node.ancestor(selector, true, '#' + elementid + "editable")) {
|
||||
allmatch = false;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of nodes that are ancestors of the selection nodes,
|
||||
* and match the specified css selector (and are contained within the editable div).
|
||||
*
|
||||
* @param {String} elementid
|
||||
* @param {String} selector
|
||||
* @return Y.NodeList
|
||||
*/
|
||||
selection_filter : function(elementid, selector) {
|
||||
var selectednodes = M.editor_atto.get_selected_nodes(elementid);
|
||||
var result = new Y.NodeList();
|
||||
selector = '.' + CSS.CONTENT + ' ' + selector;
|
||||
selectednodes.each(function(node) {
|
||||
node.ancestors(selector, true).each(function(match) {
|
||||
if (result.indexOf(match) === -1) {
|
||||
result.push(match);
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}, this);
|
||||
return allmatch;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the deepest possible list of nodes in the current selection.
|
||||
*
|
||||
* Presently this method contains logic to work out what nodes are "selected" by the user.
|
||||
* The user selection may take the form of:
|
||||
* - text : selected text within a single node, such as double clicking a word.
|
||||
* - A node : the user has clicked an image or media object that is selectable by clicking upon an element.
|
||||
* - text + nodes: the user has dragged or used shift + arrows to select a body of content that may include
|
||||
* both text and/or nodes.
|
||||
* To work out the selected nodes we look at the start and end node. If they are the same then there is only one node
|
||||
* containing selected content.
|
||||
* If they don't match we get the common ancestor of the start and end nodes, and then iterate all child nodes recording
|
||||
* all nodes that appear after the start node and before the end node.
|
||||
*
|
||||
* YUI deal with this in a totally different way to us, they have a method getSelected that gets the selected nodes by
|
||||
* exec'ing a command to add fontname, and then find all nodes that have been affected. They remove the fontname property
|
||||
* before returning the nodes.
|
||||
*
|
||||
* @param {String} elementid
|
||||
* @return Y.NodeList
|
||||
*/
|
||||
get_selected_nodes : function(elementid) {
|
||||
var nodes = [],
|
||||
results = new Y.NodeList(),
|
||||
points = [],
|
||||
range,
|
||||
i,
|
||||
sel,
|
||||
start,
|
||||
end,
|
||||
parent,
|
||||
values;
|
||||
|
||||
var editable = M.editor_atto.get_editable_node(elementid);
|
||||
var startnode = M.editor_atto.get_selection_start_container();
|
||||
var endnode = M.editor_atto.get_selection_end_container();
|
||||
|
||||
if (!startnode || !endnode) {
|
||||
return new Y.NodeList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function to walk the dom tree between 2 nodes and build
|
||||
* a list of leaf nodes.
|
||||
* @param {Y.Node} node - The current node.
|
||||
* @param {Y.Node} endnode - The node to stop at.
|
||||
* @param {Y.Node} boundingnode - A node that will contain the selection - do not result results outside this node.
|
||||
* @param {Integer} skipchildren - When returning from a child to a parent, this is how many children to skip.
|
||||
* @param {Boolean} isstart - Only true for the starting condition. Used in the case when startnode == endnode.
|
||||
* @return {Y.NodeList} The list of leaf nodes found.
|
||||
*/
|
||||
var find_leaf_nodes = function(node, endnode, boundingnode, skipchildren, isstart) {
|
||||
var leafnodes = new Y.NodeList();
|
||||
if (endnode === node) {
|
||||
// If there are child nodes and we didn't come from a child node, then skip this end condition.
|
||||
if (!(endnode === node && startnode === node && isstart && node.hasChildNodes())) {
|
||||
// If the end node is a leaf - return it.
|
||||
if (!node.hasChildNodes()) {
|
||||
leafnodes.push(node);
|
||||
// The window.getSelection method is available from IE9 onwards and for all other browsers.
|
||||
if (window.getSelection) {
|
||||
sel = window.getSelection();
|
||||
for (i = 0; i < sel.rangeCount; i++) {
|
||||
range = sel.getRangeAt(i);
|
||||
start = new Y.Node(range.startContainer);
|
||||
end = new Y.Node(range.endContainer);
|
||||
if (start.compareTo(end)) {
|
||||
// The start and end node are the same node. Only one node is selected. Easy!
|
||||
nodes.push(end);
|
||||
} else {
|
||||
// The start and end node are not the same, we have 2..n nodes selected, we'll need to work them out.
|
||||
// For the time being record them as a pair of points along with the common ancestor.
|
||||
// First up we need to check the order of the nodes, we need them in the order they appear in the DOM
|
||||
// however the selection may have been rtl or ltr and in the case of rtl we need to switch the order.
|
||||
values = this._fix_selected_nodes_from_range(
|
||||
start,
|
||||
range.startOffset,
|
||||
end,
|
||||
range.endOffset
|
||||
);
|
||||
// Ok start and end are in order. Yay!
|
||||
start = values[0];
|
||||
end = values[2];
|
||||
// We calculate the common ancestor here as we should be able to get it from the range object.
|
||||
if (range.commonAncestorContainer) {
|
||||
parent = range.commonAncestorContainer;
|
||||
} else if (range.parentElement) {
|
||||
parent = range.parentElement();
|
||||
} else {
|
||||
parent = this._get_common_parent_from_nodes(start, end);
|
||||
}
|
||||
// We reached the end of the selection.
|
||||
return leafnodes;
|
||||
points.push([
|
||||
start,
|
||||
end,
|
||||
parent
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Node is not a leaf, look at it's children.
|
||||
if (node.hasChildNodes()) {
|
||||
var children = node.get('childNodes');
|
||||
// Skipchildren is used when returning from a child node, to make the parent look at the next child node.
|
||||
var child = children.item(skipchildren);
|
||||
if (child) {
|
||||
// There was a child node, find all the leaf nodes in it.
|
||||
leafnodes.concat(find_leaf_nodes(child, endnode, boundingnode, 0, false));
|
||||
} else if (document.selection && document.selection.createRange) {
|
||||
// The document.selection.createRange is available in IE only, versions 5.5 through 10 (not IE11).
|
||||
// However s window.getSelection exists in IE9+ this code will only execute in IE 5.5 -> IE 8.
|
||||
// http://msdn.microsoft.com/en-us/library/ie/ms535869(v=vs.85).aspx
|
||||
range = document.selection.createRange();
|
||||
// Create range will return either TextRange object from the current text selection, or a controlRange
|
||||
// collection from the control selection.
|
||||
if (document.selection.type === "Control") {
|
||||
// Its a control collection, well this is easy, its an array of Nodes. All the work has been done for us.
|
||||
for (i = 0; i < range.length; i++) {
|
||||
nodes.push(new Y.Node(range(i)));
|
||||
}
|
||||
} else {
|
||||
// This node has no children - so save it.
|
||||
leafnodes.push(node);
|
||||
// Its a TextRange, in which case its a text element (nodeType 3) and its parent will be the node containing the
|
||||
// selection.
|
||||
nodes.push(new Y.Node(range.parentElement()));
|
||||
}
|
||||
// If we hit the bounding node - this is an end condition.
|
||||
if (node === boundingnode) {
|
||||
return leafnodes;
|
||||
}
|
||||
// Ok we have two array as this point.
|
||||
// - Nodes is an array containing nodes that are currently highlighted by selection.
|
||||
// - Points is an array containing start, end, and parent nodes.
|
||||
// For each item in the points array we need to find all nodes containing selected content.
|
||||
for (i in points) {
|
||||
nodes = nodes.concat(this._get_all_nodes_between.apply(this, points[i]));
|
||||
}
|
||||
// Finally for each node we've determined is highlighted we need to check it belongs to the current editable area.
|
||||
for (i in nodes) {
|
||||
if (results.indexOf(nodes[i]) === -1 && nodes[i].ancestor('#' + elementid + "editable")) {
|
||||
results.push(nodes[i]);
|
||||
}
|
||||
// We have added all the nodes in this branch, look at the next child of the parent.
|
||||
var parent = node.ancestor();
|
||||
if (parent) {
|
||||
var currentindex = parent.get('childNodes').indexOf(node);
|
||||
leafnodes.concat(find_leaf_nodes(parent, endnode, boundingnode, currentindex + 1, false));
|
||||
}
|
||||
return leafnodes;
|
||||
};
|
||||
|
||||
// Kick off the recursive function.
|
||||
return find_leaf_nodes(startnode, endnode, editable, 0, true);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the first node that contains the current selection.
|
||||
* @return DOMNode or false
|
||||
* Returns true if the selection has changed since this method was last called.
|
||||
*
|
||||
* If the selection has changed we also fire the custom event atto:selectionchanged
|
||||
*
|
||||
* @method _has_selection_changed
|
||||
* @private
|
||||
* @param {EventFacade} e
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
get_selection_start_container : function() {
|
||||
var selection = M.editor_atto.get_selection();
|
||||
|
||||
if (selection.length && selection.pop) {
|
||||
selection = selection.pop();
|
||||
_has_selection_changed : function(e) {
|
||||
var sel,
|
||||
i,
|
||||
range,
|
||||
bits = [],
|
||||
selectedstr = '',
|
||||
selectioncount = 0;
|
||||
if (window.getSelection) {
|
||||
// Hoorah - working with the alternative is horrid..
|
||||
sel = window.getSelection();
|
||||
selectioncount = sel.rangeCount;
|
||||
for (i = 0; i < selectioncount; i++) {
|
||||
range = sel.getRangeAt(i);
|
||||
bits.push(range.startOffset);
|
||||
bits.push(range.endOffset);
|
||||
bits.push(this._get_node_identifier(range.startContainer));
|
||||
bits.push(this._get_node_identifier(range.endContainer));
|
||||
bits.push('&');
|
||||
}
|
||||
} else if (document.selection && document.selection.createRange) {
|
||||
range = document.selection.createRange();
|
||||
if (range !== null) {
|
||||
if (document.selection.type === "Control") {
|
||||
// Is a controlRange: http://msdn.microsoft.com/en-us/library/ie/hh826021(v=vs.85).aspx
|
||||
selectioncount = range.length;
|
||||
for (i = 0; i < selectioncount; i++) {
|
||||
bits.push(this._get_node_identifier(range(i)));
|
||||
}
|
||||
} else {
|
||||
// Is a TextRange: http://msdn.microsoft.com/en-us/library/ie/ms535872(v=vs.85).aspx
|
||||
selectioncount = 1;
|
||||
bits.push(this._get_node_identifier(range.parentElement()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selection.startContainer) {
|
||||
return Y.one(selection.startContainer);
|
||||
} else if (selection.parentElement) {
|
||||
var range = selection.duplicate();
|
||||
|
||||
range.collapse(true);
|
||||
return Y.one(range.parentElement());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the last node that contains the current selection.
|
||||
* @return DOMNode or false
|
||||
*/
|
||||
get_selection_end_container : function() {
|
||||
var selection = M.editor_atto.get_selection();
|
||||
|
||||
if (selection.length && selection.pop) {
|
||||
selection = selection.pop();
|
||||
}
|
||||
|
||||
if (selection.endContainer) {
|
||||
return Y.one(selection.endContainer);
|
||||
} else if (selection.parentElement) {
|
||||
var range = selection.duplicate();
|
||||
|
||||
range.collapse(false);
|
||||
return Y.one(range.parentElement());
|
||||
selectedstr = bits.join('|');
|
||||
if (this.lastselection !== selectedstr) {
|
||||
this.lastselection = selectedstr;
|
||||
this.lastselectioncount = selectioncount;
|
||||
return this._fire_selectionchanged(e);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fires the atto:selectionchanged event.
|
||||
*
|
||||
* When the selectionchanged event gets fired three arguments are given:
|
||||
* - event : the original event that lead to this event being fired.
|
||||
* - elementid : the editor elementid for which this event is being fired.
|
||||
* - selectednodes : an array containing nodes that are entirely selected of contain partially selected content.
|
||||
*
|
||||
* @method _fire_selectionchanged
|
||||
* @private
|
||||
* @param {EventFacade} e
|
||||
*/
|
||||
_fire_selectionchanged : function(e) {
|
||||
var editableid = e.currentTarget.get('id'),
|
||||
elementid = editableid.substring(0, editableid.length - 8),
|
||||
selectednodes = this.get_selected_nodes(elementid);
|
||||
this.fire('atto:selectionchanged', {
|
||||
event : e,
|
||||
elementid : elementid,
|
||||
selectedNodes : selectednodes
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the best identifier for the given node that can be found. May not be unique.
|
||||
*
|
||||
* @method _get_node_identifier
|
||||
* @private
|
||||
* @param {Node} node
|
||||
* @returns {string}
|
||||
*/
|
||||
_get_node_identifier : function(node) {
|
||||
var uid = (node.nodeType === 3) ? node.length : node.id;
|
||||
return uid || node.className || node.nodeName;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fixes the selection range, normalising it, ordering the start and end by dom position, and correcting for noteType and offset
|
||||
*
|
||||
* Note about offsets:
|
||||
* If the container is a Node of type Text, Comment, or CDATASection, then the offset is the number of characters from the
|
||||
* start of the container to the boundary point of the Range.
|
||||
* For other Node types, the offset is the number of child nodes between the start of the container and the boundary
|
||||
* point of the Range.
|
||||
*
|
||||
* @method _fix_selected_nodes_from_range
|
||||
* @private
|
||||
* @param {Node} a
|
||||
* @param {integer} aoffset
|
||||
* @param {Node} b
|
||||
* @param {integer} boffset
|
||||
* @returns {[Node, integer, Node, integer]}
|
||||
*/
|
||||
_fix_selected_nodes_from_range : function(a, aoffset, b, boffset) {
|
||||
var first,
|
||||
firstoffset,
|
||||
second,
|
||||
secondoffset,
|
||||
searchfunc,
|
||||
acheckend = true,
|
||||
bcheckend = true,
|
||||
checkend = true;
|
||||
|
||||
if (a.get('nodeType') === 1) {
|
||||
a = a.get('childNodes').item(aoffset);
|
||||
acheckend = false;
|
||||
}
|
||||
if (b.get('nodeType') === 1 && boffset > 0) {
|
||||
b = b.get('childNodes').item(boffset - 1);
|
||||
bcheckend = false;
|
||||
}
|
||||
if (a._yuid === b._yuid) {
|
||||
if (aoffset < boffset) {
|
||||
first = a;
|
||||
firstoffset = aoffset;
|
||||
second = b;
|
||||
secondoffset = boffset;
|
||||
checkend = bcheckend;
|
||||
} else {
|
||||
first = b;
|
||||
firstoffset = boffset;
|
||||
second = a;
|
||||
secondoffset = aoffset;
|
||||
checkend = acheckend;
|
||||
}
|
||||
} else {
|
||||
if (a.contains(b)) {
|
||||
first = a;
|
||||
firstoffset = aoffset;
|
||||
second = b;
|
||||
secondoffset = boffset;
|
||||
checkend = bcheckend;
|
||||
} else if (b.contains(a)) {
|
||||
first = b;
|
||||
firstoffset = boffset;
|
||||
second = a;
|
||||
secondoffset = aoffset;
|
||||
checkend = acheckend;
|
||||
} else {
|
||||
searchfunc = function find_first(nodes, x, y) {
|
||||
var i, result;
|
||||
for (i in nodes) {
|
||||
if (nodes[i] === x.getDOMNode()) {
|
||||
return x;
|
||||
} else if (nodes[i] === y.getDOMNode()) {
|
||||
return y;
|
||||
} else if (nodes[i].nodeType === 1) {
|
||||
result = find_first(nodes[i].childNodes, x, y);
|
||||
if (result !== false) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
first = searchfunc(this._get_common_parent_from_nodes(a, b).get('childNodes'), a, b) || a;
|
||||
if (first === a) {
|
||||
firstoffset = aoffset;
|
||||
second = b;
|
||||
secondoffset = boffset;
|
||||
checkend = bcheckend;
|
||||
} else {
|
||||
firstoffset = boffset;
|
||||
second = a;
|
||||
secondoffset = aoffset;
|
||||
checkend = acheckend;
|
||||
}
|
||||
}
|
||||
if (checkend && second.get('nodeType') === 3 && secondoffset === 0 && second.previous('*', true)) {
|
||||
second = second.previous();
|
||||
}
|
||||
}
|
||||
return [first, firstoffset, second, secondoffset];
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an array containing all of the nodes in the Dom between the two reference nodes.
|
||||
*
|
||||
* @method _get_all_nodes_between
|
||||
* @private
|
||||
* @param {Node} a
|
||||
* @param {Node} b
|
||||
* @param {Node} parent The common parent of a and b
|
||||
* @returns {Node[]}
|
||||
*/
|
||||
_get_all_nodes_between : function(a, b, parent) {
|
||||
var startfound = false,
|
||||
endfound = false,
|
||||
nodes = [],
|
||||
parenthasselectedtext = false;
|
||||
if (a === b || a.contains(b)) {
|
||||
return [a];
|
||||
}
|
||||
if (b.contains(a)) {
|
||||
return [b];
|
||||
}
|
||||
if (!parent) {
|
||||
return [a, b];
|
||||
}
|
||||
|
||||
nodes.push(a);
|
||||
parent.get('childNodes').each(function(node) {
|
||||
if (endfound) {
|
||||
return;
|
||||
}
|
||||
if (!startfound && node === a) {
|
||||
startfound = true;
|
||||
} else if (startfound && !endfound) {
|
||||
if (node === b) {
|
||||
endfound = true;
|
||||
} else {
|
||||
if (node.get('nodeType') === 3) {
|
||||
parenthasselectedtext = true;
|
||||
} else {
|
||||
nodes.push(node);
|
||||
nodes = nodes.compact(node.all('*'));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
nodes.push(b);
|
||||
|
||||
if (parenthasselectedtext) {
|
||||
nodes.unshift(parent);
|
||||
}
|
||||
|
||||
return nodes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the common parent of the two given nodes.
|
||||
*
|
||||
* @method _get_common_parent_from_nodes
|
||||
* @private
|
||||
* @param {Node} a
|
||||
* @param {Node} b
|
||||
* @returns {Node}
|
||||
*/
|
||||
_get_common_parent_from_nodes : function(a, b) {
|
||||
var aparents = a.ancestors('*', false, '.' + CSS.CONTENT),
|
||||
bparents = b.ancestors('*', false, '.' + CSS.CONTENT),
|
||||
node;
|
||||
while ((node = aparents.pop())) {
|
||||
if (bparents.indexOf(node) !== -1) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return a.ancestor('.' + CSS.CONTENT);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the dom node representing the common anscestor of the selection nodes.
|
||||
* @return DOMNode or false
|
||||
@ -1180,17 +1388,6 @@ M.editor_atto = M.editor_atto || {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the list of child nodes of the selection.
|
||||
* @return DOMNode[]
|
||||
*/
|
||||
get_selection_text : function() {
|
||||
var selection = M.editor_atto.get_selection();
|
||||
if (selection.length > 0 && selection[0].cloneContents) {
|
||||
return selection[0].cloneContents();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the current selection. Used to restore a selection.
|
||||
*/
|
||||
@ -1433,6 +1630,9 @@ M.editor_atto = M.editor_atto || {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The editor_atto is publishing custom events that can be subscribed to.
|
||||
Y.augment(M.editor_atto, Y.EventTarget);
|
||||
var CONTROLMENU_NAME = "Controlmenu",
|
||||
CONTROLMENU;
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -126,6 +126,38 @@ M.editor_atto = M.editor_atto || {
|
||||
|
||||
focusfromclick : false,
|
||||
|
||||
/**
|
||||
* Set to true after events have been published for M.editor_atto.
|
||||
* @param eventspublished
|
||||
* @type boolean
|
||||
*/
|
||||
eventspublished : false,
|
||||
|
||||
/**
|
||||
* A unique identifier for the last selection recorded.
|
||||
* @param lastselection
|
||||
* @type string
|
||||
*/
|
||||
lastselection : null,
|
||||
|
||||
/**
|
||||
* The number of selection ranges when the selection was last recorded.
|
||||
* @param lastselectioncount
|
||||
* @type integer
|
||||
*/
|
||||
lastselectioncount : 0,
|
||||
|
||||
/**
|
||||
* Set to true after the first editor has been initialised.
|
||||
*
|
||||
* "The first transport is away. The first transport is away."
|
||||
* ―Lieutenant Romas Navander, during the Battle of Hoth
|
||||
*
|
||||
* @param firstinit
|
||||
* @type boolean
|
||||
*/
|
||||
firstinit : true,
|
||||
|
||||
/**
|
||||
* Toggle a menu.
|
||||
* @param event e
|
||||
@ -597,6 +629,9 @@ M.editor_atto = M.editor_atto || {
|
||||
* @param object params for this editor instance.
|
||||
*/
|
||||
init : function(params) {
|
||||
// Some things we only want to do on the first init.
|
||||
var firstinit = this.firstinit;
|
||||
this.firstinit = false;
|
||||
var wrapper = Y.Node.create('<div class="' + CSS.WRAPPER + '" />');
|
||||
var atto = Y.Node.create('<div id="' + params.elementid + 'editable" ' +
|
||||
'contenteditable="true" ' +
|
||||
@ -644,6 +679,14 @@ M.editor_atto = M.editor_atto || {
|
||||
|
||||
this.publish_events();
|
||||
atto.on('atto:selectionchanged', this.save_selection, this, params.elementid);
|
||||
if (firstinit) {
|
||||
// Aha this is the first initialisation.
|
||||
// Publish our custom events.
|
||||
this.publish_events();
|
||||
// Start tracking selections.
|
||||
this.on('atto:selectionchanged', this.save_selection, this);
|
||||
}
|
||||
|
||||
atto.on('focus', this.restore_selection, this, params.elementid);
|
||||
// Do not restore selection when focus is from a click event.
|
||||
atto.on('mousedown', function() { this.focusfromclick = true; }, this);
|
||||
@ -704,78 +747,11 @@ M.editor_atto = M.editor_atto || {
|
||||
* @param string elementid - the id of the textarea we created this editor from.
|
||||
*/
|
||||
publish_events : function() {
|
||||
Y.Event.define("atto:selectionchanged", {
|
||||
/**
|
||||
* Catch the keydown/mouseup events, and fire a synthetic event for the change event.
|
||||
* @param {Y.Event} e - The real event that triggers this synthetic one.
|
||||
* @param {Object} params - Object containing the subscription object and the notifier.
|
||||
*/
|
||||
changeHandler: function (e, params) {
|
||||
// Add 3 properties to the event.
|
||||
// 1. add the elementid.
|
||||
var elementid = params.sub.node.getAttribute('id');
|
||||
// Strip 'editable' from the end of the id.
|
||||
elementid = elementid.substring(0, elementid.length - 8);
|
||||
e.elementid = elementid;
|
||||
|
||||
// 2. The list of leaf nodes contained in the selection.
|
||||
e.selectedNodes = M.editor_atto.get_selected_nodes(elementid);
|
||||
// 3. The current selection (Range)
|
||||
e.selection = M.editor_atto.get_selection();
|
||||
params.notifier.fire(e);
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribe to the atto:selectionchanged event.
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
* @param {Y.Event.Notifier} notifier - YUI notifier object.
|
||||
*/
|
||||
on: function (node, sub, notifier) {
|
||||
var params = { notifier: notifier, sub: sub };
|
||||
sub.attoKeyDownDetach = node.on('keydown', Y.throttle(this.changeHandler, 100), this, params);
|
||||
sub.attoMouseUpDetach = node.on('mouseup', Y.throttle(this.changeHandler, 100), this, params);
|
||||
sub.attoFocusDetach = node.on('focus', Y.throttle(this.changeHandler, 100), this, params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Detach the atto:selectionchanged event.
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
*/
|
||||
detach: function (node, sub) {
|
||||
sub.attoKeyDownDetach.detach();
|
||||
sub.attoMouseUpDetach.detach();
|
||||
sub.attoFocusDetach.detach();
|
||||
},
|
||||
|
||||
/**
|
||||
* Delegate the atto:selectionchanged event.
|
||||
*
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
* @param {Y.Event.Notifier} notifier - YUI notifier object.
|
||||
* @param {String} filter - CSS selector for the filter.
|
||||
*/
|
||||
delegate: function(node, sub, notifier, filter) {
|
||||
var params = { notifier: notifier, sub: sub };
|
||||
sub.attoKeyDownDetachDelegate = node.delegate('keydown', Y.throttle(this.changeHandler, 100), filter, this, params);
|
||||
sub.attoMouseUpDetachDelegate = node.delegate('mouseup', Y.throttle(this.changeHandler, 100), filter, this, params);
|
||||
sub.attoFocusDetachDelegate = node.delegate('focus', Y.throttle(this.changeHandler, 100), filter, this, params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Detach a delegated atto:selectionchanged event.
|
||||
*
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
*/
|
||||
detachDelegate: function(node, sub) {
|
||||
sub.attoKeyDownDetachDelegate.detach();
|
||||
sub.attoMouseUpDetachDelegate.detach();
|
||||
sub.attoFocusDetachDelegate.detach();
|
||||
}
|
||||
});
|
||||
if (!this.eventspublished) {
|
||||
Y.publish('atto:selectionchanged', {prefix: 'atto'});
|
||||
Y.delegate(['mouseup', 'keyup', 'focus'], this._has_selection_changed, document.body, '.' + CSS.CONTENT, this);
|
||||
this.eventspublished = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -934,8 +910,9 @@ M.editor_atto = M.editor_atto || {
|
||||
* @param Y.Event event
|
||||
* @param string elementid
|
||||
*/
|
||||
save_selection : function(event, elementid) {
|
||||
if (this.is_active(elementid)) {
|
||||
save_selection : function(event) {
|
||||
var elementid = event.elementid;
|
||||
if (elementid && this.is_active(elementid)) {
|
||||
var sel = this.get_selection();
|
||||
if (sel.length > 0) {
|
||||
this.selections[elementid] = sel;
|
||||
@ -959,7 +936,7 @@ M.editor_atto = M.editor_atto || {
|
||||
|
||||
/**
|
||||
* Get the selection object that can be passed back to set_selection.
|
||||
* @return range (browser dependent)
|
||||
* @return Range[]|TextRange[] All browsers except IE8 and less will get Range[], IE8 and less will get TextRange[]
|
||||
*/
|
||||
get_selection : function() {
|
||||
if (window.getSelection) {
|
||||
@ -972,7 +949,7 @@ M.editor_atto = M.editor_atto || {
|
||||
} else if (document.selection) {
|
||||
// IE < 9
|
||||
if (document.selection.createRange) {
|
||||
return document.selection.createRange();
|
||||
return [document.selection.createRange()];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -984,10 +961,10 @@ M.editor_atto = M.editor_atto || {
|
||||
* @return boolean
|
||||
*/
|
||||
selection_contains_node : function(node) {
|
||||
// TODO: This will break in IE11 . IE11 doesn't provide containsNode and document.selection has been removed.
|
||||
var range, sel;
|
||||
if (window.getSelection) {
|
||||
sel = window.getSelection();
|
||||
|
||||
if (sel.containsNode) {
|
||||
return sel.containsNode(node.getDOMNode(), true);
|
||||
}
|
||||
@ -1008,159 +985,390 @@ M.editor_atto = M.editor_atto || {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
selection_filter_matches : function(elementid, selector, selectednodes) {
|
||||
var result = true;
|
||||
|
||||
if (!selectednodes) {
|
||||
// Find this because it was not passed as a param.
|
||||
selectednodes = M.editor_atto.get_selected_nodes(elementid);
|
||||
}
|
||||
selector = '.' + CSS.CONTENT + ' ' + selector;
|
||||
|
||||
if (selectednodes.size() === 0) {
|
||||
return false;
|
||||
}
|
||||
selectednodes.each(function(node) {
|
||||
if (!node.ancestor(selector, true)) {
|
||||
result = false;
|
||||
var allmatch = selectednodes.size() > 0;
|
||||
selectednodes.each(function(node){
|
||||
// Check each node, if it doesn't match the tags AND is not within the specified tags then fail this thing.
|
||||
if (!allmatch || !node.ancestor(selector, true, '#' + elementid + "editable")) {
|
||||
allmatch = false;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of nodes that are ancestors of the selection nodes,
|
||||
* and match the specified css selector (and are contained within the editable div).
|
||||
*
|
||||
* @param {String} elementid
|
||||
* @param {String} selector
|
||||
* @return Y.NodeList
|
||||
*/
|
||||
selection_filter : function(elementid, selector) {
|
||||
var selectednodes = M.editor_atto.get_selected_nodes(elementid);
|
||||
var result = new Y.NodeList();
|
||||
selector = '.' + CSS.CONTENT + ' ' + selector;
|
||||
selectednodes.each(function(node) {
|
||||
node.ancestors(selector, true).each(function(match) {
|
||||
if (result.indexOf(match) === -1) {
|
||||
result.push(match);
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}, this);
|
||||
return allmatch;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the deepest possible list of nodes in the current selection.
|
||||
*
|
||||
* Presently this method contains logic to work out what nodes are "selected" by the user.
|
||||
* The user selection may take the form of:
|
||||
* - text : selected text within a single node, such as double clicking a word.
|
||||
* - A node : the user has clicked an image or media object that is selectable by clicking upon an element.
|
||||
* - text + nodes: the user has dragged or used shift + arrows to select a body of content that may include
|
||||
* both text and/or nodes.
|
||||
* To work out the selected nodes we look at the start and end node. If they are the same then there is only one node
|
||||
* containing selected content.
|
||||
* If they don't match we get the common ancestor of the start and end nodes, and then iterate all child nodes recording
|
||||
* all nodes that appear after the start node and before the end node.
|
||||
*
|
||||
* YUI deal with this in a totally different way to us, they have a method getSelected that gets the selected nodes by
|
||||
* exec'ing a command to add fontname, and then find all nodes that have been affected. They remove the fontname property
|
||||
* before returning the nodes.
|
||||
*
|
||||
* @param {String} elementid
|
||||
* @return Y.NodeList
|
||||
*/
|
||||
get_selected_nodes : function(elementid) {
|
||||
var nodes = [],
|
||||
results = new Y.NodeList(),
|
||||
points = [],
|
||||
range,
|
||||
i,
|
||||
sel,
|
||||
start,
|
||||
end,
|
||||
parent,
|
||||
values;
|
||||
|
||||
var editable = M.editor_atto.get_editable_node(elementid);
|
||||
var startnode = M.editor_atto.get_selection_start_container();
|
||||
var endnode = M.editor_atto.get_selection_end_container();
|
||||
|
||||
if (!startnode || !endnode) {
|
||||
return new Y.NodeList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function to walk the dom tree between 2 nodes and build
|
||||
* a list of leaf nodes.
|
||||
* @param {Y.Node} node - The current node.
|
||||
* @param {Y.Node} endnode - The node to stop at.
|
||||
* @param {Y.Node} boundingnode - A node that will contain the selection - do not result results outside this node.
|
||||
* @param {Integer} skipchildren - When returning from a child to a parent, this is how many children to skip.
|
||||
* @param {Boolean} isstart - Only true for the starting condition. Used in the case when startnode == endnode.
|
||||
* @return {Y.NodeList} The list of leaf nodes found.
|
||||
*/
|
||||
var find_leaf_nodes = function(node, endnode, boundingnode, skipchildren, isstart) {
|
||||
var leafnodes = new Y.NodeList();
|
||||
if (endnode === node) {
|
||||
// If there are child nodes and we didn't come from a child node, then skip this end condition.
|
||||
if (!(endnode === node && startnode === node && isstart && node.hasChildNodes())) {
|
||||
// If the end node is a leaf - return it.
|
||||
if (!node.hasChildNodes()) {
|
||||
leafnodes.push(node);
|
||||
// The window.getSelection method is available from IE9 onwards and for all other browsers.
|
||||
if (window.getSelection) {
|
||||
sel = window.getSelection();
|
||||
for (i = 0; i < sel.rangeCount; i++) {
|
||||
range = sel.getRangeAt(i);
|
||||
start = new Y.Node(range.startContainer);
|
||||
end = new Y.Node(range.endContainer);
|
||||
if (start.compareTo(end)) {
|
||||
// The start and end node are the same node. Only one node is selected. Easy!
|
||||
nodes.push(end);
|
||||
} else {
|
||||
// The start and end node are not the same, we have 2..n nodes selected, we'll need to work them out.
|
||||
// For the time being record them as a pair of points along with the common ancestor.
|
||||
// First up we need to check the order of the nodes, we need them in the order they appear in the DOM
|
||||
// however the selection may have been rtl or ltr and in the case of rtl we need to switch the order.
|
||||
values = this._fix_selected_nodes_from_range(
|
||||
start,
|
||||
range.startOffset,
|
||||
end,
|
||||
range.endOffset
|
||||
);
|
||||
// Ok start and end are in order. Yay!
|
||||
start = values[0];
|
||||
end = values[2];
|
||||
// We calculate the common ancestor here as we should be able to get it from the range object.
|
||||
if (range.commonAncestorContainer) {
|
||||
parent = range.commonAncestorContainer;
|
||||
} else if (range.parentElement) {
|
||||
parent = range.parentElement();
|
||||
} else {
|
||||
parent = this._get_common_parent_from_nodes(start, end);
|
||||
}
|
||||
// We reached the end of the selection.
|
||||
return leafnodes;
|
||||
points.push([
|
||||
start,
|
||||
end,
|
||||
parent
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Node is not a leaf, look at it's children.
|
||||
if (node.hasChildNodes()) {
|
||||
var children = node.get('childNodes');
|
||||
// Skipchildren is used when returning from a child node, to make the parent look at the next child node.
|
||||
var child = children.item(skipchildren);
|
||||
if (child) {
|
||||
// There was a child node, find all the leaf nodes in it.
|
||||
leafnodes.concat(find_leaf_nodes(child, endnode, boundingnode, 0, false));
|
||||
} else if (document.selection && document.selection.createRange) {
|
||||
// The document.selection.createRange is available in IE only, versions 5.5 through 10 (not IE11).
|
||||
// However s window.getSelection exists in IE9+ this code will only execute in IE 5.5 -> IE 8.
|
||||
// http://msdn.microsoft.com/en-us/library/ie/ms535869(v=vs.85).aspx
|
||||
range = document.selection.createRange();
|
||||
// Create range will return either TextRange object from the current text selection, or a controlRange
|
||||
// collection from the control selection.
|
||||
if (document.selection.type === "Control") {
|
||||
// Its a control collection, well this is easy, its an array of Nodes. All the work has been done for us.
|
||||
for (i = 0; i < range.length; i++) {
|
||||
nodes.push(new Y.Node(range(i)));
|
||||
}
|
||||
} else {
|
||||
// This node has no children - so save it.
|
||||
leafnodes.push(node);
|
||||
// Its a TextRange, in which case its a text element (nodeType 3) and its parent will be the node containing the
|
||||
// selection.
|
||||
nodes.push(new Y.Node(range.parentElement()));
|
||||
}
|
||||
// If we hit the bounding node - this is an end condition.
|
||||
if (node === boundingnode) {
|
||||
return leafnodes;
|
||||
}
|
||||
// Ok we have two array as this point.
|
||||
// - Nodes is an array containing nodes that are currently highlighted by selection.
|
||||
// - Points is an array containing start, end, and parent nodes.
|
||||
// For each item in the points array we need to find all nodes containing selected content.
|
||||
for (i in points) {
|
||||
nodes = nodes.concat(this._get_all_nodes_between.apply(this, points[i]));
|
||||
}
|
||||
// Finally for each node we've determined is highlighted we need to check it belongs to the current editable area.
|
||||
for (i in nodes) {
|
||||
if (results.indexOf(nodes[i]) === -1 && nodes[i].ancestor('#' + elementid + "editable")) {
|
||||
results.push(nodes[i]);
|
||||
}
|
||||
// We have added all the nodes in this branch, look at the next child of the parent.
|
||||
var parent = node.ancestor();
|
||||
if (parent) {
|
||||
var currentindex = parent.get('childNodes').indexOf(node);
|
||||
leafnodes.concat(find_leaf_nodes(parent, endnode, boundingnode, currentindex + 1, false));
|
||||
}
|
||||
return leafnodes;
|
||||
};
|
||||
|
||||
// Kick off the recursive function.
|
||||
return find_leaf_nodes(startnode, endnode, editable, 0, true);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the first node that contains the current selection.
|
||||
* @return DOMNode or false
|
||||
* Returns true if the selection has changed since this method was last called.
|
||||
*
|
||||
* If the selection has changed we also fire the custom event atto:selectionchanged
|
||||
*
|
||||
* @method _has_selection_changed
|
||||
* @private
|
||||
* @param {EventFacade} e
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
get_selection_start_container : function() {
|
||||
var selection = M.editor_atto.get_selection();
|
||||
|
||||
if (selection.length && selection.pop) {
|
||||
selection = selection.pop();
|
||||
_has_selection_changed : function(e) {
|
||||
var sel,
|
||||
i,
|
||||
range,
|
||||
bits = [],
|
||||
selectedstr = '',
|
||||
selectioncount = 0;
|
||||
if (window.getSelection) {
|
||||
// Hoorah - working with the alternative is horrid..
|
||||
sel = window.getSelection();
|
||||
selectioncount = sel.rangeCount;
|
||||
for (i = 0; i < selectioncount; i++) {
|
||||
range = sel.getRangeAt(i);
|
||||
bits.push(range.startOffset);
|
||||
bits.push(range.endOffset);
|
||||
bits.push(this._get_node_identifier(range.startContainer));
|
||||
bits.push(this._get_node_identifier(range.endContainer));
|
||||
bits.push('&');
|
||||
}
|
||||
} else if (document.selection && document.selection.createRange) {
|
||||
range = document.selection.createRange();
|
||||
if (range !== null) {
|
||||
if (document.selection.type === "Control") {
|
||||
// Is a controlRange: http://msdn.microsoft.com/en-us/library/ie/hh826021(v=vs.85).aspx
|
||||
selectioncount = range.length;
|
||||
for (i = 0; i < selectioncount; i++) {
|
||||
bits.push(this._get_node_identifier(range(i)));
|
||||
}
|
||||
} else {
|
||||
// Is a TextRange: http://msdn.microsoft.com/en-us/library/ie/ms535872(v=vs.85).aspx
|
||||
selectioncount = 1;
|
||||
bits.push(this._get_node_identifier(range.parentElement()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selection.startContainer) {
|
||||
return Y.one(selection.startContainer);
|
||||
} else if (selection.parentElement) {
|
||||
var range = selection.duplicate();
|
||||
|
||||
range.collapse(true);
|
||||
return Y.one(range.parentElement());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the last node that contains the current selection.
|
||||
* @return DOMNode or false
|
||||
*/
|
||||
get_selection_end_container : function() {
|
||||
var selection = M.editor_atto.get_selection();
|
||||
|
||||
if (selection.length && selection.pop) {
|
||||
selection = selection.pop();
|
||||
}
|
||||
|
||||
if (selection.endContainer) {
|
||||
return Y.one(selection.endContainer);
|
||||
} else if (selection.parentElement) {
|
||||
var range = selection.duplicate();
|
||||
|
||||
range.collapse(false);
|
||||
return Y.one(range.parentElement());
|
||||
selectedstr = bits.join('|');
|
||||
if (this.lastselection !== selectedstr) {
|
||||
this.lastselection = selectedstr;
|
||||
this.lastselectioncount = selectioncount;
|
||||
return this._fire_selectionchanged(e);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fires the atto:selectionchanged event.
|
||||
*
|
||||
* When the selectionchanged event gets fired three arguments are given:
|
||||
* - event : the original event that lead to this event being fired.
|
||||
* - elementid : the editor elementid for which this event is being fired.
|
||||
* - selectednodes : an array containing nodes that are entirely selected of contain partially selected content.
|
||||
*
|
||||
* @method _fire_selectionchanged
|
||||
* @private
|
||||
* @param {EventFacade} e
|
||||
*/
|
||||
_fire_selectionchanged : function(e) {
|
||||
var editableid = e.currentTarget.get('id'),
|
||||
elementid = editableid.substring(0, editableid.length - 8),
|
||||
selectednodes = this.get_selected_nodes(elementid);
|
||||
this.fire('atto:selectionchanged', {
|
||||
event : e,
|
||||
elementid : elementid,
|
||||
selectedNodes : selectednodes
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the best identifier for the given node that can be found. May not be unique.
|
||||
*
|
||||
* @method _get_node_identifier
|
||||
* @private
|
||||
* @param {Node} node
|
||||
* @returns {string}
|
||||
*/
|
||||
_get_node_identifier : function(node) {
|
||||
var uid = (node.nodeType === 3) ? node.length : node.id;
|
||||
return uid || node.className || node.nodeName;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fixes the selection range, normalising it, ordering the start and end by dom position, and correcting for noteType and offset
|
||||
*
|
||||
* Note about offsets:
|
||||
* If the container is a Node of type Text, Comment, or CDATASection, then the offset is the number of characters from the
|
||||
* start of the container to the boundary point of the Range.
|
||||
* For other Node types, the offset is the number of child nodes between the start of the container and the boundary
|
||||
* point of the Range.
|
||||
*
|
||||
* @method _fix_selected_nodes_from_range
|
||||
* @private
|
||||
* @param {Node} a
|
||||
* @param {integer} aoffset
|
||||
* @param {Node} b
|
||||
* @param {integer} boffset
|
||||
* @returns {[Node, integer, Node, integer]}
|
||||
*/
|
||||
_fix_selected_nodes_from_range : function(a, aoffset, b, boffset) {
|
||||
var first,
|
||||
firstoffset,
|
||||
second,
|
||||
secondoffset,
|
||||
searchfunc,
|
||||
acheckend = true,
|
||||
bcheckend = true,
|
||||
checkend = true;
|
||||
|
||||
if (a.get('nodeType') === 1) {
|
||||
a = a.get('childNodes').item(aoffset);
|
||||
acheckend = false;
|
||||
}
|
||||
if (b.get('nodeType') === 1 && boffset > 0) {
|
||||
b = b.get('childNodes').item(boffset - 1);
|
||||
bcheckend = false;
|
||||
}
|
||||
if (a._yuid === b._yuid) {
|
||||
if (aoffset < boffset) {
|
||||
first = a;
|
||||
firstoffset = aoffset;
|
||||
second = b;
|
||||
secondoffset = boffset;
|
||||
checkend = bcheckend;
|
||||
} else {
|
||||
first = b;
|
||||
firstoffset = boffset;
|
||||
second = a;
|
||||
secondoffset = aoffset;
|
||||
checkend = acheckend;
|
||||
}
|
||||
} else {
|
||||
if (a.contains(b)) {
|
||||
first = a;
|
||||
firstoffset = aoffset;
|
||||
second = b;
|
||||
secondoffset = boffset;
|
||||
checkend = bcheckend;
|
||||
} else if (b.contains(a)) {
|
||||
first = b;
|
||||
firstoffset = boffset;
|
||||
second = a;
|
||||
secondoffset = aoffset;
|
||||
checkend = acheckend;
|
||||
} else {
|
||||
searchfunc = function find_first(nodes, x, y) {
|
||||
var i, result;
|
||||
for (i in nodes) {
|
||||
if (nodes[i] === x.getDOMNode()) {
|
||||
return x;
|
||||
} else if (nodes[i] === y.getDOMNode()) {
|
||||
return y;
|
||||
} else if (nodes[i].nodeType === 1) {
|
||||
result = find_first(nodes[i].childNodes, x, y);
|
||||
if (result !== false) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
first = searchfunc(this._get_common_parent_from_nodes(a, b).get('childNodes'), a, b) || a;
|
||||
if (first === a) {
|
||||
firstoffset = aoffset;
|
||||
second = b;
|
||||
secondoffset = boffset;
|
||||
checkend = bcheckend;
|
||||
} else {
|
||||
firstoffset = boffset;
|
||||
second = a;
|
||||
secondoffset = aoffset;
|
||||
checkend = acheckend;
|
||||
}
|
||||
}
|
||||
if (checkend && second.get('nodeType') === 3 && secondoffset === 0 && second.previous('*', true)) {
|
||||
second = second.previous();
|
||||
}
|
||||
}
|
||||
return [first, firstoffset, second, secondoffset];
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an array containing all of the nodes in the Dom between the two reference nodes.
|
||||
*
|
||||
* @method _get_all_nodes_between
|
||||
* @private
|
||||
* @param {Node} a
|
||||
* @param {Node} b
|
||||
* @param {Node} parent The common parent of a and b
|
||||
* @returns {Node[]}
|
||||
*/
|
||||
_get_all_nodes_between : function(a, b, parent) {
|
||||
var startfound = false,
|
||||
endfound = false,
|
||||
nodes = [],
|
||||
parenthasselectedtext = false;
|
||||
if (a === b || a.contains(b)) {
|
||||
return [a];
|
||||
}
|
||||
if (b.contains(a)) {
|
||||
return [b];
|
||||
}
|
||||
if (!parent) {
|
||||
return [a, b];
|
||||
}
|
||||
|
||||
nodes.push(a);
|
||||
parent.get('childNodes').each(function(node) {
|
||||
if (endfound) {
|
||||
return;
|
||||
}
|
||||
if (!startfound && node === a) {
|
||||
startfound = true;
|
||||
} else if (startfound && !endfound) {
|
||||
if (node === b) {
|
||||
endfound = true;
|
||||
} else {
|
||||
if (node.get('nodeType') === 3) {
|
||||
parenthasselectedtext = true;
|
||||
} else {
|
||||
nodes.push(node);
|
||||
nodes = nodes.compact(node.all('*'));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
nodes.push(b);
|
||||
|
||||
if (parenthasselectedtext) {
|
||||
nodes.unshift(parent);
|
||||
}
|
||||
|
||||
return nodes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the common parent of the two given nodes.
|
||||
*
|
||||
* @method _get_common_parent_from_nodes
|
||||
* @private
|
||||
* @param {Node} a
|
||||
* @param {Node} b
|
||||
* @returns {Node}
|
||||
*/
|
||||
_get_common_parent_from_nodes : function(a, b) {
|
||||
var aparents = a.ancestors('*', false, '.' + CSS.CONTENT),
|
||||
bparents = b.ancestors('*', false, '.' + CSS.CONTENT),
|
||||
node;
|
||||
while ((node = aparents.pop())) {
|
||||
if (bparents.indexOf(node) !== -1) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return a.ancestor('.' + CSS.CONTENT);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the dom node representing the common anscestor of the selection nodes.
|
||||
* @return DOMNode or false
|
||||
@ -1180,17 +1388,6 @@ M.editor_atto = M.editor_atto || {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the list of child nodes of the selection.
|
||||
* @return DOMNode[]
|
||||
*/
|
||||
get_selection_text : function() {
|
||||
var selection = M.editor_atto.get_selection();
|
||||
if (selection.length > 0 && selection[0].cloneContents) {
|
||||
return selection[0].cloneContents();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the current selection. Used to restore a selection.
|
||||
*/
|
||||
@ -1433,6 +1630,9 @@ M.editor_atto = M.editor_atto || {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The editor_atto is publishing custom events that can be subscribed to.
|
||||
Y.augment(M.editor_atto, Y.EventTarget);
|
||||
var CONTROLMENU_NAME = "Controlmenu",
|
||||
CONTROLMENU;
|
||||
|
||||
|
622
lib/editor/atto/yui/src/editor/js/editor.js
vendored
622
lib/editor/atto/yui/src/editor/js/editor.js
vendored
@ -124,6 +124,38 @@ M.editor_atto = M.editor_atto || {
|
||||
|
||||
focusfromclick : false,
|
||||
|
||||
/**
|
||||
* Set to true after events have been published for M.editor_atto.
|
||||
* @param eventspublished
|
||||
* @type boolean
|
||||
*/
|
||||
eventspublished : false,
|
||||
|
||||
/**
|
||||
* A unique identifier for the last selection recorded.
|
||||
* @param lastselection
|
||||
* @type string
|
||||
*/
|
||||
lastselection : null,
|
||||
|
||||
/**
|
||||
* The number of selection ranges when the selection was last recorded.
|
||||
* @param lastselectioncount
|
||||
* @type integer
|
||||
*/
|
||||
lastselectioncount : 0,
|
||||
|
||||
/**
|
||||
* Set to true after the first editor has been initialised.
|
||||
*
|
||||
* "The first transport is away. The first transport is away."
|
||||
* ―Lieutenant Romas Navander, during the Battle of Hoth
|
||||
*
|
||||
* @param firstinit
|
||||
* @type boolean
|
||||
*/
|
||||
firstinit : true,
|
||||
|
||||
/**
|
||||
* Toggle a menu.
|
||||
* @param event e
|
||||
@ -595,6 +627,9 @@ M.editor_atto = M.editor_atto || {
|
||||
* @param object params for this editor instance.
|
||||
*/
|
||||
init : function(params) {
|
||||
// Some things we only want to do on the first init.
|
||||
var firstinit = this.firstinit;
|
||||
this.firstinit = false;
|
||||
var wrapper = Y.Node.create('<div class="' + CSS.WRAPPER + '" />');
|
||||
var atto = Y.Node.create('<div id="' + params.elementid + 'editable" ' +
|
||||
'contenteditable="true" ' +
|
||||
@ -642,6 +677,14 @@ M.editor_atto = M.editor_atto || {
|
||||
|
||||
this.publish_events();
|
||||
atto.on('atto:selectionchanged', this.save_selection, this, params.elementid);
|
||||
if (firstinit) {
|
||||
// Aha this is the first initialisation.
|
||||
// Publish our custom events.
|
||||
this.publish_events();
|
||||
// Start tracking selections.
|
||||
this.on('atto:selectionchanged', this.save_selection, this);
|
||||
}
|
||||
|
||||
atto.on('focus', this.restore_selection, this, params.elementid);
|
||||
// Do not restore selection when focus is from a click event.
|
||||
atto.on('mousedown', function() { this.focusfromclick = true; }, this);
|
||||
@ -702,78 +745,11 @@ M.editor_atto = M.editor_atto || {
|
||||
* @param string elementid - the id of the textarea we created this editor from.
|
||||
*/
|
||||
publish_events : function() {
|
||||
Y.Event.define("atto:selectionchanged", {
|
||||
/**
|
||||
* Catch the keydown/mouseup events, and fire a synthetic event for the change event.
|
||||
* @param {Y.Event} e - The real event that triggers this synthetic one.
|
||||
* @param {Object} params - Object containing the subscription object and the notifier.
|
||||
*/
|
||||
changeHandler: function (e, params) {
|
||||
// Add 3 properties to the event.
|
||||
// 1. add the elementid.
|
||||
var elementid = params.sub.node.getAttribute('id');
|
||||
// Strip 'editable' from the end of the id.
|
||||
elementid = elementid.substring(0, elementid.length - 8);
|
||||
e.elementid = elementid;
|
||||
|
||||
// 2. The list of leaf nodes contained in the selection.
|
||||
e.selectedNodes = M.editor_atto.get_selected_nodes(elementid);
|
||||
// 3. The current selection (Range)
|
||||
e.selection = M.editor_atto.get_selection();
|
||||
params.notifier.fire(e);
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribe to the atto:selectionchanged event.
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
* @param {Y.Event.Notifier} notifier - YUI notifier object.
|
||||
*/
|
||||
on: function (node, sub, notifier) {
|
||||
var params = { notifier: notifier, sub: sub };
|
||||
sub.attoKeyDownDetach = node.on('keydown', Y.throttle(this.changeHandler, 100), this, params);
|
||||
sub.attoMouseUpDetach = node.on('mouseup', Y.throttle(this.changeHandler, 100), this, params);
|
||||
sub.attoFocusDetach = node.on('focus', Y.throttle(this.changeHandler, 100), this, params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Detach the atto:selectionchanged event.
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
*/
|
||||
detach: function (node, sub) {
|
||||
sub.attoKeyDownDetach.detach();
|
||||
sub.attoMouseUpDetach.detach();
|
||||
sub.attoFocusDetach.detach();
|
||||
},
|
||||
|
||||
/**
|
||||
* Delegate the atto:selectionchanged event.
|
||||
*
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
* @param {Y.Event.Notifier} notifier - YUI notifier object.
|
||||
* @param {String} filter - CSS selector for the filter.
|
||||
*/
|
||||
delegate: function(node, sub, notifier, filter) {
|
||||
var params = { notifier: notifier, sub: sub };
|
||||
sub.attoKeyDownDetachDelegate = node.delegate('keydown', Y.throttle(this.changeHandler, 100), filter, this, params);
|
||||
sub.attoMouseUpDetachDelegate = node.delegate('mouseup', Y.throttle(this.changeHandler, 100), filter, this, params);
|
||||
sub.attoFocusDetachDelegate = node.delegate('focus', Y.throttle(this.changeHandler, 100), filter, this, params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Detach a delegated atto:selectionchanged event.
|
||||
*
|
||||
* @param {Y.Node} node - The node for the subscription - must be the contenteditable node.
|
||||
* @param {Y.Subscription} sub - YUI Subscription object.
|
||||
*/
|
||||
detachDelegate: function(node, sub) {
|
||||
sub.attoKeyDownDetachDelegate.detach();
|
||||
sub.attoMouseUpDetachDelegate.detach();
|
||||
sub.attoFocusDetachDelegate.detach();
|
||||
}
|
||||
});
|
||||
if (!this.eventspublished) {
|
||||
Y.publish('atto:selectionchanged', {prefix: 'atto'});
|
||||
Y.delegate(['mouseup', 'keyup', 'focus'], this._has_selection_changed, document.body, '.' + CSS.CONTENT, this);
|
||||
this.eventspublished = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -932,8 +908,9 @@ M.editor_atto = M.editor_atto || {
|
||||
* @param Y.Event event
|
||||
* @param string elementid
|
||||
*/
|
||||
save_selection : function(event, elementid) {
|
||||
if (this.is_active(elementid)) {
|
||||
save_selection : function(event) {
|
||||
var elementid = event.elementid;
|
||||
if (elementid && this.is_active(elementid)) {
|
||||
var sel = this.get_selection();
|
||||
if (sel.length > 0) {
|
||||
this.selections[elementid] = sel;
|
||||
@ -957,7 +934,7 @@ M.editor_atto = M.editor_atto || {
|
||||
|
||||
/**
|
||||
* Get the selection object that can be passed back to set_selection.
|
||||
* @return range (browser dependent)
|
||||
* @return Range[]|TextRange[] All browsers except IE8 and less will get Range[], IE8 and less will get TextRange[]
|
||||
*/
|
||||
get_selection : function() {
|
||||
if (window.getSelection) {
|
||||
@ -970,7 +947,7 @@ M.editor_atto = M.editor_atto || {
|
||||
} else if (document.selection) {
|
||||
// IE < 9
|
||||
if (document.selection.createRange) {
|
||||
return document.selection.createRange();
|
||||
return [document.selection.createRange()];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -982,10 +959,10 @@ M.editor_atto = M.editor_atto || {
|
||||
* @return boolean
|
||||
*/
|
||||
selection_contains_node : function(node) {
|
||||
// TODO: This will break in IE11 . IE11 doesn't provide containsNode and document.selection has been removed.
|
||||
var range, sel;
|
||||
if (window.getSelection) {
|
||||
sel = window.getSelection();
|
||||
|
||||
if (sel.containsNode) {
|
||||
return sel.containsNode(node.getDOMNode(), true);
|
||||
}
|
||||
@ -1006,159 +983,390 @@ M.editor_atto = M.editor_atto || {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
selection_filter_matches : function(elementid, selector, selectednodes) {
|
||||
var result = true;
|
||||
|
||||
if (!selectednodes) {
|
||||
// Find this because it was not passed as a param.
|
||||
selectednodes = M.editor_atto.get_selected_nodes(elementid);
|
||||
}
|
||||
selector = '.' + CSS.CONTENT + ' ' + selector;
|
||||
|
||||
if (selectednodes.size() === 0) {
|
||||
return false;
|
||||
}
|
||||
selectednodes.each(function(node) {
|
||||
if (!node.ancestor(selector, true)) {
|
||||
result = false;
|
||||
var allmatch = selectednodes.size() > 0;
|
||||
selectednodes.each(function(node){
|
||||
// Check each node, if it doesn't match the tags AND is not within the specified tags then fail this thing.
|
||||
if (!allmatch || !node.ancestor(selector, true, '#' + elementid + "editable")) {
|
||||
allmatch = false;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of nodes that are ancestors of the selection nodes,
|
||||
* and match the specified css selector (and are contained within the editable div).
|
||||
*
|
||||
* @param {String} elementid
|
||||
* @param {String} selector
|
||||
* @return Y.NodeList
|
||||
*/
|
||||
selection_filter : function(elementid, selector) {
|
||||
var selectednodes = M.editor_atto.get_selected_nodes(elementid);
|
||||
var result = new Y.NodeList();
|
||||
selector = '.' + CSS.CONTENT + ' ' + selector;
|
||||
selectednodes.each(function(node) {
|
||||
node.ancestors(selector, true).each(function(match) {
|
||||
if (result.indexOf(match) === -1) {
|
||||
result.push(match);
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}, this);
|
||||
return allmatch;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the deepest possible list of nodes in the current selection.
|
||||
*
|
||||
* Presently this method contains logic to work out what nodes are "selected" by the user.
|
||||
* The user selection may take the form of:
|
||||
* - text : selected text within a single node, such as double clicking a word.
|
||||
* - A node : the user has clicked an image or media object that is selectable by clicking upon an element.
|
||||
* - text + nodes: the user has dragged or used shift + arrows to select a body of content that may include
|
||||
* both text and/or nodes.
|
||||
* To work out the selected nodes we look at the start and end node. If they are the same then there is only one node
|
||||
* containing selected content.
|
||||
* If they don't match we get the common ancestor of the start and end nodes, and then iterate all child nodes recording
|
||||
* all nodes that appear after the start node and before the end node.
|
||||
*
|
||||
* YUI deal with this in a totally different way to us, they have a method getSelected that gets the selected nodes by
|
||||
* exec'ing a command to add fontname, and then find all nodes that have been affected. They remove the fontname property
|
||||
* before returning the nodes.
|
||||
*
|
||||
* @param {String} elementid
|
||||
* @return Y.NodeList
|
||||
*/
|
||||
get_selected_nodes : function(elementid) {
|
||||
var nodes = [],
|
||||
results = new Y.NodeList(),
|
||||
points = [],
|
||||
range,
|
||||
i,
|
||||
sel,
|
||||
start,
|
||||
end,
|
||||
parent,
|
||||
values;
|
||||
|
||||
var editable = M.editor_atto.get_editable_node(elementid);
|
||||
var startnode = M.editor_atto.get_selection_start_container();
|
||||
var endnode = M.editor_atto.get_selection_end_container();
|
||||
|
||||
if (!startnode || !endnode) {
|
||||
return new Y.NodeList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function to walk the dom tree between 2 nodes and build
|
||||
* a list of leaf nodes.
|
||||
* @param {Y.Node} node - The current node.
|
||||
* @param {Y.Node} endnode - The node to stop at.
|
||||
* @param {Y.Node} boundingnode - A node that will contain the selection - do not result results outside this node.
|
||||
* @param {Integer} skipchildren - When returning from a child to a parent, this is how many children to skip.
|
||||
* @param {Boolean} isstart - Only true for the starting condition. Used in the case when startnode == endnode.
|
||||
* @return {Y.NodeList} The list of leaf nodes found.
|
||||
*/
|
||||
var find_leaf_nodes = function(node, endnode, boundingnode, skipchildren, isstart) {
|
||||
var leafnodes = new Y.NodeList();
|
||||
if (endnode === node) {
|
||||
// If there are child nodes and we didn't come from a child node, then skip this end condition.
|
||||
if (!(endnode === node && startnode === node && isstart && node.hasChildNodes())) {
|
||||
// If the end node is a leaf - return it.
|
||||
if (!node.hasChildNodes()) {
|
||||
leafnodes.push(node);
|
||||
// The window.getSelection method is available from IE9 onwards and for all other browsers.
|
||||
if (window.getSelection) {
|
||||
sel = window.getSelection();
|
||||
for (i = 0; i < sel.rangeCount; i++) {
|
||||
range = sel.getRangeAt(i);
|
||||
start = new Y.Node(range.startContainer);
|
||||
end = new Y.Node(range.endContainer);
|
||||
if (start.compareTo(end)) {
|
||||
// The start and end node are the same node. Only one node is selected. Easy!
|
||||
nodes.push(end);
|
||||
} else {
|
||||
// The start and end node are not the same, we have 2..n nodes selected, we'll need to work them out.
|
||||
// For the time being record them as a pair of points along with the common ancestor.
|
||||
// First up we need to check the order of the nodes, we need them in the order they appear in the DOM
|
||||
// however the selection may have been rtl or ltr and in the case of rtl we need to switch the order.
|
||||
values = this._fix_selected_nodes_from_range(
|
||||
start,
|
||||
range.startOffset,
|
||||
end,
|
||||
range.endOffset
|
||||
);
|
||||
// Ok start and end are in order. Yay!
|
||||
start = values[0];
|
||||
end = values[2];
|
||||
// We calculate the common ancestor here as we should be able to get it from the range object.
|
||||
if (range.commonAncestorContainer) {
|
||||
parent = range.commonAncestorContainer;
|
||||
} else if (range.parentElement) {
|
||||
parent = range.parentElement();
|
||||
} else {
|
||||
parent = this._get_common_parent_from_nodes(start, end);
|
||||
}
|
||||
// We reached the end of the selection.
|
||||
return leafnodes;
|
||||
points.push([
|
||||
start,
|
||||
end,
|
||||
parent
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Node is not a leaf, look at it's children.
|
||||
if (node.hasChildNodes()) {
|
||||
var children = node.get('childNodes');
|
||||
// Skipchildren is used when returning from a child node, to make the parent look at the next child node.
|
||||
var child = children.item(skipchildren);
|
||||
if (child) {
|
||||
// There was a child node, find all the leaf nodes in it.
|
||||
leafnodes.concat(find_leaf_nodes(child, endnode, boundingnode, 0, false));
|
||||
} else if (document.selection && document.selection.createRange) {
|
||||
// The document.selection.createRange is available in IE only, versions 5.5 through 10 (not IE11).
|
||||
// However s window.getSelection exists in IE9+ this code will only execute in IE 5.5 -> IE 8.
|
||||
// http://msdn.microsoft.com/en-us/library/ie/ms535869(v=vs.85).aspx
|
||||
range = document.selection.createRange();
|
||||
// Create range will return either TextRange object from the current text selection, or a controlRange
|
||||
// collection from the control selection.
|
||||
if (document.selection.type === "Control") {
|
||||
// Its a control collection, well this is easy, its an array of Nodes. All the work has been done for us.
|
||||
for (i = 0; i < range.length; i++) {
|
||||
nodes.push(new Y.Node(range(i)));
|
||||
}
|
||||
} else {
|
||||
// This node has no children - so save it.
|
||||
leafnodes.push(node);
|
||||
// Its a TextRange, in which case its a text element (nodeType 3) and its parent will be the node containing the
|
||||
// selection.
|
||||
nodes.push(new Y.Node(range.parentElement()));
|
||||
}
|
||||
// If we hit the bounding node - this is an end condition.
|
||||
if (node === boundingnode) {
|
||||
return leafnodes;
|
||||
}
|
||||
// Ok we have two array as this point.
|
||||
// - Nodes is an array containing nodes that are currently highlighted by selection.
|
||||
// - Points is an array containing start, end, and parent nodes.
|
||||
// For each item in the points array we need to find all nodes containing selected content.
|
||||
for (i in points) {
|
||||
nodes = nodes.concat(this._get_all_nodes_between.apply(this, points[i]));
|
||||
}
|
||||
// Finally for each node we've determined is highlighted we need to check it belongs to the current editable area.
|
||||
for (i in nodes) {
|
||||
if (results.indexOf(nodes[i]) === -1 && nodes[i].ancestor('#' + elementid + "editable")) {
|
||||
results.push(nodes[i]);
|
||||
}
|
||||
// We have added all the nodes in this branch, look at the next child of the parent.
|
||||
var parent = node.ancestor();
|
||||
if (parent) {
|
||||
var currentindex = parent.get('childNodes').indexOf(node);
|
||||
leafnodes.concat(find_leaf_nodes(parent, endnode, boundingnode, currentindex + 1, false));
|
||||
}
|
||||
return leafnodes;
|
||||
};
|
||||
|
||||
// Kick off the recursive function.
|
||||
return find_leaf_nodes(startnode, endnode, editable, 0, true);
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the first node that contains the current selection.
|
||||
* @return DOMNode or false
|
||||
* Returns true if the selection has changed since this method was last called.
|
||||
*
|
||||
* If the selection has changed we also fire the custom event atto:selectionchanged
|
||||
*
|
||||
* @method _has_selection_changed
|
||||
* @private
|
||||
* @param {EventFacade} e
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
get_selection_start_container : function() {
|
||||
var selection = M.editor_atto.get_selection();
|
||||
|
||||
if (selection.length && selection.pop) {
|
||||
selection = selection.pop();
|
||||
_has_selection_changed : function(e) {
|
||||
var sel,
|
||||
i,
|
||||
range,
|
||||
bits = [],
|
||||
selectedstr = '',
|
||||
selectioncount = 0;
|
||||
if (window.getSelection) {
|
||||
// Hoorah - working with the alternative is horrid..
|
||||
sel = window.getSelection();
|
||||
selectioncount = sel.rangeCount;
|
||||
for (i = 0; i < selectioncount; i++) {
|
||||
range = sel.getRangeAt(i);
|
||||
bits.push(range.startOffset);
|
||||
bits.push(range.endOffset);
|
||||
bits.push(this._get_node_identifier(range.startContainer));
|
||||
bits.push(this._get_node_identifier(range.endContainer));
|
||||
bits.push('&');
|
||||
}
|
||||
} else if (document.selection && document.selection.createRange) {
|
||||
range = document.selection.createRange();
|
||||
if (range !== null) {
|
||||
if (document.selection.type === "Control") {
|
||||
// Is a controlRange: http://msdn.microsoft.com/en-us/library/ie/hh826021(v=vs.85).aspx
|
||||
selectioncount = range.length;
|
||||
for (i = 0; i < selectioncount; i++) {
|
||||
bits.push(this._get_node_identifier(range(i)));
|
||||
}
|
||||
} else {
|
||||
// Is a TextRange: http://msdn.microsoft.com/en-us/library/ie/ms535872(v=vs.85).aspx
|
||||
selectioncount = 1;
|
||||
bits.push(this._get_node_identifier(range.parentElement()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selection.startContainer) {
|
||||
return Y.one(selection.startContainer);
|
||||
} else if (selection.parentElement) {
|
||||
var range = selection.duplicate();
|
||||
|
||||
range.collapse(true);
|
||||
return Y.one(range.parentElement());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the last node that contains the current selection.
|
||||
* @return DOMNode or false
|
||||
*/
|
||||
get_selection_end_container : function() {
|
||||
var selection = M.editor_atto.get_selection();
|
||||
|
||||
if (selection.length && selection.pop) {
|
||||
selection = selection.pop();
|
||||
}
|
||||
|
||||
if (selection.endContainer) {
|
||||
return Y.one(selection.endContainer);
|
||||
} else if (selection.parentElement) {
|
||||
var range = selection.duplicate();
|
||||
|
||||
range.collapse(false);
|
||||
return Y.one(range.parentElement());
|
||||
selectedstr = bits.join('|');
|
||||
if (this.lastselection !== selectedstr) {
|
||||
this.lastselection = selectedstr;
|
||||
this.lastselectioncount = selectioncount;
|
||||
return this._fire_selectionchanged(e);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fires the atto:selectionchanged event.
|
||||
*
|
||||
* When the selectionchanged event gets fired three arguments are given:
|
||||
* - event : the original event that lead to this event being fired.
|
||||
* - elementid : the editor elementid for which this event is being fired.
|
||||
* - selectednodes : an array containing nodes that are entirely selected of contain partially selected content.
|
||||
*
|
||||
* @method _fire_selectionchanged
|
||||
* @private
|
||||
* @param {EventFacade} e
|
||||
*/
|
||||
_fire_selectionchanged : function(e) {
|
||||
var editableid = e.currentTarget.get('id'),
|
||||
elementid = editableid.substring(0, editableid.length - 8),
|
||||
selectednodes = this.get_selected_nodes(elementid);
|
||||
this.fire('atto:selectionchanged', {
|
||||
event : e,
|
||||
elementid : elementid,
|
||||
selectedNodes : selectednodes
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the best identifier for the given node that can be found. May not be unique.
|
||||
*
|
||||
* @method _get_node_identifier
|
||||
* @private
|
||||
* @param {Node} node
|
||||
* @returns {string}
|
||||
*/
|
||||
_get_node_identifier : function(node) {
|
||||
var uid = (node.nodeType === 3) ? node.length : node.id;
|
||||
return uid || node.className || node.nodeName;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fixes the selection range, normalising it, ordering the start and end by dom position, and correcting for noteType and offset
|
||||
*
|
||||
* Note about offsets:
|
||||
* If the container is a Node of type Text, Comment, or CDATASection, then the offset is the number of characters from the
|
||||
* start of the container to the boundary point of the Range.
|
||||
* For other Node types, the offset is the number of child nodes between the start of the container and the boundary
|
||||
* point of the Range.
|
||||
*
|
||||
* @method _fix_selected_nodes_from_range
|
||||
* @private
|
||||
* @param {Node} a
|
||||
* @param {integer} aoffset
|
||||
* @param {Node} b
|
||||
* @param {integer} boffset
|
||||
* @returns {[Node, integer, Node, integer]}
|
||||
*/
|
||||
_fix_selected_nodes_from_range : function(a, aoffset, b, boffset) {
|
||||
var first,
|
||||
firstoffset,
|
||||
second,
|
||||
secondoffset,
|
||||
searchfunc,
|
||||
acheckend = true,
|
||||
bcheckend = true,
|
||||
checkend = true;
|
||||
|
||||
if (a.get('nodeType') === 1) {
|
||||
a = a.get('childNodes').item(aoffset);
|
||||
acheckend = false;
|
||||
}
|
||||
if (b.get('nodeType') === 1 && boffset > 0) {
|
||||
b = b.get('childNodes').item(boffset - 1);
|
||||
bcheckend = false;
|
||||
}
|
||||
if (a._yuid === b._yuid) {
|
||||
if (aoffset < boffset) {
|
||||
first = a;
|
||||
firstoffset = aoffset;
|
||||
second = b;
|
||||
secondoffset = boffset;
|
||||
checkend = bcheckend;
|
||||
} else {
|
||||
first = b;
|
||||
firstoffset = boffset;
|
||||
second = a;
|
||||
secondoffset = aoffset;
|
||||
checkend = acheckend;
|
||||
}
|
||||
} else {
|
||||
if (a.contains(b)) {
|
||||
first = a;
|
||||
firstoffset = aoffset;
|
||||
second = b;
|
||||
secondoffset = boffset;
|
||||
checkend = bcheckend;
|
||||
} else if (b.contains(a)) {
|
||||
first = b;
|
||||
firstoffset = boffset;
|
||||
second = a;
|
||||
secondoffset = aoffset;
|
||||
checkend = acheckend;
|
||||
} else {
|
||||
searchfunc = function find_first(nodes, x, y) {
|
||||
var i, result;
|
||||
for (i in nodes) {
|
||||
if (nodes[i] === x.getDOMNode()) {
|
||||
return x;
|
||||
} else if (nodes[i] === y.getDOMNode()) {
|
||||
return y;
|
||||
} else if (nodes[i].nodeType === 1) {
|
||||
result = find_first(nodes[i].childNodes, x, y);
|
||||
if (result !== false) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
first = searchfunc(this._get_common_parent_from_nodes(a, b).get('childNodes'), a, b) || a;
|
||||
if (first === a) {
|
||||
firstoffset = aoffset;
|
||||
second = b;
|
||||
secondoffset = boffset;
|
||||
checkend = bcheckend;
|
||||
} else {
|
||||
firstoffset = boffset;
|
||||
second = a;
|
||||
secondoffset = aoffset;
|
||||
checkend = acheckend;
|
||||
}
|
||||
}
|
||||
if (checkend && second.get('nodeType') === 3 && secondoffset === 0 && second.previous('*', true)) {
|
||||
second = second.previous();
|
||||
}
|
||||
}
|
||||
return [first, firstoffset, second, secondoffset];
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an array containing all of the nodes in the Dom between the two reference nodes.
|
||||
*
|
||||
* @method _get_all_nodes_between
|
||||
* @private
|
||||
* @param {Node} a
|
||||
* @param {Node} b
|
||||
* @param {Node} parent The common parent of a and b
|
||||
* @returns {Node[]}
|
||||
*/
|
||||
_get_all_nodes_between : function(a, b, parent) {
|
||||
var startfound = false,
|
||||
endfound = false,
|
||||
nodes = [],
|
||||
parenthasselectedtext = false;
|
||||
if (a === b || a.contains(b)) {
|
||||
return [a];
|
||||
}
|
||||
if (b.contains(a)) {
|
||||
return [b];
|
||||
}
|
||||
if (!parent) {
|
||||
return [a, b];
|
||||
}
|
||||
|
||||
nodes.push(a);
|
||||
parent.get('childNodes').each(function(node) {
|
||||
if (endfound) {
|
||||
return;
|
||||
}
|
||||
if (!startfound && node === a) {
|
||||
startfound = true;
|
||||
} else if (startfound && !endfound) {
|
||||
if (node === b) {
|
||||
endfound = true;
|
||||
} else {
|
||||
if (node.get('nodeType') === 3) {
|
||||
parenthasselectedtext = true;
|
||||
} else {
|
||||
nodes.push(node);
|
||||
nodes = nodes.compact(node.all('*'));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
nodes.push(b);
|
||||
|
||||
if (parenthasselectedtext) {
|
||||
nodes.unshift(parent);
|
||||
}
|
||||
|
||||
return nodes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the common parent of the two given nodes.
|
||||
*
|
||||
* @method _get_common_parent_from_nodes
|
||||
* @private
|
||||
* @param {Node} a
|
||||
* @param {Node} b
|
||||
* @returns {Node}
|
||||
*/
|
||||
_get_common_parent_from_nodes : function(a, b) {
|
||||
var aparents = a.ancestors('*', false, '.' + CSS.CONTENT),
|
||||
bparents = b.ancestors('*', false, '.' + CSS.CONTENT),
|
||||
node;
|
||||
while ((node = aparents.pop())) {
|
||||
if (bparents.indexOf(node) !== -1) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return a.ancestor('.' + CSS.CONTENT);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the dom node representing the common anscestor of the selection nodes.
|
||||
* @return DOMNode or false
|
||||
@ -1178,17 +1386,6 @@ M.editor_atto = M.editor_atto || {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the list of child nodes of the selection.
|
||||
* @return DOMNode[]
|
||||
*/
|
||||
get_selection_text : function() {
|
||||
var selection = M.editor_atto.get_selection();
|
||||
if (selection.length > 0 && selection[0].cloneContents) {
|
||||
return selection[0].cloneContents();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the current selection. Used to restore a selection.
|
||||
*/
|
||||
@ -1431,3 +1628,6 @@ M.editor_atto = M.editor_atto || {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The editor_atto is publishing custom events that can be subscribed to.
|
||||
Y.augment(M.editor_atto, Y.EventTarget);
|
||||
|
Loading…
x
Reference in New Issue
Block a user