Переглянути джерело

Merged #15 "My Tickets page"

tags/v1.6.0
James Moger 10 роки тому
джерело
коміт
66e4a930dc

+ 2
- 0
src/main/java/com/gitblit/wicket/GitBlitWebApp.java Переглянути файл

@@ -81,6 +81,7 @@ import com.gitblit.wicket.pages.TicketsPage;
import com.gitblit.wicket.pages.TreePage;
import com.gitblit.wicket.pages.UserPage;
import com.gitblit.wicket.pages.UsersPage;
import com.gitblit.wicket.pages.MyTicketsPage;

public class GitBlitWebApp extends WebApplication {

@@ -191,6 +192,7 @@ public class GitBlitWebApp extends WebApplication {
mount("/tickets/export", ExportTicketPage.class, "r", "h");
mount("/milestones/new", NewMilestonePage.class, "r");
mount("/milestones/edit", EditMilestonePage.class, "r", "h");
mount("/mytickets", MyTicketsPage.class, "r", "h");

// setup the markup document urls
mount("/docs", DocsPage.class, "r");

+ 3
- 1
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties Переглянути файл

@@ -672,9 +672,11 @@ gb.ticketIsClosed = This ticket is closed.
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.closedMilestones = closed milestones

+ 2
- 0
src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties Переглянути файл

@@ -670,3 +670,5 @@ gb.repositoryDoesNotAcceptPatchsets = Ce d
gb.serverDoesNotAcceptPatchsets = Ce serveur n'accepte pas de patchsets.
gb.ticketIsClosed = Ce ticket est clos.
gb.mergeToDescription = default integration branch for merging ticket patchsets
gb.myTickets = mes tickets
gb.yourAssignedTickets = dont vous êtes responsable

+ 13
- 2
src/main/java/com/gitblit/wicket/SessionlessForm.java Переглянути файл

@@ -22,6 +22,7 @@ import org.apache.wicket.PageParameters;
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 @@ public class SessionlessForm<T> extends StatelessForm<T> {

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 @@ public class SessionlessForm<T> extends StatelessForm<T> {
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;
}
}

+ 211
- 0
src/main/java/com/gitblit/wicket/TicketsUI.java Переглянути файл

@@ -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);
}
}
}

+ 3
- 1
src/main/java/com/gitblit/wicket/WicketUtils.java Переглянути файл

@@ -300,7 +300,9 @@ public class WicketUtils {
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);
}

+ 84
- 0
src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html Переглянути файл

@@ -0,0 +1,84 @@
<!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: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 wicket:id="userTitlePanel"></div>
<!-- search tickets form -->
<form class="form-search" style="margin: 10px 0px;" wicket:id="ticketSearchForm">
<div class="input-append">
<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>
<!-- query list -->
<ul class="nav nav-list">
<li class="nav-header"><wicket:message key="gb.queries"></wicket:message></li>
<li><a wicket:id="changesQuery"><i class="fa fa-code-fork"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li>
<li><a wicket:id="bugsQuery"><i class="fa fa-bug"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li>
<li><a wicket:id="enhancementsQuery"><i class="fa fa-magic"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li>
<li><a wicket:id="tasksQuery"><i class="fa fa-ticket"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li>
<li><a wicket:id="questionsQuery"><i class="fa fa-question"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li>
<li wicket:id="userDivider" class="divider"></li>
<li><a wicket:id="createdQuery"><i class="fa fa-user"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li>
<li><a wicket:id="responsibleQuery"><i class="fa fa-user"></i> <wicket:message key="gb.yourAssignedTickets"></wicket:message></a></li>
<li><a wicket:id="watchedQuery"><i class="fa fa-eye"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li>
<li><a wicket:id="mentionsQuery"><i class="fa fa-comment"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li>
<li class="divider"></li>
<li><a wicket:id="resetQuery"><i class="fa fa-bolt"></i> <wicket:message key="gb.reset"></wicket:message></a></li>
</ul>

