spontaneous 0.2.0.alpha7 → 0.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (247) hide show
  1. data/Gemfile +10 -4
  2. data/Readme.markdown +1 -1
  3. data/application/css/definitions.css.scss +5 -0
  4. data/application/css/dialogue.css.scss +62 -0
  5. data/application/js/content.js +1 -1
  6. data/application/js/dom.js +1 -1
  7. data/application/js/event_source.js +3 -0
  8. data/application/js/{field_types/date_field.js → field/date.js} +2 -2
  9. data/application/js/{field_types/file_field.js → field/file.js} +2 -2
  10. data/application/js/{field_types/image_field.js → field/image.js} +54 -20
  11. data/application/js/{field_types/long_string_field.js → field/long_string.js} +2 -2
  12. data/application/js/{field_types/markdown_field.js → field/markdown.js} +2 -2
  13. data/application/js/{field_types/select_field.js → field/select.js} +2 -2
  14. data/application/js/{field_types/string_field.js → field/string.js} +21 -7
  15. data/application/js/{field_types/webvideo_field.js → field/webvideo.js} +2 -2
  16. data/application/js/field.js +2 -2
  17. data/application/js/publish.js +99 -19
  18. data/application/js/spontaneous.js +8 -8
  19. data/application/js/top_bar.js +6 -4
  20. data/db/migrations/20130109125023_add_page_publish_lock.rb +17 -0
  21. data/db/migrations/20130111161934_convert_bcrypt_passwords.rb +22 -0
  22. data/db/migrations/20130114120000_create_revision_tables.rb +106 -0
  23. data/db/migrations/20130116220423_add_index_to_archive.rb +9 -0
  24. data/lib/spontaneous/box.rb +53 -18
  25. data/lib/spontaneous/box_style.rb +2 -3
  26. data/lib/spontaneous/change.rb +39 -13
  27. data/lib/spontaneous/cli/fields.rb +29 -0
  28. data/lib/spontaneous/cli/init.rb +2 -2
  29. data/lib/spontaneous/cli/migrate.rb +0 -1
  30. data/lib/spontaneous/cli/server.rb +14 -10
  31. data/lib/spontaneous/cli/site.rb +20 -9
  32. data/lib/spontaneous/cli.rb +8 -6
  33. data/lib/spontaneous/collections/box_set.rb +11 -0
  34. data/lib/spontaneous/collections/field_set.rb +24 -1
  35. data/lib/spontaneous/concern.rb +37 -0
  36. data/lib/spontaneous/config.rb +3 -4
  37. data/lib/spontaneous/crypt/version.rb +130 -0
  38. data/lib/spontaneous/crypt.rb +84 -0
  39. data/lib/spontaneous/data_mapper/content_model/associations.rb +199 -0
  40. data/lib/spontaneous/data_mapper/content_model/column_accessors.rb +52 -0
  41. data/lib/spontaneous/data_mapper/content_model/instance_hooks.rb +34 -0
  42. data/lib/spontaneous/data_mapper/content_model/serialization.rb +54 -0
  43. data/lib/spontaneous/data_mapper/content_model/timestamps.rb +39 -0
  44. data/lib/spontaneous/data_mapper/content_model.rb +343 -0
  45. data/lib/spontaneous/data_mapper/content_table.rb +103 -0
  46. data/lib/spontaneous/data_mapper/dataset.rb +194 -0
  47. data/lib/spontaneous/data_mapper/scope.rb +195 -0
  48. data/lib/spontaneous/data_mapper.rb +161 -0
  49. data/lib/spontaneous/facet.rb +2 -2
  50. data/lib/spontaneous/field/base.rb +418 -0
  51. data/lib/spontaneous/field/date.rb +54 -0
  52. data/lib/spontaneous/{field_version.rb → field/field_version.rb} +1 -1
  53. data/lib/spontaneous/field/file.rb +100 -0
  54. data/lib/spontaneous/{field_types/image_field.rb → field/image.rb} +33 -33
  55. data/lib/spontaneous/{field_types/location_field.rb → field/location.rb} +2 -2
  56. data/lib/spontaneous/{field_types/long_string_field.rb → field/long_string.rb} +3 -3
  57. data/lib/spontaneous/field/markdown.rb +36 -0
  58. data/lib/spontaneous/{field_types/select_field.rb → field/select.rb} +4 -5
  59. data/lib/spontaneous/field/string.rb +17 -0
  60. data/lib/spontaneous/field/update.rb +156 -0
  61. data/lib/spontaneous/field/webvideo.rb +310 -0
  62. data/lib/spontaneous/field.rb +80 -0
  63. data/lib/spontaneous/generators/site/Gemfile.tt +2 -2
  64. data/lib/spontaneous/generators/site/config/environments/development.rb.tt +1 -1
  65. data/lib/spontaneous/generators/site/config/environments/production.rb.tt +1 -1
  66. data/lib/spontaneous/generators/site/lib/content.rb.tt +6 -0
  67. data/lib/spontaneous/generators/site/schema/box.rb.tt +3 -2
  68. data/lib/spontaneous/generators/site/schema/page.rb.tt +3 -1
  69. data/lib/spontaneous/generators/site/schema/piece.rb.tt +3 -1
  70. data/lib/spontaneous/generators/site/templates/layouts/standard.html.cut.tt +3 -1
  71. data/lib/spontaneous/generators/site.rb +4 -3
  72. data/lib/spontaneous/image_size.rb +8 -1
  73. data/lib/spontaneous/layout.rb +5 -1
  74. data/lib/spontaneous/loader.rb +2 -5
  75. data/lib/spontaneous/media/file.rb +11 -2
  76. data/lib/spontaneous/media/temp_file.rb +23 -0
  77. data/lib/spontaneous/media.rb +20 -39
  78. data/lib/spontaneous/{plugins → model/box}/allowed_types.rb +38 -17
  79. data/lib/spontaneous/model/box.rb +18 -0
  80. data/lib/spontaneous/{plugins → model/core}/aliases.rb +10 -14
  81. data/lib/spontaneous/{plugins → model/core}/boxes.rb +2 -2
  82. data/lib/spontaneous/{plugins → model/core}/content_groups.rb +2 -2
  83. data/lib/spontaneous/model/core/editor_class.rb +4 -0
  84. data/lib/spontaneous/{plugins → model/core}/entries.rb +19 -7
  85. data/lib/spontaneous/{plugins → model/core}/entry.rb +3 -3
  86. data/lib/spontaneous/{plugins → model/core}/fields.rb +38 -5
  87. data/lib/spontaneous/{plugins → model/core}/instance_code.rb +2 -2
  88. data/lib/spontaneous/{plugins → model/core}/media.rb +2 -12
  89. data/lib/spontaneous/{plugins → model/core}/modifications.rb +7 -6
  90. data/lib/spontaneous/model/core/page_search.rb +36 -0
  91. data/lib/spontaneous/{plugins → model/core}/permissions.rb +4 -4
  92. data/lib/spontaneous/{plugins → model/core}/prototypes.rb +4 -4
  93. data/lib/spontaneous/{plugins → model/core}/publishing.rb +93 -115
  94. data/lib/spontaneous/{plugins → model/core}/render.rb +2 -2
  95. data/lib/spontaneous/{plugins → model/core}/schema_hierarchy.rb +7 -11
  96. data/lib/spontaneous/model/core/schema_id.rb +65 -0
  97. data/lib/spontaneous/{plugins → model/core}/schema_title.rb +2 -2
  98. data/lib/spontaneous/{plugins → model/core}/serialisation.rb +2 -2
  99. data/lib/spontaneous/{plugins → model/core}/styles.rb +2 -2
  100. data/lib/spontaneous/{plugins → model/core}/supertype.rb +2 -2
  101. data/lib/spontaneous/{plugins → model/core}/visibility.rb +7 -48
  102. data/lib/spontaneous/model/core.rb +143 -0
  103. data/lib/spontaneous/{plugins → model/page}/controllers.rb +3 -3
  104. data/lib/spontaneous/{plugins → model}/page/formats.rb +2 -2
  105. data/lib/spontaneous/{plugins → model/page}/layouts.rb +2 -2
  106. data/lib/spontaneous/model/page/locks.rb +14 -0
  107. data/lib/spontaneous/{plugins → model/page}/page_tree.rb +3 -3
  108. data/lib/spontaneous/{plugins → model/page}/paths.rb +30 -12
  109. data/lib/spontaneous/{plugins → model}/page/request.rb +2 -2
  110. data/lib/spontaneous/{plugins → model/page}/site_map.rb +2 -2
  111. data/lib/spontaneous/model/page/site_timestamps.rb +44 -0
  112. data/lib/spontaneous/{page.rb → model/page.rb} +49 -28
  113. data/lib/spontaneous/{piece.rb → model/piece.rb} +7 -6
  114. data/lib/spontaneous/model.rb +97 -0
  115. data/lib/spontaneous/output/context.rb +1 -1
  116. data/lib/spontaneous/output/format.rb +4 -0
  117. data/lib/spontaneous/output/template/renderer.rb +2 -2
  118. data/lib/spontaneous/output.rb +2 -2
  119. data/lib/spontaneous/page_lock.rb +62 -0
  120. data/lib/spontaneous/page_piece.rb +1 -1
  121. data/lib/spontaneous/permissions/access_key.rb +9 -4
  122. data/lib/spontaneous/permissions/user.rb +19 -9
  123. data/lib/spontaneous/permissions.rb +2 -5
  124. data/lib/spontaneous/plugins/application/facets.rb +1 -2
  125. data/lib/spontaneous/plugins/application/features.rb +1 -1
  126. data/lib/spontaneous/plugins/application/paths.rb +1 -1
  127. data/lib/spontaneous/plugins/application/render.rb +1 -1
  128. data/lib/spontaneous/plugins/application/serialisation.rb +1 -1
  129. data/lib/spontaneous/plugins/application/state.rb +30 -1
  130. data/lib/spontaneous/plugins/application/system.rb +12 -12
  131. data/lib/spontaneous/prototypes/box_prototype.rb +1 -1
  132. data/lib/spontaneous/prototypes/field_prototype.rb +3 -6
  133. data/lib/spontaneous/prototypes/style_prototype.rb +1 -1
  134. data/lib/spontaneous/publishing/immediate.rb +77 -49
  135. data/lib/spontaneous/publishing/revision.rb +355 -0
  136. data/lib/spontaneous/publishing/simultaneous.rb +10 -49
  137. data/lib/spontaneous/publishing.rb +1 -0
  138. data/lib/spontaneous/rack/around_back.rb +1 -1
  139. data/lib/spontaneous/rack/around_front.rb +2 -4
  140. data/lib/spontaneous/rack/around_preview.rb +1 -1
  141. data/lib/spontaneous/rack/back.rb +80 -63
  142. data/lib/spontaneous/rack/cacheable_file.rb +2 -2
  143. data/lib/spontaneous/rack/cookie_authentication.rb +1 -1
  144. data/lib/spontaneous/rack/front.rb +1 -1
  145. data/lib/spontaneous/rack/helpers.rb +8 -9
  146. data/lib/spontaneous/{page_controller.rb → rack/page_controller.rb} +1 -1
  147. data/lib/spontaneous/rack/public.rb +3 -3
  148. data/lib/spontaneous/rack.rb +15 -15
  149. data/lib/spontaneous/schema/uid.rb +4 -1
  150. data/lib/spontaneous/schema.rb +57 -24
  151. data/lib/spontaneous/search/database.rb +12 -1
  152. data/lib/spontaneous/search/index.rb +34 -6
  153. data/lib/spontaneous/search/results.rb +1 -1
  154. data/lib/spontaneous/server.rb +3 -3
  155. data/lib/spontaneous/simultaneous.rb +53 -0
  156. data/lib/spontaneous/{plugins/site → site}/features.rb +2 -2
  157. data/lib/spontaneous/{plugins/site → site}/helpers.rb +2 -3
  158. data/lib/spontaneous/{plugins/site → site}/hooks.rb +2 -2
  159. data/lib/spontaneous/{plugins/site → site}/instance.rb +4 -6
  160. data/lib/spontaneous/{plugins/site → site}/level.rb +2 -2
  161. data/lib/spontaneous/{plugins/site → site}/map.rb +4 -4
  162. data/lib/spontaneous/{plugins/site → site}/paths.rb +2 -2
  163. data/lib/spontaneous/site/publishing.rb +89 -0
  164. data/lib/spontaneous/{plugins/site → site}/schema.rb +4 -4
  165. data/lib/spontaneous/{plugins/site → site}/search.rb +2 -2
  166. data/lib/spontaneous/{plugins/site → site}/selectors.rb +15 -7
  167. data/lib/spontaneous/{plugins/site → site}/state.rb +2 -2
  168. data/lib/spontaneous/{plugins/site → site}/storage.rb +2 -2
  169. data/lib/spontaneous/{plugins/site → site}/url.rb +2 -2
  170. data/lib/spontaneous/site.rb +31 -14
  171. data/lib/spontaneous/state.rb +5 -6
  172. data/lib/spontaneous/style.rb +3 -2
  173. data/lib/spontaneous/utils/database/mysql_dumper.rb +13 -0
  174. data/lib/spontaneous/utils/database/postgres_dumper.rb +5 -0
  175. data/lib/spontaneous/version.rb +1 -1
  176. data/lib/spontaneous.rb +34 -89
  177. data/spontaneous.gemspec +112 -114
  178. data/test/experimental/test_crypt.rb +158 -0
  179. data/test/experimental/test_features.rb +3 -3
  180. data/test/fixtures/example_application/config/environments/development.rb +1 -1
  181. data/test/fixtures/example_application/lib/content.rb +5 -0
  182. data/test/fixtures/example_application/schema/page.rb +2 -1
  183. data/test/fixtures/example_application/schema/piece.rb +3 -2
  184. data/test/fixtures/serialisation/class_hash.yaml.erb +5 -5
  185. data/test/fixtures/serialisation/root_hash.yaml.erb +8 -0
  186. data/test/functional/test_application.rb +12 -1
  187. data/test/functional/test_back.rb +80 -48
  188. data/test/functional/test_front.rb +39 -46
  189. data/test/functional/test_user_manager.rb +3 -9
  190. data/test/javascript/test_markdown.rb +2 -2
  191. data/test/test_helper.rb +78 -23
  192. data/test/unit/test_alias.rb +21 -15
  193. data/test/unit/test_asset_bundler.rb +3 -3
  194. data/test/unit/test_assets.rb +2 -2
  195. data/test/unit/test_async.rb +7 -6
  196. data/test/unit/test_authentication.rb +43 -37
  197. data/test/unit/test_boxes.rb +46 -21
  198. data/test/unit/test_changesets.rb +65 -20
  199. data/test/unit/test_config.rb +9 -9
  200. data/test/unit/test_content.rb +50 -51
  201. data/test/unit/test_content_inheritance.rb +6 -20
  202. data/test/unit/test_datamapper.rb +1330 -0
  203. data/test/unit/test_datamapper_content.rb +214 -0
  204. data/test/unit/test_fields.rb +543 -54
  205. data/test/unit/test_formats.rb +2 -3
  206. data/test/unit/test_generators.rb +6 -6
  207. data/test/unit/test_helpers.rb +1 -1
  208. data/test/unit/test_image_size.rb +10 -5
  209. data/test/unit/test_images.rb +17 -18
  210. data/test/unit/test_layouts.rb +18 -3
  211. data/test/unit/test_media.rb +74 -49
  212. data/test/unit/test_modifications.rb +43 -43
  213. data/test/unit/test_page.rb +7 -10
  214. data/test/unit/test_permissions.rb +3 -10
  215. data/test/unit/test_piece.rb +5 -6
  216. data/test/unit/test_plugins.rb +7 -14
  217. data/test/unit/test_prototypes.rb +3 -3
  218. data/test/unit/test_publishing.rb +49 -27
  219. data/test/unit/test_render.rb +46 -15
  220. data/test/unit/test_revisions.rb +124 -127
  221. data/test/unit/test_schema.rb +53 -58
  222. data/test/unit/test_search.rb +64 -16
  223. data/test/unit/test_serialisation.rb +4 -11
  224. data/test/unit/test_site.rb +11 -12
  225. data/test/unit/test_structure.rb +2 -5
  226. data/test/unit/test_styles.rb +22 -24
  227. data/test/unit/test_type_hierarchy.rb +7 -5
  228. data/test/unit/test_visibility.rb +79 -55
  229. metadata +128 -102
  230. data/lib/sequel/plugins/content_table_inheritance.rb +0 -203
  231. data/lib/sequel/plugins/scoped_table_name.rb +0 -54
  232. data/lib/spontaneous/content.rb +0 -129
  233. data/lib/spontaneous/field_types/date_field.rb +0 -56
  234. data/lib/spontaneous/field_types/field.rb +0 -302
  235. data/lib/spontaneous/field_types/file_field.rb +0 -68
  236. data/lib/spontaneous/field_types/markdown_field.rb +0 -38
  237. data/lib/spontaneous/field_types/string_field.rb +0 -19
  238. data/lib/spontaneous/field_types/webvideo_field.rb +0 -313
  239. data/lib/spontaneous/field_types.rb +0 -38
  240. data/lib/spontaneous/generators/site/lib/site.rb.tt +0 -4
  241. data/lib/spontaneous/plugins/field/editor_class.rb +0 -13
  242. data/lib/spontaneous/plugins/page/site_timestamps.rb +0 -28
  243. data/lib/spontaneous/plugins/page_search.rb +0 -63
  244. data/lib/spontaneous/plugins/schema_id.rb +0 -68
  245. data/lib/spontaneous/plugins/site/publishing.rb +0 -75
  246. data/lib/spontaneous/rack/fiber_pool.rb +0 -26
  247. data/test/unit/test_table_scoping.rb +0 -80
