summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--distrib/gitblit.properties7
-rw-r--r--docs/04_design.mkd1
-rw-r--r--docs/04_releases.mkd4
-rw-r--r--resources/clippy.swfbin0 -> 5380 bytes
-rw-r--r--src/com/gitblit/wicket/panels/ObjectContainer.java160
-rw-r--r--src/com/gitblit/wicket/panels/RepositoryUrlPanel.html23
-rw-r--r--src/com/gitblit/wicket/panels/RepositoryUrlPanel.java25
-rw-r--r--src/com/gitblit/wicket/panels/ShockWaveComponent.java205
8 files changed, 418 insertions, 7 deletions
diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties
index 39e47885..537f9b67 100644
--- a/distrib/gitblit.properties
+++ b/distrib/gitblit.properties
@@ -121,6 +121,13 @@ web.allowGravatar = true
# SINCE 0.5.0
web.allowZipDownloads = true
+# Use Clippy (Flash solution) to provide a copy-to-clipboard button.
+# If false, a button with a more primitive JavaScript-based prompt box will
+# offer a 3-step (click, ctrl+c, enter) copy-to-clipboard alternative.
+#
+# SINCE 0.8.0
+web.allowFlashCopyToClipboard = true
+
# Default number of entries to include in RSS Syndication links
#
# SINCE 0.5.0
diff --git a/docs/04_design.mkd b/docs/04_design.mkd
index 921bc8bb..3fd13d47 100644
--- a/docs/04_design.mkd
+++ b/docs/04_design.mkd
@@ -11,6 +11,7 @@
The following dependencies are bundled with Gitblit.
- [Bootstrap](http://twitter.github.com/bootstrap) (Apache 2.0)
+- [Clippy](https://github.com/mojombo/clippy) (MIT)
- [google-code-prettify](http://code.google.com/p/google-code-prettify) (Apache 2.0)
- [Commons Daemon](http://commons.apache.org/daemon) (Apache 2.0)
- magnifying glass search icon courtesy of [Gnome](http://gnome.org) (Creative Commons CC-BY)
diff --git a/docs/04_releases.mkd b/docs/04_releases.mkd
index bf709183..9a4e4a83 100644
--- a/docs/04_releases.mkd
+++ b/docs/04_releases.mkd
@@ -15,7 +15,9 @@ This user service implementation allows for serialization and deserialization of
**New:** *web.timeFormat = HH:mm*
**New:** *web.datestampLongFormat = EEEE, MMMM d, yyyy*
- fixed: several a bugs in FileUserService related to cleaning up old repository permissions on a rename or delete
-- added: primitive technique for manual *copy to clipboard* of the primary repository url
+- added: optional flash-based 1-step *copy to clipboard* of the primary repository url
+- added: javascript-based 3-step (click, ctrl+c, enter) *copy to clipboard* of the primary repository url
+ **New:** *web.allowFlashCopyToClipboard = true*
- improved: empty repositories now link to the *empty repository* page which gives some direction to the user for the next step in using Gitblit. This page displays the primary push/clone url of the repository and gives sample syntax for the git command-line client. (issue 31)
- improved: unit testing framework has been migrated to JUnit4 syntax and the test suite has been redesigned to run all unit tests, including rpc, federation, and git push/clone tests
diff --git a/resources/clippy.swf b/resources/clippy.swf
new file mode 100644
index 00000000..e46886cd
--- /dev/null
+++ b/resources/clippy.swf
Binary files differ
diff --git a/src/com/gitblit/wicket/panels/ObjectContainer.java b/src/com/gitblit/wicket/panels/ObjectContainer.java
new file mode 100644
index 00000000..79bd3a7c
--- /dev/null
+++ b/src/com/gitblit/wicket/panels/ObjectContainer.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ResourceReference;
+import org.apache.wicket.Response;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.MarkupStream;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.protocol.http.ClientProperties;
+import org.apache.wicket.protocol.http.WebRequestCycle;
+import org.apache.wicket.protocol.http.WebSession;
+import org.apache.wicket.protocol.http.request.WebClientInfo;
+import org.apache.wicket.request.ClientInfo;
+import org.apache.wicket.util.value.IValueMap;
+
+import com.gitblit.wicket.WicketUtils;
+
+/**
+ * https://cwiki.apache.org/WICKET/object-container-adding-flash-to-a-wicket-application.html
+ */
+public abstract class ObjectContainer extends WebMarkupContainer {
+
+ private static final long serialVersionUID = 1L;
+
+ // Some general attributes for the object tag:
+ private static final String ATTRIBUTE_CONTENTTYPE = "type";
+ private static final String ATTRIBUTE_CLASSID = "classid";
+ private static final String ATTRIBUTE_CODEBASE = "codebase";
+
+ // This is used for browser specific adjustments
+ private ClientProperties clientProperties = null;
+
+ public ObjectContainer(String id) {
+ super(id);
+ }
+
+ // Set an attribute/property
+ public abstract void setValue(String name, String value);
+
+ // Get an attribute/property
+ public abstract String getValue(String name);
+
+ // Set the object's content type
+ protected abstract String getContentType();
+
+ // Set the object's clsid (for IE)
+ protected abstract String getClsid();
+
+ // Where to get the browser plugin (for IE)
+ protected abstract String getCodebase();
+
+ // Object's valid attribute names
+ protected abstract List<String> getAttributeNames();
+
+ // Object's valid parameter names
+ protected abstract List<String> getParameterNames();
+
+ // Utility function to get the URL for the object's data
+ protected String resolveResource(String src) {
+ // if it's an absolute path, return it:
+ if (src.startsWith("/") || src.startsWith("http://") || src.startsWith("https://"))
+ return (src);
+
+ // use the parent container class to resolve the resource reference
+ Component parent = getParent();
+ if (parent instanceof Fragment) {
+ // must check for fragment, otherwise we end up in Wicket namespace
+ parent = parent.getParent();
+ }
+ if (parent != null) {
+ ResourceReference resRef = new ResourceReference(parent.getClass(), src, false);
+ return (urlFor(resRef).toString());
+ }
+
+ return (src);
+ }
+
+ public void onComponentTag(ComponentTag tag) {
+ super.onComponentTag(tag);
+
+ // get the attributes from the html-source
+ IValueMap attributeMap = tag.getAttributes();
+
+ // set the content type
+ String contentType = getContentType();
+ if (contentType != null && !"".equals(contentType))
+ attributeMap.put(ATTRIBUTE_CONTENTTYPE, contentType);
+
+ // set clsid and codebase for IE
+ if (getClientProperties().isBrowserInternetExplorer()) {
+ String clsid = getClsid();
+ String codeBase = getCodebase();
+
+ if (clsid != null && !"".equals(clsid))
+ attributeMap.put(ATTRIBUTE_CLASSID, clsid);
+ if (codeBase != null && !"".equals(codeBase))
+ attributeMap.put(ATTRIBUTE_CODEBASE, codeBase);
+ }
+
+ // add all attributes
+ for (String name : getAttributeNames()) {
+ String value = getValue(name);
+ if (value != null)
+ attributeMap.put(name, value);
+ }
+ }
+
+ public void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
+ Response response = getResponse();
+ response.write("\n");
+
+ // add all object's parameters:
+ for (String name : getParameterNames()) {
+ String value = getValue(name);
+ if (value != null) {
+ response.write("<param name=\"");
+ response.write(name);
+ response.write("\" value=\"");
+ response.write(value);
+ response.write("\"/>\n");
+ }
+ }
+
+ super.onComponentTagBody(markupStream, openTag);
+ }
+
+ // shortcut to the client properties:
+ protected ClientProperties getClientProperties() {
+ if (clientProperties == null) {
+ ClientInfo clientInfo = WebSession.get().getClientInfo();
+
+ if (clientInfo == null || !(clientInfo instanceof WebClientInfo)) {
+ clientInfo = new WebClientInfo((WebRequestCycle) getRequestCycle());
+ WebSession.get().setClientInfo(clientInfo);
+ }
+
+ clientProperties = ((WebClientInfo) clientInfo).getProperties();
+ }
+ return (clientProperties);
+ }
+} \ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/RepositoryUrlPanel.html b/src/com/gitblit/wicket/panels/RepositoryUrlPanel.html
index 25ba1559..2b7be0a0 100644
--- a/src/com/gitblit/wicket/panels/RepositoryUrlPanel.html
+++ b/src/com/gitblit/wicket/panels/RepositoryUrlPanel.html
@@ -8,6 +8,25 @@
</wicket:head>
<wicket:panel>
- <span wicket:id="repositoryUrl" style="color: blue;">[repository url]</span><span style="padding-left:5px;"><span class="btn" style="padding:0px 3px;vertical-align:middle;"><img wicket:id="copyIcon" style="padding-top:1px;"></img></span></span>
-</wicket:panel>
+ <span wicket:id="repositoryUrl" style="color: blue;">[repository url]</span><span wicket:id="copyFunction"></span>
+
+ <!-- Plain JavaScript manual copy & paste -->
+ <wicket:fragment wicket:id="jsPanel">
+ <span class="btn" style="padding:0px 3px 0px 3px;vertical-align:middle;">
+ <img wicket:id="copyIcon" style="padding-top:1px;"></img>
+ </span>
+ </wicket:fragment>
+
+ <!-- flash-based button-press copy & paste -->
+ <wicket:fragment wicket:id="clippyPanel">
+ <object style="padding:0px 2px;vertical-align:middle;"
+ wicket:id="clippy"
+ width="110"
+ height="14"
+ bgcolor="#ffffff"
+ quality="high"
+ wmode="transparent"
+ allowScriptAccess="always"></object>
+ </wicket:fragment>
+</wicket:panel>
</html> \ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/RepositoryUrlPanel.java b/src/com/gitblit/wicket/panels/RepositoryUrlPanel.java
index bfb02f13..a98e40ab 100644
--- a/src/com/gitblit/wicket/panels/RepositoryUrlPanel.java
+++ b/src/com/gitblit/wicket/panels/RepositoryUrlPanel.java
@@ -17,7 +17,11 @@ package com.gitblit.wicket.panels;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.image.ContextImage;
+import org.apache.wicket.markup.html.panel.Fragment;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.WicketUtils;
public class RepositoryUrlPanel extends BasePanel {
@@ -27,9 +31,22 @@ public class RepositoryUrlPanel extends BasePanel {
public RepositoryUrlPanel(String wicketId, String url) {
super(wicketId);
add(new Label("repositoryUrl", url));
- ContextImage img = WicketUtils.newImage("copyIcon", "clipboard_13x13.png");
- WicketUtils.setHtmlTooltip(img, "Manual Copy to Clipboard");
- img.add(new JavascriptTextPrompt("onclick", "Copy to Clipboard (Ctrl+C, Enter)", url));
- add(img);
+ if (GitBlit.getBoolean(Keys.web.allowFlashCopyToClipboard, true)) {
+ // clippy: flash-based copy & paste
+ Fragment fragment = new Fragment("copyFunction", "clippyPanel", this);
+ String baseUrl = WicketUtils.getGitblitURL(getRequest());
+ ShockWaveComponent clippy = new ShockWaveComponent("clippy", baseUrl + "/clippy.swf");
+ clippy.setValue("flashVars", "text=" + StringUtils.encodeURL(url));
+ fragment.add(clippy);
+ add(fragment);
+ } else {
+ // javascript: manual copy & paste with modal browser prompt dialog
+ Fragment fragment = new Fragment("copyFunction", "jsPanel", this);
+ ContextImage img = WicketUtils.newImage("copyIcon", "clipboard_13x13.png");
+ WicketUtils.setHtmlTooltip(img, "Manual Copy to Clipboard");
+ img.add(new JavascriptTextPrompt("onclick", "Copy to Clipboard (Ctrl+C, Enter)", url));
+ fragment.add(img);
+ add(fragment);
+ }
}
}
diff --git a/src/com/gitblit/wicket/panels/ShockWaveComponent.java b/src/com/gitblit/wicket/panels/ShockWaveComponent.java
new file mode 100644
index 00000000..fa989453
--- /dev/null
+++ b/src/com/gitblit/wicket/panels/ShockWaveComponent.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.wicket.Response;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.MarkupStream;
+import org.apache.wicket.util.value.IValueMap;
+
+/**
+ * https://cwiki.apache.org/WICKET/object-container-adding-flash-to-a-wicket-application.html
+ *
+ * @author Jan Kriesten
+ * @author manuelbarzi
+ * @author James Moger
+ *
+ */
+public class ShockWaveComponent extends ObjectContainer {
+ private static final long serialVersionUID = 1L;
+
+ private static final String CONTENTTYPE = "application/x-shockwave-flash";
+ private static final String CLSID = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
+ private static final String CODEBASE = "http://fpdownload.adobe.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0";
+
+ // valid attributes
+ private static final List<String> attributeNames = Arrays.asList(new String[] { "classid",
+ "width", "height", "codebase", "align", "base", "data", "flashvars" });
+ // valid parameters
+ private static final List<String> parameterNames = Arrays.asList(new String[] { "devicefont",
+ "movie", "play", "loop", "quality", "bgcolor", "scale", "salign", "menu", "wmode",
+ "allowscriptaccess", "seamlesstabbing", "flashvars" });
+
+ // combined options (to iterate over them)
+ private static final List<String> optionNames = new ArrayList<String>(attributeNames.size()
+ + parameterNames.size());
+ static {
+ optionNames.addAll(attributeNames);
+ optionNames.addAll(parameterNames);
+ }
+
+ private Map<String, String> attributes;
+ private Map<String, String> parameters;
+
+ public ShockWaveComponent(String id) {
+ super(id);
+
+ attributes = new HashMap<String, String>();
+ parameters = new HashMap<String, String>();
+ }
+
+ public ShockWaveComponent(String id, String movie) {
+ this(id);
+ setValue("movie", movie);
+ }
+
+ public ShockWaveComponent(String id, String movie, String width, String height) {
+ this(id);
+
+ setValue("movie", movie);
+ setValue("width", width);
+ setValue("height", height);
+ }
+
+ public void setValue(String name, String value) {
+ // IE and other browsers handle movie/data differently. So movie is used
+ // for IE, whereas
+ // data is used for all other browsers. The class uses movie parameter
+ // to handle url and
+ // puts the values to the maps depending on the browser information
+ String parameter = name.toLowerCase();
+ if ("data".equals(parameter))
+ parameter = "movie";
+
+ if ("movie".equals(parameter) && !getClientProperties().isBrowserInternetExplorer())
+ attributes.put("data", value);
+
+ if (attributeNames.contains(parameter))
+ attributes.put(parameter, value);
+ else if (parameterNames.contains(parameter))
+ parameters.put(parameter, value);
+ }
+
+ public String getValue(String name) {
+ String parameter = name.toLowerCase();
+ String value = null;
+
+ if ("data".equals(parameter)) {
+ if (getClientProperties().isBrowserInternetExplorer())
+ return null;
+ parameter = "movie";
+ }
+
+ if (attributeNames.contains(parameter))
+ value = attributes.get(parameter);
+ else if (parameterNames.contains(parameter))
+ value = parameters.get(parameter);
+
+ // special treatment of movie to resolve to the url
+ if (value != null && parameter.equals("movie"))
+ value = resolveResource(value);
+
+ return value;
+ }
+
+ public void onComponentTag(ComponentTag tag) {
+ // get options from the markup
+ IValueMap valueMap = tag.getAttributes();
+
+ // Iterate over valid options
+ for (String s : optionNames) {
+ if (valueMap.containsKey(s)) {
+ // if option isn't set programmatically, set value from markup
+ if (!attributes.containsKey(s) && !parameters.containsKey(s))
+ setValue(s, valueMap.getString(s));
+ // remove attribute - they are added in super.onComponentTag()
+ // to
+ // the right place as attribute or param
+ valueMap.remove(s);
+ }
+ }
+
+ super.onComponentTag(tag);
+ }
+
+ public void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
+
+ super.onComponentTagBody(markupStream, openTag);
+
+ Response response = getResponse();
+
+ // add all object's parameters in embed tag too:
+ response.write("<embed");
+ addParameter(response, "type", CONTENTTYPE);
+ for (String name : getParameterNames()) {
+ String value = getValue(name);
+ if (value != null) {
+ name = "movie".equals(name) ? "src" : name;
+ addParameter(response, name, value);
+ }
+ }
+ for (String name : getAttributeNames()) {
+ if ("width".equals(name) || "height".equals(name)) {
+ String value = getValue(name);
+ if (value != null) {
+ addParameter(response, name, value);
+ }
+ }
+ }
+ response.write(" />\n");
+
+ }
+
+ private void addParameter(Response response, String name, String value) {
+ response.write(" ");
+ response.write(name);
+ response.write("=\"");
+ response.write(value);
+ response.write("\"");
+ }
+
+ @Override
+ protected String getClsid() {
+ return CLSID;
+ }
+
+ @Override
+ protected String getCodebase() {
+ return CODEBASE;
+ }
+
+ @Override
+ protected String getContentType() {
+ return CONTENTTYPE;
+ }
+
+ @Override
+ protected List<String> getAttributeNames() {
+ return attributeNames;
+ }
+
+ @Override
+ protected List<String> getParameterNames() {
+ return parameterNames;
+ }
+} \ No newline at end of file