</div>
</div>
<!-- tickets -->
<div class="span9">
<div class="btn-toolbar" style="margin-top: 0px;">
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#"><wicket:message key="gb.status"></wicket:message>: <span style="font-weight:bold;" wicket:id="selectedStatii"></span> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a wicket:id="openTickets"><wicket:message key="gb.open"></wicket:message></a></li>
<li><a wicket:id="closedTickets"><wicket:message key="gb.closed"></wicket:message></a></li>
<li><a wicket:id="allTickets"><wicket:message key="gb.all"></wicket:message></a></li>
<li class="divider"></li>
<li wicket:id="statii"><span wicket:id="statusLink"></span></li>
</ul>
</div>
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#"><i class="fa fa-sort"></i> <wicket:message key="gb.sort"></wicket:message>: <span style="font-weight:bold;" wicket:id="currentSort"></span> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li wicket:id="sort"><span wicket:id="sortLink"></span></li>
</ul>
</div>
<div class="btn-group pull-right">
<div class="pagination pagination-right pagination-small">
<ul>
<li><a wicket:id="prevLink"><i class="fa fa-angle-double-left"></i></a></li>
<li wicket:id="pageLink"><span wicket:id="page"></span></li>
<li><a wicket:id="nextLink"><i class="fa fa-angle-double-right"></i></a></li>
</ul>
</div>
</div>
</div>
<div wicket:id="ticketList"></div>
</div>
</div>
</div>
</div>
</wicket:extend>
</body>
</html>

+ 392
- 0
src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java Переглянути файл

@@ -0,0 +1,392 @@
/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;

import com.gitblit.Keys;
import com.gitblit.models.TicketModel;
import com.gitblit.models.TicketModel.Status;
import com.gitblit.models.UserModel;
import com.gitblit.tickets.QueryBuilder;
import com.gitblit.tickets.QueryResult;
import com.gitblit.tickets.TicketIndexer.Lucene;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.TicketsUI;
import com.gitblit.wicket.TicketsUI.TicketSort;
import com.gitblit.wicket.WicketUtils;
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 MyTicketsPage() {
this(null);
}

public MyTicketsPage(PageParameters params) {
super(params);
setupPage("", getString("gb.myTickets"));

UserModel currentUser = GitBlitWebSession.get().getUser();
if (currentUser == null || UserModel.ANONYMOUS.equals(currentUser)) {
setRedirect(true);
setResponsePage(getApplication().getHomePage());
return;
}

final String username = currentUser.getName();
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);
final String searchParam = (params == null) ? "" : params.getString("s", null);
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 the user title panel
add(new UserTitlePanel("userTitlePanel", currentUser, getString("gb.myTickets")));

// add search form
add(new TicketSearchForm("ticketSearchForm", null, searchParam, getClass(), params));

// standard queries
add(new BookmarkablePageLink<Void>("changesQuery", MyTicketsPage.class,
queryParameters(
Lucene.type.matches(TicketModel.Type.Proposal.name()),
milestoneParam,
statiiParam,
assignedToParam,
sortBy,
desc,
1)));

add(new BookmarkablePageLink<Void>("bugsQuery", MyTicketsPage.class,
queryParameters(
Lucene.type.matches(TicketModel.Type.Bug.name()),
milestoneParam,
statiiParam,
assignedToParam,
sortBy,
desc,
1)));

add(new BookmarkablePageLink<Void>("enhancementsQuery", MyTicketsPage.class,
queryParameters(
Lucene.type.matches(TicketModel.Type.Enhancement.name()),
milestoneParam,
statiiParam,
assignedToParam,
sortBy,
desc,
1)));

add(new BookmarkablePageLink<Void>("tasksQuery", MyTicketsPage.class,
queryParameters(
Lucene.type.matches(TicketModel.Type.Task.name()),
milestoneParam,
statiiParam,
assignedToParam,
sortBy,
desc,
1)));

add(new BookmarkablePageLink<Void>("questionsQuery", MyTicketsPage.class,
queryParameters(
Lucene.type.matches(TicketModel.Type.Question.name()),
milestoneParam,
statiiParam,
assignedToParam,
sortBy,
desc,
1)));

add(new BookmarkablePageLink<Void>("resetQuery", MyTicketsPage.class,
queryParameters(
null,
milestoneParam,
TicketsUI.openStatii,
null,
null,
true,
1)));

add(new Label("userDivider"));
add(new BookmarkablePageLink<Void>("createdQuery", MyTicketsPage.class,
queryParameters(
Lucene.createdby.matches(username),
milestoneParam,
statiiParam,
assignedToParam,
sortBy,
desc,
1)));

