|
1
|
|
|
/** |
|
2
|
|
|
* EGroupware - Filemanager - Javascript UI |
|
3
|
|
|
* |
|
4
|
|
|
* @link http://www.egroupware.org |
|
5
|
|
|
* @package filemanager |
|
6
|
|
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de> |
|
7
|
|
|
* @copyright (c) 2008-14 by Ralf Becker <RalfBecker-AT-outdoor-training.de> |
|
8
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License |
|
9
|
|
|
* @version $Id$ |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
/** |
|
13
|
|
|
* UI for filemanager |
|
14
|
|
|
* |
|
15
|
|
|
* @augments AppJS |
|
16
|
|
|
*/ |
|
17
|
|
|
app.classes.filemanager = AppJS.extend( |
|
18
|
|
|
{ |
|
19
|
|
|
appname: 'filemanager', |
|
20
|
|
|
/** |
|
21
|
|
|
* path widget, by template |
|
22
|
|
|
*/ |
|
23
|
|
|
path_widget: {}, |
|
24
|
|
|
/** |
|
25
|
|
|
* Are files cut into clipboard - need to be deleted at source on paste |
|
26
|
|
|
*/ |
|
27
|
|
|
clipboard_is_cut: false, |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* Constructor |
|
31
|
|
|
* |
|
32
|
|
|
* @memberOf app.filemanager |
|
33
|
|
|
*/ |
|
34
|
|
|
init: function() |
|
35
|
|
|
{ |
|
36
|
|
|
// call parent |
|
37
|
|
|
this._super.apply(this, arguments); |
|
38
|
|
|
|
|
39
|
|
|
// Loading filemanager in its tab and home causes us problems with |
|
40
|
|
|
// unwanted destruction, so we check for already existing path widgets |
|
41
|
|
|
var lists = etemplate2.getByApplication('home'); |
|
42
|
|
|
for(var i = 0; i < lists.length; i++) |
|
43
|
|
|
{ |
|
44
|
|
|
if(lists[i].app == 'filemanager' && lists[i].widgetContainer.getWidgetById('path')) |
|
45
|
|
|
{ |
|
46
|
|
|
this.path_widget[lists[i].uniqueId] = lists[i].widgetContainer.getWidgetById('path'); |
|
47
|
|
|
} |
|
48
|
|
|
} |
|
49
|
|
|
}, |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* Destructor |
|
53
|
|
|
*/ |
|
54
|
|
|
destroy: function() |
|
55
|
|
|
{ |
|
56
|
|
|
delete this.et2; |
|
57
|
|
|
// call parent |
|
58
|
|
|
this._super.apply(this, arguments); |
|
59
|
|
|
}, |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* This function is called when the etemplate2 object is loaded |
|
63
|
|
|
* and ready. If you must store a reference to the et2 object, |
|
64
|
|
|
* make sure to clean it up in destroy(). |
|
65
|
|
|
* |
|
66
|
|
|
* @param et2 etemplate2 Newly ready object |
|
67
|
|
|
* @param {string} name template name |
|
68
|
|
|
*/ |
|
69
|
|
|
et2_ready: function(et2,name) |
|
70
|
|
|
{ |
|
71
|
|
|
// call parent |
|
72
|
|
|
this._super.apply(this, arguments); |
|
73
|
|
|
|
|
74
|
|
|
var path_widget = this.et2.getWidgetById('path'); |
|
75
|
|
|
if(path_widget) // do NOT set not found path-widgets, as uploads works on first one only! |
|
76
|
|
|
{ |
|
77
|
|
|
this.path_widget[et2.DOMContainer.id] = path_widget; |
|
78
|
|
|
// Bind to removal to remove from list |
|
79
|
|
|
jQuery(et2.DOMContainer).on('clear', function(e) { |
|
80
|
|
|
if (app.filemanager && app.filemanager.path_widget) delete app.filemanager.path_widget[e.target.id]; |
|
81
|
|
|
}); |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
if(this.et2.getWidgetById('nm')) |
|
85
|
|
|
{ |
|
86
|
|
|
// Legacy JS only supports 2 arguments (event and widget), so set |
|
87
|
|
|
// to the actual function here |
|
88
|
|
|
this.et2.getWidgetById('nm').set_onfiledrop(jQuery.proxy(this.filedrop, this)); |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
|
|
// get clipboard from browser localstore and update button tooltips |
|
92
|
|
|
this.clipboard_tooltips(); |
|
93
|
|
|
|
|
94
|
|
|
// calling set_readonly for initial path |
|
95
|
|
|
if (this.et2.getArrayMgr('content').getEntry('initial_path_readonly')) |
|
96
|
|
|
{ |
|
97
|
|
|
this.readonly = [this.et2.getArrayMgr('content').getEntry('nm[path]'), true]; |
|
98
|
|
|
} |
|
99
|
|
|
if (typeof this.readonly != 'undefined') |
|
100
|
|
|
{ |
|
101
|
|
|
this.set_readonly.apply(this, this.readonly); |
|
102
|
|
|
delete this.readonly; |
|
103
|
|
|
} |
|
104
|
|
|
}, |
|
105
|
|
|
|
|
106
|
|
|
/** |
|
107
|
|
|
* Set the application's state to the given state. |
|
108
|
|
|
* |
|
109
|
|
|
* Extended from parent to also handle view |
|
110
|
|
|
* |
|
111
|
|
|
* |
|
112
|
|
|
* @param {{name: string, state: object}|string} state Object (or JSON string) for a state. |
|
113
|
|
|
* Only state is required, and its contents are application specific. |
|
114
|
|
|
* |
|
115
|
|
|
* @return {boolean} false - Returns false to stop event propagation |
|
116
|
|
|
*/ |
|
117
|
|
|
setState: function(state) |
|
118
|
|
|
{ |
|
119
|
|
|
// State should be an object, not a string, but we'll parse |
|
120
|
|
|
if(typeof state == "string") |
|
121
|
|
|
{ |
|
122
|
|
|
if(state.indexOf('{') != -1 || state =='null') |
|
123
|
|
|
{ |
|
124
|
|
|
state = JSON.parse(state); |
|
125
|
|
|
} |
|
126
|
|
|
} |
|
127
|
|
|
var result = this._super.call(this, state, 'filemanager.index'); |
|
128
|
|
|
|
|
129
|
|
|
// This has to happen after the parent, changing to tile recreates |
|
130
|
|
|
// nm controller |
|
131
|
|
|
if(typeof state == "object" && state.state && state.state.view) |
|
132
|
|
|
{ |
|
133
|
|
|
var et2 = etemplate2.getById('filemanager-index'); |
|
134
|
|
|
if(et2) |
|
135
|
|
|
{ |
|
136
|
|
|
this.et2 = et2.widgetContainer; |
|
137
|
|
|
this.change_view(state.state.view); |
|
138
|
|
|
} |
|
139
|
|
|
} |
|
140
|
|
|
return result; |
|
141
|
|
|
}, |
|
142
|
|
|
|
|
143
|
|
|
/** |
|
144
|
|
|
* Retrieve the current state of the application for future restoration |
|
145
|
|
|
* |
|
146
|
|
|
* Extended from parent to also set view |
|
147
|
|
|
* |
|
148
|
|
|
* @return {object} Application specific map representing the current state |
|
149
|
|
|
*/ |
|
150
|
|
|
getState: function() |
|
151
|
|
|
{ |
|
152
|
|
|
var state = this._super.apply(this); |
|
153
|
|
|
|
|
154
|
|
|
var et2 = etemplate2.getById('filemanager-index'); |
|
155
|
|
|
if(et2) |
|
156
|
|
|
{ |
|
157
|
|
|
var nm = et2.widgetContainer.getWidgetById('nm'); |
|
158
|
|
|
state.view = nm.view; |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
return state; |
|
162
|
|
|
}, |
|
163
|
|
|
|
|
164
|
|
|
/** |
|
165
|
|
|
* Regexp to convert id to a path, use this.id2path(_id) |
|
166
|
|
|
*/ |
|
167
|
|
|
remove_prefix: /^filemanager::/, |
|
168
|
|
|
/** |
|
169
|
|
|
* Convert id to path (remove "filemanager::" prefix) |
|
170
|
|
|
* |
|
171
|
|
|
* @param {string} _id |
|
172
|
|
|
* @returns string |
|
173
|
|
|
*/ |
|
174
|
|
|
id2path: function(_id) |
|
175
|
|
|
{ |
|
176
|
|
|
return _id.replace(this.remove_prefix, ''); |
|
177
|
|
|
}, |
|
178
|
|
|
|
|
179
|
|
|
/** |
|
180
|
|
|
* Convert array of elems to array of paths |
|
181
|
|
|
* |
|
182
|
|
|
* @param {egwActionObject[]} _elems selected items from actions |
|
183
|
|
|
* @return array |
|
184
|
|
|
*/ |
|
185
|
|
|
_elems2paths: function(_elems) |
|
186
|
|
|
{ |
|
187
|
|
|
var paths = []; |
|
188
|
|
|
for (var i = 0; i < _elems.length; i++) |
|
189
|
|
|
{ |
|
190
|
|
|
// If selected has no id, try parent. This happens for the placeholder row |
|
191
|
|
|
// in empty directories. |
|
192
|
|
|
paths.push(_elems[i].id? this.id2path(_elems[i].id) : _elems[i]._context._parentId); |
|
193
|
|
|
} |
|
194
|
|
|
return paths; |
|
195
|
|
|
}, |
|
196
|
|
|
|
|
197
|
|
|
/** |
|
198
|
|
|
* Get directory of a path |
|
199
|
|
|
* |
|
200
|
|
|
* @param {string} _path |
|
201
|
|
|
* @returns string |
|
202
|
|
|
*/ |
|
203
|
|
|
dirname: function(_path) |
|
204
|
|
|
{ |
|
205
|
|
|
var parts = _path.split('/'); |
|
206
|
|
|
parts.pop(); |
|
207
|
|
|
return parts.join('/') || '/'; |
|
208
|
|
|
}, |
|
209
|
|
|
|
|
210
|
|
|
/** |
|
211
|
|
|
* Get name of a path |
|
212
|
|
|
* |
|
213
|
|
|
* @param {string} _path |
|
214
|
|
|
* @returns string |
|
215
|
|
|
*/ |
|
216
|
|
|
basename: function(_path) |
|
217
|
|
|
{ |
|
218
|
|
|
return _path.split('/').pop(); |
|
219
|
|
|
}, |
|
220
|
|
|
|
|
221
|
|
|
/** |
|
222
|
|
|
* Get current working directory |
|
223
|
|
|
* |
|
224
|
|
|
* @param {string} etemplate_name |
|
225
|
|
|
* @return string |
|
226
|
|
|
*/ |
|
227
|
|
|
get_path: function(etemplate_name) |
|
228
|
|
|
{ |
|
229
|
|
|
if(!etemplate_name || typeof this.path_widget[etemplate_name] == 'undefined') |
|
230
|
|
|
{ |
|
231
|
|
|
for(etemplate_name in this.path_widget) break; |
|
232
|
|
|
} |
|
233
|
|
|
return this.path_widget[etemplate_name] ? this.path_widget[etemplate_name].get_value() : null; |
|
234
|
|
|
}, |
|
235
|
|
|
|
|
236
|
|
|
/** |
|
237
|
|
|
* Open compose with already attached files |
|
238
|
|
|
* |
|
239
|
|
|
* @param {(string|string[])} attachments path(s) |
|
240
|
|
|
* @param {object} params |
|
241
|
|
|
*/ |
|
242
|
|
|
open_mail: function(attachments, params) |
|
243
|
|
|
{ |
|
244
|
|
|
if (typeof attachments == 'undefined') attachments = this.get_clipboard_files(); |
|
245
|
|
|
if (!params || typeof params != 'object') params = {}; |
|
246
|
|
|
if (!(attachments instanceof Array)) attachments = [ attachments ]; |
|
247
|
|
|
for(var i=0; i < attachments.length; i++) |
|
248
|
|
|
{ |
|
249
|
|
|
params['preset[file]['+i+']'] = 'vfs://default'+attachments[i]; |
|
250
|
|
|
} |
|
251
|
|
|
// always open compose in html mode, as attachment links look a lot nicer in html |
|
252
|
|
|
params.mimeType = 'html'; |
|
253
|
|
|
egw.open('', 'mail', 'add', params); |
|
254
|
|
|
}, |
|
255
|
|
|
|
|
256
|
|
|
/** |
|
257
|
|
|
* Mail files action: open compose with already attached files |
|
258
|
|
|
* |
|
259
|
|
|
* @param _action |
|
260
|
|
|
* @param _elems |
|
261
|
|
|
*/ |
|
262
|
|
|
mail: function(_action, _elems) |
|
263
|
|
|
{ |
|
264
|
|
|
this.open_mail(this._elems2paths(_elems), { |
|
265
|
|
|
'preset[filemode]': _action.id.substr(5) |
|
266
|
|
|
}); |
|
267
|
|
|
}, |
|
268
|
|
|
|
|
269
|
|
|
/** |
|
270
|
|
|
* Trigger Upload after each file is uploaded |
|
271
|
|
|
* @param {type} _event |
|
272
|
|
|
*/ |
|
273
|
|
|
uploadOnOne: function (_event) |
|
274
|
|
|
{ |
|
275
|
|
|
this.upload(_event,1); |
|
276
|
|
|
}, |
|
277
|
|
|
|
|
278
|
|
|
/** |
|
279
|
|
|
* Send names of uploaded files (again) to server, to process them: either copy to vfs or ask overwrite/rename |
|
280
|
|
|
* |
|
281
|
|
|
* @param {event} _event |
|
282
|
|
|
* @param {number} _file_count |
|
283
|
|
|
* @param {string=} _path where the file is uploaded to, default current directory |
|
284
|
|
|
*/ |
|
285
|
|
|
upload: function(_event, _file_count, _path) |
|
286
|
|
|
{ |
|
287
|
|
|
if(typeof _path == 'undefined') |
|
288
|
|
|
{ |
|
289
|
|
|
_path = this.get_path(); |
|
290
|
|
|
} |
|
291
|
|
|
if (_file_count && !jQuery.isEmptyObject(_event.data.getValue())) |
|
292
|
|
|
{ |
|
293
|
|
|
var widget = _event.data; |
|
294
|
|
|
var request = egw.json('filemanager_ui::ajax_action', ['upload', widget.getValue(), _path], |
|
295
|
|
|
this._upload_callback, this, true, this |
|
296
|
|
|
).sendRequest(); |
|
297
|
|
|
widget.set_value(''); |
|
298
|
|
|
} |
|
299
|
|
|
}, |
|
300
|
|
|
|
|
301
|
|
|
/** |
|
302
|
|
|
* Finish callback for file a file dialog, to get the overwrite / rename prompt |
|
303
|
|
|
* |
|
304
|
|
|
* @param {event} _event |
|
305
|
|
|
* @param {number} _file_count |
|
306
|
|
|
*/ |
|
307
|
|
|
file_a_file_upload: function(_event, _file_count) |
|
308
|
|
|
{ |
|
309
|
|
|
var widget = _event.data; |
|
310
|
|
|
var value = widget.getValue(); |
|
311
|
|
|
var path = widget.getRoot().getWidgetById("path").getValue(); |
|
312
|
|
|
var action = widget.getRoot().getWidgetById("action").getValue(); |
|
313
|
|
|
var link = widget.getRoot().getWidgetById("entry").getValue(); |
|
314
|
|
|
if(action == 'save_as' && link.app && link.id) |
|
315
|
|
|
{ |
|
316
|
|
|
path = "/apps/"+link.app+"/"+link.id; |
|
317
|
|
|
} |
|
318
|
|
|
|
|
319
|
|
|
var props = widget.getInstanceManager().getValues(widget.getRoot()); |
|
320
|
|
|
egw.json('filemanager_ui::ajax_action', [action == 'save_as' ? 'upload' : 'link', widget.getValue(), path, props], |
|
321
|
|
|
function(_data) |
|
322
|
|
|
{ |
|
323
|
|
|
app.filemanager._upload_callback(_data); |
|
324
|
|
|
|
|
325
|
|
|
// Remove successful after a delay |
|
326
|
|
|
for(var file in _data.uploaded) |
|
327
|
|
|
{ |
|
328
|
|
|
if(!_data.uploaded[file].confirm || _data.uploaded[file].confirmed) |
|
329
|
|
|
{ |
|
330
|
|
|
// Remove that file from file widget... |
|
331
|
|
|
widget.remove_file(_data.uploaded[file].name); |
|
332
|
|
|
} |
|
333
|
|
|
} |
|
334
|
|
|
opener.egw_refresh('','filemanager',null,null,'filemanager'); |
|
335
|
|
|
}, app.filemanager, true, this |
|
336
|
|
|
).sendRequest(true); |
|
337
|
|
|
return true; |
|
338
|
|
|
}, |
|
339
|
|
|
|
|
340
|
|
|
|
|
341
|
|
|
/** |
|
342
|
|
|
* Callback for server response to upload request: |
|
343
|
|
|
* - display message and refresh list |
|
344
|
|
|
* - ask use to confirm overwritting existing files or rename upload |
|
345
|
|
|
* |
|
346
|
|
|
* @param {object} _data values for attributes msg, files, ... |
|
347
|
|
|
*/ |
|
348
|
|
|
_upload_callback: function(_data) |
|
349
|
|
|
{ |
|
350
|
|
|
if (_data.msg || _data.uploaded) window.egw_refresh(_data.msg, this.appname); |
|
351
|
|
|
|
|
352
|
|
|
var that = this; |
|
353
|
|
|
for(var file in _data.uploaded) |
|
354
|
|
|
{ |
|
355
|
|
|
if (_data.uploaded[file].confirm && !_data.uploaded[file].confirmed) |
|
356
|
|
|
{ |
|
357
|
|
|
var buttons = [ |
|
358
|
|
|
{text: this.egw.lang("Yes"), id: "overwrite", class: "ui-priority-primary", "default": true, image: 'check',}, |
|
359
|
|
|
{text: this.egw.lang("Rename"), id:"rename", image: 'edit',}, |
|
360
|
|
|
{text: this.egw.lang("Cancel"), id:"cancel"} |
|
361
|
|
|
]; |
|
362
|
|
|
if (_data.uploaded[file].confirm === "is_dir") |
|
363
|
|
|
buttons.shift(); |
|
364
|
|
|
var dialog = et2_dialog.show_prompt(function(_button_id, _value) { |
|
365
|
|
|
var uploaded = {}; |
|
366
|
|
|
uploaded[this.my_data.file] = this.my_data.data; |
|
367
|
|
|
switch (_button_id) |
|
|
|
|
|
|
368
|
|
|
{ |
|
369
|
|
|
case "overwrite": |
|
370
|
|
|
uploaded[this.my_data.file].confirmed = true; |
|
371
|
|
|
// fall through |
|
372
|
|
|
case "rename": |
|
373
|
|
|
uploaded[this.my_data.file].name = _value; |
|
374
|
|
|
delete uploaded[this.my_data.file].confirm; |
|
375
|
|
|
// send overwrite-confirmation and/or rename request to server |
|
376
|
|
|
egw.json('filemanager_ui::ajax_action', [this.my_data.action, uploaded, this.my_data.path, this.my_data.props], |
|
377
|
|
|
that._upload_callback, that, true, that |
|
378
|
|
|
).sendRequest(); |
|
379
|
|
|
return; |
|
380
|
|
|
case "cancel": |
|
381
|
|
|
// Remove that file from every file widget... |
|
382
|
|
|
that.et2.iterateOver(function(_widget) { |
|
383
|
|
|
_widget.remove_file(this.my_data.data.name); |
|
384
|
|
|
}, this, et2_file); |
|
385
|
|
|
} |
|
386
|
|
|
}, |
|
387
|
|
|
_data.uploaded[file].confirm === "is_dir" ? |
|
388
|
|
|
this.egw.lang("There's already a directory with that name!") : |
|
389
|
|
|
this.egw.lang('Do you want to overwrite existing file %1 in directory %2?', _data.uploaded[file].name, _data.path), |
|
390
|
|
|
this.egw.lang('File %1 already exists', _data.uploaded[file].name), |
|
391
|
|
|
_data.uploaded[file].name, buttons, file); |
|
392
|
|
|
// setting required data for callback in as my_data |
|
393
|
|
|
dialog.my_data = { |
|
394
|
|
|
action: _data.action, |
|
395
|
|
|
file: file, |
|
396
|
|
|
path: _data.path, |
|
397
|
|
|
data: _data.uploaded[file], |
|
398
|
|
|
props: _data.props |
|
399
|
|
|
}; |
|
400
|
|
|
} |
|
401
|
|
|
} |
|
402
|
|
|
}, |
|
403
|
|
|
|
|
404
|
|
|
/** |
|
405
|
|
|
* Get any files that are in the system clipboard |
|
406
|
|
|
* |
|
407
|
|
|
* @return {string[]} Paths |
|
408
|
|
|
*/ |
|
409
|
|
|
get_clipboard_files: function() |
|
410
|
|
|
{ |
|
411
|
|
|
var clipboard_files = []; |
|
412
|
|
|
if (typeof window.localStorage != 'undefined' && typeof egw.getSessionItem('phpgwapi', 'egw_clipboard') != 'undefined') |
|
413
|
|
|
{ |
|
414
|
|
|
var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')) || { |
|
415
|
|
|
type:[], |
|
416
|
|
|
selected:[] |
|
417
|
|
|
}; |
|
418
|
|
|
if(clipboard.type.indexOf('file') >= 0) |
|
419
|
|
|
{ |
|
420
|
|
|
for(var i = 0; i < clipboard.selected.length; i++) |
|
421
|
|
|
{ |
|
422
|
|
|
var split = clipboard.selected[i].id.split('::'); |
|
423
|
|
|
if(split[0] == 'filemanager') |
|
424
|
|
|
{ |
|
425
|
|
|
clipboard_files.push(this.id2path(clipboard.selected[i].id)); |
|
426
|
|
|
} |
|
427
|
|
|
} |
|
428
|
|
|
} |
|
429
|
|
|
} |
|
430
|
|
|
return clipboard_files; |
|
431
|
|
|
}, |
|
432
|
|
|
|
|
433
|
|
|
/** |
|
434
|
|
|
* Update clickboard tooltips in buttons |
|
435
|
|
|
*/ |
|
436
|
|
|
clipboard_tooltips: function() |
|
437
|
|
|
{ |
|
438
|
|
|
var paste_buttons = ['button[paste]', 'button[linkpaste]', 'button[mailpaste]']; |
|
439
|
|
|
for(var i=0; i < paste_buttons.length; ++i) |
|
440
|
|
|
{ |
|
441
|
|
|
var button = this.et2.getWidgetById(paste_buttons[i]); |
|
442
|
|
|
if (button) button.set_statustext(this.get_clipboard_files().join(",\n")); |
|
443
|
|
|
} |
|
444
|
|
|
}, |
|
445
|
|
|
|
|
446
|
|
|
/** |
|
447
|
|
|
* Clip files into clipboard |
|
448
|
|
|
* |
|
449
|
|
|
* @param _action |
|
450
|
|
|
* @param _elems |
|
451
|
|
|
*/ |
|
452
|
|
|
clipboard: function(_action, _elems) |
|
453
|
|
|
{ |
|
454
|
|
|
this.clipboard_is_cut = _action.id == "cut"; |
|
455
|
|
|
var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')) || { |
|
456
|
|
|
type:[], |
|
457
|
|
|
selected:[] |
|
458
|
|
|
}; |
|
459
|
|
|
if(_action.id != "add") |
|
460
|
|
|
{ |
|
461
|
|
|
clipboard = { |
|
462
|
|
|
type:[], |
|
463
|
|
|
selected:[] |
|
464
|
|
|
}; |
|
465
|
|
|
}; |
|
466
|
|
|
|
|
467
|
|
|
// When pasting we need to know the type of data - pull from actions |
|
468
|
|
|
var drag = _elems[0].getSelectedLinks('drag').links; |
|
469
|
|
|
for(var k in drag) |
|
470
|
|
|
{ |
|
471
|
|
|
if(drag[k].enabled && drag[k].actionObj.dragType.length > 0) |
|
472
|
|
|
{ |
|
473
|
|
|
clipboard.type = clipboard.type.concat(drag[k].actionObj.dragType); |
|
474
|
|
|
} |
|
475
|
|
|
} |
|
476
|
|
|
clipboard.type = jQuery.unique(clipboard.type); |
|
477
|
|
|
// egwAction is a circular structure and can't be stringified so just take what we want |
|
478
|
|
|
// Hopefully that's enough for the action handlers |
|
479
|
|
|
for(var k in _elems) |
|
480
|
|
|
{ |
|
481
|
|
|
if(_elems[k].id) clipboard.selected.push({id:_elems[k].id, data:_elems[k].data}); |
|
482
|
|
|
} |
|
483
|
|
|
|
|
484
|
|
|
// Save it in session |
|
485
|
|
|
egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify(clipboard)); |
|
486
|
|
|
|
|
487
|
|
|
this.clipboard_tooltips(); |
|
488
|
|
|
}, |
|
489
|
|
|
|
|
490
|
|
|
/** |
|
491
|
|
|
* Paste files into current directory or mail them |
|
492
|
|
|
* |
|
493
|
|
|
* @param _type 'paste', 'linkpaste', 'mailpaste' |
|
494
|
|
|
*/ |
|
495
|
|
|
paste: function(_type) |
|
496
|
|
|
{ |
|
497
|
|
|
var clipboard_files = this.get_clipboard_files(); |
|
498
|
|
|
if (clipboard_files.length == 0) |
|
499
|
|
|
{ |
|
500
|
|
|
alert(this.egw.lang('Clipboard is empty!')); |
|
501
|
|
|
return; |
|
502
|
|
|
} |
|
503
|
|
|
switch(_type) |
|
|
|
|
|
|
504
|
|
|
{ |
|
505
|
|
|
case 'mailpaste': |
|
506
|
|
|
this.open_mail(clipboard_files); |
|
507
|
|
|
break; |
|
508
|
|
|
|
|
509
|
|
|
case 'paste': |
|
510
|
|
|
this._do_action(this.clipboard_is_cut ? 'move' : 'copy', clipboard_files); |
|
511
|
|
|
|
|
512
|
|
|
if (this.clipboard_is_cut) |
|
513
|
|
|
{ |
|
514
|
|
|
this.clipboard_is_cut = false; |
|
515
|
|
|
clipboard_files = []; |
|
516
|
|
|
this.clipboard_tooltips(); |
|
517
|
|
|
} |
|
518
|
|
|
break; |
|
519
|
|
|
|
|
520
|
|
|
case 'linkpaste': |
|
521
|
|
|
this._do_action('symlink', clipboard_files); |
|
522
|
|
|
break; |
|
523
|
|
|
} |
|
524
|
|
|
}, |
|
525
|
|
|
|
|
526
|
|
|
/** |
|
527
|
|
|
* Pass action to server |
|
528
|
|
|
* |
|
529
|
|
|
* @param _action |
|
530
|
|
|
* @param _elems |
|
531
|
|
|
*/ |
|
532
|
|
|
action: function(_action, _elems) |
|
533
|
|
|
{ |
|
534
|
|
|
var paths = this._elems2paths(_elems); |
|
535
|
|
|
var path = this.get_path(_action && _action.parent.data.nextmatch.getInstanceManager().uniqueId || false); |
|
536
|
|
|
this._do_action(_action.id, paths,true, path); |
|
537
|
|
|
}, |
|
538
|
|
|
|
|
539
|
|
|
/** |
|
540
|
|
|
* Prompt user for directory to create |
|
541
|
|
|
* |
|
542
|
|
|
* @param {egwAction|undefined|jQuery.Event} action Action, event or undefined if called directly |
|
543
|
|
|
* @param {egwActionObject[] | undefined} selected Selected row, or undefined if called directly |
|
544
|
|
|
*/ |
|
545
|
|
|
createdir: function(action, selected) |
|
546
|
|
|
{ |
|
547
|
|
|
var self = this; |
|
548
|
|
|
et2_dialog.show_prompt(function(button, dir){ |
|
549
|
|
|
if (button && dir) |
|
550
|
|
|
{ |
|
551
|
|
|
var path = self.get_path(action && action.parent ? action.parent.data.nextmatch.getInstanceManager().uniqueId : false); |
|
552
|
|
|
if(action && action instanceof egwAction) |
|
553
|
|
|
{ |
|
554
|
|
|
var paths = self._elems2paths(selected); |
|
555
|
|
|
if(paths[0]) path = paths[0]; |
|
556
|
|
|
// check if target is a file --> use it's directory instead |
|
557
|
|
|
if(selected[0].id || path) |
|
558
|
|
|
{ |
|
559
|
|
|
var data = egw.dataGetUIDdata(selected[0].id || 'filemanager::'+path ); |
|
560
|
|
|
if (data && data.data.mime != 'httpd/unix-directory') |
|
561
|
|
|
{ |
|
562
|
|
|
path = self.dirname(path); |
|
563
|
|
|
} |
|
564
|
|
|
} |
|
565
|
|
|
} |
|
566
|
|
|
self._do_action('createdir', dir, true, path); // true=synchronous request |
|
567
|
|
|
self.change_dir((path == '/' ? '' : path)+'/'+ dir); |
|
568
|
|
|
} |
|
569
|
|
|
},this.egw.lang('New directory'),this.egw.lang('Create directory')); |
|
570
|
|
|
}, |
|
571
|
|
|
|
|
572
|
|
|
/** |
|
573
|
|
|
* Prompt user for directory to create |
|
574
|
|
|
*/ |
|
575
|
|
|
symlink: function() |
|
576
|
|
|
{ |
|
577
|
|
|
var self = this; |
|
578
|
|
|
et2_dialog.show_prompt(function (button, target) { |
|
579
|
|
|
if (button && target) |
|
580
|
|
|
{ |
|
581
|
|
|
self._do_action('symlink', target); |
|
582
|
|
|
} |
|
583
|
|
|
},this.egw.lang('Link target'), this.egw.lang('Create link')); |
|
584
|
|
|
}, |
|
585
|
|
|
|
|
586
|
|
|
/** |
|
587
|
|
|
* Run a serverside action via an ajax call |
|
588
|
|
|
* |
|
589
|
|
|
* @param _type 'move_file', 'copy_file', ... |
|
590
|
|
|
* @param _selected selected paths |
|
591
|
|
|
* @param _sync send a synchronous ajax request |
|
592
|
|
|
* @param _path defaults to current path |
|
593
|
|
|
*/ |
|
594
|
|
|
_do_action: function(_type, _selected, _sync, _path) |
|
595
|
|
|
{ |
|
596
|
|
|
if (typeof _path == 'undefined') _path = this.get_path(); |
|
597
|
|
|
var request = egw.json('filemanager_ui::ajax_action', [_type, _selected, _path], |
|
598
|
|
|
this._do_action_callback, this, !_sync, this |
|
599
|
|
|
).sendRequest(); |
|
600
|
|
|
}, |
|
601
|
|
|
|
|
602
|
|
|
/** |
|
603
|
|
|
* Callback for _do_action ajax call |
|
604
|
|
|
* |
|
605
|
|
|
* @param _data |
|
606
|
|
|
*/ |
|
607
|
|
|
_do_action_callback: function(_data) |
|
608
|
|
|
{ |
|
609
|
|
|
window.egw_refresh(_data.msg, this.appname); |
|
610
|
|
|
}, |
|
611
|
|
|
|
|
612
|
|
|
/** |
|
613
|
|
|
* Force download of a file by appending '?download' to it's download url |
|
614
|
|
|
* |
|
615
|
|
|
* @param _action |
|
616
|
|
|
* @param _senders |
|
617
|
|
|
*/ |
|
618
|
|
|
force_download: function(_action, _senders) |
|
619
|
|
|
{ |
|
620
|
|
|
for(var i = 0; i < _senders.length; i++) |
|
621
|
|
|
{ |
|
622
|
|
|
var data = egw.dataGetUIDdata(_senders[i].id); |
|
623
|
|
|
var url = data ? data.data.download_url : '/webdav.php'+this.id2path(_senders[i].id); |
|
624
|
|
|
if (url[0] == '/') url = egw.link(url); |
|
625
|
|
|
|
|
626
|
|
|
var a = document.createElement('a'); |
|
627
|
|
|
if(typeof a.download == "undefined") |
|
628
|
|
|
{ |
|
629
|
|
|
window.location = url+"?download"; |
|
630
|
|
|
return false; |
|
631
|
|
|
} |
|
632
|
|
|
|
|
633
|
|
|
// Multiple file download for those that support it |
|
634
|
|
|
a = jQuery(a) |
|
635
|
|
|
.prop('href', url) |
|
636
|
|
|
.prop('download', data ? data.data.name : "") |
|
637
|
|
|
.appendTo(this.et2.getDOMNode()); |
|
638
|
|
|
|
|
639
|
|
|
var evt = document.createEvent('MouseEvent'); |
|
640
|
|
|
evt.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); |
|
641
|
|
|
a[0].dispatchEvent(evt); |
|
642
|
|
|
a.remove(); |
|
643
|
|
|
} |
|
644
|
|
|
return false; |
|
645
|
|
|
}, |
|
646
|
|
|
|
|
647
|
|
|
/** |
|
648
|
|
|
* Check to see if the browser supports downloading multiple files |
|
649
|
|
|
* (using a tag download attribute) to enable/disable the context menu |
|
650
|
|
|
* |
|
651
|
|
|
* @param {egwAction} action |
|
652
|
|
|
* @param {egwActionObject[]} selected |
|
653
|
|
|
*/ |
|
654
|
|
|
is_multiple_allowed: function(action, selected) |
|
655
|
|
|
{ |
|
656
|
|
|
var allowed = typeof document.createElement('a').download != "undefined"; |
|
657
|
|
|
|
|
658
|
|
|
if(typeof action == "undefined") return allowed; |
|
659
|
|
|
|
|
660
|
|
|
return (allowed || selected.length <= 1) && action.not_disableClass.apply(action, arguments); |
|
661
|
|
|
}, |
|
662
|
|
|
|
|
663
|
|
|
|
|
664
|
|
|
/** |
|
665
|
|
|
* Change directory |
|
666
|
|
|
* |
|
667
|
|
|
* @param {string} _dir directory to change to incl. '..' for one up |
|
668
|
|
|
* @param {et2_widget} widget |
|
669
|
|
|
*/ |
|
670
|
|
|
change_dir: function(_dir, widget) |
|
671
|
|
|
{ |
|
672
|
|
|
for(var etemplate_name in this.path_widget) break; |
|
673
|
|
|
if (widget) etemplate_name = widget.getInstanceManager().uniqueId; |
|
674
|
|
|
|
|
675
|
|
|
switch (_dir) |
|
|
|
|
|
|
676
|
|
|
{ |
|
677
|
|
|
case '..': |
|
678
|
|
|
_dir = this.dirname(this.get_path(etemplate_name)); |
|
|
|
|
|
|
679
|
|
|
break; |
|
680
|
|
|
case '~': |
|
681
|
|
|
_dir = this.et2.getWidgetById('nm').options.settings.home_dir; |
|
682
|
|
|
break; |
|
683
|
|
|
} |
|
684
|
|
|
|
|
685
|
|
|
this.path_widget[etemplate_name].set_value(_dir); |
|
686
|
|
|
}, |
|
687
|
|
|
|
|
688
|
|
|
/** |
|
689
|
|
|
* Toggle view between tiles and rows |
|
690
|
|
|
* |
|
691
|
|
|
* @param {string|Event} [view] - Specify what to change the view to. Either 'tile' or 'row'. |
|
692
|
|
|
* Or, if this is used as a callback view is actually the event, and we need to find the view. |
|
693
|
|
|
* @param {et2_widget} [button_widget] - The widget that's calling |
|
694
|
|
|
*/ |
|
695
|
|
|
change_view: function(view, button_widget) |
|
696
|
|
|
{ |
|
697
|
|
|
var et2 = etemplate2.getById('filemanager-index'); |
|
698
|
|
|
var nm = false; |
|
699
|
|
|
if(et2 && et2.widgetContainer.getWidgetById('nm')) |
|
700
|
|
|
{ |
|
701
|
|
|
nm = et2.widgetContainer.getWidgetById('nm'); |
|
702
|
|
|
} |
|
703
|
|
|
if(!nm) |
|
704
|
|
|
{ |
|
705
|
|
|
egw.debug('warn', 'Could not find nextmatch to change view'); |
|
706
|
|
|
|
|
707
|
|
|
return; |
|
708
|
|
|
} |
|
709
|
|
|
|
|
710
|
|
|
if(!button_widget) |
|
711
|
|
|
{ |
|
712
|
|
|
button_widget = nm.getWidgetById('button[change_view]'); |
|
713
|
|
|
} |
|
714
|
|
|
if(button_widget && button_widget.instanceOf(et2_button)) |
|
715
|
|
|
{ |
|
716
|
|
|
// Switch view based on button icon, since controller can get re-created |
|
717
|
|
|
if(typeof view != 'string') |
|
718
|
|
|
{ |
|
719
|
|
|
view = button_widget.options.image.replace('list_',''); |
|
720
|
|
|
} |
|
721
|
|
|
|
|
722
|
|
|
// Toggle button icon to the other view |
|
723
|
|
|
button_widget.set_image("list_"+(view == nm.controller.VIEW_ROW ? nm.controller.VIEW_TILE : nm.controller.VIEW_ROW)); |
|
724
|
|
|
|
|
725
|
|
|
button_widget.set_statustext(view == nm.controller.VIEW_ROW ? this.egw.lang("Tile view") : this.egw.lang('List view')); |
|
726
|
|
|
} |
|
727
|
|
|
|
|
728
|
|
|
nm.set_view(view); |
|
729
|
|
|
// Put it into active filters (but don't refresh) |
|
730
|
|
|
nm.activeFilters.view = view; |
|
731
|
|
|
|
|
732
|
|
|
// Change template to match |
|
733
|
|
|
var template = view == nm.controller.VIEW_ROW ? 'filemanager.index.rows' : 'filemanager.tile'; |
|
734
|
|
|
nm.set_template(template); |
|
735
|
|
|
|
|
736
|
|
|
// Wait for template to load, then refresh |
|
737
|
|
|
template = nm.getWidgetById(template); |
|
738
|
|
|
if(template && template.loading) |
|
739
|
|
|
{ |
|
740
|
|
|
template.loading.done(function() { |
|
741
|
|
|
nm.applyFilters({view: view}); |
|
742
|
|
|
}); |
|
743
|
|
|
} |
|
744
|
|
|
}, |
|
745
|
|
|
|
|
746
|
|
|
/** |
|
747
|
|
|
* Open/active an item |
|
748
|
|
|
* |
|
749
|
|
|
* @param _action |
|
750
|
|
|
* @param _senders |
|
751
|
|
|
*/ |
|
752
|
|
|
open: function(_action, _senders) |
|
753
|
|
|
{ |
|
754
|
|
|
var data = egw.dataGetUIDdata(_senders[0].id); |
|
755
|
|
|
var path = this.id2path(_senders[0].id); |
|
756
|
|
|
var mime = this.et2._inst.widgetContainer.getWidgetById('$row'); |
|
757
|
|
|
// try to get mime widget DOM node out of the row DOM |
|
758
|
|
|
var mime_dom = jQuery(_senders[0].iface.getDOMNode()).find("span#filemanager-index_\\$row"); |
|
759
|
|
|
|
|
760
|
|
|
// symlinks dont have mime 'http/unix-directory', but server marks all directories with class 'isDir' |
|
761
|
|
|
if (data.data.mime == 'httpd/unix-directory' || data.data['class'] && data.data['class'].split(/ +/).indexOf('isDir') != -1) |
|
762
|
|
|
{ |
|
763
|
|
|
this.change_dir(path,_action.parent.data.nextmatch || this.et2); |
|
764
|
|
|
} |
|
765
|
|
|
else if(mime && data.data.mime.match(mime.mime_regexp) && mime_dom.length>0) |
|
766
|
|
|
{ |
|
767
|
|
|
mime_dom.click(); |
|
768
|
|
|
} |
|
769
|
|
|
else |
|
770
|
|
|
{ |
|
771
|
|
|
egw.open({path: path, type: data.data.mime, download_url: data.data.download_url}, 'file','view',null,'_browser'); |
|
772
|
|
|
} |
|
773
|
|
|
return false; |
|
774
|
|
|
}, |
|
775
|
|
|
|
|
776
|
|
|
/** |
|
777
|
|
|
* Edit prefs of current directory |
|
778
|
|
|
* |
|
779
|
|
|
* @param _action |
|
780
|
|
|
* @param _senders |
|
781
|
|
|
*/ |
|
782
|
|
|
editprefs: function(_action, _senders) |
|
783
|
|
|
{ |
|
784
|
|
|
var path = typeof _senders != 'undefined' ? this.id2path(_senders[0].id) : this.get_path(_action && _action.parent.data.nextmatch.getInstanceManager().uniqueId || false); |
|
785
|
|
|
|
|
786
|
|
|
egw().open_link(egw.link('/index.php', { |
|
787
|
|
|
menuaction: 'filemanager.filemanager_ui.file', |
|
788
|
|
|
path: path |
|
789
|
|
|
}), 'fileprefs', '510x425'); |
|
790
|
|
|
}, |
|
791
|
|
|
|
|
792
|
|
|
/** |
|
793
|
|
|
* Callback to check if the paste action is enabled. We also update the |
|
794
|
|
|
* clipboard historical targets here as well |
|
795
|
|
|
* |
|
796
|
|
|
* @param {egwAction} _action drop action we're checking |
|
797
|
|
|
* @param {egwActionObject[]} _senders selected files |
|
798
|
|
|
* @param {egwActionObject} _target Drop or context menu activated on this one |
|
799
|
|
|
* |
|
800
|
|
|
* @returns boolean true if enabled, false otherwise |
|
801
|
|
|
*/ |
|
802
|
|
|
paste_enabled: function paste_enabled(_action, _senders, _target) |
|
803
|
|
|
{ |
|
804
|
|
|
// Need files in the clipboard for this |
|
805
|
|
|
var clipboard_files = this.get_clipboard_files(); |
|
806
|
|
|
if(clipboard_files.length === 0) |
|
807
|
|
|
{ |
|
808
|
|
|
return false; |
|
809
|
|
|
} |
|
810
|
|
|
|
|
811
|
|
|
// Parent action (paste) gets run through here as well, but needs no |
|
812
|
|
|
// further processing |
|
813
|
|
|
if(_action.id == 'paste') return true; |
|
814
|
|
|
|
|
815
|
|
|
if(_action.canHaveChildren.indexOf('drop') == -1) |
|
816
|
|
|
{ |
|
817
|
|
|
_action.canHaveChildren.push('drop'); |
|
818
|
|
|
} |
|
819
|
|
|
var actions = []; |
|
820
|
|
|
var dir = undefined; |
|
|
|
|
|
|
821
|
|
|
var current_dir, target_dir = false; |
|
822
|
|
|
|
|
823
|
|
|
// Current directory |
|
824
|
|
|
current_dir = this.get_path(); |
|
825
|
|
|
dir = egw.dataGetUIDdata('filemanager::'+current_dir); |
|
826
|
|
|
var path_widget = etemplate2.getById('filemanager-index').widgetContainer.getWidgetById('button[createdir]'); |
|
827
|
|
|
actions.push({ |
|
828
|
|
|
id:_action.id+'_current', caption: current_dir, path: current_dir, |
|
829
|
|
|
enabled: dir && dir.data && dir.data.class && dir.data.class.indexOf('noEdit') === -1 || |
|
830
|
|
|
!dir && path_widget && !path_widget.options.readonly |
|
831
|
|
|
}); |
|
832
|
|
|
|
|
833
|
|
|
// Target, if directory |
|
834
|
|
|
target_dir = this.id2path(_target.id); |
|
835
|
|
|
dir = egw.dataGetUIDdata(_target.id); |
|
836
|
|
|
actions.push({ |
|
837
|
|
|
id: _action.id+'_target', |
|
838
|
|
|
caption: target_dir, |
|
839
|
|
|
path: target_dir, |
|
840
|
|
|
enabled: _target && _target.iface && jQuery(_target.iface.getDOMNode()).hasClass('isDir') && |
|
841
|
|
|
(dir && dir.data && dir.data.class && dir.data.class.indexOf('noEdit') === -1 || !dir) |
|
842
|
|
|
}); |
|
843
|
|
|
|
|
844
|
|
|
// Last 10 folders |
|
845
|
|
|
var previous_dsts = jQuery.extend([], egw.preference('drop_history',this.appname)); |
|
846
|
|
|
var action_index = 0; |
|
847
|
|
|
for(var i = 0; i < 10; i++) |
|
848
|
|
|
{ |
|
849
|
|
|
var path = i < previous_dsts.length ? previous_dsts[i] : ''; |
|
850
|
|
|
actions.push({ |
|
851
|
|
|
id: _action.id+'_target_'+action_index++, |
|
852
|
|
|
caption: path, |
|
853
|
|
|
path: path, |
|
854
|
|
|
group: 2, |
|
855
|
|
|
enabled: path && !(current_dir && path === current_dir || target_dir && path === target_dir) |
|
856
|
|
|
}); |
|
857
|
|
|
} |
|
858
|
|
|
|
|
859
|
|
|
// Common stuff, every action needs these |
|
860
|
|
|
for(var i = 0; i < actions.length; i++) |
|
861
|
|
|
{ |
|
862
|
|
|
//actions[i].type = 'drop', |
|
863
|
|
|
actions[i].acceptedTypes = _action.acceptedTypes; |
|
864
|
|
|
actions[i].no_lang = true; |
|
865
|
|
|
actions[i].hideOnDisabled = true; |
|
866
|
|
|
} |
|
867
|
|
|
|
|
868
|
|
|
_action.updateActions(actions); |
|
869
|
|
|
|
|
870
|
|
|
// Create paste action |
|
871
|
|
|
// This injects the clipboard data and calls the original handler |
|
872
|
|
|
var paste_exec = function(action, selected) { |
|
873
|
|
|
// Add in clipboard as a sender |
|
874
|
|
|
var clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')); |
|
875
|
|
|
|
|
876
|
|
|
// Set a flag so apps can tell the difference, if they need to |
|
877
|
|
|
action.set_onExecute(action.parent.onExecute.fnct); |
|
878
|
|
|
action.execute(clipboard.selected,selected[0]); |
|
879
|
|
|
|
|
880
|
|
|
// Clear the clipboard, the files are not there anymore |
|
881
|
|
|
if(action.id.indexOf('move') !== -1) |
|
882
|
|
|
{ |
|
883
|
|
|
egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify({ |
|
884
|
|
|
type:[], |
|
885
|
|
|
selected:[] |
|
886
|
|
|
})); |
|
887
|
|
|
} |
|
888
|
|
|
}; |
|
889
|
|
|
for(var i = 0; i < actions.length; i++) |
|
890
|
|
|
{ |
|
891
|
|
|
_action.getActionById(actions[i].id).onExecute = jQuery.extend(true, {}, _action.onExecute); |
|
892
|
|
|
|
|
893
|
|
|
_action.getActionById(actions[i].id).set_onExecute(paste_exec); |
|
894
|
|
|
} |
|
895
|
|
|
return actions.length > 0; |
|
896
|
|
|
}, |
|
897
|
|
|
|
|
898
|
|
|
/** |
|
899
|
|
|
* File(s) droped |
|
900
|
|
|
* |
|
901
|
|
|
* @param _action |
|
902
|
|
|
* @param _elems |
|
903
|
|
|
* @param _target |
|
904
|
|
|
* @returns |
|
905
|
|
|
*/ |
|
906
|
|
|
drop: function(_action, _elems, _target) |
|
907
|
|
|
{ |
|
908
|
|
|
var src = this._elems2paths(_elems); |
|
909
|
|
|
|
|
910
|
|
|
|
|
911
|
|
|
// Target will be missing ID if directory is empty |
|
912
|
|
|
// so start with the current directory |
|
913
|
|
|
var parent = _action; |
|
914
|
|
|
var nm = _target.manager.data.nextmatch; |
|
915
|
|
|
while(!nm && parent.parent) |
|
916
|
|
|
{ |
|
917
|
|
|
parent = parent.parent; |
|
918
|
|
|
if(parent.data.nextmatch) nm = parent.data.nextmatch; |
|
919
|
|
|
} |
|
920
|
|
|
var nm_dst = this.get_path(nm.getInstanceManager().uniqueId || false); |
|
921
|
|
|
|
|
922
|
|
|
// Action specifies a destination, target does not matter |
|
923
|
|
|
if(_action.data && _action.data.path) |
|
924
|
|
|
{ |
|
925
|
|
|
dst = _action.data.path; |
|
926
|
|
|
} |
|
927
|
|
|
// File(s) were dropped on a row, they want them inside |
|
928
|
|
|
else if(_target) |
|
929
|
|
|
{ |
|
930
|
|
|
var dst = ''; |
|
931
|
|
|
var paths = this._elems2paths([_target]); |
|
932
|
|
|
if(paths[0]) dst = paths[0]; |
|
933
|
|
|
|
|
934
|
|
|
// check if target is a file --> use it's directory instead |
|
935
|
|
|
if(_target.id) |
|
936
|
|
|
{ |
|
937
|
|
|
var data = egw.dataGetUIDdata(_target.id); |
|
938
|
|
|
if (!data || data.data.mime != 'httpd/unix-directory') |
|
939
|
|
|
{ |
|
940
|
|
|
dst = this.dirname(dst); |
|
941
|
|
|
} |
|
942
|
|
|
} |
|
943
|
|
|
} |
|
944
|
|
|
|
|
945
|
|
|
// Remember the target for next time |
|
946
|
|
|
var previous_dsts = jQuery.extend([], egw.preference('drop_history',this.appname)); |
|
947
|
|
|
previous_dsts.unshift(dst); |
|
|
|
|
|
|
948
|
|
|
previous_dsts = Array.from(new Set(previous_dsts)).slice(0,9); |
|
949
|
|
|
egw.set_preference(this.appname, 'drop_history', previous_dsts); |
|
950
|
|
|
|
|
951
|
|
|
// Actual action id will be something like file_drop_{move|copy|link}[_other_id], |
|
952
|
|
|
// but we need to send move, copy or link |
|
953
|
|
|
var action_id = _action.id.replace("file_drop_",'').split('_',1)[0]; |
|
954
|
|
|
this._do_action(action_id, src, false, dst || nm_dst); |
|
955
|
|
|
}, |
|
956
|
|
|
|
|
957
|
|
|
/** |
|
958
|
|
|
* Handle a native / HTML5 file drop from system |
|
959
|
|
|
* |
|
960
|
|
|
* This is a callback from nextmatch to prevent the default link action, and just upload instead. |
|
961
|
|
|
* |
|
962
|
|
|
* @param {string} row_uid UID of the row the files were dropped on |
|
963
|
|
|
* @param {Files[]} files |
|
964
|
|
|
*/ |
|
965
|
|
|
filedrop: function(row_uid, files) |
|
966
|
|
|
{ |
|
967
|
|
|
var self = this; |
|
968
|
|
|
var data = egw.dataGetUIDdata(row_uid); |
|
969
|
|
|
files = files || window.event.dataTransfer.files; |
|
970
|
|
|
|
|
971
|
|
|
var path = typeof data != 'undefined' && data.data.mime == "httpd/unix-directory" ? data.data.path : this.get_path(); |
|
972
|
|
|
var widget = this.et2.getWidgetById('upload'); |
|
973
|
|
|
|
|
974
|
|
|
// Override finish to specify a potentially different path |
|
975
|
|
|
var old_onfinishone = widget.options.onFinishOne; |
|
976
|
|
|
var old_onfinish = widget.options.onFinish; |
|
977
|
|
|
|
|
978
|
|
|
widget.options.onFinishOne = function(_event, _file_count) { |
|
979
|
|
|
self.upload(_event, _file_count, path); |
|
980
|
|
|
}; |
|
981
|
|
|
|
|
982
|
|
|
widget.options.onFinish = function() { |
|
983
|
|
|
widget.options.onFinish = old_onfinish; |
|
984
|
|
|
widget.options.onFinishOne = old_onfinishone; |
|
985
|
|
|
}; |
|
986
|
|
|
// This triggers the upload |
|
987
|
|
|
widget.set_value(files); |
|
988
|
|
|
|
|
989
|
|
|
// Return false to prevent the link |
|
990
|
|
|
return false; |
|
991
|
|
|
}, |
|
992
|
|
|
|
|
993
|
|
|
/** |
|
994
|
|
|
* Change readonly state for given directory |
|
995
|
|
|
* |
|
996
|
|
|
* Get call/transported with each get_rows call, but should only by applied to UI if matching curent dir |
|
997
|
|
|
* |
|
998
|
|
|
* @param {string} _path |
|
999
|
|
|
* @param {boolean} _ro |
|
1000
|
|
|
*/ |
|
1001
|
|
|
set_readonly: function(_path, _ro) |
|
1002
|
|
|
{ |
|
1003
|
|
|
//alert('set_readonly("'+_path+'", '+_ro+')'); |
|
1004
|
|
|
if (!this.path_widget) // widget not yet ready, try later |
|
1005
|
|
|
{ |
|
1006
|
|
|
this.readonly = [_path, _ro]; |
|
1007
|
|
|
return; |
|
1008
|
|
|
} |
|
1009
|
|
|
for(var id in this.path_widget) |
|
1010
|
|
|
{ |
|
1011
|
|
|
var path = this.get_path(id); |
|
1012
|
|
|
|
|
1013
|
|
|
if (_path == path) |
|
1014
|
|
|
{ |
|
1015
|
|
|
var ids = ['button[linkpaste]', 'button[paste]', 'button[createdir]', 'button[symlink]', 'upload']; |
|
1016
|
|
|
for(var i=0; i < ids.length; ++i) |
|
1017
|
|
|
{ |
|
1018
|
|
|
var widget = etemplate2.getById(id).widgetContainer.getWidgetById(ids[i]); |
|
1019
|
|
|
if (widget) |
|
1020
|
|
|
{ |
|
1021
|
|
|
widget.set_readonly(_ro); |
|
1022
|
|
|
} |
|
1023
|
|
|
} |
|
1024
|
|
|
} |
|
1025
|
|
|
} |
|
1026
|
|
|
}, |
|
1027
|
|
|
|
|
1028
|
|
|
/** |
|
1029
|
|
|
* Row or filename in select-file dialog clicked |
|
1030
|
|
|
* |
|
1031
|
|
|
* @param {jQuery.event} event |
|
1032
|
|
|
* @param {et2_widget} widget |
|
1033
|
|
|
*/ |
|
1034
|
|
|
select_clicked: function(event, widget) |
|
1035
|
|
|
{ |
|
1036
|
|
|
if (!widget || typeof widget.value != 'object') |
|
1037
|
|
|
{ |
|
|
|
|
|
|
1038
|
|
|
|
|
1039
|
|
|
} |
|
1040
|
|
|
else if (widget.value.is_dir) // true for "httpd/unix-directory" and "egw/*" |
|
1041
|
|
|
{ |
|
1042
|
|
|
var path = null; |
|
1043
|
|
|
// Cannot do this, there are multiple widgets named path |
|
1044
|
|
|
// widget.getRoot().getWidgetById("path"); |
|
1045
|
|
|
widget.getRoot().iterateOver(function(widget) { |
|
1046
|
|
|
if(widget.id == "path") path = widget; |
|
1047
|
|
|
},null, et2_textbox); |
|
1048
|
|
|
if(path) |
|
1049
|
|
|
{ |
|
1050
|
|
|
path.set_value(widget.value.path); |
|
1051
|
|
|
} |
|
1052
|
|
|
} |
|
1053
|
|
|
else if (this.et2 && this.et2.getArrayMgr('content').getEntry('mode') != 'open-multiple') |
|
1054
|
|
|
{ |
|
1055
|
|
|
var editfield = this.et2.getWidgetById('name'); |
|
1056
|
|
|
if(editfield) |
|
1057
|
|
|
{ |
|
1058
|
|
|
editfield.set_value(widget.value.name); |
|
1059
|
|
|
} |
|
1060
|
|
|
} |
|
1061
|
|
|
else |
|
1062
|
|
|
{ |
|
1063
|
|
|
var file = widget.value.name; |
|
1064
|
|
|
widget.getParent().iterateOver(function(widget) |
|
1065
|
|
|
{ |
|
1066
|
|
|
if(widget.options.selected_value == file) |
|
1067
|
|
|
{ |
|
1068
|
|
|
widget.set_value(widget.get_value() == file ? widget.options.unselected_value : file); |
|
1069
|
|
|
} |
|
1070
|
|
|
}, null, et2_checkbox); |
|
1071
|
|
|
|
|
1072
|
|
|
} |
|
1073
|
|
|
// Stop event or it will toggle back off |
|
1074
|
|
|
event.preventDefault(); |
|
1075
|
|
|
event.stopPropagation(); |
|
1076
|
|
|
return false; |
|
1077
|
|
|
}, |
|
1078
|
|
|
|
|
1079
|
|
|
/** |
|
1080
|
|
|
* Set Sudo button's label and change its onclick handler according to its action |
|
1081
|
|
|
* |
|
1082
|
|
|
* @param {widget object} _widget sudo buttononly |
|
1083
|
|
|
* @param {string} _action string of action type {login|logout} |
|
1084
|
|
|
*/ |
|
1085
|
|
|
set_sudoButton: function (_widget, _action) |
|
1086
|
|
|
{ |
|
1087
|
|
|
var widget = _widget || this.et2.getWidgetById('sudouser'); |
|
1088
|
|
|
if (widget) |
|
1089
|
|
|
{ |
|
1090
|
|
|
switch (_action) |
|
1091
|
|
|
{ |
|
1092
|
|
|
case 'login': |
|
1093
|
|
|
widget.set_label('Logout'); |
|
1094
|
|
|
this.et2._inst.submit(widget); |
|
1095
|
|
|
break; |
|
1096
|
|
|
|
|
1097
|
|
|
default: |
|
1098
|
|
|
widget.set_label('Superuser'); |
|
1099
|
|
|
widget.onclick = function(){ |
|
1100
|
|
|
jQuery('.superuser').css('display','inline'); |
|
1101
|
|
|
}; |
|
1102
|
|
|
} |
|
1103
|
|
|
} |
|
1104
|
|
|
}, |
|
1105
|
|
|
|
|
1106
|
|
|
/** |
|
1107
|
|
|
* Open file a file dialog from EPL, warn if EPL is not available |
|
1108
|
|
|
* |
|
1109
|
|
|
* @param {jQuery.event} _event |
|
1110
|
|
|
* @param {et2_widget} _widget |
|
1111
|
|
|
*/ |
|
1112
|
|
|
fileafile: function(_event, _widget) |
|
1113
|
|
|
{ |
|
1114
|
|
|
if (this.egw.user('apps').stylite) |
|
1115
|
|
|
{ |
|
1116
|
|
|
this.egw.open_link('/index.php?menuaction=stylite.stylite_filemanager.upload&path='+this.get_path(), '_blank', '670x320'); |
|
1117
|
|
|
} |
|
1118
|
|
|
else |
|
1119
|
|
|
{ |
|
1120
|
|
|
et2_dialog.show_dialog(function(_button) |
|
1121
|
|
|
{ |
|
1122
|
|
|
if (_button == et2_dialog.YES_BUTTON) window.open('http://www.egroupware.org/EPL', '_blank'); |
|
1123
|
|
|
return true; |
|
1124
|
|
|
}, this.egw.lang('File a file is only available with an EPL subscription.')+"\n\n"+ |
|
1125
|
|
|
this.egw.lang('You can use regular upload [+] button to upload files.')+"\n\n"+ |
|
1126
|
|
|
this.egw.lang('Do you want more information about EPL subscription?'), |
|
1127
|
|
|
this.egw.lang('File a file'), undefined, et2_dialog.BUTTONS_YES_NO, et2_dialog.QUESTION_MESSAGE); |
|
1128
|
|
|
} |
|
1129
|
|
|
}, |
|
1130
|
|
|
|
|
1131
|
|
|
/** |
|
1132
|
|
|
* create a share-link for the given file or directory |
|
1133
|
|
|
* @param {object} _action egw actions |
|
1134
|
|
|
* @param {object} _senders selected nm row |
|
1135
|
|
|
* @returns {Boolean} returns false if not successful |
|
1136
|
|
|
*/ |
|
1137
|
|
|
|
|
1138
|
|
|
share_link: function(_action, _senders){ |
|
1139
|
|
|
var data = egw.dataGetUIDdata(_senders[0].id) |
|
1140
|
|
|
var path = this.id2path(_senders[0].id); |
|
1141
|
|
|
|
|
1142
|
|
|
var request = egw.json('filemanager_ui::ajax_action', ['sharelink', path], |
|
1143
|
|
|
this._share_link_callback, this, true, this |
|
1144
|
|
|
).sendRequest(); |
|
1145
|
|
|
return true; |
|
1146
|
|
|
}, |
|
1147
|
|
|
|
|
1148
|
|
|
|
|
1149
|
|
|
/** |
|
1150
|
|
|
* share-link callback |
|
1151
|
|
|
*/ |
|
1152
|
|
|
|
|
1153
|
|
|
_share_link_callback: function(_data) { |
|
1154
|
|
|
if (_data.msg || _data.share_link) window.egw_refresh(_data.msg, this.appname); |
|
1155
|
|
|
console.log("_data", _data); |
|
|
|
|
|
|
1156
|
|
|
|
|
1157
|
|
|
var copy_link_to_clipboard = null; |
|
1158
|
|
|
|
|
1159
|
|
|
var copy_link_to_clipboard = function(evt){ |
|
1160
|
|
|
var $target = jQuery(evt.target); |
|
1161
|
|
|
$target.select(); |
|
1162
|
|
|
|
|
1163
|
|
|
console.log("share_link click"); |
|
|
|
|
|
|
1164
|
|
|
|
|
1165
|
|
|
try { |
|
1166
|
|
|
successful = document.execCommand('copy'); |
|
1167
|
|
|
if (successful) |
|
1168
|
|
|
{ |
|
1169
|
|
|
egw.message('Share link copied into clipboard'); |
|
1170
|
|
|
return true; |
|
1171
|
|
|
} |
|
1172
|
|
|
} |
|
1173
|
|
|
catch (e) {} |
|
|
|
|
|
|
1174
|
|
|
egw.message('Failed to copy the link!'); |
|
1175
|
|
|
|
|
1176
|
|
|
}; |
|
1177
|
|
|
jQuery("body").on("click", "[name=share_link]", copy_link_to_clipboard); |
|
1178
|
|
|
|
|
1179
|
|
|
var dialog = et2_createWidget("dialog",{ |
|
1180
|
|
|
callback: function( button_id, value){ |
|
1181
|
|
|
jQuery("body").off("click", "[name=share_link]", copy_link_to_clipboard); |
|
1182
|
|
|
return true; |
|
1183
|
|
|
}, |
|
1184
|
|
|
title: "Share", |
|
1185
|
|
|
template: _data.template, |
|
1186
|
|
|
value: { content:{ "share_link": _data.share_link } } |
|
1187
|
|
|
}); |
|
1188
|
|
|
|
|
1189
|
|
|
}, |
|
1190
|
|
|
|
|
1191
|
|
|
/** |
|
1192
|
|
|
* This function copies the selected file/folder entry as webdav link into clipboard |
|
1193
|
|
|
* |
|
1194
|
|
|
* @param {object} _action egw actions |
|
1195
|
|
|
* @param {object} _senders selected nm row |
|
1196
|
|
|
* @returns {Boolean} returns false if not successful |
|
1197
|
|
|
*/ |
|
1198
|
|
|
copy_link: function (_action, _senders) |
|
1199
|
|
|
{ |
|
1200
|
|
|
var data = egw.dataGetUIDdata(_senders[0].id); |
|
1201
|
|
|
var url = data ? data.data.download_url : '/webdav.php'+this.id2path(_senders[0].id); |
|
1202
|
|
|
if (url[0] == '/') url = egw.link(url); |
|
1203
|
|
|
if (url.substr(0,4) == 'http' && url.indexOf('://') <= 5) { |
|
1204
|
|
|
// it's already a full url |
|
1205
|
|
|
} |
|
1206
|
|
|
else |
|
1207
|
|
|
{ |
|
1208
|
|
|
var hostUrl = new URL(window.location.href); |
|
1209
|
|
|
url = hostUrl.origin + url; |
|
1210
|
|
|
} |
|
1211
|
|
|
|
|
1212
|
|
|
if (url) |
|
1213
|
|
|
{ |
|
1214
|
|
|
var elem = jQuery(document.createElement('div')); |
|
1215
|
|
|
var range = []; |
|
1216
|
|
|
elem.text(url); |
|
1217
|
|
|
elem.appendTo('body'); |
|
1218
|
|
|
if (document.selection) |
|
1219
|
|
|
{ |
|
1220
|
|
|
range = document.body.createTextRange(); |
|
1221
|
|
|
range.moveToElementText(elem); |
|
1222
|
|
|
range.select(); |
|
1223
|
|
|
} |
|
1224
|
|
|
else if (window.getSelection) |
|
1225
|
|
|
{ |
|
1226
|
|
|
var range = document.createRange(); |
|
1227
|
|
|
range.selectNode(elem[0]); |
|
1228
|
|
|
window.getSelection().removeAllRanges(); |
|
1229
|
|
|
window.getSelection().addRange(range); |
|
1230
|
|
|
} |
|
1231
|
|
|
|
|
1232
|
|
|
var successful = false; |
|
1233
|
|
|
try { |
|
1234
|
|
|
successful = document.execCommand('copy'); |
|
1235
|
|
|
if (successful) |
|
1236
|
|
|
{ |
|
1237
|
|
|
egw.message('WebDav link copied into clipboard'); |
|
1238
|
|
|
window.getSelection().removeAllRanges(); |
|
1239
|
|
|
|
|
1240
|
|
|
return true; |
|
1241
|
|
|
} |
|
1242
|
|
|
} |
|
1243
|
|
|
catch (e) {} |
|
|
|
|
|
|
1244
|
|
|
egw.message('Failed to copy the link!'); |
|
1245
|
|
|
elem.remove(); |
|
1246
|
|
|
return false; |
|
1247
|
|
|
} |
|
1248
|
|
|
} |
|
1249
|
|
|
}); |
|
1250
|
|
|
|