zena 1.0.0.rc2 → 1.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (274) hide show
  1. data/History.txt +23 -0
  2. data/README.rdoc +1 -1
  3. data/app/controllers/columns_controller.rb +3 -31
  4. data/app/controllers/comments_controller.rb +8 -3
  5. data/app/controllers/data_entries_controller.rb +1 -1
  6. data/app/controllers/documents_controller.rb +2 -2
  7. data/app/controllers/nodes_controller.rb +29 -12
  8. data/app/controllers/relations_controller.rb +2 -2
  9. data/app/controllers/sites_controller.rb +1 -1
  10. data/app/controllers/user_sessions_controller.rb +6 -3
  11. data/app/controllers/users_controller.rb +18 -16
  12. data/app/controllers/versions_controller.rb +20 -18
  13. data/app/controllers/virtual_classes_controller.rb +103 -17
  14. data/app/helpers/users_helper.rb +1 -1
  15. data/app/models/column.rb +19 -50
  16. data/app/models/comment.rb +2 -1
  17. data/app/models/node.rb +45 -22
  18. data/app/models/relation.rb +13 -0
  19. data/app/models/relation_proxy.rb +3 -2
  20. data/app/models/role.rb +213 -4
  21. data/app/models/site.rb +18 -11
  22. data/app/models/template.rb +37 -35
  23. data/app/models/version.rb +1 -1
  24. data/app/models/virtual_class.rb +154 -86
  25. data/app/views/columns/_li.html.erb +1 -1
  26. data/app/views/columns/index.html.erb +1 -9
  27. data/app/views/comments/index.rhtml +10 -8
  28. data/app/views/documents/_crop.rhtml +5 -6
  29. data/app/views/documents/crop_form.rjs +3 -2
  30. data/app/views/groups/index.rhtml +1 -1
  31. data/app/views/iformats/index.rhtml +1 -1
  32. data/app/views/nodes/_import_results.rhtml +1 -1
  33. data/app/views/nodes/_parent.rhtml +1 -2
  34. data/app/views/nodes/update.rjs +3 -4
  35. data/app/views/relations/index.erb +1 -1
  36. data/app/views/sites/index.erb +1 -1
  37. data/app/views/templates/drive_tabs/_drive.rhtml +0 -2
  38. data/app/views/templates/edit_tabs/_image.rhtml +1 -1
  39. data/app/views/templates/edit_tabs/_title.rhtml +0 -6
  40. data/app/views/users/index.rhtml +1 -1
  41. data/app/views/users/preferences.html.erb +2 -2
  42. data/app/views/versions/backup.rjs +1 -1
  43. data/app/views/versions/custom_tab.rhtml +9 -4
  44. data/app/views/versions/destroy.rjs +2 -2
  45. data/app/views/versions/update.rjs +2 -9
  46. data/app/views/virtual_classes/_form.erb +3 -2
  47. data/app/views/virtual_classes/import_prepare.html.erb +13 -0
  48. data/app/views/virtual_classes/index.erb +28 -8
  49. data/app/views/zafu/default/Node-+adminLayout.zafu +1 -13
  50. data/app/views/zafu/default/Node-+login.zafu +1 -0
  51. data/app/views/zafu/default/Node-+notFound.zafu +1 -1
  52. data/app/views/zafu/default/Node-+popupLayout.zafu +1 -2
  53. data/app/views/zafu/default/Node-+search.zafu +1 -1
  54. data/app/views/zafu/default/Node-admin.zafu +205 -0
  55. data/app/views/zafu/default/Node.zafu +11 -11
  56. data/bricks/captcha/lib/bricks/captcha.rb +3 -2
  57. data/bricks/mongrel/zena/init.rb +2 -1
  58. data/bricks/pdf/README +5 -5
  59. data/bricks/pdf/lib/bricks/pdf/engine/prince.rb +2 -2
  60. data/bricks/pdf/lib/bricks/pdf/engine/xhtml2pdf.rb +2 -2
  61. data/bricks/pdf/lib/bricks/pdf/install.rb +5 -5
  62. data/bricks/pdf/lib/bricks/pdf.rb +11 -11
  63. data/bricks/pdf/test/engines/test_prince.rb +4 -4
  64. data/bricks/pdf/test/engines/test_xhtml2pdf.rb +4 -4
  65. data/bricks/pdf/test/shoulda_macros/shoulda_pdf.rb +2 -2
  66. data/bricks/pdf/zena/init.rb +2 -2
  67. data/bricks/pdf/zena/tasks.rb +2 -2
  68. data/bricks/sphinx/lib/bricks/sphinx.rb +6 -2
  69. data/bricks/sphinx/zena/{sphinx.yml → sphinx.yml.erb} +2 -2
  70. data/bricks/sphinx/zena/tasks.rb +28 -2
  71. data/bricks/tags/lib/bricks/tags.rb +16 -1
  72. data/bricks/tags/zena/test/unit/tags_test.rb +15 -0
  73. data/bricks/tags/zena/test/zafu/tags.yml +5 -1
  74. data/bricks/worker/lib/bricks/worker.rb +39 -0
  75. data/bricks/worker/zena/deploy.rb +0 -2
  76. data/bricks/worker/zena/init.rb +1 -0
  77. data/bricks/worker/zena/test/sites/zena/delayed_jobs.yml +16 -0
  78. data/bricks/worker/zena/test/zafu/worker.yml +8 -0
  79. data/bricks/zena/zena/migrate/01_base.rb +36 -60
  80. data/bricks/zena/zena/migrate/02_zerox1_schema.rb +388 -0
  81. data/bricks/zena/zena/migrate/03_zerox1_data.rb +380 -0
  82. data/bricks/zena/zena/migrate/20110315161158_add_reverse_scope_to_roles.rb +9 -0
  83. data/config/database_example.yml +1 -1
  84. data/config/environment.rb +1 -1
  85. data/config/gems.yml +17 -14
  86. data/db/init/base/skins/default/Node-+index.zafu +8 -1
  87. data/db/init/base/skins/default/Node-+login.zafu +1 -0
  88. data/db/init/base/skins/default/Node-+popupLayout.zafu +1 -2
  89. data/db/init/base/skins/default/Node-+search.zafu +2 -2
  90. data/db/init/base/skins/default/Node.zafu +9 -9
  91. data/db/init/base/skins/default/{favicon.png → img/favicon.png} +0 -0
  92. data/db/init/base/skins/default/{style.css → img/style.css} +0 -0
  93. data/db/init/base/skins/default/img/translations.yml +11 -0
  94. data/db/init/base/skins/default/notes.zafu +7 -9
  95. data/doc/zafu_changes.yml +12 -0
  96. data/lib/bricks/loader.rb +38 -15
  97. data/lib/tasks/zena.rake +74 -24
  98. data/lib/zena/acts/enrollable.rb +4 -1
  99. data/lib/zena/acts/secure.rb +2 -48
  100. data/lib/zena/acts/serializable.rb +13 -1
  101. data/lib/zena/app.rb +9 -0
  102. data/lib/zena/code_syntax.rb +154 -151
  103. data/lib/zena/console.rb +141 -0
  104. data/lib/zena/controller/test_case.rb +1 -1
  105. data/lib/zena/db_helper/abstract_db.rb +17 -5
  106. data/lib/zena/db_helper/mysql.rb +14 -12
  107. data/lib/zena/db_helper/postgresql.rb +1 -2
  108. data/lib/zena/db_helper/sqlite3.rb +6 -6
  109. data/lib/zena/deploy/awstats.conf.rhtml +1 -1
  110. data/lib/zena/deploy/httpd.rhtml +6 -1
  111. data/lib/zena/deploy/vhost.rhtml +9 -1
  112. data/lib/zena/deploy.rb +12 -7
  113. data/lib/zena/foxy_parser.rb +3 -1
  114. data/lib/zena/info.rb +1 -1
  115. data/lib/zena/parser/zafu_tags.rb +1 -0
  116. data/lib/zena/parser/zazen_rules.rb +1 -1
  117. data/lib/zena/remote/node.rb +15 -3
  118. data/lib/zena/remote/serializable_array.rb +19 -0
  119. data/lib/zena/remote.rb +1 -0
  120. data/lib/zena/routes.rb +7 -2
  121. data/lib/zena/site_worker.rb +11 -1
  122. data/lib/zena/unit/test_case.rb +68 -0
  123. data/lib/zena/use/action.rb +6 -2
  124. data/lib/zena/use/ajax.rb +127 -53
  125. data/lib/zena/use/ancestry.rb +11 -8
  126. data/lib/zena/use/calendar.rb +265 -129
  127. data/lib/zena/use/conditional.rb +1 -1
  128. data/lib/zena/use/context.rb +5 -5
  129. data/lib/zena/use/dates.rb +172 -60
  130. data/lib/zena/use/display.rb +70 -39
  131. data/lib/zena/use/error_rendering.rb +1 -3
  132. data/lib/zena/use/field_index.rb +4 -1
  133. data/lib/zena/use/forms.rb +94 -72
  134. data/lib/zena/use/fulltext.rb +16 -24
  135. data/lib/zena/use/html_tags.rb +20 -12
  136. data/lib/zena/use/i18n.rb +37 -37
  137. data/lib/zena/use/image_builder.rb +8 -1
  138. data/lib/zena/use/ml_index.rb +16 -16
  139. data/lib/zena/use/prop_eval.rb +10 -5
  140. data/lib/zena/use/query_builder.rb +55 -23
  141. data/lib/zena/use/query_node.rb +51 -25
  142. data/lib/zena/use/refactor.rb +2 -28
  143. data/lib/zena/use/relations.rb +1 -1
  144. data/lib/zena/use/rendering.rb +29 -0
  145. data/lib/zena/use/scope_index.rb +75 -14
  146. data/lib/zena/use/search.rb +5 -10
  147. data/lib/zena/use/test_helper.rb +2 -2
  148. data/lib/zena/use/urls.rb +125 -104
  149. data/lib/zena/use/workflow.rb +2 -1
  150. data/lib/zena/use/zafu_attributes.rb +2 -2
  151. data/lib/zena/use/zafu_safe_definitions.rb +20 -0
  152. data/lib/zena/use/zafu_templates.rb +20 -6
  153. data/lib/zena/use/zazen.rb +31 -20
  154. data/lib/zena/view/test_case.rb +5 -0
  155. data/lib/zena/zafu_compiler.rb +24 -2
  156. data/lib/zena.rb +12 -6
  157. data/locale/de/LC_MESSAGES/zena.mo +0 -0
  158. data/locale/de/zena.po +1345 -1164
  159. data/locale/en/LC_MESSAGES/zena.mo +0 -0
  160. data/locale/en/zena.po +1275 -1129
  161. data/locale/fr/LC_MESSAGES/zena.mo +0 -0
  162. data/locale/fr/zena.mo +0 -0
  163. data/locale/fr/zena.po +1617 -1441
  164. data/locale/log.txt +9 -0
  165. data/locale/zena.pot +957 -748
  166. data/public/javascripts/prototype.js +1 -1
  167. data/public/javascripts/zena.js +99 -44
  168. data/public/stylesheets/admin.css +6 -4
  169. data/public/stylesheets/backend.css +71 -0
  170. data/public/stylesheets/calendar.css +24 -25
  171. data/public/stylesheets/code.css +11 -6
  172. data/public/stylesheets/comment.css +2 -1
  173. data/public/stylesheets/popup.css +7 -8
  174. data/test/custom_queries/complex.host.yml +15 -1
  175. data/test/fixtures/files/Node-test.zafu +29 -28
  176. data/test/fixtures/files/translations_de.yml +12 -1
  177. data/test/fixtures/files/translations_fr.yml +12 -1
  178. data/test/functional/comments_controller_test.rb +9 -0
  179. data/test/functional/iformats_controller_test.rb +1 -1
  180. data/test/functional/nodes_controller_test.rb +124 -35
  181. data/test/functional/users_controller_test.rb +132 -3
  182. data/test/functional/virtual_classes_controller_test.rb +75 -4
  183. data/test/integration/navigation_test.rb +51 -9
  184. data/test/integration/query_node/basic.yml +19 -7
  185. data/test/integration/query_node/complex.yml +1 -1
  186. data/test/integration/query_node/dates.yml +27 -1
  187. data/test/integration/query_node/filters.yml +1 -1
  188. data/test/integration/query_node/relations.yml +13 -4
  189. data/test/integration/query_node_test.rb +4 -0
  190. data/test/integration/xml_api_test.rb +6 -1
  191. data/test/integration/zafu_compiler/action.yml +3 -3
  192. data/test/integration/zafu_compiler/ajax.yml +103 -22
  193. data/test/integration/zafu_compiler/basic.yml +0 -52
  194. data/test/integration/zafu_compiler/calendar.yml +44 -20
  195. data/test/integration/zafu_compiler/comments.yml +53 -0
  196. data/test/integration/zafu_compiler/complex.yml +11 -11
  197. data/test/integration/zafu_compiler/complex_ok.yml +16 -3
  198. data/test/integration/zafu_compiler/conditional.yml +15 -5
  199. data/test/integration/zafu_compiler/context.yml +9 -0
  200. data/test/integration/zafu_compiler/dates.yml +43 -15
  201. data/test/integration/zafu_compiler/display.yml +60 -6
  202. data/test/integration/zafu_compiler/errors.yml +6 -2
  203. data/test/integration/zafu_compiler/forms.yml +45 -6
  204. data/test/integration/zafu_compiler/i18n.yml +8 -1
  205. data/test/integration/zafu_compiler/meta.yml +38 -0
  206. data/test/integration/zafu_compiler/query.yml +43 -4
  207. data/test/integration/zafu_compiler/relations.yml +26 -33
  208. data/test/integration/zafu_compiler/rubyless.yml +10 -0
  209. data/test/integration/zafu_compiler/safe_definitions.yml +21 -1
  210. data/test/integration/zafu_compiler/urls.yml +75 -5
  211. data/test/integration/zafu_compiler/version.yml +2 -2
  212. data/test/integration/zafu_compiler/zafu_attributes.yml +5 -1
  213. data/test/integration/zafu_compiler/zazen.yml +14 -6
  214. data/test/integration/zafu_compiler_test.rb +5 -1
  215. data/test/sites/complex/columns.yml +5 -0
  216. data/test/sites/complex/roles.yml +4 -0
  217. data/test/sites/zena/nodes.yml +13 -2
  218. data/test/sites/zena/roles.yml +13 -5
  219. data/test/sites/zena/versions.yml +27 -9
  220. data/test/unit/column_test.rb +51 -5
  221. data/test/unit/iformat_test.rb +2 -2
  222. data/test/unit/node_test.rb +29 -17
  223. data/test/unit/note_test.rb +1 -1
  224. data/test/unit/relation_proxy_test.rb +4 -5
  225. data/test/unit/relation_test.rb +16 -0
  226. data/test/unit/remote_test.rb +2 -2
  227. data/test/unit/role_test.rb +292 -4
  228. data/test/unit/site_test.rb +12 -0
  229. data/test/unit/template_test.rb +1 -1
  230. data/test/unit/text_document_test.rb +1 -1
  231. data/test/unit/virtual_class_test.rb +200 -83
  232. data/test/unit/zena/acts/enrollable_test.rb +26 -31
  233. data/test/unit/zena/use/calendar_test.rb +90 -37
  234. data/test/unit/zena/use/field_index_test.rb +28 -0
  235. data/test/unit/zena/use/html_tags_test.rb +7 -3
  236. data/test/unit/zena/use/ml_index_test.rb +2 -16
  237. data/test/unit/zena/use/nested_attributes_alias_view_test.rb +2 -2
  238. data/test/unit/zena/use/prop_eval_test.rb +50 -8
  239. data/test/unit/zena/use/query_node_test.rb +11 -0
  240. data/test/unit/zena/use/rendering_test.rb +72 -0
  241. data/test/unit/zena/use/scope_index_test.rb +37 -2
  242. data/test/unit/zena/use/urls_test.rb +10 -0
  243. data/test/unit/zena/use/zazen_test.rb +3 -3
  244. data/vendor/plugins/gettext_i18n_rails/Gemfile +11 -0
  245. data/vendor/plugins/gettext_i18n_rails/Gemfile.lock +92 -0
  246. data/vendor/plugins/gettext_i18n_rails/Rakefile +12 -17
  247. data/vendor/plugins/gettext_i18n_rails/Readme.md +215 -0
  248. data/vendor/plugins/gettext_i18n_rails/VERSION +1 -1
  249. data/vendor/plugins/gettext_i18n_rails/gettext_i18n_rails.gemspec +38 -34
  250. data/vendor/plugins/gettext_i18n_rails/lib/gettext_i18n_rails/active_record.rb +1 -1
  251. data/vendor/plugins/gettext_i18n_rails/lib/gettext_i18n_rails/backend.rb +30 -14
  252. data/vendor/plugins/gettext_i18n_rails/lib/gettext_i18n_rails/haml_parser.rb +1 -1
  253. data/vendor/plugins/gettext_i18n_rails/lib/gettext_i18n_rails/html_safe_translations.rb +29 -0
  254. data/vendor/plugins/gettext_i18n_rails/lib/gettext_i18n_rails/i18n_hacks.rb +29 -1
  255. data/vendor/plugins/gettext_i18n_rails/lib/gettext_i18n_rails/model_attributes_finder.rb +7 -1
  256. data/vendor/plugins/gettext_i18n_rails/lib/gettext_i18n_rails/railtie.rb +10 -0
  257. data/vendor/plugins/gettext_i18n_rails/lib/gettext_i18n_rails/ruby_gettext_extractor.rb +6 -2
  258. data/vendor/plugins/gettext_i18n_rails/lib/gettext_i18n_rails/string_interpolate_fix.rb +20 -0
  259. data/vendor/plugins/gettext_i18n_rails/lib/gettext_i18n_rails/tasks.rb +120 -0
  260. data/vendor/plugins/gettext_i18n_rails/lib/gettext_i18n_rails.rb +10 -3
  261. data/vendor/plugins/gettext_i18n_rails/lib/tasks/gettext_rails_i18n.rake +1 -74
  262. data/vendor/plugins/gettext_i18n_rails/spec/gettext_i18n_rails/active_record_spec.rb +51 -20
  263. data/vendor/plugins/gettext_i18n_rails/spec/gettext_i18n_rails/backend_spec.rb +12 -7
  264. data/vendor/plugins/gettext_i18n_rails/spec/gettext_i18n_rails/string_interpolate_fix_spec.rb +32 -0
  265. data/vendor/plugins/gettext_i18n_rails/spec/gettext_i18n_rails_spec.rb +38 -1
  266. data/vendor/plugins/gettext_i18n_rails/spec/rails2/Gemfile +11 -0
  267. data/vendor/plugins/gettext_i18n_rails/spec/spec_helper.rb +1 -8
  268. data/zena.gemspec +2241 -2217
  269. metadata +123 -83
  270. data/.gitignore +0 -36
  271. data/app/views/nodes/_dates.rhtml +0 -13
  272. data/db/init/base/skins/default/Node-+adminLayout.zafu +0 -46
  273. data/db/init/base/skins/default/Node-tree.zafu +0 -19
  274. data/vendor/plugins/gettext_i18n_rails/README.markdown +0 -143
