transit 0.0.1 → 0.0.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.
- data/Gemfile +19 -11
- data/app/assets/images/transit/icon24x24.png +0 -0
- data/app/assets/images/transit/icon24x24_files.png +0 -0
- data/app/assets/images/transit/jplayer.swf +0 -0
- data/app/assets/images/transit/uploadify.swf +0 -0
- data/app/assets/images/transit/video_player.swf +0 -0
- data/app/assets/javascripts/jqtools/expose.js +224 -0
- data/app/assets/javascripts/jqtools/flashembed.js +300 -0
- data/app/assets/javascripts/jqtools/overlay.js +294 -0
- data/app/assets/javascripts/jqtools/scrollable.js +3 -0
- data/app/assets/javascripts/jqtools/scrollable/autoscroll.js +81 -0
- data/app/assets/javascripts/jqtools/scrollable/base.js +335 -0
- data/app/assets/javascripts/jqtools/scrollable/navigator.js +139 -0
- data/app/assets/javascripts/jqtools/validator.js +590 -0
- data/app/assets/javascripts/jqueryui/accordion.js +611 -0
- data/app/assets/javascripts/jqueryui/autocomplete.js +612 -0
- data/app/assets/javascripts/jqueryui/button.js +388 -0
- data/app/assets/javascripts/jqueryui/core.js +312 -0
- data/app/assets/javascripts/jqueryui/datepicker.js +1791 -0
- data/app/assets/javascripts/jqueryui/dialog.js +878 -0
- data/app/assets/javascripts/jqueryui/draggable.js +815 -0
- data/app/assets/javascripts/jqueryui/droppable.js +285 -0
- data/app/assets/javascripts/jqueryui/effects/blind.js +49 -0
- data/app/assets/javascripts/jqueryui/effects/bounce.js +78 -0
- data/app/assets/javascripts/jqueryui/effects/clip.js +54 -0
- data/app/assets/javascripts/jqueryui/effects/core.js +746 -0
- data/app/assets/javascripts/jqueryui/effects/drop.js +50 -0
- data/app/assets/javascripts/jqueryui/effects/explode.js +79 -0
- data/app/assets/javascripts/jqueryui/effects/fade.js +32 -0
- data/app/assets/javascripts/jqueryui/effects/fold.js +56 -0
- data/app/assets/javascripts/jqueryui/effects/highlight.js +50 -0
- data/app/assets/javascripts/jqueryui/effects/pulsate.js +51 -0
- data/app/assets/javascripts/jqueryui/effects/scale.js +178 -0
- data/app/assets/javascripts/jqueryui/effects/shake.js +57 -0
- data/app/assets/javascripts/jqueryui/effects/slide.js +50 -0
- data/app/assets/javascripts/jqueryui/effects/transfer.js +45 -0
- data/app/assets/javascripts/jqueryui/mouse.js +160 -0
- data/app/assets/javascripts/jqueryui/position.js +252 -0
- data/app/assets/javascripts/jqueryui/progressbar.js +109 -0
- data/app/assets/javascripts/jqueryui/resizable.js +814 -0
- data/app/assets/javascripts/jqueryui/selectable.js +266 -0
- data/app/assets/javascripts/jqueryui/slider.js +666 -0
- data/app/assets/javascripts/jqueryui/sortable.js +1077 -0
- data/app/assets/javascripts/jqueryui/tabs.js +758 -0
- data/app/assets/javascripts/jqueryui/widget.js +262 -0
- data/app/assets/javascripts/libs/backbone.js +1152 -0
- data/app/assets/javascripts/libs/cookie.js +89 -0
- data/app/assets/javascripts/libs/fileinput.js +130 -0
- data/app/assets/javascripts/libs/jplayer.js +1768 -0
- data/app/assets/javascripts/libs/proper.js +541 -0
- data/app/assets/javascripts/libs/sanitize.js +282 -0
- data/app/assets/javascripts/libs/selecttolist.js +75 -0
- data/app/assets/javascripts/libs/underscore.js +807 -0
- data/app/assets/javascripts/libs/uploadify.js +677 -0
- data/app/assets/javascripts/libs/wymeditor.js +9538 -0
- data/app/assets/javascripts/transit.js +4 -0
- data/app/assets/javascripts/transit/admin.js +22 -0
- data/app/assets/javascripts/transit/admin/contexts.js +52 -0
- data/app/assets/javascripts/transit/admin/fields.js +36 -0
- data/app/assets/javascripts/transit/admin/upload.js +109 -0
- data/app/assets/javascripts/transit/config.js.erb +101 -0
- data/app/assets/javascripts/transit/contexts/audio.js +39 -0
- data/app/assets/javascripts/transit/contexts/video.js +79 -0
- data/app/assets/javascripts/transit/core.js +171 -0
- data/app/assets/javascripts/transit/frontend.js +3 -0
- data/app/assets/javascripts/transit/lib/base64.js +120 -0
- data/app/assets/javascripts/transit/lib/editor.js +177 -0
- data/app/assets/javascripts/transit/views/audio_player.jst +22 -0
- data/app/assets/javascripts/transit/views/editor_toolbar.jst +12 -0
- data/app/assets/javascripts/transit/views/file_upload.jst +5 -0
- data/app/assets/javascripts/transit/views/video_player.jst +20 -0
- data/app/assets/javascripts/transit/views/wym_box.jst +4 -0
- data/app/assets/javascripts/transit/views/wym_iframe.jst +3 -0
- data/app/assets/stylesheets/transit.css.scss.erb +42 -0
- data/app/assets/stylesheets/transit/forms.css.scss +66 -0
- data/app/assets/stylesheets/transit/media/audio.css.scss +65 -0
- data/app/assets/stylesheets/transit/media/video.css.scss +30 -0
- data/app/assets/stylesheets/transit/panel.css.scss +100 -0
- data/app/assets/stylesheets/transit/ui.css.scss +507 -0
- data/app/controllers/pages_controller.rb +3 -0
- data/app/controllers/posts_controller.rb +3 -0
- data/app/controllers/transit/assets_controller.rb +38 -0
- data/app/controllers/transit/contexts_controller.rb +12 -9
- data/app/controllers/transit/pages_controller.rb +26 -0
- data/app/controllers/transit/posts_controller.rb +31 -0
- data/app/controllers/transit/topics_controller.rb +5 -0
- data/app/controllers/transit_controller.rb +16 -0
- data/app/helpers/transit/admin_helper.rb +43 -0
- data/app/helpers/transit/form_helper.rb +17 -0
- data/app/helpers/transit/package_helper.rb +41 -0
- data/app/helpers/transit/pagination_helper.rb +58 -0
- data/app/helpers/transit_helper.rb +42 -0
- data/app/models/comment.rb +37 -0
- data/app/models/contexts/audio.rb +12 -0
- data/app/models/{text.rb → contexts/text.rb} +0 -0
- data/app/models/contexts/video.rb +24 -0
- data/app/models/topic.rb +19 -0
- data/app/models/transit/asset.rb +68 -0
- data/{lib → app/models}/transit/context.rb +29 -0
- data/app/views/contexts/_text.html.erb +1 -1
- data/app/views/posts/index.rss.builder +18 -0
- data/app/views/transit/assets/_file.html.erb +4 -0
- data/app/views/transit/assets/_image.html.erb +13 -0
- data/app/views/transit/assets/create.js.erb +8 -0
- data/app/views/transit/assets/destroy.js.erb +3 -0
- data/app/views/transit/assets/manage.html.erb +20 -0
- data/app/views/transit/contexts/_audio.html.erb +18 -0
- data/app/views/transit/contexts/_text.html.erb +6 -0
- data/app/views/transit/contexts/_video.html.erb +13 -0
- data/app/views/transit/contexts/destroy.js.erb +1 -0
- data/app/views/transit/contexts/index.html.erb +5 -0
- data/app/views/transit/contexts/new.js.erb +7 -0
- data/app/views/transit/contexts/show.html.erb +8 -0
- data/app/views/transit/index.html.erb +26 -0
- data/app/views/transit/index.js.erb +1 -0
- data/app/views/transit/interface/post_panel.html.erb +96 -0
- data/app/views/transit/pages/_table.html.erb +7 -0
- data/app/views/transit/pages/edit.html.erb +17 -0
- data/app/views/transit/pages/index.html.erb +17 -0
- data/app/views/transit/pages/update.js.erb +1 -0
- data/app/views/transit/posts/_form.html.erb +49 -0
- data/app/views/transit/posts/edit.html.erb +14 -0
- data/app/views/transit/posts/new.html.erb +21 -0
- data/app/views/transit/table.html.erb +13 -0
- data/app/views/transit/table.js.erb +8 -0
- data/app/views/transit/topics/manage.html.erb +28 -0
- data/config/locales/en.yml +22 -0
- data/config/routes.rb +3 -3
- data/lib/transit.rb +51 -17
- data/lib/transit/admin.rb +85 -0
- data/lib/transit/builders/form_builder.rb +319 -0
- data/lib/transit/builders/jst_builder.rb +38 -0
- data/lib/transit/builders/package_builder.rb +45 -0
- data/lib/transit/config.rb +20 -0
- data/lib/transit/controller/generator.rb +42 -0
- data/lib/transit/controller/responder.rb +34 -0
- data/lib/transit/core_ext.rb +18 -0
- data/lib/transit/errors/resource_not_found.rb +6 -0
- data/lib/transit/model/assets.rb +14 -0
- data/lib/transit/model/attachments.rb +55 -0
- data/lib/transit/model/auto_increment.rb +22 -0
- data/lib/transit/model/base.rb +56 -0
- data/lib/transit/model/comments.rb +19 -0
- data/lib/transit/model/hooks.rb +38 -0
- data/lib/transit/model/owners.rb +14 -0
- data/lib/transit/model/paginator.rb +92 -0
- data/lib/transit/model/topics.rb +14 -0
- data/lib/transit/package/page.rb +20 -12
- data/lib/transit/package/post.rb +87 -33
- data/lib/transit/package/post/validations.rb +14 -0
- data/lib/transit/rails/engine.rb +29 -13
- data/lib/transit/rails/railtie.rb +31 -0
- data/lib/transit/rails/routing.rb +11 -6
- data/lib/transit/services.rb +13 -0
- data/lib/transit/services/base.rb +14 -0
- data/lib/transit/services/facebook.rb +13 -0
- data/lib/transit/services/ted.rb +10 -0
- data/lib/transit/services/twitter.rb +13 -0
- data/lib/transit/services/vimeo.rb +10 -0
- data/lib/transit/services/you_tube.rb +12 -0
- data/lib/transit/version.rb +1 -1
- metadata +178 -31
- data/app/assets/stylesheets/includes/_compat.scss +0 -24
- data/app/assets/stylesheets/includes/_defaults.scss +0 -99
- data/app/assets/stylesheets/includes/_global.scss +0 -16
- data/app/assets/stylesheets/includes/_imports.scss +0 -27
- data/app/assets/stylesheets/includes/_mixins.scss +0 -38
- data/app/assets/stylesheets/includes/_setup.scss +0 -85
- data/app/assets/stylesheets/layout.css.scss +0 -29
- data/app/assets/stylesheets/transit.css +0 -3
- data/app/controllers/application_controller.rb +0 -5
- data/app/controllers/transit/index_controller.rb +0 -7
- data/app/controllers/transit/packages_controller.rb +0 -64
- data/app/controllers/transit/transit_controller.rb +0 -4
- data/app/helpers/routing_helpers.rb +0 -7
- data/app/models/audio.rb +0 -4
- data/app/models/package_asset.rb +0 -11
- data/app/models/video.rb +0 -8
- data/app/views/contexts/_audio.html.erb +0 -1
- data/app/views/contexts/_video.html.erb +0 -1
- data/app/views/layouts/transit.html.erb +0 -31
- data/app/views/transit/index/index.html.erb +0 -0
- data/lib/transit/helpers/controller_helpers.rb +0 -40
- data/lib/transit/helpers/model_helpers.rb +0 -26
- data/lib/transit/package.rb +0 -25
- data/lib/transit/package/base.rb +0 -49
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%= page_id('transit_index') %>
|
|
2
|
+
<%= page_title("Transit: Manage Pages") %>
|
|
3
|
+
<h1>Manage Pages</h1>
|
|
4
|
+
<%= paginate(@pages, :remote => true) %>
|
|
5
|
+
<table border="0" id="listing">
|
|
6
|
+
<thead>
|
|
7
|
+
<tr>
|
|
8
|
+
<th>Name</th>
|
|
9
|
+
<th>URL</th>
|
|
10
|
+
<th>Edit</th>
|
|
11
|
+
</tr>
|
|
12
|
+
</thead>
|
|
13
|
+
<tbody>
|
|
14
|
+
<%= render :partial => 'table' %>
|
|
15
|
+
</tbody>
|
|
16
|
+
</table>
|
|
17
|
+
<%= paginate(@pages, :remote => true) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
success
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<%= form_for resource, as: :post, validate: true do |f| %>
|
|
2
|
+
<%= transit_toolbar(resource) do %>
|
|
3
|
+
<ol>
|
|
4
|
+
<% unless resource.new_record %>
|
|
5
|
+
<li class="menu">
|
|
6
|
+
<span class='ui-icon ui-icon-circle-plus'></span>
|
|
7
|
+
Add a New Item
|
|
8
|
+
<ol class="ui-state-hover">
|
|
9
|
+
<% Transit.contexts.map(&:to_s).each do |type| %>
|
|
10
|
+
<li class="icon_<%= type.underscore %>">
|
|
11
|
+
<%= link_to type, new_polymorphic_path([resource, Transit::Context], type: type, form_name: 'post'), :remote => true %>
|
|
12
|
+
</li>
|
|
13
|
+
<% end %>
|
|
14
|
+
</ol>
|
|
15
|
+
</li>
|
|
16
|
+
<% end %>
|
|
17
|
+
<li class="multifield post_date menu" id="datepicker_target">
|
|
18
|
+
<span class='ui-icon ui-icon-calendar'></span>
|
|
19
|
+
Post Date
|
|
20
|
+
<span id="post_date_selects"><%= f.date_select :post_date, { order: [:month, :day, :year], use_short_month: true } %></span>
|
|
21
|
+
</li>
|
|
22
|
+
<% unless resource.new_record? %>
|
|
23
|
+
<li>
|
|
24
|
+
<%= f.check_box :published, class: 'toggle_button', data: { 'ui-icon' => 'ui-icon-star' } %>
|
|
25
|
+
<%= f.label :published %>
|
|
26
|
+
</li>
|
|
27
|
+
<li>
|
|
28
|
+
<%= toolbar_button('Delete This Post', polymorphic_path(resource), method: :delete, class: 'ui-icon-red', icon: 'closethick') %>
|
|
29
|
+
</li>
|
|
30
|
+
<% end %>
|
|
31
|
+
</ol>
|
|
32
|
+
<%= f.hidden_field(:user, value: current_user.id) if resource.delivers?(:owner) %>
|
|
33
|
+
<% end %>
|
|
34
|
+
|
|
35
|
+
<div id="post">
|
|
36
|
+
<%= render partial: 'transit/posts/fields/title', locals: { form: f } %>
|
|
37
|
+
<%= render template: 'transit/contexts/index', locals: { form: f } %>
|
|
38
|
+
</div>
|
|
39
|
+
<div id='edit_post_sidebar'>
|
|
40
|
+
<% if resource.delivers?(:topics) %>
|
|
41
|
+
<%= render(partial: 'transit/topics/model', locals: { form: f }) %>
|
|
42
|
+
<% end %>
|
|
43
|
+
<% if resource.delivers?(:assets) %>
|
|
44
|
+
<%= render(partial: 'transit/assets/model', locals: { form: f }) %>
|
|
45
|
+
<% end %>
|
|
46
|
+
|
|
47
|
+
<%= f.button %>
|
|
48
|
+
<% end %>
|
|
49
|
+
<%= text_field_tag :datepicker_holder, resource.post_date.strftime('%Y-%m-%d'), rel: 'post_post_date', data: { 'calendar-link' => "#post_date_menu" } %>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<%= page_id("#{resource_collection_name}_show") %>
|
|
2
|
+
<%= page_title("Edit Post #{resource.title}") %>
|
|
3
|
+
<h2 class='main_heading'>Edit Post</h2>
|
|
4
|
+
<%= form_for(resource, as: :post, html: { remote: true }) do |f| %>
|
|
5
|
+
<div id='edit_post_form'>
|
|
6
|
+
<div class="post">
|
|
7
|
+
<h1 class='transit-editable' rel="post_title"><%= resource.title %></h1>
|
|
8
|
+
<p class='timestamp'>Posted <%= resource.timestamp %></p>
|
|
9
|
+
<%= render(template: 'transit/contexts/index', locals: { form: f }) %>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
<%= render(template: 'transit/interface/post_panel', locals: { form: f }) %>
|
|
13
|
+
<%= f.hidden_field :title %>
|
|
14
|
+
<% end %>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<%= page_id("#{resource_collection_name}_show") %>
|
|
2
|
+
<%= page_title("Create a New Post") %>
|
|
3
|
+
<h2 class='main_heading'>New Post</h2>
|
|
4
|
+
<%= form_for(resource, as: :post, html: { remote: true }) do |f| %>
|
|
5
|
+
<div id='edit_post_form'>
|
|
6
|
+
<div class="post">
|
|
7
|
+
<h1 class='transit-editable' rel="post_title">Add your title here</h1>
|
|
8
|
+
<%= f.label :teaser, 'Teaser (optional)', style: 'padding-top:1em; display:block;' %>
|
|
9
|
+
<%= f.text_area :teaser, id: 'new_post_teaser' %>
|
|
10
|
+
<p id='new_post_help_note'>
|
|
11
|
+
Once you click Create Post! below you will be able to edit things like files/images and the overall content
|
|
12
|
+
that will be displayed in your post.
|
|
13
|
+
</p>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<%= render(template: 'transit/interface/post_panel', locals: { form: f }) %>
|
|
17
|
+
<%= f.hidden_field :title %>
|
|
18
|
+
<p style="clear:both">
|
|
19
|
+
<%= f.button 'Create Post!', class: 'green' %>
|
|
20
|
+
</p>
|
|
21
|
+
<% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<% collection.each do |item| %>
|
|
2
|
+
<tr<%= cycle(""," class='even'") %>>
|
|
3
|
+
<% resource_class.admin_options.column_hash.each do |name, value| %>
|
|
4
|
+
<% if value.is_a?(Array) %>
|
|
5
|
+
<td><%= value.last.call(item) %></td>
|
|
6
|
+
<% else %>
|
|
7
|
+
<td><%= item.send(:"#{name.to_s}") %></td>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% end %>
|
|
10
|
+
<td><%= link_to 'Edit', transit.edit_polymorphic_path(item) %></td>
|
|
11
|
+
<td><%= link_to 'Delete', transit.polymorphic_path(item), method: :delete, confirm: 'Are you sure?' %></td>
|
|
12
|
+
</tr>
|
|
13
|
+
<% end %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
$("#listing tbody").slideUp('fast',
|
|
2
|
+
function(){
|
|
3
|
+
$(this)
|
|
4
|
+
.empty()
|
|
5
|
+
.html("<%= escape_javascript(render(template: "transit/table.html.erb")) %>")
|
|
6
|
+
.slideDown('fast');
|
|
7
|
+
$('p.pagination').replaceWith("<%= escape_javascript(paginate(collection, :remote => true)) %>");
|
|
8
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<div id="post_topics">
|
|
2
|
+
<h4>Topics</h4>
|
|
3
|
+
<ol>
|
|
4
|
+
<li class='select_list'>
|
|
5
|
+
<%= select_tag :topics, options_for_select(Topic.for_editing(resource_class, resource).collect{ |t| [t.title, t.id.to_s] }), id: 'all_topics_list' %>
|
|
6
|
+
<%= link_to 'Add', '#', class: 'add_to_list icon_add ui-button', id:'add_topic_link', data: { ui_icon: 'plus' } %>
|
|
7
|
+
</li>
|
|
8
|
+
<li id="topic_selections" rel="<%= "post[topics][]" %>">
|
|
9
|
+
<ul>
|
|
10
|
+
<% form.object.topics.each do |topic| %>
|
|
11
|
+
<li rel="<%= topic.id.to_s %>">
|
|
12
|
+
<%= topic.title %>
|
|
13
|
+
<%= hidden_field_tag "post[topics][]", topic.id.to_s %>
|
|
14
|
+
<%= link_to 'Remove', '#', class: 'icon_delete remove_from_list' %>
|
|
15
|
+
</li>
|
|
16
|
+
<% end %>
|
|
17
|
+
</ul>
|
|
18
|
+
</li>
|
|
19
|
+
</ol>
|
|
20
|
+
<script type="text/javascript" charset="utf-8">
|
|
21
|
+
//<![CDATA[
|
|
22
|
+
jQuery('#all_topics_list').selectToList({
|
|
23
|
+
target:'#topic_selections ul',
|
|
24
|
+
name: jQuery('#topic_selections').attr('rel'),
|
|
25
|
+
add: jQuery('#add_topic_link') });
|
|
26
|
+
//]]>
|
|
27
|
+
</script>
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
en:
|
|
2
|
+
flash:
|
|
3
|
+
actions:
|
|
4
|
+
create:
|
|
5
|
+
success: '%{resource_name} was successfully created.'
|
|
6
|
+
error: 'Looks like there was a problem with your submission!'
|
|
7
|
+
update:
|
|
8
|
+
success: '%{resource_name} was successfully updated.'
|
|
9
|
+
error: 'Looks like there was a problem with your submission!'
|
|
10
|
+
destroy:
|
|
11
|
+
success: '%{resource_name} was successfully destroyed.'
|
|
12
|
+
error: '%{resource_name} could not be destroyed.'
|
|
13
|
+
transit:
|
|
14
|
+
post_collection: 'Posts'
|
|
15
|
+
blog_collection: 'Blog Posts'
|
|
16
|
+
article_collection: 'Articles'
|
|
17
|
+
post: 'Post'
|
|
18
|
+
blog: 'Blog Post'
|
|
19
|
+
article: 'Article'
|
|
20
|
+
|
|
21
|
+
legends:
|
|
22
|
+
topics: "Topics"
|
data/config/routes.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Transit::Engine.routes.draw do
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
resources :posts
|
|
3
|
+
resources :pages
|
|
4
|
+
resources :package_assets, :controller => 'assets'
|
|
5
5
|
end
|
data/lib/transit.rb
CHANGED
|
@@ -1,30 +1,60 @@
|
|
|
1
|
-
require 'rails'
|
|
2
1
|
require 'active_support'
|
|
3
|
-
require '
|
|
2
|
+
require 'transit/core_ext'
|
|
4
3
|
|
|
5
4
|
module Transit
|
|
6
5
|
|
|
7
|
-
autoload :
|
|
8
|
-
autoload :
|
|
6
|
+
autoload :Admin, 'transit/admin'
|
|
7
|
+
autoload :Config, 'transit/config'
|
|
9
8
|
|
|
10
|
-
module
|
|
11
|
-
autoload :
|
|
12
|
-
autoload :
|
|
9
|
+
module Package
|
|
10
|
+
autoload :Post, 'transit/package/post'
|
|
11
|
+
autoload :Page, 'transit/package/page'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module Model
|
|
15
|
+
autoload :Assets, 'transit/model/assets'
|
|
16
|
+
autoload :Attachments, 'transit/model/attachments'
|
|
17
|
+
autoload :AutoIncrement, 'transit/model/auto_increment'
|
|
18
|
+
autoload :Comments, 'transit/model/comments'
|
|
19
|
+
autoload :Topics, 'transit/model/topics'
|
|
20
|
+
autoload :Base, 'transit/model/base'
|
|
21
|
+
autoload :Hooks, 'transit/model/hooks'
|
|
22
|
+
autoload :Owners, 'transit/model/owners'
|
|
23
|
+
autoload :Paginator, 'transit/model/paginator'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module Controller
|
|
27
|
+
autoload :Generator, 'transit/controller/generator'
|
|
28
|
+
autoload :Responder, 'transit/controller/responder'
|
|
13
29
|
end
|
|
14
30
|
|
|
15
31
|
module Errors
|
|
16
|
-
autoload :InvalidContext,
|
|
32
|
+
autoload :InvalidContext, 'transit/errors/invalid_context'
|
|
33
|
+
autoload :ResourceNotFound, 'transit/errors/resource_not_found'
|
|
17
34
|
end
|
|
18
35
|
|
|
36
|
+
module Builders
|
|
37
|
+
autoload :JstBuilder, 'transit/builders/jst_builder'
|
|
38
|
+
autoload :FormBuilder, 'transit/builders/form_builder'
|
|
39
|
+
autoload :PackageBuilder, 'transit/builders/package_builder'
|
|
40
|
+
end
|
|
41
|
+
|
|
19
42
|
DESCRIPTIONS = {}
|
|
20
|
-
CONTROLLERS = []
|
|
21
43
|
|
|
22
|
-
|
|
23
|
-
|
|
44
|
+
# Store an array of controller mappings
|
|
45
|
+
mattr_accessor :mappings
|
|
46
|
+
@@mappings = []
|
|
47
|
+
|
|
48
|
+
def self.add_mapping(obj)
|
|
49
|
+
@@mappings << obj
|
|
24
50
|
end
|
|
25
|
-
|
|
51
|
+
|
|
52
|
+
def self.configure(&block)
|
|
53
|
+
yield Transit::Config
|
|
54
|
+
end
|
|
55
|
+
|
|
26
56
|
def self.contexts
|
|
27
|
-
|
|
57
|
+
["Text","Video", "Audio"]
|
|
28
58
|
end
|
|
29
59
|
|
|
30
60
|
def self.track(klass, template)
|
|
@@ -36,8 +66,12 @@ module Transit
|
|
|
36
66
|
DESCRIPTIONS[template] ||= []
|
|
37
67
|
end
|
|
38
68
|
|
|
69
|
+
def self.superclass_for(template)
|
|
70
|
+
DESCRIPTIONS[template].detect do |klass|
|
|
71
|
+
klass.constantize.superclass === Object
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
39
75
|
end
|
|
40
|
-
|
|
41
|
-
require 'transit/
|
|
42
|
-
require 'transit/rails/engine'
|
|
43
|
-
require 'transit/rails/routing'
|
|
76
|
+
require 'transit/model/hooks'
|
|
77
|
+
require 'transit/rails/engine'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Transit
|
|
2
|
+
##
|
|
3
|
+
# Manages configuration options for models.
|
|
4
|
+
# When a model is managed via the admin, register it by Transit::Admin.register and
|
|
5
|
+
# passing a configuration block.
|
|
6
|
+
#
|
|
7
|
+
# @example Assign the columns to be displayed on "index"
|
|
8
|
+
# Transit::Admin.register :model_name do |config|
|
|
9
|
+
# config.columns = [:name, :title]
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
module Admin
|
|
13
|
+
|
|
14
|
+
def self.register(klass, &block)
|
|
15
|
+
klass = klass.to_s.classify.constantize
|
|
16
|
+
unless klass.respond_to?(:admin_options)
|
|
17
|
+
klass.send(:class_attribute, :admin_options, instance_writer: false)
|
|
18
|
+
end
|
|
19
|
+
dsl = klass.admin_options ||= DSL.new
|
|
20
|
+
block.call(dsl)
|
|
21
|
+
klass.admin_options = dsl
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class DSL
|
|
25
|
+
|
|
26
|
+
attr_accessor :columns, :column_hash, :fields
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Specify the columns to be displayed in the admin. At minimum the name of a
|
|
30
|
+
# method is required. For additional options, use hash syntax.
|
|
31
|
+
#
|
|
32
|
+
# @example List a title and post_date column
|
|
33
|
+
# config.columns :title, :post_date
|
|
34
|
+
#
|
|
35
|
+
# @example List a title field, and a post_date field, where post_date uses a proc to describe its value. When called
|
|
36
|
+
# the proc will be passed a reference to the current object.
|
|
37
|
+
#
|
|
38
|
+
# config.columns :title, post_date: lambda{ |obj| obj.created_at.to_formatted_s }
|
|
39
|
+
#
|
|
40
|
+
# @example Overriding the displayed name
|
|
41
|
+
#
|
|
42
|
+
# config.columns :title, timestamp: { as: 'Post Date' }
|
|
43
|
+
#
|
|
44
|
+
def columns(*cols)
|
|
45
|
+
options = cols.extract_options!
|
|
46
|
+
@column_hash = ActiveSupport::OrderedHash.new
|
|
47
|
+
cols.each.each do |col|
|
|
48
|
+
@column_hash[col] = col.to_s.titleize
|
|
49
|
+
end
|
|
50
|
+
options.each do |name, value|
|
|
51
|
+
@column_hash[name] = case value
|
|
52
|
+
when Proc then [name.to_s.titleize, value]
|
|
53
|
+
when Hash
|
|
54
|
+
colname = (value[:as].present? ? value[:as].to_s.titleize : name.to_s.titleize)
|
|
55
|
+
(value[:proc].present? ? [colname, value[:proc]] : colname)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# Specify the form fields used for each attribute. When inheriting posts or pages
|
|
62
|
+
# defaults are already pre-defined and normally only need to be overridden.
|
|
63
|
+
#
|
|
64
|
+
# @example Specify a text_area for the title and teaser of a post
|
|
65
|
+
# config.fields title: :text_area, teaser: :text_area
|
|
66
|
+
#
|
|
67
|
+
def fields(field_hash = {})
|
|
68
|
+
@fields.merge!(field_hash) unless field_hash.empty?
|
|
69
|
+
@fields
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def initialize
|
|
73
|
+
@fields = ActiveSupport::HashWithIndifferentAccess.new({
|
|
74
|
+
title: :text_field,
|
|
75
|
+
teaser: :text_area,
|
|
76
|
+
post_date: { date_select: { order: [:month, :day, :year], use_short_month: true } },
|
|
77
|
+
image: :file_field,
|
|
78
|
+
photo: :file_field
|
|
79
|
+
})
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
module Transit
|
|
2
|
+
module Builders
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
# Base class for creating form builders
|
|
6
|
+
#
|
|
7
|
+
class FormBuilder < ActionView::Helpers::FormBuilder #:nodoc:
|
|
8
|
+
|
|
9
|
+
# Access the template object
|
|
10
|
+
attr_accessor :template
|
|
11
|
+
# Tracks the order in which fields are used in the form, this allows the easy rebuilding of the submitted form
|
|
12
|
+
# data when submitted since normally the hash isn't ordered.
|
|
13
|
+
attr_accessor :field_order
|
|
14
|
+
# Tracks the field currently being "processed"
|
|
15
|
+
attr_accessor :current_field_type
|
|
16
|
+
|
|
17
|
+
# create overrides for custom rendering
|
|
18
|
+
[:email_field, :password_field, :text_field, :text_area, :url_field].each do |method|
|
|
19
|
+
class_eval <<-FUNC, __FILE__, __LINE__ + 1
|
|
20
|
+
|
|
21
|
+
alias :_super_#{method} :#{method}
|
|
22
|
+
|
|
23
|
+
def #{method}(method_name, *args)
|
|
24
|
+
render_field_as_custom(:#{method}, method_name, *args)
|
|
25
|
+
end
|
|
26
|
+
FUNC
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Modified label tag to support adding a 'required' asterisk to the end of the label.
|
|
31
|
+
# Same params as the original implementation
|
|
32
|
+
#
|
|
33
|
+
def label(method, text = nil, options = {}, &block) #:nodoc:
|
|
34
|
+
|
|
35
|
+
options, text = text, nil if text.is_a?(Hash)
|
|
36
|
+
text ||= method.to_s.humanize
|
|
37
|
+
|
|
38
|
+
options.stringify_keys!
|
|
39
|
+
klasses = (options.delete(['class']) || "").split(" ")
|
|
40
|
+
klasses << 'field_with_errors' if errors_on_attribute?(method)
|
|
41
|
+
options['class'] = klasses.join(" ") unless klasses.compact.empty?
|
|
42
|
+
|
|
43
|
+
text = "#{text} <abbr title='Required'>*</abbr>".html_safe if attribute_required?(method) || required_by_option?(options.delete('required'))
|
|
44
|
+
super(method, text, options, &block)
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
#
|
|
50
|
+
# Creates a button tag to be used in a form instead of the default input[type=submit]
|
|
51
|
+
# to help make CSS styling easier
|
|
52
|
+
#
|
|
53
|
+
# @param [String] value The text for the button
|
|
54
|
+
# @param [Hash] options HTML options to be passed to the button
|
|
55
|
+
# @option [String] icon If included, adds an image to the button to be used as an icon
|
|
56
|
+
#
|
|
57
|
+
def button(value = nil, options = {})
|
|
58
|
+
|
|
59
|
+
value, options = nil, value if value.is_a?(Hash)
|
|
60
|
+
value ||= submit_default_value
|
|
61
|
+
|
|
62
|
+
value = [image_tag(icon, :class => 'icon'), value].join(' ') if icon = options.delete(:icon)
|
|
63
|
+
klasses = (options.delete(:class) || "").split(" ")
|
|
64
|
+
klasses << "button"
|
|
65
|
+
options['class'] = klasses.join(" ")
|
|
66
|
+
content_tag(:button, value.to_s.html_safe, options.reverse_merge!({ "type" => "submit", "name" => "commit" }))
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Overrides fields_for to make sure the form builder is set properly
|
|
71
|
+
#
|
|
72
|
+
def fields_for(record_or_name_or_array, *args, &block) #:nodoc:
|
|
73
|
+
opts = args.extract_options!
|
|
74
|
+
opts.merge!(:builder => Transit::Builders::FormBuilder)
|
|
75
|
+
args.push(opts)
|
|
76
|
+
super(record_or_name_or_array, *args, &block)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
#
|
|
82
|
+
# Generate a select tag with the 50 US states as options
|
|
83
|
+
#
|
|
84
|
+
# @param [Symbol] method The object method/attribute to be used
|
|
85
|
+
# @param [Hash] options Same as Rails' select options hash
|
|
86
|
+
# @option options [Symbol] :international Include an international option
|
|
87
|
+
# @option options [Symbol] :abbreviate Use an abbreviated version of the state name for the value
|
|
88
|
+
# @param [Hash] html_options Same as Rails' html_options hash
|
|
89
|
+
#
|
|
90
|
+
# @return [String] HTML select tag
|
|
91
|
+
#
|
|
92
|
+
def state_select(method, options = {}, html_options = {})
|
|
93
|
+
abbr = options.delete(:abbreviate)
|
|
94
|
+
abbr = !(abbr.nil? || abbr === false)
|
|
95
|
+
select(method, @template.options_for_select(options_for_state_select(abbr, options.delete(:international)), @object.try(:state)), options, html_options)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
protected
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
#
|
|
102
|
+
# Returns a list of US states as an array
|
|
103
|
+
#
|
|
104
|
+
# @param [Boolean] abbreviate Abbreviate the value
|
|
105
|
+
# @param [Boolean, String] incl_international Include an additional state for "International"
|
|
106
|
+
#
|
|
107
|
+
# @return [Array] An array of states
|
|
108
|
+
#
|
|
109
|
+
def options_for_state_select(abbreviate = false, incl_international = false)
|
|
110
|
+
incl_international ||= false
|
|
111
|
+
state_list = [
|
|
112
|
+
['Alabama', "AL"],['Alaska', "AK"],['Arizona', "AZ"],['Arkansas', "AR"],['California', "CA"],['Colorado', "CO"],
|
|
113
|
+
['Connecticut', "CT"],['District of Columbia', "DC"],['Delaware', "DE"],['Florida', "FL"],['Georgia', "GA"],
|
|
114
|
+
['Hawaii', "HI"],['Idaho', "ID"],['Illinois', "IL"],['Indiana', "IN"],['Iowa', "IA"],['Kansas', "KS"],['Kentucky', "KY"],
|
|
115
|
+
['Louisiana', "LA"],['Maine', "ME"],['Maryland', "MD"],['Massachusetts', "MA"],['Michigan', "MI"],['Minnesota', "MN"],
|
|
116
|
+
['Mississippi', "MS"],['Missouri', "MO"],['Montana', "MT"],['Nebraska', "NE"],['Nevada', "NV"],['New Hampshire', "NH"],
|
|
117
|
+
['New Jersey', "NJ"],['New Mexico', "NM"],['New York', "NY"],['North Carolina', "NC"],['North Dakota', "ND"],
|
|
118
|
+
['Ohio', "OH"],['Oklahoma', "OK"],['Oregon', "OR"],['Pennsylvania', "PA"],['Rhode Island', "RI"],['South Carolina', "SC"],
|
|
119
|
+
['South Dakota', "SD"],['Tennessee', "TN"],['Texas', "TX"],['Utah', "UT"],['Vermont', "VT"],['Virginia', "VA"],['Washington', "WA"],
|
|
120
|
+
['West Virginia', "WV"],['Wisconsin', "WI"],['Wyoming', "WY"]
|
|
121
|
+
|
|
122
|
+
].map do |state|
|
|
123
|
+
(abbreviate ? state : [state.first, state.first])
|
|
124
|
+
end
|
|
125
|
+
state_list << ['International', incl_international] unless incl_international === false
|
|
126
|
+
state_list
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
##
|
|
130
|
+
#
|
|
131
|
+
# Checks to see if a particular attribute is required, if so, a "required" attribute is added to the field.
|
|
132
|
+
#
|
|
133
|
+
# @param [Symbol] attribute The attribute to check against validators
|
|
134
|
+
# @return [Boolean]
|
|
135
|
+
#
|
|
136
|
+
def attribute_required?(attribute)
|
|
137
|
+
validates_presence?(attribute) || validates_inclusion?(attribute)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Convenience method to use the +content_tag+ method from our template
|
|
141
|
+
#
|
|
142
|
+
def content_tag(tag, content, options = {}, escape = true, &block) #:nodoc:
|
|
143
|
+
@template.content_tag(tag, content, options, escape, &block)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
##
|
|
147
|
+
# Checks to see if there are errors for the particular method or attribute
|
|
148
|
+
#
|
|
149
|
+
# @param [Symbol] method The method/attribute to check
|
|
150
|
+
#
|
|
151
|
+
# @return [Boolean]
|
|
152
|
+
#
|
|
153
|
+
def errors_on_attribute?(method)
|
|
154
|
+
return false if @object.nil?
|
|
155
|
+
!(@object.errors.empty? || !@object.errors[method.to_sym].present? || [@object.errors[method.to_sym]].flatten.empty?)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
##
|
|
159
|
+
#
|
|
160
|
+
# Checks a passed validator to see if it is required
|
|
161
|
+
# 'borrowed' from Formtastic by Justin French (see https://github.com/justinfrench/formtastic)
|
|
162
|
+
#
|
|
163
|
+
# @param [Hash] options Validator options
|
|
164
|
+
#
|
|
165
|
+
def options_require_validation?(options)
|
|
166
|
+
|
|
167
|
+
allow_blank = options[:allow_blank]
|
|
168
|
+
return !allow_blank unless allow_blank.nil?
|
|
169
|
+
if_condition = !options[:if].nil?
|
|
170
|
+
condition = if_condition ? options[:if] : options[:unless]
|
|
171
|
+
|
|
172
|
+
condition = if condition.respond_to?(:call)
|
|
173
|
+
condition.call(@object)
|
|
174
|
+
elsif condition.is_a?(::Symbol) && @object.respond_to?(condition)
|
|
175
|
+
@object.send(condition)
|
|
176
|
+
else
|
|
177
|
+
condition
|
|
178
|
+
end
|
|
179
|
+
if_condition ? !!condition : !condition
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
##
|
|
183
|
+
#
|
|
184
|
+
# Checks an options hash to determine if a required method/attribute was overridden manually
|
|
185
|
+
# @param [Hash] options The options hash to check
|
|
186
|
+
#
|
|
187
|
+
#
|
|
188
|
+
def required_by_option?(options)
|
|
189
|
+
req = (options.is_a?(Hash) ? options.stringify_keys[:required] : options)
|
|
190
|
+
!(req.to_s === 'false' || req.nil?)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
##
|
|
194
|
+
#
|
|
195
|
+
# Wrapper method used by all form fields to customize the output
|
|
196
|
+
#
|
|
197
|
+
# @param [Symbol] helper_method Original Rails helper method name
|
|
198
|
+
# @param [Symbol] method Object method / symbol used for the element
|
|
199
|
+
# @param [Array] args Array of original arguments passed to the helper
|
|
200
|
+
#
|
|
201
|
+
# @return [String] Rendered HTML tag for the element
|
|
202
|
+
#
|
|
203
|
+
def render_field_as_custom(helper_method, method, *args)
|
|
204
|
+
|
|
205
|
+
@current_field_type = helper_method
|
|
206
|
+
|
|
207
|
+
options = args.extract_options!
|
|
208
|
+
(@field_order ||= []) << method
|
|
209
|
+
|
|
210
|
+
# Add an error class to the field if it has errors
|
|
211
|
+
#
|
|
212
|
+
if errors_on_attribute?(method)
|
|
213
|
+
klasses = (options.delete(:class) || "").split(" ")
|
|
214
|
+
klasses << "field_with_errors"
|
|
215
|
+
options[:class] = klasses.join(" ")
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Add a required attribute to the field if it is required
|
|
219
|
+
# Skip if false was passed as the required option
|
|
220
|
+
#
|
|
221
|
+
options[:required] = "required" if attribute_required?(method) && options.delete(:required).to_s != 'false'
|
|
222
|
+
options['data-validates-uniqueness'] = "true" if validates_uniqueness?(method)
|
|
223
|
+
|
|
224
|
+
result = send(:"_super_#{helper_method}", method, *(args << options))
|
|
225
|
+
messages = @object.nil? ? [] : @object.errors[method]
|
|
226
|
+
render_field_with_errors(method, result, messages)
|
|
227
|
+
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
##
|
|
231
|
+
#
|
|
232
|
+
# Renders the passed +html_tag+ with the custom error_template
|
|
233
|
+
#
|
|
234
|
+
# @param [Symbol] method The method/attribute to check for errors
|
|
235
|
+
# @param [Object, String] html_tag Instance of an input tag or a string
|
|
236
|
+
# @param [Array] messages An array of all error messages to be added to the template
|
|
237
|
+
#
|
|
238
|
+
def render_field_with_errors(method, html_tag, messages)
|
|
239
|
+
|
|
240
|
+
return html_tag unless errors_on_attribute?(method)
|
|
241
|
+
|
|
242
|
+
error_class = 'field_with_errors'
|
|
243
|
+
message_error_class = 'errors_for_field'
|
|
244
|
+
render_binding = binding
|
|
245
|
+
error_template = %{
|
|
246
|
+
<span class="<%= error_class %>">
|
|
247
|
+
<%= html_tag %>
|
|
248
|
+
<span class="<%= message_error_class %>"><%= [messages].flatten.join(",") %></span>
|
|
249
|
+
</span>}
|
|
250
|
+
|
|
251
|
+
renderer = ERB.new(error_template)
|
|
252
|
+
renderer.result(render_binding).to_s.html_safe
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
##
|
|
256
|
+
#
|
|
257
|
+
# Compiles an array of all validators for a particular attribute
|
|
258
|
+
# @param [Symbol] attribtue The attribute to check
|
|
259
|
+
#
|
|
260
|
+
def validators_for(attribute)
|
|
261
|
+
|
|
262
|
+
return [] if @object.nil?
|
|
263
|
+
return [] unless @object.class.respond_to?(:validators_on)
|
|
264
|
+
|
|
265
|
+
attribute = attribute.to_s.sub(/_id$/, '').to_sym
|
|
266
|
+
@object.class.validators_on(attribute).uniq
|
|
267
|
+
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
##
|
|
271
|
+
#
|
|
272
|
+
# Convenience method to see if a particular attribute has validators
|
|
273
|
+
# @param [Symbol] attribute The attribute to check
|
|
274
|
+
#
|
|
275
|
+
def validators_for?(attribute)
|
|
276
|
+
!validators_for(attribute).empty?
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
##
|
|
280
|
+
#
|
|
281
|
+
# Checks for a presence validation on a particular attribute
|
|
282
|
+
# @param [Symbol] attribute The attribute to check
|
|
283
|
+
#
|
|
284
|
+
def validates_presence?(attribute)
|
|
285
|
+
validator_of_type_exists?(validators_for(attribute), :presence)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
##
|
|
289
|
+
#
|
|
290
|
+
# Checks for a uniqueness validation on a particular attribute
|
|
291
|
+
# @param [Symbol] attribute The attribute to check
|
|
292
|
+
#
|
|
293
|
+
def validates_uniqueness?(attribute)
|
|
294
|
+
validator_of_type_exists?(validators_for(attribute), :uniqueness, false)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
##
|
|
298
|
+
#
|
|
299
|
+
# Checks for inclusion validation on a particular attribute
|
|
300
|
+
# @param [Symbol] attribute The attribute to check
|
|
301
|
+
#
|
|
302
|
+
def validates_inclusion?(attribute)
|
|
303
|
+
validator_of_type_exists?(validators_for(attribute), :inclusion)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
private
|
|
307
|
+
|
|
308
|
+
def validator_of_type_exists?(validators, kind, check_options = true) #:nodoc: @private
|
|
309
|
+
validators.detect do |validator|
|
|
310
|
+
exists = (validator.kind.to_s == kind.to_s)
|
|
311
|
+
next exists unless (check_options && exists) && validator.options.present?
|
|
312
|
+
options_require_validation?(validator.options)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
end
|
|
319
|
+
end
|