James Moger
2014-05-01 fdd82f02dddd2d4211e9df4239f3be6c8595b2dd
Refactored common code out of My Tickets and Tickets
6 files added
10 files modified
1 files deleted
1663 ■■■■■ changed files
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/SessionlessForm.java 15 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/TicketsUI.java 211 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/WicketUtils.java 4 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html 56 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java 435 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RepositoryPage.html 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/TicketBasePage.java 126 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/TicketPage.java 21 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/TicketsPage.html 46 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/TicketsPage.java 316 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/TicketListPanel.html 55 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/TicketListPanel.java 243 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/TicketSearchForm.java 78 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/UserTitlePanel.html 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java 32 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -672,11 +672,11 @@
gb.mergeToDescription = default integration branch for merging ticket patchsets
gb.anonymousCanNotPropose = Anonymous users can not propose patchsets.
gb.youDoNotHaveClonePermission = You are not permitted to clone this repository.
gb.myTickets = my tickets
gb.yourAssignedTickets = assigned to you
gb.newMilestone = new milestone
gb.editMilestone = edit milestone
gb.notifyChangedOpenTickets = send notification for changed open tickets
gb.overdue = overdue
gb.openMilestones = open milestones
gb.closedMilestones = closed milestones
gb.myTickets = my tickets
gb.yourAssignedTickets = assigned to you
src/main/java/com/gitblit/wicket/SessionlessForm.java
@@ -22,6 +22,7 @@
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.form.StatelessForm;
import org.apache.wicket.protocol.http.RequestUtils;
import org.apache.wicket.protocol.http.WicketURLDecoder;
import org.apache.wicket.protocol.http.request.WebRequestCodingStrategy;
import org.apache.wicket.util.string.AppendingStringBuffer;
@@ -53,9 +54,9 @@
    private static final String HIDDEN_DIV_START = "<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\">";
    private final Class<? extends BasePage> pageClass;
    protected final Class<? extends BasePage> pageClass;
    private final PageParameters pageParameters;
    protected final PageParameters pageParameters;
    private final Logger log = LoggerFactory.getLogger(SessionlessForm.class);
@@ -145,4 +146,14 @@
        String un = WicketURLDecoder.QUERY_INSTANCE.decode(s);
        return Strings.escapeMarkup(un).toString();
    }
    protected String getAbsoluteUrl() {
        return getAbsoluteUrl(pageClass, pageParameters);
    }
    protected String getAbsoluteUrl(Class<? extends BasePage> pageClass, PageParameters pageParameters) {
        String relativeUrl = urlFor(pageClass, pageParameters).toString();
        String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
        return absoluteUrl;
    }
}
src/main/java/com/gitblit/wicket/TicketsUI.java
New file
@@ -0,0 +1,211 @@
/*
 * Copyright 2014 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;
import java.io.Serializable;
import java.text.MessageFormat;
import org.apache.wicket.markup.html.basic.Label;
import com.gitblit.models.TicketModel;
import com.gitblit.models.TicketModel.Status;
import com.gitblit.models.TicketModel.Type;
import com.gitblit.utils.StringUtils;
/**
 * Common tickets ui methods and classes.
 *
 * @author James Moger
 *
 */
public class TicketsUI {
    public static final String [] openStatii = new String [] { Status.New.name().toLowerCase(), Status.Open.name().toLowerCase() };
    public static final String [] closedStatii = new String [] { "!" + Status.New.name().toLowerCase(), "!" + Status.Open.name().toLowerCase() };
    public static Label getStateIcon(String wicketId, TicketModel ticket) {
        return getStateIcon(wicketId, ticket.type, ticket.status);
    }
    public static Label getStateIcon(String wicketId, Type type, Status state) {
        Label label = new Label(wicketId);
        if (type == null) {
            type = Type.defaultType;
        }
        switch (type) {
        case Proposal:
            WicketUtils.setCssClass(label, "fa fa-code-fork");
            break;
        case Bug:
            WicketUtils.setCssClass(label, "fa fa-bug");
            break;
        case Enhancement:
            WicketUtils.setCssClass(label, "fa fa-magic");
            break;
        case Question:
            WicketUtils.setCssClass(label, "fa fa-question");
            break;
        default:
            // standard ticket
            WicketUtils.setCssClass(label, "fa fa-ticket");
        }
        WicketUtils.setHtmlTooltip(label, getTypeState(type, state));
        return label;
    }
    public static String getTypeState(Type type, Status state) {
        return state.toString() + " " + type.toString();
    }
    public static String getLozengeClass(Status status, boolean subtle) {
        if (status == null) {
            status = Status.New;
        }
        String css = "";
        switch (status) {
        case Declined:
        case Duplicate:
        case Invalid:
        case Wontfix:
        case Abandoned:
            css = "aui-lozenge-error";
            break;
        case Fixed:
        case Merged:
        case Resolved:
            css = "aui-lozenge-success";
            break;
        case New:
            css = "aui-lozenge-complete";
            break;
        case On_Hold:
            css = "aui-lozenge-current";
            break;
        default:
            css = "";
            break;
        }
        return "aui-lozenge" + (subtle ? " aui-lozenge-subtle": "") + (css.isEmpty() ? "" : " ") + css;
    }
    public static String getStatusClass(Status status) {
        String css = "";
        switch (status) {
        case Declined:
        case Duplicate:
        case Invalid:
        case Wontfix:
        case Abandoned:
            css = "resolution-error";
            break;
        case Fixed:
        case Merged:
        case Resolved:
            css = "resolution-success";
            break;
        case New:
            css = "resolution-complete";
            break;
        case On_Hold:
            css = "resolution-current";
            break;
        default:
            css = "";
            break;
        }
        return "resolution" + (css.isEmpty() ? "" : " ") + css;
    }
    public static class TicketSort implements Serializable {
        private static final long serialVersionUID = 1L;
        public final String name;
        public final String sortBy;
        public final boolean desc;
        public TicketSort(String name, String sortBy, boolean desc) {
            this.name = name;
            this.sortBy = sortBy;
            this.desc = desc;
        }
    }
    public static class Indicator implements Serializable {
        private static final long serialVersionUID = 1L;
        public final String css;
        public final int count;
        public final String tooltip;
        public Indicator(String css, String tooltip) {
            this.css = css;
            this.tooltip = tooltip;
            this.count = 0;
        }
        public Indicator(String css, int count, String pattern) {
            this.css = css;
            this.count = count;
            this.tooltip = StringUtils.isEmpty(pattern) ? "" : MessageFormat.format(pattern, count);
        }
        public String getTooltip() {
            return tooltip;
        }
    }
    public static class TicketQuery implements Serializable, Comparable<TicketQuery> {
        private static final long serialVersionUID = 1L;
        public final String name;
        public final String query;
        public String color;
        public TicketQuery(String name, String query) {
            this.name = name;
            this.query = query;
        }
        public TicketQuery color(String value) {
            this.color = value;
            return this;
        }
        @Override
        public boolean equals(Object o) {
            if (o instanceof TicketQuery) {
                return ((TicketQuery) o).query.equals(query);
            }
            return false;
        }
        @Override
        public int hashCode() {
            return query.hashCode();
        }
        @Override
        public int compareTo(TicketQuery o) {
            return query.compareTo(o.query);
        }
    }
}
src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -300,7 +300,9 @@
    public static PageParameters newRepositoryParameter(String repositoryName) {
        Map<String, String> parameterMap = new HashMap<String, String>();
        parameterMap.put("r", repositoryName);
        if (!StringUtils.isEmpty(repositoryName)) {
            parameterMap.put("r", repositoryName);
        }
        return new PageParameters(parameterMap);
    }