@@ -30,9 +30,9 @@ module Zena
30
30
  current_values = relation_proxy.other_ids
31
31
  res << "<input type='hidden' name='node[#{role}_ids][]' value=''/>"
32
32
  values.each do |value|
33
- res << ("<span><input type='checkbox' name='node[#{role}_ids][]' value='#{value.zip}'" +
33
+ res << ("<p><input type='checkbox' name='node[#{role}_ids][]' value='#{value.zip}'" +
34
34
  (current_values.include?(value.id) ? " checked='checked'/> " : '/> ') +
35
- "<span>#{value.prop[attribute]}</span>")
35
+ "<span>#{value.prop[attribute]}</span></p>")
36
36
  end
37
37
  end
38
38
  res.join('')
@@ -82,7 +82,8 @@ module Zena
82
82
  # Enter the context of a newly created object
83
83
  def r_new
84
84
  return parser_error("missing 'klass' parameter") unless class_name = @params[:klass]
85
- return parser_error("invalid 'klass' parameter (not a Node)") unless klass = get_class(class_name)
85
+ return parser_error("invalid 'klass' parameter") unless klass = get_class(class_name)
86
+ return parser_error("invalid 'klass' parameter (not a Node)") unless klass <= Node
86
87
 
87
88
  res = []
88
89
  keys = {:klass => 'klass'}
