!function(a,b){"function"==typeof define&&define.amd? // AMD. Register as an anonymous module unless amdModuleId is set define("textAngular",["rangy","rangy/lib/rangy-selectionsaverestore"],function(c,d){return a["textAngular.name"]=b(c,d)}):"object"==typeof exports? // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports=b(require("rangy"),require("rangy/lib/rangy-selectionsaverestore")):a.textAngular=b(rangy)}(this,function(a){ // tests against the current jqLite/jquery implementation if this can be an element function b(a){try{return 0!==angular.element(a).length}catch(a){return!1}}/* A tool definition is an object with the following key/value parameters: action: [function(deferred, restoreSelection)] a function that is executed on clicking on the button - this will allways be executed using ng-click and will overwrite any ng-click value in the display attribute. The function is passed a deferred object ($q.defer()), if this is wanted to be used `return false;` from the action and manually call `deferred.resolve();` elsewhere to notify the editor that the action has finished. restoreSelection is only defined if the rangy library is included and it can be called as `restoreSelection()` to restore the users selection in the WYSIWYG editor. display: [string]? Optional, an HTML element to be displayed as the button. The `scope` of the button is the tool definition object with some additional functions If set this will cause buttontext and iconclass to be ignored class: [string]? Optional, if set will override the taOptions.classes.toolbarButton class. buttontext: [string]? if this is defined it will replace the contents of the element contained in the `display` element iconclass: [string]? if this is defined an icon () will be appended to the `display` element with this string as it's class tooltiptext: [string]? Optional, a plain text description of the action, used for the title attribute of the action button in the toolbar by default. activestate: [function(commonElement)]? this function is called on every caret movement, if it returns true then the class taOptions.classes.toolbarButtonActive will be applied to the `display` element, else the class will be removed disabled: [function()]? if this function returns true then the tool will have the class taOptions.classes.disabled applied to it, else it will be removed Other functions available on the scope are: name: [string] the name of the tool, this is the first parameter passed into taRegisterTool isDisabled: [function()] returns true if the tool is disabled, false if it isn't displayActiveToolClass: [function(boolean)] returns true if the tool is 'active' in the currently focussed toolbar onElementSelect: [Object] This object contains the following key/value pairs and is used to trigger the ta-element-select event element: [String] an element name, will only trigger the onElementSelect action if the tagName of the element matches this string filter: [function(element)]? an optional filter that returns a boolean, if true it will trigger the onElementSelect. action: [function(event, element, editorScope)] the action that should be executed if the onElementSelect function runs */ // name and toolDefinition to add into the tools available to be added on the toolbar function c(a,c){if(!a||""===a||e.hasOwnProperty(a))throw"textAngular Error: A unique name is required for a Tool Definition";if(c.display&&(""===c.display||!b(c.display))||!c.display&&!c.buttontext&&!c.iconclass)throw'textAngular Error: Tool Definition for "'+a+'" does not have a valid display/iconclass/buttontext value';e[a]=c} // usage is: // var t0 = performance.now(); // doSomething(); // var t1 = performance.now(); // console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to do something!'); // // turn html into pure text that shows visiblity function d(a){var b=document.createElement("DIV");b.innerHTML=a;var c=b.textContent||b.innerText||"";// zero width space return c.replace("​",""),c=c.trim()} // setup the global contstant functions for setting up the toolbar // all tool definitions var e={};angular.module("textAngularSetup",[]).constant("taRegisterTool",c).value("taTools",e).value("taOptions",{ ////////////////////////////////////////////////////////////////////////////////////// // forceTextAngularSanitize // set false to allow the textAngular-sanitize provider to be replaced // with angular-sanitize or a custom provider. forceTextAngularSanitize:!0, /////////////////////////////////////////////////////////////////////////////////////// // keyMappings // allow customizable keyMappings for specialized key boards or languages // // keyMappings provides key mappings that are attached to a given commandKeyCode. // To modify a specific keyboard binding, simply provide function which returns true // for the event you wish to map to. // Or to disable a specific keyboard binding, provide a function which returns false. // Note: 'RedoKey' and 'UndoKey' are internally bound to the redo and undo functionality. // At present, the following commandKeyCodes are in use: // 98, 'TabKey', 'ShiftTabKey', 105, 117, 'UndoKey', 'RedoKey' // // To map to an new commandKeyCode, add a new key mapping such as: // {commandKeyCode: 'CustomKey', testForKey: function (event) { // if (event.keyCode=57 && event.ctrlKey && !event.shiftKey && !event.altKey) return true; // } } // to the keyMappings. This example maps ctrl+9 to 'CustomKey' // Then where taRegisterTool(...) is called, add a commandKeyCode: 'CustomKey' and your // tool will be bound to ctrl+9. // // To disble one of the already bound commandKeyCodes such as 'RedoKey' or 'UndoKey' add: // {commandKeyCode: 'RedoKey', testForKey: function (event) { return false; } }, // {commandKeyCode: 'UndoKey', testForKey: function (event) { return false; } }, // to disable them. // keyMappings:[],toolbar:[["h1","h2","h3","h4","h5","h6","p","pre","quote"],["bold","italics","underline","strikeThrough","ul","ol","redo","undo","clear"],["justifyLeft","justifyCenter","justifyRight","justifyFull","indent","outdent"],["html","insertImage","insertLink","insertVideo","wordcount","charcount"]],classes:{focussed:"focussed",toolbar:"btn-toolbar",toolbarGroup:"btn-group",toolbarButton:"btn btn-default",toolbarButtonActive:"active",disabled:"disabled",textEditor:"form-control",htmlEditor:"form-control"},defaultTagAttributes:{a:{target:""}},setup:{ // wysiwyg mode textEditorSetup:function(a){}, // raw html htmlEditorSetup:function(a){}},defaultFileDropHandler:/* istanbul ignore next: untestable image processing */ function(a,b){var c=new FileReader;return"image"===a.type.substring(0,5)&&(c.onload=function(){""!==c.result&&b("insertImage",c.result,!0)},c.readAsDataURL(a),!0)}}).value("taSelectableElements",["a","img"]).value("taCustomRenderers",[{ // Parse back out: '
' // To correct video element. For now only support youtube selector:"img",customAttribute:"ta-insert-video",renderLogic:function(a){var b=angular.element(""),c=a.prop("attributes"); // loop through element attributes and apply them on iframe angular.forEach(c,function(a){b.attr(a.name,a.value)}),b.attr("src",b.attr("ta-insert-video")),a.replaceWith(b)}}]).value("taTranslations",{ // moved to sub-elements //toggleHTML: "Toggle HTML", //insertImage: "Please enter a image URL to insert", //insertLink: "Please enter a URL to insert", //insertVideo: "Please enter a youtube URL to embed", html:{tooltip:"Toggle html / Rich Text"}, // tooltip for heading - might be worth splitting heading:{tooltip:"Heading "},p:{tooltip:"Paragraph"},pre:{tooltip:"Preformatted text"},ul:{tooltip:"Unordered List"},ol:{tooltip:"Ordered List"},quote:{tooltip:"Quote/unquote selection or paragraph"},undo:{tooltip:"Undo"},redo:{tooltip:"Redo"},bold:{tooltip:"Bold"},italic:{tooltip:"Italic"},underline:{tooltip:"Underline"},strikeThrough:{tooltip:"Strikethrough"},justifyLeft:{tooltip:"Align text left"},justifyRight:{tooltip:"Align text right"},justifyFull:{tooltip:"Justify text"},justifyCenter:{tooltip:"Center"},indent:{tooltip:"Increase indent"},outdent:{tooltip:"Decrease indent"},clear:{tooltip:"Clear formatting"},insertImage:{dialogPrompt:"Please enter an image URL to insert",tooltip:"Insert image",hotkey:"the - possibly language dependent hotkey ... for some future implementation"},insertVideo:{tooltip:"Insert video",dialogPrompt:"Please enter a youtube URL to embed"},insertLink:{tooltip:"Insert / edit link",dialogPrompt:"Please enter a URL to insert"},editLink:{reLinkButton:{tooltip:"Relink"},unLinkButton:{tooltip:"Unlink"},targetToggle:{buttontext:"Open in New Window"}},wordcount:{tooltip:"Display words Count"},charcount:{tooltip:"Display characters Count"}}).factory("taToolFunctions",["$window","taTranslations",function(a,b){return{imgOnSelectAction:function(a,b,c){ // setup the editor toolbar // Credit to the work at http://hackerwins.github.io/summernote/ for this editbar logic/display var d=function(){c.updateTaBindtaTextElement(),c.hidePopover()};a.preventDefault(),c.displayElements.popover.css("width","375px");var e=c.displayElements.popoverContainer;e.empty();var f=angular.element('
'),g=angular.element('');g.on("click",function(a){a.preventDefault(),b.css({width:"100%",height:""}),d()});var h=angular.element('');h.on("click",function(a){a.preventDefault(),b.css({width:"50%",height:""}),d()});var i=angular.element('');i.on("click",function(a){a.preventDefault(),b.css({width:"25%",height:""}),d()});var j=angular.element('');j.on("click",function(a){a.preventDefault(),b.css({width:"",height:""}),d()}),f.append(g),f.append(h),f.append(i),f.append(j),e.append(f),f=angular.element('
');var k=angular.element('');k.on("click",function(a){a.preventDefault(), // webkit b.css("float","left"), // firefox b.css("cssFloat","left"), // IE < 8 b.css("styleFloat","left"),d()});var l=angular.element('');l.on("click",function(a){a.preventDefault(), // webkit b.css("float","right"), // firefox b.css("cssFloat","right"), // IE < 8 b.css("styleFloat","right"),d()});var m=angular.element('');m.on("click",function(a){a.preventDefault(), // webkit b.css("float",""), // firefox b.css("cssFloat",""), // IE < 8 b.css("styleFloat",""),d()}),f.append(k),f.append(m),f.append(l),e.append(f),f=angular.element('
');var n=angular.element('');n.on("click",function(a){a.preventDefault(),b.remove(),d()}),f.append(n),e.append(f),c.showPopover(b),c.showResizeOverlay(b)},aOnSelectAction:function(c,d,e){ // setup the editor toolbar // Credit to the work at http://hackerwins.github.io/summernote/ for this editbar logic c.preventDefault(),e.displayElements.popover.css("width","436px");var f=e.displayElements.popoverContainer;f.empty(),f.css("line-height","28px");var g=angular.element(''+d.attr("href")+"");g.css({display:"inline-block","max-width":"200px",overflow:"hidden","text-overflow":"ellipsis","white-space":"nowrap","vertical-align":"middle"}),f.append(g);var h=angular.element('
'),i=angular.element('');i.on("click",function(c){c.preventDefault();var f=a.prompt(b.insertLink.dialogPrompt,d.attr("href"));f&&""!==f&&"http://"!==f&&(d.attr("href",f),e.updateTaBindtaTextElement()),e.hidePopover()}),h.append(i);var j=angular.element(''); // directly before this click event is fired a digest is fired off whereby the reference to $element is orphaned off j.on("click",function(a){a.preventDefault(),d.replaceWith(d.contents()),e.updateTaBindtaTextElement(),e.hidePopover()}),h.append(j);var k=angular.element('");"_blank"===d.attr("target")&&k.addClass("active"),k.on("click",function(a){a.preventDefault(),d.attr("target","_blank"===d.attr("target")?"":"_blank"),k.toggleClass("active"),e.updateTaBindtaTextElement()}),h.append(k),f.append(h),e.showPopover(d)},extractYoutubeVideoId:function(a){var b=/(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/i,c=a.match(b);return c&&c[1]||null}}}]).run(["taRegisterTool","$window","taTranslations","taSelection","taToolFunctions","$sanitize","taOptions","$log",function(a,b,c,d,e,f,g,h){ // test for the version of $sanitize that is in use // You can disable this check by setting taOptions.textAngularSanitize == false var i={};/* istanbul ignore next, throws error */ if(f("",i),g.forceTextAngularSanitize===!0&&"taSanitize"!==i.version)throw angular.$$minErr("textAngular")("textAngularSetup","The textAngular-sanitize provider has been replaced by another -- have you included angular-sanitize by mistake?");a("html",{iconclass:"fa fa-code",tooltiptext:c.html.tooltip,action:function(){this.$editor().switchView()},activeState:function(){return this.$editor().showHtml}}); // add the Header tools // convenience functions so that the loop works correctly var j=function(a){return function(){return this.$editor().queryFormatBlockState(a)}},k=function(){return this.$editor().wrapSelection("formatBlock","<"+this.name.toUpperCase()+">")};angular.forEach(["h1","h2","h3","h4","h5","h6"],function(b){a(b.toLowerCase(),{buttontext:b.toUpperCase(),tooltiptext:c.heading.tooltip+b.charAt(1),action:k,activeState:j(b.toLowerCase())})}),a("p",{buttontext:"P",tooltiptext:c.p.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","

")},activeState:function(){return this.$editor().queryFormatBlockState("p")}}), // key: pre -> taTranslations[key].tooltip, taTranslations[key].buttontext a("pre",{buttontext:"pre",tooltiptext:c.pre.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","

")},activeState:function(){return this.$editor().queryFormatBlockState("pre")}}),a("ul",{iconclass:"fa fa-list-ul",tooltiptext:c.ul.tooltip,action:function(){return this.$editor().wrapSelection("insertUnorderedList",null)},activeState:function(){return this.$editor().queryCommandState("insertUnorderedList")}}),a("ol",{iconclass:"fa fa-list-ol",tooltiptext:c.ol.tooltip,action:function(){return this.$editor().wrapSelection("insertOrderedList",null)},activeState:function(){return this.$editor().queryCommandState("insertOrderedList")}}),a("quote",{iconclass:"fa fa-quote-right",tooltiptext:c.quote.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","
")},activeState:function(){return this.$editor().queryFormatBlockState("blockquote")}}),a("undo",{iconclass:"fa fa-undo",tooltiptext:c.undo.tooltip,action:function(){return this.$editor().wrapSelection("undo",null)}}),a("redo",{iconclass:"fa fa-repeat",tooltiptext:c.redo.tooltip,action:function(){return this.$editor().wrapSelection("redo",null)}}),a("bold",{iconclass:"fa fa-bold",tooltiptext:c.bold.tooltip,action:function(){return this.$editor().wrapSelection("bold",null)},activeState:function(){return this.$editor().queryCommandState("bold")},commandKeyCode:98}),a("justifyLeft",{iconclass:"fa fa-align-left",tooltiptext:c.justifyLeft.tooltip,action:function(){return this.$editor().wrapSelection("justifyLeft",null)},activeState:function(a){/* istanbul ignore next: */ if(a&&"#document"===a.nodeName)return!1;var b=!1;if(a) // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions // so we do try catch here... try{b="left"===a.css("text-align")||"left"===a.attr("align")||"right"!==a.css("text-align")&&"center"!==a.css("text-align")&&"justify"!==a.css("text-align")&&!this.$editor().queryCommandState("justifyRight")&&!this.$editor().queryCommandState("justifyCenter")&&!this.$editor().queryCommandState("justifyFull")}catch(a){/* istanbul ignore next: error handler */ //console.log(e); b=!1}return b=b||this.$editor().queryCommandState("justifyLeft")}}),a("justifyRight",{iconclass:"fa fa-align-right",tooltiptext:c.justifyRight.tooltip,action:function(){return this.$editor().wrapSelection("justifyRight",null)},activeState:function(a){/* istanbul ignore next: */ if(a&&"#document"===a.nodeName)return!1;var b=!1;if(a) // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions // so we do try catch here... try{b="right"===a.css("text-align")}catch(a){/* istanbul ignore next: error handler */ //console.log(e); b=!1}return b=b||this.$editor().queryCommandState("justifyRight")}}),a("justifyFull",{iconclass:"fa fa-align-justify",tooltiptext:c.justifyFull.tooltip,action:function(){return this.$editor().wrapSelection("justifyFull",null)},activeState:function(a){var b=!1;if(a) // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions // so we do try catch here... try{b="justify"===a.css("text-align")}catch(a){/* istanbul ignore next: error handler */ //console.log(e); b=!1}return b=b||this.$editor().queryCommandState("justifyFull")}}),a("justifyCenter",{iconclass:"fa fa-align-center",tooltiptext:c.justifyCenter.tooltip,action:function(){return this.$editor().wrapSelection("justifyCenter",null)},activeState:function(a){/* istanbul ignore next: */ if(a&&"#document"===a.nodeName)return!1;var b=!1;if(a) // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions // so we do try catch here... try{b="center"===a.css("text-align")}catch(a){/* istanbul ignore next: error handler */ //console.log(e); b=!1}return b=b||this.$editor().queryCommandState("justifyCenter")}}),a("indent",{iconclass:"fa fa-indent",tooltiptext:c.indent.tooltip,action:function(){return this.$editor().wrapSelection("indent",null)},activeState:function(){return this.$editor().queryFormatBlockState("blockquote")},commandKeyCode:"TabKey"}),a("outdent",{iconclass:"fa fa-outdent",tooltiptext:c.outdent.tooltip,action:function(){return this.$editor().wrapSelection("outdent",null)},activeState:function(){return!1},commandKeyCode:"ShiftTabKey"}),a("italics",{iconclass:"fa fa-italic",tooltiptext:c.italic.tooltip,action:function(){return this.$editor().wrapSelection("italic",null)},activeState:function(){return this.$editor().queryCommandState("italic")},commandKeyCode:105}),a("underline",{iconclass:"fa fa-underline",tooltiptext:c.underline.tooltip,action:function(){return this.$editor().wrapSelection("underline",null)},activeState:function(){return this.$editor().queryCommandState("underline")},commandKeyCode:117}),a("strikeThrough",{iconclass:"fa fa-strikethrough",tooltiptext:c.strikeThrough.tooltip,action:function(){return this.$editor().wrapSelection("strikeThrough",null)},activeState:function(){return document.queryCommandState("strikeThrough")}}),a("clear",{iconclass:"fa fa-ban",tooltiptext:c.clear.tooltip,action:function(a,b){var c;this.$editor().wrapSelection("removeFormat",null);var e=angular.element(d.getSelectionElement());c=d.getAllSelectedElements(); //$log.log('selectedElements:', selectedElements); // remove lists var f=function(a,b){a=angular.element(a);var c=b;return b||(c=a),angular.forEach(a.children(),function(a){if("ul"===a.tagName.toLowerCase()||"ol"===a.tagName.toLowerCase())c=f(a,c);else{var b=angular.element("

");b.html(angular.element(a).html()),c.after(b),c=b}}),a.remove(),c};angular.forEach(c,function(a){"ul"!==a.nodeName.toLowerCase()&&"ol"!==a.nodeName.toLowerCase()|| //console.log('removeListElements', element); f(a)}),angular.forEach(e.find("ul"),f),angular.forEach(e.find("ol"),f); // clear out all class attributes. These do not seem to be cleared via removeFormat var g=this.$editor(),h=function(a){a=angular.element(a),/* istanbul ignore next: this is not triggered in tests any longer since we now never select the whole displayELement */ a[0]!==g.displayElements.text[0]&&a.removeAttr("class"),angular.forEach(a.children(),h)};angular.forEach(e,h), // check if in list. If not in list then use formatBlock option e[0]&&"li"!==e[0].tagName.toLowerCase()&&"ol"!==e[0].tagName.toLowerCase()&&"ul"!==e[0].tagName.toLowerCase()&&"true"!==e[0].getAttribute("contenteditable")&&this.$editor().wrapSelection("formatBlock","default"),b()}});/* jshint -W099 */ /**************************** // we don't use this code - since the previous way CLEAR is expected to work does not clear partially selected
  • var removeListElement = function(listE){ console.log(listE); var _list = listE.parentNode.childNodes; console.log('_list', _list); var _preLis = [], _postLis = [], _found = false; for (i = 0; i < _list.length; i++) { if (_list[i] === listE) { _found = true; } else if (!_found) _preLis.push(_list[i]); else _postLis.push(_list[i]); } var _parent = angular.element(listE.parentNode); var newElem = angular.element('

    '); newElem.html(angular.element(listE).html()); if (_preLis.length === 0 || _postLis.length === 0) { if (_postLis.length === 0) _parent.after(newElem); else _parent[0].parentNode.insertBefore(newElem[0], _parent[0]); if (_preLis.length === 0 && _postLis.length === 0) _parent.remove(); else angular.element(listE).remove(); } else { var _firstList = angular.element('<' + _parent[0].tagName + '>'); var _secondList = angular.element('<' + _parent[0].tagName + '>'); for (i = 0; i < _preLis.length; i++) _firstList.append(angular.element(_preLis[i])); for (i = 0; i < _postLis.length; i++) _secondList.append(angular.element(_postLis[i])); _parent.after(_secondList); _parent.after(newElem); _parent.after(_firstList); _parent.remove(); } taSelection.setSelectionToElementEnd(newElem[0]); }; elementsSeen = []; if (selectedElements.length !==0) console.log(selectedElements); angular.forEach(selectedElements, function (element) { if (elementsSeen.indexOf(element) !== -1 || elementsSeen.indexOf(element.parentElement) !== -1) { return; } elementsSeen.push(element); if (element.nodeName.toLowerCase() === 'li') { console.log('removeListElement', element); removeListElement(element); } else if (element.parentElement && element.parentElement.nodeName.toLowerCase() === 'li') { console.log('removeListElement', element.parentElement); elementsSeen.push(element.parentElement); removeListElement(element.parentElement); } }); **********************/ /********************** if(possibleNodes[0].tagName.toLowerCase() === 'li'){ var _list = possibleNodes[0].parentNode.childNodes; var _preLis = [], _postLis = [], _found = false; for(i = 0; i < _list.length; i++){ if(_list[i] === possibleNodes[0]){ _found = true; }else if(!_found) _preLis.push(_list[i]); else _postLis.push(_list[i]); } var _parent = angular.element(possibleNodes[0].parentNode); var newElem = angular.element('

    '); newElem.html(angular.element(possibleNodes[0]).html()); if(_preLis.length === 0 || _postLis.length === 0){ if(_postLis.length === 0) _parent.after(newElem); else _parent[0].parentNode.insertBefore(newElem[0], _parent[0]); if(_preLis.length === 0 && _postLis.length === 0) _parent.remove(); else angular.element(possibleNodes[0]).remove(); }else{ var _firstList = angular.element('<'+_parent[0].tagName+'>'); var _secondList = angular.element('<'+_parent[0].tagName+'>'); for(i = 0; i < _preLis.length; i++) _firstList.append(angular.element(_preLis[i])); for(i = 0; i < _postLis.length; i++) _secondList.append(angular.element(_postLis[i])); _parent.after(_secondList); _parent.after(newElem); _parent.after(_firstList); _parent.remove(); } taSelection.setSelectionToElementEnd(newElem[0]); } *******************/ /* istanbul ignore next: if it's javascript don't worry - though probably should show some kind of error message */ var l=function(a){return a.toLowerCase().indexOf("javascript")!==-1};a("insertImage",{iconclass:"fa fa-picture-o",tooltiptext:c.insertImage.tooltip,action:function(){var a;if(a=b.prompt(c.insertImage.dialogPrompt,"http://"),a&&""!==a&&"http://"!==a&&!l(a)){d.getSelectionElement().tagName&&"a"===d.getSelectionElement().tagName.toLowerCase()&& // due to differences in implementation between FireFox and Chrome, we must move the // insertion point past the element, otherwise FireFox inserts inside the // With this change, both FireFox and Chrome behave the same way! d.setSelectionAfterElement(d.getSelectionElement()); // In the past we used the simple statement: //return this.$editor().wrapSelection('insertImage', imageLink, true); // // However on Firefox only, when the content is empty this is a problem // See Issue #1201 // Investigation reveals that Firefox only inserts a

    only!!!! // So now we use insertHTML here and all is fine. // NOTE: this is what 'insertImage' is supposed to do anyway! var e='';return this.$editor().wrapSelection("insertHTML",e,!0)}},onElementSelect:{element:"img",action:e.imgOnSelectAction}}),a("insertVideo",{iconclass:"fa fa-youtube-play",tooltiptext:c.insertVideo.tooltip,action:function(){var a; // block javascript here /* istanbul ignore else: if it's javascript don't worry - though probably should show some kind of error message */ if(a=b.prompt(c.insertVideo.dialogPrompt,"https://"),!l(a)&&a&&""!==a&&"https://"!==a&&(videoId=e.extractYoutubeVideoId(a),videoId)){ // create the embed link var f="https://www.youtube.com/embed/"+videoId,g=''; // insert /* istanbul ignore next: don't know how to test this... since it needs a dialogPrompt */ // due to differences in implementation between FireFox and Chrome, we must move the // insertion point past the element, otherwise FireFox inserts inside the // With this change, both FireFox and Chrome behave the same way! return d.getSelectionElement().tagName&&"a"===d.getSelectionElement().tagName.toLowerCase()&&d.setSelectionAfterElement(d.getSelectionElement()),this.$editor().wrapSelection("insertHTML",g,!0)}},onElementSelect:{element:"img",onlyWithAttrs:["ta-insert-video"],action:e.imgOnSelectAction}}),a("insertLink",{tooltiptext:c.insertLink.tooltip,iconclass:"fa fa-link",action:function(){var a;if( // if this link has already been set, we need to just edit the existing link /* istanbul ignore if: we do not test this */ a=d.getSelectionElement().tagName&&"a"===d.getSelectionElement().tagName.toLowerCase()?b.prompt(c.insertLink.dialogPrompt,d.getSelectionElement().href):b.prompt(c.insertLink.dialogPrompt,"http://"),a&&""!==a&&"http://"!==a&&!l(a))return this.$editor().wrapSelection("createLink",a,!0)},activeState:function(a){return!!a&&"A"===a[0].tagName},onElementSelect:{element:"a",action:e.aOnSelectAction}}),a("wordcount",{display:'

    ',disabled:!0,wordcount:0,activeState:function(){// this fires on keyup var a=this.$editor().displayElements.text,b=a[0].innerHTML||"",c=0;/* istanbul ignore if: will default to '' when undefined */ //Set current scope //Set editor scope return""!==b.replace(/\s*<[^>]*?>\s*/g,"")&&""!==b.trim()&&(c=b.replace(/<\/?(b|i|em|strong|span|u|strikethrough|a|img|small|sub|sup|label)( [^>*?])?>/gi,"").replace(/(<[^>]*?>\s*<[^>]*?>)/gi," ").replace(/(<[^>]*?>)/gi,"").replace(/\s+/gi," ").match(/\S+/g).length),this.wordcount=c,this.$editor().wordcount=c,!1}}),a("charcount",{display:'
    Characters:
    ',disabled:!0,charcount:0,activeState:function(){// this fires on keyup var a=this.$editor().displayElements.text,b=a[0].innerText||a[0].textContent,c=b.replace(/(\r\n|\n|\r)/gm,"").replace(/^\s+/g," ").replace(/\s+$/g," ").length; //Set current scope //Set editor scope return this.charcount=c,this.$editor().charcount=c,!1}})}]);// NOTE: textAngularVersion must match the Gruntfile.js 'setVersion' task.... and have format v/d+./d+./d+ var f="v1.5.16",g={ie:function(){for(var a,b=3,c=document.createElement("div"),d=c.getElementsByTagName("i");c.innerHTML="",d[0];);return b>4?b:a}(),webkit:/AppleWebKit\/([\d.]+)/i.test(navigator.userAgent),isFirefox:navigator.userAgent.toLowerCase().indexOf("firefox")>-1},h=h||{};/* istanbul ignore next: untestable browser check */ h.now=function(){return h.now||h.mozNow||h.msNow||h.oNow||h.webkitNow||function(){return(new Date).getTime()}}(); // Global to textAngular REGEXP vars for block and list elements. var i=/^(address|article|aside|audio|blockquote|canvas|center|dd|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|noscript|ol|output|p|pre|section|table|tfoot|ul|video)$/i,j=/^(ul|li|ol)$/i,k=/^(#text|span|address|article|aside|audio|blockquote|canvas|center|dd|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|noscript|ol|output|p|pre|section|table|tfoot|ul|video|li)$/i; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Compatibility /* istanbul ignore next: trim shim for older browsers */ String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")});/* Custom stylesheet for the placeholders rules. Credit to: http://davidwalsh.name/add-rules-stylesheets */ var l,m,n,o,p,q;/* istanbul ignore else: IE <8 test*/ if(g.ie>8||void 0===g.ie){/* istanbul ignore next: preference for stylesheet loaded externally */ for(var r=document.styleSheets,s=0;s tag var a=document.createElement("style");/* istanbul ignore else : WebKit hack :( */ // Add the