f42c70a5f867da65c9039c381a63ae474e0ea4a0
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / ContextMenu / context-menu.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * Copyright (c) 2003 dynarch.com. Authored by Mihai Bazon. Sponsored by www.americanbible.org.
5 * Copyright (c) 2004-2010 Stanislas Rolland <typo3(arobas)sjbr.ca>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This script is a modified version of a script published under the htmlArea License.
26 * A copy of the htmlArea License may be found in the textfile HTMLAREA_LICENSE.txt.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30 /*
31 * Context Menu Plugin for TYPO3 htmlArea RTE
32 *
33 * TYPO3 SVN ID: $Id$
34 */
35 HTMLArea.ContextMenu = HTMLArea.Plugin.extend({
36 constructor : function(editor, pluginName) {
37 this.base(editor, pluginName);
38 },
39 /*
40 * This function gets called by the class constructor
41 */
42 configurePlugin : function(editor) {
43 this.pageTSConfiguration = this.editorConfiguration.contextMenu;
44 if (!this.pageTSConfiguration) {
45 this.pageTSConfiguration = {};
46 }
47 if (this.pageTSConfiguration.showButtons) {
48 this.showButtons = this.pageTSConfiguration.showButtons;
49 }
50 if (this.pageTSConfiguration.hideButtons) {
51 this.hideButtons = this.pageTSConfiguration.hideButtons;
52 }
53 /*
54 * Registering plugin "About" information
55 */
56 var pluginInformation = {
57 version : '3.1',
58 developer : 'Mihai Bazon & Stanislas Rolland',
59 developerUrl : 'http://www.sjbr.ca/',
60 copyrightOwner : 'dynarch.com & Stanislas Rolland',
61 sponsor : 'American Bible Society & SJBR',
62 sponsorUrl : 'http://www.sjbr.ca/',
63 license : 'GPL'
64 };
65 this.registerPluginInformation(pluginInformation);
66 return true;
67 },
68 /*
69 * This function gets called when the editor gets generated
70 */
71 onGenerate: function() {
72 // Build the context menu
73 this.menu = new Ext.menu.Menu(Ext.applyIf({
74 cls: 'htmlarea-context-menu',
75 defaultType: 'menuitem',
76 listeners: {
77 itemClick: {
78 fn: this.onItemClick,
79 scope: this
80 },
81 show: {
82 fn: this.onShow,
83 scope: this
84 },
85 hide: {
86 fn: this.onHide,
87 scope: this
88 }
89 },
90 items: this.buildItemsConfig()
91 }, this.pageTSConfiguration));
92 // Monitor contextmenu clicks on the iframe
93 this.menu.mon(Ext.get(this.editor.document.documentElement), 'contextmenu', this.show, this, {single: true});
94 // Monitor editor being destroyed
95 this.menu.mon(this.editor, 'beforedestroy', this.onBeforeDestroy, this, {single: true});
96 },
97 /*
98 * Create the menu items config
99 */
100 buildItemsConfig: function () {
101 var itemsConfig = [];
102 // Walk through the editor toolbar configuration nested arrays: [ toolbar [ row [ group ] ] ]
103 var firstInGroup = true, convertedItemId;
104 Ext.each(this.editor.config.toolbar, function (row) {
105 // Add the groups
106 firstInGroup = true;
107 Ext.each(row, function (group) {
108 if (!firstInGroup) {
109 // If a visible item was added to the line
110 itemsConfig.push({
111 xtype: 'menuseparator',
112 cls: 'separator'
113 });
114 }
115 firstInGroup = true;
116 // Add each item
117 Ext.each(group, function (itemId) {
118 convertedItemId = this.editorConfiguration.convertButtonId[itemId];
119 if ((!this.showButtons || this.showButtons.indexOf(convertedItemId) !== -1)
120 && (!this.hideButtons || this.hideButtons.indexOf(convertedItemId) === -1)) {
121 var button = this.getButton(itemId);
122 if (button && button.getXType() === 'htmlareabutton' && !button.hideInContextMenu) {
123 var itemId = button.getItemId();
124 itemsConfig.push({
125 itemId: itemId,
126 cls: 'button',
127 overCls: 'hover',
128 text: (button.contextMenuTitle || button.tooltip.title),
129 iconCls: button.iconCls,
130 helpText: this.localize(itemId + '-helpText') || this.localize(itemId + '-tooltip'),
131 hidden: true
132 });
133 firstInGroup = false;
134 }
135 }
136 return true;
137 }, this);
138 return true;
139 }, this);
140 return true;
141 }, this);
142 // If a visible item was added
143 if (!firstInGroup) {
144 itemsConfig.push({
145 xtype: 'menuseparator',
146 cls: 'separator'
147 });
148 }
149 // Add special target delete item
150 var itemId = 'DeleteTarget';
151 itemsConfig.push({
152 itemId: itemId,
153 cls: 'button',
154 overCls: 'hover',
155 iconCls: 'htmlarea-action-delete-item',
156 helpText: this.localize('Remove this node from the document')
157 });
158 return itemsConfig;
159 },
160 /*
161 * Handler when the menu gets shown
162 */
163 onShow: function () {
164 this.menu.mun(Ext.get(this.editor.document.documentElement), 'contextmenu', this.show, this);
165 this.menu.mon(Ext.get(this.editor.document.documentElement), 'mousedown', this.menu.hide, this.menu, {single: true});
166 },
167 /*
168 * Handler when the menu gets hidden
169 */
170 onHide: function () {
171 this.menu.mon(Ext.get(this.editor.document.documentElement), 'contextmenu', this.show, this);
172 this.menu.mun(Ext.get(this.editor.document.documentElement), 'mousedown', this.menu.hide, this.menu);
173 },
174 /*
175 * Handler to show the context menu
176 */
177 show: function (event, target) {
178 // Need to wait a while for the toolbar state to be updated
179 this.showMenu.defer(150, this, [event, target]);
180 event.stopEvent();
181 return false;
182 },
183 /*
184 * Show the context menu
185 */
186 showMenu: function (event, target) {
187 this.showContextItems(target);
188 var iframeEl = this.editor.iframe.getEl();
189 this.menu.showAt([Ext.get(target).getX() + iframeEl.getX(), Ext.get(target).getY() + iframeEl.getY()]);
190 },
191 /*
192 * Show items depending on context
193 */
194 showContextItems: function (target) {
195 var lastIsSeparator = false, lastIsButton = false, xtype, lastVisible;
196 this.menu.cascade(function (menuItem) {
197 xtype = menuItem.getXType();
198 if (xtype === 'menuseparator') {
199 menuItem.setVisible(lastIsButton);
200 lastIsButton = false;
201 } else if (xtype === 'menuitem') {
202 var button = this.getButton(menuItem.getItemId());
203 if (button) {
204 menuItem.setText(button.tooltip.title);
205 menuItem.setVisible(!button.disabled);
206 lastIsButton = lastIsButton || !button.disabled;
207 } else {
208 // Special target delete item
209 this.deleteTarget = Ext.get(target);
210 if (/^(html|body)$/i.test(target.nodeName)) {
211 this.deleteTarget = null;
212 } else if (/^(table|thead|tbody|tr|td|th|tfoot)$/i.test(target.nodeName)) {
213 var targetAncestor = this.deleteTarget.findParent('table');
214 } else if (/^(ul|ol|dl|li|dd|dt)$/i.test(target.nodeName)) {
215 var targetAncestor = this.deleteTarget.findParent('ul') || this.deleteTarget.findParent('ol') || this.deleteTarget.findParent('dl');
216 }
217 if (targetAncestor) {
218 this.deleteTarget = Ext.get(targetAncestor);
219 }
220 if (this.deleteTarget) {
221 menuItem.setVisible(true);
222 menuItem.setText(this.localize('Remove the') + ' &lt;' + this.deleteTarget.dom.nodeName.toLowerCase() + '&gt; ');
223 lastIsButton = true;
224 } else {
225 menuItem.setVisible(false);
226 }
227 }
228 }
229 if (!menuItem.hidden) {
230 lastVisible = menuItem;
231 }
232 }, this);
233 // Hide the last item if it is a separator
234 if (!lastIsButton) {
235 lastVisible.setVisible(false);
236 }
237 },
238 /*
239 * Handler invoked when a menu item is clicked on
240 */
241 onItemClick: function (item, event) {
242 var button = this.getButton(item.getItemId());
243 if (button) {
244 button.fireEvent('context', button, event);
245 } else if (item.getItemId() === 'DeleteTarget') {
246 // Do not leave a non-ie table cell empty
247 var parent = this.deleteTarget.parent().dom;
248 parent.normalize();
249 if (!Ext.isIE && /^(td|th)$/i.test(parent.nodeName) && parent.childNodes.length == 1) {
250 // Do not leave a non-ie table cell empty
251 this.deleteTarget.insertSibling(this.editor.document.createElement('br'));
252 }
253 // Try to find a reasonable replacement selection
254 var nextSibling = this.deleteTarget.dom.nextSibling;
255 var previousSibling = this.deleteTarget.dom.previousSibling;
256 if (nextSibling) {
257 this.editor.selectNode(nextSibling, true);
258 } else if (previousSibling) {
259 this.editor.selectNode(previousSibling, false);
260 }
261 this.deleteTarget.remove();
262 this.editor.updateToolbar();
263 }
264 },
265 /*
266 * Handler invoked when the editor is about to be destroyed
267 */
268 onBeforeDestroy: function () {
269 this.menu.items.each(function (menuItem) {
270 Ext.QuickTips.unregister(menuItem);
271 });
272 this.menu.removeAll(true);
273 this.menu.destroy();
274 }
275 });