jquery.sceditor.js 93 KB

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