jquery.sceditor.bbcode.js 39 KB

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