theme.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934
  1. /**
  2. * theme.js
  3. *
  4. * Released under LGPL License.
  5. * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
  6. *
  7. * License: http://www.tinymce.com/license
  8. * Contributing: http://www.tinymce.com/contributing
  9. */
  10. /*global tinymce:true */
  11. tinymce.ThemeManager.add('modern', function(editor) {
  12. var self = this, settings = editor.settings, Factory = tinymce.ui.Factory,
  13. each = tinymce.each, DOM = tinymce.DOM, Rect = tinymce.geom.Rect, FloatPanel = tinymce.ui.FloatPanel;
  14. // Default menus
  15. var defaultMenus = {
  16. file: {title: 'File', items: 'newdocument'},
  17. edit: {title: 'Edit', items: 'undo redo | cut copy paste pastetext | selectall'},
  18. insert: {title: 'Insert', items: '|'},
  19. view: {title: 'View', items: 'visualaid |'},
  20. format: {title: 'Format', items: 'bold italic underline strikethrough superscript subscript | formats | removeformat'},
  21. table: {title: 'Table'},
  22. tools: {title: 'Tools'}
  23. };
  24. var defaultToolbar = "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | " +
  25. "bullist numlist outdent indent | link image";
  26. function createToolbar(items, size) {
  27. var toolbarItems = [], buttonGroup;
  28. if (!items) {
  29. return;
  30. }
  31. each(items.split(/[ ,]/), function(item) {
  32. var itemName;
  33. function bindSelectorChanged() {
  34. var selection = editor.selection;
  35. function setActiveItem(name) {
  36. return function(state, args) {
  37. var nodeName, i = args.parents.length;
  38. while (i--) {
  39. nodeName = args.parents[i].nodeName;
  40. if (nodeName == "OL" || nodeName == "UL") {
  41. break;
  42. }
  43. }
  44. item.active(state && nodeName == name);
  45. };
  46. }
  47. if (itemName == "bullist") {
  48. selection.selectorChanged('ul > li', setActiveItem("UL"));
  49. }
  50. if (itemName == "numlist") {
  51. selection.selectorChanged('ol > li', setActiveItem("OL"));
  52. }
  53. if (item.settings.stateSelector) {
  54. selection.selectorChanged(item.settings.stateSelector, function(state) {
  55. item.active(state);
  56. }, true);
  57. }
  58. if (item.settings.disabledStateSelector) {
  59. selection.selectorChanged(item.settings.disabledStateSelector, function(state) {
  60. item.disabled(state);
  61. });
  62. }
  63. }
  64. if (item == "|") {
  65. buttonGroup = null;
  66. } else {
  67. if (Factory.has(item)) {
  68. item = {type: item, size: size};
  69. toolbarItems.push(item);
  70. buttonGroup = null;
  71. } else {
  72. if (!buttonGroup) {
  73. buttonGroup = {type: 'buttongroup', items: []};
  74. toolbarItems.push(buttonGroup);
  75. }
  76. if (editor.buttons[item]) {
  77. // TODO: Move control creation to some UI class
  78. itemName = item;
  79. item = editor.buttons[itemName];
  80. if (typeof item == "function") {
  81. item = item();
  82. }
  83. item.type = item.type || 'button';
  84. item.size = size;
  85. item = Factory.create(item);
  86. buttonGroup.items.push(item);
  87. if (editor.initialized) {
  88. bindSelectorChanged();
  89. } else {
  90. editor.on('init', bindSelectorChanged);
  91. }
  92. }
  93. }
  94. }
  95. });
  96. return {
  97. type: 'toolbar',
  98. layout: 'flow',
  99. items: toolbarItems
  100. };
  101. }
  102. /**
  103. * Creates the toolbars from config and returns a toolbar array.
  104. *
  105. * @param {String} size Optional toolbar item size.
  106. * @return {Array} Array with toolbars.
  107. */
  108. function createToolbars(size) {
  109. var toolbars = [];
  110. function addToolbar(items) {
  111. if (items) {
  112. toolbars.push(createToolbar(items, size));
  113. return true;
  114. }
  115. }
  116. // Convert toolbar array to multiple options
  117. if (tinymce.isArray(settings.toolbar)) {
  118. // Empty toolbar array is the same as a disabled toolbar
  119. if (settings.toolbar.length === 0) {
  120. return;
  121. }
  122. tinymce.each(settings.toolbar, function(toolbar, i) {
  123. settings["toolbar" + (i + 1)] = toolbar;
  124. });
  125. delete settings.toolbar;
  126. }
  127. // Generate toolbar<n>
  128. for (var i = 1; i < 10; i++) {
  129. if (!addToolbar(settings["toolbar" + i])) {
  130. break;
  131. }
  132. }
  133. // Generate toolbar or default toolbar unless it's disabled
  134. if (!toolbars.length && settings.toolbar !== false) {
  135. addToolbar(settings.toolbar || defaultToolbar);
  136. }
  137. if (toolbars.length) {
  138. return {
  139. type: 'panel',
  140. layout: 'stack',
  141. classes: "toolbar-grp",
  142. ariaRoot: true,
  143. ariaRemember: true,
  144. items: toolbars
  145. };
  146. }
  147. }
  148. /**
  149. * Creates the menu buttons based on config.
  150. *
  151. * @return {Array} Menu buttons array.
  152. */
  153. function createMenuButtons() {
  154. var name, menuButtons = [];
  155. function createMenuItem(name) {
  156. var menuItem;
  157. if (name == '|') {
  158. return {text: '|'};
  159. }
  160. menuItem = editor.menuItems[name];
  161. return menuItem;
  162. }
  163. function createMenu(context) {
  164. var menuButton, menu, menuItems, isUserDefined, removedMenuItems;
  165. removedMenuItems = tinymce.makeMap((settings.removed_menuitems || '').split(/[ ,]/));
  166. // User defined menu
  167. if (settings.menu) {
  168. menu = settings.menu[context];
  169. isUserDefined = true;
  170. } else {
  171. menu = defaultMenus[context];
  172. }
  173. if (menu) {
  174. menuButton = {text: menu.title};
  175. menuItems = [];
  176. // Default/user defined items
  177. each((menu.items || '').split(/[ ,]/), function(item) {
  178. var menuItem = createMenuItem(item);
  179. if (menuItem && !removedMenuItems[item]) {
  180. menuItems.push(createMenuItem(item));
  181. }
  182. });
  183. // Added though context
  184. if (!isUserDefined) {
  185. each(editor.menuItems, function(menuItem) {
  186. if (menuItem.context == context) {
  187. if (menuItem.separator == 'before') {
  188. menuItems.push({text: '|'});
  189. }
  190. if (menuItem.prependToContext) {
  191. menuItems.unshift(menuItem);
  192. } else {
  193. menuItems.push(menuItem);
  194. }
  195. if (menuItem.separator == 'after') {
  196. menuItems.push({text: '|'});
  197. }
  198. }
  199. });
  200. }
  201. for (var i = 0; i < menuItems.length; i++) {
  202. if (menuItems[i].text == '|') {
  203. if (i === 0 || i == menuItems.length - 1) {
  204. menuItems.splice(i, 1);
  205. }
  206. }
  207. }
  208. menuButton.menu = menuItems;
  209. if (!menuButton.menu.length) {
  210. return null;
  211. }
  212. }
  213. return menuButton;
  214. }
  215. var defaultMenuBar = [];
  216. if (settings.menu) {
  217. for (name in settings.menu) {
  218. defaultMenuBar.push(name);
  219. }
  220. } else {
  221. for (name in defaultMenus) {
  222. defaultMenuBar.push(name);
  223. }
  224. }
  225. var enabledMenuNames = typeof settings.menubar == "string" ? settings.menubar.split(/[ ,]/) : defaultMenuBar;
  226. for (var i = 0; i < enabledMenuNames.length; i++) {
  227. var menu = enabledMenuNames[i];
  228. menu = createMenu(menu);
  229. if (menu) {
  230. menuButtons.push(menu);
  231. }
  232. }
  233. return menuButtons;
  234. }
  235. /**
  236. * Adds accessibility shortcut keys to panel.
  237. *
  238. * @param {tinymce.ui.Panel} panel Panel to add focus to.
  239. */
  240. function addAccessibilityKeys(panel) {
  241. function focus(type) {
  242. var item = panel.find(type)[0];
  243. if (item) {
  244. item.focus(true);
  245. }
  246. }
  247. editor.shortcuts.add('Alt+F9', '', function() {
  248. focus('menubar');
  249. });
  250. editor.shortcuts.add('Alt+F10', '', function() {
  251. focus('toolbar');
  252. });
  253. editor.shortcuts.add('Alt+F11', '', function() {
  254. focus('elementpath');
  255. });
  256. panel.on('cancel', function() {
  257. editor.focus();
  258. });
  259. }
  260. /**
  261. * Resizes the editor to the specified width, height.
  262. */
  263. function resizeTo(width, height) {
  264. var containerElm, iframeElm, containerSize, iframeSize;
  265. function getSize(elm) {
  266. return {
  267. width: elm.clientWidth,
  268. height: elm.clientHeight
  269. };
  270. }
  271. containerElm = editor.getContainer();
  272. iframeElm = editor.getContentAreaContainer().firstChild;
  273. containerSize = getSize(containerElm);
  274. iframeSize = getSize(iframeElm);
  275. if (width !== null) {
  276. width = Math.max(settings.min_width || 100, width);
  277. width = Math.min(settings.max_width || 0xFFFF, width);
  278. DOM.setStyle(containerElm, 'width', width + (containerSize.width - iframeSize.width));
  279. DOM.setStyle(iframeElm, 'width', width);
  280. }
  281. height = Math.max(settings.min_height || 100, height);
  282. height = Math.min(settings.max_height || 0xFFFF, height);
  283. DOM.setStyle(iframeElm, 'height', height);
  284. editor.fire('ResizeEditor');
  285. }
  286. function resizeBy(dw, dh) {
  287. var elm = editor.getContentAreaContainer();
  288. self.resizeTo(elm.clientWidth + dw, elm.clientHeight + dh);
  289. }
  290. /**
  291. * Handles contextual toolbars.
  292. */
  293. function addContextualToolbars() {
  294. var scrollContainer;
  295. function getContextToolbars() {
  296. return editor.contextToolbars || [];
  297. }
  298. function getElementRect(elm) {
  299. var pos, targetRect, root;
  300. pos = tinymce.DOM.getPos(editor.getContentAreaContainer());
  301. targetRect = editor.dom.getRect(elm);
  302. root = editor.dom.getRoot();
  303. // Adjust targetPos for scrolling in the editor
  304. if (root.nodeName == 'BODY') {
  305. targetRect.x -= root.ownerDocument.documentElement.scrollLeft || root.scrollLeft;
  306. targetRect.y -= root.ownerDocument.documentElement.scrollTop || root.scrollTop;
  307. }
  308. targetRect.x += pos.x;
  309. targetRect.y += pos.y;
  310. return targetRect;
  311. }
  312. function hideAllFloatingPanels() {
  313. each(editor.contextToolbars, function(toolbar) {
  314. if (toolbar.panel) {
  315. toolbar.panel.hide();
  316. }
  317. });
  318. }
  319. function togglePositionClass(panel, relPos, predicate) {
  320. relPos = relPos ? relPos.substr(0, 2) : '';
  321. each({
  322. t: 'down',
  323. b: 'up'
  324. }, function(cls, pos) {
  325. panel.classes.toggle('arrow-' + cls, predicate(pos, relPos.substr(0, 1)));
  326. });
  327. each({
  328. l: 'left',
  329. r: 'right'
  330. }, function(cls, pos) {
  331. panel.classes.toggle('arrow-' + cls, predicate(pos, relPos.substr(1, 1)));
  332. });
  333. }
  334. function toClientRect(geomRect) {
  335. return {
  336. left: geomRect.x,
  337. top: geomRect.y,
  338. width: geomRect.w,
  339. height: geomRect.h,
  340. right: geomRect.x + geomRect.w,
  341. bottom: geomRect.y + geomRect.h
  342. };
  343. }
  344. function userConstrain(x, y, elementRect, contentAreaRect, panelRect) {
  345. panelRect = toClientRect({x: x, y: y, w: panelRect.w, h: panelRect.h});
  346. if (settings.inline_toolbar_position_handler) {
  347. panelRect = settings.inline_toolbar_position_handler({
  348. elementRect: toClientRect(elementRect),
  349. contentAreaRect: toClientRect(contentAreaRect),
  350. panelRect: panelRect
  351. });
  352. }
  353. return panelRect;
  354. }
  355. function movePanelTo(panel, pos) {
  356. panel.moveTo(pos.left, pos.top);
  357. }
  358. function reposition(match) {
  359. var relPos, panelRect, elementRect, contentAreaRect, panel, relRect, testPositions, smallElementWidthThreshold;
  360. if (editor.removed) {
  361. return;
  362. }
  363. if (!match || !match.toolbar.panel) {
  364. hideAllFloatingPanels();
  365. return;
  366. }
  367. testPositions = [
  368. 'bc-tc', 'tc-bc',
  369. 'tl-bl', 'bl-tl',
  370. 'tr-br', 'br-tr'
  371. ];
  372. panel = match.toolbar.panel;
  373. panel.show();
  374. elementRect = getElementRect(match.element);
  375. panelRect = tinymce.DOM.getRect(panel.getEl());
  376. contentAreaRect = tinymce.DOM.getRect(editor.getContentAreaContainer() || editor.getBody());
  377. smallElementWidthThreshold = 25;
  378. // We need to use these instead of the rect values since the style
  379. // size properites might not be the same as the real size for a table
  380. elementRect.w = match.element.clientWidth;
  381. elementRect.h = match.element.clientHeight;
  382. if (!editor.inline) {
  383. contentAreaRect.w = editor.getDoc().documentElement.offsetWidth;
  384. }
  385. // Inflate the elementRect so it doesn't get placed above resize handles
  386. if (editor.selection.controlSelection.isResizable(match.element) && elementRect.w < smallElementWidthThreshold) {
  387. elementRect = Rect.inflate(elementRect, 0, 8);
  388. }
  389. relPos = Rect.findBestRelativePosition(panelRect, elementRect, contentAreaRect, testPositions);
  390. elementRect = Rect.clamp(elementRect, contentAreaRect);
  391. if (relPos) {
  392. relRect = Rect.relativePosition(panelRect, elementRect, relPos);
  393. movePanelTo(panel, userConstrain(relRect.x, relRect.y, elementRect, contentAreaRect, panelRect));
  394. } else {
  395. // Allow overflow below the editor to avoid placing toolbars ontop of tables
  396. contentAreaRect.h += panelRect.h;
  397. elementRect = Rect.intersect(contentAreaRect, elementRect);
  398. if (elementRect) {
  399. relPos = Rect.findBestRelativePosition(panelRect, elementRect, contentAreaRect, [
  400. 'bc-tc', 'bl-tl', 'br-tr'
  401. ]);
  402. if (relPos) {
  403. relRect = Rect.relativePosition(panelRect, elementRect, relPos);
  404. movePanelTo(panel, userConstrain(relRect.x, relRect.y, elementRect, contentAreaRect, panelRect));
  405. } else {
  406. movePanelTo(panel, userConstrain(elementRect.x, elementRect.y, elementRect, contentAreaRect, panelRect));
  407. }
  408. } else {
  409. panel.hide();
  410. }
  411. }
  412. togglePositionClass(panel, relPos, function(pos1, pos2) {
  413. return pos1 === pos2;
  414. });
  415. //drawRect(contentAreaRect, 'blue');
  416. //drawRect(elementRect, 'red');
  417. //drawRect(panelRect, 'green');
  418. }
  419. function repositionHandler() {
  420. function execute() {
  421. if (editor.selection) {
  422. reposition(findFrontMostMatch(editor.selection.getNode()));
  423. }
  424. }
  425. tinymce.util.Delay.requestAnimationFrame(execute);
  426. }
  427. function bindScrollEvent() {
  428. if (!scrollContainer) {
  429. scrollContainer = editor.selection.getScrollContainer() || editor.getWin();
  430. tinymce.$(scrollContainer).on('scroll', repositionHandler);
  431. editor.on('remove', function() {
  432. tinymce.$(scrollContainer).off('scroll');
  433. });
  434. }
  435. }
  436. function showContextToolbar(match) {
  437. var panel;
  438. if (match.toolbar.panel) {
  439. match.toolbar.panel.show();
  440. reposition(match);
  441. return;
  442. }
  443. bindScrollEvent();
  444. panel = Factory.create({
  445. type: 'floatpanel',
  446. role: 'dialog',
  447. classes: 'tinymce tinymce-inline arrow',
  448. ariaLabel: 'Inline toolbar',
  449. layout: 'flex',
  450. direction: 'column',
  451. align: 'stretch',
  452. autohide: false,
  453. autofix: true,
  454. fixed: true,
  455. border: 1,
  456. items: createToolbar(match.toolbar.items),
  457. oncancel: function() {
  458. editor.focus();
  459. }
  460. });
  461. match.toolbar.panel = panel;
  462. panel.renderTo(document.body).reflow();
  463. reposition(match);
  464. }
  465. function hideAllContextToolbars() {
  466. tinymce.each(getContextToolbars(), function(toolbar) {
  467. if (toolbar.panel) {
  468. toolbar.panel.hide();
  469. }
  470. });
  471. }
  472. function findFrontMostMatch(targetElm) {
  473. var i, y, parentsAndSelf, toolbars = getContextToolbars();
  474. parentsAndSelf = editor.$(targetElm).parents().add(targetElm);
  475. for (i = parentsAndSelf.length - 1; i >= 0; i--) {
  476. for (y = toolbars.length - 1; y >= 0; y--) {
  477. if (toolbars[y].predicate(parentsAndSelf[i])) {
  478. return {
  479. toolbar: toolbars[y],
  480. element: parentsAndSelf[i]
  481. };
  482. }
  483. }
  484. }
  485. return null;
  486. }
  487. editor.on('click keyup setContent', function(e) {
  488. // Only act on partial inserts
  489. if (e.type == 'setcontent' && !e.selection) {
  490. return;
  491. }
  492. // Needs to be delayed to avoid Chrome img focus out bug
  493. tinymce.util.Delay.setEditorTimeout(editor, function() {
  494. var match;
  495. match = findFrontMostMatch(editor.selection.getNode());
  496. if (match) {
  497. hideAllContextToolbars();
  498. showContextToolbar(match);
  499. } else {
  500. hideAllContextToolbars();
  501. }
  502. });
  503. });
  504. editor.on('blur hide', hideAllContextToolbars);
  505. editor.on('ObjectResizeStart', function() {
  506. var match = findFrontMostMatch(editor.selection.getNode());
  507. if (match && match.toolbar.panel) {
  508. match.toolbar.panel.hide();
  509. }
  510. });
  511. editor.on('nodeChange ResizeEditor ResizeWindow', repositionHandler);
  512. editor.on('remove', function() {
  513. tinymce.each(getContextToolbars(), function(toolbar) {
  514. if (toolbar.panel) {
  515. toolbar.panel.remove();
  516. }
  517. });
  518. editor.contextToolbars = {};
  519. });
  520. editor.shortcuts.add('ctrl+shift+e > ctrl+shift+p', '', function() {
  521. var match = findFrontMostMatch(editor.selection.getNode());
  522. if (match && match.toolbar.panel) {
  523. match.toolbar.panel.items()[0].focus();
  524. }
  525. });
  526. }
  527. function fireSkinLoaded(editor) {
  528. return function() {
  529. if (editor.initialized) {
  530. editor.fire('SkinLoaded');
  531. } else {
  532. editor.on('init', function() {
  533. editor.fire('SkinLoaded');
  534. });
  535. }
  536. };
  537. }
  538. /**
  539. * Renders the inline editor UI.
  540. *
  541. * @return {Object} Name/value object with theme data.
  542. */
  543. function renderInlineUI(args) {
  544. var panel, inlineToolbarContainer;
  545. if (settings.fixed_toolbar_container) {
  546. inlineToolbarContainer = DOM.select(settings.fixed_toolbar_container)[0];
  547. }
  548. function reposition() {
  549. if (panel && panel.moveRel && panel.visible() && !panel._fixed) {
  550. // TODO: This is kind of ugly and doesn't handle multiple scrollable elements
  551. var scrollContainer = editor.selection.getScrollContainer(), body = editor.getBody();
  552. var deltaX = 0, deltaY = 0;
  553. if (scrollContainer) {
  554. var bodyPos = DOM.getPos(body), scrollContainerPos = DOM.getPos(scrollContainer);
  555. deltaX = Math.max(0, scrollContainerPos.x - bodyPos.x);
  556. deltaY = Math.max(0, scrollContainerPos.y - bodyPos.y);
  557. }
  558. panel.fixed(false).moveRel(body, editor.rtl ? ['tr-br', 'br-tr'] : ['tl-bl', 'bl-tl', 'tr-br']).moveBy(deltaX, deltaY);
  559. }
  560. }
  561. function show() {
  562. if (panel) {
  563. panel.show();
  564. reposition();
  565. DOM.addClass(editor.getBody(), 'mce-edit-focus');
  566. }
  567. }
  568. function hide() {
  569. if (panel) {
  570. // We require two events as the inline float panel based toolbar does not have autohide=true
  571. panel.hide();
  572. // All other autohidden float panels will be closed below.
  573. FloatPanel.hideAll();
  574. DOM.removeClass(editor.getBody(), 'mce-edit-focus');
  575. }
  576. }
  577. function render() {
  578. if (panel) {
  579. if (!panel.visible()) {
  580. show();
  581. }
  582. return;
  583. }
  584. // Render a plain panel inside the inlineToolbarContainer if it's defined
  585. panel = self.panel = Factory.create({
  586. type: inlineToolbarContainer ? 'panel' : 'floatpanel',
  587. role: 'application',
  588. classes: 'tinymce tinymce-inline',
  589. layout: 'flex',
  590. direction: 'column',
  591. align: 'stretch',
  592. autohide: false,
  593. autofix: true,
  594. fixed: !!inlineToolbarContainer,
  595. border: 1,
  596. items: [
  597. settings.menubar === false ? null : {type: 'menubar', border: '0 0 1 0', items: createMenuButtons()},
  598. createToolbars(settings.toolbar_items_size)
  599. ]
  600. });
  601. // Add statusbar
  602. /*if (settings.statusbar !== false) {
  603. panel.add({type: 'panel', classes: 'statusbar', layout: 'flow', border: '1 0 0 0', items: [
  604. {type: 'elementpath'}
  605. ]});
  606. }*/
  607. editor.fire('BeforeRenderUI');
  608. panel.renderTo(inlineToolbarContainer || document.body).reflow();
  609. addAccessibilityKeys(panel);
  610. show();
  611. addContextualToolbars();
  612. editor.on('nodeChange', reposition);
  613. editor.on('activate', show);
  614. editor.on('deactivate', hide);
  615. editor.nodeChanged();
  616. }
  617. settings.content_editable = true;
  618. editor.on('focus', function() {
  619. // Render only when the CSS file has been loaded
  620. if (args.skinUiCss) {
  621. tinymce.DOM.styleSheetLoader.load(args.skinUiCss, render, render);
  622. } else {
  623. render();
  624. }
  625. });
  626. editor.on('blur hide', hide);
  627. // Remove the panel when the editor is removed
  628. editor.on('remove', function() {
  629. if (panel) {
  630. panel.remove();
  631. panel = null;
  632. }
  633. });
  634. // Preload skin css
  635. if (args.skinUiCss) {
  636. tinymce.DOM.styleSheetLoader.load(args.skinUiCss, fireSkinLoaded(editor));
  637. }
  638. return {};
  639. }
  640. /**
  641. * Renders the iframe editor UI.
  642. *
  643. * @param {Object} args Details about target element etc.
  644. * @return {Object} Name/value object with theme data.
  645. */
  646. function renderIframeUI(args) {
  647. var panel, resizeHandleCtrl, startSize;
  648. function switchMode() {
  649. return function(e) {
  650. if (e.mode == 'readonly') {
  651. panel.find('*').disabled(true);
  652. } else {
  653. panel.find('*').disabled(false);
  654. }
  655. };
  656. }
  657. if (args.skinUiCss) {
  658. tinymce.DOM.styleSheetLoader.load(args.skinUiCss, fireSkinLoaded(editor));
  659. }
  660. // Basic UI layout
  661. panel = self.panel = Factory.create({
  662. type: 'panel',
  663. role: 'application',
  664. classes: 'tinymce',
  665. style: 'visibility: hidden',
  666. layout: 'stack',
  667. border: 1,
  668. items: [
  669. settings.menubar === false ? null : {type: 'menubar', border: '0 0 1 0', items: createMenuButtons()},
  670. createToolbars(settings.toolbar_items_size),
  671. {type: 'panel', name: 'iframe', layout: 'stack', classes: 'edit-area', html: '', border: '1 0 0 0'}
  672. ]
  673. });
  674. if (settings.resize !== false) {
  675. resizeHandleCtrl = {
  676. type: 'resizehandle',
  677. direction: settings.resize,
  678. onResizeStart: function() {
  679. var elm = editor.getContentAreaContainer().firstChild;
  680. startSize = {
  681. width: elm.clientWidth,
  682. height: elm.clientHeight
  683. };
  684. },
  685. onResize: function(e) {
  686. if (settings.resize == 'both') {
  687. resizeTo(startSize.width + e.deltaX, startSize.height + e.deltaY);
  688. } else {
  689. resizeTo(null, startSize.height + e.deltaY);
  690. }
  691. }
  692. };
  693. }
  694. // Add statusbar if needed
  695. if (settings.statusbar !== false) {
  696. panel.add({type: 'panel', name: 'statusbar', classes: 'statusbar', layout: 'flow', border: '1 0 0 0', ariaRoot: true, items: [
  697. {type: 'elementpath', editor: editor},
  698. resizeHandleCtrl
  699. ]});
  700. }
  701. editor.fire('BeforeRenderUI');
  702. editor.on('SwitchMode', switchMode());
  703. panel.renderBefore(args.targetNode).reflow();
  704. if (settings.readonly) {
  705. editor.setMode('readonly');
  706. }
  707. if (settings.width) {
  708. tinymce.DOM.setStyle(panel.getEl(), 'width', settings.width);
  709. }
  710. // Remove the panel when the editor is removed
  711. editor.on('remove', function() {
  712. panel.remove();
  713. panel = null;
  714. });
  715. // Add accesibility shortcuts
  716. addAccessibilityKeys(panel);
  717. addContextualToolbars();
  718. return {
  719. iframeContainer: panel.find('#iframe')[0].getEl(),
  720. editorContainer: panel.getEl()
  721. };
  722. }
  723. /**
  724. * Renders the UI for the theme. This gets called by the editor.
  725. *
  726. * @param {Object} args Details about target element etc.
  727. * @return {Object} Theme UI data items.
  728. */
  729. self.renderUI = function(args) {
  730. var skin = settings.skin !== false ? settings.skin || 'lightgray' : false;
  731. if (skin) {
  732. var skinUrl = settings.skin_url;
  733. if (skinUrl) {
  734. skinUrl = editor.documentBaseURI.toAbsolute(skinUrl);
  735. } else {
  736. skinUrl = tinymce.baseURL + '/skins/' + skin;
  737. }
  738. // Load special skin for IE7
  739. // TODO: Remove this when we drop IE7 support
  740. if (tinymce.Env.documentMode <= 7) {
  741. args.skinUiCss = skinUrl + '/skin.ie7.min.css';
  742. } else {
  743. args.skinUiCss = skinUrl + '/skin.min.css';
  744. }
  745. // Load content.min.css or content.inline.min.css
  746. editor.contentCSS.push(skinUrl + '/content' + (editor.inline ? '.inline' : '') + '.min.css');
  747. }
  748. // Handle editor setProgressState change
  749. editor.on('ProgressState', function(e) {
  750. self.throbber = self.throbber || new tinymce.ui.Throbber(self.panel.getEl('body'));
  751. if (e.state) {
  752. self.throbber.show(e.time);
  753. } else {
  754. self.throbber.hide();
  755. }
  756. });
  757. if (settings.inline) {
  758. return renderInlineUI(args);
  759. }
  760. return renderIframeUI(args);
  761. };
  762. self.resizeTo = resizeTo;
  763. self.resizeBy = resizeBy;
  764. });