123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835 |
- // tests against the current jqLite/jquery implementation if this can be an element
- function validElementString(string){
- try{
- return angular.element(string).length !== 0;
- }catch(any){
- return false;
- }
- }
- // setup the global contstant functions for setting up the toolbar
- // all tool definitions
- var taTools = {};
- /*
- 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 (<i>) 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 registerTextAngularTool(name, toolDefinition){
- if(!name || name === '' || taTools.hasOwnProperty(name)) throw('textAngular Error: A unique name is required for a Tool Definition');
- if(
- (toolDefinition.display && (toolDefinition.display === '' || !validElementString(toolDefinition.display))) ||
- (!toolDefinition.display && !toolDefinition.buttontext && !toolDefinition.iconclass)
- )
- throw('textAngular Error: Tool Definition for "' + name + '" does not have a valid display/iconclass/buttontext value');
- taTools[name] = toolDefinition;
- }
- angular.module('textAngularSetup', [])
- .constant('taRegisterTool', registerTextAngularTool)
- .value('taTools', taTools)
- // Here we set up the global display defaults, to set your own use a angular $provider#decorator.
- .value('taOptions', {
- //////////////////////////////////////////////////////////////////////////////////////
- // forceTextAngularSanitize
- // set false to allow the textAngular-sanitize provider to be replaced
- // with angular-sanitize or a custom provider.
- forceTextAngularSanitize: true,
- ///////////////////////////////////////////////////////////////////////////////////////
- // 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($element){ /* Do some processing here */ },
- // raw html
- htmlEditorSetup: function($element){ /* Do some processing here */ }
- },
- defaultFileDropHandler:
- /* istanbul ignore next: untestable image processing */
- function(file, insertAction){
- var reader = new FileReader();
- if(file.type.substring(0, 5) === 'image'){
- reader.onload = function() {
- if(reader.result !== '') insertAction('insertImage', reader.result, true);
- };
- reader.readAsDataURL(file);
- // NOTE: For async procedures return a promise and resolve it when the editor should update the model.
- return true;
- }
- return false;
- }
- })
- // This is the element selector string that is used to catch click events within a taBind, prevents the default and $emits a 'ta-element-select' event
- // these are individually used in an angular.element().find() call. What can go here depends on whether you have full jQuery loaded or just jQLite with angularjs.
- // div is only used as div.ta-insert-video caught in filter.
- .value('taSelectableElements', ['a','img'])
- // This is an array of objects with the following options:
- // selector: <string> a jqLite or jQuery selector string
- // customAttribute: <string> an attribute to search for
- // renderLogic: <function(element)>
- // Both or one of selector and customAttribute must be defined.
- .value('taCustomRenderers', [
- {
- // Parse back out: '<div class="ta-insert-video" ta-insert-video src="' + urlLink + '" allowfullscreen="true" width="300" frameborder="0" height="250"></div>'
- // To correct video element. For now only support youtube
- selector: 'img',
- customAttribute: 'ta-insert-video',
- renderLogic: function(element){
- var iframe = angular.element('<iframe></iframe>');
- var attributes = element.prop("attributes");
- // loop through element attributes and apply them on iframe
- angular.forEach(attributes, function(attr) {
- iframe.attr(attr.name, attr.value);
- });
- iframe.attr('src', iframe.attr('ta-insert-video'));
- element.replaceWith(iframe);
- }
- }
- ])
- .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($window, taTranslations) {
- return {
- imgOnSelectAction: function(event, $element, editorScope){
- // setup the editor toolbar
- // Credit to the work at http://hackerwins.github.io/summernote/ for this editbar logic/display
- var finishEdit = function(){
- editorScope.updateTaBindtaTextElement();
- editorScope.hidePopover();
- };
- event.preventDefault();
- editorScope.displayElements.popover.css('width', '375px');
- var container = editorScope.displayElements.popoverContainer;
- container.empty();
- var buttonGroup = angular.element('<div class="btn-group" style="padding-right: 6px;">');
- var fullButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">100% </button>');
- fullButton.on('click', function(event){
- event.preventDefault();
- $element.css({
- 'width': '100%',
- 'height': ''
- });
- finishEdit();
- });
- var halfButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">50% </button>');
- halfButton.on('click', function(event){
- event.preventDefault();
- $element.css({
- 'width': '50%',
- 'height': ''
- });
- finishEdit();
- });
- var quartButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">25% </button>');
- quartButton.on('click', function(event){
- event.preventDefault();
- $element.css({
- 'width': '25%',
- 'height': ''
- });
- finishEdit();
- });
- var resetButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">Reset</button>');
- resetButton.on('click', function(event){
- event.preventDefault();
- $element.css({
- width: '',
- height: ''
- });
- finishEdit();
- });
- buttonGroup.append(fullButton);
- buttonGroup.append(halfButton);
- buttonGroup.append(quartButton);
- buttonGroup.append(resetButton);
- container.append(buttonGroup);
- buttonGroup = angular.element('<div class="btn-group" style="padding-right: 6px;">');
- var floatLeft = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-align-left"></i></button>');
- floatLeft.on('click', function(event){
- event.preventDefault();
- // webkit
- $element.css('float', 'left');
- // firefox
- $element.css('cssFloat', 'left');
- // IE < 8
- $element.css('styleFloat', 'left');
- finishEdit();
- });
- var floatRight = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-align-right"></i></button>');
- floatRight.on('click', function(event){
- event.preventDefault();
- // webkit
- $element.css('float', 'right');
- // firefox
- $element.css('cssFloat', 'right');
- // IE < 8
- $element.css('styleFloat', 'right');
- finishEdit();
- });
- var floatNone = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-align-justify"></i></button>');
- floatNone.on('click', function(event){
- event.preventDefault();
- // webkit
- $element.css('float', '');
- // firefox
- $element.css('cssFloat', '');
- // IE < 8
- $element.css('styleFloat', '');
- finishEdit();
- });
- buttonGroup.append(floatLeft);
- buttonGroup.append(floatNone);
- buttonGroup.append(floatRight);
- container.append(buttonGroup);
- buttonGroup = angular.element('<div class="btn-group">');
- var remove = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-trash-o"></i></button>');
- remove.on('click', function(event){
- event.preventDefault();
- $element.remove();
- finishEdit();
- });
- buttonGroup.append(remove);
- container.append(buttonGroup);
- editorScope.showPopover($element);
- editorScope.showResizeOverlay($element);
- },
- aOnSelectAction: function(event, $element, editorScope){
- // setup the editor toolbar
- // Credit to the work at http://hackerwins.github.io/summernote/ for this editbar logic
- event.preventDefault();
- editorScope.displayElements.popover.css('width', '436px');
- var container = editorScope.displayElements.popoverContainer;
- container.empty();
- container.css('line-height', '28px');
- var link = angular.element('<a href="' + $element.attr('href') + '" target="_blank">' + $element.attr('href') + '</a>');
- link.css({
- 'display': 'inline-block',
- 'max-width': '200px',
- 'overflow': 'hidden',
- 'text-overflow': 'ellipsis',
- 'white-space': 'nowrap',
- 'vertical-align': 'middle'
- });
- container.append(link);
- var buttonGroup = angular.element('<div class="btn-group pull-right">');
- var reLinkButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" tabindex="-1" unselectable="on" title="' + taTranslations.editLink.reLinkButton.tooltip + '"><i class="fa fa-edit icon-edit"></i></button>');
- reLinkButton.on('click', function(event){
- event.preventDefault();
- var urlLink = $window.prompt(taTranslations.insertLink.dialogPrompt, $element.attr('href'));
- if(urlLink && urlLink !== '' && urlLink !== 'http://'){
- $element.attr('href', urlLink);
- editorScope.updateTaBindtaTextElement();
- }
- editorScope.hidePopover();
- });
- buttonGroup.append(reLinkButton);
- var unLinkButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" tabindex="-1" unselectable="on" title="' + taTranslations.editLink.unLinkButton.tooltip + '"><i class="fa fa-unlink icon-unlink"></i></button>');
- // directly before this click event is fired a digest is fired off whereby the reference to $element is orphaned off
- unLinkButton.on('click', function(event){
- event.preventDefault();
- $element.replaceWith($element.contents());
- editorScope.updateTaBindtaTextElement();
- editorScope.hidePopover();
- });
- buttonGroup.append(unLinkButton);
- var targetToggle = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" tabindex="-1" unselectable="on">' + taTranslations.editLink.targetToggle.buttontext + '</button>');
- if($element.attr('target') === '_blank'){
- targetToggle.addClass('active');
- }
- targetToggle.on('click', function(event){
- event.preventDefault();
- $element.attr('target', ($element.attr('target') === '_blank') ? '' : '_blank');
- targetToggle.toggleClass('active');
- editorScope.updateTaBindtaTextElement();
- });
- buttonGroup.append(targetToggle);
- container.append(buttonGroup);
- editorScope.showPopover($element);
- },
- extractYoutubeVideoId: function(url) {
- var re = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/i;
- var match = url.match(re);
- return (match && match[1]) || null;
- }
- };
- }])
- .run(['taRegisterTool', '$window', 'taTranslations', 'taSelection', 'taToolFunctions', '$sanitize', 'taOptions', function(taRegisterTool, $window, taTranslations, taSelection, taToolFunctions, $sanitize, taOptions){
- // test for the version of $sanitize that is in use
- // You can disable this check by setting taOptions.textAngularSanitize == false
- var gv = {}; $sanitize('', gv);
- /* istanbul ignore next, throws error */
- if ((taOptions.forceTextAngularSanitize===true) && (gv.version !== 'taSanitize')) {
- throw angular.$$minErr('textAngular')("textAngularSetup", "The textAngular-sanitize provider has been replaced by another -- have you included angular-sanitize by mistake?");
- }
- taRegisterTool("html", {
- iconclass: 'fa fa-code',
- tooltiptext: taTranslations.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 _retActiveStateFunction = function(q){
- return function(){ return this.$editor().queryFormatBlockState(q); };
- };
- var headerAction = function(){
- return this.$editor().wrapSelection("formatBlock", "<" + this.name.toUpperCase() +">");
- };
- angular.forEach(['h1','h2','h3','h4','h5','h6'], function(h){
- taRegisterTool(h.toLowerCase(), {
- buttontext: h.toUpperCase(),
- tooltiptext: taTranslations.heading.tooltip + h.charAt(1),
- action: headerAction,
- activeState: _retActiveStateFunction(h.toLowerCase())
- });
- });
- taRegisterTool('p', {
- buttontext: 'P',
- tooltiptext: taTranslations.p.tooltip,
- action: function(){
- return this.$editor().wrapSelection("formatBlock", "<P>");
- },
- activeState: function(){ return this.$editor().queryFormatBlockState('p'); }
- });
- // key: pre -> taTranslations[key].tooltip, taTranslations[key].buttontext
- taRegisterTool('pre', {
- buttontext: 'pre',
- tooltiptext: taTranslations.pre.tooltip,
- action: function(){
- return this.$editor().wrapSelection("formatBlock", "<PRE>");
- },
- activeState: function(){ return this.$editor().queryFormatBlockState('pre'); }
- });
- taRegisterTool('ul', {
- iconclass: 'fa fa-list-ul',
- tooltiptext: taTranslations.ul.tooltip,
- action: function(){
- return this.$editor().wrapSelection("insertUnorderedList", null);
- },
- activeState: function(){ return this.$editor().queryCommandState('insertUnorderedList'); }
- });
- taRegisterTool('ol', {
- iconclass: 'fa fa-list-ol',
- tooltiptext: taTranslations.ol.tooltip,
- action: function(){
- return this.$editor().wrapSelection("insertOrderedList", null);
- },
- activeState: function(){ return this.$editor().queryCommandState('insertOrderedList'); }
- });
- taRegisterTool('quote', {
- iconclass: 'fa fa-quote-right',
- tooltiptext: taTranslations.quote.tooltip,
- action: function(){
- return this.$editor().wrapSelection("formatBlock", "<BLOCKQUOTE>");
- },
- activeState: function(){ return this.$editor().queryFormatBlockState('blockquote'); }
- });
- taRegisterTool('undo', {
- iconclass: 'fa fa-undo',
- tooltiptext: taTranslations.undo.tooltip,
- action: function(){
- return this.$editor().wrapSelection("undo", null);
- }
- });
- taRegisterTool('redo', {
- iconclass: 'fa fa-repeat',
- tooltiptext: taTranslations.redo.tooltip,
- action: function(){
- return this.$editor().wrapSelection("redo", null);
- }
- });
- taRegisterTool('bold', {
- iconclass: 'fa fa-bold',
- tooltiptext: taTranslations.bold.tooltip,
- action: function(){
- return this.$editor().wrapSelection("bold", null);
- },
- activeState: function(){
- return this.$editor().queryCommandState('bold');
- },
- commandKeyCode: 98
- });
- taRegisterTool('justifyLeft', {
- iconclass: 'fa fa-align-left',
- tooltiptext: taTranslations.justifyLeft.tooltip,
- action: function(){
- return this.$editor().wrapSelection("justifyLeft", null);
- },
- activeState: function(commonElement){
- /* istanbul ignore next: */
- if (commonElement && commonElement.nodeName === '#document') return false;
- var result = false;
- if (commonElement)
- result =
- commonElement.css('text-align') === 'left' ||
- commonElement.attr('align') === 'left' ||
- (
- commonElement.css('text-align') !== 'right' &&
- commonElement.css('text-align') !== 'center' &&
- commonElement.css('text-align') !== 'justify' && !this.$editor().queryCommandState('justifyRight') && !this.$editor().queryCommandState('justifyCenter')
- ) && !this.$editor().queryCommandState('justifyFull');
- result = result || this.$editor().queryCommandState('justifyLeft');
- return result;
- }
- });
- taRegisterTool('justifyRight', {
- iconclass: 'fa fa-align-right',
- tooltiptext: taTranslations.justifyRight.tooltip,
- action: function(){
- return this.$editor().wrapSelection("justifyRight", null);
- },
- activeState: function(commonElement){
- /* istanbul ignore next: */
- if (commonElement && commonElement.nodeName === '#document') return false;
- var result = false;
- if(commonElement) result = commonElement.css('text-align') === 'right';
- result = result || this.$editor().queryCommandState('justifyRight');
- return result;
- }
- });
- taRegisterTool('justifyFull', {
- iconclass: 'fa fa-align-justify',
- tooltiptext: taTranslations.justifyFull.tooltip,
- action: function(){
- return this.$editor().wrapSelection("justifyFull", null);
- },
- activeState: function(commonElement){
- var result = false;
- if(commonElement) result = commonElement.css('text-align') === 'justify';
- result = result || this.$editor().queryCommandState('justifyFull');
- return result;
- }
- });
- taRegisterTool('justifyCenter', {
- iconclass: 'fa fa-align-center',
- tooltiptext: taTranslations.justifyCenter.tooltip,
- action: function(){
- return this.$editor().wrapSelection("justifyCenter", null);
- },
- activeState: function(commonElement){
- /* istanbul ignore next: */
- if (commonElement && commonElement.nodeName === '#document') return false;
- var result = false;
- if(commonElement) result = commonElement.css('text-align') === 'center';
- result = result || this.$editor().queryCommandState('justifyCenter');
- return result;
- }
- });
- taRegisterTool('indent', {
- iconclass: 'fa fa-indent',
- tooltiptext: taTranslations.indent.tooltip,
- action: function(){
- return this.$editor().wrapSelection("indent", null);
- },
- activeState: function(){
- return this.$editor().queryFormatBlockState('blockquote');
- },
- commandKeyCode: 'TabKey'
- });
- taRegisterTool('outdent', {
- iconclass: 'fa fa-outdent',
- tooltiptext: taTranslations.outdent.tooltip,
- action: function(){
- return this.$editor().wrapSelection("outdent", null);
- },
- activeState: function(){
- return false;
- },
- commandKeyCode: 'ShiftTabKey'
- });
- taRegisterTool('italics', {
- iconclass: 'fa fa-italic',
- tooltiptext: taTranslations.italic.tooltip,
- action: function(){
- return this.$editor().wrapSelection("italic", null);
- },
- activeState: function(){
- return this.$editor().queryCommandState('italic');
- },
- commandKeyCode: 105
- });
- taRegisterTool('underline', {
- iconclass: 'fa fa-underline',
- tooltiptext: taTranslations.underline.tooltip,
- action: function(){
- return this.$editor().wrapSelection("underline", null);
- },
- activeState: function(){
- return this.$editor().queryCommandState('underline');
- },
- commandKeyCode: 117
- });
- taRegisterTool('strikeThrough', {
- iconclass: 'fa fa-strikethrough',
- tooltiptext: taTranslations.strikeThrough.tooltip,
- action: function(){
- return this.$editor().wrapSelection("strikeThrough", null);
- },
- activeState: function(){
- return document.queryCommandState('strikeThrough');
- }
- });
- taRegisterTool('clear', {
- iconclass: 'fa fa-ban',
- tooltiptext: taTranslations.clear.tooltip,
- action: function(deferred, restoreSelection){
- var i;
- this.$editor().wrapSelection("removeFormat", null);
- var possibleNodes = angular.element(taSelection.getSelectionElement());
- // remove lists
- var removeListElements = function(list){
- list = angular.element(list);
- var prevElement = list;
- angular.forEach(list.children(), function(liElem){
- var newElem = angular.element('<p></p>');
- newElem.html(angular.element(liElem).html());
- prevElement.after(newElem);
- prevElement = newElem;
- });
- list.remove();
- };
- angular.forEach(possibleNodes.find("ul"), removeListElements);
- angular.forEach(possibleNodes.find("ol"), removeListElements);
- 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('<p></p>');
- 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+'></'+_parent[0].tagName+'>');
- var _secondList = angular.element('<'+_parent[0].tagName+'></'+_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]);
- }
- // clear out all class attributes. These do not seem to be cleared via removeFormat
- var $editor = this.$editor();
- var recursiveRemoveClass = function(node){
- node = angular.element(node);
- if(node[0] !== $editor.displayElements.text[0]) node.removeAttr('class');
- angular.forEach(node.children(), recursiveRemoveClass);
- };
- angular.forEach(possibleNodes, recursiveRemoveClass);
- // check if in list. If not in list then use formatBlock option
- if(possibleNodes[0].tagName.toLowerCase() !== 'li' &&
- possibleNodes[0].tagName.toLowerCase() !== 'ol' &&
- possibleNodes[0].tagName.toLowerCase() !== 'ul') this.$editor().wrapSelection("formatBlock", "default");
- restoreSelection();
- }
- });
- taRegisterTool('insertImage', {
- iconclass: 'fa fa-picture-o',
- tooltiptext: taTranslations.insertImage.tooltip,
- action: function(){
- var imageLink;
- imageLink = $window.prompt(taTranslations.insertImage.dialogPrompt, 'http://');
- if(imageLink && imageLink !== '' && imageLink !== 'http://'){
- return this.$editor().wrapSelection('insertImage', imageLink, true);
- }
- },
- onElementSelect: {
- element: 'img',
- action: taToolFunctions.imgOnSelectAction
- }
- });
- taRegisterTool('insertVideo', {
- iconclass: 'fa fa-youtube-play',
- tooltiptext: taTranslations.insertVideo.tooltip,
- action: function(){
- var urlPrompt;
- urlPrompt = $window.prompt(taTranslations.insertVideo.dialogPrompt, 'https://');
- if (urlPrompt && urlPrompt !== '' && urlPrompt !== 'https://') {
- videoId = taToolFunctions.extractYoutubeVideoId(urlPrompt);
- /* istanbul ignore else: if it's invalid don't worry - though probably should show some kind of error message */
- if(videoId){
- // create the embed link
- var urlLink = "https://www.youtube.com/embed/" + videoId;
- // create the HTML
- // for all options see: http://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
- // maxresdefault.jpg seems to be undefined on some.
- var embed = '<img class="ta-insert-video" src="https://img.youtube.com/vi/' + videoId + '/hqdefault.jpg" ta-insert-video="' + urlLink + '" contenteditable="false" allowfullscreen="true" frameborder="0" />';
- // insert
- return this.$editor().wrapSelection('insertHTML', embed, true);
- }
- }
- },
- onElementSelect: {
- element: 'img',
- onlyWithAttrs: ['ta-insert-video'],
- action: taToolFunctions.imgOnSelectAction
- }
- });
- taRegisterTool('insertLink', {
- tooltiptext: taTranslations.insertLink.tooltip,
- iconclass: 'fa fa-link',
- action: function(){
- var urlLink;
- urlLink = $window.prompt(taTranslations.insertLink.dialogPrompt, 'http://');
- if(urlLink && urlLink !== '' && urlLink !== 'http://'){
- return this.$editor().wrapSelection('createLink', urlLink, true);
- }
- },
- activeState: function(commonElement){
- if(commonElement) return commonElement[0].tagName === 'A';
- return false;
- },
- onElementSelect: {
- element: 'a',
- action: taToolFunctions.aOnSelectAction
- }
- });
- taRegisterTool('wordcount', {
- display: '<div id="toolbarWC" style="display:block; min-width:100px;">Words: <span ng-bind="wordcount"></span></div>',
- disabled: true,
- wordcount: 0,
- activeState: function(){ // this fires on keyup
- var textElement = this.$editor().displayElements.text;
- /* istanbul ignore next: will default to '' when undefined */
- var workingHTML = textElement[0].innerHTML || '';
- var noOfWords = 0;
- /* istanbul ignore if: will default to '' when undefined */
- if (workingHTML.replace(/\s*<[^>]*?>\s*/g, '') !== '') {
- noOfWords = workingHTML.replace(/<\/?(b|i|em|strong|span|u|strikethrough|a|img|small|sub|sup|label)( [^>*?])?>/gi, '') // remove inline tags without adding spaces
- .replace(/(<[^>]*?>\s*<[^>]*?>)/ig, ' ') // replace adjacent tags with possible space between with a space
- .replace(/(<[^>]*?>)/ig, '') // remove any singular tags
- .replace(/\s+/ig, ' ') // condense spacing
- .match(/\S+/g).length; // count remaining non-space strings
- }
- //Set current scope
- this.wordcount = noOfWords;
- //Set editor scope
- this.$editor().wordcount = noOfWords;
- return false;
- }
- });
- taRegisterTool('charcount', {
- display: '<div id="toolbarCC" style="display:block; min-width:120px;">Characters: <span ng-bind="charcount"></span></div>',
- disabled: true,
- charcount: 0,
- activeState: function(){ // this fires on keyup
- var textElement = this.$editor().displayElements.text;
- var sourceText = textElement[0].innerText || textElement[0].textContent; // to cover the non-jquery use case.
- // Caculate number of chars
- var noOfChars = sourceText.replace(/(\r\n|\n|\r)/gm,"").replace(/^\s+/g,' ').replace(/\s+$/g, ' ').length;
- //Set current scope
- this.charcount = noOfChars;
- //Set editor scope
- this.$editor().charcount = noOfChars;
- return false;
- }
- });
- }]);
|