medium.patched.js 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861
  1. /*
  2. * Medium.js
  3. *
  4. * Copyright 2013, Jacob Kelley - http://jakiestfu.com/
  5. * Released under the MIT Licence
  6. * http://opensource.org/licenses/MIT
  7. *
  8. * Github: http://github.com/jakiestfu/Medium.js/
  9. * Version: 1.0
  10. */
  11. (function (w, d) {
  12. 'use strict';
  13. var Medium = (function () {
  14. var trim = function (string) {
  15. return string.replace(/^\s+|\s+$/g, '');
  16. },
  17. arrayContains = function (array, variable) {
  18. var i = array.length;
  19. while (i--) {
  20. if (array[i] === variable) {
  21. return true;
  22. }
  23. }
  24. return false;
  25. },
  26. //two modes, wild (native) or domesticated (rangy + undo.js)
  27. rangy = w['rangy'] || null,
  28. undo = w['Undo'] || null,
  29. wild = (!rangy || !undo),
  30. domesticated = (!wild),
  31. key = w.Key = {
  32. 'backspace': 8,
  33. 'tab': 9,
  34. 'enter': 13,
  35. 'shift': 16,
  36. 'ctrl': 17,
  37. 'alt': 18,
  38. 'pause': 19,
  39. 'capsLock': 20,
  40. 'escape': 27,
  41. 'pageUp': 33,
  42. 'pageDown': 34,
  43. 'end': 35,
  44. 'home': 36,
  45. 'leftArrow': 37,
  46. 'upArrow': 38,
  47. 'rightArrow': 39,
  48. 'downArrow': 40,
  49. 'insert': 45,
  50. 'delete': 46,
  51. '0': 48,
  52. '1': 49,
  53. '2': 50,
  54. '3': 51,
  55. '4': 52,
  56. '5': 53,
  57. '6': 54,
  58. '7': 55,
  59. '8': 56,
  60. '9': 57,
  61. 'a': 65,
  62. 'b': 66,
  63. 'c': 67,
  64. 'd': 68,
  65. 'e': 69,
  66. 'f': 70,
  67. 'g': 71,
  68. 'h': 72,
  69. 'i': 73,
  70. 'j': 74,
  71. 'k': 75,
  72. 'l': 76,
  73. 'm': 77,
  74. 'n': 78,
  75. 'o': 79,
  76. 'p': 80,
  77. 'q': 81,
  78. 'r': 82,
  79. 's': 83,
  80. 't': 84,
  81. 'u': 85,
  82. 'v': 86,
  83. 'w': 87,
  84. 'x': 88,
  85. 'y': 89,
  86. 'z': 90,
  87. 'leftWindow': 91,
  88. 'rightWindowKey': 92,
  89. 'select': 93,
  90. 'numpad0': 96,
  91. 'numpad1': 97,
  92. 'numpad2': 98,
  93. 'numpad3': 99,
  94. 'numpad4': 100,
  95. 'numpad5': 101,
  96. 'numpad6': 102,
  97. 'numpad7': 103,
  98. 'numpad8': 104,
  99. 'numpad9': 105,
  100. 'multiply': 106,
  101. 'add': 107,
  102. 'subtract': 109,
  103. 'decimalPoint': 110,
  104. 'divide': 111,
  105. 'f1': 112,
  106. 'f2': 113,
  107. 'f3': 114,
  108. 'f4': 115,
  109. 'f5': 116,
  110. 'f6': 117,
  111. 'f7': 118,
  112. 'f8': 119,
  113. 'f9': 120,
  114. 'f10': 121,
  115. 'f11': 122,
  116. 'f12': 123,
  117. 'numLock': 144,
  118. 'scrollLock': 145,
  119. 'semiColon': 186,
  120. 'equalSign': 187,
  121. 'comma': 188,
  122. 'dash': 189,
  123. 'period': 190,
  124. 'forwardSlash': 191,
  125. 'graveAccent': 192,
  126. 'openBracket': 219,
  127. 'backSlash': 220,
  128. 'closeBraket': 221,
  129. 'singleQuote': 222
  130. },
  131. /**
  132. * Medium.js - Taking control of content editable
  133. * @constructor
  134. * @param {Object} [userSettings] user options
  135. */
  136. Medium = function (userSettings) {
  137. var medium = this,
  138. action = new Medium.Action(),
  139. cache = new Medium.Cache(),
  140. cursor = new Medium.Cursor(),
  141. html = new Medium.HtmlAssistant(),
  142. utils = new Medium.Utilities(),
  143. selection = new Medium.Selection(),
  144. intercept = {
  145. focus: function (e) {
  146. e = e || w.event;
  147. Medium.activeElement = el;
  148. },
  149. blur: function (e) {
  150. e = e || w.event;
  151. if (Medium.activeElement === el) {
  152. Medium.activeElement = null;
  153. }
  154. html.placeholders();
  155. },
  156. down: function (e) {
  157. e = e || w.event;
  158. var keepEvent = true;
  159. //in Chrome it sends out this event before every regular event, not sure why
  160. if (e.keyCode === 229) return;
  161. utils.isCommand(e, function () {
  162. cache.cmd = true;
  163. }, function () {
  164. cache.cmd = false;
  165. });
  166. utils.isShift(e, function () {
  167. cache.shift = true;
  168. }, function () {
  169. cache.shift = false;
  170. });
  171. utils.isModifier(e, function (cmd) {
  172. if (cache.cmd) {
  173. if (( (settings.mode === Medium.inlineMode) || (settings.mode === Medium.partialMode) ) && cmd !== "paste") {
  174. utils.preventDefaultEvent(e);
  175. return;
  176. }
  177. var cmdType = typeof cmd,
  178. fn = null;
  179. if (cmdType === "function") {
  180. fn = cmd;
  181. } else {
  182. fn = intercept.command[cmd];
  183. }
  184. keepEvent = fn.call(medium, e);
  185. if (keepEvent === false) {
  186. utils.preventDefaultEvent(e);
  187. utils.stopPropagation(e);
  188. }
  189. }
  190. });
  191. if (settings.maxLength !== -1) {
  192. var len = html.text().length,
  193. hasSelection = false,
  194. selection = w.getSelection();
  195. if (selection) {
  196. hasSelection = !selection.isCollapsed;
  197. }
  198. if (len >= settings.maxLength && !utils.isSpecial(e) && !utils.isNavigational(e) && !hasSelection) {
  199. return utils.preventDefaultEvent(e);
  200. }
  201. }
  202. switch (e.keyCode) {
  203. case key['enter']:
  204. intercept.enterKey(e);
  205. break;
  206. case key['backspace']:
  207. case key['delete']:
  208. intercept.backspaceOrDeleteKey(e);
  209. break;
  210. }
  211. return keepEvent;
  212. },
  213. up: function (e) {
  214. e = e || w.event;
  215. utils.isCommand(e, function () {
  216. cache.cmd = false;
  217. }, function () {
  218. cache.cmd = true;
  219. });
  220. html.clean();
  221. html.placeholders();
  222. //here we have a key context, so if you need to create your own object within a specific context it is doable
  223. var keyContext;
  224. if (
  225. settings.keyContext !== null
  226. && ( keyContext = settings.keyContext[e.keyCode] )
  227. ) {
  228. var el = cursor.parent();
  229. if (el) {
  230. keyContext.call(medium, e, el);
  231. }
  232. }
  233. action.preserveElementFocus();
  234. },
  235. command: {
  236. bold: function (e) {
  237. utils.preventDefaultEvent(e);
  238. // IE uses strong instead of b
  239. (new Medium.Element(medium, 'bold'))
  240. .setClean(false)
  241. .invoke(settings.beforeInvokeElement);
  242. },
  243. underline: function (e) {
  244. utils.preventDefaultEvent(e);
  245. (new Medium.Element(medium, 'underline'))
  246. .setClean(false)
  247. .invoke(settings.beforeInvokeElement);
  248. },
  249. italicize: function (e) {
  250. utils.preventDefaultEvent(e);
  251. (new Medium.Element(medium, 'italic'))
  252. .setClean(false)
  253. .invoke(settings.beforeInvokeElement);
  254. },
  255. quote: function (e) {
  256. },
  257. paste: function (e) {
  258. medium.makeUndoable();
  259. if (settings.pasteAsText) {
  260. var sel = utils.selection.saveSelection();
  261. utils.pasteHook(function (text) {
  262. utils.selection.restoreSelection(sel);
  263. text = text.replace(/\n/g, '<br>');
  264. (new Medium.Html(medium, text))
  265. .setClean(false)
  266. .insert(settings.beforeInsertHtml, true);
  267. html.clean();
  268. html.placeholders();
  269. });
  270. } else {
  271. html.clean();
  272. html.placeholders();
  273. }
  274. }
  275. },
  276. enterKey: function (e) {
  277. if (settings.mode === Medium.inlineMode) {
  278. return utils.preventDefaultEvent(e);
  279. }
  280. if (!cache.shift) {
  281. var focusedElement = html.atCaret() || {},
  282. children = el.children,
  283. lastChild = focusedElement === el.lastChild ? el.lastChild : null,
  284. makeHR,
  285. secondToLast,
  286. paragraph;
  287. if (
  288. lastChild
  289. && lastChild !== el.firstChild
  290. && settings.autoHR
  291. && settings.mode !== 'partial'
  292. && settings.tags.horizontalRule
  293. ) {
  294. utils.preventDefaultEvent(e);
  295. makeHR =
  296. html.text(lastChild) === ""
  297. && lastChild.nodeName.toLowerCase() === settings.tags.paragraph;
  298. if (makeHR && children.length >= 2) {
  299. secondToLast = children[children.length - 2];
  300. if (secondToLast.nodeName.toLowerCase() === settings.tags.horizontalRule) {
  301. makeHR = false;
  302. }
  303. }
  304. if (makeHR) {
  305. html.addTag(settings.tags.horizontalRule, false, true, focusedElement);
  306. focusedElement = focusedElement.nextSibling;
  307. }
  308. if ((paragraph = html.addTag(settings.tags.paragraph, true, null, focusedElement)) !== null) {
  309. paragraph.innerHTML = '';
  310. cursor.set(0, paragraph);
  311. }
  312. }
  313. }
  314. return true;
  315. },
  316. backspaceOrDeleteKey: function (e) {
  317. if (el.lastChild === null) return;
  318. var lastChild = el.lastChild,
  319. beforeLastChild = lastChild.previousSibling;
  320. if (
  321. lastChild
  322. && settings.tags.horizontalRule
  323. && lastChild.nodeName.toLocaleLowerCase() === settings.tags.horizontalRule
  324. ) {
  325. el.removeChild(lastChild);
  326. } else if (
  327. lastChild
  328. && beforeLastChild
  329. && utils.html.text(lastChild).length < 1
  330. && beforeLastChild.nodeName.toLowerCase() === settings.tags.horizontalRule
  331. && lastChild.nodeName.toLowerCase() === settings.tags.paragraph
  332. ) {
  333. el.removeChild(lastChild);
  334. el.removeChild(beforeLastChild);
  335. }
  336. }
  337. },
  338. defaultSettings = {
  339. element: null,
  340. modifier: 'auto',
  341. placeholder: "",
  342. autofocus: false,
  343. autoHR: true,
  344. mode: Medium.richMode,
  345. maxLength: -1,
  346. modifiers: {
  347. 'b': 'bold',
  348. 'i': 'italicize',
  349. 'u': 'underline',
  350. 'v': 'paste'
  351. },
  352. tags: {
  353. 'break': 'br',
  354. 'horizontalRule': 'hr',
  355. 'paragraph': 'p',
  356. 'outerLevel': ['pre', 'blockquote', 'figure'],
  357. 'innerLevel': ['a', 'b', 'u', 'i', 'img', 'strong']
  358. },
  359. cssClasses: {
  360. editor: 'Medium',
  361. pasteHook: 'Medium-paste-hook',
  362. placeholder: 'Medium-placeholder',
  363. clear: 'Medium-clear'
  364. },
  365. attributes: {
  366. remove: ['style', 'class']
  367. },
  368. pasteAsText: true,
  369. beforeInvokeElement: function () {
  370. //this = Medium.Element
  371. },
  372. beforeInsertHtml: function () {
  373. //this = Medium.Html
  374. },
  375. beforeAddTag: function (tag, shouldFocus, isEditable, afterElement) {
  376. },
  377. keyContext: null,
  378. pasteEventHandler: function (e) {
  379. e = e || w.event;
  380. medium.makeUndoable();
  381. var length = medium.value().length,
  382. totalLength;
  383. if (settings.pasteAsText) {
  384. utils.preventDefaultEvent(e);
  385. var
  386. sel = utils.selection.saveSelection(),
  387. text = prompt(Medium.Messages.pastHere) || '';
  388. if (text.length > 0) {
  389. el.focus();
  390. Medium.activeElement = el;
  391. utils.selection.restoreSelection(sel);
  392. //encode the text first
  393. text = html.encodeHtml(text);
  394. //cut down it's length
  395. totalLength = text.length + length;
  396. if (settings.maxLength > 0 && totalLength > settings.maxLength) {
  397. text = text.substring(0, settings.maxLength - length);
  398. }
  399. if (settings.mode !== Medium.inlineMode) {
  400. text = text.replace(/\n/g, '<br>');
  401. }
  402. (new Medium.Html(medium, text))
  403. .setClean(false)
  404. .insert(settings.beforeInsertHtml, true);
  405. html.clean();
  406. html.placeholders();
  407. return false;
  408. }
  409. } else {
  410. setTimeout(function () {
  411. html.clean();
  412. html.placeholders();
  413. }, 20);
  414. }
  415. }
  416. },
  417. settings = utils.deepExtend(defaultSettings, userSettings),
  418. el,
  419. newVal,
  420. i,
  421. bridge = {};
  422. for (i in defaultSettings) {
  423. // Override defaults with data-attributes
  424. if (
  425. typeof defaultSettings[i] !== 'object'
  426. && defaultSettings.hasOwnProperty(i)
  427. && settings.element.getAttribute('data-medium-' + key)
  428. ) {
  429. newVal = settings.element.getAttribute('data-medium-' + key);
  430. if (newVal.toLowerCase() === "false" || newVal.toLowerCase() === "true") {
  431. newVal = newVal.toLowerCase() === "true";
  432. }
  433. settings[i] = newVal;
  434. }
  435. }
  436. if (settings.modifiers) {
  437. for (i in settings.modifiers) {
  438. if (typeof(key[i]) !== 'undefined') {
  439. settings.modifiers[key[i]] = settings.modifiers[i];
  440. }
  441. }
  442. }
  443. if (settings.keyContext) {
  444. for (i in settings.keyContext) {
  445. if (typeof(key[i]) !== 'undefined') {
  446. settings.keyContext[key[i]] = settings.keyContext[i];
  447. }
  448. }
  449. }
  450. // Extend Settings
  451. el = settings.element;
  452. // Editable
  453. el.contentEditable = true;
  454. el.className
  455. += (' ' + settings.cssClasses.editor)
  456. + (' ' + settings.cssClasses.editor + '-' + settings.mode);
  457. settings.tags = (settings.tags || {});
  458. if (settings.tags.outerLevel) {
  459. settings.tags.outerLevel = settings.tags.outerLevel.concat([settings.tags.paragraph, settings.tags.horizontalRule]);
  460. }
  461. this.settings = settings;
  462. this.element = el;
  463. this.intercept = intercept;
  464. this.action = action;
  465. this.cache = cache;
  466. this.cursor = cursor;
  467. this.html = html;
  468. this.utils = utils;
  469. this.selection = selection;
  470. bridge.element = el;
  471. bridge.medium = this;
  472. bridge.settings = settings;
  473. bridge.action = action;
  474. bridge.cache = cache;
  475. bridge.cursor = cursor;
  476. bridge.html = html;
  477. bridge.intercept = intercept;
  478. bridge.utils = utils;
  479. bridge.selection = selection;
  480. action.setBridge(bridge);
  481. cache.setBridge(bridge);
  482. cursor.setBridge(bridge);
  483. html.setBridge(bridge);
  484. utils.setBridge(bridge);
  485. selection.setBridge(bridge);
  486. // Initialize editor
  487. html.clean();
  488. html.placeholders();
  489. action.preserveElementFocus();
  490. // Capture Events
  491. action.listen();
  492. if (wild) {
  493. this.makeUndoable = function () {
  494. };
  495. } else {
  496. this.dirty = false;
  497. this.undoable = new Medium.Undoable(this);
  498. this.undo = this.undoable.undo;
  499. this.redo = this.undoable.redo;
  500. this.makeUndoable = this.undoable.makeUndoable;
  501. }
  502. el.medium = this;
  503. // Set as initialized
  504. cache.initialized = true;
  505. };
  506. Medium.prototype = {
  507. /**
  508. *
  509. * @param {String|Object} html
  510. * @param {Function} [callback]
  511. * @returns {Medium}
  512. */
  513. insertHtml: function (html, callback) {
  514. var result = (new Medium.Html(this, html))
  515. .insert(this.settings.beforeInsertHtml);
  516. this.utils.triggerEvent(this.element, "change");
  517. if (callback) {
  518. callback.apply(result);
  519. }
  520. return this;
  521. },
  522. /**
  523. *
  524. * @param {String} tagName
  525. * @param {Object} [attributes]
  526. * @returns {Medium}
  527. */
  528. invokeElement: function (tagName, attributes) {
  529. var settings = this.settings,
  530. attributes = attributes || {},
  531. remove = attributes.remove || [];
  532. switch (settings.mode) {
  533. case Medium.inlineMode:
  534. case Medium.partialMode:
  535. return this;
  536. default:
  537. }
  538. //invoke works off class, so if it isn't there, we just add it
  539. if (remove.length > 0) {
  540. if (!arrayContains(settings, 'class')) {
  541. remove.push('class');
  542. }
  543. }
  544. (new Medium.Element(this, tagName, attributes))
  545. .invoke(this.settings.beforeInvokeElement);
  546. this.utils.triggerEvent(this.element, "change");
  547. return this;
  548. },
  549. /**
  550. * @returns {string}
  551. */
  552. behavior: function () {
  553. return (wild ? 'wild' : 'domesticated');
  554. },
  555. /**
  556. *
  557. * @param value
  558. * @returns {Medium}
  559. */
  560. value: function (value) {
  561. if (typeof value !== 'undefined') {
  562. this.element.innerHTML = value;
  563. this.html.clean();
  564. this.html.placeholders();
  565. } else {
  566. return this.element.innerHTML;
  567. }
  568. return this;
  569. },
  570. /**
  571. * Focus on element
  572. * @returns {Medium}
  573. */
  574. focus: function () {
  575. var el = this.element;
  576. el.focus();
  577. return this;
  578. },
  579. /**
  580. * Select all text
  581. * @returns {Medium}
  582. */
  583. select: function () {
  584. var el = this.element,
  585. range,
  586. selection;
  587. el.focus();
  588. if (d.body.createTextRange) {
  589. range = d.body.createTextRange();
  590. range.moveToElementText(el);
  591. range.select();
  592. } else if (w.getSelection) {
  593. selection = w.getSelection();
  594. range = d.createRange();
  595. range.selectNodeContents(el);
  596. selection.removeAllRanges();
  597. selection.addRange(range);
  598. }
  599. return this;
  600. },
  601. isActive: function () {
  602. return (Medium.activeElement === this.element);
  603. },
  604. destroy: function () {
  605. var el = this.element,
  606. intercept = this.intercept,
  607. settings = this.settings,
  608. placeholder = this.placeholder || null;
  609. if (placeholder !== null && placeholder.setup) {
  610. //remove placeholder
  611. placeholder.parentNode.removeChild(placeholder);
  612. delete el.placeHolderActive;
  613. }
  614. //remove contenteditable
  615. el.removeAttribute('contenteditable');
  616. //remove classes
  617. el.className = trim(el.className
  618. .replace(settings.cssClasses.editor, '')
  619. .replace(settings.cssClasses.clear, '')
  620. .replace(settings.cssClasses.editor + '-' + settings.mode, ''));
  621. //remove events
  622. this.utils
  623. .removeEvent(el, 'keyup', intercept.up)
  624. .removeEvent(el, 'keydown', intercept.down)
  625. .removeEvent(el, 'focus', intercept.focus)
  626. .removeEvent(el, 'blur', intercept.focus)
  627. .removeEvent(el, 'paste', settings.pasteEventHandler);
  628. },
  629. // Clears the element and restores the placeholder
  630. clear: function () {
  631. this.element.innerHTML = '';
  632. this.html.placeholders();
  633. }
  634. };
  635. /**
  636. * @param {Medium} medium
  637. * @param {String} tagName
  638. * @param {Object} [attributes]
  639. * @constructor
  640. */
  641. Medium.Element = function (medium, tagName, attributes) {
  642. this.medium = medium;
  643. this.element = medium.settings.element;
  644. if (wild) {
  645. this.tagName = tagName;
  646. } else {
  647. switch (tagName.toLowerCase()) {
  648. case 'bold':
  649. this.tagName = 'b';
  650. break;
  651. case 'italic':
  652. this.tagName = 'i';
  653. break;
  654. case 'underline':
  655. this.tagName = 'u';
  656. break;
  657. default:
  658. this.tagName = tagName;
  659. }
  660. }
  661. this.attributes = attributes || {};
  662. this.clean = true;
  663. };
  664. /**
  665. * @constructor
  666. * @param {Medium} medium
  667. * @param {String|HtmlElement} html
  668. */
  669. Medium.Html = function (medium, html) {
  670. this.medium = medium;
  671. this.element = medium.settings.element;
  672. this.html = html;
  673. this.clean = true;
  674. };
  675. /**
  676. *
  677. * @constructor
  678. */
  679. Medium.Injector = function () {
  680. };
  681. if (wild) {
  682. Medium.Element.prototype = {
  683. /**
  684. * @methodOf Medium.Element
  685. * @param {Function} [fn]
  686. */
  687. invoke: function (fn) {
  688. if (Medium.activeElement === this.element) {
  689. if (fn) {
  690. fn.apply(this);
  691. }
  692. d.execCommand(this.tagName, false);
  693. }
  694. },
  695. setClean: function () {
  696. return this;
  697. }
  698. };
  699. Medium.Injector.prototype = {
  700. /**
  701. * @methodOf Medium.Injector
  702. * @param {String|HtmlElement} htmlRaw
  703. * @param {Boolean} [selectInserted]
  704. * @returns {null}
  705. */
  706. inject: function (htmlRaw, selectInserted) {
  707. this.insertHTML(htmlRaw, selectInserted);
  708. return null;
  709. }
  710. };
  711. /**
  712. *
  713. * @constructor
  714. */
  715. Medium.Undoable = function () {
  716. };
  717. }
  718. //if medium is domesticated (ie, not wild)
  719. else {
  720. rangy.rangePrototype.insertNodeAtEnd = function (node) {
  721. var range = this.cloneRange();
  722. range.collapse(false);
  723. range.insertNode(node);
  724. range.detach();
  725. this.setEndAfter(node);
  726. };
  727. Medium.Element.prototype = {
  728. /**
  729. * @methodOf Medium.Element
  730. * @param {Function} [fn]
  731. */
  732. invoke: function (fn) {
  733. if (Medium.activeElement === this.element) {
  734. if (fn) {
  735. fn.apply(this);
  736. }
  737. var
  738. attr = this.attributes,
  739. tagName = this.tagName.toLowerCase(),
  740. applier,
  741. cl;
  742. if (attr.className !== undefined) {
  743. cl = (attr.className.split[' '] || [attr.className]).shift();
  744. delete attr.className;
  745. } else {
  746. cl = 'medium-' + tagName;
  747. }
  748. applier = rangy.createClassApplier(cl, {
  749. elementTagName: tagName,
  750. elementAttributes: this.attributes
  751. });
  752. this.medium.makeUndoable();
  753. applier.toggleSelection(w);
  754. if (this.clean) {
  755. //cleanup
  756. this.medium.html.clean();
  757. this.medium.html.placeholders();
  758. }
  759. }
  760. },
  761. /**
  762. *
  763. * @param {Boolean} clean
  764. * @returns {Medium.Element}
  765. */
  766. setClean: function (clean) {
  767. this.clean = clean;
  768. return this;
  769. }
  770. };
  771. Medium.Injector.prototype = {
  772. /**
  773. * @methodOf Medium.Injector
  774. * @param {String|HtmlElement} htmlRaw
  775. * @returns {HtmlElement}
  776. */
  777. inject: function (htmlRaw) {
  778. var html, isConverted = false;
  779. if (typeof htmlRaw === 'string') {
  780. var htmlConverter = d.createElement('div');
  781. htmlConverter.innerHTML = htmlRaw;
  782. html = htmlConverter.childNodes;
  783. isConverted = true;
  784. } else {
  785. html = htmlRaw;
  786. }
  787. this.insertHTML('<span id="wedge"></span>');
  788. var wedge = d.getElementById('wedge'),
  789. parent = wedge.parentNode,
  790. i = 0;
  791. wedge.removeAttribute('id');
  792. if (isConverted) {
  793. while (i < html.length) {
  794. parent.insertBefore(html[i], wedge);
  795. }
  796. } else {
  797. parent.insertBefore(html, wedge);
  798. }
  799. parent.removeChild(wedge);
  800. wedge = null;
  801. return html;
  802. }
  803. };
  804. /**
  805. * @param {Medium} medium
  806. * @constructor
  807. */
  808. Medium.Undoable = function (medium) {
  809. var me = this,
  810. element = medium.settings.element,
  811. utils = medium.utils,
  812. addEvent = utils.addEvent,
  813. startValue = element.innerHTML,
  814. timer,
  815. stack = new Undo.Stack(),
  816. EditCommand = Undo.Command.extend({
  817. constructor: function (oldValue, newValue) {
  818. this.oldValue = oldValue;
  819. this.newValue = newValue;
  820. },
  821. execute: function () {
  822. },
  823. undo: function () {
  824. element.innerHTML = this.oldValue;
  825. medium.canUndo = stack.canUndo();
  826. medium.canRedo = stack.canRedo();
  827. medium.dirty = stack.dirty();
  828. },
  829. redo: function () {
  830. element.innerHTML = this.newValue;
  831. medium.canUndo = stack.canUndo();
  832. medium.canRedo = stack.canRedo();
  833. medium.dirty = stack.dirty();
  834. }
  835. }),
  836. makeUndoable = function () {
  837. var newValue = element.innerHTML;
  838. // ignore meta key presses
  839. if (newValue != startValue) {
  840. if (!me.movingThroughStack) {
  841. // this could try and make a diff instead of storing snapshots
  842. stack.execute(new EditCommand(startValue, newValue));
  843. startValue = newValue;
  844. medium.dirty = stack.dirty();
  845. }
  846. utils.triggerEvent(medium.settings.element, "change");
  847. }
  848. };
  849. this.medium = medium;
  850. this.timer = timer;
  851. this.stack = stack;
  852. this.makeUndoable = makeUndoable;
  853. this.EditCommand = EditCommand;
  854. this.movingThroughStack = false;
  855. addEvent(element, 'keyup', function (e) {
  856. if (e.ctrlKey || e.keyCode === key.z) {
  857. utils.preventDefaultEvent(e);
  858. return;
  859. }
  860. // a way too simple algorithm in place of single-character undo
  861. clearTimeout(timer);
  862. timer = setTimeout(function () {
  863. makeUndoable();
  864. }, 250);
  865. });
  866. addEvent(element, 'keydown', function (e) {
  867. if (!e.ctrlKey || e.keyCode !== key.z) {
  868. me.movingThroughStack = false;
  869. return true;
  870. }
  871. utils.preventDefaultEvent(e);
  872. me.movingThroughStack = true;
  873. if (e.shiftKey) {
  874. stack.canRedo() && stack.redo()
  875. } else {
  876. stack.canUndo() && stack.undo();
  877. }
  878. });
  879. };
  880. }
  881. //Thank you Tim Down (super uber genius): http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div/6691294#6691294
  882. Medium.Injector.prototype.insertHTML = function (html, selectPastedContent) {
  883. var sel, range;
  884. if (w.getSelection) {
  885. // IE9 and non-IE
  886. sel = w.getSelection();
  887. if (sel.getRangeAt && sel.rangeCount) {
  888. range = sel.getRangeAt(0);
  889. range.deleteContents();
  890. // Range.createContextualFragment() would be useful here but is
  891. // only relatively recently standardized and is not supported in
  892. // some browsers (IE9, for one)
  893. var el = d.createElement("div");
  894. el.innerHTML = html;
  895. var frag = d.createDocumentFragment(), node, lastNode;
  896. while ((node = el.firstChild)) {
  897. lastNode = frag.appendChild(node);
  898. }
  899. var firstNode = frag.firstChild;
  900. range.insertNode(frag);
  901. // Preserve the selection
  902. if (lastNode) {
  903. range = range.cloneRange();
  904. range.setStartAfter(lastNode);
  905. if (selectPastedContent) {
  906. range.setStartBefore(firstNode);
  907. } else {
  908. range.collapse(true);
  909. }
  910. sel.removeAllRanges();
  911. sel.addRange(range);
  912. }
  913. }
  914. } else if ((sel = d.selection) && sel.type != "Control") {
  915. // IE < 9
  916. var originalRange = sel.createRange();
  917. originalRange.collapse(true);
  918. sel.createRange().pasteHTML(html);
  919. if (selectPastedContent) {
  920. range = sel.createRange();
  921. range.setEndPoint("StartToStart", originalRange);
  922. range.select();
  923. }
  924. }
  925. };
  926. Medium.Html.prototype = {
  927. setBridge: function (bridge) {
  928. for (var i in bridge) {
  929. this[i] = bridge[i];
  930. }
  931. },
  932. /**
  933. * @methodOf Medium.Html
  934. * @param {Function} [fn]
  935. * @param {Boolean} [selectInserted]
  936. * @returns {HtmlElement}
  937. */
  938. insert: function (fn, selectInserted) {
  939. if (Medium.activeElement === this.element) {
  940. if (fn) {
  941. fn.apply(this);
  942. }
  943. var inserted = this.injector.inject(this.html, selectInserted);
  944. if (this.clean) {
  945. //cleanup
  946. this.medium.html.clean();
  947. this.medium.html.placeholders();
  948. }
  949. this.medium.makeUndoable();
  950. return inserted;
  951. } else {
  952. return null;
  953. }
  954. },
  955. /**
  956. * @attributeOf {Medium.Injector} Medium.Html
  957. */
  958. injector: new Medium.Injector(),
  959. /**
  960. * @methodOf Medium.Html
  961. * @param clean
  962. * @returns {Medium.Html}
  963. */
  964. setClean: function (clean) {
  965. this.clean = clean;
  966. return this;
  967. }
  968. };
  969. Medium.Utilities = function () {
  970. };
  971. Medium.Utilities.prototype = {
  972. setBridge: function (bridge) {
  973. for (var i in bridge) {
  974. this[i] = bridge[i];
  975. }
  976. },
  977. /*
  978. * Keyboard Interface events
  979. */
  980. isCommand: function (e, fnTrue, fnFalse) {
  981. var s = this.settings;
  982. if ((s.modifier === 'ctrl' && e.ctrlKey ) ||
  983. (s.modifier === 'cmd' && e.metaKey ) ||
  984. (s.modifier === 'auto' && (e.ctrlKey || e.metaKey) )
  985. ) {
  986. return fnTrue.call();
  987. } else {
  988. return fnFalse.call();
  989. }
  990. },
  991. isShift: function (e, fnTrue, fnFalse) {
  992. if (e.shiftKey) {
  993. return fnTrue.call();
  994. } else {
  995. return fnFalse.call();
  996. }
  997. },
  998. isModifier: function (e, fn) {
  999. var cmd = this.settings.modifiers[e.keyCode];
  1000. if (cmd) {
  1001. return fn.call(null, cmd);
  1002. }
  1003. return false;
  1004. },
  1005. special: (function () {
  1006. var special = {};
  1007. special[key['backspace']] = true;
  1008. special[key['shift']] = true;
  1009. special[key['ctrl']] = true;
  1010. special[key['alt']] = true;
  1011. special[key['delete']] = true;
  1012. special[key['cmd']] = true;
  1013. return special;
  1014. })(),
  1015. isSpecial: function (e) {
  1016. if (this.cache.cmd) {
  1017. return true;
  1018. }
  1019. return typeof this.special[e.keyCode] !== 'undefined';
  1020. },
  1021. navigational: (function () {
  1022. var navigational = {};
  1023. navigational[key['upArrow']] = true;
  1024. navigational[key['downArrow']] = true;
  1025. navigational[key['leftArrow']] = true;
  1026. navigational[key['rightArrow']] = true;
  1027. return navigational;
  1028. })(),
  1029. isNavigational: function (e) {
  1030. return typeof this.navigational[e.keyCode] !== 'undefined';
  1031. },
  1032. /*
  1033. * Handle Events
  1034. */
  1035. addEvent: function addEvent(element, eventName, func) {
  1036. if (element.addEventListener) {
  1037. element.addEventListener(eventName, func, false);
  1038. } else if (element.attachEvent) {
  1039. element.attachEvent("on" + eventName, func);
  1040. } else {
  1041. element['on' + eventName] = func;
  1042. }
  1043. return this;
  1044. },
  1045. removeEvent: function removeEvent(element, eventName, func) {
  1046. if (element.removeEventListener) {
  1047. element.removeEventListener(eventName, func, false);
  1048. } else if (element.detachEvent) {
  1049. element.detachEvent("on" + eventName, func);
  1050. } else {
  1051. element['on' + eventName] = null;
  1052. }
  1053. return this;
  1054. },
  1055. preventDefaultEvent: function (e) {
  1056. if (e.preventDefault) {
  1057. e.preventDefault();
  1058. } else {
  1059. e.returnValue = false;
  1060. }
  1061. return this;
  1062. },
  1063. stopPropagation: function (e) {
  1064. e = e || window.event;
  1065. e.cancelBubble = true;
  1066. if (e.stopPropagation !== undefined) {
  1067. e.stopPropagation();
  1068. }
  1069. },
  1070. triggerEvent: function (element, eventName) {
  1071. var e;
  1072. if (d.createEvent) {
  1073. e = d.createEvent("HTMLEvents");
  1074. e.initEvent(eventName, true, true);
  1075. e.eventName = eventName;
  1076. element.dispatchEvent(e);
  1077. } else {
  1078. e = d.createEventObject();
  1079. element.fireEvent("on" + eventName, e);
  1080. }
  1081. return this;
  1082. },
  1083. deepExtend: function (destination, source) {
  1084. for (var property in source) {
  1085. if (
  1086. source[property]
  1087. && source[property].constructor
  1088. && source[property].constructor === Object
  1089. ) {
  1090. destination[property] = destination[property] || {};
  1091. this.deepExtend(destination[property], source[property]);
  1092. } else {
  1093. destination[property] = source[property];
  1094. }
  1095. }
  1096. return destination;
  1097. },
  1098. /*
  1099. * This is a Paste Hook. When the user pastes
  1100. * content, this ultimately converts it into
  1101. * plain text before inserting the data.
  1102. */
  1103. pasteHook: function (fn) {
  1104. var textarea = d.createElement('textarea'),
  1105. el = this.element,
  1106. existingValue,
  1107. existingLength,
  1108. overallLength,
  1109. s = this.settings,
  1110. medium = this.medium,
  1111. html = this.html;
  1112. textarea.className = s.cssClasses.pasteHook;
  1113. el.parentNode.appendChild(textarea);
  1114. textarea.focus();
  1115. if (!wild) {
  1116. medium.makeUndoable();
  1117. }
  1118. setTimeout(function () {
  1119. el.focus();
  1120. if (s.maxLength > 0) {
  1121. existingValue = html.text(el);
  1122. existingLength = existingValue.length;
  1123. overallLength = existingLength + textarea.value.length;
  1124. if (overallLength > existingLength) {
  1125. textarea.value = textarea.value.substring(0, s.maxLength - existingLength);
  1126. }
  1127. }
  1128. fn(textarea.value);
  1129. html.deleteNode(textarea);
  1130. }, 2);
  1131. },
  1132. setupContents: function () {
  1133. var el = this.element,
  1134. children = el.children,
  1135. childNodes = el.childNodes,
  1136. initialParagraph;
  1137. if (
  1138. !this.settings.tags.paragraph
  1139. || children.length > 0
  1140. || this.settings.mode === Medium.inlineMode
  1141. ) {
  1142. return;
  1143. }
  1144. //has content, but no children
  1145. if (childNodes.length > 0) {
  1146. initialParagraph = d.createElement(this.settings.tags.paragraph);
  1147. if (el.innerHTML.match('^[&]nbsp[;]')) {
  1148. el.innerHTML = el.innerHTML.substring(6, el.innerHTML.length - 1);
  1149. }
  1150. initialParagraph.innerHTML = el.innerHTML;
  1151. el.innerHTML = '';
  1152. el.appendChild(initialParagraph);
  1153. this.cursor.set(initialParagraph.innerHTML.length, initialParagraph);
  1154. } else {
  1155. initialParagraph = d.createElement(this.settings.tags.paragraph);
  1156. initialParagraph.innerHTML = '&nbsp;';
  1157. el.appendChild(initialParagraph);
  1158. }
  1159. },
  1160. traverseAll: function (element, options, depth) {
  1161. var children = element.childNodes,
  1162. length = children.length,
  1163. i = 0,
  1164. node,
  1165. depth = depth || 1;
  1166. options = options || {};
  1167. if (length > 0) {
  1168. for (; i < length; i++) {
  1169. node = children[i];
  1170. switch (node.nodeType) {
  1171. case 1:
  1172. this.traverseAll(node, options, depth + 1);
  1173. if (options.element !== undefined) options.element(node, i, depth, element);
  1174. break;
  1175. case 3:
  1176. if (options.fragment !== undefined) options.fragment(node, i, depth, element);
  1177. }
  1178. //length may change
  1179. length = children.length;
  1180. //if length did change, and we are at the last item, this causes infinite recursion, so if we are at the last item, then stop to prevent this
  1181. if (node === element.lastChild) {
  1182. i = length;
  1183. }
  1184. }
  1185. }
  1186. }
  1187. };
  1188. /*
  1189. * Handle Selection Logic
  1190. */
  1191. Medium.Selection = function () {
  1192. };
  1193. Medium.Selection.prototype = {
  1194. setBridge: function (bridge) {
  1195. for (var i in bridge) {
  1196. this[i] = bridge[i];
  1197. }
  1198. },
  1199. saveSelection: function () {
  1200. if (w.getSelection) {
  1201. var sel = w.getSelection();
  1202. if (sel.rangeCount > 0) {
  1203. return sel.getRangeAt(0);
  1204. }
  1205. } else if (d.selection && d.selection.createRange) { // IE
  1206. return d.selection.createRange();
  1207. }
  1208. return null;
  1209. },
  1210. restoreSelection: function (range) {
  1211. if (range) {
  1212. if (w.getSelection) {
  1213. var sel = w.getSelection();
  1214. sel.removeAllRanges();
  1215. sel.addRange(range);
  1216. } else if (d.selection && range.select) { // IE
  1217. range.select();
  1218. }
  1219. }
  1220. }
  1221. };
  1222. /*
  1223. * Handle Cursor Logic
  1224. */
  1225. Medium.Cursor = function () {
  1226. };
  1227. Medium.Cursor.prototype = {
  1228. setBridge: function (bridge) {
  1229. for (var i in bridge) {
  1230. this[i] = bridge[i];
  1231. }
  1232. },
  1233. set: function (pos, el) {
  1234. var range,
  1235. html = this.html;
  1236. if (d.createRange) {
  1237. var selection = w.getSelection(),
  1238. lastChild = html.lastChild(),
  1239. length = html.text(lastChild).length - 1,
  1240. toModify = el ? el : lastChild,
  1241. theLength = ((typeof pos !== 'undefined') && (pos !== null) ? pos : length);
  1242. range = d.createRange();
  1243. try{
  1244. range.setStart(toModify, theLength);
  1245. }
  1246. catch(e){};
  1247. range.collapse(true);
  1248. selection.removeAllRanges();
  1249. selection.addRange(range);
  1250. } else {
  1251. range = d.body.createTextRange();
  1252. range.moveToElementText(el);
  1253. range.collapse(false);
  1254. range.select();
  1255. }
  1256. },
  1257. parent: function () {
  1258. var target = null, range;
  1259. if (w.getSelection) {
  1260. range = w.getSelection().getRangeAt(0);
  1261. target = range.commonAncestorContainer;
  1262. target = (target.nodeType === 1
  1263. ? target
  1264. : target.parentNode
  1265. );
  1266. }
  1267. else if (d.selection) {
  1268. target = d.selection.createRange().parentElement();
  1269. }
  1270. if (target.tagName == 'SPAN') {
  1271. target = target.parentNode;
  1272. }
  1273. return target;
  1274. },
  1275. caretToBeginning: function (el) {
  1276. this.set(0, el);
  1277. },
  1278. caretToEnd: function (el) {
  1279. this.set(this.html.text(el).length, el);
  1280. }
  1281. };
  1282. /*
  1283. * HTML Abstractions
  1284. */
  1285. Medium.HtmlAssistant = function () {
  1286. };
  1287. Medium.HtmlAssistant.prototype = {
  1288. setBridge: function (bridge) {
  1289. for (var i in bridge) {
  1290. this[i] = bridge[i];
  1291. }
  1292. },
  1293. encodeHtml: function (html) {
  1294. return d.createElement('a').appendChild(
  1295. d.createTextNode(html)).parentNode.innerHTML;
  1296. },
  1297. text: function (node, val) {
  1298. node = node || this.settings.element;
  1299. if (val) {
  1300. if ((node.textContent) && (typeof (node.textContent) != "undefined")) {
  1301. node.textContent = val;
  1302. } else {
  1303. node.innerText = val;
  1304. }
  1305. }
  1306. else if (node.innerText) {
  1307. return trim(node.innerText);
  1308. }
  1309. else if (node.textContent) {
  1310. return trim(node.textContent);
  1311. }
  1312. //document fragment
  1313. else if (node.data) {
  1314. return trim(node.data);
  1315. }
  1316. //for good measure
  1317. return '';
  1318. },
  1319. changeTag: function (oldNode, newTag) {
  1320. var newNode = d.createElement(newTag),
  1321. node,
  1322. nextNode;
  1323. node = oldNode.firstChild;
  1324. while (node) {
  1325. nextNode = node.nextSibling;
  1326. newNode.appendChild(node);
  1327. node = nextNode;
  1328. }
  1329. oldNode.parentNode.insertBefore(newNode, oldNode);
  1330. oldNode.parentNode.removeChild(oldNode);
  1331. return newNode;
  1332. },
  1333. deleteNode: function (el) {
  1334. el.parentNode.removeChild(el);
  1335. },
  1336. placeholders: function () {
  1337. //in IE8, just gracefully degrade to no placeholders
  1338. if (!w.getComputedStyle) return;
  1339. var that = this,
  1340. s = this.settings,
  1341. placeholder = this.medium.placeholder || (this.medium.placeholder = d.createElement('div')),
  1342. el = s.element,
  1343. style = placeholder.style,
  1344. elStyle = w.getComputedStyle(el, null),
  1345. qStyle = function (prop) {
  1346. return elStyle.getPropertyValue(prop)
  1347. },
  1348. utils = this.utils,
  1349. text = utils.html.text(el),
  1350. cursor = this.cursor,
  1351. childCount = el.children.length;
  1352. el.placeholder = placeholder;
  1353. // Empty Editor
  1354. if (text.length < 1 && childCount < 2) {
  1355. if (el.placeHolderActive) return;
  1356. if (!el.innerHTML.match('<' + s.tags.paragraph)) {
  1357. el.innerHTML = '';
  1358. }
  1359. // We need to add placeholders
  1360. if (s.placeholder.length > 0) {
  1361. if (!placeholder.setup) {
  1362. placeholder.setup = true;
  1363. //background & background color
  1364. style.background = qStyle('background');
  1365. style.backgroundColor = qStyle('background-color');
  1366. //text size & text color
  1367. style.fontSize = qStyle('font-size');
  1368. style.color = elStyle.color;
  1369. //begin box-model
  1370. //margin
  1371. style.marginTop = qStyle('margin-top');
  1372. style.marginBottom = qStyle('margin-bottom');
  1373. style.marginLeft = qStyle('margin-left');
  1374. style.marginRight = qStyle('margin-right');
  1375. //padding
  1376. style.paddingTop = qStyle('padding-top');
  1377. style.paddingBottom = qStyle('padding-bottom');
  1378. style.paddingLeft = qStyle('padding-left');
  1379. style.paddingRight = qStyle('padding-right');
  1380. //border
  1381. style.borderTopWidth = qStyle('border-top-width');
  1382. style.borderTopColor = qStyle('border-top-color');
  1383. style.borderTopStyle = qStyle('border-top-style');
  1384. style.borderBottomWidth = qStyle('border-bottom-width');
  1385. style.borderBottomColor = qStyle('border-bottom-color');
  1386. style.borderBottomStyle = qStyle('border-bottom-style');
  1387. style.borderLeftWidth = qStyle('border-left-width');
  1388. style.borderLeftColor = qStyle('border-left-color');
  1389. style.borderLeftStyle = qStyle('border-left-style');
  1390. style.borderRightWidth = qStyle('border-right-width');
  1391. style.borderRightColor = qStyle('border-right-color');
  1392. style.borderRightStyle = qStyle('border-right-style');
  1393. //end box model
  1394. //element setup
  1395. placeholder.className = s.cssClasses.placeholder + ' ' + s.cssClasses.placeholder + '-' + s.mode;
  1396. placeholder.innerHTML = '<div>' + s.placeholder + '</div>';
  1397. el.parentNode.insertBefore(placeholder, el);
  1398. }
  1399. el.className += ' ' + s.cssClasses.clear;
  1400. style.display = '';
  1401. // Add base P tag and do auto focus, give it a min height if el has one
  1402. style.minHeight = el.clientHeight + 'px';
  1403. style.minWidth = el.clientWidth + 'px';
  1404. if (s.mode !== Medium.inlineMode) {
  1405. utils.setupContents();
  1406. if (childCount === 0 && el.firstChild) {
  1407. cursor.set(0, el.firstChild);
  1408. }
  1409. }
  1410. }
  1411. el.placeHolderActive = true;
  1412. } else if (el.placeHolderActive) {
  1413. el.placeHolderActive = false;
  1414. style.display = 'none';
  1415. el.className = trim(el.className.replace(s.cssClasses.clear, ''));
  1416. utils.setupContents();
  1417. }
  1418. },
  1419. /**
  1420. * Cleans element
  1421. * @param {HtmlElement} [el] default is settings.element
  1422. */
  1423. clean: function (el) {
  1424. /*
  1425. * Deletes invalid nodes
  1426. * Removes Attributes
  1427. */
  1428. var s = this.settings,
  1429. placeholderClass = s.cssClasses.placeholder,
  1430. attributesToRemove = (s.attributes || {}).remove || [],
  1431. tags = s.tags || {},
  1432. onlyOuter = tags.outerLevel || null,
  1433. onlyInner = tags.innerLevel || null,
  1434. outerSwitch = {},
  1435. innerSwitch = {},
  1436. paragraphTag = (tags.paragraph || '').toUpperCase(),
  1437. html = this.html,
  1438. attr,
  1439. text,
  1440. j;
  1441. el = el || s.element;
  1442. if (onlyOuter !== null) {
  1443. for (j = 0; j < onlyOuter.length; j++) {
  1444. outerSwitch[onlyOuter[j].toUpperCase()] = true;
  1445. }
  1446. }
  1447. if (onlyInner !== null) {
  1448. for (j = 0; j < onlyInner.length; j++) {
  1449. innerSwitch[onlyInner[j].toUpperCase()] = true;
  1450. }
  1451. }
  1452. this.utils.traverseAll(el, {
  1453. element: function (child, i, depth, parent) {
  1454. var nodeName = child.nodeName,
  1455. shouldDelete = true;
  1456. // Remove attributes
  1457. for (j = 0; j < attributesToRemove.length; j++) {
  1458. attr = attributesToRemove[j];
  1459. if (child.hasAttribute(attr)) {
  1460. if (child.getAttribute(attr) !== placeholderClass) {
  1461. child.removeAttribute(attr);
  1462. }
  1463. }
  1464. }
  1465. if (onlyOuter === null && onlyInner === null) {
  1466. return;
  1467. }
  1468. if (depth === 1 && outerSwitch[nodeName] !== undefined) {
  1469. shouldDelete = false;
  1470. } else if (depth > 1 && innerSwitch[nodeName] !== undefined) {
  1471. shouldDelete = false;
  1472. }
  1473. // Convert tags or delete
  1474. if (shouldDelete) {
  1475. if (w.getComputedStyle(child, null).getPropertyValue('display') === 'block') {
  1476. if (paragraphTag.length > 0 && paragraphTag !== nodeName) {
  1477. html.changeTag(child, paragraphTag);
  1478. }
  1479. if (depth > 1) {
  1480. while (parent.childNodes.length > i) {
  1481. parent.parentNode.insertBefore(parent.lastChild, parent.nextSibling);
  1482. }
  1483. }
  1484. } else {
  1485. switch (nodeName) {
  1486. case 'BR':
  1487. if (child === child.parentNode.lastChild) {
  1488. if (child === child.parentNode.firstChild) {
  1489. break;
  1490. }
  1491. text = document.createTextNode("");
  1492. text.innerHTML = '&nbsp';
  1493. parent.insertBefore(text, child);
  1494. break;
  1495. }
  1496. default:
  1497. while (child.firstChild !== null) {
  1498. parent.insertBefore(child.firstChild, child);
  1499. }
  1500. html.deleteNode(child);
  1501. break;
  1502. }
  1503. }
  1504. }
  1505. }
  1506. });
  1507. },
  1508. lastChild: function () {
  1509. return this.element.lastChild;
  1510. },
  1511. addTag: function (tag, shouldFocus, isEditable, afterElement) {
  1512. if (!this.settings.beforeAddTag(tag, shouldFocus, isEditable, afterElement)) {
  1513. var newEl = d.createElement(tag),
  1514. toFocus;
  1515. if (typeof isEditable !== "undefined" && isEditable === false) {
  1516. newEl.contentEditable = false;
  1517. }
  1518. if (newEl.innerHTML.length == 0) {
  1519. newEl.innerHTML = ' ';
  1520. }
  1521. if (afterElement && afterElement.nextSibling) {
  1522. afterElement.parentNode.insertBefore(newEl, afterElement.nextSibling);
  1523. toFocus = afterElement.nextSibling;
  1524. } else {
  1525. this.settings.element.appendChild(newEl);
  1526. toFocus = this.html.lastChild();
  1527. }
  1528. if (shouldFocus) {
  1529. this.cache.focusedElement = toFocus;
  1530. this.cursor.set(0, toFocus);
  1531. }
  1532. return newEl;
  1533. }
  1534. return null;
  1535. },
  1536. baseAtCaret: function () {
  1537. if (!this.medium.isActive()) return null;
  1538. var sel = w.getSelection ? w.getSelection() : document.selection;
  1539. if (sel.rangeCount) {
  1540. var selRange = sel.getRangeAt(0),
  1541. container = selRange.endContainer;
  1542. switch (container.nodeType) {
  1543. case 3:
  1544. if (container.data && container.data.length != selRange.endOffset) return false;
  1545. break;
  1546. }
  1547. return container;
  1548. }
  1549. return null;
  1550. },
  1551. atCaret: function () {
  1552. var container = this.baseAtCaret() || {},
  1553. el = this.element;
  1554. if (container === false) return null;
  1555. while (container && container.parentNode !== el) {
  1556. container = container.parentNode;
  1557. }
  1558. if (container && container.nodeType == 1) {
  1559. return container;
  1560. }
  1561. return null;
  1562. }
  1563. };
  1564. Medium.Action = function () {
  1565. };
  1566. Medium.Action.prototype = {
  1567. setBridge: function (bridge) {
  1568. for (var i in bridge) {
  1569. this[i] = bridge[i];
  1570. }
  1571. },
  1572. listen: function () {
  1573. var el = this.element,
  1574. intercept = this.intercept;
  1575. this.utils
  1576. .addEvent(el, 'keyup', intercept.up)
  1577. .addEvent(el, 'keydown', intercept.down)
  1578. .addEvent(el, 'focus', intercept.focus)
  1579. .addEvent(el, 'blur', intercept.blur)
  1580. .addEvent(el, 'paste', this.settings.pasteEventHandler);
  1581. },
  1582. preserveElementFocus: function () {
  1583. // Fetch node that has focus
  1584. var anchorNode = w.getSelection ? w.getSelection().anchorNode : d.activeElement;
  1585. if (anchorNode) {
  1586. var cache = this.medium.cache,
  1587. s = this.settings,
  1588. cur = anchorNode.parentNode,
  1589. children = s.element.children,
  1590. diff = cur !== cache.focusedElement,
  1591. elementIndex = 0,
  1592. i;
  1593. // anchorNode is our target if element is empty
  1594. if (cur === s.element) {
  1595. cur = anchorNode;
  1596. }
  1597. // Find our child index
  1598. for (i = 0; i < children.length; i++) {
  1599. if (cur === children[i]) {
  1600. elementIndex = i;
  1601. break;
  1602. }
  1603. }
  1604. // Focused element is different
  1605. if (diff) {
  1606. cache.focusedElement = cur;
  1607. cache.focusedElementIndex = elementIndex;
  1608. }
  1609. }
  1610. }
  1611. };
  1612. Medium.Cache = function () {
  1613. this.initialized = false;
  1614. this.cmd = false;
  1615. this.focusedElement = null
  1616. };
  1617. Medium.Cache.prototype = {
  1618. setBridge: function (bridge) {
  1619. for (var i in bridge) {
  1620. this[i] = bridge[i];
  1621. }
  1622. }
  1623. };
  1624. //Modes;
  1625. Medium.inlineMode = 'inline';
  1626. Medium.partialMode = 'partial';
  1627. Medium.richMode = 'rich';
  1628. Medium.Messages = {
  1629. pastHere: 'Paste Here'
  1630. };
  1631. return Medium;
  1632. }());
  1633. if (typeof define === 'function' && define['amd']) {
  1634. define(function () { return Medium; });
  1635. } else if (typeof module !== 'undefined' && module.exports) {
  1636. module.exports = Medium;
  1637. } else if (typeof this !== 'undefined') {
  1638. this.Medium = Medium;
  1639. }
  1640. }).call(this, window, document);