@@ -40,6 +40,7 @@ | |||
<classpathentry kind="lib" path="ext/force-partner-api-24.0.0.jar" sourcepath="ext/src/force-partner-api-24.0.0.jar" /> | |||
<classpathentry kind="lib" path="ext/force-wsc-24.0.0.jar" sourcepath="ext/src/force-wsc-24.0.0.jar" /> | |||
<classpathentry kind="lib" path="ext/js-1.7R2.jar" sourcepath="ext/src/js-1.7R2.jar" /> | |||
<classpathentry kind="lib" path="ext/freemarker-2.3.19.jar" sourcepath="ext/src/freemarker-2.3.19.jar" /> | |||
<classpathentry kind="lib" path="ext/junit-4.11.jar" sourcepath="ext/src/junit-4.11.jar" /> | |||
<classpathentry kind="lib" path="ext/hamcrest-core-1.3.jar" sourcepath="ext/src/hamcrest-core-1.3.jar" /> | |||
<classpathentry kind="lib" path="ext/selenium-java-2.28.0.jar" sourcepath="ext/src/selenium-java-2.28.0.jar" /> |
@@ -269,4 +269,12 @@ AngularJS | |||
AngularJS, release under the | |||
MIT License. | |||
http://angularjs.org/ | |||
http://angularjs.org/ | |||
--------------------------------------------------------------------------- | |||
FreeMarker | |||
--------------------------------------------------------------------------- | |||
FreeMarker, release under a | |||
modified BSD License. (http://www.freemarker.org/docs/app_license.html) | |||
http://www.freemarker.org/ |
@@ -148,6 +148,7 @@ dependencies: | |||
- compile 'com.toedter:jcalendar:1.3.2' :authority | |||
- compile 'org.apache.commons:commons-compress:1.4.1' :war | |||
- compile 'com.force.api:force-partner-api:24.0.0' :war | |||
- compile 'org.freemarker:freemarker:2.3.19' :war | |||
- test 'junit' | |||
# Dependencies for Selenium web page testing | |||
- test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar |
@@ -413,6 +413,17 @@ | |||
</SOURCES> | |||
</library> | |||
</orderEntry> | |||
<orderEntry type="module-library"> | |||
<library name="freemarker-2.3.19.jar"> | |||
<CLASSES> | |||
<root url="jar://$MODULE_DIR$/ext/freemarker-2.3.19.jar!/" /> | |||
</CLASSES> | |||
<JAVADOC /> | |||
<SOURCES> | |||
<root url="jar://$MODULE_DIR$/ext/src/freemarker-2.3.19.jar!/" /> | |||
</SOURCES> | |||
</library> | |||
</orderEntry> | |||
<orderEntry type="module-library" scope="TEST"> | |||
<library name="junit-4.11.jar"> | |||
<CLASSES> |
@@ -0,0 +1,46 @@ | |||
/* | |||
* Copyright 2013 gitblit.com. | |||
* | |||
* Licensed 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.freemarker; | |||
import java.io.IOException; | |||
import java.io.Writer; | |||
import java.util.Map; | |||
import freemarker.template.Configuration; | |||
import freemarker.template.DefaultObjectWrapper; | |||
import freemarker.template.Template; | |||
import freemarker.template.TemplateException; | |||
public class Freemarker { | |||
private static final Configuration fm; | |||
static { | |||
fm = new Configuration(); | |||
fm.setObjectWrapper(new DefaultObjectWrapper()); | |||
fm.setOutputEncoding("UTF-8"); | |||
fm.setClassForTemplateLoading(Freemarker.class, "templates"); | |||
} | |||
public static Template getTemplate(String name) throws IOException { | |||
return fm.getTemplate(name); | |||
} | |||
public static void evaluate(Template template, Map<String, Object> values, Writer out) throws TemplateException, IOException { | |||
template.process(values, out); | |||
} | |||
} |
@@ -0,0 +1,308 @@ | |||
/* | |||
* 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.freemarker; | |||
import java.io.IOException; | |||
import java.io.StringWriter; | |||
import java.util.Map; | |||
import org.apache.wicket.MarkupContainer; | |||
import org.apache.wicket.WicketRuntimeException; | |||
import org.apache.wicket.markup.ComponentTag; | |||
import org.apache.wicket.markup.IMarkupCacheKeyProvider; | |||
import org.apache.wicket.markup.IMarkupResourceStreamProvider; | |||
import org.apache.wicket.markup.MarkupStream; | |||
import org.apache.wicket.markup.html.panel.Panel; | |||
import org.apache.wicket.model.IModel; | |||
import org.apache.wicket.model.Model; | |||
import org.apache.wicket.util.resource.IResourceStream; | |||
import org.apache.wicket.util.resource.StringResourceStream; | |||
import org.apache.wicket.util.string.Strings; | |||
import com.gitblit.utils.StringUtils; | |||
import freemarker.template.Template; | |||
import freemarker.template.TemplateException; | |||
/** | |||
* This class allows FreeMarker to be used as a Wicket preprocessor or as a | |||
* snippet injector for something like a CMS. There are some cases where Wicket | |||
* is not flexible enough to generate content, especially when you need to generate | |||
* hybrid HTML/JS content outside the scope of Wicket. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
@SuppressWarnings("unchecked") | |||
public class FreemarkerPanel extends Panel | |||
implements | |||
IMarkupResourceStreamProvider, | |||
IMarkupCacheKeyProvider | |||
{ | |||
private static final long serialVersionUID = 1L; | |||
private final String template; | |||
private boolean parseGeneratedMarkup; | |||
private boolean escapeHtml; | |||
private boolean throwFreemarkerExceptions; | |||
private transient String stackTraceAsString; | |||
private transient String evaluatedTemplate; | |||
/** | |||
* Construct. | |||
* | |||
* @param id | |||
* Component id | |||
* @param template | |||
* The Freemarker template | |||
* @param values | |||
* values map that can be substituted by Freemarker. | |||
*/ | |||
public FreemarkerPanel(final String id, String template, final Map<String, Object> values) | |||
{ | |||
this(id, template, Model.ofMap(values)); | |||
} | |||
/** | |||
* Construct. | |||
* | |||
* @param id | |||
* Component id | |||
* @param templateResource | |||
* The Freemarker template as a string resource | |||
* @param model | |||
* Model with variables that can be substituted by Freemarker. | |||
*/ | |||
public FreemarkerPanel(final String id, final String template, final IModel< ? extends Map<String, Object>> model) | |||
{ | |||
super(id, model); | |||
this.template = template; | |||
} | |||
/** | |||
* Gets the Freemarker template. | |||
* | |||
* @return the Freemarker template | |||
*/ | |||
private Template getTemplate() | |||
{ | |||
if (StringUtils.isEmpty(template)) | |||
{ | |||
throw new IllegalArgumentException("Template not specified!"); | |||
} | |||
try { | |||
return Freemarker.getTemplate(template); | |||
} catch (IOException e) { | |||
onException(e); | |||
} | |||
return null; | |||
} | |||
/** | |||
* @see org.apache.wicket.markup.html.panel.Panel#onComponentTagBody(org.apache.wicket.markup. | |||
* MarkupStream, org.apache.wicket.markup.ComponentTag) | |||
*/ | |||
@Override | |||
protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) | |||
{ | |||
if (!Strings.isEmpty(stackTraceAsString)) | |||
{ | |||
// TODO: only display the Freemarker error/stacktrace in development | |||
// mode? | |||
replaceComponentTagBody(markupStream, openTag, Strings | |||
.toMultilineMarkup(stackTraceAsString)); | |||
} | |||
else if (!parseGeneratedMarkup) | |||
{ | |||
// check that no components have been added in case the generated | |||
// markup should not be | |||
// parsed | |||
if (size() > 0) | |||
{ | |||
throw new WicketRuntimeException( | |||
"Components cannot be added if the generated markup should not be parsed."); | |||
} | |||
if (evaluatedTemplate == null) | |||
{ | |||
// initialize evaluatedTemplate | |||
getMarkupResourceStream(null, null); | |||
} | |||
replaceComponentTagBody(markupStream, openTag, evaluatedTemplate); | |||
} | |||
else | |||
{ | |||
super.onComponentTagBody(markupStream, openTag); | |||
} | |||
} | |||
/** | |||
* Either print or rethrow the throwable. | |||
* | |||
* @param exception | |||
* the cause | |||
* @param markupStream | |||
* the markup stream | |||
* @param openTag | |||
* the open tag | |||
*/ | |||
private void onException(final Exception exception) | |||
{ | |||
if (!throwFreemarkerExceptions) | |||
{ | |||
// print the exception on the panel | |||
stackTraceAsString = Strings.toString(exception); | |||
} | |||
else | |||
{ | |||
// rethrow the exception | |||
throw new WicketRuntimeException(exception); | |||
} | |||
} | |||
/** | |||
* Gets whether to escape HTML characters. | |||
* | |||
* @return whether to escape HTML characters. The default value is false. | |||
*/ | |||
public void setEscapeHtml(boolean value) | |||
{ | |||
this.escapeHtml = value; | |||
} | |||
/** | |||
* Evaluates the template and returns the result. | |||
* | |||
* @param templateReader | |||
* used to read the template | |||
* @return the result of evaluating the velocity template | |||
*/ | |||
private String evaluateFreemarkerTemplate(Template template) | |||
{ | |||
if (evaluatedTemplate == null) | |||
{ | |||
// Get model as a map | |||
final Map<String, Object> map = (Map<String, Object>)getDefaultModelObject(); | |||
// create a writer for capturing the Velocity output | |||
StringWriter writer = new StringWriter(); | |||
// string to be used as the template name for log messages in case | |||
// of error | |||
try | |||
{ | |||
// execute the Freemarker script and capture the output in writer | |||
Freemarker.evaluate(template, map, writer); | |||
// replace the tag's body the Freemarker output | |||
evaluatedTemplate = writer.toString(); | |||
if (escapeHtml) | |||
{ | |||
// encode the result in order to get valid html output that | |||
// does not break the rest of the page | |||
evaluatedTemplate = Strings.escapeMarkup(evaluatedTemplate).toString(); | |||
} | |||
return evaluatedTemplate; | |||
} | |||
catch (IOException e) | |||
{ | |||
onException(e); | |||
} | |||
catch (TemplateException e) | |||
{ | |||
onException(e); | |||
} | |||
return null; | |||
} | |||
return evaluatedTemplate; | |||
} | |||
/** | |||
* Gets whether to parse the resulting Wicket markup. | |||
* | |||
* @return whether to parse the resulting Wicket markup. The default is false. | |||
*/ | |||
public void setParseGeneratedMarkup(boolean value) | |||
{ | |||
this.parseGeneratedMarkup = value; | |||
} | |||
/** | |||
* Whether any Freemarker exception should be trapped and displayed on the panel (false) or thrown | |||
* up to be handled by the exception mechanism of Wicket (true). The default is false, which | |||
* traps and displays any exception without having consequences for the other components on the | |||
* page. | |||
* <p> | |||
* Trapping these exceptions without disturbing the other components is especially useful in CMS | |||
* like applications, where 'normal' users are allowed to do basic scripting. On errors, you | |||
* want them to be able to have them correct them while the rest of the application keeps on | |||
* working. | |||
* </p> | |||
* | |||
* @return Whether any Freemarker exceptions should be thrown or trapped. The default is false. | |||
*/ | |||
public void setThrowFreemarkerExceptions(boolean value) | |||
{ | |||
this.throwFreemarkerExceptions = value; | |||
} | |||
/** | |||
* @see org.apache.wicket.markup.IMarkupResourceStreamProvider#getMarkupResourceStream(org.apache | |||
* .wicket.MarkupContainer, java.lang.Class) | |||
*/ | |||
public final IResourceStream getMarkupResourceStream(MarkupContainer container, | |||
Class< ? > containerClass) | |||
{ | |||
Template template = getTemplate(); | |||
if (template == null) | |||
{ | |||
throw new WicketRuntimeException("could not find Freemarker template for panel: " + this); | |||
} | |||
// evaluate the template and return a new StringResourceStream | |||
StringBuffer sb = new StringBuffer(); | |||
sb.append("<wicket:panel>"); | |||
sb.append(evaluateFreemarkerTemplate(template)); | |||
sb.append("</wicket:panel>"); | |||
return new StringResourceStream(sb.toString()); | |||
} | |||
/** | |||
* @see org.apache.wicket.markup.IMarkupCacheKeyProvider#getCacheKey(org.apache.wicket. | |||
* MarkupContainer, java.lang.Class) | |||
*/ | |||
public final String getCacheKey(MarkupContainer container, Class< ? > containerClass) | |||
{ | |||
// don't cache the evaluated template | |||
return null; | |||
} | |||
/** | |||
* @see org.apache.wicket.Component#onDetach() | |||
*/ | |||
@Override | |||
protected void onDetach() | |||
{ | |||
super.onDetach(); | |||
stackTraceAsString = null; | |||
evaluatedTemplate = null; | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
<div ng-controller="${ngCtrl}" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;"> | |||
<div class="header" style="padding: 5px;border: none;"><i class="icon-folder-close"></i> <span wicket:id="${ngList}Title"></span> | |||
<div style="padding: 5px 0px 0px;"> | |||
<input type="text" ng-model="query.n" class="input-large" wicket:message="placeholder:gb.filter" style="border-radius: 14px; padding: 3px 14px;margin: 0px;"></input> | |||
</div> | |||
</div> | |||
<div ng-repeat="item in ${ngList} | filter:query" style="padding: 3px;border-top: 1px solid #ddd;"> | |||
<a href="project/{{item.p}}" title="{{item.i}}"><b>{{item.n}}</b></a> | |||
<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span> | |||
<span class="pull-right"> | |||
<span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;" wicket:message="title:gb.repositories">{{item.c | number}}</span> | |||
</span> | |||
</div> | |||
</div> |
@@ -0,0 +1,19 @@ | |||
<div ng-controller="${ngCtrl}" style="border: 1px solid #ddd;border-radius: 4px;"> | |||
<div class="header" style="padding: 5px;border: none;"><i wicket:id="${ngList}Icon"></i> <span wicket:id="${ngList}Title"></span> | |||
<div class="hidden-phone pull-right"> | |||
<span wicket:id="${ngList}Button"></span> | |||
</div> | |||
<div style="padding: 5px 0px 0px;"> | |||
<input type="text" ng-model="query.r" class="input-large" wicket:message="placeholder:gb.filter" style="border-radius: 14px; padding: 3px 14px;margin: 0px;"></input> | |||
</div> | |||
</div> | |||
<div ng-repeat="item in ${ngList} | filter:query" style="padding: 3px;border-top: 1px solid #ddd;"> | |||
<b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc"> </span></span></b> | |||
<a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a> | |||
<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span> | |||
<span ng-show="item.s" class="pull-right"> | |||
<span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i style="vertical-align:baseline;" class="iconic-star"></i></span> | |||
</span> | |||
</div> | |||
</div> |
@@ -29,7 +29,7 @@ | |||
<div wicket:id="active">[recently active]</div> | |||
</div> | |||
<div class="tab-pane" id="projects"> | |||
<div wicket:id="projectList">[all projects]</div> | |||
<div wicket:id="projects">[all projects]</div> | |||
</div> | |||
</div> | |||
</wicket:fragment> | |||
@@ -52,7 +52,7 @@ | |||
<div wicket:id="active">[recently active]</div> | |||
</div> | |||
<div class="tab-pane" id="projects"> | |||
<div wicket:id="projectList">[all projects]</div> | |||
<div wicket:id="projects">[all projects]</div> | |||
</div> | |||
</div> | |||
</wicket:fragment> | |||
@@ -74,85 +74,6 @@ | |||
</table> | |||
</wicket:fragment> | |||
<wicket:fragment wicket:id="starredListFragment"> | |||
<div ng-controller="starredCtrl" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;"> | |||
<div class="header" style="padding: 5px;border: none;"><i class="icon-star"></i> <wicket:message key="gb.starredRepositories"></wicket:message> ({{starred.length}}) | |||
<div style="padding: 5px 0px 0px;"> | |||
<input type="text" ng-model="query.r" class="input-large" wicket:message="placeholder:gb.filter" style="border-radius: 14px; padding: 3px 14px;margin: 0px;"></input> | |||
</div> | |||
</div> | |||
<div ng-repeat="item in starred | filter:query" style="padding: 3px;border-top: 1px solid #ddd;"> | |||
<b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc"> </span></span></b> | |||
<a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a> | |||
<span class="link hidden-tablet hidden-phone" style="color: #aaa;" title="{{item.d}}">{{item.t}}</span> | |||
<span ng-show="item.s" class="pull-right"> | |||
<span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i style="vertical-align:baseline;" class="iconic-star"></i></span> | |||
</span> | |||
</div> | |||
</div> | |||
</wicket:fragment> | |||
<wicket:fragment wicket:id="ownedListFragment"> | |||
<div ng-controller="ownedCtrl" style="border: 1px solid #ddd;border-radius: 4px;"> | |||
<div class="header" style="padding: 5px;border: none;"><i class="icon-user"></i> <wicket:message key="gb.myRepositories"></wicket:message> ({{owned.length}}) | |||
<div class="hidden-phone pull-right"> | |||
<span wicket:id="create"></span> | |||
</div> | |||
<div style="padding: 5px 0px 0px;"> | |||
<input type="text" ng-model="query.r" class="input-large" wicket:message="placeholder:gb.filter" style="border-radius: 14px; padding: 3px 14px;margin: 0px;"></input> | |||
</div> | |||
</div> | |||
<div ng-repeat="item in owned | filter:query" style="padding: 3px;border-top: 1px solid #ddd;"> | |||
<b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc"> </span></span></b> | |||
<a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a> | |||
<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span> | |||
<span ng-show="item.s" class="pull-right"> | |||
<span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i style="vertical-align:baseline;" class="iconic-star"></i></span> | |||
</span> | |||
</div> | |||
</div> | |||
</wicket:fragment> | |||
<wicket:fragment wicket:id="activeListFragment"> | |||
<div ng-controller="activeCtrl" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;"> | |||
<div class="header" style="padding: 5px;border: none;"><i class="icon-user"></i> <wicket:message key="gb.activeRepositories"></wicket:message> ({{active.length}}) | |||
<div style="padding: 5px 0px 0px;"> | |||
<input type="text" ng-model="query.r" class="input-large" wicket:message="placeholder:gb.filter" style="border-radius: 14px; padding: 3px 14px;margin: 0px;"></input> | |||
</div> | |||
</div> | |||
<div ng-repeat="item in active | filter:query" style="padding: 3px;border-top: 1px solid #ddd;"> | |||
<b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc"> </span></span></b> | |||
<a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a> | |||
<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span> | |||
<span ng-show="item.s" class="pull-right"> | |||
<span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i style="vertical-align:baseline;" class="iconic-star"></i></span> | |||
</span> | |||
</div> | |||
</div> | |||
</wicket:fragment> | |||
<wicket:fragment wicket:id="projectListFragment"> | |||
<div ng-controller="projectListCtrl" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;"> | |||
<div class="header" style="padding: 5px;border: none;"><i class="icon-folder-close"></i> <wicket:message key="gb.projects"></wicket:message> ({{projectList.length}}) | |||
<div style="padding: 5px 0px 0px;"> | |||
<input type="text" ng-model="query.n" class="input-large" wicket:message="placeholder:gb.filter" style="border-radius: 14px; padding: 3px 14px;margin: 0px;"></input> | |||
</div> | |||
</div> | |||
<div ng-repeat="item in projectList | filter:query" style="padding: 3px;border-top: 1px solid #ddd;"> | |||
<a href="project/{{item.p}}" title="{{item.i}}"><b>{{item.n}}</b></a> | |||
<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span> | |||
<span class="pull-right"> | |||
<span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;" wicket:message="title:gb.repositories">{{item.c | number}}</span> | |||
</span> | |||
</div> | |||
</div> | |||
</wicket:fragment> | |||
</wicket:extend> | |||
</body> | |||
</html> |
@@ -19,10 +19,7 @@ import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.InputStream; | |||
import java.io.InputStreamReader; | |||
import java.io.Serializable; | |||
import java.text.DateFormat; | |||
import java.text.MessageFormat; | |||
import java.text.SimpleDateFormat; | |||
import java.util.ArrayList; | |||
import java.util.Calendar; | |||
import java.util.Collections; | |||
@@ -34,7 +31,6 @@ import java.util.Set; | |||
import org.apache.wicket.Component; | |||
import org.apache.wicket.PageParameters; | |||
import org.apache.wicket.behavior.HeaderContributor; | |||
import org.apache.wicket.markup.html.basic.Label; | |||
import org.apache.wicket.markup.html.panel.Fragment; | |||
import org.eclipse.jgit.lib.Constants; | |||
@@ -49,8 +45,8 @@ import com.gitblit.utils.MarkdownUtils; | |||
import com.gitblit.utils.StringUtils; | |||
import com.gitblit.wicket.GitBlitWebSession; | |||
import com.gitblit.wicket.WicketUtils; | |||
import com.gitblit.wicket.ng.NgController; | |||
import com.gitblit.wicket.panels.LinkPanel; | |||
import com.gitblit.wicket.panels.FilterableProjectList; | |||
import com.gitblit.wicket.panels.FilterableRepositoryList; | |||
public class MyDashboardPage extends DashboardPage { | |||
@@ -152,38 +148,36 @@ public class MyDashboardPage extends DashboardPage { | |||
add(repositoryTabs); | |||
Fragment projectList = createProjectList(); | |||
repositoryTabs.add(projectList); | |||
// projects list | |||
List<ProjectModel> projects = GitBlit.self().getProjectModels(getRepositoryModels(), false); | |||
repositoryTabs.add(new FilterableProjectList("projects", projects)); | |||
// active repository list | |||
if (active.isEmpty()) { | |||
repositoryTabs.add(new Label("active").setVisible(false)); | |||
} else { | |||
Fragment activeView = createNgList("active", "activeListFragment", "activeCtrl", active); | |||
repositoryTabs.add(activeView); | |||
FilterableRepositoryList repoList = new FilterableRepositoryList("active", active); | |||
repoList.setTitle(getString("gb.activeRepositories"), "icon-time"); | |||
repositoryTabs.add(repoList); | |||
} | |||
// starred repository list | |||
if (ArrayUtils.isEmpty(starred)) { | |||
repositoryTabs.add(new Label("starred").setVisible(false)); | |||
} else { | |||
Fragment starredView = createNgList("starred", "starredListFragment", "starredCtrl", starred); | |||
repositoryTabs.add(starredView); | |||
FilterableRepositoryList repoList = new FilterableRepositoryList("starred", starred); | |||
repoList.setTitle(getString("gb.starredRepositories"), "icon-star"); | |||
repositoryTabs.add(repoList); | |||
} | |||
// owned repository list | |||
if (ArrayUtils.isEmpty(owned)) { | |||
repositoryTabs.add(new Label("owned").setVisible(false)); | |||
} else { | |||
Fragment ownedView = createNgList("owned", "ownedListFragment", "ownedCtrl", owned); | |||
if (user.canCreate) { | |||
// create button | |||
ownedView.add(new LinkPanel("create", "btn btn-mini", getString("gb.newRepository"), EditRepositoryPage.class)); | |||
} else { | |||
// no button | |||
ownedView.add(new Label("create").setVisible(false)); | |||
} | |||
repositoryTabs.add(ownedView); | |||
FilterableRepositoryList repoList = new FilterableRepositoryList("owned", starred); | |||
repoList.setTitle(getString("gb.myRepositories"), "icon-user"); | |||
repoList.setAllowCreate(user.canCreate() || user.canAdmin()); | |||
repositoryTabs.add(repoList); | |||
} | |||
} | |||
@@ -259,53 +253,4 @@ public class MyDashboardPage extends DashboardPage { | |||
} | |||
return MessageFormat.format(getString("gb.failedToReadMessage"), file); | |||
} | |||
protected Fragment createProjectList() { | |||
String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy"); | |||
final DateFormat df = new SimpleDateFormat(format); | |||
df.setTimeZone(getTimeZone()); | |||
List<ProjectModel> projects = GitBlit.self().getProjectModels(getRepositoryModels(), false); | |||
Collections.sort(projects, new Comparator<ProjectModel>() { | |||
@Override | |||
public int compare(ProjectModel o1, ProjectModel o2) { | |||
return o2.lastChange.compareTo(o1.lastChange); | |||
} | |||
}); | |||
List<ProjectListItem> list = new ArrayList<ProjectListItem>(); | |||
for (ProjectModel proj : projects) { | |||
if (proj.isUserProject() || proj.repositories.isEmpty()) { | |||
// exclude user projects from list | |||
continue; | |||
} | |||
ProjectListItem item = new ProjectListItem(); | |||
item.p = proj.name; | |||
item.n = StringUtils.isEmpty(proj.title) ? proj.name : proj.title; | |||
item.i = proj.description; | |||
item.t = getTimeUtils().timeAgo(proj.lastChange); | |||
item.d = df.format(proj.lastChange); | |||
item.c = proj.repositories.size(); | |||
list.add(item); | |||
} | |||
// inject an AngularJS controller with static data | |||
NgController ctrl = new NgController("projectListCtrl"); | |||
ctrl.addVariable("projectList", list); | |||
add(new HeaderContributor(ctrl)); | |||
Fragment fragment = new Fragment("projectList", "projectListFragment", this); | |||
return fragment; | |||
} | |||
protected class ProjectListItem implements Serializable { | |||
private static final long serialVersionUID = 1L; | |||
String p; // path | |||
String n; // name | |||
String t; // time ago | |||
String d; // last updated | |||
String i; // information/description | |||
long c; // repository count | |||
} | |||
} |
@@ -51,25 +51,7 @@ | |||
</tr> | |||
</table> | |||
</wicket:fragment> | |||
<wicket:fragment wicket:id="repositoryListFragment"> | |||
<div ng-controller="repositoryListCtrl" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;"> | |||
<div class="header" style="padding: 5px;border: none;"><img style="vertical-align: middle;" src="git-black-16x16.png"/> <wicket:message key="gb.repositories"></wicket:message> ({{repositoryList.length}}) | |||
<div style="padding: 5px 0px 0px;"> | |||
<input type="text" ng-model="query.r" class="input-large" wicket:message="placeholder:gb.filter" style="border-radius: 14px; padding: 3px 14px;margin: 0px;"></input> | |||
</div> | |||
</div> | |||
<div ng-repeat="item in repositoryList | filter:query" style="padding: 3px;border-top: 1px solid #ddd;"> | |||
<b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc"> </span></span></b> | |||
<a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a> | |||
<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span> | |||
<span ng-show="item.s" class="pull-right"> | |||
<span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i style="vertical-align:baseline;" class="iconic-star"></i></span> | |||
</span> | |||
</div> | |||
</div> | |||
</wicket:fragment> | |||
</wicket:extend> | |||
</wicket:extend> | |||
</body> | |||
</html> |
@@ -24,7 +24,6 @@ import org.apache.wicket.Component; | |||
import org.apache.wicket.PageParameters; | |||
import org.apache.wicket.markup.html.basic.Label; | |||
import org.apache.wicket.markup.html.link.ExternalLink; | |||
import org.apache.wicket.markup.html.panel.Fragment; | |||
import com.gitblit.GitBlit; | |||
import com.gitblit.Keys; | |||
@@ -41,6 +40,7 @@ import com.gitblit.wicket.PageRegistration; | |||
import com.gitblit.wicket.PageRegistration.DropDownMenuItem; | |||
import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration; | |||
import com.gitblit.wicket.WicketUtils; | |||
import com.gitblit.wicket.panels.FilterableRepositoryList; | |||
public class ProjectPage extends DashboardPage { | |||
@@ -128,8 +128,9 @@ public class ProjectPage extends DashboardPage { | |||
if (repositories.isEmpty()) { | |||
add(new Label("repositoryList").setVisible(false)); | |||
} else { | |||
Fragment activeView = createNgList("repositoryList", "repositoryListFragment", "repositoryListCtrl", repositories); | |||
add(activeView); | |||
FilterableRepositoryList repoList = new FilterableRepositoryList("repositoryList", repositories); | |||
repoList.setAllowCreate(user.canCreate(project.name + "/")); | |||
add(repoList); | |||
} | |||
} | |||
@@ -0,0 +1,10 @@ | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" | |||
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" | |||
xml:lang="en" | |||
lang="en"> | |||
<wicket:panel> | |||
<div wicket:id="listComponent">[component]</div> | |||
</wicket:panel> | |||
</html> |
@@ -0,0 +1,139 @@ | |||
/* | |||
* Copyright 2013 gitblit.com. | |||
* | |||
* Licensed 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.io.Serializable; | |||
import java.text.DateFormat; | |||
import java.text.MessageFormat; | |||
import java.text.SimpleDateFormat; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.Comparator; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.apache.wicket.behavior.HeaderContributor; | |||
import org.apache.wicket.markup.html.basic.Label; | |||
import com.gitblit.GitBlit; | |||
import com.gitblit.Keys; | |||
import com.gitblit.models.ProjectModel; | |||
import com.gitblit.utils.StringUtils; | |||
import com.gitblit.wicket.WicketUtils; | |||
import com.gitblit.wicket.freemarker.FreemarkerPanel; | |||
import com.gitblit.wicket.ng.NgController; | |||
/** | |||
* A client-side filterable rich project list which uses Freemarker, Wicket, | |||
* and AngularJS. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
public class FilterableProjectList extends BasePanel { | |||
private static final long serialVersionUID = 1L; | |||
private final List<ProjectModel> projects; | |||
private String title; | |||
private String iconClass; | |||
public FilterableProjectList(String id, List<ProjectModel> projects) { | |||
super(id); | |||
this.projects = projects; | |||
} | |||
public void setTitle(String title, String iconClass) { | |||
this.title = title; | |||
this.iconClass = iconClass; | |||
} | |||
@Override | |||
protected void onInitialize() { | |||
super.onInitialize(); | |||
String id = getId(); | |||
String ngCtrl = id + "Ctrl"; | |||
String ngList = id + "List"; | |||
Map<String, Object> values = new HashMap<String, Object>(); | |||
values.put("ngCtrl", ngCtrl); | |||
values.put("ngList", ngList); | |||
// use Freemarker to setup an AngularJS/Wicket html snippet | |||
FreemarkerPanel panel = new FreemarkerPanel("listComponent", "FilterableProjectList.fm", values); | |||
panel.setParseGeneratedMarkup(true); | |||
panel.setRenderBodyOnly(true); | |||
add(panel); | |||
// add the Wicket controls that are referenced in the snippet | |||
String listTitle = StringUtils.isEmpty(title) ? getString("gb.projects") : title; | |||
panel.add(new Label(ngList + "Title", MessageFormat.format("{0} ({1})", listTitle, projects.size()))); | |||
if (StringUtils.isEmpty(iconClass)) { | |||
panel.add(new Label(ngList + "Icon").setVisible(false)); | |||
} else { | |||
Label icon = new Label(ngList + "Icon"); | |||
WicketUtils.setCssClass(icon, iconClass); | |||
panel.add(icon); | |||
} | |||
String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy"); | |||
final DateFormat df = new SimpleDateFormat(format); | |||
df.setTimeZone(getTimeZone()); | |||
Collections.sort(projects, new Comparator<ProjectModel>() { | |||
@Override | |||
public int compare(ProjectModel o1, ProjectModel o2) { | |||
return o2.lastChange.compareTo(o1.lastChange); | |||
} | |||
}); | |||
List<ProjectListItem> list = new ArrayList<ProjectListItem>(); | |||
for (ProjectModel proj : projects) { | |||
if (proj.isUserProject() || proj.repositories.isEmpty()) { | |||
// exclude user projects from list | |||
continue; | |||
} | |||
ProjectListItem item = new ProjectListItem(); | |||
item.p = proj.name; | |||
item.n = StringUtils.isEmpty(proj.title) ? proj.name : proj.title; | |||
item.i = proj.description; | |||
item.t = getTimeUtils().timeAgo(proj.lastChange); | |||
item.d = df.format(proj.lastChange); | |||
item.c = proj.repositories.size(); | |||
list.add(item); | |||
} | |||
// inject an AngularJS controller with static data | |||
NgController ctrl = new NgController(ngCtrl); | |||
ctrl.addVariable(ngList, list); | |||
add(new HeaderContributor(ctrl)); | |||
} | |||
protected class ProjectListItem implements Serializable { | |||
private static final long serialVersionUID = 1L; | |||
String p; // path | |||
String n; // name | |||
String t; // time ago | |||
String d; // last updated | |||
String i; // information/description | |||
long c; // repository count | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" | |||
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" | |||
xml:lang="en" | |||
lang="en"> | |||
<wicket:panel> | |||
<div wicket:id="listComponent">[component]</div> | |||
</wicket:panel> | |||
</html> |
@@ -0,0 +1,154 @@ | |||
/* | |||
* Copyright 2013 gitblit.com. | |||
* | |||
* Licensed 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.io.Serializable; | |||
import java.text.DateFormat; | |||
import java.text.MessageFormat; | |||
import java.text.SimpleDateFormat; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.apache.wicket.behavior.HeaderContributor; | |||
import org.apache.wicket.markup.html.basic.Label; | |||
import com.gitblit.GitBlit; | |||
import com.gitblit.Keys; | |||
import com.gitblit.models.RepositoryModel; | |||
import com.gitblit.utils.StringUtils; | |||
import com.gitblit.wicket.WicketUtils; | |||
import com.gitblit.wicket.freemarker.FreemarkerPanel; | |||
import com.gitblit.wicket.ng.NgController; | |||
import com.gitblit.wicket.pages.EditRepositoryPage; | |||
/** | |||
* A client-side filterable rich repository list which uses Freemarker, Wicket, | |||
* and AngularJS. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
public class FilterableRepositoryList extends BasePanel { | |||
private static final long serialVersionUID = 1L; | |||
private final List<RepositoryModel> repositories; | |||
private String title; | |||
private String iconClass; | |||
private boolean allowCreate; | |||
public FilterableRepositoryList(String id, List<RepositoryModel> repositories) { | |||
super(id); | |||
this.repositories = repositories; | |||
} | |||
public void setTitle(String title, String iconClass) { | |||
this.title = title; | |||
this.iconClass = iconClass; | |||
} | |||
public void setAllowCreate(boolean value) { | |||
this.allowCreate = value; | |||
} | |||
@Override | |||
protected void onInitialize() { | |||
super.onInitialize(); | |||
String id = getId(); | |||
String ngCtrl = id + "Ctrl"; | |||
String ngList = id + "List"; | |||
Map<String, Object> values = new HashMap<String, Object>(); | |||
values.put("ngCtrl", ngCtrl); | |||
values.put("ngList", ngList); | |||
// use Freemarker to setup an AngularJS/Wicket html snippet | |||
FreemarkerPanel panel = new FreemarkerPanel("listComponent", "FilterableRepositoryList.fm", values); | |||
panel.setParseGeneratedMarkup(true); | |||
panel.setRenderBodyOnly(true); | |||
add(panel); | |||
// add the Wicket controls that are referenced in the snippet | |||
String listTitle = StringUtils.isEmpty(title) ? getString("gb.repositories") : title; | |||
panel.add(new Label(ngList + "Title", MessageFormat.format("{0} ({1})", listTitle, repositories.size()))); | |||
if (StringUtils.isEmpty(iconClass)) { | |||
panel.add(new Label(ngList + "Icon").setVisible(false)); | |||
} else { | |||
Label icon = new Label(ngList + "Icon"); | |||
WicketUtils.setCssClass(icon, iconClass); | |||
panel.add(icon); | |||
} | |||
if (allowCreate) { | |||
panel.add(new LinkPanel(ngList + "Button", "btn btn-mini", getString("gb.newRepository"), EditRepositoryPage.class)); | |||
} else { | |||
panel.add(new Label(ngList + "Button").setVisible(false)); | |||
} | |||
String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy"); | |||
final DateFormat df = new SimpleDateFormat(format); | |||
df.setTimeZone(getTimeZone()); | |||
// prepare the simplified repository models list | |||
List<RepoListItem> list = new ArrayList<RepoListItem>(); | |||
for (RepositoryModel repo : repositories) { | |||
String name = StringUtils.stripDotGit(repo.name); | |||
String path = ""; | |||
if (name.indexOf('/') > -1) { | |||
path = name.substring(0, name.lastIndexOf('/') + 1); | |||
name = name.substring(name.lastIndexOf('/') + 1); | |||
} | |||
RepoListItem item = new RepoListItem(); | |||
item.n = name; | |||
item.p = path; | |||
item.r = repo.name; | |||
item.i = repo.description; | |||
item.s = GitBlit.self().getStarCount(repo); | |||
item.t = getTimeUtils().timeAgo(repo.lastChange); | |||
item.d = df.format(repo.lastChange); | |||
item.c = StringUtils.getColor(StringUtils.stripDotGit(repo.name)); | |||
item.wc = repo.isBare ? 0 : 1; | |||
list.add(item); | |||
} | |||
// inject an AngularJS controller with static data | |||
NgController ctrl = new NgController(ngCtrl); | |||
ctrl.addVariable(ngList, list); | |||
add(new HeaderContributor(ctrl)); | |||
} | |||
protected class RepoListItem implements Serializable { | |||
private static final long serialVersionUID = 1L; | |||
String r; // repository | |||
String n; // name | |||
String p; // project/path | |||
String t; // time ago | |||
String d; // last updated | |||
String i; // information/description | |||
long s; // stars | |||
String c; // html color | |||
int wc; // working copy: 1 = true, 0 = false | |||
} | |||
} |
@@ -47,6 +47,7 @@ The following dependencies are automatically downloaded by Gitblit GO (or alread | |||
- [JCalendar](http://www.toedter.com/en/jcalendar) (LGPL 2.1) | |||
- [Commons-Compress](http://commons.apache.org/compress) (Apache 2.0) | |||
- [XZ for Java](http://tukaani.org/xz/java.html) (Public Domain) | |||
- [FreeMarker](http://www.freemarker.org) (modified BSD) | |||
### Other Build Dependencies | |||
- [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed) |