zena 1.2.1 → 1.2.2

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 (202) hide show
  1. data/History.txt +38 -1
  2. data/app/controllers/documents_controller.rb +7 -5
  3. data/app/controllers/nodes_controller.rb +47 -6
  4. data/app/controllers/user_sessions_controller.rb +12 -3
  5. data/app/controllers/virtual_classes_controller.rb +8 -2
  6. data/app/models/acl.rb +5 -2
  7. data/app/models/cached_page.rb +5 -5
  8. data/app/models/column.rb +27 -4
  9. data/app/models/group.rb +1 -1
  10. data/app/models/node.rb +106 -24
  11. data/app/models/note.rb +2 -1
  12. data/app/models/relation.rb +9 -4
  13. data/app/models/relation_proxy.rb +2 -2
  14. data/app/models/role.rb +12 -5
  15. data/app/models/site.rb +10 -9
  16. data/app/models/skin.rb +8 -0
  17. data/app/models/string_hash.rb +65 -0
  18. data/app/models/text_document.rb +1 -1
  19. data/app/models/user.rb +2 -0
  20. data/app/models/virtual_class.rb +43 -10
  21. data/app/views/comments/create.rjs +1 -32
  22. data/app/views/comments/edit.rjs +1 -1
  23. data/app/views/comments/update.rjs +1 -1
  24. data/app/views/documents/show.rhtml +1 -1
  25. data/app/views/groups/_form.rhtml +7 -0
  26. data/app/views/groups/_li.rhtml +1 -1
  27. data/app/views/nodes/500.html +2 -1
  28. data/app/views/nodes/destroy.rjs +2 -0
  29. data/app/views/sites/jobs.erb +2 -3
  30. data/app/views/templates/document_create_tabs/_file.rhtml +1 -1
  31. data/app/views/templates/document_create_tabs/_import.rhtml +4 -1
  32. data/app/views/templates/document_create_tabs/_template.rhtml +3 -0
  33. data/app/views/templates/document_create_tabs/_text_document.rhtml +3 -0
  34. data/app/views/versions/custom_tab.rhtml +1 -1
  35. data/app/views/versions/edit.rhtml +1 -1
  36. data/bricks/acls/lib/bricks/acls.rb +3 -3
  37. data/bricks/acls/zena/test/unit/acl_test.rb +15 -0
  38. data/bricks/fs_skin/lib/bricks/fs_skin.rb +190 -0
  39. data/bricks/fs_skin/zena/init.rb +1 -0
  40. data/bricks/fs_skin/zena/migrate/20110702010330_add_fs_skin_to_idx_templates.rb +12 -0
  41. data/bricks/{static → fs_skin}/zena/skins/blog/Image-edit.zafu +0 -0
  42. data/bricks/{static → fs_skin}/zena/skins/blog/Image.zafu +0 -0
  43. data/bricks/{static → fs_skin}/zena/skins/blog/Node-+index.zafu +0 -0
  44. data/bricks/{static → fs_skin}/zena/skins/blog/Node-+notFound.zafu +0 -0
  45. data/bricks/{static → fs_skin}/zena/skins/blog/Node-+search.zafu +0 -0
  46. data/bricks/{static → fs_skin}/zena/skins/blog/Node.zafu +1 -1
  47. data/bricks/{static → fs_skin}/zena/skins/blog/Post.zafu +0 -0
  48. data/bricks/{static → fs_skin}/zena/skins/blog/Project--kml.zafu +0 -0
  49. data/bricks/{static → fs_skin}/zena/skins/blog/Project.zafu +0 -0
  50. data/bricks/{static → fs_skin}/zena/skins/blog/comments.zafu +0 -0
  51. data/bricks/{static → fs_skin}/zena/skins/blog/dict.yml +0 -0
  52. data/bricks/{static → fs_skin}/zena/skins/blog/img/dateBg.jpg +0 -0
  53. data/bricks/{static → fs_skin}/zena/skins/blog/img/header.png +0 -0
  54. data/bricks/{static → fs_skin}/zena/skins/blog/img/mapPin.png +0 -0
  55. data/bricks/{static → fs_skin}/zena/skins/blog/img/menu.gif +0 -0
  56. data/bricks/{static → fs_skin}/zena/skins/blog/img/menuover.gif +0 -0
  57. data/bricks/{static → fs_skin}/zena/skins/blog/img/style.css +0 -0
  58. data/bricks/fs_skin/zena/tasks.rb +26 -0
  59. data/bricks/{static/zena/test/integration/static_integration_test.rb → fs_skin/zena/test/integration/fs_skin_integration_test.rb} +6 -6
  60. data/bricks/fs_skin/zena/test/unit/fs_skin_test.rb +33 -0
  61. data/bricks/grid/lib/bricks/grid.rb +4 -3
  62. data/bricks/tags/lib/bricks/tags.rb +1 -7
  63. data/bricks/zena/zena/migrate/20120605091558_add_ssl_login_to_site.rb +7 -0
  64. data/bricks/zena/zena/migrate/20120630123551_add_auto_publish_to_group.rb +9 -0
  65. data/config/bricks.yml +3 -3
  66. data/config/gems.yml +2 -3
  67. data/lib/tasks/zena.rake +7 -3
  68. data/lib/zafu.rb +7 -0
  69. data/lib/zafu/all.rb +21 -0
  70. data/lib/zafu/compiler.rb +7 -0
  71. data/lib/zafu/controller_methods.rb +58 -0
  72. data/lib/zafu/handler.rb +57 -0
  73. data/lib/zafu/info.rb +4 -0
  74. data/lib/zafu/markup.rb +309 -0
  75. data/lib/zafu/mock_helper.rb +42 -0
  76. data/lib/zafu/node_context.rb +203 -0
  77. data/lib/zafu/ordered_hash.rb +53 -0
  78. data/lib/zafu/parser.rb +676 -0
  79. data/lib/zafu/parsing_rules.rb +382 -0
  80. data/lib/zafu/process/ajax.rb +530 -0
  81. data/lib/zafu/process/conditional.rb +92 -0
  82. data/lib/zafu/process/context.rb +186 -0
  83. data/lib/zafu/process/forms.rb +143 -0
  84. data/lib/zafu/process/html.rb +186 -0
  85. data/lib/zafu/process/ruby_less_processing.rb +321 -0
  86. data/lib/zafu/security.rb +15 -0
  87. data/lib/zafu/template.rb +25 -0
  88. data/lib/zafu/test_helper.rb +19 -0
  89. data/lib/zafu/view_methods.rb +6 -0
  90. data/lib/zena.rb +1 -1
  91. data/lib/zena/acts/enrollable.rb +1 -1
  92. data/lib/zena/app.rb +4 -17
  93. data/lib/zena/console.rb +18 -1
  94. data/lib/zena/core_ext/file_utils.rb +13 -1
  95. data/lib/zena/core_ext/fixnum.rb +4 -0
  96. data/lib/zena/core_ext/float.rb +7 -0
  97. data/lib/zena/deploy.rb +4 -2
  98. data/lib/zena/deploy/app_init.rhtml +2 -1
  99. data/lib/zena/deploy/database.rhtml +1 -1
  100. data/lib/zena/info.rb +1 -1
  101. data/lib/zena/parser/zazen_rules.rb +4 -4
  102. data/lib/zena/routes.rb +1 -1
  103. data/lib/zena/test_controller.rb +1 -1
  104. data/lib/zena/use.rb +14 -1
  105. data/lib/zena/use/action.rb +4 -2
  106. data/lib/zena/use/ajax.rb +86 -38
  107. data/lib/zena/use/authlogic.rb +16 -1
  108. data/lib/zena/use/calendar.rb +37 -17
  109. data/lib/zena/use/conditional.rb +2 -2
  110. data/lib/zena/use/context.rb +30 -9
  111. data/lib/zena/use/dates.rb +39 -3
  112. data/lib/zena/use/display.rb +6 -19
  113. data/lib/zena/use/forms.rb +100 -79
  114. data/lib/zena/use/i18n.rb +40 -16
  115. data/lib/zena/use/query_builder.rb +0 -6
  116. data/lib/zena/use/query_node.rb +17 -4
  117. data/lib/zena/use/relations.rb +1 -3
  118. data/lib/zena/use/rendering.rb +10 -8
  119. data/lib/zena/use/scope_index.rb +5 -1
  120. data/lib/zena/use/search.rb +2 -1
  121. data/lib/zena/use/urls.rb +82 -77
  122. data/lib/zena/use/workflow.rb +12 -4
  123. data/lib/zena/use/zafu_safe_definitions.rb +37 -9
  124. data/lib/zena/use/zafu_templates.rb +49 -20
  125. data/lib/zena/use/zazen.rb +6 -2
  126. data/locale/it/LC_MESSAGES/zena.mo +0 -0
  127. data/locale/it/zena.mo +0 -0
  128. data/locale/it/zena.po +1982 -0
  129. data/public/images/arrow_back.png +0 -0
  130. data/public/images/remove_tag.png +0 -0
  131. data/public/javascripts/grid.js +800 -199
  132. data/public/javascripts/window.js +1 -1
  133. data/public/javascripts/zena.js +130 -21
  134. data/public/stylesheets/grid.css +11 -2
  135. data/public/stylesheets/zena.css +2 -1
  136. data/test/custom_queries/complex.host.yml +5 -0
  137. data/test/fixtures/files/TestNode.zafu +36 -0
  138. data/test/functional/nodes_controller_test.rb +18 -1
  139. data/test/integration/zafu_compiler/action.yml +2 -2
  140. data/test/integration/zafu_compiler/ajax.yml +44 -26
  141. data/test/integration/zafu_compiler/asset.yml +12 -2
  142. data/test/integration/zafu_compiler/basic.yml +0 -16
  143. data/test/integration/zafu_compiler/calendar.yml +6 -6
  144. data/test/integration/zafu_compiler/complex_ok.yml +23 -1
  145. data/test/integration/zafu_compiler/conditional.yml +5 -5
  146. data/test/integration/zafu_compiler/context.yml +6 -5
  147. data/test/integration/zafu_compiler/dates.yml +23 -2
  148. data/test/integration/zafu_compiler/display.yml +46 -2
  149. data/test/integration/zafu_compiler/errors.yml +2 -2
  150. data/test/integration/zafu_compiler/eval.yml +35 -7
  151. data/test/integration/zafu_compiler/forms.yml +47 -13
  152. data/test/integration/zafu_compiler/i18n.yml +2 -2
  153. data/test/integration/zafu_compiler/meta.yml +35 -1
  154. data/test/integration/zafu_compiler/query.yml +23 -4
  155. data/test/integration/zafu_compiler/relations.yml +10 -6
  156. data/test/integration/zafu_compiler/roles.yml +4 -4
  157. data/test/integration/zafu_compiler/rubyless.yml +11 -1
  158. data/test/integration/zafu_compiler/safe_definitions.yml +23 -5
  159. data/test/integration/zafu_compiler/security.yml +10 -6
  160. data/test/integration/zafu_compiler/urls.yml +23 -6
  161. data/test/integration/zafu_compiler/zafu_attributes.yml +1 -1
  162. data/test/integration/zafu_compiler/zazen.yml +14 -0
  163. data/test/selenium/Add/add3.rsel +8 -8
  164. data/test/selenium/Destroy/0setup.rsel +12 -0
  165. data/test/selenium/Destroy/destroy1.rsel +16 -0
  166. data/test/selenium/Edit/edit2.rsel +9 -9
  167. data/test/selenium/Edit/edit5.rsel +9 -9
  168. data/test/selenium/Edit/edit6.rsel +9 -9
  169. data/test/selenium/Form/form4.rsel +17 -0
  170. data/test/selenium/Toggle/toggle1.rsel +2 -0
  171. data/test/selenium/Toggle/toggle2.rsel +18 -0
  172. data/test/sites/zena/columns.yml +3 -0
  173. data/test/sites/zena/versions.yml +7 -0
  174. data/test/unit/cached_page_test.rb +13 -13
  175. data/test/unit/column_test.rb +26 -0
  176. data/test/unit/node_test.rb +16 -1
  177. data/test/unit/project_test.rb +6 -1
  178. data/test/unit/relation_test.rb +1 -1
  179. data/test/unit/role_test.rb +1 -1
  180. data/test/unit/string_hash_test.rb +30 -0
  181. data/test/unit/virtual_class_test.rb +31 -17
  182. data/test/unit/zafu_markup_test.rb +414 -0
  183. data/test/unit/zafu_node_context_test.rb +375 -0
  184. data/test/unit/zafu_ordered_hash_test.rb +69 -0
  185. data/test/unit/zena/acts/enrollable_test.rb +1 -1
  186. data/test/unit/zena/parser/zafu_asset.yml +0 -10
  187. data/test/unit/zena/parser/zazen.yml +1 -1
  188. data/test/unit/zena/parser_test.rb +1 -72
  189. data/test/unit/zena/use/dates_test.rb +1 -1
  190. data/test/unit/zena/use/rendering_test.rb +24 -7
  191. data/test/unit/zena/use/scope_index_test.rb +17 -0
  192. data/test/unit/zena/use/zazen_test.rb +2 -1
  193. data/zena.gemspec +71 -37
  194. metadata +104 -83
  195. data/app/views/nodes/destroy.erb +0 -0
  196. data/bricks/static/lib/bricks/static.rb +0 -151
  197. data/bricks/static/zena/init.rb +0 -1
  198. data/bricks/static/zena/migrate/20110702010330_add_static_to_idx_templates.rb +0 -12
  199. data/bricks/static/zena/test/unit/static_test.rb +0 -33
  200. data/lib/zena/parser/zafu_rules.rb +0 -244
  201. data/lib/zena/parser/zafu_tags.rb +0 -198
  202. data/lib/zena/parser/zena_rules.rb +0 -23
