Browse Source

Generate filterable project/repository list with FreeMarker

tags/v1.3.0
James Moger 11 years ago
parent
commit
4304963171

+ 1
- 0
.classpath View File

@@ -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" />

+ 9
- 1
NOTICE View File

@@ -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/

+ 1
- 0
build.moxie View File

@@ -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

+ 11
- 0
gitblit.iml View File

@@ -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>

+ 46
- 0
src/main/java/com/gitblit/wicket/freemarker/Freemarker.java View File

@@ -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);
}
}

+ 308
- 0
src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java View File

@@ -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;
}
}

+ 15
- 0
src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm View File

@@ -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>

+ 19
- 0
src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm View File

@@ -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">&nbsp;</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>

+ 2
- 81
src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html View File

@@ -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">&nbsp;</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">&nbsp;</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">&nbsp;</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>

+ 15
- 70
src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java View File

@@ -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
}
}

+ 2
- 20
src/main/java/com/gitblit/wicket/pages/ProjectPage.html View File

@@ -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">&nbsp;</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>

+ 4
- 3
src/main/java/com/gitblit/wicket/pages/ProjectPage.java View File

@@ -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);
}
}

+ 10
- 0
src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html View File

@@ -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>

+ 139
- 0
src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java View File

@@ -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
}
}

+ 10
- 0
src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html View File

@@ -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>

+ 154
- 0
src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java View File

@@ -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
}
}

+ 1
- 0
src/site/design.mkd View File

@@ -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)

Loading…
Cancel
Save