jquery.sceditor.js 91 KB

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