radiant-taggable-extension 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ var TagSuggester = Behavior.create({
2
+ initialize: function() {
3
+ var textbox = this.element;
4
+ new Autocomplete(textbox, {
5
+ serviceUrl: '/admin/tags.json',
6
+ minChars: 2,
7
+ maxHeight: 400,
8
+ deferRequestBy: 500,
9
+ multiple: true
10
+ });
11
+ }
12
+ });
13
+
14
+ Event.addBehavior({
15
+ 'input.toggle': Toggle.CheckboxBehavior({ onLoad: function(link) { if (!this.checked) Toggle.hide(this.toggleWrappers, this.effect); } }),
16
+ 'input.tagger': TagSuggester
17
+ });
18
+
19
+
@@ -0,0 +1,334 @@
1
+ /*
2
+ *
3
+ * Ajax Autocomplete for Prototype, version 1.0.4
4
+ * (c) 2010 Tomas Kirda
5
+ *
6
+ * Ajax Autocomplete for Prototype is freely distributable under the terms of an MIT-style license.
7
+ * For details, see the web site: http://www.devbridge.com/projects/autocomplete/
8
+ *
9
+ * Adapted by Will 11/4/2011 to take a 'multiple' option and work with a comma-separated list of values.
10
+ * Type-ahead would be nice too.
11
+ */
12
+
13
+ var Autocomplete = function(el, options){
14
+ this.el = $(el);
15
+ this.id = this.el.identify();
16
+ this.el.setAttribute('autocomplete','off');
17
+ this.suggestions = [];
18
+ this.data = [];
19
+ this.badQueries = [];
20
+ this.selectedIndex = -1;
21
+ this.currentValue = this.el.value;
22
+ this.intervalId = 0;
23
+ this.cachedResponse = [];
24
+ this.instanceId = null;
25
+ this.onChangeInterval = null;
26
+ this.ignoreValueChange = false;
27
+ this.serviceUrl = options.serviceUrl;
28
+ this.options = {
29
+ autoSubmit:false,
30
+ minChars:1,
31
+ maxHeight:300,
32
+ deferRequestBy:0,
33
+ width:0,
34
+ container:null
35
+ };
36
+ if(options){ Object.extend(this.options, options); }
37
+ if(Autocomplete.isDomLoaded){
38
+ this.initialize();
39
+ }else{
40
+ Event.observe(document, 'dom:loaded', this.initialize.bind(this), false);
41
+ }
42
+ };
43
+
44
+ Autocomplete.instances = [];
45
+ Autocomplete.isDomLoaded = false;
46
+
47
+ Autocomplete.getInstance = function(id){
48
+ var instances = Autocomplete.instances;
49
+ var i = instances.length;
50
+ while(i--){ if(instances[i].id === id){ return instances[i]; }}
51
+ };
52
+
53
+ Autocomplete.highlight = function(value, re){
54
+ return value.replace(re, function(match){ return '<strong>' + match + '<\/strong>'; });
55
+ };
56
+
57
+ Autocomplete.prototype = {
58
+
59
+ killerFn: null,
60
+
61
+ initialize: function() {
62
+ var me = this;
63
+ this.killerFn = function(e) {
64
+ if (!$(Event.element(e)).up('.autocomplete')) {
65
+ me.killSuggestions();
66
+ me.disableKillerFn();
67
+ }
68
+ } .bindAsEventListener(this);
69
+
70
+ if (!this.options.width) { this.options.width = this.el.getWidth(); }
71
+
72
+ var div = new Element('div', { style: 'position:absolute;' });
73
+ div.update('<div class="autocomplete-w1"><div class="autocomplete-w2"><div class="autocomplete" id="Autocomplete_' + this.id + '" style="display:none; width:' + this.options.width + 'px;"></div></div></div>');
74
+
75
+ this.options.container = $(this.options.container);
76
+ if (this.options.container) {
77
+ this.options.container.appendChild(div);
78
+ this.fixPosition = function() { };
79
+ } else {
80
+ document.body.appendChild(div);
81
+ }
82
+
83
+ this.mainContainerId = div.identify();
84
+ this.container = $('Autocomplete_' + this.id);
85
+ this.fixPosition();
86
+
87
+ Event.observe(this.el, window.opera ? 'keypress':'keydown', this.onKeyPress.bind(this));
88
+ Event.observe(this.el, 'keyup', this.onKeyUp.bind(this));
89
+ Event.observe(this.el, 'blur', this.enableKillerFn.bind(this));
90
+ Event.observe(this.el, 'focus', this.fixPosition.bind(this));
91
+ this.container.setStyle({ maxHeight: this.options.maxHeight + 'px' });
92
+ this.instanceId = Autocomplete.instances.push(this) - 1;
93
+ },
94
+
95
+ fixPosition: function() {
96
+ var offset = this.el.cumulativeOffset();
97
+ $(this.mainContainerId).setStyle({ top: (offset.top + this.el.getHeight()) + 'px', left: offset.left + 'px' });
98
+ },
99
+
100
+ enableKillerFn: function() {
101
+ Event.observe(document.body, 'click', this.killerFn);
102
+ },
103
+
104
+ disableKillerFn: function() {
105
+ Event.stopObserving(document.body, 'click', this.killerFn);
106
+ },
107
+
108
+ killSuggestions: function() {
109
+ this.stopKillSuggestions();
110
+ this.intervalId = window.setInterval(function() { this.hide(); this.stopKillSuggestions(); } .bind(this), 300);
111
+ },
112
+
113
+ stopKillSuggestions: function() {
114
+ window.clearInterval(this.intervalId);
115
+ },
116
+
117
+ onKeyPress: function(e) {
118
+ if (!this.enabled) { return; }
119
+ // return will exit the function
120
+ // and event will not fire
121
+ switch (e.keyCode) {
122
+ case Event.KEY_ESC:
123
+ this.el.value = this.currentValue;
124
+ this.hide();
125
+ break;
126
+ case Event.KEY_TAB:
127
+ case Event.KEY_RETURN:
128
+ if (this.selectedIndex === -1) {
129
+ this.hide();
130
+ return;
131
+ }
132
+ this.select(this.selectedIndex);
133
+ if (e.keyCode === Event.KEY_TAB) { return; }
134
+ break;
135
+ case Event.KEY_UP:
136
+ this.moveUp();
137
+ break;
138
+ case Event.KEY_DOWN:
139
+ this.moveDown();
140
+ break;
141
+ default:
142
+ return;
143
+ }
144
+ Event.stop(e);
145
+ },
146
+
147
+ onKeyUp: function(e) {
148
+ switch (e.keyCode) {
149
+ case Event.KEY_UP:
150
+ case Event.KEY_DOWN:
151
+ return;
152
+ }
153
+ clearInterval(this.onChangeInterval);
154
+ if (this.currentValue !== this.el.value) {
155
+ if (this.options.deferRequestBy > 0) {
156
+ // Defer lookup in case when value changes very quickly:
157
+ this.onChangeInterval = setInterval((function() {
158
+ this.onValueChange();
159
+ }).bind(this), this.options.deferRequestBy);
160
+ } else {
161
+ this.onValueChange();
162
+ }
163
+ }
164
+ },
165
+
166
+ onValueChange: function() {
167
+ clearInterval(this.onChangeInterval);
168
+ var newValue = this.activeValue();
169
+ this.selectedIndex = -1;
170
+ if (this.ignoreValueChange) {
171
+ this.ignoreValueChange = false;
172
+ return;
173
+ }
174
+ if (newValue === '' || newValue.length < this.options.minChars) {
175
+ this.hide();
176
+ } else {
177
+ this.getSuggestions();
178
+ }
179
+ },
180
+
181
+ activeValue: function () {
182
+ if (this.options.multiple) {
183
+ return this.el.value.split(/,\s*/).last();
184
+ } else {
185
+ return this.el.value;
186
+ }
187
+ },
188
+
189
+ getSuggestions: function() {
190
+ var newValue = this.activeValue();
191
+ console.log('getting suggestions for ', newValue);
192
+ var cr = this.cachedResponse[newValue];
193
+ if (cr && Object.isArray(cr.suggestions)) {
194
+ this.suggestions = cr.suggestions;
195
+ this.data = cr.data;
196
+ this.suggest();
197
+ } else if (!this.isBadQuery(newValue)) {
198
+ new Ajax.Request(this.serviceUrl, {
199
+ parameters: { query: newValue },
200
+ onComplete: this.processResponse.bind(this),
201
+ method: 'get'
202
+ });
203
+ }
204
+ },
205
+
206
+ isBadQuery: function(q) {
207
+ var i = this.badQueries.length;
208
+ while (i--) {
209
+ if (q.indexOf(this.badQueries[i]) === 0) { return true; }
210
+ }
211
+ return false;
212
+ },
213
+
214
+ hide: function() {
215
+ this.enabled = false;
216
+ this.selectedIndex = -1;
217
+ this.container.hide();
218
+ },
219
+
220
+ suggest: function() {
221
+ if (this.suggestions.length === 0) {
222
+ this.hide();
223
+ return;
224
+ }
225
+ var content = [];
226
+ var re = new RegExp('\\b' + this.activeValue().match(/\w+/g).join('|\\b'), 'gi');
227
+ this.suggestions.each(function(value, i) {
228
+ content.push((this.selectedIndex === i ? '<div class="selected"' : '<div'), ' title="', value, '" onclick="Autocomplete.instances[', this.instanceId, '].select(', i, ');" onmouseover="Autocomplete.instances[', this.instanceId, '].activate(', i, ');">', Autocomplete.highlight(value, re), '</div>');
229
+ } .bind(this));
230
+ this.enabled = true;
231
+ this.container.update(content.join('')).show();
232
+ },
233
+
234
+ processResponse: function(xhr) {
235
+ var response;
236
+ try {
237
+ response = xhr.responseText.evalJSON();
238
+ if (!Object.isArray(response.data)) { response.data = []; }
239
+ } catch (err) { return; }
240
+ this.cachedResponse[response.query] = response;
241
+ if (response.suggestions.length === 0) {
242
+ this.badQueries.push(response.query);
243
+ }
244
+ if (response.query === this.activeValue()) {
245
+ this.suggestions = response.suggestions;
246
+ this.data = response.data;
247
+ this.suggest();
248
+ }
249
+ },
250
+
251
+ activate: function(index) {
252
+ var divs = this.container.childNodes;
253
+ var activeItem;
254
+ // Clear previous selection:
255
+ if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {
256
+ divs[this.selectedIndex].className = '';
257
+ }
258
+ this.selectedIndex = index;
259
+ if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) {
260
+ activeItem = divs[this.selectedIndex];
261
+ activeItem.className = 'selected';
262
+ }
263
+ return activeItem;
264
+ },
265
+
266
+ deactivate: function(div, index) {
267
+ div.className = '';
268
+ if (this.selectedIndex === index) { this.selectedIndex = -1; }
269
+ },
270
+
271
+ select: function(i) {
272
+ var selectedValue = this.suggestions[i];
273
+ if (selectedValue) {
274
+ this.updateValue(selectedValue);
275
+ if (this.options.autoSubmit && this.el.form) {
276
+ this.el.form.submit();
277
+ }
278
+ this.ignoreValueChange = true;
279
+ this.hide();
280
+ this.onSelect(i);
281
+ }
282
+ },
283
+
284
+ updateValue: function (selectedValue) {
285
+ if (this.options.multiple) {
286
+ var values = this.el.value.split(/,\s*/);
287
+ values.pop();
288
+ values.push(selectedValue, '');
289
+ this.el.value = values.uniq().join(', ');
290
+
291
+ } else {
292
+ this.el.value = selectedValue;
293
+ }
294
+ this.currentValue = this.el.value;
295
+ this.el.focus();
296
+ },
297
+
298
+ moveUp: function() {
299
+ if (this.selectedIndex === -1) { return; }
300
+ if (this.selectedIndex === 0) {
301
+ this.container.childNodes[0].className = '';
302
+ this.selectedIndex = -1;
303
+ this.updateValue(this.currentValue);
304
+ return;
305
+ }
306
+ this.adjustScroll(this.selectedIndex - 1);
307
+ },
308
+
309
+ moveDown: function() {
310
+ if (this.selectedIndex === (this.suggestions.length - 1)) { return; }
311
+ this.adjustScroll(this.selectedIndex + 1);
312
+ },
313
+
314
+ adjustScroll: function(i) {
315
+ var container = this.container;
316
+ var activeItem = this.activate(i);
317
+ var offsetTop = activeItem.offsetTop;
318
+ var upperBound = container.scrollTop;
319
+ var lowerBound = upperBound + this.options.maxHeight - 25;
320
+ if (offsetTop < upperBound) {
321
+ container.scrollTop = offsetTop;
322
+ } else if (offsetTop > lowerBound) {
323
+ container.scrollTop = offsetTop - this.options.maxHeight + 25;
324
+ }
325
+ this.updateValue(this.suggestions[i]);
326
+ },
327
+
328
+ onSelect: function(i) {
329
+ (this.options.onSelect || Prototype.emptyFunction)(this.suggestions[i], this.data[i]);
330
+ }
331
+
332
+ };
333
+
334
+ Event.observe(document, 'dom:loaded', function(){ Autocomplete.isDomLoaded = true; }, false);
@@ -0,0 +1,89 @@
1
+ @import compass/css3
2
+
3
+ // index
4
+
5
+ #tags-table
6
+ table
7
+ margin-bottom: 0
8
+
9
+ tr.secret
10
+ td
11
+ color: #999999
12
+ a
13
+ color: #999999
14
+
15
+ td.tag-title
16
+ font-size: 115%
17
+ font-weight: bold
18
+
19
+ td.tag-title a
20
+ text-decoration: none
21
+ color: black
22
+
23
+ td.tag-description
24
+ font-size: 80%
25
+ color: #999999
26
+
27
+ ul.cloud
28
+ list-style: none
29
+ margin: 0
30
+ padding: 0
31
+ line-height: 20px
32
+ ul.cloud li
33
+ display: inline
34
+ font-family: "Lucida Grande", "Bitstream Vera Sans", Helvetica, Verdana, Arial, sans-serif
35
+ letter-spacing: -0.04em
36
+ white-space: nowrap
37
+ line-height: 20px
38
+ margin: 0
39
+
40
+ // edit-page
41
+
42
+ p.title
43
+ width: 70%
44
+
45
+ p.keywords
46
+ width: 28%
47
+ float: right
48
+ margin: 0 1% 0 0
49
+
50
+ #content
51
+ form
52
+ .keywords
53
+ .textbox
54
+ font-family: Georgia, Palatino, "Times New Roman", Times, serif
55
+ font-size: 200%
56
+ width: 100%
57
+ color: #cc0000
58
+ margin-top: 4px
59
+
60
+ #attributes
61
+ clear: both
62
+
63
+ .autocomplete-w1
64
+ +box-shadow
65
+ position: absolute
66
+ top: 0
67
+ left: 0
68
+ background-color: #333
69
+
70
+ .autocomplete-w2
71
+ padding: 0
72
+
73
+ .autocomplete
74
+ font-family: Georgia, Palatino, "Times New Roman", Times, serif
75
+ width: 300px
76
+ opacity: 0.8
77
+ cursor: default
78
+ text-align: left
79
+ max-height: 350px
80
+ overflow: auto
81
+ margin: 0
82
+ div
83
+ padding: 5px 10px
84
+ white-space: nowrap
85
+ .selected
86
+ background: #f0f0f0
87
+ strong
88
+ font-weight: normal
89
+ color: #3399ff
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{radiant-taggable-extension}
8
- s.version = "1.2.3"
8
+ s.version = "1.2.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["spanner"]
12
- s.date = %q{2011-03-13}
12
+ s.date = %q{2011-04-12}
13
13
  s.description = %q{General purpose tagging extension: more versatile but less focused than the tags extension}
