togo 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/togo-admin +5 -5
- data/lib/togo/admin/admin.rb +45 -3
- data/lib/togo/admin/helpers.rb +0 -0
- data/lib/togo/admin/public/css/screen.css +43 -3
- data/lib/togo/admin/public/js/edit.js +35 -0
- data/lib/togo/admin/public/js/index.js +17 -0
- data/lib/togo/admin/public/js/togo.js +372 -1
- data/lib/togo/admin/views/edit.erb +3 -2
- data/lib/togo/admin/views/index.erb +11 -2
- data/lib/togo/dispatch/dispatch.rb +17 -7
- data/lib/togo/model/model.rb +50 -3
- data/lib/togo/model/types/belongs_to.erb +48 -12
- data/lib/togo/model/types/datetime.erb +1 -1
- data/lib/togo/model/types/has_n.erb +41 -10
- metadata +7 -5
data/bin/togo-admin
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'optparse'
|
4
|
-
files_to_require =
|
4
|
+
files_to_require = %w( dm-core dm-serializer )
|
5
5
|
|
6
6
|
config = {
|
7
7
|
:environment => :development,
|
@@ -18,9 +18,10 @@ OptionParser.new{|opt|
|
|
18
18
|
puts "Usage: togo-admin [options]"
|
19
19
|
puts "Options: "
|
20
20
|
puts "\t-p [port]: which port to run on"
|
21
|
-
puts "\t-
|
21
|
+
puts "\t-h [host]: which host to run on"
|
22
22
|
puts "\t-e [environment]: which environment to boot in"
|
23
|
-
puts "\t-r [file]:
|
23
|
+
puts "\t-r [file]: specify a file to require before running"
|
24
|
+
puts "\t-a [handler]: which handler to run the server with (e.g. Thin, Webrick, etc)"
|
24
25
|
puts "\t--help: what you're seeing"
|
25
26
|
puts "TIP: To load code automatically before running togo-admin, create a file called togo-admin-config.rb."
|
26
27
|
exit
|
@@ -42,5 +43,4 @@ config.merge!({
|
|
42
43
|
:reloader => Togo::TogoReloader
|
43
44
|
})
|
44
45
|
|
45
|
-
Togo::Admin.
|
46
|
-
Togo::Admin.run!
|
46
|
+
Togo::Admin.run!(config)
|
data/lib/togo/admin/admin.rb
CHANGED
@@ -1,9 +1,30 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Helpers
|
2
|
+
|
3
|
+
def paging_links(page, count, qs = {})
|
4
|
+
prev_link, next_link = 'Previous', 'Next'
|
5
|
+
if not qs.blank?
|
6
|
+
qs = qs.keys.collect{|k|
|
7
|
+
[k,escape(qs[k])].join('=') if not qs[k].blank?
|
8
|
+
}.compact.join('&')
|
9
|
+
qs = nil if qs.blank?
|
10
|
+
end
|
11
|
+
|
12
|
+
if not page == 1
|
13
|
+
prev_link = "<a href=\"?p=#{[page-1, qs].compact.join('&')}\" rel=\"previous\">#{prev_link}</a>"
|
14
|
+
end
|
15
|
+
if not page == count and count > 1
|
16
|
+
next_link = "<a href=\"?p=#{[page+1, qs].compact.join('&')}\" rel=\"next\">#{next_link}</a>"
|
17
|
+
end
|
18
|
+
[prev_link, next_link]
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
3
22
|
|
4
23
|
module Togo
|
5
24
|
class Admin < Dispatch
|
6
25
|
|
26
|
+
include Helpers
|
27
|
+
|
7
28
|
before do
|
8
29
|
@model = Togo.const_get(params[:model]) if params[:model]
|
9
30
|
end
|
@@ -13,7 +34,14 @@ module Togo
|
|
13
34
|
end
|
14
35
|
|
15
36
|
get '/:model' do
|
16
|
-
@
|
37
|
+
@q = params[:q] || ''
|
38
|
+
@p = (params[:p] || 1).to_i
|
39
|
+
@limit = 50
|
40
|
+
@offset = @limit*(@p-1)
|
41
|
+
@count = (@q.blank? ? @model.all : @model.search(:q => @q)).size
|
42
|
+
@page_count = @count == 0 ? 1 : (@count.to_f/@limit.to_f).ceil
|
43
|
+
@criteria = {:limit => @limit, :offset => @offset}
|
44
|
+
@content = @q.blank? ? @model.all(@criteria) : @model.search(@criteria.merge(:q => @q))
|
17
45
|
erb :index
|
18
46
|
end
|
19
47
|
|
@@ -63,6 +91,20 @@ module Togo
|
|
63
91
|
end
|
64
92
|
end
|
65
93
|
|
94
|
+
get '/search/:model' do
|
95
|
+
@limit = params[:limit] || 10
|
96
|
+
@offset = params[:offset] || 0
|
97
|
+
@q = params[:q] || ''
|
98
|
+
@count = (@q.blank? ? @model.all : @model.search(:q => @q)).size
|
99
|
+
@criteria = {:offset => @offset, :limit => @limit}
|
100
|
+
if params[:ids]
|
101
|
+
@items = @model.all(@criteria.merge(:id => params[:ids].split(',').map(&:to_i)))
|
102
|
+
else
|
103
|
+
@items = @model.search(@criteria.merge(:q => @q))
|
104
|
+
end
|
105
|
+
{:count => @count, :results => @items}.to_json
|
106
|
+
end
|
107
|
+
|
66
108
|
end
|
67
109
|
|
68
110
|
# Subclass Rack Reloader to call DataMapper.auto_upgrade! on file reload
|
File without changes
|
@@ -71,7 +71,7 @@ body {
|
|
71
71
|
position: absolute;
|
72
72
|
right: 0;
|
73
73
|
z-index: 3;
|
74
|
-
width:
|
74
|
+
width: 220px;
|
75
75
|
top: 0;
|
76
76
|
}
|
77
77
|
#main #search-form fieldset {
|
@@ -80,6 +80,12 @@ body {
|
|
80
80
|
#main #search-form input[type=text] {
|
81
81
|
width: 190px;
|
82
82
|
}
|
83
|
+
#main #paging {
|
84
|
+
position: absolute;
|
85
|
+
right: 240px;
|
86
|
+
z-index: 3;
|
87
|
+
top: 22px;
|
88
|
+
}
|
83
89
|
#main h1 a {
|
84
90
|
text-decoration: none;
|
85
91
|
color: #4076c7;
|
@@ -105,7 +111,8 @@ body {
|
|
105
111
|
border-bottom: 1px solid #CECECE;
|
106
112
|
background: #FFF;
|
107
113
|
}
|
108
|
-
#main table td.checkbox
|
114
|
+
#main table td.checkbox,
|
115
|
+
#main table th.checkbox {
|
109
116
|
width: 10px;
|
110
117
|
text-align: center;
|
111
118
|
padding-right: 0;
|
@@ -154,11 +161,44 @@ body {
|
|
154
161
|
#main form fieldset.has_n {
|
155
162
|
padding-right: 20px;
|
156
163
|
}
|
164
|
+
#main form fieldset.belongs_to input[type=text],
|
165
|
+
#main form fieldset.has_n input[type=text] {
|
166
|
+
width: 190px;
|
167
|
+
}
|
168
|
+
#main form fieldset.belongs_to label,
|
169
|
+
#main form fieldset.has_n label {
|
170
|
+
float: left;
|
171
|
+
}
|
172
|
+
#main form fieldset.belongs_to div.search,
|
173
|
+
#main form fieldset.has_n div.search {
|
174
|
+
display: none;
|
175
|
+
width: 100%;
|
176
|
+
text-align: right;
|
177
|
+
}
|
178
|
+
#main form fieldset.belongs_to div.paging,
|
179
|
+
#main form fieldset.has_n div.paging {
|
180
|
+
float: right;
|
181
|
+
margin-left: 10px;
|
182
|
+
line-height: 30px;
|
183
|
+
}
|
184
|
+
#main form fieldset.belongs_to .checkbox,
|
185
|
+
#main form fieldset.has_n .checkbox {
|
186
|
+
display: none;
|
187
|
+
}
|
188
|
+
#main form fieldset.belongs_to .active,
|
189
|
+
#main form fieldset.has_n .active {
|
190
|
+
display: table-cell;
|
191
|
+
}
|
157
192
|
#main form fieldset table th,
|
158
193
|
#main form fieldset table td {
|
159
194
|
padding: 5px;
|
160
195
|
}
|
161
|
-
|
196
|
+
#main form fieldset table tfoot td {
|
197
|
+
background: #DDD;
|
198
|
+
}
|
199
|
+
#main form fieldset table tfoot td button {
|
200
|
+
float: right;
|
201
|
+
}
|
162
202
|
div.actions {
|
163
203
|
position: absolute;
|
164
204
|
bottom: 0;
|
@@ -0,0 +1,35 @@
|
|
1
|
+
var hasModified = false;
|
2
|
+
function saveBeforeLeaving(e) {
|
3
|
+
return 'You have made changes to this item. Are you sure you want to leave before saving?';
|
4
|
+
}
|
5
|
+
function checkForModifiedText(e) {
|
6
|
+
if ((e.target.nodeName == 'INPUT' && e.target.getAttribute('type') == 'text') ||
|
7
|
+
e.target.nodeName == 'TEXTAREA') {
|
8
|
+
console.log('modified');
|
9
|
+
hasModified = (e.target.value != e.target.defaultValue);
|
10
|
+
setBeforeLeaving();
|
11
|
+
}
|
12
|
+
}
|
13
|
+
function checkForModifiedInputs(e) {
|
14
|
+
if (e.target.nodeName == 'SELECT' ||
|
15
|
+
(e.target.nodeName == 'INPUT' && e.target.getAttribute('type') == 'checkbox') ||
|
16
|
+
(e.target.nodeName == 'INPUT' && e.target.getAttribute('type') == 'radio')) {
|
17
|
+
console.log('modified');
|
18
|
+
hasModified = true;
|
19
|
+
setBeforeLeaving();
|
20
|
+
}
|
21
|
+
}
|
22
|
+
function setBeforeLeaving() {
|
23
|
+
if (hasModified) {
|
24
|
+
listen('beforeunload', window, saveBeforeLeaving);
|
25
|
+
} else {
|
26
|
+
unlisten('beforeunload', window, saveBeforeLeaving);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
listen('keyup', window, checkForModifiedText);
|
31
|
+
listen('click', window, checkForModifiedInputs);
|
32
|
+
listen('submit', el('edit-form'), function() {
|
33
|
+
hasModified = false;
|
34
|
+
setBeforeLeaving();
|
35
|
+
});
|
@@ -1,6 +1,20 @@
|
|
1
1
|
var app = (function() {
|
2
2
|
|
3
3
|
var selectedItems = [];
|
4
|
+
var searchInput = el('search-form').getElementsByTagName('input')[0];
|
5
|
+
var searchInputDefaultValue = 'Search...';
|
6
|
+
|
7
|
+
var searchInputHandler = function(action) {
|
8
|
+
return function(e) {
|
9
|
+
console.log(e);
|
10
|
+
if (this.value == '') {
|
11
|
+
this.value = searchInputDefaultValue;
|
12
|
+
}
|
13
|
+
if (this.value == searchInputDefaultValue && action == 'focus') {
|
14
|
+
this.value = '';
|
15
|
+
}
|
16
|
+
};
|
17
|
+
};
|
4
18
|
|
5
19
|
var handleMultiSelect = function(e) {
|
6
20
|
var id = e.target.getAttribute('id').split('_')[1];
|
@@ -22,6 +36,9 @@ var app = (function() {
|
|
22
36
|
el('delete-list').value = selectedItems.join(',');
|
23
37
|
};
|
24
38
|
|
39
|
+
listen('focus', searchInput, searchInputHandler('focus'));
|
40
|
+
listen('blur', searchInput, searchInputHandler('blur'));
|
41
|
+
|
25
42
|
var c = el('list-table').getElementsByTagName('input');
|
26
43
|
for (var i = 0; i < c.length; i++) {
|
27
44
|
if (c[i].getAttribute('type') == 'checkbox') {
|
@@ -1,3 +1,374 @@
|
|
1
1
|
function el(node) {
|
2
2
|
return document.getElementById(node);
|
3
|
-
}
|
3
|
+
}
|
4
|
+
|
5
|
+
function hasClass(node, name) {
|
6
|
+
if (!node || !node.className) { return false; }
|
7
|
+
return node.className.split(' ').some(function (n) { return n === name; });
|
8
|
+
}
|
9
|
+
|
10
|
+
function addClass(node, name) {
|
11
|
+
if (!node) { return; }
|
12
|
+
if (typeof(node.length) == 'undefined') { node = [node]; }
|
13
|
+
for (var i = 0; i < node.length; i++) {
|
14
|
+
if (!hasClass(node[i], name)) {
|
15
|
+
var curClasses = node[i].className.split(' ');
|
16
|
+
curClasses.push(name);
|
17
|
+
node[i].className = curClasses.join(' ');
|
18
|
+
}
|
19
|
+
}
|
20
|
+
};
|
21
|
+
|
22
|
+
function removeClass(node, name) {
|
23
|
+
if (!node) { return; }
|
24
|
+
if (typeof(node.length) == 'undefined') { node = [node]; }
|
25
|
+
for (var i = 0; i < node.length; i++) {
|
26
|
+
node[i].className = node[i].className.replace(new RegExp("(\\s" + name + ")|(" + name + "\\s)|" + name), "");
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
function toggleClass(node,name) {
|
31
|
+
if (hasClass(node,name)) {
|
32
|
+
removeClass(node,name);
|
33
|
+
} else {
|
34
|
+
addClass(node,name);
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
var parseJSON = function(json) {
|
39
|
+
if (window.JSON) {
|
40
|
+
parseJSON = function(json) {
|
41
|
+
return JSON.parse(json);
|
42
|
+
};
|
43
|
+
} else {
|
44
|
+
parseJSON = function(json) {
|
45
|
+
return eval("(" + json + ")");
|
46
|
+
};
|
47
|
+
}
|
48
|
+
return parseJSON(json);
|
49
|
+
};
|
50
|
+
|
51
|
+
var ajaxObject = function() {
|
52
|
+
if (window.ActiveXObject) {
|
53
|
+
ajaxObject = function() {
|
54
|
+
return new ActiveXObject('Microsoft.XMLHTTP');
|
55
|
+
};
|
56
|
+
} else {
|
57
|
+
ajaxObject = function() {
|
58
|
+
return new XMLHttpRequest();
|
59
|
+
};
|
60
|
+
}
|
61
|
+
return ajaxObject();
|
62
|
+
}
|
63
|
+
|
64
|
+
var ajax = function(opts) {
|
65
|
+
var req = ajaxObject();
|
66
|
+
req.onreadystatechange = function() {
|
67
|
+
switch (req.readyState) {
|
68
|
+
case 1:
|
69
|
+
if (typeof opts.loading == 'function') { opts.loading(); }
|
70
|
+
break;
|
71
|
+
case 2:
|
72
|
+
if (typeof opts.loaded == 'function') { opts.loaded(); }
|
73
|
+
break;
|
74
|
+
case 4:
|
75
|
+
var res = req.responseText;
|
76
|
+
switch (req.status) {
|
77
|
+
case 200: if (typeof opts.success == 'function') { opts.success(res); } break;
|
78
|
+
case 404: if (typeof opts.error == 'function') { opts.error(); } break;
|
79
|
+
case 500: if (typeof opts.error == 'function') { opts.error(); } break;
|
80
|
+
};
|
81
|
+
if (typeof opts.complete == 'function') { opts.complete(); }
|
82
|
+
break;
|
83
|
+
};
|
84
|
+
};
|
85
|
+
req.open((opts.type || 'get').toUpperCase(),opts.url,true);
|
86
|
+
req.send(null);
|
87
|
+
};
|
88
|
+
|
89
|
+
var listen = function(evnt, elem, func) {
|
90
|
+
if (elem.addEventListener) {
|
91
|
+
listen = function(evnt, elem, func) {
|
92
|
+
elem.addEventListener(evnt,func,false);
|
93
|
+
};
|
94
|
+
} else if (elem.attachEvent) {
|
95
|
+
listen = function(evnt, elem, func) {
|
96
|
+
elem.attachEvent('on' + evnt, func);
|
97
|
+
};
|
98
|
+
}
|
99
|
+
listen(evnt,elem,func);
|
100
|
+
};
|
101
|
+
|
102
|
+
var unlisten = function(evnt, elem, func) {
|
103
|
+
if (elem.removeEventListener) {
|
104
|
+
unlisten = function(evnt, elem, func) {
|
105
|
+
elem.removeEventListener(evnt,func,false);
|
106
|
+
};
|
107
|
+
} else if (elem.attachEvent) {
|
108
|
+
unlisten = function(evnt, elem, func) {
|
109
|
+
elem.detachEvent('on' + evnt, func);
|
110
|
+
};
|
111
|
+
}
|
112
|
+
unlisten(evnt,elem,func);
|
113
|
+
};
|
114
|
+
|
115
|
+
var getElementsByClassName = function(classname, el) {
|
116
|
+
if (document.getElementsByClassName) {
|
117
|
+
getElementsByClassName = function(classname, el) {
|
118
|
+
if (!el) { el = document; }
|
119
|
+
return el.getElementsByClassName(classname);
|
120
|
+
};
|
121
|
+
} else {
|
122
|
+
getElementsByClassName = function(classname, el) {
|
123
|
+
var classElements = new Array();
|
124
|
+
if (!el) { el = document; }
|
125
|
+
var els = el.getElementsByTagName('*');
|
126
|
+
var elsLen = els.length;
|
127
|
+
var pattern = new RegExp("(^|\\s)"+classname+"(\\s|$)");
|
128
|
+
for (var i = 0, j = 0; i < elsLen; i++) {
|
129
|
+
if ( pattern.test(els[i].className) ) {
|
130
|
+
classElements[j] = els[i];
|
131
|
+
j++;
|
132
|
+
}
|
133
|
+
}
|
134
|
+
return classElements;
|
135
|
+
};
|
136
|
+
}
|
137
|
+
return getElementsByClassName(classname, el);
|
138
|
+
};
|
139
|
+
|
140
|
+
var AssociationManager = function(opts) {
|
141
|
+
this.relationship = opts.relationship;
|
142
|
+
this.name = opts.name;
|
143
|
+
this.model = opts.model;
|
144
|
+
this.fields = opts.fields;
|
145
|
+
this.count = opts.count;
|
146
|
+
this.pageCount = Math.ceil(this.count/10);
|
147
|
+
this.pageNumber = 1;
|
148
|
+
this.relatedIds = {};
|
149
|
+
|
150
|
+
for (var i = 0, len = opts.related_ids.length; i < len; i++) {
|
151
|
+
this.relatedIds[opts.related_ids[i]] = true;
|
152
|
+
}
|
153
|
+
|
154
|
+
this.fieldset = el('property-' + this.name);
|
155
|
+
|
156
|
+
this.pageNumberField = getElementsByClassName('page-number',this.fieldset)[0];
|
157
|
+
this.pageCountField = getElementsByClassName('page-count',this.fieldset)[0];
|
158
|
+
|
159
|
+
this.search = getElementsByClassName('search', this.fieldset)[0];
|
160
|
+
this.searchInput = this.search.getElementsByTagName('input')[0];
|
161
|
+
this.defaultSearchValue = this.searchInput.value;
|
162
|
+
|
163
|
+
listen('keydown',this.search,this.searchHandler(this));
|
164
|
+
listen('focus', this.searchInput,this.searchInputHandler(this, 'focus'));
|
165
|
+
listen('blur', this.searchInput,this.searchInputHandler(this, 'blur'));
|
166
|
+
|
167
|
+
this.table = this.fieldset.getElementsByTagName('table')[0];
|
168
|
+
this.tbody = this.table.getElementsByTagName('tbody')[0];
|
169
|
+
this.associatedCountDisplay = getElementsByClassName('associated-count-display',this.table)[0];
|
170
|
+
this.associatedCount = getElementsByClassName('associated-count',this.table)[0];
|
171
|
+
|
172
|
+
listen('click',this.table,this.tableHandler(this));
|
173
|
+
this.modifyButton = getElementsByClassName('association-modify', this.table)[0];
|
174
|
+
listen('click',this.modifyButton,this.modifyHandler(this));
|
175
|
+
|
176
|
+
this.paging = getElementsByClassName('paging',this.fieldset)[0];
|
177
|
+
listen('click',this.paging,this.pagingHandler(this));
|
178
|
+
|
179
|
+
this.selectorType = this.relationship == 'belongs_to' ? 'radio' : 'checkbox';
|
180
|
+
|
181
|
+
this.states = {
|
182
|
+
MODIFY: 'modify',
|
183
|
+
SEARCH: 'search',
|
184
|
+
PAGING: 'paging'
|
185
|
+
};
|
186
|
+
};
|
187
|
+
|
188
|
+
// Shows/hides all checkboxes table cells, called when 'modify' is clicked
|
189
|
+
AssociationManager.prototype.toggleCheckboxes = function() {
|
190
|
+
var checkboxes = getElementsByClassName('checkbox', this.table),
|
191
|
+
func = (this.currentState == this.states.MODIFY) ? addClass : removeClass;
|
192
|
+
for (var i = 0, len = checkboxes.length; i < len; i++) {
|
193
|
+
func(checkboxes[i],'active');
|
194
|
+
}
|
195
|
+
};
|
196
|
+
|
197
|
+
// Adjust the list of related content IDs, called after clicking 'done'
|
198
|
+
AssociationManager.prototype.adjustRelatedList = function() {
|
199
|
+
var selected = [];
|
200
|
+
for (k in this.relatedIds) {
|
201
|
+
if (this.relatedIds[k] == true) {
|
202
|
+
selected.push(k);
|
203
|
+
}
|
204
|
+
}
|
205
|
+
getElementsByClassName('related_ids', this.fieldset)[0].value = selected.join(',');
|
206
|
+
var handler = this;
|
207
|
+
ajax({
|
208
|
+
url: "/search/" + this.model + "?ids=" + selected.slice(0,10).join(','),
|
209
|
+
success: function(data) {
|
210
|
+
handler.searchResultsHandler(handler)(data);
|
211
|
+
handler.count = selected.length;
|
212
|
+
if (selected.length < 10) {
|
213
|
+
handler.associatedCount.innerHTML = 0;
|
214
|
+
handler.associatedCountDisplay.style.display = 'none';
|
215
|
+
} else {
|
216
|
+
handler.associatedCount.innerHTML = selected.length-10;
|
217
|
+
handler.associatedCountDisplay.style.display = 'block';
|
218
|
+
}
|
219
|
+
}
|
220
|
+
});
|
221
|
+
};
|
222
|
+
|
223
|
+
AssociationManager.prototype.setToModifyState = function() {
|
224
|
+
console.log('setToModifyState');
|
225
|
+
ajax({
|
226
|
+
url: "/search/" + this.model,
|
227
|
+
success: this.searchResultsHandler(this)
|
228
|
+
});
|
229
|
+
};
|
230
|
+
|
231
|
+
AssociationManager.prototype.addLinkToValue = function(id, value, linkIt) {
|
232
|
+
if (!linkIt) { return value; }
|
233
|
+
return '<a href="/edit/' + this.model + '/' + id + '">' + value + '</a>';
|
234
|
+
};
|
235
|
+
|
236
|
+
AssociationManager.prototype.generateRow = function(data) {
|
237
|
+
var out = '<tr><td class="checkbox active">' +
|
238
|
+
'<input type="' + this.selectorType + '" value="' + data.id + '" id="selection_' + this.model + '_' + data.id + '"';
|
239
|
+
if (this.relatedIds[data.id]) {
|
240
|
+
out += ' checked="checked"';
|
241
|
+
}
|
242
|
+
if (this.selectorType == 'radio') {
|
243
|
+
out += ' name=' + this.model + '_' + this.name + '_selection';
|
244
|
+
}
|
245
|
+
out += ' /></td>';
|
246
|
+
var linkIt = true;
|
247
|
+
for (var i = 0, len = this.fields.length; i < len; i++) {
|
248
|
+
if (i == 1) { linkIt = false; }
|
249
|
+
out += '<td>' + this.addLinkToValue(data.id, (data[this.fields[i]] == null ? '-' : data[this.fields[i]]), linkIt) + '</td>';
|
250
|
+
}
|
251
|
+
out += '</tr>';
|
252
|
+
return out;
|
253
|
+
};
|
254
|
+
|
255
|
+
AssociationManager.prototype.clearTable = function() {
|
256
|
+
while (this.tbody.hasChildNodes()) {
|
257
|
+
this.tbody.removeChild(this.tbody.lastChild);
|
258
|
+
}
|
259
|
+
};
|
260
|
+
|
261
|
+
// Handles clicking next/previous on paging
|
262
|
+
AssociationManager.prototype.pagingHandler = function(handler) {
|
263
|
+
return function(e) {
|
264
|
+
e.preventDefault();
|
265
|
+
if (e.target.nodeName == 'A') {
|
266
|
+
var q = '';
|
267
|
+
if (handler.searchInput.value != handler.defaultSearchValue) {
|
268
|
+
q = handler.searchInput.value;
|
269
|
+
}
|
270
|
+
var direction = e.target.getAttribute('rel') == 'next' ? 1 : -1;
|
271
|
+
handler.pageNumber = handler.pageNumber-(direction*-1);
|
272
|
+
if (handler.pageNumber <= 0) {
|
273
|
+
handler.pageNumber = 1;
|
274
|
+
} else if (handler.pageNumber > handler.pageCount) {
|
275
|
+
handler.pageNumber = handler.pageCount;
|
276
|
+
}
|
277
|
+
handler.pageNumberField.innerHTML = handler.pageNumber;
|
278
|
+
var offset = (handler.pageNumber-1)*10;
|
279
|
+
ajax({
|
280
|
+
url: "/search/" + handler.model + "?q=" + q + "&limit=10&offset=" + offset,
|
281
|
+
success: handler.searchResultsHandler(handler)
|
282
|
+
});
|
283
|
+
}
|
284
|
+
};
|
285
|
+
};
|
286
|
+
|
287
|
+
// Handles clicks on the table, catching checkbox clicks
|
288
|
+
AssociationManager.prototype.tableHandler = function(handler) {
|
289
|
+
return function(e) {
|
290
|
+
if (e.target.getAttribute('type') == 'checkbox') {
|
291
|
+
handler.relatedIds[e.target.value] = e.target.checked;
|
292
|
+
}
|
293
|
+
if (e.target.getAttribute('type') == 'radio') {
|
294
|
+
handler.relatedIds = {};
|
295
|
+
handler.relatedIds[e.target.value] = e.target.checked;
|
296
|
+
console.log(handler.relatedIds);
|
297
|
+
}
|
298
|
+
};
|
299
|
+
};
|
300
|
+
|
301
|
+
// Handlers clicking on the "modify" button on association table
|
302
|
+
AssociationManager.prototype.modifyHandler = function(handler) {
|
303
|
+
return function(e) {
|
304
|
+
e.preventDefault();
|
305
|
+
switch (handler.currentState) {
|
306
|
+
case handler.states.MODIFY:
|
307
|
+
handler.modifyButton.innerHTML = 'Modify';
|
308
|
+
handler.search.style.display = 'none';
|
309
|
+
handler.associatedCountDisplay.style.display = 'table-cell';
|
310
|
+
handler.currentState = null;
|
311
|
+
handler.pageNumber = 1;
|
312
|
+
handler.searchInput.value = handler.defaultSearchValue;
|
313
|
+
handler.adjustRelatedList();
|
314
|
+
break;
|
315
|
+
default:
|
316
|
+
handler.modifyButton.innerHTML = 'Done';
|
317
|
+
handler.search.style.display = 'block';
|
318
|
+
handler.associatedCountDisplay.style.display = 'none';
|
319
|
+
handler.currentState = handler.states.MODIFY;
|
320
|
+
handler.setToModifyState();
|
321
|
+
}
|
322
|
+
};
|
323
|
+
};
|
324
|
+
|
325
|
+
// Handles search results coming back from paging and searching
|
326
|
+
AssociationManager.prototype.searchResultsHandler = function(handler) {
|
327
|
+
return function(data) {
|
328
|
+
data = parseJSON(data);
|
329
|
+
handler.clearTable();
|
330
|
+
handler.count = data.count;
|
331
|
+
handler.pageCount = Math.ceil(handler.count/10);
|
332
|
+
handler.pageCountField.innerHTML = handler.pageCount;
|
333
|
+
handler.pageNumberField.innerHTML = handler.pageNumber;
|
334
|
+
console.log(data);
|
335
|
+
if (data.results.length > 0) {
|
336
|
+
var out = [];
|
337
|
+
for (var i = 0, len = data.results.length; i < len; i++) {
|
338
|
+
out.push(handler.generateRow(data.results[i]));
|
339
|
+
}
|
340
|
+
handler.tbody.innerHTML = out.join('');
|
341
|
+
} else {
|
342
|
+
}
|
343
|
+
handler.toggleCheckboxes();
|
344
|
+
};
|
345
|
+
};
|
346
|
+
|
347
|
+
// Handlers search box input
|
348
|
+
AssociationManager.prototype.searchHandler = function(handler) {
|
349
|
+
return function(e) {
|
350
|
+
if (e.keyCode == 13) {
|
351
|
+
e.preventDefault();
|
352
|
+
var q = handler.searchInput.value;
|
353
|
+
handler.pageNumber = 1;
|
354
|
+
var offset = (handler.pageNumber-1)*10;
|
355
|
+
ajax({
|
356
|
+
url: "/search/" + handler.model + "?q=" + q + "&limit=10&offset=" + offset,
|
357
|
+
success: handler.searchResultsHandler(handler)
|
358
|
+
});
|
359
|
+
return false;
|
360
|
+
}
|
361
|
+
return true;
|
362
|
+
};
|
363
|
+
};
|
364
|
+
|
365
|
+
AssociationManager.prototype.searchInputHandler = function(handler, action) {
|
366
|
+
return function(e) {
|
367
|
+
if (this.value == '') {
|
368
|
+
this.value = handler.defaultSearchValue;
|
369
|
+
}
|
370
|
+
if (this.value == handler.defaultSearchValue && action == 'focus') {
|
371
|
+
this.value = '';
|
372
|
+
}
|
373
|
+
};
|
374
|
+
};
|
@@ -1,9 +1,9 @@
|
|
1
1
|
<h1><a href="/<%= @model.name %>"><%= @model.display_name %></a> / Edit</h1>
|
2
|
-
<form action="/update/<%= @model.name %>/<%= @content.id %>" method="post">
|
2
|
+
<form action="/update/<%= @model.name %>/<%= @content.id %>" method="post" id="edit-form">
|
3
3
|
<div class="wrapper">
|
4
4
|
<% if @errors %><div id="save_errors" class="errors"><%= @errors %></div><% end %>
|
5
5
|
<% @model.form_properties.each do |p| %>
|
6
|
-
<fieldset id="
|
6
|
+
<fieldset id="property-<%= p.name %>" class="<%= @model.field_class_for(p) %>">
|
7
7
|
<%= @model.form_for(p,@content) %>
|
8
8
|
</fieldset>
|
9
9
|
<% end %>
|
@@ -18,3 +18,4 @@
|
|
18
18
|
<button type="submit" id="delete-button">Delete</button>
|
19
19
|
</form>
|
20
20
|
</div>
|
21
|
+
<script type="text/javascript" src="/js/edit.js"></script>
|
@@ -1,7 +1,16 @@
|
|
1
1
|
<h1><%= @model.display_name %></h1>
|
2
|
+
<div id="paging">
|
3
|
+
<%= @count %> items
|
4
|
+
<% if @page_count > 1 %>
|
5
|
+
Page <span class="page-number"><%= @p %></span> of <span class="page-count"><%= @page_count %></span>
|
6
|
+
<% prev_link, next_link = paging_links(@p, @page_count, :q => params[:q]) %>
|
7
|
+
<%= prev_link %>
|
8
|
+
<%= next_link %>
|
9
|
+
<% end %>
|
10
|
+
</div>
|
2
11
|
<form method="get" id="search-form">
|
3
12
|
<fieldset>
|
4
|
-
<input type="text" name="q" value="<%= params[:q]
|
13
|
+
<input type="text" name="q" value="<%= params[:q].blank? ? 'Search...' : params[:q] %>" size="40" />
|
5
14
|
</fieldset>
|
6
15
|
</form>
|
7
16
|
<div class="wrapper">
|
@@ -38,7 +47,7 @@
|
|
38
47
|
<a href="/new/<%= @model.name %>" class="action new" id="new-button">New</a>
|
39
48
|
<form action="/delete/<%= @model.name %>" method="post" id="delete-form">
|
40
49
|
<input type="hidden" id="delete-list" name="id" value="" />
|
41
|
-
<button type="submit" id="delete-button" disabled="disabled">Delete</button>
|
50
|
+
<button type="submit" id="delete-button" disabled="disabled" onclick="return confirm('Are you sure you want to delete these items?');">Delete</button>
|
42
51
|
</form>
|
43
52
|
</div>
|
44
53
|
<script type="text/javascript" src="/js/index.js"></script>
|
@@ -43,13 +43,13 @@ module Togo
|
|
43
43
|
@response.status = 404
|
44
44
|
@response.write("404 Not Found")
|
45
45
|
else
|
46
|
-
|
46
|
+
begin
|
47
47
|
__before if defined? __before
|
48
48
|
@response.write(send(method))
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
rescue => detail
|
50
|
+
@response.status = 500
|
51
|
+
@response.write(["Error: #{detail}",$!.backtrace.join("<br />\n")].join("<br />\n"))
|
52
|
+
end
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
@@ -112,8 +112,11 @@ module Togo
|
|
112
112
|
method_name = "__#{type.downcase}#{clean_path(route)}"
|
113
113
|
k = []
|
114
114
|
p = route.gsub(/(:\w+)/){|m| k << m[1..-1]; "([^?/#&]+)"}
|
115
|
-
|
116
|
-
|
115
|
+
r = [/^#{p}$/,k,method_name]
|
116
|
+
if not routes[type].rindex{|t| t[0] == r[0]}
|
117
|
+
routes[type].push(r)
|
118
|
+
define_method(method_name, &block)
|
119
|
+
end
|
117
120
|
end
|
118
121
|
|
119
122
|
def before(&block)
|
@@ -136,6 +139,13 @@ module Togo
|
|
136
139
|
builder.use Rack::ShowExceptions
|
137
140
|
builder.use opts[:reloader] if opts[:reloader]
|
138
141
|
end
|
142
|
+
if opts[:routes] and opts[:routes].is_a?(Array)
|
143
|
+
opts[:routes].each do |r|
|
144
|
+
send(r[:type].to_sym, r[:path]) do
|
145
|
+
erb r[:template].to_sym
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
139
149
|
builder.use Rack::Static, :urls => opts[:static_urls], :root => opts[:public_path]
|
140
150
|
builder.run new(opts)
|
141
151
|
if opts[:standalone]
|
data/lib/togo/model/model.rb
CHANGED
@@ -51,6 +51,11 @@ module Togo
|
|
51
51
|
|
52
52
|
def stage_content(content,attrs)
|
53
53
|
content.attributes = properties.inject({}){|m,p| attrs[p.name.to_sym] ? m.merge!(p.name.to_sym => attrs[p.name.to_sym]) : m}
|
54
|
+
relationships.each do |r|
|
55
|
+
key = "related_#{r[0]}".to_sym
|
56
|
+
next if not attrs[key] or attrs[key] == 'unset'
|
57
|
+
content = RelationshipManager.new(content, r, :ids => attrs[key]).relate
|
58
|
+
end
|
54
59
|
content
|
55
60
|
end
|
56
61
|
|
@@ -68,12 +73,16 @@ module Togo
|
|
68
73
|
|
69
74
|
def search(opts)
|
70
75
|
q = "%#{opts[:q].gsub(/\s+/,'%')}%"
|
76
|
+
limit = opts[:limit]
|
77
|
+
offset = opts[:offset]
|
71
78
|
conditions, values = [], []
|
72
79
|
search_properties.each{|l|
|
73
80
|
conditions << "#{l.name} like ?"
|
74
81
|
values << q
|
75
82
|
}
|
76
|
-
|
83
|
+
params = {:conditions => [conditions.join(' OR ')] + values}
|
84
|
+
params.merge!(:limit => limit.to_i, :offset => offset.to_i) if limit and offset
|
85
|
+
all(params)
|
77
86
|
end
|
78
87
|
|
79
88
|
def field_class_for(property)
|
@@ -89,7 +98,7 @@ module Togo
|
|
89
98
|
def type_from_property(property)
|
90
99
|
case property
|
91
100
|
when ::DataMapper::Property
|
92
|
-
Extlib::Inflection.demodulize(property.type).downcase
|
101
|
+
Extlib::Inflection.demodulize(property.type || property.class).downcase # type seems to be deprecated in 1.0
|
93
102
|
when ::DataMapper::Associations::ManyToOne::Relationship
|
94
103
|
'belongs_to'
|
95
104
|
when ::DataMapper::Associations::OneToMany::Relationship
|
@@ -112,12 +121,50 @@ module Togo
|
|
112
121
|
end
|
113
122
|
|
114
123
|
def search_properties
|
124
|
+
# Support dm 0.10.x and 1.x by checking for deprecated(?) types
|
115
125
|
only_properties = [String, ::DataMapper::Types::Text]
|
116
|
-
|
126
|
+
begin # rescue exception when not using dm-core 1.0, these don't exist in the 0.10.x series
|
127
|
+
only_properties.concat([::DataMapper::Property::String, ::DataMapper::Property::Text])
|
128
|
+
rescue
|
129
|
+
end
|
130
|
+
properties.select{|p| only_properties.include?(p.type || p.class)} # type seems to be depracated in 1.0
|
117
131
|
end
|
118
132
|
|
119
133
|
end
|
120
134
|
|
121
135
|
end # Model
|
136
|
+
|
137
|
+
class RelationshipManager
|
138
|
+
def initialize(content, relationship, opts = {})
|
139
|
+
@content = content
|
140
|
+
@relationship = relationship[1]
|
141
|
+
@relationship_name = relationship[0]
|
142
|
+
@ids = (opts[:ids] || '').split(',').map(&:to_i)
|
143
|
+
define_values
|
144
|
+
end
|
145
|
+
|
146
|
+
def relate
|
147
|
+
@content.send("#{@relationship_name}=", find_for_assignment)
|
148
|
+
@content
|
149
|
+
end
|
150
|
+
|
151
|
+
def define_values
|
152
|
+
case @relationship
|
153
|
+
when ::DataMapper::Associations::ManyToOne::Relationship
|
154
|
+
@unset_value = nil
|
155
|
+
@related_model = @relationship.parent_model
|
156
|
+
@find_op = Proc.new{|p| p.get(@ids.first)}
|
157
|
+
when ::DataMapper::Associations::OneToMany::Relationship
|
158
|
+
@unset_value = []
|
159
|
+
@related_model = @relationship.child_model
|
160
|
+
@find_op = Proc.new{|p| p.all(:id => @ids)}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def find_for_assignment
|
165
|
+
return @unset_value if @ids.blank?
|
166
|
+
@find_op.call(@related_model)
|
167
|
+
end
|
168
|
+
end # RelationshipManager
|
122
169
|
end # DataMapper
|
123
170
|
end # Togo
|
@@ -1,29 +1,65 @@
|
|
1
|
-
<%
|
2
|
-
<%
|
3
|
-
|
4
|
-
|
1
|
+
<% content_count = content.send(property.name.to_sym).blank? ? 0 : 1 %>
|
2
|
+
<% related_content = [content.send(property.name.to_sym)].compact %>
|
3
|
+
<% related_ids = related_content.map(&:id) %>
|
4
|
+
<% related_model = property.parent_model %>
|
5
|
+
<input type="hidden" name="related_<%= property.name %>" class="related_ids" value="unset" />
|
6
|
+
<label for="<%= property.name %>"><%= related_model.display_name %></label>
|
7
|
+
<div class="search" id="search-<%= related_model.name %>">
|
8
|
+
<input type="text" name="search-<%= property.name %>" value="Search..." />
|
9
|
+
<div class="paging">
|
10
|
+
Page <span class="page-number">1</span> of <span class="page-count"><%= (content_count/10).ceil %></span>
|
11
|
+
<a href="#" rel="prev">« Previous</a> <a href="#" rel="next">Next »</a>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
<table>
|
5
15
|
<thead>
|
6
16
|
<tr>
|
7
|
-
|
8
|
-
<th></th>
|
9
|
-
<% end %>
|
17
|
+
<th class="checkbox"></th>
|
10
18
|
<% related_model.list_properties.each do |p| %>
|
11
19
|
<th><%= p.name.to_s.humanize.titleize %></th>
|
12
20
|
<% end %>
|
21
|
+
<% if related_model.list_properties.size < 2 %>
|
22
|
+
<th></th>
|
23
|
+
<% end %>
|
13
24
|
</tr>
|
14
25
|
</thead>
|
26
|
+
<tfoot>
|
27
|
+
<tr>
|
28
|
+
<td class="checkbox"></td>
|
29
|
+
<td>
|
30
|
+
<span class="associated-count-display" style="<%= (content_count == 0 or content_count < 10) ? 'display: none;' : '' %>">
|
31
|
+
...and <span class="associated-count"><%= content_count-related_content.size %></span> more associated items
|
32
|
+
</span>
|
33
|
+
</td>
|
34
|
+
<td colspan="<%= related_model.form_properties.size-1 %>">
|
35
|
+
<button class="association-modify">Modify</button>
|
36
|
+
</td>
|
37
|
+
</tr>
|
38
|
+
</tfoot>
|
15
39
|
<tbody>
|
16
|
-
<% if
|
40
|
+
<% if related_content.empty? %>
|
17
41
|
<tr>
|
18
|
-
<td colspan="<%= related_model.
|
42
|
+
<td colspan="<%= related_model.form_properties.size %>">No Content Associated.</td>
|
19
43
|
</tr>
|
20
44
|
<% else %>
|
45
|
+
<% related_content.each do |c| %>
|
21
46
|
<tr>
|
22
|
-
<td class="checkbox"><input type="
|
47
|
+
<td class="checkbox"><input type="radio" name="selection[<%= c.id %>]" value="<%= c.id %>" id="selection_<%= related_model.name %>_<%= c.id %>" checked="checked" /></td>
|
23
48
|
<% related_model.list_properties.each_with_index do |p,i| %>
|
24
|
-
<td><% if i == 0 %><a href="/edit/<%= related_model.name %>/<%=
|
49
|
+
<td><% if i == 0 %><a href="/edit/<%= related_model.name %>/<%= c.id %>"><%= c.send(p.name.to_sym) || '-' %></a><% else %><%= c.send(p.name.to_sym) %><% end %></td>
|
25
50
|
<% end %>
|
26
|
-
</tr>
|
51
|
+
</tr>
|
52
|
+
<% end %>
|
27
53
|
<% end %>
|
28
54
|
</tbody>
|
29
55
|
</table>
|
56
|
+
<script type="text/javascript">
|
57
|
+
new AssociationManager({
|
58
|
+
relationship: 'belongs_to',
|
59
|
+
name: '<%= property.name %>',
|
60
|
+
model: '<%= related_model.name %>',
|
61
|
+
fields: <%= related_model.list_properties.map(&:name).to_json %>,
|
62
|
+
count: <%= content_count %>,
|
63
|
+
related_ids: <%= related_ids.to_json %>
|
64
|
+
});
|
65
|
+
</script>
|
@@ -1,2 +1,2 @@
|
|
1
1
|
<label for="<%= property.name %>"><%= property.model.property_options[property.name][:label] rescue property.name.to_s.humanize.titleize %></label>
|
2
|
-
<input type="text" name="<%= property.name %>" id="<%= property.name %>" value="<%= content.send(property.name.to_sym) %>" />
|
2
|
+
<input type="text" name="<%= property.name %>" id="<%= property.name %>" value="<%= content.send(property.name.to_sym).strftime("%B %d, %Y %I:%M%p") rescue '' %>" />
|
@@ -1,31 +1,62 @@
|
|
1
|
-
<%
|
2
|
-
<%
|
1
|
+
<% content_count = content.send(property.name.to_sym).size %>
|
2
|
+
<% related_ids = content.send(property.name.to_sym).map(&:id) %>
|
3
|
+
<% related_content = content.send(property.name.to_sym)[0...10] %>
|
4
|
+
<% related_model = property.child_model %>
|
5
|
+
<input type="hidden" name="related_<%= property.name %>" class="related_ids" value="unset" />
|
3
6
|
<label for="<%= property.name %>"><%= related_model.display_name %></label>
|
4
|
-
<
|
7
|
+
<div class="search" id="search-<%= related_model.name %>">
|
8
|
+
<input type="text" name="search-<%= property.name %>" value="Search..." />
|
9
|
+
<div class="paging">
|
10
|
+
Page <span class="page-number">1</span> of <span class="page-count"><%= (content_count/10).ceil %></span>
|
11
|
+
<a href="#" rel="prev">« Previous</a> <a href="#" rel="next">Next »</a>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
<table>
|
5
15
|
<thead>
|
6
16
|
<tr>
|
7
|
-
|
8
|
-
<th></th>
|
9
|
-
<% end %>
|
17
|
+
<th class="checkbox"></th>
|
10
18
|
<% related_model.list_properties.each do |p| %>
|
11
19
|
<th><%= p.name.to_s.humanize.titleize %></th>
|
12
20
|
<% end %>
|
13
21
|
</tr>
|
14
22
|
</thead>
|
23
|
+
<tfoot>
|
24
|
+
<tr>
|
25
|
+
<td class="checkbox"></td>
|
26
|
+
<td>
|
27
|
+
<span class="associated-count-display" style="<%= (content_count == 0 or content_count < 10) ? 'display: none;' : '' %>">
|
28
|
+
...and <span class="associated-count"><%= content_count-related_content.size %></span> more associated items
|
29
|
+
</span>
|
30
|
+
</td>
|
31
|
+
<td colspan="<%= related_model.form_properties.size-1 %>">
|
32
|
+
<button class="association-modify">Modify</button>
|
33
|
+
</td>
|
34
|
+
</tr>
|
35
|
+
</tfoot>
|
15
36
|
<tbody>
|
16
|
-
<% if
|
37
|
+
<% if related_content.empty? %>
|
17
38
|
<tr>
|
18
|
-
<td colspan="<%= related_model.
|
39
|
+
<td colspan="<%= related_model.form_properties.size %>">No Content Associated.</td>
|
19
40
|
</tr>
|
20
41
|
<% else %>
|
21
42
|
<% related_content.each do |c| %>
|
22
43
|
<tr>
|
23
|
-
<td class="checkbox"><input type="checkbox" name="selection[<%= c.id %>]" value="
|
44
|
+
<td class="checkbox"><input type="checkbox" name="selection[<%= c.id %>]" value="<%= c.id %>" id="selection_<%= related_model.name %>_<%= c.id %>" checked="checked" /></td>
|
24
45
|
<% related_model.list_properties.each_with_index do |p,i| %>
|
25
46
|
<td><% if i == 0 %><a href="/edit/<%= related_model.name %>/<%= c.id %>"><%= c.send(p.name.to_sym) || '-' %></a><% else %><%= c.send(p.name.to_sym) %><% end %></td>
|
26
47
|
<% end %>
|
27
48
|
</tr>
|
28
|
-
<% end
|
49
|
+
<% end %>
|
29
50
|
<% end %>
|
30
51
|
</tbody>
|
31
52
|
</table>
|
53
|
+
<script type="text/javascript">
|
54
|
+
new AssociationManager({
|
55
|
+
relationship: 'has_n',
|
56
|
+
name: '<%= property.name %>',
|
57
|
+
model: '<%= related_model.name %>',
|
58
|
+
fields: <%= related_model.list_properties.map(&:name).to_json %>,
|
59
|
+
count: <%= content_count %>,
|
60
|
+
related_ids: <%= related_ids.to_json %>
|
61
|
+
});
|
62
|
+
</script>
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: togo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matt King
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-08-10 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -63,6 +63,7 @@ files:
|
|
63
63
|
- Changelog
|
64
64
|
- LICENSE
|
65
65
|
- lib/togo/admin/admin.rb
|
66
|
+
- lib/togo/admin/helpers.rb
|
66
67
|
- lib/togo/admin/public/css/screen.css
|
67
68
|
- lib/togo/admin/public/img/bg-header.png
|
68
69
|
- lib/togo/admin/public/img/bg-headline.png
|
@@ -70,6 +71,7 @@ files:
|
|
70
71
|
- lib/togo/admin/public/img/bg-side.png
|
71
72
|
- lib/togo/admin/public/img/btn-bg.gif
|
72
73
|
- lib/togo/admin/public/img/btn-bg.png
|
74
|
+
- lib/togo/admin/public/js/edit.js
|
73
75
|
- lib/togo/admin/public/js/index.js
|
74
76
|
- lib/togo/admin/public/js/togo.js
|
75
77
|
- lib/togo/admin/views/custom_title.erb
|