From 6a89eeedf3e2130031ac75b8329037424f936013 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fernando=20San=20Juli=C3=A1n?= <fjsanjulian@hotmail.com>
Date: Mon, 3 Aug 2015 21:27:07 +0200
Subject: [PATCH 1/2] browser supports copy detection

---
 .gopmfile                   |  2 ++
 modules/middleware/repo.go  | 26 ++++++++++++++++++++++++++
 templates/ng/base/head.tmpl |  2 +-
 3 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/.gopmfile b/.gopmfile
index 0a70cccee..d4cd97017 100644
--- a/.gopmfile
+++ b/.gopmfile
@@ -33,6 +33,8 @@ golang.org/x/net = commit:937a34c9de13
 golang.org/x/text = commit:5b2527008a4c
 gopkg.in/ini.v1 = commit:caf3f03ad9
 gopkg.in/redis.v2 = commit:e617904962
+github.com/hashicorp/go-version = commit:999359b6b7
+github.com/mssola/user_agent = commit:f659b98638
 
 [res]
 include = etc|public|scripts|templates
diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go
index a200d6d66..e8a362bd4 100644
--- a/modules/middleware/repo.go
+++ b/modules/middleware/repo.go
@@ -16,6 +16,14 @@ import (
 	"github.com/gogits/gogs/modules/git"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/setting"
+
+	"github.com/hashicorp/go-version"
+	"github.com/mssola/user_agent"
+)
+
+const (
+	FIREFOX_COPY_SUPPORT = "41.0"
+	CHROME_COPY_SUPPORT = "43.0.2356"
 )
 
 func ApiRepoAssignment() macaron.Handler {
@@ -345,6 +353,24 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
 
 		ctx.Data["BranchName"] = ctx.Repo.BranchName
 		ctx.Data["CommitId"] = ctx.Repo.CommitId
+
+		userAgent := ctx.Req.Header.Get("User-Agent")
+		ua := user_agent.New(userAgent);
+		browserName, browserVer := ua.Browser()
+
+		sliceVer := strings.Split(browserVer, ".")
+		var max int
+		if max = len(sliceVer); 3 < max {
+			max = 3
+		}
+		browserVer = strings.Join(sliceVer[:max], ".")
+
+		browserVersion, err := version.NewVersion(browserVer)
+		chromeConstraint, err := version.NewConstraint(">= " + CHROME_COPY_SUPPORT)
+		firefoxConstraint, err := version.NewConstraint(">= " + FIREFOX_COPY_SUPPORT)
+
+		ctx.Data["BrowserSupportsCopy"] = (browserName == "Chrome" && chromeConstraint.Check(browserVersion)) || (browserName == "Firefox" && firefoxConstraint.Check(browserVersion))
+
 	}
 }
 
diff --git a/templates/ng/base/head.tmpl b/templates/ng/base/head.tmpl
index 6d5001a9c..faafef563 100644
--- a/templates/ng/base/head.tmpl
+++ b/templates/ng/base/head.tmpl
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html>
+<html{{if .BrowserSupportsCopy}} class="is-copy-enabled"{{end}}>
 	<head data-suburl="{{AppSubUrl}}">
 		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
         <meta http-equiv="X-UA-Compatible" content="IE=edge"/>

From eebcbf9d3438a9910dee56fd76ab624726eb207c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fernando=20San=20Juli=C3=A1n?= <fjsanjulian@hotmail.com>
Date: Mon, 3 Aug 2015 21:27:15 +0200
Subject: [PATCH 2/2] add clipboard API support

---
 public/js/app.js     | 78 +++++++++++++++++++++++++++++---------------
 public/ng/js/gogs.js | 68 ++++++++++++++++++++++++++------------
 2 files changed, 100 insertions(+), 46 deletions(-)

diff --git a/public/js/app.js b/public/js/app.js
index 6208dbedc..3dbf29c14 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -269,36 +269,62 @@ var Gogits = {};
         if ($(selector).hasClass('js-copy-bind')) {
             return;
         }
