From 5e090a897690a7b3936d90eae6a89826b85576d4 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 23 May 2016 07:55:52 +0200 Subject: [PATCH] Added support to copy T#xxx into clipboard by pressing "ctrl+alt+y" if ticket is open in ticket zoom view. --- LICENSE-3RD-PARTY.txt | 5 + .../_application_controller_form.coffee | 2 +- .../widget/keyboard_shortcuts.coffee | 14 ++ .../javascripts/app/lib/base/clipboard.js | 125 ++++++++++++++++++ .../app/views/ticket_zoom/meta.jst.eco | 2 +- 5 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/app/lib/base/clipboard.js diff --git a/LICENSE-3RD-PARTY.txt b/LICENSE-3RD-PARTY.txt index 98274dce3..cabdcb4e4 100644 --- a/LICENSE-3RD-PARTY.txt +++ b/LICENSE-3RD-PARTY.txt @@ -24,6 +24,11 @@ Source: https://github.com/sliptree/bootstrap-tokenfield Copyright 2013-2014 Sliptree and other contributors License: MIT license ----------------------------------------------------------------------------- +clipboard.js +Source: https://github.com/lgarron/clipboard.js +Copyright: 2015 Lucas Garron (https://garron.net/) +License: MIT license +----------------------------------------------------------------------------- cropper.js Source: https://github.com/fengyuanchen/cropper Copyright: 2014-2015 Fengyuan Chen and contributors diff --git a/app/assets/javascripts/app/controllers/_application_controller_form.coffee b/app/assets/javascripts/app/controllers/_application_controller_form.coffee index 46ec95120..378cf602e 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_form.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_form.coffee @@ -281,7 +281,7 @@ class App.ControllerForm extends App.Controller # lookup relation if needed if action.bind.relation - data = App[action.bind.relation].find( value ) + data = App[action.bind.relation].find(value) value = data.name # check if value is used in condition diff --git a/app/assets/javascripts/app/controllers/widget/keyboard_shortcuts.coffee b/app/assets/javascripts/app/controllers/widget/keyboard_shortcuts.coffee index 325cc799f..670487219 100644 --- a/app/assets/javascripts/app/controllers/widget/keyboard_shortcuts.coffee +++ b/app/assets/javascripts/app/controllers/widget/keyboard_shortcuts.coffee @@ -240,6 +240,20 @@ App.Config.set( } ] } + { + where: 'Used in object views' + shortcuts: [ + { + key: 'y' + hotkeys: true + description: 'Copy current object number (e. g. Ticket#) into clipboard' + callback: -> + App.Event.trigger('keyboard_shortcuts_close') + text = $('.active.content .js-objectNumber').data('number') || '' + clipboard.copy(text) + } + ] + } ] } { diff --git a/app/assets/javascripts/app/lib/base/clipboard.js b/app/assets/javascripts/app/lib/base/clipboard.js new file mode 100644 index 000000000..09a4c3d00 --- /dev/null +++ b/app/assets/javascripts/app/lib/base/clipboard.js @@ -0,0 +1,125 @@ +// https://github.com/lgarron/clipboard.js +window.clipboard = (function(){ + + var clipboard = {}; + + clipboard.copy = (function() { + var _intercept = false; + var _data = null; // Map from data type (e.g. "text/html") to value. + + function cleanup() { + _intercept = false; + _data = null; + } + + document.addEventListener("copy", function(e) { + if (_intercept) { + for (var key in _data) { + e.clipboardData.setData(key, _data[key]); + } + e.preventDefault(); + } + }); + + return function(data) { + return new Promise(function(resolve, reject) { + _intercept = true; + if (typeof data === "string") { + _data = {"text/plain": data}; + } else if (data instanceof Node) { + _data = {"text/html": new XMLSerializer().serializeToString(data)}; + } else { + _data = data; + } + try { + if (document.execCommand("copy")) { + // document.execCommand is synchronous: http://www.w3.org/TR/2015/WD-clipboard-apis-20150421/#integration-with-rich-text-editing-apis + // So we can call resolve() back here. + cleanup(); + resolve(); + } + else { + throw new Error("Unable to copy. Perhaps it's not available in your browser?"); + } + } catch (e) { + cleanup(); + reject(e); + } + }); + }; + })(); + + clipboard.paste = (function() { + var _intercept = false; + var _resolve; + var _dataType; + + document.addEventListener("paste", function(e) { + if (_intercept) { + _intercept = false; + e.preventDefault(); + var resolve = _resolve; + _resolve = null; + resolve(e.clipboardData.getData(_dataType)); + } + }); + + return function(dataType) { + return new Promise(function(resolve, reject) { + _intercept = true; + _resolve = resolve; + _dataType = dataType || "text/plain"; + try { + if (!document.execCommand("paste")) { + _intercept = false; + reject(new Error("Unable to paste. Pasting only works in Internet Explorer at the moment.")); + } + } catch (e) { + _intercept = false; + reject(new Error(e)); + } + }); + }; + })(); + + // Handle IE behaviour. + if (typeof ClipboardEvent === "undefined" && + typeof window.clipboardData !== "undefined" && + typeof window.clipboardData.setData !== "undefined") { + + /*! promise-polyfill 2.0.1 */ + (function(a){function b(a,b){return function(){a.apply(b,arguments)}}function c(a){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof a)throw new TypeError("not a function");this._state=null,this._value=null,this._deferreds=[],i(a,b(e,this),b(f,this))}function d(a){var b=this;return null===this._state?void this._deferreds.push(a):void j(function(){var c=b._state?a.onFulfilled:a.onRejected;if(null===c)return void(b._state?a.resolve:a.reject)(b._value);var d;try{d=c(b._value)}catch(e){return void a.reject(e)}a.resolve(d)})}function e(a){try{if(a===this)throw new TypeError("A promise cannot be resolved with itself.");if(a&&("object"==typeof a||"function"==typeof a)){var c=a.then;if("function"==typeof c)return void i(b(c,a),b(e,this),b(f,this))}this._state=!0,this._value=a,g.call(this)}catch(d){f.call(this,d)}}function f(a){this._state=!1,this._value=a,g.call(this)}function g(){for(var a=0,b=this._deferreds.length;b>a;a++)d.call(this,this._deferreds[a]);this._deferreds=null}function h(a,b,c,d){this.onFulfilled="function"==typeof a?a:null,this.onRejected="function"==typeof b?b:null,this.resolve=c,this.reject=d}function i(a,b,c){var d=!1;try{a(function(a){d||(d=!0,b(a))},function(a){d||(d=!0,c(a))})}catch(e){if(d)return;d=!0,c(e)}}var j=c.immediateFn||"function"==typeof setImmediate&&setImmediate||function(a){setTimeout(a,1)},k=Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)};c.prototype["catch"]=function(a){return this.then(null,a)},c.prototype.then=function(a,b){var e=this;return new c(function(c,f){d.call(e,new h(a,b,c,f))})},c.all=function(){var a=Array.prototype.slice.call(1===arguments.length&&k(arguments[0])?arguments[0]:arguments);return new c(function(b,c){function d(f,g){try{if(g&&("object"==typeof g||"function"==typeof g)){var h=g.then;if("function"==typeof h)return void h.call(g,function(a){d(f,a)},c)}a[f]=g,0===--e&&b(a)}catch(i){c(i)}}if(0===a.length)return b([]);for(var e=a.length,f=0;fd;d++)a[d].then(b,c)})},"undefined"!=typeof module&&module.exports?module.exports=c:a.Promise||(a.Promise=c)})(this); + + clipboard.copy = function(data) { + return new Promise(function(resolve, reject) { + // IE supports string and URL types: https://msdn.microsoft.com/en-us/library/ms536744(v=vs.85).aspx + // We only support the string type for now. + if (typeof data !== "string" && !("text/plain" in data)) { + throw new Error("You must provide a text/plain type."); + } + + var strData = (typeof data === "string" ? data : data["text/plain"]); + var copySucceeded = window.clipboardData.setData("Text", strData); + if (copySucceeded) { + resolve(); + } else { + reject(new Error("Copying was rejected.")); + } + }); + }; + + clipboard.paste = function() { + return new Promise(function(resolve, reject) { + var strData = window.clipboardData.getData("Text"); + if (strData) { + resolve(strData); + } else { + // The user rejected the paste request. + reject(new Error("Pasting was rejected.")); + } + }); + }; + } + + return clipboard; +})(); diff --git a/app/assets/javascripts/app/views/ticket_zoom/meta.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/meta.jst.eco index 2fe7bdac4..5f1130a17 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/meta.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/meta.jst.eco @@ -1,3 +1,3 @@ - <%- @C('ticket_hook') %> <%- @ticket.number %> - <%- @T('created') %> <%- @humanTime(@ticket.created_at) %> <% if !@isCustomer && @ticket.escalation_time: %> - <%- @T('escalation') %> <%- @humanTime(@ticket.escalation_time, true) %><% end %> + <%- @C('ticket_hook') %> <%- @ticket.number %> - <%- @T('created') %> <%- @humanTime(@ticket.created_at) %> <% if !@isCustomer && @ticket.escalation_time: %> - <%- @T('escalation') %> <%- @humanTime(@ticket.escalation_time, true) %><% end %> \ No newline at end of file