From 430496317177893eeb94579b2946dbafea6d0727 Mon Sep 17 00:00:00 2001 From: James Moger Date: Wed, 19 Jun 2013 16:26:32 -0400 Subject: Generate filterable project/repository list with FreeMarker --- .classpath | 1 + NOTICE | 10 +- build.moxie | 1 + gitblit.iml | 11 + .../com/gitblit/wicket/freemarker/Freemarker.java | 46 +++ .../gitblit/wicket/freemarker/FreemarkerPanel.java | 308 +++++++++++++++++++++ .../freemarker/templates/FilterableProjectList.fm | 15 + .../templates/FilterableRepositoryList.fm | 19 ++ .../com/gitblit/wicket/pages/MyDashboardPage.html | 83 +----- .../com/gitblit/wicket/pages/MyDashboardPage.java | 85 +----- .../java/com/gitblit/wicket/pages/ProjectPage.html | 22 +- .../java/com/gitblit/wicket/pages/ProjectPage.java | 7 +- .../wicket/panels/FilterableProjectList.html | 10 + .../wicket/panels/FilterableProjectList.java | 139 ++++++++++ .../wicket/panels/FilterableRepositoryList.html | 10 + .../wicket/panels/FilterableRepositoryList.java | 154 +++++++++++ src/site/design.mkd | 1 + 17 files changed, 747 insertions(+), 175 deletions(-) create mode 100644 src/main/java/com/gitblit/wicket/freemarker/Freemarker.java create mode 100644 src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java create mode 100644 src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm create mode 100644 src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm create mode 100644 src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html create mode 100644 src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java create mode 100644 src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html create mode 100644 src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java diff --git a/.classpath b/.classpath index e25f68c5..8cd68de9 100644 --- a/.classpath +++ b/.classpath @@ -40,6 +40,7 @@ + diff --git a/NOTICE b/NOTICE index 0e23d53c..ab0a0868 100644 --- a/NOTICE +++ b/NOTICE @@ -269,4 +269,12 @@ AngularJS AngularJS, release under the MIT License. - http://angularjs.org/ \ No newline at end of file + http://angularjs.org/ + +--------------------------------------------------------------------------- +FreeMarker +--------------------------------------------------------------------------- + FreeMarker, release under a + modified BSD License. (http://www.freemarker.org/docs/app_license.html) + + http://www.freemarker.org/ \ No newline at end of file diff --git a/build.moxie b/build.moxie index be9a21cf..9fc08dc6 100644 --- a/build.moxie +++ b/build.moxie @@ -148,6 +148,7 @@ dependencies: - compile 'com.toedter:jcalendar:1.3.2' :authority - compile 'org.apache.commons:commons-compress:1.4.1' :war - compile 'com.force.api:force-partner-api:24.0.0' :war +- compile 'org.freemarker:freemarker:2.3.19' :war - test 'junit' # Dependencies for Selenium web page testing - test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar diff --git a/gitblit.iml b/gitblit.iml index b90adbd0..38a014a6 100644 --- a/gitblit.iml +++ b/gitblit.iml @@ -413,6 +413,17 @@ + + + + + + + + + + + diff --git a/src/main/java/com/gitblit/wicket/freemarker/Freemarker.java b/src/main/java/com/gitblit/wicket/freemarker/Freemarker.java new file mode 100644 index 00000000..ad7aa964 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/freemarker/Freemarker.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket.freemarker; + +import java.io.IOException; +import java.io.Writer; +import java.util.Map; + +import freemarker.template.Configuration; +import freemarker.template.DefaultObjectWrapper; +import freemarker.template.Template; +import freemarker.template.TemplateException; + +public class Freemarker { + + private static final Configuration fm; + + static { + fm = new Configuration(); + fm.setObjectWrapper(new DefaultObjectWrapper()); + fm.setOutputEncoding("UTF-8"); + fm.setClassForTemplateLoading(Freemarker.class, "templates"); + } + + public static Template getTemplate(String name) throws IOException { + return fm.getTemplate(name); + } + + public static void evaluate(Template template, Map values, Writer out) throws TemplateException, IOException { + template.process(values, out); + } + +} diff --git a/src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java b/src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java new file mode 100644 index 00000000..d57c3a09 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java @@ -0,0 +1,308 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket.freemarker; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Map; + +import org.apache.wicket.MarkupContainer; +import org.apache.wicket.WicketRuntimeException; +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.markup.IMarkupCacheKeyProvider; +import org.apache.wicket.markup.IMarkupResourceStreamProvider; +import org.apache.wicket.markup.MarkupStream; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.util.resource.IResourceStream; +import org.apache.wicket.util.resource.StringResourceStream; +import org.apache.wicket.util.string.Strings; + +import com.gitblit.utils.StringUtils; + +import freemarker.template.Template; +import freemarker.template.TemplateException; + +/** + * This class allows FreeMarker to be used as a Wicket preprocessor or as a + * snippet injector for something like a CMS. There are some cases where Wicket + * is not flexible enough to generate content, especially when you need to generate + * hybrid HTML/JS content outside the scope of Wicket. + * + * @author James Moger + * + */ +@SuppressWarnings("unchecked") +public class FreemarkerPanel extends Panel + implements + IMarkupResourceStreamProvider, + IMarkupCacheKeyProvider +{ + private static final long serialVersionUID = 1L; + + private final String template; + private boolean parseGeneratedMarkup; + private boolean escapeHtml; + private boolean throwFreemarkerExceptions; + private transient String stackTraceAsString; + private transient String evaluatedTemplate; + + + /** + * Construct. + * + * @param id + * Component id + * @param template + * The Freemarker template + * @param values + * values map that can be substituted by Freemarker. + */ + public FreemarkerPanel(final String id, String template, final Map values) + { + this(id, template, Model.ofMap(values)); + } + + /** + * Construct. + * + * @param id + * Component id + * @param templateResource + * The Freemarker template as a string resource + * @param model + * Model with variables that can be substituted by Freemarker. + */ + public FreemarkerPanel(final String id, final String template, final IModel< ? extends Map> model) + { + super(id, model); + this.template = template; + } + + /** + * Gets the Freemarker template. + * + * @return the Freemarker template + */ + private Template getTemplate() + { + if (StringUtils.isEmpty(template)) + { + throw new IllegalArgumentException("Template not specified!"); + } + + try { + return Freemarker.getTemplate(template); + } catch (IOException e) { + onException(e); + } + + return null; + } + + /** + * @see org.apache.wicket.markup.html.panel.Panel#onComponentTagBody(org.apache.wicket.markup. + * MarkupStream, org.apache.wicket.markup.ComponentTag) + */ + @Override + protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) + { + if (!Strings.isEmpty(stackTraceAsString)) + { + // TODO: only display the Freemarker error/stacktrace in development + // mode? + replaceComponentTagBody(markupStream, openTag, Strings + .toMultilineMarkup(stackTraceAsString)); + } + else if (!parseGeneratedMarkup) + { + // check that no components have been added in case the generated + // markup should not be + // parsed + if (size() > 0) + { + throw new WicketRuntimeException( + "Components cannot be added if the generated markup should not be parsed."); + } + + if (evaluatedTemplate == null) + { + // initialize evaluatedTemplate + getMarkupResourceStream(null, null); + } + replaceComponentTagBody(markupStream, openTag, evaluatedTemplate); + } + else + { + super.onComponentTagBody(markupStream, openTag); + } + } + + /** + * Either print or rethrow the throwable. + * + * @param exception + * the cause + * @param markupStream + * the markup stream + * @param openTag + * the open tag + */ + private void onException(final Exception exception) + { + if (!throwFreemarkerExceptions) + { + // print the exception on the panel + stackTraceAsString = Strings.toString(exception); + } + else + { + // rethrow the exception + throw new WicketRuntimeException(exception); + } + } + + /** + * Gets whether to escape HTML characters. + * + * @return whether to escape HTML characters. The default value is false. + */ + public void setEscapeHtml(boolean value) + { + this.escapeHtml = value; + } + + /** + * Evaluates the template and returns the result. + * + * @param templateReader + * used to read the template + * @return the result of evaluating the velocity template + */ + private String evaluateFreemarkerTemplate(Template template) + { + if (evaluatedTemplate == null) + { + // Get model as a map + final Map map = (Map)getDefaultModelObject(); + + // create a writer for capturing the Velocity output + StringWriter writer = new StringWriter(); + + // string to be used as the template name for log messages in case + // of error + try + { + // execute the Freemarker script and capture the output in writer + Freemarker.evaluate(template, map, writer); + + // replace the tag's body the Freemarker output + evaluatedTemplate = writer.toString(); + + if (escapeHtml) + { + // encode the result in order to get valid html output that + // does not break the rest of the page + evaluatedTemplate = Strings.escapeMarkup(evaluatedTemplate).toString(); + } + return evaluatedTemplate; + } + catch (IOException e) + { + onException(e); + } + catch (TemplateException e) + { + onException(e); + } + return null; + } + return evaluatedTemplate; + } + + /** + * Gets whether to parse the resulting Wicket markup. + * + * @return whether to parse the resulting Wicket markup. The default is false. + */ + public void setParseGeneratedMarkup(boolean value) + { + this.parseGeneratedMarkup = value; + } + + /** + * Whether any Freemarker exception should be trapped and displayed on the panel (false) or thrown + * up to be handled by the exception mechanism of Wicket (true). The default is false, which + * traps and displays any exception without having consequences for the other components on the + * page. + *