-        $(selector).zclip({
-            path: "/js/ZeroClipboard.swf",
-            copy: function () {
-                var t = $(this).data("copy-val");
-                var to = $($(this).data("copy-from"));
-                var str = "";
-                if (t == "txt") {
-                    str = to.text();
-                }
-                if (t == 'val') {
-                    str = to.val();
-                }
-                if (t == 'html') {
-                    str = to.html();
-                }
-                return str;
-            },
-            afterCopy: function () {
+
+        if ( document.documentElement.classList.contains("is-copy-enabled") ) {
+
+            $(selector).click(function(event) {
                 var $this = $(this);
-                $this.tooltip('hide')
-                    .attr('data-original-title', 'Copied OK');
+
+                var cfrom = $this.attr('data-copy-from');
+                $(cfrom).select();
+                document.execCommand('copy');
+                getSelection().removeAllRanges();
+
+                $this.tipsy("hide").attr('original-title', $this.data('after-title'));
                 setTimeout(function () {
-                    $this.tooltip("show");
+                    $this.tipsy("show");
                 }, 200);
                 setTimeout(function () {
-                    $this.tooltip('hide')
-                        .attr('data-original-title', 'Copy to Clipboard');
-                }, 3000);
-            }
-        }).addClass("js-copy-bind");
+                    $this.tipsy('hide').attr('original-title', $this.data('original-title'));
+                }, 2000);
+                
+                this.blur();
+                return;
+            });
+
+            $(selector).addClass("js-copy-bind");
+
+        } else {
+
+            $(selector).zclip({
+                path: Gogs.AppSubUrl + "/js/ZeroClipboard.swf",
+                copy: function () {
+                    var t = $(this).data("copy-val");
+                    var to = $($(this).data("copy-from"));
+                    var str = "";
+                    if (t == "txt") {
+                        str = to.text();
+                    }
+                    if (t == 'val') {
+                        str = to.val();
+                    }
+                    if (t == 'html') {
+                        str = to.html();
+                    }
+                    return str;
+                },
+                afterCopy: function () {
+                    var $this = $(this);
+                    $this.tipsy("hide").attr('original-title', $this.data('after-title'));
+                    setTimeout(function () {
+                        $this.tipsy("show");
+                    }, 200);
+                    setTimeout(function () {
+                        $this.tipsy('hide').attr('original-title', $this.data('original-title'));
+                    }, 2000);
+                }
+            }).addClass("js-copy-bind");
+        }
     }
 
     // api working
diff --git a/public/ng/js/gogs.js b/public/ng/js/gogs.js
index 7ffef8af8..38b34c615 100644
--- a/public/ng/js/gogs.js
+++ b/public/ng/js/gogs.js
@@ -333,25 +333,17 @@ var Gogs = {};
         if ($(selector).hasClass('js-copy-bind')) {
             return;
         }
-        $(selector).zclip({
-            path: Gogs.AppSubUrl + "/js/ZeroClipboard.swf",
-            copy: function () {
-                var t = $(this).data("copy-val");
-                var to = $($(this).data("copy-from"));
-                var str = "";
-                if (t == "txt") {
-                    str = to.text();
-                }
-                if (t == 'val') {
-                    str = to.val();
-                }
-                if (t == 'html') {
-                    str = to.html();
-                }
-                return str;
-            },
-            afterCopy: function () {
+
+        if ( document.documentElement.classList.contains("is-copy-enabled") ) {
+
+            $(selector).click(function(event) {
                 var $this = $(this);
+
+                var cfrom = $this.attr('data-copy-from');
+                $(cfrom).select();
+                document.execCommand('copy');
+                getSelection().removeAllRanges();
+
                 $this.tipsy("hide").attr('original-title', $this.data('after-title'));
                 setTimeout(function () {
                     $this.tipsy("show");
@@ -359,8 +351,44 @@ var Gogs = {};
                 setTimeout(function () {
                     $this.tipsy('hide').attr('original-title', $this.data('original-title'));
                 }, 2000);
-            }
-        }).addClass("js-copy-bind");
+                
+                this.blur();
+                return;
+            });
+
+            $(selector).addClass("js-copy-bind");
+
+        } else {
+
+            $(selector).zclip({
+                path: Gogs.AppSubUrl + "/js/ZeroClipboard.swf",
+                copy: function () {
+                    var t = $(this).data("copy-val");
+                    var to = $($(this).data("copy-from"));
+                    var str = "";
+                    if (t == "txt") {
+                        str = to.text();
+                    }
+                    if (t == 'val') {
+                        str = to.val();
+                    }
+                    if (t == 'html') {
+                        str = to.html();
+                    }
+                    return str;
+                },
+                afterCopy: function () {
+                    var $this = $(this);
+                    $this.tipsy("hide").attr('original-title', $this.data('after-title'));
+                    setTimeout(function () {
+                        $this.tipsy("show");
+                    }, 200);
+                    setTimeout(function () {
+                        $this.tipsy('hide').attr('original-title', $this.data('original-title'));
+                    }, 2000);
+                }
+            }).addClass("js-copy-bind");
+        }
     }
 })(jQuery);