From: James Moger Date: Tue, 13 Dec 2011 21:51:36 +0000 (-0500) Subject: Integrated Clippy for a better copy-to-clipboard experience X-Git-Tag: v0.8.0~79 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=3b6904b1d92b987e308f5fb3308fec215ba1f1ae;p=gitblit.git Integrated Clippy for a better copy-to-clipboard experience --- 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 Binary files /dev/null and b/resources/clippy.swf 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 getAttributeNames(); + + // Object's valid parameter names + protected abstract List 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("\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 @@ - [repository url] - + [repository url] + + + + + + + + + + + + + \ 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 attributeNames = Arrays.asList(new String[] { "classid", + "width", "height", "codebase", "align", "base", "data", "flashvars" }); + // valid parameters + private static final List 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 optionNames = new ArrayList(attributeNames.size() + + parameterNames.size()); + static { + optionNames.addAll(attributeNames); + optionNames.addAll(parameterNames); + } + + private Map attributes; + private Map parameters; + + public ShockWaveComponent(String id) { + super(id); + + attributes = new HashMap(); + parameters = new HashMap(); + } + + 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("\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 getAttributeNames() { + return attributeNames; + } + + @Override + protected List getParameterNames() { + return parameterNames; + } +} \ No newline at end of file