<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" />
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
- 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
</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>
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+<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
--- /dev/null
+<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"> </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
<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
<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"> </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"> </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"> </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
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
\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
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
\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
}\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
</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"> </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
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
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
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
--- /dev/null
+<!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
--- /dev/null
+/*\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
--- /dev/null
+<!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
--- /dev/null
+/*\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
- [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