data/Gemfile CHANGED
@@ -2,13 +2,19 @@ source :rubygems
2
2
 
3
3
  gemspec
4
4
 
5
- # gem "cutaneous", :path => "/Users/garry/Dropbox/Development/spontaneous3/cutaneous"
6
- gem 'xapian-full', "~> 1.2.3"
7
- gem 'xapian-fu', "~> 1.3"
8
-
9
5
  group :development do
10
6
  gem 'shoulda', '~> 2.11.3', :git => 'https://github.com/dasch/shoulda.git', :branch => 'minitest'
11
7
  gem 'selenium-client', '~> 1.2.18', :platforms => [:mri_18]
12
8
  # gem 'Selenium', '~> 1.1.14'
13
9
  end
14
10
 
11
+ platforms :jruby do
12
+ gem 'jruby-openssl'
13
+ gem 'jdbc-postgres'
14
+ gem 'jdbc-mysql'
15
+ end
16
+
17
+ platforms :mri do
18
+ gem 'xapian-ruby', "~> 1.2.12"
19
+ gem 'xapian-fu', "~> 1.3"
20
+ end
data/Readme.markdown CHANGED
@@ -8,7 +8,7 @@ This is version 2 of an existing (closed source) CMS that has been in active pro
8
8
 
9
9
  Spontaneous uses a powerful hierarchical system to organise your information. This breaks out of the bonds of the traditional "title, slug, text" model of CMS content and instead allows content authors to build complex, highly styled pages out of simple, easily editable blocks.
