plugin.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. ( function( $, QUnit, tinymce, setTimeout ) {
  2. var editor,
  3. count = 0;
  4. if ( tinymce.Env.ie && tinymce.Env.ie < 11 ) {
  5. return;
  6. }
  7. function mceType( chr, noKeyUp ) {
  8. var editor = tinymce.activeEditor, keyCode, charCode, evt, startElm, rng, startContainer, startOffset, textNode;
  9. function charCodeToKeyCode(charCode) {
  10. var lookup = {
  11. '0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55, '8': 56, '9': 57,'a': 65, 'b': 66, 'c': 67,
  12. 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73, 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81,
  13. 'r': 82, 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, ' ': 32, ',': 188, '-': 189, '.': 190, '/': 191, '\\': 220,
  14. '[': 219, ']': 221, '\'': 222, ';': 186, '=': 187, ')': 41,
  15. '`': 48 // Anything will do.
  16. };
  17. return lookup[String.fromCharCode(charCode)];
  18. }
  19. function fakeEvent(target, type, evt) {
  20. editor.dom.fire(target, type, evt);
  21. }
  22. // Numeric keyCode.
  23. if (typeof(chr) === 'number') {
  24. charCode = chr;
  25. keyCode = charCodeToKeyCode(charCode);
  26. } else if (typeof(chr) === 'string') {
  27. // String value.
  28. if (chr === '\b') {
  29. keyCode = 8;
  30. charCode = chr.charCodeAt(0);
  31. } else if (chr === '\n') {
  32. keyCode = 13;
  33. charCode = chr.charCodeAt(0);
  34. } else {
  35. charCode = chr.charCodeAt(0);
  36. keyCode = charCodeToKeyCode(charCode);
  37. }
  38. } else {
  39. evt = chr;
  40. if (evt.charCode) {
  41. chr = String.fromCharCode(evt.charCode);
  42. }
  43. if (evt.keyCode) {
  44. keyCode = evt.keyCode;
  45. }
  46. }
  47. evt = evt || {keyCode: keyCode, charCode: charCode};
  48. startElm = editor.selection.getStart();
  49. fakeEvent(startElm, 'keydown', evt);
  50. fakeEvent(startElm, 'keypress', evt);
  51. if (!evt.isDefaultPrevented()) {
  52. if (keyCode === 8) {
  53. if (editor.getDoc().selection) {
  54. rng = editor.getDoc().selection.createRange();
  55. if (rng.text.length === 0) {
  56. rng.moveStart('character', -1);
  57. rng.select();
  58. }
  59. rng.execCommand('Delete', false, null);
  60. } else {
  61. rng = editor.selection.getRng();
  62. startContainer = rng.startContainer;
  63. if (startContainer.nodeType === 1 && rng.collapsed) {
  64. var nodes = rng.startContainer.childNodes;
  65. startContainer = nodes[nodes.length - 1];
  66. }
  67. // If caret is at <p>abc|</p> and after the abc text node then move it to the end of the text node.
  68. // Expand the range to include the last char <p>ab[c]</p> since IE 11 doesn't delete otherwise.
  69. if ( rng.collapsed && startContainer && startContainer.nodeType === 3 && startContainer.data.length > 0) {
  70. rng.setStart(startContainer, startContainer.data.length - 1);
  71. rng.setEnd(startContainer, startContainer.data.length);
  72. editor.selection.setRng(rng);
  73. }
  74. editor.getDoc().execCommand('Delete', false, null);
  75. }
  76. } else if (typeof(chr) === 'string') {
  77. rng = editor.selection.getRng(true);
  78. if (rng.startContainer.nodeType === 3 && rng.collapsed) {
  79. // `insertData` may alter the range.
  80. startContainer = rng.startContainer;
  81. startOffset = rng.startOffset;
  82. rng.startContainer.insertData( rng.startOffset, chr );
  83. rng.setStart( startContainer, startOffset + 1 );
  84. } else {
  85. textNode = editor.getDoc().createTextNode(chr);
  86. rng.insertNode(textNode);
  87. rng.setStart(textNode, 1);
  88. }
  89. rng.collapse(true);
  90. editor.selection.setRng(rng);
  91. }
  92. }
  93. if ( ! noKeyUp ) {
  94. fakeEvent(startElm, 'keyup', evt);
  95. }
  96. }
  97. function type() {
  98. var args = arguments;
  99. // Wait once for conversions to be triggered,
  100. // and once for the `canUndo` flag to be set.
  101. setTimeout( function() {
  102. setTimeout( function() {
  103. if ( typeof args[0] === 'string' ) {
  104. args[0] = args[0].split( '' );
  105. }
  106. if ( typeof args[0] === 'function' ) {
  107. args[0]();
  108. } else {
  109. mceType( args[0].shift() );
  110. }
  111. if ( ! args[0].length ) {
  112. [].shift.call( args );
  113. }
  114. if ( args.length ) {
  115. type.apply( null, args );
  116. }
  117. } );
  118. } );
  119. }
  120. QUnit.module( 'tinymce.plugins.wptextpattern', {
  121. beforeEach: function( assert ) {
  122. var done;
  123. if ( ! editor ) {
  124. done = assert.async();
  125. $( document.body ).append( '<textarea id="editor">' );
  126. tinymce.init( {
  127. selector: '#editor',
  128. skin: false,
  129. plugins: 'wptextpattern',
  130. wptextpattern: {
  131. inline: [
  132. { delimiter: '`', format: 'code' },
  133. { delimiter: '``', format: 'bold' },
  134. { delimiter: '```', format: 'italic' }
  135. ]
  136. },
  137. init_instance_callback: function() {
  138. editor = arguments[0];
  139. editor.focus();
  140. editor.selection.setCursorLocation();
  141. done();
  142. }
  143. } );
  144. } else {
  145. editor.setContent( '' );
  146. editor.selection.setCursorLocation();
  147. }
  148. },
  149. afterEach: function( assert ) {
  150. count++;
  151. if ( count === assert.test.module.tests.length ) {
  152. editor.remove();
  153. $( '#editor' ).remove();
  154. }
  155. }
  156. } );
  157. QUnit.test( 'Unordered list.', function( assert ) {
  158. type( '* a', function() {
  159. assert.equal( editor.getContent(), '<ul>\n<li>a</li>\n</ul>' );
  160. }, assert.async() );
  161. } );
  162. QUnit.test( 'Unordered list. (fast)', function( assert ) {
  163. type( '*', function() {
  164. mceType( ' ', true );
  165. }, 'a', function() {
  166. assert.equal( editor.getContent(), '<ul>\n<li>a</li>\n</ul>' );
  167. }, assert.async() );
  168. } );
  169. QUnit.test( 'Ordered list.', function( assert ) {
  170. type( '1. a', function() {
  171. assert.equal( editor.getContent(), '<ol>\n<li>a</li>\n</ol>' );
  172. }, assert.async() );
  173. } );
  174. QUnit.test( 'Ordered list with content. (1)', function( assert ) {
  175. editor.setContent( '<p><strong>test</strong></p>' );
  176. editor.selection.setCursorLocation();
  177. type( '* ', function() {
  178. assert.equal( editor.getContent(), '<ul>\n<li><strong>test</strong></li>\n</ul>' );
  179. }, assert.async() );
  180. } );
  181. QUnit.test( 'Ordered list with content. (2)', function( assert ) {
  182. editor.setContent( '<p><strong>test</strong></p>' );
  183. editor.selection.setCursorLocation( editor.$( 'p' )[0], 0 );
  184. type( '* ', function() {
  185. assert.equal( editor.getContent(), '<ul>\n<li><strong>test</strong></li>\n</ul>' );
  186. }, assert.async() );
  187. } );
  188. QUnit.test( 'Only transform inside a P tag.', function( assert ) {
  189. editor.setContent( '<h1>test</h1>' );
  190. editor.selection.setCursorLocation();
  191. type( '* ', function() {
  192. assert.equal( editor.getContent(), '<h1>* test</h1>' );
  193. }, assert.async() );
  194. } );
  195. QUnit.test( 'Only transform at the start of a P tag.', function( assert ) {
  196. editor.setContent( '<p>test <strong>test</strong></p>' );
  197. editor.selection.setCursorLocation( editor.$( 'strong' )[0].firstChild, 0 );
  198. type( '* ', function() {
  199. assert.equal( editor.getContent(), '<p>test <strong>* test</strong></p>' );
  200. }, assert.async() );
  201. } );
  202. QUnit.test( 'Only transform when at the cursor is at the start.', function( assert ) {
  203. editor.setContent( '<p>* test</p>' );
  204. editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 6 );
  205. type( ' a', function() {
  206. assert.equal( editor.getContent(), '<p>* test a</p>' );
  207. }, assert.async() );
  208. } );
  209. QUnit.test( 'Backspace should undo the transformation.', function( assert ) {
  210. editor.setContent( '<p>test</p>' );
  211. editor.selection.setCursorLocation();
  212. type( '* \b', function() {
  213. assert.equal( editor.getContent(), '<p>* test</p>' );
  214. assert.equal( editor.selection.getRng().startOffset, 2 );
  215. }, assert.async() );
  216. } );
  217. QUnit.test( 'Backspace should undo the transformation only right after it happened.', function( assert ) {
  218. editor.setContent( '<p>test</p>' );
  219. editor.selection.setCursorLocation();
  220. type( '* ', function() {
  221. editor.selection.setCursorLocation( editor.$( 'li' )[0].firstChild, 4 );
  222. // Gecko.
  223. editor.fire( 'click' );
  224. }, '\b', function() {
  225. assert.equal( editor.getContent(), '<ul>\n<li>tes</li>\n</ul>' );
  226. }, assert.async() );
  227. } );
  228. QUnit.test( 'Heading 3', function( assert ) {
  229. editor.setContent( '<p>### test</p>' );
  230. editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 8 );
  231. type( '\n', function() {
  232. assert.equal( editor.$( 'h3' )[0].firstChild.data, 'test' );
  233. assert.equal( editor.getContent(), '<h3>test</h3>\n<p>&nbsp;</p>' );
  234. }, assert.async() );
  235. } );
  236. QUnit.test( 'Heading 3 with elements.', function( assert ) {
  237. editor.setContent( '<p>###<del>test</del></p>' );
  238. editor.selection.setCursorLocation( editor.$( 'del' )[0].firstChild, 4 );
  239. type( '\n', function() {
  240. assert.equal( editor.getContent(), '<h3><del>test</del></h3>\n<p>&nbsp;</p>' );
  241. }, assert.async() );
  242. } );
  243. QUnit.test( 'Don\'t convert without content', function( assert ) {
  244. editor.setContent( '<p>###&nbsp;</p>' );
  245. editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 4 );
  246. type( '\n', function() {
  247. assert.equal( editor.getContent(), '<p>###&nbsp;</p>\n<p>&nbsp;</p>' );
  248. }, assert.async() );
  249. } );
  250. QUnit.test( 'Horizontal Rule', function( assert ) {
  251. type( '---\n', function() {
  252. assert.equal( editor.getContent(), '<hr />\n<p>&nbsp;</p>' );
  253. }, assert.async() );
  254. } );
  255. QUnit.test( 'Inline: single character.', function( assert ) {
  256. type( '`test`', function() {
  257. assert.equal( editor.getContent(), '<p><code>test</code></p>' );
  258. assert.equal( editor.selection.getRng().startOffset, 1 );
  259. }, assert.async() );
  260. } );
  261. QUnit.test( 'Inline: two characters.', function( assert ) {
  262. type( '``test``', function() {
  263. assert.equal( editor.getContent(), '<p><strong>test</strong></p>' );
  264. assert.equal( editor.selection.getRng().startOffset, 1 );
  265. }, assert.async() );
  266. } );
  267. QUnit.test( 'Inline: allow spaces within text.', function( assert ) {
  268. type( '`a a`', function() {
  269. assert.equal( editor.getContent(), '<p><code>a a</code></p>' );
  270. assert.equal( editor.selection.getRng().startOffset, 1 );
  271. }, assert.async() );
  272. } );
  273. QUnit.test( 'Inline: disallow \\S-delimiter-\\s.', function( assert ) {
  274. type( 'a` a`', function() {
  275. assert.equal( editor.getContent(), '<p>a` a`</p>' );
  276. assert.equal( editor.selection.getRng().startOffset, 5 );
  277. }, assert.async() );
  278. } );
  279. QUnit.test( 'Inline: allow \\s-delimiter-\\s.', function( assert ) {
  280. type( 'a ` a`', function() {
  281. assert.equal( editor.getContent(), '<p>a <code> a</code></p>' );
  282. assert.equal( editor.selection.getRng().startOffset, 1 );
  283. }, assert.async() );
  284. } );
  285. QUnit.test( 'Inline: allow \\S-delimiter-\\S.', function( assert ) {
  286. type( 'a`a`', function() {
  287. assert.equal( editor.getContent(), '<p>a<code>a</code></p>' );
  288. assert.equal( editor.selection.getRng().startOffset, 1 );
  289. }, assert.async() );
  290. } );
  291. QUnit.test( 'Inline: after typing.', function( assert ) {
  292. editor.setContent( '<p>test test test</p>' );
  293. editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 5 );
  294. type( '`', function() {
  295. editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 10 );
  296. }, '`', function() {
  297. assert.equal( editor.getContent(), '<p>test <code>test</code> test</p>' );
  298. assert.equal( editor.selection.getRng().startOffset, 1 );
  299. }, assert.async() );
  300. } );
  301. QUnit.test( 'Inline: no change without content.', function( assert ) {
  302. type( 'test `` ``` ````', function() {
  303. assert.equal( editor.getContent(), '<p>test `` ``` ````</p>' );
  304. }, assert.async() );
  305. } );
  306. QUnit.test( 'Inline: convert with previously unconverted pattern.', function( assert ) {
  307. editor.setContent( '<p>`test` test&nbsp;</p>' );
  308. editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 12 );
  309. type( '`test`', function() {
  310. assert.equal( editor.getContent(), '<p>`test` test&nbsp;<code>test</code></p>' );
  311. }, assert.async() );
  312. } );
  313. QUnit.test( 'Inline: convert with previous pattern characters.', function( assert ) {
  314. editor.setContent( '<p>test``` 123</p>' );
  315. editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 11 );
  316. type( '``456``', function() {
  317. assert.equal( editor.getContent(), '<p>test``` 123<strong>456</strong></p>' );
  318. }, assert.async() );
  319. } );
  320. QUnit.test( 'Inline: disallow after previous pattern characters and leading space.', function( assert ) {
  321. editor.setContent( '<p>test``` 123</p>' );
  322. editor.selection.setCursorLocation( editor.$( 'p' )[0].firstChild, 11 );
  323. type( '``` 456```', function() {
  324. assert.equal( editor.getContent(), '<p>test``` 123``` 456```</p>' );
  325. }, assert.async() );
  326. } );
  327. } )( window.jQuery, window.QUnit, window.tinymce, window.setTimeout );