flight-rails 0.0.1
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/.gitignore +17 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +36 -0
- data/Rakefile +1 -0
- data/flight-rails.gemspec +19 -0
- data/lib/flight-rails.rb +8 -0
- data/lib/flight-rails/engine.rb +5 -0
- data/lib/flight-rails/version.rb +5 -0
- data/vendor/assets/javascript/advice.js +75 -0
- data/vendor/assets/javascript/component.js +257 -0
- data/vendor/assets/javascript/compose.js +86 -0
- data/vendor/assets/javascript/flight.js +7 -0
- data/vendor/assets/javascript/index.js +30 -0
- data/vendor/assets/javascript/logger.js +93 -0
- data/vendor/assets/javascript/registry.js +234 -0
- data/vendor/assets/javascript/utils.js +228 -0
- metadata +62 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Viktor Tomilin
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Flight for Rails 3.1 Asset Pipeline
|
2
|
+
|
3
|
+
A lightweight, component-based JavaScript framework from Twitter.
|
4
|
+
|
5
|
+
Official repo: https://github.com/twitter/bower
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'flight-rails'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install flight-rails
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Remove all Sprockets directives such as //= require jquery from application.js and elsewhere. Instead establish JavaScript dependencies using AMD-style define() and require() calls.
|
24
|
+
|
25
|
+
## Dependencies
|
26
|
+
|
27
|
+
$ requirejs-rails
|
28
|
+
$ es5-shim-rails
|
29
|
+
|
30
|
+
## Contributing
|
31
|
+
|
32
|
+
1. Fork it
|
33
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
34
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
35
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
36
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'flight-rails/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "flight-rails"
|
8
|
+
gem.version = Flight::Rails::VERSION
|
9
|
+
gem.authors = ["Viktor Tomilin"]
|
10
|
+
gem.email = ["caballerosolar@gmail.com"]
|
11
|
+
gem.description = 'Flight: an event driven component framework'
|
12
|
+
gem.summary = 'Flight: an event driven component framework'
|
13
|
+
gem.homepage = "http://twitter.github.com/flight/"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
end
|
data/lib/flight-rails.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
// ==========================================
|
2
|
+
// Copyright 2013 Twitter, Inc
|
3
|
+
// Licensed under The MIT License
|
4
|
+
// http://opensource.org/licenses/MIT
|
5
|
+
// ==========================================
|
6
|
+
|
7
|
+
"use strict";
|
8
|
+
|
9
|
+
define(
|
10
|
+
|
11
|
+
[
|
12
|
+
'./utils',
|
13
|
+
'./compose'
|
14
|
+
],
|
15
|
+
|
16
|
+
function (util, compose) {
|
17
|
+
|
18
|
+
var advice = {
|
19
|
+
|
20
|
+
around: function(base, wrapped) {
|
21
|
+
return function() {
|
22
|
+
var args = util.toArray(arguments);
|
23
|
+
return wrapped.apply(this, [base.bind(this)].concat(args));
|
24
|
+
}
|
25
|
+
},
|
26
|
+
|
27
|
+
before: function(base, before) {
|
28
|
+
return this.around(base, function() {
|
29
|
+
var args = util.toArray(arguments),
|
30
|
+
orig = args.shift(),
|
31
|
+
beforeFn;
|
32
|
+
|
33
|
+
beforeFn = (typeof before == 'function') ? before : before.obj[before.fnName];
|
34
|
+
beforeFn.apply(this, args);
|
35
|
+
return (orig).apply(this, args);
|
36
|
+
});
|
37
|
+
},
|
38
|
+
|
39
|
+
after: function(base, after) {
|
40
|
+
return this.around(base, function() {
|
41
|
+
var args = util.toArray(arguments),
|
42
|
+
orig = args.shift(),
|
43
|
+
afterFn;
|
44
|
+
|
45
|
+
// this is a separate statement for debugging purposes.
|
46
|
+
var res = (orig.unbound || orig).apply(this, args);
|
47
|
+
|
48
|
+
afterFn = (typeof after == 'function') ? after : after.obj[after.fnName];
|
49
|
+
afterFn.apply(this, args);
|
50
|
+
return res;
|
51
|
+
});
|
52
|
+
},
|
53
|
+
|
54
|
+
// a mixin that allows other mixins to augment existing functions by adding additional
|
55
|
+
// code before, after or around.
|
56
|
+
withAdvice: function() {
|
57
|
+
['before', 'after', 'around'].forEach(function(m) {
|
58
|
+
this[m] = function(method, fn) {
|
59
|
+
|
60
|
+
compose.unlockProperty(this, method, function() {
|
61
|
+
if (typeof this[method] == 'function') {
|
62
|
+
return this[method] = advice[m](this[method], fn);
|
63
|
+
} else {
|
64
|
+
return this[method] = fn;
|
65
|
+
}
|
66
|
+
});
|
67
|
+
|
68
|
+
};
|
69
|
+
}, this);
|
70
|
+
}
|
71
|
+
};
|
72
|
+
|
73
|
+
return advice;
|
74
|
+
}
|
75
|
+
);
|
@@ -0,0 +1,257 @@
|
|
1
|
+
// ==========================================
|
2
|
+
// Copyright 2013 Twitter, Inc
|
3
|
+
// Licensed under The MIT License
|
4
|
+
// http://opensource.org/licenses/MIT
|
5
|
+
// ==========================================
|
6
|
+
|
7
|
+
"use strict";
|
8
|
+
|
9
|
+
define(
|
10
|
+
|
11
|
+
[
|
12
|
+
'./advice',
|
13
|
+
'./utils',
|
14
|
+
'./compose',
|
15
|
+
'./registry'
|
16
|
+
],
|
17
|
+
|
18
|
+
function(advice, utils, compose, registry) {
|
19
|
+
|
20
|
+
function teardownInstance(instanceInfo){
|
21
|
+
instanceInfo.events.slice().forEach(function(event) {
|
22
|
+
var args = [event.type];
|
23
|
+
|
24
|
+
event.element && args.unshift(event.element);
|
25
|
+
(typeof event.callback == 'function') && args.push(event.callback);
|
26
|
+
|
27
|
+
this.off.apply(this, args);
|
28
|
+
}, instanceInfo.instance);
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
function teardown() {
|
33
|
+
this.trigger("componentTearDown");
|
34
|
+
teardownInstance(registry.findInstanceInfo(this));
|
35
|
+
}
|
36
|
+
|
37
|
+
//teardown for all instances of this constructor
|
38
|
+
function teardownAll() {
|
39
|
+
var componentInfo = registry.findComponentInfo(this);
|
40
|
+
|
41
|
+
componentInfo && componentInfo.instances.slice().forEach(function(info) {
|
42
|
+
info.instance.teardown();
|
43
|
+
});
|
44
|
+
}
|
45
|
+
|
46
|
+
//common mixin allocates basic functionality - used by all component prototypes
|
47
|
+
//callback context is bound to component
|
48
|
+
function withBaseComponent() {
|
49
|
+
|
50
|
+
// delegate trigger, bind and unbind to an element
|
51
|
+
// if $element not supplied, use component's node
|
52
|
+
// other arguments are passed on
|
53
|
+
this.trigger = function() {
|
54
|
+
var $element, type, data;
|
55
|
+
var args = utils.toArray(arguments);
|
56
|
+
|
57
|
+
if (typeof args[args.length - 1] != "string") {
|
58
|
+
data = args.pop();
|
59
|
+
}
|
60
|
+
|
61
|
+
$element = (args.length == 2) ? $(args.shift()) : this.$node;
|
62
|
+
type = args[0];
|
63
|
+
|
64
|
+
if (window.DEBUG && window.postMessage) {
|
65
|
+
try {
|
66
|
+
window.postMessage(data, '*');
|
67
|
+
} catch(e) {
|
68
|
+
console.log('unserializable data for event',type,':',data);
|
69
|
+
throw new Error(
|
70
|
+
["The event", event.type, "on component", this.describe, "was triggered with non-serializable data"].join(" ")
|
71
|
+
);
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
if (typeof this.attr.eventData === 'object') {
|
76
|
+
data = $.extend(true, {}, this.attr.eventData, data);
|
77
|
+
}
|
78
|
+
|
79
|
+
return $element.trigger(type, data);
|
80
|
+
};
|
81
|
+
|
82
|
+
this.on = function() {
|
83
|
+
var $element, type, callback, originalCb;
|
84
|
+
var args = utils.toArray(arguments);
|
85
|
+
|
86
|
+
if (typeof args[args.length - 1] == "object") {
|
87
|
+
//delegate callback
|
88
|
+
originalCb = utils.delegate(
|
89
|
+
this.resolveDelegateRules(args.pop())
|
90
|
+
);
|
91
|
+
} else {
|
92
|
+
originalCb = args.pop();
|
93
|
+
}
|
94
|
+
|
95
|
+
callback = originalCb && originalCb.bind(this);
|
96
|
+
callback.target = originalCb;
|
97
|
+
|
98
|
+
$element = (args.length == 2) ? $(args.shift()) : this.$node;
|
99
|
+
type = args[0];
|
100
|
+
|
101
|
+
if (typeof callback == 'undefined') {
|
102
|
+
throw new Error("Unable to bind to '" + type + "' because the given callback is undefined");
|
103
|
+
}
|
104
|
+
|
105
|
+
$element.on(type, callback);
|
106
|
+
|
107
|
+
// get jquery's guid from our bound fn, so unbinding will work
|
108
|
+
originalCb.guid = callback.guid;
|
109
|
+
|
110
|
+
return callback;
|
111
|
+
};
|
112
|
+
|
113
|
+
this.off = function() {
|
114
|
+
var $element, type, callback;
|
115
|
+
var args = utils.toArray(arguments);
|
116
|
+
|
117
|
+
if (typeof args[args.length - 1] == "function") {
|
118
|
+
callback = args.pop();
|
119
|
+
}
|
120
|
+
|
121
|
+
$element = (args.length == 2) ? $(args.shift()) : this.$node;
|
122
|
+
type = args[0];
|
123
|
+
|
124
|
+
return $element.off(type, callback);
|
125
|
+
};
|
126
|
+
|
127
|
+
this.resolveDelegateRules = function(ruleInfo) {
|
128
|
+
var rules = {};
|
129
|
+
|
130
|
+
Object.keys(ruleInfo).forEach(
|
131
|
+
function(r) {
|
132
|
+
if (!this.attr.hasOwnProperty(r)) {
|
133
|
+
throw new Error('Component "' + this.describe + '" wants to listen on "' + r + '" but no such attribute was defined.');
|
134
|
+
}
|
135
|
+
rules[this.attr[r]] = ruleInfo[r];
|
136
|
+
},
|
137
|
+
this
|
138
|
+
);
|
139
|
+
|
140
|
+
return rules;
|
141
|
+
};
|
142
|
+
|
143
|
+
this.defaultAttrs = function(defaults) {
|
144
|
+
utils.push(this.defaults, defaults, true) || (this.defaults = defaults);
|
145
|
+
};
|
146
|
+
|
147
|
+
this.select = function(attributeKey) {
|
148
|
+
return this.$node.find(this.attr[attributeKey]);
|
149
|
+
};
|
150
|
+
|
151
|
+
this.initialize = $.noop;
|
152
|
+
this.teardown = teardown;
|
153
|
+
}
|
154
|
+
|
155
|
+
function attachTo(selector/*, options args */) {
|
156
|
+
if (!selector) {
|
157
|
+
throw new Error("Component needs to be attachTo'd a jQuery object, native node or selector string");
|
158
|
+
}
|
159
|
+
|
160
|
+
var options = utils.merge.apply(utils, utils.toArray(arguments, 1));
|
161
|
+
|
162
|
+
$(selector).each(function(i, node) {
|
163
|
+
new this(node, options);
|
164
|
+
}.bind(this));
|
165
|
+
}
|
166
|
+
|
167
|
+
// define the constructor for a custom component type
|
168
|
+
// takes an unlimited number of mixin functions as arguments
|
169
|
+
// typical api call with 3 mixins: define(timeline, withTweetCapability, withScrollCapability);
|
170
|
+
function define(/*mixins*/) {
|
171
|
+
var mixins = utils.toArray(arguments);
|
172
|
+
|
173
|
+
Component.toString = function() {
|
174
|
+
var prettyPrintMixins = mixins.map(function(mixin) {
|
175
|
+
if ($.browser.msie) {
|
176
|
+
var m = mixin.toString().match(/function (.*?)\s?\(/);
|
177
|
+
return (m && m[1]) ? m[1] : "";
|
178
|
+
} else {
|
179
|
+
return mixin.name;
|
180
|
+
}
|
181
|
+
}).join(', ').replace(/\s\,/g,'');//weed out no-named mixins
|
182
|
+
|
183
|
+
return prettyPrintMixins;
|
184
|
+
};
|
185
|
+
|
186
|
+
Component.describe = Component.toString();
|
187
|
+
|
188
|
+
//'options' is optional hash to be merged with 'defaults' in the component definition
|
189
|
+
function Component(node, options) {
|
190
|
+
var fnCache = {}, uuid = 0;
|
191
|
+
|
192
|
+
if (!node) {
|
193
|
+
throw new Error("Component needs a node");
|
194
|
+
}
|
195
|
+
|
196
|
+
if (node.jquery) {
|
197
|
+
this.node = node[0];
|
198
|
+
this.$node = node;
|
199
|
+
} else {
|
200
|
+
this.node = node;
|
201
|
+
this.$node = $(node);
|
202
|
+
}
|
203
|
+
|
204
|
+
this.describe = this.constructor.describe;
|
205
|
+
|
206
|
+
this.bind = function(func) {
|
207
|
+
var bound;
|
208
|
+
|
209
|
+
if (func.uuid && (bound = fnCache[func.uuid])) {
|
210
|
+
return bound;
|
211
|
+
}
|
212
|
+
|
213
|
+
var bindArgs = utils.toArray(arguments, 1);
|
214
|
+
bindArgs.unshift(this); //prepend context
|
215
|
+
|
216
|
+
bound = func.bind.apply(func, bindArgs);
|
217
|
+
bound.target = func;
|
218
|
+
func.uuid = uuid++;
|
219
|
+
fnCache[func.uuid] = bound;
|
220
|
+
|
221
|
+
return bound;
|
222
|
+
};
|
223
|
+
|
224
|
+
//merge defaults with supplied options
|
225
|
+
this.attr = utils.merge(this.defaults, options);
|
226
|
+
this.defaults && Object.keys(this.defaults).forEach(function(key) {
|
227
|
+
if (this.defaults[key] === null && this.attr[key] === null) {
|
228
|
+
throw new Error('Required attribute "' + key + '" not specified in attachTo for component "' + this.describe + '".');
|
229
|
+
}
|
230
|
+
}, this);
|
231
|
+
|
232
|
+
this.initialize.call(this, options || {});
|
233
|
+
|
234
|
+
this.trigger('componentInitialized');
|
235
|
+
}
|
236
|
+
|
237
|
+
Component.attachTo = attachTo;
|
238
|
+
Component.teardownAll = teardownAll;
|
239
|
+
|
240
|
+
// prepend common mixins to supplied list, then mixin all flavors
|
241
|
+
mixins.unshift(withBaseComponent, advice.withAdvice, registry.withRegistration);
|
242
|
+
|
243
|
+
compose.mixin(Component.prototype, mixins);
|
244
|
+
|
245
|
+
return Component;
|
246
|
+
}
|
247
|
+
|
248
|
+
define.teardownAll = function() {
|
249
|
+
registry.components.slice().forEach(function(c) {
|
250
|
+
c.component.teardownAll();
|
251
|
+
});
|
252
|
+
registry.reset();
|
253
|
+
};
|
254
|
+
|
255
|
+
return define;
|
256
|
+
}
|
257
|
+
);
|
@@ -0,0 +1,86 @@
|
|
1
|
+
// ==========================================
|
2
|
+
// Copyright 2013 Twitter, Inc
|
3
|
+
// Licensed under The MIT License
|
4
|
+
// http://opensource.org/licenses/MIT
|
5
|
+
// ==========================================
|
6
|
+
|
7
|
+
"use strict";
|
8
|
+
|
9
|
+
define(
|
10
|
+
|
11
|
+
[
|
12
|
+
'./utils',
|
13
|
+
'../tools/debug/debug'
|
14
|
+
],
|
15
|
+
|
16
|
+
function(util, debug) {
|
17
|
+
|
18
|
+
//enumerables are shims - getOwnPropertyDescriptor shim doesn't work
|
19
|
+
var canWriteProtect = debug.enabled && !util.isEnumerable(Object, 'getOwnPropertyDescriptor');
|
20
|
+
//whitelist of unlockable property names
|
21
|
+
var dontLock = ['mixedIn'];
|
22
|
+
|
23
|
+
if (canWriteProtect) {
|
24
|
+
//IE8 getOwnPropertyDescriptor is built-in but throws exeption on non DOM objects
|
25
|
+
try {
|
26
|
+
Object.getOwnPropertyDescriptor(Object, 'keys');
|
27
|
+
} catch(e) {
|
28
|
+
canWriteProtect = false;
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
function setPropertyWritability(obj, isWritable) {
|
33
|
+
if (!canWriteProtect) {
|
34
|
+
return;
|
35
|
+
}
|
36
|
+
|
37
|
+
var props = Object.create(null);
|
38
|
+
|
39
|
+
Object.keys(obj).forEach(
|
40
|
+
function (key) {
|
41
|
+
if (dontLock.indexOf(key) < 0) {
|
42
|
+
var desc = Object.getOwnPropertyDescriptor(obj, key);
|
43
|
+
desc.writable = isWritable;
|
44
|
+
props[key] = desc;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
);
|
48
|
+
|
49
|
+
Object.defineProperties(obj, props);
|
50
|
+
}
|
51
|
+
|
52
|
+
function unlockProperty(obj, prop, op) {
|
53
|
+
var writable;
|
54
|
+
|
55
|
+
if (!canWriteProtect || !obj.hasOwnProperty(prop)) {
|
56
|
+
op.call(obj);
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
|
60
|
+
writable = Object.getOwnPropertyDescriptor(obj, prop).writable;
|
61
|
+
Object.defineProperty(obj, prop, { writable: true });
|
62
|
+
op.call(obj);
|
63
|
+
Object.defineProperty(obj, prop, { writable: writable });
|
64
|
+
}
|
65
|
+
|
66
|
+
function mixin(base, mixins) {
|
67
|
+
base.mixedIn = base.hasOwnProperty('mixedIn') ? base.mixedIn : [];
|
68
|
+
|
69
|
+
mixins.forEach(function(mixin) {
|
70
|
+
if (base.mixedIn.indexOf(mixin) == -1) {
|
71
|
+
setPropertyWritability(base, false);
|
72
|
+
mixin.call(base);
|
73
|
+
base.mixedIn.push(mixin);
|
74
|
+
}
|
75
|
+
});
|
76
|
+
|
77
|
+
setPropertyWritability(base, true);
|
78
|
+
}
|
79
|
+
|
80
|
+
return {
|
81
|
+
mixin: mixin,
|
82
|
+
unlockProperty: unlockProperty
|
83
|
+
};
|
84
|
+
|
85
|
+
}
|
86
|
+
);
|
@@ -0,0 +1,30 @@
|
|
1
|
+
// ==========================================
|
2
|
+
// Copyright 2013 Twitter, Inc
|
3
|
+
// Licensed under The MIT License
|
4
|
+
// http://opensource.org/licenses/MIT
|
5
|
+
// ==========================================
|
6
|
+
|
7
|
+
define(
|
8
|
+
|
9
|
+
[
|
10
|
+
'./advice',
|
11
|
+
'./component',
|
12
|
+
'./compose',
|
13
|
+
'./logger',
|
14
|
+
'./registry',
|
15
|
+
'./utils'
|
16
|
+
],
|
17
|
+
|
18
|
+
function (advice, component, compose, logger, registry, utils) {
|
19
|
+
|
20
|
+
return {
|
21
|
+
advice: advice,
|
22
|
+
component: component,
|
23
|
+
compose: compose,
|
24
|
+
logger: logger,
|
25
|
+
registry: registry,
|
26
|
+
utils: utils
|
27
|
+
};
|
28
|
+
|
29
|
+
}
|
30
|
+
);
|
@@ -0,0 +1,93 @@
|
|
1
|
+
// ==========================================
|
2
|
+
// Copyright 2013 Twitter, Inc
|
3
|
+
// Licensed under The MIT License
|
4
|
+
// http://opensource.org/licenses/MIT
|
5
|
+
// ==========================================
|
6
|
+
|
7
|
+
"use strict";
|
8
|
+
|
9
|
+
define(
|
10
|
+
|
11
|
+
[
|
12
|
+
'./compose',
|
13
|
+
'./utils'
|
14
|
+
],
|
15
|
+
|
16
|
+
function (compose, util) {
|
17
|
+
|
18
|
+
var actionSymbols = {
|
19
|
+
on:'<-',
|
20
|
+
trigger: '->',
|
21
|
+
off: 'x '
|
22
|
+
};
|
23
|
+
|
24
|
+
function elemToString(elem) {
|
25
|
+
var tagStr = elem.tagName ? elem.tagName.toLowerCase() : elem.toString();
|
26
|
+
var classStr = elem.className ? "." + (elem.className) : "";
|
27
|
+
var result = tagStr + classStr;
|
28
|
+
return elem.tagName ? ['\'', '\''].join(result) : result;
|
29
|
+
}
|
30
|
+
|
31
|
+
function log(action, component, eventArgs) {
|
32
|
+
|
33
|
+
var name, elem, fn, fnName, logFilter, toRegExp, actionLoggable, nameLoggable;
|
34
|
+
|
35
|
+
if (typeof eventArgs[eventArgs.length-1] == 'function') {
|
36
|
+
fn = eventArgs.pop();
|
37
|
+
fn = fn.unbound || fn; //use unbound version if any (better info)
|
38
|
+
}
|
39
|
+
|
40
|
+
if (typeof eventArgs[eventArgs.length - 1] == 'object') {
|
41
|
+
eventArgs.pop(); //trigger data arg - not logged right now
|
42
|
+
}
|
43
|
+
|
44
|
+
if (eventArgs.length == 2) {
|
45
|
+
elem = eventArgs[0];
|
46
|
+
name = eventArgs[1];
|
47
|
+
} else {
|
48
|
+
elem = component.$node[0];
|
49
|
+
name = eventArgs[0];
|
50
|
+
}
|
51
|
+
|
52
|
+
if (window.DEBUG) {
|
53
|
+
logFilter = DEBUG.events.logFilter;
|
54
|
+
|
55
|
+
// no regex for you, actions...
|
56
|
+
actionLoggable = logFilter.actions=="all" || (logFilter.actions.indexOf(action) > -1);
|
57
|
+
// event name filter allow wildcards or regex...
|
58
|
+
toRegExp = function(expr) {
|
59
|
+
return expr.test ? expr : new RegExp("^" + expr.replace(/\*/g, ".*") + "$");
|
60
|
+
};
|
61
|
+
nameLoggable =
|
62
|
+
logFilter.eventNames=="all" ||
|
63
|
+
logFilter.eventNames.some(function(e) {return toRegExp(e).test(name)});
|
64
|
+
|
65
|
+
if (actionLoggable && nameLoggable) {
|
66
|
+
console.info(
|
67
|
+
actionSymbols[action],
|
68
|
+
action,
|
69
|
+
'[' + name + ']',
|
70
|
+
elemToString(elem),
|
71
|
+
component.constructor.describe,
|
72
|
+
fn && (fnName = fn.name || fn.displayName) && '-> ' + fnName
|
73
|
+
);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
|
79
|
+
function withLogging() {
|
80
|
+
this.before('trigger', function() {
|
81
|
+
log('trigger', this, util.toArray(arguments));
|
82
|
+
});
|
83
|
+
this.before('on', function() {
|
84
|
+
log('on', this, util.toArray(arguments));
|
85
|
+
});
|
86
|
+
this.before('off', function(eventArgs) {
|
87
|
+
log('off', this, util.toArray(arguments));
|
88
|
+
});
|
89
|
+
}
|
90
|
+
|
91
|
+
return withLogging;
|
92
|
+
}
|
93
|
+
);
|
@@ -0,0 +1,234 @@
|
|
1
|
+
// ==========================================
|
2
|
+
// Copyright 2013 Twitter, Inc
|
3
|
+
// Licensed under The MIT License
|
4
|
+
// http://opensource.org/licenses/MIT
|
5
|
+
// ==========================================
|
6
|
+
|
7
|
+
"use strict";
|
8
|
+
|
9
|
+
define(
|
10
|
+
|
11
|
+
[
|
12
|
+
'./utils'
|
13
|
+
],
|
14
|
+
|
15
|
+
function (util) {
|
16
|
+
|
17
|
+
function parseEventArgs(instance, args) {
|
18
|
+
var element, type, callback;
|
19
|
+
|
20
|
+
args = util.toArray(args);
|
21
|
+
|
22
|
+
if (typeof args[args.length-1] === 'function') {
|
23
|
+
callback = args.pop();
|
24
|
+
}
|
25
|
+
|
26
|
+
if (typeof args[args.length-1] === 'object') {
|
27
|
+
args.pop();
|
28
|
+
}
|
29
|
+
|
30
|
+
if (args.length == 2) {
|
31
|
+
element = args[0];
|
32
|
+
type = args[1];
|
33
|
+
} else {
|
34
|
+
element = instance.node;
|
35
|
+
type = args[0];
|
36
|
+
}
|
37
|
+
|
38
|
+
return {
|
39
|
+
element: element,
|
40
|
+
type: type,
|
41
|
+
callback: callback
|
42
|
+
};
|
43
|
+
}
|
44
|
+
|
45
|
+
function matchEvent(a, b) {
|
46
|
+
return (
|
47
|
+
(a.element == b.element) &&
|
48
|
+
(a.type == b.type) &&
|
49
|
+
(b.callback == null || (a.callback == b.callback))
|
50
|
+
);
|
51
|
+
}
|
52
|
+
|
53
|
+
function Registry() {
|
54
|
+
|
55
|
+
var registry = this;
|
56
|
+
|
57
|
+
(this.reset = function() {
|
58
|
+
this.components = [];
|
59
|
+
this.allInstances = [];
|
60
|
+
this.events = [];
|
61
|
+
}).call(this);
|
62
|
+
|
63
|
+
function ComponentInfo(component) {
|
64
|
+
this.component = component;
|
65
|
+
this.instances = [];
|
66
|
+
|
67
|
+
this.addInstance = function(instance) {
|
68
|
+
this.throwIfInstanceExistsOnNode(instance);
|
69
|
+
|
70
|
+
var instanceInfo = new InstanceInfo(instance);
|
71
|
+
this.instances.push(instanceInfo);
|
72
|
+
|
73
|
+
return instanceInfo;
|
74
|
+
}
|
75
|
+
|
76
|
+
this.throwIfInstanceExistsOnNode = function(instance) {
|
77
|
+
this.instances.forEach(function (instanceInfo) {
|
78
|
+
if (instanceInfo.instance.$node[0] === instance.$node[0]) {
|
79
|
+
throw new Error('Instance of ' + instance.constructor + ' already exists on node ' + instance.$node[0]);
|
80
|
+
}
|
81
|
+
});
|
82
|
+
}
|
83
|
+
|
84
|
+
this.removeInstance = function(instance) {
|
85
|
+
var instanceInfo = this.instances.filter(function(instanceInfo) {
|
86
|
+
return instanceInfo.instance == instance;
|
87
|
+
})[0];
|
88
|
+
|
89
|
+
var index = this.instances.indexOf(instanceInfo);
|
90
|
+
|
91
|
+
(index > -1) && this.instances.splice(index, 1);
|
92
|
+
|
93
|
+
if (!this.instances.length) {
|
94
|
+
//if I hold no more instances remove me from registry
|
95
|
+
registry.removeComponentInfo(this);
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
function InstanceInfo(instance) {
|
101
|
+
this.instance = instance;
|
102
|
+
this.events = [];
|
103
|
+
|
104
|
+
this.addTrigger = function() {};
|
105
|
+
|
106
|
+
this.addBind = function(event) {
|
107
|
+
this.events.push(event);
|
108
|
+
registry.events.push(event);
|
109
|
+
};
|
110
|
+
|
111
|
+
this.removeBind = function(event) {
|
112
|
+
for (var i = 0, e; e = this.events[i]; i++) {
|
113
|
+
if (matchEvent(e, event)) {
|
114
|
+
this.events.splice(i, 1);
|
115
|
+
}
|
116
|
+
}
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
this.addInstance = function(instance) {
|
121
|
+
var component = this.findComponentInfo(instance);
|
122
|
+
|
123
|
+
if (!component) {
|
124
|
+
component = new ComponentInfo(instance.constructor);
|
125
|
+
this.components.push(component);
|
126
|
+
}
|
127
|
+
|
128
|
+
var inst = component.addInstance(instance);
|
129
|
+
|
130
|
+
this.allInstances.push(inst);
|
131
|
+
|
132
|
+
return component;
|
133
|
+
};
|
134
|
+
|
135
|
+
this.removeInstance = function(instance) {
|
136
|
+
var index, instInfo = this.findInstanceInfo(instance);
|
137
|
+
|
138
|
+
//remove from component info
|
139
|
+
var componentInfo = this.findComponentInfo(instance);
|
140
|
+
componentInfo.removeInstance(instance);
|
141
|
+
|
142
|
+
//remove from registry
|
143
|
+
var index = this.allInstances.indexOf(instInfo);
|
144
|
+
(index > -1) && this.allInstances.splice(index, 1);
|
145
|
+
};
|
146
|
+
|
147
|
+
this.removeComponentInfo = function(componentInfo) {
|
148
|
+
var index = this.components.indexOf(componentInfo);
|
149
|
+
(index > -1) && this.components.splice(index, 1);
|
150
|
+
};
|
151
|
+
|
152
|
+
this.findComponentInfo = function(which) {
|
153
|
+
var component = which.attachTo ? which : which.constructor;
|
154
|
+
|
155
|
+
for (var i = 0, c; c = this.components[i]; i++) {
|
156
|
+
if (c.component === component) {
|
157
|
+
return c;
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
return null;
|
162
|
+
};
|
163
|
+
|
164
|
+
this.findInstanceInfo = function(which) {
|
165
|
+
var testFn;
|
166
|
+
|
167
|
+
if (which.node) {
|
168
|
+
//by instance (returns matched instance)
|
169
|
+
testFn = function(inst) {return inst.instance === which};
|
170
|
+
} else {
|
171
|
+
//by node (returns array of matches)
|
172
|
+
testFn = function(inst) {return inst.instance.node === which};
|
173
|
+
}
|
174
|
+
|
175
|
+
var matches = this.allInstances.filter(testFn);
|
176
|
+
if (!matches.length) {
|
177
|
+
return which.node ? null : [];
|
178
|
+
}
|
179
|
+
return which.node ? matches[0] : matches;
|
180
|
+
};
|
181
|
+
|
182
|
+
this.trigger = function() {
|
183
|
+
var event = parseEventArgs(this, arguments),
|
184
|
+
instance = registry.findInstanceInfo(this);
|
185
|
+
|
186
|
+
if (instance) {
|
187
|
+
instance.addTrigger(event);
|
188
|
+
}
|
189
|
+
};
|
190
|
+
|
191
|
+
this.on = function(componentOn) {
|
192
|
+
var otherArgs = util.toArray(arguments, 1);
|
193
|
+
var instance = registry.findInstanceInfo(this);
|
194
|
+
var boundCallback;
|
195
|
+
|
196
|
+
if (instance) {
|
197
|
+
boundCallback = componentOn.apply(null, otherArgs);
|
198
|
+
if(boundCallback) {
|
199
|
+
otherArgs[otherArgs.length-1] = boundCallback;
|
200
|
+
}
|
201
|
+
var event = parseEventArgs(this, otherArgs);
|
202
|
+
instance.addBind(event);
|
203
|
+
}
|
204
|
+
};
|
205
|
+
|
206
|
+
this.off = function(el, type, callback) {
|
207
|
+
var event = parseEventArgs(this, arguments),
|
208
|
+
instance = registry.findInstanceInfo(this);
|
209
|
+
|
210
|
+
if (instance) {
|
211
|
+
instance.removeBind(event);
|
212
|
+
}
|
213
|
+
};
|
214
|
+
|
215
|
+
this.teardown = function() {
|
216
|
+
registry.removeInstance(this);
|
217
|
+
};
|
218
|
+
|
219
|
+
this.withRegistration = function() {
|
220
|
+
this.before('initialize', function() {
|
221
|
+
registry.addInstance(this);
|
222
|
+
});
|
223
|
+
|
224
|
+
this.after('trigger', registry.trigger);
|
225
|
+
this.around('on', registry.on);
|
226
|
+
this.after('off', registry.off);
|
227
|
+
this.after('teardown', {obj:registry, fnName:'teardown'});
|
228
|
+
};
|
229
|
+
|
230
|
+
}
|
231
|
+
|
232
|
+
return new Registry;
|
233
|
+
}
|
234
|
+
);
|
@@ -0,0 +1,228 @@
|
|
1
|
+
// ==========================================
|
2
|
+
// Copyright 2013 Twitter, Inc
|
3
|
+
// Licensed under The MIT License
|
4
|
+
// http://opensource.org/licenses/MIT
|
5
|
+
// ==========================================
|
6
|
+
|
7
|
+
"use strict";
|
8
|
+
|
9
|
+
define(
|
10
|
+
|
11
|
+
[],
|
12
|
+
|
13
|
+
function () {
|
14
|
+
|
15
|
+
var arry = [];
|
16
|
+
var DEFAULT_INTERVAL = 100;
|
17
|
+
|
18
|
+
var utils = {
|
19
|
+
|
20
|
+
isDomObj: function(obj) {
|
21
|
+
return !!(obj.nodeType || (obj === window));
|
22
|
+
},
|
23
|
+
|
24
|
+
toArray: function(obj, from) {
|
25
|
+
return arry.slice.call(obj, from);
|
26
|
+
},
|
27
|
+
|
28
|
+
// returns new object representing multiple objects merged together
|
29
|
+
// optional final argument is boolean which specifies if merge is recursive
|
30
|
+
// original objects are unmodified
|
31
|
+
//
|
32
|
+
// usage:
|
33
|
+
// var base = {a:2, b:6};
|
34
|
+
// var extra = {b:3, c:4};
|
35
|
+
// merge(base, extra); //{a:2, b:3, c:4}
|
36
|
+
// base; //{a:2, b:6}
|
37
|
+
//
|
38
|
+
// var base = {a:2, b:6};
|
39
|
+
// var extra = {b:3, c:4};
|
40
|
+
// var extraExtra = {a:4, d:9};
|
41
|
+
// merge(base, extra, extraExtra); //{a:4, b:3, c:4. d: 9}
|
42
|
+
// base; //{a:2, b:6}
|
43
|
+
//
|
44
|
+
// var base = {a:2, b:{bb:4, cc:5}};
|
45
|
+
// var extra = {a:4, b:{cc:7, dd:1}};
|
46
|
+
// merge(base, extra, true); //{a:4, b:{bb:4, cc:7, dd:1}}
|
47
|
+
// base; //{a:2, b:6}
|
48
|
+
|
49
|
+
merge: function(/*obj1, obj2,....deepCopy*/) {
|
50
|
+
var args = this.toArray(arguments);
|
51
|
+
|
52
|
+
//start with empty object so a copy is created
|
53
|
+
args.unshift({});
|
54
|
+
|
55
|
+
if (args[args.length - 1] === true) {
|
56
|
+
//jquery extend requires deep copy as first arg
|
57
|
+
args.pop();
|
58
|
+
args.unshift(true);
|
59
|
+
}
|
60
|
+
|
61
|
+
return $.extend.apply(undefined, args);
|
62
|
+
},
|
63
|
+
|
64
|
+
// updates base in place by copying properties of extra to it
|
65
|
+
// optionally clobber protected
|
66
|
+
// usage:
|
67
|
+
// var base = {a:2, b:6};
|
68
|
+
// var extra = {c:4};
|
69
|
+
// push(base, extra); //{a:2, b:6, c:4}
|
70
|
+
// base; //{a:2, b:6, c:4}
|
71
|
+
//
|
72
|
+
// var base = {a:2, b:6};
|
73
|
+
// var extra = {b: 4 c:4};
|
74
|
+
// push(base, extra, true); //Error ("utils.push attempted to overwrite 'b' while running in protected mode")
|
75
|
+
// base; //{a:2, b:6}
|
76
|
+
//
|
77
|
+
// objects with the same key will merge recursively when protect is false
|
78
|
+
// eg:
|
79
|
+
// var base = {a:16, b:{bb:4, cc:10}};
|
80
|
+
// var extra = {b:{cc:25, dd:19}, c:5};
|
81
|
+
// push(base, extra); //{a:16, {bb:4, cc:25, dd:19}, c:5}
|
82
|
+
//
|
83
|
+
push: function(base, extra, protect) {
|
84
|
+
if (base) {
|
85
|
+
Object.keys(extra || {}).forEach(function(key) {
|
86
|
+
if (base[key] && protect) {
|
87
|
+
throw Error("utils.push attempted to overwrite '" + key + "' while running in protected mode");
|
88
|
+
}
|
89
|
+
|
90
|
+
if (typeof base[key] == "object" && typeof extra[key] == "object") {
|
91
|
+
//recurse
|
92
|
+
this.push(base[key], extra[key]);
|
93
|
+
} else {
|
94
|
+
//no protect, so extra wins
|
95
|
+
base[key] = extra[key];
|
96
|
+
}
|
97
|
+
}, this);
|
98
|
+
}
|
99
|
+
|
100
|
+
return base;
|
101
|
+
},
|
102
|
+
|
103
|
+
isEnumerable: function(obj, property) {
|
104
|
+
return Object.keys(obj).indexOf(property) > -1;
|
105
|
+
},
|
106
|
+
|
107
|
+
//build a function from other function(s)
|
108
|
+
//util.compose(a,b,c) -> a(b(c()));
|
109
|
+
//implementation lifted from underscore.js (c) 2009-2012 Jeremy Ashkenas
|
110
|
+
compose: function() {
|
111
|
+
var funcs = arguments;
|
112
|
+
|
113
|
+
return function() {
|
114
|
+
var args = arguments;
|
115
|
+
|
116
|
+
for (var i = funcs.length-1; i >= 0; i--) {
|
117
|
+
args = [funcs[i].apply(this, args)];
|
118
|
+
}
|
119
|
+
|
120
|
+
return args[0];
|
121
|
+
};
|
122
|
+
},
|
123
|
+
|
124
|
+
// Can only unique arrays of homogeneous primitives, e.g. an array of only strings, an array of only booleans, or an array of only numerics
|
125
|
+
uniqueArray: function(array) {
|
126
|
+
var u = {}, a = [];
|
127
|
+
|
128
|
+
for (var i = 0, l = array.length; i < l; ++i) {
|
129
|
+
if (u.hasOwnProperty(array[i])) {
|
130
|
+
continue;
|
131
|
+
}
|
132
|
+
|
133
|
+
a.push(array[i]);
|
134
|
+
u[array[i]] = 1;
|
135
|
+
}
|
136
|
+
|
137
|
+
return a;
|
138
|
+
},
|
139
|
+
|
140
|
+
debounce: function(func, wait, immediate) {
|
141
|
+
if (typeof wait != 'number') {
|
142
|
+
wait = DEFAULT_INTERVAL;
|
143
|
+
}
|
144
|
+
|
145
|
+
var timeout, result;
|
146
|
+
|
147
|
+
return function() {
|
148
|
+
var context = this, args = arguments;
|
149
|
+
var later = function() {
|
150
|
+
timeout = null;
|
151
|
+
if (!immediate) {
|
152
|
+
result = func.apply(context, args);
|
153
|
+
}
|
154
|
+
};
|
155
|
+
var callNow = immediate && !timeout;
|
156
|
+
|
157
|
+
clearTimeout(timeout);
|
158
|
+
timeout = setTimeout(later, wait);
|
159
|
+
|
160
|
+
if (callNow) {
|
161
|
+
result = func.apply(context, args);
|
162
|
+
}
|
163
|
+
|
164
|
+
return result;
|
165
|
+
};
|
166
|
+
},
|
167
|
+
|
168
|
+
throttle: function(func, wait) {
|
169
|
+
if (typeof wait != 'number') {
|
170
|
+
wait = DEFAULT_INTERVAL;
|
171
|
+
}
|
172
|
+
|
173
|
+
var context, args, timeout, throttling, more, result;
|
174
|
+
var whenDone = this.debounce(function(){
|
175
|
+
more = throttling = false;
|
176
|
+
}, wait);
|
177
|
+
|
178
|
+
return function() {
|
179
|
+
context = this; args = arguments;
|
180
|
+
var later = function() {
|
181
|
+
timeout = null;
|
182
|
+
if (more) {
|
183
|
+
result = func.apply(context, args);
|
184
|
+
}
|
185
|
+
whenDone();
|
186
|
+
};
|
187
|
+
|
188
|
+
if (!timeout) {
|
189
|
+
timeout = setTimeout(later, wait);
|
190
|
+
}
|
191
|
+
|
192
|
+
if (throttling) {
|
193
|
+
more = true;
|
194
|
+
} else {
|
195
|
+
throttling = true;
|
196
|
+
result = func.apply(context, args);
|
197
|
+
}
|
198
|
+
|
199
|
+
whenDone();
|
200
|
+
return result;
|
201
|
+
};
|
202
|
+
},
|
203
|
+
|
204
|
+
countThen: function(num, base) {
|
205
|
+
return function() {
|
206
|
+
if (!--num) { return base.apply(this, arguments); }
|
207
|
+
};
|
208
|
+
},
|
209
|
+
|
210
|
+
delegate: function(rules) {
|
211
|
+
return function(e, data) {
|
212
|
+
var target = $(e.target), parent;
|
213
|
+
|
214
|
+
Object.keys(rules).forEach(function(selector) {
|
215
|
+
if ((parent = target.closest(selector)).length) {
|
216
|
+
data = data || {};
|
217
|
+
data.el = parent[0];
|
218
|
+
return rules[selector].apply(this, [e, data]);
|
219
|
+
}
|
220
|
+
}, this);
|
221
|
+
};
|
222
|
+
}
|
223
|
+
|
224
|
+
};
|
225
|
+
|
226
|
+
return utils;
|
227
|
+
}
|
228
|
+
);
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flight-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Viktor Tomilin
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-02 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! 'Flight: an event driven component framework'
|
15
|
+
email:
|
16
|
+
- caballerosolar@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- Gemfile
|
23
|
+
- LICENSE.txt
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- flight-rails.gemspec
|
27
|
+
- lib/flight-rails.rb
|
28
|
+
- lib/flight-rails/engine.rb
|
29
|
+
- lib/flight-rails/version.rb
|
30
|
+
- vendor/assets/javascript/advice.js
|
31
|
+
- vendor/assets/javascript/component.js
|
32
|
+
- vendor/assets/javascript/compose.js
|
33
|
+
- vendor/assets/javascript/flight.js
|
34
|
+
- vendor/assets/javascript/index.js
|
35
|
+
- vendor/assets/javascript/logger.js
|
36
|
+
- vendor/assets/javascript/registry.js
|
37
|
+
- vendor/assets/javascript/utils.js
|
38
|
+
homepage: http://twitter.github.com/flight/
|
39
|
+
licenses: []
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
requirements: []
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.8.24
|
59
|
+
signing_key:
|
60
|
+
specification_version: 3
|
61
|
+
summary: ! 'Flight: an event driven component framework'
|
62
|
+
test_files: []
|