web-connect 0.2.2 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 810c9f86338860a0ced0f39617ccc0a0b9cff27f
4
- data.tar.gz: fdb92385501bde33487bb9ab8b340ce954083cc2
3
+ metadata.gz: a7b6165c1609c61f31bdc2da429040c476f0f7bf
4
+ data.tar.gz: 69f1ff505d4ccf3d6321d891b665f81ac96c4482
5
5
  SHA512:
6
- metadata.gz: 73e97a19149d5dc6e05267a2625cbc5446b7313764280d98e3da3b7591a53f112a6041774fb204689dfc357a887b798aa58473a69f9babc75c8357286c059a59
7
- data.tar.gz: f7a7bc98991f2a17cbffd0ad86e753d8a1c69a37600debe4bf314f9b537cbb2ef113a026e6a5b8f27e10230a8286aed11d9f066a06a39fa64e3eb46a74dad4fb
6
+ metadata.gz: 42793ad4d2d3806fd64c73ec47e31be5c3824bea42dbbec5f35f21150e9a9b231830bf928aa0196c6e383da4589898f85c1f2398b5813b63e176c82021297ecd
7
+ data.tar.gz: 97f707bfb922fd415a4817934ffb8e0a83b703b2b06b432ecba679778a4bf040aeb551972b950c098df0eb8bc13385fde5f3888439be73913d368c76eee09214
@@ -0,0 +1,16 @@
1
+ require 'erb'
2
+
3
+ module Netfira::WebConnect
4
+ class RackApp::Action
5
+
6
+ def send_file(path)
7
+ content = nil
8
+ if path.to_s.end_with? '.erb'
9
+ template = ERB.new(path.read)
10
+ content = template.result(binding)
11
+ end
12
+ raise RackApp::Exceptions::SendFile.new(path, content)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ class Netfira::WebConnect::RackApp
2
+ module Action::Version8
3
+ class Web < Action
4
+
5
+ WEB_BASE = Netfira::WebConnect::ROOT.parent + 'web'
6
+ SEARCH_PATH = [:public, :protected].map { |x| [x, WEB_BASE + x.to_s] }.to_h
7
+
8
+ def call
9
+ raise NotFound unless path
10
+ path = self.path.join '/'
11
+ options = SEARCH_PATH.map do |access, dir|
12
+ ['', '.erb'].map do |extension|
13
+ [access, dir.join(path + extension)]
14
+ end
15
+ end.flatten(1).select { |option| option[1].exist? }
16
+ raise NotFound if options.empty?
17
+ raise InternalServerError, "Ambiguous file name (#{options.length} variations)" unless options.length == 1
18
+ send_file options.first.last
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ module Netfira::WebConnect::RackApp::Exceptions
2
+ class SendFile < Base
3
+
4
+ attr_reader :path
5
+
6
+ def initialize(path, content = nil)
7
+ @path = path
8
+ @content = content
9
+ end
10
+
11
+ def content
12
+ @content ||= path.read.force_encoding('ASCII-8BIT')
13
+ end
14
+
15
+ end
16
+ end
@@ -4,3 +4,4 @@ module Netfira::WebConnect::RackApp::Exceptions
4
4
  end
5
5
 
6
6
  require_relative 'exceptions/http_exception'
7
+ require_relative 'exceptions/send_file'
@@ -12,6 +12,15 @@ module Netfira::WebConnect
12
12
  else
13
13
  make_response 200, body, headers
14
14
  end
15
+ rescue Exceptions::SendFile => e
16
+ [
17
+ 200,
18
+ {
19
+ 'Content-Type' => MIME::Types.type_for(e.path.to_s.sub(/\.erb$/, '')).first.content_type,
20
+ 'Content-Length' => e.content.length.to_s
21
+ },
22
+ [e.content]
23
+ ]
15
24
  rescue Exceptions::HttpException => e
16
25
  make_response e.code, e.body, e.headers, e.code
17
26
  end
@@ -1,6 +1,6 @@
1
1
  module Netfira
2
2
  module WebConnect
3
- VERSION = '0.2.2'
3
+ VERSION = '0.3.0'
4
4
  PLATFORM_AND_VERSION = 'Rack/' << VERSION
5
5
  end
6
6
  end
