egroupware/filemanager/js/app.js

1163 lines
47 KiB
JavaScript

/**
* EGroupware - Filemanager - Javascript UI
*
* @link https://www.egroupware.org
* @package filemanager
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2008-21 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
/*egw:uses
/api/js/jsapi/egw_app.js;
*/
import { EgwApp } from "../../api/js/jsapi/egw_app";
import { etemplate2 } from "../../api/js/etemplate/etemplate2";
import { et2_dialog } from "../../api/js/etemplate/et2_widget_dialog";
import { et2_file } from "../../api/js/etemplate/et2_widget_file";
import { et2_button } from "../../api/js/etemplate/et2_widget_button";
import { et2_nextmatch_controller } from "../../api/js/etemplate/et2_extension_nextmatch_controller";
import { egw_get_file_editor_prefered_mimes } from "../../api/js/jsapi/egw_global";
import { et2_createWidget } from "../../api/js/etemplate/et2_core_widget";
/**
* UI for filemanager
*/
export class filemanagerAPP extends EgwApp {
/**
* Constructor
*
* @memberOf app.filemanager
*/
constructor() {
// call parent
super('filemanager');
/**
* path widget, by template
*/
this.path_widget = {};
/**
* Are files cut into clipboard - need to be deleted at source on paste
*/
this.clipboard_is_cut = false;
/**
* Regexp to convert id to a path, use this.id2path(_id)
*/
this.remove_prefix = /^filemanager::/;
// Loading filemanager in its tab and home causes us problems with
// unwanted destruction, so we check for already existing path widgets
let lists = etemplate2.getByApplication('home');
for (let i = 0; i < lists.length; i++) {
if (lists[i].app == 'filemanager' && lists[i].widgetContainer.getWidgetById('path')) {
this.path_widget[lists[i].uniqueId] = lists[i].widgetContainer.getWidgetById('path');
}
}
}
/**
* Destructor
*/
destroy(_app) {
delete this.et2;
// call parent
super.destroy(_app);
}
/**
* This function is called when the etemplate2 object is loaded
* and ready. If you must store a reference to the et2 object,
* make sure to clean it up in destroy().
*
* @param et2 etemplate2 Newly ready object
* @param {string} name template name
*/
et2_ready(et2, name) {
// call parent
super.et2_ready(et2, name);
let path_widget = this.et2.getWidgetById('path');
if (path_widget) // do NOT set not found path-widgets, as uploads works on first one only!
{
this.path_widget[et2.DOMContainer.id] = path_widget;
// Bind to removal to remove from list
jQuery(et2.DOMContainer).on('clear', function (e) {
if (app.filemanager && app.filemanager.path_widget)
delete app.filemanager.path_widget[e.target.id];
});
}
if (this.et2.getWidgetById('nm')) {
// Legacy JS only supports 2 arguments (event and widget), so set
// to the actual function here
this.et2.getWidgetById('nm').set_onfiledrop(jQuery.proxy(this.filedrop, this));
}
// get clipboard from browser localstore and update button tooltips
this.clipboard_tooltips();
// calling set_readonly for initial path
if (this.et2.getArrayMgr('content').getEntry('initial_path_readonly')) {
this.readonly = [this.et2.getArrayMgr('content').getEntry('nm[path]'), true];
}
if (typeof this.readonly != 'undefined') {
this.set_readonly.apply(this, this.readonly);
delete this.readonly;
}
if (name == 'filemanager.index') {
let fe = egw.link_get_registry('filemanager-editor');
let new_widget = this.et2.getWidgetById('new');
if (fe && fe["edit"]) {
let new_options = this.et2.getArrayMgr('sel_options').getEntry('new');
new_widget.set_select_options(new_options);
}
else if (new_widget) {
new_widget.set_disabled(true);
}
}
}
/**
* Set the application's state to the given state.
*
* Extended from parent to also handle view
*
*
* @param {{name: string, state: object}|string} state Object (or JSON string) for a state.
* Only state is required, and its contents are application specific.
*
* @return {boolean} false - Returns false to stop event propagation
*/
setState(state) {
// State should be an object, not a string, but we'll parse
if (typeof state == "string") {
if (state.indexOf('{') != -1 || state == 'null') {
state = JSON.parse(state);
}
}
let result = super.setState(state, 'filemanager.index');
// This has to happen after the parent, changing to tile recreates
// nm controller
if (typeof state == "object" && state.state && state.state.view) {
let et2 = etemplate2.getById('filemanager-index');
if (et2) {
this.et2 = et2.widgetContainer;
this.change_view(state.state.view);
}
}
return result;
}
/**
* Retrieve the current state of the application for future restoration
*
* Extended from parent to also set view
*
* @return {object} Application specific map representing the current state
*/
getState() {
let state = super.getState();
let et2 = etemplate2.getById('filemanager-index');
if (et2) {
let nm = et2.widgetContainer.getWidgetById('nm');
state.view = nm.view;
}
return state;
}
/**
* Convert id to path (remove "filemanager::" prefix)
*/
id2path(_id) {
return _id.replace(this.remove_prefix, '');
}
/**
* Convert array of elems to array of paths
*/
_elems2paths(_elems) {
let paths = [];
for (let i = 0; i < _elems.length; i++) {
// If selected has no id, try parent. This happens for the placeholder row
// in empty directories.
paths.push(_elems[i].id ? this.id2path(_elems[i].id) : _elems[i]._context._parentId);
}
return paths;
}
/**
* Get directory of a path
*/
dirname(_path) {
let parts = _path.split('/');
parts.pop();
return parts.join('/') || '/';
}
/**
* Get name of a path
*/
basename(_path) {
return _path.split('/').pop();
}
/**
* Get current working directory
*/
get_path(etemplate_name) {
if (!etemplate_name || typeof this.path_widget[etemplate_name] == 'undefined') {
for (etemplate_name in this.path_widget)
break;
}
let path_widget = this.path_widget[etemplate_name];
return path_widget ? path_widget.get_value.apply(path_widget) : null;
}
/**
* Open compose with already attached files
*
* @param {(string|string[])} attachments path(s)
* @param {object} params
*/
open_mail(attachments, params) {
if (typeof attachments == 'undefined')
attachments = this.get_clipboard_files();
if (!params || typeof params != 'object')
params = {};
if (!(attachments instanceof Array))
attachments = [attachments];
let content = { data: { files: { file: [] } } };
for (let i = 0; i < attachments.length; i++) {
params['preset[file][' + i + ']'] = 'vfs://default' + attachments[i];
content.data.files.file.push('vfs://default' + attachments[i]);
}
content.data.files["filemode"] = params['preset[filemode]'];
// always open compose in html mode, as attachment links look a lot nicer in html
params["mimeType"] = 'html';
return egw.openWithinWindow("mail", "setCompose", content, params, /mail.mail_compose.compose/, true);
}
/**
* Mail files action: open compose with already attached files
*
* @param _action
* @param _elems
*/
mail(_action, _elems) {
this.open_mail(this._elems2paths(_elems), {
'preset[filemode]': _action.id.substr(5)
});
}
/**
* Mail files action: open compose with already linked files
* We're only interested in hidden upload shares here, open_mail can handle
* the rest
*
* @param {egwAction} _action
* @param {egwActionObject[]} _selected
*/
mail_share_link(_action, _selected) {
if (_action.id !== 'mail_shareUploadDir') {
return this.mail(_action, _selected);
}
let path = this.id2path(_selected[0].id);
this.share_link(_action, _selected, null, false, false, this._mail_link_callback);
return true;
}
/**
* Callback with the share link to append to an email
*
* @param {Object} _data
* @param {String} _data.share_link Link to the share
* @param {String} _data.title Title for the link
* @param {String} [_data.msg] Error message
*/
_mail_link_callback(_data) {
debugger;
if (_data.msg || !_data.share_link)
window.egw_refresh(_data.msg, this.appname);
let params = {
'preset[body]': '<a href="' + _data.share_link + '">' + _data.title + '</a>',
'mimeType': 'html' // always open compose in html mode, as attachment links look a lot nicer in html
};
let content = {
mail_htmltext: ['<br /><a href="' + _data.share_link + '">' + _data.title + '</a>'],
mail_plaintext: ["\n" + _data.share_link]
};
return egw.openWithinWindow("mail", "setCompose", content, params, /mail.mail_compose.compose/);
}
/**
* Trigger Upload after each file is uploaded
* @param {type} _event
*/
uploadOnOne(_event) {
this.upload(_event, 1);
}
/**
* Send names of uploaded files (again) to server, to process them: either copy to vfs or ask overwrite/rename
*
* @param {event} _event
* @param {number} _file_count
* @param {string=} _path where the file is uploaded to, default current directory
* @param {string} _conflict What to do if the file conflicts with one on the server
* @param {string} _target Upload processing target. Sharing classes can override this.
*/
upload(_event, _file_count, _path, _conflict = "ask", _target = 'filemanager_ui::ajax_action') {
if (typeof _path == 'undefined') {
_path = this.get_path();
}
if (_file_count && !jQuery.isEmptyObject(_event.data.getValue())) {
let widget = _event.data;
let value = widget.getValue();
value.conflict = _conflict;
egw.json(_target, ['upload', value, _path, { ui_path: this.egw.window.location.pathname }], this._upload_callback, this, true, this).sendRequest();
widget.set_value('');
}
}
/**
* Finish callback for file a file dialog, to get the overwrite / rename prompt
*
* @param {event} _event
* @param {number} _file_count
*/
file_a_file_upload(_event, _file_count) {
let widget = _event.data;
let path = widget.getRoot().getWidgetById("path").getValue();
let action = widget.getRoot().getWidgetById("action").getValue();
let link = widget.getRoot().getWidgetById("entry").getValue();
if (action == 'save_as' && link.app && link.id) {
path = "/apps/" + link.app + "/" + link.id;
}
let props = widget.getInstanceManager().getValues(widget.getRoot());
egw.json('filemanager_ui::ajax_action', [action == 'save_as' ? 'upload' : 'link', widget.getValue(), path, props], function (_data) {
app.filemanager._upload_callback(_data);
// Remove successful after a delay
for (var file in _data.uploaded) {
if (!_data.uploaded[file].confirm || _data.uploaded[file].confirmed) {
// Remove that file from file widget...
widget.remove_file(_data.uploaded[file].name);
}
}
opener.egw_refresh('', 'filemanager', null, null, 'filemanager');
}, app.filemanager, true, this).sendRequest(true);
return true;
}
/**
* Callback for server response to upload request:
* - display message and refresh list
* - ask use to confirm overwritting existing files or rename upload
*
* @param {object} _data values for attributes msg, files, ...
*/
_upload_callback(_data) {
if (_data.msg || _data.uploaded)
window.egw_refresh(_data.msg, this.appname, undefined, undefined, undefined, undefined, undefined, _data.type);
let that = this;
for (let file in _data.uploaded) {
if (_data.uploaded[file].confirm && !_data.uploaded[file].confirmed) {
let buttons = [
{
text: this.egw.lang("Yes"),
id: "overwrite",
class: "ui-priority-primary",
"default": true,
image: 'check'
},
{ text: this.egw.lang("Rename"), id: "rename", image: 'edit' },
{ text: this.egw.lang("Cancel"), id: "cancel" }
];
if (_data.uploaded[file].confirm === "is_dir")
buttons.shift();
let dialog = et2_dialog.show_prompt(function (_button_id, _value) {
let uploaded = {};
uploaded[this.my_data.file] = this.my_data.data;
switch (_button_id) {
case "overwrite":
uploaded[this.my_data.file].confirmed = true;
// fall through
case "rename":
uploaded[this.my_data.file].name = _value;
delete uploaded[this.my_data.file].confirm;
// send overwrite-confirmation and/or rename request to server
egw.json('filemanager_ui::ajax_action', [this.my_data.action, uploaded, this.my_data.path, this.my_data.props], that._upload_callback, that, true, that).sendRequest();
return;
case "cancel":
// Remove that file from every file widget...
that.et2.iterateOver(function (_widget) {
_widget.remove_file(this.my_data.data.name);
}, this, et2_file);
}
}, _data.uploaded[file].confirm === "is_dir" ?
this.egw.lang("There's already a directory with that name!") :
this.egw.lang('Do you want to overwrite existing file %1 in directory %2?', _data.uploaded[file].name, _data.path), this.egw.lang('File %1 already exists', _data.uploaded[file].name), _data.uploaded[file].name, buttons, file);
// setting required data for callback in as my_data
dialog.my_data = {
action: _data.action,
file: file,
path: _data.path,
data: _data.uploaded[file],
props: _data.props
};
}
}
}
/**
* Get any files that are in the system clipboard
*
* @return {string[]} Paths
*/
get_clipboard_files() {
let clipboard_files = [];
if (typeof window.localStorage != 'undefined' && typeof egw.getSessionItem('phpgwapi', 'egw_clipboard') != 'undefined') {
let clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')) || {
type: [],
selected: []
};
if (clipboard.type.indexOf('file') >= 0) {
for (let i = 0; i < clipboard.selected.length; i++) {
let split = clipboard.selected[i].id.split('::');
if (split[0] == 'filemanager') {
clipboard_files.push(this.id2path(clipboard.selected[i].id));
}
}
}
}
return clipboard_files;
}
/**
* Update clickboard tooltips in buttons
*/
clipboard_tooltips() {
let paste_buttons = ['button[paste]', 'button[linkpaste]', 'button[mailpaste]'];
for (let i = 0; i < paste_buttons.length; ++i) {
let button = this.et2.getWidgetById(paste_buttons[i]);
if (button)
button.set_statustext(this.get_clipboard_files().join(",\n"));
}
}
/**
* Clip files into clipboard
*
* @param _action
* @param _elems
*/
clipboard(_action, _elems) {
this.clipboard_is_cut = _action.id == "cut";
let clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard')) || {
type: [],
selected: []
};
if (_action.id != "add") {
clipboard = {
type: [],
selected: []
};
}
// When pasting we need to know the type of data - pull from actions
let drag = _elems[0].getSelectedLinks('drag').links;
for (let k in drag) {
if (drag[k].enabled && drag[k].actionObj.dragType.length > 0) {
clipboard.type = clipboard.type.concat(drag[k].actionObj.dragType);
}
}
clipboard.type = jQuery.unique(clipboard.type);
// egwAction is a circular structure and can't be stringified so just take what we want
// Hopefully that's enough for the action handlers
for (let k in _elems) {
if (_elems[k].id)
clipboard.selected.push({ id: _elems[k].id, data: _elems[k].data });
}
// Save it in session
egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify(clipboard));
this.clipboard_tooltips();
}
/**
* Paste files into current directory or mail them
*
* @param _type 'paste', 'linkpaste', 'mailpaste'
*/
paste(_type) {
let clipboard_files = this.get_clipboard_files();
if (clipboard_files.length == 0) {
alert(this.egw.lang('Clipboard is empty!'));
return;
}
switch (_type) {
case 'mailpaste':
this.open_mail(clipboard_files);
break;
case 'paste':
this._do_action(this.clipboard_is_cut ? 'move' : 'copy', clipboard_files);
if (this.clipboard_is_cut) {
this.clipboard_is_cut = false;
clipboard_files = [];
this.clipboard_tooltips();
}
break;
case 'linkpaste':
this._do_action('symlink', clipboard_files);
break;
}
}
/**
* Pass action to server
*
* @param _action
* @param _elems
*/
action(_action, _elems) {
let paths = this._elems2paths(_elems);
let path = this.get_path(_action && _action.parent.data.nextmatch.getInstanceManager().uniqueId || false);
this._do_action(_action.id, paths, true, path);
}
/**
* Prompt user for directory to create
*
* @param {egwAction|undefined} action Action, event or undefined if called directly
* @param {egwActionObject[] | undefined} selected Selected row, or undefined if called directly
*/
createdir(action, selected) {
let self = this;
et2_dialog.show_prompt(function (button, dir) {
if (button && dir) {
let path = self.get_path(action && action.parent ? action.parent.data.nextmatch.getInstanceManager().uniqueId : false);
if (action && action instanceof egwAction) {
let paths = self._elems2paths(selected);
if (paths[0])
path = paths[0];
// check if target is a file --> use it's directory instead
if (selected[0].id || path) {
let data = egw.dataGetUIDdata(selected[0].id || 'filemanager::' + path);
if (data && data.data.mime != 'httpd/unix-directory') {
path = self.dirname(path);
}
}
}
self._do_action('createdir', egw.encodePathComponent(dir), true, path); // true=synchronous request
self.change_dir((path == '/' ? '' : path) + '/' + egw.encodePathComponent(dir));
}
}, this.egw.lang('New directory'), this.egw.lang('Create directory'));
}
/**
* Prompt user for directory to create
*/
symlink() {
let self = this;
et2_dialog.show_prompt(function (button, target) {
if (button && target) {
self._do_action('symlink', target);
}
}, this.egw.lang('Link target'), this.egw.lang('Create link'));
}
/**
* Run a serverside action via an ajax call
*
* @param _type 'move_file', 'copy_file', ...
* @param _selected selected paths
* @param _sync send a synchronous ajax request
* @param _path defaults to current path
*/
_do_action(_type, _selected, _sync, _path) {
if (typeof _path == 'undefined')
_path = this.get_path();
egw.json('filemanager_ui::ajax_action', [_type, _selected, _path], this._do_action_callback, this, !_sync, this).sendRequest();
}
/**
* Callback for _do_action ajax call
*
* @param _data
*/
_do_action_callback(_data) {
window.egw_refresh(_data.msg, this.appname, undefined, undefined, undefined, undefined, undefined, _data.type);
}
/**
* Force download of a file by appending '?download' to it's download url
*
* @param _action
* @param _senders
*/
force_download(_action, _senders) {
for (let i = 0; i < _senders.length; i++) {
let data = egw.dataGetUIDdata(_senders[i].id);
let url = data ? data.data.download_url : '/webdav.php' + this.id2path(_senders[i].id);
if (url[0] == '/')
url = egw.link(url);
let a = document.createElement('a');
if (typeof a.download == "undefined") {
window.location = (url + "?download");
return false;
}
// Multiple file download for those that support it
let $a = jQuery(a)
.prop('href', url)
.prop('download', data ? data.data.name : "")
.appendTo(this.et2.getDOMNode());
window.setTimeout(jQuery.proxy(function () {
let evt = document.createEvent('MouseEvent');
evt.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
this[0].dispatchEvent(evt);
this.remove();
}, $a), 100 * i);
}
return false;
}
/**
* Check to see if the browser supports downloading multiple files
* (using a tag download attribute) to enable/disable the context menu
*
* @param {egwAction} action
* @param {egwActionObject[]} selected
*/
is_multiple_allowed(action, selected) {
let allowed = typeof document.createElement('a').download != "undefined";
if (typeof action == "undefined")
return allowed;
return (allowed || selected.length <= 1) && action.not_disableClass.apply(action, arguments);
}
/**
* Change directory
*
* @param {string} _dir directory to change to incl. '..' for one up
* @param {et2_widget} widget
*/
change_dir(_dir, widget) {
for (var etemplate_name in this.path_widget)
break;
if (widget)
etemplate_name = widget.getInstanceManager().uniqueId;
// Make sure everything is in place for changing directory
if (!this.et2 || typeof etemplate_name !== 'string' ||
typeof this.path_widget[etemplate_name] === 'undefined') {
return false;
}
switch (_dir) {
case '..':
_dir = this.dirname(this.get_path(etemplate_name));
break;
case '~':
_dir = this.et2.getWidgetById('nm').options.settings.home_dir;
break;
}
this.path_widget[etemplate_name].set_value(_dir);
}
/**
* Toggle view between tiles and rows
*
* @param {string|Event} [view] - Specify what to change the view to. Either 'tile' or 'row'.
* Or, if this is used as a callback view is actually the event, and we need to find the view.
* @param {et2_widget} [button_widget] - The widget that's calling
*/
change_view(view, button_widget) {
let et2 = etemplate2.getById('filemanager-index');
let nm;
if (et2 && et2.widgetContainer.getWidgetById('nm')) {
nm = et2.widgetContainer.getWidgetById('nm');
}
if (!nm) {
egw.debug('warn', 'Could not find nextmatch to change view');
return;
}
if (!button_widget) {
button_widget = nm.getWidgetById('button[change_view]');
}
if (button_widget && button_widget.instanceOf(et2_button)) {
// Switch view based on button icon, since controller can get re-created
if (typeof view != 'string') {
view = button_widget.options.image.replace('list_', '');
}
// Toggle button icon to the other view
//todo: nm.controller needs to be changed to nm.getController after merging typescript branch into master
button_widget.set_image("list_" + (view == et2_nextmatch_controller.VIEW_ROW ? et2_nextmatch_controller.VIEW_TILE : et2_nextmatch_controller.VIEW_ROW));
button_widget.set_statustext(view == et2_nextmatch_controller.VIEW_ROW ? this.egw.lang("Tile view") : this.egw.lang('List view'));
}
nm.set_view(view);
// Put it into active filters (but don't refresh)
nm.activeFilters["view"] = view;
// Change template to match
let template = view == et2_nextmatch_controller.VIEW_ROW ? 'filemanager.index.rows' : 'filemanager.tile';
nm.set_template(template);
// Wait for template to load, then refresh
template = nm.getWidgetById(template);
if (template && template.loading) {
template.loading.done(function () {
nm.applyFilters({ view: view });
});
}
}
/**
* Open/active an item
*
* @param _action
* @param _senders
*/
open(_action, _senders) {
let data = egw.dataGetUIDdata(_senders[0].id);
let path = this.id2path(_senders[0].id);
this.et2 = this.et2 ? this.et2 : etemplate2.getById('filemanager-index').widgetContainer;
let mime = this.et2._inst.widgetContainer.getWidgetById('$row');
// try to get mime widget DOM node out of the row DOM
let mime_dom = jQuery(_senders[0].iface.getDOMNode()).find("span#filemanager-index_\\$row");
let fe = egw_get_file_editor_prefered_mimes();
// symlinks dont have mime 'http/unix-directory', but server marks all directories with class 'isDir'
if (data.data.mime == 'httpd/unix-directory' || data.data['class'] && data.data['class'].split(/ +/).indexOf('isDir') != -1) {
this.change_dir(path, _action.parent.data.nextmatch || this.et2);
}
else if (mime && data.data.mime.match(mime.mime_regexp) && mime_dom.length > 0) {
mime_dom.click();
}
else if (mime && this.isEditable(_action, _senders) && fe && fe.edit) {
egw.open_link(egw.link('/index.php', {
menuaction: fe.edit.menuaction,
path: decodeURIComponent(data.data.download_url)
}), '', fe.edit_popup);
}
else {
let url;
// Build ViewerJS url
if (data.data.mime.match(/application\/vnd\.oasis\.opendocument/) &&
egw.preference('document_doubleclick_action', 'filemanager') == 'collabeditor') {
url = '/ViewerJS/#..' + data.data.download_url;
}
egw.open({ path: path, type: data.data.mime, download_url: url }, 'file', 'view', null, '_browser');
}
return false;
}
/**
* Edit prefs of current directory
*
* @param _action
* @param _senders
*/
editprefs(_action, _senders) {
let path = typeof _senders != 'undefined' ? this.id2path(_senders[0].id) : this.get_path(_action && _action.parent.data.nextmatch.getInstanceManager().uniqueId || false);
egw().open_link(egw.link('/index.php', {
menuaction: 'filemanager.filemanager_ui.file',
path: path
}), 'fileprefs', '510x425');
}
/**
* Callback to check if the paste action is enabled. We also update the
* clipboard historical targets here as well
*
* @param {egwAction} _action drop action we're checking
* @param {egwActionObject[]} _senders selected files
* @param {egwActionObject} _target Drop or context menu activated on this one
*
* @returns boolean true if enabled, false otherwise
*/
paste_enabled(_action, _senders, _target) {
// Need files in the clipboard for this
let clipboard_files = this.get_clipboard_files();
if (clipboard_files.length === 0) {
return false;
}
// Parent action (paste) gets run through here as well, but needs no
// further processing
if (_action.id == 'paste')
return true;
if (_action.canHaveChildren.indexOf('drop') == -1) {
_action.canHaveChildren.push('drop');
}
let actions = [];
// Current directory
let current_dir = this.get_path();
let dir = egw.dataGetUIDdata('filemanager::' + current_dir);
let path_widget = etemplate2.getById('filemanager-index').widgetContainer.getWidgetById('button[createdir]');
actions.push({
id: _action.id + '_current', caption: current_dir, path: current_dir,
enabled: dir && dir.data && dir.data.class && dir.data.class.indexOf('noEdit') === -1 ||
!dir && path_widget && !path_widget.options.readonly
});
// Target, if directory
let target_dir = this.id2path(_target.id);
dir = egw.dataGetUIDdata(_target.id);
actions.push({
id: _action.id + '_target',
caption: target_dir,
path: target_dir,
enabled: _target && _target.iface && jQuery(_target.iface.getDOMNode()).hasClass('isDir') &&
(dir && dir.data && dir.data.class && dir.data.class.indexOf('noEdit') === -1 || !dir)
});
// Last 10 folders
let previous_dsts = jQuery.extend([], egw.preference('drop_history', this.appname));
let action_index = 0;
for (let i = 0; i < 10; i++) {
let path = i < previous_dsts.length ? previous_dsts[i] : '';
actions.push({
id: _action.id + '_target_' + action_index++,
caption: path,
path: path,
group: 2,
enabled: path && !(current_dir && path === current_dir || target_dir && path === target_dir)
});
}
// Common stuff, every action needs these
for (let i = 0; i < actions.length; i++) {
//actions[i].type = 'drop',
actions[i].acceptedTypes = _action.acceptedTypes;
actions[i].no_lang = true;
actions[i].hideOnDisabled = true;
}
_action.updateActions(actions);
// Create paste action
// This injects the clipboard data and calls the original handler
let paste_exec = function (action, selected) {
// Add in clipboard as a sender
let clipboard = JSON.parse(egw.getSessionItem('phpgwapi', 'egw_clipboard'));
// Set a flag so apps can tell the difference, if they need to
action.set_onExecute(action.parent.onExecute.fnct);
action.execute(clipboard.selected, selected[0]);
// Clear the clipboard, the files are not there anymore
if (action.id.indexOf('move') !== -1) {
egw.setSessionItem('phpgwapi', 'egw_clipboard', JSON.stringify({
type: [],
selected: []
}));
}
};
for (let i = 0; i < actions.length; i++) {
_action.getActionById(actions[i].id).onExecute = jQuery.extend(true, {}, _action.onExecute);
_action.getActionById(actions[i].id).set_onExecute(paste_exec);
}
return actions.length > 0;
}
/**
* File(s) droped
*
* @param _action
* @param _elems
* @param _target
* @returns
*/
drop(_action, _elems, _target) {
let src = this._elems2paths(_elems);
// Target will be missing ID if directory is empty
// so start with the current directory
let parent = _action;
let nm = _target ? _target.manager.data.nextmatch : null;
while (!nm && parent.parent) {
parent = parent.parent;
if (parent.data.nextmatch)
nm = parent.data.nextmatch;
}
let nm_dst = this.get_path(nm.getInstanceManager().uniqueId || false);
let dst;
// Action specifies a destination, target does not matter
if (_action.data && _action.data.path) {
dst = _action.data.path;
}
// File(s) were dropped on a row, they want them inside
else if (_target) {
dst = '';
let paths = this._elems2paths([_target]);
if (paths[0])
dst = paths[0];
// check if target is a file --> use it's directory instead
if (_target.id) {
let data = egw.dataGetUIDdata(_target.id);
if (!data || data.data.mime != 'httpd/unix-directory') {
dst = this.dirname(dst);
}
}
}
// Remember the target for next time
let previous_dsts = jQuery.extend([], egw.preference('drop_history', this.appname));
previous_dsts.unshift(dst);
previous_dsts = Array.from(new Set(previous_dsts)).slice(0, 9);
egw.set_preference(this.appname, 'drop_history', previous_dsts);
// Actual action id will be something like file_drop_{move|copy|link}[_other_id],
// but we need to send move, copy or link
let action_id = _action.id.replace("file_drop_", '').split('_', 1)[0];
this._do_action(action_id, src, false, dst || nm_dst);
}
/**
* Handle a native / HTML5 file drop from system
*
* This is a callback from nextmatch to prevent the default link action, and just upload instead.
*
* @param {string} row_uid UID of the row the files were dropped on
* @param {Files[]} files
*/
filedrop(row_uid, files) {
let self = this;
let data = egw.dataGetUIDdata(row_uid);
files = files || window.event.dataTransfer.files;
let path = typeof data != 'undefined' && data.data.mime == "httpd/unix-directory" ? data.data.path : this.get_path();
let widget = this.et2.getWidgetById('upload');
// Override finish to specify a potentially different path
let old_onfinishone = widget.options.onFinishOne;
let old_onfinish = widget.options.onFinish;
widget.options.onFinishOne = function (_event, _file_count) {
self.upload(_event, _file_count, path);
};
widget.options.onFinish = function () {
widget.options.onFinish = old_onfinish;
widget.options.onFinishOne = old_onfinishone;
};
// This triggers the upload
widget.set_value(files);
// Return false to prevent the link
return false;
}
/**
* Change readonly state for given directory
*
* Get call/transported with each get_rows call, but should only by applied to UI if matching curent dir
*
* @param {string} _path
* @param {boolean} _ro
*/
set_readonly(_path, _ro) {
//alert('set_readonly("'+_path+'", '+_ro+')');
if (!this.path_widget) // widget not yet ready, try later
{
this.readonly = [_path, _ro];
return;
}
for (let id in this.path_widget) {
let path = this.get_path(id);
if (_path == path) {
let ids = ['button[linkpaste]', 'button[paste]', 'button[createdir]', 'button[symlink]', 'upload', 'new'];
for (let i = 0; i < ids.length; ++i) {
let widget = etemplate2.getById(id).widgetContainer.getWidgetById(ids[i]);
if (widget) {
widget.set_readonly(_ro);
}
}
}
}
}
/**
* Row or filename in select-file dialog clicked
*
* @param {jQuery.event} event
* @param {et2_widget} widget
*/
select_clicked(event, widget) {
var _a;
if ((_a = widget === null || widget === void 0 ? void 0 : widget.value) === null || _a === void 0 ? void 0 : _a.is_dir) {
let path = null;
// Cannot do this, there are multiple widgets named path
// widget.getRoot().getWidgetById("path");
widget.getRoot().iterateOver(function (widget) {
if (widget.id == "path")
path = widget;
}, null, et2_textbox);
if (path) {
path.set_value(widget.value.path);
}
}
else if (this.et2 && this.et2.getArrayMgr('content').getEntry('mode') != 'open-multiple') {
let editfield = this.et2.getWidgetById('name');
if (editfield) {
editfield.set_value(widget.value.name);
}
}
else {
let file = widget.value.name;
widget.getParent().iterateOver(function (widget) {
if (widget.options.selected_value == file) {
widget.set_value(widget.get_value() == file ? widget.options.unselected_value : file);
}
}, null, et2_checkbox);
}
// Stop event or it will toggle back off
event.preventDefault();
event.stopPropagation();
return false;
}
/**
* Set Sudo button's label and change its onclick handler according to its action
*
* @param {widget object} _widget sudo buttononly
* @param {string} _action string of action type {login|logout}
*/
set_sudoButton(_widget, _action) {
let widget = _widget || this.et2.getWidgetById('sudouser');
if (widget) {
switch (_action) {
case 'login':
widget.set_label('Logout');
widget.getRoot().getInstanceManager().submit(widget);
break;
default:
widget.set_label('Superuser');
widget.onclick = function () {
jQuery('.superuser').css('display', 'inline');
};
}
}
}
/**
* Open file a file dialog from EPL, warn if EPL is not available
*/
fileafile() {
if (this.egw.user('apps').stylite) {
this.egw.open_link('/index.php?menuaction=stylite.stylite_filemanager.upload&path=' + this.get_path(), '_blank', '670x320');
}
else {
// This is shown if stylite code is there, but the app is not available
et2_dialog.show_dialog(function (_button) {
if (_button == et2_dialog.YES_BUTTON)
window.open('http://www.egroupware.org/EPL', '_blank');
return true;
}, this.egw.lang('this feature is only available in epl version.') + "\n\n" +
this.egw.lang('You can use regular upload [+] button to upload files.') + "\n\n" +
this.egw.lang('Do you want more information about EPL subscription?'), this.egw.lang('File a file'), undefined, et2_dialog.BUTTONS_YES_NO, et2_dialog.QUESTION_MESSAGE);
}
}
/**
* create a share-link for the given entry
* Overriden from parent to handle empty directories
*
* @param {egwAction} _action egw actions
* @param {egwActionObject[]} _senders selected nm row
* @param {egwActionObject} _target Drag source. Not used here.
* @param {Boolean} _writable Allow edit access from the share.
* @param {Boolean} _files Allow access to files from the share.
* @param {Function} _callback Callback with results
* @returns {Boolean} returns false if not successful
*/
share_link(_action, _senders, _target, _writable, _files, _callback) {
// Check to see if we're in the empty row (No matches found.) and use current path
let path = _senders[0].id;
if (!path) {
_senders[0] = { id: this.get_path() };
}
// Pass along any action data
let _extra = {};
for (let i in _action.data) {
if (i.indexOf('share') == 0) {
_extra[i] = _action.data[i];
}
}
super.share_link(_action, _senders, _target, _writable, _files, _callback, _extra);
}
/**
* Share-link callback
* @param {object} _data
*/
_share_link_callback(_data) {
if (_data.msg || _data.share_link)
window.egw_refresh(_data.msg, this.appname);
console.log("_data", _data);
let app = this;
let copy_link_to_clipboard = function (evt) {
let $target = jQuery(evt.target);
$target.select();
try {
let successful = document.execCommand('copy');
if (successful) {
egw.message(app.egw.lang('Share link copied into clipboard'));
return true;
}
}
catch (e) { }
egw.message('Failed to copy the link!');
};
jQuery("body").on("click", "[name=share_link]", copy_link_to_clipboard);
et2_createWidget("dialog", {
callback: function () {
jQuery("body").off("click", "[name=share_link]", copy_link_to_clipboard);
return true;
},
title: _data.title ? _data.title : (_data.writable || _data.action === 'shareWritableLink' ?
this.egw.lang("Writable share link") : this.egw.lang("Readonly share link")),
template: _data.template,
width: 450,
value: { content: { "share_link": _data.share_link } }
});
}
/**
* Check if a row can have the Hidden Uploads action
* Needs to be a directory
*/
hidden_upload_enabled(_action, _senders) {
if (_senders[0].id == 'nm')
return false;
let data = egw.dataGetUIDdata(_senders[0].id);
let readonly = ((data === null || data === void 0 ? void 0 : data.data.class) || '').split(/ +/).indexOf('noEdit') >= 0;
// symlinks dont have mime 'http/unix-directory', but server marks all directories with class 'isDir'
return (!_senders[0].id || data.data.is_dir && !readonly);
}
/**
* View the link from an existing share
* (EPL only)
*
* @param {egwAction} _action The shareLink action
* @param {egwActionObject[]} _senders The row clicked on
*/
view_link(_action, _senders) {
let id = egw.dataGetUIDdata(_senders[0].id).data.share_id;
egw.json('stylite_filemanager::ajax_view_link', [id], this._share_link_callback, this, true, this).sendRequest();
return true;
}
/**
* This function copies the selected file/folder entry as webdav link into clipboard
*
* @param {object} _action egw actions
* @param {object} _senders selected nm row
* @returns {Boolean} returns false if not successful
*/
copy_link(_action, _senders) {
let data = egw.dataGetUIDdata(_senders[0].id);
let url = data ? data.data.download_url : '/webdav.php' + this.id2path(_senders[0].id);
if (url[0] == '/')
url = egw.link(url);
if (url.substr(0, 4) == 'http' && url.indexOf('://') <= 5) {
// it's already a full url
}
else {
let hostUrl = new URL(window.location.href);
url = hostUrl.origin + url;
}
if (url) {
let elem = jQuery(document.createElement('div'));
let range;
elem.text(url);
elem.appendTo('body');
if (document.selection) {
range = document.body.createTextRange();
range.moveToElementText(elem);
range.select();
}
else if (window.getSelection) {
range = document.createRange();
range.selectNode(elem[0]);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
}
let successful = false;
try {
successful = document.execCommand('copy');
if (successful) {
egw.message(this.egw.lang('WebDav link copied into clipboard'));
window.getSelection().removeAllRanges();
return true;
}
}
catch (e) { }
egw.message('Failed to copy the link!');
elem.remove();
return false;
}
}
/**
* Function to check wheter selected file is editable. ATM only .odt is supported.
*
* @param {object} _egwAction egw action object
* @param {object} _senders object of selected row
*
* @returns {boolean} returns true if is editable otherwise false
*/
isEditable(_egwAction, _senders) {
if (_senders.length > 1)
return false;
let data = egw.dataGetUIDdata(_senders[0].id);
let mime = this.et2.getInstanceManager().widgetContainer.getWidgetById('$row');
let fe = egw_get_file_editor_prefered_mimes(data.data.mime);
if (fe && fe.mime && !fe.mime[data.data.mime])
return false;
return !!data.data.mime.match(mime.mime_odf_regex);
}
/**
* Method to create a new document
* @param {object} _action either action or node
* @param {object} _selected either widget or selected row
*
* @return {boolean} returns true
*/
create_new(_action, _selected) {
let fe = egw.link_get_registry('filemanager-editor');
if (fe && fe["edit"]) {
egw.open_link(egw.link('/index.php', {
menuaction: fe["edit"].menuaction
}), '', fe["popup_edit"]);
}
return true;
}
}
app.classes.filemanager = filemanagerAPP;
//# sourceMappingURL=app.js.map