@@ -0,0 +1,382 @@
1
+ require 'zafu/markup'
2
+
3
+ module Zafu
4
+ PARAM_KEY_REGEXP = %r{^\s+([\w_\-\[\]:]+)=}m
5
+ PARAM_VALUE_REGEXP = %r{('|")(|[^\1]*?[^\\])\1}m
6
+ module ParsingRules
7
+ # The context informs the rendering element about the current Node, node class, existing ids, etc. The
8
+ # context is inherited by sub-elements.
9
+ attr_reader :context
10
+
11
+ # The helper is used to connect the compiler to the world of the application (read/write templates, access traductions, etc)
12
+ attr_reader :helper
13
+
14
+ # The markup (of class Markup) holds information on the tag (<li>), tag attributes (.. class='foo') and
15
+ # indentation information that should be used when rendered. This context is not inherited.
16
+ attr_accessor :markup
17
+
18
+ # We need this flag to detect cases like <r:with part='list' do='other list finder'/>
19
+ attr_reader :sub_do
20
+
21
+ def self.included(base)
22
+ base.before_parse :remove_erb
23
+ base.before_process :unescape_ruby
24
+ end
25
+
26
+ # This callback is run just after the block is initialized (Parser#initialize).
27
+ def start(mode)
28
+ # tag_context
29
+ @markup = Markup.new(@options.delete(:html_tag))
30
+
31
+ # html_tag
32
+ if html_params = @options.delete(:html_tag_params)
33
+ @markup.params = html_params
34
+ end
35
+
36
+ # end_tag is used to know when to close parsing in sub-do
37
+ # Example:
38
+ # <li do='each' do='images'>
39
+ # <ul>
40
+ # <li><r:link/></li> <!-- do not close outer LI now: @end_tag_count != 0 -->
41
+ # </ul>
42
+ # </li> <!-- close outer LI now: @end_tag_count == 0 -->
43
+ #
44
+ @end_tag = @markup.tag || @options.delete(:end_tag) || "r:#{@method}"
45
+ @end_tag_count = 1
46
+
47
+ # code indentation
48
+ @markup.space_before = @options.delete(:space_before)
49
+
50
+ if sub = @params.delete(:do)
51
+ # we have a sub 'do'
52
+ sub_method = sub.delete(:method)
53
+
54
+ # We need this flag to detect cases cases like <r:with part='list' do='other list finder'/>
55
+ @sub_do = true
56
+
57
+ opts = {:method => sub_method, :params => sub}
58
+
59
+ # the matching zafu tag will be parsed by the last 'do', we must inform it to halt properly :
60
+ opts[:end_tag] = @end_tag
61
+
62
+ sub = make(:void, opts)
63
+ @markup.space_after = sub.markup.space_after
64
+ sub.markup.space_after = ""
65
+ end
66
+
67
+ # set name used for include/replace from html_tag if not already set by superclass
68
+ @name = extract_name
69
+
70
+ if !@markup.tag && (@markup.tag = @params.delete(:tag))
71
+ # Extract html tag parameters from @params
72
+ @markup.steal_html_params_from(@params)
73
+ end
74
+
75
+ if @method == 'include' && @params[:template]
76
+ include_template
77
+ elsif mode == :tag && !sub
78
+ scan_tag
79
+ elsif !sub
80
+ enter(mode)
81
+ end
82
+ end
83
+
84
+ # Used to debug parser.
85
+ def to_s
86
+ "[#{@method}#{@name.blank? ? '' : " '#{@name}'"}#{@params.empty? ? '' : " #{@params.map{|k,v| ":#{k}=>#{v.inspect}"}.join(', ')}"}]" + (@blocks||[]).join('') + "[/#{@method}]"
87
+ end
88
+
89
+ def extract_name
90
+ @options[:name] ||
91
+ (%w{input select textarea}.include?(@method) ? nil : @params[:name]) ||
92
+ @markup.params[:id] ||
93
+ @params[:id]
94
+ end
95
+
96
+ def remove_erb(text)
97
+ text.gsub('<%', '&lt;%').gsub('%>', '%&gt;').gsub(/<\Z/, '&lt;')
98
+ end
99
+
100
+ def unescape_ruby
101
+ @params.each do |k,v|
102
+ v.gsub!('&gt;', '>')
103
+ v.gsub!('&lt;', '<')
104
+ end
105
+ @method.gsub!('&gt;', '>')
106
+ @method.gsub!('&lt;', '<')
107
+ end
108
+
109
+ def single_child_method
110
+ return @single_child_method if defined?(@single_child_method)
111
+ @single_child_method = if @blocks.size == 1
112
+ single_child = @blocks[0]
113
+ return nil if single_child.kind_of?(String)
114
+ single_child.markup.tag ? nil : single_child.method
115
+ else
116
+ nil
117
+ end
118
+ end
119
+
120
+ # scan rules
121
+ def scan
122
+ #puts "SCAN(#{@method}): [#{@text[0..20]}]"
123
+ if @text =~ %r{\A([^<]*?)(\s*)//!}m
124
+ # comment
125
+ found = $1
126
+ flush found
127
+ eat $2
128
+ scan_comment
129
+ elsif @text =~ /\A(([^<]*?)(^ *|))</m
130
+ # Warning: this regexp looks too complicated but it's the only one that
131
+ # works without capturing too much or not enough space in "space_before".
132
+ flush $2
133
+ space = $3
134
+ eat space
135
+
136
+ if @text[1..1] == '/'
137
+ store space
138
+ scan_close_tag
139
+ elsif %w{! ?}.include?(@text[1..1])
140
+ if @text[2..3] == '--'
141
+ store space
142
+ scan_html_comment
143
+ elsif @text[2..8] == '[CDATA['
144
+ # We do not flush because space has been eaten
145
+ store space
146
+ flush '<![CDATA['
147
+ elsif @text =~ /\A\s*<([^>]+)>/m
148
+ # Doctype/xml
149
+ flush $&
150
+ end
151
+ elsif $1.last == ' ' && @text[0..1] == '< '
152
+ # solitary ' < '
153
+ store space
154
+ flush '< '
155
+ scan
156
+ else
157
+ scan_tag(:space_before => space)
158
+ end
159
+ else
160
+ # no more tags
161
+ flush
162
+ end
163
+ end
164
+
165
+ def scan_close_tag
166
+ if @text =~ /\A<\/([^>]+)>( *\n+|)/m
167
+ # puts "CLOSE:[#{$&}]}" # ztag
168
+ # closing tag
169
+ if $1 == @end_tag
170
+ @end_tag_count -= 1
171
+ if @end_tag_count == 0
172
+ eat $&
173
+
174
+ @markup.space_after = $2
175
+ leave
176
+ else
177
+ # keep the tag (false alert)
178
+ flush $&
179
+ end
180
+ elsif $1[0..1] == 'r:'
181
+ # /rtag
182
+ eat $&
183
+ if $1 != @end_tag
184
+ # error bad closing rtag
185
+ store "<span class='parser_error'>#{$&.gsub('<', '&lt;').gsub('>','&gt;')} should be &lt;/#{@end_tag}&gt;</span>"
186
+ end
187
+ leave
188
+ else
189
+ # other html tag closing
190
+ flush $&
191
+ end
192
+ else
193
+ # error
194
+ flush
195
+ end
196
+ end
197
+
198
+ def scan_html_comment(opts={})
199
+ if @text =~ /\A<!--\|(.*?)-->/m
200
+ # zafu html escaped
201
+ #puts "ZAFU_HTML_ESCAPED[#{$&}]"
202
+ eat $&
203
+ @text = opts[:space_before] + $1 + @text
204
+ elsif @text =~ /\A<!--.*?-->/m
205
+ # html comment
206
+ #puts "HTML_COMMENT[#{$&}]"
207
+ flush $&
208
+ else
209
+ # error
210
+ flush
211
+ end
212
+ end
213
+
214
+ def scan_comment
215
+ if @text =~ %r{\A//!.*(\n|\Z)}
216
+ # zafu html escaped
217
+ eat $&
218
+ else
219
+ # error
220
+ flush
221
+ end
222
+ end
223
+
224
+ def get_params
225
+ params = Zafu::OrderedHash.new
226
+ raw = ''
227
+ while @text =~ PARAM_KEY_REGEXP
228
+ raw << $&
229
+ eat $&
230
+ key = $1
231
+
232
+ if @text =~ PARAM_VALUE_REGEXP
233
+ raw_t = $&
234
+ quote = $1
235
+ eat $&
236
+ value = $2.gsub("\\#{quote}", quote)
237
+ if key == 'do'
238
+ # Sub do
239
+ sub, raw = get_params
240
+ sub[:method] = value
241
+ params[:do] = sub
242
+ return params
243
+ else
244
+ raw << raw_t
245
+ params[key.to_sym] = value
246
+ end
247
+ end
248
+ end
249
+ return params, raw
250
+ end
251
+
252
+ def scan_tag(opts={})
253
+ #puts "TAG(#{@method}): [#{@text[0..20]}]"
254
+ # FIXME: Better parameters parsing could avoid the &gt; hack. Create a "scan_params" method.
255
+ if @text =~ /\A<r:([\w_]+\??)/
256
+ #puts "RTAG:#{$~.to_a.inspect}" # ztag
257
+ method = $1
258
+ eat $&
259
+ params, raw = get_params
260
+ #puts "AFTER(#{@method}): [#{@text[0..20]}]"
261
+ if @text =~ /\A\s*(\/?)>/
262
+ eat $&
263
+ opts.merge!(:method=>method, :params=>params)
264
+ opts.merge!(:text=>'') if $1 != ''
265
+ make(:void, opts)
266
+ else
267
+ # ERROR
268
+ flush
269
+ end
270
+ #elsif @text =~ /\A<(\w+)([^>]*?)do\s*=('([^>]*?[^\\]|)'|"([^>]*?[^\\]|)")([^>]*?)(\/?)>/
271
+ elsif @text =~ /\A<([\w:]+)/
272
+ html_tag = $1
273
+ eat $&
274
+ params, raw = get_params
275
+
276
+ #puts "HTML(#{html_tag}):[#{@text}]" # html tag
277
+ if @text =~ /\A\s*(\/?)>/
278
+ eat $&
279
+ is_end_tag = !$1.blank?
280
+
281
+ if sub = params.delete(:do)
282
+ # puts "SUB_DO:#{params.inspect}"
283
+ # do tag
284
+ method = sub.delete(:method)
285
+ opts.merge!(:text=>'') if is_end_tag
286
+ opts.merge!(
287
+ :html_tag => html_tag,
288
+ :html_tag_params => params,
289
+ :method => method,
290
+ :params => sub
291
+ )
292
+ make(:void, opts)
293
+ elsif raw =~ /\#\{/ || params[:id]
294
+ # puts "HTML_DYN|ID:#{@params.inspect}"
295
+ # If we have an :id, we need to store this as a block in case it is replaced
296
+ # html tag with dynamic params
297
+ opts.merge!(:text=>'') if is_end_tag
298
+ opts.merge!(:method => 'void', :html_tag => html_tag, :html_tag_params => params)
299
+ make(:void, opts)
300
+ elsif @end_tag && html_tag == @end_tag
301
+ #puts "PLAIN(END):#{@params.inspect}"
302
+ # plain html tag
303
+ store "#{opts[:space_before]}<#{html_tag}#{raw}#{is_end_tag ? '/' : ''}>"
304
+ @end_tag_count += 1 unless is_end_tag
305
+ elsif %w{link img script}.include?(html_tag)
306
+ #puts "ASSET: [#{@text}]"
307
+ opts.merge!(:text=>'') if is_end_tag
308
+ opts.merge!(:method => 'rename_asset', :html_tag_params => params, :params => params, :html_tag => html_tag)
309
+ make(:asset, opts)
310
+ else
311
+ #puts "PLAIN:<#{html_tag}#{raw}#{is_end_tag ? '/' : ''}>"
312
+ # plain html tag
313
+ store "#{opts[:space_before]}<#{html_tag}#{raw}#{is_end_tag ? '/' : ''}>"
314
+ end
315
+ else
316
+ # ERROR
317
+ flush
318
+ end
319
+ else
320
+ # unknown tag type
321
+ store %Q{<span class='parser_error'>Invalid tag near '#{@text[0..10].gsub('>','&gt;').gsub('<','&lt;')}'</span>}
322
+ @text = ''
323
+ end
324
+ end
325
+
326
+ def scan_asset
327
+ @end_tag = @markup.tag
328
+ if @markup.tag == 'script'
329
+ enter(:void)
330
+ else
331
+ enter(:inside_asset)
332
+ end
333
+ end
334
+
335
+ def scan_inside_asset
336
+ if @text =~ /\A(.*?)<\/#{@end_tag.gsub('?', '\\?')}>/m
337
+ eat $&
338
+ store $1
339
+ leave(:asset)
340
+ else
341
+ # never ending asset
342
+ flush
343
+ end
344
+ end
345
+
346
+ # Helper during compilation to make a block
347
+ def add_block(text_or_opts, at_start = false)
348
+ # avoid wrapping objects in [void][/void]
349
+ bak = @blocks
350
+ @blocks = []
351
+ if text_or_opts.kind_of?(String)
352
+ new_blocks = make(:void, :method => 'void', :text => text_or_opts).blocks
353
+ else
354
+ new_blocks = [make(:void, text_or_opts)]
355
+ end
356
+ if at_start
357
+ bak = new_blocks + bak
358
+ else
359
+ bak += new_blocks
360
+ end
361
+ @blocks = bak
362
+ # Force descendants rebuild
363
+ @all_descendants = nil
364
+ end
365
+
366
+ # Helper during compilation to wrap current content in a new block
367
+ def wrap_in_block(text_or_opts)
368
+ # avoid wrapping objects in [void][/void]
369
+ bak = @blocks
370
+ @blocks = []
371
+ if text_or_opts.kind_of?(String)
372
+ wrapper = make(:void, :method => 'void', :text => text_or_opts)
373
+ else
374
+ wrapper = make(:void, text_or_opts)
375
+ end
376
+ wrapper.blocks = bak
377
+ @blocks = [wrapper]
378
+ # Force descendants rebuild
379
+ @all_descendants = nil
380
+ end
381
+ end # ParsingRules
382
+ end # Zafu
@@ -0,0 +1,530 @@
1
+ module Zafu
2
+ module Process
3
+ module Ajax
4
+ def save_state
5
+ super.merge(:@markup => @markup.dup)
6
+ end
7
+
8
+
9
+ # This method process a list and handles building the necessary templates for ajax 'add'.
10
+ def expand_with_finder(finder)
11
+ @context.delete(:make_form) # Do not propagate.
12
+ return super unless finder[:class].kind_of?(Array)
13
+
14
+ # reset scope
15
+ @context[:saved_template] = nil
16
+
17
+ # Get the block responsible for rendering each elements in the list
18
+ each_block = descendant('each')
19
+ add_block = descendant('add')
20
+ form_block = descendant('form') || each_block
21
+ edit_block = descendant('edit')
22
+
23
+
24
+ # Should 'edit' and 'add' auto-publish ?
25
+ publish_after_save = (form_block && form_block.params[:publish]) ||
26
+ (edit_block && edit_block.params[:publish])
27
+
28
+ # class name for create form
29
+ if class_name = (add_block && add_block.params[:klass]) ||
30
+ (form_block && form_block.params[:klass])
31
+ unless klass = get_class(class_name)
32
+ return parser_error("Invalid class '#{class_name}'")
33
+ end
34
+ else
35
+ klass = finder[:class].first
36
+ end
37
+
38
+ if need_ajax?(each_block)
39
+ # 1. Render inline
40
+ # assign [] to var
41
+ out "<% if (#{var} = #{finder[:method]}) || (#{node}.#{finder[:class].first <= Comment ? "can_comment?" : "can_write?"} && #{var}=[]) %>"
42
+ # The list is not empty or we have enough rights to add new elements.
43
+
44
+ # New node context.
45
+ open_node_context(finder, :node => self.node.move_to(var, finder[:class])) do
46
+ # Pagination count and other contextual variables exist here.
47
+
48
+ tmplt_node = self.node.move_to(var, finder[:class])
49
+ # Own scope
50
+ node.dom_prefix = dom_name
51
+ tmplt_node.dom_prefix = dom_name
52
+
53
+ # INLINE ==========
54
+ out wrap(
55
+ expand_with(
56
+ :in_if => false,
57
+ # 'r_add' needs the form when rendering. Send with :form.
58
+ :form => form_block,
59
+ :publish_after_save => publish_after_save,
60
+ # Do not render the form block directly: let [add] do this.
61
+ :ignore => ['form'],
62
+ :klass => klass
63
+ )
64
+ )
65
+
66
+ # Render 'else' clauses
67
+ else_clauses = expand_with(
68
+ :in_if => true,
69
+ :only => ['elsif', 'else'],
70
+ :markup => @markup
71
+ )
72
+
73
+ # 2. Save 'each' template
74
+ store_block(each_block, :node => tmplt_node)
75
+
76
+ # 3. Save 'form' template
77
+ cont = {
78
+ :saved_template => form_url(tmplt_node.dom_prefix),
79
+ :klass => klass,
80
+ :make_form => each_block == form_block,
81
+ # Used to get parameters like 'publish', 'done', 'after'
82
+ :add => add_block,
83
+ :publish_after_save => publish_after_save,
84
+ :new_keys => {:parent_id => '@node.parent_zip'}
85
+ }
86
+
87
+ store_block(form_block, cont)
88
+ end
89
+ out "<% end %>"
90
+ else
91
+ super
92
+ end
93
+
94
+ # out wrap(expand_with(:node => node.move_to(var, finder[:class]), :in_if => true))
95
+
96
+
97
+ #query = opts[:query]
98
+ #
99
+ #
100
+ #if need_ajax?
101
+ # new_dom_scope
102
+ # # ajax, build template. We could merge the following code with 'r_block'.
103
+ #
104
+ # # FORM ============
105
+ # if each_block != form_block
106
+ # form = expand_block(form_block, :klass => klass, :add=>add_block, :publish_after_save => publish_after_save, :saved_template => true)
107
+ # else
108
+ # form = expand_block(form_block, :klass => klass, :add=>add_block, :make_form=>true, :publish_after_save => publish_after_save, :saved_template => true)
109
+ # end
110
+ # out helper.save_erb_to_url(form, form_url)
111
+ #else
112
+ # # no form, render, edit and add are not ajax
113
+ # if descendant('add') || descendant('add_document')
114
+ # out "<% if (#{list_var} = #{list_finder}) || (#{node}.#{node.will_be?(Comment) ? "can_comment?" : "can_write?"} && #{list_var}=[]) %>"
115
+ # elsif list_finder != 'nil'
116
+ # out "<% if #{list_var} = #{list_finder} %>"
117
+ # else
118
+ # out "<% if nil %>"
119
+ # end
120
+ #
121
+ #
122
+ # out render_html_tag(expand_with(:list=>list_var, :in_if => false))
123
+ # out expand_with(:in_if=>true, :only=>['elsif', 'else'], :html_tag => @html_tag, :html_tag_params => @html_tag_params)
124
+ # out "<% end %>"
125
+ #end
126
+ end
127
+
128
+ # Store a context as a sub-template that can be used in ajax calls
129
+ def r_block
130
+ if parent.method == 'each' && @method == parent.single_child_method
131
+ # Block stored in 'each', do nothing
132
+ # What happens when this is used as remote target ?
133
+ return expand_with
134
+ end
135
+
136
+ @markup.done = false
137
+
138
+ if @context[:block] == self
139
+ # Storing our block template
140
+ #node.dom_prefix = dom_name
141
+ @markup.set_id(node.dom_id(:list => false))
142
+ @markup.set_dyn_param(:"data-z", "<%= #{node}.zip %>")
143
+ expand_with
144
+ elsif @context[:saved_template]
145
+ # already in a parent's store operation. Reset scope and simply render inline elements
146
+ # reset scope
147
+ with_context(:node => node.dup, :saved_template => nil) do
148
+ node.saved_dom_id = nil
149
+ node.dom_prefix = dom_name
150
+ @markup.set_id(node.dom_id(:list => false))
151
+ expand_with
152
+ end
153
+ else
154
+ # Since we are using ajax, we will need this object to have an ID set and
155
+ # have its own template_url and such.
156
+ with_context(:node => node.dup, :saved_template => nil) do
157
+ # reset scope. We only keep current id when we are called from
158
+ # r_drop.
159
+ @markup = @markup.dup
160
+ @markup.set_id(nil)
161
+ node.saved_dom_id = nil
162
+ # our own domain
163
+ node.dom_prefix = dom_name
164
+
165
+ # 1. inline
166
+ # Set id with the current node context (<%= var1.zip %>).
167
+ @markup.set_id(node.dom_id(:list => false))
168
+ if node.dom_id(:list => false) != node.dom_prefix
169
+ @markup.set_param(:"data-t", node.dom_prefix)
170
+ end
171
+ @markup.set_dyn_param(:"data-z", "<%= #{node}.zip %>")
172
+
173
+ out expand_with
174
+ # 2. store template
175
+ # will wrap with @markup
176
+ store_block(self, :ajax_action => 'show')
177
+
178
+ if edit_block = descendant('edit')
179
+ form_block = descendant('form') || self
180
+
181
+ publish_after_save = form_block.params[:publish] ||
182
+ (edit_block && edit_block.params[:publish])
183
+
184
+ # 3. store form
185
+ cont = {
186
+ :saved_template => form_url(node.dom_prefix),
187
+ :make_form => self == form_block,
188
+ :publish_after_save => publish_after_save,
189
+ :ajax_action => 'edit',
190
+ }
191
+
192
+ store_block(form_block, cont)
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+
199
+ def r_edit
200
+ # ajax
201
+ if cancel = @context[:form_cancel]
202
+ # cancel button
203
+ out cancel
204
+ else
205
+ # edit button
206
+
207
+ # TODO: show 'reply' instead of 'edit' in comments if visitor != author
208
+ block = ancestor(%w{each block})
209
+
210
+ # These parameters are detected by r_block and set in form.
211
+ # removed so they do not polute link
212
+ @params.delete(:publish)
213
+ @params.delete(:cancel)
214
+ @params.delete(:tcancel)
215
+
216
+ link = wrap(make_link(:default_text => _('edit'), :update => block, :action => 'edit'))
217
+
218
+ out "<% if #{node}.can_write? %>#{link}<% end %>"
219
+ end
220
+ end
221
+
222
+ def r_cancel
223
+ r_edit
224
+ end
225
+
226
+ def r_add
227
+ return parser_error("Should not be called from within 'each'") if parent.method == 'each'
228
+ return parser_error("Should not be called outside list context") unless node.list_context?
229
+ return '' if @context[:make_form]
230
+
231
+ if node.will_be?(Comment)
232
+ out "<% if #{node.up(Node)}.can_comment? %>"
233
+ else
234
+ out "<% if #{node.up(Node)}.can_write? %>"
235
+ end
236
+
237
+ unless descendant('add_btn')
238
+ # Add a descendant between self and blocks. ==> add( add_btn(blocks) )
239
+ blocks = @blocks.dup
240
+ @blocks = []
241
+ add_btn = make(:void, :method => 'add_btn', :params => @params.dup, :text => '')
242
+ add_btn.blocks = blocks
243
+ @all_descendants = nil
244
+ end
245
+
246
+ if @context[:form]
247
+ # ajax add
248
+ @markup.set_id("#{node.dom_prefix}_add")
249
+ @markup.append_param(:class, 'btn_add')
250
+
251
+ if @params[:focus]
252
+ focus = "$(\"#{node.dom_prefix}_#{@params[:focus]}\").focus();"
253
+ else
254
+ focus = "$(\"#{node.dom_prefix}_form_t\").focusFirstElement();"
255
+ end
256
+
257
+ # Expand 'add' block
258
+ out wrap("#{expand_with(:onclick=>"[\"#{node.dom_prefix}_add\", \"#{node.dom_prefix}_0\"].each(Element.toggle);#{focus}return false;")}")
259
+
260
+ klass = @context[:klass] || node.single_class
261
+
262
+ # New object to render form.
263
+ new_node = node.move_to("#{var}_new", klass, :new_keys => {})
264
+
265
+
266
+ if new_node.will_be?(Node)
267
+ # FIXME: BUG if we set <r:form klass='Post'/> the user cannot select class with menu...
268
+
269
+ if @context[:saved_template]
270
+ parent_id = "#{node}.parent_zip"
271
+ else
272
+ parent_id = "#{node(Node)}.zip"
273
+ end
274
+
275
+ new_node.opts[:new_keys]['parent_id'] = parent_id
276
+
277
+ # FIXME: inspect '@context[:form]' to see if it contains v_klass ?
278
+ out "<% if #{new_node} = secure(Node) { Node.new_node('class' => '#{new_node.klass}', 'parent_id' => #{parent_id}) } %>"
279
+ # if node.will_be?(Node)
280
+ # # Nested contexts:
281
+ # # 1. @node
282
+ # # 2. var1 = @node.children
283
+ # # 3. var1_new = Node.new
284
+ # if node.opts[:new_record] || @context[:saved_template]
285
+ # if @context[:saved_template] || !@context[:in_add]
286
+ # # TODO: we should not add parent_id on every saved_template. Why is this needed ?
287
+ # parent_id = "#{node}.parent_zip"
288
+ # else
289
+ # # We are in var2_new
290
+ # parent_id = "#{node.up(Node)}.zip"
291
+ # end
292
+ #
293
+ # hidden_fields['node[parent_id]'] = "<%= #{parent_id} %>"
294
+ # end
295
+ # els
296
+ else
297
+ out "<% if #{new_node} = #{new_node.class_name}.new %>"
298
+ end
299
+
300
+ form_block = @context[:form]
301
+
302
+ # Expand (inline) 'form' block
303
+ out expand_block(form_block,
304
+ # Needed in form to be able to return the result
305
+ :template_url => template_url(node.dom_prefix),
306
+ # Used to avoid wrong dom_id in hidden form. Should not be
307
+ # necessary but it's hard to fix when node changes a lot (drop in add).
308
+ :dom_prefix => node.dom_prefix,
309
+ # Used to add needed hidden fields in form
310
+ :in_add => true,
311
+ # Used to get parameters like 'publish' or 'klass'
312
+ :add => self,
313
+ # Transform 'each' block into a form
314
+ :make_form => form_block.method == 'each',
315
+ # Node context = new node
316
+ :node => new_node
317
+ )
318
+ out "<% end %>"
319
+ else
320
+ # no ajax
321
+ @markup.append_param(:class, 'btn_add') if @markup.tag
322
+ out wrap(expand_with)
323
+ end
324
+ out "<% end %>"
325
+ end
326
+
327
+ def r_add_btn
328
+ default = node.will_be?(Comment) ? _("btn_add_comment") : _("btn_add")
329
+
330
+ out "<a href='javascript:void(0)' onclick='#{@context[:onclick]}'>#{text_for_link(default)}</a>"
331
+ end
332
+
333
+ def r_each
334
+ if @context[:saved_template]
335
+ # render to start a saved template
336
+ node.saved_dom_id = "\#{ndom_id(#{node})}"
337
+ node.dom_prefix = dom_name unless node.raw_dom_prefix
338
+ node.propagate_dom_scope!
339
+ @markup.set_id(node.dom_id)
340
+
341
+ options = form_options
342
+ @markup.set_param(:style, options[:style]) if options[:style]
343
+
344
+ out wrap(expand_with)
345
+ else
346
+ super
347
+ end
348
+ end
349
+
350
+ # Block visibility of descendance with 'do_list'.
351
+ def public_descendants
352
+ all = super
353
+ if ['context', 'each', 'block'].include?(method)
354
+ # do not propagate 'form',etc up
355
+ all.reject do |k,v|
356
+ # FIXME: Zena leakage
357
+ ['form','unlink', 'count'].include?(k)
358
+ end
359
+ elsif ['if', 'case'].include?(method) || (method =~ /^[A-Z]/)
360
+ # conditional
361
+ all.reject do |k,v|
362
+ ['else', 'elsif', 'when'].include?(k)
363
+ end
364
+ else
365
+ all
366
+ end
367
+ end
368
+
369
+ # Return true if we need to insert the dom id for this element.
370
+ def need_dom_id?
371
+ @context[:form] || child['unlink'] || (single_child_method && single_child_method == child['drop'])
372
+ end
373
+
374
+ # Unique template_url, ending with dom_id
375
+ def template_url(dom_prefix = node.dom_prefix)
376
+ "#{root.options[:root]}/#{dom_prefix}"
377
+ end
378
+
379
+ def form_url(dom_prefix = node.dom_prefix)
380
+ template_url(dom_prefix) + '_form'
381
+ end
382
+
383
+ # Return object id (or name). Sets name when needed.
384
+ # Context is passed when the parent needs to set dom_name on a child
385
+ # before @context is passed down.
386
+ def dom_name(context = @context)
387
+ @dom_name ||= unique_name(context)
388
+ end
389
+
390
+ # Return a different name on each call
391
+ def unique_name(context = @context)
392
+ base = @name || context[:name] || 'list'
393
+ root.get_unique_name(base, base == @name).gsub(/[^\d\w\/]/,'_')
394
+ end
395
+
396
+ def get_unique_name(key, own_id = false)
397
+ @next_name_index ||= {}
398
+ if @next_name_index[key]
399
+ @next_name_index[key] += 1
400
+ key + @next_name_index[key].to_s
401
+ elsif own_id
402
+ @next_name_index[key] = 0
403
+ key
404
+ else
405
+ @next_name_index[key] = 1
406
+ key + '1'
407
+ end
408
+ end
409
+
410
+ protected
411
+
412
+ def need_ajax?(each_block)
413
+ return false unless each_block
414
+ # Inline editable
415
+ each_block.descendant('edit') ||
416
+ # Ajax add
417
+ descendant('add') ||
418
+ # List is reloaded from the 'add_document' popup
419
+ descendant('add_document') ||
420
+ # We use 'each' as block instead of the declared 'block' or 'drop'
421
+ ['block', 'drop'].include?(each_block.single_child_method)
422
+ end
423
+
424
+ def context_for_partial(cont)
425
+ context_without_vars.merge(cont)
426
+ end
427
+ private
428
+
429
+ # Find a block to update on the page
430
+ def find_target(name)
431
+ # Hack for drop until descendants is rebuilt on set_dom_prefix
432
+ return self if name == self.name
433
+
434
+ root.descendants('block').each do |block|
435
+ return block if block.name == name
436
+ end
437
+
438
+ out parser_error("could not find a block named '#{name}'")
439
+ return nil
440
+ end
441
+
442
+ def store_block(block, cont = {})
443
+ cont, prefix = context_for_partial(cont)
444
+
445
+ # Create new node context
446
+ node = cont[:node].as_main(ActiveRecord::Base)
447
+ node.opts[:new_keys] = cont.delete(:new_keys)
448
+
449
+ # The dom_id will be calculated from the Ajax params in the view.
450
+ node.saved_dom_id = "\#{ndom_id(#{node})}"
451
+
452
+ cont[:template_url] = template_url(node.dom_prefix)
453
+ cont[:node] = node
454
+ cont[:block] = block
455
+ cont[:saved_template] ||= cont[:template_url]
456
+
457
+ template = nil
458
+
459
+ # We overwrite all context: no merge.
460
+ with_context(cont, false) do
461
+ template = prefix.to_s + expand_block(block)
462
+ end
463
+
464
+ out helper.save_erb_to_url(template, cont[:saved_template])
465
+ end
466
+
467
+ #template = expand_block(self, :)
468
+ #
469
+ #if @context[:block] == self
470
+ # # called from self (storing template)
471
+ # @context.reject! do |k,v|
472
+ # # FIXME: reject all stored elements in a better way then this
473
+ # k.kind_of?(String) && k =~ /\ANode_\w/
474
+ # end
475
+ # @markup.done = false
476
+ # @markup.params.merge!(:id=>node.dom_id)
477
+ # @context[:scope_node] = node if @context[:scope_node]
478
+ # out expand_with(:node => node)
479
+ # if @method == 'drop' && !@context[:make_form]
480
+ # out drop_javascript
481
+ # end
482
+ #else
483
+ # if parent.method == 'each' && @method == parent.single_child_method
484
+ # # use parent as block
485
+ # # FIXME: will not work with block as distant target...
486
+ # # do nothing
487
+ # else
488
+ # @markup.tag ||= 'div'
489
+ # new_dom_scope
490
+ #
491
+ # unless @context[:make_form]
492
+ # # STORE TEMPLATE ========
493
+ #
494
+ # context_bak = @context.dup # avoid side effects when rendering the same block
495
+ # ignore_list = @method == 'block' ? ['form'] : [] # do not show the form in the normal template of a block
496
+ # template = expand_block(self, :block=>self, :list=>false, :saved_template=>true, :ignore => ignore_list)
497
+ # @context = context_bak
498
+ # @result = ''
499
+ # out helper.save_erb_to_url(template, template_url)
500
+ #
501
+ # # STORE FORM ============
502
+ # if edit = descendant('edit')
503
+ # publish_after_save = (edit.params[:publish] == 'true')
504
+ # if form = descendant('form')
505
+ # # USE BLOCK FORM ========
506
+ # form_text = expand_block(form, :saved_template=>true, :publish_after_save => publish_after_save)
507
+ # else
508
+ # # MAKE A FORM FROM BLOCK ========
509
+ # form = self.dup
510
+ # form.method = 'form'
511
+ # form_text = expand_block(form, :make_form => true, :list => false, :saved_template => true, :publish_after_save => publish_after_save)
512
+ # end
513
+ # out helper.save_erb_to_url(form_text, form_url)
514
+ # end
515
+ # end
516
+ #
517
+ # # RENDER
518
+ # @markup.done = false
519
+ # @markup.params.merge!(:id=>node.dom_id)
520
+ # end
521
+ #
522
+ # out expand_with
523
+ # if @method == 'drop' && !@context[:make_form]
524
+ # out drop_javascript
525
+ # end
526
+ #end
527
+
528
+ end
529
+ end
530
+ end