(function($) { tinymce.create('tinymce.plugins.textorumInsertion', { init : function(ed, url) { var self = this, disabled = true, editor = ed, //cm = new tinymce.ControlManager(ed), //dropmenu = cm.createDropMenu('textorum-insertion-dropmenu'), // Not used in current editor dropmenuVisible = false, dropmenuJustClicked = false, ignoredElements = [ 'media', 'xref', 'disp-quote', 'inline-graphic', 'inline-formula', 'disp-formula', 'table-wrap', 'bold', 'italic', 'monospace', 'underline', 'sup', 'sub' ]; // Addresses Chrome 33 issue where fonts didnt displace unless repainted. jQuery.each(jQuery('.mceIcon'), function(index, el) { var $el = jQuery(el); var opacity = $el.css('opacity'); $el.animate({opacity: opacity}, 1); }) // Key Bindings // ed.onKeyDown.addToTop(function(ed, e) { var target, listItemParent, siblingNode, range, bogusNode, dispParent, pParent; var content, rangeClone, inTitle, parentNode, node = ed.selection.getNode(); // Backspace // Disable backspace when the cursor is at the first character in a title such that it doesn't // remove the title element when its pressed. // Titles are required in every section if (8 == e.keyCode) { // Check if in a title node range = ed.selection.getRng(1); parentNode = ed.dom.getParent(node, 'div.title'); if (parentNode || (3 != range.commonAncestorContainer.nodeType && 'title' == range.endContainer.getAttribute('class'))) { bogusNode = ed.dom.create( 'span', {'_mce_bogus': '1'}, '' ); range.insertNode(bogusNode); rangeClone = range.cloneRange(); rangeClone.setStartBefore(node); rangeClone.setEndBefore(bogusNode); ed.selection.setRng(rangeClone); var content = ed.selection.getContent({format: 'text'}); if ( '' == content ) { tinymce.dom.Event.cancel(e); ed.dom.remove(bogusNode); ed.dom.select(range.endContainer); ed.selection.collapse(0); return false; } else { ed.dom.remove(bogusNode); ed.selection.setRng(range); } } return true; } // Delete // Check if this is the last paragraph and last character in a section // If so, delete does nothing // Titles are required in every section if (46 == e.keyCode) { var parents = ed.dom.getParents(node, '.p'); if (!!parents && parents.length > 0) { var topP = parents[parents.length - 1]; if (!topP.nextSibling || topP.nextSibling.getAttribute('class') == 'sec') { bogusNode = ed.dom.create( 'span', {'_mce_bogus': '1'}, '' ); range = ed.selection.getRng(1); range.insertNode(bogusNode); rangeClone = range.cloneRange(); rangeClone.setEndBefore(bogusNode); ed.selection.setRng(rangeClone); var content = ed.selection.getContent({format: 'text'}); if ('' == content) { tinymce.dom.Event.cancel(e); ed.dom.remove(bogusNode); return false; } else { ed.dom.remove(bogusNode); ed.selection.setRng(range); } } } return true; } // Enter if (!e.shiftKey && e.keyCode == 13) { listItemParent = ed.dom.getParent(node, '.list-item'); // What to do if empty editor if (ed.getContent() == '') { // Abstract gets a

tag if (ed.id == 'excerpt') { tinymce.dom.Event.cancel(e); return ed.setContent('

 
'); } // Presumably article content. else { tinymce.dom.Event.cancel(e); return ed.setContent('

 
'); } } // Ctrl + Enter if (e.ctrlKey) { tinymce.dom.Event.cancel(e); return ed.plugins.annoFormats.insertSection(); } // List items if (listItemParent) { tinymce.dom.Event.cancel(e); // insert bookmark/bogus range = ed.selection.getRng(); // get range bogusNode = ed.dom.create( 'span', {'_mce_bogus': '1'}, '' ); range.insertNode(bogusNode); self.split(ed, listItemParent, bogusNode); siblingNode = bogusNode.nextSibling; ed.dom.remove(bogusNode); // Move to the appropriate place (beginning of the split node) ed.selection.select(siblingNode, true); ed.selection.collapse(true); return false; } // Get the parent tag and class parentTag = ed.dom.getParent(node.parentNode); elementClass = jQuery(node).attr('class'); parentClass = jQuery(parentTag).attr('class'); // If it's a 'p' tag, we need its parent if (parentClass == 'p') { parentTag = ed.dom.getParent(parentTag.parentNode); parentClass = jQuery(ed.dom.getParent(parentTag)).attr('class'); } // Only allow sections to be extended if ( parentClass == 'caption' || parentClass == 'fig' || parentClass == 'tr' ) { console.log(parentClass + ' cannot be extended'); tinymce.dom.Event.cancel(e); } // If it's a title, then add a P tag instead of splitting else if ( elementClass == 'title' ) { tinymce.dom.Event.cancel(e); return !self.insertElement('p', 'after', node); } // Hitting enter in display-quote while not in a p tag should insert a bogus br after it dispParent = ed.dom.getParent(node, '.disp-quote'); if (dispParent) { pParent = ed.dom.getParent(node, '.p', dispParent); if (!pParent) { bogusNode = ed.dom.create( 'br', {'_mce_bogus': '1'}, '' ); // Jump to the bogus element ed.dom.insertAfter(bogusNode, dispParent); ed.selection.select(bogusNode); ed.selection.collapse(0); tinymce.dom.Event.cancel(e); return false; } } } return true; }); editor.addCommand('Textorum_Insertion_Menu', function(ui, where) { var options, $button; $button = $('#'+editor.editorId).next('.mceEditor').find('.mceButton.mce_textorum-insertion-' + where); if (dropmenuVisible) { dropmenu.hideMenu(); dropmenuVisible = false; } else { where = where || 'inside'; options = self.getValidElements(editor.selection.getNode(), where); dropmenu.removeAll(); if (options.length) { options = options.filter(function(a) { return ignoredElements.indexOf(a) === -1; }); tinymce.each(options, function(element_name) { switch (element_name) { case 'list': dropmenu.add({ title: 'list (bulleted)', onclick: function() { self.insertElement(element_name, where, null, { 'list-type': 'bullet' }); }}); dropmenu.add({ title: 'list (ordered)', onclick: function() { self.insertElement(element_name, where, null, { 'list-type': 'order' }); }}); break; default: dropmenu.add(new tinymce.ui.MenuItem('textorum-insertion-item-' + element_name ,{ title: element_name, onclick: function() { if (element_name == 'list') { self.insertElement(element_name, where, null, { 'list-type': 'bullet' }); } else { self.insertElement(element_name, where); } } })); break; } }); } else { dropmenu.add(new tinymce.ui.MenuItem('textorum-insertion-item-none', { title: ed.getLang('annotextorum.noElement'), style: 'color:#999', onclick: function() { dropmenuVisible = false; return false; } })); } dropmenu.showMenu($button.offset().left, $button.offset().top + 24); dropmenuJustClicked = dropmenuVisible = true; setTimeout(function() { dropmenuJustClicked = false; }, 250); } }); editor.onNodeChange.add(function(ed, cm, node) { var options, inlineNames, buttonElementMap, firstBlock; if (node.nodeName == 'BR') { node = node.parentNode; } options = editor.plugins.textorum.validator.validElementsForNode(node, "inside", "array"); inlineNames = ['SPAN', 'TT', 'EM', 'U', 'STRONG', 'SUP', 'SUB']; buttonElementMap = { list : [ 'annoorderedlist', 'annobulletlist' ], xref : [ 'annoreferences' ], 'disp-quote' : [ 'annoquote' ], monospace : [ 'annomonospace' ], preformat : [ 'annopreformat' ], 'table-wrap' : [ 'table' ], sup : [ 'superscript' ], sub : [ 'subscript' ] }; firstBlock = node; while (inlineNames.indexOf(firstBlock.nodeName) !== -1) { firstBlock = firstBlock.parentNode; } jQuery.each(buttonElementMap, function(elementName, buttons) { var tf = false; if (options.indexOf(elementName) === -1) { tf = true; } jQuery.each(buttons, function(index, buttonName) { buttonDisable(buttonName, tf); }); }); if (dropmenuVisible && !dropmenuJustClicked) { dropmenu.hideMenu(); dropmenuVisible = false; } // enable indent when parent list if (ed.dom.getParent(node, '.list')) { buttonDisable('annooutdentlist', false); buttonDisable('annoindentlist', false); buttonDisable('annoorderedlist', false); buttonDisable('annobulletlist', false); } else { buttonDisable('annooutdentlist', true); buttonDisable('annoindentlist', true); buttonDisable('annoorderedlist', true); buttonDisable('annobulletlist', true); } if (firstBlock.getAttribute('data-xmlel') == 'p') { buttonDisable('annoorderedlist', false); buttonDisable('annobulletlist', false); } else { buttonDisable('annoorderedlist', true); buttonDisable('annobulletlist', true); } if (options.indexOf('inline-graphic') === -1 || options.indexOf('fig') === -1) { buttonDisable('annoimages', true); buttonDisable('annoequations', true); } else { buttonDisable('annoimages', false); buttonDisable('annoequations', false); } if (options.indexOf('disp-quote') === -1) { buttonDisable('annoquote', true); } else { buttonDisable('annoquote', false); } function buttonDisable(buttonName, tf) { if (tf) { cm.setActive(buttonName, false); } return cm.setDisabled(buttonName, tf); } }); // Register example button editor.addButton('textorum-insertion-before', { title : ed.getLang('annotextorum.elementBefore'), cmd : 'Textorum_Insertion_Menu', value: 'before' }); editor.addButton('textorum-insertion-inside', { title : ed.getLang('annotextorum.elementInside'), cmd : 'Textorum_Insertion_Menu', value: 'inside' }); editor.addButton('textorum-insertion-after', { title : ed.getLang('annotextorum.elementafter'), cmd : 'Textorum_Insertion_Menu', value: 'after' }); editor.addShortcut('ctrl+enter', ed.getLang('annotextorum.insertElement'), 'Textorum_Insertion'); }, insertElement: function(element_name, where, target, attrs) { var editor = tinyMCE.activeEditor, newNode = editor.dom.create( editor.plugins.textorum.translateElement(element_name), tinymce.extend({'class': element_name, 'data-xmlel': element_name}, attrs), ' ' ), range, elYPos, options; where = where || 'inside'; target = target || editor.selection.getNode(); options = this.getValidElements(target, where); if (options.indexOf(element_name) === -1) { return false; } // Add additionally required child elements switch (element_name) { case 'sec': newNode.innerHTML = ''; newNode.appendChild( editor.dom.create( editor.plugins.textorum.translateElement('title'), {'class': 'title', 'data-xmlel': 'title'}, ' ' ) ); break; case 'fig': (function() { var cap = editor.dom.create( editor.plugins.textorum.translateElement('caption'), {'class': 'caption', 'data-xmlel': 'caption'} ); newNode.innerHTML = ''; newNode.appendChild( editor.dom.create( editor.plugins.textorum.translateElement('label'), {'class': 'label', 'data-xmlel': 'label'}, ' ' ) ); cap.appendChild( editor.dom.create( editor.plugins.textorum.translateElement('p'), {'class': 'p', 'data-xmlel': 'p'}, ' ' ) ); newNode.appendChild(cap); })(); break; case 'list': newNode.innerHTML = ""; this.insertElement('list-item', 'inside', newNode); break; case 'list-item': newNode.innerHTML = ''; newNode.appendChild( editor.dom.create( editor.plugins.textorum.translateElement('p'), {'class': 'p', 'data-xmlel': 'p'}, ' ' ) ); break; } dropmenuVisible = false; switch(where) { case 'before': newNode = target.parentNode.insertBefore(newNode, target); break; case 'inside': newNode = target.appendChild(newNode); break; case 'after': if (target.nextSibling) { newNode = target.parentNode.insertBefore(newNode, target.nextSibling); } else { newNode = target.parentNode.appendChild(newNode); } break; } if (document.createRange) { // all browsers, except IE before version 9 range = document.createRange(); if (newNode.firstChild) { range.selectNodeContents(newNode.firstChild); } else { range.selectNodeContents(newNode); } } range.collapse(1); editor.selection.setRng(range); elYPos = editor.dom.getPos(newNode).y; // Scroll to new section if (elYPos > editor.dom.getViewPort(editor.getWin()).h) { editor.getWin().scrollTo(0, elYPos); } editor.nodeChanged(); return newNode; }, getValidElements: function(target, where) { var options, ed = tinyMCE.activeEditor; target = target || ed.selection.getNode(); where = where || 'inside'; options = ed.plugins.textorum.validator.validElementsForNode(target, where, "array"); // Textorum doesn't like putting things at the top level body, so account for top level section availability if (!options.length && target.className.toUpperCase() == 'SEC' && where !== 'inside') { options.push('sec'); } return options; }, getInfo : function() { return { longname : 'Textorum Context Aware Element Insertion', author : 'Crowd Favorite', authorurl : 'http://crowdfavorite.com', infourl : '', version : "1.0" }; }, // Based on tinymce.editor.dom.split // It does not strip empty tags like the tinyMCE version // It also inserts an space into empty nodes. split: function(ed, parentElm, splitElm, replacementElm) { var self = ed.dom, r = self.createRng(), bef, aft, pa; if (parentElm && splitElm) { // Get before chunk r.setStart(parentElm.parentNode, self.nodeIndex(parentElm)); r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm)); bef = r.extractContents(); if ('' == bef.textContent) { if (bef.firstElementChild.firstElementChild) { bef.firstElementChild.firstElementChild.textContent = '\xA0'; } } // Get after chunk r = self.createRng(); r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1); r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1); aft = r.extractContents(); if ('' == aft.textContent) { if (aft.firstElementChild.firstElementChild) { aft.firstElementChild.firstElementChild.textContent = '\xA0'; } } // Insert before chunk pa = parentElm.parentNode; pa.insertBefore(bef, parentElm); // Insert middle chunk if (replacementElm) { pa.replaceChild(replacementElm, splitElm); } else { pa.insertBefore(splitElm, parentElm); } // Insert after chunk pa.insertBefore(aft, parentElm); self.remove(parentElm); return replacementElm || splitElm; } }, }); // Register plugin tinymce.PluginManager.add('textorumInsertion', tinymce.plugins.textorumInsertion); })(jQuery);