add(new BookmarkablePageLink<Void>("watchedQuery", MyTicketsPage.class,
queryParameters(
Lucene.watchedby.matches(username),
milestoneParam,
statiiParam,
assignedToParam,
sortBy,
desc,
1)));
add(new BookmarkablePageLink<Void>("mentionsQuery", MyTicketsPage.class,
queryParameters(
Lucene.mentions.matches(username),
milestoneParam,
statiiParam,
assignedToParam,
sortBy,
desc,
1)));
add(new BookmarkablePageLink<Void>("responsibleQuery", MyTicketsPage.class,
queryParameters(
Lucene.responsible.matches(username),
milestoneParam,
statiiParam,
assignedToParam,
sortBy,
desc,
1)));

// states
if (ArrayUtils.isEmpty(statiiParam)) {
add(new Label("selectedStatii", getString("gb.all")));
} else {
add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ",")));
}
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
List<Status> statii = new ArrayList<Status>(Arrays.asList(Status.values()));
statii.remove(Status.Closed);
ListDataProvider<Status> resolutionsDp = new ListDataProvider<Status>(statii);
DataView<Status> statiiLinks = new DataView<Status>("statii", resolutionsDp) {
private static final long serialVersionUID = 1L;

@Override
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 = TicketsUI.getStatusClass(status);
item.add(new LinkPanel("statusLink", css, status.toString(), MyTicketsPage.class, p).setRenderBodyOnly(true));
}
};
add(statiiLinks);

List<TicketSort> sortChoices = new ArrayList<TicketSort>();
sortChoices.add(new TicketSort(getString("gb.sortNewest"), Lucene.created.name(), true));
sortChoices.add(new TicketSort(getString("gb.sortOldest"), Lucene.created.name(), false));
sortChoices.add(new TicketSort(getString("gb.sortMostRecentlyUpdated"), Lucene.updated.name(), true));
sortChoices.add(new TicketSort(getString("gb.sortLeastRecentlyUpdated"), Lucene.updated.name(), false));
sortChoices.add(new TicketSort(getString("gb.sortMostComments"), Lucene.comments.name(), true));
sortChoices.add(new TicketSort(getString("gb.sortLeastComments"), Lucene.comments.name(), false));
sortChoices.add(new TicketSort(getString("gb.sortMostPatchsetRevisions"), Lucene.patchsets.name(), true));
sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));
sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));
sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));

TicketSort currentSort = sortChoices.get(0);
for (TicketSort ts : sortChoices) {
if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
currentSort = ts;
break;
}
}
add(new Label("currentSort", currentSort.name));

ListDataProvider<TicketSort> sortChoicesDp = new ListDataProvider<TicketSort>(sortChoices);
DataView<TicketSort> sortMenu = new DataView<TicketSort>("sort", sortChoicesDp) {
private static final long serialVersionUID = 1L;

@Override
public void populateItem(final Item<TicketSort> item) {
final TicketSort ts = item.getModelObject();
PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, ts.sortBy, ts.desc, 1);
item.add(new LinkPanel("sortLink", null, ts.name, MyTicketsPage.class, params).setRenderBodyOnly(true));
}
};
add(sortMenu);

// Build Query here
QueryBuilder qb = new QueryBuilder(queryParam);
if (!qb.containsField(Lucene.status.name()) && !ArrayUtils.isEmpty(statiiParam)) {
// specify the states
boolean not = false;
QueryBuilder q = new QueryBuilder();
for (String state : statiiParam) {
if (state.charAt(0) == '!') {
not = true;
q.and(Lucene.status.doesNotMatch(state.substring(1)));
} else {
q.or(Lucene.status.matches(state));
}
}
if (not) {
qb.and(q.toString());
} else {
qb.and(q.toSubquery().toString());
}
}

final String luceneQuery;
if (qb.containsField(Lucene.createdby.name())
|| qb.containsField(Lucene.responsible.name())
|| qb.containsField(Lucene.watchedby.name())) {
// focused "my tickets" query
luceneQuery = qb.build();
} else {
// general "my tickets" query
QueryBuilder myQuery = new QueryBuilder();
myQuery.or(Lucene.createdby.matches(username));
myQuery.or(Lucene.responsible.matches(username));
myQuery.or(Lucene.watchedby.matches(username));
myQuery.and(qb.toSubquery().toString());
luceneQuery = myQuery.build();
}

// paging links
int page = (params != null) ? Math.max(1, WicketUtils.getPage(params)) : 1;
int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25);