@@ -157,23 +158,7 @@ module Zena
157
158
  end
158
159
  else
159
160
  super
160
- #when 'text', 'summary'
161
- # make_textarea(:name => method)
162
- #when :r_show
163
- # make_input(:name => (@params[:attr] || @params[:tattr]), :date => @params[:date])
164
- #when :r_text
165
- # make_textarea(:name => 'text')
166
- #when :r_summary
167
- # make_textarea(:name => 'summary')
168
- #when :r_zazen
169
- # make_textarea(:name => @params[:attr])
170
- #else
171
- # if node.will_be?(DataEntry) && @method.to_s =~ /node_/
172
- # # select node_id
173
- # "<%= select_id('#{node.form_name}', '#{@method}_id') %>"
174
- # end
175
161
  end
176
- #res = "<#{@html_tag || 'div'} class='zazen'>#{res}</#{@html_tag || 'div'}>" if [:r_summary, :r_text].include?(method)
177
162
  end
178
163
 
179
164
  def form_options
@@ -191,11 +176,34 @@ module Zena
191
176
  if template_url = @context[:template_url]
192
177
  # Ajax
193
178
 
179
+ if edit_or_cancel = descendant('cancel') || descendant('edit')
180
+ if cancel_text = edit_or_cancel.params[:cancel] ||
181
+ (edit_or_cancel.method == 'cancel' && edit_or_cancel.params[:text])
182
+ elsif cancel_text = edit_or_cancel.params[:tcancel] ||
183
+ (edit_or_cancel.method == 'cancel' && edit_or_cancel.params[:t])
184
+ cancel_text = RubyLess.translate(self, "t(%Q{#{cancel_text}})")
185
+ if cancel_text.literal
186
+ cancel_text = cancel_text.literal
187
+ else
188
+ cancel_text_ruby = cancel_text
189
+ cancel_text = "<%= #{cancel_text} %>"
190
+ end
191
+ end
192
+ cancel_pre = ''
193
+ cancel_post = ''
194
+ else
195
+ cancel_pre = "<p class='btn_x'>"
196
+ cancel_post = "</p>"
197
+ end
198
+
199
+ cancel_text ||= _('btn_x')
200
+ cancel_text_ruby ||= cancel_text.inspect
201
+
194
202
  if @context[:in_add]
