jquery.sceditor.js 90 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611
  1. /**
  2. * SCEditor
  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. * @fileoverview SCEditor - A lightweight WYSIWYG BBCode and HTML editor
  11. * @author Sam Clarke
  12. * @version 1.3.7
  13. * @requires jQuery
  14. */
  15. // ==ClosureCompiler==
  16. // @output_file_name jquery.sceditor.min.js
  17. // @compilation_level SIMPLE_OPTIMIZATIONS
  18. // ==/ClosureCompiler==
  19. /*jshint smarttabs: true, scripturl: true, jquery: true, devel:true, eqnull:true, curly: false */
  20. /*global XMLSerializer: true*/
  21. ;(function ($, window, document) {
  22. 'use strict';
  23. var _templates = {
  24. html: '<!DOCTYPE html>' +
  25. '<html>' +
  26. '<head>' +
  27. '<!--[if IE]><style>* {min-height: auto !important}</style><![endif]-->' +
  28. '<meta http-equiv="Content-Type" content="text/html;charset={charset}" />' +
  29. '<link rel="stylesheet" type="text/css" href="{style}" />' +
  30. '</head>' +
  31. '<body contenteditable="true"></body>' +
  32. '</html>',
  33. toolbarButton: '<a class="sceditor-button sceditor-button-{name}" data-sceditor-command="{name}" unselectable="on"><div unselectable="on">{dispName}</div></a>',
  34. emoticon: '<img src="{url}" data-sceditor-emoticon="{key}" alt="{key}" />',
  35. fontOpt: '<a class="sceditor-font-option" href="#" data-font="{font}"><font face="{font}">{font}</font></a>',
  36. sizeOpt: '<a class="sceditor-fontsize-option" data-size="{size}" style="line-height:{points}pt" href="#"><font size="{size}">{size}</font></a>',
  37. pastetext: '<div><label for="txt">{label}</label> ' +
  38. '<textarea cols="20" rows="7" id="txt"></textarea></div>' +
  39. '<div><input type="button" class="button" value="{insert}" /></div>',
  40. table: '<div><label for="rows">{rows}</label><input type="text" id="rows" value="2" /></div>' +
  41. '<div><label for="cols">{cols}</label><input type="text" id="cols" value="2" /></div>' +
  42. '<div><input type="button" class="button" value="{insert}" /></div>',
  43. image: '<div><label for="link">{url}</label> <input type="text" id="image" value="http://" /></div>' +
  44. '<div><label for="width">{width}</label> <input type="text" id="width" size="2" /></div>' +
  45. '<div><label for="height">{height}</label> <input type="text" id="height" size="2" /></div>' +
  46. '<div><input type="button" class="button" value="{insert}" /></div>',
  47. email: '<div><label for="email">{label}</label> <input type="text" id="email" /></div>' +
  48. '<div><input type="button" class="button" value="{insert}" /></div>',
  49. link: '<div><label for="link">{url}</label> <input type="text" id="link" value="http://" /></div>' +
  50. '<div><label for="des">{desc}</label> <input type="text" id="des" /></div>' +
  51. '<div><input type="button" class="button" value="{ins}" /></div>',
  52. youtubeMenu: '<div><label for="link">{label}</label> <input type="text" id="link" value="http://" /></div><div><input type="button" class="button" value="{insert}" /></div>',
  53. youtube: '<iframe width="560" height="315" src="http://www.youtube.com/embed/{id}?wmode=opaque" data-youtube-id="{id}" frameborder="0" allowfullscreen></iframe>'
  54. };
  55. /**
  56. * <p>Replaces any params in a template with the passed params.</p>
  57. *
  58. * <p>If createHTML is passed it will use jQuery to create the HTML. The
  59. * same as doing: $(editor.tmpl("html", {params...}));</p>
  60. *
  61. * @param {string} templateName
  62. * @param {Object} params
  63. * @param {Boolean} createHTML
  64. * @private
  65. */
  66. var _tmpl = function(name, params, createHTML) {
  67. var template = _templates[name];
  68. $.each(params, function(name, val) {
  69. template = template.replace(new RegExp('\\{' + name + '\\}', 'g'), val);
  70. });
  71. if(createHTML)
  72. template = $(template);
  73. return template;
  74. };
  75. /**
  76. * SCEditor - A lightweight WYSIWYG editor
  77. *
  78. * @param {Element} el The textarea to be converted
  79. * @return {Object} options
  80. * @class sceditor
  81. * @name jQuery.sceditor
  82. */
  83. $.sceditor = function (el, options) {
  84. /**
  85. * Alias of this
  86. * @private
  87. */
  88. var base = this;
  89. /**
  90. * The textarea element being replaced
  91. * @private
  92. */
  93. var $textarea = $(el);
  94. var textarea = el;
  95. /**
  96. * The div which contains the editor and toolbar
  97. * @private
  98. */
  99. var $editorContainer;
  100. /**
  101. * The editors toolbar
  102. * @private
  103. */
  104. var $toolbar;
  105. /**
  106. * The editors iframe which should be in design mode
  107. * @private
  108. */
  109. var $wysiwygEditor;
  110. var wysiwygEditor;
  111. /**
  112. * The editors textarea for viewing source
  113. * @private
  114. */
  115. var $textEditor;
  116. var textEditor;
  117. /**
  118. * The current dropdown
  119. * @private
  120. */
  121. var $dropdown;
  122. /**
  123. * Array of all the commands key press functions
  124. * @private
  125. */
  126. var keyPressFuncs = [];
  127. /**
  128. * Store the last cursor position. Needed for IE because it forgets
  129. * @private
  130. */
  131. var lastRange;
  132. /**
  133. * The editors locale
  134. * @private
  135. */
  136. var locale;
  137. /**
  138. * Stores a cache of preloaded images
  139. * @private
  140. */
  141. var preLoadCache = [];
  142. var rangeHelper;
  143. var $blurElm;
  144. var init,
  145. replaceEmoticons,
  146. handleCommand,
  147. saveRange,
  148. handlePasteEvt,
  149. handlePasteData,
  150. handleKeyPress,
  151. handleFormReset,
  152. handleMouseDown,
  153. initEditor,
  154. initToolBar,
  155. initKeyPressFuncs,
  156. initResize,
  157. documentClickHandler,
  158. formSubmitHandler,
  159. initEmoticons,
  160. getWysiwygDoc,
  161. handleWindowResize,
  162. initLocale,
  163. updateToolBar,
  164. textEditorSelectedText,
  165. autofocus;
  166. /**
  167. * All the commands supported by the editor
  168. */
  169. base.commands = $.extend({}, (options.commands || $.sceditor.commands));
  170. /**
  171. * Initializer. Creates the editor iframe and textarea
  172. * @private
  173. * @name sceditor.init
  174. */
  175. init = function () {
  176. $textarea.data("sceditor", base);
  177. base.options = $.extend({}, $.sceditor.defaultOptions, options);
  178. // Load locale
  179. if(base.options.locale && base.options.locale !== "en")
  180. initLocale();
  181. // if either width or height are % based, add the resize handler to update the editor
  182. // when the window is resized
  183. var h = base.options.height, w = base.options.width;
  184. if((h && (h + "").indexOf("%") > -1) || (w && (w + "").indexOf("%") > -1))
  185. $(window).resize(handleWindowResize);
  186. $editorContainer = $('<div class="sceditor-container" />').insertAfter($textarea);
  187. // create the editor
  188. initToolBar();
  189. initEditor();
  190. initKeyPressFuncs();
  191. if(base.options.resizeEnabled)
  192. initResize();
  193. if(base.options.id)
  194. $editorContainer.attr('id', base.options.id);
  195. $(document).click(documentClickHandler);
  196. $(textarea.form)
  197. .attr('novalidate','novalidate')
  198. .bind("reset", handleFormReset)
  199. .submit(formSubmitHandler);
  200. // load any textarea value into the editor
  201. base.val($textarea.hide().val());
  202. /*
  203. // Pass the value though the getTextHandler if it is set so that
  204. // BBCode, ect. can be converted
  205. if(base.options.getTextHandler && base.options.supportedWysiwyg)
  206. {
  207. val = base.options.getTextHandler(val);
  208. base.setWysiwygEditorValue(val);
  209. }
  210. else
  211. {
  212. base.toggleTextMode();
  213. base.setTextareaValue(val);
  214. }
  215. */
  216. if(base.options.autofocus)
  217. autofocus();
  218. // force into source mode if is a browser that can't handle
  219. // full editing
  220. if(!$.sceditor.isWysiwygSupported())
  221. base.toggleTextMode();
  222. if(base.options.toolbar.indexOf('emoticon') !== -1)
  223. initEmoticons();
  224. // Can't use load event as it gets fired before CSS is loaded
  225. // in some browsers
  226. if(base.options.autoExpand)
  227. var interval = setInterval(function() {
  228. if (!document.readyState || document.readyState === "complete") {
  229. base.expandToContent();
  230. clearInterval(interval);
  231. }
  232. }, 10);
  233. };
  234. /**
  235. * Creates the editor iframe and textarea
  236. * @private
  237. */
  238. initEditor = function () {
  239. var $doc, $body;
  240. $textEditor = $('<textarea></textarea>').hide();
  241. $wysiwygEditor = $('<iframe frameborder="0"></iframe>');
  242. if(window.location.protocol === "https:")
  243. $wysiwygEditor.attr("src", "javascript:false");
  244. // add the editor to the HTML and store the editors element
  245. $editorContainer.append($wysiwygEditor).append($textEditor);
  246. wysiwygEditor = $wysiwygEditor[0];
  247. textEditor = $textEditor[0];
  248. base.width(base.options.width || $textarea.width());
  249. base.height(base.options.height || $textarea.height());
  250. getWysiwygDoc().open();
  251. getWysiwygDoc().write(_tmpl("html", {
  252. charset: base.options.charset,
  253. style: base.options.style
  254. }));
  255. getWysiwygDoc().close();
  256. base.readOnly(!!base.options.readOnly);
  257. $doc = $(getWysiwygDoc());
  258. $body = $doc.find("body");
  259. // Add IE version class to the HTML element so can apply
  260. // conditional styling without CSS hacks
  261. if($.sceditor.ie)
  262. $doc.find("html").addClass('ie' + $.sceditor.ie);
  263. // iframe overflow fix
  264. if(/iPhone|iPod|iPad| wosbrowser\//i.test(navigator.userAgent))
  265. $body.height('100%');
  266. // set the key press event
  267. $body.keypress(handleKeyPress);
  268. $doc.keypress(handleKeyPress)
  269. .mousedown(handleMouseDown)
  270. .bind("beforedeactivate keyup", saveRange)
  271. .focus(function() {
  272. lastRange = null;
  273. });
  274. if(base.options.rtl)
  275. {
  276. $body.attr('dir', 'rtl');
  277. $textEditor.attr('dir', 'rtl');
  278. }
  279. if(base.options.enablePasteFiltering)
  280. $body.bind("paste", handlePasteEvt);
  281. if(base.options.autoExpand)
  282. $doc.bind("keyup", base.expandToContent);
  283. rangeHelper = new $.sceditor.rangeHelper(wysiwygEditor.contentWindow);
  284. };
  285. /**
  286. * Creates the toolbar and appends it to the container
  287. * @private
  288. */
  289. initToolBar = function () {
  290. var $group, $button, buttons,
  291. i, x, buttonClick,
  292. groups = base.options.toolbar.split("|");
  293. buttonClick = function () {
  294. var self = $(this);
  295. if(!self.hasClass('disabled'))
  296. handleCommand(self, base.commands[self.data("sceditor-command")]);
  297. return false;
  298. };
  299. $toolbar = $('<div class="sceditor-toolbar" />');
  300. var rows = base.options.toolbar.split("||");
  301. for (var r=0; r < rows.length; r++) {
  302. var row = $('<div class="sceditor-row" />');
  303. var groups = rows[r].split("|"),
  304. buttons, accessibilityName, button, i;
  305. for (i=0; i < groups.length; i++) {
  306. $group = $('<div class="sceditor-group" />');
  307. buttons = groups[i].split(",");
  308. for (x=0; x < buttons.length; x++) {
  309. // the button must be a valid command otherwise ignore it
  310. if(!base.commands[buttons[x]])
  311. continue;
  312. $button = _tmpl("toolbarButton", {
  313. name: buttons[x],
  314. dispName: base.commands[buttons[x]].tooltip || buttons[x]
  315. }, true).click(buttonClick);
  316. if(base.commands[buttons[x]].hasOwnProperty("tooltip"))
  317. $button.attr('title', base._(base.commands[buttons[x]].tooltip));
  318. if(base.commands[buttons[x]].exec)
  319. $button.data('sceditor-wysiwygmode', true);
  320. else
  321. $button.addClass('disabled');
  322. if(base.commands[buttons[x]].txtExec)
  323. $button.data('sceditor-txtmode', true);
  324. $group.append($button);
  325. }
  326. row.append($group);
  327. }
  328. $toolbar.append(row);
  329. }
  330. // append the toolbar to the toolbarContainer option if given
  331. if(base.options.toolbarContainer)
  332. $(base.options.toolbarContainer).append($toolbar);
  333. else
  334. $editorContainer.append($toolbar);
  335. };
  336. /**
  337. * Autofocus the editor
  338. * @private
  339. */
  340. autofocus = function() {
  341. var doc = wysiwygEditor.contentWindow.document,
  342. body = doc.body, rng;
  343. if(!doc.createRange)
  344. return base.focus();
  345. if(!body.firstChild)
  346. return;
  347. rng = doc.createRange();
  348. rng.setStart(body.firstChild, 0);
  349. rng.setEnd(body.firstChild, 0);
  350. rangeHelper.selectRange(rng);
  351. body.focus();
  352. };
  353. /**
  354. * Gets the readOnly property of the editor
  355. *
  356. * @since 1.3.5
  357. * @function
  358. * @memberOf jQuery.sceditor.prototype
  359. * @name readOnly
  360. * @return {boolean}
  361. */
  362. /**
  363. * Sets the readOnly property of the editor
  364. *
  365. * @param {boolean} readOnly
  366. * @since 1.3.5
  367. * @function
  368. * @memberOf jQuery.sceditor.prototype
  369. * @name readOnly^2
  370. * @return {this}
  371. */
  372. base.readOnly = function(readOnly) {
  373. if(typeof readOnly !== 'boolean')
  374. return $textEditor.attr('readonly') === 'readonly';
  375. getWysiwygDoc().body.contentEditable = !readOnly;
  376. if(!readOnly)
  377. $textEditor.removeAttr('readonly');
  378. else
  379. $textEditor.attr('readonly', 'readonly');
  380. updateToolBar(readOnly);
  381. return this;
  382. };
  383. /**
  384. * Updates the toolbar to disable/enable the appropriate buttons
  385. * @private
  386. */
  387. updateToolBar = function(disable) {
  388. $toolbar.find('.sceditor-button').removeClass('disabled');
  389. $toolbar.find('.sceditor-button').each(function () {
  390. var button = $(this);
  391. if(disable === true)
  392. button.addClass('disabled');
  393. else if(base.inSourceMode() && !button.data('sceditor-txtmode'))
  394. button.addClass('disabled');
  395. else if (!base.inSourceMode() && !button.data('sceditor-wysiwygmode'))
  396. button.addClass('disabled');
  397. });
  398. };
  399. /**
  400. * Creates an array of all the key press functions
  401. * like emoticons, ect.
  402. * @private
  403. */
  404. initKeyPressFuncs = function () {
  405. $.each(base.commands, function (command, values) {
  406. if(values.keyPress)
  407. keyPressFuncs.push(values.keyPress);
  408. });
  409. };
  410. /**
  411. * Gets the width of the editor in px
  412. *
  413. * @since 1.3.5
  414. * @function
  415. * @memberOf jQuery.sceditor.prototype
  416. * @name width
  417. * @return {int}
  418. */
  419. /**
  420. * Sets the width of the editor
  421. *
  422. * @param {int} width Width in px
  423. * @since 1.3.5
  424. * @function
  425. * @memberOf jQuery.sceditor.prototype
  426. * @name width^2
  427. * @return {this}
  428. */
  429. base.width = function (width) {
  430. if(!width)
  431. return $editorContainer.width();
  432. $editorContainer.width(width);
  433. // fix the height and width of the textarea/iframe
  434. $wysiwygEditor.width(width);
  435. $wysiwygEditor.width(width + (width - $wysiwygEditor.outerWidth(true)));
  436. $textEditor.width(width);
  437. $textEditor.width(width + (width - $textEditor.outerWidth(true)));
  438. return this;
  439. };
  440. /**
  441. * Gets the height of the editor in px
  442. *
  443. * @since 1.3.5
  444. * @function
  445. * @memberOf jQuery.sceditor.prototype
  446. * @name height
  447. * @return {int}
  448. */
  449. /**
  450. * Sets the height of the editor
  451. *
  452. * @param {int} height Height in px
  453. * @since 1.3.5
  454. * @function
  455. * @memberOf jQuery.sceditor.prototype
  456. * @name height^2
  457. * @return {this}
  458. */
  459. base.height = function (height) {
  460. if(!height)
  461. return $editorContainer.height();
  462. $editorContainer.height(height);
  463. height -= !base.options.toolbarContainer ? $toolbar.outerHeight(true) : 0;
  464. // fix the height and width of the textarea/iframe
  465. $wysiwygEditor.height(height);
  466. $wysiwygEditor.height(height + (height - $wysiwygEditor.outerHeight(true)));
  467. $textEditor.height(height);
  468. $textEditor.height(height + (height - $textEditor.outerHeight(true)));
  469. return this;
  470. };
  471. /**
  472. * Expands the editor to the size of it's content
  473. *
  474. * @since 1.3.5
  475. * @param {Boolean} [ignoreMaxHeight=false]
  476. * @function
  477. * @name expandToContent
  478. * @memberOf jQuery.sceditor.prototype
  479. * @see #resizeToContent
  480. */
  481. base.expandToContent = function(ignoreMaxHeight) {
  482. var doc = getWysiwygDoc(),
  483. currentHeight = $editorContainer.height(),
  484. height = doc.body.scrollHeight || doc.documentElement.scrollHeight,
  485. padding = (currentHeight - $wysiwygEditor.height()),
  486. maxHeight = base.options.resizeMaxHeight || ((base.options.height || $textarea.height()) * 2);
  487. height += padding;
  488. if(ignoreMaxHeight !== true && height > maxHeight)
  489. height = maxHeight;
  490. if(height > currentHeight)
  491. base.height(height);
  492. };
  493. /**
  494. * Creates the resizer.
  495. * @private
  496. */
  497. initResize = function () {
  498. var $grip = $('<div class="sceditor-grip" />'),
  499. // cover is used to cover the editor iframe so document still gets mouse move events
  500. $cover = $('<div class="sceditor-resize-cover" />'),
  501. startX = 0,
  502. startY = 0,
  503. startWidth = 0,
  504. startHeight = 0,
  505. origWidth = $editorContainer.width(),
  506. origHeight = $editorContainer.height(),
  507. dragging = false,
  508. minHeight, maxHeight, minWidth, maxWidth, mouseMoveFunc;
  509. minHeight = base.options.resizeMinHeight || origHeight / 1.5;
  510. maxHeight = base.options.resizeMaxHeight || origHeight * 2.5;
  511. minWidth = base.options.resizeMinWidth || origWidth / 1.25;
  512. maxWidth = base.options.resizeMaxWidth || origWidth * 1.25;
  513. mouseMoveFunc = function (e) {
  514. var newHeight = startHeight + (e.pageY - startY),
  515. newWidth = startWidth + (e.pageX - startX);
  516. if (newWidth >= minWidth && (maxWidth < 0 || newWidth <= maxWidth))
  517. base.width(newWidth);
  518. if (newHeight >= minHeight && (maxHeight < 0 || newHeight <= maxHeight))
  519. base.height(newHeight);
  520. e.preventDefault();
  521. };
  522. $editorContainer.append($grip);
  523. $editorContainer.append($cover.hide());
  524. $grip.mousedown(function (e) {
  525. startX = e.pageX;
  526. startY = e.pageY;
  527. startWidth = $editorContainer.width();
  528. startHeight = $editorContainer.height();
  529. dragging = true;
  530. $editorContainer.addClass('resizing');
  531. $cover.show();
  532. $(document).bind('mousemove', mouseMoveFunc);
  533. e.preventDefault();
  534. });
  535. $(document).mouseup(function (e) {
  536. if(!dragging)
  537. return;
  538. dragging = false;
  539. $cover.hide();
  540. $editorContainer.removeClass('resizing');
  541. $(document).unbind('mousemove', mouseMoveFunc);
  542. e.preventDefault();
  543. });
  544. };
  545. /**
  546. * Handles the forms submit event
  547. * @private
  548. */
  549. formSubmitHandler = function(e) {
  550. base.updateTextareaValue();
  551. $(this).removeAttr('novalidate');
  552. if(this.checkValidity && !this.checkValidity())
  553. e.preventDefault();
  554. $(this).attr('novalidate','novalidate');
  555. base.blur();
  556. };
  557. /**
  558. * Destroys the editor, removing all elements and
  559. * event handlers.
  560. *
  561. * @function
  562. * @name destory
  563. * @memberOf jQuery.sceditor.prototype
  564. */
  565. base.destory = function () {
  566. $(document).unbind('click', documentClickHandler);
  567. $(window).unbind('resize', handleWindowResize);
  568. $(textarea.form).removeAttr('novalidate')
  569. .unbind('submit', formSubmitHandler)
  570. .unbind("reset", handleFormReset);
  571. $(getWysiwygDoc()).find('*').remove();
  572. $(getWysiwygDoc()).unbind("keypress mousedown beforedeactivate keyup focus paste keypress");
  573. $editorContainer.find('*').remove();
  574. $editorContainer.remove();
  575. $textarea.removeData("sceditor").removeData("sceditorbbcode").show();
  576. };
  577. /**
  578. * Preloads the emoticon images
  579. * Idea from: http://engineeredweb.com/blog/09/12/preloading-images-jquery-and-javascript
  580. * @private
  581. */
  582. initEmoticons = function () {
  583. // prefix emoticon root to emoticon urls
  584. if(base.options.emoticonsRoot && base.options.emoticons)
  585. {
  586. $.each(base.options.emoticons, function (idx, emoticons) {
  587. $.each(emoticons, function (key, url) {
  588. base.options.emoticons[idx][key] = base.options.emoticonsRoot + url;
  589. });
  590. });
  591. }
  592. var emoticons = $.extend({}, base.options.emoticons.more, base.options.emoticons.dropdown, base.options.emoticons.hidden),
  593. emoticon;
  594. $.each(emoticons, function (key, url) {
  595. // In SMF an empty entry means a new line
  596. if (key == '')
  597. emoticon = document.createElement('br');
  598. else
  599. {
  600. emoticon = document.createElement('img');
  601. emoticon.src = url;
  602. }
  603. preLoadCache.push(emoticon);
  604. });
  605. };
  606. /**
  607. * Creates a menu item drop down
  608. *
  609. * @param HTMLElement menuItem The button to align the drop down with
  610. * @param string dropDownName Used for styling the dropown, will be a class sceditor-dropDownName
  611. * @param string content The HTML content of the dropdown
  612. * @param bool ieUnselectable If to add the unselectable attribute to all the contents elements. Stops IE from deselecting the text in the editor
  613. * @function
  614. * @name createDropDown
  615. * @memberOf jQuery.sceditor.prototype
  616. */
  617. base.createDropDown = function (menuItem, dropDownName, content, ieUnselectable) {
  618. base.closeDropDown();
  619. // IE needs unselectable attr to stop it from unselecting the text in the editor.
  620. // The editor can cope if IE does unselect the text it's just not nice.
  621. if(ieUnselectable !== false) {
  622. $(content).find(':not(input,textarea)')
  623. .filter(function() {
  624. return this.nodeType===1;
  625. })
  626. .attr('unselectable', 'on');
  627. }
  628. var css = {
  629. top: menuItem.offset().top,
  630. left: menuItem.offset().left
  631. };
  632. $.extend(css, base.options.dropDownCss);
  633. $dropdown = $('<div class="sceditor-dropdown sceditor-' + dropDownName + '" />')
  634. .css(css)
  635. .append(content)
  636. .appendTo($('body'))
  637. .click(function (e) {
  638. // stop clicks within the dropdown from being handled
  639. e.stopPropagation();
  640. });
  641. };
  642. /**
  643. * Handles any document click and closes the dropdown if open
  644. * @private
  645. */
  646. documentClickHandler = function (e) {
  647. // ignore right clicks
  648. if(e.which !== 3)
  649. base.closeDropDown();
  650. };
  651. handlePasteEvt = function(e) {
  652. var elm = getWysiwygDoc().body,
  653. checkCount = 0,
  654. pastearea = elm.ownerDocument.createElement('div'),
  655. prePasteContent = elm.ownerDocument.createDocumentFragment();
  656. rangeHelper.saveRange();
  657. document.body.appendChild(pastearea);
  658. if (e && e.clipboardData && e.clipboardData.getData)
  659. {
  660. var html, handled=true;
  661. if ((html = e.clipboardData.getData('text/html')) || (html = e.clipboardData.getData('text/plain')))
  662. pastearea.innerHTML = html;
  663. else
  664. handled = false;
  665. if(handled)
  666. {
  667. handlePasteData(elm, pastearea);
  668. if (e.preventDefault)
  669. {
  670. e.stopPropagation();
  671. e.preventDefault();
  672. }
  673. return false;
  674. }
  675. }
  676. while(elm.firstChild)
  677. prePasteContent.appendChild(elm.firstChild);
  678. function handlePaste(elm, pastearea) {
  679. if (elm.childNodes.length > 0)
  680. {
  681. while(elm.firstChild)
  682. pastearea.appendChild(elm.firstChild);
  683. while(prePasteContent.firstChild)
  684. elm.appendChild(prePasteContent.firstChild);
  685. handlePasteData(elm, pastearea);
  686. }
  687. else
  688. {
  689. // Allow max 25 checks before giving up.
  690. // Needed inscase empty input is posted or
  691. // something gose wrong.
  692. if(checkCount > 25)
  693. {
  694. while(prePasteContent.firstChild)
  695. elm.appendChild(prePasteContent.firstChild);
  696. return;
  697. }
  698. ++checkCount;
  699. setTimeout(function () {
  700. handlePaste(elm, pastearea);
  701. }, 20);
  702. }
  703. }
  704. handlePaste(elm, pastearea);
  705. base.focus();
  706. return true;
  707. };
  708. /**
  709. * @param {Element} elm
  710. * @param {Element} pastearea
  711. * @private
  712. */
  713. handlePasteData = function(elm, pastearea) {
  714. // fix any invalid nesting
  715. $.sceditor.dom.fixNesting(pastearea);
  716. var pasteddata = pastearea.innerHTML;
  717. if(base.options.getHtmlHandler)
  718. pasteddata = base.options.getHtmlHandler(pasteddata, $(pastearea));
  719. pastearea.parentNode.removeChild(pastearea);
  720. if(base.options.getTextHandler)
  721. pasteddata = base.options.getTextHandler(pasteddata, true);
  722. rangeHelper.restoreRange();
  723. rangeHelper.insertHTML(pasteddata);
  724. };
  725. /**
  726. * Closes the current drop down
  727. *
  728. * @param bool focus If to focus the editor on close
  729. * @function
  730. * @name closeDropDown
  731. * @memberOf jQuery.sceditor.prototype
  732. */
  733. base.closeDropDown = function (focus) {
  734. if($dropdown) {
  735. $dropdown.remove();
  736. $dropdown = null;
  737. }
  738. if(focus === true)
  739. base.focus();
  740. };
  741. /**
  742. * Gets the WYSIWYG editors document
  743. * @private
  744. */
  745. getWysiwygDoc = function () {
  746. if (wysiwygEditor.contentDocument)
  747. return wysiwygEditor.contentDocument;
  748. if (wysiwygEditor.contentWindow && wysiwygEditor.contentWindow.document)
  749. return wysiwygEditor.contentWindow.document;
  750. if (wysiwygEditor.document)
  751. return wysiwygEditor.document;
  752. return null;
  753. };
  754. /**
  755. * <p>Inserts HTML into WYSIWYG editor.</p>
  756. *
  757. * <p>If endHtml is specified instead of the inserted HTML replacing the selected
  758. * text the selected text will be placed between html and endHtml. If there is
  759. * no selected text html and endHtml will be concated together.</p>
  760. *
  761. * @param {string} html
  762. * @param {string} [endHtml=null]
  763. * @param {boolean} [overrideCodeBlocking=false]
  764. * @function
  765. * @name wysiwygEditorInsertHtml
  766. * @memberOf jQuery.sceditor.prototype
  767. */
  768. base.wysiwygEditorInsertHtml = function (html, endHtml, overrideCodeBlocking) {
  769. base.focus();
  770. // don't apply to code elements
  771. if(!overrideCodeBlocking && ($(rangeHelper.parentNode()).is('code') ||
  772. $(rangeHelper.parentNode()).parents('code').length !== 0))
  773. return;
  774. rangeHelper.insertHTML(html, endHtml);
  775. };
  776. /**
  777. * Like wysiwygEditorInsertHtml except it will convert any HTML into text
  778. * before inserting it.
  779. *
  780. * @param {String} text
  781. * @param {String} [endText=null]
  782. * @function
  783. * @name wysiwygEditorInsertText
  784. * @memberOf jQuery.sceditor.prototype
  785. */
  786. base.wysiwygEditorInsertText = function (text, endText) {
  787. var escape = function(str) {
  788. if(!str)
  789. return str;
  790. return str.replace(/&/g, "&amp;")
  791. .replace(/</g, "&lt;")
  792. .replace(/>/g, "&gt;")
  793. .replace(/ /g, "&nbsp;")
  794. .replace(/\r\n|\r/g, "\n")
  795. .replace(/\n/g, "<br />");
  796. };
  797. base.wysiwygEditorInsertHtml(escape(text), escape(endText));
  798. };
  799. /**
  800. * <p>Inserts text into either WYSIWYG or textEditor depending on which
  801. * mode the editor is in.</p>
  802. *
  803. * <p>If endText is specified any selected text will be placed between
  804. * text and endText. If no text is selected text and endText will
  805. * just be concated together.</p>
  806. *
  807. * @param {String} text
  808. * @param {String} [endText=null]
  809. * @since 1.3.5
  810. * @function
  811. * @name insertText
  812. * @memberOf jQuery.sceditor.prototype
  813. */
  814. base.insertText = function (text, endText) {
  815. if(base.inSourceMode())
  816. base.textEditorInsertText(text, endText);
  817. else
  818. base.wysiwygEditorInsertText(text, endText);
  819. return this;
  820. };
  821. /**
  822. * Like wysiwygEditorInsertHtml but inserts text into the text
  823. * (source mode) editor instead
  824. *
  825. * @param {String} text
  826. * @param {String} [endText=null]
  827. * @function
  828. * @name textEditorInsertText
  829. * @memberOf jQuery.sceditor.prototype
  830. */
  831. base.textEditorInsertText = function (text, endText) {
  832. var range, start, end, txtLen;
  833. textEditor.focus();
  834. if(typeof textEditor.selectionStart !== "undefined")
  835. {
  836. start = textEditor.selectionStart;
  837. end = textEditor.selectionEnd;
  838. txtLen = text.length;
  839. if(endText)
  840. text += textEditor.value.substring(start, end) + endText;
  841. textEditor.value = textEditor.value.substring(0, start) + text + textEditor.value.substring(end, textEditor.value.length);
  842. if(endText)
  843. textEditor.selectionStart = (start + text.length) - endText.length;
  844. else
  845. textEditor.selectionStart = start + text.length;
  846. textEditor.selectionEnd = textEditor.selectionStart;
  847. }
  848. else if(typeof document.selection.createRange !== "undefined")
  849. {
  850. range = document.selection.createRange();
  851. if(endText)
  852. text += range.text + endText;
  853. range.text = text;
  854. if(endText)
  855. range.moveEnd('character', 0-endText.length);
  856. range.moveStart('character', range.End - range.Start);
  857. range.select();
  858. }
  859. else
  860. textEditor.value += text + endText;
  861. textEditor.focus();
  862. };
  863. /**
  864. * Gets the current rangeHelper instance
  865. *
  866. * @return jQuery.sceditor.rangeHelper
  867. * @function
  868. * @name getRangeHelper
  869. * @memberOf jQuery.sceditor.prototype
  870. */
  871. base.getRangeHelper = function () {
  872. return rangeHelper;
  873. };
  874. /**
  875. * Gets the value of the editor
  876. *
  877. * @since 1.3.5
  878. * @return {string}
  879. * @function
  880. * @name val
  881. * @memberOf jQuery.sceditor.prototype
  882. */
  883. /**
  884. * Sets the value of the editor
  885. *
  886. * @param {String} val
  887. * @param {Boolean} [filter]
  888. * @return {this}
  889. * @since 1.3.5
  890. * @function
  891. * @name val^2
  892. * @memberOf jQuery.sceditor.prototype
  893. */
  894. base.val = function (val, filter) {
  895. if(typeof val === "string")
  896. {
  897. if(base.inSourceMode())
  898. base.setTextareaValue(val);
  899. else
  900. {
  901. if(filter !== false && base.options.getTextHandler)
  902. val = base.options.getTextHandler(val);
  903. base.setWysiwygEditorValue(val);
  904. }
  905. return this;
  906. }
  907. return base.inSourceMode() ?
  908. base.getTextareaValue(false) :
  909. base.getWysiwygEditorValue();
  910. };
  911. /**
  912. * <p>Inserts HTML/BBCode into the editor</p>
  913. *
  914. * <p>If end is supplied any slected text will be placed between
  915. * start and end. If there is no selected text start and end
  916. * will be concated together.</p>
  917. *
  918. * @param {String} start
  919. * @param {String} [end=null]
  920. * @param {Boolean} [filter=true]
  921. * @param {Boolean} [convertEmoticons=true]
  922. * @return {this}
  923. * @since 1.3.5
  924. * @function
  925. * @name insert
  926. * @memberOf jQuery.sceditor.prototype
  927. */
  928. base.insert = function (start, end, filter, convertEmoticons) {
  929. if(base.inSourceMode())
  930. base.textEditorInsertText(start, end);
  931. else
  932. {
  933. if(end)
  934. {
  935. var html = base.getRangeHelper().selectedHtml(),
  936. frag = $('<div>').appendTo($('body')).hide().html(html);
  937. if(filter !== false && base.options.getHtmlHandler)
  938. {
  939. html = base.options.getHtmlHandler(html, frag);
  940. frag.remove();
  941. }
  942. start += html + end;
  943. }
  944. if(filter !== false && base.options.getTextHandler)
  945. start = base.options.getTextHandler(start, true);
  946. if(convertEmoticons !== false)
  947. start = replaceEmoticons(start);
  948. base.wysiwygEditorInsertHtml(start);
  949. }
  950. return this;
  951. };
  952. /**
  953. * Gets the WYSIWYG editors HTML which is between the body tags
  954. *
  955. * @param {bool} [filter=true]
  956. * @return {string}
  957. * @function
  958. * @name getWysiwygEditorValue
  959. * @memberOf jQuery.sceditor.prototype
  960. */
  961. base.getWysiwygEditorValue = function (filter) {
  962. // Possible replacement:
  963. // if(!$.sceditor.isWysiwygSupported())
  964. //if (!base.options.supportedWysiwyg)
  965. // return;
  966. var $body = $wysiwygEditor.contents().find("body"),
  967. html;
  968. // fix any invalid nesting
  969. $.sceditor.dom.fixNesting($body.get(0));
  970. html = $body.html();
  971. if(filter !== false && base.options.getHtmlHandler)
  972. html = base.options.getHtmlHandler(html, $body, filter);
  973. return html;
  974. };
  975. /**
  976. * Gets the text editor value
  977. *
  978. * @param {bool} [filter=true]
  979. * @return {string}
  980. * @function
  981. * @name getTextareaValue
  982. * @memberOf jQuery.sceditor.prototype
  983. */
  984. base.getTextareaValue = function (filter) {
  985. var val = $textEditor.val();
  986. if(filter !== false && base.options.getTextHandler)
  987. val = base.options.getTextHandler(val);
  988. return val;
  989. };
  990. /**
  991. * Sets the WYSIWYG HTML editor value. Should only be the HTML
  992. * contained within the body tags
  993. *
  994. * @param {string} value
  995. * @function
  996. * @name setWysiwygEditorValue
  997. * @memberOf jQuery.sceditor.prototype
  998. */
  999. base.setWysiwygEditorValue = function (value) {
  1000. if(!value)
  1001. value = '<p>' + ($.sceditor.ie ? '' : '<br />') + '</p>';
  1002. getWysiwygDoc().body.innerHTML = replaceEmoticons(value);
  1003. };
  1004. /**
  1005. * Sets the text editor value
  1006. *
  1007. * @param {string} value
  1008. * @function
  1009. * @name setTextareaValue
  1010. * @memberOf jQuery.sceditor.prototype
  1011. */
  1012. base.setTextareaValue = function (value) {
  1013. $textEditor.val(value);
  1014. };
  1015. /**
  1016. * Updates the textarea that the editor is replacing
  1017. * with the value currently inside the editor.
  1018. *
  1019. * @function
  1020. * @name updateTextareaValue
  1021. * @memberOf jQuery.sceditor.prototype
  1022. */
  1023. base.updateTextareaValue = function () {
  1024. if(base.inSourceMode())
  1025. $textarea.val(base.getTextareaValue(false));
  1026. else
  1027. $textarea.val(base.getWysiwygEditorValue());
  1028. };
  1029. /**
  1030. * Replaces any emoticon codes in the passed HTML with their emoticon images
  1031. * @private
  1032. */
  1033. replaceEmoticons = function (html) {
  1034. if(base.options.toolbar.indexOf('emoticon') === -1)
  1035. return html;
  1036. var emoticons = $.extend({}, base.options.emoticons.more, base.options.emoticons.dropdown, base.options.emoticons.hidden);
  1037. $.each(emoticons, function (key, url) {
  1038. // In SMF an empty entry means a new line
  1039. if (key == '')
  1040. return;
  1041. // escape the key before using it as a regex
  1042. // and append the regex to only find emoticons outside
  1043. // of HTML tags
  1044. var reg = $.sceditor.regexEscape(key) + "(?=([^\\<\\>]*?<(?!/code)|[^\\<\\>]*?$))",
  1045. group = '';
  1046. // Make sure the emoticon is surrounded by whitespace or is at the start/end of a string or html tag
  1047. if(base.options.emoticonsCompat)
  1048. {
  1049. reg = "((>|^|\\s|\xA0|\u2002|\u2003|\u2009|&nbsp;))" + reg + "(?=(\\s|$|<|\xA0|\u2002|\u2003|\u2009|&nbsp;))";
  1050. group = '$1';
  1051. }
  1052. html = html.replace(
  1053. new RegExp(reg, 'gm'),
  1054. group + _tmpl('emoticon', {key: key, url: url})
  1055. );
  1056. });
  1057. return html;
  1058. };
  1059. /**
  1060. * If the editor is in source code mode
  1061. *
  1062. * @return {bool}
  1063. * @function
  1064. * @name inSourceMode
  1065. * @memberOf jQuery.sceditor.prototype
  1066. */
  1067. base.inSourceMode = function () {
  1068. return $textEditor.is(':visible');
  1069. };
  1070. /**
  1071. * Gets if the editor is in sourceMode
  1072. *
  1073. * @return boolean
  1074. * @function
  1075. * @name sourceMode
  1076. * @memberOf jQuery.sceditor.prototype
  1077. */
  1078. /**
  1079. * Sets if the editor is in sourceMode
  1080. *
  1081. * @param {bool} enable
  1082. * @return {this}
  1083. * @function
  1084. * @name sourceMode^2
  1085. * @memberOf jQuery.sceditor.prototype
  1086. */
  1087. base.sourceMode = function (enable) {
  1088. if(typeof enable !== 'boolean')
  1089. return base.inSourceMode();
  1090. if((base.inSourceMode() && !enable) || (!base.inSourceMode() && enable))
  1091. base.toggleTextMode();
  1092. return this;
  1093. };
  1094. /**
  1095. * Switches between the WYSIWYG and plain text modes
  1096. *
  1097. * @function
  1098. * @name toggleTextMode
  1099. * @memberOf jQuery.sceditor.prototype
  1100. */
  1101. base.toggleTextMode = function () {
  1102. // don't allow switching to WYSIWYG if doesn't support it
  1103. if(!$.sceditor.isWysiwygSupported() && base.inSourceMode())
  1104. return;
  1105. if(base.inSourceMode())
  1106. base.setWysiwygEditorValue(base.getTextareaValue());
  1107. else
  1108. base.setTextareaValue(base.getWysiwygEditorValue());
  1109. lastRange = null;
  1110. $textEditor.toggle();
  1111. $wysiwygEditor.toggle();
  1112. $editorContainer.removeClass('sourceMode');
  1113. $editorContainer.removeClass('wysiwygMode');
  1114. if(base.inSourceMode())
  1115. $editorContainer.addClass('sourceMode');
  1116. else
  1117. $editorContainer.addClass('wysiwygMode');
  1118. updateToolBar();
  1119. };
  1120. textEditorSelectedText = function () {
  1121. textEditor.focus();
  1122. if(textEditor.selectionStart != null)
  1123. return textEditor.value.substring(textEditor.selectionStart, textEditor.selectionEnd);
  1124. else if(document.selection.createRange)
  1125. return document.selection.createRange().text;
  1126. };
  1127. /**
  1128. * Handles the passed command
  1129. * @private
  1130. */
  1131. handleCommand = function (caller, command) {
  1132. // check if in text mode and handle text commands
  1133. if(base.inSourceMode())
  1134. {
  1135. if(command.txtExec)
  1136. {
  1137. if($.isArray(command.txtExec))
  1138. base.textEditorInsertText.apply(base, command.txtExec);
  1139. else
  1140. command.txtExec.call(base, caller, textEditorSelectedText());
  1141. }
  1142. return;
  1143. }
  1144. if(!command.exec)
  1145. return;
  1146. if($.isFunction(command.exec))
  1147. command.exec.call(base, caller);
  1148. else
  1149. base.execCommand(command.exec, command.hasOwnProperty("execParam") ? command.execParam : null);
  1150. };
  1151. /**
  1152. * Fucuses the editors input area
  1153. *
  1154. * @return {this}
  1155. * @function
  1156. * @name focus
  1157. * @memberOf jQuery.sceditor.prototype
  1158. */
  1159. base.focus = function () {
  1160. if(!base.inSourceMode())
  1161. {
  1162. wysiwygEditor.contentWindow.focus();
  1163. // Needed for IE < 9
  1164. if(lastRange) {
  1165. rangeHelper.selectRange(lastRange);
  1166. // remove the stored range after being set.
  1167. // If the editor loses focus it should be
  1168. // saved again.
  1169. lastRange = null;
  1170. }
  1171. }
  1172. else
  1173. textEditor.focus();
  1174. return this;
  1175. };
  1176. /**
  1177. * Blurs the editors input area
  1178. *
  1179. * @return {this}
  1180. * @function
  1181. * @name blur
  1182. * @memberOf jQuery.sceditor.prototype
  1183. * @since 1.3.6
  1184. */
  1185. base.blur = function () {
  1186. // Must use an element that isn't display:hidden or visibility:hidden for iOS
  1187. // so create a special blur element to use
  1188. if(!$blurElm)
  1189. $blurElm = $('<input style="width:0; height:0; opacity:0; filter: alpha(opacity=0)" type="text" />').appendTo($editorContainer);
  1190. $blurElm.removeAttr("disabled")
  1191. .focus()
  1192. .blur()
  1193. .attr("disabled", "disabled");
  1194. return this;
  1195. };
  1196. /**
  1197. * Saves the current range. Needed for IE because it forgets
  1198. * where the cursor was and what was selected
  1199. * @private
  1200. */
  1201. saveRange = function () {
  1202. /* this is only needed for IE */
  1203. if(!$.sceditor.ie)
  1204. return;
  1205. lastRange = rangeHelper.selectedRange();
  1206. };
  1207. /**
  1208. * Executes a command on the WYSIWYG editor
  1209. *
  1210. * @param {String|Function} command
  1211. * @param {String|Boolean} [param]
  1212. * @function
  1213. * @name execCommand
  1214. * @memberOf jQuery.sceditor.prototype
  1215. */
  1216. base.execCommand = function (command, param) {
  1217. var executed = false,
  1218. $parentNode = $(rangeHelper.parentNode());
  1219. base.focus();
  1220. // don't apply any comannds to code elements
  1221. if($parentNode.is('code') || $parentNode.parents('code').length !== 0)
  1222. return;
  1223. if(getWysiwygDoc())
  1224. {
  1225. try
  1226. {
  1227. executed = getWysiwygDoc().execCommand(command, false, param);
  1228. }
  1229. catch (e) {}
  1230. }
  1231. // show error if execution failed and an error message exists
  1232. if(!executed && base.commands[command] && base.commands[command].errorMessage)
  1233. alert(base._(base.commands[command].errorMessage));
  1234. };
  1235. /**
  1236. * Handles any key press in the WYSIWYG editor
  1237. *
  1238. * @private
  1239. */
  1240. handleKeyPress = function(e) {
  1241. base.closeDropDown();
  1242. var $parentNode = $(rangeHelper.parentNode());
  1243. // "Fix" (ok it's a cludge) for blocklevel elements being duplicated in some browsers when
  1244. // enter is pressed instead of inserting a newline
  1245. if(e.which === 13)
  1246. {
  1247. if($parentNode.is('code,blockquote,pre') || $parentNode.parents('code,blockquote,pre').length !== 0)
  1248. {
  1249. lastRange = null;
  1250. base.wysiwygEditorInsertHtml('<br />', null, true);
  1251. return false;
  1252. }
  1253. }
  1254. // make sure there is always a newline after code or quote tags
  1255. var d = getWysiwygDoc();
  1256. $.sceditor.dom.rTraverse(d.body, function(node) {
  1257. if((node.nodeType === 3 && node.nodeValue !== "") ||
  1258. node.nodeName.toLowerCase() === 'br') {
  1259. // this is the last text or br node, if its in a code or quote tag
  1260. // then add a newline after it
  1261. if($(node).parents('code, blockquote').length > 0)
  1262. $(d.body).append(d.createElement('br'));
  1263. return false;
  1264. }
  1265. }, true);
  1266. // don't apply to code elements
  1267. if($parentNode.is('code') || $parentNode.parents('code').length !== 0)
  1268. return;
  1269. var i = keyPressFuncs.length;
  1270. while(i--)
  1271. keyPressFuncs[i].call(base, e, wysiwygEditor, $textEditor);
  1272. };
  1273. /**
  1274. * Handles any mousedown press in the WYSIWYG editor
  1275. * @private
  1276. */
  1277. handleFormReset = function() {
  1278. base.val($textarea.val());
  1279. };
  1280. /**
  1281. * Handles any mousedown press in the WYSIWYG editor
  1282. * @private
  1283. */
  1284. handleMouseDown = function() {
  1285. base.closeDropDown();
  1286. lastRange = null;
  1287. };
  1288. /**
  1289. * Handles the window resize event. Needed to resize then editor
  1290. * when the window size changes in fluid deisgns.
  1291. * @ignore
  1292. */
  1293. handleWindowResize = function() {
  1294. if(base.options.height && base.options.height.toString().indexOf("%") > -1)
  1295. base.height($editorContainer.parent().height() *
  1296. (parseFloat(base.options.height) / 100));
  1297. if(base.options.width && base.options.width.toString().indexOf("%") > -1)
  1298. base.width($editorContainer.parent().width() *
  1299. (parseFloat(base.options.width) / 100));
  1300. };
  1301. /**
  1302. * Translates the string into the locale language.
  1303. *
  1304. * Replaces any {0}, {1}, {2}, ect. with the params provided.
  1305. *
  1306. * @param {string} str
  1307. * @param {...String} args
  1308. * @return {string}
  1309. * @function
  1310. * @name _
  1311. * @memberOf jQuery.sceditor.prototype
  1312. */
  1313. base._ = function() {
  1314. var args = arguments;
  1315. if(locale && locale[args[0]])
  1316. args[0] = locale[args[0]];
  1317. return args[0].replace(/\{(\d+)\}/g, function(str, p1) {
  1318. return typeof args[p1-0+1] !== "undefined" ?
  1319. args[p1-0+1] :
  1320. '{' + p1 + '}';
  1321. });
  1322. };
  1323. /**
  1324. * Init the locale variable with the specified locale if possible
  1325. * @private
  1326. * @return void
  1327. */
  1328. initLocale = function() {
  1329. if($.sceditor.locale[base.options.locale])
  1330. locale = $.sceditor.locale[base.options.locale];
  1331. else
  1332. {
  1333. var lang = base.options.locale.split("-");
  1334. if($.sceditor.locale[lang[0]])
  1335. locale = $.sceditor.locale[lang[0]];
  1336. }
  1337. if(locale && locale.dateFormat)
  1338. base.options.dateFormat = locale.dateFormat;
  1339. };
  1340. // run the initializer
  1341. init();
  1342. };
  1343. /**
  1344. * Detects which version of IE is being used if any.
  1345. *
  1346. * Will be the IE version number or undefined if not IE.
  1347. *
  1348. * Source: https://gist.github.com/527683
  1349. * @type {int}
  1350. * @memberOf jQuery.sceditor
  1351. */
  1352. $.sceditor.ie = (function(){
  1353. var undef,
  1354. v = 3,
  1355. div = document.createElement('div'),
  1356. all = div.getElementsByTagName('i');
  1357. do {
  1358. div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->';
  1359. } while (all[0]);
  1360. return v > 4 ? v : undef;
  1361. }());
  1362. /**
  1363. * Detects if WYSIWYG is supported by the browser
  1364. *
  1365. * @return {bool}
  1366. * @memberOf jQuery.sceditor
  1367. */
  1368. $.sceditor.isWysiwygSupported = function() {
  1369. var contentEditable = $('<div contenteditable="true">')[0].contentEditable,
  1370. contentEditableSupported = typeof contentEditable !== 'undefined' && contentEditable !== 'inherit',
  1371. userAgent = navigator.userAgent,
  1372. match;
  1373. if(!contentEditableSupported)
  1374. return false;
  1375. // I think blackberry supports it or will at least
  1376. // give a valid value for the contentEditable detection above
  1377. // so it's' not included here.
  1378. // The latest WebOS dose support contentEditable.
  1379. // But I still till need to check if all supported
  1380. // versions of WebOS support contentEditable
  1381. // I hate having to use UA sniffing but as some mobile browsers say they support
  1382. // contentediable/design mode when it isn't usable (i.e. you can't eneter text, ect.).
  1383. // This is the only way I can think of to detect them. It's also how every other editor
  1384. // I've seen detects them
  1385. var isUnsupported = /Opera Mobi|Opera Mini/i.test(userAgent);
  1386. if(/Android/i.test(userAgent))
  1387. {
  1388. isUnsupported = true;
  1389. if(/Safari/.test(userAgent))
  1390. {
  1391. // android browser 534+ supports content editable
  1392. match = /Safari\/(\d+)/.exec(userAgent);
  1393. isUnsupported = (!match || !match[1] ? true : match[1] < 534);
  1394. }
  1395. }
  1396. // Amazon Silk doesn't but as it uses webkit like android
  1397. // it might in a later version if it uses version >= 534
  1398. if(/ Silk\//i.test(userAgent))
  1399. {
  1400. match = /AppleWebKit\/(\d+)/.exec(userAgent);
  1401. isUnsupported = (!match || !match[1] ? true : match[1] < 534);
  1402. }
  1403. // iOS 5+ supports content editable
  1404. if(/iPhone|iPod|iPad/i.test(userAgent))
  1405. isUnsupported = !/OS 5(_\d)+ like Mac OS X/i.test(userAgent);
  1406. // FireFox dose support WYSIWYG on mobiles so override
  1407. // any previous value if using FF
  1408. if(/fennec/i.test(userAgent))
  1409. isUnsupported = false;
  1410. return !isUnsupported;
  1411. };
  1412. /**
  1413. * Escapes a string so it's safe to use in regex
  1414. *
  1415. * @param {string} str
  1416. * @return {string}
  1417. * @memberOf jQuery.sceditor
  1418. */
  1419. $.sceditor.regexEscape = function (str) {
  1420. return str.replace(/[\$\?\[\]\.\*\(\)\|\\]/g, "\\$&")
  1421. .replace("<", "&lt;")
  1422. .replace(">", "&gt;");
  1423. };
  1424. $.sceditor.locale = {};
  1425. $.sceditor.commands = {
  1426. // START_COMMAND: Bold
  1427. bold: {
  1428. exec: "bold",
  1429. tooltip: "Bold"
  1430. },
  1431. // END_COMMAND
  1432. // START_COMMAND: Italic
  1433. italic: {
  1434. exec: "italic",
  1435. tooltip: "Italic"
  1436. },
  1437. // END_COMMAND
  1438. // START_COMMAND: Underline
  1439. underline: {
  1440. exec: "underline",
  1441. tooltip: "Underline"
  1442. },
  1443. // END_COMMAND
  1444. // START_COMMAND: Strikethrough
  1445. strike: {
  1446. exec: "strikethrough",
  1447. tooltip: "Strikethrough"
  1448. },
  1449. // END_COMMAND
  1450. // START_COMMAND: Subscript
  1451. subscript: {
  1452. exec: "subscript",
  1453. tooltip: "Subscript"
  1454. },
  1455. // END_COMMAND
  1456. // START_COMMAND: Superscript
  1457. superscript: {
  1458. exec: "superscript",
  1459. tooltip: "Superscript"
  1460. },
  1461. // END_COMMAND
  1462. // START_COMMAND: Left
  1463. left: {
  1464. exec: "justifyleft",
  1465. tooltip: "Align left"
  1466. },
  1467. // END_COMMAND
  1468. // START_COMMAND: Centre
  1469. center: {
  1470. exec: "justifycenter",
  1471. tooltip: "Center"
  1472. },
  1473. // END_COMMAND
  1474. // START_COMMAND: Right
  1475. right: {
  1476. exec: "justifyright",
  1477. tooltip: "Align right"
  1478. },
  1479. // END_COMMAND
  1480. // START_COMMAND: Justify
  1481. justify: {
  1482. exec: "justifyfull",
  1483. tooltip: "Justify"
  1484. },
  1485. // END_COMMAND
  1486. // START_COMMAND: Font
  1487. font: {
  1488. _dropDown: function(editor, caller, callback) {
  1489. var fonts = editor.options.fonts.split(","),
  1490. content = $("<div />"),
  1491. /** @private */
  1492. clickFunc = function () {
  1493. callback($(this).data('font'));
  1494. editor.closeDropDown(true);
  1495. return false;
  1496. };
  1497. for (var i=0; i < fonts.length; i++)
  1498. content.append(_tmpl('fontOpt', {font: fonts[i]}, true).click(clickFunc));
  1499. editor.createDropDown(caller, "font-picker", content);
  1500. },
  1501. exec: function (caller) {
  1502. var editor = this;
  1503. $.sceditor.command.get('font')._dropDown(
  1504. editor,
  1505. caller,
  1506. function(fontName) {
  1507. editor.execCommand("fontname", fontName);
  1508. }
  1509. );
  1510. },
  1511. tooltip: "Font Name"
  1512. },
  1513. // END_COMMAND
  1514. // START_COMMAND: Size
  1515. size: {
  1516. _dropDown: function(editor, caller, callback) {
  1517. var sizes = [0, 8, 10, 12, 14, 18, 24, 36];
  1518. var content = $("<div />"),
  1519. /** @private */
  1520. clickFunc = function (e) {
  1521. callback($(this).data('size'));
  1522. editor.closeDropDown(true);
  1523. e.preventDefault();
  1524. };
  1525. for (var i=1; i<= 7; i++)
  1526. content.append(_tmpl('sizeOpt', {size: i, points: sizes[i]}, true).click(clickFunc));
  1527. editor.createDropDown(caller, "fontsize-picker", content);
  1528. },
  1529. exec: function (caller) {
  1530. var editor = this;
  1531. $.sceditor.command.get('size')._dropDown(
  1532. editor,
  1533. caller,
  1534. function(fontSize) {
  1535. editor.execCommand("fontsize", fontSize);
  1536. }
  1537. );
  1538. },
  1539. tooltip: "Font Size"
  1540. },
  1541. // END_COMMAND
  1542. // START_COMMAND: Colour
  1543. color: {
  1544. _dropDown: function(editor, caller, callback) {
  1545. var genColor = {r: 255, g: 255, b: 255},
  1546. content = $("<div />"),
  1547. colorColumns = editor.options.colors?editor.options.colors.split("|"):new Array(21),
  1548. // IE is slow at string concation so use an array
  1549. html = [],
  1550. htmlIndex = 0;
  1551. for (var i=0; i < colorColumns.length; ++i) {
  1552. var colors = colorColumns[i]?colorColumns[i].split(","):new Array(21);
  1553. html[htmlIndex++] = '<div class="sceditor-color-column">';
  1554. for (var x=0; x < colors.length; ++x) {
  1555. // use pre defined colour if can otherwise use the generated color
  1556. var color = colors[x]?colors[x]:"#" + genColor.r.toString(16) + genColor.g.toString(16) + genColor.b.toString(16);
  1557. html[htmlIndex++] = '<a href="#" class="sceditor-color-option" style="background-color: '+color+'" data-color="'+color+'"></a>';
  1558. // calculate the next generated color
  1559. if(x%5===0)
  1560. genColor = {r: genColor.r, g: genColor.g-51, b: 255};
  1561. else
  1562. genColor = {r: genColor.r, g: genColor.g, b: genColor.b-51};
  1563. }
  1564. html[htmlIndex++] = '</div>';
  1565. // calculate the next generated color
  1566. if(i%5===0)
  1567. genColor = {r: genColor.r-51, g: 255, b: 255};
  1568. else
  1569. genColor = {r: genColor.r, g: 255, b: 255};
  1570. }
  1571. content.append(html.join(''))
  1572. .find('a')
  1573. .click(function (e) {
  1574. callback($(this).attr('data-color'));
  1575. editor.closeDropDown(true);
  1576. e.preventDefault();
  1577. });
  1578. editor.createDropDown(caller, "color-picker", content);
  1579. },
  1580. exec: function (caller) {
  1581. var editor = this;
  1582. $.sceditor.command.get('color')._dropDown(
  1583. editor,
  1584. caller,
  1585. function(color) {
  1586. editor.execCommand("forecolor", color);
  1587. }
  1588. );
  1589. },
  1590. tooltip: "Font Color"
  1591. },
  1592. // END_COMMAND
  1593. // START_COMMAND: Remove Format
  1594. removeformat: {
  1595. exec: "removeformat",
  1596. tooltip: "Remove Formatting"
  1597. },
  1598. // END_COMMAND
  1599. // START_COMMAND: Cut
  1600. cut: {
  1601. exec: "cut",
  1602. tooltip: "Cut",
  1603. errorMessage: "Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X"
  1604. },
  1605. // END_COMMAND
  1606. // START_COMMAND: Copy
  1607. copy: {
  1608. exec: "copy",
  1609. tooltip: "Copy",
  1610. errorMessage: "Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C"
  1611. },
  1612. // END_COMMAND
  1613. // START_COMMAND: Paste
  1614. paste: {
  1615. exec: "paste",
  1616. tooltip: "Paste",
  1617. errorMessage: "Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V"
  1618. },
  1619. // END_COMMAND
  1620. // START_COMMAND: Paste Text
  1621. pastetext: {
  1622. exec: function (caller) {
  1623. var val,
  1624. editor = this,
  1625. content = _tmpl("pastetext", {
  1626. label: editor._("Paste your text inside the following box:"),
  1627. insert: editor._("Insert")
  1628. }, true);
  1629. content.find('.button').click(function (e) {
  1630. val = content.find("#txt").val();
  1631. if(val)
  1632. editor.wysiwygEditorInsertText(val);
  1633. editor.closeDropDown(true);
  1634. e.preventDefault();
  1635. });
  1636. editor.createDropDown(caller, "pastetext", content);
  1637. },
  1638. tooltip: "Paste Text"
  1639. },
  1640. // END_COMMAND
  1641. // START_COMMAND: Bullet List
  1642. bulletlist: {
  1643. exec: "insertunorderedlist",
  1644. tooltip: "Bullet list"
  1645. },
  1646. // END_COMMAND
  1647. // START_COMMAND: Ordered List
  1648. orderedlist: {
  1649. exec: "insertorderedlist",
  1650. tooltip: "Numbered list"
  1651. },
  1652. // END_COMMAND
  1653. // START_COMMAND: Table
  1654. table: {
  1655. exec: function (caller) {
  1656. var editor = this,
  1657. content = _tmpl("table", {
  1658. rows: editor._("Rows:"),
  1659. cols: editor._("Cols:"),
  1660. insert: editor._("Insert")
  1661. }, true);
  1662. content.find('.button').click(function (e) {
  1663. var rows = content.find("#rows").val() - 0,
  1664. cols = content.find("#cols").val() - 0,
  1665. html = '<table>';
  1666. if(rows < 1 || cols < 1)
  1667. return;
  1668. for (var row=0; row < rows; row++) {
  1669. html += '<tr>';
  1670. for (var col=0; col < cols; col++)
  1671. html += '<td>' + ($.sceditor.ie ? '' : '<br class="sceditor-ignore" />') + '</td>';
  1672. html += '</tr>';
  1673. }
  1674. html += '</table>';
  1675. editor.wysiwygEditorInsertHtml(html);
  1676. editor.closeDropDown(true);
  1677. e.preventDefault();
  1678. });
  1679. editor.createDropDown(caller, "inserttable", content);
  1680. },
  1681. tooltip: "Insert a table"
  1682. },
  1683. // END_COMMAND
  1684. // START_COMMAND: Horizontal Rule
  1685. horizontalrule: {
  1686. exec: "inserthorizontalrule",
  1687. tooltip: "Insert a horizontal rule"
  1688. },
  1689. // END_COMMAND
  1690. // START_COMMAND: Code
  1691. code: {
  1692. exec: function () {
  1693. this.wysiwygEditorInsertHtml('<code>', '<br /></code>');
  1694. },
  1695. tooltip: "Code"
  1696. },
  1697. // END_COMMAND
  1698. // START_COMMAND: Image
  1699. image: {
  1700. exec: function (caller) {
  1701. var editor = this,
  1702. content = _tmpl("image", {
  1703. url: editor._("URL:"),
  1704. width: editor._("Width (optional):"),
  1705. height: editor._("Height (optional):"),
  1706. insert: editor._("Insert")
  1707. }, true);
  1708. content.find('.button').click(function (e) {
  1709. var val = content.find("#image").val(),
  1710. attrs = '',
  1711. width, height;
  1712. if((width = content.find("#width").val()))
  1713. attrs += ' width="' + width + '"';
  1714. if((height = content.find("#height").val()))
  1715. attrs += ' height="' + height + '"';
  1716. if(val && val !== "http://")
  1717. editor.wysiwygEditorInsertHtml('<img' + attrs + ' src="' + val + '" />');
  1718. editor.closeDropDown(true);
  1719. e.preventDefault();
  1720. });
  1721. editor.createDropDown(caller, "insertimage", content);
  1722. },
  1723. tooltip: "Insert an image"
  1724. },
  1725. // END_COMMAND
  1726. // START_COMMAND: E-mail
  1727. email: {
  1728. exec: function (caller) {
  1729. var editor = this,
  1730. content = _tmpl("email", {
  1731. label: editor._("E-mail:"),
  1732. insert: editor._("Insert")
  1733. }, true);
  1734. content.find('.button').click(function (e) {
  1735. var val = content.find("#email").val();
  1736. if(val)
  1737. {
  1738. // needed for IE to reset the last range
  1739. editor.focus();
  1740. if(!editor.getRangeHelper().selectedHtml())
  1741. editor.wysiwygEditorInsertHtml('<a href="' + 'mailto:' + val + '">' + val + '</a>');
  1742. else
  1743. editor.execCommand("createlink", 'mailto:' + val);
  1744. }
  1745. editor.closeDropDown(true);
  1746. e.preventDefault();
  1747. });
  1748. editor.createDropDown(caller, "insertemail", content);
  1749. },
  1750. tooltip: "Insert an email"
  1751. },
  1752. // END_COMMAND
  1753. // START_COMMAND: Link
  1754. link: {
  1755. exec: function (caller) {
  1756. var editor = this,
  1757. content = _tmpl("link", {
  1758. url: editor._("URL:"),
  1759. desc: editor._("Description (optional):"),
  1760. ins: editor._("Insert")
  1761. }, true);
  1762. content.find('.button').click(function (e) {
  1763. var val = content.find("#link").val(),
  1764. description = content.find("#des").val();
  1765. if(val !== "" && val !== "http://") {
  1766. // needed for IE to reset the last range
  1767. editor.focus();
  1768. if(!editor.getRangeHelper().selectedHtml() || description)
  1769. {
  1770. if(!description)
  1771. description = val;
  1772. editor.wysiwygEditorInsertHtml('<a href="' + val + '">' + description + '</a>');
  1773. }
  1774. else
  1775. editor.execCommand("createlink", val);
  1776. }
  1777. editor.closeDropDown(true);
  1778. e.preventDefault();
  1779. });
  1780. editor.createDropDown(caller, "insertlink", content);
  1781. },
  1782. tooltip: "Insert a link"
  1783. },
  1784. // END_COMMAND
  1785. // START_COMMAND: Unlink
  1786. unlink: {
  1787. exec: "unlink",
  1788. tooltip: "Unlink"
  1789. },
  1790. // END_COMMAND
  1791. // START_COMMAND: Quote
  1792. quote: {
  1793. exec: function (caller, html, author) {
  1794. var before = '<blockquote>',
  1795. end = '</blockquote>';
  1796. // if there is HTML passed set end to null so any selected
  1797. // text is replaced
  1798. if(html)
  1799. {
  1800. author = (author ? '<cite>' + author + '</cite>' : '');
  1801. before = before + author + html + end + '<br />';
  1802. end = null;
  1803. }
  1804. // if not add a newline to the end of the inserted quote
  1805. else if(this.getRangeHelper().selectedHtml() === "")
  1806. end = '<br />' + end;
  1807. this.wysiwygEditorInsertHtml(before, end);
  1808. },
  1809. tooltip: "Insert a Quote"
  1810. },
  1811. // END_COMMAND
  1812. // START_COMMAND: Emoticons
  1813. emoticon: {
  1814. exec: function (caller) {
  1815. var appendEmoticon,
  1816. editor = this,
  1817. end = (editor.options.emoticonsCompat ? ' ' : ''),
  1818. content = $('<div />'),
  1819. line = $('<div />');
  1820. appendEmoticon = function (code, emoticon) {
  1821. line.append($('<img />')
  1822. .attr({
  1823. src: emoticon,
  1824. alt: code
  1825. })
  1826. .click(function (e) {
  1827. editor.insert($(this).attr('alt') + end);
  1828. editor.closeDropDown(true);
  1829. e.preventDefault();
  1830. })
  1831. );
  1832. if(line.children().length > 3) {
  1833. content.append(line);
  1834. line = $('<div />');
  1835. }
  1836. };
  1837. $.each(editor.options.emoticons.dropdown, appendEmoticon);
  1838. if(line.children().length > 0)
  1839. content.append(line);
  1840. if(editor.options.emoticons.more) {
  1841. var more = $(
  1842. this._('<a class="sceditor-more">{0}</a>', this._("More"))
  1843. ).click(function () {
  1844. var emoticons = $.extend({}, editor.options.emoticons.dropdown, editor.options.emoticons.more);
  1845. content = $('<div />');
  1846. $.each(emoticons, appendEmoticon);
  1847. if(line.children().length > 0)
  1848. content.append(line);
  1849. editor.createDropDown(caller, "insertemoticon", content);
  1850. return false;
  1851. });
  1852. content.append(more);
  1853. }
  1854. editor.createDropDown(caller, "insertemoticon", content);
  1855. },
  1856. txtExec: function(caller) {
  1857. $.sceditor.command.get('emoticon').exec.call(this, caller);
  1858. },
  1859. keyPress: function (e)
  1860. {
  1861. // make sure emoticons command is in the toolbar before running
  1862. if(this.options.toolbar.indexOf('emoticon') === -1)
  1863. return;
  1864. var editor = this,
  1865. pos = 0,
  1866. curChar = String.fromCharCode(e.which);
  1867. if(!editor.EmoticonsCache) {
  1868. editor.EmoticonsCache = [];
  1869. $.each($.extend({}, editor.options.emoticons.more, editor.options.emoticons.dropdown, editor.options.emoticons.hidden), function(key, url) {
  1870. editor.EmoticonsCache[pos++] = [
  1871. key,
  1872. _tmpl("emoticon", {key: key, url: url})
  1873. ];
  1874. });
  1875. editor.EmoticonsCache.sort(function(a, b){
  1876. return a[0].length - b[0].length;
  1877. });
  1878. }
  1879. if(!editor.longestEmoticonCode)
  1880. editor.longestEmoticonCode = editor.EmoticonsCache[editor.EmoticonsCache.length - 1][0].length;
  1881. if(editor.getRangeHelper().raplaceKeyword(editor.EmoticonsCache, true, true, editor.longestEmoticonCode, editor.options.emoticonsCompat, curChar))
  1882. {
  1883. if(/^\s$/.test(curChar) && editor.options.emoticonsCompat)
  1884. return true;
  1885. e.preventDefault();
  1886. e.stopPropagation();
  1887. return false;
  1888. }
  1889. },
  1890. tooltip: "Insert an emoticon"
  1891. },
  1892. // END_COMMAND
  1893. // START_COMMAND: YouTube
  1894. youtube: {
  1895. _dropDown: function (editor, caller, handleIdFunc) {
  1896. var matches,
  1897. content = _tmpl("youtubeMenu", {
  1898. label: editor._("Video URL:"),
  1899. insert: editor._("Insert")
  1900. }, true);
  1901. content.find('.button').click(function (e) {
  1902. var val = content.find("#link").val().replace("http://", "");
  1903. if (val !== "") {
  1904. matches = val.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/);
  1905. if (matches) val = matches[1];
  1906. if (/^[a-zA-Z0-9_\-]{11}$/.test(val))
  1907. handleIdFunc(val);
  1908. else
  1909. alert('Invalid YouTube video');
  1910. }
  1911. editor.closeDropDown(true);
  1912. e.preventDefault();
  1913. });
  1914. editor.createDropDown(caller, "insertlink", content);
  1915. },
  1916. exec: function (caller) {
  1917. var editor = this;
  1918. $.sceditor.command.get('youtube')._dropDown(
  1919. editor,
  1920. caller,
  1921. function(id) {
  1922. editor.wysiwygEditorInsertHtml(_tmpl("youtube", { id: id }));
  1923. }
  1924. );
  1925. },
  1926. tooltip: "Insert a YouTube video"
  1927. },
  1928. // END_COMMAND
  1929. // START_COMMAND: Date
  1930. date: {
  1931. _date: function (editor) {
  1932. var now = new Date(),
  1933. year = now.getYear(),
  1934. month = now.getMonth()+1,
  1935. day = now.getDate();
  1936. if(year < 2000)
  1937. year = 1900 + year;
  1938. if(month < 10)
  1939. month = "0" + month;
  1940. if(day < 10)
  1941. day = "0" + day;
  1942. return editor.options.dateFormat.replace(/year/i, year).replace(/month/i, month).replace(/day/i, day);
  1943. },
  1944. exec: function () {
  1945. this.insertText($.sceditor.command.get('date')._date(this));
  1946. },
  1947. txtExec: function () {
  1948. this.insertText($.sceditor.command.get('date')._date(this));
  1949. },
  1950. tooltip: "Insert current date"
  1951. },
  1952. // END_COMMAND
  1953. // START_COMMAND: Time
  1954. time: {
  1955. _time: function () {
  1956. var now = new Date(),
  1957. hours = now.getHours(),
  1958. mins = now.getMinutes(),
  1959. secs = now.getSeconds();
  1960. if(hours < 10)
  1961. hours = "0" + hours;
  1962. if(mins < 10)
  1963. mins = "0" + mins;
  1964. if(secs < 10)
  1965. secs = "0" + secs;
  1966. return hours + ':' + mins + ':' + secs;
  1967. },
  1968. exec: function () {
  1969. this.insertText($.sceditor.command.get('time')._time());
  1970. },
  1971. txtExec: function () {
  1972. this.insertText($.sceditor.command.get('time')._time());
  1973. },
  1974. tooltip: "Insert current time"
  1975. },
  1976. // END_COMMAND
  1977. // START_COMMAND: Ltr
  1978. ltr: {
  1979. exec: function() {
  1980. var editor = this,
  1981. elm = editor.getRangeHelper().getFirstBlockParent(),
  1982. $elm = $(elm);
  1983. editor.focus();
  1984. if(!elm || $elm.is('body'))
  1985. {
  1986. editor.execCommand("formatBlock", "p");
  1987. elm = editor.getRangeHelper().getFirstBlockParent();
  1988. $elm = $(elm);
  1989. if(!elm || $elm.is('body'))
  1990. return;
  1991. }
  1992. if($elm.css('direction') === 'ltr')
  1993. $(elm).css('direction', '');
  1994. else
  1995. $(elm).attr('direction', 'ltr');
  1996. },
  1997. tooltip: "Left-to-Right"
  1998. },
  1999. // END_COMMAND
  2000. // START_COMMAND: Rtl
  2001. rtl: {
  2002. exec: function() {
  2003. var editor = this,
  2004. elm = editor.getRangeHelper().getFirstBlockParent(),
  2005. $elm = $(elm);
  2006. editor.focus();
  2007. if(!elm || $elm.is('body'))
  2008. {
  2009. editor.execCommand("formatBlock", "p");
  2010. elm = editor.getRangeHelper().getFirstBlockParent();
  2011. $elm = $(elm);
  2012. if(!elm || $elm.is('body'))
  2013. return;
  2014. }
  2015. if($elm.css('direction') === 'rtl')
  2016. $(elm).css('direction', '');
  2017. else
  2018. $(elm).css('direction', 'rtl');
  2019. },
  2020. tooltip: "Right-to-Left"
  2021. },
  2022. // END_COMMAND
  2023. // START_COMMAND: Print
  2024. print: {
  2025. exec: "print",
  2026. tooltip: "Print"
  2027. },
  2028. // END_COMMAND
  2029. // START_COMMAND: Source
  2030. source: {
  2031. exec: function () {
  2032. this.toggleTextMode();
  2033. },
  2034. txtExec: function () {
  2035. this.toggleTextMode();
  2036. },
  2037. tooltip: "View source"
  2038. },
  2039. // END_COMMAND
  2040. // this is here so that commands above can be removed
  2041. // without having to remove the , after the last one.
  2042. // Needed for IE.
  2043. ignore: {}
  2044. };
  2045. /**
  2046. * Range helper class
  2047. * @class rangeHelper
  2048. * @name jQuery.sceditor.rangeHelper
  2049. */
  2050. $.sceditor.rangeHelper = function(w, d) {
  2051. var win, doc,
  2052. isW3C = true,
  2053. startMarker = "sceditor-start-marker",
  2054. endMarker = "sceditor-end-marker",
  2055. base = this,
  2056. init, _createMarker, _getOuterText, _selectOuterText;
  2057. /**
  2058. * @constructor
  2059. * @param Window window
  2060. * @param Document document
  2061. * @private
  2062. */
  2063. init = function (window, document) {
  2064. doc = document || window.contentDocument || window.document;
  2065. win = window;
  2066. isW3C = !!window.getSelection;
  2067. }(w, d);
  2068. /**
  2069. * <p>Inserts HTML into the current range replacing any selected
  2070. * text.</p>
  2071. *
  2072. * <p>If endHTML is specified the selected contents will be put between
  2073. * html and endHTML. If there is nothing selected html and endHTML are
  2074. * just concated together.</p>
  2075. *
  2076. * @param {string} html
  2077. * @param {string} endHTML
  2078. * @function
  2079. * @name insertHTML
  2080. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2081. */
  2082. base.insertHTML = function(html, endHTML) {
  2083. var node, div;
  2084. if(endHTML)
  2085. html += base.selectedHtml() + endHTML;
  2086. if(isW3C)
  2087. {
  2088. div = doc.createElement('div');
  2089. node = doc.createDocumentFragment();
  2090. div.innerHTML = html;
  2091. while(div.firstChild)
  2092. node.appendChild(div.firstChild);
  2093. base.insertNode(node);
  2094. }
  2095. else
  2096. base.selectedRange().pasteHTML(html);
  2097. };
  2098. /**
  2099. * <p>The same as insertHTML except with DOM nodes instead</p>
  2100. *
  2101. * <p><strong>Warning:</strong> the nodes must belong to the
  2102. * document they are being inserted into. Some browsers
  2103. * will throw exceptions if they don't.</p>
  2104. *
  2105. * @param {Node} node
  2106. * @param {Node} endNode
  2107. *
  2108. * @function
  2109. * @name insertNode
  2110. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2111. */
  2112. base.insertNode = function(node, endNode) {
  2113. if(isW3C)
  2114. {
  2115. var toInsert = doc.createDocumentFragment(),
  2116. range = base.selectedRange(),
  2117. selection, selectAfter;
  2118. toInsert.appendChild(node);
  2119. if(endNode)
  2120. {
  2121. toInsert.appendChild(range.extractContents());
  2122. toInsert.appendChild(endNode);
  2123. }
  2124. selectAfter = toInsert.lastChild;
  2125. range.deleteContents();
  2126. range.insertNode(toInsert);
  2127. selection = doc.createRange();
  2128. selection.setStartAfter(selectAfter);
  2129. base.selectRange(selection);
  2130. }
  2131. else
  2132. base.insertHTML(node.outerHTML, endNode?endNode.outerHTML:null);
  2133. };
  2134. /**
  2135. * <p>Clones the selected Range</p>
  2136. *
  2137. * <p>IE <= 8 will return a TextRange, all other browsers
  2138. * will return a Range object.</p>
  2139. *
  2140. * @return {Range|TextRange}
  2141. * @function
  2142. * @name cloneSelected
  2143. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2144. */
  2145. base.cloneSelected = function() {
  2146. if(!isW3C)
  2147. return base.selectedRange().duplicate();
  2148. return base.selectedRange().cloneRange();
  2149. };
  2150. /**
  2151. * <p>Gets the selected Range</p>
  2152. *
  2153. * <p>IE <= 8 will return a TextRange, all other browsers
  2154. * will return a Range object.</p>
  2155. *
  2156. * @return {Range|TextRange}
  2157. * @function
  2158. * @name selectedRange
  2159. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2160. */
  2161. base.selectedRange = function() {
  2162. var sel;
  2163. if(win.getSelection)
  2164. sel = win.getSelection();
  2165. else
  2166. sel = doc.selection;
  2167. if(sel.getRangeAt && sel.rangeCount <= 0)
  2168. sel.addRange(doc.createRange());
  2169. if(!isW3C)
  2170. return sel.createRange();
  2171. return sel.getRangeAt(0);
  2172. };
  2173. /**
  2174. * Gets the currently selected HTML
  2175. *
  2176. * @return {string}
  2177. * @function
  2178. * @name selectedHtml
  2179. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2180. */
  2181. base.selectedHtml = function() {
  2182. var range = base.selectedRange();
  2183. if(!range)
  2184. return '';
  2185. // IE9+ and all other browsers
  2186. if (window.XMLSerializer)
  2187. return new XMLSerializer().serializeToString(range.cloneContents());
  2188. // IE < 9
  2189. if(!isW3C)
  2190. {
  2191. if(range.text !== '' && range.htmlText)
  2192. return range.htmlText;
  2193. }
  2194. return '';
  2195. };
  2196. /**
  2197. * Gets the parent node of the selected contents in the range
  2198. *
  2199. * @return {HTMLElement}
  2200. * @function
  2201. * @name parentNode
  2202. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2203. */
  2204. base.parentNode = function() {
  2205. var range = base.selectedRange();
  2206. if(isW3C)
  2207. return range.commonAncestorContainer;
  2208. else
  2209. return range.parentElement();
  2210. };
  2211. /**
  2212. * Gets the first block level parent of the selected
  2213. * contents of the range.
  2214. *
  2215. * @return {HTMLElement}
  2216. * @function
  2217. * @name getFirstBlockParent
  2218. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2219. */
  2220. base.getFirstBlockParent = function() {
  2221. var func = function(node) {
  2222. if(!$.sceditor.dom.isInline(node))
  2223. return node;
  2224. var p = node.parentNode;
  2225. if(p)
  2226. return func(p);
  2227. return null;
  2228. };
  2229. return func(base.parentNode());
  2230. };
  2231. /**
  2232. * Inserts a node at either the start or end of the current selection
  2233. *
  2234. * @param {Bool} start
  2235. * @param {Node} node
  2236. * @function
  2237. * @name insertNodeAt
  2238. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2239. */
  2240. base.insertNodeAt = function(start, node) {
  2241. var range = base.cloneSelected();
  2242. range.collapse(start);
  2243. if(range.insertNode)
  2244. range.insertNode(node);
  2245. else
  2246. range.pasteHTML(node.outerHTML);
  2247. };
  2248. /**
  2249. * Creates a marker node
  2250. *
  2251. * @param {String} id
  2252. * @return {Node}
  2253. * @private
  2254. */
  2255. _createMarker = function(id) {
  2256. base.removeMarker(id);
  2257. var marker = doc.createElement("span");
  2258. marker.id = id;
  2259. marker.style.lineHeight = "0";
  2260. marker.style.display = "none";
  2261. marker.className = "sceditor-selection";
  2262. return marker;
  2263. };
  2264. /**
  2265. * Inserts start/end markers for the current selection
  2266. * which can be used by restoreRange to re-select the
  2267. * range.
  2268. *
  2269. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2270. * @function
  2271. * @name insertMarkers
  2272. */
  2273. base.insertMarkers = function() {
  2274. base.insertNodeAt(true, _createMarker(startMarker));
  2275. base.insertNodeAt(false, _createMarker(endMarker));
  2276. };
  2277. /**
  2278. * Gets the marker with the specified ID
  2279. *
  2280. * @param {String} id
  2281. * @return {Node}
  2282. * @function
  2283. * @name getMarker
  2284. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2285. */
  2286. base.getMarker = function(id) {
  2287. return doc.getElementById(id);
  2288. };
  2289. /**
  2290. * Removes the marker with the specified ID
  2291. *
  2292. * @param {String} id
  2293. * @function
  2294. * @name removeMarker
  2295. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2296. */
  2297. base.removeMarker = function(id) {
  2298. var marker = base.getMarker(id);
  2299. if(marker)
  2300. marker.parentNode.removeChild(marker);
  2301. };
  2302. /**
  2303. * Removes the start/end markers
  2304. *
  2305. * @function
  2306. * @name removeMarkers
  2307. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2308. */
  2309. base.removeMarkers = function() {
  2310. base.removeMarker(startMarker);
  2311. base.removeMarker(endMarker);
  2312. };
  2313. /**
  2314. * Saves the current range location. Alias of insertMarkers()
  2315. *
  2316. * @function
  2317. * @name saveRage
  2318. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2319. */
  2320. base.saveRange = function() {
  2321. base.insertMarkers();
  2322. };
  2323. /**
  2324. * Select the specified range
  2325. *
  2326. * @param {Range|TextRange} range
  2327. * @function
  2328. * @name selectRange
  2329. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2330. */
  2331. base.selectRange = function(range) {
  2332. if(!isW3C)
  2333. range.select();
  2334. else
  2335. {
  2336. win.getSelection().removeAllRanges();
  2337. win.getSelection().addRange(range);
  2338. }
  2339. };
  2340. /**
  2341. * Restores the last range saved by saveRange() or insertMarkers()
  2342. *
  2343. * @function
  2344. * @name restoreRange
  2345. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2346. */
  2347. base.restoreRange = function() {
  2348. var range = base.selectedRange(),
  2349. start = base.getMarker(startMarker),
  2350. end = base.getMarker(endMarker);
  2351. if(!start || !end)
  2352. return false;
  2353. if(!isW3C)
  2354. {
  2355. range = doc.body.createTextRange();
  2356. var marker = doc.body.createTextRange();
  2357. marker.moveToElementText(start);
  2358. range.setEndPoint('StartToStart', marker);
  2359. range.moveStart('character', 0);
  2360. marker.moveToElementText(end);
  2361. range.setEndPoint('EndToStart', marker);
  2362. range.moveEnd('character', 0);
  2363. base.selectRange(range);
  2364. }
  2365. else
  2366. {
  2367. range = doc.createRange();
  2368. range.setStartBefore(start);
  2369. range.setEndAfter(end);
  2370. base.selectRange(range);
  2371. }
  2372. base.removeMarkers();
  2373. };
  2374. /**
  2375. * Selects the text left and right of the current selection
  2376. * @param int left
  2377. * @param int right
  2378. * @private
  2379. */
  2380. _selectOuterText = function(left, right) {
  2381. var range = base.cloneSelected();
  2382. range.collapse(false);
  2383. if(!isW3C)
  2384. {
  2385. range.moveStart('character', 0-left);
  2386. range.moveEnd('character', right);
  2387. }
  2388. else
  2389. {
  2390. range.setStart(range.startContainer, range.startOffset-left);
  2391. range.setEnd(range.endContainer, range.endOffset+right);
  2392. //range.deleteContents();
  2393. }
  2394. base.selectRange(range);
  2395. };
  2396. /**
  2397. * Gets the text left or right of the current selection
  2398. * @param bool before
  2399. * @param int length
  2400. * @private
  2401. */
  2402. _getOuterText = function(before, length) {
  2403. var ret = "",
  2404. range = base.cloneSelected();
  2405. range.collapse(false);
  2406. if(before)
  2407. {
  2408. if(!isW3C)
  2409. {
  2410. range.moveStart('character', 0-length);
  2411. ret = range.text;
  2412. }
  2413. else
  2414. {
  2415. ret = range.startContainer.textContent.substr(0, range.startOffset);
  2416. ret = ret.substr(Math.max(0, ret.length - length));
  2417. }
  2418. }
  2419. else
  2420. {
  2421. if(!isW3C)
  2422. {
  2423. range.moveEnd('character', length);
  2424. ret = range.text;
  2425. }
  2426. else
  2427. ret = range.startContainer.textContent.substr(range.startOffset, length);
  2428. }
  2429. return ret;
  2430. };
  2431. /**
  2432. * Replaces keys with values based on the current range
  2433. *
  2434. * @param {Array} rep
  2435. * @param {Bool} includePrev If to include text before or just text after
  2436. * @param {Bool} repSorted If the keys array is pre sorted
  2437. * @param {Int} longestKey Length of the longest key
  2438. * @param {Bool} requireWhiteSpace If the key must be surrounded by whitespace
  2439. * @function
  2440. * @name raplaceKeyword
  2441. * @memberOf jQuery.sceditor.rangeHelper.prototype
  2442. */
  2443. base.raplaceKeyword = function(rep, includeAfter, repSorted, longestKey, requireWhiteSpace, curChar) {
  2444. if(!repSorted)
  2445. rep.sort(function(a, b){
  2446. return a.length - b.length;
  2447. });
  2448. var maxKeyLen = longestKey || rep[rep.length-1][0].length,
  2449. before, after, str, i, start, left, pat, lookStart;
  2450. before = after = str = "";
  2451. if(requireWhiteSpace)
  2452. {
  2453. // forcing spaces around doesn't work with textRanges as they will select text
  2454. // on the other side of an image causing space-img-key to be returned as
  2455. // space-key which would be valid when it's not.
  2456. if(!isW3C)
  2457. return false;
  2458. ++maxKeyLen;
  2459. }
  2460. before = _getOuterText(true, maxKeyLen);
  2461. if(includeAfter)
  2462. after = _getOuterText(false, maxKeyLen);
  2463. str = before + (curChar!=null?curChar:"") + after;
  2464. i = rep.length;
  2465. while(i--)
  2466. {
  2467. pat = new RegExp("(?:[\\s\xA0\u2002\u2003\u2009])" + $.sceditor.regexEscape(rep[i][0]) + "(?=[\\s\xA0\u2002\u2003\u2009])");
  2468. lookStart = before.length - 1 - rep[i][0].length;
  2469. if(requireWhiteSpace)
  2470. --lookStart;
  2471. lookStart = Math.max(0, lookStart);
  2472. if((!requireWhiteSpace && (start = str.indexOf(rep[i][0], lookStart)) > -1) ||
  2473. (requireWhiteSpace && (start = str.substr(lookStart).search(pat)) > -1))
  2474. {
  2475. if(requireWhiteSpace)
  2476. start += lookStart + 1;
  2477. // make sure the substr is between before and after not entierly in one
  2478. // or the other
  2479. if(start > before.length || start+rep[i][0].length + (requireWhiteSpace?1:0) < before.length)
  2480. continue;
  2481. left = before.length - start;
  2482. _selectOuterText(left, rep[i][0].length-left-(curChar!=null&&/^\S/.test(curChar)?1:0));
  2483. base.insertHTML(rep[i][1]);
  2484. return true;
  2485. }
  2486. }
  2487. return false;
  2488. };
  2489. };
  2490. /**
  2491. * Static DOM helper class
  2492. * @class dom
  2493. * @name jQuery.sceditor.dom
  2494. */
  2495. $.sceditor.dom =
  2496. /** @lends jQuery.sceditor.dom */
  2497. {
  2498. /**
  2499. * Loop all child nodes of the passed node
  2500. *
  2501. * The function should accept 1 parameter being the node.
  2502. * If the function returns false the loop will be exited.
  2503. *
  2504. * @param {HTMLElement} node
  2505. * @param {function} func Function that is called for every node, should accept 1 param for the node
  2506. * @param {bool} innermostFirst If the innermost node should be passed to the function before it's parents
  2507. * @param {bool} siblingsOnly If to only traverse the nodes siblings
  2508. * @param {bool} reverse If to traverse the nodes in reverse
  2509. */
  2510. traverse: function(node, func, innermostFirst, siblingsOnly, reverse) {
  2511. if(node)
  2512. {
  2513. node = reverse ? node.lastChild : node.firstChild;
  2514. while(node != null)
  2515. {
  2516. var next = reverse ? node.previousSibling : node.nextSibling;
  2517. if(!innermostFirst && func(node) === false)
  2518. return false;
  2519. // traverse all children
  2520. if(!siblingsOnly && this.traverse(node, func, innermostFirst, siblingsOnly, reverse) === false)
  2521. return false;
  2522. if(innermostFirst && func(node) === false)
  2523. return false;
  2524. // move to next child
  2525. node = next;
  2526. }
  2527. }
  2528. },
  2529. /**
  2530. * Like traverse but loops in reverse
  2531. * @see traverse
  2532. */
  2533. rTraverse: function(node, func, innermostFirst, siblingsOnly) {
  2534. this.traverse(node, func, innermostFirst, siblingsOnly, true);
  2535. },
  2536. /**
  2537. * List of block level elements seperated by bars (|)
  2538. * @type {string}
  2539. */
  2540. blockLevelList: "|body|hr|p|div|h1|h2|h3|h4|h5|h6|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|blockquote|center|",
  2541. /**
  2542. * Checks if an element is inline
  2543. *
  2544. * @return {bool}
  2545. */
  2546. isInline: function(elm, includeCodeAsBlock) {
  2547. if(!elm || elm.nodeType !== 1)
  2548. return true;
  2549. if(includeCodeAsBlock && elm.tagName.toLowerCase() === 'code')
  2550. return false;
  2551. return $.sceditor.dom.blockLevelList.indexOf("|" + elm.tagName.toLowerCase() + "|") < 0;
  2552. },
  2553. /**
  2554. * Copys the CSS from 1 node to another
  2555. *
  2556. * @param {HTMLElement} from
  2557. * @param {HTMLElement} to
  2558. */
  2559. copyCSS: function(from, to) {
  2560. to.style.cssText = from.style.cssText;
  2561. },
  2562. /**
  2563. * Fixes block level elements inside in inline elements.
  2564. *
  2565. * @param {HTMLElement} node
  2566. */
  2567. fixNesting: function(node) {
  2568. var base = this,
  2569. getLastInlineParent = function(node) {
  2570. while(base.isInline(node.parentNode, true))
  2571. node = node.parentNode;
  2572. return node;
  2573. };
  2574. base.traverse(node, function(node) {
  2575. // if node is an element, and it is blocklevel and the parent isn't block level
  2576. // then it needs fixing
  2577. if(node.nodeType === 1 && !base.isInline(node, true) && base.isInline(node.parentNode, true))
  2578. {
  2579. var parent = getLastInlineParent(node),
  2580. rParent = parent.parentNode,
  2581. before = base.extractContents(parent, node),
  2582. middle = node;
  2583. // copy current styling so when moved out of the parent
  2584. // it still has the same styling
  2585. base.copyCSS(middle, middle);
  2586. rParent.insertBefore(before, parent);
  2587. rParent.insertBefore(middle, parent);
  2588. }
  2589. });
  2590. },
  2591. /**
  2592. * Finds the common parent of two nodes
  2593. *
  2594. * @param {HTMLElement} node1
  2595. * @param {HTMLElement} node2
  2596. * @return {HTMLElement}
  2597. */
  2598. findCommonAncestor: function(node1, node2) {
  2599. // not as fast as making two arrays of parents and comparing
  2600. // but is a lot smaller and as it's currently only used with
  2601. // fixing invalid nesting it doesn't need to be very fast
  2602. return $(node1).parents().has($(node2)).first();
  2603. },
  2604. /**
  2605. * Removes unused whitespace from the root and all it's children
  2606. *
  2607. * @param HTMLElement root
  2608. * @return void
  2609. */
  2610. removeWhiteSpace: function(root) {
  2611. // 00A0 is non-breaking space which should not be striped
  2612. var regex = /[^\S|\u00A0]+/g;
  2613. this.traverse(root, function(node) {
  2614. if(node.nodeType === 3 && $(node).parents('code, pre').length === 0 && node.nodeValue)
  2615. {
  2616. // new lines in text nodes are always ignored in normal handling
  2617. node.nodeValue = node.nodeValue.replace(/[\r\n]/, "");
  2618. //remove empty nodes
  2619. if(!node.nodeValue.length)
  2620. {
  2621. node.parentNode.removeChild(node);
  2622. return;
  2623. }
  2624. if(!/\S|\u00A0/.test(node.nodeValue))
  2625. node.nodeValue = " ";
  2626. else if(regex.test(node.nodeValue))
  2627. node.nodeValue = node.nodeValue.replace(regex, " ");
  2628. }
  2629. });
  2630. },
  2631. /**
  2632. * Extracts all the nodes between the start and end nodes
  2633. *
  2634. * @param {HTMLElement} startNode The node to start extracting at
  2635. * @param {HTMLElement} endNode The node to stop extracting at
  2636. * @return {DocumentFragment}
  2637. */
  2638. extractContents: function(startNode, endNode) {
  2639. var base = this,
  2640. $commonAncestor = base.findCommonAncestor(startNode, endNode),
  2641. commonAncestor = !$commonAncestor?null:$commonAncestor.get(0),
  2642. startReached = false,
  2643. endReached = false;
  2644. return (function extract(root) {
  2645. var df = startNode.ownerDocument.createDocumentFragment();
  2646. base.traverse(root, function(node) {
  2647. // if end has been reached exit loop
  2648. if(endReached || (node === endNode && startReached))
  2649. {
  2650. endReached = true;
  2651. return false;
  2652. }
  2653. if(node === startNode)
  2654. startReached = true;
  2655. var c, n;
  2656. if(startReached)
  2657. {
  2658. // if the start has been reached and this elm contains
  2659. // the end node then clone it
  2660. if(jQuery.contains(node, endNode) && node.nodeType === 1)
  2661. {
  2662. c = extract(node);
  2663. n = node.cloneNode(false);
  2664. n.appendChild(c);
  2665. df.appendChild(n);
  2666. }
  2667. // otherwise just move it
  2668. else
  2669. df.appendChild(node);
  2670. }
  2671. // if this node contains the start node then add it
  2672. else if(jQuery.contains(node, startNode) && node.nodeType === 1)
  2673. {
  2674. c = extract(node);
  2675. n = node.cloneNode(false);
  2676. n.appendChild(c);
  2677. df.appendChild(n);
  2678. }
  2679. }, false);
  2680. return df;
  2681. }(commonAncestor));
  2682. }
  2683. };
  2684. /**
  2685. * Static command helper class
  2686. * @class command
  2687. * @name jQuery.sceditor.command
  2688. */
  2689. $.sceditor.command =
  2690. /** @lends jQuery.sceditor.command */
  2691. {
  2692. /**
  2693. * Gets a command
  2694. *
  2695. * @param {String} name
  2696. * @return {Object|null}
  2697. * @since v1.3.5
  2698. */
  2699. get: function(name) {
  2700. return $.sceditor.commands[name] || null;
  2701. },
  2702. /**
  2703. * <p>Adds a command to the editor or updates an exisiting
  2704. * command if a command with the specified name already exists.</p>
  2705. *
  2706. * <p>Once a command is add it can be included in the toolbar by
  2707. * adding it's name to the toolbar option in the constructor. It
  2708. * can also be executed manually by calling {@link jQuery.sceditor.execCommand}</p>
  2709. *
  2710. * @example
  2711. * $.sceditor.command.set("hello",
  2712. * {
  2713. * exec: function() {
  2714. * alert("Hello World!");
  2715. * }
  2716. * });
  2717. *
  2718. * @param {String} name
  2719. * @param {Object} cmd
  2720. * @return {this|false} Returns false if name or cmd is false
  2721. * @since v1.3.5
  2722. */
  2723. set: function(name, cmd) {
  2724. if(!name || !cmd)
  2725. return false;
  2726. // merge any existing command properties
  2727. cmd = $.extend($.sceditor.commands[name] || {}, cmd);
  2728. cmd.remove = function() { $.sceditor.command.remove(name); };
  2729. $.sceditor.commands[name] = cmd;
  2730. return this;
  2731. },
  2732. /**
  2733. * Removes a command
  2734. *
  2735. * @param {String} name
  2736. * @return {this}
  2737. * @since v1.3.5
  2738. */
  2739. remove: function(name) {
  2740. if($.sceditor.commands[name])
  2741. delete $.sceditor.commands[name];
  2742. return this;
  2743. }
  2744. };
  2745. /**
  2746. * Checks if a command with the specified name exists
  2747. *
  2748. * @param {String} name
  2749. * @return {Bool}
  2750. * @deprecated Since v1.3.5
  2751. * @memberOf jQuery.sceditor
  2752. */
  2753. $.sceditor.commandExists = function(name) {
  2754. return !!$.sceditor.command.get(name);
  2755. };
  2756. /**
  2757. * Adds/updates a command.
  2758. *
  2759. * Only name and exec are required. Exec is only required if
  2760. * the command dose not already exist.
  2761. *
  2762. * @param {String} name The commands name
  2763. * @param {String|Function} exec The commands exec function or string for the native execCommand
  2764. * @param {String} tooltip The commands tooltip text
  2765. * @param {Function} keypress Function that gets called every time a key is pressed
  2766. * @param {Function|Array} txtExec Called when the command is executed in source mode or array containing prepend and optional append
  2767. * @return {Bool}
  2768. * @deprecated Since v1.3.5
  2769. * @memberOf jQuery.sceditor
  2770. */
  2771. $.sceditor.setCommand = function(name, exec, tooltip, keypress, txtExec) {
  2772. return !!$.sceditor.command.set(name, {
  2773. exec: exec,
  2774. tooltip: tooltip,
  2775. keypress: keypress,
  2776. txtExec: txtExec
  2777. });
  2778. };
  2779. $.sceditor.defaultOptions = {
  2780. // Toolbar buttons order and groups. Should be comma seperated and have a bar | to seperate groups
  2781. toolbar: "bold,italic,underline,strike,subscript,superscript|left,center,right,justify|" +
  2782. "font,size,color,removeformat|cut,copy,paste,pastetext|bulletlist,orderedlist|" +
  2783. "table|code,quote|horizontalrule,image,email,link,unlink|emoticon,youtube,date,time|" +
  2784. "ltr,rtl|print,source",
  2785. // Stylesheet to include in the WYSIWYG editor. Will style the WYSIWYG elements
  2786. style: "jquery.sceditor.default.css",
  2787. // Comma seperated list of fonts for the font selector
  2788. fonts: "Arial,Arial Black,Comic Sans MS,Courier New,Georgia,Impact,Sans-serif,Serif,Times New Roman,Trebuchet MS,Verdana",
  2789. // Colors should be comma seperated and have a bar | to signal a new column. If null the colors will be auto generated.
  2790. colors: null,
  2791. locale: "en",
  2792. charset: "utf-8",
  2793. // compatibility mode for if you have emoticons such as :/ This mode requires
  2794. // emoticons to be surrounded by whitespace or end of line chars. This mode
  2795. // has limited As You Type emoticon converstion support (end of line chars)
  2796. // are not accepted as whitespace so only emoticons surrounded by whitespace
  2797. // will work
  2798. emoticonsCompat: false,
  2799. emoticonsRoot: '',
  2800. emoticons: {
  2801. dropdown: {
  2802. ":)": "emoticons/smile.png",
  2803. ":angel:": "emoticons/angel.png",
  2804. ":angry:": "emoticons/angry.png",
  2805. "8-)": "emoticons/cool.png",
  2806. ":'(": "emoticons/cwy.png",
  2807. ":ermm:": "emoticons/ermm.png",
  2808. ":D": "emoticons/grin.png",
  2809. "<3": "emoticons/heart.png",
  2810. ":(": "emoticons/sad.png",
  2811. ":O": "emoticons/shocked.png",
  2812. ":P": "emoticons/tongue.png",
  2813. ";)": "emoticons/wink.png"
  2814. },
  2815. more: {
  2816. ":alien:": "emoticons/alien.png",
  2817. ":blink:": "emoticons/blink.png",
  2818. ":blush:": "emoticons/blush.png",
  2819. ":cheerful:": "emoticons/cheerful.png",
  2820. ":devil:": "emoticons/devil.png",
  2821. ":dizzy:": "emoticons/dizzy.png",
  2822. ":getlost:": "emoticons/getlost.png",
  2823. ":happy:": "emoticons/happy.png",
  2824. ":kissing:": "emoticons/kissing.png",
  2825. ":ninja:": "emoticons/ninja.png",
  2826. ":pinch:": "emoticons/pinch.png",
  2827. ":pouty:": "emoticons/pouty.png",
  2828. ":sick:": "emoticons/sick.png",
  2829. ":sideways:": "emoticons/sideways.png",
  2830. ":silly:": "emoticons/silly.png",
  2831. ":sleeping:": "emoticons/sleeping.png",
  2832. ":unsure:": "emoticons/unsure.png",
  2833. ":woot:": "emoticons/w00t.png",
  2834. ":wassat:": "emoticons/wassat.png"
  2835. },
  2836. hidden: {
  2837. ":whistling:": "emoticons/whistling.png",
  2838. ":love:": "emoticons/wub.png"
  2839. }
  2840. },
  2841. // Width of the editor. Set to null for automatic with
  2842. width: null,
  2843. // Height of the editor including toolbat. Set to null for automatic height
  2844. height: null,
  2845. // If to allow the editor to be resized
  2846. resizeEnabled: true,
  2847. // Min resize to width, set to null for half textarea width or -1 for unlimited
  2848. resizeMinWidth: null,
  2849. // Min resize to height, set to null for half textarea height or -1 for unlimited
  2850. resizeMinHeight: null,
  2851. // Max resize to height, set to null for double textarea height or -1 for unlimited
  2852. resizeMaxHeight: null,
  2853. // Max resize to width, set to null for double textarea width or -1 for unlimited
  2854. resizeMaxWidth: null,
  2855. getHtmlHandler: null,
  2856. getTextHandler: null,
  2857. // date format. year, month and day will be replaced with the users current year, month and day.
  2858. dateFormat: "year-month-day",
  2859. toolbarContainer: null,
  2860. // Curently experimental
  2861. enablePasteFiltering: false,
  2862. readOnly: false,
  2863. rtl: false,
  2864. autofocus: false,
  2865. autoExpand: false,
  2866. // If to run the editor without WYSIWYG support
  2867. runWithoutWysiwygSupport: false,
  2868. id: null,
  2869. //add css to dropdown menu (eg. z-index)
  2870. dropDownCss: { }
  2871. };
  2872. $.fn.sceditor = function (options) {
  2873. if((!options || !options.runWithoutWysiwygSupport) && !$.sceditor.isWysiwygSupported())
  2874. return;
  2875. return this.each(function () {
  2876. (new $.sceditor(this, options));
  2877. });
  2878. };
  2879. })(jQuery, window, document);
  2880. (function($) {
  2881. var extensionMethods = {
  2882. InsertText: function(text, bClear) {
  2883. var bIsSource = this.inSourceMode();
  2884. // @TODO make it put the quote close to the current selection
  2885. if (!bIsSource)
  2886. this.toggleTextMode();
  2887. var current_value = bClear ? text + "\n" : this.getTextareaValue(false) + "\n" + text + "\n";
  2888. this.setTextareaValue(current_value);
  2889. if (!bIsSource)
  2890. this.toggleTextMode();
  2891. },
  2892. getText: function(filter) {
  2893. var current_value = '';
  2894. if(this.inSourceMode())
  2895. current_value = this.getTextareaValue(false);
  2896. else
  2897. current_value = this.getWysiwygEditorValue(filter);
  2898. return current_value;
  2899. },
  2900. appendEmoticon: function (code, emoticon) {
  2901. if (code == '')
  2902. line.append($('<br />'));
  2903. else
  2904. line.append($('<img />')
  2905. .attr({
  2906. src: emoticon,
  2907. alt: code,
  2908. })
  2909. .click(function (e) {
  2910. var start = '', end = '';
  2911. if (base.options.emoticonsCompat)
  2912. {
  2913. start = '<span> ';
  2914. end = ' </span>';
  2915. }
  2916. if (base.inSourceMode())
  2917. base.textEditorInsertText(' ' + $(this).attr('alt') + ' ');
  2918. else
  2919. base.wysiwygEditorInsertHtml(start + '<img src="' + $(this).attr("src") +
  2920. '" data-sceditor-emoticon="' + $(this).attr('alt') + '" />' + end);
  2921. e.preventDefault();
  2922. })
  2923. );
  2924. if (line.children().length > 0)
  2925. content.append(line);
  2926. $(".sceditor-toolbar").append(content);
  2927. },
  2928. storeLastState: function (){
  2929. this.wasSource = this.inSourceMode();
  2930. },
  2931. setTextMode: function () {
  2932. if (!this.inSourceMode())
  2933. this.toggleTextMode();
  2934. },
  2935. createPermanentDropDown: function() {
  2936. var emoticons = $.extend({}, this.options.emoticons.dropdown);
  2937. var popup_exists = false;
  2938. content = $('<div class="sceditor-insertemoticon" />');
  2939. line = $('<div />');
  2940. base = this;
  2941. for (smiley_popup in this.options.emoticons.popup)
  2942. {
  2943. popup_exists = true;
  2944. break;
  2945. }
  2946. if (popup_exists)
  2947. {
  2948. this.options.emoticons.more = this.options.emoticons.popup;
  2949. moreButton = $('<div class="sceditor-more-button" />').attr({'class': "sceditor-more"}).text('[' + this._('More') + ']').click(function () {
  2950. if ($(".sceditor-smileyPopup").length > 0)
  2951. {
  2952. $(".sceditor-smileyPopup").fadeIn('fast');
  2953. }
  2954. else
  2955. {
  2956. var emoticons = $.extend({}, base.options.emoticons.popup);
  2957. var popup_position;
  2958. var titlebar = $('<div class="catbg sceditor-popup-grip"/>');
  2959. popupContent = $('<div id="sceditor-popup"/>');
  2960. allowHide = true;
  2961. popupContent.append(titlebar);
  2962. line = $('<div />');
  2963. closeButton = $('<span />').text('[' + base._('Close') + ']').click(function () {
  2964. $(".sceditor-smileyPopup").fadeOut('fast');
  2965. });
  2966. $.each(emoticons, base.appendEmoticon);
  2967. if (line.children().length > 0)
  2968. popupContent.append(line);
  2969. if (typeof closeButton !== "undefined")
  2970. popupContent.append(closeButton);
  2971. // IE needs unselectable attr to stop it from unselecting the text in the editor.
  2972. // The editor can cope if IE does unselect the text it's just not nice.
  2973. if(base.ieUnselectable !== false) {
  2974. content = $(content);
  2975. content.find(':not(input,textarea)').filter(function() { return this.nodeType===1; }).attr('unselectable', 'on');
  2976. }
  2977. $dropdown = $('<div class="sceditor-dropdown sceditor-smileyPopup" />').append(popupContent);
  2978. $dropdown.appendTo($('body'));
  2979. dropdownIgnoreLastClick = true;
  2980. $dropdown.css({
  2981. position: "fixed",
  2982. top: $(window).height() * 0.2,
  2983. left: $(window).width() * 0.5 - ($dropdown.width() / 2),
  2984. "max-width": "50%"
  2985. });
  2986. $('.sceditor-smileyPopup').animaDrag({
  2987. speed: 150,
  2988. interval: 120,
  2989. grip: '.sceditor-popup-grip'
  2990. });
  2991. // stop clicks within the dropdown from being handled
  2992. $dropdown.click(function (e) {
  2993. e.stopPropagation();
  2994. });
  2995. }
  2996. });
  2997. }
  2998. $.each(emoticons, base.appendEmoticon);
  2999. if (typeof moreButton !== "undefined")
  3000. content.append(moreButton);
  3001. }
  3002. };
  3003. $.extend(true, $['sceditor'].prototype, extensionMethods);
  3004. })(jQuery);
  3005. $.sceditor.setCommand(
  3006. 'ftp',
  3007. function (caller) {
  3008. var editor = this,
  3009. content = $(this._('<form><div><label for="link">{0}</label> <input type="text" id="link" value="ftp://" /></div>' +
  3010. '<div><label for="des">{1}</label> <input type="text" id="des" value="" /></div></form>',
  3011. this._("URL:"),
  3012. this._("Description (optional):")
  3013. ))
  3014. .submit(function () {return false;});
  3015. content.append($(
  3016. this._('<div><input type="button" class="button" value="{0}" /></div>',
  3017. this._("Insert")
  3018. )).click(function (e) {
  3019. var val = $(this).parent("form").find("#link").val(),
  3020. description = $(this).parent("form").find("#des").val();
  3021. if(val !== "" && val !== "ftp://") {
  3022. // needed for IE to reset the last range
  3023. editor.focus();
  3024. if(!editor.getRangeHelper().selectedHtml() || description)
  3025. {
  3026. if(!description)
  3027. description = val;
  3028. editor.wysiwygEditorInsertHtml('<a href="' + val + '">' + description + '</a>');
  3029. }
  3030. else
  3031. editor.execCommand("createlink", val);
  3032. }
  3033. editor.closeDropDown(true);
  3034. e.preventDefault();
  3035. }));
  3036. editor.createDropDown(caller, "insertlink", content);
  3037. },
  3038. 'Insert FTP Link'
  3039. );
  3040. $.sceditor.setCommand(
  3041. 'glow',
  3042. function () {
  3043. this.wysiwygEditorInsertHtml('[glow=red,2,300]', '[/glow]');
  3044. },
  3045. 'Glow'
  3046. );
  3047. $.sceditor.setCommand(
  3048. 'shadow',
  3049. function () {
  3050. this.wysiwygEditorInsertHtml('[shadow=red,left]', '[/shadow]');
  3051. },
  3052. 'Shadow'
  3053. );
  3054. $.sceditor.setCommand(
  3055. 'tt',
  3056. function () {
  3057. this.wysiwygEditorInsertHtml('<tt>', '</tt>');
  3058. },
  3059. 'Teletype'
  3060. );
  3061. $.sceditor.setCommand(
  3062. 'pre',
  3063. function () {
  3064. this.wysiwygEditorInsertHtml('<pre>', '</pre>');
  3065. },
  3066. 'Pre'
  3067. );