diff --git a/api/js/egw_action/egw_action_popup.js b/api/js/egw_action/egw_action_popup.js index 613a89abb0..29950e5de7 100644 --- a/api/js/egw_action/egw_action_popup.js +++ b/api/js/egw_action/egw_action_popup.js @@ -786,7 +786,7 @@ export function egwPopupActionImplementation() { try { - copyTextToClipboard(event, clipboard_action, os_clipboard_caption).then((successful) => + egw.copyTextToClipboard(os_clipboard_caption, clipboard_action.data.target, event).then((successful) => { // Fallback if (typeof successful == "undefined") @@ -936,83 +936,4 @@ export function egwPopupActionImplementation() } }; return ai; -} - -/** - * Try some deprecated ways of copying to the OS clipboard - * - * @param event - * @param clipboard_action - * @param text - * @returns {boolean} - */ -function fallbackCopyTextToClipboard(event, clipboard_action, text) -{ - // Cancel any no-select css - var target = jQuery(clipboard_action.data.target); - var old_select = target.css('user-select'); - target.css('user-select', 'all'); - - var range = document.createRange(); - range.selectNode(clipboard_action.data.target); - window.getSelection().removeAllRanges(); - window.getSelection().addRange(range); - - target.css('user-select', old_select); - - // detect we are in IE via checking setActive, since it's - // only supported in IE, and make sure there's clipboardData object - if (typeof event.target.setActive != 'undefined' && window.clipboardData) - { - window.clipboardData.setData('Text', jQuery(clipboard_action.data.target).text().trim()); - } - if (event.clipboardData) - { - event.clipboardData.setData('text/plain', jQuery(clipboard_action.data.target).text().trim()); - event.clipboardData.setData('text/html', jQuery(clipboard_action.data.target).html()); - } - let textArea; - if (!window.clipboardData) - { - - textArea = document.createElement("textarea"); - textArea.value = text; - - // Avoid scrolling to bottom - textArea.style.top = "0"; - textArea.style.left = "0"; - textArea.style.position = "fixed"; - - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - } - - let successful = false; - try - { - successful = document.execCommand('copy'); - const msg = successful ? 'successful' : 'unsuccessful'; - console.log('Fallback: Copying text command was ' + msg); - } - catch (err) - { - successful = false; - } - - document.body.removeChild(textArea); - return successful; -} - -function copyTextToClipboard(event, action, text) -{ - if (!navigator.clipboard) - { - let success = fallbackCopyTextToClipboard(event, action, text); - return Promise.resolve(success ? undefined : false); - } - // Use Clipboard API - return navigator.clipboard.writeText(text); -} - - +} \ No newline at end of file diff --git a/api/js/etemplate/Et2Dialog/Et2Dialog.ts b/api/js/etemplate/Et2Dialog/Et2Dialog.ts index 0f2c248313..cadf3154ea 100644 --- a/api/js/etemplate/Et2Dialog/Et2Dialog.ts +++ b/api/js/etemplate/Et2Dialog/Et2Dialog.ts @@ -21,6 +21,7 @@ import {etemplate2} from "../etemplate2"; import {IegwAppLocal} from "../../jsapi/egw_global"; import interact from "@interactjs/interactjs"; import type {InteractEvent} from "@interactjs/core/InteractEvent"; +import {Et2Button} from "../Et2Button/Et2Button"; export interface DialogButton { @@ -326,6 +327,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo this._onOpen = this._onOpen.bind(this); this._onClose = this._onClose.bind(this); this._onClick = this._onClick.bind(this); + this._onButtonClick = this._onButtonClick.bind(this); this._onMoveResize = this._onMoveResize.bind(this); this._adoptTemplateButtons = this._adoptTemplateButtons.bind(this); @@ -348,6 +350,8 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo this._overlayCtrl?.addEventListener("show", this._onOpen); this._overlayCtrl.addEventListener('hide', this._onClose); + // Bind on the ancestor, not the buttons, so their click handler gets a chance to run + this._overlayContentNode.addEventListener("click", this._onButtonClick); window.setTimeout(this.open, 0); }); } @@ -357,6 +361,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo super.disconnectedCallback(); this._overlayCtrl.removeEventListener("hide", this._onClose); this._overlayCtrl.removeEventListener("show", this._onOpen); + this._overlayContentNode.removeEventListener("click", this._onButtonClick); } // Need to wait for Overlay @@ -370,9 +375,6 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo { await this._template_promise; } - - // This calls _onClose() when the dialog is closed - this._overlayContentNode.querySelectorAll("et2-button").forEach((button) => button.addEventListener("click", this._onClick)); } getComplete() : Promise<[number, Object]> @@ -409,13 +411,28 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo } } + /** + * Only internally do our onClick on buttons + * This calls _onClose() when the dialog is closed + * + * @param {MouseEvent} ev + * @returns {boolean} + */ + _onButtonClick(ev : MouseEvent) + { + if(ev.target instanceof Et2Button) + { + return this._onClick(ev); + } + } + _onClick(ev : MouseEvent) { // @ts-ignore this._button_id = ev.target?.getAttribute("button_id") ? parseInt(ev.target?.getAttribute("button_id")) : (ev.target?.getAttribute("id") || null); // we need to consider still buttons used in dialogs that may actually submit and have server-side interactions(eg.vfsSelect) - if (!ev.target?.getInstanceManager()?._etemplate_exec_id) + if(!ev.target?.getInstanceManager()?._etemplate_exec_id) { // we always need to stop the event as otherwise the result would be submitted to server-side eT2 handler // which does not know what to do with it, as the dialog was initiated from client-side (no eT2 request) diff --git a/api/js/jsapi/egw_utils.js b/api/js/jsapi/egw_utils.js index d730766a47..61c9d3c743 100644 --- a/api/js/jsapi/egw_utils.js +++ b/api/js/jsapi/egw_utils.js @@ -143,6 +143,76 @@ egw.extend('utils', egw.MODULE_GLOBAL, function() } } + + /** + * Try some deprecated ways of copying to the OS clipboard + * + * @param event Optional, but if you have an event we can try some things on it + * @param target_element Element whose contents you're trying to copy + * @param text Actual text. Usually target_element.value. + * @returns {boolean} + */ + function fallbackCopyTextToClipboard(event, target_element, text) + { + // Cancel any no-select css + if (target_element) + { + let old_select = target_element.style.userSelect; + target_element.style.userSelect = 'all' + + let range = document.createRange(); + range.selectNode(target_element); + window.getSelection().removeAllRanges(); + window.getSelection().addRange(range); + + target_element.style.userSelect = old_select; + + + // detect we are in IE via checking setActive, since it's + // only supported in IE, and make sure there's clipboardData object + if (event && typeof event.target.setActive != 'undefined' && window.clipboardData) + { + window.clipboardData.setData('Text', target_element.textContent.trim()); + } + if (event && event.clipboardData) + { + event.clipboardData.setData('text/plain', target_element.textContent.trim()); + event.clipboardData.setData('text/html', target_element.outerHTML); + } + } + let textArea; + if (!window.clipboardData) + { + + textArea = document.createElement("textarea"); + textArea.value = text; + + // Avoid scrolling to bottom + textArea.style.top = "0"; + textArea.style.left = "0"; + textArea.style.position = "fixed"; + + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + } + + let successful = false; + try + { + successful = document.execCommand('copy'); + const msg = successful ? 'successful' : 'unsuccessful'; + console.log('Fallback: Copying text command was ' + msg); + } + catch (err) + { + successful = false; + } + + document.body.removeChild(textArea); + return successful; + } + var uid_counter = 0; /** @@ -410,16 +480,41 @@ egw.extend('utils', egw.MODULE_GLOBAL, function() * @param {Window|String} closed Window that was closed, or its name * @returns {undefined} */ - windowClosed: function(appname, closed) { + windowClosed: function (appname, closed) + { var closed_name = typeof closed == "string" ? closed : closed.name; var closed_window = typeof closed == "string" ? null : closed; - window.setTimeout(function() { - if(closed_window != null && !closed_window.closed) return; + window.setTimeout(function () + { + if (closed_window != null && !closed_window.closed) + { + return; + } var open_windows = JSON.parse(egw().getSessionItem(appname, 'windows')) || {}; delete open_windows[closed_name]; egw.setSessionItem(appname, 'windows', JSON.stringify(open_windows)); }, 100); + }, + + /** + * Copy text to the clipboard + * + * @param text Actual text to copy. Usually target_element.value + * @param target_element Optional, but useful for fallback copy attempts + * @param event Optional, but if you have an event we can try some fallback options with it + * + * @returns {Promise|Promise} + */ + copyTextToClipboard: function (text, target_element, event) + { + if (!navigator.clipboard) + { + let success = fallbackCopyTextToClipboard(event, target_element, text); + return Promise.resolve(success ? undefined : false); + } + // Use Clipboard API + return navigator.clipboard.writeText(text); } }; diff --git a/filemanager/js/filemanager.ts b/filemanager/js/filemanager.ts index 4c06113f09..bba4c9216a 100644 --- a/filemanager/js/filemanager.ts +++ b/filemanager/js/filemanager.ts @@ -342,6 +342,22 @@ export class filemanagerAPP extends EgwApp }); } + /** + * Copy a share link to the system clipboard + * + * @param widget + */ + copy_share_link(ev, widget) + { + egw.copyTextToClipboard(widget.value, widget, ev).then((success) => + { + if(success !== false) + { + egw.message(egw.lang('share link copied into clipboard')); + } + }); + } + /** * Mail files action: open compose with already linked files * We're only interested in hidden upload shares here, open_mail can handle diff --git a/filemanager/templates/default/share_dialog.xet b/filemanager/templates/default/share_dialog.xet index 4a982a9b7c..306ff4ca06 100644 --- a/filemanager/templates/default/share_dialog.xet +++ b/filemanager/templates/default/share_dialog.xet @@ -3,6 +3,15 @@ - + + + + + + et2-button[slot='suffix'] { + padding-inline-end: 0; + } + + + \ No newline at end of file