| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284 |
- /**
- * Super simple wysiwyg editor on Bootstrap v0.5.3
- * http://hackerwins.github.io/summernote/
- *
- * summernote.js
- * Copyright 2013 Alan Hong. and outher contributors
- * summernote may be freely distributed under the MIT license./
- *
- * Date: 2014-07-27T05:15Z
- */
- (function (factory) {
- /* global define */
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
- define(['jquery'], factory);
- } else {
- // Browser globals: jQuery
- factory(window.jQuery);
- }
- }(function ($) {
- if ('function' !== typeof Array.prototype.reduce) {
- /**
- * Array.prototype.reduce fallback
- *
- * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
- */
- Array.prototype.reduce = function (callback, optInitialValue) {
- var idx, value, length = this.length >>> 0, isValueSet = false;
- if (1 < arguments.length) {
- value = optInitialValue;
- isValueSet = true;
- }
- for (idx = 0; length > idx; ++idx) {
- if (this.hasOwnProperty(idx)) {
- if (isValueSet) {
- value = callback(value, this[idx], idx, this);
- } else {
- value = this[idx];
- isValueSet = true;
- }
- }
- }
- if (!isValueSet) {
- throw new TypeError('Reduce of empty array with no initial value');
- }
- return value;
- };
- }
- var isSupportAmd = typeof define === 'function' && define.amd;
- /**
- * returns whether font is installed or not.
- * @param {String} fontName
- * @return {Boolean}
- */
- var isFontInstalled = function (fontName) {
- var testFontName = fontName === 'Comic Sans MS' ? 'Courier New' : 'Comic Sans MS';
- var $tester = $('<div>').css({
- position: 'absolute',
- left: '-9999px',
- top: '-9999px',
- fontSize: '200px'
- }).text('mmmmmmmmmwwwwwww').appendTo(document.body);
- var originalWidth = $tester.css('fontFamily', testFontName).width();
- var width = $tester.css('fontFamily', fontName + ',' + testFontName).width();
- $tester.remove();
- return originalWidth !== width;
- };
- /**
- * Object which check platform and agent
- */
- var agent = {
- isMac: navigator.appVersion.indexOf('Mac') > -1,
- isMSIE: navigator.userAgent.indexOf('MSIE') > -1 || navigator.userAgent.indexOf('Trident') > -1,
- isFF: navigator.userAgent.indexOf('Firefox') > -1,
- jqueryVersion: parseFloat($.fn.jquery),
- isSupportAmd: isSupportAmd,
- hasCodeMirror: isSupportAmd ? require.specified('CodeMirror') : !!window.CodeMirror,
- isFontInstalled: isFontInstalled
- };
- /**
- * func utils (for high-order func's arg)
- */
- var func = (function () {
- var eq = function (elA) {
- return function (elB) {
- return elA === elB;
- };
- };
- var eq2 = function (elA, elB) {
- return elA === elB;
- };
- var ok = function () {
- return true;
- };
- var fail = function () {
- return false;
- };
- var not = function (f) {
- return function () {
- return !f.apply(f, arguments);
- };
- };
- var self = function (a) {
- return a;
- };
- var idCounter = 0;
- /**
- * generate a globally-unique id
- *
- * @param {String} [prefix]
- */
- var uniqueId = function (prefix) {
- var id = ++idCounter + '';
- return prefix ? prefix + id : id;
- };
- /**
- * returns bnd (bounds) from rect
- *
- * - IE Compatability Issue: http://goo.gl/sRLOAo
- * - Scroll Issue: http://goo.gl/sNjUc
- *
- * @param {Rect} rect
- * @return {Object} bounds
- * @return {Number} bounds.top
- * @return {Number} bounds.left
- * @return {Number} bounds.width
- * @return {Number} bounds.height
- */
- var rect2bnd = function (rect) {
- var $document = $(document);
- return {
- top: rect.top + $document.scrollTop(),
- left: rect.left + $document.scrollLeft(),
- width: rect.right - rect.left,
- height: rect.bottom - rect.top
- };
- };
- /**
- * returns a copy of the object where the keys have become the values and the values the keys.
- * @param {Object} obj
- * @return {Object}
- */
- var invertObject = function (obj) {
- var inverted = {};
- for (var key in obj) {
- if (obj.hasOwnProperty(key)) {
- inverted[obj[key]] = key;
- }
- }
- return inverted;
- };
- return {
- eq: eq,
- eq2: eq2,
- ok: ok,
- fail: fail,
- not: not,
- self: self,
- uniqueId: uniqueId,
- rect2bnd: rect2bnd,
- invertObject: invertObject
- };
- })();
- /**
- * list utils
- */
- var list = (function () {
- /**
- * returns the first element of an array.
- * @param {Array} array
- */
- var head = function (array) {
- return array[0];
- };
- /**
- * returns the last element of an array.
- * @param {Array} array
- */
- var last = function (array) {
- return array[array.length - 1];
- };
- /**
- * returns everything but the last entry of the array.
- * @param {Array} array
- */
- var initial = function (array) {
- return array.slice(0, array.length - 1);
- };
- /**
- * returns the rest of the elements in an array.
- * @param {Array} array
- */
- var tail = function (array) {
- return array.slice(1);
- };
- /**
- * returns next item.
- * @param {Array} array
- */
- var next = function (array, item) {
- var idx = array.indexOf(item);
- if (idx === -1) { return null; }
- return array[idx + 1];
- };
- /**
- * returns prev item.
- * @param {Array} array
- */
- var prev = function (array, item) {
- var idx = array.indexOf(item);
- if (idx === -1) { return null; }
- return array[idx - 1];
- };
- /**
- * get sum from a list
- * @param {Array} array - array
- * @param {Function} fn - iterator
- */
- var sum = function (array, fn) {
- fn = fn || func.self;
- return array.reduce(function (memo, v) {
- return memo + fn(v);
- }, 0);
- };
- /**
- * returns a copy of the collection with array type.
- * @param {Collection} collection - collection eg) node.childNodes, ...
- */
- var from = function (collection) {
- var result = [], idx = -1, length = collection.length;
- while (++idx < length) {
- result[idx] = collection[idx];
- }
- return result;
- };
- /**
- * cluster elements by predicate function.
- * @param {Array} array - array
- * @param {Function} fn - predicate function for cluster rule
- * @param {Array[]}
- */
- var clusterBy = function (array, fn) {
- if (!array.length) { return []; }
- var aTail = tail(array);
- return aTail.reduce(function (memo, v) {
- var aLast = last(memo);
- if (fn(last(aLast), v)) {
- aLast[aLast.length] = v;
- } else {
- memo[memo.length] = [v];
- }
- return memo;
- }, [[head(array)]]);
- };
- /**
- * returns a copy of the array with all falsy values removed
- * @param {Array} array - array
- * @param {Function} fn - predicate function for cluster rule
- */
- var compact = function (array) {
- var aResult = [];
- for (var idx = 0, sz = array.length; idx < sz; idx ++) {
- if (array[idx]) { aResult.push(array[idx]); }
- }
- return aResult;
- };
- return { head: head, last: last, initial: initial, tail: tail,
- prev: prev, next: next, sum: sum, from: from,
- compact: compact, clusterBy: clusterBy };
- })();
- /**
- * Dom functions
- */
- var dom = (function () {
- /**
- * returns whether node is `note-editable` or not.
- *
- * @param {Element} node
- * @return {Boolean}
- */
- var isEditable = function (node) {
- return node && $(node).hasClass('note-editable');
- };
- var isControlSizing = function (node) {
- return node && $(node).hasClass('note-control-sizing');
- };
- /**
- * build layoutInfo from $editor(.note-editor)
- *
- * @param {jQuery} $editor
- * @return {Object}
- */
- var buildLayoutInfo = function ($editor) {
- var makeFinder;
- // air mode
- if ($editor.hasClass('note-air-editor')) {
- var id = list.last($editor.attr('id').split('-'));
- makeFinder = function (sIdPrefix) {
- return function () { return $(sIdPrefix + id); };
- };
- return {
- editor: function () { return $editor; },
- editable: function () { return $editor; },
- popover: makeFinder('#note-popover-'),
- handle: makeFinder('#note-handle-'),
- dialog: makeFinder('#note-dialog-')
- };
- // frame mode
- } else {
- makeFinder = function (sClassName) {
- return function () { return $editor.find(sClassName); };
- };
- return {
- editor: function () { return $editor; },
- dropzone: makeFinder('.note-dropzone'),
- toolbar: makeFinder('.note-toolbar'),
- editable: makeFinder('.note-editable'),
- codable: makeFinder('.note-codable'),
- statusbar: makeFinder('.note-statusbar'),
- popover: makeFinder('.note-popover'),
- handle: makeFinder('.note-handle'),
- dialog: makeFinder('.note-dialog')
- };
- }
- };
- /**
- * returns predicate which judge whether nodeName is same
- * @param {String} sNodeName
- */
- var makePredByNodeName = function (sNodeName) {
- // nodeName is always uppercase.
- return function (node) {
- return node && node.nodeName === sNodeName;
- };
- };
- var isPara = function (node) {
- // Chrome(v31.0), FF(v25.0.1) use DIV for paragraph
- return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName);
- };
- var isList = function (node) {
- return node && /^UL|^OL/.test(node.nodeName);
- };
- var isCell = function (node) {
- return node && /^TD|^TH/.test(node.nodeName);
- };
- /**
- * find nearest ancestor predicate hit
- *
- * @param {Element} node
- * @param {Function} pred - predicate function
- */
- var ancestor = function (node, pred) {
- while (node) {
- if (pred(node)) { return node; }
- if (isEditable(node)) { break; }
- node = node.parentNode;
- }
- return null;
- };
- /**
- * returns new array of ancestor nodes (until predicate hit).
- *
- * @param {Element} node
- * @param {Function} [optional] pred - predicate function
- */
- var listAncestor = function (node, pred) {
- pred = pred || func.fail;
- var aAncestor = [];
- ancestor(node, function (el) {
- aAncestor.push(el);
- return pred(el);
- });
- return aAncestor;
- };
- /**
- * returns common ancestor node between two nodes.
- *
- * @param {Element} nodeA
- * @param {Element} nodeB
- */
- var commonAncestor = function (nodeA, nodeB) {
- var aAncestor = listAncestor(nodeA);
- for (var n = nodeB; n; n = n.parentNode) {
- if ($.inArray(n, aAncestor) > -1) { return n; }
- }
- return null; // difference document area
- };
- /**
- * listing all Nodes between two nodes.
- * FIXME: nodeA and nodeB must be sorted, use comparePoints later.
- *
- * @param {Element} nodeA
- * @param {Element} nodeB
- */
- var listBetween = function (nodeA, nodeB) {
- var aNode = [];
- var isStart = false, isEnd = false;
- // DFS(depth first search) with commonAcestor.
- (function fnWalk(node) {
- if (!node) { return; } // traverse fisnish
- if (node === nodeA) { isStart = true; } // start point
- if (isStart && !isEnd) { aNode.push(node); } // between
- if (node === nodeB) { isEnd = true; return; } // end point
- for (var idx = 0, sz = node.childNodes.length; idx < sz; idx++) {
- fnWalk(node.childNodes[idx]);
- }
- })(commonAncestor(nodeA, nodeB));
- return aNode;
- };
- /**
- * listing all previous siblings (until predicate hit).
- * @param {Element} node
- * @param {Function} [optional] pred - predicate function
- */
- var listPrev = function (node, pred) {
- pred = pred || func.fail;
- var aNext = [];
- while (node) {
- aNext.push(node);
- if (pred(node)) { break; }
- node = node.previousSibling;
- }
- return aNext;
- };
- /**
- * listing next siblings (until predicate hit).
- *
- * @param {Element} node
- * @param {Function} [pred] - predicate function
- */
- var listNext = function (node, pred) {
- pred = pred || func.fail;
- var aNext = [];
- while (node) {
- aNext.push(node);
- if (pred(node)) { break; }
- node = node.nextSibling;
- }
- return aNext;
- };
- /**
- * listing descendant nodes
- *
- * @param {Element} node
- * @param {Function} [pred] - predicate function
- */
- var listDescendant = function (node, pred) {
- var aDescendant = [];
- pred = pred || func.ok;
- // start DFS(depth first search) with node
- (function fnWalk(current) {
- if (node !== current && pred(current)) {
- aDescendant.push(current);
- }
- for (var idx = 0, sz = current.childNodes.length; idx < sz; idx++) {
- fnWalk(current.childNodes[idx]);
- }
- })(node);
- return aDescendant;
- };
- /**
- * insert node after preceding
- *
- * @param {Element} node
- * @param {Element} preceding - predicate function
- */
- var insertAfter = function (node, preceding) {
- var next = preceding.nextSibling, parent = preceding.parentNode;
- if (next) {
- parent.insertBefore(node, next);
- } else {
- parent.appendChild(node);
- }
- return node;
- };
- /**
- * append elements.
- *
- * @param {Element} node
- * @param {Collection} aChild
- */
- var appends = function (node, aChild) {
- $.each(aChild, function (idx, child) {
- node.appendChild(child);
- });
- return node;
- };
- var isText = makePredByNodeName('#text');
- /**
- * returns #text's text size or element's childNodes size
- *
- * @param {Element} node
- */
- var length = function (node) {
- if (isText(node)) { return node.nodeValue.length; }
- return node.childNodes.length;
- };
- /**
- * returns whether boundaryPoint is edge or not.
- *
- * @param {BoundaryPoint} boundaryPoitn
- * @return {Boolean}
- */
- var isEdgeBP = function (boundaryPoint) {
- return boundaryPoint.offset === 0 ||
- boundaryPoint.offset === length(boundaryPoint.node);
- };
- /**
- * returns offset from parent.
- *
- * @param {Element} node
- */
- var position = function (node) {
- var offset = 0;
- while ((node = node.previousSibling)) { offset += 1; }
- return offset;
- };
- var hasChildren = function (node) {
- return node && node.childNodes && node.childNodes.length;
- };
- /**
- * returns previous boundaryPoint
- *
- * @param {BoundaryPoint} boundaryPoitn
- * @return {BoundaryPoint}
- */
- var prevBP = function (boundaryPoint) {
- var node = boundaryPoint.node,
- offset = boundaryPoint.offset;
- if (offset === 0) {
- if (isEditable(node)) { return null; }
- return {node: node.parentNode, offset: position(node)};
- } else {
- if (hasChildren(node)) {
- var child = node.childNodes[offset - 1];
- return {node: child, offset: length(child)};
- } else {
- return {node: node, offset: offset - 1};
- }
- }
- };
- /**
- * return offsetPath(array of offset) from ancestor
- *
- * @param {Element} ancestor - ancestor node
- * @param {Element} node
- */
- var makeOffsetPath = function (ancestor, node) {
- var aAncestor = list.initial(listAncestor(node, func.eq(ancestor)));
- return $.map(aAncestor, position).reverse();
- };
- /**
- * return element from offsetPath(array of offset)
- *
- * @param {Element} ancestor - ancestor node
- * @param {array} aOffset - offsetPath
- */
- var fromOffsetPath = function (ancestor, aOffset) {
- var current = ancestor;
- for (var i = 0, sz = aOffset.length; i < sz; i++) {
- current = current.childNodes[aOffset[i]];
- }
- return current;
- };
- /**
- * split element or #text
- *
- * @param {Element} node
- * @param {Number} offset
- */
- var split = function (node, offset) {
- if (offset === 0) { return node; }
- if (offset >= length(node)) { return node.nextSibling; }
- // splitText
- if (isText(node)) { return node.splitText(offset); }
- // splitElement
- var child = node.childNodes[offset];
- node = insertAfter(node.cloneNode(false), node);
- return appends(node, listNext(child));
- };
- /**
- * split dom tree by boundaryPoint(pivot and offset)
- *
- * @param {Element} root
- * @param {Element} pivot - this will be boundaryPoint's node
- * @param {Number} offset - this will be boundaryPoint's offset
- */
- var splitTree = function (root, pivot, offset) {
- var aAncestor = listAncestor(pivot, func.eq(root));
- if (aAncestor.length === 1) { return split(pivot, offset); }
- return aAncestor.reduce(function (node, parent) {
- var clone = parent.cloneNode(false);
- insertAfter(clone, parent);
- if (node === pivot) {
- node = split(node, offset);
- }
- appends(clone, listNext(node));
- return clone;
- });
- };
- /**
- * remove node, (bRemoveChild: remove child or not)
- * @param {Element} node
- * @param {Boolean} bRemoveChild
- */
- var remove = function (node, bRemoveChild) {
- if (!node || !node.parentNode) { return; }
- if (node.removeNode) { return node.removeNode(bRemoveChild); }
- var elParent = node.parentNode;
- if (!bRemoveChild) {
- var aNode = [];
- var i, sz;
- for (i = 0, sz = node.childNodes.length; i < sz; i++) {
- aNode.push(node.childNodes[i]);
- }
- for (i = 0, sz = aNode.length; i < sz; i++) {
- elParent.insertBefore(aNode[i], node);
- }
- }
- elParent.removeChild(node);
- };
- var html = function ($node) {
- return dom.isTextarea($node[0]) ? $node.val() : $node.html();
- };
- return {
- blank: agent.isMSIE ? ' ' : '<br/>',
- emptyPara: '<p><br/></p>',
- isEditable: isEditable,
- isControlSizing: isControlSizing,
- buildLayoutInfo: buildLayoutInfo,
- isText: isText,
- isPara: isPara,
- isList: isList,
- isTable: makePredByNodeName('TABLE'),
- isCell: isCell,
- isAnchor: makePredByNodeName('A'),
- isDiv: makePredByNodeName('DIV'),
- isLi: makePredByNodeName('LI'),
- isSpan: makePredByNodeName('SPAN'),
- isB: makePredByNodeName('B'),
- isU: makePredByNodeName('U'),
- isS: makePredByNodeName('S'),
- isI: makePredByNodeName('I'),
- isImg: makePredByNodeName('IMG'),
- isTextarea: makePredByNodeName('TEXTAREA'),
- length: length,
- isEdgeBP: isEdgeBP,
- prevBP: prevBP,
- ancestor: ancestor,
- listAncestor: listAncestor,
- listNext: listNext,
- listPrev: listPrev,
- listDescendant: listDescendant,
- commonAncestor: commonAncestor,
- listBetween: listBetween,
- insertAfter: insertAfter,
- position: position,
- makeOffsetPath: makeOffsetPath,
- fromOffsetPath: fromOffsetPath,
- splitTree: splitTree,
- remove: remove,
- html: html
- };
- })();
- var settings = {
- // version
- version: '0.5.3',
- /**
- * options
- */
- options: {
- width: null, // set editor width
- height: null, // set editor height, ex) 300
- minHeight: null, // set minimum height of editor
- maxHeight: null, // set maximum height of editor
- focus: false, // set focus to editable area after initializing summernote
- tabsize: 4, // size of tab ex) 2 or 4
- styleWithSpan: true, // style with span (Chrome and FF only)
- disableLinkTarget: false, // hide link Target Checkbox
- disableDragAndDrop: false, // disable drag and drop event
- disableResizeEditor: false, // disable resizing editor
- codemirror: { // codemirror options
- mode: 'text/html',
- htmlMode: true,
- lineNumbers: true,
- autoFormatOnStart: false
- },
- // language
- lang: 'en-US', // language 'en-US', 'ko-KR', ...
- direction: null, // text direction, ex) 'rtl'
- // toolbar
- toolbar: [
- ['style', ['style']],
- ['font', ['bold', 'italic', 'underline', 'superscript', 'subscript', 'strikethrough', 'clear']],
- ['fontname', ['fontname']],
- // ['fontsize', ['fontsize']], // Still buggy
- ['color', ['color']],
- ['para', ['ul', 'ol', 'paragraph']],
- ['height', ['height']],
- ['table', ['table']],
- ['insert', ['link', 'picture', 'video', 'hr']],
- ['view', ['fullscreen', 'codeview']],
- ['help', ['help']]
- ],
- // air mode: inline editor
- airMode: false,
- // airPopover: [
- // ['style', ['style']],
- // ['font', ['bold', 'italic', 'underline', 'clear']],
- // ['fontname', ['fontname']],
- // ['fontsize', ['fontsize']], // Still buggy
- // ['color', ['color']],
- // ['para', ['ul', 'ol', 'paragraph']],
- // ['height', ['height']],
- // ['table', ['table']],
- // ['insert', ['link', 'picture', 'video']],
- // ['help', ['help']]
- // ],
- airPopover: [
- ['color', ['color']],
- ['font', ['bold', 'underline', 'clear']],
- ['para', ['ul', 'paragraph']],
- ['table', ['table']],
- ['insert', ['link', 'picture']]
- ],
- // style tag
- styleTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
- // default fontName
- defaultFontName: 'Helvetica Neue',
- // fontName
- fontNames: [
- 'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New',
- 'Helvetica Neue', 'Impact', 'Lucida Grande',
- 'Tahoma', 'Times New Roman', 'Verdana'
- ],
- // pallete colors(n x n)
- colors: [
- ['#000000', '#424242', '#636363', '#9C9C94', '#CEC6CE', '#EFEFEF', '#F7F7F7', '#FFFFFF'],
- ['#FF0000', '#FF9C00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#9C00FF', '#FF00FF'],
- ['#F7C6CE', '#FFE7CE', '#FFEFC6', '#D6EFD6', '#CEDEE7', '#CEE7F7', '#D6D6E7', '#E7D6DE'],
- ['#E79C9C', '#FFC69C', '#FFE79C', '#B5D6A5', '#A5C6CE', '#9CC6EF', '#B5A5D6', '#D6A5BD'],
- ['#E76363', '#F7AD6B', '#FFD663', '#94BD7B', '#73A5AD', '#6BADDE', '#8C7BC6', '#C67BA5'],
- ['#CE0000', '#E79439', '#EFC631', '#6BA54A', '#4A7B8C', '#3984C6', '#634AA5', '#A54A7B'],
- ['#9C0000', '#B56308', '#BD9400', '#397B21', '#104A5A', '#085294', '#311873', '#731842'],
- ['#630000', '#7B3900', '#846300', '#295218', '#083139', '#003163', '#21104A', '#4A1031']
- ],
- // fontSize
- fontSizes: ['8', '9', '10', '11', '12', '14', '18', '24', '36'],
- // lineHeight
- lineHeights: ['1.0', '1.2', '1.4', '1.5', '1.6', '1.8', '2.0', '3.0'],
- // insertTable max size
- insertTableMaxSize: {
- col: 10,
- row: 10
- },
- // callbacks
- oninit: null, // initialize
- onfocus: null, // editable has focus
- onblur: null, // editable out of focus
- onenter: null, // enter key pressed
- onkeyup: null, // keyup
- onkeydown: null, // keydown
- onImageUpload: null, // imageUpload
- onImageUploadError: null, // imageUploadError
- onToolbarClick: null,
- /**
- * manipulate link address when user create link
- * @param {String} sLinkUrl
- * @return {String}
- */
- onCreateLink: function (sLinkUrl) {
- if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) {
- sLinkUrl = 'mailto:' + sLinkUrl;
- } else if (sLinkUrl.indexOf('://') === -1) {
- sLinkUrl = 'http://' + sLinkUrl;
- }
- return sLinkUrl;
- },
- keyMap: {
- pc: {
- 'CTRL+Z': 'undo',
- 'CTRL+Y': 'redo',
- 'TAB': 'tab',
- 'SHIFT+TAB': 'untab',
- 'CTRL+B': 'bold',
- 'CTRL+I': 'italic',
- 'CTRL+U': 'underline',
- 'CTRL+SHIFT+S': 'strikethrough',
- 'CTRL+BACKSLASH': 'removeFormat',
- 'CTRL+SHIFT+L': 'justifyLeft',
- 'CTRL+SHIFT+E': 'justifyCenter',
- 'CTRL+SHIFT+R': 'justifyRight',
- 'CTRL+SHIFT+J': 'justifyFull',
- 'CTRL+SHIFT+NUM7': 'insertUnorderedList',
- 'CTRL+SHIFT+NUM8': 'insertOrderedList',
- 'CTRL+LEFTBRACKET': 'outdent',
- 'CTRL+RIGHTBRACKET': 'indent',
- 'CTRL+NUM0': 'formatPara',
- 'CTRL+NUM1': 'formatH1',
- 'CTRL+NUM2': 'formatH2',
- 'CTRL+NUM3': 'formatH3',
- 'CTRL+NUM4': 'formatH4',
- 'CTRL+NUM5': 'formatH5',
- 'CTRL+NUM6': 'formatH6',
- 'CTRL+ENTER': 'insertHorizontalRule',
- 'CTRL+K': 'showLinkDialog'
- },
- mac: {
- 'CMD+Z': 'undo',
- 'CMD+SHIFT+Z': 'redo',
- 'TAB': 'tab',
- 'SHIFT+TAB': 'untab',
- 'CMD+B': 'bold',
- 'CMD+I': 'italic',
- 'CMD+U': 'underline',
- 'CMD+SHIFT+S': 'strikethrough',
- 'CMD+BACKSLASH': 'removeFormat',
- 'CMD+SHIFT+L': 'justifyLeft',
- 'CMD+SHIFT+E': 'justifyCenter',
- 'CMD+SHIFT+R': 'justifyRight',
- 'CMD+SHIFT+J': 'justifyFull',
- 'CMD+SHIFT+NUM7': 'insertUnorderedList',
- 'CMD+SHIFT+NUM8': 'insertOrderedList',
- 'CMD+LEFTBRACKET': 'outdent',
- 'CMD+RIGHTBRACKET': 'indent',
- 'CMD+NUM0': 'formatPara',
- 'CMD+NUM1': 'formatH1',
- 'CMD+NUM2': 'formatH2',
- 'CMD+NUM3': 'formatH3',
- 'CMD+NUM4': 'formatH4',
- 'CMD+NUM5': 'formatH5',
- 'CMD+NUM6': 'formatH6',
- 'CMD+ENTER': 'insertHorizontalRule',
- 'CMD+K': 'showLinkDialog'
- }
- }
- },
- // default language: en-US
- lang: {
- 'en-US': {
- font: {
- bold: 'Bold',
- italic: 'Italic',
- underline: 'Underline',
- strikethrough: 'Strikethrough',
- subscript: 'Subscript',
- superscript: 'Superscript',
- clear: 'Remove Font Style',
- height: 'Line Height',
- name: 'Font Family',
- size: 'Font Size'
- },
- image: {
- image: 'Picture',
- insert: 'Insert Image',
- resizeFull: 'Resize Full',
- resizeHalf: 'Resize Half',
- resizeQuarter: 'Resize Quarter',
- floatLeft: 'Float Left',
- floatRight: 'Float Right',
- floatNone: 'Float None',
- dragImageHere: 'Drag an image here',
- selectFromFiles: 'Select from files',
- url: 'Image URL',
- remove: 'Remove Image'
- },
- link: {
- link: 'Link',
- insert: 'Insert Link',
- unlink: 'Unlink',
- edit: 'Edit',
- textToDisplay: 'Text to display',
- url: 'To what URL should this link go?',
- openInNewWindow: 'Open in new window'
- },
- video: {
- video: 'Video',
- videoLink: 'Video Link',
- insert: 'Insert Video',
- url: 'Video URL?',
- providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion or Youku)'
- },
- table: {
- table: 'Table'
- },
- hr: {
- insert: 'Insert Horizontal Rule'
- },
- style: {
- style: 'Style',
- normal: 'Normal',
- blockquote: 'Quote',
- pre: 'Code',
- h1: 'Header 1',
- h2: 'Header 2',
- h3: 'Header 3',
- h4: 'Header 4',
- h5: 'Header 5',
- h6: 'Header 6'
- },
- lists: {
- unordered: 'Unordered list',
- ordered: 'Ordered list'
- },
- options: {
- help: 'Help',
- fullscreen: 'Full Screen',
- codeview: 'Code View'
- },
- paragraph: {
- paragraph: 'Paragraph',
- outdent: 'Outdent',
- indent: 'Indent',
- left: 'Align left',
- center: 'Align center',
- right: 'Align right',
- justify: 'Justify full'
- },
- color: {
- recent: 'Recent Color',
- more: 'More Color',
- background: 'Background Color',
- foreground: 'Foreground Color',
- transparent: 'Transparent',
- setTransparent: 'Set transparent',
- reset: 'Reset',
- resetToDefault: 'Reset to default'
- },
- shortcut: {
- shortcuts: 'Keyboard shortcuts',
- close: 'Close',
- textFormatting: 'Text formatting',
- action: 'Action',
- paragraphFormatting: 'Paragraph formatting',
- documentStyle: 'Document Style'
- },
- history: {
- undo: 'Undo',
- redo: 'Redo'
- }
- }
- }
- };
- /**
- * Async functions which returns `Promise`
- */
- var async = (function () {
- /**
- * read contents of file as representing URL
- *
- * @param {File} file
- * @return {Promise} - then: sDataUrl
- */
- var readFileAsDataURL = function (file) {
- return $.Deferred(function (deferred) {
- $.extend(new FileReader(), {
- onload: function (e) {
- var sDataURL = e.target.result;
- deferred.resolve(sDataURL);
- },
- onerror: function () {
- deferred.reject(this);
- }
- }).readAsDataURL(file);
- }).promise();
- };
- /**
- * create `<image>` from url string
- *
- * @param {String} sUrl
- * @return {Promise} - then: $image
- */
- var createImage = function (sUrl) {
- return $.Deferred(function (deferred) {
- $('<img>').one('load', function () {
- deferred.resolve($(this));
- }).one('error abort', function () {
- deferred.reject($(this));
- }).css({
- display: 'none'
- }).appendTo(document.body).attr('src', sUrl);
- }).promise();
- };
- return {
- readFileAsDataURL: readFileAsDataURL,
- createImage: createImage
- };
- })();
- /**
- * Object for keycodes.
- */
- var key = {
- isEdit: function (keyCode) {
- return [8, 9, 13, 32].indexOf(keyCode) !== -1;
- },
- nameFromCode: {
- '8': 'BACKSPACE',
- '9': 'TAB',
- '13': 'ENTER',
- '32': 'SPACE',
- // Number: 0-9
- '48': 'NUM0',
- '49': 'NUM1',
- '50': 'NUM2',
- '51': 'NUM3',
- '52': 'NUM4',
- '53': 'NUM5',
- '54': 'NUM6',
- '55': 'NUM7',
- '56': 'NUM8',
- // Alphabet: a-z
- '66': 'B',
- '69': 'E',
- '73': 'I',
- '74': 'J',
- '75': 'K',
- '76': 'L',
- '82': 'R',
- '83': 'S',
- '85': 'U',
- '89': 'Y',
- '90': 'Z',
- '191': 'SLASH',
- '219': 'LEFTBRACKET',
- '220': 'BACKSLASH',
- '221': 'RIGHTBRACKET'
- }
- };
- /**
- * Style
- * @class
- */
- var Style = function () {
- /**
- * passing an array of style properties to .css()
- * will result in an object of property-value pairs.
- * (compability with version < 1.9)
- *
- * @param {jQuery} $obj
- * @param {Array} propertyNames - An array of one or more CSS properties.
- * @returns {Object}
- */
- var jQueryCSS = function ($obj, propertyNames) {
- if (agent.jqueryVersion < 1.9) {
- var result = {};
- $.each(propertyNames, function (idx, propertyName) {
- result[propertyName] = $obj.css(propertyName);
- });
- return result;
- }
- return $obj.css.call($obj, propertyNames);
- };
- /**
- * paragraph level style
- *
- * @param {WrappedRange} rng
- * @param {Object} oStyle
- */
- this.stylePara = function (rng, oStyle) {
- $.each(rng.nodes(dom.isPara), function (idx, elPara) {
- $(elPara).css(oStyle);
- });
- };
- /**
- * get current style on cursor
- *
- * @param {WrappedRange} rng
- * @param {Element} elTarget - target element on event
- * @return {Object} - object contains style properties.
- */
- this.current = function (rng, elTarget) {
- var $cont = $(dom.isText(rng.sc) ? rng.sc.parentNode : rng.sc);
- var properties = ['font-family', 'font-size', 'text-align', 'list-style-type', 'line-height'];
- var oStyle = jQueryCSS($cont, properties) || {};
- oStyle['font-size'] = parseInt(oStyle['font-size'], 10);
- // document.queryCommandState for toggle state
- oStyle['font-bold'] = document.queryCommandState('bold') ? 'bold' : 'normal';
- oStyle['font-italic'] = document.queryCommandState('italic') ? 'italic' : 'normal';
- oStyle['font-underline'] = document.queryCommandState('underline') ? 'underline' : 'normal';
- oStyle['font-strikethrough'] = document.queryCommandState('strikeThrough') ? 'strikethrough' : 'normal';
- oStyle['font-superscript'] = document.queryCommandState('superscript') ? 'superscript' : 'normal';
- oStyle['font-subscript'] = document.queryCommandState('subscript') ? 'subscript' : 'normal';
- // list-style-type to list-style(unordered, ordered)
- if (!rng.isOnList()) {
- oStyle['list-style'] = 'none';
- } else {
- var aOrderedType = ['circle', 'disc', 'disc-leading-zero', 'square'];
- var isUnordered = $.inArray(oStyle['list-style-type'], aOrderedType) > -1;
- oStyle['list-style'] = isUnordered ? 'unordered' : 'ordered';
- }
- var elPara = dom.ancestor(rng.sc, dom.isPara);
- if (elPara && elPara.style['line-height']) {
- oStyle['line-height'] = elPara.style.lineHeight;
- } else {
- var lineHeight = parseInt(oStyle['line-height'], 10) / parseInt(oStyle['font-size'], 10);
- oStyle['line-height'] = lineHeight.toFixed(1);
- }
- oStyle.image = dom.isImg(elTarget) && elTarget;
- oStyle.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor);
- oStyle.aAncestor = dom.listAncestor(rng.sc, dom.isEditable);
- oStyle.range = rng;
- return oStyle;
- };
- };
- /**
- * range module
- */
- var range = (function () {
- var isW3CRangeSupport = !!document.createRange;
- /**
- * return boundaryPoint from TextRange, inspired by Andy Na's HuskyRange.js
- * @param {TextRange} textRange
- * @param {Boolean} isStart
- * @return {BoundaryPoint}
- */
- var textRange2bp = function (textRange, isStart) {
- var elCont = textRange.parentElement(), nOffset;
- var tester = document.body.createTextRange(), elPrevCont;
- var aChild = list.from(elCont.childNodes);
- for (nOffset = 0; nOffset < aChild.length; nOffset++) {
- if (dom.isText(aChild[nOffset])) { continue; }
- tester.moveToElementText(aChild[nOffset]);
- if (tester.compareEndPoints('StartToStart', textRange) >= 0) { break; }
- elPrevCont = aChild[nOffset];
- }
- if (nOffset !== 0 && dom.isText(aChild[nOffset - 1])) {
- var textRangeStart = document.body.createTextRange(), elCurText = null;
- textRangeStart.moveToElementText(elPrevCont || elCont);
- textRangeStart.collapse(!elPrevCont);
- elCurText = elPrevCont ? elPrevCont.nextSibling : elCont.firstChild;
- var pointTester = textRange.duplicate();
- pointTester.setEndPoint('StartToStart', textRangeStart);
- var nTextCount = pointTester.text.replace(/[\r\n]/g, '').length;
- while (nTextCount > elCurText.nodeValue.length && elCurText.nextSibling) {
- nTextCount -= elCurText.nodeValue.length;
- elCurText = elCurText.nextSibling;
- }
- /* jshint ignore:start */
- var sDummy = elCurText.nodeValue; //enforce IE to re-reference elCurText, hack
- /* jshint ignore:end */
- if (isStart && elCurText.nextSibling && dom.isText(elCurText.nextSibling) &&
- nTextCount === elCurText.nodeValue.length) {
- nTextCount -= elCurText.nodeValue.length;
- elCurText = elCurText.nextSibling;
- }
- elCont = elCurText;
- nOffset = nTextCount;
- }
- return {cont: elCont, offset: nOffset};
- };
- /**
- * return TextRange from boundary point (inspired by google closure-library)
- * @param {BoundaryPoint} bp
- * @return {TextRange}
- */
- var bp2textRange = function (bp) {
- var textRangeInfo = function (elCont, nOffset) {
- var elNode, isCollapseToStart;
- if (dom.isText(elCont)) {
- var aPrevText = dom.listPrev(elCont, func.not(dom.isText));
- var elPrevCont = list.last(aPrevText).previousSibling;
- elNode = elPrevCont || elCont.parentNode;
- nOffset += list.sum(list.tail(aPrevText), dom.length);
- isCollapseToStart = !elPrevCont;
- } else {
- elNode = elCont.childNodes[nOffset] || elCont;
- if (dom.isText(elNode)) {
- return textRangeInfo(elNode, nOffset);
- }
- nOffset = 0;
- isCollapseToStart = false;
- }
- return {cont: elNode, collapseToStart: isCollapseToStart, offset: nOffset};
- };
- var textRange = document.body.createTextRange();
- var info = textRangeInfo(bp.cont, bp.offset);
- textRange.moveToElementText(info.cont);
- textRange.collapse(info.collapseToStart);
- textRange.moveStart('character', info.offset);
- return textRange;
- };
- /**
- * Wrapped Range
- *
- * @param {Element} sc - start container
- * @param {Number} so - start offset
- * @param {Element} ec - end container
- * @param {Number} eo - end offset
- */
- var WrappedRange = function (sc, so, ec, eo) {
- this.sc = sc;
- this.so = so;
- this.ec = ec;
- this.eo = eo;
- // nativeRange: get nativeRange from sc, so, ec, eo
- var nativeRange = function () {
- if (isW3CRangeSupport) {
- var w3cRange = document.createRange();
- w3cRange.setStart(sc, so);
- w3cRange.setEnd(ec, eo);
- return w3cRange;
- } else {
- var textRange = bp2textRange({cont: sc, offset: so});
- textRange.setEndPoint('EndToEnd', bp2textRange({cont: ec, offset: eo}));
- return textRange;
- }
- };
- this.getBPs = function () {
- return {
- sc: sc,
- so: so,
- ec: ec,
- eo: eo
- };
- };
- this.getStartBP = function () {
- return {
- node: sc,
- offset: so
- };
- };
- this.getEndBP = function () {
- return {
- node: ec,
- offset: eo
- };
- };
- /**
- * select update visible range
- */
- this.select = function () {
- var nativeRng = nativeRange();
- if (isW3CRangeSupport) {
- var selection = document.getSelection();
- if (selection.rangeCount > 0) { selection.removeAllRanges(); }
- selection.addRange(nativeRng);
- } else {
- nativeRng.select();
- }
- };
- /**
- * returns matched nodes on range
- *
- * @param {Function} [pred] - predicate function
- * @return {Element[]}
- */
- this.nodes = function (pred) {
- pred = pred || func.ok;
- var aNode = dom.listBetween(sc, ec);
- var aMatched = list.compact($.map(aNode, function (node) {
- return dom.ancestor(node, pred);
- }));
- return $.map(list.clusterBy(aMatched, func.eq2), list.head);
- };
- /**
- * returns commonAncestor of range
- * @return {Element} - commonAncestor
- */
- this.commonAncestor = function () {
- return dom.commonAncestor(sc, ec);
- };
- /**
- * returns expanded range by pred
- *
- * @param {Function} pred - predicate function
- * @return {WrappedRange}
- */
- this.expand = function (pred) {
- var startAncestor = dom.ancestor(sc, pred);
- var endAncestor = dom.ancestor(ec, pred);
- if (!startAncestor && !endAncestor) {
- return new WrappedRange(sc, so, ec, eo);
- }
- var boundaryPoints = this.getBPs();
- if (startAncestor) {
- boundaryPoints.sc = startAncestor;
- boundaryPoints.so = 0;
- }
- if (endAncestor) {
- boundaryPoints.ec = endAncestor;
- boundaryPoints.eo = dom.length(endAncestor);
- }
- return new WrappedRange(
- boundaryPoints.sc,
- boundaryPoints.so,
- boundaryPoints.ec,
- boundaryPoints.eo
- );
- };
- /**
- * @param {Boolean} isCollapseToStart
- * @return {WrappedRange}
- */
- this.collapse = function (isCollapseToStart) {
- if (isCollapseToStart) {
- return new WrappedRange(sc, so, sc, so);
- } else {
- return new WrappedRange(ec, eo, ec, eo);
- }
- };
- /**
- * splitText on range
- */
- this.splitText = function () {
- var isSameContainer = sc === ec;
- var boundaryPoints = this.getBPs();
- if (dom.isText(ec) && !dom.isEdgeBP(this.getEndBP())) {
- ec.splitText(eo);
- }
- if (dom.isText(sc) && !dom.isEdgeBP(this.getStartBP())) {
- boundaryPoints.sc = sc.splitText(so);
- boundaryPoints.so = 0;
- if (isSameContainer) {
- boundaryPoints.ec = boundaryPoints.sc;
- boundaryPoints.eo = eo - so;
- }
- }
- return new WrappedRange(
- boundaryPoints.sc,
- boundaryPoints.so,
- boundaryPoints.ec,
- boundaryPoints.eo
- );
- };
- /**
- * delete contents on range
- * @return {WrappedRange}
- */
- this.deleteContents = function () {
- if (this.isCollapsed()) {
- return this;
- }
- var rng = this.splitText();
- var prevBP = dom.prevBP(rng.getStartBP());
- $.each(rng.nodes(), function (idx, node) {
- dom.remove(node, !dom.isPara(node));
- });
- return new WrappedRange(
- prevBP.node,
- prevBP.offset,
- prevBP.node,
- prevBP.offset
- );
- };
- /**
- * makeIsOn: return isOn(pred) function
- */
- var makeIsOn = function (pred) {
- return function () {
- var elAncestor = dom.ancestor(sc, pred);
- return !!elAncestor && (elAncestor === dom.ancestor(ec, pred));
- };
- };
- // isOnEditable: judge whether range is on editable or not
- this.isOnEditable = makeIsOn(dom.isEditable);
- // isOnList: judge whether range is on list node or not
- this.isOnList = makeIsOn(dom.isList);
- // isOnAnchor: judge whether range is on anchor node or not
- this.isOnAnchor = makeIsOn(dom.isAnchor);
- // isOnAnchor: judge whether range is on cell node or not
- this.isOnCell = makeIsOn(dom.isCell);
- // isCollapsed: judge whether range was collapsed
- this.isCollapsed = function () { return sc === ec && so === eo; };
- /**
- * insert node at current cursor
- * @param {Element} node
- */
- this.insertNode = function (node) {
- var nativeRng = nativeRange();
- if (isW3CRangeSupport) {
- nativeRng.insertNode(node);
- } else {
- var tmpId = 'node-insert-node-target';
- node.id = tmpId;
- // NOTE: missing node reference.
- nativeRng.pasteHTML(node.outerHTML);
- node = $('#' + tmpId)[0];
- }
- return node;
- };
- this.toString = function () {
- var nativeRng = nativeRange();
- return isW3CRangeSupport ? nativeRng.toString() : nativeRng.text;
- };
- /**
- * create offsetPath bookmark
- * @param {Element} elEditable
- */
- this.bookmark = function (elEditable) {
- return {
- s: { path: dom.makeOffsetPath(elEditable, sc), offset: so },
- e: { path: dom.makeOffsetPath(elEditable, ec), offset: eo }
- };
- };
- /**
- * getClientRects
- * @return {Rect[]}
- */
- this.getClientRects = function () {
- var nativeRng = nativeRange();
- return nativeRng.getClientRects();
- };
- };
- return {
- /**
- * create Range Object From arguments or Browser Selection
- *
- * @param {Element} sc - start container
- * @param {Number} so - start offset
- * @param {Element} ec - end container
- * @param {Number} eo - end offset
- */
- create : function (sc, so, ec, eo) {
- if (!arguments.length) { // from Browser Selection
- if (isW3CRangeSupport) { // webkit, firefox
- var selection = document.getSelection();
- if (selection.rangeCount === 0) { return null; }
- var nativeRng = selection.getRangeAt(0);
- sc = nativeRng.startContainer;
- so = nativeRng.startOffset;
- ec = nativeRng.endContainer;
- eo = nativeRng.endOffset;
- } else { // IE8: TextRange
- var textRange = document.selection.createRange();
- var textRangeEnd = textRange.duplicate();
- textRangeEnd.collapse(false);
- var textRangeStart = textRange;
- textRangeStart.collapse(true);
- var bpStart = textRange2bp(textRangeStart, true),
- bpEnd = textRange2bp(textRangeEnd, false);
- sc = bpStart.cont;
- so = bpStart.offset;
- ec = bpEnd.cont;
- eo = bpEnd.offset;
- }
- } else if (arguments.length === 2) { //collapsed
- ec = sc;
- eo = so;
- }
- return new WrappedRange(sc, so, ec, eo);
- },
- /**
- * create WrappedRange from node
- *
- * @param {Element} node
- * @return {WrappedRange}
- */
- createFromNode: function (node) {
- return this.create(node, 0, node, 1);
- },
- /**
- * create WrappedRange from Bookmark
- *
- * @param {Element} elEditable
- * @param {Obkect} bookmark
- * @return {WrappedRange}
- */
- createFromBookmark : function (elEditable, bookmark) {
- var sc = dom.fromOffsetPath(elEditable, bookmark.s.path);
- var so = bookmark.s.offset;
- var ec = dom.fromOffsetPath(elEditable, bookmark.e.path);
- var eo = bookmark.e.offset;
- return new WrappedRange(sc, so, ec, eo);
- }
- };
- })();
- /**
- * Table
- * @class
- */
- var Table = function () {
- /**
- * handle tab key
- *
- * @param {WrappedRange} rng
- * @param {Boolean} isShift
- */
- this.tab = function (rng, isShift) {
- var elCell = dom.ancestor(rng.commonAncestor(), dom.isCell);
- var elTable = dom.ancestor(elCell, dom.isTable);
- var aCell = dom.listDescendant(elTable, dom.isCell);
- var elNext = list[isShift ? 'prev' : 'next'](aCell, elCell);
- if (elNext) {
- range.create(elNext, 0).select();
- }
- };
- /**
- * create empty table element
- *
- * @param {Number} nRow
- * @param {Number} nCol
- */
- this.createTable = function (nCol, nRow) {
- var aTD = [], sTD;
- for (var idxCol = 0; idxCol < nCol; idxCol++) {
- aTD.push('<td>' + dom.blank + '</td>');
- }
- sTD = aTD.join('');
- var aTR = [], sTR;
- for (var idxRow = 0; idxRow < nRow; idxRow++) {
- aTR.push('<tr>' + sTD + '</tr>');
- }
- sTR = aTR.join('');
- var sTable = '<table class="table table-bordered">' + sTR + '</table>';
- return $(sTable)[0];
- };
- };
- /**
- * Editor
- * @class
- */
- var Editor = function () {
- var style = new Style();
- var table = new Table();
- /**
- * save current range
- *
- * @param {jQuery} $editable
- */
- this.saveRange = function ($editable) {
- $editable.focus();
- $editable.data('range', range.create());
- };
- /**
- * restore lately range
- *
- * @param {jQuery} $editable
- */
- this.restoreRange = function ($editable) {
- var rng = $editable.data('range');
- if (rng) {
- rng.select();
- $editable.focus();
- }
- };
- /**
- * current style
- * @param {Element} elTarget
- */
- this.currentStyle = function (elTarget) {
- var rng = range.create();
- return rng ? rng.isOnEditable() && style.current(rng, elTarget) : false;
- };
- /**
- * undo
- * @param {jQuery} $editable
- */
- this.undo = function ($editable) {
- $editable.data('NoteHistory').undo($editable);
- };
- /**
- * redo
- * @param {jQuery} $editable
- */
- this.redo = function ($editable) {
- $editable.data('NoteHistory').redo($editable);
- };
- /**
- * record Undo
- * @param {jQuery} $editable
- */
- var recordUndo = this.recordUndo = function ($editable) {
- $editable.data('NoteHistory').recordUndo($editable);
- };
- /* jshint ignore:start */
- // native commands(with execCommand), generate function for execCommand
- var aCmd = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript',
- 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull',
- 'insertOrderedList', 'insertUnorderedList',
- 'indent', 'outdent', 'formatBlock', 'removeFormat',
- 'backColor', 'foreColor', 'insertHorizontalRule', 'fontName'];
- for (var idx = 0, len = aCmd.length; idx < len; idx ++) {
- this[aCmd[idx]] = (function (sCmd) {
- return function ($editable, sValue) {
- recordUndo($editable);
- document.execCommand(sCmd, false, sValue);
- };
- })(aCmd[idx]);
- }
- /* jshint ignore:end */
- /**
- * @param {jQuery} $editable
- * @param {WrappedRange} rng
- * @param {Number} nTabsize
- */
- var insertTab = function ($editable, rng, nTabsize) {
- recordUndo($editable);
- var sNbsp = new Array(nTabsize + 1).join(' ');
- rng.insertNode($('<span id="noteTab">' + sNbsp + '</span>')[0]);
- var $tab = $('#noteTab').removeAttr('id');
- rng = range.create($tab[0], 1);
- rng.select();
- dom.remove($tab[0]);
- };
- /**
- * handle tab key
- * @param {jQuery} $editable
- * @param {Object} options
- */
- this.tab = function ($editable, options) {
- var rng = range.create();
- if (rng.isCollapsed() && rng.isOnCell()) {
- table.tab(rng);
- } else {
- insertTab($editable, rng, options.tabsize);
- }
- };
- /**
- * handle shift+tab key
- */
- this.untab = function () {
- var rng = range.create();
- if (rng.isCollapsed() && rng.isOnCell()) {
- table.tab(rng, true);
- }
- };
- /**
- * insert image
- *
- * @param {jQuery} $editable
- * @param {String} sUrl
- */
- this.insertImage = function ($editable, sUrl) {
- async.createImage(sUrl).then(function ($image) {
- recordUndo($editable);
- $image.css({
- display: '',
- width: Math.min($editable.width(), $image.width())
- });
- range.create().insertNode($image[0]);
- }).fail(function () {
- var callbacks = $editable.data('callbacks');
- if (callbacks.onImageUploadError) {
- callbacks.onImageUploadError();
- }
- });
- };
- /**
- * insert video
- * @param {jQuery} $editable
- * @param {String} sUrl
- */
- this.insertVideo = function ($editable, sUrl) {
- recordUndo($editable);
- // video url patterns(youtube, instagram, vimeo, dailymotion, youku)
- var ytRegExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
- var ytMatch = sUrl.match(ytRegExp);
- var igRegExp = /\/\/instagram.com\/p\/(.[a-zA-Z0-9]*)/;
- var igMatch = sUrl.match(igRegExp);
- var vRegExp = /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/;
- var vMatch = sUrl.match(vRegExp);
- var vimRegExp = /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/;
- var vimMatch = sUrl.match(vimRegExp);
- var dmRegExp = /.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/;
- var dmMatch = sUrl.match(dmRegExp);
- var youkuRegExp = /\/\/v\.youku\.com\/v_show\/id_(\w+)\.html/;
- var youkuMatch = sUrl.match(youkuRegExp);
- var $video;
- if (ytMatch && ytMatch[2].length === 11) {
- var youtubeId = ytMatch[2];
- $video = $('<iframe>')
- .attr('src', '//www.youtube.com/embed/' + youtubeId)
- .attr('width', '640').attr('height', '360');
- } else if (igMatch && igMatch[0].length) {
- $video = $('<iframe>')
- .attr('src', igMatch[0] + '/embed/')
- .attr('width', '612').attr('height', '710')
- .attr('scrolling', 'no')
- .attr('allowtransparency', 'true');
- } else if (vMatch && vMatch[0].length) {
- $video = $('<iframe>')
- .attr('src', vMatch[0] + '/embed/simple')
- .attr('width', '600').attr('height', '600')
- .attr('class', 'vine-embed');
- } else if (vimMatch && vimMatch[3].length) {
- $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
- .attr('src', '//player.vimeo.com/video/' + vimMatch[3])
- .attr('width', '640').attr('height', '360');
- } else if (dmMatch && dmMatch[2].length) {
- $video = $('<iframe>')
- .attr('src', '//www.dailymotion.com/embed/video/' + dmMatch[2])
- .attr('width', '640').attr('height', '360');
- } else if (youkuMatch && youkuMatch[1].length) {
- $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
- .attr('height', '498')
- .attr('width', '510')
- .attr('src', '//player.youku.com/embed/' + youkuMatch[1]);
- } else {
- // this is not a known video link. Now what, Cat? Now what?
- }
- if ($video) {
- $video.attr('frameborder', 0);
- range.create().insertNode($video[0]);
- }
- };
- /**
- * formatBlock
- *
- * @param {jQuery} $editable
- * @param {String} sTagName
- */
- this.formatBlock = function ($editable, sTagName) {
- recordUndo($editable);
- sTagName = agent.isMSIE ? '<' + sTagName + '>' : sTagName;
- document.execCommand('FormatBlock', false, sTagName);
- };
- this.formatPara = function ($editable) {
- this.formatBlock($editable, 'P');
- };
- /* jshint ignore:start */
- for (var idx = 1; idx <= 6; idx ++) {
- this['formatH' + idx] = function (idx) {
- return function ($editable) {
- this.formatBlock($editable, 'H' + idx);
- };
- }(idx);
- };
- /* jshint ignore:end */
- /**
- * fontsize
- * FIXME: Still buggy
- *
- * @param {jQuery} $editable
- * @param {String} sValue - px
- */
- this.fontSize = function ($editable, sValue) {
- recordUndo($editable);
- document.execCommand('fontSize', false, 3);
- if (agent.isFF) {
- // firefox: <font size="3"> to <span style='font-size={sValue}px;'>, buggy
- $editable.find('font[size=3]').removeAttr('size').css('font-size', sValue + 'px');
- } else {
- // chrome: <span style="font-size: medium"> to <span style='font-size={sValue}px;'>
- $editable.find('span').filter(function () {
- return this.style.fontSize === 'medium';
- }).css('font-size', sValue + 'px');
- }
- };
- /**
- * lineHeight
- * @param {jQuery} $editable
- * @param {String} sValue
- */
- this.lineHeight = function ($editable, sValue) {
- recordUndo($editable);
- style.stylePara(range.create(), {lineHeight: sValue});
- };
- /**
- * unlink
- * @param {jQuery} $editable
- */
- this.unlink = function ($editable) {
- var rng = range.create();
- if (rng.isOnAnchor()) {
- recordUndo($editable);
- var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
- rng = range.createFromNode(elAnchor);
- rng.select();
- document.execCommand('unlink');
- }
- };
- /**
- * create link
- *
- * @param {jQuery} $editable
- * @param {Object} linkInfo
- * @param {Object} options
- */
- this.createLink = function ($editable, linkInfo, options) {
- var sLinkUrl = linkInfo.url;
- var sLinkText = linkInfo.text;
- var isNewWindow = linkInfo.newWindow;
- var rng = linkInfo.range;
- recordUndo($editable);
- if (options.onCreateLink) {
- sLinkUrl = options.onCreateLink(sLinkUrl);
- }
- rng = rng.deleteContents();
- // Create a new link when there is no anchor on range.
- var anchor = rng.insertNode($('<A>' + sLinkText + '</A>')[0]);
- $(anchor).attr({
- href: sLinkUrl,
- target: isNewWindow ? '_blank' : ''
- });
- rng = range.createFromNode(anchor);
- rng.select();
- };
- /**
- * returns link info
- *
- * @return {Object}
- */
- this.getLinkInfo = function ($editable) {
- $editable.focus();
- var rng = range.create().expand(dom.isAnchor);
- // Get the first anchor on range(for edit).
- var $anchor = $(list.head(rng.nodes(dom.isAnchor)));
- return {
- range: rng,
- text: rng.toString(),
- isNewWindow: $anchor.length ? $anchor.attr('target') === '_blank' : true,
- url: $anchor.length ? $anchor.attr('href') : ''
- };
- };
- /**
- * get video info
- *
- * @param {jQuery} $editable
- * @return {Object}
- */
- this.getVideoInfo = function ($editable) {
- $editable.focus();
- var rng = range.create();
- if (rng.isOnAnchor()) {
- var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
- rng = range.createFromNode(elAnchor);
- }
- return {
- text: rng.toString()
- };
- };
- this.color = function ($editable, sObjColor) {
- var oColor = JSON.parse(sObjColor);
- var foreColor = oColor.foreColor, backColor = oColor.backColor;
- recordUndo($editable);
- if (foreColor) { document.execCommand('foreColor', false, foreColor); }
- if (backColor) { document.execCommand('backColor', false, backColor); }
- };
- this.insertTable = function ($editable, sDim) {
- recordUndo($editable);
- var aDim = sDim.split('x');
- range.create().insertNode(table.createTable(aDim[0], aDim[1]));
- };
- /**
- * @param {jQuery} $editable
- * @param {String} sValue
- * @param {jQuery} $target
- */
- this.floatMe = function ($editable, sValue, $target) {
- recordUndo($editable);
- $target.css('float', sValue);
- };
- /**
- * resize overlay element
- * @param {jQuery} $editable
- * @param {String} sValue
- * @param {jQuery} $target - target element
- */
- this.resize = function ($editable, sValue, $target) {
- recordUndo($editable);
- $target.css({
- width: $editable.width() * sValue + 'px',
- height: ''
- });
- };
- /**
- * @param {Position} pos
- * @param {jQuery} $target - target element
- * @param {Boolean} [bKeepRatio] - keep ratio
- */
- this.resizeTo = function (pos, $target, bKeepRatio) {
- var szImage;
- if (bKeepRatio) {
- var newRatio = pos.y / pos.x;
- var ratio = $target.data('ratio');
- szImage = {
- width: ratio > newRatio ? pos.x : pos.y / ratio,
- height: ratio > newRatio ? pos.x * ratio : pos.y
- };
- } else {
- szImage = {
- width: pos.x,
- height: pos.y
- };
- }
- $target.css(szImage);
- };
- /**
- * remove media object
- *
- * @param {jQuery} $editable
- * @param {String} sValue - dummy argument (for keep interface)
- * @param {jQuery} $target - target element
- */
- this.removeMedia = function ($editable, sValue, $target) {
- recordUndo($editable);
- $target.detach();
- };
- };
- /**
- * History
- * @class
- */
- var History = function () {
- var aUndo = [], aRedo = [];
- var makeSnap = function ($editable) {
- var elEditable = $editable[0], rng = range.create();
- return {
- contents: $editable.html(),
- bookmark: rng.bookmark(elEditable),
- scrollTop: $editable.scrollTop()
- };
- };
- var applySnap = function ($editable, oSnap) {
- $editable.html(oSnap.contents).scrollTop(oSnap.scrollTop);
- range.createFromBookmark($editable[0], oSnap.bookmark).select();
- };
- this.undo = function ($editable) {
- var oSnap = makeSnap($editable);
- if (!aUndo.length) { return; }
- applySnap($editable, aUndo.pop());
- aRedo.push(oSnap);
- };
- this.redo = function ($editable) {
- var oSnap = makeSnap($editable);
- if (!aRedo.length) { return; }
- applySnap($editable, aRedo.pop());
- aUndo.push(oSnap);
- };
- this.recordUndo = function ($editable) {
- aRedo = [];
- aUndo.push(makeSnap($editable));
- };
- };
- /**
- * Button
- */
- var Button = function () {
- /**
- * update button status
- *
- * @param {jQuery} $container
- * @param {Object} oStyle
- */
- this.update = function ($container, oStyle) {
- /**
- * handle dropdown's check mark (for fontname, fontsize, lineHeight).
- * @param {jQuery} $btn
- * @param {Number} nValue
- */
- var checkDropdownMenu = function ($btn, nValue) {
- $btn.find('.dropdown-menu li a').each(function () {
- // always compare string to avoid creating another func.
- var isChecked = ($(this).data('value') + '') === (nValue + '');
- this.className = isChecked ? 'checked' : '';
- });
- };
- /**
- * update button state(active or not).
- *
- * @param {String} sSelector
- * @param {Function} pred
- */
- var btnState = function (sSelector, pred) {
- var $btn = $container.find(sSelector);
- $btn.toggleClass('active', pred());
- };
- // fontname
- var $fontname = $container.find('.note-fontname');
- if ($fontname.length) {
- var selectedFont = oStyle['font-family'];
- if (!!selectedFont) {
- selectedFont = list.head(selectedFont.split(','));
- selectedFont = selectedFont.replace(/\'/g, '');
- $fontname.find('.note-current-fontname').text(selectedFont);
- checkDropdownMenu($fontname, selectedFont);
- }
- }
- // fontsize
- var $fontsize = $container.find('.note-fontsize');
- $fontsize.find('.note-current-fontsize').text(oStyle['font-size']);
- checkDropdownMenu($fontsize, parseFloat(oStyle['font-size']));
- // lineheight
- var $lineHeight = $container.find('.note-height');
- checkDropdownMenu($lineHeight, parseFloat(oStyle['line-height']));
- btnState('button[data-event="bold"]', function () {
- return oStyle['font-bold'] === 'bold';
- });
- btnState('button[data-event="italic"]', function () {
- return oStyle['font-italic'] === 'italic';
- });
- btnState('button[data-event="underline"]', function () {
- return oStyle['font-underline'] === 'underline';
- });
- btnState('button[data-event="strikethrough"]', function () {
- return oStyle['font-strikethrough'] === 'strikethrough';
- });
- btnState('button[data-event="superscript"]', function () {
- return oStyle['font-superscript'] === 'superscript';
- });
- btnState('button[data-event="subscript"]', function () {
- return oStyle['font-subscript'] === 'subscript';
- });
- btnState('button[data-event="justifyLeft"]', function () {
- return oStyle['text-align'] === 'left' || oStyle['text-align'] === 'start';
- });
- btnState('button[data-event="justifyCenter"]', function () {
- return oStyle['text-align'] === 'center';
- });
- btnState('button[data-event="justifyRight"]', function () {
- return oStyle['text-align'] === 'right';
- });
- btnState('button[data-event="justifyFull"]', function () {
- return oStyle['text-align'] === 'justify';
- });
- btnState('button[data-event="insertUnorderedList"]', function () {
- return oStyle['list-style'] === 'unordered';
- });
- btnState('button[data-event="insertOrderedList"]', function () {
- return oStyle['list-style'] === 'ordered';
- });
- };
- /**
- * update recent color
- *
- * @param {Element} elBtn
- * @param {String} sEvent
- * @param {sValue} sValue
- */
- this.updateRecentColor = function (elBtn, sEvent, sValue) {
- var $color = $(elBtn).closest('.note-color');
- var $recentColor = $color.find('.note-recent-color');
- var oColor = JSON.parse($recentColor.attr('data-value'));
- oColor[sEvent] = sValue;
- $recentColor.attr('data-value', JSON.stringify(oColor));
- var sKey = sEvent === 'backColor' ? 'background-color' : 'color';
- $recentColor.find('i').css(sKey, sValue);
- };
- };
- /**
- * Toolbar
- */
- var Toolbar = function () {
- var button = new Button();
- this.update = function ($toolbar, oStyle) {
- button.update($toolbar, oStyle);
- };
- this.updateRecentColor = function (elBtn, sEvent, sValue) {
- button.updateRecentColor(elBtn, sEvent, sValue);
- };
- /**
- * activate buttons exclude codeview
- * @param {jQuery} $toolbar
- */
- this.activate = function ($toolbar) {
- $toolbar.find('button').not('button[data-event="codeview"]').removeClass('disabled');
- };
- /**
- * deactivate buttons exclude codeview
- * @param {jQuery} $toolbar
- */
- this.deactivate = function ($toolbar) {
- $toolbar.find('button').not('button[data-event="codeview"]').addClass('disabled');
- };
- this.updateFullscreen = function ($container, bFullscreen) {
- var $btn = $container.find('button[data-event="fullscreen"]');
- $btn.toggleClass('active', bFullscreen);
- };
- this.updateCodeview = function ($container, isCodeview) {
- var $btn = $container.find('button[data-event="codeview"]');
- $btn.toggleClass('active', isCodeview);
- };
- };
- /**
- * Popover (http://getbootstrap.com/javascript/#popovers)
- */
- var Popover = function () {
- var button = new Button();
- /**
- * returns position from placeholder
- * @param {Element} placeholder
- * @param {Boolean} isAirMode
- */
- var posFromPlaceholder = function (placeholder, isAirMode) {
- var $placeholder = $(placeholder);
- var pos = isAirMode ? $placeholder.offset() : $placeholder.position();
- var height = $placeholder.outerHeight(true); // include margin
- // popover below placeholder.
- return {
- left: pos.left,
- top: pos.top + height
- };
- };
- /**
- * show popover
- * @param {jQuery} popover
- * @param {Position} pos
- */
- var showPopover = function ($popover, pos) {
- $popover.css({
- display: 'block',
- left: pos.left,
- top: pos.top
- });
- };
- var PX_POPOVER_ARROW_OFFSET_X = 20;
- /**
- * update current state
- * @param {jQuery} $popover - popover container
- * @param {Object} oStyle - style object
- * @param {Boolean} isAirMode
- */
- this.update = function ($popover, oStyle, isAirMode) {
- button.update($popover, oStyle);
- var $linkPopover = $popover.find('.note-link-popover');
- if (oStyle.anchor) {
- var $anchor = $linkPopover.find('a');
- var href = $(oStyle.anchor).attr('href');
- $anchor.attr('href', href).html(href);
- showPopover($linkPopover, posFromPlaceholder(oStyle.anchor, isAirMode));
- } else {
- $linkPopover.hide();
- }
- var $imagePopover = $popover.find('.note-image-popover');
- if (oStyle.image) {
- showPopover($imagePopover, posFromPlaceholder(oStyle.image, isAirMode));
- } else {
- $imagePopover.hide();
- }
- var $airPopover = $popover.find('.note-air-popover');
- if (isAirMode && !oStyle.range.isCollapsed()) {
- var bnd = func.rect2bnd(list.last(oStyle.range.getClientRects()));
- showPopover($airPopover, {
- left: Math.max(bnd.left + bnd.width / 2 - PX_POPOVER_ARROW_OFFSET_X, 0),
- top: bnd.top + bnd.height
- });
- } else {
- $airPopover.hide();
- }
- };
- this.updateRecentColor = function (elBtn, sEvent, sValue) {
- button.updateRecentColor(elBtn, sEvent, sValue);
- };
- /**
- * hide all popovers
- * @param {jQuery} $popover - popover contaienr
- */
- this.hide = function ($popover) {
- $popover.children().hide();
- };
- };
- /**
- * Handle
- */
- var Handle = function () {
- /**
- * update handle
- * @param {jQuery} $handle
- * @param {Object} oStyle
- * @param {Boolean} isAirMode
- */
- this.update = function ($handle, oStyle, isAirMode) {
- var $selection = $handle.find('.note-control-selection');
- if (oStyle.image) {
- var $image = $(oStyle.image);
- var pos = isAirMode ? $image.offset() : $image.position();
- // include margin
- var szImage = {
- w: $image.outerWidth(true),
- h: $image.outerHeight(true)
- };
- $selection.css({
- display: 'block',
- left: pos.left,
- top: pos.top,
- width: szImage.w,
- height: szImage.h
- }).data('target', oStyle.image); // save current image element.
- var sSizing = szImage.w + 'x' + szImage.h;
- $selection.find('.note-control-selection-info').text(sSizing);
- } else {
- $selection.hide();
- }
- };
- this.hide = function ($handle) {
- $handle.children().hide();
- };
- };
- /**
- * Dialog
- *
- * @class
- */
- var Dialog = function () {
- /**
- * toggle button status
- *
- * @param {jQuery} $btn
- * @param {Boolean} isEnable
- */
- var toggleBtn = function ($btn, isEnable) {
- $btn.toggleClass('disabled', !isEnable);
- $btn.attr('disabled', !isEnable);
- };
- /**
- * show image dialog
- *
- * @param {jQuery} $editable
- * @param {jQuery} $dialog
- * @return {Promise}
- */
- this.showImageDialog = function ($editable, $dialog) {
- return $.Deferred(function (deferred) {
- var $imageDialog = $dialog.find('.note-image-dialog');
- var $imageInput = $dialog.find('.note-image-input'),
- $imageUrl = $dialog.find('.note-image-url'),
- $imageBtn = $dialog.find('.note-image-btn');
- $imageDialog.one('shown.bs.modal', function () {
- // Cloning imageInput to clear element.
- $imageInput.replaceWith($imageInput.clone()
- .on('change', function () {
- deferred.resolve(this.files);
- $imageDialog.modal('hide');
- })
- );
- $imageBtn.click(function (event) {
- event.preventDefault();
- deferred.resolve($imageUrl.val());
- $imageDialog.modal('hide');
- });
- $imageUrl.on('keyup paste', function (event) {
- var url;
- if (event.type === 'paste') {
- url = event.originalEvent.clipboardData.getData('text');
- } else {
- url = $imageUrl.val();
- }
- toggleBtn($imageBtn, url);
- }).val('').trigger('focus');
- }).one('hidden.bs.modal', function () {
- $imageInput.off('change');
- $imageUrl.off('keyup paste');
- $imageBtn.off('click');
- if (deferred.state() === 'pending') {
- deferred.reject();
- }
- }).modal('show');
- });
- };
- /**
- * Show video dialog and set event handlers on dialog controls.
- *
- * @param {jQuery} $dialog
- * @param {Object} videoInfo
- * @return {Promise}
- */
- this.showVideoDialog = function ($editable, $dialog, videoInfo) {
- return $.Deferred(function (deferred) {
- var $videoDialog = $dialog.find('.note-video-dialog');
- var $videoUrl = $videoDialog.find('.note-video-url'),
- $videoBtn = $videoDialog.find('.note-video-btn');
- $videoDialog.one('shown.bs.modal', function () {
- $videoUrl.val(videoInfo.text).keyup(function () {
- toggleBtn($videoBtn, $videoUrl.val());
- }).trigger('keyup').trigger('focus');
- $videoBtn.click(function (event) {
- event.preventDefault();
- deferred.resolve($videoUrl.val());
- $videoDialog.modal('hide');
- });
- }).one('hidden.bs.modal', function () {
- // dettach events
- $videoUrl.off('keyup');
- $videoBtn.off('click');
- if (deferred.state() === 'pending') {
- deferred.reject();
- }
- }).modal('show');
- });
- };
- /**
- * Show link dialog and set event handlers on dialog controls.
- *
- * @param {jQuery} $dialog
- * @param {Object} linkInfo
- * @return {Promise}
- */
- this.showLinkDialog = function ($editable, $dialog, linkInfo) {
- return $.Deferred(function (deferred) {
- var $linkDialog = $dialog.find('.note-link-dialog');
- var $linkText = $linkDialog.find('.note-link-text'),
- $linkUrl = $linkDialog.find('.note-link-url'),
- $linkBtn = $linkDialog.find('.note-link-btn'),
- $openInNewWindow = $linkDialog.find('input[type=checkbox]');
- $linkDialog.one('shown.bs.modal', function () {
- $linkText.val(linkInfo.text);
- $linkText.keyup(function () {
- // if linktext was modified by keyup,
- // stop cloning text from linkUrl
- linkInfo.text = $linkText.val();
- });
- // if no url was given, copy text to url
- if (!linkInfo.url) {
- linkInfo.url = linkInfo.text;
- toggleBtn($linkBtn, linkInfo.text);
- }
- $linkUrl.keyup(function () {
- toggleBtn($linkBtn, $linkUrl.val());
- // display same link on `Text to display` input
- // when create a new link
- if (!linkInfo.text) {
- $linkText.val($linkUrl.val());
- }
- }).val(linkInfo.url).trigger('focus').trigger('select');
- $openInNewWindow.prop('checked', linkInfo.newWindow);
- $linkBtn.one('click', function (event) {
- event.preventDefault();
- deferred.resolve({
- range: linkInfo.range,
- url: $linkUrl.val(),
- text: $linkText.val(),
- newWindow: $openInNewWindow.is(':checked')
- });
- $linkDialog.modal('hide');
- });
- }).one('hidden.bs.modal', function () {
- // dettach events
- $linkText.off('keyup');
- $linkUrl.off('keyup');
- $linkBtn.off('click');
- if (deferred.state() === 'pending') {
- deferred.reject();
- }
- }).modal('show');
- }).promise();
- };
- /**
- * show help dialog
- *
- * @param {jQuery} $dialog
- */
- this.showHelpDialog = function ($editable, $dialog) {
- return $.Deferred(function (deferred) {
- var $helpDialog = $dialog.find('.note-help-dialog');
- $helpDialog.one('hidden.bs.modal', function () {
- deferred.resolve();
- }).modal('show');
- }).promise();
- };
- };
- var CodeMirror;
- if (agent.hasCodeMirror) {
- if (agent.isSupportAmd) {
- require(['CodeMirror'], function (cm) {
- CodeMirror = cm;
- });
- } else {
- CodeMirror = window.CodeMirror;
- }
- }
- /**
- * EventHandler
- */
- var EventHandler = function () {
- var $window = $(window);
- var $document = $(document);
- var $scrollbar = $('html, body');
- var editor = new Editor();
- var toolbar = new Toolbar(), popover = new Popover();
- var handle = new Handle(), dialog = new Dialog();
- /**
- * returns makeLayoutInfo from editor's descendant node.
- *
- * @param {Element} descendant
- * @returns {Object}
- */
- var makeLayoutInfo = function (descendant) {
- var $target = $(descendant).closest('.note-editor, .note-air-editor, .note-air-layout');
- if (!$target.length) { return null; }
- var $editor;
- if ($target.is('.note-editor, .note-air-editor')) {
- $editor = $target;
- } else {
- $editor = $('#note-editor-' + list.last($target.attr('id').split('-')));
- }
- return dom.buildLayoutInfo($editor);
- };
- /**
- * insert Images from file array.
- *
- * @param {jQuery} $editable
- * @param {File[]} files
- */
- var insertImages = function ($editable, files) {
- editor.restoreRange($editable);
- var callbacks = $editable.data('callbacks');
- // If onImageUpload options setted
- if (callbacks.onImageUpload) {
- callbacks.onImageUpload(files, editor, $editable);
- // else insert Image as dataURL
- } else {
- $.each(files, function (idx, file) {
- async.readFileAsDataURL(file).then(function (sDataURL) {
- editor.insertImage($editable, sDataURL);
- }).fail(function () {
- if (callbacks.onImageUploadError) {
- callbacks.onImageUploadError();
- }
- });
- });
- }
- };
- var commands = {
- /**
- * @param {Object} oLayoutInfo
- */
- showLinkDialog: function (oLayoutInfo) {
- var $editor = oLayoutInfo.editor(),
- $dialog = oLayoutInfo.dialog(),
- $editable = oLayoutInfo.editable(),
- linkInfo = editor.getLinkInfo($editable);
- var options = $editor.data('options');
- editor.saveRange($editable);
- dialog.showLinkDialog($editable, $dialog, linkInfo).then(function (linkInfo) {
- editor.restoreRange($editable);
- editor.createLink($editable, linkInfo, options);
- // hide popover after creating link
- popover.hide(oLayoutInfo.popover());
- }).fail(function () {
- editor.restoreRange($editable);
- });
- },
- /**
- * @param {Object} oLayoutInfo
- */
- showImageDialog: function (oLayoutInfo) {
- var $dialog = oLayoutInfo.dialog(),
- $editable = oLayoutInfo.editable();
- editor.saveRange($editable);
- dialog.showImageDialog($editable, $dialog).then(function (data) {
- editor.restoreRange($editable);
- if (typeof data === 'string') {
- // image url
- editor.insertImage($editable, data);
- } else {
- // array of files
- insertImages($editable, data);
- }
- }).fail(function () {
- editor.restoreRange($editable);
- });
- },
- /**
- * @param {Object} oLayoutInfo
- */
- showVideoDialog: function (oLayoutInfo) {
- var $dialog = oLayoutInfo.dialog(),
- $editable = oLayoutInfo.editable(),
- videoInfo = editor.getVideoInfo($editable);
- editor.saveRange($editable);
- dialog.showVideoDialog($editable, $dialog, videoInfo).then(function (sUrl) {
- editor.restoreRange($editable);
- editor.insertVideo($editable, sUrl);
- }).fail(function () {
- editor.restoreRange($editable);
- });
- },
- /**
- * @param {Object} oLayoutInfo
- */
- showHelpDialog: function (oLayoutInfo) {
- var $dialog = oLayoutInfo.dialog(),
- $editable = oLayoutInfo.editable();
- editor.saveRange($editable);
- dialog.showHelpDialog($editable, $dialog).then(function () {
- editor.restoreRange($editable);
- });
- },
- fullscreen: function (oLayoutInfo) {
- var $editor = oLayoutInfo.editor(),
- $toolbar = oLayoutInfo.toolbar(),
- $editable = oLayoutInfo.editable(),
- $codable = oLayoutInfo.codable();
- var options = $editor.data('options');
- var resize = function (size) {
- $editor.css('width', size.w);
- $editable.css('height', size.h);
- $codable.css('height', size.h);
- if ($codable.data('cmeditor')) {
- $codable.data('cmeditor').setsize(null, size.h);
- }
- };
- $editor.toggleClass('fullscreen');
- var isFullscreen = $editor.hasClass('fullscreen');
- if (isFullscreen) {
- $editable.data('orgheight', $editable.css('height'));
- $window.on('resize', function () {
- resize({
- w: $window.width(),
- h: $window.height() - $toolbar.outerHeight()
- });
- }).trigger('resize');
- $scrollbar.css('overflow', 'hidden');
- } else {
- $window.off('resize');
- resize({
- w: options.width || '',
- h: $editable.data('orgheight')
- });
- $scrollbar.css('overflow', 'visible');
- }
- toolbar.updateFullscreen($toolbar, isFullscreen);
- },
- codeview: function (oLayoutInfo) {
- var $editor = oLayoutInfo.editor(),
- $toolbar = oLayoutInfo.toolbar(),
- $editable = oLayoutInfo.editable(),
- $codable = oLayoutInfo.codable(),
- $popover = oLayoutInfo.popover();
- var options = $editor.data('options');
- var cmEditor, server;
- $editor.toggleClass('codeview');
- var isCodeview = $editor.hasClass('codeview');
- if (isCodeview) {
- $codable.val($editable.html());
- $codable.height($editable.height());
- toolbar.deactivate($toolbar);
- popover.hide($popover);
- $codable.focus();
- // activate CodeMirror as codable
- if (agent.hasCodeMirror) {
- cmEditor = CodeMirror.fromTextArea($codable[0], options.codemirror);
- // CodeMirror TernServer
- if (options.codemirror.tern) {
- server = new CodeMirror.TernServer(options.codemirror.tern);
- cmEditor.ternServer = server;
- cmEditor.on('cursorActivity', function (cm) {
- server.updateArgHints(cm);
- });
- }
- // CodeMirror hasn't Padding.
- cmEditor.setSize(null, $editable.outerHeight());
- // autoFormatRange If formatting included
- if (options.codemirror.autoFormatOnStart && cmEditor.autoFormatRange) {
- cmEditor.autoFormatRange({line: 0, ch: 0}, {
- line: cmEditor.lineCount(),
- ch: cmEditor.getTextArea().value.length
- });
- }
- $codable.data('cmEditor', cmEditor);
- }
- } else {
- // deactivate CodeMirror as codable
- if (agent.hasCodeMirror) {
- cmEditor = $codable.data('cmEditor');
- $codable.val(cmEditor.getValue());
- cmEditor.toTextArea();
- }
- $editable.html($codable.val() || dom.emptyPara);
- $editable.height(options.height ? $codable.height() : 'auto');
- toolbar.activate($toolbar);
- $editable.focus();
- }
- toolbar.updateCodeview(oLayoutInfo.toolbar(), isCodeview);
- }
- };
- var hMousedown = function (event) {
- //preventDefault Selection for FF, IE8+
- if (dom.isImg(event.target)) {
- event.preventDefault();
- }
- };
- var hToolbarAndPopoverUpdate = function (event) {
- // delay for range after mouseup
- setTimeout(function () {
- var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
- var oStyle = editor.currentStyle(event.target);
- if (!oStyle) { return; }
- var isAirMode = oLayoutInfo.editor().data('options').airMode;
- if (!isAirMode) {
- toolbar.update(oLayoutInfo.toolbar(), oStyle);
- }
- popover.update(oLayoutInfo.popover(), oStyle, isAirMode);
- handle.update(oLayoutInfo.handle(), oStyle, isAirMode);
- }, 0);
- };
- var hScroll = function (event) {
- var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
- //hide popover and handle when scrolled
- popover.hide(oLayoutInfo.popover());
- handle.hide(oLayoutInfo.handle());
- };
- /**
- * paste clipboard image
- *
- * @param {Event} event
- */
- var hPasteClipboardImage = function (event) {
- var clipboardData = event.originalEvent.clipboardData;
- if (!clipboardData || !clipboardData.items || !clipboardData.items.length) {
- return;
- }
- var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
- var item = list.head(clipboardData.items);
- var isClipboardImage = item.kind === 'file' && item.type.indexOf('image/') !== -1;
- if (isClipboardImage) {
- insertImages(oLayoutInfo.editable(), [item.getAsFile()]);
- }
- };
- /**
- * `mousedown` event handler on $handle
- * - controlSizing: resize image
- *
- * @param {MouseEvent} event
- */
- var hHandleMousedown = function (event) {
- if (dom.isControlSizing(event.target)) {
- event.preventDefault();
- event.stopPropagation();
- var oLayoutInfo = makeLayoutInfo(event.target),
- $handle = oLayoutInfo.handle(), $popover = oLayoutInfo.popover(),
- $editable = oLayoutInfo.editable(),
- $editor = oLayoutInfo.editor();
- var elTarget = $handle.find('.note-control-selection').data('target'),
- $target = $(elTarget), posStart = $target.offset(),
- scrollTop = $document.scrollTop();
- var isAirMode = $editor.data('options').airMode;
- $document.on('mousemove', function (event) {
- editor.resizeTo({
- x: event.clientX - posStart.left,
- y: event.clientY - (posStart.top - scrollTop)
- }, $target, !event.shiftKey);
- handle.update($handle, {image: elTarget}, isAirMode);
- popover.update($popover, {image: elTarget}, isAirMode);
- }).one('mouseup', function () {
- $document.off('mousemove');
- });
- if (!$target.data('ratio')) { // original ratio.
- $target.data('ratio', $target.height() / $target.width());
- }
- editor.recordUndo($editable);
- }
- };
- var hToolbarAndPopoverMousedown = function (event) {
- // prevent default event when insertTable (FF, Webkit)
- var $btn = $(event.target).closest('[data-event]');
- if ($btn.length) {
- event.preventDefault();
- }
- };
- var hToolbarAndPopoverClick = function (event) {
- var $btn = $(event.target).closest('[data-event]');
- if ($btn.length) {
- var sEvent = $btn.attr('data-event'),
- sValue = $btn.attr('data-value');
- var oLayoutInfo = makeLayoutInfo(event.target);
- event.preventDefault();
- // before command: detect control selection element($target)
- var $target;
- if ($.inArray(sEvent, ['resize', 'floatMe', 'removeMedia']) !== -1) {
- var $selection = oLayoutInfo.handle().find('.note-control-selection');
- $target = $($selection.data('target'));
- }
- if (editor[sEvent]) { // on command
- var $editable = oLayoutInfo.editable();
- $editable.trigger('focus');
- editor[sEvent]($editable, sValue, $target);
- } else if (commands[sEvent]) {
- commands[sEvent].call(this, oLayoutInfo);
- }
- // after command
- if ($.inArray(sEvent, ['backColor', 'foreColor']) !== -1) {
- var options = oLayoutInfo.editor().data('options', options);
- var module = options.airMode ? popover : toolbar;
- module.updateRecentColor(list.head($btn), sEvent, sValue);
- }
- hToolbarAndPopoverUpdate(event);
- }
- };
- var EDITABLE_PADDING = 24;
- /**
- * `mousedown` event handler on statusbar
- *
- * @param {MouseEvent} event
- */
- var hStatusbarMousedown = function (event) {
- event.preventDefault();
- event.stopPropagation();
- var $editable = makeLayoutInfo(event.target).editable();
- var nEditableTop = $editable.offset().top - $document.scrollTop();
- var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
- var options = oLayoutInfo.editor().data('options');
- $document.on('mousemove', function (event) {
- var nHeight = event.clientY - (nEditableTop + EDITABLE_PADDING);
- nHeight = (options.minHeight > 0) ? Math.max(nHeight, options.minHeight) : nHeight;
- nHeight = (options.maxHeight > 0) ? Math.min(nHeight, options.maxHeight) : nHeight;
- $editable.height(nHeight);
- }).one('mouseup', function () {
- $document.off('mousemove');
- });
- };
- var PX_PER_EM = 18;
- var hDimensionPickerMove = function (event, options) {
- var $picker = $(event.target.parentNode); // target is mousecatcher
- var $dimensionDisplay = $picker.next();
- var $catcher = $picker.find('.note-dimension-picker-mousecatcher');
- var $highlighted = $picker.find('.note-dimension-picker-highlighted');
- var $unhighlighted = $picker.find('.note-dimension-picker-unhighlighted');
- var posOffset;
- // HTML5 with jQuery - e.offsetX is undefined in Firefox
- if (event.offsetX === undefined) {
- var posCatcher = $(event.target).offset();
- posOffset = {
- x: event.pageX - posCatcher.left,
- y: event.pageY - posCatcher.top
- };
- } else {
- posOffset = {
- x: event.offsetX,
- y: event.offsetY
- };
- }
- var dim = {
- c: Math.ceil(posOffset.x / PX_PER_EM) || 1,
- r: Math.ceil(posOffset.y / PX_PER_EM) || 1
- };
- $highlighted.css({ width: dim.c + 'em', height: dim.r + 'em' });
- $catcher.attr('data-value', dim.c + 'x' + dim.r);
- if (3 < dim.c && dim.c < options.insertTableMaxSize.col) {
- $unhighlighted.css({ width: dim.c + 1 + 'em'});
- }
- if (3 < dim.r && dim.r < options.insertTableMaxSize.row) {
- $unhighlighted.css({ height: dim.r + 1 + 'em'});
- }
- $dimensionDisplay.html(dim.c + ' x ' + dim.r);
- };
- /**
- * attach Drag and Drop Events
- *
- * @param {Object} oLayoutInfo - layout Informations
- */
- var attachDragAndDropEvent = function (oLayoutInfo) {
- var collection = $(), $dropzone = oLayoutInfo.dropzone,
- $dropzoneMessage = oLayoutInfo.dropzone.find('.note-dropzone-message');
- // show dropzone on dragenter when dragging a object to document.
- $document.on('dragenter', function (e) {
- var isCodeview = oLayoutInfo.editor.hasClass('codeview');
- if (!isCodeview && !collection.length) {
- oLayoutInfo.editor.addClass('dragover');
- $dropzone.width(oLayoutInfo.editor.width());
- $dropzone.height(oLayoutInfo.editor.height());
- $dropzoneMessage.text('Drag Image Here');
- }
- collection = collection.add(e.target);
- }).on('dragleave', function (e) {
- collection = collection.not(e.target);
- if (!collection.length) {
- oLayoutInfo.editor.removeClass('dragover');
- }
- }).on('drop', function () {
- collection = $();
- oLayoutInfo.editor.removeClass('dragover');
- });
- // change dropzone's message on hover.
- $dropzone.on('dragenter', function () {
- $dropzone.addClass('hover');
- $dropzoneMessage.text('Drop Image');
- }).on('dragleave', function () {
- $dropzone.removeClass('hover');
- $dropzoneMessage.text('Drag Image Here');
- });
- // attach dropImage
- $dropzone.on('drop', function (event) {
- event.preventDefault();
- var dataTransfer = event.originalEvent.dataTransfer;
- if (dataTransfer && dataTransfer.files) {
- var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
- oLayoutInfo.editable().focus();
- insertImages(oLayoutInfo.editable(), dataTransfer.files);
- }
- }).on('dragover', false); // prevent default dragover event
- };
- /**
- * bind KeyMap on keydown
- *
- * @param {Object} oLayoutInfo
- * @param {Object} keyMap
- */
- this.bindKeyMap = function (oLayoutInfo, keyMap) {
- var $editor = oLayoutInfo.editor;
- var $editable = oLayoutInfo.editable;
- oLayoutInfo = makeLayoutInfo($editable);
- $editable.on('keydown', function (event) {
- var aKey = [];
- // modifier
- if (event.metaKey) { aKey.push('CMD'); }
- if (event.ctrlKey && !event.altKey) { aKey.push('CTRL'); }
- if (event.shiftKey) { aKey.push('SHIFT'); }
- // keycode
- var keyName = key.nameFromCode[event.keyCode];
- if (keyName) { aKey.push(keyName); }
- var sEvent = keyMap[aKey.join('+')];
- if (sEvent) {
- event.preventDefault();
- if (editor[sEvent]) {
- editor[sEvent]($editable, $editor.data('options'));
- } else if (commands[sEvent]) {
- commands[sEvent].call(this, oLayoutInfo);
- }
- } else if (key.isEdit(event.keyCode)) {
- editor.recordUndo($editable);
- }
- });
- };
- /**
- * attach eventhandler
- *
- * @param {Object} oLayoutInfo - layout Informations
- * @param {Object} options - user options include custom event handlers
- * @param {Function} options.enter - enter key handler
- */
- this.attach = function (oLayoutInfo, options) {
- // handlers for editable
- this.bindKeyMap(oLayoutInfo, options.keyMap[agent.isMac ? 'mac' : 'pc']);
- oLayoutInfo.editable.on('mousedown', hMousedown);
- oLayoutInfo.editable.on('keyup mouseup', hToolbarAndPopoverUpdate);
- oLayoutInfo.editable.on('scroll', hScroll);
- oLayoutInfo.editable.on('paste', hPasteClipboardImage);
- // handler for handle and popover
- oLayoutInfo.handle.on('mousedown', hHandleMousedown);
- oLayoutInfo.popover.on('click', hToolbarAndPopoverClick);
- oLayoutInfo.popover.on('mousedown', hToolbarAndPopoverMousedown);
- // handlers for frame mode (toolbar, statusbar)
- if (!options.airMode) {
- // handler for drag and drop
- if (!options.disableDragAndDrop) {
- attachDragAndDropEvent(oLayoutInfo);
- }
- // handler for toolbar
- oLayoutInfo.toolbar.on('click', hToolbarAndPopoverClick);
- oLayoutInfo.toolbar.on('mousedown', hToolbarAndPopoverMousedown);
- // handler for statusbar
- if (!options.disableResizeEditor) {
- oLayoutInfo.statusbar.on('mousedown', hStatusbarMousedown);
- }
- }
- // handler for table dimension
- var $catcherContainer = options.airMode ? oLayoutInfo.popover :
- oLayoutInfo.toolbar;
- var $catcher = $catcherContainer.find('.note-dimension-picker-mousecatcher');
- $catcher.css({
- width: options.insertTableMaxSize.col + 'em',
- height: options.insertTableMaxSize.row + 'em'
- }).on('mousemove', function (event) {
- hDimensionPickerMove(event, options);
- });
- // save options on editor
- oLayoutInfo.editor.data('options', options);
- // ret styleWithCSS for backColor / foreColor clearing with 'inherit'.
- if (options.styleWithSpan && !agent.isMSIE) {
- // protect FF Error: NS_ERROR_FAILURE: Failure
- setTimeout(function () {
- document.execCommand('styleWithCSS', 0, true);
- }, 0);
- }
- // History
- oLayoutInfo.editable.data('NoteHistory', new History());
- // basic event callbacks (lowercase)
- // enter, focus, blur, keyup, keydown
- if (options.onenter) {
- oLayoutInfo.editable.keypress(function (event) {
- if (event.keyCode === key.ENTER) { options.onenter(event); }
- });
- }
- if (options.onfocus) { oLayoutInfo.editable.focus(options.onfocus); }
- if (options.onblur) { oLayoutInfo.editable.blur(options.onblur); }
- if (options.onkeyup) { oLayoutInfo.editable.keyup(options.onkeyup); }
- if (options.onkeydown) { oLayoutInfo.editable.keydown(options.onkeydown); }
- if (options.onpaste) { oLayoutInfo.editable.on('paste', options.onpaste); }
- // callbacks for advanced features (camel)
- if (options.onToolbarClick) { oLayoutInfo.toolbar.click(options.onToolbarClick); }
- if (options.onChange) {
- var hChange = function () {
- options.onChange(oLayoutInfo.editable, oLayoutInfo.editable.html());
- };
- if (agent.isMSIE) {
- var sDomEvents = 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted';
- oLayoutInfo.editable.on(sDomEvents, hChange);
- } else {
- oLayoutInfo.editable.on('input', hChange);
- }
- }
- // All editor status will be saved on editable with jquery's data
- // for support multiple editor with singleton object.
- oLayoutInfo.editable.data('callbacks', {
- onAutoSave: options.onAutoSave,
- onImageUpload: options.onImageUpload,
- onImageUploadError: options.onImageUploadError,
- onFileUpload: options.onFileUpload,
- onFileUploadError: options.onFileUpload
- });
- };
- this.dettach = function (oLayoutInfo, options) {
- oLayoutInfo.editable.off();
- oLayoutInfo.popover.off();
- oLayoutInfo.handle.off();
- oLayoutInfo.dialog.off();
- if (!options.airMode) {
- oLayoutInfo.dropzone.off();
- oLayoutInfo.toolbar.off();
- oLayoutInfo.statusbar.off();
- }
- };
- };
- /**
- * renderer
- *
- * rendering toolbar and editable
- */
- var Renderer = function () {
- /**
- * bootstrap button template
- *
- * @param {String} sLabel
- * @param {Object} [options]
- * @param {String} [options.event]
- * @param {String} [options.value]
- * @param {String} [options.title]
- * @param {String} [options.dropdown]
- */
- var tplButton = function (sLabel, options) {
- var event = options.event;
- var value = options.value;
- var title = options.title;
- var className = options.className;
- var dropdown = options.dropdown;
- return '<button type="button"' +
- ' class="btn btn-default btn-sm btn-small' +
- (className ? ' ' + className : '') +
- (dropdown ? ' dropdown-toggle' : '') +
- '"' +
- (dropdown ? ' data-toggle="dropdown"' : '') +
- (title ? ' title="' + title + '"' : '') +
- (event ? ' data-event="' + event + '"' : '') +
- (value ? ' data-value=\'' + value + '\'' : '') +
- ' tabindex="-1">' +
- sLabel +
- (dropdown ? ' <span class="caret"></span>' : '') +
- '</button>' +
- (dropdown || '');
- };
- /**
- * bootstrap icon button template
- *
- * @param {String} sIconClass
- * @param {Object} [options]
- * @param {String} [options.event]
- * @param {String} [options.value]
- * @param {String} [options.title]
- * @param {String} [options.dropdown]
- */
- var tplIconButton = function (sIconClass, options) {
- var sLabel = '<i class="' + sIconClass + '"></i>';
- return tplButton(sLabel, options);
- };
- /**
- * bootstrap popover template
- *
- * @param {String} className
- * @param {String} content
- */
- var tplPopover = function (className, content) {
- return '<div class="' + className + ' popover bottom in" style="display: none;">' +
- '<div class="arrow"></div>' +
- '<div class="popover-content">' +
- content +
- '</div>' +
- '</div>';
- };
- /**
- * bootstrap dialog template
- *
- * @param {String} className
- * @param {String} [title]
- * @param {String} body
- * @param {String} [footer]
- */
- var tplDialog = function (className, title, body, footer) {
- return '<div class="' + className + ' modal" aria-hidden="false">' +
- '<div class="modal-dialog">' +
- '<div class="modal-content">' +
- (title ?
- '<div class="modal-header">' +
- '<button type="button" class="close" aria-hidden="true" tabindex="-1">×</button>' +
- '<h4 class="modal-title">' + title + '</h4>' +
- '</div>' : ''
- ) +
- '<form class="note-modal-form">' +
- '<div class="modal-body">' +
- '<div class="row-fluid">' + body + '</div>' +
- '</div>' +
- (footer ?
- '<div class="modal-footer">' + footer + '</div>' : ''
- ) +
- '</form>' +
- '</div>' +
- '</div>' +
- '</div>';
- };
- var tplButtonInfo = {
- picture: function (lang) {
- return tplIconButton('glyph-icon icon-picture-o icon-picture', {
- event: 'showImageDialog',
- title: lang.image.image
- });
- },
- link: function (lang) {
- return tplIconButton('glyph-icon icon-link icon-link', {
- event: 'showLinkDialog',
- title: lang.link.link
- });
- },
- video: function (lang) {
- return tplIconButton('glyph-icon icon-youtube-play icon-play', {
- event: 'showVideoDialog',
- title: lang.video.video
- });
- },
- table: function (lang) {
- var dropdown = '<ul class="dropdown-menu">' +
- '<div class="note-dimension-picker">' +
- '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>' +
- '<div class="note-dimension-picker-highlighted"></div>' +
- '<div class="note-dimension-picker-unhighlighted"></div>' +
- '</div>' +
- '<div class="note-dimension-display"> 1 x 1 </div>' +
- '</ul>';
- return tplIconButton('glyph-icon icon-table icon-table', {
- title: lang.table.table,
- dropdown: dropdown
- });
- },
- style: function (lang, options) {
- var items = options.styleTags.reduce(function (memo, v) {
- var label = lang.style[v === 'p' ? 'normal' : v];
- return memo + '<li><a data-event="formatBlock" href="#" data-value="' + v + '">' +
- (
- (v === 'p' || v === 'pre') ? label :
- '<' + v + '>' + label + '</' + v + '>'
- ) +
- '</a></li>';
- }, '');
- return tplIconButton('glyph-icon icon-magic icon-magic', {
- title: lang.style.style,
- dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
- });
- },
- fontname: function (lang, options) {
- var items = options.fontNames.reduce(function (memo, v) {
- if (!agent.isFontInstalled(v)) { return memo; }
- return memo + '<li><a data-event="fontName" href="#" data-value="' + v + '">' +
- '<i class="glyph-icon icon-check icon-ok"></i> ' + v +
- '</a></li>';
- }, '');
- var sLabel = '<span class="note-current-fontname">' +
- options.defaultFontName +
- '</span>';
- return tplButton(sLabel, {
- title: lang.font.name,
- dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
- });
- },
- fontsize: function (lang, options) {
- var items = options.fontSizes.reduce(function (memo, v) {
- return memo + '<li><a data-event="fontSize" href="#" data-value="' + v + '">' +
- '<i class="glyph-icon icon-check icon-ok"></i> ' + v +
- '</a></li>';
- }, '');
- var sLabel = '<span class="note-current-fontsize">11</span>';
- return tplButton(sLabel, {
- title: lang.font.size,
- dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
- });
- },
- color: function (lang) {
- var colorButtonLabel = '<i class="glyph-icon icon-font icon-font" style="color:black;background-color:yellow;"></i>';
- var colorButton = tplButton(colorButtonLabel, {
- className: 'note-recent-color',
- title: lang.color.recent,
- event: 'color',
- value: '{"backColor":"yellow"}'
- });
- var dropdown = '<ul class="dropdown-menu">' +
- '<li>' +
- '<div class="btn-group">' +
- '<div class="note-palette-title">' + lang.color.background + '</div>' +
- '<div class="note-color-reset" data-event="backColor"' +
- ' data-value="inherit" title="' + lang.color.transparent + '">' +
- lang.color.setTransparent +
- '</div>' +
- '<div class="note-color-palette" data-target-event="backColor"></div>' +
- '</div>' +
- '<div class="btn-group">' +
- '<div class="note-palette-title">' + lang.color.foreground + '</div>' +
- '<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="' + lang.color.reset + '">' +
- lang.color.resetToDefault +
- '</div>' +
- '<div class="note-color-palette" data-target-event="foreColor"></div>' +
- '</div>' +
- '</li>' +
- '</ul>';
- var moreButton = tplButton('', {
- title: lang.color.more,
- dropdown: dropdown
- });
- return colorButton + moreButton;
- },
- bold: function (lang) {
- return tplIconButton('glyph-icon icon-bold icon-bold', {
- event: 'bold',
- title: lang.font.bold
- });
- },
- italic: function (lang) {
- return tplIconButton('glyph-icon icon-italic icon-italic', {
- event: 'italic',
- title: lang.font.italic
- });
- },
- underline: function (lang) {
- return tplIconButton('glyph-icon icon-underline icon-underline', {
- event: 'underline',
- title: lang.font.underline
- });
- },
- strikethrough: function (lang) {
- return tplIconButton('glyph-icon icon-strikethrough icon-strikethrough', {
- event: 'strikethrough',
- title: lang.font.strikethrough
- });
- },
- superscript: function (lang) {
- return tplIconButton('glyph-icon icon-superscript icon-superscript', {
- event: 'superscript',
- title: lang.font.superscript
- });
- },
- subscript: function (lang) {
- return tplIconButton('glyph-icon icon-subscript icon-subscript', {
- event: 'subscript',
- title: lang.font.subscript
- });
- },
- clear: function (lang) {
- return tplIconButton('glyph-icon icon-eraser icon-eraser', {
- event: 'removeFormat',
- title: lang.font.clear
- });
- },
- ul: function (lang) {
- return tplIconButton('glyph-icon icon-list-ul icon-list-ul', {
- event: 'insertUnorderedList',
- title: lang.lists.unordered
- });
- },
- ol: function (lang) {
- return tplIconButton('glyph-icon icon-list-ol icon-list-ol', {
- event: 'insertOrderedList',
- title: lang.lists.ordered
- });
- },
- paragraph: function (lang) {
- var leftButton = tplIconButton('glyph-icon icon-align-left icon-align-left', {
- title: lang.paragraph.left,
- event: 'justifyLeft'
- });
- var centerButton = tplIconButton('glyph-icon icon-align-center icon-align-center', {
- title: lang.paragraph.center,
- event: 'justifyCenter'
- });
- var rightButton = tplIconButton('glyph-icon icon-align-right icon-align-right', {
- title: lang.paragraph.right,
- event: 'justifyRight'
- });
- var justifyButton = tplIconButton('glyph-icon icon-align-justify icon-align-justify', {
- title: lang.paragraph.justify,
- event: 'justifyFull'
- });
- var outdentButton = tplIconButton('glyph-icon icon-outdent icon-indent-left', {
- title: lang.paragraph.outdent,
- event: 'outdent'
- });
- var indentButton = tplIconButton('glyph-icon icon-indent icon-indent-right', {
- title: lang.paragraph.indent,
- event: 'indent'
- });
- var dropdown = '<div class="dropdown-menu">' +
- '<div class="note-align btn-group">' +
- leftButton + centerButton + rightButton + justifyButton +
- '</div>' +
- '<div class="note-list btn-group">' +
- indentButton + outdentButton +
- '</div>' +
- '</div>';
- return tplIconButton('glyph-icon icon-align-left icon-align-left', {
- title: lang.paragraph.paragraph,
- dropdown: dropdown
- });
- },
- height: function (lang, options) {
- var items = options.lineHeights.reduce(function (memo, v) {
- return memo + '<li><a data-event="lineHeight" href="#" data-value="' + parseFloat(v) + '">' +
- '<i class="glyph-icon icon-check icon-ok"></i> ' + v +
- '</a></li>';
- }, '');
- return tplIconButton('glyph-icon icon-text-height icon-text-height', {
- title: lang.font.height,
- dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
- });
- },
- help: function (lang) {
- return tplIconButton('glyph-icon icon-question icon-question', {
- event: 'showHelpDialog',
- title: lang.options.help
- });
- },
- fullscreen: function (lang) {
- return tplIconButton('glyph-icon icon-arrows-alt icon-fullscreen', {
- event: 'fullscreen',
- title: lang.options.fullscreen
- });
- },
- codeview: function (lang) {
- return tplIconButton('glyph-icon icon-code icon-code', {
- event: 'codeview',
- title: lang.options.codeview
- });
- },
- undo: function (lang) {
- return tplIconButton('glyph-icon icon-undo icon-undo', {
- event: 'undo',
- title: lang.history.undo
- });
- },
- redo: function (lang) {
- return tplIconButton('glyph-icon icon-repeat icon-repeat', {
- event: 'redo',
- title: lang.history.redo
- });
- },
- hr: function (lang) {
- return tplIconButton('glyph-icon icon-minus icon-hr', {
- event: 'insertHorizontalRule',
- title: lang.hr.insert
- });
- }
- };
- var tplPopovers = function (lang, options) {
- var tplLinkPopover = function () {
- var linkButton = tplIconButton('glyph-icon icon-edit icon-edit', {
- title: lang.link.edit,
- event: 'showLinkDialog'
- });
- var unlinkButton = tplIconButton('glyph-icon icon-unlink icon-unlink', {
- title: lang.link.unlink,
- event: 'unlink'
- });
- var content = '<a href="http://www.google.com" target="_blank">www.google.com</a> ' +
- '<div class="note-insert btn-group">' +
- linkButton + unlinkButton +
- '</div>';
- return tplPopover('note-link-popover', content);
- };
- var tplImagePopover = function () {
- var fullButton = tplButton('<span class="note-fontsize-10">100%</span>', {
- title: lang.image.resizeFull,
- event: 'resize',
- value: '1'
- });
- var halfButton = tplButton('<span class="note-fontsize-10">50%</span>', {
- title: lang.image.resizeHalf,
- event: 'resize',
- value: '0.5'
- });
- var quarterButton = tplButton('<span class="note-fontsize-10">25%</span>', {
- title: lang.image.resizeQuarter,
- event: 'resize',
- value: '0.25'
- });
- var leftButton = tplIconButton('glyph-icon icon-align-left icon-align-left', {
- title: lang.image.floatLeft,
- event: 'floatMe',
- value: 'left'
- });
- var rightButton = tplIconButton('glyph-icon icon-align-right icon-align-right', {
- title: lang.image.floatRight,
- event: 'floatMe',
- value: 'right'
- });
- var justifyButton = tplIconButton('glyph-icon icon-align-justify icon-align-justify', {
- title: lang.image.floatNone,
- event: 'floatMe',
- value: 'none'
- });
- var removeButton = tplIconButton('glyph-icon icon-trash-o icon-trash', {
- title: lang.image.remove,
- event: 'removeMedia',
- value: 'none'
- });
- var content = '<div class="btn-group">' + fullButton + halfButton + quarterButton + '</div>' +
- '<div class="btn-group">' + leftButton + rightButton + justifyButton + '</div>' +
- '<div class="btn-group">' + removeButton + '</div>';
- return tplPopover('note-image-popover', content);
- };
- var tplAirPopover = function () {
- var content = '';
- for (var idx = 0, sz = options.airPopover.length; idx < sz; idx ++) {
- var group = options.airPopover[idx];
- content += '<div class="note-' + group[0] + ' btn-group">';
- for (var i = 0, szGroup = group[1].length; i < szGroup; i++) {
- content += tplButtonInfo[group[1][i]](lang, options);
- }
- content += '</div>';
- }
- return tplPopover('note-air-popover', content);
- };
- return '<div class="note-popover">' +
- tplLinkPopover() +
- tplImagePopover() +
- (options.airMode ? tplAirPopover() : '') +
- '</div>';
- };
- var tplHandles = function () {
- return '<div class="note-handle">' +
- '<div class="note-control-selection">' +
- '<div class="note-control-selection-bg"></div>' +
- '<div class="note-control-holder note-control-nw"></div>' +
- '<div class="note-control-holder note-control-ne"></div>' +
- '<div class="note-control-holder note-control-sw"></div>' +
- '<div class="note-control-sizing note-control-se"></div>' +
- '<div class="note-control-selection-info"></div>' +
- '</div>' +
- '</div>';
- };
- /**
- * shortcut table template
- * @param {String} title
- * @param {String} body
- */
- var tplShortcut = function (title, body) {
- return '<table class="note-shortcut">' +
- '<thead>' +
- '<tr><th></th><th>' + title + '</th></tr>' +
- '</thead>' +
- '<tbody>' + body + '</tbody>' +
- '</table>';
- };
- var tplShortcutText = function (lang) {
- var body = '<tr><td>⌘ + B</td><td>' + lang.font.bold + '</td></tr>' +
- '<tr><td>⌘ + I</td><td>' + lang.font.italic + '</td></tr>' +
- '<tr><td>⌘ + U</td><td>' + lang.font.underline + '</td></tr>' +
- '<tr><td>⌘ + ⇧ + S</td><td>' + lang.font.strikethrough + '</td></tr>' +
- '<tr><td>⌘ + \\</td><td>' + lang.font.clear + '</td></tr>';
- return tplShortcut(lang.shortcut.textFormatting, body);
- };
- var tplShortcutAction = function (lang) {
- var body = '<tr><td>⌘ + Z</td><td>' + lang.history.undo + '</td></tr>' +
- '<tr><td>⌘ + ⇧ + Z</td><td>' + lang.history.redo + '</td></tr>' +
- '<tr><td>⌘ + ]</td><td>' + lang.paragraph.indent + '</td></tr>' +
- '<tr><td>⌘ + [</td><td>' + lang.paragraph.outdent + '</td></tr>' +
- '<tr><td>⌘ + ENTER</td><td>' + lang.hr.insert + '</td></tr>';
- return tplShortcut(lang.shortcut.action, body);
- };
- var tplShortcutPara = function (lang) {
- var body = '<tr><td>⌘ + ⇧ + L</td><td>' + lang.paragraph.left + '</td></tr>' +
- '<tr><td>⌘ + ⇧ + E</td><td>' + lang.paragraph.center + '</td></tr>' +
- '<tr><td>⌘ + ⇧ + R</td><td>' + lang.paragraph.right + '</td></tr>' +
- '<tr><td>⌘ + ⇧ + J</td><td>' + lang.paragraph.justify + '</td></tr>' +
- '<tr><td>⌘ + ⇧ + NUM7</td><td>' + lang.lists.ordered + '</td></tr>' +
- '<tr><td>⌘ + ⇧ + NUM8</td><td>' + lang.lists.unordered + '</td></tr>';
- return tplShortcut(lang.shortcut.paragraphFormatting, body);
- };
- var tplShortcutStyle = function (lang) {
- var body = '<tr><td>⌘ + NUM0</td><td>' + lang.style.normal + '</td></tr>' +
- '<tr><td>⌘ + NUM1</td><td>' + lang.style.h1 + '</td></tr>' +
- '<tr><td>⌘ + NUM2</td><td>' + lang.style.h2 + '</td></tr>' +
- '<tr><td>⌘ + NUM3</td><td>' + lang.style.h3 + '</td></tr>' +
- '<tr><td>⌘ + NUM4</td><td>' + lang.style.h4 + '</td></tr>' +
- '<tr><td>⌘ + NUM5</td><td>' + lang.style.h5 + '</td></tr>' +
- '<tr><td>⌘ + NUM6</td><td>' + lang.style.h6 + '</td></tr>';
- return tplShortcut(lang.shortcut.documentStyle, body);
- };
- var tplExtraShortcuts = function (lang, options) {
- var extraKeys = options.extraKeys;
- var body = '';
- for (var key in extraKeys) {
- if (extraKeys.hasOwnProperty(key)) {
- body += '<tr><td>' + key + '</td><td>' + extraKeys[key] + '</td></tr>';
- }
- }
- return tplShortcut(lang.shortcut.extraKeys, body);
- };
- var tplShortcutTable = function (lang, options) {
- var template = '<table class="note-shortcut-layout">' +
- '<tbody>' +
- '<tr><td>' + tplShortcutAction(lang, options) + '</td><td>' + tplShortcutText(lang, options) + '</td></tr>' +
- '<tr><td>' + tplShortcutStyle(lang, options) + '</td><td>' + tplShortcutPara(lang, options) + '</td></tr>';
- if (options.extraKeys) {
- template += '<tr><td colspan="2">' + tplExtraShortcuts(lang, options) + '</td></tr>';
- }
- template += '</tbody</table>';
- return template;
- };
- var replaceMacKeys = function (sHtml) {
- return sHtml.replace(/⌘/g, 'Ctrl').replace(/⇧/g, 'Shift');
- };
- var tplDialogs = function (lang, options) {
- var tplImageDialog = function () {
- var body = '<h5>' + lang.image.selectFromFiles + '</h5>' +
- '<input class="note-image-input" type="file" name="files" accept="image/*" />' +
- '<h5>' + lang.image.url + '</h5>' +
- '<input class="note-image-url form-control span12" type="text" />';
- var footer = '<button href="#" class="btn btn-primary note-image-btn disabled" disabled>' + lang.image.insert + '</button>';
- return tplDialog('note-image-dialog', lang.image.insert, body, footer);
- };
- var tplLinkDialog = function () {
- var body = '<div class="form-group">' +
- '<label>' + lang.link.textToDisplay + '</label>' +
- '<input class="note-link-text form-control span12" type="text" />' +
- '</div>' +
- '<div class="form-group">' +
- '<label>' + lang.link.url + '</label>' +
- '<input class="note-link-url form-control span12" type="text" />' +
- '</div>' +
- (!options.disableLinkTarget ?
- '<div class="checkbox">' +
- '<label>' + '<input type="checkbox" checked> ' +
- lang.link.openInNewWindow +
- '</label>' +
- '</div>' : ''
- );
- var footer = '<button href="#" class="btn btn-primary note-link-btn disabled" disabled>' + lang.link.insert + '</button>';
- return tplDialog('note-link-dialog', lang.link.insert, body, footer);
- };
- var tplVideoDialog = function () {
- var body = '<div class="form-group">' +
- '<label>' + lang.video.url + '</label> <small class="text-muted">' + lang.video.providers + '</small>' +
- '<input class="note-video-url form-control span12" type="text" />' +
- '</div>';
- var footer = '<button href="#" class="btn btn-primary note-video-btn disabled" disabled>' + lang.video.insert + '</button>';
- return tplDialog('note-video-dialog', lang.video.insert, body, footer);
- };
- var tplHelpDialog = function () {
- var body = '<a class="modal-close pull-right" aria-hidden="true" tabindex="-1">' + lang.shortcut.close + '</a>' +
- '<div class="title">' + lang.shortcut.shortcuts + '</div>' +
- (agent.isMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) +
- '<p class="text-center">' +
- '<a href="//hackerwins.github.io/summernote/" target="_blank">Summernote 0.5.3</a> · ' +
- '<a href="//github.com/HackerWins/summernote" target="_blank">Project</a> · ' +
- '<a href="//github.com/HackerWins/summernote/issues" target="_blank">Issues</a>' +
- '</p>';
- return tplDialog('note-help-dialog', '', body, '');
- };
- return '<div class="note-dialog">' +
- tplImageDialog() +
- tplLinkDialog() +
- tplVideoDialog() +
- tplHelpDialog() +
- '</div>';
- };
- var tplStatusbar = function () {
- return '<div class="note-resizebar">' +
- '<div class="note-icon-bar"></div>' +
- '<div class="note-icon-bar"></div>' +
- '<div class="note-icon-bar"></div>' +
- '</div>';
- };
- var representShortcut = function (str) {
- if (agent.isMac) {
- str = str.replace('CMD', '⌘').replace('SHIFT', '⇧');
- }
- return str.replace('BACKSLASH', '\\')
- .replace('SLASH', '/')
- .replace('LEFTBRACKET', '[')
- .replace('RIGHTBRACKET', ']');
- };
- /**
- * createTooltip
- *
- * @param {jQuery} $container
- * @param {Object} keyMap
- * @param {String} [sPlacement]
- */
- var createTooltip = function ($container, keyMap, sPlacement) {
- var invertedKeyMap = func.invertObject(keyMap);
- var $buttons = $container.find('button');
- $buttons.each(function (i, elBtn) {
- var $btn = $(elBtn);
- var sShortcut = invertedKeyMap[$btn.data('event')];
- if (sShortcut) {
- $btn.attr('title', function (i, v) {
- return v + ' (' + representShortcut(sShortcut) + ')';
- });
- }
- // bootstrap tooltip on btn-group bug
- // https://github.com/twbs/bootstrap/issues/5687
- }).tooltip({
- container: 'body',
- trigger: 'hover',
- placement: sPlacement || 'top'
- }).on('click', function () {
- $(this).tooltip('hide');
- });
- };
- // createPalette
- var createPalette = function ($container, options) {
- var aaColor = options.colors;
- $container.find('.note-color-palette').each(function () {
- var $palette = $(this), sEvent = $palette.attr('data-target-event');
- var aPaletteContents = [];
- for (var row = 0, szRow = aaColor.length; row < szRow; row++) {
- var aColor = aaColor[row];
- var aButton = [];
- for (var col = 0, szCol = aColor.length; col < szCol; col++) {
- var sColor = aColor[col];
- aButton.push(['<button type="button" class="note-color-btn" style="background-color:', sColor,
- ';" data-event="', sEvent,
- '" data-value="', sColor,
- '" title="', sColor,
- '" data-toggle="button" tabindex="-1"></button>'].join(''));
- }
- aPaletteContents.push('<div class="note-color-row">' + aButton.join('') + '</div>');
- }
- $palette.html(aPaletteContents.join(''));
- });
- };
- /**
- * create summernote layout (air mode)
- *
- * @param {jQuery} $holder
- * @param {Object} options
- */
- this.createLayoutByAirMode = function ($holder, options) {
- var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
- var langInfo = $.summernote.lang[options.lang];
- var id = func.uniqueId();
- $holder.addClass('note-air-editor note-editable');
- $holder.attr({
- 'id': 'note-editor-' + id,
- 'contentEditable': true
- });
- var body = document.body;
- // create Popover
- var $popover = $(tplPopovers(langInfo, options));
- $popover.addClass('note-air-layout');
- $popover.attr('id', 'note-popover-' + id);
- $popover.appendTo(body);
- createTooltip($popover, keyMap);
- createPalette($popover, options);
- // create Handle
- var $handle = $(tplHandles());
- $handle.addClass('note-air-layout');
- $handle.attr('id', 'note-handle-' + id);
- $handle.appendTo(body);
- // create Dialog
- var $dialog = $(tplDialogs(langInfo, options));
- $dialog.addClass('note-air-layout');
- $dialog.attr('id', 'note-dialog-' + id);
- $dialog.find('button.close, a.modal-close').click(function () {
- $(this).closest('.modal').modal('hide');
- });
- $dialog.appendTo(body);
- };
- /**
- * create summernote layout (normal mode)
- *
- * @param {jQuery} $holder
- * @param {Object} options
- */
- this.createLayoutByFrame = function ($holder, options) {
- //01. create Editor
- var $editor = $('<div class="note-editor"></div>');
- if (options.width) {
- $editor.width(options.width);
- }
- //02. statusbar (resizebar)
- if (options.height > 0) {
- $('<div class="note-statusbar">' + (options.disableResizeEditor ? '' : tplStatusbar()) + '</div>').prependTo($editor);
- }
- //03. create Editable
- var isContentEditable = !$holder.is(':disabled');
- var $editable = $('<div class="note-editable" contentEditable="' + isContentEditable + '"></div>')
- .prependTo($editor);
- if (options.height) {
- $editable.height(options.height);
- }
- if (options.direction) {
- $editable.attr('dir', options.direction);
- }
- $editable.html(dom.html($holder) || dom.emptyPara);
- //031. create codable
- $('<textarea class="note-codable"></textarea>').prependTo($editor);
- var langInfo = $.summernote.lang[options.lang];
- //04. create Toolbar
- var sToolbar = '';
- for (var idx = 0, sz = options.toolbar.length; idx < sz; idx ++) {
- var groupName = options.toolbar[idx][0];
- var groupButtons = options.toolbar[idx][1];
- sToolbar += '<div class="note-' + groupName + ' btn-group">';
- for (var i = 0, btnLength = groupButtons.length; i < btnLength; i++) {
- // continue When a toolbar button does'nt exist
- if (!$.isFunction(tplButtonInfo[groupButtons[i]])) { continue; }
- sToolbar += tplButtonInfo[groupButtons[i]](langInfo, options);
- }
- sToolbar += '</div>';
- }
- sToolbar = '<div class="note-toolbar btn-toolbar">' + sToolbar + '</div>';
- var $toolbar = $(sToolbar).prependTo($editor);
- var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
- createPalette($toolbar, options);
- createTooltip($toolbar, keyMap, 'bottom');
- //05. create Popover
- var $popover = $(tplPopovers(langInfo, options)).prependTo($editor);
- createPalette($popover, options);
- createTooltip($popover, keyMap);
- //06. handle(control selection, ...)
- $(tplHandles()).prependTo($editor);
- //07. create Dialog
- var $dialog = $(tplDialogs(langInfo, options)).prependTo($editor);
- $dialog.find('button.close, a.modal-close').click(function () {
- $(this).closest('.modal').modal('hide');
- });
- //08. create Dropzone
- $('<div class="note-dropzone"><div class="note-dropzone-message"></div></div>').prependTo($editor);
- //09. Editor/Holder switch
- $editor.insertAfter($holder);
- $holder.hide();
- };
- this.noteEditorFromHolder = function ($holder) {
- if ($holder.hasClass('note-air-editor')) {
- return $holder;
- } else if ($holder.next().hasClass('note-editor')) {
- return $holder.next();
- } else {
- return $();
- }
- };
- /**
- * create summernote layout
- *
- * @param {jQuery} $holder
- * @param {Object} options
- */
- this.createLayout = function ($holder, options) {
- if (this.noteEditorFromHolder($holder).length) {
- return;
- }
- if (options.airMode) {
- this.createLayoutByAirMode($holder, options);
- } else {
- this.createLayoutByFrame($holder, options);
- }
- };
- /**
- * returns layoutInfo from holder
- *
- * @param {jQuery} $holder - placeholder
- * @returns {Object}
- */
- this.layoutInfoFromHolder = function ($holder) {
- var $editor = this.noteEditorFromHolder($holder);
- if (!$editor.length) { return; }
- var layoutInfo = dom.buildLayoutInfo($editor);
- // cache all properties.
- for (var key in layoutInfo) {
- if (layoutInfo.hasOwnProperty(key)) {
- layoutInfo[key] = layoutInfo[key].call();
- }
- }
- return layoutInfo;
- };
- /**
- * removeLayout
- *
- * @param {jQuery} $holder - placeholder
- * @param {Object} oLayoutInfo
- * @param {Object} options
- *
- */
- this.removeLayout = function ($holder, oLayoutInfo, options) {
- if (options.airMode) {
- $holder.removeClass('note-air-editor note-editable')
- .removeAttr('id contentEditable');
- oLayoutInfo.popover.remove();
- oLayoutInfo.handle.remove();
- oLayoutInfo.dialog.remove();
- } else {
- $holder.html(oLayoutInfo.editable.html());
- oLayoutInfo.editor.remove();
- $holder.show();
- }
- };
- };
- // jQuery namespace for summernote
- $.summernote = $.summernote || {};
- // extends default `settings`
- $.extend($.summernote, settings);
- var renderer = new Renderer();
- var eventHandler = new EventHandler();
- /**
- * extend jquery fn
- */
- $.fn.extend({
- /**
- * initialize summernote
- * - create editor layout and attach Mouse and keyboard events.
- *
- * @param {Object} options
- * @returns {this}
- */
- summernote: function (options) {
- // extend default options
- options = $.extend({}, $.summernote.options, options);
- this.each(function (idx, elHolder) {
- var $holder = $(elHolder);
- // createLayout with options
- renderer.createLayout($holder, options);
- var info = renderer.layoutInfoFromHolder($holder);
- eventHandler.attach(info, options);
- // Textarea: auto filling the code before form submit.
- if (dom.isTextarea($holder[0])) {
- $holder.closest('form').submit(function () {
- $holder.val($holder.code());
- });
- }
- });
- // focus on first editable element
- if (this.first().length && options.focus) {
- var info = renderer.layoutInfoFromHolder(this.first());
- info.editable.focus();
- }
- // callback on init
- if (this.length && options.oninit) {
- options.oninit();
- }
- return this;
- },
- //
- /**
- * get the HTML contents of note or set the HTML contents of note.
- *
- * @param {String} [sHTML] - HTML contents(optional, set)
- * @returns {this|String} - context(set) or HTML contents of note(get).
- */
- code: function (sHTML) {
- // get the HTML contents of note
- if (sHTML === undefined) {
- var $holder = this.first();
- if (!$holder.length) { return; }
- var info = renderer.layoutInfoFromHolder($holder);
- if (!!(info && info.editable)) {
- var isCodeview = info.editor.hasClass('codeview');
- if (isCodeview && agent.hasCodeMirror) {
- info.codable.data('cmEditor').save();
- }
- return isCodeview ? info.codable.val() : info.editable.html();
- }
- return dom.isTextarea($holder[0]) ? $holder.val() : $holder.html();
- }
- // set the HTML contents of note
- this.each(function (i, elHolder) {
- var info = renderer.layoutInfoFromHolder($(elHolder));
- if (info && info.editable) { info.editable.html(sHTML); }
- });
- return this;
- },
- /**
- * destroy Editor Layout and dettach Key and Mouse Event
- * @returns {this}
- */
- destroy: function () {
- this.each(function (idx, elHolder) {
- var $holder = $(elHolder);
- var info = renderer.layoutInfoFromHolder($holder);
- if (!info || !info.editable) { return; }
- var options = info.editor.data('options');
- eventHandler.dettach(info, options);
- renderer.removeLayout($holder, info, options);
- });
- return this;
- }
- });
- }));
|