10
10
 
11
- - Ruby 1.9
11
+ - Ruby 1.9.3
12
12
  - Using classes instead of db for metadata
13
13
  - Versioning of metadata
14
14
  - Keeps the developer in the text editor
@@ -85,6 +85,11 @@ $target-name-size: 14px;
85
85
  -moz-border-radius: 0 $radius $radius 0;
86
86
  border-radius: 0 $radius $radius 0; }
87
87
 
88
+ @mixin rounded-left($radius: $corner-radius) {
89
+ -webkit-border-radius: $radius 0 0 $radius;
90
+ -moz-border-radius: $radius 0 0 $radius;
91
+ border-radius: $radius 0 0 $radius; }
92
+
88
93
  .shadow {
89
94
  -webkit-box-shadow: 0px 3px 30px #cccccc;
90
95
  -moz-box-shadow: 0px 3px 30px #cccccc; }
@@ -123,6 +123,7 @@ $dialogue-title-height: 32px;
123
123
  border: solid 1px #f2f2f2;
124
124
  margin: $unit;
125
125
  margin-top: 0;
126
+ color: #333333;
126
127
  &:hover {
127
128
  background-color: #666;
128
129
  color: #fff;
@@ -369,6 +370,67 @@ $dialogue-title-height: 32px;
369
370
  }
370
371
  .change-set.selected {
371
372
  }
373
+ .change-set.locked {
374
+ .pages {
375
+ position: relative;
376
+ }
377
+ .info {
378
+ @include rounded-left;
379
+ position: absolute;
380
+ overflow: hidden;
381
+ right: 0;
382
+ top: 0;
383
+ bottom: 0;
384
+ width: 0;
385
+ background-color: rgba(33, 33, 33, 0.9);
386
+ color: #aaaaaa;
387
+ font-size: 0.7em;
388
+ .lock-state {
389
+ overflow: auto;
390
+ padding: $unit/2;
391
+ }
392
+ .locks {
393
+ strong {
394
+ margin-right: $unit/2;
395
+ &:after {
396
+ content: ":";
397
+ }
398
+ }
399
+ p {
400
+ margin: 0 0 $unit/2 0;
401
+ }
402
+ }
403
+ h3 {
404
+ font-size: 1em;
405
+ font-weight: normal;
406
+ margin: 0;
407
+ color: #fafafa;
408
+ margin: 0;
409
+ margin-bottom: $unit/2;
410
+ }
411
+ }
412
+ .page-title {
413
+ color: #999;
414
+ }
415
+ .add {
416
+ span {
417
+ &:before {
418
+ @include awesome-icon("\f023");
419
+ }
420
+ }
421
+ }
422
+ }
423
+ &.first-publish {
424
+ #changes {
425
+ display: none;
426
+ }
427
+ #to-publish {
428
+ position: absolute;
429
+ left: 0;
430
+ right: 0;
431
+ width: auto;
432
+ }
433
+ }
372
434
  }
