Skip to content

Commit 6c06165

Browse files
Merge remote-tracking branch 'collapsible/ticket/527' into 725_nested_repos
2 parents f365daa + 40ee965 commit 6c06165

File tree

7 files changed

+295
-3
lines changed

7 files changed

+295
-3
lines changed

src/main/distrib/data/defaults.properties

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,3 +2147,17 @@ filestore.storageFolder = ${baseFolder}/lfs
21472147
# Common unit suffixes of k, m, or g are supported.
21482148
# SINCE 1.7.0
21492149
filestore.maxUploadSize = -1
2150+
2151+
# Specify the behaviour of the Repository groups on the "Repositories"
2152+
# page, specifically whether they can be collapsed and expanded, and
2153+
# their default state on loading the page.
2154+
# Only on repositoryListType grouped
2155+
#
2156+
# Values (case-insensitive):
2157+
# disabled - Repository groups cannot collapsed; maintains behaviour
2158+
# from previous versions of GitBlit.
2159+
# expanded - On loading the page all repository groups are expanded.
2160+
# collapsed - On loading the page all repository groups are collapsed.
2161+
#
2162+
# SINCE 1.9.0
2163+
web.collapsibleRepositoryGroups = disabled

src/main/java/com/gitblit/wicket/pages/BasePage.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151

5252
<!-- Include scripts at end for faster page loading -->
5353
<script type="text/javascript" src="bootstrap/js/jquery.js"></script>
54-
<script type="text/javascript" src="bootstrap/js/bootstrap.js"></script>
54+
<script type="text/javascript" src="bootstrap/js/bootstrap.js"></script>
55+
<script type="text/javascript" src="gitblit/js/collapsible-table.js"></script>
5556
<wicket:container wicket:id="bottomScripts"></wicket:container>
5657
</body>
5758
</html>

src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
</tbody>
1919
</table>
2020

21+
<wicket:fragment wicket:id="emptyFragment">
22+
</wicket:fragment>
23+
2124
<wicket:fragment wicket:id="repoIconFragment">
2225
<span class="octicon octicon-centered octicon-repo"></span>
2326
</wicket:fragment>
@@ -72,9 +75,15 @@
7275
</tr>
7376
</wicket:fragment>
7477

78+
<wicket:fragment wicket:id="tableAllCollapsible">
79+
<i title="Click to expand all" class="fa fa-plus-square-o table-openall-collapsible" aria-hidden="true" style="padding-right:3px;cursor:pointer;"></i>
80+
<i title="Click to collapse all" class="fa fa-minus-square-o table-closeall-collapsible" aria-hidden="true" style="padding-right:3px;cursor:pointer;"></i>
81+
</wicket:fragment>
82+
7583
<wicket:fragment wicket:id="groupRepositoryHeader">
7684
<tr>
7785
<th class="left">
86+
<span wicket:id="allCollapsible"></span>
7887
<img style="vertical-align: middle;" src="git-black-16x16.png"/>
7988
<wicket:message key="gb.repository">Repository</wicket:message>
8089
</th>
@@ -86,8 +95,16 @@
8695
</tr>
8796
</wicket:fragment>
8897

98+
<wicket:fragment wicket:id="tableGroupMinusCollapsible">
99+
<i title="Click to expand/collapse" class="fa fa-minus-square-o table-group-collapsible" aria-hidden="true" style="padding-right:3px;cursor:pointer;"></i>
100+
</wicket:fragment>
101+
102+
<wicket:fragment wicket:id="tableGroupPlusCollapsible">
103+
<i title="Click to expand/collapse" class="fa fa-plus-square-o table-group-collapsible" aria-hidden="true" style="padding-right:3px;cursor:pointer;"></i>
104+
</wicket:fragment>
105+
89106
<wicket:fragment wicket:id="groupRepositoryRow">
90-
<td colspan="1"><span wicket:id="groupName">[group name]</span></td>
107+
<td colspan="1"><span wicket:id="groupCollapsible"></span><span wicket:id="groupName">[group name]</span></td>
91108
<td colspan="6" style="padding: 2px;"><span class="hidden-phone" style="font-weight:normal;color:#666;" wicket:id="groupDescription">[description]</span></td>
92109
</wicket:fragment>
93110