src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html
@@ -7,19 +7,12 @@
<body>
    <wicket:extend>
        <div class="container">
            <div class="row" style="padding-top:15px;min-height:500px;" >
                <div class="tab-pane active" id="tickets">
                    <!-- query controls -->
                    <div class="span3">
                        <div class="hidden-phone">
                            <div>
                                <div style="display:inline-block;vertical-align:top;padding: 0px 2px 2px;"><img wicket:id="userGravatar"></img></div>
                                <div style="display:inline-block;">
                                    <div style="font-size:1.5em;" wicket:id="userDisplayName"></div>
                                    <div style="color:#888;font-size:1.2em;padding-top:4px;"><wicket:message key="gb.myTickets"></wicket:message></div>
                                </div>
                            </div>
                            <div wicket:id="userTitlePanel"></div>
                            
                            <!-- search tickets form -->
                            <form class="form-search" style="margin: 10px 0px;" wicket:id="ticketSearchForm">
@@ -79,54 +72,13 @@
                                </div>
                            </div>
                        </div>
                        <table class="table tickets">
                            <tbody>
                                   <tr wicket:id="row">
                                       <td class="ticket-list-icon">
                                           <i wicket:id="state"></i>
                                       </td>
                                    <td>
                                        <span wicket:id="title">[title]</span> <span wicket:id="labels" style="font-weight: normal;color:white;"><span class="label" wicket:id="label"></span></span>
                                        <div class="ticket-list-details">
                                            <span style="padding-right: 10px;" class="hidden-phone">
                                                <wicket:message key="gb.createdBy"></wicket:message>
                                                <span style="padding: 0px 2px" wicket:id="createdBy">[createdBy]</span> <span class="date" wicket:id="createDate">[create date]</span>
                                            </span>
                                            <span wicket:id="indicators" style="white-space:nowrap;"><i wicket:id="icon"></i> <span style="padding-right:10px;" wicket:id="count"></span></span>
                                        </div>
                                        <div class="hidden-phone" wicket:id="updated"></div>
                                        <div class="ticket-list-details" wicket:id="repositoryLink">[repository link]</div>
                                    </td>
                                    <td class="ticket-list-state">
                                           <span class="badge badge-info" wicket:id="votes"></span>
                                    </td>
                                    <td class="hidden-phone ticket-list-state">
                                           <i wicket:message="title:gb.watching" style="color:#888;" class="fa fa-eye" wicket:id="watching"></i>
                                    </td>
                                    <td class="ticket-list-state">
                                           <div wicket:id="status"></div>
                                    </td>
                                    <td class="indicators">
                                        <div>
                                             <b>#<span wicket:id="id">[id]</span></b>
                                         </div>
                                        <div wicket:id="responsible"></div>
                                    </td>
                                   </tr>
                            </tbody>
                        </table>
                        <div wicket:id="ticketList"></div>
                    </div>
                </div>
            </div>
        </div>
<wicket:fragment wicket:id="updatedFragment">
    <div class="ticket-list-details">
        <wicket:message key="gb.updatedBy"></wicket:message>
        <span style="padding: 0px 2px" wicket:id="updatedBy">[updatedBy]</span> <span class="date" wicket:id="updateDate">[update date]</span>
    </div>
</wicket:fragment>
</wicket:extend>
</body>
</html>
src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
@@ -1,72 +1,74 @@
/*
 * Copyright 2014 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.pages;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.wicket.PageParameters;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.target.basic.RedirectRequestTarget;
import org.eclipse.jgit.lib.Repository;
import com.gitblit.Constants;
import com.gitblit.Keys;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TicketModel;
import com.gitblit.models.TicketModel.Status;
import com.gitblit.models.TicketModel.Type;
import com.gitblit.models.UserModel;
import com.gitblit.tickets.ITicketService;
import com.gitblit.tickets.QueryBuilder;
import com.gitblit.tickets.QueryResult;
import com.gitblit.tickets.TicketIndexer.Lucene;
import com.gitblit.tickets.TicketLabel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.BugtraqProcessor;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebApp;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.SessionlessForm;
import com.gitblit.wicket.TicketsUI;
import com.gitblit.wicket.TicketsUI.TicketSort;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.GravatarImage;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.TicketListPanel;
import com.gitblit.wicket.panels.TicketSearchForm;
import com.gitblit.wicket.panels.UserTitlePanel;
/**
 * My Tickets page
 *
 * @author Christian Buisson
 * @author James Moger
 */
