rocket_cms 0.1.13

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.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +2 -0
  6. data/.travis.yml +19 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +36 -0
  10. data/Rakefile +1 -0
  11. data/app/assets/javascripts/head.load.js +707 -0
  12. data/app/assets/javascripts/jquery.placeholder.js +157 -0
  13. data/app/assets/javascripts/rocket_cms.js.coffee +10 -0
  14. data/app/assets/javascripts/rocket_cms/flash.js.coffee +3 -0
  15. data/app/assets/javascripts/rocket_cms/map.js.coffee +22 -0
  16. data/app/assets/stylesheets/rocket_cms.css.sass +2 -0
  17. data/app/assets/stylesheets/rocket_cms/flash.css.sass +66 -0
  18. data/app/assets/stylesheets/rocket_cms/normalize.css.scss +406 -0
  19. data/app/controllers/concerns/no_cache.rb +12 -0
  20. data/app/controllers/concerns/rs_errors.rb +58 -0
  21. data/app/controllers/concerns/rs_menu.rb +45 -0
  22. data/app/controllers/concerns/rs_pages.rb +41 -0
  23. data/app/controllers/contacts_controller.rb +29 -0
  24. data/app/controllers/news_controller.rb +22 -0
  25. data/app/controllers/pages_controller.rb +12 -0
  26. data/app/controllers/search_controller.rb +25 -0
  27. data/app/mailers/contact_mailer.rb +15 -0
  28. data/app/models/ckeditor/asset.rb +5 -0
  29. data/app/models/ckeditor/attachment_file.rb +15 -0
  30. data/app/models/ckeditor/picture.rb +16 -0
  31. data/app/models/concerns/boolean_field.rb +9 -0
  32. data/app/models/concerns/enableable.rb +8 -0
  33. data/app/models/concerns/geocodeable.rb +4 -0
  34. data/app/models/concerns/manual_slug.rb +38 -0
  35. data/app/models/concerns/mappable.rb +77 -0
  36. data/app/models/concerns/seoable.rb +35 -0
  37. data/app/models/concerns/sort_field.rb +12 -0
  38. data/app/models/concerns/sortable.rb +8 -0
  39. data/app/models/concerns/trackable.rb +8 -0
  40. data/app/models/contact_message.rb +6 -0
  41. data/app/models/menu.rb +6 -0
  42. data/app/models/news.rb +5 -0
  43. data/app/models/page.rb +6 -0
  44. data/app/views/contact_mailer/new_message_email.html.haml +15 -0
  45. data/app/views/contacts/new.html.haml +10 -0
  46. data/app/views/contacts/sent.html.haml +4 -0
  47. data/app/views/errors/_base.html.haml +3 -0
  48. data/app/views/errors/error_403.html.haml +1 -0
  49. data/app/views/errors/error_404.html.haml +1 -0
  50. data/app/views/errors/error_500.html.haml +1 -0
  51. data/app/views/news/index.html.haml +8 -0
  52. data/app/views/news/show.html.haml +8 -0
  53. data/app/views/pages/show.html.haml +1 -0
  54. data/app/views/rails_admin/main/_check_boxes.html.haml +27 -0
  55. data/app/views/rails_admin/main/_form_raw.html.haml +1 -0
  56. data/app/views/search/index.html.haml +19 -0
  57. data/app/views/shared/_admin_link.html.haml +3 -0
  58. data/app/views/shared/_messages.html.haml +7 -0
  59. data/app/views/shared/_meta.html.haml +6 -0
  60. data/app/views/shared/_obj.html.haml +14 -0
  61. data/app/views/shared/_og.html.haml +4 -0
  62. data/config/locales/en.rocket_admin.yml +6 -0
  63. data/config/locales/en.rs.yml +17 -0
  64. data/config/locales/ru.cancan.yml +4 -0
  65. data/config/locales/ru.devise.yml +65 -0
  66. data/config/locales/ru.kaminari.yml +17 -0
  67. data/config/locales/ru.models.yml +78 -0
  68. data/config/locales/ru.mongoid.yml +450 -0
  69. data/config/locales/ru.rails_admin.yml +147 -0
  70. data/config/locales/ru.rocket_admin.yml +6 -0
  71. data/config/locales/ru.rs.yml +17 -0
  72. data/config/locales/ru.simple_captcha.yml +3 -0
  73. data/config/locales/ru.simple_form.yml +9 -0
  74. data/lib/filename_to_slug.rb +32 -0
  75. data/lib/generators/rocket_cms/admin_generator.rb +20 -0
  76. data/lib/generators/rocket_cms/templates/ability.erb +17 -0
  77. data/lib/generators/rocket_cms/templates/admin.erb +71 -0
  78. data/lib/generators/rocket_cms/utils.rb +22 -0
  79. data/lib/history_tracker.rb +4 -0
  80. data/lib/rails_admin/custom_show_in_app.rb +39 -0
  81. data/lib/rocket_cms.rb +57 -0
  82. data/lib/rocket_cms/admin.rb +128 -0
  83. data/lib/rocket_cms/configuration.rb +54 -0
  84. data/lib/rocket_cms/controller.rb +24 -0
  85. data/lib/rocket_cms/elastic_search.rb +32 -0
  86. data/lib/rocket_cms/engine.rb +4 -0
  87. data/lib/rocket_cms/model.rb +16 -0
  88. data/lib/rocket_cms/models/contact_message.rb +37 -0
  89. data/lib/rocket_cms/models/menu.rb +16 -0
  90. data/lib/rocket_cms/models/news.rb +61 -0
  91. data/lib/rocket_cms/models/page.rb +86 -0
  92. data/lib/rocket_cms/patch.rb +58 -0
  93. data/lib/rocket_cms/rails_admin_menu.rb +137 -0
  94. data/lib/rocket_cms/railtie.rb +39 -0
  95. data/lib/rocket_cms/tasks.rb +14 -0
  96. data/lib/rocket_cms/version.rb +3 -0
  97. data/lib/smart_excerpt.rb +98 -0
  98. data/rocket_cms.gemspec +50 -0
  99. metadata +533 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ed1a64eb57baf03872734690527a98a0e18941ad
