jquery.sceditor.bbcode.js 39 KB

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