dolt 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/Gemfile.lock +3 -3
  2. data/Readme.org +161 -0
  3. data/bin/dolt +23 -3
  4. data/dolt.gemspec +1 -1
  5. data/vendor/ui/.gitignore +3 -1
  6. data/vendor/ui/.gitmodules +21 -18
  7. data/vendor/ui/build +18 -19
  8. data/vendor/ui/buster.js +20 -5
  9. data/vendor/ui/css/gitorious.css +19 -3
  10. data/vendor/ui/dist/gts-ui-deps.js +1 -27
  11. data/vendor/ui/images/gitorious.png +0 -0
  12. data/vendor/ui/js/src/app.js +345 -0
  13. data/vendor/ui/js/src/components/abbrev.js +25 -0
  14. data/vendor/ui/js/src/components/commit-linker.js +26 -0
  15. data/vendor/ui/js/src/components/profile-menu.js +34 -0
  16. data/vendor/ui/js/{components → src/components}/ref-selector.js +32 -4
  17. data/vendor/ui/js/src/components/tree-history.js +138 -0
  18. data/vendor/ui/js/src/components/url.js +64 -0
  19. data/vendor/ui/js/{gitorious.js → src/gitorious.js} +19 -0
  20. data/vendor/ui/js/test-libs/jquery-1.9.1.min.js +5 -0
  21. data/vendor/ui/js/test/app-test.js +386 -0
  22. data/vendor/ui/{test → js/test/components}/abbrev-test.js +0 -0
  23. data/vendor/ui/js/test/components/commit-linker-test.js +41 -0
  24. data/vendor/ui/js/test/components/profile-menu-test.js +46 -0
  25. data/vendor/ui/{test → js/test/components}/ref-selector-test.js +4 -1
  26. data/vendor/ui/{test → js/test/components}/tree-history-test.js +0 -0
  27. data/vendor/ui/{test → js/test/components}/url-template-test.js +9 -0
  28. data/vendor/ui/todo.org +4 -0
  29. metadata +38 -30
  30. data/Readme.md +0 -97
  31. data/vendor/ui/js/components/abbrev.js +0 -17
  32. data/vendor/ui/js/components/tree-history.js +0 -79
  33. data/vendor/ui/js/components/url.js +0 -31