List<QueryResult> results;
if(StringUtils.isEmpty(searchParam)) {
results = app().tickets().queryFor(luceneQuery, page, pageSize, sortBy, desc);
} else {
results = app().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);
add(new TicketListPanel("ticketList", results, showSwatch, true));
}

protected PageParameters queryParameters(
String query,
String milestone,
String[] states,
String assignedTo,
String sort,
boolean descending,
int page) {

PageParameters params = WicketUtils.newRepositoryParameter("");
if (!StringUtils.isEmpty(query)) {
params.add("q", query);
}
if (!StringUtils.isEmpty(milestone)) {
params.add(Lucene.milestone.name(), milestone);
}
if (!ArrayUtils.isEmpty(states)) {
for (String state : states) {
params.add(Lucene.status.name(), state);
}
}
if (!StringUtils.isEmpty(assignedTo)) {
params.add(Lucene.responsible.name(), assignedTo);
}
if (!StringUtils.isEmpty(sort)) {
params.add("sort", sort);
}
if (!descending) {
params.add("direction", "asc");
}
if (page > 1) {
params.add("pg", "" + page);
}
return params;
}

protected void buildPager(
final String query,
final String milestone,
final String [] states,
final String assignedTo,
final String sort,
final boolean desc,
final int page,
int pageSize,
int count,
int total) {

boolean showNav = total > (2 * pageSize);
boolean allowPrev = page > 1;
boolean allowNext = (pageSize * (page - 1) + count) < total;
add(new BookmarkablePageLink<Void>("prevLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page - 1)).setEnabled(allowPrev).setVisible(showNav));
add(new BookmarkablePageLink<Void>("nextLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page + 1)).setEnabled(allowNext).setVisible(showNav));

if (total <= pageSize) {
add(new Label("pageLink").setVisible(false));
return;
}

// determine page numbers to display
int pages = count == 0 ? 0 : ((total / pageSize) + (total % pageSize == 0 ? 0 : 1));
// preferred number of pagelinks
int segments = 5;
if (pages < segments) {
// not enough data for preferred number of page links
segments = pages;
}
int minpage = Math.min(Math.max(1, page - 2), pages - (segments - 1));
int maxpage = Math.min(pages, minpage + (segments - 1));
List<Integer> sequence = new ArrayList<Integer>();
for (int i = minpage; i <= maxpage; i++) {
sequence.add(i);
}

ListDataProvider<Integer> pagesDp = new ListDataProvider<Integer>(sequence);
DataView<Integer> pagesView = new DataView<Integer>("pageLink", pagesDp) {
private static final long serialVersionUID = 1L;

@Override
public void populateItem(final Item<Integer> item) {
final Integer i = item.getModelObject();
LinkPanel link = new LinkPanel("page", null, "" + i, MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, i));
link.setRenderBodyOnly(true);
if (i == page) {
WicketUtils.setCssClass(item, "active");
}
item.add(link);
}
};
add(pagesView);
}
}

+ 1
- 1
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>

+ 2
- 1
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java Переглянути файл

@@ -69,6 +69,7 @@ import com.gitblit.wicket.GitBlitWebSession;
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 @@ public abstract class RepositoryPage extends RootPage {
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));

+ 10
- 5
src/main/java/com/gitblit/wicket/pages/RootPage.java Переглянути файл

@@ -134,6 +134,8 @@ public abstract class RootPage extends BasePage {
boolean authenticateView = app().settings().getBoolean(Keys.web.authenticateViewPages, false);
boolean authenticateAdmin = app().settings().getBoolean(Keys.web.authenticateAdminPages, true);
boolean allowAdmin = app().settings().getBoolean(Keys.web.allowAdministration, true);
boolean allowLucene = app().settings().getBoolean(Keys.web.allowLuceneIndexing, true);
boolean isLoggedIn = GitBlitWebSession.get().isLoggedIn();
if (authenticateAdmin) {
showAdmin = allowAdmin && GitBlitWebSession.get().canAdmin();
@@ -151,7 +153,7 @@ public abstract class RootPage extends BasePage {
}
if (authenticateView || authenticateAdmin) {
if (GitBlitWebSession.get().isLoggedIn()) {
if (isLoggedIn) {
UserMenu userFragment = new UserMenu("userPanel", "userMenuFragment", RootPage.this);
add(userFragment);
} else {
@@ -167,13 +169,16 @@ public abstract class RootPage extends BasePage {
// navigation links
List<PageRegistration> pages = new ArrayList<PageRegistration>();
if (!authenticateView || (authenticateView && GitBlitWebSession.get().isLoggedIn())) {
pages.add(new PageRegistration(GitBlitWebSession.get().isLoggedIn() ? "gb.myDashboard" : "gb.dashboard", MyDashboardPage.class,
if (!authenticateView || (authenticateView && isLoggedIn)) {
pages.add(new PageRegistration(isLoggedIn ? "gb.myDashboard" : "gb.dashboard", MyDashboardPage.class,
getRootPageParameters()));
if (isLoggedIn && app().tickets().isReady()) {
pages.add(new PageRegistration("gb.myTickets", MyTicketsPage.class));
}
pages.add(new PageRegistration("gb.repositories", RepositoriesPage.class,
getRootPageParameters()));
pages.add(new PageRegistration("gb.activity", ActivityPage.class, getRootPageParameters()));
if (app().settings().getBoolean(Keys.web.allowLuceneIndexing, true)) {
if (allowLucene) {
pages.add(new PageRegistration("gb.search", LuceneSearchPage.class));
}
if (showAdmin) {
@@ -183,7 +188,7 @@ public abstract class RootPage extends BasePage {
pages.add(new PageRegistration("gb.federation", FederationPage.class));
}
if (!authenticateView || (authenticateView && GitBlitWebSession.get().isLoggedIn())) {
if (!authenticateView || (authenticateView && isLoggedIn)) {
addDropDownMenus(pages);
}
}

+ 0
- 126
src/main/java/com/gitblit/wicket/pages/TicketBasePage.java Переглянути файл

@@ -1,126 +0,0 @@
/*
* 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.pages;
import org.apache.wicket.PageParameters;
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.wicket.WicketUtils;
public abstract class TicketBasePage extends RepositoryPage {
public TicketBasePage(PageParameters params) {
super(params);
}
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;
}
}

+ 11
- 10
src/main/java/com/gitblit/wicket/pages/TicketPage.java Переглянути файл

@@ -86,6 +86,7 @@ import com.gitblit.utils.MarkdownUtils;
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 @@ import com.gitblit.wicket.panels.SimpleAjaxLink;
* @author James Moger
*
*/
public class TicketPage extends TicketBasePage {
public class TicketPage extends RepositoryPage {
static final String NIL = "<nil>";
@@ -154,7 +155,7 @@ public class TicketPage extends TicketBasePage {
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 @@ public class TicketPage extends TicketBasePage {
* 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 @@ public class TicketPage extends TicketBasePage {
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 @@ public class TicketPage extends TicketBasePage {
*/
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 @@ public class TicketPage extends TicketBasePage {
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 @@ public class TicketPage extends TicketBasePage {
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:

+ 2
- 44
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">
@@ -240,13 +205,6 @@
</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>
</body>
</html>

+ 16
- 300
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.Component;
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,19 +50,18 @@ import com.gitblit.tickets.TicketResponsible;
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 @@ public class TicketsPage extends TicketBasePage {
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 @@ public class TicketsPage extends TicketBasePage {
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 @@ public class TicketsPage extends TicketBasePage {
queryParameters(
null,
milestoneParam,
openStatii,
TicketsUI.openStatii,
null,
null,
true,
@@ -397,8 +387,8 @@ public class TicketsPage extends TicketBasePage {
} 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 class TicketsPage extends TicketBasePage {
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 @@ public class TicketsPage extends TicketBasePage {
// 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 @@ public class TicketsPage extends TicketBasePage {
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);
@@ -864,123 +699,4 @@ public class TicketsPage extends TicketBasePage {
};
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));
}
}
}

+ 55
- 0
src/main/java/com/gitblit/wicket/panels/TicketListPanel.html Переглянути файл

@@ -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>

+ 243
- 0
src/main/java/com/gitblit/wicket/panels/TicketListPanel.java Переглянути файл

@@ -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);
}
}


+ 78
- 0
src/main/java/com/gitblit/wicket/panels/TicketSearchForm.java Переглянути файл

@@ -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));
}
}

+ 16
- 0
src/main/java/com/gitblit/wicket/panels/UserTitlePanel.html Переглянути файл

@@ -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>

+ 32
- 0
src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java Переглянути файл

@@ -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));
}
}

Завантаження…
Відмінити
Зберегти