forked from extern/egroupware
Filemanager: Add copy to clipboard button to share link dialog
This commit is contained in:
parent
bde2b7f86d
commit
2659a8ab63
@ -786,7 +786,7 @@ export function egwPopupActionImplementation()
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
copyTextToClipboard(event, clipboard_action, os_clipboard_caption).then((successful) =>
|
egw.copyTextToClipboard(os_clipboard_caption, clipboard_action.data.target, event).then((successful) =>
|
||||||
{
|
{
|
||||||
// Fallback
|
// Fallback
|
||||||
if (typeof successful == "undefined")
|
if (typeof successful == "undefined")
|
||||||
@ -936,83 +936,4 @@ export function egwPopupActionImplementation()
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
return ai;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {etemplate2} from "../etemplate2";
|
|||||||
import {IegwAppLocal} from "../../jsapi/egw_global";
|
import {IegwAppLocal} from "../../jsapi/egw_global";
|
||||||
import interact from "@interactjs/interactjs";
|
import interact from "@interactjs/interactjs";
|
||||||
import type {InteractEvent} from "@interactjs/core/InteractEvent";
|
import type {InteractEvent} from "@interactjs/core/InteractEvent";
|
||||||
|
import {Et2Button} from "../Et2Button/Et2Button";
|
||||||
|
|
||||||
export interface DialogButton
|
export interface DialogButton
|
||||||
{
|
{
|
||||||
@ -326,6 +327,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
this._onOpen = this._onOpen.bind(this);
|
this._onOpen = this._onOpen.bind(this);
|
||||||
this._onClose = this._onClose.bind(this);
|
this._onClose = this._onClose.bind(this);
|
||||||
this._onClick = this._onClick.bind(this);
|
this._onClick = this._onClick.bind(this);
|
||||||
|
this._onButtonClick = this._onButtonClick.bind(this);
|
||||||
this._onMoveResize = this._onMoveResize.bind(this);
|
this._onMoveResize = this._onMoveResize.bind(this);
|
||||||
this._adoptTemplateButtons = this._adoptTemplateButtons.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("show", this._onOpen);
|
||||||
this._overlayCtrl.addEventListener('hide', this._onClose);
|
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);
|
window.setTimeout(this.open, 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -357,6 +361,7 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._overlayCtrl.removeEventListener("hide", this._onClose);
|
this._overlayCtrl.removeEventListener("hide", this._onClose);
|
||||||
this._overlayCtrl.removeEventListener("show", this._onOpen);
|
this._overlayCtrl.removeEventListener("show", this._onOpen);
|
||||||
|
this._overlayContentNode.removeEventListener("click", this._onButtonClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to wait for Overlay
|
// Need to wait for Overlay
|
||||||
@ -370,9 +375,6 @@ export class Et2Dialog extends Et2Widget(ScopedElementsMixin(SlotMixin(LionDialo
|
|||||||
{
|
{
|
||||||
await this._template_promise;
|
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]>
|
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)
|
_onClick(ev : MouseEvent)
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this._button_id = ev.target?.getAttribute("button_id") ? parseInt(ev.target?.getAttribute("button_id")) : (ev.target?.getAttribute("id") || null);
|
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)
|
// 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
|
// 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)
|
// which does not know what to do with it, as the dialog was initiated from client-side (no eT2 request)
|
||||||
|
@ -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;
|
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
|
* @param {Window|String} closed Window that was closed, or its name
|
||||||
* @returns {undefined}
|
* @returns {undefined}
|
||||||
*/
|
*/
|
||||||
windowClosed: function(appname, closed) {
|
windowClosed: function (appname, closed)
|
||||||
|
{
|
||||||
var closed_name = typeof closed == "string" ? closed : closed.name;
|
var closed_name = typeof closed == "string" ? closed : closed.name;
|
||||||
var closed_window = typeof closed == "string" ? null : closed;
|
var closed_window = typeof closed == "string" ? null : closed;
|
||||||
window.setTimeout(function() {
|
window.setTimeout(function ()
|
||||||
if(closed_window != null && !closed_window.closed) return;
|
{
|
||||||
|
if (closed_window != null && !closed_window.closed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var open_windows = JSON.parse(egw().getSessionItem(appname, 'windows')) || {};
|
var open_windows = JSON.parse(egw().getSessionItem(appname, 'windows')) || {};
|
||||||
delete open_windows[closed_name];
|
delete open_windows[closed_name];
|
||||||
egw.setSessionItem(appname, 'windows', JSON.stringify(open_windows));
|
egw.setSessionItem(appname, 'windows', JSON.stringify(open_windows));
|
||||||
}, 100);
|
}, 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<undefined|boolean>|Promise<void>}
|
||||||
|
*/
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
* Mail files action: open compose with already linked files
|
||||||
* We're only interested in hidden upload shares here, open_mail can handle
|
* We're only interested in hidden upload shares here, open_mail can handle
|
||||||
|
@ -3,6 +3,15 @@
|
|||||||
<!-- $Id$ -->
|
<!-- $Id$ -->
|
||||||
<overlay>
|
<overlay>
|
||||||
<template id="filemanager.share_dialog" template="" lang="" group="0" version="1.9.003">
|
<template id="filemanager.share_dialog" template="" lang="" group="0" version="1.9.003">
|
||||||
<textbox id="share_link" no_lang="1" class="et2_fullWidth"/>
|
<textbox id="share_link" no_lang="1" class="et2_fullWidth"
|
||||||
</template>
|
onclick="app.filemanager.copy_share_link">
|
||||||
</overlay>
|
<!-- just let this click bubble -->
|
||||||
|
<et2-button slot="suffix" image="copy" noSubmit="true" statustext="Copy to clipboard"/>
|
||||||
|
</textbox>
|
||||||
|
<styles>
|
||||||
|
et2-button[slot='suffix'] {
|
||||||
|
padding-inline-end: 0;
|
||||||
|
}
|
||||||
|
</styles>
|
||||||
|
</template>
|
||||||
|
</overlay>
|
Loading…
Reference in New Issue
Block a user