redmine_extensions 0.0.28 → 0.0.29
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/redmine_extensions/jquery.entityarray.js +130 -0
- data/app/assets/javascripts/redmine_extensions/redmine_extensions.js +287 -0
- data/app/helpers/redmine_extensions/application_helper.rb +136 -0
- data/app/models/easy_entity_assignment.rb +6 -0
- data/app/views/easy_entity_assignments/_assignments_container.html.erb +30 -0
- data/app/views/easy_entity_assignments/_query_index.html.erb +11 -0
- data/app/views/easy_queries/_index.html.erb +1 -1
- data/db/migrate/20160519161300_create_entity_assignments.rb +19 -0
- data/lib/generators/redmine_extensions/entity/entity_generator.rb +10 -7
- data/lib/generators/redmine_extensions/entity/templates/_form.html.erb.erb +2 -2
- data/lib/generators/redmine_extensions/entity/templates/_sidebar.html.erb.erb +2 -2
- data/lib/generators/redmine_extensions/entity/templates/controller.rb.erb +3 -0
- data/lib/generators/redmine_extensions/entity/templates/en.yml.erb +4 -1
- data/lib/generators/redmine_extensions/entity/templates/model.rb.erb +4 -0
- data/lib/generators/redmine_extensions/entity/templates/query.rb.erb +3 -3
- data/lib/generators/redmine_extensions/entity/templates/routes.rb.erb +1 -0
- data/lib/generators/redmine_extensions/entity/templates/show.html.erb.erb +3 -3
- data/lib/generators/redmine_extensions/plugin/templates/Gemfile.erb +1 -1
- data/lib/redmine_extensions/easy_query_adapter.rb +16 -2
- data/lib/redmine_extensions/hooks.rb +1 -0
- data/lib/redmine_extensions/redmine_patches/controllers/application_controller_patch.rb +0 -1
- data/lib/redmine_extensions/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b1aa0e104dd814d76200dc13913de85ee164c63
|
4
|
+
data.tar.gz: 69323429240bced2bbe3073800e38da7d8677209
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 06f43d7bbd756a988f48003df31d67c416cec480bfe1ea5e97fcf8f8e018bb67b2960618087fc21582de340e40158c222bcc5802b93c592fbce05365b546f269
|
7
|
+
data.tar.gz: bb7111418801f304c9a8d163101558ac37d02684b736b6aa0406a85c1ab2e975b1a1df4b7a7abaa83d09bad163253290ad398b1b38ad9852f682fa195a6a6bfe
|
@@ -0,0 +1,130 @@
|
|
1
|
+
;(function ($, window, document, undefined) {
|
2
|
+
"use strict";
|
3
|
+
|
4
|
+
var pluginName = "entityArray",
|
5
|
+
defaults = {
|
6
|
+
propertyName: "value"
|
7
|
+
};
|
8
|
+
|
9
|
+
function Plugin (element, options) {
|
10
|
+
this.element = element;
|
11
|
+
this.options = $.extend({}, defaults, options);
|
12
|
+
this._defaults = defaults;
|
13
|
+
this._name = pluginName;
|
14
|
+
this.init();
|
15
|
+
}
|
16
|
+
|
17
|
+
Plugin.prototype = {
|
18
|
+
|
19
|
+
init: function () {
|
20
|
+
var self = this;
|
21
|
+
this.entities = {};
|
22
|
+
this.element.addClass("entity-array");
|
23
|
+
this.createEmptyInput();
|
24
|
+
|
25
|
+
if (this.options.entities) {
|
26
|
+
$.each(this.options.entities, function () {
|
27
|
+
self.add(this);
|
28
|
+
});
|
29
|
+
}
|
30
|
+
},
|
31
|
+
|
32
|
+
add: function (entity) {
|
33
|
+
var self = this;
|
34
|
+
|
35
|
+
this.normalizeEntityData(entity);
|
36
|
+
if (!entity.id || entity === "" || this.entities[entity.id]) {
|
37
|
+
return null;
|
38
|
+
}
|
39
|
+
|
40
|
+
entity.element = $("<span/>")
|
41
|
+
.html(entity.name)
|
42
|
+
.addClass(entity.className || this.options.className)
|
43
|
+
.appendTo(this.element)
|
44
|
+
.after(" ");
|
45
|
+
|
46
|
+
$("<input/>")
|
47
|
+
.attr("type", "hidden")
|
48
|
+
.attr("name", this.options.inputNames)
|
49
|
+
.val(entity.id)
|
50
|
+
.appendTo(entity.element);
|
51
|
+
|
52
|
+
$("<span/>")
|
53
|
+
.html(' ')
|
54
|
+
.addClass("icon icon-del")
|
55
|
+
.appendTo(entity.element)
|
56
|
+
.data("entity-id", entity.id)
|
57
|
+
.click(function () {
|
58
|
+
self.removeEntity($(this).data("entity-id"));
|
59
|
+
});
|
60
|
+
|
61
|
+
this.entities[entity.id] = entity;
|
62
|
+
|
63
|
+
this.removeEmptyInput();
|
64
|
+
|
65
|
+
return entity;
|
66
|
+
},
|
67
|
+
|
68
|
+
removeEntity: function (entityId) {
|
69
|
+
var entity = this.entities[entityId];
|
70
|
+
entity.element.remove();
|
71
|
+
|
72
|
+
delete this.entities[entityId];
|
73
|
+
|
74
|
+
if (Object.keys(this.entities).length === 0) {
|
75
|
+
this.createEmptyInput();
|
76
|
+
}
|
77
|
+
|
78
|
+
if (typeof this.options.afterRemove === "function") {
|
79
|
+
this.options.afterRemove(entity);
|
80
|
+
}
|
81
|
+
},
|
82
|
+
|
83
|
+
clear: function () {
|
84
|
+
for( var id in this.entities ) {
|
85
|
+
this.removeEntity(id);
|
86
|
+
}
|
87
|
+
},
|
88
|
+
|
89
|
+
getValue: function() {
|
90
|
+
return Object.keys(this.entities);
|
91
|
+
},
|
92
|
+
|
93
|
+
normalizeEntityData: function (entityData) {
|
94
|
+
entityData.id = entityData.id.toString();
|
95
|
+
entityData.name = entityData.name.toString();
|
96
|
+
},
|
97
|
+
|
98
|
+
createEmptyInput: function () {
|
99
|
+
this.emptyInput = $("<input/>")
|
100
|
+
.attr("type", "hidden")
|
101
|
+
.attr("name", this.options.inputNames)
|
102
|
+
.appendTo(this.element);
|
103
|
+
},
|
104
|
+
|
105
|
+
removeEmptyInput: function () {
|
106
|
+
if (this.emptyInput) {
|
107
|
+
this.emptyInput.remove();
|
108
|
+
this.emptyInput = null;
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
};
|
113
|
+
|
114
|
+
$.fn[pluginName] = function (options, methodAttrs) {
|
115
|
+
var value = null;
|
116
|
+
this.each(function () {
|
117
|
+
var instance = $.data(this, "plugin_" + pluginName);
|
118
|
+
if (!instance) {
|
119
|
+
$.data(this, "plugin_" + pluginName, new Plugin($(this), options));
|
120
|
+
} else {
|
121
|
+
value = instance[options].call(instance, methodAttrs);
|
122
|
+
}
|
123
|
+
});
|
124
|
+
if( value !== null )
|
125
|
+
return value;
|
126
|
+
else
|
127
|
+
return this;
|
128
|
+
};
|
129
|
+
|
130
|
+
})(jQuery, window, document);
|
@@ -297,3 +297,290 @@ window.showFlashMessage = (function(type, message, delay){
|
|
297
297
|
window.closeFlashMessage = (function($element){
|
298
298
|
$element.closest('.flash').fadeOut(500, function(){$element.remove();});
|
299
299
|
});
|
300
|
+
|
301
|
+
|
302
|
+
(function($, undefined) {
|
303
|
+
|
304
|
+
$.widget('easy.easymultiselect', {
|
305
|
+
options: {
|
306
|
+
source: null,
|
307
|
+
selected: null,
|
308
|
+
multiple: true, // multiple values can be selected
|
309
|
+
preload: true, // load all possible values
|
310
|
+
position: {collision: 'flip'},
|
311
|
+
autofocus: false,
|
312
|
+
inputName: null, // defaults to element prop name
|
313
|
+
render_item: function(ul, item) {
|
314
|
+
return $("<li>")
|
315
|
+
.data("item.autocomplete", item)
|
316
|
+
.append(item.label)
|
317
|
+
.appendTo(ul);
|
318
|
+
},
|
319
|
+
activate_on_input_click: true,
|
320
|
+
load_immediately: false,
|
321
|
+
show_toggle_button: true,
|
322
|
+
select_first_value: true,
|
323
|
+
autocomplete_options: {}
|
324
|
+
},
|
325
|
+
|
326
|
+
_create: function() {
|
327
|
+
this.selectedValues = this.options.selected;
|
328
|
+
this._createUI();
|
329
|
+
this.expanded = false;
|
330
|
+
this.valuesLoaded = false;
|
331
|
+
this.afterLoaded = [];
|
332
|
+
if ( Array.isArray(this.options.source) ) {
|
333
|
+
this.options.preload = true;
|
334
|
+
this._initData(this.options.source);
|
335
|
+
} else if ( this.options.preload && this.options.load_immediately) {
|
336
|
+
this.load();
|
337
|
+
}
|
338
|
+
},
|
339
|
+
|
340
|
+
_createUI: function() {
|
341
|
+
var that = this;
|
342
|
+
this.element.wrap('<span class="easy-autocomplete-tag"></span>');
|
343
|
+
this.tag = this.element.parent();
|
344
|
+
this.inputName = this.options.inputName || this.element.prop('name');
|
345
|
+
|
346
|
+
if( this.options.multiple ) { // multiple values
|
347
|
+
this.valueElement = $('<span></span>');
|
348
|
+
this.tag.after(this.valueElement);
|
349
|
+
|
350
|
+
if (this.options.show_toggle_button)
|
351
|
+
this._createToggleButton();
|
352
|
+
|
353
|
+
this.valueElement.entityArray({
|
354
|
+
inputNames: this.inputName,
|
355
|
+
afterRemove: function (entity) {
|
356
|
+
that.element.trigger('change');
|
357
|
+
}
|
358
|
+
});
|
359
|
+
} else { //single value
|
360
|
+
this.valueElement = $('<input>', {type: 'hidden', name: this.inputName});
|
361
|
+
this.element.after(this.valueElement);
|
362
|
+
}
|
363
|
+
|
364
|
+
this._createAutocomplete();
|
365
|
+
if( !this.options.multiple ) {
|
366
|
+
this.element.css('margin-right', 0);
|
367
|
+
}
|
368
|
+
},
|
369
|
+
|
370
|
+
_createToggleButton: function() {
|
371
|
+
var that = this;
|
372
|
+
this.link_ac_toggle = $('<a>').attr('class', 'icon icon-add clear-link');
|
373
|
+
this.link_ac_toggle.click(function(evt) {
|
374
|
+
var $elem = $(this);
|
375
|
+
evt.preventDefault();
|
376
|
+
that.load(function(){
|
377
|
+
select = $('<select>').prop('multiple', true).prop('size', 5).prop('name', that.inputName);
|
378
|
+
$.each(that.possibleValues, function(i, v) {
|
379
|
+
option = $('<option>').prop('value', v.id).text(v.value);
|
380
|
+
option.prop('selected', that.getValue().indexOf(v.id) > -1);
|
381
|
+
select.append(option);
|
382
|
+
});
|
383
|
+
$container = $elem.closest('.easy-multiselect-tag-container')
|
384
|
+
$container.children().hide();
|
385
|
+
$container.append(select);
|
386
|
+
that.valueElement = select;
|
387
|
+
that.expanded = true;
|
388
|
+
});
|
389
|
+
});
|
390
|
+
this.element.parent().addClass('input-append');
|
391
|
+
this.element.after(this.link_ac_toggle);
|
392
|
+
},
|
393
|
+
|
394
|
+
_createAutocomplete: function() {
|
395
|
+
var that = this;
|
396
|
+
|
397
|
+
that.element.autocomplete($.extend({
|
398
|
+
source: function(request, response) {
|
399
|
+
if( that.options.preload ) {
|
400
|
+
that.load(function(){
|
401
|
+
var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i");
|
402
|
+
response($.grep(that.possibleValues, function(val, i) {
|
403
|
+
return ( !that.options.multiple || !request.term || matcher.test(val.value));
|
404
|
+
}));
|
405
|
+
}, function(){
|
406
|
+
response();
|
407
|
+
});
|
408
|
+
} else { // asking server everytime
|
409
|
+
$.getJSON(that.options.source, {
|
410
|
+
term: request.term
|
411
|
+
}, function(json) {
|
412
|
+
response(that.options.rootElement ? json[that.options.rootElement] : json);
|
413
|
+
});
|
414
|
+
}
|
415
|
+
},
|
416
|
+
minLength: 0,
|
417
|
+
select: function(event, ui) {
|
418
|
+
that.selectValue(ui.item)
|
419
|
+
return false;
|
420
|
+
},
|
421
|
+
change: function(event, ui) {
|
422
|
+
if (!ui.item) {
|
423
|
+
$(this).val('');
|
424
|
+
if( !that.options.multiple ) {
|
425
|
+
that.valueElement.val('');
|
426
|
+
that.valueElement.change();
|
427
|
+
}
|
428
|
+
}
|
429
|
+
},
|
430
|
+
position: this.options.position,
|
431
|
+
autoFocus: this.options.autofocus
|
432
|
+
}, this.options.autocomplete_options)).data("ui-autocomplete")._renderItem = this.options.render_item;
|
433
|
+
|
434
|
+
this.element.click(function() {
|
435
|
+
$(this).select();
|
436
|
+
});
|
437
|
+
if( this.options.activate_on_input_click ) {
|
438
|
+
this.element.on('click', function() {
|
439
|
+
if(!that.options.preload)
|
440
|
+
that.element.focus().val('');
|
441
|
+
that.element.trigger('keydown');
|
442
|
+
that.element.autocomplete("search", that.element.val());
|
443
|
+
});
|
444
|
+
}
|
445
|
+
|
446
|
+
$("<button type='button'> </button>")
|
447
|
+
.attr("tabIndex", -1)
|
448
|
+
.insertAfter(that.element)
|
449
|
+
.button({
|
450
|
+
icons: {
|
451
|
+
primary: "ui-icon-triangle-1-s"
|
452
|
+
},
|
453
|
+
text: false
|
454
|
+
})
|
455
|
+
.removeClass("ui-corner-all")
|
456
|
+
.addClass("ui-corner-right ui-button-icon")
|
457
|
+
.css('font-size', '10px')
|
458
|
+
.css('margin-left', -1)
|
459
|
+
.click(function() {
|
460
|
+
if (that.element.autocomplete("widget").is(":visible")) {
|
461
|
+
that.element.autocomplete("close");
|
462
|
+
that.element.blur();
|
463
|
+
return;
|
464
|
+
}
|
465
|
+
$(this).blur();
|
466
|
+
if(!that.options.preload)
|
467
|
+
that.element.focus().val('');
|
468
|
+
that.element.trigger('keydown');
|
469
|
+
that.element.autocomplete("search", that.element.val());
|
470
|
+
});
|
471
|
+
},
|
472
|
+
|
473
|
+
_formatData: function(data) {
|
474
|
+
return $.map(data, function(elem, i){
|
475
|
+
var id, value;
|
476
|
+
if( $.isArray(elem) ) {
|
477
|
+
value = elem[0];
|
478
|
+
id = elem[1];
|
479
|
+
} else {
|
480
|
+
id = value = elem;
|
481
|
+
}
|
482
|
+
return {value: value, id: id};
|
483
|
+
});
|
484
|
+
},
|
485
|
+
|
486
|
+
_initData: function(data) {
|
487
|
+
this.possibleValues = this._formatData(data)
|
488
|
+
this.valuesLoaded = true;
|
489
|
+
|
490
|
+
this.selectedValues = this.selectedValues ? this.selectedValues : [];
|
491
|
+
if( this.selectedValues.length == 0 && this.options.preload && this.options.select_first_value && this.possibleValues.length > 0 ) {
|
492
|
+
this.selectedValues.push(this.possibleValues[0]['id'])
|
493
|
+
}
|
494
|
+
|
495
|
+
this.setValue(this.selectedValues);
|
496
|
+
},
|
497
|
+
|
498
|
+
load: function(success, fail) {
|
499
|
+
var that = this;
|
500
|
+
if( this.valuesLoaded ) {
|
501
|
+
if( typeof success === 'function' )
|
502
|
+
success();
|
503
|
+
return;
|
504
|
+
}
|
505
|
+
|
506
|
+
if( typeof success === 'function' )
|
507
|
+
this.afterLoaded.push(success);
|
508
|
+
|
509
|
+
if( this.loading )
|
510
|
+
return;
|
511
|
+
|
512
|
+
this.loading = true;
|
513
|
+
$.ajax(this.options.source, {
|
514
|
+
dataType: 'json',
|
515
|
+
success: function(data, status, xhr) {
|
516
|
+
that._initData(data);
|
517
|
+
for (var i = that.afterLoaded.length - 1; i >= 0; i--) {
|
518
|
+
that.afterLoaded[i].call();
|
519
|
+
}
|
520
|
+
},
|
521
|
+
error: fail
|
522
|
+
}).always(function(){
|
523
|
+
that.loading = false;
|
524
|
+
});
|
525
|
+
},
|
526
|
+
|
527
|
+
selectValue: function(value) {
|
528
|
+
if( this.options.multiple ) {
|
529
|
+
this.valueElement.entityArray('add', {
|
530
|
+
id: value.id,
|
531
|
+
name: value.value
|
532
|
+
});
|
533
|
+
this.element.trigger('change');
|
534
|
+
} else {
|
535
|
+
this.element.val(value.value);
|
536
|
+
this.valueElement.val(value.id);
|
537
|
+
this.valueElement.change();
|
538
|
+
}
|
539
|
+
},
|
540
|
+
|
541
|
+
setValue: function(values) {
|
542
|
+
var that = this;
|
543
|
+
if( typeof values == 'undefined' || !values )
|
544
|
+
return false;
|
545
|
+
|
546
|
+
if( this.options.preload ) {
|
547
|
+
this.load(function(){
|
548
|
+
if( that.options.multiple ) {
|
549
|
+
that.valueElement.entityArray('clear');
|
550
|
+
}
|
551
|
+
that._setValues(values)
|
552
|
+
});
|
553
|
+
} else {
|
554
|
+
// TODO - where to get real text value?
|
555
|
+
this.element.val(values[0]);
|
556
|
+
this.valueElement.val(values[0]);
|
557
|
+
}
|
558
|
+
},
|
559
|
+
|
560
|
+
_setValues: function(values) {
|
561
|
+
var that = this;
|
562
|
+
$.each(that.possibleValues, function(i, val) {
|
563
|
+
if ( values.indexOf(val.id) > -1 || (values.indexOf(val.id.toString()) > -1)) {
|
564
|
+
if(that.options.multiple) {
|
565
|
+
that.valueElement.entityArray('add', { id: val.id, name: val.value });
|
566
|
+
} else {
|
567
|
+
that.element.val(val.value);
|
568
|
+
that.valueElement.val(val.id);
|
569
|
+
}
|
570
|
+
}
|
571
|
+
});
|
572
|
+
},
|
573
|
+
|
574
|
+
getValue: function() {
|
575
|
+
if( this.options.multiple && !this.expanded ) {
|
576
|
+
return this.valueElement.entityArray('getValue'); // entityArray
|
577
|
+
} else if ( this.options.multiple ) {
|
578
|
+
return this.valueElement.val(); //select multiple=true
|
579
|
+
} else {
|
580
|
+
return [this.valueElement.val()]; // hidden field
|
581
|
+
}
|
582
|
+
}
|
583
|
+
|
584
|
+
});
|
585
|
+
|
586
|
+
})(jQuery);
|
@@ -44,6 +44,142 @@ module RedmineExtensions
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
+
def render_entity_assignments(entity, target_class, options = {}, &block)
|
48
|
+
options ||= {}
|
49
|
+
collection_name = options.delete(:collection_name) || target_class.name.pluralize.underscore
|
50
|
+
|
51
|
+
project = options.delete(:project)
|
52
|
+
query_class = options.delete(:query_class)
|
53
|
+
|
54
|
+
if query_class.nil?
|
55
|
+
query_class_name = target_class.name + 'Query'
|
56
|
+
|
57
|
+
query_class = query_class_name.constantize #if Object.const_defined?(query_class_name)
|
58
|
+
end
|
59
|
+
|
60
|
+
return '' if !query_class || !(query_class < EasyQuery) || !entity.respond_to?(collection_name)
|
61
|
+
|
62
|
+
query = query_class.new(:name => 'c_query')
|
63
|
+
query.project = project
|
64
|
+
query.set_entity_scope(entity, collection_name)
|
65
|
+
query.column_names = options[:query_column_names] unless options[:query_column_names].blank?
|
66
|
+
|
67
|
+
entities = query.entities
|
68
|
+
|
69
|
+
entities_count = entities.size
|
70
|
+
options[:entities_count] = entities_count
|
71
|
+
|
72
|
+
options[:module_name] ||= "entity_#{entity.class.name.underscore}_#{entity.id}_#{collection_name}"
|
73
|
+
options[:heading] ||= l("label_#{target_class.name.underscore}_plural", :default => 'Heading')
|
74
|
+
|
75
|
+
if options[:context_menus_path].nil?
|
76
|
+
options[:context_menus_path] = [
|
77
|
+
"context_menu_#{collection_name}_path".to_sym,
|
78
|
+
"context_menus_#{collection_name}_path".to_sym,
|
79
|
+
"#{collection_name}_context_menu_path".to_sym
|
80
|
+
].detect do |m|
|
81
|
+
m if respond_to?(m)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
render(:partial => 'easy_entity_assignments/assignments_container', :locals => {
|
86
|
+
:entity => entity,
|
87
|
+
:query => query, :target_class => target_class, :project => project,
|
88
|
+
:entities => entities, :entities_count => entities_count, :options => options})
|
89
|
+
end
|
90
|
+
|
91
|
+
def entity_css_icon(entity_or_entity_class)
|
92
|
+
return '' if entity_or_entity_class.nil?
|
93
|
+
|
94
|
+
if entity_or_entity_class.is_a?(Class) && entity_or_entity_class.respond_to?(:css_icon)
|
95
|
+
entity_or_entity_class.css_icon
|
96
|
+
elsif entity_or_entity_class.is_a?(ActiveRecord::Base)
|
97
|
+
if entity_or_entity_class.respond_to?(:css_icon)
|
98
|
+
entity_or_entity_class.css_icon
|
99
|
+
elsif entity_or_entity_class.class.respond_to?(:css_icon)
|
100
|
+
entity_or_entity_class.class.css_icon
|
101
|
+
else
|
102
|
+
"icon icon-#{entity_or_entity_class.class.name.dasherize}"
|
103
|
+
end
|
104
|
+
else
|
105
|
+
"icon icon-#{entity_or_entity_class.class.name.dasherize}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# options:
|
110
|
+
# => options[:heading] = text beside of plus button
|
111
|
+
# => options[:container_html] = a hash of html attributes
|
112
|
+
# => options[:default_button_state] = (true => expanded -), (false => collapsed +)
|
113
|
+
# => options[:ajax_call] = make ajax call for saving state (true => ajax call, false => no call, no save)
|
114
|
+
# => options[:wrapping_heading_element] = html element outside heading => h3, h4
|
115
|
+
def render_toggler(container_uniq_id, user = nil, options={}, &block)
|
116
|
+
user ||= User.current
|
117
|
+
options[:heading] ||= ''
|
118
|
+
options[:heading_links] ||= []
|
119
|
+
options[:heading_links] = [options[:heading_links]] if options[:heading_links] && !options[:heading_links].is_a?(Array)
|
120
|
+
options[:container_html] ||= {}
|
121
|
+
options[:default_button_state] = false #if is_mobile_device?
|
122
|
+
options[:default_button_state] = true if options[:default_button_state].nil?
|
123
|
+
options[:ajax_call] = true if options[:ajax_call].nil?
|
124
|
+
|
125
|
+
s = ''
|
126
|
+
if !options.key?(:no_heading_button)
|
127
|
+
options[:heading] << content_tag(:div, options[:heading_links].join(' ').html_safe, :class => 'module-heading-links') unless options[:heading_links].blank?
|
128
|
+
s << render_toggler_header(user, options[:heading].html_safe, container_uniq_id, options)
|
129
|
+
end
|
130
|
+
|
131
|
+
if options[:ajax_call] == false
|
132
|
+
expanded = options[:default_button_state]
|
133
|
+
else
|
134
|
+
expanded = true
|
135
|
+
end
|
136
|
+
|
137
|
+
s << (content_tag(:div, {
|
138
|
+
:id => container_uniq_id,
|
139
|
+
:style => (expanded ? '' : 'display:none')
|
140
|
+
}.merge(options[:container_html]) { |k, o, n| "#{o}; #{n}" }, &block))
|
141
|
+
s.html_safe
|
142
|
+
end
|
143
|
+
|
144
|
+
def render_toggler_header(user, content, modul_uniq_id, options={})
|
145
|
+
expander_options = options[:expander_options] || {}
|
146
|
+
wrapping_heading_element = options[:wrapping_heading_element] || 'h3'
|
147
|
+
wrapping_heading_element_classes = (options[:wrapping_heading_element_classes] || '') + ' module-heading'
|
148
|
+
wrapping_heading_element_styles = options[:wrapping_heading_element_styles]
|
149
|
+
ajax_call = options.delete(:ajax_call) ? 'true' : 'false'
|
150
|
+
|
151
|
+
html = ''
|
152
|
+
|
153
|
+
if options[:no_expander]
|
154
|
+
html << content_tag(wrapping_heading_element, content, :class => wrapping_heading_element_classes, :style => wrapping_heading_element_styles)
|
155
|
+
else
|
156
|
+
html << '<div class="module-toggle-button">'
|
157
|
+
html << "<div class='group open' >"
|
158
|
+
html << content_tag(wrapping_heading_element, content, :class => wrapping_heading_element_classes, :style => wrapping_heading_element_styles, :onclick => "var event = arguments[0] || window.event; if( !$(event.target).hasClass('do_not_toggle') && !$(event.target).parent().hasClass('module-heading-links') ) toggleMyPageModule(this,'#{modul_uniq_id}','#{user.id}', #{ajax_call})")
|
159
|
+
html << "<span class='expander #{expander_options[:class]}' onclick=\"toggleMyPageModule($(this),'#{modul_uniq_id}','#{user.id}', #{ajax_call}); return false;\" id=\"expander_#{modul_uniq_id}\"> </span>"
|
160
|
+
html << '</div></div>'
|
161
|
+
end
|
162
|
+
|
163
|
+
html.html_safe
|
164
|
+
end
|
165
|
+
|
166
|
+
def autocomplete_field_tag(name, jsonpath_or_array, selected_values, options = {})
|
167
|
+
options.reverse_merge!({select_first_value: false, show_toggle_button: false, load_immediately: false})
|
168
|
+
options[:id] ||= sanitize_to_id(name)
|
169
|
+
|
170
|
+
selected_values ||= []
|
171
|
+
|
172
|
+
if jsonpath_or_array.is_a?(Array)
|
173
|
+
source = jsonpath_or_array.to_json
|
174
|
+
else
|
175
|
+
source = "'#{jsonpath_or_array}'"
|
176
|
+
end
|
177
|
+
|
178
|
+
content_tag(:span, :class => 'easy-multiselect-tag-container') do
|
179
|
+
text_field_tag('', '', (options[:html_options] || {}).merge(id: options[:id])) +
|
180
|
+
javascript_tag("$('##{options[:id]}').easymultiselect({multiple: true, inputName: '#{name}', preload: true, source: #{source}, selected: #{selected_values.to_json}, show_toggle_button: #{options[:show_toggle_button]}, select_first_value: #{options[:select_first_value]}, load_immediately: #{options[:load_immediately]}, autocomplete_options: #{(options[:jquery_auto_complete_options]||{}).to_json} });")
|
181
|
+
end
|
182
|
+
end
|
47
183
|
|
48
184
|
end
|
49
185
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<%
|
2
|
+
display_style = options[:display_style] || (entities_count > 3 ? :list : :tile)
|
3
|
+
heading = "#{options[:heading]} (#{entities_count})"
|
4
|
+
%>
|
5
|
+
<% if entities_count > 0 %>
|
6
|
+
<div id="<%= options[:module_name] %>_container" class="entity_references_container easy-dropper-target easy-drop-issue <%= display_style.to_s %>" data-drop-action="<%= entity.class.name.underscore %>" data-entity-id="<%= entity.id %>" data-issue-show="1">
|
7
|
+
<%= call_hook(:view_easy_entity_assignemnt_top, :entity => entity, :query => query, :entities => entities, :options => options, :target_class => target_class) %>
|
8
|
+
<div class="box">
|
9
|
+
<%# entity_cards_params = {:module_name => options[:module_name], :project_id => project, :source_entity_type => entity.class.name, :source_entity_id => entity, :referenced_entity_type => referenced_entity_type, :referenced_collection_name => options[:referenced_collection_name]}.merge!(query.to_params) %>
|
10
|
+
<% entity_cards_params = {} %>
|
11
|
+
<%= render_toggler(options[:module_name], User.current, {:heading => heading + ':', :wrapping_heading_element_classes => entity_css_icon(target_class),
|
12
|
+
:container_html => {:class => 'module-inside'}, :default_button_state => false, :heading_links => [
|
13
|
+
link_to('', {}, :class => 'icon-slab', :remote => true, :data => {:display => 'slab'}, :title => l(:title_easy_card_display_changer_tile)),
|
14
|
+
link_to('', {}, :class => 'icon-bullet-list', :remote => true, :data => {:display => 'list'}, :title => l(:title_easy_card_display_changer_list))
|
15
|
+
]}) do %>
|
16
|
+
|
17
|
+
|
18
|
+
<%= render(:partial => 'easy_entity_assignments/query_index', :locals => {
|
19
|
+
:entity => entity,
|
20
|
+
:query => query, :target_class => target_class,
|
21
|
+
:project => project, :options => options, :entities => entities, :display_style => display_style}) %>
|
22
|
+
|
23
|
+
|
24
|
+
<% if options[:context_menus_path] && respond_to?(options[:context_menus_path]) %>
|
25
|
+
<%# context_menu(send(options[:context_menus_path], {:project_id => project, :back_url => url_for(params)}), "##{options[:module_name]}_container") -%>
|
26
|
+
<% end %>
|
27
|
+
<% end %>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% if display_style == :list %>
|
2
|
+
<%= render :partial => 'easy_queries/easy_query_entities_list', :locals => {:query => query, :entities => referenced_entities, :options => {:hascontextmenu => true, :disable_sort => true} } -%>
|
3
|
+
<% elsif display_style == :tile %>
|
4
|
+
<div class="easy-entity-cards-container">
|
5
|
+
<div class="splitcontent">
|
6
|
+
<% entities.each do |referenced_entity| %>
|
7
|
+
<%# render_easy_entity_card(referenced_entity, entity, options.merge(:referenced_entities => referenced_entities)) %>
|
8
|
+
<% end %>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
<% end %>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreateEntityAssignments < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
unless table_exists?(:easy_entity_assignments)
|
4
|
+
create_table :easy_entity_assignments do |t|
|
5
|
+
t.references :entity_from, :polymorphic => true
|
6
|
+
t.references :entity_to, :polymorphic => true
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :easy_entity_assignments, [:entity_from_type, :entity_from_id, :entity_to_type, :entity_to_id], :name => 'entity_assignment_idx', :unique => true
|
11
|
+
add_index :easy_entity_assignments, :entity_from_id, :name => 'entity_assignment_idx_from'
|
12
|
+
add_index :easy_entity_assignments, :entity_to_id, :name => 'entity_assignment_idx_to'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.down
|
17
|
+
drop_table :easy_entity_assignments
|
18
|
+
end
|
19
|
+
end
|
@@ -48,7 +48,11 @@ module RedmineExtensions
|
|
48
48
|
|
49
49
|
if File.exists?("#{plugin_path}/config/locales/en.yml")
|
50
50
|
append_to_file "#{plugin_path}/config/locales/en.yml" do
|
51
|
-
"\n
|
51
|
+
"\n activerecord:" +
|
52
|
+
"\n easy_query:" +
|
53
|
+
"\n attributes:" +
|
54
|
+
"\n #{model_name_underscored}:" +
|
55
|
+
db_columns.collect { |column_name, column_options| "\n #{column_options[:lang_key]}: #{column_name.humanize}" }.join +
|
52
56
|
"\n name:" +
|
53
57
|
"\n #{model_name_underscored}_query: #{model_name_pluralize_underscored.titleize}" +
|
54
58
|
"\n heading_#{model_name_underscored}_new: New #{model_name_underscored.titleize}" +
|
@@ -60,9 +64,6 @@ module RedmineExtensions
|
|
60
64
|
"\n permission_manage_#{model_name_pluralize_underscored}: Manage #{model_name_pluralize_underscored.titleize}" +
|
61
65
|
"\n title_#{model_name_underscored}_new: Click to create new #{model_name_underscored.titleize}"
|
62
66
|
end
|
63
|
-
append_to_file "#{plugin_path}/config/locales/en.yml" do
|
64
|
-
db_columns.collect { |column_name, column_options| "\n #{column_options[:lang_key]}: #{column_name.humanize}" }.join
|
65
|
-
end
|
66
67
|
else
|
67
68
|
template 'en.yml.erb', "#{plugin_path}/config/locales/en.yml"
|
68
69
|
end
|
@@ -96,6 +97,7 @@ module RedmineExtensions
|
|
96
97
|
append_to_file "#{plugin_path}/config/routes.rb" do
|
97
98
|
"\nresources :#{model_name_pluralize_underscored} do" +
|
98
99
|
"\n collection do " +
|
100
|
+
"\n get 'autocomplete'" +
|
99
101
|
"\n get 'bulk_edit'" +
|
100
102
|
"\n post 'bulk_update'" +
|
101
103
|
"\n get 'context_menu'" +
|
@@ -111,8 +113,8 @@ module RedmineExtensions
|
|
111
113
|
s << "\n require '#{plugin_name_underscored}/#{model_name_underscored}_hooks'\n"
|
112
114
|
s << "\n Redmine::AccessControl.map do |map|"
|
113
115
|
s << "\n map.project_module :#{model_name_pluralize_underscored} do |pmap|"
|
114
|
-
s << "\n pmap.permission :view_#{model_name_pluralize_underscored}, { #{model_name_pluralize_underscored}: [:index, :show] }, read: true"
|
115
|
-
s << "\n pmap.permission :manage_#{model_name_pluralize_underscored}, { #{model_name_pluralize_underscored}: [:new, :create, :edit, :update, :destroy, :bulk_edit, :bulk_update
|
116
|
+
s << "\n pmap.permission :view_#{model_name_pluralize_underscored}, { #{model_name_pluralize_underscored}: [:index, :show, :autocomplete, :context_menu] }, read: true"
|
117
|
+
s << "\n pmap.permission :manage_#{model_name_pluralize_underscored}, { #{model_name_pluralize_underscored}: [:new, :create, :edit, :update, :destroy, :bulk_edit, :bulk_update] }"
|
116
118
|
s << "\n end "
|
117
119
|
s << "\n end\n"
|
118
120
|
s << "\n Redmine::MenuManager.map :top_menu do |menu|"
|
@@ -201,7 +203,8 @@ module RedmineExtensions
|
|
201
203
|
|
202
204
|
attributes.each do |attr|
|
203
205
|
attr_name, attr_type, attr_idx = attr.split(':')
|
204
|
-
lang_key = "field_#{model_name_underscored}_#{attr_name.to_s.sub(/_id$/, '').sub(/^.+\./, '')}"
|
206
|
+
#lang_key = "field_#{model_name_underscored}_#{attr_name.to_s.sub(/_id$/, '').sub(/^.+\./, '')}"
|
207
|
+
lang_key = "#{attr_name.to_s.sub(/_id$/, '').sub(/^.+\./, '')}"
|
205
208
|
|
206
209
|
@db_columns[attr_name] = {type: attr_type || 'string', idx: attr_idx, null: true, safe: true, query_type: attr_type || 'string', lang_key: lang_key}
|
207
210
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<%- if project? -%>
|
3
3
|
<%% if <%= model_name_underscored %>.safe_attribute?('project_id') && !@project %>
|
4
4
|
<p>
|
5
|
-
<%%= f.label :project_id,
|
5
|
+
<%%= f.label :project_id, ::<%= model_name %>.human_attribute_name(:project_id) %>
|
6
6
|
<%%= f.select :project_id, Project.allowed_to(:manage_<%= model_name_pluralize_underscored %>).collect{|x| [x.name, x.id]}, include_blank: true %>
|
7
7
|
</p>
|
8
8
|
<%% end %>
|
@@ -10,7 +10,7 @@
|
|
10
10
|
<%- safe_columns.each do |column_name, column_options| -%>
|
11
11
|
<%% if <%= model_name_underscored %>.safe_attribute?('<%= column_name %>') %>
|
12
12
|
<p>
|
13
|
-
<%%= f.label :<%= column_name %>,
|
13
|
+
<%%= f.label :<%= column_name %>, ::<%= model_name %>.human_attribute_name(:<%= column_name %>) %>
|
14
14
|
<%- if column_options[:query_type] == 'string' || column_options[:query_type] == 'integer' -%>
|
15
15
|
<%%= f.text_field :<%= column_name %> %>
|
16
16
|
<%- elsif column_options[:query_type] == 'list' || column_options[:query_type] == 'list_optional' -%>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<li><%%= link_to l(:button_edit), edit_polymorphic_path([@project, @<%= model_name_underscored %>]), title: l(:button_edit), class: 'icon icon-edit' %></li>
|
4
4
|
<%% end %>
|
5
5
|
<%% if @<%= model_name_underscored %>.nil? %>
|
6
|
-
<li><%%= link_to l(:button_<%= model_name_underscored %>_new), new_polymorphic_path([@project, :<%= model_name_underscored %>]), title: l(:title_<%= model_name_underscored %>_new), class: 'icon icon-add' %></li>
|
6
|
+
<li><%%= link_to l(:button_<%= model_name_underscored %>_new), new_polymorphic_path([@project, :<%= model_name_underscored %>]), title: l(:title_<%= model_name_underscored %>_new), class: 'icon icon-add button button-positive' %></li>
|
7
7
|
<%% end %>
|
8
|
-
<li><%%= link_to l(:label_<%= model_name_pluralize_underscored %>), polymorphic_path([@project,
|
8
|
+
<li><%%= link_to l(:label_<%= model_name_pluralize_underscored %>), polymorphic_path([@project, :<%= model_name_pluralize_underscored %>], set_filter: '1'), title: l(:label_<%= model_name_pluralize_underscored %>), class: 'icon icon-folder button' %></li>
|
9
9
|
</ul>
|
@@ -1,4 +1,8 @@
|
|
1
1
|
en:
|
2
|
+
activerecord:
|
3
|
+
attributes:
|
4
|
+
<%= model_name_underscored %>:
|
5
|
+
<%= db_columns.collect{|column_name, column_options| "\n #{column_options[:lang_key]}: #{column_name.humanize}" }.join %>
|
2
6
|
easy_query:
|
3
7
|
name:
|
4
8
|
<%= model_name_underscored %>_query: <%= model_name_pluralize_underscored.titleize %>
|
@@ -10,4 +14,3 @@ en:
|
|
10
14
|
permission_view_<%= model_name_pluralize_underscored %>: View <%= model_name_pluralize_underscored.titleize %>
|
11
15
|
permission_manage_<%= model_name_pluralize_underscored %>: Manage <%= model_name_pluralize_underscored.titleize %>
|
12
16
|
title_<%= model_name_underscored %>_new: Click to create new <%= model_name_underscored.titleize %>
|
13
|
-
<%= db_columns.collect{|column_name, column_options| "\n #{column_options[:lang_key]}: #{column_name.humanize}" }.join %>
|
@@ -53,6 +53,10 @@ class <%= model_name %> < ActiveRecord::Base
|
|
53
53
|
after_update :send_update_notification
|
54
54
|
<%- end -%>
|
55
55
|
|
56
|
+
def self.human_attribute_name(attribute, *args)
|
57
|
+
l(attribute.to_s.gsub(/\_id$/, ''), :scope => [:activerecord, :attributes, :<%= model_name_underscored %>])
|
58
|
+
end
|
59
|
+
|
56
60
|
def self.visible_condition(user, options={})
|
57
61
|
<%- if project? -%>
|
58
62
|
Project.allowed_to_condition(user, :<%= view_permission %>, options)
|
@@ -6,10 +6,10 @@ class <%= model_name %>Query < EasyQuery
|
|
6
6
|
<%- db_columns.each do |column_name, column_options| -%>
|
7
7
|
<%- if project? && column_name == 'project_id' -%>
|
8
8
|
if project.nil?
|
9
|
-
add_available_filter '<%= column_name %>', name:
|
9
|
+
add_available_filter '<%= column_name %>', name: ::<%= model_name %>.human_attribute_name(:<%= column_name %>), type: :<%= column_options[:query_type] %>, values: all_projects_values
|
10
10
|
end
|
11
11
|
<%- else -%>
|
12
|
-
add_available_filter '<%= column_name %>', name:
|
12
|
+
add_available_filter '<%= column_name %>', name: ::<%= model_name %>.human_attribute_name(:<%= column_name %>), type: :<%= column_options[:query_type] -%>
|
13
13
|
<%- end -%>
|
14
14
|
<%- end -%>
|
15
15
|
<%- if acts_as_customizable? -%>
|
@@ -21,7 +21,7 @@ class <%= model_name %>Query < EasyQuery
|
|
21
21
|
return @available_columns if @available_columns
|
22
22
|
|
23
23
|
<%- db_columns.each do |column_name, column_options| -%>
|
24
|
-
add_available_column '<%= column_options[:query_column_name] || column_name %>',
|
24
|
+
add_available_column '<%= column_options[:query_column_name] || column_name %>', caption: ::<%= model_name %>.human_attribute_name(:<%= column_name %>)
|
25
25
|
<%- end -%>
|
26
26
|
<%- if acts_as_customizable? -%>
|
27
27
|
@available_columns += <%= model_name %>CustomField.visible.collect {|cf| QueryCustomFieldColumn.new(cf) }
|
@@ -5,9 +5,9 @@
|
|
5
5
|
<%- safe_columns.each_with_index do |column, idx| -%>
|
6
6
|
<%- next if description_column? && column[0] == description_column -%>
|
7
7
|
<%- if idx % 2 == 0 -%>
|
8
|
-
<%% rows.left
|
8
|
+
<%% rows.left ::<%= model_name %>.human_attribute_name(:<%= column[0] %>), format_object(@<%= model_name_underscored %>.<%= column[0] %>) %>
|
9
9
|
<%- else -%>
|
10
|
-
<%% rows.right
|
10
|
+
<%% rows.right ::<%= model_name %>.human_attribute_name(:<%= column[0] %>), format_object(@<%= model_name_underscored %>.<%= column[0] %>) %>
|
11
11
|
<%- end -%>
|
12
12
|
<%- end -%>
|
13
13
|
<%% end %>
|
@@ -18,7 +18,7 @@
|
|
18
18
|
<%% if !@<%= model_name_underscored %>.<%= description_column %>.blank? %>
|
19
19
|
<hr />
|
20
20
|
<div class="description">
|
21
|
-
<p><strong><%%=
|
21
|
+
<p><strong><%%= ::<%= model_name %>.human_attribute_name(:<%= description_column %>) %></strong></p>
|
22
22
|
<div class="wiki">
|
23
23
|
<%%= textilizable @<%= model_name_underscored %>, :<%= description_column %>, attachments: @<%= model_name_underscored %>.attachments %>
|
24
24
|
</div>
|
@@ -1 +1 @@
|
|
1
|
-
gem 'redmine_extensions'
|
1
|
+
gem 'redmine_extensions' unless Dir.exist?(File.expand_path('../../easyproject', __FILE__))
|
@@ -63,8 +63,12 @@ class EasyQueryAdapter < Query
|
|
63
63
|
@formatter = formatter_klass.new(object)
|
64
64
|
end
|
65
65
|
|
66
|
+
def entity
|
67
|
+
self.class.queried_class
|
68
|
+
end
|
69
|
+
|
66
70
|
def entity_scope
|
67
|
-
if
|
71
|
+
if !@entity_scope.nil?
|
68
72
|
@entity_scope
|
69
73
|
elsif entity.respond_to?(:visible)
|
70
74
|
entity.visible
|
@@ -73,6 +77,16 @@ class EasyQueryAdapter < Query
|
|
73
77
|
end
|
74
78
|
end
|
75
79
|
|
80
|
+
def set_entity_scope(entity, reference_collection = nil)
|
81
|
+
return nil if entity.nil? || reference_collection.nil?
|
82
|
+
|
83
|
+
@entity_scope = entity.send(reference_collection.to_sym)
|
84
|
+
|
85
|
+
self.filters = {}
|
86
|
+
|
87
|
+
@entity_scope
|
88
|
+
end
|
89
|
+
|
76
90
|
def create_entity_scope(options={})
|
77
91
|
scope = entity_scope.where(statement)
|
78
92
|
|
@@ -111,7 +125,7 @@ class EasyQueryAdapter < Query
|
|
111
125
|
end
|
112
126
|
|
113
127
|
def entities(options={})
|
114
|
-
create_entity_scope(options).order(options[:order])
|
128
|
+
create_entity_scope(options).order(options[:order]).to_a
|
115
129
|
end
|
116
130
|
|
117
131
|
def add_available_column(name, options={})
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redmine_extensions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.29
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Easy Software Ltd
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -105,14 +105,18 @@ files:
|
|
105
105
|
- LICENSE
|
106
106
|
- Rakefile
|
107
107
|
- app/assets/javascripts/redmine_extensions/application.js
|
108
|
+
- app/assets/javascripts/redmine_extensions/jquery.entityarray.js
|
108
109
|
- app/assets/javascripts/redmine_extensions/redmine_extensions.js
|
109
110
|
- app/assets/stylesheets/redmine_extensions/application.css
|
110
111
|
- app/controllers/easy_settings_controller.rb
|
111
112
|
- app/helpers/redmine_extensions/application_helper.rb
|
112
113
|
- app/helpers/redmine_extensions/rendering_helper.rb
|
114
|
+
- app/models/easy_entity_assignment.rb
|
113
115
|
- app/models/easy_setting.rb
|
114
116
|
- app/presenters/redmine_extensions/base_presenter.rb
|
115
117
|
- app/presenters/redmine_extensions/easy_setting_presenter.rb
|
118
|
+
- app/views/easy_entity_assignments/_assignments_container.html.erb
|
119
|
+
- app/views/easy_entity_assignments/_query_index.html.erb
|
116
120
|
- app/views/easy_queries/_entities.html.erb
|
117
121
|
- app/views/easy_queries/_index.html.erb
|
118
122
|
- app/views/easy_queries/_settings.html.erb
|
@@ -121,6 +125,7 @@ files:
|
|
121
125
|
- config/locales/en.yml
|
122
126
|
- config/routes.rb
|
123
127
|
- db/migrate/20150705172511_create_easy_settings.rb
|
128
|
+
- db/migrate/20160519161300_create_entity_assignments.rb
|
124
129
|
- lib/generators/redmine_extensions/entity/USAGE
|
125
130
|
- lib/generators/redmine_extensions/entity/entity_generator.rb
|
126
131
|
- lib/generators/redmine_extensions/entity/templates/_form.html.erb.erb
|