jquery.sceditor.bbcode.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509
  1. /**
  2. * SCEditor BBCode Plugin
  3. * http://www.samclarke.com/2011/07/sceditor/
  4. *
  5. * Copyright (C) 2011-2012, 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. * @version 1.3.7
  12. * @requires jQuery
  13. */
  14. // ==ClosureCompiler==
  15. // @output_file_name jquery.sceditor.min.js
  16. // @compilation_level SIMPLE_OPTIMIZATIONS
  17. // ==/ClosureCompiler==
  18. /*jshint smarttabs: true, jquery: true, eqnull:true, curly: false */
  19. (function($) {
  20. 'use strict';
  21. /**
  22. * BBCode plugin for SCEditor
  23. *
  24. * @param {Element} el The textarea to be converted
  25. * @return {Object} options
  26. * @class sceditorBBCodePlugin
  27. * @name jQuery.sceditorBBCodePlugin
  28. */
  29. $.sceditorBBCodePlugin = function(element, options) {
  30. var base = this;
  31. /**
  32. * Private methods
  33. * @private
  34. */
  35. var init,
  36. buildBbcodeCache,
  37. handleStyles,
  38. handleTags,
  39. formatString,
  40. getStyle,
  41. wrapInDivs,
  42. isEmpty,
  43. mergeTextModeCommands;
  44. base.bbcodes = $.sceditorBBCodePlugin.bbcodes;
  45. /**
  46. * cache of all the tags pointing to their bbcodes to enable
  47. * faster lookup of which bbcode a tag should have
  48. * @private
  49. */
  50. var tagsToBbcodes = {};
  51. /**
  52. * Same as tagsToBbcodes but instead of HTML tags it's styles
  53. * @private
  54. */
  55. var stylesToBbcodes = {};
  56. /**
  57. * Allowed children of specific HTML tags. Empty array if no
  58. * children other than text nodes are allowed
  59. * @private
  60. */
  61. var validChildren = {
  62. list: ['li'],
  63. table: ['tr'],
  64. tr: ['td', 'th'],
  65. code: ['br', 'p', 'div'],
  66. youtube: []
  67. };
  68. /**
  69. * Initializer
  70. * @private
  71. * @name sceditorBBCodePlugin.init
  72. */
  73. init = function() {
  74. $.data(element, "sceditorbbcode", base);
  75. base.options = $.extend({}, $.sceditor.defaultOptions, options);
  76. // build the BBCode cache
  77. buildBbcodeCache();
  78. (new $.sceditor(element,
  79. $.extend({}, base.options, {
  80. getHtmlHandler: base.getHtmlHandler,
  81. getTextHandler: base.getTextHandler,
  82. commands: mergeTextModeCommands()
  83. })
  84. ));
  85. };
  86. mergeTextModeCommands = function() {
  87. var merge = {
  88. bold: { txtExec: ["[b]", "[/b]"] },
  89. italic: { txtExec: ["[i]", "[/i]"] },
  90. underline: { txtExec: ["[u]", "[/u]"] },
  91. strike: { txtExec: ["[s]", "[/s]"] },
  92. subscript: { txtExec: ["[sub]", "[/sub]"] },
  93. superscript: { txtExec: ["[sup]", "[/sup]"] },
  94. left: { txtExec: ["[left]", "[/left]"] },
  95. center: { txtExec: ["[center]", "[/center]"] },
  96. right: { txtExec: ["[right]", "[/right]"] },
  97. justify: { txtExec: ["[justify]", "[/justify]"] },
  98. ftp: { txtExec: ["[ftp]", "[/ftp]"] },
  99. tt: { txtExec: ["[tt]", "[/tt]"] },
  100. glow: { txtExec: ["[glow=red,2,300]", "[/glow]"] },
  101. shadow: { txtExec: ["[shadow=red,left]", "[/shadow]"] },
  102. pre: { txtExec: ["[pre]", "[/pre]"] },
  103. // @todo: check tooltip
  104. font: { txtExec: function(caller) {
  105. var editor = this;
  106. $.sceditor.command.get('font')._dropDown(
  107. editor,
  108. caller,
  109. function(fontName) {
  110. editor.insertText("[font="+fontName+"]", "[/font]");
  111. }
  112. );
  113. } },
  114. // @todo: check overlapping
  115. size: { txtExec: function(caller) {
  116. var editor = this;
  117. $.sceditor.command.get('size')._dropDown(
  118. editor,
  119. caller,
  120. function(fontSize) {
  121. editor.insertText("[size="+fontSize+"]", "[/size]");
  122. }
  123. );
  124. } },
  125. color: { txtExec: function(caller) {
  126. var editor = this;
  127. $.sceditor.command.get('color')._dropDown(
  128. editor,
  129. caller,
  130. function(color) {
  131. editor.insertText("[color="+color+"]", "[/color]");
  132. }
  133. );
  134. } },
  135. bulletlist: { txtExec: ["[list]\n[li]", "[/li]\n[li][/li]\n[/list]"] },
  136. orderedlist: { txtExec: ["[list type=decimal]\n[li]", "[/li]\n[li][/li]\n[/list]"] },
  137. table: { txtExec: ["[table]\n[tr]\n[td]", "[/td]\n[/tr]\n[/table]"] },
  138. horizontalrule: { txtExec: ["[hr]"] },
  139. code: { txtExec: ["[code]", "[/code]"] },
  140. image: { txtExec: function(caller, selected) {
  141. var url = prompt(this._("Enter the image URL:"), selected);
  142. if(url)
  143. this.insertText("[img]" + url + "[/img]");
  144. } },
  145. email: { txtExec: function(caller, selected) {
  146. var display = selected && selected.indexOf('@') > -1 ? null : selected,
  147. email = prompt(this._("Enter the e-mail address:"), (display ? '' : selected)),
  148. text = prompt(this._("Enter the displayed text:"), display || email) || email;
  149. if(email)
  150. this.insertText("[email=" + email + "]" + text + "[/email]");
  151. } },
  152. link: { txtExec: function(caller, selected) {
  153. var display = selected && selected.indexOf('http://') > -1 ? null : selected,
  154. url = prompt(this._("Enter URL:"), (display ? 'http://' : selected)),
  155. text = prompt(this._("Enter the displayed text:"), display || url) || url;
  156. if(url)
  157. this.insertText("[url=" + url + "]" + text + "[/url]");
  158. } },
  159. quote: { txtExec: ["[quote]", "[/quote]"] },
  160. youtube: { txtExec: function(caller) {
  161. var editor = this;
  162. $.sceditor.command.get('youtube')._dropDown(
  163. editor,
  164. caller,
  165. function(id) {
  166. editor.insertText("[youtube]" + id + "[/youtube]");
  167. }
  168. );
  169. } },
  170. rtl: { txtExec: ["[rtl]", "[/rtl]"] },
  171. ltr: { txtExec: ["[ltr]", "[/ltr]"] }
  172. };
  173. return $.extend(true, {}, merge, $.sceditor.commands);
  174. };
  175. /**
  176. * Populates tagsToBbcodes and stylesToBbcodes to enable faster lookups
  177. *
  178. * @private
  179. */
  180. buildBbcodeCache = function() {
  181. $.each(base.bbcodes, function(bbcode, info) {
  182. if(typeof base.bbcodes[bbcode].tags !== "undefined")
  183. $.each(base.bbcodes[bbcode].tags, function(tag, values) {
  184. var isBlock = !!base.bbcodes[bbcode].isBlock;
  185. tagsToBbcodes[tag] = (tagsToBbcodes[tag] || {});
  186. tagsToBbcodes[tag][isBlock] = (tagsToBbcodes[tag][isBlock] || {});
  187. tagsToBbcodes[tag][isBlock][bbcode] = values;
  188. });
  189. if(typeof base.bbcodes[bbcode].styles !== "undefined")
  190. $.each(base.bbcodes[bbcode].styles, function(style, values) {
  191. var isBlock = !!base.bbcodes[bbcode].isBlock;
  192. stylesToBbcodes[isBlock] = (stylesToBbcodes[isBlock] || {});
  193. stylesToBbcodes[isBlock][style] = (stylesToBbcodes[isBlock][style] || {});
  194. stylesToBbcodes[isBlock][style][bbcode] = values;
  195. });
  196. });
  197. };
  198. getStyle = function(element, property) {
  199. var name = $.camelCase(property),
  200. $elm, ret, dir;
  201. // add exception for align
  202. if("text-align" === property)
  203. {
  204. $elm = $(element);
  205. if($elm.parent().css(property) !== $elm.css(property) &&
  206. $elm.css('display') === "block" && !$elm.is('hr') && !$elm.is('th'))
  207. ret = $elm.css(property);
  208. // IE changes text-align to the same as direction so skip unless overried by user
  209. dir = element.style.direction;
  210. if(dir && ((/right/i.test(ret) && dir === 'rtl') || (/left/i.test(ret) && dir === 'ltr')))
  211. return null;
  212. return ret;
  213. }
  214. if(element.style)
  215. return element.style[name];
  216. return null;
  217. };
  218. isEmpty = function(element) {
  219. var childNodes = element.childNodes,
  220. i = childNodes.length;
  221. if(element.nodeValue)
  222. return false;
  223. if(childNodes.length === 0 || (childNodes.length === 1 && (/br/i.test(childNodes[0].nodeName) || isEmpty(childNodes[0]))))
  224. return true;
  225. while(i--)
  226. if(!isEmpty(childNodes[i]))
  227. return false;
  228. return true;
  229. };
  230. /**
  231. * Checks if any bbcode styles match the elements styles
  232. *
  233. * @private
  234. * @return string Content with any matching bbcode tags wrapped around it.
  235. * @Private
  236. */
  237. handleStyles = function(element, content, blockLevel) {
  238. var elementPropVal;
  239. // convert blockLevel to boolean
  240. blockLevel = !!blockLevel;
  241. if(!stylesToBbcodes[blockLevel])
  242. return content;
  243. $.each(stylesToBbcodes[blockLevel], function(property, bbcodes) {
  244. elementPropVal = getStyle(element[0], property);
  245. // if the parent has the same style use that instead of this one
  246. // so you dont end up with [i]parent[i]child[/i][/i]
  247. if(!elementPropVal || getStyle(element.parent()[0], property) === elementPropVal)
  248. return;
  249. $.each(bbcodes, function(bbcode, values) {
  250. if(!/\S|\u00A0/.test(content) && !base.bbcodes[bbcode].allowsEmpty && isEmpty(element[0]))
  251. return;
  252. if(!values || $.inArray(elementPropVal.toString(), values) > -1) {
  253. if($.isFunction(base.bbcodes[bbcode].format))
  254. content = base.bbcodes[bbcode].format.call(base, element, content);
  255. else
  256. content = formatString(base.bbcodes[bbcode].format, content);
  257. }
  258. });
  259. });
  260. return content;
  261. };
  262. /**
  263. * Handles a HTML tag and finds any matching bbcodes
  264. *
  265. * @private
  266. * @param jQuery element element The element to convert
  267. * @param string content The Tags text content
  268. * @param bool blockLevel If to convert block level tags
  269. * @return string Content with any matching bbcode tags wrapped around it.
  270. * @Private
  271. */
  272. handleTags = function(element, content, blockLevel) {
  273. var tag = element[0].nodeName.toLowerCase();
  274. // convert blockLevel to boolean
  275. blockLevel = !!blockLevel;
  276. if(tagsToBbcodes[tag] && tagsToBbcodes[tag][blockLevel]) {
  277. // loop all bbcodes for this tag
  278. $.each(tagsToBbcodes[tag][blockLevel], function(bbcode, bbcodeAttribs) {
  279. if(!/\S|\u00A0/.test(content) && !base.bbcodes[bbcode].allowsEmpty && isEmpty(element[0]))
  280. return;
  281. // if the bbcode requires any attributes then check this has
  282. // all needed
  283. if(bbcodeAttribs) {
  284. var runBbcode = false;
  285. // loop all the bbcode attribs
  286. $.each(bbcodeAttribs, function(attrib, values)
  287. {
  288. // if the element has the bbcodes attribute and the bbcode attribute
  289. // has values check one of the values matches
  290. if(!element.attr(attrib) || (values && $.inArray(element.attr(attrib), values) < 0))
  291. return;
  292. // break this loop as we have matched this bbcode
  293. runBbcode = true;
  294. return false;
  295. });
  296. if(!runBbcode)
  297. return;
  298. }
  299. if($.isFunction(base.bbcodes[bbcode].format))
  300. content = base.bbcodes[bbcode].format.call(base, element, content);
  301. else
  302. content = formatString(base.bbcodes[bbcode].format, content);
  303. });
  304. }
  305. // add newline after paragraph elements p and div (WebKit uses divs) and br tags
  306. if(blockLevel && /^(br|div|p)$/.test(tag))
  307. {
  308. // Only treat divs/p as a newline if their last child was not a new line.
  309. if(!(/^(div|p)$/i.test(tag) && element[0].lastChild && element[0].lastChild.nodeName.toLowerCase() === "br"))
  310. content += "\n";
  311. // needed for browsers that enter textnode then when return is pressed put the rest in a div, i.e.:
  312. // text<div>line 2</div>
  313. if("br" !== tag && !$.sceditor.dom.isInline(element[0].parentNode) && element[0].previousSibling &&
  314. element[0].previousSibling.nodeType === 3) {
  315. content = "\n" + content;
  316. }
  317. }
  318. return content;
  319. };
  320. /**
  321. * Formats a string in the format
  322. * {0}, {1}, {2}, ect. with the params provided
  323. * @private
  324. * @return string
  325. * @Private
  326. */
  327. formatString = function() {
  328. var args = arguments;
  329. return args[0].replace(/\{(\d+)\}/g, function(str, p1) {
  330. return typeof args[p1-0+1] !== "undefined" ?
  331. args[p1-0+1] :
  332. '{' + p1 + '}';
  333. });
  334. };
  335. /**
  336. * Removes any leading or trailing quotes ('")
  337. *
  338. * @return string
  339. * @memberOf jQuery.sceditorBBCodePlugin.prototype
  340. */
  341. base.stripQuotes = function(str) {
  342. return str.replace(/^(["'])(.*?)\1$/, "$2");
  343. };
  344. /**
  345. * Converts HTML to BBCode
  346. * @param string html Html string, this function ignores this, it works off domBody
  347. * @param HtmlElement domBody Editors dom body object to convert
  348. * @return string BBCode which has been converted from HTML
  349. * @memberOf jQuery.sceditorBBCodePlugin.prototype
  350. */
  351. base.getHtmlHandler = function(html, domBody) {
  352. $.sceditor.dom.removeWhiteSpace(domBody[0]);
  353. return $.trim(base.elementToBbcode(domBody));
  354. };
  355. /**
  356. * Converts a HTML dom element to BBCode starting from
  357. * the innermost element and working backwards
  358. *
  359. * @private
  360. * @param HtmlElement element The element to convert to BBCode
  361. * @param array vChildren Valid child tags allowed
  362. * @return string BBCode
  363. * @memberOf jQuery.sceditorBBCodePlugin.prototype
  364. */
  365. base.elementToBbcode = function($element) {
  366. return (function toBBCode(node, vChildren) {
  367. var ret = '';
  368. $.sceditor.dom.traverse(node, function(node) {
  369. var $node = $(node),
  370. curTag = '',
  371. tag = node.nodeName.toLowerCase(),
  372. vChild = validChildren[tag],
  373. isValidChild = true;
  374. if(typeof vChildren === 'object')
  375. {
  376. isValidChild = $.inArray(tag, vChildren) > -1;
  377. // if this tag is one of the parents allowed children
  378. // then set this tags allowed children to whatever it allows,
  379. // otherwise set to what the parent allows
  380. if(!isValidChild)
  381. vChild = vChildren;
  382. }
  383. // 3 is text element
  384. if(node.nodeType !== 3)
  385. {
  386. // skip ignored elments
  387. if($node.hasClass("sceditor-ignore"))
  388. return;
  389. // don't loop inside iframes
  390. if(tag !== 'iframe')
  391. curTag = toBBCode(node, vChild);
  392. if(isValidChild)
  393. {
  394. // code tags should skip most styles
  395. if(!$node.is('code'))
  396. {
  397. // handle inline bbcodes
  398. curTag = handleStyles($node, curTag);
  399. curTag = handleTags($node, curTag);
  400. // handle blocklevel bbcodes
  401. curTag = handleStyles($node, curTag, true);
  402. }
  403. ret += handleTags($node, curTag, true);
  404. }
  405. else
  406. ret += curTag;
  407. }
  408. else if(node.wholeText && (!node.previousSibling || node.previousSibling.nodeType !== 3))
  409. {
  410. if($(node).parents('code').length === 0)
  411. ret += node.wholeText.replace(/ +/g, " ");
  412. else
  413. ret += node.wholeText;
  414. }
  415. else if(!node.wholeText)
  416. ret += node.nodeValue;
  417. }, false, true);
  418. return ret;
  419. }($element.get(0)));
  420. };
  421. /**
  422. * Converts BBCode to HTML
  423. *
  424. * @param {String} text
  425. * @param {Bool} isFragment
  426. * @return {String} HTML
  427. * @memberOf jQuery.sceditorBBCodePlugin.prototype
  428. */
  429. base.getTextHandler = function(text, isFragment) {
  430. var oldText, replaceBBCodeFunc,
  431. // Previous bbcodeRegex = /\[([^\[\s=]*?)(?:([\s=][^\[]*?))?\]((?:[\s\S(?!=\[\\\1)](?!\[\1))*?)\[\/(\1)\]/g,
  432. bbcodeRegex = /\[([^\[\s=]+)(?:([\s=][^\[\]]+))?\]((?:[\s\S](?!\[\1))*?)\[\/(\1)\]/g,
  433. atribsRegex = /(\S+)=((?:(?:(["'])(?:\\\3|[^\3])*?\3))|(?:[^'"\s]+))/g;
  434. replaceBBCodeFunc = function(str, bbcode, attrs, content)
  435. {
  436. var attrsMap = {},
  437. matches;
  438. bbcode = bbcode.toLowerCase();
  439. if(attrs)
  440. {
  441. attrs = $.trim(attrs);
  442. // if only one attribute then remove the = from the start and strip any quotes
  443. if((attrs.charAt(0) === "=" && (attrs.split("=").length - 1) <= 1) || bbcode === 'url')
  444. attrsMap.defaultattr = base.stripQuotes(attrs.substr(1));
  445. else
  446. {
  447. if(attrs.charAt(0) === "=")
  448. attrs = "defaultattr" + attrs;
  449. if (typeof base.bbcodes[bbcode].attrs == 'function')
  450. {
  451. var declaredAttrs = base.bbcodes[bbcode].attrs();
  452. var attrArray = new Array;
  453. var compatArray = new Array;
  454. for (var i = 0; i < declaredAttrs.length; i++)
  455. {
  456. var attrPos = attrs.indexOf(declaredAttrs[i]);
  457. if (attrPos != -1)
  458. {
  459. attrArray[attrPos] = [declaredAttrs[i], attrPos + declaredAttrs[i].length + 1];
  460. }
  461. }
  462. for (var attrElem in attrArray)
  463. compatArray.push(attrArray[attrElem]);
  464. for (var i = 0; i < compatArray.length; i++)
  465. {
  466. if (typeof compatArray[i+1] != 'undefined')
  467. attrsMap[compatArray[i][0].toLowerCase()] = attrs.substr(compatArray[i][1], attrs.indexOf(compatArray[i+1][0]) - compatArray[i][1]).trim();
  468. else
  469. attrsMap[compatArray[i][0].toLowerCase()] = attrs.substr(compatArray[i][1], attrs.length);
  470. }
  471. }
  472. else
  473. while((matches = atribsRegex.exec(attrs)))
  474. attrsMap[matches[1].toLowerCase()] = base.stripQuotes(matches[2]);
  475. }
  476. }
  477. if(!base.bbcodes[bbcode])
  478. return str;
  479. if($.isFunction(base.bbcodes[bbcode].html))
  480. return base.bbcodes[bbcode].html.call(base, bbcode, attrsMap, content);
  481. else
  482. return formatString(base.bbcodes[bbcode].html, content);
  483. };
  484. text = text.replace(/&/g, "&amp;")
  485. .replace(/</g, "&lt;")
  486. .replace(/>/g, "&gt;")
  487. .replace(/\r/g, "")
  488. .replace(/(\[\/?(?:left|center|right|justify|align|rtl|ltr)\])\n/g, "$1")
  489. .replace(/\n/g, "<br />");
  490. while(text !== oldText)
  491. {
  492. oldText = text;
  493. text = text.replace(bbcodeRegex, replaceBBCodeFunc);
  494. }
  495. // As hr is the only bbcode not to have a start and end tag it's
  496. // just being replace here instead of adding support for it above.
  497. text = text.replace(/\[hr\]/gi, "<hr>")
  498. .replace(/\[\*\]/gi, "<li>");
  499. // replace multi-spaces which are not inside tags with a non-breaking space
  500. // to preserve them. Otherwise they will just be converted to 1!
  501. text = text.replace(/ {2}(?=([^<\>]*?<|[^<\>]*?$))/g, " &nbsp;");
  502. return wrapInDivs(text, isFragment);
  503. };
  504. /**
  505. * Wraps divs around inline HTML. Needed for IE
  506. *
  507. * @param string html
  508. * @return string HTML
  509. * @private
  510. */
  511. wrapInDivs = function(html, excludeFirstLast)
  512. {
  513. var d = document,
  514. inlineFrag = d.createDocumentFragment(),
  515. outputDiv = d.createElement('div'),
  516. tmpDiv = d.createElement('div'),
  517. div, node, next, nodeName;
  518. $(tmpDiv).hide().appendTo(d.body);
  519. tmpDiv.innerHTML = html;
  520. node = tmpDiv.firstChild;
  521. while(node)
  522. {
  523. next = node.nextSibling;
  524. nodeName = node.nodeName.toLowerCase();
  525. if((node.nodeType === 1 && !$.sceditor.dom.isInline(node)) || nodeName === "br")
  526. {
  527. if(inlineFrag.childNodes.length > 0 || nodeName === "br")
  528. {
  529. div = d.createElement('div');
  530. div.appendChild(inlineFrag);
  531. // Putting BR in a div in IE9 causes it to do a double line break,
  532. // as much as I hate browser UA sniffing, to do feature detection would
  533. // be more code than it's worth for this specific bug.
  534. if(nodeName === "br" && !$.sceditor.ie)
  535. div.appendChild(d.createElement('br'));
  536. // If it's an empty DIV and in compatibility mode is below IE8 then
  537. // we must add a non-breaking space to the div otherwise the div
  538. // will be collapsed. Adding a BR works but when you press enter
  539. // to make a newline it suddenly gose back to the normal IE div
  540. // behaviour and creates two lines, one for the newline and one
  541. // for the BR. I'm sure there must be a better fix but I've yet to
  542. // find one.
  543. // Cannot do zoom: 1; or set a height on the div to fix it as that
  544. // causes resize handles to be added to the div when it's clicked on/
  545. if(!div.childNodes.length && (d.documentMode && d.documentMode < 8 || $.sceditor.ie < 8))
  546. div.appendChild(d.createTextNode('\u00a0'));
  547. outputDiv.appendChild(div);
  548. inlineFrag = d.createDocumentFragment();
  549. }
  550. if(nodeName !== "br")
  551. outputDiv.appendChild(node);
  552. }
  553. else
  554. inlineFrag.appendChild(node);
  555. node = next;
  556. }
  557. if(inlineFrag.childNodes.length > 0)
  558. {
  559. div = d.createElement('div');
  560. div.appendChild(inlineFrag);
  561. outputDiv.appendChild(div);
  562. }
  563. // needed for paste, the first shouldn't be wrapped in a div
  564. if(excludeFirstLast)
  565. {
  566. node = outputDiv.firstChild;
  567. if(node && node.nodeName.toLowerCase() === "div")
  568. {
  569. while((next = node.firstChild))
  570. outputDiv.insertBefore(next, node);
  571. if($.sceditor.ie >= 9)
  572. outputDiv.insertBefore(d.createElement('br'), node);
  573. outputDiv.removeChild(node);
  574. }
  575. node = outputDiv.lastChild;
  576. if(node && node.nodeName.toLowerCase() === "div")
  577. {
  578. while((next = node.firstChild))
  579. outputDiv.insertBefore(next, node);
  580. if($.sceditor.ie >= 9)
  581. outputDiv.insertBefore(d.createElement('br'), node);
  582. outputDiv.removeChild(node);
  583. }
  584. }
  585. $(tmpDiv).remove();
  586. return outputDiv.innerHTML;
  587. };
  588. init();
  589. };
  590. $.sceditorBBCodePlugin.bbcodes = {
  591. // START_COMMAND: Bold
  592. b: {
  593. tags: {
  594. b: null,
  595. strong: null
  596. },
  597. styles: {
  598. // 401 is for FF 3.5
  599. "font-weight": ["bold", "bolder", "401", "700", "800", "900"]
  600. },
  601. format: "[b]{0}[/b]",
  602. html: '<strong>{0}</strong>'
  603. },
  604. // END_COMMAND
  605. // START_COMMAND: Italic
  606. i: {
  607. tags: {
  608. i: null,
  609. em: null
  610. },
  611. styles: {
  612. "font-style": ["italic", "oblique"]
  613. },
  614. format: "[i]{0}[/i]",
  615. html: '<em>{0}</em>'
  616. },
  617. // END_COMMAND
  618. // START_COMMAND: Underline
  619. u: {
  620. tags: {
  621. u: null
  622. },
  623. styles: {
  624. "text-decoration": ["underline"]
  625. },
  626. format: "[u]{0}[/u]",
  627. html: '<u>{0}</u>'
  628. },
  629. // END_COMMAND
  630. // START_COMMAND: Strikethrough
  631. s: {
  632. tags: {
  633. s: null,
  634. strike: null
  635. },
  636. styles: {
  637. "text-decoration": ["line-through"]
  638. },
  639. format: "[s]{0}[/s]",
  640. html: '<s>{0}</s>'
  641. },
  642. // END_COMMAND
  643. // START_COMMAND: Subscript
  644. sub: {
  645. tags: {
  646. sub: null
  647. },
  648. format: "[sub]{0}[/sub]",
  649. html: '<sub>{0}</sub>'
  650. },
  651. // END_COMMAND
  652. // START_COMMAND: Superscript
  653. sup: {
  654. tags: {
  655. sup: null
  656. },
  657. format: "[sup]{0}[/sup]",
  658. html: '<sup>{0}</sup>'
  659. },
  660. // END_COMMAND
  661. // START_COMMAND: Font
  662. font: {
  663. tags: {
  664. font: {
  665. face: null
  666. }
  667. },
  668. styles: {
  669. "font-family": null
  670. },
  671. format: function(element, content) {
  672. if(element[0].nodeName.toLowerCase() === "font" && element.attr('face'))
  673. return '[font=' + this.stripQuotes(element.attr('face')) + ']' + content + '[/font]';
  674. return '[font=' + this.stripQuotes(element.css('font-family')) + ']' + content + '[/font]';
  675. },
  676. html: function(element, attrs, content) {
  677. return '<font face="' + attrs.defaultattr + '">' + content + '</font>';
  678. }
  679. },
  680. // END_COMMAND
  681. // START_COMMAND: Size
  682. size: {
  683. tags: {
  684. font: {
  685. size: null
  686. }
  687. },
  688. styles: {
  689. "font-size": null
  690. },
  691. format: function(element, content) {
  692. var fontSize = element.css('fontSize'),
  693. size = 1;
  694. if(element.attr('size'))
  695. size = element.attr('size');
  696. // Most browsers return px value but IE returns 1-7
  697. else if(fontSize.indexOf("px") > -1) {
  698. // convert size to an int
  699. fontSize = fontSize.replace("px", "") - 0;
  700. if(fontSize > 12)
  701. size = 2;
  702. if(fontSize > 15)
  703. size = 3;
  704. if(fontSize > 17)
  705. size = 4;
  706. if(fontSize > 23)
  707. size = 5;
  708. if(fontSize > 31)
  709. size = 6;
  710. if(fontSize > 47)
  711. size = 7;
  712. }
  713. else
  714. size = fontSize;
  715. return '[size=' + size + ']' + content + '[/size]';
  716. },
  717. html: function(element, attrs, content) {
  718. return '<font size="' + attrs.defaultattr + '">' + content + '</font>';
  719. }
  720. },
  721. // END_COMMAND
  722. // START_COMMAND: Color
  723. color: {
  724. tags: {
  725. font: {
  726. color: null
  727. }
  728. },
  729. styles: {
  730. color: null
  731. },
  732. format: function(element, content) {
  733. /**
  734. * Converts CSS rgb value into hex
  735. * @private
  736. * @return string Hex color
  737. */
  738. var rgbToHex = function(rgbStr) {
  739. var m;
  740. function toHex(n) {
  741. n = parseInt(n,10);
  742. if(isNaN(n))
  743. return "00";
  744. n = Math.max(0,Math.min(n,255)).toString(16);
  745. return n.length<2 ? '0'+n : n;
  746. }
  747. // rgb(n,n,n);
  748. if((m = rgbStr.match(/rgb\((\d+),\s*?(\d+),\s*?(\d+)\)/i)))
  749. return '#' + toHex(m[1]) + toHex(m[2]-0) + toHex(m[3]-0);
  750. // expand shorthand
  751. if((m = rgbStr.match(/#([0-f])([0-f])([0-f])\s*?$/i)))
  752. return '#' + m[1] + m[1] + m[2] + m[2] + m[3] + m[3];
  753. return rgbStr;
  754. };
  755. var color = element.css('color');
  756. if(element[0].nodeName.toLowerCase() === "font" && element.attr('color'))
  757. color = element.attr('color');
  758. color = rgbToHex(color);
  759. return '[color=' + color + ']' + content + '[/color]';
  760. },
  761. html: function(element, attrs, content) {
  762. return '<font color="' + attrs.defaultattr + '">' + content + '</font>';
  763. }
  764. },
  765. black: {
  766. html: '<font color="black">{0}</font>'
  767. },
  768. blue: {
  769. html: '<font color="blue">{0}</font>'
  770. },
  771. green: {
  772. html: '<font color="green">{0}</font>'
  773. },
  774. red: {
  775. html: '<font color="red">{0}</font>'
  776. },
  777. white: {
  778. html: '<font color="white">{0}</font>'
  779. },
  780. // END_COMMAND
  781. // START_COMMAND: Lists
  782. list: {
  783. isBlock: true,
  784. html: function(element, attrs, content) {
  785. var style = '';
  786. var code = 'ul';
  787. if (attrs.type)
  788. style = ' style="list-style-type: ' + attrs.type + '"';
  789. return '<' + code + style + '>' + content + '</' + code + '>';
  790. }
  791. },
  792. ul: {
  793. tags: {
  794. ul: null
  795. },
  796. isBlock: true,
  797. format: function(element, content) {
  798. if ($(element[0]).css('list-style-type') == 'disc')
  799. return '[list]' + content + '[/list]';
  800. else
  801. return '[list type=' + $(element[0]).css('list-style-type') + ']' + content + '[/list]';
  802. },
  803. html: '<ul>{0}</ul>'
  804. },
  805. ol: {
  806. tags: {
  807. ol: null
  808. },
  809. isBlock: true,
  810. format: '[list type=decimal]{0}[/list]',
  811. html: '<ol>{0}</ol>'
  812. },
  813. li: {
  814. tags: {
  815. li: null
  816. },
  817. format: "[li]{0}[/li]",
  818. html: '<li>{0}</li>'
  819. },
  820. "*": {
  821. html: '<li>{0}</li>'
  822. },
  823. // END_COMMAND
  824. // START_COMMAND: Table
  825. table: {
  826. tags: {
  827. table: null
  828. },
  829. format: "[table]{0}[/table]",
  830. html: '<table>{0}</table>'
  831. },
  832. tr: {
  833. tags: {
  834. tr: null
  835. },
  836. format: "[tr]{0}[/tr]",
  837. html: '<tr>{0}</tr>'
  838. },
  839. th: {
  840. tags: {
  841. th: null
  842. },
  843. isBlock: true,
  844. format: "[th]{0}[/th]",
  845. html: '<th>{0}</th>'
  846. },
  847. td: {
  848. tags: {
  849. td: null
  850. },
  851. isBlock: true,
  852. format: "[td]{0}[/td]",
  853. html: '<td>{0}<br class="sceditor-ignore" /></td>'
  854. },
  855. // END_COMMAND
  856. // START_COMMAND: Emoticons
  857. emoticon: {
  858. allowsEmpty: true,
  859. tags: {
  860. img: {
  861. src: null,
  862. "data-sceditor-emoticon": null
  863. }
  864. },
  865. format: function(element, content) {
  866. if (element.attr('data-sceditor-emoticon') == '')
  867. return content;
  868. return element.attr('data-sceditor-emoticon') + content;
  869. },
  870. html: '{0}'
  871. },
  872. // END_COMMAND
  873. // START_COMMAND: Horizontal Rule
  874. horizontalrule: {
  875. allowsEmpty: true,
  876. tags: {
  877. hr: null
  878. },
  879. format: "[hr]{0}",
  880. html: "<hr />"
  881. },
  882. // END_COMMAND
  883. // START_COMMAND: Image
  884. img: {
  885. allowsEmpty: true,
  886. tags: {
  887. img: {
  888. src: null
  889. }
  890. },
  891. format: function(element, content) {
  892. var attribs = '',
  893. style = function(name) {
  894. return element.style ? element.style[name] : null;
  895. };
  896. // check if this is an emoticon image
  897. if(typeof element.attr('data-sceditor-emoticon') !== "undefined")
  898. return content;
  899. var width = ' width=' + $(element).width();
  900. var height = ' height=' + $(element).height();
  901. var alt = $(element).attr('alt') != undefined ? ' alt=' + $(element).attr('author').php_unhtmlspecialchars() : '';
  902. return '[img' + width + height + alt + ']' + element.attr('src') + '[/img]';
  903. },
  904. attrs: function () {
  905. return ['alt', 'width', 'height'];
  906. },
  907. html: function(element, attrs, content) {
  908. var attribs = "", parts;
  909. // handle [img width=340 height=240]url[/img]
  910. if(typeof attrs.width !== "undefined")
  911. attribs += ' width="' + attrs.width + '"';
  912. if(typeof attrs.height !== "undefined")
  913. attribs += ' height="' + attrs.height + '"';
  914. if(typeof attrs.alt !== "undefined")
  915. attribs += ' alt="' + attrs.alt + '"';
  916. return '<img ' + attribs + ' src="' + content + '" />';
  917. }
  918. },
  919. // END_COMMAND
  920. // START_COMMAND: URL
  921. url: {
  922. allowsEmpty: true,
  923. tags: {
  924. a: {
  925. href: null
  926. }
  927. },
  928. format: function(element, content) {
  929. // make sure this link is not an e-mail, if it is return e-mail BBCode
  930. if(element.attr('href').substr(0, 7) === 'mailto:')
  931. return '[email=' + element.attr('href').substr(7) + ']' + content + '[/email]';
  932. if(element.attr('target') !== undefined)
  933. return '[url=' + decodeURI(element.attr('href')) + ']' + content + '[/url]';
  934. else
  935. return '[iurl=' + decodeURI(element.attr('href')) + ']' + content + '[/iurl]';
  936. },
  937. html: function(element, attrs, content) {
  938. if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
  939. attrs.defaultAttr = content;
  940. return '<a trget="_blank" href="' + encodeURI(attrs.defaultAttr) + '">' + content + '</a>';
  941. }
  942. },
  943. iurl: {
  944. html: function(element, attrs, content) {
  945. if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
  946. attrs.defaultAttr = content;
  947. return '<a href="' + encodeURI(attrs.defaultAttr) + '">' + content + '</a>';
  948. }
  949. },
  950. ftp: {
  951. html: function(element, attrs, content) {
  952. if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
  953. attrs.defaultAttr = content;
  954. return '<a trget="_blank" href="' + encodeURI(attrs.defaultAttr) + '">' + content + '</a>';
  955. }
  956. },
  957. // END_COMMAND
  958. // START_COMMAND: E-mail
  959. email: {
  960. html: function(element, attrs, content) {
  961. if(typeof attrs.defaultattr === "undefined")
  962. attrs.defaultattr = content;
  963. return '<a href="mailto:' + attrs.defaultattr + '">' + content + '</a>';
  964. }
  965. },
  966. // END_COMMAND
  967. // START_COMMAND: Quote
  968. quote: {
  969. tags: {
  970. blockquote: null
  971. },
  972. isBlock: true,
  973. format: function(element, content) {
  974. var author = '';
  975. var date = '';
  976. var link = '';
  977. if ($(element).children("cite:first").length === 1)
  978. {
  979. author = $(element).children("cite:first").find("author").text();
  980. date = $(element).children("cite:first").find("date").attr('timestamp');
  981. link = $(element).children("cite:first").find("quote_link").text();
  982. $(element).attr({'author': author.php_htmlspecialchars(), 'date': date, 'link': link});
  983. if (author != '')
  984. author = ' author=' + author;
  985. if (date != '')
  986. date = ' date=' + date;
  987. if (link != '')
  988. link = ' link=' + link;
  989. content = '';
  990. $(element).children("cite:first").remove();
  991. var preserve_newline = $(element).children().last().is("br") && $(element).children().last().last().is("br");
  992. content = this.elementToBbcode($(element)) + (preserve_newline ? "\n" : '');
  993. }
  994. else
  995. {
  996. if ($(element).attr('author') != undefined)
  997. author = ' author=' + $(element).attr('author').php_unhtmlspecialchars();
  998. if ($(element).attr('date') != undefined)
  999. date = ' date=' + $(element).attr('date');
  1000. if ($(element).attr('link') != undefined)
  1001. link = ' link=' + $(element).attr('link');
  1002. }
  1003. return '[quote' + author + date + link + ']' + content + '[/quote]';
  1004. },
  1005. attrs: function () {
  1006. return ['author', 'date', 'link'];
  1007. },
  1008. html: function(element, attrs, content) {
  1009. var author = '';
  1010. var sDate = '';
  1011. var link = '';
  1012. if(typeof attrs.author !== "undefined")
  1013. author = bbc_quote_from + ': <author>' + attrs.author + '</author>';
  1014. // Links could be in the form: link=topic=71.msg201#msg201 that would fool javascript, so we need a workaround
  1015. // Probably no more necessary
  1016. for (var key in attrs)
  1017. {
  1018. if (key.substr(0, 4) == 'link' && attrs.hasOwnProperty(key))
  1019. {
  1020. var possible_url = key.length > 4 ? key.substr(5) + '=' + attrs[key] : attrs[key];
  1021. link = possible_url.substr(0, 7) == 'http://' ? possible_url : smf_scripturl + '?' + possible_url;
  1022. author = author == '' ? '<a href="' + link + '">' + bbc_quote_from + ': <author src=">' + link + '</author></a>' : '<a href="' + link + '">' + author + '</a>';
  1023. link = '<quote_link style="display:none">' + possible_url + '</quote_link>';
  1024. }
  1025. }
  1026. if(typeof attrs.date !== "undefined")
  1027. {
  1028. var date = new Date(attrs.date * 1000);
  1029. sDate = date;
  1030. }
  1031. if (author == '' && sDate == '')
  1032. author = bbc_quote;
  1033. else
  1034. author += ' ';
  1035. content = '<blockquote><cite>' + author + bbc_search_on + ' ' + '<date timestamp="' + attrs.date + '">' + sDate + '</date>' + link + '</cite>' + content + '</blockquote>';
  1036. return content;
  1037. }
  1038. },
  1039. // END_COMMAND
  1040. // START_COMMAND: Code
  1041. code: {
  1042. tags: {
  1043. code: null
  1044. },
  1045. isBlock: true,
  1046. format: function(element, content) {
  1047. if ($(element[0]).hasClass('php'))
  1048. return '[php]' + content.replace('&#91;', '[') + '[/php]';
  1049. var from = '';
  1050. if ($(element).children("cite:first").length === 1)
  1051. {
  1052. from = $(element).children("cite:first").text();
  1053. $(element).attr({'from': from.php_htmlspecialchars()});
  1054. from = '=' + from;
  1055. content = '';
  1056. $(element).children("cite:first").remove();
  1057. content = this.elementToBbcode($(element));
  1058. }
  1059. else
  1060. {
  1061. if ($(element).attr('from') != undefined)
  1062. {
  1063. from = '=' + $(element).attr('from').php_unhtmlspecialchars();
  1064. }
  1065. }
  1066. return '[code' + from + ']' + content.replace('&#91;', '[') + '[/code]';
  1067. },
  1068. html: function(element, attrs, content) {
  1069. var from = '';
  1070. if(typeof attrs.defaultAttr !== "undefined")
  1071. from = '<cite>' + attrs.defaultAttr + '</cite>';
  1072. return '<code>' + from + content.replace('[', '&#91;') + '</code>'
  1073. }
  1074. },
  1075. php: {
  1076. isBlock: true,
  1077. format: "[php]{0}[/php]",
  1078. html: '<code class="php">{0}</code>'
  1079. },
  1080. // END_COMMAND
  1081. // START_COMMAND: Left
  1082. left: {
  1083. styles: {
  1084. "text-align": ["left", "-webkit-left", "-moz-left", "-khtml-left"]
  1085. },
  1086. isBlock: true,
  1087. format: "[left]{0}[/left]",
  1088. html: '<div align="left">{0}</div>'
  1089. },
  1090. // END_COMMAND
  1091. // START_COMMAND: Centre
  1092. center: {
  1093. styles: {
  1094. "text-align": ["center", "-webkit-center", "-moz-center", "-khtml-center"]
  1095. },
  1096. isBlock: true,
  1097. format: "[center]{0}[/center]",
  1098. html: '<div align="center">{0}</div>'
  1099. },
  1100. // END_COMMAND
  1101. // START_COMMAND: Right
  1102. right: {
  1103. styles: {
  1104. "text-align": ["right", "-webkit-right", "-moz-right", "-khtml-right"]
  1105. },
  1106. isBlock: true,
  1107. format: "[right]{0}[/right]",
  1108. html: '<div align="right">{0}</div>'
  1109. },
  1110. // END_COMMAND
  1111. // START_COMMAND: Justify
  1112. justify: {
  1113. styles: {
  1114. "text-align": ["justify", "-webkit-justify", "-moz-justify", "-khtml-justify"]
  1115. },
  1116. isBlock: true,
  1117. format: "[justify]{0}[/justify]",
  1118. html: '<div align="justify">{0}</div>'
  1119. },
  1120. // END_COMMAND
  1121. // START_COMMAND: YouTube
  1122. youtube: {
  1123. allowsEmpty: true,
  1124. tags: {
  1125. iframe: {
  1126. 'data-youtube-id': null
  1127. }
  1128. },
  1129. format: function(element, content) {
  1130. if(!element.attr('data-youtube-id'))
  1131. return content;
  1132. return '[youtube]' + element.attr('data-youtube-id') + '[/youtube]';
  1133. },
  1134. html: '<iframe width="560" height="315" src="http://www.youtube.com/embed/{0}?wmode=opaque' +
  1135. '" data-youtube-id="{0}" frameborder="0" allowfullscreen></iframe>'
  1136. },
  1137. // END_COMMAND
  1138. // START_COMMAND: Rtl
  1139. rtl: {
  1140. styles: {
  1141. "direction": ["rtl"]
  1142. },
  1143. format: "[rtl]{0}[/rtl]",
  1144. html: '<div style="direction: rtl">{0}</div>'
  1145. },
  1146. // END_COMMAND
  1147. // START_COMMAND: Ltr
  1148. ltr: {
  1149. styles: {
  1150. "direction": ["ltr"]
  1151. },
  1152. format: "[ltr]{0}[/ltr]",
  1153. html: '<div style="direction: ltr">{0}</div>'
  1154. },
  1155. // END_COMMAND
  1156. abbr: {
  1157. tags: {
  1158. abbr: {
  1159. title: null
  1160. }
  1161. },
  1162. format: function(element, content) {
  1163. return '[abbr=' + element.attr('title') + ']' + content + '[/abbr]';
  1164. },
  1165. html: function(element, attrs, content) {
  1166. if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
  1167. return content;
  1168. return '<abbr title="' + attrs.defaultAttr + '">' + content + '</abbr>';
  1169. }
  1170. },
  1171. acronym: {
  1172. tags: {
  1173. acronym: {
  1174. title: null
  1175. }
  1176. },
  1177. format: function(element, content) {
  1178. return '[acronym=' + element.attr('title') + ']' + content + '[/acronym]';
  1179. },
  1180. html: function(element, attrs, content) {
  1181. if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
  1182. return content;
  1183. return '<acronym title="' + attrs.defaultAttr + '">' + content + '</acronym>';
  1184. }
  1185. },
  1186. bdo: {
  1187. tags: {
  1188. bdo: {
  1189. dir: null
  1190. }
  1191. },
  1192. format: function(element, content) {
  1193. return '[bdo=' + element.attr('dir') + ']' + content + '[/bdo]';
  1194. },
  1195. html: function(element, attrs, content) {
  1196. if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
  1197. return content;
  1198. if (attrs.defaultAttr != 'rtl' && attrs.defaultAttr != 'ltr')
  1199. return '[bdo=' + attrs.defaultAttr + ']' + content + '[/bdo]';
  1200. return '<bdo dir="' + attrs.defaultAttr + '">' + content + '</bdo>';
  1201. }
  1202. },
  1203. tt: {
  1204. tags: {
  1205. tt: null
  1206. },
  1207. format: "[tt]{0}[/tt]",
  1208. html: '<tt>{0}</tt>'
  1209. },
  1210. pre: {
  1211. tags: {
  1212. pre: null
  1213. },
  1214. isBlock: true,
  1215. format: "[pre]{0}[/pre]",
  1216. html: "<pre>{0}</pre>\n"
  1217. },
  1218. move: {
  1219. tags: {
  1220. marquee: null
  1221. },
  1222. format: "[move]{0}[/move]",
  1223. html: '<marquee>{0}</marquee>'
  1224. },
  1225. // this is here so that commands above can be removed
  1226. // without having to remove the , after the last one.
  1227. // Needed for IE.
  1228. ignore: {}
  1229. };
  1230. /**
  1231. * Static BBCode helper class
  1232. * @class command
  1233. * @name jQuery.sceditorBBCodePlugin.bbcode
  1234. */
  1235. $.sceditorBBCodePlugin.bbcode =
  1236. /** @lends jQuery.sceditorBBCodePlugin.bbcode */
  1237. {
  1238. /**
  1239. * Gets a BBCode
  1240. *
  1241. * @param {String} name
  1242. * @return {Object|null}
  1243. * @since v1.3.5
  1244. */
  1245. get: function(name) {
  1246. return $.sceditorBBCodePlugin.bbcodes[name] || null;
  1247. },
  1248. /**
  1249. * <p>Adds a BBCode to the parser or updates an exisiting
  1250. * BBCode if a BBCode with the specified name already exists.</p>
  1251. *
  1252. * @param {String} name
  1253. * @param {Object} bbcode
  1254. * @return {this|false} Returns false if name or bbcode is false
  1255. * @since v1.3.5
  1256. */
  1257. set: function(name, bbcode) {
  1258. if(!name || !bbcode)
  1259. return false;
  1260. // merge any existing command properties
  1261. bbcode = $.extend($.sceditorBBCodePlugin.bbcodes[name] || {}, bbcode);
  1262. bbcode.remove = function() { $.sceditorBBCodePlugin.bbcode.remove(name); };
  1263. $.sceditorBBCodePlugin.bbcodes[name] = bbcode;
  1264. return this;
  1265. },
  1266. /**
  1267. * Removes a BBCode
  1268. *
  1269. * @param {String} name
  1270. * @return {this}
  1271. * @since v1.3.5
  1272. */
  1273. remove: function(name) {
  1274. if($.sceditorBBCodePlugin.bbcodes[name])
  1275. delete $.sceditorBBCodePlugin.bbcodes[name];
  1276. return this;
  1277. }
  1278. };
  1279. /**
  1280. * Checks if a command with the specified name exists
  1281. *
  1282. * @param string name
  1283. * @return bool
  1284. * @deprecated Since v1.3.5
  1285. * @memberOf jQuery.sceditorBBCodePlugin
  1286. */
  1287. $.sceditorBBCodePlugin.commandExists = function(name) {
  1288. return !!$.sceditorBBCodePlugin.bbcode.get(name);
  1289. };
  1290. /**
  1291. * Adds/updates a BBCode.
  1292. *
  1293. * @param String name The BBCode name
  1294. * @param Object tags Any html tags this bbcode applies to, i.e. strong for [b]
  1295. * @param Object styles Any style properties this applies to, i.e. font-weight for [b]
  1296. * @param String|Function format Function or string to convert the element into BBCode
  1297. * @param String|Function html String or function to format the BBCode back into HTML.
  1298. * @param bool allowsEmpty If this BBCodes is allowed to be empty, e.g. [b][/b]
  1299. * @return Bool
  1300. * @deprecated Since v1.3.5
  1301. * @memberOf jQuery.sceditorBBCodePlugin
  1302. */
  1303. $.sceditorBBCodePlugin.setCommand = function(name, tags, styles, format, html, allowsEmpty, isBlock) {
  1304. return $.sceditorBBCodePlugin.bbcode.set(name,
  1305. {
  1306. tags: tags || {},
  1307. styles: styles || {},
  1308. allowsEmpty: allowsEmpty,
  1309. isBlock: isBlock,
  1310. format: format,
  1311. html: html
  1312. });
  1313. };
  1314. $.fn.sceditorBBCodePlugin = function(options) {
  1315. if((!options || !options.runWithoutWysiwygSupport) && !$.sceditor.isWysiwygSupported())
  1316. return;
  1317. return this.each(function() {
  1318. (new $.sceditorBBCodePlugin(this, options));
  1319. });
  1320. };
  1321. })(jQuery);