@@ -0,0 +1,345 @@
1
+ /*global gts, dome, cull, bane, when*/
2
+ this.gts = this.gts || {};
3
+
4
+ /**
5
+ * An "app" is a mechanism for configuring "features"/UI components that should
6
+ * launch on page load and possibly also at later times. A "feature" is simply a
7
+ * function that may depend on an element, some data from the network and/or
8
+ * local variables. The app defines an API for adding such features, and
9
+ * provides a simple mechanism for them to declaratively state their
10
+ * dependencies.
11
+ *
12
+ * The app also has a mechanism for processing errors, logging debug information
13
+ * and reloading the features if e.g. parts of the page has been
14
+ * modified/reloaded.
15
+ *
16
+ * Environment variables
17
+ *
18
+ * Environment variables can be set by anyone at any time. This is typically
19
+ * useful whenever a server-side HTML template needs to inject some data into
20
+ * the client-side scripts, e.g.:
21
+ *
22
+ * <script>appInstance.env("ip_addr", "192.168.0.1");</script>
23
+ *
24
+ * Features can list environment variables as dependencies.
25
+ *
26
+ * Data
27
+ *
28
+ * Some features may require access to data that is not immediately present in
29
+ * the page. The typical example is data fetched via XMLHttpRequest, but 'data'
30
+ * is by no means restricted to that.
31
+ *
32
+ * Data is registered with a name and a function. The function will only ever be
33
+ * called if a task depends on it via its name. The function may either return
34
+ * some data directly, or return a promise. If it throws an error, or the
35
+ * returned promise rejects, the error will be passed on to the app's failure
36
+ * listeners. When the returned promise resolves, the result is passed to any
37
+ * features waiting for the data. Data may also have dependencies (e.g. on
38
+ * certain variables or other, see "Feature" below).
39
+ *
40
+ * Feature
41
+ *
42
+ * A "feature" is a function to be called on page load, and possibly also at a
43
+ * later point (e.g. if parts of the page has been rebuilt). Features may
44
+ * depend on specific elements to be available, data and environment variables.
45
+ * A feature may also have additional data provided as input for when it is
46
+ * called (if dependencies are resolved).
47
+ *
48
+ * Events
49
+ *
50
+ * The app emits the following events:
51
+ *
52
+ * "loading" (name)
53
+ *
54
+ * When a feature's dependencies are satiesfied, it is scheduled for loading. At
55
+ * this point some of the feature's input may still be unresolved (if any of it
56
+ * is the result of asynchronous operations). At this point, the feature may
57
+ * still fail to load, if asynchronous dependencies fail to materialize.
58
+ *
59
+ * "loaded" (name[, args...])
60
+ *
61
+ * When a feature has successfully materialized (i.e. the returned promise
62
+ * resolved, or it didn't return a promise). If a feature depends on multiple
63
+ * elements, this event will be emitted once per element.
64
+ *
65
+ * "pending" (name)
66
+ *
67
+ * A feature's dependencies were not satiesfied, thus it was not loaded. To
68
+ * investigate why the feature did not load, use the app object:
69
+ *
70
+ * app.on("pending", function (name) {
71
+ * app.dependencies(name); // [{ name: "A", loaded: false }, ...]
72
+ * });
73
+ *
74
+ * "error" (error)
75
+ *
76
+ * When errors occur, or promises are rejected as the app is loading features.
77
+ */
78
+ this.gts.app = function () {
79
+ var C = cull;
80
+
81
+ /**
82
+ * Check if `feature` has all its dependencies satiesfied in the `features`
83
+ * object (which uses feature/dependency names as keys, feature descriptions
84
+ * as values).
85
+ */
86
+ function dependenciesSatiesfied(features, feature) {
87
+ return C.reduce(function (satiesfied, dep) {
88
+ return satiesfied && features[dep] && features[dep].loaded;
89
+ }, true, feature.depends || []);
90
+ }
91
+
92
+ /**
93
+ * Return an array of "results" (return-values and/or resolved values from
94
+ * returned promises) of the features listed in `dependencies`.
95
+ */
96
+ function dependencyResults(features, deps) {
97
+ return C.map(function (dep) { return features[dep].result; }, deps);
98
+ }
99
+
100
+ /**
101
+ * Mark the feature as loaded and load it when all arguments have
102
+ * materialized.
103
+ */
104
+ function loadFeature(features, feature, element) {
105
+ var args = dependencyResults(features, feature.depends || []);
106
+ feature.loaded = true;
107
+ var deferred = when.defer();
108
+ feature.result = deferred.promise;
109
+ when.all(args).then(function (materialized) {
110
+ var allArgs = (element ? [element] : []).concat(materialized);
111
+ deferred.resolve(feature.action.apply(null, allArgs));
112
+ });
113
+ }
114
+
115
+ /**
116
+ * Attempt to load a feature in a given context. If the feature depends on
117
+ * elements, it will not be load if the provided context does not contain any
118
+ * matching elements.
119
+ */
120
+ function tryFeatureInContext(features, feature, context) {
121
+ var load = C.partial(loadFeature, features, feature);
122
+ if (feature.elements) {
123
+ C.doall(load, dome.byClass(feature.elements, context));
124
+ } else {
125
+ load();
126
+ }
127
+ }
128
+
129
+ /**
130
+ * When trying to load features, this function is used to determine if a
131
+ * feature is ready to be proactively loaded (and has not already been
132
+ * loaded).
133
+ */
134
+ function isReady(features, feature) {
135
+ return !feature.lazy &&
136
+ !feature.loaded &&
137
+ feature.action &&
138
+ dependenciesSatiesfied(features, feature);
139
+ }
140
+
141
+ /** Returns true if the feature is both pending (not loaded) and lazy */
142
+ function pendingLazy(feature) {
143
+ return feature && feature.lazy && !feature.loaded;
144
+ };
145
+
146
+ /**
147
+ * For all the features in `featureArr`, find the unique set of dependencies
148
+ * that are both pending and lazy.
149
+ */
150
+ function lazyDependencies(features, featureArr) {
151
+ var getDep = function (dep) { return features[dep]; };
152
+ return C.uniq(C.reduce(function (lazy, feature) {
153
+ return lazy.concat(C.select(
154
+ pendingLazy,
155
+ C.map(getDep, feature.depends || [])
156
+ ));
157
+ }, [], featureArr));
158
+ }
159
+
160
+ /** Set properties on all objects in the collection */
161
+ function setAll(objects, props) {
162
+ return C.doall(function (object) {
163
+ C.doall(function (prop) {
164
+ object[prop] = props[prop];
165
+ }, C.keys(props));
166
+ }, objects);
167
+ }
168
+
169
+ /** Temporarily mark a set of features as eager (i.e. not lazy) */
170
+ function makeEager(features) {
171
+ return setAll(features, { lazy: false, wasLazy: true });
172
+ }
173
+
174
+ /**
175
+ * Reset the state of features: Revert temporarily eager ones, and mark
176
+ * loaded features as not loaded so they can be considered for loading again
177
+ * (used for consecutive calls to load()).
178
+ */
179
+ function reset(features) {
180
+ C.doall(function (feature) {
181
+ // Environment variables don't have actions, they're always loaded
182
+ if (feature.action) { feature.loaded = false; }
183
+ if (feature.wasLazy) {
184
+ delete feature.wasLazy;
185
+ feature.lazy = true;
186
+ }
187
+ }, C.values(features));
188
+ }
189
+
190
+ /**
191
+ * Keep trying to load features until there are no more features ready to
192
+ * load. When one feature is enabled we start from the top again as that
193
+ * may have enabled features that were previously not ready.
194
+ */
195
+ function tryFeatures(app, features, context) {
196
+ var idx, feature, featureArr = C.values(features);
197
+ var deps = makeEager(lazyDependencies(features, featureArr));
198
+ var isReadyToLoad = C.partial(isReady, features);
199
+ var toTry = deps.concat(featureArr);
200
+
201
+ while ((feature = C.first(isReadyToLoad, toTry))) {
202
+ app.emit("loading", feature);
203
+ tryFeatureInContext(features, feature, context);
204
+ // If the feature is not loaded after trying, it's depending on
205
+ // elements, but no matching elements were found. Ignore this
206
+ // feature for now, re-evaluate during the next pass.
207
+ if (!feature.loaded) {
208
+ idx = C.indexOf(feature, toTry);
209
+ toTry = toTry.slice(0, idx).concat(toTry.slice(idx + 1));
210
+ }
211
+ }
212
+ }
213
+
214
+ function ensureUnique(features, name) {
215
+ if (features[name]) {
216
+ throw new Error("Cannot add duplicate " + name);
217
+ }
218
+ }
219
+
220
+ var appInstance;
221
+
222
+ function getDependencies() {
223
+ return cull.map(function (depName) {
224
+ return appInstance.features[depName] || {
225
+ name: depName,
226
+ type: "Unknown"
227
+ };
228
+ }, this.depends || []);
229
+ }
230
+
231
+ appInstance = bane.createEventEmitter({
232
+ features: {},
233
+
234
+ /**
235
+ * Set environment data. Values are not specially treated and can be
236
+ * anything. If the app has been loaded, setting an environment variable
237
+ * will result in trying to load pending features.
238
+ */
239
+ env: function (name, value) {
240
+ // ensureUnique(this.features, name);
241
+ this.env[name] = value;
242
+ this.feature(name, undefined, { result: value, loaded: true });
243
+ // this.features[name] = createFeature({ result: value, loaded: true });
244
+ // this.tryPending();
245
+ },
246
+
247
+ /**
248
+ * The data function may return a promise. If there are no tasks that
249
+ * depend on this piece of data, the function will never be called. It
250
+ * is possible to express dependencies for data - see lazy features
251
+ * below.
252
+ */
253
+ data: function (name, fn, opt) {
254
+ var options = opt || {};
255
+ if (typeof options.lazy !== "boolean") {
256
+ options.lazy = true;
257
+ }
258
+ return this.feature(name, fn, options);
259
+ },
260
+
261
+ /**
262
+ * Register a feature. Features may depend on environment variables,
263
+ * data, and even other features. Additionally, features may depend on
264
+ * DOM elements. DOM elements can only be selected by a single class
265
+ * name. If the class name matches no elements, the feature will not be
266
+ * called. Otherwise, the feature is called once for each element, like
267
+ * so:
268
+ *
269
+ * feature(element[, dependencies][, options]);
270
+ *
271
+ * Given the following feature:
272
+ *
273
+ * appInstance.feature("tweetui", loadTweets, {
274
+ * elements: "tweet-placeholder",
275
+ * depends: ["account", "tweets"]
276
+ * });
277
+ *
278
+ * Where "account" is an environment variable and "tweets" is a data
279
+ * event, the function will eventually be called like this:
280
+ *
281
+ * loadTweets(element1, accountValue, tweetsData);
282
+ * loadTweets(element2, accountValue, tweetsData);
283
+ * // ...
284
+ *
285
+ * If depending on another feature, its return-value will be the input.
286
+ * If the feature in question returned a promise, the resolution will be
287
+ * passed as input (after that feature has resolved).
288
+ *
289
+ * A feature may be "lazy", in which case it is only loaded if another
290
+ * feature depends on it. Data events are just lazy features, e.g.:
291
+ *
292
+ * appInstance.feature("tweets", function () {
293
+ * return reqwest({ url: "/tweets" });
294
+ * }, { lazy: true });
295
+ *
296
+ * Is equivalent to:
297
+ *
298
+ * appInstance.data("tweets", function () {
299
+ * return reqwest({ url: "/tweets" });
300
+ * });
301
+ *
302
+ * The `name` can be any string.
303
+ */
304
+ feature: function (name, fn, opt) {
305
+ ensureUnique(this.features, name);
306
+ var feature = opt || {};
307
+ feature.name = name;
308
+ feature.action = fn;
309
+ this.features[name] = feature;
310
+ this.features[name].dependencies = getDependencies;
311
+ this.tryPending();
312
+ },
313
+
314
+ /**
315
+ * Load the app. This function may be called multiple times. It
316
+ * optionally accepts a DOM element to use as its root. If it is not
317
+ * provided, the document itself is used as the root.
318
+ */
319
+ load: function (context) {
320
+ if (this.loaded) { reset(this.features); }
321
+ this.loaded = true;
322
+ this.context = context;
323
+ this.tryPending();
324
+ },
325
+
326
+ /**
327
+ * After loading the app, some features may still not be loaded if the
328
+ * elements they depend on are not available. `tryPending` retries all
329
+ * those features. If you call app.load(context) and then modify the DOM
330
+ * within the context element you may want to call this to ensure your
331
+ * modified elements are considered for pending features.
332
+ *
333
+ * If you want already loaded features to reload for newly added
334
+ * elements/changed DOM structure you need to call load() over again.
335
+ *
336
+ * If app is not loaded, this method does nothing.
337
+ */
338
+ tryPending: function () {
339
+ if (!this.loaded) { return; }
340
+ tryFeatures(this, this.features, this.context);
341
+ }
342
+ });
343
+
344
+ return appInstance;
345
+ };
@@ -0,0 +1,25 @@
1
+ /*global cull*/
2
+ // The global, shared Gitorious namespace
3
+ var gts = this.gts || {};
4
+
5
+ /**
6
+ * Abbreviates a string to fit within a predefined width. If the
7
+ * string is shorter than, or just as long as the specified width, it
8
+ * is returned untouched.
9
+ *
10
+ * If the optional suffix is provided, it is appended to the
11
+ * abbreviated string, and the full length of the abbreviated string
12
+ * with the suffix is guaranteed to not be wider than the specified
13
+ * width.
14
+ */
15
+ gts.abbrev = function (sentence, width, suffix) {
16
+ if (sentence.length <= width) { return sentence; }
17
+ suffix = suffix || "";
18
+
19
+ return cull.reduce(function (words, word) {
20
+ if (words.join(" ").length + word.length + suffix.length <= width) {
21
+ words.push(word);
22
+ }
23
+ return words;
24
+ }, [], sentence.split(" ")).join(" ") + suffix;
25
+ };
@@ -0,0 +1,26 @@
1
+ /*global cull, dome*/
2
+ // The global, shared Gitorious namespace
3
+ var gts = this.gts || {};
4
+
5
+ /**
6
+ * Adds a live event handler to a root element that fires on any
7
+ * element with the class "gts-commit-oid". This is used to link
8
+ * commit oids from Dolt to commit pages in Gitorious.
9
+ *
10
+ * root - The root element where "gts-commit-oid" links are expected
11
+ * urlTemplate - The URL template to link to. Should be a URL with the
12
+ * #{oid} placeholder.
13
+ * handler - The function to call when a commit link is clicked. The
14
+ * handler will be called with the resolved URL as its only
15
+ * argument.
16
+ */
17
+ (function (partial, d) {
18
+ gts.commitLinker = function (root, urlTemplate, handler) {
19
+ d.cn.add("gts-commit-linker", root);
20
+ d.delegate.bycn("gts-commit-oid", root, "click", function (e) {
21
+ handler(gts.url.render(urlTemplate, {
22
+ oid: dome.data.get("gts-commit-oid", this)
23
+ }));
24
+ });
25
+ };
26
+ }(cull.partial, dome));
@@ -0,0 +1,34 @@
1
+ /*global dome*/
2
+ // The global, shared Gitorious namespace
3
+ var gts = this.gts || {};
4
+
5
+ /**
6
+ * Builds the profile menu for the specified user and replaces the
7
+ * login button. If there is no user, nothing happens.
8
+ */
9
+ (function (d, e) {
10
+ function button(text, path, icon) {
11
+ var content = icon ? [e.i({className: "icon-" + icon}), text] : [text];
12
+ return e.li(e.a({ href: path }, content));
13
+ }
14
+
15
+ gts.profileMenu = function (root, user) {
16
+ if (!user) { return; }
17
+ d.setContent([
18
+ e.a({ className: "btn btn-inverse", href: user.dashboardPath },
19
+ e.i({ className: "icon-user icon-white" }, user.login)),
20
+ e.a({ className: "btn btn-inverse dropdown-toggle",
21
+ href: "#",
22
+ data: { toggle: "dropdown" }
23
+ }, e.span({ className: "caret" }, " ")),
24
+ e.ul({ className: "dropdown-menu" }, [
25
+ button("Edit", user.editPath, "pencil"),
26
+ button("Messages", user.messagesPath, "envelope"),
27
+ e.li({ className: "divider" }),
28
+ button("Dashboard", user.dashboardPath),
29
+ button("Public profile", user.profilePath),
30
+ button("Log out", user.logoutPath)
31
+ ])
32
+ ], root);
33
+ };
34
+ }(dome, dome.el));
@@ -1,13 +1,41 @@
1
1
  /*global cull*/