@@ -0,0 +1,4 @@
1
+ (function() {
2
+ this.fileTables = <%= Netfira::WebConnect.schema.select { |model| model.has_file? }.map{ |model| model[0].name.demodulize.pluralize }.to_json %>;
3
+ this.DELETE_KEY = '@delete';
4
+ }).call(this);
Binary file
@@ -0,0 +1,394 @@
1
+ nf.UI = {
2
+ Pager: function(owner) {
3
+ this.owner = owner;
4
+ this.current = 1;
5
+ this.total = 1;
6
+ },
7
+
8
+ Actions: function(owner) {
9
+ this.owner = owner || window;
10
+ this.items = {};
11
+ this.rootElement = null;
12
+ this.searchField = null;
13
+ },
14
+
15
+ Labels: {
16
+ create: 'Create',
17
+ createAll: 'Create All',
18
+ search: 'Search',
19
+ accept: 'Accept',
20
+ unpair: 'Unpair',
21
+ cancel: 'Cancel',
22
+ acceptAll: 'Accept all suggestions',
23
+ goToFirst: String.fromCharCode(0xAB), // looks like <<
24
+ goToLast: String.fromCharCode(0xBB), // looks like >>
25
+ goToPrevious: String.fromCharCode(0x2039), // looks like <
26
+ goToNext: String.fromCharCode(0x203A), // looks like >
27
+ of: 'of'
28
+ },
29
+
30
+ Tests: {
31
+
32
+ email: function(a, b) {
33
+
34
+ // direct comparison (case sensitive)
35
+ if(a == b) {
36
+ return 1;
37
+ }
38
+
39
+ // go case-insensitive
40
+ var x = (a || '').toLowerCase(),
41
+ y = (b || '').toLowerCase();
42
+
43
+ // direct comparision
44
+ if(x == y) {
45
+ return .95;
46
+ }
47
+
48
+ // compare without domain suffixes
49
+ if(x.replace(arguments.callee.variation, '') ==
50
+ y.replace(arguments.callee.variation, '')) {
51
+ return .85;
52
+ }
53
+
54
+ // use deprioritised string comparison
55
+ return nf.UI.Tests.string(a, b) / 3;
56
+ },
57
+
58
+ fullName: function(a, b) {
59
+
60
+ a = (a || '').trim();
61
+ b = (b || '').trim();
62
+
63
+ // direct comparison
64
+
65
+ if(a == b) {
66
+ return 1;
67
+ }
68
+ else if(a.toLowerCase() == b.toLowerCase()) {
69
+ return .9;
70
+ }
71
+
72
+ // go to word by word
73
+
74
+ var p = [a, b].map(function(x) {
75
+ return x
76
+ .trim()
77
+ .split(/[^a-z]+/i)
78
+ .filter(function(y) {
79
+ return y && nf.UI.Tests.fullName.salutations.indexOf(y.toLowerCase()) == -1;
80
+ });
81
+ });
82
+
83
+ var score = 0, i, j, k = p[0].length, l = p[1].length, x, y;
84
+
85
+ for(i = 0; i < k; i++) {
86
+ for(j = 0; j < l; j++) {
87
+
88
+ x = p[0][i]; // word from a
89
+ y = p[1][j]; // word from b
90
+
91
+ // whole word match (with case):
92
+ if(x == y) {
93
+ score += 1;
94
+ }
95
+
96
+ // go case insensitive
97
+ else {
98
+ x = x.toLowerCase();
99
+ y = y.toLowerCase();
100
+
101
+ // whole word match:
102
+ if(x == y) {
103
+ score += .8;
104
+ }
105
+
106
+ // shortened version (eg Rob for Robert or J for John):
107
+ else if(x.indexOf(y) == 0 || y.indexOf(x) == 0) {
108
+ score += .5;
109
+ }
110
+
111
+ // contained (eg Liz for Elizabeth):
112
+ else if(x.indexOf(y) >= 0 || y.indexOf(x) >= 0) {
113
+ score += .3;
114
+ }
115
+
116
+ // first letter (eg Jim for James):
117
+ else if(x.substr(0, 1) == y.substr(0, 1)) {
118
+ score += .1;
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ // max score will be 0.8. normalise to highest word count.
125
+ return .8 * score / Math.max(p[0].length, p[1].length);
126
+ },
127
+
128
+ string: function(a, b) {
129
+
130
+ if(a == b) {
131
+ return 1;
132
+ }
133
+
134
+ a = a || '';
135
+ b = b || '';
136
+
137
+ var i, x, y;
138
+ if(a.length > b.length) {
139
+ x = a.toLowerCase().trim();
140
+ y = b.toLowerCase().trim();
141
+ } else {
142
+ x = b.toLowerCase().trim();
143
+ y = a.toLowerCase().trim();
144
+ }
145
+
146
+ if(x === '' || y === '') {
147
+ return 0;
148
+ }
149
+
150
+ if(x.indexOf(y) != -1) {
151
+ return .95 * y.length / x.length;
152
+ }
153
+
154
+ return 0;
155
+
156
+ },
157
+
158
+ amount: function(a, b) {
159
+
160
+ if(a == b) {
161
+ return 1;
162
+ }
163
+
164
+ var x = parseFloat((a || 0).toString().replace(/[^-.\d]+/g, '')),
165
+ y = parseFloat((b || 0).toString().replace(/[^-.\d]+/g, ''));
166
+
167
+ if(isNaN(x) || isNaN(y)) {
168
+ return 0;
169
+ }
170
+
171
+ if(x == y) {
172
+ return 1;
173
+ }
174
+
175
+ if(x == 0 || y == 0) {
176
+ return 0;
177
+ }
178
+
179
+ return (x > y ? y / x : x / y) * 1.8 - 1;
180
+
181
+ }
182
+ }
183
+
184
+ };
185
+
186
+ nf.UI.Pager.prototype = {
187
+ goTo: function(dest) {
188
+ this.current = Math.max(Math.min(this.total, dest), 1);
189
+ this.update();
190
+ this.owner.update();
191
+ },
192
+
193
+ update: function(totalItems, itemsPerPage) {
194
+ if(typeof totalItems == 'number' && typeof itemsPerPage == 'number') {
195
+ this.total = Math.max(1, Math.ceil(totalItems / itemsPerPage));
196
+ this.current = Math.max(Math.min(this.current, this.total), 1);
197
+ }
198
+ this.currentElement.innerHTML = this.current.toString();
199
+ this.totalElement.innerHTML = this.total.toString();
200
+ nf.className(this.rootElement, 'bof', this.current == 1 ? 1 : -1);
201
+ nf.className(this.rootElement, 'eof', this.current == this.total ? 1 : -1);
202
+ },
203
+
204
+ init: function() {
205
+
206
+ var l = nf.UI.Labels, pager = this;
207
+
208
+ nf('<a>', this.rootElement, {
209
+ className: 'first',
210
+ href: 'javascript:',
211
+ innerHTML: l.goToFirst.toHTML(),
212
+ onclick: function() {
213
+ pager.goTo(1)
214
+ }
215
+ });
216
+
217
+ nf('<', this.rootElement, ' ');
218
+
219
+ nf('<a>', this.rootElement, {
220
+ className: 'previous',
221
+ href: 'javascript:',
222
+ innerHTML: l.goToPrevious.toHTML(),
223
+ onclick: function() {
224
+ pager.goTo(pager.current - 1)
225
+ }
226
+ });
227
+
228
+ nf('<', this.rootElement, ' ');
229
+
230
+ this.currentElement = nf('<span>', this.rootElement, {className: 'current'});
231
+
232
+ nf('<', this.rootElement, ' ');
233
+
234
+ nf('<span>', this.rootElement, {className: 'of', innerHTML: l.of.toHTML()});
235
+
236
+ nf('<', this.rootElement, ' ');
237
+
238
+ this.totalElement = nf('<span>', this.rootElement, {className: 'total'});
239
+
240
+ nf('<', this.rootElement, ' ');
241
+
242
+ nf('<a>', this.rootElement, {
243
+ className: 'next',
244
+ href: 'javascript:',
245
+ innerHTML: l.goToNext.toHTML(),
246
+ onclick: function() {
247
+ pager.goTo(pager.current + 1)
248
+ }
249
+ });
250
+
251
+ nf('<', this.rootElement, ' ');
252
+
253
+ nf('<a>', this.rootElement, {
254
+ className: 'last',
255
+ href: 'javascript:',
256
+ innerHTML: l.goToLast.toHTML(),
257
+ onclick: function() {
258
+ pager.goTo(pager.total)
259
+ }
260
+ });
261
+
262
+ this.update();
263
+ }
264
+ };
265
+
266
+ nf.UI.Actions.prototype = {
267
+ add: function(id, label, callback) {
268
+ if(!(id in this.items)) {
269
+ this.items[id] = [label, callback];
270
+ this.redraw();
271
+ }
272
+ },
273
+ remove: function(id) {
274
+ if(id in this.items) {
275
+ delete this.items[id];
276
+ this.redraw();
277
+ }
278
+ },
279
+ redraw: function() {
280
+ if(!this.rootElement) {
281
+ return;
282
+ }
283
+ this.rootElement.innerHTML = '';
284
+ var i = null;
285
+ for(i in this.items) {
286
+ nf('<a>', nf('<li>', this.rootElement, {className: i}), {
287
+ href: 'javascript:',
288
+ innerHTML: this.items[i][0].toHTML(),
289
+ onclick: (function(c, that) {
290
+ return function() {
291
+ c.call(that)
292
+ }
293
+ })(this.items[i][1], this.owner)
294
+ });
295
+ }
296
+ if(i) {
297
+ nf.className(this.rootElement.firstChild, 'first', 1);
298
+ nf.className(this.rootElement.childNodes[this.rootElement.childNodes.length - 1], 'last', 1);
299
+ }
300
+ this.redrawSearch();
301
+ },
302
+ redrawSearch: function() {
303
+
304
+ var searchValue = this.searchField ? this.searchField.value : '',
305
+ id, li, l, o;
306
+
307
+ if(this.owner.searchable) {
308
+
309
+ l = nf.UI.Labels.search;
310
+
311
+ li = nf('<li>', this.rootElement, {className: 'last search'});
312
+ if(this.rootElement.childNodes.length == 1) {
313
+ nf.className(li, 'first', 1);
314
+ }
315
+ else {
316
+ nf.className(li.previousSibling, 'last', -1);
317
+ }
318
+
319
+ id = this.owner.appId + 'Search';
320
+
321
+ nf('<label>', li, {
322
+ innerHTML: l.toHTML(),
323
+ htmlFor: id
324
+ });
325
+
326
+ o = this.owner;
327
+
328
+ this.searchField = nf('<input>', li, {
329
+ placeholder: l,
330
+ title: l,
331
+ type: 'text',
332
+ id: id,
333
+ value: searchValue,
334
+ onkeyup: function() {
335
+ o.update()
336
+ }
337
+ });
338
+
339
+ }
340
+ }
341
+ };
342
+
343
+ // list view class
344
+ nf.ListView = function(rootElement) {
345
+ this.rootElement = rootElement;
346
+ this.clear();
347
+ };
348
+
349
+ // list view class
350
+ nf.ListView.prototype = {
351
+ addItem: function() {
352
+ var ret = nf('<li>');
353
+ this.rootElement.appendChild(ret);
354
+ this.refreshClasses();
355
+ return ret;
356
+ },
357
+ activateItem: function(item) {
358
+ if(typeof item == 'number') {
359
+ item = this.rootElement.childNodes[item];
360
+ }
361
+ var i = this.rootElement.childNodes.length;
362
+ while(i--) {
363
+ nf.className(
364
+ this.rootElement.childNodes[i],
365
+ 'active',
366
+ this.rootElement.childNodes[i] === item ? 1 : -1
367
+ );
368
+ }
369
+ },
370
+ removeItem: function(item) {
371
+ if(typeof item == 'number') {
372
+ item = this.rootElement.childNodes[item];
373
+ }
374
+ this.rootElement.removeChild(item);
375
+ this.refreshClasses();
376
+ },
377
+ refreshClasses: function() {
378
+ var c = this.rootElement.childNodes,
379
+ i = c.length;
380
+ while(i--) {
381
+ nf.className(c[i], 'first', !i ? 1 : -1);
382
+ nf.className(c[i], 'last', i == c.length - 1 ? 1 : -1);
383
+ }
384
+ },
385
+ clear: function() {
386
+ if(this.rootElement) {
387
+ this.rootElement.innerHTML = '';
388
+ }
389
+ }
390
+ };
391
+
392
+ nf.UI.Tests.fullName.salutations = "mr mrs ms dr prof".split(' ');
393
+
394
+ nf.UI.Tests.email.variation = /\.(com|net|org|gov|edu|[a-z]{1,2})\b.*|\.+$/i;