blog_logic 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,14 +1,14 @@
1
1
  source 'http://rubygems.org'
2
- source 'http://jose.seologic.com:8808/'
3
2
 
4
3
  gem 'bson_ext'
5
4
  gem 'carrierwave-mongoid', :require => 'carrierwave/mongoid'
6
5
  gem 'ckeditor', :git => 'git://github.com/Bantik/rails-ckeditor.git', :branch => 'rails3_mongoid'
7
6
  gem 'kaminari'
8
- gem 'mongoid', '>= 2.0.0.beta.17'
9
- gem 'rails', '>= 3.0.1'
7
+ gem 'mongoid-tree', :require => 'mongoid/tree'
8
+ gem 'rails', '3.0.10'
10
9
  gem 'rmagick', '2.12.2' # version compatible with heroku
11
- gem 'scaffold_logic', '>= 1.0.3'
10
+ gem 'scaffold_logic'
11
+ gem 'stringex'
12
12
  gem 'tanker'
13
13
 
14
14
  group :development do
@@ -17,7 +17,6 @@ group :development do
17
17
  end
18
18
 
19
19
  group :test do
20
- gem 'angry-pickle'
21
20
  gem 'be_valid_asset'
22
21
  gem 'capybara'
23
22
  gem 'cucumber-rails'
@@ -30,6 +29,4 @@ group :test do
30
29
  gem 'mocha'
31
30
  gem 'rcov'
32
31
  gem 'rspec-rails'
33
- gem 'simplecov', '>= 0.4.0', :require => false
34
- gem 'viewcumber'
35
32
  end
data/Gemfile.local CHANGED
@@ -1,15 +1,15 @@
1
1
  source 'http://rubygems.org'
2
- source 'http://jose.seologic.com:8808/'
3
2
 
4
3
  gem 'bson_ext'
5
- gem 'carrierwave'
4
+ gem 'carrierwave-mongoid', :require => 'carrierwave/mongoid'
6
5
  gem 'ckeditor', :git => 'git://github.com/Bantik/rails-ckeditor.git', :branch => 'rails3_mongoid'
7
6
  gem 'kaminari'
8
- gem 'mongoid', '>= 2.0.0.beta.17'
9
- gem 'rails', '>= 3.0.1'
7
+ gem 'mongoid-tree'
8
+ gem 'rails', '3.0.10'
10
9
  gem 'rmagick', '2.12.2' # version compatible with heroku
11
10
  gem 'scaffold_logic', :path => '~/Documents/projects/scaffold_logic'
12
- gem 'SystemTimer'
11
+ gem 'stringex'
12
+ gem 'tanker'
13
13
 
14
14
  group :development do
15
15
  gem 'jeweler'
@@ -17,7 +17,6 @@ group :development do
17
17
  end
18
18
 
19
19
  group :test do
20
- gem 'angry-pickle'
21
20
  gem 'be_valid_asset'
22
21
  gem 'capybara'
23
22
  gem 'cucumber-rails'
@@ -30,6 +29,4 @@ group :test do
30
29
  gem 'mocha'
31
30
  gem 'rcov'
32
31
  gem 'rspec-rails'
