summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Moger <james.moger@gitblit.com>2013-06-19 16:26:32 -0400
committerJames Moger <james.moger@gitblit.com>2013-06-19 16:26:58 -0400
commit430496317177893eeb94579b2946dbafea6d0727 (patch)
tree85a37b386d1d144d51e2644d302ec00b7f691583
parentbdfb4cbb8175c09beaf77c7270d36403b127a0de (diff)
downloadgitblit-430496317177893eeb94579b2946dbafea6d0727.tar.gz
gitblit-430496317177893eeb94579b2946dbafea6d0727.zip
Generate filterable project/repository list with FreeMarker
-rw-r--r--.classpath1
-rw-r--r--NOTICE10
-rw-r--r--build.moxie1
-rw-r--r--gitblit.iml11
-rw-r--r--src/main/java/com/gitblit/wicket/freemarker/Freemarker.java46
-rw-r--r--src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java308
-rw-r--r--src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm15
-rw-r--r--src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm19
-rw-r--r--src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html83
-rw-r--r--src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java85
-rw-r--r--src/main/java/com/gitblit/wicket/pages/ProjectPage.html22
-rw-r--r--src/main/java/com/gitblit/wicket/pages/ProjectPage.java7
-rw-r--r--src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html10
-rw-r--r--src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java139
-rw-r--r--src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html10
-rw-r--r--src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java154
-rw-r--r--src/site/design.mkd1
17 files changed, 747 insertions, 175 deletions
diff --git a/.classpath b/.classpath
index e25f68c5..8cd68de9 100644
--- a/.classpath
+++ b/.classpath
@@ -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" />
diff --git a/NOTICE b/NOTICE
index 0e23d53c..ab0a0868 100644
--- a/NOTICE
+++ b/NOTICE
@@ -269,4 +269,12 @@ AngularJS
AngularJS, release under the
MIT License.
- http://angularjs.org/ \ No newline at end of file
+ http://angularjs.org/
+
+---------------------------------------------------------------------------
+FreeMarker
+---------------------------------------------------------------------------
+ FreeMarker, release under a
+ modified BSD License. (http://www.freemarker.org/docs/app_license.html)
+
+ http://www.freemarker.org/ \ No newline at end of file
diff --git a/build.moxie b/build.moxie
index be9a21cf..9fc08dc6 100644
--- a/build.moxie
+++ b/build.moxie
@@ -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
diff --git a/gitblit.iml b/gitblit.iml
index b90adbd0..38a014a6 100644
--- a/gitblit.iml
+++ b/gitblit.iml
@@ -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>
diff --git a/src/main/java/com/gitblit/wicket/freemarker/Freemarker.java b/src/main/java/com/gitblit/wicket/freemarker/Freemarker.java
new file mode 100644
index 00000000..ad7aa964
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/freemarker/Freemarker.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java b/src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java
new file mode 100644
index 00000000..d57c3a09
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm
new file mode 100644
index 00000000..691f089b
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm
@@ -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> \ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm
new file mode 100644
index 00000000..cf1b7a8b
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm
@@ -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> \ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html
index a9fd1bae..ce1f0281 100644
--- a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html
@@ -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> \ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
index 858821d4..f6f96853 100644
--- a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
@@ -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
- }
}
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectPage.html b/src/main/java/com/gitblit/wicket/pages/ProjectPage.html
index 102a49e6..32139d05 100644
--- a/src/main/java/com/gitblit/wicket/pages/ProjectPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectPage.html
@@ -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> \ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectPage.java b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java
index b101b400..bfc8493c 100644
--- a/src/main/java/com/gitblit/wicket/pages/ProjectPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java
@@ -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);
}
}
diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html
new file mode 100644
index 00000000..4c3aecdf
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html
@@ -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> \ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java
new file mode 100644
index 00000000..a5b74131
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java
@@ -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
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html
new file mode 100644
index 00000000..4c3aecdf
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html
@@ -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> \ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
new file mode 100644
index 00000000..6c43b787
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
@@ -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
+ }
+} \ No newline at end of file
diff --git a/src/site/design.mkd b/src/site/design.mkd
index 8392a9fe..7171197c 100644
--- a/src/site/design.mkd
+++ b/src/site/design.mkd
@@ -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)