The version of vichan running on lainchan.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1320 lines
26KB

  1. /**
  2. * SCEditor XHTML Plugin
  3. * http://www.sceditor.com/
  4. *
  5. * Copyright (C) 2011-2013, Sam Clarke (samclarke.com)
  6. *
  7. * SCEditor is licensed under the MIT license:
  8. * http://www.opensource.org/licenses/mit-license.php
  9. *
  10. * @author Sam Clarke
  11. * @requires jQuery
  12. */
  13. /*global prompt: true*/
  14. (function ($) {
  15. 'use strict';
  16. var SCEditor = $.sceditor;
  17. var sceditorPlugins = SCEditor.plugins;
  18. var dom = SCEditor.dom;
  19. var defaultCommandsOverrides = {
  20. bold: {
  21. txtExec: [
  22. '<strong>',
  23. '</strong>'
  24. ]
  25. },
  26. italic: {
  27. txtExec: [
  28. '<em>',
  29. '</em>'
  30. ]
  31. },
  32. underline: {
  33. txtExec: [
  34. '<span style="text-decoration: underline;">',
  35. '</span>'
  36. ]
  37. },
  38. strike: {
  39. txtExec: [
  40. '<span style="text-decoration: line-through;">',
  41. '</span>'
  42. ]
  43. },
  44. subscript: {
  45. txtExec: [
  46. '<sub>',
  47. '</sub>'
  48. ]
  49. },
  50. superscript: {
  51. txtExec: [
  52. '<sup>',
  53. '</sup>'
  54. ]
  55. },
  56. left: {
  57. txtExec: [
  58. '<div style="text-align: left;">',
  59. '</div>'
  60. ]
  61. },
  62. center: {
  63. txtExec: [
  64. '<div style="text-align: center;">',
  65. '</div>'
  66. ]
  67. },
  68. right: {
  69. txtExec: [
  70. '<div style="text-align: right;">',
  71. '</div>'
  72. ]
  73. },
  74. justify: {
  75. txtExec: [
  76. '<div style="text-align: justify;">',
  77. '</div>'
  78. ]
  79. },
  80. font: {
  81. txtExec: function (caller) {
  82. var editor = this;
  83. SCEditor.command.get('font')._dropDown(
  84. editor,
  85. caller,
  86. function (fontName) {
  87. editor.insertText('<span style="font-family: ' +
  88. fontName + ';">', '</span>');
  89. }
  90. );
  91. }
  92. },
  93. size: {
  94. txtExec: function (caller) {
  95. var editor = this;
  96. SCEditor.command.get('size')._dropDown(
  97. editor,
  98. caller,
  99. function (fontSize) {
  100. editor.insertText('<span style="font-size: ' +
  101. fontSize + ';">', '</span>');
  102. }
  103. );
  104. }
  105. },
  106. color: {
  107. txtExec: function (caller) {
  108. var editor = this;
  109. SCEditor.command.get('color')._dropDown(
  110. editor,
  111. caller,
  112. function (color) {
  113. editor.insertText('<span style="color: ' +
  114. color + ';">', '</span>');
  115. }
  116. );
  117. }
  118. },
  119. bulletlist: {
  120. txtExec: [
  121. '<ul><li>',
  122. '</li></ul>'
  123. ]
  124. },
  125. orderedlist: {
  126. txtExec: [
  127. '<ol><li>',
  128. '</li></ol>'
  129. ]
  130. },
  131. table: {
  132. txtExec: [
  133. '<table><tr><td>',
  134. '</td></tr></table>'
  135. ]
  136. },
  137. horizontalrule: {
  138. txtExec: [
  139. '<hr />'
  140. ]
  141. },
  142. code: {
  143. txtExec: [
  144. '<code>',
  145. '</code>'
  146. ]
  147. },
  148. image: {
  149. txtExec: function (caller, selected) {
  150. var url = prompt(this._('Enter the image URL:'), selected);
  151. if (url) {
  152. this.insertText('<img src="' + url + '" />');
  153. }
  154. }
  155. },
  156. email: {
  157. txtExec: function (caller, sel) {
  158. var email, text,
  159. display = sel && sel.indexOf('@') > -1 ? null : sel;
  160. email = prompt(
  161. this._('Enter the e-mail address:'),
  162. (display ? '' : sel)
  163. );
  164. text = prompt(
  165. this._('Enter the displayed text:'),
  166. display || email
  167. ) || email;
  168. if (email) {
  169. this.insertText(
  170. '<a href="mailto:' + email + '">' + text + '</a>'
  171. );
  172. }
  173. }
  174. },
  175. link: {
  176. txtExec: function (caller, sel) {
  177. var display = sel && sel.indexOf('http://') > -1 ? null : sel,
  178. url = prompt(this._('Enter URL:'),
  179. (display ? 'http://' : sel)),
  180. text = prompt(this._('Enter the displayed text:'),
  181. display || url) || url;
  182. if (url) {
  183. this.insertText(
  184. '<a href="' + url + '">' + text + '</a>'
  185. );
  186. }
  187. }
  188. },
  189. quote: {
  190. txtExec: [
  191. '<blockquote>',
  192. '</blockquote>'
  193. ]
  194. },
  195. youtube: {
  196. txtExec: function (caller) {
  197. var editor = this;
  198. SCEditor.command.get('youtube')._dropDown(
  199. editor,
  200. caller,
  201. function (id) {
  202. editor.insertText(
  203. '<iframe width="560" height="315" ' +
  204. 'src="https://www.youtube.com/embed/{id}?' +
  205. 'wmode=opaque" data-youtube-id="' + id + '" ' +
  206. 'frameborder="0" allowfullscreen></iframe>'
  207. );
  208. }
  209. );
  210. }
  211. },
  212. rtl: {
  213. txtExec: [
  214. '<div stlye="direction: rtl;">',
  215. '</div>'
  216. ]
  217. },
  218. ltr: {
  219. txtExec: [
  220. '<div stlye="direction: ltr;">',
  221. '</div>'
  222. ]
  223. }
  224. };
  225. /**
  226. * XHTMLSerializer part of the XHTML plugin.
  227. *
  228. * @class XHTMLSerializer
  229. * @name jQuery.sceditor.XHTMLSerializer
  230. * @since v1.4.1
  231. */
  232. SCEditor.XHTMLSerializer = function () {
  233. var base = this;
  234. var opts = {
  235. indentStr: '\t'
  236. };
  237. /**
  238. * Array containing the output, used as it's faster
  239. * than string concatenation in slow browsers.
  240. * @type {Array}
  241. * @private
  242. */
  243. var outputStringBuilder = [];
  244. /**
  245. * Current indention level
  246. * @type {Number}
  247. * @private
  248. */
  249. var currentIndent = 0;
  250. /**
  251. * @private
  252. */
  253. var escapeEntites,
  254. trim,
  255. serializeNode,
  256. handleDoc,
  257. handleElement,
  258. handleCdata,
  259. handleComment,
  260. handleText,
  261. output,
  262. canIndent;
  263. // TODO: use escape.entities
  264. /**
  265. * Escapes XHTML entities
  266. *
  267. * @param {String} str
  268. * @return {String}
  269. * @private
  270. */
  271. escapeEntites = function (str) {
  272. var entites = {
  273. '&': '&amp;',
  274. '<': '&lt;',
  275. '>': '&gt;',
  276. '"': '&quot;'
  277. };
  278. return !str ? '' : str.replace(/[&<>"]/g, function (entity) {
  279. return entites[entity] || entity;
  280. });
  281. };
  282. /**
  283. * @param {string} str
  284. * @return {string}
  285. * @private
  286. */
  287. trim = function (str) {
  288. return str
  289. // New lines will be shown as spaces so just convert to spaces.
  290. .replace(/[\r\n]/, ' ')
  291. .replace(/[^\S|\u00A0]+/g, ' ');
  292. };
  293. /**
  294. * Serializes a node to XHTML
  295. *
  296. * @param {Node} node Node to serialize
  297. * @param {Boolean} onlyChildren If to only serialize the nodes
  298. * children and not the node
  299. * itself
  300. * @return {String} The serialized node
  301. * @name serialize
  302. * @memberOf jQuery.sceditor.XHTMLSerializer.prototype
  303. * @since v1.4.1
  304. */
  305. base.serialize = function (node, onlyChildren) {
  306. outputStringBuilder = [];
  307. if (onlyChildren) {
  308. node = node.firstChild;
  309. while (node) {
  310. serializeNode(node);
  311. node = node.nextSibling;
  312. }
  313. } else {
  314. serializeNode(node);
  315. }
  316. return outputStringBuilder.join('');
  317. };
  318. /**
  319. * Serializes a node to the outputStringBuilder
  320. *
  321. * @param {Node} node
  322. * @return {Void}
  323. * @private
  324. */
  325. serializeNode = function (node, parentIsPre) {
  326. switch (node.nodeType) {
  327. case 1: // element
  328. var tagName = node.nodeName.toLowerCase();
  329. // IE comment
  330. if (tagName === '!') {
  331. handleComment(node);
  332. } else {
  333. handleElement(node, parentIsPre);
  334. }
  335. break;
  336. case 3: // text
  337. handleText(node, parentIsPre);
  338. break;
  339. case 4: // cdata section
  340. handleCdata(node);
  341. break;
  342. case 8: // comment
  343. handleComment(node);
  344. break;
  345. case 9: // document
  346. case 11: // document fragment
  347. handleDoc(node);
  348. break;
  349. // Ignored types
  350. case 2: // attribute
  351. case 5: // entity ref
  352. case 6: // entity
  353. case 7: // processing instruction
  354. case 10: // document type
  355. case 12: // notation
  356. break;
  357. }
  358. };
  359. /**
  360. * Handles doc node
  361. * @param {Node} node
  362. * @return {void}
  363. * @private
  364. */
  365. handleDoc = function (node) {
  366. var child = node.firstChild;
  367. while (child) {
  368. serializeNode(child);
  369. child = child.nextSibling;
  370. }
  371. };
  372. /**
  373. * Handles element nodes
  374. * @param {Node} node
  375. * @return {void}
  376. * @private
  377. */
  378. handleElement = function (node, parentIsPre) {
  379. var child, attr, attrValue,
  380. tagName = node.nodeName.toLowerCase(),
  381. isIframe = tagName === 'iframe',
  382. attrIdx = node.attributes.length,
  383. firstChild = node.firstChild,
  384. // pre || pre-wrap with any vendor prefix
  385. isPre = parentIsPre ||
  386. /pre(?:\-wrap)?$/i.test($(node).css('whiteSpace')),
  387. selfClosing = !node.firstChild && !dom.canHaveChildren(node) &&
  388. !isIframe;
  389. if ($(node).hasClass('sceditor-ignore')) {
  390. return;
  391. }
  392. output('<' + tagName, !parentIsPre && canIndent(node));
  393. while (attrIdx--) {
  394. attr = node.attributes[attrIdx];
  395. // IE < 8 returns all possible attributes not just specified
  396. // ones. IE < 8 also doesn't say value on input is specified
  397. // so just assume it is.
  398. if (!SCEditor.ie || attr.specified ||
  399. (tagName === 'input' && attr.name === 'value')) {
  400. // IE < 8 doesn't return the CSS for the style attribute
  401. if (SCEditor.ie < 8 && /style/i.test(attr.name)) {
  402. attrValue = node.style.cssText;
  403. } else {
  404. attrValue = attr.value;
  405. }
  406. output(' ' + attr.name.toLowerCase() + '="' +
  407. escapeEntites(attrValue) + '"', false);
  408. }
  409. }
  410. output(selfClosing ? ' />' : '>', false);
  411. if (!isIframe) {
  412. child = firstChild;
  413. }
  414. while (child) {
  415. currentIndent++;
  416. serializeNode(child, isPre);
  417. child = child.nextSibling;
  418. currentIndent--;
  419. }
  420. if (!selfClosing) {
  421. output(
  422. '</' + tagName + '>',
  423. !isPre && !isIframe && canIndent(node) &&
  424. firstChild && canIndent(firstChild)
  425. );
  426. }
  427. };
  428. /**
  429. * Handles CDATA nodes
  430. * @param {Node} node
  431. * @return {void}
  432. * @private
  433. */
  434. handleCdata = function (node) {
  435. output('<![CDATA[' + escapeEntites(node.nodeValue) + ']]>');
  436. };
  437. /**
  438. * Handles comment nodes
  439. * @param {Node} node
  440. * @return {void}
  441. * @private
  442. */
  443. handleComment = function (node) {
  444. output('<!-- ' + escapeEntites(node.nodeValue) + ' -->');
  445. };
  446. /**
  447. * Handles text nodes
  448. * @param {Node} node
  449. * @return {void}
  450. * @private
  451. */
  452. handleText = function (node, parentIsPre) {
  453. var text = node.nodeValue;
  454. if (!parentIsPre) {
  455. text = trim(text);
  456. }
  457. if (text) {
  458. output(escapeEntites(text), !parentIsPre && canIndent(node));
  459. }
  460. };
  461. /**
  462. * Adds a string to the outputStringBuilder.
  463. *
  464. * The string will be indented unless indent is set to boolean false.
  465. * @param {String} str
  466. * @param {Boolean} indent
  467. * @return {void}
  468. * @private
  469. */
  470. output = function (str, indent) {
  471. var i = currentIndent;
  472. if (indent !== false) {
  473. // Don't add a new line if it's the first element
  474. if (outputStringBuilder.length) {
  475. outputStringBuilder.push('\n');
  476. }
  477. while (i--) {
  478. outputStringBuilder.push(opts.indentStr);
  479. }
  480. }
  481. outputStringBuilder.push(str);
  482. };
  483. /**
  484. * Checks if should indent the node or not
  485. * @param {Node} node
  486. * @return {boolean}
  487. * @private
  488. */
  489. canIndent = function (node) {
  490. var prev = node.previousSibling;
  491. if (node.nodeType !== 1 && prev) {
  492. return !dom.isInline(prev);
  493. }
  494. // first child of a block element
  495. if (!prev && !dom.isInline(node.parentNode)) {
  496. return true;
  497. }
  498. return !dom.isInline(node);
  499. };
  500. };
  501. /**
  502. * SCEditor XHTML plugin
  503. * @class xhtml
  504. * @name jQuery.sceditor.plugins.xhtml
  505. * @since v1.4.1
  506. */
  507. sceditorPlugins.xhtml = function () {
  508. var base = this;
  509. /**
  510. * Tag converstions cache
  511. * @type {Object}
  512. * @private
  513. */
  514. var tagConvertersCache = {};
  515. /**
  516. * Attributes filter cache
  517. * @type {Object}
  518. * @private
  519. */
  520. var attrsCache = {};
  521. /**
  522. * Private methods
  523. * @private
  524. */
  525. var convertTags,
  526. convertNode,
  527. isEmpty,
  528. removeTags,
  529. mergeAttribsFilters,
  530. removeAttribs,
  531. wrapInlines;
  532. /**
  533. * Init
  534. * @return {void}
  535. */
  536. base.init = function () {
  537. if (!$.isEmptyObject(sceditorPlugins.xhtml.converters || {})) {
  538. $.each(
  539. sceditorPlugins.xhtml.converters,
  540. function (idx, converter) {
  541. $.each(converter.tags, function (tagname) {
  542. if (!tagConvertersCache[tagname]) {
  543. tagConvertersCache[tagname] = [];
  544. }
  545. tagConvertersCache[tagname].push(converter);
  546. });
  547. }
  548. );
  549. }
  550. this.commands = $.extend(true,
  551. {}, defaultCommandsOverrides, this.commands);
  552. };
  553. /**
  554. * Converts the WYSIWYG content to XHTML
  555. * @param {String} html
  556. * @param {Node} domBody
  557. * @return {String}
  558. * @memberOf jQuery.sceditor.plugins.xhtml.prototype
  559. */
  560. base.signalToSource = function (html, domBody) {
  561. domBody = domBody.jquery ? domBody[0] : domBody;
  562. convertTags(domBody);
  563. removeTags(domBody);
  564. removeAttribs(domBody);
  565. wrapInlines(domBody);
  566. return (new SCEditor.XHTMLSerializer()).serialize(domBody, true);
  567. };
  568. /**
  569. * Converts the XHTML to WYSIWYG content.
  570. *
  571. * This doesn't currently do anything as XHTML
  572. * is valid WYSIWYG content.
  573. * @param {String} text
  574. * @return {String}
  575. * @memberOf jQuery.sceditor.plugins.xhtml.prototype
  576. */
  577. base.signalToWysiwyg = function (text) {
  578. return text;
  579. };
  580. /**
  581. * Deprecated, use dom.convertElement() instead.
  582. * @deprecated
  583. */
  584. base.convertTagTo = dom.convertElement;
  585. /**
  586. * Runs all converters for the specified tagName
  587. * against the DOM node.
  588. * @param {String} tagName
  589. * @param {jQuery} $node
  590. * @return {Node} node
  591. * @private
  592. */
  593. convertNode = function (tagName, $node, node) {
  594. if (!tagConvertersCache[tagName]) {
  595. return;
  596. }
  597. $.each(tagConvertersCache[tagName], function (idx, converter) {
  598. if (converter.tags[tagName]) {
  599. $.each(converter.tags[tagName], function (attr, values) {
  600. if (!node.getAttributeNode) {
  601. return;
  602. }
  603. attr = node.getAttributeNode(attr);
  604. // IE < 8 always returns an attribute regardless of if
  605. // it has been specified so must check it.
  606. if (!attr || (SCEditor.ie < 8 && !attr.specified)) {
  607. return;
  608. }
  609. if (values && $.inArray(attr.value, values) < 0) {
  610. return;
  611. }
  612. converter.conv.call(base, node, $node);
  613. });
  614. } else if (converter.conv) {
  615. converter.conv.call(base, node, $node);
  616. }
  617. });
  618. };
  619. /**
  620. * Converts any tags/attributes to their XHTML equivalents
  621. * @param {Node} node
  622. * @return {Void}
  623. * @private
  624. */
  625. convertTags = function (node) {
  626. dom.traverse(node, function (node) {
  627. var $node = $(node),
  628. tagName = node.nodeName.toLowerCase();
  629. convertNode('*', $node, node);
  630. convertNode(tagName, $node, node);
  631. }, true);
  632. };
  633. /**
  634. * Tests if a node is empty and can be removed.
  635. *
  636. * @param {Node} node
  637. * @return {Boolean}
  638. * @private
  639. */
  640. isEmpty = function (node, excludeBr) {
  641. var childNodes = node.childNodes,
  642. tagName = node.nodeName.toLowerCase(),
  643. nodeValue = node.nodeValue,
  644. childrenLength = childNodes.length;
  645. if (excludeBr && tagName === 'br') {
  646. return true;
  647. }
  648. if ($(node).hasClass('sceditor-ignore')) {
  649. return true;
  650. }
  651. if (!dom.canHaveChildren(node)) {
  652. return false;
  653. }
  654. // \S|\u00A0 = any non space char
  655. if (nodeValue && /\S|\u00A0/.test(nodeValue)) {
  656. return false;
  657. }
  658. while (childrenLength--) {
  659. if (!isEmpty(childNodes[childrenLength],
  660. excludeBr && !node.previousSibling && !node.nextSibling)) {
  661. return false;
  662. }
  663. }
  664. return true;
  665. };
  666. /**
  667. * Removes any tags that are not white listed or if no
  668. * tags are white listed it will remove any tags that
  669. * are black listed.
  670. *
  671. * @param {Node} rootNode
  672. * @return {Void}
  673. * @private
  674. */
  675. removeTags = function (rootNode) {
  676. dom.traverse(rootNode, function (node) {
  677. var remove,
  678. tagName = node.nodeName.toLowerCase(),
  679. parentNode = node.parentNode,
  680. nodeType = node.nodeType,
  681. isBlock = !dom.isInline(node),
  682. previousSibling = node.previousSibling,
  683. nextSibling = node.nextSibling,
  684. isTopLevel = parentNode === rootNode,
  685. noSiblings = !previousSibling && !nextSibling,
  686. empty = tagName !== 'iframe' && isEmpty(node,
  687. isTopLevel && noSiblings && tagName !== 'br'),
  688. document = node.ownerDocument,
  689. allowedtags = sceditorPlugins.xhtml.allowedTags,
  690. disallowedTags = sceditorPlugins.xhtml.disallowedTags;
  691. // 3 = text node
  692. if (nodeType === 3) {
  693. return;
  694. }
  695. if (nodeType === 4) {
  696. tagName = '!cdata';
  697. } else if (tagName === '!' || nodeType === 8) {
  698. tagName = '!comment';
  699. }
  700. if (empty) {
  701. remove = true;
  702. // 3 is text node which do not get filtered
  703. } else if (allowedtags && allowedtags.length) {
  704. remove = ($.inArray(tagName, allowedtags) < 0);
  705. } else if (disallowedTags && disallowedTags.length) {
  706. remove = ($.inArray(tagName, disallowedTags) > -1);
  707. }
  708. if (remove) {
  709. if (!empty) {
  710. if (isBlock && previousSibling &&
  711. dom.isInline(previousSibling)) {
  712. parentNode.insertBefore(
  713. document.createTextNode(' '), node);
  714. }
  715. // Insert all the childen after node
  716. while (node.firstChild) {
  717. parentNode.insertBefore(node.firstChild,
  718. nextSibling);
  719. }
  720. if (isBlock && nextSibling &&
  721. dom.isInline(nextSibling)) {
  722. parentNode.insertBefore(
  723. document.createTextNode(' '), nextSibling);
  724. }
  725. }
  726. parentNode.removeChild(node);
  727. }
  728. }, true);
  729. };
  730. /**
  731. * Merges two sets of attribute filters into one
  732. *
  733. * @param {Object} filtersA
  734. * @param {Object} filtersB
  735. * @return {Object}
  736. * @private
  737. */
  738. mergeAttribsFilters = function (filtersA, filtersB) {
  739. var ret = {};
  740. if (filtersA) {
  741. $.extend(ret, filtersA);
  742. }
  743. if (!filtersB) {
  744. return ret;
  745. }
  746. $.each(filtersB, function (attrName, values) {
  747. if ($.isArray(values)) {
  748. ret[attrName] = $.merge(ret[attrName] || [], values);
  749. } else if (!ret[attrName]) {
  750. ret[attrName] = null;
  751. }
  752. });
  753. return ret;
  754. };
  755. /**
  756. * Wraps adjacent inline child nodes of root
  757. * in paragraphs.
  758. *
  759. * @param {Node} root
  760. * @private
  761. */
  762. wrapInlines = function (root) {
  763. var adjacentInlines = [];
  764. var wrapAdjacents = function () {
  765. if (adjacentInlines.length) {
  766. $('<p>', root.ownerDocument)
  767. .insertBefore(adjacentInlines[0])
  768. .append(adjacentInlines);
  769. adjacentInlines = [];
  770. }
  771. };
  772. // Strip empty text nodes so they don't get wrapped.
  773. dom.removeWhiteSpace(root);
  774. var node = root.firstChild;
  775. while (node) {
  776. if (dom.isInline(node) && !$(node).is('.sceditor-ignore')) {
  777. adjacentInlines.push(node);
  778. } else {
  779. wrapAdjacents();
  780. }
  781. node = node.nextSibling;
  782. }
  783. wrapAdjacents();
  784. };
  785. /**
  786. * Removes any attributes that are not white listed or
  787. * if no attributes are white listed it will remove
  788. * any attributes that are black listed.
  789. * @param {Node} node
  790. * @return {Void}
  791. * @private
  792. */
  793. removeAttribs = function (node) {
  794. var tagName, attr, attrName, attrsLength, validValues, remove,
  795. allowedAttribs = sceditorPlugins.xhtml.allowedAttribs,
  796. isAllowed = allowedAttribs &&
  797. !$.isEmptyObject(allowedAttribs),
  798. disallowedAttribs = sceditorPlugins.xhtml.disallowedAttribs,
  799. isDisallowed = disallowedAttribs &&
  800. !$.isEmptyObject(disallowedAttribs);
  801. attrsCache = {};
  802. dom.traverse(node, function (node) {
  803. if (!node.attributes) {
  804. return;
  805. }
  806. tagName = node.nodeName.toLowerCase();
  807. attrsLength = node.attributes.length;
  808. if (attrsLength) {
  809. if (!attrsCache[tagName]) {
  810. if (isAllowed) {
  811. attrsCache[tagName] = mergeAttribsFilters(
  812. allowedAttribs['*'],
  813. allowedAttribs[tagName]
  814. );
  815. } else {
  816. attrsCache[tagName] = mergeAttribsFilters(
  817. disallowedAttribs['*'],
  818. disallowedAttribs[tagName]
  819. );
  820. }
  821. }
  822. while (attrsLength--) {
  823. attr = node.attributes[attrsLength];
  824. attrName = attr.name;
  825. validValues = attrsCache[tagName][attrName];
  826. remove = false;
  827. if (isAllowed) {
  828. remove = validValues !== null &&
  829. (!$.isArray(validValues) ||
  830. $.inArray(attr.value, validValues) < 0);
  831. } else if (isDisallowed) {
  832. remove = validValues === null ||
  833. ($.isArray(validValues) &&
  834. $.inArray(attr.value, validValues) > -1);
  835. }
  836. if (remove) {
  837. node.removeAttribute(attrName);
  838. }
  839. }
  840. }
  841. });
  842. };
  843. };
  844. /**
  845. * Tag conveters, a converter is applied to all
  846. * tags that match the criteria.
  847. * @type {Array}
  848. * @name jQuery.sceditor.plugins.xhtml.converters
  849. * @since v1.4.1
  850. */
  851. sceditorPlugins.xhtml.converters = [
  852. {
  853. tags: {
  854. '*': {
  855. width: null
  856. }
  857. },
  858. conv: function (node, $node) {
  859. $node.css('width', $node.attr('width')).removeAttr('width');
  860. }
  861. },
  862. {
  863. tags: {
  864. '*': {
  865. height: null
  866. }
  867. },
  868. conv: function (node, $node) {
  869. $node.css('height', $node.attr('height')).removeAttr('height');
  870. }
  871. },
  872. {
  873. tags: {
  874. 'li': {
  875. value: null
  876. }
  877. },
  878. conv: function (node, $node) {
  879. if (SCEditor.ie < 8) {
  880. node.removeAttribute('value');
  881. } else {
  882. $node.removeAttr('value');
  883. }
  884. }
  885. },
  886. {
  887. tags: {
  888. '*': {
  889. text: null
  890. }
  891. },
  892. conv: function (node, $node) {
  893. $node.css('color', $node.attr('text')).removeAttr('text');
  894. }
  895. },
  896. {
  897. tags: {
  898. '*': {
  899. color: null
  900. }
  901. },
  902. conv: function (node, $node) {
  903. $node.css('color', $node.attr('color')).removeAttr('color');
  904. }
  905. },
  906. {
  907. tags: {
  908. '*': {
  909. face: null
  910. }
  911. },
  912. conv: function (node, $node) {
  913. $node.css('fontFamily', $node.attr('face')).removeAttr('face');
  914. }
  915. },
  916. {
  917. tags: {
  918. '*': {
  919. align: null
  920. }
  921. },
  922. conv: function (node, $node) {
  923. $node.css('textAlign', $node.attr('align')).removeAttr('align');
  924. }
  925. },
  926. {
  927. tags: {
  928. '*': {
  929. border: null
  930. }
  931. },
  932. conv: function (node, $node) {
  933. $node
  934. .css('borderWidth',$node.attr('border'))
  935. .removeAttr('border');
  936. }
  937. },
  938. {
  939. tags: {
  940. applet: {
  941. name: null
  942. },
  943. img: {
  944. name: null
  945. },
  946. layer: {
  947. name: null
  948. },
  949. map: {
  950. name: null
  951. },
  952. object: {
  953. name: null
  954. },
  955. param: {
  956. name: null
  957. }
  958. },
  959. conv: function (node, $node) {
  960. if (!$node.attr('id')) {
  961. $node.attr('id', $node.attr('name'));
  962. }
  963. $node.removeAttr('name');
  964. }
  965. },
  966. {
  967. tags: {
  968. '*': {
  969. vspace: null
  970. }
  971. },
  972. conv: function (node, $node) {
  973. $node
  974. .css('marginTop', $node.attr('vspace') - 0)
  975. .css('marginBottom', $node.attr('vspace') - 0)
  976. .removeAttr('vspace');
  977. }
  978. },
  979. {
  980. tags: {
  981. '*': {
  982. hspace: null
  983. }
  984. },
  985. conv: function (node, $node) {
  986. $node
  987. .css('marginLeft', $node.attr('hspace') - 0)
  988. .css('marginRight', $node.attr('hspace') - 0)
  989. .removeAttr('hspace');
  990. }
  991. },
  992. {
  993. tags: {
  994. 'hr': {
  995. noshade: null
  996. }
  997. },
  998. conv: function (node, $node) {
  999. $node.css('borderStyle', 'solid').removeAttr('noshade');
  1000. }
  1001. },
  1002. {
  1003. tags: {
  1004. '*': {
  1005. nowrap: null
  1006. }
  1007. },
  1008. conv: function (node, $node) {
  1009. $node.css('white-space', 'nowrap').removeAttr('nowrap');
  1010. }
  1011. },
  1012. {
  1013. tags: {
  1014. big: null
  1015. },
  1016. conv: function (node) {
  1017. $(this.convertTagTo(node, 'span')).css('fontSize', 'larger');
  1018. }
  1019. },
  1020. {
  1021. tags: {
  1022. small: null
  1023. },
  1024. conv: function (node) {
  1025. $(this.convertTagTo(node, 'span')).css('fontSize', 'smaller');
  1026. }
  1027. },
  1028. {
  1029. tags: {
  1030. b: null
  1031. },
  1032. conv: function (node) {
  1033. $(this.convertTagTo(node, 'strong'));
  1034. }
  1035. },
  1036. {
  1037. tags: {
  1038. u: null
  1039. },
  1040. conv: function (node) {
  1041. $(this.convertTagTo(node, 'span'))
  1042. .css('textDecoration', 'underline');
  1043. }
  1044. },
  1045. {
  1046. tags: {
  1047. i: null
  1048. },
  1049. conv: function (node) {
  1050. $(this.convertTagTo(node, 'em'));
  1051. }
  1052. },
  1053. {
  1054. tags: {
  1055. s: null,
  1056. strike: null
  1057. },
  1058. conv: function (node) {
  1059. $(this.convertTagTo(node, 'span'))
  1060. .css('textDecoration', 'line-through');
  1061. }
  1062. },
  1063. {
  1064. tags: {
  1065. dir: null
  1066. },
  1067. conv: function (node) {
  1068. this.convertTagTo(node, 'ul');
  1069. }
  1070. },
  1071. {
  1072. tags: {
  1073. center: null
  1074. },
  1075. conv: function (node) {
  1076. $(this.convertTagTo(node, 'div'))
  1077. .css('textAlign', 'center');
  1078. }
  1079. },
  1080. {
  1081. tags: {
  1082. font: {
  1083. size: null
  1084. }
  1085. },
  1086. conv: function (node, $node) {
  1087. var size = $node.css('fontSize'),
  1088. fontSize = size;
  1089. // IE < 8 sets a font tag with no size to +0 so
  1090. // should just skip it.
  1091. if (fontSize !== '+0') {
  1092. // IE 8 and below incorrectly returns the value of the size
  1093. // attribute instead of the px value so must convert it
  1094. if (SCEditor.ie < 9) {
  1095. fontSize = 10;
  1096. if (size > 1) {
  1097. fontSize = 13;
  1098. }
  1099. if (size > 2) {
  1100. fontSize = 16;
  1101. }
  1102. if (size > 3) {
  1103. fontSize = 18;
  1104. }
  1105. if (size > 4) {
  1106. fontSize = 24;
  1107. }
  1108. if (size > 5) {
  1109. fontSize = 32;
  1110. }
  1111. if (size > 6) {
  1112. fontSize = 48;
  1113. }
  1114. }
  1115. $node.css('fontSize', fontSize);
  1116. }
  1117. $node.removeAttr('size');
  1118. }
  1119. },
  1120. {
  1121. tags: {
  1122. font: null
  1123. },
  1124. conv: function (node) {
  1125. // All it's attributes will be converted
  1126. // by the attribute converters
  1127. this.convertTagTo(node, 'span');
  1128. }
  1129. },
  1130. {
  1131. tags: {
  1132. '*': {
  1133. type: ['_moz']
  1134. }
  1135. },
  1136. conv: function (node, $node) {
  1137. $node.removeAttr('type');
  1138. }
  1139. },
  1140. {
  1141. tags: {
  1142. '*': {
  1143. '_moz_dirty': null
  1144. }
  1145. },
  1146. conv: function (node, $node) {
  1147. $node.removeAttr('_moz_dirty');
  1148. }
  1149. },
  1150. {
  1151. tags: {
  1152. '*': {
  1153. '_moz_editor_bogus_node': null
  1154. }
  1155. },
  1156. conv: function (node, $node) {
  1157. $node.remove();
  1158. }
  1159. }
  1160. ];
  1161. /**
  1162. * Allowed attributes map.
  1163. *
  1164. * To allow an attribute for all tags use * as the tag name.
  1165. *
  1166. * Leave empty or null to allow all attributes. (the disallow
  1167. * list will be used to filter them instead)
  1168. * @type {Object}
  1169. * @name jQuery.sceditor.plugins.xhtml.allowedAttribs
  1170. * @since v1.4.1
  1171. */
  1172. sceditorPlugins.xhtml.allowedAttribs = {};
  1173. /**
  1174. * Attributes that are not allowed.
  1175. *
  1176. * Only used if allowed attributes is null or empty.
  1177. * @type {Object}
  1178. * @name jQuery.sceditor.plugins.xhtml.disallowedAttribs
  1179. * @since v1.4.1
  1180. */
  1181. sceditorPlugins.xhtml.disallowedAttribs = {};
  1182. /**
  1183. * Array containing all the allowed tags.
  1184. *
  1185. * If null or empty all tags will be allowed.
  1186. * @type {Array}
  1187. * @name jQuery.sceditor.plugins.xhtml.allowedTags
  1188. * @since v1.4.1
  1189. */
  1190. sceditorPlugins.xhtml.allowedTags = [];
  1191. /**
  1192. * Array containing all the disallowed tags.
  1193. *
  1194. * Only used if allowed tags is null or empty.
  1195. * @type {Array}
  1196. * @name jQuery.sceditor.plugins.xhtml.disallowedTags
  1197. * @since v1.4.1
  1198. */
  1199. sceditorPlugins.xhtml.disallowedTags = [];
  1200. }(jQuery));