33
- gem 'simplecov', '>= 0.4.0', :require => false
34
- gem 'viewcumber'
35
32
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -0,0 +1,49 @@
1
+ class Admin::BlogCategoriesController < ApplicationController
2
+ before_filter :authenticate_user! if Object.const_defined?('Devise')
3
+ before_filter :find_blog_category, :only => [:destroy, :edit, :update, :show]
4
+
5
+ def index
6
+ @blog_categories = BlogCategory.roots
7
+ end
8
+
9
+ def show
10
+ end
11
+
12
+ def new
13
+ @blog_category = BlogCategory.new :parent_id => params[:parent_id]
14
+ end
15
+
16
+ def create
17
+ @blog_category = BlogCategory.new params[:blog_category]
18
+ if @blog_category.save
19
+ flash[:notice] = 'Successfully created the blog_category.'
20
+ redirect_to @blog_category.root? ? admin_blog_category_path(@blog_category) : admin_blog_category_path(@blog_category.parent)
21
+ else
22
+ render :action => 'new'
23
+ end
24
+ end
25
+
26
+ def edit
27
+ end
28
+
29
+ def update
30
+ if @blog_category.update_attributes params[:blog_category]
31
+ flash[:notice] = 'Successfully updated the blog_category.'
32
+ redirect_to admin_blog_categories_path
33
+ else
34
+ render :action => 'edit'
35
+ end
36
+ end
37
+
38
+ def destroy
39
+ @blog_category.destroy
40
+ flash[:notice] = 'Successfully destroyed the blog_category.'
41
+ redirect_to @blog_category.root? ? admin_blog_categories_path : admin_blog_category_path(@blog_category.parent)
42
+ end
43
+
44
+ private
45
+
46
+ def find_blog_category
47
+ @blog_category = BlogCategory.find params[:id]
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ class BlogCategoriesController < ApplicationController
2
+ before_filter :find_category_by_permalink, :only => :show
3
+
4
+ def index
5
+ @blog_categories = BlogCategory.roots_with_posts
6
+ end
7
+
8
+ def show
9
+ if @blog_category.blank?
10
+ flash[:notice] = "That category doesn't exist."
11
+ redirect_to Blog.first.path
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def find_category_by_permalink
18
+ @blog_category ||= BlogCategory.by_slug(params[:id])
19
+ end
20
+ end
data/app/models/blog.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  class Blog
2
+ include BlogLogic::Base
2
3
  include Mongoid::Document
3
4
  include Mongoid::Timestamps
4
- include BlogLogic::Base
5
5
 
6
- # Attributes =====================================================================================
6
+ # Scopes =========================================================================================
7
+ scope :by_slug, lambda {|slug| {:where => {:slug => "#{slug}".gsub('//','/')} } }
8
+
9
+ # Mongoid ========================================================================================
7
10
  field :title
8
11
  field :slug
9
12
  field :description
@@ -11,37 +14,36 @@ class Blog
11
14
  field :posts_per_page, :type => Integer
12
15
  field :rss_enabled, :type => Boolean
13
16
  field :has_topics, :type => Boolean, :default => false
14
-
15
- # Indices ========================================================================================
16
-
17
- # Constants ======================================================================================
18
-
19
- # Scopes =========================================================================================
20
- scope :by_slug, lambda {|slug| {:where => {:slug => "#{slug}".gsub('//','/')} } }
21
-
22
- # Relationships ==================================================================================
23
17
  embeds_many :posts
18
+ has_many :blog_categories
19
+ has_many :post2s
24
20
 
25
21
  # Behavior =======================================================================================
26
-
27
22
  attr_accessor :desired_slug
28
23
  has_slug :desired_slug
29
24
 
30
- # Callbacks ======================================================================================
31
-
32
25
  # Validations ====================================================================================
33
-
34
26
  validates_presence_of :title, :description, :desired_slug
35
27
 
36
- # Class methods ==================================================================================
37
-
38
28
  # Instance methods ===============================================================================
29
+ def copy_posts
30
+ self.posts.each do |post|
31
+ p = Post2.create :slug => post.slug, :content => post.content, :tags => post.tags, :author => post.author, :published_at => post.published_at, :state => post.state, :publication_date => post.publication_date, :summary => post.summary
32
+ p.title = post.title
33
+ p.save
34
+ self.post2s << p
35
+ end
36
+ end
39
37
 
40
38
  def feed_address
41
39
  "/#{self.slug}/feed.rss"
42
40
  end
43
41
 
44
42
  def humanize_path
