API Docs for: 3.13.0
Show:

File: editor/js/exec-command.js

  1.  
  2. /**
  3. * Plugin for the frame module to handle execCommands for Editor
  4. * @class Plugin.ExecCommand
  5. * @extends Base
  6. * @constructor
  7. * @module editor
  8. * @submodule exec-command
  9. */
  10. var ExecCommand = function() {
  11. ExecCommand.superclass.constructor.apply(this, arguments);
  12. },
  13. /**
  14. * This method is meant to normalize IE's in ability to exec the proper command on elements with CSS styling.
  15. * @method fixIETags
  16. * @protected
  17. * @param {String} cmd The command to execute
  18. * @param {String} tag The tag to create
  19. * @param {String} rule The rule that we are looking for.
  20. */
  21. fixIETags = function(cmd, tag, rule) {
  22. var inst = this.getInstance(),
  23. doc = inst.config.doc,
  24. sel = doc.selection.createRange(),
  25. o = doc.queryCommandValue(cmd),
  26. html, reg, m, p, d, s, c;
  27.  
  28. if (o) {
  29. html = sel.htmlText;
  30. reg = new RegExp(rule, 'g');
  31. m = html.match(reg);
  32.  
  33. if (m) {
  34. html = html.replace(rule + ';', '').replace(rule, '');
  35.  
  36. sel.pasteHTML('<var id="yui-ie-bs">');
  37.  
  38. p = doc.getElementById('yui-ie-bs');
  39. d = doc.createElement('div');
  40. s = doc.createElement(tag);
  41.  
  42. d.innerHTML = html;
  43. if (p.parentNode !== inst.config.doc.body) {
  44. p = p.parentNode;
  45. }
  46.  
  47. c = d.childNodes;
  48.  
  49. p.parentNode.replaceChild(s, p);
  50.  
  51. Y.each(c, function(f) {
  52. s.appendChild(f);
  53. });
  54. sel.collapse();
  55. if (sel.moveToElementText) {
  56. sel.moveToElementText(s);
  57. }
  58. sel.select();
  59. }
  60. }
  61. this._command(cmd);
  62. };
  63.  
  64. Y.extend(ExecCommand, Y.Base, {
  65. /**
  66. * An internal reference to the keyCode of the last key that was pressed.
  67. * @private
  68. * @property _lastKey
  69. */
  70. _lastKey: null,
  71. /**
  72. * An internal reference to the instance of the frame plugged into.
  73. * @private
  74. * @property _inst
  75. */
  76. _inst: null,
  77. /**
  78. * Execute a command on the frame's document.
  79. * @method command
  80. * @param {String} action The action to perform (bold, italic, fontname)
  81. * @param {String} value The optional value (helvetica)
  82. * @return {Node/NodeList} Should return the Node/Nodelist affected
  83. */
  84. command: function(action, value) {
  85. var fn = ExecCommand.COMMANDS[action];
  86.  
  87. Y.log('execCommand(' + action + '): "' + value + '"', 'info', 'exec-command');
  88. if (fn) {
  89. Y.log('OVERIDE execCommand(' + action + '): "' + value + '"', 'info', 'exec-command');
  90. return fn.call(this, action, value);
  91. } else {
  92. return this._command(action, value);
  93. }
  94. },
  95. /**
  96. * The private version of execCommand that doesn't filter for overrides.
  97. * @private
  98. * @method _command
  99. * @param {String} action The action to perform (bold, italic, fontname)
  100. * @param {String} value The optional value (helvetica)
  101. */
  102. _command: function(action, value) {
  103. var inst = this.getInstance();
  104. try {
  105. try {
  106. inst.config.doc.execCommand('styleWithCSS', null, 1);
  107. } catch (e1) {
  108. try {
  109. inst.config.doc.execCommand('useCSS', null, 0);
  110. } catch (e2) {
  111. }
  112. }
  113. Y.log('Using default browser execCommand(' + action + '): "' + value + '"', 'info', 'exec-command');
  114. inst.config.doc.execCommand(action, null, value);
  115. } catch (e) {
  116. Y.log(e.message, 'warn', 'exec-command');
  117. }
  118. },
  119. /**
  120. * Get's the instance of YUI bound to the parent frame
  121. * @method getInstance
  122. * @return {YUI} The YUI instance bound to the parent frame
  123. */
  124. getInstance: function() {
  125. if (!this._inst) {
  126. this._inst = this.get('host').getInstance();
  127. }
  128. return this._inst;
  129. },
  130. initializer: function() {
  131. Y.mix(this.get('host'), {
  132. execCommand: function(action, value) {
  133. return this.exec.command(action, value);
  134. },
  135. _execCommand: function(action, value) {
  136. return this.exec._command(action, value);
  137. }
  138. });
  139.  
  140. this.get('host').on('dom:keypress', Y.bind(function(e) {
  141. this._lastKey = e.keyCode;
  142. }, this));
  143. },
  144. _wrapContent: function(str, override) {
  145. var useP = (this.getInstance().host.editorPara && !override ? true : false);
  146.  
  147. if (useP) {
  148. str = '<p>' + str + '</p>';
  149. } else {
  150. str = str + '<br>';
  151. }
  152. return str;
  153. }
  154. }, {
  155. /**
  156. * execCommand
  157. * @property NAME
  158. * @static
  159. */
  160. NAME: 'execCommand',
  161. /**
  162. * exec
  163. * @property NS
  164. * @static
  165. */
  166. NS: 'exec',
  167. ATTRS: {
  168. host: {
  169. value: false
  170. }
  171. },
  172. /**
  173. * Static object literal of execCommand overrides
  174. * @property COMMANDS
  175. * @static
  176. */
  177. COMMANDS: {
  178. /**
  179. * Wraps the content with a new element of type (tag)
  180. * @method COMMANDS.wrap
  181. * @static
  182. * @param {String} cmd The command executed: wrap
  183. * @param {String} tag The tag to wrap the selection with
  184. * @return {NodeList} NodeList of the items touched by this command.
  185. */
  186. wrap: function(cmd, tag) {
  187. var inst = this.getInstance();
  188. return (new inst.EditorSelection()).wrapContent(tag);
  189. },
  190. /**
  191. * Inserts the provided HTML at the cursor, should be a single element.
  192. * @method COMMANDS.inserthtml
  193. * @static
  194. * @param {String} cmd The command executed: inserthtml
  195. * @param {String} html The html to insert
  196. * @return {Node} Node instance of the item touched by this command.
  197. */
  198. inserthtml: function(cmd, html) {
  199. var inst = this.getInstance();
  200. if (inst.EditorSelection.hasCursor() || Y.UA.ie) {
  201. return (new inst.EditorSelection()).insertContent(html);
  202. } else {
  203. this._command('inserthtml', html);
  204. }
  205. },
  206. /**
  207. * Inserts the provided HTML at the cursor, and focuses the cursor afterwards.
  208. * @method COMMANDS.insertandfocus
  209. * @static
  210. * @param {String} cmd The command executed: insertandfocus
  211. * @param {String} html The html to insert
  212. * @return {Node} Node instance of the item touched by this command.
  213. */
  214. insertandfocus: function(cmd, html) {
  215. var inst = this.getInstance(), out, sel;
  216. if (inst.EditorSelection.hasCursor()) {
  217. html += inst.EditorSelection.CURSOR;
  218. out = this.command('inserthtml', html);
  219. sel = new inst.EditorSelection();
  220. sel.focusCursor(true, true);
  221. } else {
  222. this.command('inserthtml', html);
  223. }
  224. return out;
  225. },
  226. /**
  227. * Inserts a BR at the current cursor position
  228. * @method COMMANDS.insertbr
  229. * @static
  230. * @param {String} cmd The command executed: insertbr
  231. */
  232. insertbr: function() {
  233. var inst = this.getInstance(),
  234. sel = new inst.EditorSelection(),
  235. html = '<var>|</var>', last = null,
  236. root = inst.EditorSelection.ROOT,
  237. q = (Y.UA.webkit) ? 'span.Apple-style-span,var' : 'var',
  238. insert = function(n) {
  239. var c = inst.Node.create('<br>');
  240. n.insert(c, 'before');
  241. return c;
  242. };
  243.  
  244. if (sel._selection.pasteHTML) {
  245. sel._selection.pasteHTML(html);
  246. } else {
  247. this._command('inserthtml', html);
  248. }
  249.  
  250.  
  251. root.all(q).each(function(n) {
  252. var g = true, s;
  253. if (Y.UA.webkit) {
  254. g = false;
  255. if (n.get('innerHTML') === '|') {
  256. g = true;
  257. }
  258. }
  259. if (g) {
  260. last = insert(n);
  261. if ((!last.previous() || !last.previous().test('br')) && Y.UA.gecko) {
  262. s = last.cloneNode();
  263. last.insert(s, 'after');
  264. last = s;
  265. }
  266. n.remove();
  267. }
  268. });
  269. if (Y.UA.webkit && last) {
  270. insert(last);
  271. sel.selectNode(last);
  272. }
  273. },
  274. /**
  275. * Inserts an image at the cursor position
  276. * @method COMMANDS.insertimage
  277. * @static
  278. * @param {String} cmd The command executed: insertimage
  279. * @param {String} img The url of the image to be inserted
  280. * @return {Node} Node instance of the item touched by this command.
  281. */
  282. insertimage: function(cmd, img) {
  283. return this.command('inserthtml', '<img src="' + img + '">');
  284. },
  285. /**
  286. * Add a class to all of the elements in the selection
  287. * @method COMMANDS.addclass
  288. * @static
  289. * @param {String} cmd The command executed: addclass
  290. * @param {String} cls The className to add
  291. * @return {NodeList} NodeList of the items touched by this command.
  292. */
  293. addclass: function(cmd, cls) {
  294. var inst = this.getInstance();
  295. return (new inst.EditorSelection()).getSelected().addClass(cls);
  296. },
  297. /**
  298. * Remove a class from all of the elements in the selection
  299. * @method COMMANDS.removeclass
  300. * @static
  301. * @param {String} cmd The command executed: removeclass
  302. * @param {String} cls The className to remove
  303. * @return {NodeList} NodeList of the items touched by this command.
  304. */
  305. removeclass: function(cmd, cls) {
  306. var inst = this.getInstance();
  307. return (new inst.EditorSelection()).getSelected().removeClass(cls);
  308. },
  309. /**
  310. * Adds a forecolor to the current selection, or creates a new element and applies it
  311. * @method COMMANDS.forecolor
  312. * @static
  313. * @param {String} cmd The command executed: forecolor
  314. * @param {String} val The color value to apply
  315. * @return {NodeList} NodeList of the items touched by this command.
  316. */
  317. forecolor: function(cmd, val) {
  318. var inst = this.getInstance(),
  319. sel = new inst.EditorSelection(), n;
  320.  
  321. if (!Y.UA.ie) {
  322. this._command('useCSS', false);
  323. }
  324. if (inst.EditorSelection.hasCursor()) {
  325. if (sel.isCollapsed) {
  326. if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === '&nbsp;')) {
  327. sel.anchorNode.setStyle('color', val);
  328. n = sel.anchorNode;
  329. } else {
  330. n = this.command('inserthtml', '<span style="color: ' + val + '">' + inst.EditorSelection.CURSOR + '</span>');
  331. sel.focusCursor(true, true);
  332. }
  333. return n;
  334. } else {
  335. return this._command(cmd, val);
  336. }
  337. } else {
  338. this._command(cmd, val);
  339. }
  340. },
  341. /**
  342. * Adds a background color to the current selection, or creates a new element and applies it
  343. * @method COMMANDS.backcolor
  344. * @static
  345. * @param {String} cmd The command executed: backcolor
  346. * @param {String} val The color value to apply
  347. * @return {NodeList} NodeList of the items touched by this command.
  348. */
  349. backcolor: function(cmd, val) {
  350. var inst = this.getInstance(),
  351. sel = new inst.EditorSelection(), n;
  352.  
  353. if (Y.UA.gecko || Y.UA.opera) {
  354. cmd = 'hilitecolor';
  355. }
  356. if (!Y.UA.ie) {
  357. this._command('useCSS', false);
  358. }
  359. if (inst.EditorSelection.hasCursor()) {
  360. if (sel.isCollapsed) {
  361. if (sel.anchorNode && (sel.anchorNode.get('innerHTML') === '&nbsp;')) {
  362. sel.anchorNode.setStyle('backgroundColor', val);
  363. n = sel.anchorNode;
  364. } else {
  365. n = this.command('inserthtml',
  366. '<span style="background-color: ' + val + '">' + inst.EditorSelection.CURSOR + '</span>');
  367. sel.focusCursor(true, true);
  368. }
  369. return n;
  370. } else {
  371. return this._command(cmd, val);
  372. }
  373. } else {
  374. this._command(cmd, val);
  375. }
  376. },
  377. /**
  378. * Sugar method, calles backcolor
  379. * @method COMMANDS.hilitecolor
  380. * @static
  381. * @param {String} cmd The command executed: backcolor
  382. * @param {String} val The color value to apply
  383. * @return {NodeList} NodeList of the items touched by this command.
  384. */
  385. hilitecolor: function() {
  386. return ExecCommand.COMMANDS.backcolor.apply(this, arguments);
  387. },
  388. /**
  389. * Adds a font name to the current selection, or creates a new element and applies it
  390. * @method COMMANDS.fontname2
  391. * @deprecated
  392. * @static
  393. * @param {String} cmd The command executed: fontname
  394. * @param {String} val The font name to apply
  395. * @return {NodeList} NodeList of the items touched by this command.
  396. */
  397. fontname2: function(cmd, val) {
  398. this._command('fontname', val);
  399. var inst = this.getInstance(),
  400. sel = new inst.EditorSelection();
  401.  
  402. if (sel.isCollapsed && (this._lastKey !== 32)) {
  403. if (sel.anchorNode.test('font')) {
  404. sel.anchorNode.set('face', val);
  405. }
  406. }
  407. },
  408. /**
  409. * Adds a fontsize to the current selection, or creates a new element and applies it
  410. * @method COMMANDS.fontsize2
  411. * @deprecated
  412. * @static
  413. * @param {String} cmd The command executed: fontsize
  414. * @param {String} val The font size to apply
  415. * @return {NodeList} NodeList of the items touched by this command.
  416. */
  417. fontsize2: function(cmd, val) {
  418. this._command('fontsize', val);
  419.  
  420. var inst = this.getInstance(),
  421. sel = new inst.EditorSelection(), p;
  422.  
  423. if (sel.isCollapsed && sel.anchorNode && (this._lastKey !== 32)) {
  424. if (Y.UA.webkit) {
  425. if (sel.anchorNode.getStyle('lineHeight')) {
  426. sel.anchorNode.setStyle('lineHeight', '');
  427. }
  428. }
  429. if (sel.anchorNode.test('font')) {
  430. sel.anchorNode.set('size', val);
  431. } else if (Y.UA.gecko) {
  432. p = sel.anchorNode.ancestor(inst.EditorSelection.DEFAULT_BLOCK_TAG);
  433. if (p) {
  434. p.setStyle('fontSize', '');
  435. }
  436. }
  437. }
  438. },
  439. /**
  440. * Overload for COMMANDS.list
  441. * @method COMMANDS.insertorderedlist
  442. * @static
  443. * @param {String} cmd The command executed: list, ul
  444. */
  445. insertunorderedlist: function() {
  446. this.command('list', 'ul');
  447. },
  448. /**
  449. * Overload for COMMANDS.list
  450. * @method COMMANDS.insertunorderedlist
  451. * @static
  452. * @param {String} cmd The command executed: list, ol
  453. */
  454. insertorderedlist: function() {
  455. this.command('list', 'ol');
  456. },
  457. /**
  458. * Noramlizes lists creation/destruction for IE. All others pass through to native calls
  459. * @method COMMANDS.list
  460. * @static
  461. * @param {String} cmd The command executed: list (not used)
  462. * @param {String} tag The tag to deal with
  463. */
  464. list: function(cmd, tag) {
  465. var inst = this.getInstance(), html, self = this,
  466. /*
  467. The yui3- class name below is not a skinnable class,
  468. it's a utility class used internally by editor and
  469. stripped when completed, calling getClassName on this
  470. is a waste of resources.
  471. */
  472. DIR = 'dir', cls = 'yui3-touched',
  473. dir, range, div, elm, n, str, s, par, list, lis,
  474. useP = (inst.host.editorPara ? true : false), tmp,
  475. sdir, hasPParent, fc,
  476. root = inst.EditorSelection.ROOT,
  477. sel = new inst.EditorSelection();
  478.  
  479. cmd = 'insert' + ((tag === 'ul') ? 'un' : '') + 'orderedlist';
  480.  
  481. if (Y.UA.ie && !sel.isCollapsed) {
  482. range = sel._selection;
  483. html = range.htmlText;
  484. div = inst.Node.create(html) || root;
  485.  
  486. if (div.test('li') || div.one('li')) {
  487. this._command(cmd, null);
  488. return;
  489. }
  490. if (div.test(tag)) {
  491. elm = range.item ? range.item(0) : range.parentElement();
  492. n = inst.one(elm);
  493. lis = n.all('li');
  494.  
  495. str = '<div>';
  496. lis.each(function(l) {
  497. str = self._wrapContent(l.get('innerHTML'));
  498. });
  499. str += '</div>';
  500. s = inst.Node.create(str);
  501. if (n.get('parentNode').test('div')) {
  502. n = n.get('parentNode');
  503. }
  504. if (n && n.hasAttribute(DIR)) {
  505. if (useP) {
  506. s.all('p').setAttribute(DIR, n.getAttribute(DIR));
  507. } else {
  508. s.setAttribute(DIR, n.getAttribute(DIR));
  509. }
  510. }
  511. if (useP) {
  512. n.replace(s.get('innerHTML'));
  513. } else {
  514. n.replace(s);
  515. }
  516. if (range.moveToElementText) {
  517. range.moveToElementText(s._node);
  518. }
  519. range.select();
  520. } else {
  521. par = Y.one(range.parentElement());
  522. if (!par.test(inst.EditorSelection.BLOCKS)) {
  523. par = par.ancestor(inst.EditorSelection.BLOCKS);
  524. }
  525. if (par) {
  526. if (par.hasAttribute(DIR)) {
  527. dir = par.getAttribute(DIR);
  528. }
  529. }
  530. if (html.indexOf('<br>') > -1) {
  531. html = html.split(/<br>/i);
  532. } else {
  533. tmp = inst.Node.create(html),
  534. ps = tmp ? tmp.all('p') : null;
  535.  
  536. if (ps && ps.size()) {
  537. html = [];
  538. ps.each(function(n) {
  539. html.push(n.get('innerHTML'));
  540. });
  541. } else {
  542. html = [html];
  543. }
  544. }
  545. list = '<' + tag + ' id="ie-list">';
  546. Y.each(html, function(v) {
  547. var a = inst.Node.create(v);
  548. if (a && a.test('p')) {
  549. if (a.hasAttribute(DIR)) {
  550. dir = a.getAttribute(DIR);
  551. }
  552. v = a.get('innerHTML');
  553. }
  554. list += '<li>' + v + '</li>';
  555. });
  556. list += '</' + tag + '>';
  557. range.pasteHTML(list);
  558. elm = inst.config.doc.getElementById('ie-list');
  559. elm.id = '';
  560. if (dir) {
  561. elm.setAttribute(DIR, dir);
  562. }
  563. if (range.moveToElementText) {
  564. range.moveToElementText(elm);
  565. }
  566. range.select();
  567. }
  568. } else if (Y.UA.ie) {
  569. par = inst.one(sel._selection.parentElement());
  570. if (par.test('p')) {
  571. if (par && par.hasAttribute(DIR)) {
  572. dir = par.getAttribute(DIR);
  573. }
  574. html = Y.EditorSelection.getText(par);
  575. if (html === '') {
  576. sdir = '';
  577. if (dir) {
  578. sdir = ' dir="' + dir + '"';
  579. }
  580. list = inst.Node.create(Y.Lang.sub('<{tag}{dir}><li></li></{tag}>', { tag: tag, dir: sdir }));
  581. par.replace(list);
  582. sel.selectNode(list.one('li'));
  583. } else {
  584. this._command(cmd, null);
  585. }
  586. } else {
  587. this._command(cmd, null);
  588. }
  589. } else {
  590. root.all(tag).addClass(cls);
  591. if (sel.anchorNode.test(inst.EditorSelection.BLOCKS)) {
  592. par = sel.anchorNode;
  593. } else {
  594. par = sel.anchorNode.ancestor(inst.EditorSelection.BLOCKS);
  595. }
  596. if (!par) { //No parent, find the first block under the anchorNode
  597. par = sel.anchorNode.one(inst.EditorSelection.BLOCKS);
  598. }
  599.  
  600. if (par && par.hasAttribute(DIR)) {
  601. dir = par.getAttribute(DIR);
  602. }
  603. if (par && par.test(tag)) {
  604. hasPParent = par.ancestor('p');
  605. html = inst.Node.create('<div/>');
  606. elm = par.all('li');
  607. elm.each(function(h) {
  608. html.append(self._wrapContent(h.get('innerHTML'), hasPParent));
  609. });
  610. if (dir) {
  611. if (useP) {
  612. html.all('p').setAttribute(DIR, dir);
  613. } else {
  614. html.setAttribute(DIR, dir);
  615. }
  616. }
  617. if (useP) {
  618. html = inst.Node.create(html.get('innerHTML'));
  619. }
  620. fc = html.get('firstChild');
  621. par.replace(html);
  622. sel.selectNode(fc);
  623. } else {
  624. this._command(cmd, null);
  625. }
  626. list = root.all(tag);
  627. if (dir) {
  628. if (list.size()) {
  629. //Changed to a List
  630. list.each(function(n) {
  631. if (!n.hasClass(cls)) {
  632. n.setAttribute(DIR, dir);
  633. }
  634. });
  635. }
  636. }
  637.  
  638. list.removeClass(cls);
  639. }
  640. },
  641. /**
  642. * Noramlizes alignment for Webkit Browsers
  643. * @method COMMANDS.justify
  644. * @static
  645. * @param {String} cmd The command executed: justify (not used)
  646. * @param {String} val The actual command from the justify{center,all,left,right} stubs
  647. */
  648. justify: function(cmd, val) {
  649. if (Y.UA.webkit) {
  650. var inst = this.getInstance(),
  651. sel = new inst.EditorSelection(),
  652. aNode = sel.anchorNode, html,
  653. bgColor = aNode.getStyle('backgroundColor');
  654.  
  655. this._command(val);
  656. sel = new inst.EditorSelection();
  657. if (sel.anchorNode.test('div')) {
  658. html = '<span>' + sel.anchorNode.get('innerHTML') + '</span>';
  659. sel.anchorNode.set('innerHTML', html);
  660. sel.anchorNode.one('span').setStyle('backgroundColor', bgColor);
  661. sel.selectNode(sel.anchorNode.one('span'));
  662. }
  663. } else {
  664. this._command(val);
  665. }
  666. },
  667. /**
  668. * Override method for COMMANDS.justify
  669. * @method COMMANDS.justifycenter
  670. * @static
  671. */
  672. justifycenter: function() {
  673. this.command('justify', 'justifycenter');
  674. },
  675. /**
  676. * Override method for COMMANDS.justify
  677. * @method COMMANDS.justifyleft
  678. * @static
  679. */
  680. justifyleft: function() {
  681. this.command('justify', 'justifyleft');
  682. },
  683. /**
  684. * Override method for COMMANDS.justify
  685. * @method COMMANDS.justifyright
  686. * @static
  687. */
  688. justifyright: function() {
  689. this.command('justify', 'justifyright');
  690. },
  691. /**
  692. * Override method for COMMANDS.justify
  693. * @method COMMANDS.justifyfull
  694. * @static
  695. */
  696. justifyfull: function() {
  697. this.command('justify', 'justifyfull');
  698. }
  699. }
  700. });
  701.  
  702. if (Y.UA.ie) {
  703. ExecCommand.COMMANDS.bold = function() {
  704. fixIETags.call(this, 'bold', 'b', 'FONT-WEIGHT: bold');
  705. };
  706. ExecCommand.COMMANDS.italic = function() {
  707. fixIETags.call(this, 'italic', 'i', 'FONT-STYLE: italic');
  708. };
  709. ExecCommand.COMMANDS.underline = function() {
  710. fixIETags.call(this, 'underline', 'u', 'TEXT-DECORATION: underline');
  711. };
  712. }
  713.  
  714. Y.namespace('Plugin');
  715. Y.Plugin.ExecCommand = ExecCommand;
  716.  
  717.