factories.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. angular.module('textAngular.factories', [])
  2. .factory('taBrowserTag', [function(){
  3. return function(tag){
  4. /* istanbul ignore next: ie specific test */
  5. if(!tag) return (_browserDetect.ie <= 8)? 'P' : 'p';
  6. else if(tag === '') return (_browserDetect.ie === undefined)? 'div' : (_browserDetect.ie <= 8)? 'P' : 'p';
  7. else return (_browserDetect.ie <= 8)? tag.toUpperCase() : tag;
  8. };
  9. }]).factory('taApplyCustomRenderers', ['taCustomRenderers', 'taDOM', function(taCustomRenderers, taDOM){
  10. return function(val){
  11. var element = angular.element('<div></div>');
  12. element[0].innerHTML = val;
  13. angular.forEach(taCustomRenderers, function(renderer){
  14. var elements = [];
  15. // get elements based on what is defined. If both defined do secondary filter in the forEach after using selector string
  16. if(renderer.selector && renderer.selector !== '')
  17. elements = element.find(renderer.selector);
  18. /* istanbul ignore else: shouldn't fire, if it does we're ignoring everything */
  19. else if(renderer.customAttribute && renderer.customAttribute !== '')
  20. elements = taDOM.getByAttribute(element, renderer.customAttribute);
  21. // process elements if any found
  22. angular.forEach(elements, function(_element){
  23. _element = angular.element(_element);
  24. if(renderer.selector && renderer.selector !== '' && renderer.customAttribute && renderer.customAttribute !== ''){
  25. if(_element.attr(renderer.customAttribute) !== undefined) renderer.renderLogic(_element);
  26. } else renderer.renderLogic(_element);
  27. });
  28. });
  29. return element[0].innerHTML;
  30. };
  31. }]).factory('taFixChrome', function(){
  32. // get whaterever rubbish is inserted in chrome
  33. // should be passed an html string, returns an html string
  34. var taFixChrome = function(html){
  35. if(!html || !angular.isString(html) || html.length <= 0) return html;
  36. // grab all elements with a style attibute
  37. var spanMatch = /<([^>\/]+?)style=("([^"]+)"|'([^']+)')([^>]*)>/ig;
  38. var match, styleVal, newTag, finalHtml = '', lastIndex = 0;
  39. while(match = spanMatch.exec(html)){
  40. // one of the quoted values ' or "
  41. /* istanbul ignore next: quotations match */
  42. styleVal = match[3] || match[4];
  43. // test for chrome inserted junk
  44. if(styleVal && styleVal.match(/line-height: 1.[0-9]{3,12};|color: inherit; line-height: 1.1;/i)){
  45. // replace original tag with new tag
  46. styleVal = styleVal.replace(/( |)font-family: inherit;|( |)line-height: 1.[0-9]{3,12};|( |)color: inherit;/ig, '');
  47. newTag = '<' + match[1].trim();
  48. if(styleVal.trim().length > 0) newTag += ' style=' + match[2].substring(0,1) + styleVal + match[2].substring(0,1);
  49. newTag += match[5].trim() + ">";
  50. finalHtml += html.substring(lastIndex, match.index) + newTag;
  51. lastIndex = match.index + match[0].length;
  52. }
  53. }
  54. finalHtml += html.substring(lastIndex);
  55. // only replace when something has changed, else we get focus problems on inserting lists
  56. if(lastIndex > 0){
  57. // replace all empty strings
  58. return finalHtml.replace(/<span\s?>(.*?)<\/span>(<br(\/|)>|)/ig, '$1');
  59. } else return html;
  60. };
  61. return taFixChrome;
  62. }).factory('taSanitize', ['$sanitize', function taSanitizeFactory($sanitize){
  63. var convert_infos = [
  64. {
  65. property: 'font-weight',
  66. values: [ 'bold' ],
  67. tag: 'b'
  68. },
  69. {
  70. property: 'font-style',
  71. values: [ 'italic' ],
  72. tag: 'i'
  73. }
  74. ];
  75. var styleMatch = [];
  76. for(var i = 0; i < convert_infos.length; i++){
  77. var _partialStyle = '(' + convert_infos[i].property + ':\\s*(';
  78. for(var j = 0; j < convert_infos[i].values.length; j++){
  79. /* istanbul ignore next: not needed to be tested yet */
  80. if(j > 0) _partialStyle += '|';
  81. _partialStyle += convert_infos[i].values[j];
  82. }
  83. _partialStyle += ');)';
  84. styleMatch.push(_partialStyle);
  85. }
  86. var styleRegexString = '(' + styleMatch.join('|') + ')';
  87. function wrapNested(html, wrapTag) {
  88. var depth = 0;
  89. var lastIndex = 0;
  90. var match;
  91. var tagRegex = /<[^>]*>/ig;
  92. while(match = tagRegex.exec(html)){
  93. lastIndex = match.index;
  94. if(match[0].substr(1, 1) === '/'){
  95. if(depth === 0) break;
  96. else depth--;
  97. }else depth++;
  98. }
  99. return wrapTag +
  100. html.substring(0, lastIndex) +
  101. // get the start tags reversed - this is safe as we construct the strings with no content except the tags
  102. angular.element(wrapTag)[0].outerHTML.substring(wrapTag.length) +
  103. html.substring(lastIndex);
  104. }
  105. function transformLegacyStyles(html){
  106. if(!html || !angular.isString(html) || html.length <= 0) return html;
  107. var i;
  108. var styleElementMatch = /<([^>\/]+?)style=("([^"]+)"|'([^']+)')([^>]*)>/ig;
  109. var match, subMatch, styleVal, newTag, lastNewTag = '', newHtml, finalHtml = '', lastIndex = 0;
  110. while(match = styleElementMatch.exec(html)){
  111. // one of the quoted values ' or "
  112. /* istanbul ignore next: quotations match */
  113. styleVal = match[3] || match[4];
  114. var styleRegex = new RegExp(styleRegexString, 'i');
  115. // test for style values to change
  116. if(angular.isString(styleVal) && styleRegex.test(styleVal)){
  117. // remove build tag list
  118. newTag = '';
  119. // init regex here for exec
  120. var styleRegexExec = new RegExp(styleRegexString, 'ig');
  121. // find relevand tags and build a string of them
  122. while(subMatch = styleRegexExec.exec(styleVal)){
  123. for(i = 0; i < convert_infos.length; i++){
  124. if(!!subMatch[(i*2) + 2]){
  125. newTag += '<' + convert_infos[i].tag + '>';
  126. }
  127. }
  128. }
  129. // recursively find more legacy styles in html before this tag and after the previous match (if any)
  130. newHtml = transformLegacyStyles(html.substring(lastIndex, match.index));
  131. // build up html
  132. if(lastNewTag.length > 0){
  133. finalHtml += wrapNested(newHtml, lastNewTag);
  134. }else finalHtml += newHtml;
  135. // grab the style val without the transformed values
  136. styleVal = styleVal.replace(new RegExp(styleRegexString, 'ig'), '');
  137. // build the html tag
  138. finalHtml += '<' + match[1].trim();
  139. if(styleVal.length > 0) finalHtml += ' style="' + styleVal + '"';
  140. finalHtml += match[5] + '>';
  141. // update the start index to after this tag
  142. lastIndex = match.index + match[0].length;
  143. lastNewTag = newTag;
  144. }
  145. }
  146. if(lastNewTag.length > 0){
  147. finalHtml += wrapNested(html.substring(lastIndex), lastNewTag);
  148. }
  149. else finalHtml += html.substring(lastIndex);
  150. return finalHtml;
  151. }
  152. function transformLegacyAttributes(html){
  153. if(!html || !angular.isString(html) || html.length <= 0) return html;
  154. // replace all align='...' tags with text-align attributes
  155. var attrElementMatch = /<([^>\/]+?)align=("([^"]+)"|'([^']+)')([^>]*)>/ig;
  156. var match, finalHtml = '', lastIndex = 0;
  157. // match all attr tags
  158. while(match = attrElementMatch.exec(html)){
  159. // add all html before this tag
  160. finalHtml += html.substring(lastIndex, match.index);
  161. // record last index after this tag
  162. lastIndex = match.index + match[0].length;
  163. // construct tag without the align attribute
  164. var newTag = '<' + match[1] + match[5];
  165. // add the style attribute
  166. if(/style=("([^"]+)"|'([^']+)')/ig.test(newTag)){
  167. /* istanbul ignore next: quotations match */
  168. newTag = newTag.replace(/style=("([^"]+)"|'([^']+)')/i, 'style="$2$3 text-align:' + (match[3] || match[4]) + ';"');
  169. }else{
  170. /* istanbul ignore next: quotations match */
  171. newTag += ' style="text-align:' + (match[3] || match[4]) + ';"';
  172. }
  173. newTag += '>';
  174. // add to html
  175. finalHtml += newTag;
  176. }
  177. // return with remaining html
  178. return finalHtml + html.substring(lastIndex);
  179. }
  180. return function taSanitize(unsafe, oldsafe, ignore){
  181. // unsafe html should NEVER built into a DOM object via angular.element. This allows XSS to be inserted and run.
  182. if ( !ignore ) {
  183. try {
  184. unsafe = transformLegacyStyles(unsafe);
  185. } catch (e) {
  186. }
  187. }
  188. // unsafe and oldsafe should be valid HTML strings
  189. // any exceptions (lets say, color for example) should be made here but with great care
  190. // setup unsafe element for modification
  191. unsafe = transformLegacyAttributes(unsafe);
  192. var safe;
  193. try {
  194. safe = $sanitize(unsafe);
  195. // do this afterwards, then the $sanitizer should still throw for bad markup
  196. if(ignore) safe = unsafe;
  197. } catch (e){
  198. safe = oldsafe || '';
  199. }
  200. // Do processing for <pre> tags, removing tabs and return carriages outside of them
  201. var _preTags = safe.match(/(<pre[^>]*>.*?<\/pre[^>]*>)/ig);
  202. var processedSafe = safe.replace(/(&#(9|10);)*/ig, '');
  203. var re = /<pre[^>]*>.*?<\/pre[^>]*>/ig;
  204. var index = 0;
  205. var lastIndex = 0;
  206. var origTag;
  207. safe = '';
  208. while((origTag = re.exec(processedSafe)) !== null && index < _preTags.length){
  209. safe += processedSafe.substring(lastIndex, origTag.index) + _preTags[index];
  210. lastIndex = origTag.index + origTag[0].length;
  211. index++;
  212. }
  213. return safe + processedSafe.substring(lastIndex);
  214. };
  215. }]).factory('taToolExecuteAction', ['$q', '$log', function($q, $log){
  216. // this must be called on a toolScope or instance
  217. return function(editor){
  218. if(editor !== undefined) this.$editor = function(){ return editor; };
  219. var deferred = $q.defer(),
  220. promise = deferred.promise,
  221. _editor = this.$editor();
  222. // pass into the action the deferred function and also the function to reload the current selection if rangy available
  223. var result;
  224. try{
  225. result = this.action(deferred, _editor.startAction());
  226. // We set the .finally callback here to make sure it doesn't get executed before any other .then callback.
  227. promise['finally'](function(){
  228. _editor.endAction.call(_editor);
  229. });
  230. }catch(exc){
  231. $log.error(exc);
  232. }
  233. if(result || result === undefined){
  234. // if true or undefined is returned then the action has finished. Otherwise the deferred action will be resolved manually.
  235. deferred.resolve();
  236. }
  237. };
  238. }]);