.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Redmine Subtask List Accordion
2 |
3 | This plugin provide accordion tree to subtask-list on issue.
4 |
5 | * http://www.redmine.org/plugins/redmine_subtask_list_accordion
6 |
7 | ## Features
8 |
9 | * Add accordion feature to subtask-list.
10 | * Add 'Expand this tree', 'Collapse this tree' and 'Expand next level all' to context-menu.
11 | * Add preferences of expand tree at first time.
12 | * Add plugin setting for server/client processing mode switch. (server mode default)
13 | Server mode is faster than client mode, but server mode is tradeoff other subtask's plugin. (for exsample 'subtask_list_columns' plugin)
14 |
15 | ## Compatibility
16 |
17 | Redmine 3.2 or 3.3 or 3.4 or 4.0 stable
18 |
19 | Tested on:
20 | * 3.2.9
21 | * 3.3.6
22 | * 3.4.8
23 | * 4.0.1
24 |
25 | ## Installation
26 |
27 | 1. Follow the Redmine plugin installation steps at: http://www.redmine.org/wiki/redmine/Plugins
28 | 2. Run the plugin migrations `rake redmine:plugins:migrate RAILS_ENV=production`
29 | 3. Restart your Redmine web server
30 |
--------------------------------------------------------------------------------
/app/views/context_menus/_accordion_menu.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= l(:context_menu_expand) %>
3 |
4 |
5 | <%= l(:context_menu_collapse) %>
6 |
7 |
8 | <%= l(:context_menu_all_expand_next) %>
9 |
--------------------------------------------------------------------------------
/app/views/issues/_subtask_list_accordion_partial.html.erb:
--------------------------------------------------------------------------------
1 | <% if sla_has_grandson_issues?(@issue) %>
2 | <%= content_for :header_tags do
3 | stylesheet_link_tag(sla_use_css, :plugin => "redmine_subtask_list_accordion") +
4 | javascript_include_tag("subtask_list_accordion" + (subtask_tree_client_processing? ? "_client" : ""), :plugin => "redmine_subtask_list_accordion")
5 | end %>
6 |
10 | <% if subtask_tree_client_processing? %>
11 |
14 | <% end %>
15 | <% end %>
16 |
39 |
--------------------------------------------------------------------------------
/app/views/my/_subtask_list_accordion_preferences.erb:
--------------------------------------------------------------------------------
1 | <%= labelled_fields_for :pref, @user.pref do |pref_fields| %>
2 | <%= pref_fields.text_field :subtasks_default_expand_limit_upper %>
3 | <% end %>
4 |
--------------------------------------------------------------------------------
/app/views/settings/_subtask_list_accordion_settings.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= check_box_tag 'settings[enable_server_scripting_mode]', true, settings['enable_server_scripting_mode'] %>
3 | <%= l(:label_enable_server_scripting_mode).html_safe %>
4 |
5 |
--------------------------------------------------------------------------------
/assets/javascripts/subtask_list_accordion.js:
--------------------------------------------------------------------------------
1 | function childIssueShowOrHide(parentTR)
2 | {
3 | var childlen = slaTRs.filter(function(index){
4 | return index >= (parentTR.attr('cs') - 0) && index <= (parentTR.attr('ce') - 0);
5 | });
6 |
7 | if (childlen.is(":visible"))
8 | {
9 | parentTR.removeClass("expand").addClass("collapse");
10 | childlen.hide("fast").filter(".haschild").removeClass("expand").addClass("collapse");
11 | }
12 | else
13 | {
14 | parentTR.removeClass("collapse").addClass("expand");
15 | childlen.filter('.idnt-' + (parentTR.attr('rank') - 0 + 1)).show("fast");
16 | }
17 | }
18 |
19 | function seletedTreeOpen()
20 | {
21 | var targetParents = slaTRs.filter("tr:has(td.checkbox > input:checked)");
22 | for (var i = 0; i < targetParents.size(); i++)
23 | {
24 | var parentTR = targetParents.eq(i);
25 | if (!parentTR.hasClass("haschild"))
26 | {
27 | continue;
28 | }
29 |
30 | //show
31 | var childlen = slaTRs.filter(function(index){
32 | return index >= (parentTR.attr('cs') - 0) && index <= (parentTR.attr('ce') - 0);
33 | });
34 | childlen.show().filter(".haschild").removeClass("collapse").addClass("expand");
35 | parentTR.removeClass("collapse").addClass("expand");
36 | }
37 | }
38 |
39 | function seletedTreeClose()
40 | {
41 | var targetParents = slaTRs.filter("tr:has(td.checkbox > input:checked)");
42 | for (var i = 0; i < targetParents.size(); i++)
43 | {
44 | var parentTR = targetParents.eq(i);
45 | if (!parentTR.hasClass("haschild"))
46 | {
47 | continue;
48 | }
49 |
50 | //hide
51 | var childlen = slaTRs.filter(function(index){
52 | return index >= (parentTR.attr('cs') - 0) && index <= (parentTR.attr('ce') - 0);
53 | });
54 | childlen.hide().filter(".haschild").removeClass("expand").addClass("collapse");
55 | parentTR.removeClass("expand").addClass("collapse");
56 | }
57 | }
58 |
59 | function allExpandNext()
60 | {
61 | var parentTR = slaTRs.filter("tr:has(td.checkbox > input:checked)");
62 | if (parentTR.size() != 1)
63 | {
64 | exit;
65 | }
66 | if (!parentTR.hasClass("haschild"))
67 | {
68 | exit;
69 | }
70 |
71 | for (var rank = 0; rank <= (parentTR.attr("rank") - 0); rank++)
72 | {
73 | //show
74 | slaTRs.filter("tr[rank='" + rank + "'].haschild.collapse").each(function(){
75 | childIssueShowOrHide($(this));
76 | });
77 | }
78 | }
79 |
80 | $(document).ready(function()
81 | {
82 | //set toggle event
83 | slaTRs = $("table.list > tbody > tr");
84 | slaTRs.find("td.subject > span.treearrow").click(function()
85 | {
86 | childIssueShowOrHide($(this).parent().parent());
87 | return false;
88 | });
89 |
90 | //all expand
91 | $("a.subtask_all_expand").click(function(){
92 | slaTRs.show().filter(".haschild").removeClass("collapse").addClass("expand");
93 |
94 | //for debug
95 | if (slaTRs.filter("tr:visible").size() != slaTRs.size()) alert("NG");
96 |
97 | return false;
98 | });
99 |
100 | //all collapese
101 | $("a.subtask_all_collapse").click(function(){
102 | slaTRs.filter(".idnt").hide();
103 | slaTRs.filter(".haschild").removeClass("expand").addClass("collapse");
104 | return false;
105 | });
106 |
107 | //link move
108 | $("div.accordion_control").insertAfter("#issue_tree > p");
109 | });
110 |
--------------------------------------------------------------------------------
/assets/javascripts/subtask_list_accordion_client.js:
--------------------------------------------------------------------------------
1 | function getParentIssue(startPos, rank, sameTreeOnly)
2 | {
3 | if (sameTreeOnly)
4 | {
5 | if (rank > 0 &&
6 | !slaTRs.eq(startPos.val + 1).hasClass("idnt-" + rank) &&
7 | !slaTRs.eq(startPos.val + 1).hasClass("idnt-" + (rank + 1)))
8 | {
9 | //skip because other tree
10 | return $();
11 | }
12 | }
13 |
14 | //find potential parent
15 | var issuesSelector = "table.list > tbody > tr";
16 | var rankAttr = rank <= 0 ? ":not(.idnt)" : "tr.idnt-" + rank;
17 | var nextAttr = "tr.idnt-" + (rank + 1 - 0);
18 | var selectorP = (startPos.val + rank) > 0 ? ":gt(" + startPos.val + ")" + rankAttr : rankAttr;
19 | var selectorC = nextAttr + ":first";
20 | var pp = $(issuesSelector + selectorP + " + " + selectorC);
21 |
22 | if (pp.size() != 1)
23 | {
24 | //no parent
25 | return $();
26 | }
27 |
28 | //get parent
29 | startPos.val = pp.index() - 1;
30 | return slaTRs.filter(function(index){
31 | return index == startPos.val;
32 | });
33 | }
34 |
35 | function getChildIssues(startPos, rank)
36 | {
37 | //find first child
38 | var nextAttr = ".idnt-" + (rank - 0 + 1);
39 | var selectorFirst = ":gt(" + startPos.val + ")" + nextAttr + ":first";
40 | var startIdx = slaTRs.filter(selectorFirst).index();
41 | if (startIdx <= 0)
42 | {
43 | return $();
44 | }
45 |
46 | //find last child
47 | var cc = startIdx;
48 | var rankCount = rank + 1;
49 | var endIdx;
50 | do
51 | {
52 | if (slaTRs.eq(cc).is('.idnt-' + rankCount))
53 | {
54 | endIdx = cc++;
55 | }
56 | else if (slaTRs.eq(cc).is('.idnt-' + (rankCount - 0 + 1)))
57 | {
58 | endIdx = cc++;
59 | rankCount++;
60 | }
61 | else if (slaTRs.eq(cc).is('.idnt') && (rankCount > (rank + 1)))
62 | {
63 | rankCount--;
64 | }
65 | else
66 | {
67 | break;
68 | }
69 | }
70 | while (cc < slaTRsSize);
71 |
72 | startPos.val = endIdx;
73 | return slaTRs.filter(function(index){ return index >= startIdx && index <= endIdx; });
74 | }
75 |
76 | function addRangeIndex(parentTR, childlen, rank)
77 | {
78 | parentTR.attr('cs', childlen.filter("tr:first").index()).attr('ce', childlen.filter("tr:last").index()).attr('rank', rank);
79 | }
80 |
81 | function childIssueShowOrHide(parentTR)
82 | {
83 | var childlen = slaTRs.filter(function(index){
84 | return index >= (parentTR.attr('cs') - 0) && index <= (parentTR.attr('ce') - 0);
85 | });
86 |
87 | if (childlen.is(":visible"))
88 | {
89 | parentTR.removeClass("expand").addClass("collapse");
90 | childlen.hide("fast").filter(".haschild").removeClass("expand").addClass("collapse");
91 | }
92 | else
93 | {
94 | parentTR.removeClass("collapse").addClass("expand");
95 | childlen.filter('.idnt-' + (parentTR.attr('rank') - 0 + 1)).show("fast");
96 | }
97 | }
98 |
99 | function setAccordion(parentPos, rank, isHiding, sameTreeOnly)
100 | {
101 | var parentTR = getParentIssue(parentPos, rank, sameTreeOnly);
102 | if (parentTR.size() != 1)
103 | {
104 | return false;
105 | }
106 | else if (parentTR.hasClass("haschild"))
107 | {
108 | //search exit or skip
109 | return !sameTreeOnly;
110 | }
111 | parentTR.addClass("haschild").addClass(isHiding ? "collapse" : "expand").find("td.subject > a").before('');
112 |
113 | var childlen = getChildIssues(parentPos, rank);
114 | addRangeIndex(parentTR, childlen, rank);
115 | parentTR.find('td.subject > span.treearrow').click(function()
116 | {
117 | childIssueShowOrHide($(this).parent().parent());
118 | return false;
119 | });
120 | if (isHiding)
121 | {
122 | childlen.hide();
123 | }
124 |
125 | //make next rank
126 | if (sameTreeOnly)
127 | {
128 | parentTR.find("td.subject > span.treearrow").one('click', function(){
129 | var parentTR = $(this).parent().parent();
130 | var parentFound = false;
131 | var parentPos = { val: 0 };
132 | parentPos.val = parentTR.index();
133 | do
134 | {
135 | parentFound = setAccordion(parentPos, rank + 1, true, true);
136 | }
137 | while(parentFound);
138 | });
139 | }
140 |
141 | //do next
142 | return true;
143 | }
144 |
145 | function seletedTreeOpen()
146 | {
147 | var targetParents = slaTRs.filter("tr:has(td.checkbox > input:checked)");
148 | for (var i = 0; i < targetParents.size(); i++)
149 | {
150 | var parentTR = targetParents.eq(i);
151 | if (!parentTR.hasClass("haschild"))
152 | {
153 | continue;
154 | }
155 |
156 | //make rank
157 | for (var rank = (parentTR.attr("rank") - 0 + 1); rank < 10; rank++)
158 | {
159 | var parentFound = false;
160 | var parentPos = { val: 0 };
161 | parentPos.val = parentTR.index();
162 | do
163 | {
164 | parentFound = setAccordion(parentPos, rank, false, false);
165 | }
166 | while(parentFound && parentPos.val < (parentTR.attr('ce') - 0));
167 | }
168 |
169 | //show
170 | var childlen = slaTRs.filter(function(index){
171 | return index >= (parentTR.attr('cs') - 0) && index <= (parentTR.attr('ce') - 0);
172 | });
173 | childlen.show().filter(".haschild").removeClass("collapse").addClass("expand");
174 | parentTR.removeClass("collapse").addClass("expand");
175 | }
176 | }
177 |
178 | function seletedTreeClose()
179 | {
180 | var targetParents = slaTRs.filter("tr:has(td.checkbox > input:checked)");
181 | for (var i = 0; i < targetParents.size(); i++)
182 | {
183 | var parentTR = targetParents.eq(i);
184 | if (!parentTR.hasClass("haschild"))
185 | {
186 | continue;
187 | }
188 |
189 | //hide
190 | var childlen = slaTRs.filter(function(index){
191 | return index >= (parentTR.attr('cs') - 0) && index <= (parentTR.attr('ce') - 0);
192 | });
193 | childlen.hide().filter(".haschild").removeClass("expand").addClass("collapse");
194 | parentTR.removeClass("expand").addClass("collapse");
195 | }
196 | }
197 |
198 | function allExpandNext()
199 | {
200 | var parentTR = slaTRs.filter("tr:has(td.checkbox > input:checked)");
201 | if (parentTR.size() != 1)
202 | {
203 | exit;
204 | }
205 | if (!parentTR.hasClass("haschild"))
206 | {
207 | exit;
208 | }
209 |
210 | //make rank
211 | for (var rank = 0; rank <= (parentTR.attr("rank") - 0 + 1); rank++)
212 | {
213 | if (rank > 0)
214 | {
215 | var parentFound = false;
216 | var parentPos = { val: 0 };
217 | do
218 | {
219 | parentFound = setAccordion(parentPos, rank, true, false);
220 | }
221 | while(parentFound);
222 | }
223 |
224 | //show
225 | if (rank <= (parentTR.attr("rank") - 0))
226 | {
227 | slaTRs.filter("tr[rank='" + rank + "'].haschild.collapse").each(function(){
228 | childIssueShowOrHide($(this));
229 | });
230 | }
231 | }
232 | }
233 |
234 | $(document).ready(function()
235 | {
236 | var expandTreeAtFirst = window.subtaskListAccordionExpandTreeAtFirst;
237 | //make rank first time
238 | slaTRs = $("table.list > tbody > tr");
239 | slaTRsSize = slaTRs.size();
240 | var analyzeTo = expandTreeAtFirst ? 10 : 1;
241 | var isHiding = !expandTreeAtFirst;
242 | var isSameTreeOnly = isHiding;
243 |
244 | for (var rank = 0; rank < analyzeTo; rank++)
245 | {
246 | var parentPos = { val: 0 };
247 | var parentFound = false;
248 | do
249 | {
250 | parentFound = setAccordion(parentPos, rank, isHiding, isSameTreeOnly);
251 | }
252 | while(parentFound);
253 | }
254 |
255 | //all expand
256 | $("a.subtask_all_expand").one("click", function(){
257 | for (var rank = 1; rank < 10; rank++)
258 | {
259 | var parentPos = { val: 0 };
260 | var parentFound = false;
261 | do
262 | {
263 | parentFound = setAccordion(parentPos, rank, false, false);
264 | }
265 | while(parentFound);
266 | }
267 | }).click(function(){
268 | slaTRs.show().filter(".haschild").removeClass("collapse").addClass("expand");
269 |
270 | //for debug
271 | if (slaTRs.filter("tr:visible").size() != slaTRsSize)
272 | {
273 | alert("NG");
274 | }
275 |
276 | return false;
277 | });
278 |
279 | //all collapese
280 | $("a.subtask_all_collapse").click(function(){
281 | slaTRs.filter(".idnt").hide();
282 | slaTRs.filter(".haschild").removeClass("expand").addClass("collapse");
283 | return false;
284 | });
285 |
286 | //link move
287 | $("div.accordion_control").insertAfter("#issue_tree > p");
288 | });
289 |
--------------------------------------------------------------------------------
/assets/stylesheets/subtask_list_accordion.css:
--------------------------------------------------------------------------------
1 | #issue_tree > p {
2 | margin-bottom: 5px;
3 | }
4 |
5 | .contextual.accordion_control {
6 | float: left;
7 | margin-bottom: 5px;
8 | padding: 2px 7px 2px 2px;
9 | }
10 |
11 | .accordion_control a {
12 | background-position: 5px 50%;
13 | background-repeat: no-repeat;
14 | padding-bottom: 3px;
15 | padding-left: 20px;
16 | padding-top: 2px;
17 | }
18 |
19 | .accordion_control a.subtask_all_expand {
20 | background-image: url(../../../images/arrow_down.png);
21 | }
22 |
23 | .accordion_control a.subtask_all_collapse {
24 | background-image: url(../../../images/arrow_right.png);
25 | }
26 |
27 | tr.issue.idnt td.subject {background: none;}
28 | tr.issue.idnt-1 td.subject {padding-left: 18px;}
29 | tr.issue.idnt-2 td.subject {padding-left: 34px;}
30 | tr.issue.idnt-3 td.subject {padding-left: 50px;}
31 | tr.issue.idnt-4 td.subject {padding-left: 66px;}
32 | tr.issue.idnt-5 td.subject {padding-left: 82px;}
33 | tr.issue.idnt-6 td.subject {padding-left: 98px;}
34 | tr.issue.idnt-7 td.subject {padding-left: 114px;}
35 | tr.issue.idnt-8 td.subject {padding-left: 130px;}
36 | tr.issue.idnt-9 td.subject {padding-left: 146px;}
37 |
38 | tr.issue:not(.idnt):not(.haschild) td.subject a {
39 | margin-left: 16px;
40 | }
41 |
42 | tr.issue.idnt:not(.haschild) td.subject a {
43 | margin-left: 16px;
44 | }
45 |
46 | tr.issue.haschild > td.subject > span.treearrow {
47 | cursor: pointer;
48 | padding-left: 16px;
49 | }
50 |
51 | tr.issue.haschild.collapse > td.subject > span.treearrow {
52 | background: url(../../../images/arrow_right.png) no-repeat 2px 50%;
53 | }
54 |
55 | tr.issue.haschild.expand > td.subject > span.treearrow {
56 | background: url(../../../images/arrow_down.png) no-repeat 2px 50%;
57 | }
58 |
59 | .icon-arrow-expanded {
60 | background-position: 5px 50%;
61 | background-image: url(../../../images/arrow_down.png);
62 | background-repeat: no-repeat;
63 | }
64 |
65 | .icon-arrow-collapsed {
66 | background-position: 5px 50%;
67 | background-image: url(../../../images/arrow_right.png);
68 | background-repeat: no-repeat;
69 | }
70 |
--------------------------------------------------------------------------------
/assets/stylesheets/subtask_list_accordion_under32.css:
--------------------------------------------------------------------------------
1 | #issue_tree > p {
2 | margin-bottom: 5px;
3 | }
4 |
5 | .contextual.accordion_control {
6 | float: left;
7 | margin-bottom: 5px;
8 | padding: 2px 7px 2px 2px;
9 | }
10 |
11 | .accordion_control a {
12 | background-position: 0 50%;
13 | background-repeat: no-repeat;
14 | padding-bottom: 3px;
15 | padding-left: 20px;
16 | padding-top: 2px;
17 | }
18 |
19 | .accordion_control a.subtask_all_expand {
20 | background-image: url(../../../images/arrow_expanded.png);
21 | }
22 |
23 | .accordion_control a.subtask_all_collapse {
24 | background-image: url(../../../images/arrow_collapsed.png);
25 | }
26 |
27 | tr.issue.idnt td.subject a {
28 | background: none;
29 | padding-left: 0px;
30 | }
31 |
32 | tr.issue:not(.idnt):not(.haschild) td.subject a {
33 | margin-left: 16px;
34 | }
35 |
36 | tr.issue.idnt:not(.haschild) td.subject a {
37 | margin-left: 32px;
38 | }
39 |
40 | tr.issue.haschild > td.subject > span.treearrow {
41 | cursor: pointer;
42 | }
43 |
44 | tr.issue.haschild:not(.idnt) > td.subject > span.treearrow {
45 | padding-left: 16px;
46 | }
47 |
48 | tr.issue.haschild.idnt > td.subject > span.treearrow {
49 | margin-left: 16px;
50 | padding-left: 16px;
51 | }
52 |
53 | tr.issue.haschild.collapse > td.subject > span.treearrow {
54 | background: url(../../../images/arrow_collapsed.png) no-repeat right bottom;
55 | }
56 |
57 | tr.issue.haschild.expand > td.subject > span.treearrow {
58 | background: url(../../../images/arrow_expanded.png) no-repeat right bottom;
59 | }
60 |
61 | .icon-arrow-expanded { background-image: url(../../../images/arrow_expanded.png); }
62 |
63 | .icon-arrow-collapsed { background-image: url(../../../images/arrow_collapsed.png); }
64 |
--------------------------------------------------------------------------------
/assets/stylesheets/subtask_list_accordion_under34.css:
--------------------------------------------------------------------------------
1 | #issue_tree > p {
2 | margin-bottom: 5px;
3 | }
4 |
5 | .contextual.accordion_control {
6 | float: left;
7 | margin-bottom: 5px;
8 | padding: 2px 7px 2px 2px;
9 | }
10 |
11 | .accordion_control a {
12 | background-position: 0 50%;
13 | background-repeat: no-repeat;
14 | padding-bottom: 3px;
15 | padding-left: 20px;
16 | padding-top: 2px;
17 | }
18 |
19 | .accordion_control a.subtask_all_expand {
20 | background-image: url(../../../images/arrow_expanded.png);
21 | }
22 |
23 | .accordion_control a.subtask_all_collapse {
24 | background-image: url(../../../images/arrow_collapsed.png);
25 | }
26 |
27 | tr.issue.idnt td.subject {background: none;}
28 | tr.issue.idnt-1 td.subject {padding-left: 18px;}
29 | tr.issue.idnt-2 td.subject {padding-left: 34px;}
30 | tr.issue.idnt-3 td.subject {padding-left: 50px;}
31 | tr.issue.idnt-4 td.subject {padding-left: 66px;}
32 | tr.issue.idnt-5 td.subject {padding-left: 82px;}
33 | tr.issue.idnt-6 td.subject {padding-left: 98px;}
34 | tr.issue.idnt-7 td.subject {padding-left: 114px;}
35 | tr.issue.idnt-8 td.subject {padding-left: 130px;}
36 | tr.issue.idnt-9 td.subject {padding-left: 146px;}
37 |
38 | tr.issue:not(.idnt):not(.haschild) td.subject a {
39 | margin-left: 16px;
40 | }
41 |
42 | tr.issue.idnt:not(.haschild) td.subject a {
43 | margin-left: 16px;
44 | }
45 |
46 | tr.issue.haschild > td.subject > span.treearrow {
47 | cursor: pointer;
48 | padding-left: 16px;
49 | }
50 |
51 | tr.issue.haschild.collapse > td.subject > span.treearrow {
52 | background: url(../../../images/arrow_collapsed.png) no-repeat right bottom;
53 | }
54 |
55 | tr.issue.haschild.expand > td.subject > span.treearrow {
56 | background: url(../../../images/arrow_expanded.png) no-repeat right bottom;
57 | }
58 |
59 | .icon-arrow-expanded { background-image: url(../../../images/arrow_expanded.png); }
60 |
61 | .icon-arrow-collapsed { background-image: url(../../../images/arrow_collapsed.png); }
62 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # English strings go here for Rails i18n
2 | en:
3 | list_all_expand: "Expand All"
4 | list_all_collapse: "Collapse All"
5 | context_menu_expand: "Expand this tree"
6 | context_menu_collapse: "Collapse this tree"
7 | context_menu_all_expand_next: "Expand next level all"
8 | field_subtasks_default_expand_limit_upper: "Upper limit of child issues to tree expanding from the first time"
9 | label_enable_server_scripting_mode: "Enable server processing mode (Has many subtasks tree display more than faster, but tradeoff other subtask's plugin. for exsample 'subtask_list_columns' plugin)"
10 |
--------------------------------------------------------------------------------
/config/locales/fr.yml:
--------------------------------------------------------------------------------
1 | # French - Laurent HADJADJ - 09/11/2016
2 | fr:
3 | list_all_expand: "Ouvrir tout"
4 | list_all_collapse: "Fermer tout"
5 | context_menu_expand: "Ouvrir l'arborescence"
6 | context_menu_collapse: "Fermer l'arborescence"
7 | context_menu_all_expand_next: "Ouvrir le niveau suivant"
8 | field_subtasks_default_expand_limit_upper: "Upper limit of child issues to tree expanding from the first time"
9 | label_enable_server_scripting_mode: "Enable server processing mode (Has many subtasks tree display more than faster, but tradeoff other subtask's plugin. for exsample 'subtask_list_columns' plugin)"
10 |
--------------------------------------------------------------------------------
/config/locales/ja.yml:
--------------------------------------------------------------------------------
1 | # Japanese strings go here for Rails i18n
2 | ja:
3 | list_all_expand: "すべて展開"
4 | list_all_collapse: "すべて収縮"
5 | context_menu_expand: "このツリーを展開"
6 | context_menu_collapse: "このツリーを収縮"
7 | context_menu_all_expand_next: "この階層をすべて展開"
8 | field_subtasks_default_expand_limit_upper: "デフォルトでツリーを展開する時の子チケット数上限"
9 | label_enable_server_scripting_mode: "サーバー処理モードを有効にする (小孫チケットが大量にある時のツリー表示が早くなりますが、子チケット一覧に関する他のプラグインとは共存できなくなります。 例 'subtask_list_columns' プラグイン)"
10 |
--------------------------------------------------------------------------------
/config/locales/zh.yml:
--------------------------------------------------------------------------------
1 | # Simplified Chinese strings go here for Rails i18n
2 | zh:
3 | list_all_expand: "全部展开"
4 | list_all_collapse: "全部收拢"
5 | context_menu_expand: "展开当前树形结构"
6 | context_menu_collapse: "收拢当前树形结构"
7 | context_menu_all_expand_next: "下一级全部展开"
8 | field_subtasks_default_expand_limit_upper: "子任务树中的问题数量上限(初次加载)"
9 | label_enable_server_scripting_mode: "启用服务器处理模式(若存在多级树结构,启用该选项会较快,但可能会与其他子任务相关插件有冲突,如插件 'subtask_list_columns' ) "
10 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | # Plugin's routes
2 | # See: http://guides.rubyonrails.org/routing.html
3 |
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | require_dependency 'redmine_subtask_list_accordion/hooks/subtask_list_accordion_hook'
2 | require_dependency 'redmine_subtask_list_accordion/patches/issues_helper_patch'
3 | require_dependency 'redmine_subtask_list_accordion/patches/user_preference_patch'
4 |
5 | reloader = defined?(ActiveSupport::Reloader) ? ActiveSupport::Reloader : ActionDispatch::Reloader
6 | reloader.to_prepare do
7 | unless UserPreference.included_modules.include?(RedmineSubtaskListAccordion::Patches::UserPreferencePatch)
8 | UserPreference.send :prepend, RedmineSubtaskListAccordion::Patches::UserPreferencePatch
9 | end
10 |
11 | unless IssuesHelper.included_modules.include?(RedmineSubtaskListAccordion::Patches::IssuesHelperPatch)
12 | IssuesHelper.send :include, RedmineSubtaskListAccordion::Patches::IssuesHelperPatch
13 | end
14 | end
15 |
16 | Redmine::Plugin.register :redmine_subtask_list_accordion do
17 | name 'Redmine Subtask List Accordion plugin'
18 | author 'Ryuta Tobita'
19 | description 'This plugin provide accordion to subtask list of issue.'
20 | version '2.2.0'
21 | url 'https://github.com/GEROMAX/redmine_subtask_list_accordion'
22 | author_url 'https://github.com/GEROMAX'
23 | settings default: { 'enable_server_scripting_mode' => true }, :partial => 'settings/subtask_list_accordion_settings'
24 | end
25 |
--------------------------------------------------------------------------------
/lib/redmine_subtask_list_accordion/hooks/subtask_list_accordion_hook.rb:
--------------------------------------------------------------------------------
1 | class SubtaskListAccordionHook < Redmine::Hook::ViewListener
2 | render_on :view_issues_show_description_bottom, :partial => 'issues/subtask_list_accordion_partial'
3 | render_on :view_my_account_preferences, :partial => 'my/subtask_list_accordion_preferences'
4 |
5 | def view_issues_context_menu_start(context={})
6 | if isIssuePage?(context[:back])
7 | context[:controller].send(:render_to_string, {
8 | :partial => "context_menus/accordion_menu",
9 | :locals => context
10 | })
11 | end
12 | end
13 |
14 | private
15 | def isIssuePage?(path)
16 | path =~ Regexp.new("issues/+[0-9]")
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/redmine_subtask_list_accordion/patches/issues_helper_patch.rb:
--------------------------------------------------------------------------------
1 | require_dependency("issues_helper")
2 |
3 | module RedmineSubtaskListAccordion
4 | module Patches
5 | module IssuesHelperPatch
6 | extend ActiveSupport::Concern
7 |
8 | #wrap original method
9 | included do
10 | alias_method :render_descendants_tree_original, :render_descendants_tree
11 | alias_method :render_descendants_tree, :switch_render_descendants_tree
12 | end
13 |
14 | #switch by enable condition
15 | def switch_render_descendants_tree(issue)
16 | if sla_has_grandson_issues?(issue) && !subtask_tree_client_processing?
17 | render_descendants_tree_accordion(issue)
18 | else
19 | render_descendants_tree_original(issue)
20 | end
21 | end
22 |
23 | # add method to IssuesHelper
24 | def render_descendants_tree_accordion(issue)
25 | trIdx = 0
26 | #switch by redmine version
27 | if subtask_list_accordion_tree_render_32?
28 | s = ''
45 | s.html_safe
46 | else
47 | s = ''
48 | issue_list(issue.descendants.visible.preload(:status, :priority, :tracker, :assigned_to).sort_by(&:lft)) do |child, level|
49 | arrow = (child.descendants.visible.count > 0 ? content_tag('span', '', :class => 'treearrow') : ''.html_safe)
50 | css = "issue issue-#{child.id} hascontextmenu #{child.css_classes}"
51 | css << " haschild" if child.children?
52 | css << (expand_tree_at_first?(issue) ? " expand" : " collapse")
53 | css << " idnt idnt-#{level}" if level > 0
54 | hide_or_show = 'display: none;' unless level <= 0 || expand_tree_at_first?(issue)
55 | s << content_tag('tr',
56 | content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
57 | content_tag('td', arrow + link_to_issue(child, :project => (issue.project_id != child.project_id)), :class => 'subject', :style => 'width: 50%') +
58 | content_tag('td', h(child.status), :class => 'status') +
59 | content_tag('td', link_to_user(child.assigned_to), :class => 'assigned_to') +
60 | content_tag('td', child.disabled_core_fields.include?('done_ratio') ? '' : progress_bar(child.done_ratio), :class=> 'done_ratio') +
61 | #Compatible 3.4 under
62 | (subtask_list_accordion_tree_render_34? ? '' : content_tag('td', link_to_context_menu, :class => 'buttons')),
63 | :class => css, :cs => (trIdx+=1).to_s, :ce => (trIdx + child.descendants.visible.count - 1).to_s, :rank => level.to_s, :style => hide_or_show)
64 | end
65 | s << '
'
66 | #Compatible 3.3 under
67 | subtask_list_accordion_tree_render_33? ? ('').html_safe : s.html_safe
68 | end
69 | end
70 |
71 | def expand_tree_at_first?(issue)
72 | return issue.descendants.visible.count <= User.current.pref.subtasks_default_expand_limit_upper
73 | end
74 |
75 | def sla_has_grandson_issues?(issue)
76 | return issue.descendants.visible.where(["issues.parent_id <> ?", issue.id]).count > 0
77 | end
78 |
79 | def subtask_tree_client_processing?
80 | return !Setting.plugin_redmine_subtask_list_accordion['enable_server_scripting_mode']
81 | end
82 |
83 | def subtask_list_accordion_tree_render_32?
84 | threshold = [3,3,0]
85 | return (Redmine::VERSION.to_a[0, 3] <=> threshold) < 0
86 | end
87 |
88 | def subtask_list_accordion_tree_render_33?
89 | threshold = [3,4,0]
90 | return (Redmine::VERSION.to_a[0, 3] <=> threshold) < 0
91 | end
92 |
93 | def subtask_list_accordion_tree_render_34?
94 | threshold = [4,0,0]
95 | return (Redmine::VERSION.to_a[0, 3] <=> threshold) < 0
96 | end
97 |
98 | def sla_use_css
99 | case
100 | when subtask_list_accordion_tree_render_32?
101 | "subtask_list_accordion_under32"
102 | when subtask_list_accordion_tree_render_34?
103 | "subtask_list_accordion_under34"
104 | else
105 | "subtask_list_accordion"
106 | end
107 | end
108 | end
109 | end
110 | end
111 |
--------------------------------------------------------------------------------
/lib/redmine_subtask_list_accordion/patches/user_preference_patch.rb:
--------------------------------------------------------------------------------
1 | require_dependency 'user_preference'
2 |
3 | module RedmineSubtaskListAccordion
4 | module Patches
5 | module UserPreferencePatch
6 |
7 | def self.prepended(base)
8 | base.class_eval do
9 | if defined? safe_attributes
10 | safe_attributes :subtasks_default_expand_limit_upper
11 | end
12 | end
13 | end
14 |
15 | def subtasks_default_expand_limit_upper; (self[:subtasks_default_expand_limit_upper] || 0).to_i; end
16 | def subtasks_default_expand_limit_upper=(val); self[:subtasks_default_expand_limit_upper] = val; end
17 |
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------