14
14
  s.email = %q{will@spanner.org}
15
15
  s.extra_rdoc_files = [
@@ -22,6 +22,7 @@ Gem::Specification.new do |s|
22
22
  "VERSION",
23
23
  "app/controllers/admin/taggings_controller.rb",
24
24
  "app/controllers/admin/tags_controller.rb",
25
+ "app/helpers/taggable_helper.rb",
25
26
  "app/models/tag.rb",
26
27
  "app/models/tagging.rb",
27
28
  "app/views/admin/pages/_edit_title.html.haml",
@@ -32,9 +33,12 @@ Gem::Specification.new do |s|
32
33
  "app/views/admin/tags/index.html.haml",
33
34
  "app/views/admin/tags/new.html.haml",
34
35
  "app/views/admin/tags/show.html.haml",
36
+ "config/locales/en.yml",
35
37
  "config/routes.rb",
36
38
  "db/migrate/001_create_tags.rb",
37
39
  "db/migrate/002_import_keywords.rb",
40
+ "db/migrate/20110316210834_structural_tags.rb",
41
+ "db/migrate/20110411075109_metaphones.rb",
38
42
  "lib/natcmp.rb",
39
43
  "lib/radiant-taggable-extension.rb",
40
44
  "lib/taggable_admin_page_controller.rb",
@@ -43,9 +47,13 @@ Gem::Specification.new do |s|
43
47
  "lib/taggable_page.rb",
44
48
  "lib/taggable_tags.rb",
45
49
  "lib/tasks/taggable_extension_tasks.rake",
50
+ "lib/text/double_metaphone.rb",
51
+ "lib/text/metaphone.rb",
46
52
  "public/images/admin/new-tag.png",
47
53
  "public/images/admin/tag.png",
48
- "public/stylesheets/admin/tags.css",
54
+ "public/javascripts/admin/taggable.js",
55
+ "public/javascripts/autocomplete.js",
56
+ "public/stylesheets/sass/admin/taggable.sass",
49
57
  "public/stylesheets/sass/tagcloud.sass",
50
58
  "radiant-taggable-extension.gemspec",
51
59
  "spec/datasets/tag_sites_dataset.rb",