src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,32 @@ public class RepositoriesPanel extends BasePanel {
6161

6262
private static final long serialVersionUID = 1L;
6363

64+
private enum CollapsibleRepositorySetting {
65+
DISABLED,
66+
67+
EXPANDED,
68+
69+
COLLAPSED;
70+
71+
public static CollapsibleRepositorySetting get(String name) {
72+
CollapsibleRepositorySetting returnVal = CollapsibleRepositorySetting.DISABLED;
73+
for (CollapsibleRepositorySetting setting : values()) {
74+
if (setting.name().equalsIgnoreCase(name)) {
75+
returnVal = setting;
76+
break;
77+
}
78+
}
79+
return returnVal;
80+
}
81+
}
6482
public RepositoriesPanel(String wicketId, final boolean showAdmin, final boolean showManagement, List<RepositoryModel> models, boolean enableLinks,
6583
final Map<AccessRestrictionType, String> accessRestrictionTranslations) {
6684
super(wicketId);
6785

6886
final boolean linksActive = enableLinks;
6987
final boolean showSize = app().settings().getBoolean(Keys.web.showRepositorySizes, true);
88+
final String collapsibleRespositorySetting = app().settings().getString(Keys.web.collapsibleRepositoryGroups, null);
89+
final CollapsibleRepositorySetting collapsibleRepoGroups = CollapsibleRepositorySetting.get(collapsibleRespositorySetting);
7090

7191
final UserModel user = GitBlitWebSession.get().getUser();
7292

@@ -194,6 +214,16 @@ public void populateItem(final Item<RepositoryModel> item) {
194214
GroupRepositoryModel groupRow = (GroupRepositoryModel) entry;
195215
currGroupName = entry.name;
196216
Fragment row = new Fragment("rowContent", "groupRepositoryRow", this);
217+
if(collapsibleRepoGroups == CollapsibleRepositorySetting.EXPANDED) {
218+
Fragment groupCollapsible = new Fragment("groupCollapsible", "tableGroupMinusCollapsible", this);
219+
row.add(groupCollapsible);
220+
} else if(collapsibleRepoGroups == CollapsibleRepositorySetting.COLLAPSED) {
221+
Fragment groupCollapsible = new Fragment("groupCollapsible", "tableGroupPlusCollapsible", this);
222+
row.add(groupCollapsible);
223+
} else {
224+
Fragment groupCollapsible = new Fragment("groupCollapsible", "emptyFragment", this);
225+
row.add(groupCollapsible);
226+
}
197227
item.add(row);
198228

199229
String name = groupRow.name;
@@ -209,7 +239,7 @@ public void populateItem(final Item<RepositoryModel> item) {
209239
row.add(new LinkPanel("groupName", null, groupRow.toString(), ProjectPage.class, WicketUtils.newProjectParameter(entry.name)));
210240
row.add(new Label("groupDescription", entry.description == null ? "" : entry.description));
211241
}
212-
WicketUtils.setCssClass(item, "group");
242+
WicketUtils.setCssClass(item, "group collapsible");
213243
// reset counter so that first row is light background
214244
counter = 0;
215245
return;
@@ -345,6 +375,14 @@ public void populateItem(final Item<RepositoryModel> item) {
345375
} else {
346376
// not sortable
347377
Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this);
378+
if(collapsibleRepoGroups == CollapsibleRepositorySetting.EXPANDED ||
379+
collapsibleRepoGroups == CollapsibleRepositorySetting.COLLAPSED) {
380+
Fragment allCollapsible = new Fragment("allCollapsible", "tableAllCollapsible", this);
381+
fragment.add(allCollapsible);
382+
} else {
383+
Fragment allCollapsible = new Fragment("allCollapsible", "emptyFragment", this);
384+
fragment.add(allCollapsible);
385+
}
348386
add(fragment);
349387
}
350388
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package com.gitblit.wicket.panels;
2+
3+
import java.io.Serializable;
4+
import java.util.ArrayList;
5+
import java.util.List;
6+
7+
import com.gitblit.models.RepositoryModel;
8+
import com.gitblit.utils.StringUtils;
9+
10+
public class TreeNodeModel implements Serializable {
11+
12+
private static final long serialVersionUID = 1L;
13+
final TreeNodeModel parent;
14+
final String name;
15+
private final List<TreeNodeModel> subFolders = new ArrayList<>();
16+
private final List<RepositoryModel> repositories = new ArrayList<>();
17+
18+
/**
19+
* Create a new tree root
20+
*/
21+
public TreeNodeModel() {
22+
this.name = "";
23+
this.parent = null;
24+
}
25+
26+
protected TreeNodeModel(String name, TreeNodeModel parent) {
27+
this.name = name;
28+
this.parent = parent;
29+
}
30+
31+
public int getDepth() {
32+
if(parent == null) {
33+
return 0;
34+
}else {
35+
return parent.getDepth() +1;
36+
}
37+
}
38+
39+
/**
40+
* Add a new sub folder to the current folder
41+
*
42+
* @param subFolder the subFolder to create
43+
* @return the newly created folder to allow chaining
44+
*/
45+
public TreeNodeModel add(String subFolder) {
46+
TreeNodeModel n = new TreeNodeModel(subFolder, this);
47+
subFolders.add(n);
48+
return n;
49+
}
50+
51+
/**
52+
* Add the given repo to the current folder
53+
*
54+
* @param repo
55+
*/
56+
public void add(RepositoryModel repo) {
57+
repositories.add(repo);
58+
}
59+
60+
/**
61+
* Adds the given repository model within the given path. Creates parent folders if they do not exist
62+
*
63+
* @param path
64+
* @param model
65+
*/
66+
public void add(String path, RepositoryModel model) {
67+
TreeNodeModel folder = getSubTreeNode(this, path, true);
68+
folder.add(model);
69+
}
70+
71+
@Override
72+
public String toString() {
73+
String string = name + "\n";
74+
for(TreeNodeModel n : subFolders) {
75+
string += "f";
76+
for(int i = 0; i < n.getDepth(); i++) {
77+
string += "-";
78+
}
79+
string += n.toString();
80+
}
81+
82+
for(RepositoryModel n : repositories) {
83+
string += "r";
84+
for(int i = 0; i < getDepth()+1; i++) {
85+
string += "-";
86+
}
87+
string += n.toString() + "\n";
88+
}
89+
90+
return string;
91+
}
92+
93+
public boolean containsSubFolder(String path) {
94+
return containsSubFolder(this, path);
95+
}
96+
97+
public TreeNodeModel getSubFolder(String path) {
98+
return getSubTreeNode(this, path, false);
99+
}
100+
101+
102+
103+
104+
private static TreeNodeModel getSubTreeNode(TreeNodeModel node, String path, boolean create) {
105+
if(!StringUtils.isEmpty(path)) {
106+
boolean isPathInCurrentHierarchyLevel = path.lastIndexOf('/') < 0;
107+
if(isPathInCurrentHierarchyLevel) {
108+
for(TreeNodeModel t : node.subFolders) {
109+
if(t.name.equals(path) ) {
110+
return t;
111+
}
112+
}
113+
114+
if(create) {
115+
node.add(path);
116+
return getSubTreeNode(node, path, true);
117+
}
118+
}else {
119+
//traverse into subFolder
120+
String folderInCurrentHierarchyLevel = path.substring(0, path.indexOf('/'));
121+
122+
for(TreeNodeModel t : node.subFolders) {
123+
if(t.name.equals(folderInCurrentHierarchyLevel) ) {
124+
String folderInNextHierarchyLevel = path.substring(path.indexOf('/') + 1, path.length());
125+
return getSubTreeNode(t, folderInNextHierarchyLevel, create);
126+
}
127+
}
128+
129+
if(create) {
130+
String folderInNextHierarchyLevel = path.substring(path.indexOf('/') + 1, path.length());
131+
return getSubTreeNode(node.add(folderInCurrentHierarchyLevel), folderInNextHierarchyLevel, true);
132+
}
133+
}
134+
}
135+
136+
return null;
137+
}
138+
139+
private static boolean containsSubFolder(TreeNodeModel node, String path) {
140+
return getSubTreeNode(node, path, false) != null;
141+
}
142+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
$(function() {
2+
$('i.table-group-collapsible')
3+
.click(function(){
4+
$(this).closest('tr.group.collapsible').nextUntil('tr.group.collapsible').toggle();
5+
$(this).toggleClass('fa-minus-square-o');
6+
$(this).toggleClass('fa-plus-square-o');
7+
});
8+
9+
$('i.table-openall-collapsible')
10+
.click(function(){
11+
$('tr.group.collapsible').first().find('i').addClass('fa-minus-square-o');
12+
$('tr.group.collapsible').first().find('i').removeClass('fa-plus-square-o');
13+
$('tr.group.collapsible').first().nextAll('tr:not(tr.group.collapsible)').show();
14+
$('tr.group.collapsible').first().nextAll('tr.group.collapsible').find('i').addClass('fa-minus-square-o');
15+
$('tr.group.collapsible').first().nextAll('tr.group.collapsible').find('i').removeClass('fa-plus-square-o');
16+
});
17+
18+
$('i.table-closeall-collapsible')
19+
.click(function(){
20+
$('tr.group.collapsible').first().find('i').addClass('fa-plus-square-o');
21+
$('tr.group.collapsible').first().find('i').removeClass('fa-minus-square-o');
22+
$('tr.group.collapsible').first().nextAll('tr:not(tr.group.collapsible)').hide();
23+
$('tr.group.collapsible').first().nextAll('tr.group.collapsible').find('i').addClass('fa-plus-square-o');
24+
$('tr.group.collapsible').first().nextAll('tr.group.collapsible').find('i').removeClass('fa-minus-square-o');
25+
});
26+
27+
$( document ).ready(function() {
28+
if($('tr.group.collapsible').first().find('i').hasClass('fa-plus-square-o')) {
29+
$('tr.group.collapsible').first().nextAll('tr:not(tr.group.collapsible)').hide();
30+
}
31+
});
32+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.gitblit.wicket.panels;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertTrue;
6+
7+
import org.junit.Test;
8+
9+
import com.gitblit.models.RepositoryModel;
10+
11+
public class TreeNodeModelTest {
12+
13+
@Test
14+
public void testContainsSubFolder() {
15+
TreeNodeModel tree = new TreeNodeModel();
16+
tree.add("foo").add("bar").add("baz");
17+
18+
assertTrue(tree.containsSubFolder("foo/bar/baz"));
19+
assertTrue(tree.containsSubFolder("foo/bar"));
20+
assertFalse(tree.containsSubFolder("foo/bar/blub"));
21+
}
22+
23+
@Test
24+
public void testAddInHierarchy() {
25+
TreeNodeModel tree = new TreeNodeModel();
26+
tree.add("foo").add("bar");
27+
28+
RepositoryModel model = new RepositoryModel("test","","",null);
29+
30+
// add model to non-existing folder. should be created automatically
31+
tree.add("foo/bar/baz", model);
32+
tree.add("another/non/existing/folder", model);
33+
34+
assertTrue(tree.containsSubFolder("foo/bar/baz"));
35+
assertTrue(tree.containsSubFolder("another/non/existing/folder"));
36+
}
37+
38+
@Test
39+
public void testGetDepth() {
40+
TreeNodeModel tree = new TreeNodeModel();
41+
TreeNodeModel bar = tree.add("foo").add("bar").add("baz");
42+
43+
assertEquals(0, tree.getDepth());
44+
assertEquals(3, bar.getDepth());
45+
}
46+
47+
48+
}

0 commit comments

Comments
 (0)