+ * Trapping these exceptions without disturbing the other components is especially useful in CMS + * like applications, where 'normal' users are allowed to do basic scripting. On errors, you + * want them to be able to have them correct them while the rest of the application keeps on + * working. + *

+ * + * @return Whether any Freemarker exceptions should be thrown or trapped. The default is false. + */ + public void setThrowFreemarkerExceptions(boolean value) + { + this.throwFreemarkerExceptions = value; + } + + /** + * @see org.apache.wicket.markup.IMarkupResourceStreamProvider#getMarkupResourceStream(org.apache + * .wicket.MarkupContainer, java.lang.Class) + */ + public final IResourceStream getMarkupResourceStream(MarkupContainer container, + Class< ? > containerClass) + { + Template template = getTemplate(); + if (template == null) + { + throw new WicketRuntimeException("could not find Freemarker template for panel: " + this); + } + + // evaluate the template and return a new StringResourceStream + StringBuffer sb = new StringBuffer(); + sb.append(""); + sb.append(evaluateFreemarkerTemplate(template)); + sb.append(""); + return new StringResourceStream(sb.toString()); + } + + /** + * @see org.apache.wicket.markup.IMarkupCacheKeyProvider#getCacheKey(org.apache.wicket. + * MarkupContainer, java.lang.Class) + */ + public final String getCacheKey(MarkupContainer container, Class< ? > containerClass) + { + // don't cache the evaluated template + return null; + } + + /** + * @see org.apache.wicket.Component#onDetach() + */ + @Override + protected void onDetach() + { + super.onDetach(); + stackTraceAsString = null; + evaluatedTemplate = null; + } +} diff --git a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm new file mode 100644 index 00000000..691f089b --- /dev/null +++ b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm @@ -0,0 +1,15 @@ +
+
+
+ +
+
+ +
+ {{item.n}} + {{item.t}} + + {{item.c | number}} + +
+
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm new file mode 100644 index 00000000..cf1b7a8b --- /dev/null +++ b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm @@ -0,0 +1,19 @@ +
+
+
+ +
+
+ +
+
+ +
+ !  + {{item.p}}{{item.n}} + {{item.t}} + + {{item.s | number}} + +
+
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html index a9fd1bae..ce1f0281 100644 --- a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html +++ b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html @@ -29,7 +29,7 @@
[recently active]
-
[all projects]
+
[all projects]
@@ -52,7 +52,7 @@
[recently active]
-
[all projects]
+
[all projects]
@@ -74,85 +74,6 @@ - -
-
({{starred.length}}) -
- -
-
- -
- !  - {{item.p}}{{item.n}} - {{item.t}} - - {{item.s | number}} - -
- -
-
- - -
-
({{owned.length}}) -
- -
-
- -
-
- -
- !  - {{item.p}}{{item.n}} - {{item.t}} - - {{item.s | number}} - -
-
-
- - -
-
({{active.length}}) -
- -
-
- -
- !  - {{item.p}}{{item.n}} - {{item.t}} - - {{item.s | number}} - -
-
-
- - -
-
({{projectList.length}}) -
- -
-
- -
- {{item.n}} - {{item.t}} - - {{item.c | number}} - -
-
-
- \ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java index 858821d4..f6f96853 100644 --- a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java +++ b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java @@ -19,10 +19,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.Serializable; -import java.text.DateFormat; import java.text.MessageFormat; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; @@ -34,7 +31,6 @@ import java.util.Set; import org.apache.wicket.Component; import org.apache.wicket.PageParameters; -import org.apache.wicket.behavior.HeaderContributor; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Fragment; import org.eclipse.jgit.lib.Constants; @@ -49,8 +45,8 @@ import com.gitblit.utils.MarkdownUtils; import com.gitblit.utils.StringUtils; import com.gitblit.wicket.GitBlitWebSession; import com.gitblit.wicket.WicketUtils; -import com.gitblit.wicket.ng.NgController; -import com.gitblit.wicket.panels.LinkPanel; +import com.gitblit.wicket.panels.FilterableProjectList; +import com.gitblit.wicket.panels.FilterableRepositoryList; public class MyDashboardPage extends DashboardPage { @@ -152,38 +148,36 @@ public class MyDashboardPage extends DashboardPage { add(repositoryTabs); - Fragment projectList = createProjectList(); - repositoryTabs.add(projectList); + // projects list + List projects = GitBlit.self().getProjectModels(getRepositoryModels(), false); + repositoryTabs.add(new FilterableProjectList("projects", projects)); // active repository list if (active.isEmpty()) { repositoryTabs.add(new Label("active").setVisible(false)); } else { - Fragment activeView = createNgList("active", "activeListFragment", "activeCtrl", active); - repositoryTabs.add(activeView); + FilterableRepositoryList repoList = new FilterableRepositoryList("active", active); + repoList.setTitle(getString("gb.activeRepositories"), "icon-time"); + repositoryTabs.add(repoList); } // starred repository list if (ArrayUtils.isEmpty(starred)) { repositoryTabs.add(new Label("starred").setVisible(false)); } else { - Fragment starredView = createNgList("starred", "starredListFragment", "starredCtrl", starred); - repositoryTabs.add(starredView); + FilterableRepositoryList repoList = new FilterableRepositoryList("starred", starred); + repoList.setTitle(getString("gb.starredRepositories"), "icon-star"); + repositoryTabs.add(repoList); } // owned repository list if (ArrayUtils.isEmpty(owned)) { repositoryTabs.add(new Label("owned").setVisible(false)); } else { - Fragment ownedView = createNgList("owned", "ownedListFragment", "ownedCtrl", owned); - if (user.canCreate) { - // create button - ownedView.add(new LinkPanel("create", "btn btn-mini", getString("gb.newRepository"), EditRepositoryPage.class)); - } else { - // no button - ownedView.add(new Label("create").setVisible(false)); - } - repositoryTabs.add(ownedView); + FilterableRepositoryList repoList = new FilterableRepositoryList("owned", starred); + repoList.setTitle(getString("gb.myRepositories"), "icon-user"); + repoList.setAllowCreate(user.canCreate() || user.canAdmin()); + repositoryTabs.add(repoList); } } @@ -259,53 +253,4 @@ public class MyDashboardPage extends DashboardPage { } return MessageFormat.format(getString("gb.failedToReadMessage"), file); } - - protected Fragment createProjectList() { - String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy"); - final DateFormat df = new SimpleDateFormat(format); - df.setTimeZone(getTimeZone()); - List projects = GitBlit.self().getProjectModels(getRepositoryModels(), false); - Collections.sort(projects, new Comparator() { - @Override - public int compare(ProjectModel o1, ProjectModel o2) { - return o2.lastChange.compareTo(o1.lastChange); - } - }); - - List list = new ArrayList(); - for (ProjectModel proj : projects) { - if (proj.isUserProject() || proj.repositories.isEmpty()) { - // exclude user projects from list - continue; - } - ProjectListItem item = new ProjectListItem(); - item.p = proj.name; - item.n = StringUtils.isEmpty(proj.title) ? proj.name : proj.title; - item.i = proj.description; - item.t = getTimeUtils().timeAgo(proj.lastChange); - item.d = df.format(proj.lastChange); - item.c = proj.repositories.size(); - list.add(item); - } - - // inject an AngularJS controller with static data - NgController ctrl = new NgController("projectListCtrl"); - ctrl.addVariable("projectList", list); - add(new HeaderContributor(ctrl)); - - Fragment fragment = new Fragment("projectList", "projectListFragment", this); - return fragment; - } - - protected class ProjectListItem implements Serializable { - - private static final long serialVersionUID = 1L; - - String p; // path - String n; // name - String t; // time ago - String d; // last updated - String i; // information/description - long c; // repository count - } } diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectPage.html b/src/main/java/com/gitblit/wicket/pages/ProjectPage.html index 102a49e6..32139d05 100644 --- a/src/main/java/com/gitblit/wicket/pages/ProjectPage.html +++ b/src/main/java/com/gitblit/wicket/pages/ProjectPage.html @@ -51,25 +51,7 @@ - - -
-
({{repositoryList.length}}) -
- -
-
- -
- !  - {{item.p}}{{item.n}} - {{item.t}} - - {{item.s | number}} - -
-
-
- + + \ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectPage.java b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java index b101b400..bfc8493c 100644 --- a/src/main/java/com/gitblit/wicket/pages/ProjectPage.java +++ b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java @@ -24,7 +24,6 @@ import org.apache.wicket.Component; import org.apache.wicket.PageParameters; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.ExternalLink; -import org.apache.wicket.markup.html.panel.Fragment; import com.gitblit.GitBlit; import com.gitblit.Keys; @@ -41,6 +40,7 @@ import com.gitblit.wicket.PageRegistration; import com.gitblit.wicket.PageRegistration.DropDownMenuItem; import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration; import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.panels.FilterableRepositoryList; public class ProjectPage extends DashboardPage { @@ -128,8 +128,9 @@ public class ProjectPage extends DashboardPage { if (repositories.isEmpty()) { add(new Label("repositoryList").setVisible(false)); } else { - Fragment activeView = createNgList("repositoryList", "repositoryListFragment", "repositoryListCtrl", repositories); - add(activeView); + FilterableRepositoryList repoList = new FilterableRepositoryList("repositoryList", repositories); + repoList.setAllowCreate(user.canCreate(project.name + "/")); + add(repoList); } } diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html new file mode 100644 index 00000000..4c3aecdf --- /dev/null +++ b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html @@ -0,0 +1,10 @@ + + + + +
[component]
+
+ \ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java new file mode 100644 index 00000000..a5b74131 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java @@ -0,0 +1,139 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket.panels; + +import java.io.Serializable; +import java.text.DateFormat; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.wicket.behavior.HeaderContributor; +import org.apache.wicket.markup.html.basic.Label; + +import com.gitblit.GitBlit; +import com.gitblit.Keys; +import com.gitblit.models.ProjectModel; +import com.gitblit.utils.StringUtils; +import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.freemarker.FreemarkerPanel; +import com.gitblit.wicket.ng.NgController; + +/** + * A client-side filterable rich project list which uses Freemarker, Wicket, + * and AngularJS. + * + * @author James Moger + * + */ +public class FilterableProjectList extends BasePanel { + + private static final long serialVersionUID = 1L; + + private final List projects; + + private String title; + + private String iconClass; + + public FilterableProjectList(String id, List projects) { + super(id); + this.projects = projects; + } + + public void setTitle(String title, String iconClass) { + this.title = title; + this.iconClass = iconClass; + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + String id = getId(); + String ngCtrl = id + "Ctrl"; + String ngList = id + "List"; + + Map values = new HashMap(); + values.put("ngCtrl", ngCtrl); + values.put("ngList", ngList); + + // use Freemarker to setup an AngularJS/Wicket html snippet + FreemarkerPanel panel = new FreemarkerPanel("listComponent", "FilterableProjectList.fm", values); + panel.setParseGeneratedMarkup(true); + panel.setRenderBodyOnly(true); + add(panel); + + // add the Wicket controls that are referenced in the snippet + String listTitle = StringUtils.isEmpty(title) ? getString("gb.projects") : title; + panel.add(new Label(ngList + "Title", MessageFormat.format("{0} ({1})", listTitle, projects.size()))); + if (StringUtils.isEmpty(iconClass)) { + panel.add(new Label(ngList + "Icon").setVisible(false)); + } else { + Label icon = new Label(ngList + "Icon"); + WicketUtils.setCssClass(icon, iconClass); + panel.add(icon); + } + + String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy"); + final DateFormat df = new SimpleDateFormat(format); + df.setTimeZone(getTimeZone()); + Collections.sort(projects, new Comparator() { + @Override + public int compare(ProjectModel o1, ProjectModel o2) { + return o2.lastChange.compareTo(o1.lastChange); + } + }); + + List list = new ArrayList(); + for (ProjectModel proj : projects) { + if (proj.isUserProject() || proj.repositories.isEmpty()) { + // exclude user projects from list + continue; + } + ProjectListItem item = new ProjectListItem(); + item.p = proj.name; + item.n = StringUtils.isEmpty(proj.title) ? proj.name : proj.title; + item.i = proj.description; + item.t = getTimeUtils().timeAgo(proj.lastChange); + item.d = df.format(proj.lastChange); + item.c = proj.repositories.size(); + list.add(item); + } + + // inject an AngularJS controller with static data + NgController ctrl = new NgController(ngCtrl); + ctrl.addVariable(ngList, list); + add(new HeaderContributor(ctrl)); + } + + protected class ProjectListItem implements Serializable { + + private static final long serialVersionUID = 1L; + + String p; // path + String n; // name + String t; // time ago + String d; // last updated + String i; // information/description + long c; // repository count + } +} \ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html new file mode 100644 index 00000000..4c3aecdf --- /dev/null +++ b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html @@ -0,0 +1,10 @@ + + + + +
[component]
+
+ \ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java new file mode 100644 index 00000000..6c43b787 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java @@ -0,0 +1,154 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket.panels; + +import java.io.Serializable; +import java.text.DateFormat; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.wicket.behavior.HeaderContributor; +import org.apache.wicket.markup.html.basic.Label; + +import com.gitblit.GitBlit; +import com.gitblit.Keys; +import com.gitblit.models.RepositoryModel; +import com.gitblit.utils.StringUtils; +import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.freemarker.FreemarkerPanel; +import com.gitblit.wicket.ng.NgController; +import com.gitblit.wicket.pages.EditRepositoryPage; + +/** + * A client-side filterable rich repository list which uses Freemarker, Wicket, + * and AngularJS. + * + * @author James Moger + * + */ +public class FilterableRepositoryList extends BasePanel { + + private static final long serialVersionUID = 1L; + + private final List repositories; + + private String title; + + private String iconClass; + + private boolean allowCreate; + + public FilterableRepositoryList(String id, List repositories) { + super(id); + this.repositories = repositories; + } + + public void setTitle(String title, String iconClass) { + this.title = title; + this.iconClass = iconClass; + } + + public void setAllowCreate(boolean value) { + this.allowCreate = value; + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + String id = getId(); + String ngCtrl = id + "Ctrl"; + String ngList = id + "List"; + + Map values = new HashMap(); + values.put("ngCtrl", ngCtrl); + values.put("ngList", ngList); + + // use Freemarker to setup an AngularJS/Wicket html snippet + FreemarkerPanel panel = new FreemarkerPanel("listComponent", "FilterableRepositoryList.fm", values); + panel.setParseGeneratedMarkup(true); + panel.setRenderBodyOnly(true); + add(panel); + + // add the Wicket controls that are referenced in the snippet + String listTitle = StringUtils.isEmpty(title) ? getString("gb.repositories") : title; + panel.add(new Label(ngList + "Title", MessageFormat.format("{0} ({1})", listTitle, repositories.size()))); + if (StringUtils.isEmpty(iconClass)) { + panel.add(new Label(ngList + "Icon").setVisible(false)); + } else { + Label icon = new Label(ngList + "Icon"); + WicketUtils.setCssClass(icon, iconClass); + panel.add(icon); + } + + if (allowCreate) { + panel.add(new LinkPanel(ngList + "Button", "btn btn-mini", getString("gb.newRepository"), EditRepositoryPage.class)); + } else { + panel.add(new Label(ngList + "Button").setVisible(false)); + } + + String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy"); + final DateFormat df = new SimpleDateFormat(format); + df.setTimeZone(getTimeZone()); + + // prepare the simplified repository models list + List list = new ArrayList(); + for (RepositoryModel repo : repositories) { + String name = StringUtils.stripDotGit(repo.name); + String path = ""; + if (name.indexOf('/') > -1) { + path = name.substring(0, name.lastIndexOf('/') + 1); + name = name.substring(name.lastIndexOf('/') + 1); + } + + RepoListItem item = new RepoListItem(); + item.n = name; + item.p = path; + item.r = repo.name; + item.i = repo.description; + item.s = GitBlit.self().getStarCount(repo); + item.t = getTimeUtils().timeAgo(repo.lastChange); + item.d = df.format(repo.lastChange); + item.c = StringUtils.getColor(StringUtils.stripDotGit(repo.name)); + item.wc = repo.isBare ? 0 : 1; + list.add(item); + } + + // inject an AngularJS controller with static data + NgController ctrl = new NgController(ngCtrl); + ctrl.addVariable(ngList, list); + add(new HeaderContributor(ctrl)); + } + + protected class RepoListItem implements Serializable { + + private static final long serialVersionUID = 1L; + + String r; // repository + String n; // name + String p; // project/path + String t; // time ago + String d; // last updated + String i; // information/description + long s; // stars + String c; // html color + int wc; // working copy: 1 = true, 0 = false + } +} \ No newline at end of file diff --git a/src/site/design.mkd b/src/site/design.mkd index 8392a9fe..7171197c 100644 --- a/src/site/design.mkd +++ b/src/site/design.mkd @@ -47,6 +47,7 @@ The following dependencies are automatically downloaded by Gitblit GO (or alread - [JCalendar](http://www.toedter.com/en/jcalendar) (LGPL 2.1) - [Commons-Compress](http://commons.apache.org/compress) (Apache 2.0) - [XZ for Java](http://tukaani.org/xz/java.html) (Public Domain) +- [FreeMarker](http://www.freemarker.org) (modified BSD) ### Other Build Dependencies - [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed) -- cgit v1.2.3