43
+ self.path
44
+ end
45
+
46
+ def path
45
47
  "/#{self.slug}/".gsub(/\/\//,'/').gsub(/\/\//,'/')
46
48
  end
47
49
 
@@ -60,5 +62,4 @@ class Blog
60
62
  def search(keyword)
61
63
  self.posts.published.where(:content => /#{keyword}/i)
62
64
  end
63
-
64
65
  end
@@ -0,0 +1,88 @@
1
+ class BlogCategory
2
+ include LuckySneaks::StringExtensions
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include Mongoid::Tree
6
+
7
+ # Mongoid ========================================================================================
8
+ field :name
9
+ field :description
10
+ field :slug
11
+
12
+ # Behavior =======================================================================================
13
+ before_destroy :destroy_children
14
+ before_save :set_slug
15
+ validates_presence_of :name
16
+ validates_uniqueness_of :name
17
+
18
+ # Class methods: Overrides =======================================================================
19
+ def self.children
20
+ super.asc :name
21
+ end
22
+
23
+ def self.roots
24
+ super.asc :name
25
+ end
26
+
27
+ # Class methods ==================================================================================
28
+ def self.by_name value
29
+ self.find :first, :conditions => {:name => value}
30
+ end
31
+
32
+ def self.by_slug value
33
+ self.find :first, :conditions => {:slug => /^#{value}$/i}
34
+ end
35
+
36
+ def self.roots_with_posts
37
+ self.roots.asc(:name).select{ |c| c.has_posts? }
38
+ end
39
+
40
+ # Instance methods: Overrides ====================================================================
41
+ def children_with_posts
42
+ self.children.asc(:name).select{ |c| c.has_posts? }
43
+ end
44
+
45
+ def parent_id= value
46
+ self[:parent_id] = value == 'nil' ? nil : value
47
+ end
48
+
49
+ # Instance methods ===============================================================================
50
+ def has_posts?
51
+ ! self.all_posts.blank?
52
+ end
53
+
54
+ def parent_category=(name)
55
+ unless name.blank?
56
+ self.parent = BlogCategory.find_or_create_by :name => name
57
+ self.save
58
+ end
59
+ end
60
+
61
+ def all_posts
62
+ (self.posts + self.children.map{ |c| c.posts }).uniq.flatten
63
+ end
64
+
65
+ def posts
66
+ Blog.first.posts.select{ |p| p.blog_category_ids.include? self.id }
67
+ end
68
+
69
+ def subcategory?
70
+ ! self.root?
71
+ end
72
+
73
+ def url
74
+ "#{Blog.first.path}topics/#{self.slug}/"
75
+ end
76
+
77
+ private
78
+
79
+ # Returns an array of this category's and its ancestors' names.
80
+ def ancestors_and_self_names
81
+ self.ancestors_and_self.map{ |l| l.name }
82
+ end
83
+
84
+ # Returns the permalink for this locale.
85
+ def set_slug
86
+ self.slug = (ancestors_and_self_names * '/').to_url.gsub('-slash-', '/')
87
+ end
88
+ end
data/app/models/post.rb CHANGED
@@ -27,6 +27,7 @@ class Post
27
27
  index :state, :unique => false
28
28
 
29
29
  embedded_in :blog, :inverse_of => :posts
30
+ has_and_belongs_to_many :blog_categories
30
31
 
31
32
  # Behavior =======================================================================================
32
33
  attr_accessor :desired_slug
@@ -0,0 +1,126 @@
1
+ class Post2
2
+ include LuckySneaks::StringExtensions
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include Tanker
6
+
7
+ # Constants ======================================================================================
8
+ STATES = ['draft', 'published']
9
+
10
+ # Scopes =========================================================================================
11
+ scope :drafts, :where => {:state => 'draft'}
12
+ scope :published, :where => {:state => 'published'}, :descending => :publication_date
13
+ scope :by_slug, lambda {|slug| {:where => {:slug.in => ["#{slug}".gsub('//','/'), "/#{slug}/".gsub('//','/')] } } }
14
+
15
+ # Mongoid ========================================================================================
16
+ field :title
17
+ field :content
18
+ field :tags, :type => Array
19
+ field :slug
20
+ field :author
21
+ field :published_at, :type => Date
22
+ field :state
23
+ field :publication_date, :type => DateTime
24
+ field :summary
25
+
26
+ index :slug, :unique => false
27
+ index :state, :unique => false
28
+
29
+ belongs_to :blog
30
+ references_and_referenced_in_many :blog_categories
31
+
32
+ # Behavior =======================================================================================
33
+ after_create :set_slug
34
+
35
+ # Tanker =========================================================================================
36
+ tankit 'idx' do
37
+ indexes :author
38
+ indexes :content
39
+ indexes :summary
40
+ indexes :tags
41
+ indexes :title
42
+ end
43
+
44
+ after_destroy :delete_tank_indexes
45
+ after_save :update_tank_indexes
46
+
47
+ # Validations ====================================================================================
48
+ # class DesiredSlugPresenceAndUniquenessValidator < ActiveModel::EachValidator
49
+ # def validate_each(object, attribute, value)
50
+ # object.desired_slug = object.title unless object.desired_slug
51
+ # if object.blog && object.blog.posts.map{|p| p.slug unless p == object}.include?(object.desired_slug)
52
+ # object.errors[attribute] << (options[:message] || "must be unique.")
53
+ # end
54
+ # end
55
+ # end
56
+
57
+ # validates :desired_slug, :desired_slug_presence_and_uniqueness => true
58
+ validates_presence_of :title
59
+ validates_presence_of :content
60
+
61
+ # Instance methods ===============================================================================
62
+ def draft?
63
+ self.state == 'draft' || self.state.nil?
64
+ end
65
+
66
+ def full_path
67
+ self.path
68
+ end
69
+
70
+ def humanize_path
71
+ "/#{self.slug}/".gsub(/\/\//,'/').gsub(/\/\//,'/')
72
+ end
73
+
74
+ def my_index
75
+ self.blog.posts.index(self)
76
+ end
77
+
78
+ def next_post
79
+ i = self.my_index + 1
80
+ i = 0 if i > (self.blog.posts.size - 1)
81
+ self.blog.posts[i]
82
+ end
83
+
84
+ def path
85
+ "#{self.blog.humanize_path}#{self.humanize_path}".gsub('//', '/')
86
+ end
87
+
88
+ def previous_post
89
+ i = self.my_index - 1
90
+ i = self.blog.posts.size - 1 if i < 0
91
+ self.blog.posts[i]
92
+ end
93
+
94
+ def publish!
95
+ self.update_attributes(:state => 'published', :publication_date => Time.zone.now)
96
+ end
97
+
98
+ def published?
99
+ self.state == 'published'
100
+ end
101
+
102
+ def search_description
103
+ self.summary.gsub(/<\/?[^>]*>/, '')[0..199].html_safe
104
+ end
105
+
106
+ def search_title
107
+ self.title
108
+ end
109
+
110
+ # Sets the slug for this locale. Slugs from the locale tree are used to build this locale's URL.
111
+ def set_slug
112
+ self.slug = self.title.to_s.to_url
113
+ end
114
+
115
+ def unpublish!
116
+ self.update_attributes :state => 'draft', :publication_date => nil
117
+ end
118
+
119
+ def state=(arg)
120
+ self[:state] = arg.downcase
121
+ end
122
+
123
+ def status
124
+ self.state ? self.state.capitalize : 'Draft'
125
+ end
126
+ end
@@ -0,0 +1,5 @@
1
+ <tr class="<%= cycle('odd', 'even') -%>">
2
+ <td><%= link_to blog_category.name, admin_blog_category_path(blog_category) -%></td>
3
+ <td><%= blog_category.description -%></td>
4
+ <td class="crud_links"><%= admin_crud_links(blog_category, [:show, :edit, :destroy]) -%></td>
5
+ </tr>
@@ -0,0 +1,30 @@
1
+ <% if @blog_category.errors.any? %>
2
+ <div id="error_explanation">
3
+ <h2>Please correct the following errors:</h2>
4
+ <% @blog_category.errors.to_a.in_groups(2, false).each do |group| %>
5
+ <ul>
6
+ <% group.each do |msg| %>
7
+ <li><%= msg %></li>
8
+ <% end %>
9
+ </ul>
10
+ <% end %>
11
+ </div>
12
+ <% end %>
13
+
14
+ <fieldset class="form_container">
15
+ <%= legend_tag 'About This Blog Category' -%>
16
+
17
+ <div class="form_column">
18
+ <%= f.text_field :name -%>
19
+ <%= f.select :parent_id, [['(None)', 'nil']] + BlogCategory.all.map{ |c| [c.name, c.id] } -%>
20
+ </div>
21
+ <div class="form_column">
22
+ <%= f.text_area :description -%>
23
+ </div>
24
+ <div class="link_block">
25
+ <%= f.submit 'Save', :class => 'link_button' -%>
26
+ <%= link_to 'Cancel', admin_blog_categories_path, :class => 'link_button' -%>
27
+ </div>
28
+ </fieldset>
29
+
30
+ <br style="clear: both;" />
@@ -0,0 +1,5 @@
1
+ <% set_title 'Edit a Blog Category' %>
2
+
3
+ <%= form_for @blog_category, :url => admin_blog_category_url(@blog_category) do |f| %>
4
+ <%= render :partial => 'form', :locals => {:f => f } %>
5
+ <%- end -%>
@@ -0,0 +1,18 @@
1
+ <%- set_title 'Blog Categories' -%>
2
+
3
+ <table class="standard">
4
+ <tr>
5
+ <th><%= sort_link('blog_categories', 'name', params) -%></th>
6
+ <th><%= sort_link('blog_categories', 'description', params) -%></th>
7
+ <th> </th>
8
+ </tr>
9
+ <%- if @blog_categories.empty? -%>
10
+ <tr><td colspan="4">No blog categories have been created yet.</td></tr>
11
+ <%- else -%>
12
+ <%= render :partial => 'blog_category', :collection => @blog_categories -%>
13
+ <%- end -%>
14
+ </table>
15
+
16
+ <div class="link_block attached table">
17
+ <%= link_to 'New Blog Category', new_admin_blog_category_path, :class => 'link_button' -%>
18
+ </div>
@@ -0,0 +1,5 @@
1
+ <% set_title 'Create a New Blog Category' %>
2
+
3
+ <%= form_for @blog_category, :url => admin_blog_categories_url do |f| %>
4
+ <%= render :partial => 'form', :locals => {:f => f } %>
5
+ <%- end -%>
@@ -0,0 +1,41 @@
1
+ <%- set_title 'Blog-Category Details' -%>
2
+
3
+ <div class="form_container">
4
+ <%= legend_tag 'About This Blog Category' -%>
5
+ <div class="form_column">
6
+ <%= faux_field 'Name', @blog_category.name %>
7
+ <%= faux_field 'Description', @blog_category.description %>
8
+ </div>
9
+ <div class="form_column">
10
+ <%- if @blog_category.parent_id -%>
11
+ <%= faux_field 'Parent', @blog_category.parent.name %>
12
+ <%- end -%>
13
+ </div>
14
+ <br style="clear: both;" />
15
+ </div>
16
+
17
+ <div class="link_block attached">
18
+ <%= link_to 'Edit', edit_admin_blog_category_path(@blog_category), :class => 'link_button' -%>
19
+ <%= link_to 'Back', admin_blog_categories_path, :class => 'link_button' -%>
20
+ </div>
21
+
22
+ <div class="form_container">
23
+ <%= legend_tag 'Subcategories' -%>
24
+
25
+ <%- if @blog_category.children.empty? -%>
26
+ <p><em>No subcategories have been created yet.</em></p>
27
+ <%- else -%>
28
+ <table class="standard">
29
+ <tr>
30
+ <th><%= sort_link('blog_categories', 'name', params) -%></th>
31
+ <th><%= sort_link('blog_categories', 'description', params) -%></th>
32
+ <th> </th>
33
+ </tr>
34
+ <%= render :partial => 'blog_category', :collection => @blog_category.children -%>
35
+ </table>
36
+ <%- end -%>
37
+ </div>
38
+ <div class="link_block attached">
39
+ <%= link_to 'Add Subcategory', new_admin_blog_category_path(:parent_id => @blog_category.id), :class => 'link_button' -%>
40
+ <%= link_to 'Back', admin_blog_categories_path, :class => 'link_button' -%>
41
+ </div>
@@ -9,11 +9,9 @@
9
9
  <li><%= msg %></li>
10
10
  <% end %>
11
11
  </ul>
12
- <br style="clear: both;" />
13
12
  <% end %>
14
13
  <br style="clear: both;" />
15
14
  </div>
16
- <br style="clear: both;" />
17
15
  <% end %>
18
16
 
19
17
  <fieldset class="form_container">
@@ -44,17 +42,30 @@
44
42
  <div class="form_column">
45
43
  <%= f.cktext_area :content, :toolbar => 'Full', :id => 'content_field', :height => '500px' -%>
46
44
  </div>
45
+ </fieldset>
46
+
47
+ <fieldset class="form_container">
48
+ <%= legend_tag 'Categories' -%>
49
+
47
50
  <div class="form_column">
51
+ <%- BlogCategory.roots.asc(:name).each do |blog_category| -%>
52
+ <label><%= blog_category.name -%></label><br />
53
+ <%- blog_category.leaves.in_groups(3, false).each do |group| -%>
54
+ <div class="three_column">
55
+ <%- group.each do |child| -%>
56
+ <%= check_box_tag 'post[blog_category_ids][]', child.id, @post.blog_category_ids.include?(child.id), :class => 'category_selector', :id => child.id -%><label for="<%= child.id -%>" class="inline"><%= child.name -%></label>
57
+ <%- end -%><br />
58
+ </div>
59
+ <%- end -%><br />
60
+ <br />
61
+ <%- end -%>
48
62
  </div>
49
63
  <div class="link_block">
50
64
  <%= f.submit 'Publish', :class => 'link_button' -%>
51
65
  <%= f.submit 'Save as Draft', :class => 'link_button' -%>
52
66
  <%= f.submit 'Preview', :class => 'link_button' -%>
53
67
  <%- if @post.new_record? -%>
54
- <%= link_to 'Cancel', admin_blog_posts_path(@blog), :class => 'link_button' -%>
55
- <%- else -%>
56
- <%= link_to 'Cancel', admin_blog_post_path(@blog, @post), :class => 'link_button' -%>
68
+ <%= link_to 'Cancel', @post.new_record? ? admin_blog_posts_path(@blog) : admin_blog_post_path(@blog, @post), :class => 'link_button' -%>
57
69
  <%- end -%>
58
70
  </div>
59
71
  </fieldset>
60
-
@@ -29,10 +29,17 @@
29
29
  <%= @post.content.html_safe -%>
30
30
  </div>
31
31
  <%- end -%>
32
+ </fieldset>
33
+
34
+ <fieldset class="form_container">
35
+ <%= legend_tag 'Categories' -%>
36
+ <ul>
37
+ <%- @post.blog_categories.each do |c| -%>
38
+ <li><%= link_to c.name, c.url -%></li>
39
+ <%- end -%>
40
+ </ul>
32
41
  <div class="link_block">
33
42
  <%= link_to 'Edit', edit_admin_blog_post_path(@blog, @post), :class => 'link_button' -%>
34
43
  <%= link_to 'Back', admin_blog_posts_path(@blog), :class => 'link_button' -%>
35
44
  </div>
36
- </fieldset>
37
-
38
-
45
+ </fieldset>
@@ -0,0 +1,16 @@
1
+ <%- set_title "Topics in #{Blog.first.title}" -%>
2
+
3
+ <%- if @blog_categories.empty? -%>
4
+ <p>
5
+ <em>No blog categories found.</em>
6
+ </p>
7
+ <%- else -%>
8
+ <ul>
9
+ <%- @blog_categories.each do |blog_category| -%>
10
+ <li>
11
+ <%= link_to blog_category.name, blog_category.url -%><br />
12
+ <%= blog_category.description -%>
13
+ </li>
14
+ <%- end -%>
15
+ </ul>
16
+ <%- end -%>
@@ -0,0 +1,25 @@
1
+ <%-
2
+ set_title "#{Blog.first.title}: #{@blog_category ? @blog_category.name : ''}"
3
+ @meta_description = @blog_category.description
4
+ -%>
5
+
6
+ <%- unless @blog_category.all_posts.empty? -%>
7
+ <ul>
8
+ <%- @blog_category.all_posts.each do |post| -%>
9
+ <li>
10
+ <%= link_to post.title, post.path -%><br />
11
+ <%= post.summary -%>
12
+ </li>
13
+ <%- end -%>
14
+ </ul>
15
+ <%- end -%>
16
+
17
+ <%- unless @blog_category.children.empty? -%>
18
+ <p>Subcategories</p>
19
+
20
+ <ul>
21
+ <%- @blog_category.children.each do |category| -%>
22
+ <li><%= link_to category.name, category.url -%></li>
23
+ <%- end -%>
24
+ </ul>
25
+ <%- end -%>