4
+ data.tar.gz: 86cf1d01b615448a3614335bbd3f082cc252191b
5
+ SHA512:
6
+ metadata.gz: f488b1b964d7aec6568244a537d1c77d07ac86846e5d8e82ba56838390c10da9597c7c27ebab134b781f1161e84c98390c57d0434bfcf0b9b67fef64b85da246
7
+ data.tar.gz: 3bcc95ae984e863f9564a45a0132c43ac4d815a6f09624e52778ab9d489be73ba4210c6d34e7a9bf54a5567bb10806ccdf5a66f155f8a0d9ea4ef76070c0c247
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ rocket_cms
data/.ruby-version ADDED
@@ -0,0 +1,2 @@
1
+ 2.1.2
2
+
data/.travis.yml ADDED
@@ -0,0 +1,19 @@
1
+ language: ruby
2
+ services: mongodb
3
+
4
+ notifications:
5
+ email: false
6
+
7
+ rvm:
8
+ - 1.9.3
9
+ - 2.0.0
10
+ - 2.1.0
11
+ - jruby-20mode
12
+
13
+ env:
14
+ - "UPLOADS=paperclip"
15
+ - "UPLOADS=carrierwave"
16
+
17
+ gemfile:
18
+ - gemfiles/mongoid-3.1.gemfile
19
+ - gemfiles/mongoid-4.0.gemfile
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rocket_cms.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 glebtv
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
+ # RocketCMS
2
+
3
+ rails_admin + mongoid + elasticsearch CMS
4
+
5
+ Very opinionated and tuned for our needs.
6
+
7
+ Sorry, no documentation or examples are availiable yet. Stay tuned
8
+
9
+ **Before 1.0 API and class names should be considered unstable and can change at
10
+ any time!**
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'rocket_cms'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install rocket_cms
25
+
26
+ ## Usage
27
+
28
+ TODO: Write usage instructions here
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,707 @@
1
+ ///#source 1 1 /src/1.0.0/load.js
2
+ /*! head.load - v1.0.3 */
3
+ /*
4
+ * HeadJS The only script in your <HEAD>
5
+ * Author Tero Piirainen (tipiirai)
6
+ * Maintainer Robert Hoffmann (itechnology)
7
+ * License MIT / http://bit.ly/mit-license
8
+ * WebSite http://headjs.com
9
+ */
10
+ (function (win, undefined) {
11
+ "use strict";
12
+
13
+ //#region variables
14
+ var doc = win.document,
15
+ domWaiters = [],
16
+ handlers = {}, // user functions waiting for events
17
+ assets = {}, // loadable items in various states
18
+ isAsync = "async" in doc.createElement("script") || "MozAppearance" in doc.documentElement.style || win.opera,
19
+ isDomReady,
20
+
21
+ /*** public API ***/
22
+ headVar = win.head_conf && win.head_conf.head || "head",
23
+ api = win[headVar] = (win[headVar] || function () { api.ready.apply(null, arguments); }),
24
+
25
+ // states
26
+ PRELOADING = 1,
27
+ PRELOADED = 2,
28
+ LOADING = 3,
29
+ LOADED = 4;
30
+ //#endregion
31
+
32
+ //#region PRIVATE functions
33
+
34
+ //#region Helper functions
35
+ function noop() {
36
+ // does nothing
37
+ }
38
+
39
+ function each(arr, callback) {
40
+ if (!arr) {
41
+ return;
42
+ }
43
+
44
+ // arguments special type
45
+ if (typeof arr === "object") {
46
+ arr = [].slice.call(arr);
47
+ }
48
+
49
+ // do the job
50
+ for (var i = 0, l = arr.length; i < l; i++) {
51
+ callback.call(arr, arr[i], i);
52
+ }
53
+ }
54
+
55
+ /* A must read: http://bonsaiden.github.com/JavaScript-Garden
56
+ ************************************************************/
57
+ function is(type, obj) {
58
+ var clas = Object.prototype.toString.call(obj).slice(8, -1);
59
+ return obj !== undefined && obj !== null && clas === type;
60
+ }
61
+
62
+ function isFunction(item) {
63
+ return is("Function", item);
64
+ }
65
+
66
+ function isArray(item) {
67
+ return is("Array", item);
68
+ }
69
+
70
+ function toLabel(url) {
71
+ ///<summary>Converts a url to a file label</summary>
72
+ var items = url.split("/"),
73
+ name = items[items.length - 1],
74
+ i = name.indexOf("?");
75
+
76
+ return i !== -1 ? name.substring(0, i) : name;
77
+ }
78
+
79
+ // INFO: this look like a "im triggering callbacks all over the place, but only wanna run it one time function" ..should try to make everything work without it if possible
80
+ // INFO: Even better. Look into promises/defered's like jQuery is doing
81
+ function one(callback) {
82
+ ///<summary>Execute a callback only once</summary>
83
+ callback = callback || noop;
84
+
85
+ if (callback._done) {
86
+ return;
87
+ }
88
+
89
+ callback();
90
+ callback._done = 1;
91
+ }
92
+ //#endregion
93
+
94
+ function conditional(test, success, failure, callback) {
95
+ ///<summary>
96
+ /// INFO: use cases:
97
+ /// head.test(condition, null , "file.NOk" , callback);
98
+ /// head.test(condition, "fileOk.js", null , callback);
99
+ /// head.test(condition, "fileOk.js", "file.NOk" , callback);
100
+ /// head.test(condition, "fileOk.js", ["file.NOk", "file.NOk"], callback);
101
+ /// head.test({
102
+ /// test : condition,
103
+ /// success : [{ label1: "file1Ok.js" }, { label2: "file2Ok.js" }],
104
+ /// failure : [{ label1: "file1NOk.js" }, { label2: "file2NOk.js" }],
105
+ /// callback: callback
106
+ /// );
107
+ /// head.test({
108
+ /// test : condition,
109
+ /// success : ["file1Ok.js" , "file2Ok.js"],
110
+ /// failure : ["file1NOk.js", "file2NOk.js"],
111
+ /// callback: callback
112
+ /// );
113
+ ///</summary>
114
+ var obj = (typeof test === "object") ? test : {
115
+ test: test,
116
+ success: !!success ? isArray(success) ? success : [success] : false,
117
+ failure: !!failure ? isArray(failure) ? failure : [failure] : false,
118
+ callback: callback || noop
119
+ };
120
+
121
+ // Test Passed ?
122
+ var passed = !!obj.test;
123
+
124
+ // Do we have a success case
125
+ if (passed && !!obj.success) {
126
+ obj.success.push(obj.callback);
127
+ api.load.apply(null, obj.success);
128
+ }
129
+ // Do we have a fail case
130
+ else if (!passed && !!obj.failure) {
131
+ obj.failure.push(obj.callback);
132
+ api.load.apply(null, obj.failure);
133
+ }
134
+ else {
135
+ callback();
136
+ }
137
+
138
+ return api;
139
+ }
140
+
141
+ function getAsset(item) {
142
+ ///<summary>
143
+ /// Assets are in the form of
144
+ /// {
145
+ /// name : label,
146
+ /// url : url,
147
+ /// state: state
148
+ /// }
149
+ ///</summary>
150
+ var asset = {};
151
+
152
+ if (typeof item === "object") {
153
+ for (var label in item) {
154
+ if (!!item[label]) {
155
+ asset = {
156
+ name: label,
157
+ url : item[label]
158
+ };
159
+ }
160
+ }
161
+ }
162
+ else {
163
+ asset = {
164
+ name: toLabel(item),
165
+ url : item
166
+ };
167
+ }
168
+
169
+ // is the item already existant
170
+ var existing = assets[asset.name];
171
+ if (existing && existing.url === asset.url) {
172
+ return existing;
173
+ }
174
+
175
+ assets[asset.name] = asset;
176
+ return asset;
177
+ }
178
+
179
+ function allLoaded(items) {
180
+ items = items || assets;
181
+
182
+ for (var name in items) {
183
+ if (items.hasOwnProperty(name) && items[name].state !== LOADED) {
184
+ return false;
185
+ }
186
+ }
187
+
188
+ return true;
189
+ }
190
+
191
+ function onPreload(asset) {
192
+ asset.state = PRELOADED;
193
+
194
+ each(asset.onpreload, function (afterPreload) {
195
+ afterPreload.call();
196
+ });
197
+ }
198
+
199
+ function preLoad(asset, callback) {
200
+ if (asset.state === undefined) {
201
+
202
+ asset.state = PRELOADING;
203
+ asset.onpreload = [];
204
+
205
+ loadAsset({ url: asset.url, type: "cache" }, function () {
206
+ onPreload(asset);
207
+ });
208
+ }
209
+ }
210
+
211
+ function apiLoadHack() {
212
+ /// <summary>preload with text/cache hack
213
+ ///
214
+ /// head.load("http://domain.com/file.js","http://domain.com/file.js", callBack)
215
+ /// head.load(["http://domain.com/file.js","http://domain.com/file.js"], callBack)
216
+ /// head.load({ label1: "http://domain.com/file.js" }, { label2: "http://domain.com/file.js" }, callBack)
217
+ /// head.load([{ label1: "http://domain.com/file.js" }, { label2: "http://domain.com/file.js" }], callBack)
218
+ /// </summary>
219
+ var args = arguments,
220
+ callback = args[args.length - 1],
221
+ rest = [].slice.call(args, 1),
222
+ next = rest[0];
223
+
224
+ if (!isFunction(callback)) {
225
+ callback = null;
226
+ }
227
+
228
+ // if array, repush as args
229
+ if (isArray(args[0])) {
230
+ args[0].push(callback);
231
+ api.load.apply(null, args[0]);
232
+
233
+ return api;
234
+ }
235
+
236
+ // multiple arguments
237
+ if (!!next) {
238
+ /* Preload with text/cache hack (not good!)
239
+ * http://blog.getify.com/on-script-loaders/
240
+ * http://www.nczonline.net/blog/2010/12/21/thoughts-on-script-loaders/
241
+ * If caching is not configured correctly on the server, then items could load twice !
242
+ *************************************************************************************/
243
+ each(rest, function (item) {
244
+ // item is not a callback or empty string
245
+ if (!isFunction(item) && !!item) {
246
+ preLoad(getAsset(item));
247
+ }
248
+ });
249
+
250
+ // execute
251
+ load(getAsset(args[0]), isFunction(next) ? next : function () {
252
+ api.load.apply(null, rest);
253
+ });
254
+ }
255
+ else {
256
+ // single item
257
+ load(getAsset(args[0]));
258
+ }
259
+
260
+ return api;
261
+ }
262
+
263
+ function apiLoadAsync() {
264
+ ///<summary>
265
+ /// simply load and let browser take care of ordering
266
+ ///
267
+ /// head.load("http://domain.com/file.js","http://domain.com/file.js", callBack)
268
+ /// head.load(["http://domain.com/file.js","http://domain.com/file.js"], callBack)
269
+ /// head.load({ label1: "http://domain.com/file.js" }, { label2: "http://domain.com/file.js" }, callBack)
270
+ /// head.load([{ label1: "http://domain.com/file.js" }, { label2: "http://domain.com/file.js" }], callBack)
271
+ ///</summary>
272
+ var args = arguments,
273
+ callback = args[args.length - 1],
274
+ items = {};
275
+
276
+ if (!isFunction(callback)) {
277
+ callback = null;
278
+ }
279
+
280
+ // if array, repush as args
281
+ if (isArray(args[0])) {
282
+ args[0].push(callback);
283
+ api.load.apply(null, args[0]);
284
+
285
+ return api;
286
+ }
287
+
288
+ // JRH 262#issuecomment-26288601
289
+ // First populate the items array.
290
+ // When allLoaded is called, all items will be populated.
291
+ // Issue when lazy loaded, the callback can execute early.
292
+ each(args, function (item, i) {
293
+ if (item !== callback) {
294
+ item = getAsset(item);
295
+ items[item.name] = item;
296
+ }
297
+ });
298
+
299
+ each(args, function (item, i) {
300
+ if (item !== callback) {
301
+ item = getAsset(item);
302
+
303
+ load(item, function () {
304
+ if (allLoaded(items)) {
305
+ one(callback);
306
+ }
307
+ });
308
+ }
309
+ });
310
+
311
+ return api;
312
+ }
313
+
314
+ function load(asset, callback) {
315
+ ///<summary>Used with normal loading logic</summary>
316
+ callback = callback || noop;
317
+
318
+ if (asset.state === LOADED) {
319
+ callback();
320
+ return;
321
+ }
322
+
323
+ // INFO: why would we trigger a ready event when its not really loaded yet ?
324
+ if (asset.state === LOADING) {
325
+ api.ready(asset.name, callback);
326
+ return;
327
+ }
328
+
329
+ if (asset.state === PRELOADING) {
330
+ asset.onpreload.push(function () {
331
+ load(asset, callback);
332
+ });
333
+ return;
334
+ }
335
+
336
+ asset.state = LOADING;
337
+
338
+ loadAsset(asset, function () {
339
+ asset.state = LOADED;
340
+
341
+ callback();
342
+
343
+ // handlers for this asset
344
+ each(handlers[asset.name], function (fn) {
345
+ one(fn);
346
+ });
347
+
348
+ // dom is ready & no assets are queued for loading
349
+ // INFO: shouldn't we be doing the same test above ?
350
+ if (isDomReady && allLoaded()) {
351
+ each(handlers.ALL, function (fn) {
352
+ one(fn);
353
+ });
354
+ }
355
+ });
356
+ }
357
+
358
+ function getExtension(url) {
359
+ url = url || "";
360
+
361
+ var items = url.split("?")[0].split(".");
362
+ return items[items.length-1].toLowerCase();
363
+ }
364
+
365
+ /* Parts inspired from: https://github.com/cujojs/curl
366
+ ******************************************************/
367
+ function loadAsset(asset, callback) {
368
+ callback = callback || noop;
369
+
370
+ function error(event) {
371
+ event = event || win.event;
372
+
373
+ // release event listeners
374
+ ele.onload = ele.onreadystatechange = ele.onerror = null;
375
+
376
+ // do callback
377
+ callback();
378
+
379
+ // need some more detailed error handling here
380
+ }
381
+
382
+ function process(event) {
383
+ event = event || win.event;
384
+
385
+ // IE 7/8 (2 events on 1st load)
386
+ // 1) event.type = readystatechange, s.readyState = loading
387
+ // 2) event.type = readystatechange, s.readyState = loaded
388
+
389
+ // IE 7/8 (1 event on reload)
390
+ // 1) event.type = readystatechange, s.readyState = complete
391
+
392
+ // event.type === 'readystatechange' && /loaded|complete/.test(s.readyState)
393
+
394
+ // IE 9 (3 events on 1st load)
395
+ // 1) event.type = readystatechange, s.readyState = loading
396
+ // 2) event.type = readystatechange, s.readyState = loaded
397
+ // 3) event.type = load , s.readyState = loaded
398
+
399
+ // IE 9 (2 events on reload)
400
+ // 1) event.type = readystatechange, s.readyState = complete
401
+ // 2) event.type = load , s.readyState = complete
402
+
403
+ // event.type === 'load' && /loaded|complete/.test(s.readyState)
404
+ // event.type === 'readystatechange' && /loaded|complete/.test(s.readyState)
405
+
406
+ // IE 10 (3 events on 1st load)
407
+ // 1) event.type = readystatechange, s.readyState = loading
408
+ // 2) event.type = load , s.readyState = complete
409
+ // 3) event.type = readystatechange, s.readyState = loaded
410
+
411
+ // IE 10 (3 events on reload)
412
+ // 1) event.type = readystatechange, s.readyState = loaded
413
+ // 2) event.type = load , s.readyState = complete
414
+ // 3) event.type = readystatechange, s.readyState = complete
415
+
416
+ // event.type === 'load' && /loaded|complete/.test(s.readyState)
417
+ // event.type === 'readystatechange' && /complete/.test(s.readyState)
418
+
419
+ // Other Browsers (1 event on 1st load)
420
+ // 1) event.type = load, s.readyState = undefined
421
+
422
+ // Other Browsers (1 event on reload)
423
+ // 1) event.type = load, s.readyState = undefined
424
+
425
+ // event.type == 'load' && s.readyState = undefined
426
+
427
+ // !doc.documentMode is for IE6/7, IE8+ have documentMode
428
+ if (event.type === "load" || (/loaded|complete/.test(ele.readyState) && (!doc.documentMode || doc.documentMode < 9))) {
429
+ // remove timeouts
430
+ win.clearTimeout(asset.errorTimeout);
431
+ win.clearTimeout(asset.cssTimeout);
432
+
433
+ // release event listeners
434
+ ele.onload = ele.onreadystatechange = ele.onerror = null;
435
+
436
+ // do callback
437
+ callback();
438
+ }
439
+ }
440
+
441
+ function isCssLoaded() {
442
+ // should we test again ? 20 retries = 5secs ..after that, the callback will be triggered by the error handler at 7secs
443
+ if (asset.state !== LOADED && asset.cssRetries <= 20) {
444
+
445
+ // loop through stylesheets
446
+ for (var i = 0, l = doc.styleSheets.length; i < l; i++) {
447
+ // do we have a match ?
448
+ // we need to tests agains ele.href and not asset.url, because a local file will be assigned the full http path on a link element
449
+ if (doc.styleSheets[i].href === ele.href) {
450
+ process({ "type": "load" });
451
+ return;
452
+ }
453
+ }
454
+
455
+ // increment & try again
456
+ asset.cssRetries++;
457
+ asset.cssTimeout = win.setTimeout(isCssLoaded, 250);
458
+ }
459
+ }
460
+
461
+ var ele;
462
+ var ext = getExtension(asset.url);
463
+
464
+ if (ext === "css") {
465
+ ele = doc.createElement("link");
466
+ ele.type = "text/" + (asset.type || "css");
467
+ ele.rel = "stylesheet";
468
+ ele.href = asset.url;
469
+
470
+ /* onload supported for CSS on unsupported browsers
471
+ * Safari windows 5.1.7, FF < 10
472
+ */
473
+
474
+ // Set counter to zero
475
+ asset.cssRetries = 0;
476
+ asset.cssTimeout = win.setTimeout(isCssLoaded, 500);
477
+ }
478
+ else {
479
+ ele = doc.createElement("script");
480
+ ele.type = "text/" + (asset.type || "javascript");
481
+ ele.src = asset.url;
482
+ }
483
+
484
+ ele.onload = ele.onreadystatechange = process;
485
+ ele.onerror = error;
486
+
487
+ /* Good read, but doesn't give much hope !
488
+ * http://blog.getify.com/on-script-loaders/
489
+ * http://www.nczonline.net/blog/2010/12/21/thoughts-on-script-loaders/
490
+ * https://hacks.mozilla.org/2009/06/defer/
491
+ */
492
+
493
+ // ASYNC: load in parallel and execute as soon as possible
494
+ ele.async = false;
495
+ // DEFER: load in parallel but maintain execution order
496
+ ele.defer = false;
497
+
498
+ // timout for asset loading
499
+ asset.errorTimeout = win.setTimeout(function () {
500
+ error({ type: "timeout" });
501
+ }, 7e3);
502
+
503
+ // use insertBefore to keep IE from throwing Operation Aborted (thx Bryan Forbes!)
504
+ var head = doc.head || doc.getElementsByTagName("head")[0];
505
+
506
+ // but insert at end of head, because otherwise if it is a stylesheet, it will not override values
507
+ head.insertBefore(ele, head.lastChild);
508
+ }
509
+
510
+ /* Parts inspired from: https://github.com/jrburke/requirejs
511
+ ************************************************************/
512
+ function init() {
513
+ var items = doc.getElementsByTagName("script");
514
+
515
+ // look for a script with a data-head-init attribute
516
+ for (var i = 0, l = items.length; i < l; i++) {
517
+ var dataMain = items[i].getAttribute("data-headjs-load");
518
+ if (!!dataMain) {
519
+ api.load(dataMain);
520
+ return;
521
+ }
522
+ }
523
+ }
524
+
525
+ function ready(key, callback) {
526
+ ///<summary>
527
+ /// INFO: use cases:
528
+ /// head.ready(callBack);
529
+ /// head.ready(document , callBack);
530
+ /// head.ready("file.js", callBack);
531
+ /// head.ready("label" , callBack);
532
+ /// head.ready(["label1", "label2"], callback);
533
+ ///</summary>
534
+
535
+ // DOM ready check: head.ready(document, function() { });
536
+ if (key === doc) {
537
+ if (isDomReady) {
538
+ one(callback);
539
+ }
540
+ else {
541
+ domWaiters.push(callback);
542
+ }
543
+
544
+ return api;
545
+ }
546
+
547
+ // shift arguments
548
+ if (isFunction(key)) {
549
+ callback = key;
550
+ key = "ALL"; // holds all callbacks that where added without labels: ready(callBack)
551
+ }
552
+
553
+ // queue all items from key and return. The callback will be executed if all items from key are already loaded.
554
+ if (isArray(key)) {
555
+ var items = {};
556
+
557
+ each(key, function (item) {
558
+ items[item] = assets[item];
559
+
560
+ api.ready(item, function() {
561
+ if (allLoaded(items)) {
562
+ one(callback);
563
+ }
564
+ });
565
+ });
566
+
567
+ return api;
568
+ }
569
+
570
+ // make sure arguments are sane
571
+ if (typeof key !== "string" || !isFunction(callback)) {
572
+ return api;
573
+ }
574
+
575
+ // this can also be called when we trigger events based on filenames & labels
576
+ var asset = assets[key];
577
+
578
+ // item already loaded --> execute and return
579
+ if (asset && asset.state === LOADED || key === "ALL" && allLoaded() && isDomReady) {
580
+ one(callback);
581
+ return api;
582
+ }
583
+
584
+ var arr = handlers[key];
585
+ if (!arr) {
586
+ arr = handlers[key] = [callback];
587
+ }
588
+ else {
589
+ arr.push(callback);
590
+ }
591
+
592
+ return api;
593
+ }
594
+
595
+ /* Mix of stuff from jQuery & IEContentLoaded
596
+ * http://dev.w3.org/html5/spec/the-end.html#the-end
597
+ ***************************************************/
598
+ function domReady() {
599
+ // Make sure body exists, at least, in case IE gets a little overzealous (jQuery ticket #5443).
600
+ if (!doc.body) {
601
+ // let's not get nasty by setting a timeout too small.. (loop mania guaranteed if assets are queued)
602
+ win.clearTimeout(api.readyTimeout);
603
+ api.readyTimeout = win.setTimeout(domReady, 50);
604
+ return;
605
+ }
606
+
607
+ if (!isDomReady) {
608
+ isDomReady = true;
609
+
610
+ init();
611
+ each(domWaiters, function (fn) {
612
+ one(fn);
613
+ });
614
+ }
615
+ }
616
+
617
+ function domContentLoaded() {
618
+ // W3C
619
+ if (doc.addEventListener) {
620
+ doc.removeEventListener("DOMContentLoaded", domContentLoaded, false);
621
+ domReady();
622
+ }
623
+
624
+ // IE
625
+ else if (doc.readyState === "complete") {
626
+ // we're here because readyState === "complete" in oldIE
627
+ // which is good enough for us to call the dom ready!
628
+ doc.detachEvent("onreadystatechange", domContentLoaded);
629
+ domReady();
630
+ }
631
+ }
632
+
633
+ // Catch cases where ready() is called after the browser event has already occurred.
634
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
635
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
636
+ if (doc.readyState === "complete") {
637
+ domReady();
638
+ }
639
+
640
+ // W3C
641
+ else if (doc.addEventListener) {
642
+ doc.addEventListener("DOMContentLoaded", domContentLoaded, false);
643
+
644
+ // A fallback to window.onload, that will always work
645
+ win.addEventListener("load", domReady, false);
646
+ }
647
+
648
+ // IE
649
+ else {
650
+ // Ensure firing before onload, maybe late but safe also for iframes
651
+ doc.attachEvent("onreadystatechange", domContentLoaded);
652
+
653
+ // A fallback to window.onload, that will always work
654
+ win.attachEvent("onload", domReady);
655
+
656
+ // If IE and not a frame
657
+ // continually check to see if the document is ready
658
+ var top = false;
659
+
660
+ try {
661
+ top = !win.frameElement && doc.documentElement;
662
+ } catch (e) { }
663
+
664
+ if (top && top.doScroll) {
665
+ (function doScrollCheck() {
666
+ if (!isDomReady) {
667
+ try {
668
+ // Use the trick by Diego Perini
669
+ // http://javascript.nwbox.com/IEContentLoaded/
670
+ top.doScroll("left");
671
+ } catch (error) {
672
+ // let's not get nasty by setting a timeout too small.. (loop mania guaranteed if assets are queued)
673
+ win.clearTimeout(api.readyTimeout);
674
+ api.readyTimeout = win.setTimeout(doScrollCheck, 50);
675
+ return;
676
+ }
677
+
678
+ // and execute any waiting functions
679
+ domReady();
680
+ }
681
+ }());
682
+ }
683
+ }
684
+ //#endregion
685
+
686
+ //#region Public Exports
687
+ // INFO: determine which method to use for loading
688
+ api.load = api.js = isAsync ? apiLoadAsync : apiLoadHack;
689
+ api.test = conditional;
690
+ api.ready = ready;
691
+ //#endregion
692
+
693
+ //#region INIT
694
+ // perform this when DOM is ready
695
+ api.ready(doc, function () {
696
+ if (allLoaded()) {
697
+ each(handlers.ALL, function (callback) {
698
+ one(callback);
699
+ });
700
+ }
701
+
702
+ if (api.feature) {
703
+ api.feature("domloaded", true);
704
+ }
705
+ });
706
+ //#endregion
707
+ }(window));