195
203
  # Inline form used to create new elements: set values to '' and 'parent_id' from context
196
204
  opts[:id] = "#{node.dom_prefix}_form"
197
205
  opts[:form_tag] = "<% remote_form_for(:#{node.form_name}, #{node}, :url => #{node.form_name.pluralize}_path, :html => {:id => \"#{dom_name}_form_t\"}) do |f| %>"
198
- opts[:form_cancel] = "<p class='btn_x'><a href='#' onclick='[\"#{dom_name}_add\", \"#{dom_name}_form\"].each(Element.toggle);return false;'>#{_('btn_x')}</a></p>\n"
206
+ opts[:form_cancel] = "#{cancel_pre}<a href='#' onclick='[\"#{dom_name}_add\", \"#{dom_name}_form\"].each(Element.toggle);return false;'>#{cancel_text}</a>#{cancel_post}\n"
199
207
  else
200
208
  # Saved form
201
209
  opts[:id] = "<%= ndom_id(#{node}) %>"
@@ -206,9 +214,9 @@ module Zena
206
214
 
207
215
  opts[:form_cancel] = %Q{
208
216
  <% if #{node}.new_record? %>
209
- <p class='btn_x'><a href='#' onclick='[\"<%= params[:dom_id] %>_add\", \"<%= params[:dom_id] %>_form\"].each(Element.toggle);return false;'>#{_('btn_x')}</a></p>
217
+ #{cancel_pre}<a href='#' onclick='[\"<%= params[:dom_id] %>_add\", \"<%= params[:dom_id] %>_form\"].each(Element.toggle);return false;'>#{cancel_text}</a>#{cancel_post}
210
218
  <% else %>
211
- <p class='btn_x'><%= link_to_remote(#{_('btn_x').inspect}, :url => #{node.form_name}_path(#{node}.zip) + \"/zafu?t_url=#{CGI.escape(template_url)}&dom_id=\#{params[:dom_id]}#{@context[:has_link_id] ? "&link_id=\#{#{node}.link_id}" : ''}\", :method => :get) %></p>
219
+ #{cancel_pre}<%= link_to_remote(#{cancel_text_ruby}, :url => #{node.form_name}_path(#{node}.zip) + \"/zafu?t_url=#{CGI.escape(template_url)}&dom_id=\#{params[:dom_id]}#{@context[:has_link_id] ? "&link_id=\#{#{node}.link_id}" : ''}\", :method => :get) %>#{cancel_post}
212
220
  <% end %>
213
221
  }
214
222
  end
@@ -281,12 +289,12 @@ module Zena
281
289
  # 1. @node
282
290
  # 2. var1 = @node.children
283
291
  # 3. var1_new = Node.new
284
- if node.opts[:new_record]
292
+ if node.opts[:new_record] || @context[:saved_template]
285
293
  hidden_fields['node[parent_id]'] = "<%= #{@context[:in_add] ? "#{node.up.up}.zip" : "#{node}.parent_zip"} %>"
286
294
  end
287
295
  elsif node.will_be?(Comment)
288
296
  # FIXME: the "... || '@node'" is a hack and I don't understand why it's needed...
289
- hidden_fields['node_id'] = "<%= #{node.up || '@node'}.zip %>"
297
+ hidden_fields['node_id'] = "<%= #{node.get(Node) || '@node'}.zip %>"
290
298
  elsif node.will_be?(DataEntry)
291
299
  return parser_error("Missing :data_root in context (internal error)") unless data_root = @context[:data_root]
292
300
  hidden_fields["data_entry[#{data_root}_id]"] = "<%= #{@context[:in_add] ? node(Node) : "#{node}.#{data_root}"}.zip %>"
@@ -345,7 +353,7 @@ module Zena
345
353
  hidden_fields['mode'] = @params[:mode]
346
354
  end
347
355
 
348
- hidden_fields['node[v_status]'] = Zena::Status[:pub].to_s if add_params[:publish] || auto_publish_param
356
+ hidden_fields['node[v_status]'] = Zena::Status[:pub].to_s if add_params[:publish] || auto_publish_param || @context[:publish_after_save]
349
357
 
350
358
  # All default values set in the <r:new> field should at least appear as hidden fields
351
359
  if new_keys = node.opts[:new_keys]
@@ -365,12 +373,20 @@ module Zena
365
373
  hidden_fields["node[#{key}]"] = "<%= #{node}.#{value} %>"
366
374
  end
367
375
  end
368
- # ===
369
- # TODO: reject set_fields from hidden_fields
370
- # ? what is this ?
371
- # ===
376
+
377
+ # Read @params
378
+ @params.each do |key, value|
379
+ next if [:klass, :done].include?(key)
380
+ code = ::RubyLess.translate_string(self, value)
381
+ if code.literal.kind_of?(String)
382
+ hidden_fields[key] = "#{code.literal}"
383
+ else
384
+ hidden_fields[key] = "<%= #{code} %>"
385
+ end
386
+ end
372
387
 
373
388
  hidden_fields.reject! do |k,v|
389
+ # There is an explicit <input> field for this key, remove hidden value
374
390
  set_fields.include?(k)
375
391
  end
376
392
 
@@ -378,9 +394,26 @@ module Zena
378
394
  end
379
395
 
380
396
  def r_textarea
381
- params = @markup.params.merge(@params)
382
- res = make_textarea(params)
383
- extract_label(res, params, params[:name])
397
+ html_attributes, attribute = get_input_params()
398
+ erb_attr = html_attributes.delete(:erb_attr)
399
+ value = html_attributes.delete(:value)
400
+
401
+ return parser_error('Missing name.') unless attribute || html_attributes[:name]
402
+
403
+ @markup.tag = 'textarea'
404
+ @markup.set_dyn_params(html_attributes)
405
+
406
+ if @blocks == [] || @blocks == ['']
407
+ if @context[:in_add]
408
+ value = ''
409
+ end
410
+ else
411
+ value = expand_with
412
+ end
413
+
414
+ res = @markup.wrap(value)
415
+
416
+ extract_label(res, attribute || erb_attr)
384
417
  end
385
418
 
386
419
  # <r:select name='klass' root_class='...'/>
@@ -389,6 +422,7 @@ module Zena
389
422
  # TODO: optimization (avoid loading full AR to only use [id, name])
390
423
  def r_select
391
424
  html_attributes, attribute = get_input_params()
425
+ erb_attr = html_attributes.delete(:erb_attr)
392
426
  # TEMPORARY HACK UNTIL WE FIX get_input_params to return a single hash with
393
427
  # {:html => { prepared html attributes }, :raw => {:value => '..', :name => '..', :param => '..'}}
394
428
  if param = @params[:param]
@@ -408,7 +442,10 @@ module Zena
408
442
  elsif attribute =~ /^(.*)_id$/
409
443
  # relation
410
444
  selected = "#{node}.rel[#{$1.inspect}].other_zip.to_s"
445
+ elsif type = node.klass.safe_method_type([attribute])
446
+ selected = "#{node}.#{type[:method]}.to_s"
411
447
  else
448
+ # ???
412
449
  selected = "#{node}.prop[#{attribute.inspect}].to_s"
413
450
  end
414
451
  end
@@ -424,7 +461,7 @@ module Zena
424
461
  class_opts = ''
425
462
  class_opts << ", :without => #{@params[:without].inspect}" if @params[:without]
426
463
  # do not use 'selected' if the node is not new
427
- "#{select_tag}<%= options_for_select(Node.classes_for_form(:class => #{klass.inspect}#{class_opts}), (#{node}.new_record? ? #{selected} : #{node}.klass)) %></select>"
464
+ "#{select_tag}<%= options_for_select(Node.classes_for_form(:class => #{klass.inspect}#{class_opts}, :class_attr => #{(@params[:attr] || 'name').inspect}), (#{node}.new_record? ? #{selected} : #{node}.klass)) %></select>"
428
465
  elsif @params[:type] == 'time_zone'
429
466
  # <r:select name='d_tz' type='time_zone'/>
430
467
  "#{select_tag}<%= options_for_select(TZInfo::Timezone.all_identifiers, #{selected}) %></select>"
@@ -434,12 +471,13 @@ module Zena
434
471
  parser_error("missing 'nodes', 'root_class' or 'values'")
435
472
  end
436
473
 
437
- extract_label(res, @params, attribute)
474
+ extract_label(res, attribute || erb_attr)
438
475
  end
439
476
 
440
477
 
441
478
  def r_input
442
479
  html_attributes, attribute = get_input_params()
480
+ erb_attr = html_attributes.delete(:erb_attr)
443
481
  # TODO: get attribute type from get_input_params
444
482
  res = case @params[:type]
445
483
  when 'select' # FIXME: why is this only for classes ?
@@ -450,7 +488,7 @@ module Zena
450
488
  code = ::RubyLess.translate(self, "this.#{attribute}")
451
489
  value = @context[:in_add] ? "''" : code
452
490
  html_params = [':size => 15']
453
- [:style, :class, :onclick, :size].each do |key|
491
+ [:style, :class, :onclick, :size, :time].each do |key|
454
492
  html_params << ":#{key} => #{@params[key].inspect}" if @params[key]
455
493
  end
456
494
  html_params << ":id=>\"#{dom_id}_#{attribute}\"" if @context[:dom_prefix]
@@ -476,13 +514,12 @@ module Zena
476
514
  @markup.set_param(:type, @params[:type] || 'text')
477
515
 
478
516
  checked = html_attributes.delete(:checked)
479
-
480
517
  @markup.set_dyn_params(html_attributes)
481
518
  @markup.append_attribute checked if checked
482
519
  wrap('')
483
520
  end
484
521
 
485
- extract_label(res, @params, attribute || html_attributes[:name])
522
+ extract_label(res, attribute || erb_attr)
486
523
  end
487
524
 
488
525
  # <r:checkbox role='collaborator_for' values='projects' in='site'/>"
@@ -531,7 +568,7 @@ module Zena
531
568
  res = "<%= make_checkbox(#{node}, :list => #{finder}, :role => #{meth.inspect}, :attr => #{attribute.inspect}) %>"
532
569
  end
533
570
 
534
- extract_label(res, @params, attribute)
571
+ extract_label(res, attribute)
535
572
  end
536
573
 
537
574
  alias r_radio r_checkbox
@@ -568,12 +605,10 @@ module Zena
568
605
  attribute = res[:name]
569
606
  end
570
607
 
571
- unless @context[:in_filter] || attribute == 's'
572
- if sub_attr
573
- res[:name] = "#{node.form_name}[#{attribute}][#{sub_attr}]"
574
- else
575
- res[:name] = "#{node.form_name}[#{attribute}]"
576
- end
608
+ if sub_attr
609
+ res[:name] = "#{node.form_name}[#{attribute}][#{sub_attr}]"
610
+ else
611
+ res[:name] = "#{node.form_name}[#{attribute}]"
577
612
  end
578
613
  end
579
614
 
@@ -622,11 +657,12 @@ module Zena
622
657
  # end
623
658
  #end
624
659
  elsif node.will_be?(Column)
660
+ res[:erb_attr] = "<%= #{node}.name %>"
625
661
  res[:name] = "node[<%= #{node}.name %>]"
626
662
  res[:value] = "<%= fquote #{node(Node)}.prop[#{node}.name] %>"
627
663
  end
628
664
 
629
- if node.dom_prefix
665
+ if node.dom_prefix && !params[:param]
630
666
  res[:id] = "#{node.dom_prefix}_#{attribute}"
631
667
  else
632
668
  res[:id] = params[:id] if params[:id]
@@ -662,6 +698,17 @@ module Zena
662
698
  @html_tag_params.merge!(id_hash)
663
699
  out render_html_tag(res)
664
700
  =end
701
+
702
+ def r_crop
703
+ return parser_error("Invalid node type #{node.klass} (should be an Image).") unless node.will_be?(Image)
704
+ @markup.tag ||= 'div'
705
+ node.dom_prefix = dom_name
706
+ @markup.set_id(node.dom_id(:list => false))
707
+ dom = node.dom_id(:erb => false, :list => false)
708
+ out %Q{<%= render :partial => 'documents/crop', :locals => {:node => #{node(Node)}, :img_id => "img#{dom}"} %>}
709
+ out %Q{<% js_data << %Q{new Zena.Div_editor("img#{dom}", 'posx', 'posy', 'width', 'height', \#{#{node}.width / #{node}.width(Iformat['edit']).to_f}, Element.viewportOffset('#{dom}').left, Element.viewportOffset('#{dom}').top);} %>}
710
+ end
711
+
665
712
  protected
666
713
 
667
714
  # Get current attribute in forms
@@ -676,7 +723,7 @@ module Zena
676
723
  end
677
724
  end
678
725
 
679
- # Set v_status parameter value
726
+ # Set auto publish parameter value
680
727
  def auto_publish_param(in_string = false)
681
728
  if in_string
682
729
  %w{true force}.include?(@params[:publish]) ? "&publish=#{@params[:publish]}" : ''
@@ -700,7 +747,7 @@ module Zena
700
747
  begin
701
748
  finder = build_finder(:all, nodes, @params)
702
749
  klass = finder[:class].first
703
- rescue ::QueryBuilder::SyntaxError => err
750
+ rescue ::QueryBuilder::Error => err
704
751
  out parser_error(err.message)
705
752
  return nil
706
753
  end
@@ -743,31 +790,6 @@ module Zena
743
790
  end
744
791
  end
745
792
 
746
-
747
- # Transform a 'zazen' tag into a textarea input field.
748
- def make_textarea(params)
749
- return parser_error("missing 'name'") unless name = params[:name]
750
- if name =~ /\A([\w_]+)\[(.*?)\]/
751
- attribute = $2
752
- else
753
- attribute = name
754
- name = "#{node.form_name}[#{attribute}]"
755
- end
756
- return '' if attribute == 'parent_id' # set with 'r_form'
757
-
758
- if @blocks == [] || @blocks == ['']
759
- if @context[:in_add]
760
- value = ''
761
- else
762
- value = attribute ? "<%= #{node_attribute(attribute)} %>" : ""
763
- end
764
- else
765
- value = expand_with
766
- end
767
- html_id = node.dom_prefix ? " id='#{node.dom_prefix}_#{attribute}'" : ''
768
- "<textarea#{html_id} name='#{name}'>#{value}</textarea>"
769
- end
770
-
771
793
  # Return the default field that will receive focus on form display.
772
794
  def default_focus_field
773
795
  if (input_fields = descendants('input')) != []
@@ -15,6 +15,7 @@ module Zena
15
15
  end
16
16
  end
17
17
  end
18
+ base.export_attributes += FULLTEXT_FIELDS
18
19
  end
19
20
 
20
21
  private
@@ -45,38 +46,29 @@ module Zena
45
46
 
46
47
  def self.included(base)
47
48
  base.before_validation :build_fulltext_indices
48
- base.alias_method_chain :rebuild_index!, :fulltext
49
+ base.alias_method_chain :rebuild_index_for_version, :fulltext
49
50
  end
50
51
 
51
- def rebuild_index_with_fulltext!
52
- visible_versions.each do |version|
53
- build_fulltext_indices(version)
54
- fields_to_set = []
55
- FULLTEXT_FIELDS.each do |idx_group|
56
- next unless version.changes[idx_group]
57
- fields_to_set << "#{idx_group}=#{Zena::Db.quote(version[idx_group])}"
58
- end
52
+ def rebuild_index_for_version_with_fulltext(version)
53
+ # Call PropEval or other modules inserted before
54
+ rebuild_index_for_version_without_fulltext(version)
59
55
 
60
- unless fields_to_set.empty?
61
- Version.connection.execute "UPDATE versions SET #{fields_to_set.join(',')} WHERE id=#{version.id}"
62
- end
56
+ build_fulltext_indices(true)
57
+ fields_to_set = []
58
+ FULLTEXT_FIELDS.each do |idx_group|
59
+ next unless version.changes[idx_group]
60
+ fields_to_set << "#{idx_group}=#{Zena::Db.quote(version[idx_group])}"
61
+ end
62
+
63
+ unless fields_to_set.empty?
64
+ Version.connection.execute "UPDATE versions SET #{fields_to_set.join(',')} WHERE id=#{version.id}"
63
65
  end
64
- rebuild_index_without_fulltext!
65
66
  end
66
67
 
67
68
  private
68
69
  # Prepare roles to add/remove to object.
69
- def build_fulltext_indices(rebuild_version = nil)
70
- # Make sure roles are loaded because we compile RubyLess.
71
-
72
- if rebuild_version
73
- version = rebuild_version
74
- # make sure prop corresponds to the correct version content
75
- @properties = version.prop
76
- else
77
- return unless prop.changed?
78
- version = self.version
79
- end
70
+ def build_fulltext_indices(force_rebuild = false)
71
+ return if !force_rebuild && !prop.changed?
80
72
 
81
73
  if vclass = self.virtual_class
82
74
  vclass_prop = vclass.prop
@@ -100,7 +100,7 @@ module Zena
100
100
 
101
101
  def unique_id
102
102
  @counter ||= 0
103
- "#{Time.now.to_i}_#{@counter += 1}"
103
+ "h#{Time.now.to_i}_#{@counter += 1}"
104
104
  end
105
105
 
106
106
  # Display the list of readers (group names).
@@ -158,22 +158,30 @@ module Zena
158
158
  module ViewMethods
159
159
  include FormTags
160
160
  include LinkTags
161
+ include RubyLess
162
+
163
+ safe_method [:flash_messages] => String
164
+ # TODO: replace 'flash_messages' with a FlashHash context or a list
165
+ # of Flash messages.
161
166
 
162
- # Display flash[:notice] or flash[:error] if any. <%= flash <i>[:notice, :error, :both]</i> %>"
163
167
  def flash_messages(opts={})
164
168
  type = opts[:show] || 'both'
165
- "<div id='messages'>" +
169
+
166
170
  if (type == 'notice' || type == 'both') && flash[:notice]
167
- "<div id='notice' class='flash' onclick='new Effect.Fade(\"notice\")'>#{flash[:notice]}</div>"
168
- else
169
- ''
170
- end +
171
+ notice = "<div class='auto_fade notice' onclick='new Effect.Fade(this)'>#{flash[:notice]}</div>"
172
+ end
173
+
171
174
  if (type == 'error' || type == 'both') && flash[:error ]
172
- "<div id='error' class='flash' onclick='new Effect.Fade(\"error\")'>#{flash[:error]}</div>"
173
- else
174
- ''
175
- end +
176
- "</div>"
175
+ error = "<div class='error' onclick='new Effect.Fade(this)'>#{flash[:error]}</div>"
176
+ end
177
+
178
+ if page = opts[:page]
179
+ page << %q{$$('#flash_messages .auto_fade').each(function(o) {
180
+ o.opacity = 100.0;
181
+ Effect.Fade(o, {duration: 3.0});
182
+ });}
183
+ end
184
+ "<div id='flash_messages'>#{notice}#{error}</div>"
177
185
  end
178
186
  end # ViewMethods
179
187
  end # HtmlTags
data/lib/zena/use/i18n.rb CHANGED
@@ -8,7 +8,7 @@ module Zena
8
8
  ::ENV['LANG'] = 'C'
9
9
 
10
10
  class TranslationDict
11
- attr_reader :last_error
11
+ attr_reader :last_error, :node_id
12
12
 
13
13
  include Zena::Acts::Secure
14
14
  include RubyLess
@@ -279,21 +279,29 @@ module Zena
279
279
  if dict = @params[:dictionary]
280
280
  # FIXME: replace @options[:base_path] by @options[:skin_id]
281
281
  dict_content, absolute_url, base_path, doc = @options[:helper].send(:get_template_text, dict, @options[:base_path])
282
- return parser_error("dictionary #{dict.inspect} not found") unless base_path
283
- # Lazy dictionary used for literal resolution
284
- dict = TranslationDict.new(doc.id)
282
+ if base_path
283
+ # Lazy dictionary used for literal resolution
284
+ dict = TranslationDict.new(doc.id)
285
285
 
286
- if dict.load!(dict_content)
287
- # Save dictionary in template for dynamic uses
288
- dict_name = get_var_name('dictionary', 'dict')
286
+ if dict.load!(dict_content)
287
+ # Save dictionary in template for dynamic uses
288
+ dict_name = get_var_name('dictionary', 'dict')
289
289
 
290
- # This is to use in RubyLess translations and static translations in Zafu
291
- set_context_var('set_var', 'dictionary', RubyLess::TypedString.new(dict_name, :class => TranslationDict, :literal => dict, :dictionary_id => doc.id))
290
+ # This is to use in RubyLess translations and static translations in Zafu
291
+ set_context_var('set_var', 'dictionary', RubyLess::TypedString.new(dict_name, :class => TranslationDict, :literal => dict))
292
292
 
293
- # Lazy loading (loads file on first request)
294
- out "<% #{dict_name} = load_dictionary(#{doc.id}) %>"
293
+ # Lazy loading (loads file on first request)
294
+ out "<% #{dict_name} = load_dictionary(#{doc.id}) %>"
295
+ else
296
+ return parser_error(dict.last_error)
297
+ end
295
298
  else
296
- return parser_error(dict.last_error)
299
+ # not found
300
+ if @params[:missing] == 'ignore'
301
+ # ignore
302
+ else
303
+ return parser_error("dictionary #{dict.inspect} not found") unless base_path
304
+ end
297
305
  end
298
306
  else
299
307
  return parser_error("missing 'dictionary'")
@@ -346,31 +354,6 @@ module Zena
346
354
  end
347
355
  end
348
356
 
349
- def r_block
350
- # when saving template, add loaded dictionary
351
- if @context[:block] == self
352
- if @dict
353
- # pass loaded dictionary to ajax templates
354
-
355
- # Save dictionary in template for dynamic uses
356
- dict_name = get_var_name('dictionary', 'dict')
357
-
358
- # This is to use in RubyLess translations and static translations in Zafu
359
- set_context_var('set_var', 'dictionary', @dict)
360
-
361
- # Lazy loading (loads file on first request)
362
- out "<% #{dict_name} = load_dictionary(#{@dict.opts[:dictionary_id]}) %>"
363
- end
364
- super
365
- else
366
- @dict = get_context_var('set_var', 'dictionary')
367
- res = super
368
- @dict = nil
369
-
370
- res
371
- end
372
- end
373
-
374
357
  def r_trans
375
358
  # _1 ==> insert this param ==> trans(@params[:text])
376
359
  return nil unless method = get_attribute_or_eval
@@ -449,6 +432,23 @@ module Zena
449
432
  # end
450
433
  # res.join(opts[:join] || '')
451
434
  #end
435
+ protected
436
+
437
+ # Overwriten from Zafu to insert dictionary in partial if there is one
438
+ def context_for_partial(cont)
439
+ cleared_context, prefix = super(cont)
440
+ prefix = prefix.to_s
441
+ dict = get_context_var('set_var', 'dictionary')
442
+ if dict && dict.klass <= TranslationDict
443
+ # Lazy loading (loads file on first request)
444
+ dict_name = get_var_name('dictionary', 'dict', cleared_context)
445
+ set_context_var('set_var', 'dictionary', dict, cleared_context)
446
+ prefix += "<% #{dict_name} = load_dictionary(#{dict.literal.node_id}) %>"
447
+ return cleared_context, prefix
448
+ else
449
+ return cleared_context, nil
450
+ end
451
+ end
452
452
  end
453
453
  end # I18n
454
454
  end # Use
@@ -35,7 +35,14 @@ module Zena
35
35
  module Use
36
36
  class ImageBuilder
37
37
  DEFAULT_FORMATS = {
38
- 'tiny' => { :name=>'tiny', :size=>:force, :width=>16, :height=>16 , :gravity=>Magick::CenterGravity },
38
+ 'tiny' => { :name=>'tiny', :size=>:force, :width=>16, :height=>16 , :gravity=>Magick::CenterGravity },
39
+ 'tipop' => { :name=>'tipop', :size=>:force, :width=>16, :height=>16 , :gravity=>Magick::CenterGravity,
40
+ :popup => {
41
+ :name => 'std',
42
+ :options => {'title' => 'link'},
43
+ :show => ['navigation','title','summary']
44
+ }
45
+ },
39
46
  'mini' => { :name=>'mini', :size=>:force, :width=>32, :height=>32 , :gravity=>Magick::CenterGravity },
40
47
  'square' => { :name=>'square', :size=>:limit, :width=>180, :height=>180, :gravity=>Magick::CenterGravity },
41
48
  'med' => { :name=>'med', :size=>:limit, :width=>280, :height=>186, :gravity=>Magick::CenterGravity },
@@ -7,34 +7,34 @@ module Zena
7
7
  end
8
8
 
9
9
  def rebuild_index_with_multi_lingual!
10
- # We call rebuild_index_without_multi_lingual first with our hack
11
- # to avoid inclusion order problems with fulltext index.
12
-
13
- # Skip multi lingual indices
14
- @index_langs = []
15
-
16
- # Build std index
17
- rebuild_index_without_multi_lingual!
18
-
19
- # Skip std index
20
- @skip_std_index = true
21
-
22
10
  visible_versions.each do |version|
11
+ # 1. for each visible version
23
12
  self.version = version
24
13
  @properties = version.prop
25
- @index_langs = nil # force rebuild
26
- # Build ml index for each version
14
+ # rebuild for each lang
15
+ @index_langs = nil
16
+ # Forces a to skip multi lingual indices
17
+ # @index_langs = []
18
+
19
+ # Build std index
20
+ rebuild_index_for_version(version)
27
21
  rebuild_index_without_multi_lingual!
22
+ # 2. PropEval::rebuild_index!
23
+ # 3. Fulltext::rebuild_index!
24
+ # 4. Properties::rebuild_index!
28
25
  end
29
26
  end
30
27
 
28
+ def rebuild_index_for_version(v)
29
+ # noop (method chaining in PropEval, Fulltext, etc)
30
+ end
31
+
32
+
31
33
  # Hash used to read current values
32
34
  def index_reader(group_name)
33
35
  if group_name =~ /^ml_/
34
36
  return nil if index_langs.empty?
35
37
  super.merge(:with => {'lang' => index_langs})
36
- elsif @skip_std_index
37
- nil
38
38
  else
39
39
  super
40
40
  end
@@ -9,6 +9,7 @@ module Zena
9
9
  property do |p|
10
10
  p.string 'prop_eval'
11
11
  end
12
+ self.export_attributes += %w{prop_eval}
12
13
  end
13
14
  end
14
15
 
@@ -42,12 +43,15 @@ module Zena
42
43
  base.before_validation :merge_prop_eval
43
44
  base.before_validation :need_set__id
44
45
  base.before_save :set__id
45
- base.alias_method_chain :rebuild_index!, :prop_eval
46
+ base.alias_method_chain :rebuild_index_for_version, :prop_eval
46
47
  end
47
48
 
48
- def rebuild_index_with_prop_eval!
49
+ def rebuild_index_for_version_with_prop_eval(version)
50
+ # Call other modules inserted before
51
+ rebuild_index_for_version_without_prop_eval(version)
49
52
  merge_prop_eval(true)
50
- rebuild_index_without_prop_eval!
53
+ # Only save properties, without changing updated_at date or other callbacks
54
+ Zena::Db.set_attribute(version, 'properties', encode_properties(@properties)) if version.changed?
51
55
  end
52
56
 
53
57
  def need_set__id
@@ -64,9 +68,10 @@ module Zena
64
68
  self._id = self.title if @need_set__id
65
69
  end
66
70
 
71
+ # Must happend after 'change_klass'
67
72
  def merge_prop_eval(force_rebuild = false)
68
- return unless self[:vclass_id]
69
- return unless prop.changed? || force_rebuild
73
+ return unless vclass_id
74
+ return unless force_rebuild || prop.changed? || klass_changed?
70
75
 
71
76
  if code = vclass.prop_eval
72
77
  hash = safe_eval(code)