public class MyTicketsPage extends RootPage {
    public static final String [] openStatii = new String [] { Status.New.name().toLowerCase(), Status.Open.name().toLowerCase() };
    public static final String [] closedStatii = new String [] { "!" + Status.New.name().toLowerCase(), "!" + Status.Open.name().toLowerCase() };
    public MyTicketsPage()
    {
    public MyTicketsPage() {
        this(null);
    }
    public MyTicketsPage(PageParameters params)
    {
    public MyTicketsPage(PageParameters params)    {
        super(params);
        setupPage("", getString("gb.myTickets"));
        UserModel currentUser = GitBlitWebSession.get().getUser();
        if (currentUser == null) {
        if (currentUser == null || UserModel.ANONYMOUS.equals(currentUser)) {
            setRedirect(true);
            setResponsePage(getApplication().getHomePage());
            return;
        }
        final String username = currentUser.getName();
        final String[] statiiParam = (params == null) ? openStatii : params.getStringArray(Lucene.status.name());
        final String[] statiiParam = (params == null) ? TicketsUI.openStatii : params.getStringArray(Lucene.status.name());
        final String assignedToParam = (params == null) ? "" : params.getString(Lucene.responsible.name(), null);
        final String milestoneParam = (params == null) ? "" : params.getString(Lucene.milestone.name(), null);
        final String queryParam = (params == null || StringUtils.isEmpty(params.getString("q", null))) ? "watchedby:" + username : params.getString("q", null);
@@ -74,13 +76,11 @@
        final String sortBy = (params == null) ? "" : Lucene.fromString(params.getString("sort", Lucene.created.name())).name();
        final boolean desc = (params == null) ? true : !"asc".equals(params.getString("direction", "desc"));
        add(new GravatarImage("userGravatar", currentUser, "gravatar", 36, false));
        add(new Label("userDisplayName", currentUser.getDisplayName()));
        // add the user title panel
        add(new UserTitlePanel("userTitlePanel", currentUser, getString("gb.myTickets")));
        // add search form
        TicketSearchForm searchForm = new TicketSearchForm("ticketSearchForm", searchParam);
        add(searchForm);
        searchForm.setTranslatedAttributes();
        add(new TicketSearchForm("ticketSearchForm", null, searchParam, getClass(), params));
        // standard queries
        add(new BookmarkablePageLink<Void>("changesQuery", MyTicketsPage.class,
@@ -137,7 +137,7 @@
                queryParameters(
                        null,
                        milestoneParam,
                        openStatii,
                        TicketsUI.openStatii,
                        null,
                        null,
                        true,
@@ -188,8 +188,8 @@
        } else {
            add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ",")));
        }
        add(new BookmarkablePageLink<Void>("openTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, openStatii, assignedToParam, sortBy, desc, 1)));
        add(new BookmarkablePageLink<Void>("closedTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, closedStatii, assignedToParam, sortBy, desc, 1)));
        add(new BookmarkablePageLink<Void>("openTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, 1)));
        add(new BookmarkablePageLink<Void>("closedTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, 1)));
        add(new BookmarkablePageLink<Void>("allTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, 1)));
        // by status
@@ -203,7 +203,7 @@
            public void populateItem(final Item<Status> item) {
                final Status status = item.getModelObject();
                PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, 1);
                String css = getStatusClass(status);
                String css = TicketsUI.getStatusClass(status);
                item.add(new LinkPanel("statusLink", css, status.toString(), MyTicketsPage.class, p).setRenderBodyOnly(true));
            }
        };
@@ -284,342 +284,18 @@
        int page = (params != null) ? Math.max(1, WicketUtils.getPage(params)) : 1;
        int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25);
        ITicketService tickets = GitBlitWebApp.get().tickets();
        List<QueryResult> results;
        if(StringUtils.isEmpty(searchParam))
        {
            results = tickets.queryFor(luceneQuery, page, pageSize, sortBy, desc);
        if(StringUtils.isEmpty(searchParam)) {
            results = app().tickets().queryFor(luceneQuery, page, pageSize, sortBy, desc);
        } else {
            results = app().tickets().searchFor(null, searchParam, page, pageSize);
        }
        else
        {
            results = tickets.searchFor(null, searchParam, page, pageSize);
        }
        int totalResults = results.size() == 0 ? 0 : results.get(0).totalResults;
        buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, page, pageSize, results.size(), totalResults);
        final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);
        final ListDataProvider<QueryResult> dp = new ListDataProvider<QueryResult>(results);
        DataView<QueryResult> dataView = new DataView<QueryResult>("row", dp) {
            private static final long serialVersionUID = 1L;
            @Override
            protected void populateItem(Item<QueryResult> item) {
                final QueryResult ticket = item.getModelObject();
                final RepositoryModel repository = app().repositories().getRepositoryModel(ticket.repository);
                if (showSwatch) {
                    // set repository color
                    String color = StringUtils.getColor(StringUtils.stripDotGit(repository.name));
                    WicketUtils.setCssStyle(item, MessageFormat.format("border-left: 2px solid {0};", color));
                }
                PageParameters rp = WicketUtils.newRepositoryParameter(ticket.repository);
                PageParameters tp = WicketUtils.newObjectParameter(ticket.repository, "" + ticket.number);
                item.add(new LinkPanel("repositoryLink", null, StringUtils.stripDotGit(ticket.repository), SummaryPage.class, rp));
                item.add(getStateIcon("state", ticket.type, ticket.status));
                item.add(new Label("id", "" + ticket.number));
                UserModel creator = app().users().getUserModel(ticket.createdBy);
                if (creator != null) {
                    item.add(new LinkPanel("createdBy", null, creator.getDisplayName(),
                        UserPage.class, WicketUtils.newUsernameParameter(ticket.createdBy)));
                } else {
                    item.add(new Label("createdBy", ticket.createdBy));
                }
                item.add(WicketUtils.createDateLabel("createDate", ticket.createdAt, GitBlitWebSession
                        .get().getTimezone(), getTimeUtils(), false));
                if (ticket.updatedAt == null) {
                    item.add(new Label("updated").setVisible(false));
                } else {
                    Fragment updated = new Fragment("updated", "updatedFragment", this);
                    UserModel updater = app().users().getUserModel(ticket.updatedBy);
                    if (updater != null) {
                        updated.add(new LinkPanel("updatedBy", null, updater.getDisplayName(),
                                UserPage.class, WicketUtils.newUsernameParameter(ticket.updatedBy)));
                    } else {
                        updated.add(new Label("updatedBy", ticket.updatedBy));
                    }
                    updated.add(WicketUtils.createDateLabel("updateDate", ticket.updatedAt, GitBlitWebSession
                            .get().getTimezone(), getTimeUtils(), false));
                    item.add(updated);
                }
                item.add(new LinkPanel("title", "list subject", StringUtils.trimString(
                        ticket.title, Constants.LEN_SHORTLOG), TicketsPage.class, tp));
                ListDataProvider<String> labelsProvider = new ListDataProvider<String>(ticket.getLabels());
                DataView<String> labelsView = new DataView<String>("labels", labelsProvider) {
                    private static final long serialVersionUID = 1L;
                    @Override
                    public void populateItem(final Item<String> labelItem) {
                        BugtraqProcessor btp  = new BugtraqProcessor(app().settings());
                        Repository db = app().repositories().getRepository(repository.name);
                        String content = btp.processPlainCommitMessage(db, repository.name, labelItem.getModelObject());
                        db.close();
                        Label label = new Label("label", content);
                        label.setEscapeModelStrings(false);
                        TicketLabel tLabel = app().tickets().getLabel(repository, labelItem.getModelObject());
                        String background = MessageFormat.format("background-color:{0};", tLabel.color);
                        label.add(new SimpleAttributeModifier("style", background));
                        labelItem.add(label);
                    }
                };
                item.add(labelsView);
                if (StringUtils.isEmpty(ticket.responsible)) {
                    item.add(new Label("responsible").setVisible(false));
                } else {
                    UserModel responsible = app().users().getUserModel(ticket.responsible);
                    if (responsible == null) {
                        responsible = new UserModel(ticket.responsible);
                    }
                    GravatarImage avatar = new GravatarImage("responsible", responsible.getDisplayName(),
                            responsible.emailAddress, null, 16, true);
                    avatar.setTooltip(getString("gb.responsible") + ": " + responsible.getDisplayName());
                    item.add(avatar);
                }
                // votes indicator
                Label v = new Label("votes", "" + ticket.votesCount);
                WicketUtils.setHtmlTooltip(v, getString("gb.votes"));
                item.add(v.setVisible(ticket.votesCount > 0));
                // watching indicator
                item.add(new Label("watching").setVisible(ticket.isWatching(GitBlitWebSession.get().getUsername())));
                // status indicator
                String css = getLozengeClass(ticket.status, true);
                Label l = new Label("status", ticket.status.toString());
                WicketUtils.setCssClass(l, css);
                item.add(l);
                // add the ticket indicators/icons
                List<Indicator> indicators = new ArrayList<Indicator>();
                // comments
                if (ticket.commentsCount > 0) {
                    int count = ticket.commentsCount;
                    String pattern = "gb.nComments";
                    if (count == 1) {
                        pattern = "gb.oneComment";
                    }
                    indicators.add(new Indicator("fa fa-comment", count, pattern));
                }
                // participants
                if (!ArrayUtils.isEmpty(ticket.participants)) {
                    int count = ticket.participants.size();
                    if (count > 1) {
                        String pattern = "gb.nParticipants";
                        indicators.add(new Indicator("fa fa-user", count, pattern));
                    }
                }
                // attachments
                if (!ArrayUtils.isEmpty(ticket.attachments)) {
                    int count = ticket.attachments.size();
                    String pattern = "gb.nAttachments";
                    if (count == 1) {
                        pattern = "gb.oneAttachment";
                    }
                    indicators.add(new Indicator("fa fa-file", count, pattern));
                }
                // patchset revisions
                if (ticket.patchset != null) {
                    int count = ticket.patchset.commits;
                    String pattern = "gb.nCommits";
                    if (count == 1) {
                        pattern = "gb.oneCommit";
                    }
                    indicators.add(new Indicator("fa fa-code", count, pattern));
                }
                // milestone
                if (!StringUtils.isEmpty(ticket.milestone)) {
                    indicators.add(new Indicator("fa fa-bullseye", ticket.milestone));
                }
                ListDataProvider<Indicator> indicatorsDp = new ListDataProvider<Indicator>(indicators);
                DataView<Indicator> indicatorsView = new DataView<Indicator>("indicators", indicatorsDp) {
                    private static final long serialVersionUID = 1L;
                    @Override
                    public void populateItem(final Item<Indicator> item) {
                        Indicator indicator = item.getModelObject();
                        String tooltip = indicator.getTooltip();
                        Label icon = new Label("icon");
                        WicketUtils.setCssClass(icon, indicator.css);
                        item.add(icon);
                        if (indicator.count > 0) {
                            Label count = new Label("count", "" + indicator.count);
                            item.add(count.setVisible(!StringUtils.isEmpty(tooltip)));
                        } else {
                            item.add(new Label("count").setVisible(false));
                        }
                        WicketUtils.setHtmlTooltip(item, tooltip);
                    }
                };
                item.add(indicatorsView);
            }
        };
        add(dataView);
    }
    protected Label getStateIcon(String wicketId, TicketModel ticket) {
        return getStateIcon(wicketId, ticket.type, ticket.status);
    }
    protected Label getStateIcon(String wicketId, Type type, Status state) {
        Label label = new Label(wicketId);
        if (type == null) {
            type = Type.defaultType;
        }
        switch (type) {
        case Proposal:
            WicketUtils.setCssClass(label, "fa fa-code-fork");
            break;
        case Bug:
            WicketUtils.setCssClass(label, "fa fa-bug");
            break;
        case Enhancement:
            WicketUtils.setCssClass(label, "fa fa-magic");
            break;
        case Question:
            WicketUtils.setCssClass(label, "fa fa-question");
            break;
        default:
            // standard ticket
            WicketUtils.setCssClass(label, "fa fa-ticket");
        }
        WicketUtils.setHtmlTooltip(label, getTypeState(type, state));
        return label;
    }
    protected String getTypeState(Type type, Status state) {
        return state.toString() + " " + type.toString();
    }
    protected String getLozengeClass(Status status, boolean subtle) {
        if (status == null) {
            status = Status.New;
        }
        String css = "";
        switch (status) {
        case Declined:
        case Duplicate:
        case Invalid:
        case Wontfix:
        case Abandoned:
            css = "aui-lozenge-error";
            break;
        case Fixed:
        case Merged:
        case Resolved:
            css = "aui-lozenge-success";
            break;
        case New:
            css = "aui-lozenge-complete";
            break;
        case On_Hold:
            css = "aui-lozenge-current";
            break;
        default:
            css = "";
            break;
        }
        return "aui-lozenge" + (subtle ? " aui-lozenge-subtle": "") + (css.isEmpty() ? "" : " ") + css;
    }
    protected String getStatusClass(Status status) {
        String css = "";
        switch (status) {
        case Declined:
        case Duplicate:
        case Invalid:
        case Wontfix:
        case Abandoned:
            css = "resolution-error";
            break;
        case Fixed:
        case Merged:
        case Resolved:
            css = "resolution-success";
            break;
        case New:
            css = "resolution-complete";
            break;
        case On_Hold:
            css = "resolution-current";
            break;
        default:
            css = "";
            break;
        }
        return "resolution" + (css.isEmpty() ? "" : " ") + css;
    }
    private class TicketSort implements Serializable {
        private static final long serialVersionUID = 1L;
        final String name;
        final String sortBy;
        final boolean desc;
        TicketSort(String name, String sortBy, boolean desc) {
            this.name = name;
            this.sortBy = sortBy;
            this.desc = desc;
        }
    }
    private class TicketSearchForm extends SessionlessForm<Void> implements Serializable {
        private static final long serialVersionUID = 1L;
        private final IModel<String> searchBoxModel;;
        public TicketSearchForm(String id, String text) {
            super(id, MyTicketsPage.this.getClass(), MyTicketsPage.this.getPageParameters());
            this.searchBoxModel = new Model<String>(text == null ? "" : text);
            TextField<String> searchBox = new TextField<String>("ticketSearchBox", searchBoxModel);
            add(searchBox);
        }
        void setTranslatedAttributes() {
            WicketUtils.setHtmlTooltip(get("ticketSearchBox"),
                    MessageFormat.format(getString("gb.searchTicketsTooltip"), ""));
            WicketUtils.setInputPlaceholder(get("ticketSearchBox"), getString("gb.searchTickets"));
        }
        @Override
        public void onSubmit() {
            String searchString = searchBoxModel.getObject();
            if (StringUtils.isEmpty(searchString)) {
                // redirect to self to avoid wicket page update bug
                String absoluteUrl = getCanonicalUrl();
                getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
                return;
            }
            // use an absolute url to workaround Wicket-Tomcat problems with
            // mounted url parameters (issue-111)
            PageParameters params = WicketUtils.newRepositoryParameter("");
            params.add("s", searchString);
            String absoluteUrl = getCanonicalUrl(MyTicketsPage.class, params);
            getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
        }
        add(new TicketListPanel("ticketList", results, showSwatch, true));
    }
    protected PageParameters queryParameters(
@@ -712,30 +388,5 @@
            }
        };
        add(pagesView);
    }
    private class Indicator implements Serializable {
        private static final long serialVersionUID = 1L;
        final String css;
        final int count;
        final String tooltip;
        Indicator(String css, String tooltip) {
            this.css = css;
            this.tooltip = tooltip;
            this.count = 0;
        }
        Indicator(String css, int count, String pattern) {
            this.css = css;
            this.count = count;
            this.tooltip = StringUtils.isEmpty(pattern) ? "" : MessageFormat.format(getString(pattern), count);
        }
        String getTooltip() {
            return tooltip;
        }
    }
}
src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
@@ -17,7 +17,7 @@
                                <form class="form-search" style="margin: 0px;" wicket:id="searchForm">
                                    <div class="input-append">
                                        <select class="span2" style="border-radius: 4px;" wicket:id="searchType"/>
                                        <input type="text" class="search-query" style="width: 170px;border-radius: 14px 0 0 14px; padding-left: 14px;" id="searchBox" wicket:id="searchBox" value=""/>
                                        <input type="text" class="input-medium search-query" style="border-radius: 14px 0 0 14px; padding-left: 14px;" id="searchBox" wicket:id="searchBox" value=""/>
                                        <button class="btn" style="border-radius: 0 14px 14px 0px;margin-left:-5px;" type="submit"><i class="icon-search"></i></button>
                                    </div>
                                </form>
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -69,6 +69,7 @@
import com.gitblit.wicket.PageRegistration;
import com.gitblit.wicket.PageRegistration.OtherPageLink;
import com.gitblit.wicket.SessionlessForm;
import com.gitblit.wicket.TicketsUI;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.NavigationPanel;
@@ -204,7 +205,7 @@
        pages.put("tree", new PageRegistration("gb.tree", TreePage.class, params));
        if (app().tickets().isReady() && (app().tickets().isAcceptingNewTickets(getRepositoryModel()) || app().tickets().hasTickets(getRepositoryModel()))) {
            PageParameters tParams = new PageParameters(params);
            for (String state : TicketsPage.openStatii) {
            for (String state : TicketsUI.openStatii) {
                tParams.add(Lucene.status.name(), state);
            }
            pages.put("tickets", new PageRegistration("gb.tickets", TicketsPage.class, tParams));
src/main/java/com/gitblit/wicket/pages/TicketBasePage.java
File was deleted
src/main/java/com/gitblit/wicket/pages/TicketPage.java
@@ -86,6 +86,7 @@
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.TicketsUI;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.BasePanel.JavascriptTextPrompt;
import com.gitblit.wicket.panels.CommentPanel;
@@ -102,7 +103,7 @@
 * @author James Moger
 *
 */
public class TicketPage extends TicketBasePage {
public class TicketPage extends RepositoryPage {
    static final String NIL = "<nil>";
@@ -154,7 +155,7 @@
        String href = urlFor(TicketsPage.class, params).toString();
        add(new ExternalLink("ticketNumber", href, "#" + ticket.number));
        Label headerStatus = new Label("headerStatus", ticket.status.toString());
        WicketUtils.setCssClass(headerStatus, getLozengeClass(ticket.status, false));
        WicketUtils.setCssClass(headerStatus, TicketsUI.getLozengeClass(ticket.status, false));
        add(headerStatus);
        add(new Label("ticketTitle", ticket.title));
        if (currentPatchset == null) {
@@ -317,10 +318,10 @@
         * LARGE STATUS INDICATOR WITH ICON (DISCUSSION TAB->SIDE BAR)
         */
        Fragment ticketStatus = new Fragment("ticketStatus", "ticketStatusFragment", this);
        Label ticketIcon = getStateIcon("ticketIcon", ticket);
        Label ticketIcon = TicketsUI.getStateIcon("ticketIcon", ticket);
        ticketStatus.add(ticketIcon);
        ticketStatus.add(new Label("ticketStatus", ticket.status.toString()));
        WicketUtils.setCssClass(ticketStatus, getLozengeClass(ticket.status, false));
        WicketUtils.setCssClass(ticketStatus, TicketsUI.getLozengeClass(ticket.status, false));
        add(ticketStatus);
@@ -370,7 +371,7 @@
                                setResponsePage(TicketsPage.class, getPageParameters());
                            }
                        };
                        String css = getStatusClass(item.getModel().getObject());
                        String css = TicketsUI.getStatusClass(item.getModel().getObject());
                        WicketUtils.setCssClass(link, css);
                        item.add(link);
                    }
@@ -665,7 +666,7 @@
                         */
                        Fragment frag = new Fragment("entry", "statusFragment", this);
                        Label status = new Label("statusChange", entry.getStatus().toString());
                        String css = getLozengeClass(entry.getStatus(), false);
                        String css = TicketsUI.getLozengeClass(entry.getStatus(), false);
                        WicketUtils.setCssClass(status, css);
                        for (IBehavior b : status.getBehaviors()) {
                            if (b instanceof SimpleAttributeModifier) {
@@ -936,7 +937,7 @@
                            case status:
                                // special handling for status
                                Status status = event.getStatus();
                                String css = getLozengeClass(status, true);
                                String css = TicketsUI.getLozengeClass(status, true);
                                value = String.format("<span class=\"%1$s\">%2$s</span>", css, status.toString());
                                break;
                            default:
@@ -1525,14 +1526,14 @@
        switch (type) {
            case Rebase:
            case Rebase_Squash:
                typeCss = getLozengeClass(Status.Declined, false);
                typeCss = TicketsUI.getLozengeClass(Status.Declined, false);
                break;
            case Squash:
            case Amend:
                typeCss = getLozengeClass(Status.On_Hold, false);
                typeCss = TicketsUI.getLozengeClass(Status.On_Hold, false);
                break;
            case Proposal:
                typeCss = getLozengeClass(Status.New, false);
                typeCss = TicketsUI.getLozengeClass(Status.New, false);
                break;
            case FastForward:
            default:
src/main/java/com/gitblit/wicket/pages/TicketsPage.html
@@ -11,7 +11,7 @@
    <div class="hidden-phone pull-right">
        <form class="form-search" style="margin: 0px;" wicket:id="ticketSearchForm">
            <div class="input-append">
                <input type="text" class="search-query" style="width: 170px;border-radius: 14px 0 0 14px; padding-left: 14px;" id="ticketSearchBox" wicket:id="ticketSearchBox" value=""/>
                <input type="text" class="input-medium search-query" style="border-radius: 14px 0 0 14px; padding-left: 14px;" id="ticketSearchBox" wicket:id="ticketSearchBox" value=""/>
                <button class="btn" style="border-radius: 0 14px 14px 0px;margin-left:-5px;" type="submit"><i class="icon-search"></i></button>
            </div>
        </form>
@@ -88,42 +88,7 @@
                </div>
            </div>
        
            <table class="table tickets">
            <tbody>
               <tr wicket:id="ticket">
                   <td class="ticket-list-icon">
                       <i wicket:id="state"></i>
                   </td>
                <td>
                    <span wicket:id="title">[title]</span> <span wicket:id="labels" style="font-weight: normal;color:white;"><span class="label" wicket:id="label"></span></span>
                    <div class="ticket-list-details">
                        <span style="padding-right: 10px;" class="hidden-phone">
                            <wicket:message key="gb.createdBy"></wicket:message>
                            <span style="padding: 0px 2px" wicket:id="createdBy">[createdBy]</span> <span class="date" wicket:id="createDate">[create date]</span>
                        </span>
                        <span wicket:id="indicators" style="white-space:nowrap;"><i wicket:id="icon"></i> <span style="padding-right:10px;" wicket:id="count"></span></span>
                    </div>
                    <div class="hidden-phone" wicket:id="updated"></div>
                </td>
                <td class="ticket-list-state">
                       <span class="badge badge-info" wicket:id="votes"></span>
                </td>
                <td class="hidden-phone ticket-list-state">
                       <i wicket:message="title:gb.watching" style="color:#888;" class="fa fa-eye" wicket:id="watching"></i>
                </td>
                <td class="ticket-list-state">
                       <div wicket:id="status"></div>
                </td>
                <td class="indicators">
                    <div>
                         <b>#<span wicket:id="id">[id]</span></b>
                     </div>
                    <div wicket:id="responsible"></div>
                </td>
               </tr>
            </tbody>
            </table>
            <div wicket:id="ticketList"></div>
        
            <div class="btn-group pull-right">
                    <div class="pagination pagination-right pagination-small">
@@ -238,13 +203,6 @@
        <li class="nav-header"><wicket:message key="gb.topicsAndLabels"></wicket:message></li>
        <li class="dynamicQuery" wicket:id="dynamicQuery"><span><span wicket:id="swatch"></span> <span wicket:id="link"></span></span><span class="pull-right"><i style="font-size: 18px;" wicket:id="checked"></i></span></li>
    </ul>
</wicket:fragment>
<wicket:fragment wicket:id="updatedFragment">
    <div class="ticket-list-details">
        <wicket:message key="gb.updatedBy"></wicket:message>
        <span style="padding: 0px 2px" wicket:id="updatedBy">[updatedBy]</span> <span class="date" wicket:id="updateDate">[update date]</span>
    </div>
</wicket:fragment>
</wicket:extend>
src/main/java/com/gitblit/wicket/pages/TicketsPage.java
@@ -15,7 +15,6 @@
 */
package com.gitblit.wicket.pages;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -29,17 +28,12 @@
import org.apache.wicket.PageParameters;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.target.basic.RedirectRequestTarget;
import com.gitblit.Constants;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Keys;
import com.gitblit.models.RegistrantAccessPermission;
@@ -56,18 +50,17 @@
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.SessionlessForm;
import com.gitblit.wicket.TicketsUI;
import com.gitblit.wicket.TicketsUI.TicketQuery;
import com.gitblit.wicket.TicketsUI.TicketSort;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.GravatarImage;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.TicketListPanel;
import com.gitblit.wicket.panels.TicketSearchForm;
public class TicketsPage extends TicketBasePage {
public class TicketsPage extends RepositoryPage {
    final TicketResponsible any;
    public static final String [] openStatii = new String [] { Status.New.name().toLowerCase(), Status.Open.name().toLowerCase() };
    public static final String [] closedStatii = new String [] { "!" + Status.New.name().toLowerCase(), "!" + Status.Open.name().toLowerCase() };
    public TicketsPage(PageParameters params) {
        super(params);
@@ -102,11 +95,8 @@
        final String sortBy = Lucene.fromString(params.getString("sort", Lucene.created.name())).name();
        final boolean desc = !"asc".equals(params.getString("direction", "desc"));
        // add search form
        TicketSearchForm searchForm = new TicketSearchForm("ticketSearchForm", repositoryName, searchParam);
        add(searchForm);
        searchForm.setTranslatedAttributes();
        add(new TicketSearchForm("ticketSearchForm", repositoryName, searchParam, getClass(), params));
        final String activeQuery;
        if (!StringUtils.isEmpty(searchParam)) {
@@ -192,12 +182,12 @@
            milestonePanel.add(new LinkPanel("openTickets", null,
                    MessageFormat.format(getString("gb.nOpenTickets"), currentMilestone.getOpenTickets()),
                    TicketsPage.class,
                    queryParameters(null, currentMilestone.name, openStatii, null, sortBy, desc, 1)));
                    queryParameters(null, currentMilestone.name, TicketsUI.openStatii, null, sortBy, desc, 1)));
            milestonePanel.add(new LinkPanel("closedTickets", null,
                    MessageFormat.format(getString("gb.nClosedTickets"), currentMilestone.getClosedTickets()),
                    TicketsPage.class,
                    queryParameters(null, currentMilestone.name, closedStatii, null, sortBy, desc, 1)));
                    queryParameters(null, currentMilestone.name, TicketsUI.closedStatii, null, sortBy, desc, 1)));
            milestonePanel.add(new Label("totalTickets", MessageFormat.format(getString("gb.nTotalTickets"), currentMilestone.getTotalTickets())));
            add(milestonePanel);
@@ -287,7 +277,7 @@
                queryParameters(
                        null,
                        milestoneParam,
                        openStatii,
                        TicketsUI.openStatii,
                        null,
                        null,
                        true,
@@ -397,8 +387,8 @@
        } else {
            add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ",")));
        }
        add(new BookmarkablePageLink<Void>("openTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, openStatii, assignedToParam, sortBy, desc, 1)));
        add(new BookmarkablePageLink<Void>("closedTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, closedStatii, assignedToParam, sortBy, desc, 1)));
        add(new BookmarkablePageLink<Void>("openTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, 1)));
        add(new BookmarkablePageLink<Void>("closedTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, 1)));
        add(new BookmarkablePageLink<Void>("allTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, 1)));
        // by status
@@ -412,7 +402,7 @@
            public void populateItem(final Item<Status> item) {
                final Status status = item.getModelObject();
                PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, 1);
                String css = getStatusClass(status);
                String css = TicketsUI.getStatusClass(status);
                item.add(new LinkPanel("statusLink", css, status.toString(), TicketsPage.class, p).setRenderBodyOnly(true));
            }
        };
@@ -491,162 +481,7 @@
        // paging links
        buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, page, pageSize, results.size(), totalResults);
        ListDataProvider<QueryResult> resultsDataProvider = new ListDataProvider<QueryResult>(results);
        DataView<QueryResult> ticketsView = new DataView<QueryResult>("ticket", resultsDataProvider) {
            private static final long serialVersionUID = 1L;
            @Override
            public void populateItem(final Item<QueryResult> item) {
                final QueryResult ticket = item.getModelObject();
                item.add(getStateIcon("state", ticket.type, ticket.status));
                item.add(new Label("id", "" + ticket.number));
                UserModel creator = app().users().getUserModel(ticket.createdBy);
                if (creator != null) {
                    item.add(new LinkPanel("createdBy", null, creator.getDisplayName(),
                        UserPage.class, WicketUtils.newUsernameParameter(ticket.createdBy)));
                } else {
                    item.add(new Label("createdBy", ticket.createdBy));
                }
                item.add(WicketUtils.createDateLabel("createDate", ticket.createdAt, GitBlitWebSession
                        .get().getTimezone(), getTimeUtils(), false));
                if (ticket.updatedAt == null) {
                    item.add(new Label("updated").setVisible(false));
                } else {
                    Fragment updated = new Fragment("updated", "updatedFragment", this);
                    UserModel updater = app().users().getUserModel(ticket.updatedBy);
                    if (updater != null) {
                        updated.add(new LinkPanel("updatedBy", null, updater.getDisplayName(),
                                UserPage.class, WicketUtils.newUsernameParameter(ticket.updatedBy)));
                    } else {
                        updated.add(new Label("updatedBy", ticket.updatedBy));
                    }
                    updated.add(WicketUtils.createDateLabel("updateDate", ticket.updatedAt, GitBlitWebSession
                            .get().getTimezone(), getTimeUtils(), false));
                    item.add(updated);
                }
                item.add(new LinkPanel("title", "list subject", StringUtils.trimString(
                        ticket.title, Constants.LEN_SHORTLOG), TicketsPage.class, newTicketParameter(ticket)));
                ListDataProvider<String> labelsProvider = new ListDataProvider<String>(ticket.getLabels());
                DataView<String> labelsView = new DataView<String>("labels", labelsProvider) {
                    private static final long serialVersionUID = 1L;
                    @Override
                    public void populateItem(final Item<String> labelItem) {
                        String content = bugtraqProcessor().processPlainCommitMessage(getRepository(), repositoryName, labelItem.getModelObject());
                        Label label = new Label("label", content);
                        label.setEscapeModelStrings(false);
                        TicketLabel tLabel = app().tickets().getLabel(getRepositoryModel(), labelItem.getModelObject());
                        String background = MessageFormat.format("background-color:{0};", tLabel.color);
                        label.add(new SimpleAttributeModifier("style", background));
                        labelItem.add(label);
                    }
                };
                item.add(labelsView);
                if (StringUtils.isEmpty(ticket.responsible)) {
                    item.add(new Label("responsible").setVisible(false));
                } else {
                    UserModel responsible = app().users().getUserModel(ticket.responsible);
                    if (responsible == null) {
                        responsible = new UserModel(ticket.responsible);
                    }
                    GravatarImage avatar = new GravatarImage("responsible", responsible.getDisplayName(),
                            responsible.emailAddress, null, 16, true);
                    avatar.setTooltip(getString("gb.responsible") + ": " + responsible.getDisplayName());
                    item.add(avatar);
                }
                // votes indicator
                Label v = new Label("votes", "" + ticket.votesCount);
                WicketUtils.setHtmlTooltip(v, getString("gb.votes"));
                item.add(v.setVisible(ticket.votesCount > 0));
                // watching indicator
                item.add(new Label("watching").setVisible(ticket.isWatching(GitBlitWebSession.get().getUsername())));
                // status indicator
                String css = getLozengeClass(ticket.status, true);
                Label l = new Label("status", ticket.status.toString());
                WicketUtils.setCssClass(l, css);
                item.add(l);
                // add the ticket indicators/icons
                List<Indicator> indicators = new ArrayList<Indicator>();
                // comments
                if (ticket.commentsCount > 0) {
                    int count = ticket.commentsCount;
                    String pattern = "gb.nComments";
                    if (count == 1) {
                        pattern = "gb.oneComment";
                    }
                    indicators.add(new Indicator("fa fa-comment", count, pattern));
                }
                // participants
                if (!ArrayUtils.isEmpty(ticket.participants)) {
                    int count = ticket.participants.size();
                    if (count > 1) {
                        String pattern = "gb.nParticipants";
                        indicators.add(new Indicator("fa fa-user", count, pattern));
                    }
                }
                // attachments
                if (!ArrayUtils.isEmpty(ticket.attachments)) {
                    int count = ticket.attachments.size();
                    String pattern = "gb.nAttachments";
                    if (count == 1) {
                        pattern = "gb.oneAttachment";
                    }
                    indicators.add(new Indicator("fa fa-file", count, pattern));
                }
                // patchset revisions
                if (ticket.patchset != null) {
                    int count = ticket.patchset.commits;
                    String pattern = "gb.nCommits";
                    if (count == 1) {
                        pattern = "gb.oneCommit";
                    }
                    indicators.add(new Indicator("fa fa-code", count, pattern));
                }
                // milestone
                if (!StringUtils.isEmpty(ticket.milestone)) {
                    indicators.add(new Indicator("fa fa-bullseye", ticket.milestone));
                }
                ListDataProvider<Indicator> indicatorsDp = new ListDataProvider<Indicator>(indicators);
                DataView<Indicator> indicatorsView = new DataView<Indicator>("indicators", indicatorsDp) {
                    private static final long serialVersionUID = 1L;
                    @Override
                    public void populateItem(final Item<Indicator> item) {
                        Indicator indicator = item.getModelObject();
                        String tooltip = indicator.getTooltip();
                        Label icon = new Label("icon");
                        WicketUtils.setCssClass(icon, indicator.css);
                        item.add(icon);
                        if (indicator.count > 0) {
                            Label count = new Label("count", "" + indicator.count);
                            item.add(count.setVisible(!StringUtils.isEmpty(tooltip)));
                        } else {
                            item.add(new Label("count").setVisible(false));
                        }
                        WicketUtils.setHtmlTooltip(item, tooltip);
                    }
                };
                item.add(indicatorsView);
            }
        };
        add(ticketsView);
        add(new TicketListPanel("ticketList", results, false, false));
        // new milestone link
        RepositoryModel repositoryModel = getRepositoryModel();
@@ -747,12 +582,12 @@
                    milestonePanel.add(new LinkPanel("openTickets", null,
                            MessageFormat.format(getString("gb.nOpenTickets"), m.getOpenTickets()),
                            TicketsPage.class,
                            queryParameters(null, tm.name, openStatii, null, null, true, 1)));
                            queryParameters(null, tm.name, TicketsUI.openStatii, null, null, true, 1)));
                    milestonePanel.add(new LinkPanel("closedTickets", null,
                            MessageFormat.format(getString("gb.nClosedTickets"), m.getClosedTickets()),
                            TicketsPage.class,
                            queryParameters(null, tm.name, closedStatii, null, null, true, 1)));
                            queryParameters(null, tm.name, TicketsUI.closedStatii, null, null, true, 1)));
                    milestonePanel.add(new Label("totalTickets", MessageFormat.format(getString("gb.nTotalTickets"), m.getTotalTickets())));
                    entryPanel.add(milestonePanel);
@@ -863,124 +698,5 @@
            }
        };
        add(pagesView);
    }
    private class Indicator implements Serializable {
        private static final long serialVersionUID = 1L;
        final String css;
        final int count;
        final String tooltip;
        Indicator(String css, String tooltip) {
            this.css = css;
            this.tooltip = tooltip;
            this.count = 0;
        }
        Indicator(String css, int count, String pattern) {
            this.css = css;
            this.count = count;
            this.tooltip = StringUtils.isEmpty(pattern) ? "" : MessageFormat.format(getString(pattern), count);
        }
        String getTooltip() {
            return tooltip;
        }
    }
    private class TicketQuery implements Serializable, Comparable<TicketQuery> {
        private static final long serialVersionUID = 1L;
        final String name;
        final String query;
        String color;
        TicketQuery(String name, String query) {
            this.name = name;
            this.query = query;
        }
        TicketQuery color(String value) {
            this.color = value;
            return this;
        }
        @Override
        public boolean equals(Object o) {
            if (o instanceof TicketQuery) {
                return ((TicketQuery) o).query.equals(query);
            }
            return false;
        }
        @Override
        public int hashCode() {
            return query.hashCode();
        }
        @Override
        public int compareTo(TicketQuery o) {
            return query.compareTo(o.query);
        }
    }
    private class TicketSort implements Serializable {
        private static final long serialVersionUID = 1L;
        final String name;
        final String sortBy;
        final boolean desc;
        TicketSort(String name, String sortBy, boolean desc) {
            this.name = name;
            this.sortBy = sortBy;
            this.desc = desc;
        }
    }
    private class TicketSearchForm extends SessionlessForm<Void> implements Serializable {
        private static final long serialVersionUID = 1L;
        private final String repositoryName;
        private final IModel<String> searchBoxModel;;
        public TicketSearchForm(String id, String repositoryName, String text) {
            super(id, TicketsPage.this.getClass(), TicketsPage.this.getPageParameters());
            this.repositoryName = repositoryName;
            this.searchBoxModel = new Model<String>(text == null ? "" : text);
            TextField<String> searchBox = new TextField<String>("ticketSearchBox", searchBoxModel);
            add(searchBox);
        }
        void setTranslatedAttributes() {
            WicketUtils.setHtmlTooltip(get("ticketSearchBox"),
                    MessageFormat.format(getString("gb.searchTicketsTooltip"), repositoryName));
            WicketUtils.setInputPlaceholder(get("ticketSearchBox"), getString("gb.searchTickets"));
        }
        @Override
        public void onSubmit() {
            String searchString = searchBoxModel.getObject();
            if (StringUtils.isEmpty(searchString)) {
                // redirect to self to avoid wicket page update bug
                String absoluteUrl = getCanonicalUrl();
                getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
                return;
            }
            // use an absolute url to workaround Wicket-Tomcat problems with
            // mounted url parameters (issue-111)
            PageParameters params = WicketUtils.newRepositoryParameter(repositoryName);
            params.add("s", searchString);
            String absoluteUrl = getCanonicalUrl(TicketsPage.class, params);
            getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
        }
    }
}
src/main/java/com/gitblit/wicket/panels/TicketListPanel.html
New file
@@ -0,0 +1,55 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
      xml:lang="en"
      lang="en">
<body>
<wicket:panel>
<table class="table tickets">
    <tbody>
        <tr wicket:id="row">
               <td class="ticket-list-icon">
                   <i wicket:id="state"></i>
               </td>
            <td>
                <span wicket:id="title">[title]</span> <span wicket:id="labels" style="font-weight: normal;color:white;"><span class="label" wicket:id="label"></span></span>
                <div class="ticket-list-details">
                    <span style="padding-right: 10px;" class="hidden-phone">
                        <wicket:message key="gb.createdBy"></wicket:message>
                        <span style="padding: 0px 2px" wicket:id="createdBy">[createdBy]</span> <span class="date" wicket:id="createDate">[create date]</span>
                    </span>
                    <span wicket:id="indicators" style="white-space:nowrap;"><i wicket:id="icon"></i> <span style="padding-right:10px;" wicket:id="count"></span></span>
                </div>
                <div class="hidden-phone" wicket:id="updated"></div>
                <div class="ticket-list-details"><span class="activitySwatch" wicket:id="repositoryLink">[repository link]</span></div>
            </td>
            <td class="ticket-list-state">
                   <span class="badge badge-info" wicket:id="votes"></span>
            </td>
            <td class="hidden-phone ticket-list-state">
                   <i wicket:message="title:gb.watching" style="color:#888;" class="fa fa-eye" wicket:id="watching"></i>
            </td>
            <td class="ticket-list-state">
                   <div wicket:id="status"></div>
            </td>
            <td class="indicators">
                <div>
                        <b>#<span wicket:id="id">[id]</span></b>
                    </div>
                <div wicket:id="responsible"></div>
            </td>
           </tr>
    </tbody>
</table>
<wicket:fragment wicket:id="updatedFragment">
    <div class="ticket-list-details">
        <wicket:message key="gb.updatedBy"></wicket:message>
        <span style="padding: 0px 2px" wicket:id="updatedBy">[updatedBy]</span> <span class="date" wicket:id="updateDate">[update date]</span>
    </div>
</wicket:fragment>
</wicket:panel>
</body>
</html>
src/main/java/com/gitblit/wicket/panels/TicketListPanel.java
New file
@@ -0,0 +1,243 @@
/*
 * Copyright 2014 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.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.PageParameters;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.eclipse.jgit.lib.Repository;
import com.gitblit.Constants;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.tickets.QueryResult;
import com.gitblit.tickets.TicketLabel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.BugtraqProcessor;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.TicketsUI;
import com.gitblit.wicket.TicketsUI.Indicator;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.SummaryPage;
import com.gitblit.wicket.pages.TicketsPage;
import com.gitblit.wicket.pages.UserPage;
/**
 *
 * The ticket list panel lists tickets in a table.
 *
 * @author James Moger
 *
 */
public class TicketListPanel extends BasePanel {
    private static final long serialVersionUID = 1L;
    public TicketListPanel(String wicketId, List<QueryResult> list, final boolean showSwatch, final boolean showRepository) {
        super(wicketId);
        final ListDataProvider<QueryResult> dp = new ListDataProvider<QueryResult>(list);
        DataView<QueryResult> dataView = new DataView<QueryResult>("row", dp) {
            private static final long serialVersionUID = 1L;
            @Override
            protected void populateItem(Item<QueryResult> item) {
                final QueryResult ticket = item.getModelObject();
                final RepositoryModel repository = app().repositories().getRepositoryModel(ticket.repository);
                if (showSwatch) {
                    // set repository color
                    String color = StringUtils.getColor(StringUtils.stripDotGit(repository.name));
                    WicketUtils.setCssStyle(item, MessageFormat.format("border-left: 2px solid {0};", color));
                }
                PageParameters rp = WicketUtils.newRepositoryParameter(ticket.repository);
                PageParameters tp = WicketUtils.newObjectParameter(ticket.repository, "" + ticket.number);
                if (showRepository) {
                    String name = StringUtils.stripDotGit(ticket.repository);
                    LinkPanel link = new LinkPanel("repositoryLink", null, name, SummaryPage.class, rp);
                    WicketUtils.setCssBackground(link, name);
                    item.add(link);
                } else {
                    item.add(new Label("repositoryLink").setVisible(false));
                }
                item.add(TicketsUI.getStateIcon("state", ticket.type, ticket.status));
                item.add(new Label("id", "" + ticket.number));
                UserModel creator = app().users().getUserModel(ticket.createdBy);
                if (creator != null) {
                    item.add(new LinkPanel("createdBy", null, creator.getDisplayName(),
                            UserPage.class, WicketUtils.newUsernameParameter(ticket.createdBy)));
                } else {
                    item.add(new Label("createdBy", ticket.createdBy));
                }
                item.add(WicketUtils.createDateLabel("createDate", ticket.createdAt, GitBlitWebSession
                        .get().getTimezone(), getTimeUtils(), false));
                if (ticket.updatedAt == null) {
                    item.add(new Label("updated").setVisible(false));
                } else {
                    Fragment updated = new Fragment("updated", "updatedFragment", this);
                    UserModel updater = app().users().getUserModel(ticket.updatedBy);
                    if (updater != null) {
                        updated.add(new LinkPanel("updatedBy", null, updater.getDisplayName(),
                                UserPage.class, WicketUtils.newUsernameParameter(ticket.updatedBy)));
                    } else {
                        updated.add(new Label("updatedBy", ticket.updatedBy));
                    }
                    updated.add(WicketUtils.createDateLabel("updateDate", ticket.updatedAt, GitBlitWebSession
                            .get().getTimezone(), getTimeUtils(), false));
                    item.add(updated);
                }
                item.add(new LinkPanel("title", "list subject", StringUtils.trimString(
                        ticket.title, Constants.LEN_SHORTLOG), TicketsPage.class, tp));
                ListDataProvider<String> labelsProvider = new ListDataProvider<String>(ticket.getLabels());
                DataView<String> labelsView = new DataView<String>("labels", labelsProvider) {
                    private static final long serialVersionUID = 1L;
                    @Override
                    public void populateItem(final Item<String> labelItem) {
                        BugtraqProcessor btp  = new BugtraqProcessor(app().settings());
                        Repository db = app().repositories().getRepository(repository.name);
                        String content = btp.processPlainCommitMessage(db, repository.name, labelItem.getModelObject());
                        db.close();
                        Label label = new Label("label", content);
                        label.setEscapeModelStrings(false);
                        TicketLabel tLabel = app().tickets().getLabel(repository, labelItem.getModelObject());
                        String background = MessageFormat.format("background-color:{0};", tLabel.color);
                        label.add(new SimpleAttributeModifier("style", background));
                        labelItem.add(label);
                    }
                };
                item.add(labelsView);
                if (StringUtils.isEmpty(ticket.responsible)) {
                    item.add(new Label("responsible").setVisible(false));
                } else {
                    UserModel responsible = app().users().getUserModel(ticket.responsible);
                    if (responsible == null) {
                        responsible = new UserModel(ticket.responsible);
                    }
                    GravatarImage avatar = new GravatarImage("responsible", responsible.getDisplayName(),
                            responsible.emailAddress, null, 16, true);
                    avatar.setTooltip(getString("gb.responsible") + ": " + responsible.getDisplayName());
                    item.add(avatar);
                }
                // votes indicator
                Label v = new Label("votes", "" + ticket.votesCount);
                WicketUtils.setHtmlTooltip(v, getString("gb.votes"));
                item.add(v.setVisible(ticket.votesCount > 0));
                // watching indicator
                item.add(new Label("watching").setVisible(ticket.isWatching(GitBlitWebSession.get().getUsername())));
                // status indicator
                String css = TicketsUI.getLozengeClass(ticket.status, true);
                Label l = new Label("status", ticket.status.toString());
                WicketUtils.setCssClass(l, css);
                item.add(l);
                // add the ticket indicators/icons
                List<Indicator> indicators = new ArrayList<Indicator>();
                // comments
                if (ticket.commentsCount > 0) {
                    int count = ticket.commentsCount;
                    String pattern = getString("gb.nComments");
                    if (count == 1) {
                        pattern = getString("gb.oneComment");
                    }
                    indicators.add(new Indicator("fa fa-comment", count, pattern));
                }
                // participants
                if (!ArrayUtils.isEmpty(ticket.participants)) {
                    int count = ticket.participants.size();
                    if (count > 1) {
                        String pattern = getString("gb.nParticipants");
                        indicators.add(new Indicator("fa fa-user", count, pattern));
                    }
                }
                // attachments
                if (!ArrayUtils.isEmpty(ticket.attachments)) {
                    int count = ticket.attachments.size();
                    String pattern = getString("gb.nAttachments");
                    if (count == 1) {
                        pattern = getString("gb.oneAttachment");
                    }
                    indicators.add(new Indicator("fa fa-file", count, pattern));
                }
                // patchset revisions
                if (ticket.patchset != null) {
                    int count = ticket.patchset.commits;
                    String pattern = getString("gb.nCommits");
                    if (count == 1) {
                        pattern = getString("gb.oneCommit");
                    }
                    indicators.add(new Indicator("fa fa-code", count, pattern));
                }
                // milestone
                if (!StringUtils.isEmpty(ticket.milestone)) {
                    indicators.add(new Indicator("fa fa-bullseye", ticket.milestone));
                }
                ListDataProvider<Indicator> indicatorsDp = new ListDataProvider<Indicator>(indicators);
                DataView<Indicator> indicatorsView = new DataView<Indicator>("indicators", indicatorsDp) {
                    private static final long serialVersionUID = 1L;
                    @Override
                    public void populateItem(final Item<Indicator> item) {
                        Indicator indicator = item.getModelObject();
                        String tooltip = indicator.getTooltip();
                        Label icon = new Label("icon");
                        WicketUtils.setCssClass(icon, indicator.css);
                        item.add(icon);
                        if (indicator.count > 0) {
                            Label count = new Label("count", "" + indicator.count);
                            item.add(count.setVisible(!StringUtils.isEmpty(tooltip)));
                        } else {
                            item.add(new Label("count").setVisible(false));
                        }
                        WicketUtils.setHtmlTooltip(item, tooltip);
                    }
                };
                item.add(indicatorsView);
            }
        };
        add(dataView);
    }
}
src/main/java/com/gitblit/wicket/panels/TicketSearchForm.java
New file
@@ -0,0 +1,78 @@
/*
 * Copyright 2014 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.MessageFormat;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.target.basic.RedirectRequestTarget;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.SessionlessForm;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.BasePage;
public class TicketSearchForm extends SessionlessForm<Void> implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String repositoryName;
    private final IModel<String> searchBoxModel;
    public TicketSearchForm(String id, String repositoryName, String text,
            Class<? extends BasePage> pageClass, PageParameters params) {
        super(id, pageClass, params);
        this.repositoryName = repositoryName;
        this.searchBoxModel = new Model<String>(text == null ? "" : text);
        TextField<String> searchBox = new TextField<String>("ticketSearchBox", searchBoxModel);
        add(searchBox);
    }
    @Override
    protected
    void onInitialize() {
        super.onInitialize();
        WicketUtils.setHtmlTooltip(get("ticketSearchBox"),
                MessageFormat.format(getString("gb.searchTicketsTooltip"), ""));
        WicketUtils.setInputPlaceholder(get("ticketSearchBox"), getString("gb.searchTickets"));
    }
    @Override
    public void onSubmit() {
        String searchString = searchBoxModel.getObject();
        if (StringUtils.isEmpty(searchString)) {
            // redirect to self to avoid wicket page update bug
            String absoluteUrl = getAbsoluteUrl();
            getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
            return;
        }
        // use an absolute url to workaround Wicket-Tomcat problems with
        // mounted url parameters (issue-111)
        PageParameters params = WicketUtils.newRepositoryParameter(repositoryName);
        params.add("s", searchString);
        String absoluteUrl = getAbsoluteUrl(pageClass, params);
        getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
    }
}
src/main/java/com/gitblit/wicket/panels/UserTitlePanel.html
New file
@@ -0,0 +1,16 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
      xml:lang="en"
      lang="en">
<body>
<wicket:panel>
<div style="display:inline-block;vertical-align:top;padding: 0px 2px 2px;"><img wicket:id="userGravatar"></img></div>
    <div style="display:inline-block;">
    <div style="font-size:1.5em;" wicket:id="userDisplayName"></div>
    <div style="color:#888;font-size:1.2em;padding-top:4px;"><span wicket:id="userTitle"></span></div>
</div>
</wicket:panel>
</body>
</html>
src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java
New file
@@ -0,0 +1,32 @@
/*
 * Copyright 2014 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 org.apache.wicket.markup.html.basic.Label;
import com.gitblit.models.UserModel;
public class UserTitlePanel extends BasePanel {
    private static final long serialVersionUID = 1L;
    public UserTitlePanel(String wicketId, UserModel user, String title) {
        super(wicketId);
        add(new GravatarImage("userGravatar", user, "gravatar", 36, false));
        add(new Label("userDisplayName", user.getDisplayName()));
        add(new Label("userTitle", title));
    }
}