]> source.dussan.org Git - gitblit.git/commitdiff
Generate filterable project/repository list with FreeMarker
authorJames Moger <james.moger@gitblit.com>
Wed, 19 Jun 2013 20:26:32 +0000 (16:26 -0400)
committerJames Moger <james.moger@gitblit.com>
Wed, 19 Jun 2013 20:26:58 +0000 (16:26 -0400)
17 files changed:
.classpath
NOTICE
build.moxie
gitblit.iml
src/main/java/com/gitblit/wicket/freemarker/Freemarker.java [new file with mode: 0644]
src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java [new file with mode: 0644]
src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm [new file with mode: 0644]
src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm [new file with mode: 0644]
src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html
src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
src/main/java/com/gitblit/wicket/pages/ProjectPage.html
src/main/java/com/gitblit/wicket/pages/ProjectPage.java
src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html [new file with mode: 0644]
src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java [new file with mode: 0644]
src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html [new file with mode: 0644]
src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java [new file with mode: 0644]
src/site/design.mkd

index e25f68c51bcd7421128a2d1cf0b6070a785883fb..8cd68de9ab6b30f879a8c4369d00d5f1fcce1c45 100644 (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" />
diff --git a/NOTICE b/NOTICE
index 0e23d53cb54cf359d105338b5a1e577e938dff7a..ab0a0868fb36535a9573e553eaee5f5e8f20495f 100644 (file)
--- a/NOTICE
+++ b/NOTICE
@@ -269,4 +269,12 @@ AngularJS
    AngularJS, release under the\r
    MIT License.\r
    \r
-   http://angularjs.org/   
\ No newline at end of file
+   http://angularjs.org/   \r
+   \r
+---------------------------------------------------------------------------\r
+FreeMarker\r
+---------------------------------------------------------------------------\r
+   FreeMarker, release under a\r
+   modified BSD License. (http://www.freemarker.org/docs/app_license.html)\r
+   \r
+   http://www.freemarker.org/
\ No newline at end of file
index be9a21cf8205250d4ee65901af67ed19dd03ff5f..9fc08dc6cf86806bde4fe6fecd75a198caab563f 100644 (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
index b90adbd02a93a32bdafc2596ffe0e52765a597a3..38a014a6dcdcfb376d06812069678e6889a0c15c 100644 (file)
         </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 (file)
index 0000000..ad7aa96
--- /dev/null
@@ -0,0 +1,46 @@
+/*\r
+ * Copyright 2013 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.wicket.freemarker;\r
+\r
+import java.io.IOException;\r
+import java.io.Writer;\r
+import java.util.Map;\r
+\r
+import freemarker.template.Configuration;\r
+import freemarker.template.DefaultObjectWrapper;\r
+import freemarker.template.Template;\r
+import freemarker.template.TemplateException;\r
+\r
+public class Freemarker {\r
+\r
+       private static final Configuration fm;\r
+       \r
+       static {\r
+               fm = new Configuration();\r
+               fm.setObjectWrapper(new DefaultObjectWrapper());\r
+               fm.setOutputEncoding("UTF-8");\r
+               fm.setClassForTemplateLoading(Freemarker.class, "templates");\r
+       }\r
+       \r
+       public static Template getTemplate(String name) throws IOException {\r
+               return fm.getTemplate(name);\r
+       }\r
+       \r
+       public static void evaluate(Template template, Map<String, Object> values, Writer out) throws TemplateException, IOException {\r
+               template.process(values, out);\r
+       }\r
+\r
+}\r
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 (file)
index 0000000..d57c3a0
--- /dev/null
@@ -0,0 +1,308 @@
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.wicket.freemarker;\r
+\r
+import java.io.IOException;\r
+import java.io.StringWriter;\r
+import java.util.Map;\r
+\r
+import org.apache.wicket.MarkupContainer;\r
+import org.apache.wicket.WicketRuntimeException;\r
+import org.apache.wicket.markup.ComponentTag;\r
+import org.apache.wicket.markup.IMarkupCacheKeyProvider;\r
+import org.apache.wicket.markup.IMarkupResourceStreamProvider;\r
+import org.apache.wicket.markup.MarkupStream;\r
+import org.apache.wicket.markup.html.panel.Panel;\r
+import org.apache.wicket.model.IModel;\r
+import org.apache.wicket.model.Model;\r
+import org.apache.wicket.util.resource.IResourceStream;\r
+import org.apache.wicket.util.resource.StringResourceStream;\r
+import org.apache.wicket.util.string.Strings;\r
+\r
+import com.gitblit.utils.StringUtils;\r
+\r
+import freemarker.template.Template;\r
+import freemarker.template.TemplateException;\r
+\r
+/**\r
+ * This class allows FreeMarker to be used as a Wicket preprocessor or as a\r
+ * snippet injector for something like a CMS.  There are some cases where Wicket\r
+ * is not flexible enough to generate content, especially when you need to generate\r
+ * hybrid HTML/JS content outside the scope of Wicket.\r
+ * \r
+ * @author James Moger\r
+ * \r
+ */\r
+@SuppressWarnings("unchecked")\r
+public class FreemarkerPanel extends Panel\r
+               implements\r
+                       IMarkupResourceStreamProvider,\r
+                       IMarkupCacheKeyProvider\r
+{\r
+       private static final long serialVersionUID = 1L;\r
+\r
+       private final String template;\r
+       private boolean parseGeneratedMarkup;\r
+       private boolean escapeHtml;\r
+       private boolean throwFreemarkerExceptions;\r
+       private transient String stackTraceAsString;\r
+       private transient String evaluatedTemplate;\r
+\r
+       \r
+       /**\r
+        * Construct.\r
+        * \r
+        * @param id\r
+        *            Component id\r
+        * @param template\r
+        *            The Freemarker template\r
+        * @param values\r
+        *            values map that can be substituted by Freemarker.\r
+        */\r
+       public FreemarkerPanel(final String id, String template, final Map<String, Object> values)\r
+       {\r
+               this(id, template, Model.ofMap(values));\r
+       }\r
+       \r
+       /**\r
+        * Construct.\r
+        * \r
+        * @param id\r
+        *            Component id\r
+        * @param templateResource\r
+        *            The Freemarker template as a string resource\r
+        * @param model\r
+        *            Model with variables that can be substituted by Freemarker.\r
+        */\r
+       public FreemarkerPanel(final String id, final String template, final IModel< ? extends Map<String, Object>> model)\r
+       {\r
+               super(id, model);\r
+               this.template = template;\r
+       }\r
+\r
+       /**\r
+        * Gets the Freemarker template.\r
+        * \r
+        * @return the Freemarker template\r
+        */\r
+       private Template getTemplate()\r
+       {\r
+               if (StringUtils.isEmpty(template))\r
+               {\r
+                       throw new IllegalArgumentException("Template not specified!");\r
+               }\r
+\r
+               try {\r
+                       return Freemarker.getTemplate(template);\r
+               } catch (IOException e) {\r
+                       onException(e);\r
+               }\r
+\r
+               return null;\r
+       }\r
+\r
+       /**\r
+        * @see org.apache.wicket.markup.html.panel.Panel#onComponentTagBody(org.apache.wicket.markup.\r
+        *      MarkupStream, org.apache.wicket.markup.ComponentTag)\r
+        */\r
+       @Override\r
+       protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag)\r
+       {\r
+               if (!Strings.isEmpty(stackTraceAsString))\r
+               {\r
+                       // TODO: only display the Freemarker error/stacktrace in development\r
+                       // mode?\r
+                       replaceComponentTagBody(markupStream, openTag, Strings\r
+                                       .toMultilineMarkup(stackTraceAsString));\r
+               }\r
+               else if (!parseGeneratedMarkup)\r
+               {\r
+                       // check that no components have been added in case the generated\r
+                       // markup should not be\r
+                       // parsed\r
+                       if (size() > 0)\r
+                       {\r
+                               throw new WicketRuntimeException(\r
+                                               "Components cannot be added if the generated markup should not be parsed.");\r
+                       }\r
+\r
+                       if (evaluatedTemplate == null)\r
+                       {\r
+                               // initialize evaluatedTemplate\r
+                               getMarkupResourceStream(null, null);\r
+                       }\r
+                       replaceComponentTagBody(markupStream, openTag, evaluatedTemplate);\r
+               }\r
+               else\r
+               {\r
+                       super.onComponentTagBody(markupStream, openTag);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * Either print or rethrow the throwable.\r
+        * \r
+        * @param exception\r
+        *            the cause\r
+        * @param markupStream\r
+        *            the markup stream\r
+        * @param openTag\r
+        *            the open tag\r
+        */\r
+       private void onException(final Exception exception)\r
+       {\r
+               if (!throwFreemarkerExceptions)\r
+               {\r
+                       // print the exception on the panel\r
+                       stackTraceAsString = Strings.toString(exception);\r
+               }\r
+               else\r
+               {\r
+                       // rethrow the exception\r
+                       throw new WicketRuntimeException(exception);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * Gets whether to escape HTML characters.\r
+        * \r
+        * @return whether to escape HTML characters. The default value is false.\r
+        */\r
+       public void setEscapeHtml(boolean value)\r
+       {\r
+               this.escapeHtml = value;\r
+       }\r
+\r
+       /**\r
+        * Evaluates the template and returns the result.\r
+        * \r
+        * @param templateReader\r
+        *            used to read the template\r
+        * @return the result of evaluating the velocity template\r
+        */\r
+       private String evaluateFreemarkerTemplate(Template template)\r
+       {\r
+               if (evaluatedTemplate == null)\r
+               {\r
+                       // Get model as a map\r
+                       final Map<String, Object> map = (Map<String, Object>)getDefaultModelObject();\r
+\r
+                       // create a writer for capturing the Velocity output\r
+                       StringWriter writer = new StringWriter();\r
+\r
+                       // string to be used as the template name for log messages in case\r
+                       // of error\r
+                       try\r
+                       {\r
+                               // execute the Freemarker script and capture the output in writer\r
+                               Freemarker.evaluate(template, map, writer);\r
+\r
+                               // replace the tag's body the Freemarker output\r
+                               evaluatedTemplate = writer.toString();\r
+\r
+                               if (escapeHtml)\r
+                               {\r
+                                       // encode the result in order to get valid html output that\r
+                                       // does not break the rest of the page\r
+                                       evaluatedTemplate = Strings.escapeMarkup(evaluatedTemplate).toString();\r
+                               }\r
+                               return evaluatedTemplate;\r
+                       }\r
+                       catch (IOException e)\r
+                       {\r
+                               onException(e);\r
+                       }\r
+                       catch (TemplateException e)\r
+                       {\r
+                               onException(e);\r
+                       }\r
+                       return null;\r
+               }\r
+               return evaluatedTemplate;\r
+       }\r
+\r
+       /**\r
+        * Gets whether to parse the resulting Wicket markup.\r
+        * \r
+        * @return whether to parse the resulting Wicket markup. The default is false.\r
+        */\r
+       public void setParseGeneratedMarkup(boolean value)\r
+       {\r
+               this.parseGeneratedMarkup = value;\r
+       }\r
+\r
+       /**\r
+        * Whether any Freemarker exception should be trapped and displayed on the panel (false) or thrown\r
+        * up to be handled by the exception mechanism of Wicket (true). The default is false, which\r
+        * traps and displays any exception without having consequences for the other components on the\r
+        * page.\r
+        * <p>\r
+        * Trapping these exceptions without disturbing the other components is especially useful in CMS\r
+        * like applications, where 'normal' users are allowed to do basic scripting. On errors, you\r
+        * want them to be able to have them correct them while the rest of the application keeps on\r
+        * working.\r
+        * </p>\r
+        * \r
+        * @return Whether any Freemarker exceptions should be thrown or trapped. The default is false.\r
+        */\r
+       public void setThrowFreemarkerExceptions(boolean value)\r
+       {\r
+               this.throwFreemarkerExceptions = value;\r
+       }\r
+\r
+       /**\r
+        * @see org.apache.wicket.markup.IMarkupResourceStreamProvider#getMarkupResourceStream(org.apache\r
+        *      .wicket.MarkupContainer, java.lang.Class)\r
+        */\r
+       public final IResourceStream getMarkupResourceStream(MarkupContainer container,\r
+                       Class< ? > containerClass)\r
+       {\r
+               Template template = getTemplate();\r
+               if (template == null)\r
+               {\r
+                       throw new WicketRuntimeException("could not find Freemarker template for panel: " + this);\r
+               }\r
+\r
+               // evaluate the template and return a new StringResourceStream\r
+               StringBuffer sb = new StringBuffer();\r
+               sb.append("<wicket:panel>");\r
+               sb.append(evaluateFreemarkerTemplate(template));\r
+               sb.append("</wicket:panel>");\r
+               return new StringResourceStream(sb.toString());\r
+       }\r
+\r
+       /**\r
+        * @see org.apache.wicket.markup.IMarkupCacheKeyProvider#getCacheKey(org.apache.wicket.\r
+        *      MarkupContainer, java.lang.Class)\r
+        */\r
+       public final String getCacheKey(MarkupContainer container, Class< ? > containerClass)\r
+       {\r
+               // don't cache the evaluated template\r
+               return null;\r
+       }\r
+\r
+       /**\r
+        * @see org.apache.wicket.Component#onDetach()\r
+        */\r
+       @Override\r
+       protected void onDetach()\r
+       {\r
+               super.onDetach();\r
+               stackTraceAsString = null;\r
+               evaluatedTemplate = null;\r
+       }\r
+}\r
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 (file)
index 0000000..691f089
--- /dev/null
@@ -0,0 +1,15 @@
+<div ng-controller="${ngCtrl}" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;">\r
+       <div class="header" style="padding: 5px;border: none;"><i class="icon-folder-close"></i> <span wicket:id="${ngList}Title"></span>\r
+               <div style="padding: 5px 0px 0px;">\r
+                       <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>\r
+               </div>\r
+       </div>\r
+               \r
+       <div ng-repeat="item in ${ngList} | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">\r
+               <a href="project/{{item.p}}" title="{{item.i}}"><b>{{item.n}}</b></a>\r
+               <span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>\r
+               <span class="pull-right">\r
+                       <span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;" wicket:message="title:gb.repositories">{{item.c | number}}</span>\r
+               </span>\r
+       </div>\r
+</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 (file)
index 0000000..cf1b7a8
--- /dev/null
@@ -0,0 +1,19 @@
+<div ng-controller="${ngCtrl}" style="border: 1px solid #ddd;border-radius: 4px;">\r
+       <div class="header" style="padding: 5px;border: none;"><i wicket:id="${ngList}Icon"></i> <span wicket:id="${ngList}Title"></span>\r
+               <div class="hidden-phone pull-right">\r
+                       <span wicket:id="${ngList}Button"></span>\r
+               </div>\r
+               <div style="padding: 5px 0px 0px;">\r
+                       <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>\r
+               </div>\r
+       </div>\r
+       \r
+       <div ng-repeat="item in ${ngList} | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">\r
+               <b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc">&nbsp;</span></span></b>\r
+               <a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a>\r
+               <span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>\r
+               <span ng-show="item.s" class="pull-right">\r
+                       <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>\r
+               </span>\r
+       </div>          \r
+</div>
\ No newline at end of file
index a9fd1bae6540a4f830147be909f0c51fd9f40e64..ce1f0281840e8c683d2671d267b5edee5baa21a7 100644 (file)
@@ -29,7 +29,7 @@
                        <div wicket:id="active">[recently active]</div>\r
                </div>\r
                <div class="tab-pane" id="projects">\r
-                       <div wicket:id="projectList">[all projects]</div>\r
+                       <div wicket:id="projects">[all projects]</div>\r
                </div>\r
        </div>\r
 </wicket:fragment>\r
@@ -52,7 +52,7 @@
                        <div wicket:id="active">[recently active]</div>\r
                </div>\r
                <div class="tab-pane" id="projects">\r
-                       <div wicket:id="projectList">[all projects]</div>\r
+                       <div wicket:id="projects">[all projects]</div>\r
                </div>\r
        </div>\r
 </wicket:fragment>\r
        </table>\r
 </wicket:fragment>\r
 \r
-<wicket:fragment wicket:id="starredListFragment">\r
-       <div ng-controller="starredCtrl" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;">\r
-               <div class="header" style="padding: 5px;border: none;"><i class="icon-star"></i> <wicket:message key="gb.starredRepositories"></wicket:message> ({{starred.length}})\r
-                       <div style="padding: 5px 0px 0px;">\r
-                               <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>\r
-                       </div>\r
-               </div>\r
-               \r
-               <div ng-repeat="item in starred | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">\r
-                       <b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc">&nbsp;</span></span></b>\r
-                       <a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a>\r
-                       <span class="link hidden-tablet hidden-phone" style="color: #aaa;" title="{{item.d}}">{{item.t}}</span>\r
-                       <span ng-show="item.s" class="pull-right">\r
-                               <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>\r
-                       </span>\r
-               </div>\r
-               \r
-       </div>\r
-</wicket:fragment>\r
-\r
-<wicket:fragment wicket:id="ownedListFragment">\r
-       <div ng-controller="ownedCtrl" style="border: 1px solid #ddd;border-radius: 4px;">\r
-               <div class="header" style="padding: 5px;border: none;"><i class="icon-user"></i> <wicket:message key="gb.myRepositories"></wicket:message> ({{owned.length}})\r
-                       <div class="hidden-phone pull-right">\r
-                               <span wicket:id="create"></span>\r
-                       </div>\r
-                       <div style="padding: 5px 0px 0px;">\r
-                               <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>\r
-                       </div>\r
-               </div>\r
-               \r
-               <div ng-repeat="item in owned | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">\r
-                       <b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc">&nbsp;</span></span></b>\r
-                       <a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a>\r
-                       <span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>\r
-                       <span ng-show="item.s" class="pull-right">\r
-                               <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>\r
-                       </span>\r
-               </div>          \r
-       </div>\r
-</wicket:fragment>\r
-\r
-<wicket:fragment wicket:id="activeListFragment">\r
-       <div ng-controller="activeCtrl" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;">\r
-               <div class="header" style="padding: 5px;border: none;"><i class="icon-user"></i> <wicket:message key="gb.activeRepositories"></wicket:message> ({{active.length}})\r
-                       <div style="padding: 5px 0px 0px;">\r
-                               <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>\r
-                       </div>\r
-               </div>\r
-               \r
-               <div ng-repeat="item in active | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">\r
-                       <b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc">&nbsp;</span></span></b>\r
-                       <a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a>\r
-                       <span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>\r
-                       <span ng-show="item.s" class="pull-right">\r
-                               <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>\r
-                       </span>\r
-               </div>          \r
-       </div>\r
-</wicket:fragment>\r
-\r
-<wicket:fragment wicket:id="projectListFragment">\r
-       <div ng-controller="projectListCtrl" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;">\r
-               <div class="header" style="padding: 5px;border: none;"><i class="icon-folder-close"></i> <wicket:message key="gb.projects"></wicket:message> ({{projectList.length}})\r
-                       <div style="padding: 5px 0px 0px;">\r
-                               <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>\r
-                       </div>\r
-               </div>\r
-               \r
-               <div ng-repeat="item in projectList | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">\r
-                       <a href="project/{{item.p}}" title="{{item.i}}"><b>{{item.n}}</b></a>\r
-                       <span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>\r
-                       <span class="pull-right">\r
-                               <span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;" wicket:message="title:gb.repositories">{{item.c | number}}</span>\r
-                       </span>\r
-               </div>\r
-       </div>\r
-</wicket:fragment>\r
-\r
 </wicket:extend>\r
 </body>\r
 </html>
\ No newline at end of file
index 858821d46bea5e504e6df12676ad37b7e3c3ed83..f6f96853a81dbcbf36e698bb6f715103c0aba7c9 100644 (file)
@@ -19,10 +19,7 @@ import java.io.File;
 import java.io.FileInputStream;\r
 import java.io.InputStream;\r
 import java.io.InputStreamReader;\r
-import java.io.Serializable;\r
-import java.text.DateFormat;\r
 import java.text.MessageFormat;\r
-import java.text.SimpleDateFormat;\r
 import java.util.ArrayList;\r
 import java.util.Calendar;\r
 import java.util.Collections;\r
@@ -34,7 +31,6 @@ import java.util.Set;
 \r
 import org.apache.wicket.Component;\r
 import org.apache.wicket.PageParameters;\r
-import org.apache.wicket.behavior.HeaderContributor;\r
 import org.apache.wicket.markup.html.basic.Label;\r
 import org.apache.wicket.markup.html.panel.Fragment;\r
 import org.eclipse.jgit.lib.Constants;\r
@@ -49,8 +45,8 @@ import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;\r
 import com.gitblit.wicket.GitBlitWebSession;\r
 import com.gitblit.wicket.WicketUtils;\r
-import com.gitblit.wicket.ng.NgController;\r
-import com.gitblit.wicket.panels.LinkPanel;\r
+import com.gitblit.wicket.panels.FilterableProjectList;\r
+import com.gitblit.wicket.panels.FilterableRepositoryList;\r
 \r
 public class MyDashboardPage extends DashboardPage {\r
 \r
@@ -152,38 +148,36 @@ public class MyDashboardPage extends DashboardPage {
                \r
                add(repositoryTabs);\r
                \r
-               Fragment projectList = createProjectList();\r
-               repositoryTabs.add(projectList);\r
+               // projects list\r
+               List<ProjectModel> projects = GitBlit.self().getProjectModels(getRepositoryModels(), false);\r
+               repositoryTabs.add(new FilterableProjectList("projects", projects));\r
                \r
                // active repository list\r
                if (active.isEmpty()) {\r
                        repositoryTabs.add(new Label("active").setVisible(false));\r
                } else {\r
-                       Fragment activeView = createNgList("active", "activeListFragment", "activeCtrl", active);\r
-                       repositoryTabs.add(activeView);\r
+                       FilterableRepositoryList repoList = new FilterableRepositoryList("active", active);\r
+                       repoList.setTitle(getString("gb.activeRepositories"), "icon-time");\r
+                       repositoryTabs.add(repoList);\r
                }\r
                \r
                // starred repository list\r
                if (ArrayUtils.isEmpty(starred)) {\r
                        repositoryTabs.add(new Label("starred").setVisible(false));\r
                } else {\r
-                       Fragment starredView = createNgList("starred", "starredListFragment", "starredCtrl", starred);\r
-                       repositoryTabs.add(starredView);\r
+                       FilterableRepositoryList repoList = new FilterableRepositoryList("starred", starred);\r
+                       repoList.setTitle(getString("gb.starredRepositories"), "icon-star");\r
+                       repositoryTabs.add(repoList);\r
                }\r
                \r
                // owned repository list\r
                if (ArrayUtils.isEmpty(owned)) {\r
                        repositoryTabs.add(new Label("owned").setVisible(false));\r
                } else {\r
-                       Fragment ownedView = createNgList("owned", "ownedListFragment", "ownedCtrl", owned);\r
-                       if (user.canCreate) {\r
-                               // create button\r
-                               ownedView.add(new LinkPanel("create", "btn btn-mini", getString("gb.newRepository"), EditRepositoryPage.class));\r
-                       } else {\r
-                               // no button\r
-                               ownedView.add(new Label("create").setVisible(false));\r
-                       }\r
-                       repositoryTabs.add(ownedView);\r
+                       FilterableRepositoryList repoList = new FilterableRepositoryList("owned", starred);\r
+                       repoList.setTitle(getString("gb.myRepositories"), "icon-user");\r
+                       repoList.setAllowCreate(user.canCreate() || user.canAdmin());\r
+                       repositoryTabs.add(repoList);\r
                }\r
        }\r
        \r
@@ -259,53 +253,4 @@ public class MyDashboardPage extends DashboardPage {
                }\r
                return MessageFormat.format(getString("gb.failedToReadMessage"), file);\r
        }\r
-       \r
-       protected Fragment createProjectList() {\r
-               String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy");\r
-               final DateFormat df = new SimpleDateFormat(format);\r
-               df.setTimeZone(getTimeZone());\r
-               List<ProjectModel> projects = GitBlit.self().getProjectModels(getRepositoryModels(), false);\r
-               Collections.sort(projects, new Comparator<ProjectModel>() {\r
-                       @Override\r
-                       public int compare(ProjectModel o1, ProjectModel o2) {\r
-                               return o2.lastChange.compareTo(o1.lastChange);\r
-                       }\r
-               });\r
-\r
-               List<ProjectListItem> list = new ArrayList<ProjectListItem>();\r
-               for (ProjectModel proj : projects) {\r
-                       if (proj.isUserProject() || proj.repositories.isEmpty()) {\r
-                               // exclude user projects from list\r
-                               continue;\r
-                       }\r
-                       ProjectListItem item = new ProjectListItem();\r
-                       item.p = proj.name;\r
-                       item.n = StringUtils.isEmpty(proj.title) ? proj.name : proj.title;\r
-                       item.i = proj.description;\r
-                       item.t = getTimeUtils().timeAgo(proj.lastChange);\r
-                       item.d = df.format(proj.lastChange);\r
-                       item.c = proj.repositories.size();\r
-                       list.add(item);\r
-               }\r
-               \r
-               // inject an AngularJS controller with static data\r
-               NgController ctrl = new NgController("projectListCtrl");\r
-               ctrl.addVariable("projectList", list);\r
-               add(new HeaderContributor(ctrl));\r
-               \r
-               Fragment fragment = new Fragment("projectList", "projectListFragment", this);\r
-               return fragment;\r
-       }\r
-       \r
-       protected class ProjectListItem implements Serializable {\r
-\r
-               private static final long serialVersionUID = 1L;\r
-               \r
-               String p; // path\r
-               String n; // name\r
-               String t; // time ago\r
-               String d; // last updated\r
-               String i; // information/description\r
-               long c;   // repository count\r
-       }\r
 }\r
index 102a49e606837318a9e159192bd0b1114f3eeb95..32139d05d78226e22a27d78d8cb8b0571e8f73d7 100644 (file)
                </tr>\r
        </table>\r
 </wicket:fragment>\r
-               \r
-<wicket:fragment wicket:id="repositoryListFragment">\r
-       <div ng-controller="repositoryListCtrl" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;">\r
-               <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}})\r
-                       <div style="padding: 5px 0px 0px;">\r
-                               <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>\r
-                       </div>\r
-               </div>\r
-               \r
-               <div ng-repeat="item in repositoryList | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">\r
-                       <b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc">&nbsp;</span></span></b>\r
-                       <a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a>\r
-                       <span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>\r
-                       <span ng-show="item.s" class="pull-right">\r
-                               <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>\r
-                       </span>\r
-               </div>          \r
-       </div>\r
-</wicket:fragment>             \r
-       </wicket:extend>\r
+\r
+</wicket:extend>\r
 </body>\r
 </html>
\ No newline at end of file
index b101b4005c442a0c3a1eb85083a198397c44196e..bfc8493ccd25f93962233a7b9303140043f327ec 100644 (file)
@@ -24,7 +24,6 @@ import org.apache.wicket.Component;
 import org.apache.wicket.PageParameters;\r
 import org.apache.wicket.markup.html.basic.Label;\r
 import org.apache.wicket.markup.html.link.ExternalLink;\r
-import org.apache.wicket.markup.html.panel.Fragment;\r
 \r
 import com.gitblit.GitBlit;\r
 import com.gitblit.Keys;\r
@@ -41,6 +40,7 @@ import com.gitblit.wicket.PageRegistration;
 import com.gitblit.wicket.PageRegistration.DropDownMenuItem;\r
 import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;\r
 import com.gitblit.wicket.WicketUtils;\r
+import com.gitblit.wicket.panels.FilterableRepositoryList;\r
 \r
 public class ProjectPage extends DashboardPage {\r
        \r
@@ -128,8 +128,9 @@ public class ProjectPage extends DashboardPage {
                if (repositories.isEmpty()) {\r
                        add(new Label("repositoryList").setVisible(false));\r
                } else {\r
-                       Fragment activeView = createNgList("repositoryList", "repositoryListFragment", "repositoryListCtrl", repositories);\r
-                       add(activeView);\r
+                       FilterableRepositoryList repoList = new FilterableRepositoryList("repositoryList", repositories);\r
+                       repoList.setAllowCreate(user.canCreate(project.name + "/"));\r
+                       add(repoList);\r
                }\r
        }\r
        \r
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 (file)
index 0000000..4c3aecd
--- /dev/null
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml"  \r
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  \r
+      xml:lang="en"  \r
+      lang="en"> \r
+\r
+<wicket:panel>\r
+       <div wicket:id="listComponent">[component]</div>\r
+</wicket:panel>\r
+</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 (file)
index 0000000..a5b7413
--- /dev/null
@@ -0,0 +1,139 @@
+/*\r
+ * Copyright 2013 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.wicket.panels;\r
+\r
+import java.io.Serializable;\r
+import java.text.DateFormat;\r
+import java.text.MessageFormat;\r
+import java.text.SimpleDateFormat;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+import org.apache.wicket.behavior.HeaderContributor;\r
+import org.apache.wicket.markup.html.basic.Label;\r
+\r
+import com.gitblit.GitBlit;\r
+import com.gitblit.Keys;\r
+import com.gitblit.models.ProjectModel;\r
+import com.gitblit.utils.StringUtils;\r
+import com.gitblit.wicket.WicketUtils;\r
+import com.gitblit.wicket.freemarker.FreemarkerPanel;\r
+import com.gitblit.wicket.ng.NgController;\r
+\r
+/**\r
+ * A client-side filterable rich project list which uses Freemarker, Wicket,\r
+ * and AngularJS. \r
+ * \r
+ * @author James Moger\r
+ *\r
+ */\r
+public class FilterableProjectList extends BasePanel {\r
+\r
+       private static final long serialVersionUID = 1L;\r
+\r
+       private final List<ProjectModel> projects;\r
+       \r
+       private String title;\r
+       \r
+       private String iconClass;\r
+       \r
+       public FilterableProjectList(String id, List<ProjectModel> projects) {\r
+               super(id);\r
+               this.projects = projects;\r
+       }\r
+       \r
+       public void setTitle(String title, String iconClass) {\r
+               this.title = title;\r
+               this.iconClass = iconClass;\r
+       }\r
+       \r
+       @Override\r
+       protected void onInitialize() {\r
+               super.onInitialize();\r
+\r
+               String id = getId();\r
+               String ngCtrl = id + "Ctrl";\r
+               String ngList = id + "List";\r
+               \r
+               Map<String, Object> values = new HashMap<String, Object>();\r
+               values.put("ngCtrl",  ngCtrl);\r
+               values.put("ngList",  ngList);\r
+               \r
+               // use Freemarker to setup an AngularJS/Wicket html snippet\r
+               FreemarkerPanel panel = new FreemarkerPanel("listComponent", "FilterableProjectList.fm", values);\r
+               panel.setParseGeneratedMarkup(true);\r
+               panel.setRenderBodyOnly(true);\r
+               add(panel);\r
+               \r
+               // add the Wicket controls that are referenced in the snippet \r
+               String listTitle = StringUtils.isEmpty(title) ? getString("gb.projects") : title;\r
+               panel.add(new Label(ngList + "Title", MessageFormat.format("{0} ({1})", listTitle, projects.size())));\r
+               if (StringUtils.isEmpty(iconClass)) {\r
+                       panel.add(new Label(ngList + "Icon").setVisible(false));\r
+               } else {\r
+                       Label icon = new Label(ngList + "Icon");\r
+                       WicketUtils.setCssClass(icon, iconClass);\r
+                       panel.add(icon);\r
+               }\r
+               \r
+               String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy");\r
+               final DateFormat df = new SimpleDateFormat(format);\r
+               df.setTimeZone(getTimeZone());\r
+               Collections.sort(projects, new Comparator<ProjectModel>() {\r
+                       @Override\r
+                       public int compare(ProjectModel o1, ProjectModel o2) {\r
+                               return o2.lastChange.compareTo(o1.lastChange);\r
+                       }\r
+               });\r
+\r
+               List<ProjectListItem> list = new ArrayList<ProjectListItem>();\r
+               for (ProjectModel proj : projects) {\r
+                       if (proj.isUserProject() || proj.repositories.isEmpty()) {\r
+                               // exclude user projects from list\r
+                               continue;\r
+                       }\r
+                       ProjectListItem item = new ProjectListItem();\r
+                       item.p = proj.name;\r
+                       item.n = StringUtils.isEmpty(proj.title) ? proj.name : proj.title;\r
+                       item.i = proj.description;\r
+                       item.t = getTimeUtils().timeAgo(proj.lastChange);\r
+                       item.d = df.format(proj.lastChange);\r
+                       item.c = proj.repositories.size();\r
+                       list.add(item);\r
+               }\r
+               \r
+               // inject an AngularJS controller with static data\r
+               NgController ctrl = new NgController(ngCtrl);\r
+               ctrl.addVariable(ngList, list);\r
+               add(new HeaderContributor(ctrl));\r
+       }\r
+\r
+       protected class ProjectListItem implements Serializable {\r
+\r
+               private static final long serialVersionUID = 1L;\r
+               \r
+               String p; // path\r
+               String n; // name\r
+               String t; // time ago\r
+               String d; // last updated\r
+               String i; // information/description\r
+               long c;   // repository count\r
+       }\r
+}
\ 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 (file)
index 0000000..4c3aecd
--- /dev/null
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml"  \r
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  \r
+      xml:lang="en"  \r
+      lang="en"> \r
+\r
+<wicket:panel>\r
+       <div wicket:id="listComponent">[component]</div>\r
+</wicket:panel>\r
+</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 (file)
index 0000000..6c43b78
--- /dev/null
@@ -0,0 +1,154 @@
+/*\r
+ * Copyright 2013 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.wicket.panels;\r
+\r
+import java.io.Serializable;\r
+import java.text.DateFormat;\r
+import java.text.MessageFormat;\r
+import java.text.SimpleDateFormat;\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+import org.apache.wicket.behavior.HeaderContributor;\r
+import org.apache.wicket.markup.html.basic.Label;\r
+\r
+import com.gitblit.GitBlit;\r
+import com.gitblit.Keys;\r
+import com.gitblit.models.RepositoryModel;\r
+import com.gitblit.utils.StringUtils;\r
+import com.gitblit.wicket.WicketUtils;\r
+import com.gitblit.wicket.freemarker.FreemarkerPanel;\r
+import com.gitblit.wicket.ng.NgController;\r
+import com.gitblit.wicket.pages.EditRepositoryPage;\r
+\r
+/**\r
+ * A client-side filterable rich repository list which uses Freemarker, Wicket,\r
+ * and AngularJS. \r
+ * \r
+ * @author James Moger\r
+ *\r
+ */\r
+public class FilterableRepositoryList extends BasePanel {\r
+\r
+       private static final long serialVersionUID = 1L;\r
+\r
+       private final List<RepositoryModel> repositories;\r
+       \r
+       private String title;\r
+       \r
+       private String iconClass;\r
+       \r
+       private boolean allowCreate;\r
+       \r
+       public FilterableRepositoryList(String id, List<RepositoryModel> repositories) {\r
+               super(id);\r
+               this.repositories = repositories;\r
+       }\r
+       \r
+       public void setTitle(String title, String iconClass) {\r
+               this.title = title;\r
+               this.iconClass = iconClass;\r
+       }\r
+       \r
+       public void setAllowCreate(boolean value) {\r
+               this.allowCreate = value;\r
+       }\r
+\r
+       @Override\r
+       protected void onInitialize() {\r
+               super.onInitialize();\r
+\r
+               String id = getId();\r
+               String ngCtrl = id + "Ctrl";\r
+               String ngList = id + "List";\r
+               \r
+               Map<String, Object> values = new HashMap<String, Object>();\r
+               values.put("ngCtrl",  ngCtrl);\r
+               values.put("ngList",  ngList);\r
+               \r
+               // use Freemarker to setup an AngularJS/Wicket html snippet\r
+               FreemarkerPanel panel = new FreemarkerPanel("listComponent", "FilterableRepositoryList.fm", values);\r
+               panel.setParseGeneratedMarkup(true);\r
+               panel.setRenderBodyOnly(true);\r
+               add(panel);\r
+               \r
+               // add the Wicket controls that are referenced in the snippet \r
+               String listTitle = StringUtils.isEmpty(title) ? getString("gb.repositories") : title;\r
+               panel.add(new Label(ngList + "Title", MessageFormat.format("{0} ({1})", listTitle, repositories.size())));\r
+               if (StringUtils.isEmpty(iconClass)) {\r
+                       panel.add(new Label(ngList + "Icon").setVisible(false));\r
+               } else {\r
+                       Label icon = new Label(ngList + "Icon");\r
+                       WicketUtils.setCssClass(icon, iconClass);\r
+                       panel.add(icon);\r
+               }\r
+               \r
+               if (allowCreate) {\r
+                       panel.add(new LinkPanel(ngList + "Button", "btn btn-mini", getString("gb.newRepository"), EditRepositoryPage.class));\r
+               } else {\r
+                       panel.add(new Label(ngList + "Button").setVisible(false));\r
+               }\r
+               \r
+               String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy");\r
+               final DateFormat df = new SimpleDateFormat(format);\r
+               df.setTimeZone(getTimeZone());\r
+\r
+               // prepare the simplified repository models list\r
+               List<RepoListItem> list = new ArrayList<RepoListItem>();\r
+               for (RepositoryModel repo : repositories) {\r
+                       String name = StringUtils.stripDotGit(repo.name); \r
+                       String path = "";\r
+                       if (name.indexOf('/') > -1) {\r
+                               path = name.substring(0, name.lastIndexOf('/') + 1);\r
+                               name = name.substring(name.lastIndexOf('/') + 1);\r
+                       }\r
+                       \r
+                       RepoListItem item = new RepoListItem();\r
+                       item.n = name;\r
+                       item.p = path;\r
+                       item.r = repo.name;\r
+                       item.i = repo.description;\r
+                       item.s = GitBlit.self().getStarCount(repo);\r
+                       item.t = getTimeUtils().timeAgo(repo.lastChange);\r
+                       item.d = df.format(repo.lastChange);\r
+                       item.c = StringUtils.getColor(StringUtils.stripDotGit(repo.name));\r
+                       item.wc = repo.isBare ? 0 : 1;\r
+                       list.add(item);\r
+               }\r
+               \r
+               // inject an AngularJS controller with static data\r
+               NgController ctrl = new NgController(ngCtrl);\r
+               ctrl.addVariable(ngList, list);\r
+               add(new HeaderContributor(ctrl));\r
+       }\r
+       \r
+       protected class RepoListItem implements Serializable {\r
+\r
+               private static final long serialVersionUID = 1L;\r
+               \r
+               String r; // repository\r
+               String n; // name\r
+               String p; // project/path\r
+               String t; // time ago\r
+               String d; // last updated\r
+               String i; // information/description\r
+               long s;   // stars\r
+               String c; // html color\r
+               int wc;   // working copy: 1 = true, 0 = false\r
+       }\r
+}
\ No newline at end of file
index 8392a9febc4ac2c479e6401c5a9ad644d2698c94..7171197cc92551dec255da5a7b86869bf062e516 100644 (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)\r
 - [Commons-Compress](http://commons.apache.org/compress) (Apache 2.0)\r
 - [XZ for Java](http://tukaani.org/xz/java.html) (Public Domain)\r
+- [FreeMarker](http://www.freemarker.org) (modified BSD)\r
 \r
 ### Other Build Dependencies\r
 - [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed)\r