373
435
 
374
436
  #root-menu {
@@ -101,7 +101,7 @@ Spontaneous.Content = (function($, S) {
101
101
  "type:", "'"+type.title+"'",
102
102
  "field_name:", f.name
103
103
  );
104
- type_class = Spontaneous.FieldTypes.StringField;
104
+ type_class = Spontaneous.Field.String;
105
105
  }
106
106
  var field = new type_class(this, f);
107
107
  // field.watch('value', this.field_updated.bind(this, field));
@@ -1,7 +1,7 @@
1
1
  // console.log('Loading DOM...');
2
2
 
3
3
  Spontaneous.Dom = (function($, S) {
4
- var tags = 'div p iframe a span img select option label ul li dl dt dd table tr td h1 h2 h3 h4 header input button form textarea optgroup'.split(' ');
4
+ var tags = 'div p iframe a span strong img select option label ul li dl dt dd table tr td h1 h2 h3 h4 header input button form textarea optgroup'.split(' ');
5
5
  var Dom = {
6
6
  body: function() {
7
7
  return $(document.body);
@@ -11,6 +11,9 @@ Spontaneous.EventSource = (function($, S) {
11
11
 
12
12
  addEventListener: function(event, callback) {
13
13
  this.eventSource().addEventListener(event, callback);
14
+ },
15
+ removeEventListener: function(event, callback) {
16
+ this.eventSource().removeEventListener(event, callback);
14
17
  }
15
18
  });
16
19
  return EventSource;
@@ -1,7 +1,7 @@
1
1
  // console.log('Loading DateField...')
2
- Spontaneous.FieldTypes.DateField = (function($, S) {
2
+ Spontaneous.Field.Date = (function($, S) {
3
3
  var dom = S.Dom;
4
- var DateField = new JS.Class(Spontaneous.FieldTypes.StringField, {
4
+ var DateField = new JS.Class(Spontaneous.Field.String, {
5
5
  input: function() {
6
6
  var input = this.callSuper();
7
7
  input.datepicker({ "dateFormat": "DD, d MM yy" });
@@ -1,7 +1,7 @@
1
1
  // console.log('Loading FileField...')
2
- Spontaneous.FieldTypes.FileField = (function($, S) {
2
+ Spontaneous.Field.File = (function($, S) {
3
3
  var dom = S.Dom;
4
- var FileField = new JS.Class(Spontaneous.FieldTypes.StringField, {
4
+ var FileField = new JS.Class(Spontaneous.Field.String, {
5
5
  selected_files: false,
6
6
 
7
7
  preview: function() {
@@ -1,7 +1,7 @@
1
1
  // console.log('Loading ImageField...')
2
- Spontaneous.FieldTypes.ImageField = (function($, S) {
2
+ Spontaneous.Field.Image = (function($, S) {
3
3
  var dom = S.Dom;
4
- var ImageFieldConflictView = new JS.Class(S.FieldTypes.StringField.ConflictView, {
4
+ var ImageFieldConflictView = new JS.Class(S.Field.String.ConflictView, {
5
5
 
6
6
  panel: function() {
7
7
  var labels = dom.div('.image-field-conflict.labels.differences'),
@@ -29,7 +29,7 @@ Spontaneous.FieldTypes.ImageField = (function($, S) {
29
29
  }
30
30
  });
31
31
 
32
- var ImageField = new JS.Class(Spontaneous.FieldTypes.FileField, {
32
+ var ImageField = new JS.Class(Spontaneous.Field.File, {
33
33
  is_image: function() {
34
34
  return true;
35
35
  },
@@ -57,10 +57,40 @@ Spontaneous.FieldTypes.ImageField = (function($, S) {
57
57
  this.callSuper();
58
58
  },
59
59
 
60
+ currentValue: function() {
61
+ var v = this.get('value');
62
+ if ((pending = v['__pending__'])) {
63
+ return pending['value'];
64
+ }
65
+ return v['__ui__'] || v['original'];
66
+ },
67
+
68
+ /*
69
+ * HACK: The async nature of image updates means that the version setting
70
+ * may be out of date not because of the actions of another, but because
71
+ * the field version has been updated in the background.
72
+ * The right way to do this would be to use an event to update the field
73
+ * values at the point where the update is complete, but that's a big change.
74
+ *
75
+ * If I do that then I could use it to update all field values across all sessions
76
+ * and avoid most conflicts by keeping the field values up-to-date automatically
77
+ * but I'm not ready for that just yet...
78
+ *
79
+ * Instead hackily use the pending version and hope it's not going to cause
80
+ * weird problems with simultaneous updates.
81
+ */
82
+ version: function() {
83
+ var value = this.get("value");
84
+ if ((pending = value["__pending__"])) {
85
+ return pending["version"];
86
+ }
87
+ return this.data.version;
88
+ },
89
+
60
90
  preview: function(container) {
61
91
  Spontaneous.UploadManager.register(this);
62
92
  var self = this
63
- , value = this.get('value').original
93
+ , value = this.currentValue()
64
94
  , src = value.src
65
95
  , img = null
66
96
  , dim = 45
@@ -109,7 +139,7 @@ Spontaneous.FieldTypes.ImageField = (function($, S) {
109
139
  this.spinner().indeterminate();
110
140
 
111
141
  if (files.length > 0) {
112
- this.selected_files = files;
142
+ this.select_files(files);
113
143
  var file = files[0],
114
144
  url = window.URL.createObjectURL(file)
115
145
  , image = this.image;
@@ -162,7 +192,7 @@ Spontaneous.FieldTypes.ImageField = (function($, S) {
162
192
  return outer;
163
193
  },
164
194
  conflicts_resolved: function(resolution_list) {
165
- console.log('conflicts_resolved', resolution_list)
195
+ // console.log('conflicts_resolved', resolution_list)
166
196
  var resolution = resolution_list[0];
167
197
  this.set_edited_value(resolution.value);
168
198
  this.set_version(resolution.version);
@@ -187,10 +217,10 @@ Spontaneous.FieldTypes.ImageField = (function($, S) {
187
217
  return this._spinner;
188
218
  },
189
219
  upload_complete: function(values) {
190
-
220
+ this.mark_unmodified();
191
221
  this.callSuper(values)
192
222
  if (values) {
193
- var value = this.value().original;
223
+ var value = this.currentValue();
194
224
  if (this.image) {
195
225
  var img = new Image()
196
226
  img.onload = function() {
@@ -202,20 +232,20 @@ Spontaneous.FieldTypes.ImageField = (function($, S) {
202
232
  },
203
233
 
204
234
  width: function() {
205
- if (this.data.values && this.data.values.original) {
206
- return this.data.values.original.width;
235
+ if (this.data.values && this.currentValue()) {
236
+ return this.currentValue().width;
207
237
  }
208
238
  return 0;
209
239
  },
210
240
  height: function() {
211
- if (this.data.values && this.data.values.original) {
212
- return this.data.values.original.height;
241
+ if (this.data.values && this.currentValue()) {
242
+ return this.currentValue().height;
213
243
  }
214
244
  return 0;
215
245
  },
216
246
  edit: function() {
217
247
  var wrap = dom.div({'style':'position:relative;'}),
218
- value = this.value().original,
248
+ value = this.currentValue(),
219
249
  src = value.src,
220
250
  img = dom.img({'src':src}),
221
251
  info, sizes, filename_info, filesize_info, dimensions_info;
@@ -255,12 +285,11 @@ Spontaneous.FieldTypes.ImageField = (function($, S) {
255
285
  if (files.length > 0) {
256
286
  var file = files[0], url = window.URL.createObjectURL(file);
257
287
  img.attr('src', url).removeClass('empty');
258
- this.selected_files = files;
288
+ this.select_files(files);
259
289
  img.attr('src', url)
260
290
  this._edited_value = url;
261
291
  this.image.attr('src', url)
262
292
  window.URL.revokeObjectURL(url);
263
- console.log(file)
264
293
  set_info(File.filename(file), file.fileSize, null, null)
265
294
  }
266
295
  }.bind(this);
@@ -328,8 +357,16 @@ Spontaneous.FieldTypes.ImageField = (function($, S) {
328
357
  return wrap;
329
358
  },
330
359
 
360
+ select_files: function(files) {
361
+ this.selected_files = files;
362
+ this.mark_modified();
363
+ },
364
+
365
+ is_modified: function() {
366
+ return this.get_modified_state();
367
+ },
368
+
331
369
  get_input: function() {
332
- console.log("FileField", "#get_input")
333
370
  this.input = this.generate_input();
334
371
  return this.input;
335
372
  },
@@ -338,14 +375,11 @@ Spontaneous.FieldTypes.ImageField = (function($, S) {
338
375
  return this.input.val();
339
376
  },
340
377
  cancel_edit: function() {
341
- this.image.attr('src', this.original_value().src);
378
+ this.image.attr('src', this.currentValue().src);
342
379
  },
343
380
  conflict_view: function(dialogue, conflict) {
344
381
  return new ImageFieldConflictView(dialogue, conflict);
345
382
  },
346
- original_value: function() {
347
- return this.value().original;
348
- },
349
383
  edited_value: function() {
350
384
  return this._edited_value;
351
385
  },
@@ -1,7 +1,7 @@
1
1
  // console.log('Loading LongStringField...')
2
- Spontaneous.FieldTypes.LongStringField = (function($, S) {
2
+ Spontaneous.Field.LongString = (function($, S) {
3
3
  var dom = S.Dom;
4
- var LongStringField = new JS.Class(Spontaneous.FieldTypes.StringField, {
4
+ var LongStringField = new JS.Class(Spontaneous.Field.String, {
5
5
  generate_input: function() {
6
6
  return dom.textarea(dom.id(this.css_id()), {'name':this.form_name(), 'rows':10, 'cols':30}).text(this.unprocessed_value());
7
7
  }
@@ -1,6 +1,6 @@
1
1
  // console.log('Loading DiscountField...')
2
2
 
3
- Spontaneous.FieldTypes.MarkdownField = (function($, S) {
3
+ Spontaneous.Field.Markdown = (function($, S) {
4
4
  var dom = S.Dom;
5
5
  var TextCommand = new JS.Class({
6
6
  name: '',
@@ -594,7 +594,7 @@ Spontaneous.FieldTypes.MarkdownField = (function($, S) {
594
594
  });
595
595
 
596
596
 
597
- var MarkdownField = new JS.Class(Spontaneous.FieldTypes.StringField, {
597
+ var MarkdownField = new JS.Class(Spontaneous.Field.String, {
598
598
  actions: [Bold, Italic, H1, H2, UL, OL, Link],
599
599
  generate_input: function() {
600
600
  var input = dom.textarea(dom.id(this.css_id()), {'name':this.form_name(), 'rows':10, 'cols':90}).val(this.unprocessed_value()), height = this.content.getFieldMetadata(this, 'height');
@@ -1,11 +1,11 @@
1
1
 
2
- Spontaneous.FieldTypes.SelectField = (function($, S) {
2
+ Spontaneous.Field.Select = (function($, S) {
3
3
  "use strict";
4
4
 
5
5
  var dom = S.Dom
6
6
  , ajax = S.Ajax;
7
7
 
8
- var SelectField = new JS.Class(Spontaneous.FieldTypes.StringField, {
8
+ var SelectField = new JS.Class(Spontaneous.Field.String, {
9
9
  edit: function() {
10
10
  var self = this
11
11
  , type = this.type
@@ -1,5 +1,5 @@
1
1
  // console.log('Loading StringField...')
2
- Spontaneous.FieldTypes.StringField = (function($, S) {
2
+ Spontaneous.Field.String = (function($, S) {
3
3
  var dom = S.Dom;
4
4
  var StringFieldConflictView = new JS.Class({
5
5
  initialize: function(dialogue, conflict) {
@@ -106,13 +106,15 @@ Spontaneous.FieldTypes.StringField = (function($, S) {
106
106
  return 'field-'+this.name+'-'+this.id();
107
107
  },
108
108
  form_name: function() {
109
- return this.input_name('unprocessed_value');
109
+ return this.input_name();
110
110
  },
111
- version_name: function() {
112
- return this.input_name('version');
113
- },
114
- input_name: function(name) {
115
- return 'field['+this.schema_id()+']['+name+']';
111
+ // version_name: function() {
112
+ // return this.input_name('version');
113
+ // },
114
+ input_name: function(param) {
115
+ var name = 'field['+this.schema_id()+']';
116
+ if (param) { n += '['+param+']'; }
117
+ return name;
116
118
  },
117
119
  schema_id: function() {
118
120
  return this.type.schema_id;
@@ -145,6 +147,18 @@ Spontaneous.FieldTypes.StringField = (function($, S) {
145
147
  // version in the case of a conflict
146
148
  return true;
147
149
  },
150
+ mark_modified: function() {
151
+ this.set_modified_state(true);
152
+ },
153
+ mark_unmodified: function() {
154
+ this.set_modified_state(false);
155
+ },
156
+ set_modified_state: function(state) {
157
+ this._is_modified = state;
158
+ },
159
+ get_modified_state: function() {
160
+ return this._is_modified;
161
+ },
148
162
  original_value: function() {
149
163
  return this.unprocessed_value();
150
164
  },
@@ -1,7 +1,7 @@
1
1
  // console.log('Loading DateField...')
2
- Spontaneous.FieldTypes.WebVideoField = (function($, S) {
2
+ Spontaneous.Field.WebVideo = (function($, S) {
3
3
  var dom = S.Dom;
4
- var WebVideoField = new JS.Class(Spontaneous.FieldTypes.StringField, {
4
+ var WebVideoField = new JS.Class(Spontaneous.Field.String, {
5
5
  // get_input: function() {
6
6
  // this.input = $(dom.textarea, {'id':this.css_id(), 'name':this.form_name(), 'rows':10, 'cols':30}).text(this.unprocessed_value());
7
7
  // return this.input;
@@ -1,4 +1,4 @@
1
- // console.log('Loading FieldTypes...')
1
+ // console.log('Loading Field...')
2
2
 
3
- Spontaneous.FieldTypes = {};
3
+ Spontaneous.Field = {};
4
4
 
@@ -6,6 +6,25 @@ Spontaneous.Publishing = (function($, S) {
6
6
  var PublishingDialogue = new JS.Class(Dialogue, {
7
7
  initialize: function(content) {
8
8
  this.change_sets = [];
9
+ var callback = this.page_lock_removed.bind(this);
10
+ var page_lock_removed = function(event) {
11
+ callback($.parseJSON(event.data))
12
+ };
13
+ S.EventSource.addEventListener('page_lock_status', page_lock_removed);
14
+ this.page_lock_removed_listener = page_lock_removed;
15
+ },
16
+ page_lock_removed: function(page_ids) {
17
+ var changes = this.change_sets, toUnlock = [];
18
+ page_ids.forEach(function(id) {
19
+ toUnlock = toUnlock.concat(changes.filter(function(c) { return c.change.id == id }))
20
+ });
21
+
22
+ toUnlock.forEach(function(cs) {
23
+ cs.unlock();
24
+ });
25
+ },
26
+ cleanup: function() {
27
+ S.EventSource.removeEventListener('page_lock_status', this.page_lock_removed_listener);
9
28
  },
10
29
  width: function() {
11
30
  return '90%';
@@ -44,35 +63,54 @@ Spontaneous.Publishing = (function($, S) {
44
63
  this.close();
45
64
  },
46
65
 
47
- change_list_loaded: function(change_list) {
48
- var w = this.wrapper, __dialogue = this;
66
+ change_list_loaded: function(outstanding) {
67
+ var change_list = outstanding.changes
68
+ , w = this.wrapper
69
+ , self = this
70
+ , changed_wrap = dom.div("#changes.change-list")
71
+ , publish_wrap = dom.div("#to-publish.change-list")
72
+ , first_publish = outstanding.first_publish
73
+ , append_to;
74
+ if (first_publish) {
75
+ w.addClass("first-publish");
76
+ }
49
77
  w.empty();
50
- var changed_wrap = dom.div("#changes.change-list"), publish_wrap = dom.div("#to-publish.change-list")
51
78
  w.append(changed_wrap, publish_wrap)
52
79
  if (change_list.length === 0) {
53
80
  var summary = dom.p('.publish-up-to-date').text("The site is up to date");
54
81
  w.append(summary);
55
- this.disable_button('Publish');
82
+ self.disable_button('Publish');
56
83
  } else {
57
84
  var publish_all = dom.a('.button').text('Publish All').click(function() {
58
- this.set_publish_all(true);
59
- }.bind(this));
85
+ self.set_publish_all(true);
86
+ }.bind(self));
60
87
  var clear_all = dom.a('.button').text('Clear All').click(function() {
61
- this.set_publish_all(false);
62
- }.bind(this));
88
+ self.set_publish_all(false);
89
+ }.bind(self));
90
+
63
91
  var changed_toolbar = dom.div('.actions').append(dom.div().text("Modified pages")).append(publish_all);
64
- var publish_toolbar = dom.div('.actions').append(dom.div().text("Publish pages")).append(clear_all);
92
+ var publish_toolbar = dom.div('.actions').append(dom.div().text("Publish pages"));
93
+ if (!first_publish) {
94
+ publish_toolbar.append(clear_all);
95
+ }
65
96
  var changed_entries = dom.div('.change-sets'), publish_entries = dom.div('.change-sets')
66
97
  changed_wrap.append(changed_toolbar, changed_entries);
67
98
  publish_wrap.append(publish_toolbar, publish_entries);
99
+ append_to = changed_entries;
100
+ if (first_publish) {
101
+ append_to = publish_entries;
102
+ }
68
103
  for (var i = 0, ii = change_list.length; i < ii; i++) {
69
- var cs = new ChangeSet(i, this, change_list[i]);
70
- this.change_sets.push(cs);
71
- changed_entries.append(cs.panel())
104
+ var cs = new ChangeSet(i, self, change_list[i], first_publish);
105
+ self.change_sets.push(cs);
106
+ append_to.append(cs.panel())
72
107
  }
73
- publish_entries.append(dom.div('.instructions').text('Add pages to publish from the list on the left'));
74
- this.changed_entries = changed_entries;
75
- this.publish_entries = publish_entries;
108
+ if (!first_publish) {
109
+ publish_entries.append(dom.div('.instructions').text('Add pages to publish from the list on the left'));
110
+ }
111
+
112
+ self.changed_entries = changed_entries;
113
+ self.publish_entries = publish_entries;
76
114
  }
77
115
  },
78
116
  set_publish_all: function(state) {
@@ -183,11 +221,23 @@ Spontaneous.Publishing = (function($, S) {
183
221
  }
184
222
  });
185
223
  var ChangeSet = new JS.Class({
186
- initialize: function(id, dialogue, change) {
224
+ initialize: function(id, dialogue, change, selected) {
187
225
  this.id = id;
188
226
  this.dialogue = dialogue;
189
227
  this.change = change;
190
- this.selected = false;
228
+ this.selected = selected || false;
229
+ this.mustPublish = selected || false;
230
+ },
231
+ locks: function(){
232
+ return this.change.update_locks;
233
+ },
234
+ isLocked: function() {
235
+ return this.locks().length > 0;
236
+ },
237
+ unlock: function() {
238
+ var w = this.wrapper;
239
+ w.removeClass("locked").find(".lock-state").remove();
240
+ this.locks().length = 0;
191
241
  },
192
242
  page_ids: function() {
193
243
  var ids = [this.change.id];
@@ -206,19 +256,48 @@ Spontaneous.Publishing = (function($, S) {
206
256
  , page_list = dom.div('.pages')
207
257
  , add = dom.div('.add').append(dom.span().text(''))
208
258
  , page = this.page()
209
- , pages = this.dependent_pages();
259
+ , pages = this.dependent_pages()
260
+ , locked = this.isLocked()
261
+ , info = dom.div(".info").hide();
210
262
 
211
263
  if (page.isUnpublished()) {
212
264
  w.addClass('unpublished');
213
265
  }
266
+ if (locked) {
267
+ w.addClass('locked');
268
+ var lockState = dom.div(".lock-state")
269
+ , title = dom.h3().html("<strong>Cannot publish page</strong> until the following actions have completed:")
270
+ , details = dom.div(".locks")
271
+ , locks = this.locks();
272
+ locks.forEach(function(lock) {
273
+ var line = dom.p();
274
+ line.text(lock.description);
275
+ line.prepend(dom.strong().text(lock.location));
276
+ details.append(line);
277
+ });
278
+ lockState.append(title, details)
279
+ info.append(lockState);
280
+ page_list.append(info);
281
+ add.hover(function() {
282
+ info.show().animate({"width": "100%"}, 150);
283
+ }, function() {
284
+ info.hide().css("width", 0);
285
+ });
286
+ }
214
287
  page_list.append(page.panel());
215
288
  for (var i = 0, ii = pages.length; i < ii; i++) {
216
289
  page_list.append(pages[i].panel());
217
290
  }
291
+
218
292
  add.click(function() {
219
293
  this.select_toggle();
220
294
  }.bind(this));
221
- inner.append(page_list, add);
295
+
296
+ inner.append(page_list);
297
+
298
+ if (!this.mustPublish) {
299
+ inner.append(add);
300
+ }
222
301
  w.append(inner);
223
302
  return w;
224
303
  },
@@ -229,6 +308,7 @@ Spontaneous.Publishing = (function($, S) {
229
308
  },
230
309
  select: function(state) {
231
310
  var self = this;
311
+ if (this.isLocked()) { return ; }
232
312
  if (state === self.selected) { return; }
233
313
  if (!state && self.dependency_forces_publish()) {
234
314
  return;
@@ -30,14 +30,14 @@
30
30
  //= require box
31
31
  //= require page
32
32
  //= require field
33
- //= require field_types/string_field
34
- //= require field_types/long_string_field
35
- //= require field_types/file_field
36
- //= require field_types/image_field
37
- //= require field_types/markdown_field
38
- //= require field_types/date_field
39
- //= require field_types/webvideo_field
40
- //= require field_types/select_field
33
+ //= require field/string
34
+ //= require field/long_string
35
+ //= require field/file
36
+ //= require field/image
37
+ //= require field/markdown
38
+ //= require field/date
39
+ //= require field/webvideo
40
+ //= require field/select
41
41
  //= require content_area
42
42
  //= require preview
43
43
  //= require editing
@@ -144,7 +144,7 @@ Spontaneous.TopBar = (function($, S) {
144
144
  } else {
145
145
  this.select.show();
146
146
  }
147
- return '('+(count)+' pages)';
147
+ return '('+(count)+' page'+(count === 1 ? '' : 's')+')';
148
148
  },
149
149
  optgroup: function(boxname) {
150
150
  return dom.optgroup().attr('label', boxname);
@@ -378,9 +378,11 @@ Spontaneous.TopBar = (function($, S) {
378
378
  }
379
379
  });
380
380
 
381
- page.watch('slug', function(title) {
382
- self.navigation_current.set_title(title);
383
- });
381
+ if (!page.is_root()) {
382
+ page.watch('slug', function(title) {
383
+ self.navigation_current.set_title(title);
384
+ });
385
+ }
384
386
 
385
387
  page.title_field().watch('value', function(title) {
386
388
  Spontaneous.set_browser_title(title);