| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597 |
- /**
- * Super simple wysiwyg editor on Bootstrap v0.5.2
- * 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-05-05T12:16Z
- */
- (function (factory) {
- /* global define */
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
- define(['jquery', 'codemirror'], factory);
- } else {
- // Browser globals: jQuery, CodeMirror
- factory(window.jQuery, window.CodeMirror);
- }
- }(function ($, CodeMirror) {
-
- 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;
- };
- }
- /**
- * Object which check platform and agent
- */
- var agent = {
- bMac: navigator.appVersion.indexOf('Mac') > -1,
- bMSIE: navigator.userAgent.indexOf('MSIE') > -1 || navigator.userAgent.indexOf('Trident') > -1,
- bFF: navigator.userAgent.indexOf('Firefox') > -1,
- jqueryVersion: parseFloat($.fn.jquery),
- bCodeMirror: !!CodeMirror
- };
- /**
- * 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;
- };
- return {
- eq: eq,
- eq2: eq2,
- ok: ok,
- fail: fail,
- not: not,
- self: self
- };
- })();
- /**
- * 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 === 0) { 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 = 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 bStart = false, bEnd = false;
- // DFS(depth first search) with commonAcestor.
- (function fnWalk(node) {
- if (!node) { return; } // traverse fisnish
- if (node === nodeA) { bStart = true; } // start point
- if (bStart && !bEnd) { aNode.push(node); } // between
- if (node === nodeB) { bEnd = 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 offset from parent.
- *
- * @param {Element} node
- */
- var position = function (node) {
- var offset = 0;
- while ((node = node.previousSibling)) { offset += 1; }
- return offset;
- };
-
- /**
- * 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 splitData = 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 split = function (root, pivot, offset) {
- var aAncestor = listAncestor(pivot, func.eq(root));
- if (aAncestor.length === 1) { return splitData(pivot, offset); }
- return aAncestor.reduce(function (node, parent) {
- var clone = parent.cloneNode(false);
- insertAfter(clone, parent);
- if (node === pivot) {
- node = splitData(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.bMSIE ? ' ' : '<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'),
- ancestor: ancestor,
- listAncestor: listAncestor,
- listNext: listNext,
- listPrev: listPrev,
- listDescendant: listDescendant,
- commonAncestor: commonAncestor,
- listBetween: listBetween,
- insertAfter: insertAfter,
- position: position,
- makeOffsetPath: makeOffsetPath,
- fromOffsetPath: fromOffsetPath,
- split: split,
- remove: remove,
- html: html
- };
- })();
- var settings = {
- // version
- version: '0.5.2',
- /**
- * options
- */
- options: {
- width: null, // set editor width
- height: null, // set editable height, ex) 300
- focus: false, // set focus after initilize 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
- codemirror: { // codemirror options
- mode: 'text/html',
- lineNumbers: true
- },
- // language
- lang: 'en-US', // language 'en-US', 'ko-KR', ...
- direction: null, // text direction, ex) 'rtl'
- // default toolbar
- toolbar: [
- ['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']],
- ['view', ['fullscreen', 'codeview']],
- ['help', ['help']]
- ],
- // style tag
- styleTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
- // default fontName
- defaultFontName: 'Arial',
- // fontName
- fontNames: [
- 'Serif', 'Sans', 'Arial', 'Arial Black', 'Courier',
- 'Courier New', 'Comic Sans MS', 'Helvetica', 'Impact', 'Lucida Grande',
- 'Lucida Sans', 'Tahoma', 'Times', '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'],
- // 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, // imageUploadHandler
- onImageUploadError: null, // imageUploadErrorHandler
- onToolbarClick: null,
- 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'
- },
- 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'
- }
- }
- },
- // default language: en-US
- lang: {
- 'en-US': {
- font: {
- bold: 'Bold',
- italic: 'Italic',
- underline: 'Underline',
- strike: 'Strike',
- 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, or DailyMotion)'
- },
- 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: 'BackColor',
- foreground: 'FontColor',
- 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';
- // 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 bUnordered = $.inArray(oStyle['list-style-type'], aOrderedType) > -1;
- oStyle['list-style'] = bUnordered ? '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);
- return oStyle;
- };
- };
- /**
- * range module
- */
- var range = (function () {
- var bW3CRangeSupport = !!document.createRange;
-
- /**
- * return boundaryPoint from TextRange, inspired by Andy Na's HuskyRange.js
- * @param {TextRange} textRange
- * @param {Boolean} bStart
- * @return {BoundaryPoint}
- */
- var textRange2bp = function (textRange, bStart) {
- 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 (bStart && 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, bCollapseToStart;
-
- 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);
- bCollapseToStart = !elPrevCont;
- } else {
- elNode = elCont.childNodes[nOffset] || elCont;
- if (dom.isText(elNode)) {
- return textRangeInfo(elNode, nOffset);
- }
-
- nOffset = 0;
- bCollapseToStart = false;
- }
-
- return {cont: elNode, collapseToStart: bCollapseToStart, 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 (bW3CRangeSupport) {
- 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;
- }
- };
- /**
- * select update visible range
- */
- this.select = function () {
- var nativeRng = nativeRange();
- if (bW3CRangeSupport) {
- 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) {
- 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);
- };
-
- /**
- * 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 (bW3CRangeSupport) {
- nativeRng.insertNode(node);
- } else {
- nativeRng.pasteHTML(node.outerHTML); // NOTE: missing node reference.
- }
- };
-
- this.toString = function () {
- var nativeRng = nativeRange();
- return bW3CRangeSupport ? nativeRng.toString() : nativeRng.text;
- };
-
- // bookmark: offsetPath bookmark
- this.bookmark = function (elEditable) {
- return {
- s: { path: dom.makeOffsetPath(elEditable, sc), offset: so },
- e: { path: dom.makeOffsetPath(elEditable, ec), offset: eo }
- };
- };
- };
-
- 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 === 0) { // from Browser Selection
- if (bW3CRangeSupport) { // 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} bShift
- */
- this.tab = function (rng, bShift) {
- 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[bShift ? '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.data('range', range.create());
- };
- /**
- * restore lately range
- *
- * @param {jQuery} $editable
- */
- this.restoreRange = function ($editable) {
- var rng = $editable.data('range');
- if (rng) { rng.select(); }
- };
- /**
- * current style
- * @param {Element} elTarget
- */
- this.currentStyle = function (elTarget) {
- var rng = range.create();
- return rng.isOnEditable() && style.current(rng, elTarget);
- };
- /**
- * 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',
- '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 {Number} nTabsize
- * @param {Boolean} bShift
- */
- 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)
- 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 $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 > 0) {
- $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 > 0) {
- $video = $('<iframe>')
- .attr('src', vMatch[0] + '/embed/simple')
- .attr('width', '600').attr('height', '600')
- .attr('class', 'vine-embed');
- } else if (vimMatch && vimMatch[3].length > 0) {
- $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 > 0) {
- $video = $('<iframe>')
- .attr('src', '//www.dailymotion.com/embed/video/' + dmMatch[2])
- .attr('width', '640').attr('height', '360');
- } 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.bMSIE ? '<' + 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.bFF) {
- // 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 {String} sLinkUrl
- * @param {Boolean} bNewWindow
- */
- this.createLink = function ($editable, sLinkUrl, bNewWindow) {
- var rng = range.create();
- recordUndo($editable);
- // protocol
- var sLinkUrlWithProtocol = sLinkUrl;
- if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) {
- sLinkUrlWithProtocol = 'mailto:' + sLinkUrl;
- } else if (sLinkUrl.indexOf('://') === -1) {
- sLinkUrlWithProtocol = 'http://' + sLinkUrl;
- }
- // createLink when range collapsed (IE, Firefox).
- if ((agent.bMSIE || agent.bFF) && rng.isCollapsed()) {
- rng.insertNode($('<A id="linkAnchor">' + sLinkUrl + '</A>')[0]);
- var $anchor = $('#linkAnchor').attr('href', sLinkUrlWithProtocol).removeAttr('id');
- rng = range.createFromNode($anchor[0]);
- rng.select();
- } else {
- document.execCommand('createlink', false, sLinkUrlWithProtocol);
- rng = range.create();
- }
- // target
- $.each(rng.nodes(dom.isAnchor), function (idx, elAnchor) {
- if (bNewWindow) {
- $(elAnchor).attr('target', '_blank');
- } else {
- $(elAnchor).removeAttr('target');
- }
- });
- };
- /**
- * get link info
- *
- * @return {Promise}
- */
- this.getLinkInfo = function () {
- var rng = range.create();
- var bNewWindow = true;
- var sUrl = '';
- // If range on anchor expand range on anchor(for edit link).
- if (rng.isOnAnchor()) {
- var elAnchor = dom.ancestor(rng.sc, dom.isAnchor);
- rng = range.createFromNode(elAnchor);
- bNewWindow = $(elAnchor).attr('target') === '_blank';
- sUrl = elAnchor.href;
- }
- return {
- text: rng.toString(),
- url: sUrl,
- newWindow: bNewWindow
- };
- };
- /**
- * get video info
- *
- * @return {Object}
- */
- this.getVideoInfo = function () {
- 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 === 0) { return; }
- applySnap($editable, aUndo.pop());
- aRedo.push(oSnap);
- };
- this.redo = function ($editable) {
- var oSnap = makeSnap($editable);
- if (aRedo.length === 0) { return; }
- applySnap($editable, aRedo.pop());
- aUndo.push(oSnap);
- };
- this.recordUndo = function ($editable) {
- aRedo = [];
- aUndo.push(makeSnap($editable));
- };
- };
- /**
- * Toolbar
- */
- var Toolbar = function () {
- /**
- * update button status
- *
- * @param {jQuery} $toolbar
- * @param {Object} oStyle
- */
- this.update = function ($toolbar, 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 bChecked = ($(this).data('value') + '') === (nValue + '');
- this.className = bChecked ? 'checked' : '';
- });
- };
- /**
- * update button state(active or not).
- *
- * @param {String} sSelector
- * @param {Function} pred
- */
- var btnState = function (sSelector, pred) {
- var $btn = $toolbar.find(sSelector);
- $btn.toggleClass('active', pred());
- };
- // fontname
- var $fontname = $toolbar.find('.note-fontname');
- if ($fontname.length > 0) {
- 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 = $toolbar.find('.note-fontsize');
- $fontsize.find('.note-current-fontsize').text(oStyle['font-size']);
- checkDropdownMenu($fontsize, parseFloat(oStyle['font-size']));
- // lineheight
- var $lineHeight = $toolbar.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="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);
- };
- this.updateFullscreen = function ($toolbar, bFullscreen) {
- var $btn = $toolbar.find('button[data-event="fullscreen"]');
- $btn.toggleClass('active', bFullscreen);
- };
- this.updateCodeview = function ($toolbar, bCodeview) {
- var $btn = $toolbar.find('button[data-event="codeview"]');
- $btn.toggleClass('active', bCodeview);
- };
- /**
- * 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');
- };
- };
- /**
- * Popover (http://getbootstrap.com/javascript/#popovers)
- */
- var Popover = function () {
- /**
- * show popover
- * @param {jQuery} popover
- * @param {Element} elPlaceholder - placeholder for popover
- */
- var showPopover = function ($popover, elPlaceholder) {
- var $placeholder = $(elPlaceholder);
- var pos = $placeholder.position(), height = $placeholder.height();
- // display popover below placeholder.
- $popover.css({
- display: 'block',
- left: pos.left,
- top: pos.top + height
- });
- };
- /**
- * update current state
- * @param {jQuery} $popover - popover container
- * @param {Object} oStyle - style object
- */
- this.update = function ($popover, oStyle) {
- var $linkPopover = $popover.find('.note-link-popover');
- if (oStyle.anchor) {
- var $anchor = $linkPopover.find('a');
- $anchor.attr('href', oStyle.anchor.href).html(oStyle.anchor.href);
- showPopover($linkPopover, oStyle.anchor);
- } else {
- $linkPopover.hide();
- }
- var $imagePopover = $popover.find('.note-image-popover');
- if (oStyle.image) {
- showPopover($imagePopover, oStyle.image);
- } else {
- $imagePopover.hide();
- }
- };
- /**
- * 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
- */
- this.update = function ($handle, oStyle) {
- var $selection = $handle.find('.note-control-selection');
- if (oStyle.image) {
- var $image = $(oStyle.image);
- var pos = $image.position();
- var szImage = {w: $image.width(), h: $image.height()};
- $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} bEnable
- */
- var toggleBtn = function ($btn, bEnable) {
- $btn.toggleClass('disabled', !bEnable);
- $btn.attr('disabled', !bEnable);
- };
- /**
- * 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 () {
- $imageDialog.modal('hide');
- deferred.resolve(this.files);
- })
- );
- $imageBtn.click(function (event) {
- event.preventDefault();
- $imageDialog.modal('hide');
- deferred.resolve($imageUrl.val());
- });
- $imageUrl.keyup(function () {
- toggleBtn($imageBtn, $imageUrl.val());
- }).val('').focus();
- }).one('hidden.bs.modal', function () {
- $editable.focus();
- $imageInput.off('change');
- $imageUrl.off('keyup');
- $imageBtn.off('click');
- }).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();
- $videoDialog.modal('hide');
- deferred.resolve($videoUrl.val());
- });
- }).one('hidden.bs.modal', function () {
- $editable.focus();
- $videoUrl.off('keyup');
- $videoBtn.off('click');
- }).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);
- $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');
- $openInNewWindow.prop('checked', linkInfo.newWindow);
- $linkBtn.one('click', function (event) {
- event.preventDefault();
- $linkDialog.modal('hide');
- deferred.resolve($linkUrl.val(), $openInNewWindow.is(':checked'));
- });
- }).one('hidden.bs.modal', function () {
- $editable.focus();
- $linkUrl.off('keyup');
- }).modal('show');
- }).promise();
- };
- /**
- * show help dialog
- *
- * @param {jQuery} $dialog
- */
- this.showHelpDialog = function ($editable, $dialog) {
- var $helpDialog = $dialog.find('.note-help-dialog');
- $helpDialog.one('hidden.bs.modal', function () {
- $editable.focus();
- }).modal('show');
- };
- };
- /**
- * EventHandler
- */
- var EventHandler = function () {
- 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 $editor = $(descendant).closest('.note-editor');
- return $editor.length > 0 && 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 hMousedown = function (event) {
- //preventDefault Selection for FF, IE8+
- if (dom.isImg(event.target)) { event.preventDefault(); }
- };
- var hToolbarAndPopoverUpdate = function (event) {
- var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
- var oStyle = editor.currentStyle(event.target);
- if (!oStyle) { return; }
- toolbar.update(oLayoutInfo.toolbar(), oStyle);
- popover.update(oLayoutInfo.popover(), oStyle);
- handle.update(oLayoutInfo.handle(), oStyle);
- };
- 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 originalEvent = event.originalEvent;
- if (!originalEvent.clipboardData ||
- !originalEvent.clipboardData.items ||
- !originalEvent.clipboardData.items.length) {
- return;
- }
- var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
- var item = list.head(originalEvent.clipboardData.items);
- var bClipboardImage = item.kind === 'file' && item.type.indexOf('image/') !== -1;
- if (bClipboardImage) {
- 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();
- $editor.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});
- popover.update($popover, {image: elTarget});
- }).one('mouseup', function () {
- $editor.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 > 0) { event.preventDefault(); }
- };
- var hToolbarAndPopoverClick = function (event) {
- var $btn = $(event.target).closest('[data-event]');
- if ($btn.length > 0) {
- var sEvent = $btn.attr('data-event'),
- sValue = $btn.attr('data-value');
- var oLayoutInfo = makeLayoutInfo(event.target);
- var $editor = oLayoutInfo.editor(),
- $toolbar = oLayoutInfo.toolbar(),
- $dialog = oLayoutInfo.dialog(),
- $editable = oLayoutInfo.editable(),
- $codable = oLayoutInfo.codable();
- var server;
- var cmEditor;
- var options = $editor.data('options');
- // before command: detect control selection element($target)
- var $target;
- if ($.inArray(sEvent, ['resize', 'floatMe', 'removeMedia']) !== -1) {
- var $handle = oLayoutInfo.handle();
- var $selection = $handle.find('.note-control-selection');
- $target = $($selection.data('target'));
- }
- if (editor[sEvent]) { // on command
- $editable.trigger('focus');
- editor[sEvent]($editable, sValue, $target);
- }
- // after command
- if ($.inArray(sEvent, ['backColor', 'foreColor']) !== -1) {
- toolbar.updateRecentColor($btn[0], sEvent, sValue);
- } else if (sEvent === 'showLinkDialog') { // popover to dialog
- $editable.focus();
- var linkInfo = editor.getLinkInfo();
- editor.saveRange($editable);
- dialog.showLinkDialog($editable, $dialog, linkInfo).then(function (sLinkUrl, bNewWindow) {
- editor.restoreRange($editable);
- editor.createLink($editable, sLinkUrl, bNewWindow);
- });
- } else if (sEvent === 'showImageDialog') {
- $editable.focus();
- dialog.showImageDialog($editable, $dialog).then(function (data) {
- if (typeof data === 'string') {
- editor.restoreRange($editable);
- editor.insertImage($editable, data);
- } else {
- insertImages($editable, data);
- }
- });
- } else if (sEvent === 'showVideoDialog') {
- $editable.focus();
- var videoInfo = editor.getVideoInfo();
- editor.saveRange($editable);
- dialog.showVideoDialog($editable, $dialog, videoInfo).then(function (sUrl) {
- editor.restoreRange($editable);
- editor.insertVideo($editable, sUrl);
- });
- } else if (sEvent === 'showHelpDialog') {
- dialog.showHelpDialog($editable, $dialog);
- } else if (sEvent === 'fullscreen') {
- var $scrollbar = $('html, body');
- 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', 'auto');
- }
- toolbar.updateFullscreen($toolbar, isFullscreen);
- } else if (sEvent === 'codeview') {
- $editor.toggleClass('codeview');
- var bCodeview = $editor.hasClass('codeview');
- if (bCodeview) {
- $codable.val($editable.html());
- $codable.height($editable.height());
- toolbar.deactivate($toolbar);
- $codable.focus();
- // activate CodeMirror as codable
- if (agent.bCodeMirror) {
- 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 (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.bCodeMirror) {
- 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(), bCodeview);
- }
- hToolbarAndPopoverUpdate(event);
- }
- };
- var EDITABLE_PADDING = 24;
- /**
- * `mousedown` event handler on statusbar
- *
- * @param {MouseEvent} event
- */
- var hStatusbarMousedown = function (event) {
- var $document = $(document);
- var $editable = makeLayoutInfo(event.target).editable();
- var nEditableTop = $editable.offset().top - $document.scrollTop();
- $document.on('mousemove', function (event) {
- var nHeight = event.clientY - (nEditableTop + EDITABLE_PADDING);
- $editable.height(nHeight);
- }).one('mouseup', function () {
- $document.off('mousemove');
- });
- event.stopPropagation();
- event.preventDefault();
- };
- var PX_PER_EM = 18;
- var hDimensionPickerMove = function (event) {
- 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 < 10) { // 5~10
- $unhighlighted.css({ width: dim.c + 1 + 'em'});
- }
- if (3 < dim.r && dim.r < 10) { // 5~10
- $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 bCodeview = oLayoutInfo.editor.hasClass('codeview');
- if (!bCodeview && collection.length === 0) {
- 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 === 0) {
- 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) {
- var dataTransfer = event.originalEvent.dataTransfer;
- if (dataTransfer && dataTransfer.files) {
- var oLayoutInfo = makeLayoutInfo(event.currentTarget || event.target);
- oLayoutInfo.editable().focus();
- insertImages(oLayoutInfo.editable(), dataTransfer.files);
- }
- event.preventDefault();
- }).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;
- $editable.on('keydown', function (event) {
- var aKey = [];
- // modifier
- if (event.metaKey) { aKey.push('CMD'); }
- if (event.ctrlKey) { aKey.push('CTRL'); }
- if (event.shiftKey) { aKey.push('SHIFT'); }
- // keycode
- var keyName = key.nameFromCode[event.keyCode];
- if (keyName) { aKey.push(keyName); }
- var handler = keyMap[aKey.join('+')];
- if (handler) {
- event.preventDefault();
- editor[handler]($editable, $editor.data('options'));
- } 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.bMac ? '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 drag and drop
- if (!options.disableDragAndDrop) { attachDragAndDropEvent(oLayoutInfo); }
- // handler for handle(sizing image handle)
- oLayoutInfo.handle.on('mousedown', hHandleMousedown);
- // handler for toolbar, popover and statusbar
- oLayoutInfo.toolbar.on('click', hToolbarAndPopoverClick);
- oLayoutInfo.popover.on('click', hToolbarAndPopoverClick);
- oLayoutInfo.toolbar.on('mousedown', hToolbarAndPopoverMousedown);
- oLayoutInfo.popover.on('mousedown', hToolbarAndPopoverMousedown);
- oLayoutInfo.statusbar.on('mousedown', hStatusbarMousedown);
- // handler for toolbar's table dimension
- var $toolbar = oLayoutInfo.toolbar;
- var $catcher = $toolbar.find('.note-dimension-picker-mousecatcher');
- $catcher.on('mousemove', hDimensionPickerMove);
- // save selection when focusout
- oLayoutInfo.editable.on('blur', function () {
- editor.saveRange(oLayoutInfo.editable);
- });
- // save options on editor
- oLayoutInfo.editor.data('options', options);
- // ret styleWithCSS for backColor / foreColor clearing with 'inherit'.
- if (options.styleWithSpan && !agent.bMSIE) {
- // protect FF Error: NS_ERROR_FAILURE: Failure
- setTimeout(function () {
- document.execCommand('styleWithCSS', 0, true);
- });
- }
- // 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 handler = function () {
- options.onChange(oLayoutInfo.editable, oLayoutInfo.editable.html());
- };
- if (agent.bMSIE) {
- oLayoutInfo.editable.on('DOMCharacterDataModified, DOMSubtreeModified, DOMNodeInserted', handler);
- } else {
- oLayoutInfo.editable.on('input', handler);
- }
- }
- // 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) {
- oLayoutInfo.editable.off();
- oLayoutInfo.dropzone.off();
- oLayoutInfo.toolbar.off();
- oLayoutInfo.statusbar.off();
- oLayoutInfo.popover.off();
- oLayoutInfo.handle.off();
- oLayoutInfo.dialog.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>' + title + '</h4>' +
- '</div>' : ''
- ) +
- '<div class="modal-body">' +
- '<div class="row-fluid">' + body + '</div>' +
- '</div>' +
- (footer ?
- '<div class="modal-footer">' + footer + '</div>' : ''
- ) +
- '</div>' +
- '</div>' +
- '</div>';
- };
- var tplToolbarInfo = {
- picture: function (lang) {
- return tplIconButton('fa fa-picture-o icon-picture', {
- event: 'showImageDialog',
- title: lang.image.image
- });
- },
- link: function (lang) {
- return tplIconButton('fa fa-link icon-link', {
- event: 'showLinkDialog',
- title: lang.link.link
- });
- },
- video: function (lang) {
- return tplIconButton('fa fa-youtube-play icon-play', {
- event: 'showVideoDialog',
- title: lang.link.link
- });
- },
- 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('fa fa-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" data-value="' + v + '">' +
- (
- (v === 'p' || v === 'pre') ? label :
- '<' + v + '>' + label + '</' + v + '>'
- ) +
- '</a></li>';
- }, '');
- return tplIconButton('fa fa-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) {
- return memo + '<li><a data-event="fontName" data-value="' + v + '">' +
- '<i class="fa fa-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" data-value="' + v + '">' +
- '<i class="fa fa-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="fa fa-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('fa fa-bold icon-bold', {
- event: 'bold',
- title: lang.font.bold
- });
- },
- italic: function (lang) {
- return tplIconButton('fa fa-italic icon-italic', {
- event: 'italic',
- title: lang.font.italic
- });
- },
- underline: function (lang) {
- return tplIconButton('fa fa-underline icon-underline', {
- event: 'underline',
- title: lang.font.underline
- });
- },
- strike: function (lang) {
- return tplIconButton('fa fa-strikethrough icon-strikethrough', {
- event: 'strikethrough',
- title: lang.font.strike
- });
- },
- clear: function (lang) {
- return tplIconButton('fa fa-eraser icon-eraser', {
- event: 'removeFormat',
- title: lang.font.clear
- });
- },
- ul: function (lang) {
- return tplIconButton('fa fa-list-ul icon-list-ul', {
- event: 'insertUnorderedList',
- title: lang.lists.unordered
- });
- },
- ol: function (lang) {
- return tplIconButton('fa fa-list-ol icon-list-ol', {
- event: 'insertOrderedList',
- title: lang.lists.ordered
- });
- },
- paragraph: function (lang) {
- var leftButton = tplIconButton('fa fa-align-left icon-align-left', {
- title: lang.paragraph.left,
- event: 'justifyLeft'
- });
- var centerButton = tplIconButton('fa fa-align-center icon-align-center', {
- title: lang.paragraph.center,
- event: 'justifyCenter'
- });
- var rightButton = tplIconButton('fa fa-align-right icon-align-right', {
- title: lang.paragraph.right,
- event: 'justifyRight'
- });
- var justifyButton = tplIconButton('fa fa-align-justify icon-align-justify', {
- title: lang.paragraph.justify,
- event: 'justifyFull'
- });
- var outdentButton = tplIconButton('fa fa-outdent icon-indent-left', {
- title: lang.paragraph.outdent,
- event: 'outdent'
- });
- var indentButton = tplIconButton('fa fa-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('fa fa-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" data-value="' + v + '">' +
- '<i class="fa fa-check icon-ok"></i> ' + v +
- '</a></li>';
- }, '');
- return tplIconButton('fa fa-text-height icon-text-height', {
- title: lang.font.height,
- dropdown: '<ul class="dropdown-menu">' + items + '</ul>'
- });
- },
- help: function (lang) {
- return tplIconButton('fa fa-question icon-question', {
- event: 'showHelpDialog',
- title: lang.options.help
- });
- },
- fullscreen: function (lang) {
- return tplIconButton('fa fa-arrows-alt icon-fullscreen', {
- event: 'fullscreen',
- title: lang.options.fullscreen
- });
- },
- codeview: function (lang) {
- return tplIconButton('fa fa-code icon-code', {
- event: 'codeview',
- title: lang.options.codeview
- });
- },
- undo: function (lang) {
- return tplIconButton('fa fa-undo icon-undo', {
- event: 'undo',
- title: lang.history.undo
- });
- },
- redo: function (lang) {
- return tplIconButton('fa fa-repeat icon-repeat', {
- event: 'redo',
- title: lang.history.redo
- });
- }
- };
- var tplPopovers = function (lang) {
- var tplLinkPopover = function () {
- var linkButton = tplIconButton('fa fa-edit icon-edit', {
- title: lang.link.edit,
- event: 'showLinkDialog'
- });
- var unlinkButton = tplIconButton('fa fa-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('fa fa-align-left icon-align-left', {
- title: lang.image.floatLeft,
- event: 'floatMe',
- value: 'left'
- });
- var rightButton = tplIconButton('fa fa-align-left icon-align-right', {
- title: lang.image.floatRight,
- event: 'floatMe',
- value: 'right'
- });
- var justifyButton = tplIconButton('fa fa-align-justify icon-align-justify', {
- title: lang.image.floatNone,
- event: 'floatMe',
- value: 'none'
- });
- var removeButton = tplIconButton('fa fa-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);
- };
- return '<div class="note-popover">' + tplLinkPopover() + tplImagePopover() + '</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.strike + '</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" disabled />' +
- '</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.bMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) +
- '<p class="text-center">' +
- '<a href="//hackerwins.github.io/summernote/" target="_blank">Summernote 0.5.2</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 invertObject = function (obj) {
- var inverted = {};
- for (var key in obj) {
- if (obj.hasOwnProperty(key)) {
- inverted[obj[key]] = key;
- }
- }
- return inverted;
- };
- var representShortcut = function (str) {
- if (agent.bMac) {
- 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 = 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/twitter/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>' + aButton.join('') + '</div>');
- }
- $palette.html(aPaletteContents.join(''));
- });
- };
- /**
- * create summernote layout
- *
- * @param {jQuery} $holder
- * @param {Object} options
- */
- this.createLayout = function ($holder, options) {
- //already created
- var next = $holder.next();
- if (next && next.hasClass('note-editor')) { return; }
- //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">' + 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 group = options.toolbar[idx];
- sToolbar += '<div class="note-' + group[0] + ' btn-group">';
- for (var i = 0, szGroup = group[1].length; i < szGroup; i++) {
- sToolbar += tplToolbarInfo[group[1][i]](langInfo, options);
- }
- sToolbar += '</div>';
- }
- sToolbar = '<div class="note-toolbar btn-toolbar">' + sToolbar + '</div>';
- var $toolbar = $(sToolbar).prependTo($editor);
- var keyMap = options.keyMap[agent.bMac ? 'mac' : 'pc'];
- createPalette($toolbar, options);
- createTooltip($toolbar, keyMap, 'bottom');
- //05. create Popover
- var $popover = $(tplPopovers(langInfo)).prependTo($editor);
- 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();
- };
- /**
- * returns layoutInfo from holder
- *
- * @param {jQuery} $holder - placeholder
- * @returns {Object}
- */
- this.layoutInfoFromHolder = function ($holder) {
- var $editor = $holder.next();
- if (!$editor.hasClass('note-editor')) { 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
- */
- this.removeLayout = function ($holder) {
- var info = this.layoutInfoFromHolder($holder);
- if (!info) { return; }
- $holder.html(info.editable.html());
- info.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.html($holder.code());
- });
- }
- });
- // focus on first editable element
- if (this.first() && options.focus) {
- var info = renderer.layoutInfoFromHolder(this.first());
- info.editable.focus();
- }
- // callback on init
- if (this.length > 0 && 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 === 0) { return; }
- var info = renderer.layoutInfoFromHolder($holder);
- if (!!(info && info.editable)) {
- var bCodeview = info.editor.hasClass('codeview');
- if (bCodeview && agent.bCodeMirror) {
- info.codable.data('cmEditor').save();
- }
- return bCodeview ? info.codable.val() : info.editable.html();
- }
- return $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; }
- eventHandler.dettach(info);
- renderer.removeLayout($holder);
- });
- return this;
- }
- });
- }));
|