4 * Copyright, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://www.tinymce.com/license
8 * Contributing: http://www.tinymce.com/contributing
11 /*global tinymce:true */
13 tinymce.ThemeManager.add('modern', function(editor) {
14 var self = this, settings = editor.settings, Factory = tinymce.ui.Factory, each = tinymce.each, DOM = tinymce.DOM;
18 file: {title: 'File', items: 'newdocument'},
19 edit: {title: 'Edit', items: 'undo redo | cut copy paste pastetext | selectall'},
20 insert: {title: 'Insert', items: '|'},
21 view: {title: 'View', items: 'visualaid |'},
22 format: {title: 'Format', items: 'bold italic underline strikethrough superscript subscript | formats | removeformat'},
23 table: {title: 'Table'},
24 tools: {title: 'Tools'}
27 var defaultToolbar = "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | " +
28 "bullist numlist outdent indent | link image";
31 * Creates the toolbars from config and returns a toolbar array.
33 * @return {Array} Array with toolbars.
35 function createToolbars() {
38 function addToolbar(items) {
39 var toolbarItems = [], buttonGroup;
45 each(items.split(/[ ,]/), function(item) {
48 function bindSelectorChanged() {
49 var selection = editor.selection;
51 if (itemName == "bullist") {
52 selection.selectorChanged('ul > li', function(state, args) {
53 var nodeName, i = args.parents.length;
56 nodeName = args.parents[i].nodeName;
57 if (nodeName == "OL" || nodeName == "UL") {
62 item.active(state && nodeName == "UL");
66 if (itemName == "numlist") {
67 selection.selectorChanged('ol > li', function(state, args) {
68 var nodeName, i = args.parents.length;
71 nodeName = args.parents[i].nodeName;
72 if (nodeName == "OL" || nodeName == "UL") {
77 item.active(state && nodeName == "OL");
81 if (item.settings.stateSelector) {
82 selection.selectorChanged(item.settings.stateSelector, function(state) {
87 if (item.settings.disabledStateSelector) {
88 selection.selectorChanged(item.settings.disabledStateSelector, function(state) {
97 if (Factory.has(item)) {
100 if (settings.toolbar_items_size) {
101 item.size = settings.toolbar_items_size;
104 toolbarItems.push(item);
108 buttonGroup = {type: 'buttongroup', items: []};
109 toolbarItems.push(buttonGroup);
112 if (editor.buttons[item]) {
113 // TODO: Move control creation to some UI class
115 item = editor.buttons[itemName];
117 if (typeof item == "function") {
121 item.type = item.type || 'button';
123 if (settings.toolbar_items_size) {
124 item.size = settings.toolbar_items_size;
127 item = Factory.create(item);
128 buttonGroup.items.push(item);
130 if (editor.initialized) {
131 bindSelectorChanged();
133 editor.on('init', bindSelectorChanged);
140 toolbars.push({type: 'toolbar', layout: 'flow', items: toolbarItems});
145 // Convert toolbar array to multiple options
146 if (tinymce.isArray(settings.toolbar)) {
147 // Empty toolbar array is the same as a disabled toolbar
148 if (settings.toolbar.length === 0) {
152 tinymce.each(settings.toolbar, function(toolbar, i) {
153 settings["toolbar" + (i + 1)] = toolbar;
156 delete settings.toolbar;
159 // Generate toolbar<n>
160 for (var i = 1; i < 10; i++) {
161 if (!addToolbar(settings["toolbar" + i])) {
166 // Generate toolbar or default toolbar unless it's disabled
167 if (!toolbars.length && settings.toolbar !== false) {
168 addToolbar(settings.toolbar || defaultToolbar);
171 if (toolbars.length) {
175 classes: "toolbar-grp",
184 * Creates the menu buttons based on config.
186 * @return {Array} Menu buttons array.
188 function createMenuButtons() {
189 var name, menuButtons = [];
191 function createMenuItem(name) {
198 menuItem = editor.menuItems[name];
203 function createMenu(context) {
204 var menuButton, menu, menuItems, isUserDefined, removedMenuItems;
206 removedMenuItems = tinymce.makeMap((settings.removed_menuitems || '').split(/[ ,]/));
210 menu = settings.menu[context];
211 isUserDefined = true;
213 menu = defaultMenus[context];
217 menuButton = {text: menu.title};
220 // Default/user defined items
221 each((menu.items || '').split(/[ ,]/), function(item) {
222 var menuItem = createMenuItem(item);
224 if (menuItem && !removedMenuItems[item]) {
225 menuItems.push(createMenuItem(item));
229 // Added though context
230 if (!isUserDefined) {
231 each(editor.menuItems, function(menuItem) {
232 if (menuItem.context == context) {
233 if (menuItem.separator == 'before') {
234 menuItems.push({text: '|'});
237 if (menuItem.prependToContext) {
238 menuItems.unshift(menuItem);
240 menuItems.push(menuItem);
243 if (menuItem.separator == 'after') {
244 menuItems.push({text: '|'});
250 for (var i = 0; i < menuItems.length; i++) {
251 if (menuItems[i].text == '|') {
252 if (i === 0 || i == menuItems.length - 1) {
253 menuItems.splice(i, 1);
258 menuButton.menu = menuItems;
260 if (!menuButton.menu.length) {
268 var defaultMenuBar = [];
270 for (name in settings.menu) {
271 defaultMenuBar.push(name);
274 for (name in defaultMenus) {
275 defaultMenuBar.push(name);
279 var enabledMenuNames = typeof settings.menubar == "string" ? settings.menubar.split(/[ ,]/) : defaultMenuBar;
280 for (var i = 0; i < enabledMenuNames.length; i++) {
281 var menu = enabledMenuNames[i];
282 menu = createMenu(menu);
285 menuButtons.push(menu);
293 * Adds accessibility shortcut keys to panel.
295 * @param {tinymce.ui.Panel} panel Panel to add focus to.
297 function addAccessibilityKeys(panel) {
298 function focus(type) {
299 var item = panel.find(type)[0];
306 editor.shortcuts.add('Alt+F9', '', function() {
310 editor.shortcuts.add('Alt+F10', '', function() {
314 editor.shortcuts.add('Alt+F11', '', function() {
315 focus('elementpath');
318 panel.on('cancel', function() {
324 * Resizes the editor to the specified width, height.
326 function resizeTo(width, height) {
327 var containerElm, iframeElm, containerSize, iframeSize;
329 function getSize(elm) {
331 width: elm.clientWidth,
332 height: elm.clientHeight
336 containerElm = editor.getContainer();
337 iframeElm = editor.getContentAreaContainer().firstChild;
338 containerSize = getSize(containerElm);
339 iframeSize = getSize(iframeElm);
341 if (width !== null) {
342 width = Math.max(settings.min_width || 100, width);
343 width = Math.min(settings.max_width || 0xFFFF, width);
345 DOM.setStyle(containerElm, 'width', width + (containerSize.width - iframeSize.width));
346 DOM.setStyle(iframeElm, 'width', width);
349 height = Math.max(settings.min_height || 100, height);
350 height = Math.min(settings.max_height || 0xFFFF, height);
351 DOM.setStyle(iframeElm, 'height', height);
353 editor.fire('ResizeEditor');
356 function resizeBy(dw, dh) {
357 var elm = editor.getContentAreaContainer();
358 self.resizeTo(elm.clientWidth + dw, elm.clientHeight + dh);
362 * Renders the inline editor UI.
364 * @return {Object} Name/value object with theme data.
366 function renderInlineUI(args) {
367 var panel, inlineToolbarContainer;
369 if (settings.fixed_toolbar_container) {
370 inlineToolbarContainer = DOM.select(settings.fixed_toolbar_container)[0];
373 function reposition() {
374 if (panel && panel.moveRel && panel.visible() && !panel._fixed) {
375 // TODO: This is kind of ugly and doesn't handle multiple scrollable elements
376 var scrollContainer = editor.selection.getScrollContainer(), body = editor.getBody();
377 var deltaX = 0, deltaY = 0;
379 if (scrollContainer) {
380 var bodyPos = DOM.getPos(body), scrollContainerPos = DOM.getPos(scrollContainer);
382 deltaX = Math.max(0, scrollContainerPos.x - bodyPos.x);
383 deltaY = Math.max(0, scrollContainerPos.y - bodyPos.y);
386 panel.fixed(false).moveRel(body, editor.rtl ? ['tr-br', 'br-tr'] : ['tl-bl', 'bl-tl', 'tr-br']).moveBy(deltaX, deltaY);
394 DOM.addClass(editor.getBody(), 'mce-edit-focus');
401 DOM.removeClass(editor.getBody(), 'mce-edit-focus');
407 if (!panel.visible()) {
414 // Render a plain panel inside the inlineToolbarContainer if it's defined
415 panel = self.panel = Factory.create({
416 type: inlineToolbarContainer ? 'panel' : 'floatpanel',
418 classes: 'tinymce tinymce-inline',
424 fixed: !!inlineToolbarContainer,
427 settings.menubar === false ? null : {type: 'menubar', border: '0 0 1 0', items: createMenuButtons()},
433 /*if (settings.statusbar !== false) {
434 panel.add({type: 'panel', classes: 'statusbar', layout: 'flow', border: '1 0 0 0', items: [
435 {type: 'elementpath'}
439 editor.fire('BeforeRenderUI');
440 panel.renderTo(inlineToolbarContainer || document.body).reflow();
442 addAccessibilityKeys(panel);
445 editor.on('nodeChange', reposition);
446 editor.on('activate', show);
447 editor.on('deactivate', hide);
449 editor.nodeChanged();
452 settings.content_editable = true;
454 editor.on('focus', function() {
455 // Render only when the CSS file has been loaded
456 if (args.skinUiCss) {
457 tinymce.DOM.styleSheetLoader.load(args.skinUiCss, render, render);
463 editor.on('blur hide', hide);
465 // Remove the panel when the editor is removed
466 editor.on('remove', function() {
474 if (args.skinUiCss) {
475 tinymce.DOM.styleSheetLoader.load(args.skinUiCss);
482 * Renders the iframe editor UI.
484 * @param {Object} args Details about target element etc.
485 * @return {Object} Name/value object with theme data.
487 function renderIframeUI(args) {
488 var panel, resizeHandleCtrl, startSize;
490 if (args.skinUiCss) {
491 tinymce.DOM.loadCSS(args.skinUiCss);
495 panel = self.panel = Factory.create({
499 style: 'visibility: hidden',
503 settings.menubar === false ? null : {type: 'menubar', border: '0 0 1 0', items: createMenuButtons()},
505 {type: 'panel', name: 'iframe', layout: 'stack', classes: 'edit-area', html: '', border: '1 0 0 0'}
509 if (settings.resize !== false) {
511 type: 'resizehandle',
512 direction: settings.resize,
514 onResizeStart: function() {
515 var elm = editor.getContentAreaContainer().firstChild;
518 width: elm.clientWidth,
519 height: elm.clientHeight
523 onResize: function(e) {
524 if (settings.resize == 'both') {
525 resizeTo(startSize.width + e.deltaX, startSize.height + e.deltaY);
527 resizeTo(null, startSize.height + e.deltaY);
533 // Add statusbar if needed
534 if (settings.statusbar !== false) {
535 panel.add({type: 'panel', name: 'statusbar', classes: 'statusbar', layout: 'flow', border: '1 0 0 0', ariaRoot: true, items: [
536 {type: 'elementpath'},
541 if (settings.readonly) {
542 panel.find('*').disabled(true);
545 editor.fire('BeforeRenderUI');
546 panel.renderBefore(args.targetNode).reflow();
548 if (settings.width) {
549 tinymce.DOM.setStyle(panel.getEl(), 'width', settings.width);
552 // Remove the panel when the editor is removed
553 editor.on('remove', function() {
558 // Add accesibility shortkuts
559 addAccessibilityKeys(panel);
562 iframeContainer: panel.find('#iframe')[0].getEl(),
563 editorContainer: panel.getEl()
568 * Renders the UI for the theme. This gets called by the editor.
570 * @param {Object} args Details about target element etc.
571 * @return {Object} Theme UI data items.
573 self.renderUI = function(args) {
574 var skin = settings.skin !== false ? settings.skin || 'lightgray' : false;
577 var skinUrl = settings.skin_url;
580 skinUrl = editor.documentBaseURI.toAbsolute(skinUrl);
582 skinUrl = tinymce.baseURL + '/skins/' + skin;
585 // Load special skin for IE7
586 // TODO: Remove this when we drop IE7 support
587 if (tinymce.Env.documentMode <= 7) {
588 args.skinUiCss = skinUrl + '/skin.ie7.min.css';
590 args.skinUiCss = skinUrl + '/skin.min.css';
593 // Load content.min.css or content.inline.min.css
594 editor.contentCSS.push(skinUrl + '/content' + (editor.inline ? '.inline' : '') + '.min.css');
597 // Handle editor setProgressState change
598 editor.on('ProgressState', function(e) {
599 self.throbber = self.throbber || new tinymce.ui.Throbber(self.panel.getEl('body'));
602 self.throbber.show(e.time);
604 self.throbber.hide();
608 if (settings.inline) {
609 return renderInlineUI(args);
612 return renderIframeUI(args);
615 self.resizeTo = resizeTo;
616 self.resizeBy = resizeBy;