From cf17b2267bab439cadafbc1def298bae0d5ea659 Mon Sep 17 00:00:00 2001 From: James Moger Date: Thu, 30 May 2013 23:29:25 -0400 Subject: Added a Dashboard page with a daily ref log of starred/owned repos This is a work-in-progress and is mostly working like I want, but will require some refactoring to make it even more useful and less complex. --- .../java/com/gitblit/models/DailyLogEntry.java | 59 +++ src/main/java/com/gitblit/models/PushLogEntry.java | 17 + src/main/java/com/gitblit/utils/PushLogUtils.java | 176 +++++++-- .../java/com/gitblit/wicket/GitBlitWebApp.java | 5 +- .../com/gitblit/wicket/GitBlitWebApp.properties | 15 +- .../com/gitblit/wicket/charting/GoogleChart.java | 6 + .../gitblit/wicket/charting/GooglePieChart.java | 4 +- .../java/com/gitblit/wicket/ng/NgController.java | 79 ++++ .../com/gitblit/wicket/pages/ActivityPage.html | 8 +- .../com/gitblit/wicket/pages/ActivityPage.java | 10 +- .../java/com/gitblit/wicket/pages/BasePage.html | 3 +- .../com/gitblit/wicket/pages/DashboardPage.html | 99 +++++ .../com/gitblit/wicket/pages/DashboardPage.java | 418 +++++++++++++++++++++ .../java/com/gitblit/wicket/pages/HomePage.html | 16 - .../java/com/gitblit/wicket/pages/HomePage.java | 182 --------- .../com/gitblit/wicket/pages/OverviewPage.java | 2 +- .../java/com/gitblit/wicket/pages/PushesPage.java | 2 +- .../java/com/gitblit/wicket/pages/RootPage.java | 4 +- .../com/gitblit/wicket/panels/ActivityPanel.html | 2 +- .../com/gitblit/wicket/panels/PushesPanel.html | 10 +- .../com/gitblit/wicket/panels/PushesPanel.java | 100 +++-- src/main/resources/bootstrap/js/angular.js | 163 ++++++++ src/main/resources/gitblit.css | 24 +- src/site/design.mkd | 1 + 24 files changed, 1125 insertions(+), 280 deletions(-) create mode 100644 src/main/java/com/gitblit/models/DailyLogEntry.java create mode 100644 src/main/java/com/gitblit/wicket/ng/NgController.java create mode 100644 src/main/java/com/gitblit/wicket/pages/DashboardPage.html create mode 100644 src/main/java/com/gitblit/wicket/pages/DashboardPage.java delete mode 100644 src/main/java/com/gitblit/wicket/pages/HomePage.html delete mode 100644 src/main/java/com/gitblit/wicket/pages/HomePage.java create mode 100644 src/main/resources/bootstrap/js/angular.js (limited to 'src') diff --git a/src/main/java/com/gitblit/models/DailyLogEntry.java b/src/main/java/com/gitblit/models/DailyLogEntry.java new file mode 100644 index 00000000..db605268 --- /dev/null +++ b/src/main/java/com/gitblit/models/DailyLogEntry.java @@ -0,0 +1,59 @@ +/* + * 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.models; + +import java.io.Serializable; +import java.util.Date; + +import org.eclipse.jgit.lib.PersonIdent; + +/** + * Model class to simulate a push for presentation in the push log news feed + * for a repository that does not have a Gitblit push log. Commits are grouped + * by date and may be additionally split by ref. + * + * @author James Moger + */ +public class DailyLogEntry extends PushLogEntry implements Serializable { + + private static final long serialVersionUID = 1L; + + public DailyLogEntry(String repository, Date date) { + super(repository, date, new UserModel("digest")); + } + + public DailyLogEntry(String repository, Date date, UserModel user) { + super(repository, date, user); + } + + @Override + public PersonIdent getCommitterIdent() { + if (getAuthorCount() == 1) { + return getCommits().get(0).getCommitterIdent(); + } + + return super.getCommitterIdent(); + } + + @Override + public PersonIdent getAuthorIdent() { + if (getAuthorCount() == 1) { + return getCommits().get(0).getAuthorIdent(); + } + + return super.getAuthorIdent(); + } +} diff --git a/src/main/java/com/gitblit/models/PushLogEntry.java b/src/main/java/com/gitblit/models/PushLogEntry.java index d8f0b091..8b006d96 100644 --- a/src/main/java/com/gitblit/models/PushLogEntry.java +++ b/src/main/java/com/gitblit/models/PushLogEntry.java @@ -54,6 +54,8 @@ public class PushLogEntry implements Serializable, Comparable { private final Map refUpdates; private final Map refIdChanges; + + private int authorCount; /** * Constructor for specified duration of push from start date. @@ -72,6 +74,7 @@ public class PushLogEntry implements Serializable, Comparable { this.commits = new LinkedHashSet(); this.refUpdates = new HashMap(); this.refIdChanges = new HashMap(); + this.authorCount = -1; } /** @@ -152,6 +155,7 @@ public class PushLogEntry implements Serializable, Comparable { public RepositoryCommit addCommit(String branch, RevCommit commit) { RepositoryCommit commitModel = new RepositoryCommit(repository, branch, commit); if (commits.add(commitModel)) { + authorCount = -1; return commitModel; } return null; @@ -165,6 +169,7 @@ public class PushLogEntry implements Serializable, Comparable { */ public void addCommits(List list) { commits.addAll(list); + authorCount = -1; } /** @@ -254,6 +259,18 @@ public class PushLogEntry implements Serializable, Comparable { return list; } + public int getAuthorCount() { + if (authorCount == -1) { + Set authors = new HashSet(); + for (RepositoryCommit commit : commits) { + String name = commit.getAuthorIdent().getName(); + authors.add(name); + } + authorCount = authors.size(); + } + return authorCount; + } + /** * The total number of commits in the push. * diff --git a/src/main/java/com/gitblit/utils/PushLogUtils.java b/src/main/java/com/gitblit/utils/PushLogUtils.java index 2f076fb5..e10a6864 100644 --- a/src/main/java/com/gitblit/utils/PushLogUtils.java +++ b/src/main/java/com/gitblit/utils/PushLogUtils.java @@ -16,8 +16,11 @@ package com.gitblit.utils; import java.io.IOException; +import java.text.DateFormat; import java.text.MessageFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -50,6 +53,7 @@ import org.eclipse.jgit.treewalk.TreeWalk; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.gitblit.models.DailyLogEntry; import com.gitblit.models.PathModel.PathChangeModel; import com.gitblit.models.PushLogEntry; import com.gitblit.models.RefModel; @@ -109,6 +113,25 @@ public class PushLogUtils { return null; } + private static UserModel newUserModelFrom(PersonIdent ident) { + String name = ident.getName(); + String username; + String displayname; + if (name.indexOf('/') > -1) { + int slash = name.indexOf('/'); + displayname = name.substring(0, slash); + username = name.substring(slash + 1); + } else { + displayname = name; + username = ident.getEmailAddress(); + } + + UserModel user = new UserModel(username); + user.displayName = displayname; + user.emailAddress = ident.getEmailAddress(); + return user; + } + /** * Updates a push log. * @@ -135,9 +158,15 @@ public class PushLogUtils { DirCache index = createIndex(repository, headId, commands); ObjectId indexTreeId = index.writeTree(odi); - PersonIdent ident = - new PersonIdent(MessageFormat.format("{0}/{1}", user.getDisplayName(), user.username), + PersonIdent ident; + if (UserModel.ANONYMOUS.equals(user)) { + // anonymous push + ident = new PersonIdent("anonymous", "anonymous"); + } else { + // construct real pushing account + ident = new PersonIdent(MessageFormat.format("{0}/{1}", user.getDisplayName(), user.username), user.emailAddress == null ? user.username : user.emailAddress); + } // Create a commit object CommitBuilder commit = new CommitBuilder(); @@ -339,23 +368,9 @@ public class PushLogUtils { continue; } - String name = push.getAuthorIdent().getName(); - String username; - String displayname; - if (name.indexOf('/') > -1) { - int slash = name.indexOf('/'); - displayname = name.substring(0, slash); - username = name.substring(slash + 1); - } else { - displayname = name; - username = push.getAuthorIdent().getEmailAddress(); - } - - UserModel user = new UserModel(username); - user.displayName = displayname; - user.emailAddress = push.getAuthorIdent().getEmailAddress(); - + UserModel user = newUserModelFrom(push.getAuthorIdent()); Date date = push.getAuthorIdent().getWhen(); + PushLogEntry log = new PushLogEntry(repositoryName, date, user); list.add(log); List changedRefs = JGitUtils.getFilesInCommit(repository, push); @@ -413,14 +428,22 @@ public class PushLogUtils { int maxCount) { // break the push log into ref push logs and then merge them back into a list Map> refMap = new HashMap>(); - for (PushLogEntry push : getPushLog(repositoryName, repository, offset, maxCount)) { + List pushes = getPushLog(repositoryName, repository, offset, maxCount); + for (PushLogEntry push : pushes) { for (String ref : push.getChangedRefs()) { if (!refMap.containsKey(ref)) { refMap.put(ref, new ArrayList()); } // construct new ref-specific push log entry - PushLogEntry refPush = new PushLogEntry(push.repository, push.date, push.user); + PushLogEntry refPush; + if (push instanceof DailyLogEntry) { + // simulated push log from commits grouped by date + refPush = new DailyLogEntry(push.repository, push.date); + } else { + // real push log entry + refPush = new PushLogEntry(push.repository, push.date, push.user); + } refPush.updateRef(ref, push.getChangeType(ref), push.getOldId(ref), push.getNewId(ref)); refPush.addCommits(push.getCommits(ref)); refMap.get(ref).add(refPush); @@ -451,15 +474,16 @@ public class PushLogUtils { public static List getPushLogByRef(String repositoryName, Repository repository, Date minimumDate) { // break the push log into ref push logs and then merge them back into a list Map> refMap = new HashMap>(); - for (PushLogEntry push : getPushLog(repositoryName, repository, minimumDate)) { + List pushes = getPushLog(repositoryName, repository, minimumDate); + for (PushLogEntry push : pushes) { for (String ref : push.getChangedRefs()) { if (!refMap.containsKey(ref)) { refMap.put(ref, new ArrayList()); } - - // construct new ref-specific push log entry - PushLogEntry refPush = new PushLogEntry(push.repository, push.date, push.user); - refPush.updateRef(ref, push.getChangeType(ref), push.getOldId(ref), push.getNewId(ref)); + + // construct new ref-specific push log entry + PushLogEntry refPush = new PushLogEntry(push.repository, push.date, push.user); + refPush.updateRef(ref, push.getChangeType(ref), push.getOldId(ref), push.getNewId(ref)); refPush.addCommits(push.getCommits(ref)); refMap.get(ref).add(refPush); } @@ -476,4 +500,106 @@ public class PushLogUtils { return refPushLog; } + + /** + * Returns a commit log grouped by day. + * + * @param repositoryName + * @param repository + * @param minimumDate + * @param offset + * @param maxCount + * if < 0, all pushes are returned. + * @return a list of grouped commit log entries + */ + public static List getDailyLog(String repositoryName, Repository repository, + Date minimumDate, int offset, int maxCount) { + + DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); +// df.setTimeZone(timezone); + + Map> allRefs = JGitUtils.getAllRefs(repository); + Map tags = new HashMap(); + Map dailydigests = new HashMap(); + for (RefModel local : JGitUtils.getLocalBranches(repository, true, -1)) { + String branch = local.getName(); + List commits = JGitUtils.getRevLog(repository, branch, minimumDate); + for (RevCommit commit : commits) { + Date date = JGitUtils.getCommitDate(commit); + String dateStr = df.format(date); + if (!dailydigests.containsKey(dateStr)) { + dailydigests.put(dateStr, new DailyLogEntry(repositoryName, date)); + } + PushLogEntry digest = dailydigests.get(dateStr); + digest.updateRef(branch, ReceiveCommand.Type.UPDATE, commit.getParents()[0].getId().getName(), commit.getName()); + RepositoryCommit repoCommit = digest.addCommit(branch, commit); + if (repoCommit != null) { + repoCommit.setRefs(allRefs.get(commit.getId())); + if (!ArrayUtils.isEmpty(repoCommit.getRefs())) { + // treat tags as special events in the log + for (RefModel ref : repoCommit.getRefs()) { + if (ref.getName().startsWith(Constants.R_TAGS)) { + if (!tags.containsKey(dateStr)) { + UserModel tagUser = newUserModelFrom(commit.getAuthorIdent()); + Date tagDate = commit.getAuthorIdent().getWhen(); + tags.put(dateStr, new DailyLogEntry(repositoryName, tagDate, tagUser)); + } + PushLogEntry tagEntry = tags.get(dateStr); + tagEntry.updateRef(ref.getName(), ReceiveCommand.Type.CREATE); + tagEntry.addCommits(Arrays.asList(repoCommit)); + } + } + } + } + } + } + + List list = new ArrayList(dailydigests.values()); + list.addAll(tags.values()); + Collections.sort(list); + return list; + } + + /** + * Returns the list of commits separated by ref (e.g. each ref has it's own + * PushLogEntry object for each day). + * + * @param repositoryName + * @param repository + * @param minimumDate + * @return a list of push log entries separated by ref and date + */ + public static List getDailyLogByRef(String repositoryName, Repository repository, Date minimumDate) { + // break the push log into ref push logs and then merge them back into a list + Map> refMap = new HashMap>(); + List pushes = getDailyLog(repositoryName, repository, minimumDate, 0, -1); + for (DailyLogEntry push : pushes) { + for (String ref : push.getChangedRefs()) { + if (!refMap.containsKey(ref)) { + refMap.put(ref, new ArrayList()); + } + + // construct new ref-specific push log entry + DailyLogEntry refPush = new DailyLogEntry(push.repository, push.date, push.user); + refPush.updateRef(ref, push.getChangeType(ref), push.getOldId(ref), push.getNewId(ref)); + refPush.addCommits(push.getCommits(ref)); + refMap.get(ref).add(refPush); + } + } + + // merge individual ref pushes into master list + List refPushLog = new ArrayList(); + for (List refPush : refMap.values()) { + for (DailyLogEntry entry : refPush) { + if (entry.getCommitCount() > 0) { + refPushLog.add(entry); + } + } + } + + // sort ref push log + Collections.sort(refPushLog); + + return refPushLog; + } } diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java index bdb4d455..f4180faa 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java @@ -44,6 +44,7 @@ import com.gitblit.wicket.pages.ForksPage; import com.gitblit.wicket.pages.GitSearchPage; import com.gitblit.wicket.pages.GravatarProfilePage; import com.gitblit.wicket.pages.HistoryPage; +import com.gitblit.wicket.pages.DashboardPage; import com.gitblit.wicket.pages.LogPage; import com.gitblit.wicket.pages.LogoutPage; import com.gitblit.wicket.pages.LuceneSearchPage; @@ -68,7 +69,7 @@ import com.gitblit.wicket.pages.UsersPage; public class GitBlitWebApp extends WebApplication { - public final static Class HOME_PAGE_CLASS = RepositoriesPage.class; + public final static Class HOME_PAGE_CLASS = DashboardPage.class; @Override public void init() { @@ -94,7 +95,7 @@ public class GitBlitWebApp extends WebApplication { } // setup the standard gitweb-ish urls -// mount("/repositories", RepositoriesPage.class); + mount("/repositories", RepositoriesPage.class); mount("/overview", OverviewPage.class, "r", "h"); mount("/summary", SummaryPage.class, "r"); mount("/pushes", PushesPage.class, "r", "h"); diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties index 1b8583e6..036af264 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties @@ -452,7 +452,7 @@ gb.incrementalPushTagMessage = Auto-tagged [{0}] branch on push gb.externalPermissions = {0} access permissions are externally maintained gb.viewAccess = You do not have Gitblit read or write access gb.overview = overview -gb.home = home +gb.dashboard = dashboard gb.monthlyActivity = monthly activity gb.myProfile = my profile gb.compare = compare @@ -460,17 +460,28 @@ gb.manual = manual gb.from = from gb.to = to gb.at = at +gb.of = of +gb.in = in gb.morePushes = all pushes... gb.pushes = pushes gb.pushedNCommitsTo = pushed {0} commits to gb.pushedOneCommitTo = pushed 1 commit to +gb.commitsTo = {0} commits to +gb.oneCommitTo = 1 commit to +gb.byNAuthors = by {0} authors +gb.byOneAuthor = by {0} gb.viewComparison = view comparison of these {0} commits \u00bb gb.nMoreCommits = {0} more commits \u00bb gb.oneMoreCommit = 1 more commit \u00bb gb.pushedNewTag = pushed new tag +gb.createdNewTag = created new tag gb.deletedTag = deleted tag gb.pushedNewBranch = pushed new branch gb.deletedBranch = deleted branch gb.rewind = REWIND gb.star = star -gb.unstar = unstar \ No newline at end of file +gb.unstar = unstar +gb.stargazers = stargazers +gb.starredRepositories = starred repositories +gb.failedToUpdateUser = Failed to update user account! +gb.myRepositories = my repositories diff --git a/src/main/java/com/gitblit/wicket/charting/GoogleChart.java b/src/main/java/com/gitblit/wicket/charting/GoogleChart.java index b6309ffe..334b870d 100644 --- a/src/main/java/com/gitblit/wicket/charting/GoogleChart.java +++ b/src/main/java/com/gitblit/wicket/charting/GoogleChart.java @@ -38,6 +38,7 @@ public abstract class GoogleChart implements Serializable { final List values; int width; int height; + boolean showLegend; public GoogleChart(String tagId, String title, String keyName, String valueName) { this.tagId = tagId; @@ -46,6 +47,7 @@ public abstract class GoogleChart implements Serializable { this.keyName = keyName; this.valueName = valueName; values = new ArrayList(); + showLegend = true; } public void setWidth(int width) { @@ -55,6 +57,10 @@ public abstract class GoogleChart implements Serializable { public void setHeight(int height) { this.height = height; } + + public void setShowLegend(boolean val) { + this.showLegend = val; + } public void addValue(String name, int value) { values.add(new ChartValue(name, value)); diff --git a/src/main/java/com/gitblit/wicket/charting/GooglePieChart.java b/src/main/java/com/gitblit/wicket/charting/GooglePieChart.java index 119a8248..945e08b1 100644 --- a/src/main/java/com/gitblit/wicket/charting/GooglePieChart.java +++ b/src/main/java/com/gitblit/wicket/charting/GooglePieChart.java @@ -68,8 +68,8 @@ public class GooglePieChart extends GoogleChart { cName, tagId)); line(sb, MessageFormat - .format("{0}.draw({1}, '{'width: {2,number,0}, height: {3,number,0}, chartArea:'{'left:20,top:20'}', title: ''{4}'', {5} '}');", - cName, dName, width, height, title, colors.toString())); + .format("{0}.draw({1}, '{' title: ''{4}'', {5}, legend: '{' position:''{6}'' '}' '}');", + cName, dName, width, height, title, colors.toString(), showLegend ? "right" : "none")); line(sb, ""); } } \ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/ng/NgController.java b/src/main/java/com/gitblit/wicket/ng/NgController.java new file mode 100644 index 00000000..fb88cf35 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/ng/NgController.java @@ -0,0 +1,79 @@ +/* + 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.ng; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import org.apache.wicket.markup.html.IHeaderContributor; +import org.apache.wicket.markup.html.IHeaderResponse; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Simple AngularJS data controller which injects scoped objects as static, + * embedded JSON within the generated page. This allows use of AngularJS + * client-side databinding (magic) with server-generated pages. + * + * @author James Moger + * + */ +public class NgController implements IHeaderContributor { + + private static final long serialVersionUID = 1L; + + final String name; + + final Map variables; + + public NgController(String name) { + this.name = name; + this.variables = new HashMap(); + } + + public void addVariable(String name, Object o) { + variables.put(name, o); + } + + @Override + public void renderHead(IHeaderResponse response) { + // add Google AngularJS reference + response.renderJavascriptReference("bootstrap/js/angular.js"); + + Gson gson = new GsonBuilder().create(); + + StringBuilder sb = new StringBuilder(); + line(sb, MessageFormat.format("", name)); + line(sb, MessageFormat.format("function {0}($scope) '{'", name)); + for (Map.Entry entry : variables.entrySet()) { + String var = entry.getKey(); + Object o = entry.getValue(); + String json = gson.toJson(o); + line(sb, MessageFormat.format("\t$scope.{0} = {1};", var, json)); + } + line(sb, "}"); + + response.renderJavascript(sb.toString(), null); + } + + private void line(StringBuilder sb, String line) { + sb.append(line); + sb.append('\n'); + } +} \ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/pages/ActivityPage.html b/src/main/java/com/gitblit/wicket/pages/ActivityPage.html index 14ae459f..f43b26fd 100644 --- a/src/main/java/com/gitblit/wicket/pages/ActivityPage.html +++ b/src/main/java/com/gitblit/wicket/pages/ActivityPage.html @@ -9,12 +9,12 @@

/ [days back]

-
+
- - - + + +
diff --git a/src/main/java/com/gitblit/wicket/pages/ActivityPage.java b/src/main/java/com/gitblit/wicket/pages/ActivityPage.java index bceac8f4..8e841c79 100644 --- a/src/main/java/com/gitblit/wicket/pages/ActivityPage.java +++ b/src/main/java/com/gitblit/wicket/pages/ActivityPage.java @@ -162,8 +162,6 @@ public class ActivityPage extends RootPage { } // build google charts - int w = 310; - int h = 150; GoogleCharts charts = new GoogleCharts(); // sort in reverse-chronological order and then reverse that @@ -178,8 +176,6 @@ public class ActivityPage extends RootPage { for (Activity metric : recentActivity) { chart.addValue(df.format(metric.startDate), metric.getCommitCount()); } - chart.setWidth(w); - chart.setHeight(h); charts.addChart(chart); // active repositories pie chart @@ -188,8 +184,7 @@ public class ActivityPage extends RootPage { for (Metric metric : repositoryMetrics.values()) { chart.addValue(metric.name, metric.count); } - chart.setWidth(w); - chart.setHeight(h); + chart.setShowLegend(false); charts.addChart(chart); // active authors pie chart @@ -198,8 +193,7 @@ public class ActivityPage extends RootPage { for (Metric metric : authorMetrics.values()) { chart.addValue(metric.name, metric.count); } - chart.setWidth(w); - chart.setHeight(h); + chart.setShowLegend(false); charts.addChart(chart); return charts; diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.html b/src/main/java/com/gitblit/wicket/pages/BasePage.html index a24f2362..0f96f343 100644 --- a/src/main/java/com/gitblit/wicket/pages/BasePage.html +++ b/src/main/java/com/gitblit/wicket/pages/BasePage.html @@ -2,7 +2,8 @@ + lang="en" + ng-app> diff --git a/src/main/java/com/gitblit/wicket/pages/DashboardPage.html b/src/main/java/com/gitblit/wicket/pages/DashboardPage.html new file mode 100644 index 00000000..d2516f0f --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/DashboardPage.html @@ -0,0 +1,99 @@ + + + + + +
+
[repositories message]
+ +
+
+
+ + + + + +
+
+
+
+
+
[active]
+
[starred]
+
[owned]
+
+ +
+
+ + +
+
({{starred.length}}) +
+ more +
+
+ +
+
+ +
+ !  + {{item.p}}{{item.n}} + {{item.t}} + + {{item.s | number}} + +
+ +
+
+ + +
+
({{owned.length}}) +
+ +
+
+ +
+
+ +
+ !  + {{item.p}}{{item.n}} + {{item.t}} + + {{item.s | number}} + +
+
+
+ + +
+
({{active.length}}) +
+ +
+
+ +
+ !  + {{item.p}}{{item.n}} + {{item.t}} + + {{item.s | number}} + +
+
+
+ +
+ + \ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/pages/DashboardPage.java b/src/main/java/com/gitblit/wicket/pages/DashboardPage.java new file mode 100644 index 00000000..6a4c5658 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/DashboardPage.java @@ -0,0 +1,418 @@ +/* + * 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 java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Serializable; +import java.text.DateFormat; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.wicket.Component; +import org.apache.wicket.PageParameters; +import org.apache.wicket.behavior.HeaderContributor; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.panel.Fragment; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Repository; + +import com.gitblit.GitBlit; +import com.gitblit.Keys; +import com.gitblit.models.DailyLogEntry; +import com.gitblit.models.Metric; +import com.gitblit.models.PushLogEntry; +import com.gitblit.models.RepositoryCommit; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.MarkdownUtils; +import com.gitblit.utils.PushLogUtils; +import com.gitblit.utils.StringUtils; +import com.gitblit.wicket.GitBlitWebApp; +import com.gitblit.wicket.GitBlitWebSession; +import com.gitblit.wicket.PageRegistration; +import com.gitblit.wicket.PageRegistration.DropDownMenuItem; +import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration; +import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.charting.GoogleChart; +import com.gitblit.wicket.charting.GoogleCharts; +import com.gitblit.wicket.charting.GooglePieChart; +import com.gitblit.wicket.ng.NgController; +import com.gitblit.wicket.panels.LinkPanel; +import com.gitblit.wicket.panels.PushesPanel; + +public class DashboardPage extends RootPage { + + public DashboardPage() { + super(); + setup(null); + } + + public DashboardPage(PageParameters params) { + super(params); + setup(params); + } + + @Override + protected boolean reusePageParameters() { + return true; + } + + private void setup(PageParameters params) { + setupPage("", ""); + // check to see if we should display a login message + boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true); + if (authenticateView && !GitBlitWebSession.get().isLoggedIn()) { + String messageSource = GitBlit.getString(Keys.web.loginMessage, "gitblit"); + String message = readMarkdown(messageSource, "login.mkd"); + Component repositoriesMessage = new Label("repositoriesMessage", message); + add(repositoriesMessage.setEscapeModelStrings(false)); + add(new Label("repositoriesPanel")); + return; + } + + // Load the markdown welcome message + String messageSource = GitBlit.getString(Keys.web.repositoriesMessage, "gitblit"); + String message = readMarkdown(messageSource, "welcome.mkd"); + Component repositoriesMessage = new Label("repositoriesMessage", message) + .setEscapeModelStrings(false).setVisible(message.length() > 0); + add(repositoriesMessage); + + UserModel user = GitBlitWebSession.get().getUser(); + + Comparator lastUpdateSort = new Comparator() { + @Override + public int compare(RepositoryModel o1, RepositoryModel o2) { + return o2.lastChange.compareTo(o1.lastChange); + } + }; + + Map reposMap = new HashMap(); + + // owned repositories + List owned = new ArrayList(); + if (user != null && !UserModel.ANONYMOUS.equals(user)) { + for (RepositoryModel model : GitBlit.self().getRepositoryModels(user)) { + reposMap.put(model.name, model); + if (model.isUsersPersonalRepository(user.username) || model.isOwner(user.username)) { + owned.add(model); + } + } + } + Collections.sort(owned, lastUpdateSort); + + // starred repositories + List starred = new ArrayList(); + if (user != null && !UserModel.ANONYMOUS.equals(user)) { + for (String name : user.getPreferences().getStarredRepositories()) { + if (!reposMap.containsKey(name)) { + RepositoryModel repo = GitBlit.self().getRepositoryModel(name); + reposMap.put(name, repo); + } + starred.add(reposMap.get(name)); + } + } + Collections.sort(starred, lastUpdateSort); + + // parameters + int daysBack = params == null ? 0 : WicketUtils.getDaysBack(params); + if (daysBack < 1) { + daysBack = 14; + } + Calendar c = Calendar.getInstance(); + c.add(Calendar.DATE, -1*daysBack); + Date minimumDate = c.getTime(); + + // active repositories (displayed for anonymous users) + List active = new ArrayList(); + if (user == null || UserModel.ANONYMOUS.equals(user)) { + List list = GitBlit.self().getRepositoryModels(UserModel.ANONYMOUS); + for (RepositoryModel model : list) { + if (model.lastChange.after(minimumDate)) { + active.add(model); + reposMap.put(model.name, model); + } + } + Collections.sort(active, lastUpdateSort); + } + + // show pushlog feed + List pushes = new ArrayList(); + for (RepositoryModel model : reposMap.values()) { + Repository repository = GitBlit.self().getRepository(model.name); + List entries = PushLogUtils.getDailyLogByRef(model.name, repository, minimumDate); + pushes.addAll(entries); + repository.close(); + } + + if (pushes.size() == 0) { + if (reposMap.size() == 0) { + add(new LinkPanel("pushes", null, "find some repositories", RepositoriesPage.class)); + } else { + add(new Label("pushes", "all is quiet")); + } + } else { + Collections.sort(pushes); + add(new PushesPanel("pushes", pushes)); + } + + // add the nifty charts + if (!ArrayUtils.isEmpty(pushes)) { + GoogleCharts charts = createCharts(pushes); + add(new HeaderContributor(charts)); + } + + // active repository list + if (ArrayUtils.isEmpty(active)) { + add(new Label("active").setVisible(false)); + } else { + Fragment activeView = createNgList("active", "activeListFragment", "activeCtrl", active); + add(activeView); + } + + // starred repository list + if (ArrayUtils.isEmpty(starred)) { + add(new Label("starred").setVisible(false)); + } else { + Fragment starredView = createNgList("starred", "starredListFragment", "starredCtrl", starred); + add(starredView); + } + + // owned repository list + if (ArrayUtils.isEmpty(owned)) { + add(new Label("owned").setVisible(false)); + } else { + Fragment ownedView = createNgList("owned", "ownedListFragment", "ownedCtrl", owned); + if (user.canCreate) { + // create button + ownedView.add(new LinkPanel("create", "btn btn-mini", getString("gb.newRepository"), EditRepositoryPage.class)); + } else { + // no button + ownedView.add(new Label("create").setVisible(false)); + } + add(ownedView); + } + } + + protected Fragment createNgList(String wicketId, String fragmentId, String ngController, List repositories) { + String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy"); + final DateFormat df = new SimpleDateFormat(format); + df.setTimeZone(getTimeZone()); + + Fragment fragment = new Fragment(wicketId, fragmentId, this); + + List list = new ArrayList(); + for (RepositoryModel repo : repositories) { + String name = StringUtils.stripDotGit(repo.name); + String path = ""; + if (name.indexOf('/') > -1) { + path = name.substring(0, name.lastIndexOf('/') + 1); + name = name.substring(name.lastIndexOf('/') + 1); + } + + RepoListItem item = new RepoListItem(); + item.n = name; + item.p = path; + item.r = repo.name; + item.s = GitBlit.self().getStarCount(repo); + item.t = getTimeUtils().timeAgo(repo.lastChange); + item.d = df.format(repo.lastChange); + item.c = StringUtils.getColor(StringUtils.stripDotGit(repo.name)); + item.wc = repo.isBare ? 0 : 1; + list.add(item); + } + + // inject an AngularJS controller with static data + NgController ctrl = new NgController(ngController); + ctrl.addVariable(wicketId, list); + add(new HeaderContributor(ctrl)); + + return fragment; + } + + @Override + protected void addDropDownMenus(List pages) { + PageParameters params = getPageParameters(); + + DropDownMenuRegistration menu = new DropDownMenuRegistration("gb.filters", + GitBlitWebApp.HOME_PAGE_CLASS); + // preserve time filter option on repository choices + menu.menuItems.addAll(getRepositoryFilterItems(params)); + + // preserve repository filter option on time choices + menu.menuItems.addAll(getTimeFilterItems(params)); + + if (menu.menuItems.size() > 0) { + // Reset Filter + menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null)); + } + + pages.add(menu); + } + + private String readMarkdown(String messageSource, String resource) { + String message = ""; + if (messageSource.equalsIgnoreCase("gitblit")) { + // Read default message + message = readDefaultMarkdown(resource); + } else { + // Read user-supplied message + if (!StringUtils.isEmpty(messageSource)) { + File file = GitBlit.getFileOrFolder(messageSource); + if (file.exists()) { + try { + FileInputStream fis = new FileInputStream(file); + InputStreamReader reader = new InputStreamReader(fis, + Constants.CHARACTER_ENCODING); + message = MarkdownUtils.transformMarkdown(reader); + reader.close(); + } catch (Throwable t) { + message = getString("gb.failedToRead") + " " + file; + warn(message, t); + } + } else { + message = messageSource + " " + getString("gb.isNotValidFile"); + } + } + } + return message; + } + + private String readDefaultMarkdown(String file) { + String base = file.substring(0, file.lastIndexOf('.')); + String ext = file.substring(file.lastIndexOf('.')); + String lc = getLanguageCode(); + String cc = getCountryCode(); + + // try to read file_en-us.ext, file_en.ext, file.ext + List files = new ArrayList(); + if (!StringUtils.isEmpty(lc)) { + if (!StringUtils.isEmpty(cc)) { + files.add(base + "_" + lc + "-" + cc + ext); + files.add(base + "_" + lc + "_" + cc + ext); + } + files.add(base + "_" + lc + ext); + } + files.add(file); + + for (String name : files) { + String message; + InputStreamReader reader = null; + try { + InputStream is = getClass().getResourceAsStream("/" + name); + if (is == null) { + continue; + } + reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING); + message = MarkdownUtils.transformMarkdown(reader); + reader.close(); + return message; + } catch (Throwable t) { + message = MessageFormat.format(getString("gb.failedToReadMessage"), file); + error(message, t, false); + return message; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (Exception e) { + } + } + } + } + return MessageFormat.format(getString("gb.failedToReadMessage"), file); + } + + /** + * Creates the daily activity line chart, the active repositories pie chart, + * and the active authors pie chart + * + * @param recentPushes + * @return + */ + private GoogleCharts createCharts(List recentPushes) { + // activity metrics + Map repositoryMetrics = new HashMap(); + Map authorMetrics = new HashMap(); + + // aggregate repository and author metrics + for (PushLogEntry push : recentPushes) { + + // aggregate repository metrics + String repository = StringUtils.stripDotGit(push.repository); + if (!repositoryMetrics.containsKey(repository)) { + repositoryMetrics.put(repository, new Metric(repository)); + } + repositoryMetrics.get(repository).count += 1; + + for (RepositoryCommit commit : push.getCommits()) { + String author = commit.getAuthorIdent().getName(); + if (!authorMetrics.containsKey(author)) { + authorMetrics.put(author, new Metric(author)); + } + authorMetrics.get(author).count += 1; + } + } + + // build google charts + GoogleCharts charts = new GoogleCharts(); + + // active repositories pie chart + GoogleChart chart = new GooglePieChart("chartRepositories", getString("gb.activeRepositories"), + getString("gb.repository"), getString("gb.commits")); + for (Metric metric : repositoryMetrics.values()) { + chart.addValue(metric.name, metric.count); + } + chart.setShowLegend(false); + charts.addChart(chart); + + // active authors pie chart + chart = new GooglePieChart("chartAuthors", getString("gb.activeAuthors"), + getString("gb.author"), getString("gb.commits")); + for (Metric metric : authorMetrics.values()) { + chart.addValue(metric.name, metric.count); + } + chart.setShowLegend(false); + charts.addChart(chart); + + return charts; + } + + class RepoListItem implements Serializable { + + private static final long serialVersionUID = 1L; + + String r; // repository + String n; // name + String p; // project/path + String t; // time ago + String d; // last updated + long s; // stars + String c; // html color + int wc; // working copy, 1 = true + } +} diff --git a/src/main/java/com/gitblit/wicket/pages/HomePage.html b/src/main/java/com/gitblit/wicket/pages/HomePage.html deleted file mode 100644 index 3730d786..00000000 --- a/src/main/java/com/gitblit/wicket/pages/HomePage.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - -
-
[repositories message]
- - -
-
- - \ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/pages/HomePage.java b/src/main/java/com/gitblit/wicket/pages/HomePage.java deleted file mode 100644 index 9dfd1ae4..00000000 --- a/src/main/java/com/gitblit/wicket/pages/HomePage.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2011 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.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.List; - -import org.apache.wicket.Component; -import org.apache.wicket.PageParameters; -import org.apache.wicket.markup.html.basic.Label; -import org.eclipse.jgit.lib.Constants; - -import com.gitblit.GitBlit; -import com.gitblit.Keys; -import com.gitblit.utils.MarkdownUtils; -import com.gitblit.utils.StringUtils; -import com.gitblit.wicket.GitBlitWebApp; -import com.gitblit.wicket.GitBlitWebSession; -import com.gitblit.wicket.PageRegistration; -import com.gitblit.wicket.PageRegistration.DropDownMenuItem; -import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration; - -public class HomePage extends RootPage { - - public HomePage() { - super(); - setup(null); - } - - public HomePage(PageParameters params) { - super(params); - setup(params); - } - - @Override - protected boolean reusePageParameters() { - return true; - } - - private void setup(PageParameters params) { - setupPage("", ""); - // check to see if we should display a login message - boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true); - if (authenticateView && !GitBlitWebSession.get().isLoggedIn()) { - String messageSource = GitBlit.getString(Keys.web.loginMessage, "gitblit"); - String message = readMarkdown(messageSource, "login.mkd"); - Component repositoriesMessage = new Label("repositoriesMessage", message); - add(repositoriesMessage.setEscapeModelStrings(false)); - add(new Label("repositoriesPanel")); - return; - } - - // Load the markdown welcome message - String messageSource = GitBlit.getString(Keys.web.repositoriesMessage, "gitblit"); - String message = readMarkdown(messageSource, "welcome.mkd"); - Component repositoriesMessage = new Label("repositoriesMessage", message) - .setEscapeModelStrings(false).setVisible(message.length() > 0); - add(repositoriesMessage); - -// List repositories = getRepositories(params); -// -// RepositoriesPanel repositoriesPanel = new RepositoriesPanel("repositoriesPanel", showAdmin, -// true, repositories, true, getAccessRestrictions()); -// // push the panel down if we are hiding the admin controls and the -// // welcome message -// if (!showAdmin && !repositoriesMessage.isVisible()) { -// WicketUtils.setCssStyle(repositoriesPanel, "padding-top:5px;"); -// } -// add(repositoriesPanel); - } - - @Override - protected void addDropDownMenus(List pages) { - PageParameters params = getPageParameters(); - - DropDownMenuRegistration menu = new DropDownMenuRegistration("gb.filters", - GitBlitWebApp.HOME_PAGE_CLASS); - // preserve time filter option on repository choices - menu.menuItems.addAll(getRepositoryFilterItems(params)); - - // preserve repository filter option on time choices - menu.menuItems.addAll(getTimeFilterItems(params)); - - if (menu.menuItems.size() > 0) { - // Reset Filter - menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null)); - } - - pages.add(menu); - } - - private String readMarkdown(String messageSource, String resource) { - String message = ""; - if (messageSource.equalsIgnoreCase("gitblit")) { - // Read default message - message = readDefaultMarkdown(resource); - } else { - // Read user-supplied message - if (!StringUtils.isEmpty(messageSource)) { - File file = GitBlit.getFileOrFolder(messageSource); - if (file.exists()) { - try { - FileInputStream fis = new FileInputStream(file); - InputStreamReader reader = new InputStreamReader(fis, - Constants.CHARACTER_ENCODING); - message = MarkdownUtils.transformMarkdown(reader); - reader.close(); - } catch (Throwable t) { - message = getString("gb.failedToRead") + " " + file; - warn(message, t); - } - } else { - message = messageSource + " " + getString("gb.isNotValidFile"); - } - } - } - return message; - } - - private String readDefaultMarkdown(String file) { - String base = file.substring(0, file.lastIndexOf('.')); - String ext = file.substring(file.lastIndexOf('.')); - String lc = getLanguageCode(); - String cc = getCountryCode(); - - // try to read file_en-us.ext, file_en.ext, file.ext - List files = new ArrayList(); - if (!StringUtils.isEmpty(lc)) { - if (!StringUtils.isEmpty(cc)) { - files.add(base + "_" + lc + "-" + cc + ext); - files.add(base + "_" + lc + "_" + cc + ext); - } - files.add(base + "_" + lc + ext); - } - files.add(file); - - for (String name : files) { - String message; - InputStreamReader reader = null; - try { - InputStream is = getClass().getResourceAsStream("/" + name); - if (is == null) { - continue; - } - reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING); - message = MarkdownUtils.transformMarkdown(reader); - reader.close(); - return message; - } catch (Throwable t) { - message = MessageFormat.format(getString("gb.failedToReadMessage"), file); - error(message, t, false); - return message; - } finally { - if (reader != null) { - try { - reader.close(); - } catch (Exception e) { - } - } - } - } - return MessageFormat.format(getString("gb.failedToReadMessage"), file); - } -} diff --git a/src/main/java/com/gitblit/wicket/pages/OverviewPage.java b/src/main/java/com/gitblit/wicket/pages/OverviewPage.java index aea07bfe..6fd38915 100644 --- a/src/main/java/com/gitblit/wicket/pages/OverviewPage.java +++ b/src/main/java/com/gitblit/wicket/pages/OverviewPage.java @@ -110,7 +110,7 @@ public class OverviewPage extends RepositoryPage { add(new RepositoryUrlPanel("repositoryUrlPanel", false, user, model)); int pushCount = GitBlit.getInteger(Keys.web.overviewPushCount, 5); - PushesPanel pushes = new PushesPanel("pushesPanel", getRepositoryModel(), r, pushCount, 0); + PushesPanel pushes = new PushesPanel("pushesPanel", getRepositoryModel(), r, pushCount, 0, false); add(pushes); add(new TagsPanel("tagsPanel", repositoryName, r, numberRefs).hideIfEmpty()); add(new BranchesPanel("branchesPanel", getRepositoryModel(), r, numberRefs, false).hideIfEmpty()); diff --git a/src/main/java/com/gitblit/wicket/pages/PushesPage.java b/src/main/java/com/gitblit/wicket/pages/PushesPage.java index a0e7c973..866964ac 100644 --- a/src/main/java/com/gitblit/wicket/pages/PushesPage.java +++ b/src/main/java/com/gitblit/wicket/pages/PushesPage.java @@ -33,7 +33,7 @@ public class PushesPage extends RepositoryPage { int nextPage = pageNumber + 1; PushesPanel pushesPanel = new PushesPanel("pushesPanel", getRepositoryModel(), getRepository(), -1, - pageNumber - 1); + pageNumber - 1, false); boolean hasMore = pushesPanel.hasMore(); add(pushesPanel); diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.java b/src/main/java/com/gitblit/wicket/pages/RootPage.java index 30251516..bbe20f5b 100644 --- a/src/main/java/com/gitblit/wicket/pages/RootPage.java +++ b/src/main/java/com/gitblit/wicket/pages/RootPage.java @@ -118,8 +118,8 @@ public abstract class RootPage extends BasePage { // navigation links List pages = new ArrayList(); if (!authenticateView || (authenticateView && GitBlitWebSession.get().isLoggedIn())) { -// pages.add(new PageRegistration("gb.home", HomePage.class, -// getRootPageParameters())); + pages.add(new PageRegistration("gb.dashboard", DashboardPage.class, + getRootPageParameters())); pages.add(new PageRegistration("gb.repositories", RepositoriesPage.class, getRootPageParameters())); pages.add(new PageRegistration("gb.activity", ActivityPage.class, getRootPageParameters())); diff --git a/src/main/java/com/gitblit/wicket/panels/ActivityPanel.html b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.html index b818e94a..9dff0a7c 100644 --- a/src/main/java/com/gitblit/wicket/panels/ActivityPanel.html +++ b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.html @@ -13,7 +13,7 @@ [time of day] - [repository link] + [repository link] diff --git a/src/main/java/com/gitblit/wicket/panels/PushesPanel.html b/src/main/java/com/gitblit/wicket/panels/PushesPanel.html index cad59323..9fb5aed0 100644 --- a/src/main/java/com/gitblit/wicket/panels/PushesPanel.html +++ b/src/main/java/com/gitblit/wicket/panels/PushesPanel.html @@ -6,21 +6,21 @@ -
+
- +
[rewind]
-
[pusher]
+
[pusher]
- + - + diff --git a/src/main/java/com/gitblit/wicket/panels/PushesPanel.java b/src/main/java/com/gitblit/wicket/panels/PushesPanel.java index 29161ba8..423c44b4 100644 --- a/src/main/java/com/gitblit/wicket/panels/PushesPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/PushesPanel.java @@ -29,6 +29,7 @@ import org.eclipse.jgit.lib.Repository; import com.gitblit.Constants; import com.gitblit.GitBlit; import com.gitblit.Keys; +import com.gitblit.models.DailyLogEntry; import com.gitblit.models.PushLogEntry; import com.gitblit.models.RepositoryCommit; import com.gitblit.models.RepositoryModel; @@ -51,7 +52,7 @@ public class PushesPanel extends BasePanel { private boolean hasMore; - public PushesPanel(String wicketId, final RepositoryModel model, Repository r, int limit, int pageOffset) { + public PushesPanel(String wicketId, final RepositoryModel model, Repository r, int limit, int pageOffset, boolean showRepo) { super(wicketId); boolean pageResults = limit <= 0; int pushesPerPage = GitBlit.getInteger(Keys.web.pushesPerPage, 10); @@ -71,7 +72,7 @@ public class PushesPanel extends BasePanel { hasMore = pushes.size() >= pushesPerPage; hasPushes = pushes.size() > 0; - setup(pushes); + setup(pushes, showRepo); // determine to show pager, more, or neither if (limit <= 0) { @@ -99,11 +100,11 @@ public class PushesPanel extends BasePanel { public PushesPanel(String wicketId, List pushes) { super(wicketId); hasPushes = pushes.size() > 0; - setup(pushes); + setup(pushes, true); add(new Label("morePushes").setVisible(false)); } - protected void setup(List pushes) { + protected void setup(List pushes, final boolean showRepo) { final int hashLen = GitBlit.getInteger(Keys.web.shortCommitIdLength, 6); ListDataProvider dp = new ListDataProvider(pushes); @@ -121,36 +122,58 @@ public class PushesPanel extends BasePanel { shortRefName = shortRefName.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length()); isTag = true; } + boolean isDigest = push instanceof DailyLogEntry; pushItem.add(WicketUtils.createDateLabel("whenPushed", push.date, getTimeZone(), getTimeUtils())); Label pushIcon = new Label("pushIcon"); + if (showRepo) { + // if we are showing the repo, we are showing multiple + // repos. use the repository hash color to differentiate + // the icon. + String color = StringUtils.getColor(StringUtils.stripDotGit(push.repository)); + WicketUtils.setCssStyle(pushIcon, "color: " + color); + } if (isTag) { WicketUtils.setCssClass(pushIcon, "iconic-tag"); - } else { + } else if (isDigest) { WicketUtils.setCssClass(pushIcon, "iconic-loop"); - } - pushItem.add(pushIcon); - if (push.user.username.equals(push.user.emailAddress) && push.user.emailAddress.indexOf('@') > -1) { - // username is an email address - 1.2.1 push log bug - pushItem.add(new Label("whoPushed", push.user.getDisplayName())); } else { - // link to user acount page - pushItem.add(new LinkPanel("whoPushed", null, push.user.getDisplayName(), - UserPage.class, WicketUtils.newUsernameParameter(push.user.username))); + WicketUtils.setCssClass(pushIcon, "iconic-upload"); } + pushItem.add(pushIcon); + + if (isDigest && !isTag) { + pushItem.add(new Label("whoPushed").setVisible(false)); + } else { + if (push.user.username.equals(push.user.emailAddress) && push.user.emailAddress.indexOf('@') > -1) { + // username is an email address - 1.2.1 push log bug + pushItem.add(new Label("whoPushed", push.user.getDisplayName())); + } else { + // link to user account page + pushItem.add(new LinkPanel("whoPushed", null, push.user.getDisplayName(), + UserPage.class, WicketUtils.newUsernameParameter(push.user.username))); + } + } - String preposition = "gb.at"; + String preposition = "gb.of"; boolean isDelete = false; boolean isRewind = false; String what; + String by = null; switch(push.getChangeType(fullRefName)) { case CREATE: if (isTag) { - what = getString("gb.pushedNewTag"); + if (isDigest) { + what = getString("gb.createdNewTag"); + preposition = "gb.in"; + } else { + what = getString("gb.pushedNewTag"); + preposition = "gb.to"; + } } else { what = getString("gb.pushedNewBranch"); + preposition = "gb.to"; } - preposition = "gb.to"; break; case DELETE: isDelete = true; @@ -164,10 +187,21 @@ public class PushesPanel extends BasePanel { case UPDATE_NONFASTFORWARD: isRewind = true; default: - what = MessageFormat.format(push.getCommitCount() > 1 ? getString("gb.pushedNCommitsTo") : getString("gb.pushedOneCommitTo") , push.getCommitCount()); + if (isDigest) { + what = MessageFormat.format(push.getCommitCount() > 1 ? getString("gb.commitsTo") : getString("gb.oneCommitTo"), push.getCommitCount()); + } else { + what = MessageFormat.format(push.getCommitCount() > 1 ? getString("gb.pushedNCommitsTo") : getString("gb.pushedOneCommitTo") , push.getCommitCount()); + } + + if (push.getAuthorCount() == 1) { + by = MessageFormat.format(getString("gb.byOneAuthor"), push.getAuthorIdent().getName()); + } else { + by = MessageFormat.format(getString("gb.byNAuthors"), push.getAuthorCount()); + } break; } pushItem.add(new Label("whatPushed", what)); + pushItem.add(new Label("byAuthors", by).setVisible(!StringUtils.isEmpty(by))); pushItem.add(new Label("refRewind", getString("gb.rewind")).setVisible(isRewind)); @@ -184,13 +218,20 @@ public class PushesPanel extends BasePanel { TreePage.class, WicketUtils.newObjectParameter(push.repository, fullRefName))); } - // to/from/etc - pushItem.add(new Label("repoPreposition", getString(preposition))); - - String repoName = StringUtils.stripDotGit(push.repository); - pushItem.add(new LinkPanel("repoPushed", null, repoName, - SummaryPage.class, WicketUtils.newRepositoryParameter(push.repository))); + if (showRepo) { + // to/from/etc + pushItem.add(new Label("repoPreposition", getString(preposition))); + String repoName = StringUtils.stripDotGit(push.repository); + pushItem.add(new LinkPanel("repoPushed", null, repoName, + SummaryPage.class, WicketUtils.newRepositoryParameter(push.repository))); + } else { + // do not display repository name if we are viewing the push + // log of a repository. + pushItem.add(new Label("repoPreposition").setVisible(false)); + pushItem.add(new Label("repoPushed").setVisible(false)); + } + int maxCommitCount = 5; List commits = push.getCommits(); if (commits.size() > maxCommitCount) { @@ -213,6 +254,8 @@ public class PushesPanel extends BasePanel { pushItem.add(new LinkPanel("compareLink", null, compareLinkText, ComparePage.class, WicketUtils.newRangeParameter(push.repository, startRangeId, endRangeId))); } + final boolean showSwatch = showRepo && GitBlit.getBoolean(Keys.web.repositoryListSwatches, true); + ListDataProvider cdp = new ListDataProvider(commits); DataView commitsView = new DataView("commit", cdp) { private static final long serialVersionUID = 1L; @@ -254,15 +297,20 @@ public class PushesPanel extends BasePanel { WicketUtils.setCssClass(commitHash, "shortsha1"); WicketUtils.setHtmlTooltip(commitHash, commit.getName()); commitItem.add(commitHash); + + if (showSwatch) { + // set repository color + String color = StringUtils.getColor(StringUtils.stripDotGit(push.repository)); + WicketUtils.setCssStyle(commitItem, MessageFormat.format("border-left: 2px solid {0};", color)); + } } }; - + pushItem.add(commitsView); } }; + add(pushView); - - } public boolean hasMore() { diff --git a/src/main/resources/bootstrap/js/angular.js b/src/main/resources/bootstrap/js/angular.js new file mode 100644 index 00000000..472d7ff7 --- /dev/null +++ b/src/main/resources/bootstrap/js/angular.js @@ -0,0 +1,163 @@ +/* + AngularJS v1.0.7 + (c) 2010-2012 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(P,T,q){'use strict';function m(b,a,c){var d;if(b)if(H(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==m)b.forEach(a,c);else if(!b||typeof b.length!=="number"?0:typeof b.hasOwnProperty!="function"&&typeof b.constructor!="function"||b instanceof K||ca&&b instanceof ca||wa.call(b)!=="[object Object]"||typeof b.callee==="function")for(d=0;d=0&&b.splice(c,1);return a}function U(b,a){if(oa(b)||b&&b.$evalAsync&&b.$watch)throw Error("Can't copy Window or Scope");if(a){if(b===a)throw Error("Can't copy equivalent objects or arrays");if(E(b))for(var c=a.length=0;c2?ha.call(arguments,2):[];return H(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ha.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function ic(b,a){var c=a;/^\$+/.test(b)?c=q:oa(a)?c="$WINDOW":a&&T===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function da(b,a){return JSON.stringify(b, +ic,a?" ":null)}function pb(b){return B(b)?JSON.parse(b):b}function Ua(b){b&&b.length!==0?(b=z(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;return b}function pa(b){b=u(b).clone();try{b.html("")}catch(a){}var c=u("
").append(b).html();try{return b[0].nodeType===3?z(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+z(b)})}catch(d){return z(c)}}function Va(b){var a={},c,d;m((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]), +a[d]=y(c[1])?decodeURIComponent(c[1]):!0)});return a}function qb(b){var a=[];m(b,function(b,d){a.push(Wa(d,!0)+(b===!0?"":"="+Wa(b,!0)))});return a.length?a.join("&"):""}function Xa(b){return Wa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function jc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,h=["ng:app","ng-app","x-ng-app", +"data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;m(h,function(a){h[a]=!0;c(T.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(m(b.querySelectorAll("."+a),c),m(b.querySelectorAll("."+a+"\\:"),c),m(b.querySelectorAll("["+a+"]"),c))});m(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):m(a.attributes,function(b){if(!e&&h[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}function rb(b,a){var c=function(){b=u(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement", +b)}]);a.unshift("ng");var c=sb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(P&&!d.test(P.name))return c();P.name=P.name.replace(d,"");Ya.resumeBootstrap=function(b){m(b,function(b){a.push(b)});c()}}function Za(b,a){a=a||"_";return b.replace(kc,function(b,d){return(d?a:"")+b.toLowerCase()})}function $a(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"required")); +return b}function qa(b,a,c){c&&E(b)&&(b=b[b.length-1]);$a(H(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function lc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,d,e){return function(){b[e||"push"]([c,d,arguments]);return k}}if(!e)throw Error("No module: "+d);var b=[],c=[],j=a("$injector", +"invoke"),k={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:j,run:function(a){c.push(a);return this}};g&&j(g);return k})}})}function tb(b){return b.replace(mc,function(a,b,d,e){return e?d.toUpperCase(): +d}).replace(nc,"Moz$1")}function ab(b,a){function c(){var e;for(var b=[this],c=a,h,f,i,j,k,l;b.length;){h=b.shift();f=0;for(i=h.length;f 
"+b;a.removeChild(a.firstChild);bb(this,a.childNodes);this.remove()}else bb(this,b)}function cb(b){return b.cloneNode(!0)}function ra(b){ub(b);for(var a=0,b=b.childNodes||[];a-1}function xb(b,a){a&&m(a.split(" "),function(a){b.className=Q((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+Q(a)+" "," "))})} +function yb(b,a){a&&m(a.split(" "),function(a){if(!Ca(b,a))b.className=Q(b.className+" "+Q(a))})}function bb(b,a){if(a)for(var a=!a.nodeName&&y(a.length)&&!oa(a)?a:[a],c=0;c4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!")}else{if(i.cookie!==$){$=i.cookie;d=$.split("; ");r={};for(f=0;f0&&(a=unescape(e.substring(0,j)),r[a]===q&&(r[a]=unescape(e.substring(j+1))))}return r}};f.defer=function(a,b){var c; +p++;c=l(function(){delete o[c];e(a)},b||0);o[c]=!0;return c};f.defer.cancel=function(a){return o[a]?(delete o[a],n(a),e(C),!0):!1}}function wc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new vc(b,d,a,c)}]}function xc(){this.$get=function(){function b(b,d){function e(a){if(a!=l){if(n){if(n==a)n=a.n}else n=a;g(a.n,a.p);g(a,l);l=a;l.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error("cacheId "+b+" taken");var h=0,f=v({},d,{id:b}),i={},j=d&& +d.capacity||Number.MAX_VALUE,k={},l=null,n=null;return a[b]={put:function(a,b){var c=k[a]||(k[a]={key:a});e(c);w(b)||(a in i||h++,i[a]=b,h>j&&this.remove(n.key))},get:function(a){var b=k[a];if(b)return e(b),i[a]},remove:function(a){var b=k[a];if(b){if(b==l)l=b.p;if(b==n)n=b.n;g(b.n,b.p);delete k[a];delete i[a];h--}},removeAll:function(){i={};h=0;k={};l=n=null},destroy:function(){k=f=i=null;delete a[b]},info:function(){return v({},f,{size:h})}}}var a={};b.info=function(){var b={};m(a,function(a,e){b[e]= +a.info()});return b};b.get=function(b){return a[b]};return b}}function yc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Db(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ",h=/^\s*(https?|ftp|mailto|file):/;this.directive=function i(d,e){B(d)?($a(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];m(a[d], +function(a){try{var g=b.invoke(a);if(H(g))g={compile:I(g)};else if(!g.compile&&g.link)g.compile=I(g.link);g.priority=g.priority||0;g.name=g.name||d;g.require=g.require||g.controller&&g.name;g.restrict=g.restrict||"A";e.push(g)}catch(h){c(h)}});return e}])),a[d].push(e)):m(d,nb(i));return this};this.urlSanitizationWhitelist=function(a){return y(a)?(h=a,this):h};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document",function(b, +j,k,l,n,o,p,s,t){function x(a,b,c){a instanceof u||(a=u(a));m(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\S+/)&&(a[c]=u(b).wrap("").parent()[0])});var d=A(a,b,a,c);return function(b,c){$a(b,"scope");for(var e=c?ua.clone.call(a):a,j=0,g=e.length;jr.priority)break;if(Y=r.scope)ta("isolated scope",J,r,D),L(Y)&&(M(D,"ng-isolate-scope"),J=r),M(D,"ng-scope"),s=s||r;F=r.name;if(Y=r.controller)y=y||{},ta("'"+F+"' controller",y[F],r,D),y[F]=r;if(Y=r.transclude)ta("transclusion",ja,r,D),ja=r,l=r.priority,Y=="element"?(W=u(b),D=c.$$element=u(T.createComment(" "+ +F+": "+c[F]+" ")),b=D[0],C(e,u(W[0]),b),V=x(W,d,l)):(W=u(cb(b)).contents(),D.html(""),V=x(W,d));if(Y=r.template)if(ta("template",A,r,D),A=r,Y=Fb(Y),r.replace){W=u("
"+Q(Y)+"
").contents();b=W[0];if(W.length!=1||b.nodeType!==1)throw Error(g+Y);C(e,D,b);F={$attr:{}};a=a.concat(N(b,a.splice(v+1,a.length-(v+1)),F));$(c,F);z=a.length}else D.html(Y);if(r.templateUrl)ta("template",A,r,D),A=r,i=R(a.splice(v,a.length-v),i,D,c,e,r.replace,V),z=a.length;else if(r.compile)try{w=r.compile(D,c,V),H(w)? +j(null,w):w&&j(w.pre,w.post)}catch(G){k(G,pa(D))}if(r.terminal)i.terminal=!0,l=Math.max(l,r.priority)}i.scope=s&&s.scope;i.transclude=ja&&V;return i}function r(d,e,g,j){var h=!1;if(a.hasOwnProperty(e))for(var o,e=b.get(e+c),l=0,p=e.length;lo.priority)&&o.restrict.indexOf(g)!=-1)d.push(o),h=!0}catch(n){k(n)}return h}function $(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b, +function(b,g){g=="class"?(M(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):g=="style"?e.attr("style",e.attr("style")+";"+b):g.charAt(0)!="$"&&!a.hasOwnProperty(g)&&(a[g]=b,d[g]=c[g])})}function R(a,b,c,d,e,j,h){var i=[],k,o,p=c[0],t=a.shift(),s=v({},t,{controller:null,templateUrl:null,transclude:null,scope:null});c.html("");l.get(t.templateUrl,{cache:n}).success(function(l){var n,t,l=Fb(l);if(j){t=u("
"+Q(l)+"
").contents();n=t[0];if(t.length!=1||n.nodeType!==1)throw Error(g+l);l={$attr:{}}; +C(e,c,n);N(n,a,l);$(d,l)}else n=p,c.html(l);a.unshift(s);k=J(a,n,d,h);for(o=A(c[0].childNodes,h);i.length;){var r=i.pop(),l=i.pop();t=i.pop();var ia=i.pop(),D=n;t!==p&&(D=cb(n),C(l,u(t),D));k(function(){b(o,ia,D,e,r)},ia,D,e,r)}i=null}).error(function(a,b,c,d){throw Error("Failed to load template: "+d.url);});return function(a,c,d,e,g){i?(i.push(c),i.push(d),i.push(e),i.push(g)):k(function(){b(o,c,d,e,g)},c,d,e,g)}}function F(a,b){return b.priority-a.priority}function ta(a,b,c,d){if(b)throw Error("Multiple directives ["+ +b.name+", "+c.name+"] asking for "+a+" on: "+pa(d));}function y(a,b){var c=j(b,!0);c&&a.push({priority:0,compile:I(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);M(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function V(a,b,c,d){var e=j(c,!0);e&&b.push({priority:100,compile:I(function(a,b,c){b=c.$$observers||(c.$$observers={});d==="class"&&(e=j(c[d],!0));c[d]=q;(b[d]||(b[d]=[])).$$inter=!0;(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e, +function(a){c.$set(d,a)})})})}function C(a,b,c){var d=b[0],e=d.parentNode,g,j;if(a){g=0;for(j=a.length;g +0){var e=R[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b,c,d,f){return(b=h(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),R.shift(),b):!1}function i(a){f(a)||e("is unexpected, expecting ["+a+"]",h())}function j(a,b){return function(c,d){return a(c,d,b)}}function k(a,b,c){return function(d,e){return b(d,e,a,c)}}function l(){for(var a=[];;)if(R.length>0&&!h("}",")",";","]")&&a.push(w()),!f(";"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e","<=",">="))a=k(a,b.fn,t());return a}function x(){for(var a=m(),b;b=f("*","/","%");)a=k(a,b.fn,m());return a}function m(){var a;return f("+")?A():(a=f("-"))?k(r,a.fn,m()):(a=f("!"))?j(a.fn,m()):A()}function A(){var a;if(f("("))a=w(),i(")");else if(f("["))a=N();else if(f("{"))a=J();else{var b=f();(a=b.fn)||e("not a primary expression",b)}for(var c;b=f("(","[",".");)b.text==="("?(a=y(a,c),c=null):b.text==="["?(c=a,a=V(a)):b.text==="."?(c=a,a=u(a)):e("IMPOSSIBLE");return a}function N(){var a= +[];if(g().text!="]"){do a.push(F());while(f(","))}i("]");return function(b,c){for(var d=[],e=0;e1;d++){var e=a.shift(),g=b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]= +c}function gb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,h=0;h7),hasEvent:function(c){if(c=="input"&&Z==9)return!1;if(w(a[c])){var e=b.document.createElement("div");a[c]="on"+c in e}return a[c]},csp:!1}}]}function Vc(){this.$get=I(P)}function Ob(b){var a={},c,d,e;if(!b)return a;m(b.split("\n"),function(b){e=b.indexOf(":");c=z(Q(b.substr(0, +e)));d=Q(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Pb(b){var a=L(b)?b:q;return function(c){a||(a=Ob(b));return c?a[z(c)]||null:a}}function Qb(b,a,c){if(H(c))return c(b,a);m(c,function(c){b=c(b,a)});return b}function Wc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){B(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=pb(d,!0)));return d}],transformRequest:[function(a){return L(a)&&wa.apply(a)!=="[object File]"?da(a):a}], +headers:{common:{Accept:"application/json, text/plain, */*","X-Requested-With":"XMLHttpRequest"},post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}}},e=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,i,j,k){function l(a){function c(a){var b=v({},a,{data:Qb(a.data,a.headers,f)});return 200<=a.status&&a.status<300?b:j.reject(b)}a.method=la(a.method);var e=a.transformRequest|| +d.transformRequest,f=a.transformResponse||d.transformResponse,g=d.headers,g=v({"X-XSRF-TOKEN":b.cookies()["XSRF-TOKEN"]},g.common,g[z(a.method)],a.headers),e=Qb(a.data,Pb(g),e),i;w(a.data)&&delete g["Content-Type"];i=n(a,e,g);i=i.then(c,c);m(s,function(a){i=a(i)});i.success=function(b){i.then(function(c){b(c.data,c.status,c.headers,a)});return i};i.error=function(b){i.then(null,function(c){b(c.data,c.status,c.headers,a)});return i};return i}function n(b,c,d){function e(a,b,c){m&&(200<=a&&a<300?m.put(q, +[a,b,Ob(c)]):m.remove(q));f(b,a,c);i.$apply()}function f(a,c,d){c=Math.max(c,0);(200<=c&&c<300?k.resolve:k.reject)({data:a,status:c,headers:Pb(d),config:b})}function h(){var a=za(l.pendingRequests,b);a!==-1&&l.pendingRequests.splice(a,1)}var k=j.defer(),n=k.promise,m,s,q=o(b.url,b.params);l.pendingRequests.push(b);n.then(h,h);b.cache&&b.method=="GET"&&(m=L(b.cache)?b.cache:p);if(m)if(s=m.get(q))if(s.then)return s.then(h,h),s;else E(s)?f(s[1],s[0],U(s[2])):f(s,200,{});else m.put(q,n);s||a(b.method, +q,c,e,d,b.timeout,b.withCredentials);return n}function o(a,b){if(!b)return a;var c=[];fc(b,function(a,b){a==null||a==q||(L(a)&&(a=da(a)),c.push(encodeURIComponent(b)+"="+encodeURIComponent(a)))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var p=c("$http"),s=[];m(e,function(a){s.push(B(a)?k.get(a):k.invoke(a))});l.pendingRequests=[];(function(a){m(arguments,function(a){l[a]=function(b,c){return l(v(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){m(arguments,function(a){l[a]= +function(b,c,d){return l(v(d||{},{method:a,url:b,data:c}))}})})("post","put");l.defaults=d;return l}]}function Xc(){this.$get=["$browser","$window","$document",function(b,a,c){return Yc(b,Zc,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]}function Yc(b,a,c,d,e,g){function h(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;Z?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror= +d;e.body.appendChild(c)}return function(e,i,j,k,l,n,o){function p(a,c,d,e){c=(i.match(Hb)||["",g])[1]=="file"?d?200:404:c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(C)}b.$$incOutstandingRequestCount();i=i||b.url();if(z(e)=="jsonp"){var s="_"+(d.counter++).toString(36);d[s]=function(a){d[s].data=a};h(i.replace("JSON_CALLBACK","angular.callbacks."+s),function(){d[s].data?p(k,200,d[s].data):p(k,-2);delete d[s]})}else{var t=new a;t.open(e,i,!0);m(l,function(a,b){a&&t.setRequestHeader(b,a)}); +var q;t.onreadystatechange=function(){if(t.readyState==4){var a=t.getAllResponseHeaders(),b=["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified","Pragma"];a||(a="",m(b,function(b){var c=t.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));p(k,q||t.status,t.responseText,a)}};if(o)t.withCredentials=!0;t.send(j||"");n>0&&c(function(){q=-1;t.abort()},n)}}}function $c(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0, +maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","), +AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function ad(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,i){var j=c.defer(),k=j.promise,l=y(i)&&!i,f=a.defer(function(){try{j.resolve(e())}catch(a){j.reject(a),d(a)}l||b.$apply()},f),i=function(){delete g[k.$$timeoutId]}; +k.$$timeoutId=f;g[f]=j;k.then(i,i);return k}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Rb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Sb);a("date",Tb);a("filter",bd);a("json",cd);a("limitTo",dd);a("lowercase",ed);a("number",Ub);a("orderBy",Vb);a("uppercase",fd)}function bd(){return function(b, +a){if(!E(b))return b;var c=[];c.check=function(a){for(var b=0;b-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;ce+1?h="0":(f=h,j=!0)}if(!j){h=(h.split(Xb)[1]||"").length;w(e)&&(e=Math.min(Math.max(a.minFrac,h),a.maxFrac));var h=Math.pow(10,e),b=Math.round(b*h)/h,b=(""+b).split(Xb),h=b[0],b=b[1]||"",j=0,k=a.lgSize, +l=a.gSize;if(h.length>=k+l)for(var j=h.length-k,n=0;n0||e> +-c)e+=c;e===0&&c==-12&&(e=12);return jb(e,a,d)}}function Ja(b,a){return function(c,d){var e=c["get"+b](),g=la(a?"SHORT"+b:b);return d[g][e]}}function Tb(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,h=0;b[9]&&(g=G(b[9]+b[10]),h=G(b[9]+b[11]));a.setUTCFullYear(G(b[1]),G(b[2])-1,G(b[3]));a.setUTCHours(G(b[4]||0)-g,G(b[5]||0)-h,G(b[6]||0),G(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c, +e){var g="",h=[],f,i,e=e||"mediumDate",e=b.DATETIME_FORMATS[e]||e;B(c)&&(c=gd.test(c)?G(c):a(c));Qa(c)&&(c=new Date(c));if(!na(c))return c;for(;e;)(i=hd.exec(e))?(h=h.concat(ha.call(i,1)),e=h.pop()):(h.push(e),e=null);m(h,function(a){f=id[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function cd(){return function(b){return da(b,!0)}}function dd(){return function(b,a){if(!(b instanceof Array))return b;var a=G(a),c=[],d,e;if(!b||!(b instanceof Array))return c; +a>b.length?a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;dn?(d.$setValidity("maxlength",!1),q):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(c);d.$formatters.push(c)}}function kb(b,a){b="ngClass"+b;return S(function(c,d,e){function g(b){if(a===!0||c.$index%2===a)i&&!fa(b,i)&&h(i),f(b);i=U(b)}function h(a){L(a)&& +!E(a)&&(a=Ra(a,function(a,b){if(a)return b}));d.removeClass(E(a)?a.join(" "):a)}function f(a){L(a)&&!E(a)&&(a=Ra(a,function(a,b){if(a)return b}));a&&d.addClass(E(a)?a.join(" "):a)}var i=q;c.$watch(e[b],g,!0);e.$observe("class",function(){var a=c.$eval(e[b]);g(a,a)});b!=="ngClass"&&c.$watch("$index",function(d,g){var i=d&1;i!==g&1&&(i===a?f(c.$eval(e[b])):h(c.$eval(e[b])))})})}var z=function(b){return B(b)?b.toLowerCase():b},la=function(b){return B(b)?b.toUpperCase():b},Z=G((/msie (\d+)/.exec(z(navigator.userAgent))|| +[])[1]),u,ca,ha=[].slice,Pa=[].push,wa=Object.prototype.toString,Ya=P.angular||(P.angular={}),sa,fb,aa=["0","0","0"];C.$inject=[];ma.$inject=[];fb=Z<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?la(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var kc=/[A-Z]/g,jd={full:"1.0.7",major:1,minor:0,dot:7,codeName:"monochromatic-rainbow"},Ba=K.cache={},Aa=K.expando="ng-"+(new Date).getTime(),oc=1,$b=P.document.addEventListener? +function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},db=P.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},mc=/([\:\-\_]+(.))/g,nc=/^moz([A-Z])/,ua=K.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;this.bind("DOMContentLoaded",a);K(P).bind("load",a)},toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?u(this[b]):u(this[this.length+ +b])},length:0,push:Pa,sort:[].sort,splice:[].splice},Ea={};m("multiple,selected,checked,disabled,readOnly,required".split(","),function(b){Ea[z(b)]=b});var Bb={};m("input,select,option,textarea,button,form".split(","),function(b){Bb[la(b)]=!0});m({data:wb,inheritedData:Da,scope:function(b){return Da(b,"$scope")},controller:zb,injector:function(b){return Da(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ca,css:function(b,a,c){a=tb(a);if(y(c))b.style[a]=c;else{var d;Z<=8&&(d= +b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];Z<=8&&(d=d===""?q:d);return d}},attr:function(b,a,c){var d=z(a);if(Ea[d])if(y(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||C).specified?d:q;else if(y(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?q:b},prop:function(b,a,c){if(y(c))b[a]=c;else return b[a]},text:v(Z<9?function(b,a){if(b.nodeType==1){if(w(a))return b.innerText; +b.innerText=a}else{if(w(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(w(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(w(a))return b.value;b.value=a},html:function(b,a){if(w(a))return b.innerHTML;for(var c=0,d=b.childNodes;c":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Mc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},ib={},Zc=P.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw Error("This browser does not support XMLHttpRequest."); +};Rb.$inject=["$provide"];Sb.$inject=["$locale"];Ub.$inject=["$locale"];var Xb=".",id={yyyy:O("FullYear",4),yy:O("FullYear",2,0,!0),y:O("FullYear",1),MMMM:Ja("Month"),MMM:Ja("Month",!0),MM:O("Month",2,1),M:O("Month",1,1),dd:O("Date",2),d:O("Date",1),HH:O("Hours",2),H:O("Hours",1),hh:O("Hours",2,-12),h:O("Hours",1,-12),mm:O("Minutes",2),m:O("Minutes",1),ss:O("Seconds",2),s:O("Seconds",1),EEEE:Ja("Day"),EEE:Ja("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){var a= +-1*a.getTimezoneOffset(),c=a>=0?"+":"";c+=jb(Math[a>0?"floor":"ceil"](a/60),2)+jb(Math.abs(a%60),2);return c}},hd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,gd=/^\d+$/;Tb.$inject=["$locale"];var ed=I(z),fd=I(la);Vb.$inject=["$parse"];var kd=I({restrict:"E",compile:function(a,c){Z<=8&&(!c.href&&!c.name&&c.$set("href",""),a.append(T.createComment("IE fix")));return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),lb={};m(Ea,function(a, +c){var d=ea("ng-"+c);lb[d]=function(){return{priority:100,compile:function(){return function(a,g,h){a.$watch(h[d],function(a){h.$set(c,!!a)})}}}}});m(["src","href"],function(a){var c=ea("ng-"+a);lb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),Z&&e.prop(a,g[a]))})}}}});var Ma={$addControl:C,$removeControl:C,$setValidity:C,$setDirty:C};Yb.$inject=["$element","$attrs","$scope"];var Pa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E", +controller:Yb,compile:function(){return{pre:function(a,d,h,f){if(!h.action){var i=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};$b(d[0],"submit",i);d.bind("$destroy",function(){c(function(){db(d[0],"submit",i)},0,!1)})}var j=d.parent().controller("form"),k=h.name||h.ngForm;k&&(a[k]=f);j&&d.bind("$destroy",function(){j.$removeControl(f);k&&(a[k]=q);v(f,Ma)})}}}};return a?v(U(d),{restrict:"EAC"}):d}]},ld=Pa(),md=Pa(!0),nd=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/, +od=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,pd=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,bc={text:Oa,number:function(a,c,d,e,g,h){Oa(a,c,d,e,g,h);e.$parsers.push(function(a){var c=X(a);return c||pd.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),q)});e.$formatters.push(function(a){return X(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!X(a)&&ai?(e.$setValidity("max",!1),q):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return X(a)||Qa(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),q)})},url:function(a,c,d,e,g,h){Oa(a,c,d,e,g,h);a=function(a){return X(a)||nd.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a, +c,d,e,g,h){Oa(a,c,d,e,g,h);a=function(a){return X(a)||od.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){w(d.name)&&c.attr("name",xa());c.bind("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,h=d.ngFalseValue;B(g)||(g=!0);B(h)||(h=!1);c.bind("click", +function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:h})},hidden:C,button:C,submit:C,reset:C},cc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,h){h&&(bc[z(g.type)]||bc.text)(d,e,g,h,c,a)}}}],La="ng-valid",Ka="ng-invalid",Na="ng-pristine",Zb="ng-dirty",qd=["$scope","$exceptionHandler","$attrs","$element","$parse", +function(a,c,d,e,g){function h(a,c){c=c?"-"+Za(c,"-"):"";e.removeClass((a?Ka:La)+c).addClass((a?La:Ka)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),i=f.assign;if(!i)throw Error(Eb+d.ngModel+" ("+pa(e)+")");this.$render=C;var j=e.inheritedData("$formController")||Ma,k=0,l=this.$error={};e.addClass(Na);h(!0);this.$setValidity=function(a, +c){if(l[a]!==!c){if(c){if(l[a]&&k--,!k)h(!0),this.$valid=!0,this.$invalid=!1}else h(!1),this.$invalid=!0,this.$valid=!1,k++;l[a]=!c;h(c,a);j.$setValidity(a,c,this)}};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(Na).addClass(Zb),j.$setDirty();m(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,i(a,d),m(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};var n=this;a.$watch(function(){var c= +f(a);if(n.$modelValue!==c){var d=n.$formatters,e=d.length;for(n.$modelValue=c;e--;)c=d[e](c);if(n.$viewValue!==c)n.$viewValue=c,n.$render()}})}],rd=function(){return{require:["ngModel","^?form"],controller:qd,link:function(a,c,d,e){var g=e[0],h=e[1]||Ma;h.$addControl(g);c.bind("$destroy",function(){h.$removeControl(g)})}}},sd=I({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),dc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required= +!0;var g=function(a){if(d.required&&(X(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},td=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&m(a.split(g),function(a){a&&c.push(Q(a))});return c});e.$formatters.push(function(a){return E(a)?a.join(", "): +q})}}},ud=/^(true|false|\d+)$/,vd=function(){return{priority:100,compile:function(a,c){return ud.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},wd=S(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==q?"":a)})}),xd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate", +function(a){d.text(a)})}}],yd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],zd=kb("",!0),Ad=kb("Odd",0),Bd=kb("Even",1),Cd=S({compile:function(a,c){c.$set("ngCloak",q);a.removeClass("ng-cloak")}}),Dd=[function(){return{scope:!0,controller:"@"}}],Ed=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],ec={};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave".split(" "), +function(a){var c=ea("ng-"+a);ec[c]=["$parse",function(d){return function(e,g,h){var f=d(h[c]);g.bind(z(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Fd=S(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),Gd=["$http","$templateCache","$anchorScroll","$compile",function(a,c,d,e){return{restrict:"ECA",terminal:!0,compile:function(g,h){var f=h.ngInclude||h.src,i=h.onload||"",j=h.autoscroll;return function(g,h){var n=0,o,p=function(){o&&(o.$destroy(),o=null);h.html("")}; +g.$watch(f,function(f){var m=++n;f?a.get(f,{cache:c}).success(function(a){m===n&&(o&&o.$destroy(),o=g.$new(),h.html(a),e(h.contents())(o),y(j)&&(!j||g.$eval(j))&&d(),o.$emit("$includeContentLoaded"),g.$eval(i))}).error(function(){m===n&&p()}):p()})}}}}],Hd=S({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Id=S({terminal:!0,priority:1E3}),Jd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,h){var f=h.count,i=g.attr(h.$attr.when),j=h.offset|| +0,k=e.$eval(i),l={},n=c.startSymbol(),o=c.endSymbol();m(k,function(a,e){l[e]=c(a.replace(d,n+f+"-"+j+o))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(c in k||(c=a.pluralCat(c-j)),l[c](e,g,!0))},function(a){g.text(a)})}}}],Kd=S({transclude:"element",priority:1E3,terminal:!0,compile:function(a,c,d){return function(a,c,h){var f=h.ngRepeat,h=f.match(/^\s*(.+)\s+in\s+(.*)\s*$/),i,j,k;if(!h)throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '"+f+"'.");f= +h[1];i=h[2];h=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!h)throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '"+f+"'.");j=h[3]||h[1];k=h[2];var l=new eb;a.$watch(function(a){var e,f,h=a.$eval(i),m=c,q=new eb,y,A,u,w,r,v;if(E(h))r=h||[];else{r=[];for(u in h)h.hasOwnProperty(u)&&u.charAt(0)!="$"&&r.push(u);r.sort()}y=r.length-1;e=0;for(f=r.length;ez;)u.pop().element.remove()}for(;r.length> +x;)r.pop()[0].element.remove()}var i;if(!(i=s.match(d)))throw Error("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+s+"'.");var j=c(i[2]||i[1]),k=i[4]||i[6],l=i[5],m=c(i[3]||""),n=c(i[2]?i[1]:k),o=c(i[7]),r=[[{element:f,label:""}]];t&&(a(t)(e),t.removeClass("ng-scope"),t.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=o(e)||[],d={},h,i,j,m,s,t;if(p){i=[];m=0;for(t=r.length;m@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}'); \ No newline at end of file diff --git a/src/main/resources/gitblit.css b/src/main/resources/gitblit.css index 40c1d69b..526d5561 100644 --- a/src/main/resources/gitblit.css +++ b/src/main/resources/gitblit.css @@ -124,6 +124,15 @@ navbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.navbar d background-color: #002060; } +div.push { + border-bottom: 1px solid #ddd; + margin-bottom: 15px; +} +div.push i { + font-size:3.25em; + color:#bbb; +} + .repositorynavbar { background-color: #fbfbfb; border-bottom: 1px solid #ccc; @@ -613,12 +622,23 @@ span.link em, div.link span em { font-size: 11px; } +span.activitySwatch { + border-radius: 3px; + padding: 1px 4px 2px 4px; + color: #ffffff; + vertical-align: center; +} + +span.activitySwatch a { + color: inherit; +} + span.repositorySwatch { - border-radius: 3px; - padding: 1px 4px 2px 4px; + padding: 1px 1px 2px 1px; color: #ffffff; vertical-align: center; } + span.repositorySwatch a { color: inherit; } diff --git a/src/site/design.mkd b/src/site/design.mkd index f0da77a1..8392a9fe 100644 --- a/src/site/design.mkd +++ b/src/site/design.mkd @@ -13,6 +13,7 @@ The following dependencies are bundled with Gitblit. - [Bootstrap](http://twitter.github.com/bootstrap) (Apache 2.0) - [GLYPHICONS](http://glyphicons.com) (Creative Commons CC-BY) - [Iconic](http://somerandomdude.com/work/iconic) (Creative Commons Share Alike 3.0) +- [AngularJS](http://angularjs.org) (MIT) - [Clippy](https://github.com/mojombo/clippy) (MIT) - [google-code-prettify](http://code.google.com/p/google-code-prettify) (Apache 2.0) - [Commons Daemon](http://commons.apache.org/daemon) (Apache 2.0) -- cgit v1.2.3
[hash link] [commit short message]