2
+ // The global, shared Gitorious namespace
2
3
  this.gts = this.gts || {};
3
4
 
4
- this.gts.refSelector = (function () {
5
- var e = cull.dom.el;
6
-
5
+ /**
6
+ * The ref selector builds an interactive "drop-down" menu from which the
7
+ * user can select a head or tag, or lookup a specific ref. The menu will
8
+ * reload the current page for the chosen ref.
9
+ *
10
+ * gts.refSelector(refs, current, urlTemplate)
11
+ *
12
+ * refs is an object containing available heads and tags and the oids they
13
+ * refer to, e.g.:
14
+ *
15
+ * { "heads": [["production", "24e9c0d4ce36fbe1dfca4029e3bd206d64e2eecc"],
16
+ * ["redesign", "4c22773401aa2cdd2391bc04443ad7eea193e7b6"],
17
+ * ["web-hooks", "a08053012b9aee5d9733fceb2cf3083f29d9aa7d"],
18
+ * ["master", "48ac677757da7ca052c59ebec0ded6e11eef2642"]],
19
+ * "tags": [["v2.4.3", "7a3cffcb3c3db89e8005962850e29a8aab2ab09b"]]
20
+ *
21
+ * current is the active ref on the current page, either the refname
22
+ * or the actual oid.
23
+ *
24
+ * urlTemplate is the URL that will be loaded, where the ref exists as a
25
+ * placeholder, e.g. "/gitorious/mainline/source/#{ref}:lib". A suitable
26
+ * template can be created from the current page's URL using
27
+ * gts.url.templatize(window.location.href, { ref: currentRef });
28
+ */
29
+ this.gts.refSelector = (function (e) {
7
30
  function tpl(template, ref) {
8
31
  return (template || "#{ref}").replace(/#\{ref\}/g, ref);
9
32
  }
10
33
 
34
+ /**
35
+ * Gets the current ref. type is {heads,tags,remotes} and
36
+ * refs[type] is an array of ref tuples where each tuple contains
37
+ * the object id and a ref (e.g. "master").
38
+ */
11
39
  function getCurrent(type, refName, refs) {
12
40
  return cull.select(function (ref) {
13
41
  return ref[0] === refName || ref[1] === refName;
@@ -73,4 +101,4 @@ this.gts.refSelector = (function () {
73
101
  className: "dropdown gts-branch-selector pull-right"
74
102
  }, [currentRefLink(refs, current), refsList(refs, urlTemplate)]);
75
103
  };
76
- }());
104
+ }(dome.el));