Commit 37fcaf7570b1503b139039113b889f38925f753a

Authored by Leandro Santos
1 parent b0512b43

mergin with master

Gemfile
... ... @@ -17,7 +17,6 @@ gem 'nokogiri'
17 17 gem 'rake', :require => false
18 18 gem 'rest-client'
19 19 gem 'exception_notification'
20   -gem 'gettext_rails'
21 20  
22 21 # FIXME list here all actual dependencies (i.e. the ones in debian/control),
23 22 # with their GEM names (not the Debian package names)
... ...
Gemfile.lock
... ... @@ -67,15 +67,6 @@ GEM
67 67 activesupport (>= 3.0.4)
68 68 fast_gettext (0.6.8)
69 69 ffi (1.0.11)
70   - gettext (2.2.1)
71   - locale
72   - gettext_activerecord (2.1.0)
73   - activerecord (>= 2.3.2)
74   - gettext (>= 2.1.0)
75   - gettext_rails (2.1.0)
76   - gettext_activerecord (>= 2.1.0)
77   - locale_rails (>= 2.0.5)
78   - rails (>= 2.3.2)
79 70 gherkin (2.4.21)
80 71 json (>= 1.4.6)
81 72 hike (1.2.1)
... ... @@ -83,9 +74,6 @@ GEM
83 74 i18n (0.6.0)
84 75 journey (1.0.3)
85 76 json (1.7.3)
86   - locale (2.0.5)
87   - locale_rails (2.0.5)
88   - locale (>= 2.0.5)
89 77 mail (2.4.4)
90 78 i18n (>= 0.4.0)
91 79 mime-types (~> 1.16)
... ... @@ -184,7 +172,6 @@ DEPENDENCIES
184 172 database_cleaner
185 173 exception_notification
186 174 fast_gettext
187   - gettext_rails
188 175 hpricot
189 176 mocha
190 177 nokogiri
... ...
app/controllers/admin/environment_design_controller.rb
... ... @@ -3,9 +3,7 @@ class EnvironmentDesignController < BoxOrganizerController
3 3 protect 'edit_environment_design', :environment
4 4  
5 5 def available_blocks
6   - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
7   - # the Noosfero core soon, see ActionItem3045
8   - @blocks ||= [ ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ]
  6 + @blocks ||= [ ArticleBlock, LoginBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ]
9 7 @blocks += plugins.dispatch(:extra_blocks, :type => Environment)
10 8 end
11 9  
... ...
app/controllers/my_profile/cms_controller.rb
... ... @@ -4,6 +4,12 @@ class CmsController < MyProfileController
4 4  
5 5 include ArticleHelper
6 6  
  7 + def search_tags
  8 + arg = params[:term].downcase
  9 + result = ActsAsTaggableOn::Tag.find(:all, :conditions => ['LOWER(name) LIKE ?', "%#{arg}%"])
  10 + render :text => prepare_to_token_input_by_label(result).to_json, :content_type => 'application/json'
  11 + end
  12 +
7 13 def self.protect_if(*args)
8 14 before_filter(*args) do |c|
9 15 user, profile = c.send(:user), c.send(:profile)
... ...
app/helpers/article_helper.rb
... ... @@ -83,6 +83,10 @@ module ArticleHelper
83 83 array.map { |object| {:id => object.id, :name => object.name} }
84 84 end
85 85  
  86 + def prepare_to_token_input_by_label(array)
  87 + array.map { |object| {:label => object.name, :value => object.name} }
  88 + end
  89 +
86 90 def cms_label_for_new_children
87 91 _('New article')
88 92 end
... ...
app/helpers/layout_helper.rb
... ... @@ -28,6 +28,7 @@ module LayoutHelper
28 28 'lightbox',
29 29 'colorbox',
30 30 'block_store',
  31 + 'inputosaurus',
31 32 pngfix_stylesheet_path,
32 33 ] + tokeninput_stylesheets
33 34 plugins_stylesheets = @plugins.select(&:stylesheet?).map { |plugin| plugin.class.public_path('style.css') }
... ...
app/models/box.rb
... ... @@ -28,9 +28,6 @@ class Box < ActiveRecord::Base
28 28 CategoriesBlock,
29 29 CommunitiesBlock,
30 30 EnterprisesBlock,
31   - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
32   - # the Noosfero core soon, see ActionItem3045
33   - EnvironmentStatisticsBlock,
34 31 FansBlock,
35 32 FavoriteEnterprisesBlock,
36 33 FeedReaderBlock,
... ... @@ -53,9 +50,6 @@ class Box < ActiveRecord::Base
53 50 CommunitiesBlock,
54 51 DisabledEnterpriseMessageBlock,
55 52 EnterprisesBlock,
56   - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
57   - # the Noosfero core soon, see ActionItem3045
58   - EnvironmentStatisticsBlock,
59 53 FansBlock,
60 54 FavoriteEnterprisesBlock,
61 55 FeaturedProductsBlock,
... ...
app/models/environment.rb
... ... @@ -177,9 +177,6 @@ class Environment < ActiveRecord::Base
177 177  
178 178 # "left" area
179 179 env.boxes[1].blocks << LoginBlock.new
180   - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
181   - # the Noosfero core soon, see ActionItem3045
182   - env.boxes[1].blocks << EnvironmentStatisticsBlock.new
183 180 env.boxes[1].blocks << RecentDocumentsBlock.new
184 181  
185 182 # "right" area
... ...
app/models/environment_statistics_block.rb
... ... @@ -1,33 +0,0 @@
1   -# TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
2   -# the Noosfero core soon, see ActionItem3045
3   -
4   -class EnvironmentStatisticsBlock < Block
5   -
6   - def self.description
7   - _('Environment stastistics (DEPRECATED)')
8   - end
9   -
10   - def default_title
11   - _('Statistics for %s') % owner.name
12   - end
13   -
14   - def help
15   - _('This block presents some statistics about your environment.')
16   - end
17   -
18   - def content(args={})
19   - users = owner.people.visible.count
20   - enterprises = owner.enterprises.visible.count
21   - communities = owner.communities.visible.count
22   -
23   - info = []
24   - info << (n_('One user', '%{num} users', users) % { :num => users })
25   - unless owner.enabled?('disable_asset_enterprises')
26   - info << (n_('One enterprise', '%{num} enterprises', enterprises) % { :num => enterprises })
27   - end
28   - info << (n_('One community', '%{num} communities', communities) % { :num => communities })
29   -
30   - block_title(title) + content_tag('ul', info.map {|item| content_tag('li', item) }.join("\n"))
31   - end
32   -
33   -end
app/sweepers/profile_sweeper.rb
... ... @@ -8,9 +8,6 @@ class ProfileSweeper # &lt; ActiveRecord::Observer
8 8 end
9 9  
10 10 def after_create(profile)
11   - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
12   - # the Noosfero core soon, see ActionItem3045
13   - expire_statistics_block_cache(profile)
14 11 end
15 12  
16 13 protected
... ... @@ -31,13 +28,6 @@ protected
31 28 expire_blogs(profile) if profile.organization?
32 29 end
33 30  
34   - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
35   - # the Noosfero core soon, see ActionItem3045
36   - def expire_statistics_block_cache(profile)
37   - blocks = profile.environment.blocks.select { |b| b.kind_of?(EnvironmentStatisticsBlock) }
38   - BlockSweeper.expire_blocks(blocks)
39   - end
40   -
41 31 def expire_blogs(profile)
42 32 profile.blogs.select{|b| !b.empty?}.each do |blog|
43 33 pages = blog.posts.count / blog.posts_per_page + 1
... ...
app/views/cms/edit.html.erb
... ... @@ -31,9 +31,18 @@
31 31  
32 32 <%= select_categories(:article, _('Categorize your article')) %>
33 33  
  34 + <br />
  35 +
34 36 <%= f.text_field('tag_list', :size => 64) %>
35 37 <%= content_tag( 'small', _('Separate tags with commas') ) %>
36 38  
  39 + <script>
  40 + jQuery('#article_tag_list').inputosaurus({
  41 + autoCompleteSource: <%= "'/myprofile/#{profile.identifier}/cms/search_tags'," %>
  42 + activateFinalResult : true
  43 + })
  44 + </script>
  45 +
37 46 <div id='edit-article-options'>
38 47 <%= options_for_article(@article, @tokenized_children) %>
39 48 </div>
... ...
app/views/layouts/_javascript.html.erb
... ... @@ -4,7 +4,7 @@
4 4 'jquery-ui-1.10.4/js/jquery-ui-1.10.4.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate',
5 5 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput',
6 6 'add-and-join', 'report-abuse', 'catalog', 'manage-products', 'autogrow',
7   -'jquery-timepicker-addon/dist/jquery-ui-timepicker-addon', 'application.js', 'rails.js', :cache => 'cache/application' %>
  7 +'jquery-timepicker-addon/dist/jquery-ui-timepicker-addon', 'application.js', 'rails.js', 'inputosaurus.js', :cache => 'cache/application' %>
8 8  
9 9 <% language = FastGettext.locale %>
10 10 <% %w{messages methods}.each do |type| %>
... ...
baseplugins/statistics 0 → 120000
... ... @@ -0,0 +1 @@
  1 +../plugins/statistics
0 2 \ No newline at end of file
... ...
db/migrate/20140827191326_remove_environment_statistics_block.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class RemoveEnvironmentStatisticsBlock < ActiveRecord::Migration
  2 + def self.up
  3 + update("UPDATE blocks SET type = 'StatisticsBlock' WHERE type = 'EnvironmentStatisticsBlock'")
  4 + end
  5 +
  6 + def self.down
  7 + say("Nothing to undo (cannot recover the data)")
  8 + end
  9 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(:version => 20140808185510) do
  14 +ActiveRecord::Schema.define(:version => 20140827191326) do
15 15  
16 16 create_table "abuse_reports", :force => true do |t|
17 17 t.integer "reporter_id"
... ... @@ -241,6 +241,7 @@ ActiveRecord::Schema.define(:version =&gt; 20140808185510) do
241 241 t.string "source_type"
242 242 t.string "user_agent"
243 243 t.string "referrer"
  244 + t.text "setting"
244 245 end
245 246  
246 247 add_index "comments", ["source_id", "spam"], :name => "index_comments_on_source_id_and_spam"
... ... @@ -294,8 +295,8 @@ ActiveRecord::Schema.define(:version =&gt; 20140808185510) do
294 295 t.text "signup_welcome_text"
295 296 t.string "languages"
296 297 t.string "default_language"
297   - t.string "noreply_email"
298 298 t.string "redirection_after_signup", :default => "keep_on_same_page"
  299 + t.string "noreply_email"
299 300 end
300 301  
301 302 create_table "external_feeds", :force => true do |t|
... ... @@ -450,6 +451,21 @@ ActiveRecord::Schema.define(:version =&gt; 20140808185510) do
450 451 add_index "products", ["product_category_id"], :name => "index_products_on_product_category_id"
451 452 add_index "products", ["profile_id"], :name => "index_products_on_profile_id"
452 453  
  454 + create_table "profile_suggestions", :force => true do |t|
  455 + t.integer "person_id"
  456 + t.integer "suggestion_id"
  457 + t.string "suggestion_type"
  458 + t.text "categories"
  459 + t.boolean "enabled", :default => true
  460 + t.float "score", :default => 0.0
  461 + t.datetime "created_at", :null => false
  462 + t.datetime "updated_at", :null => false
  463 + end
  464 +
  465 + add_index "profile_suggestions", ["person_id"], :name => "index_profile_suggestions_on_person_id"
  466 + add_index "profile_suggestions", ["score"], :name => "index_profile_suggestions_on_score"
  467 + add_index "profile_suggestions", ["suggestion_id"], :name => "index_profile_suggestions_on_suggestion_id"
  468 +
453 469 create_table "profiles", :force => true do |t|
454 470 t.string "name"
455 471 t.string "type"
... ... @@ -483,11 +499,12 @@ ActiveRecord::Schema.define(:version =&gt; 20140808185510) do
483 499 t.boolean "is_template", :default => false
484 500 t.integer "template_id"
485 501 t.string "redirection_after_login"
  502 + t.string "personal_website"
  503 + t.string "jabber_id"
486 504 t.integer "friends_count", :default => 0, :null => false
487 505 t.integer "members_count", :default => 0, :null => false
488 506 t.integer "activities_count", :default => 0, :null => false
489   - t.string "personal_website"
490   - t.string "jabber_id"
  507 + t.integer "welcome_page_id"
491 508 end
492 509  
493 510 add_index "profiles", ["activities_count"], :name => "index_profiles_on_activities_count"
... ... @@ -556,6 +573,31 @@ ActiveRecord::Schema.define(:version =&gt; 20140808185510) do
556 573 t.integer "context_id"
557 574 end
558 575  
  576 + create_table "search_term_occurrences", :force => true do |t|
  577 + t.integer "search_term_id"
  578 + t.datetime "created_at"
  579 + t.integer "total", :default => 0
  580 + t.integer "indexed", :default => 0
  581 + end
  582 +
  583 + add_index "search_term_occurrences", ["created_at"], :name => "index_search_term_occurrences_on_created_at"
  584 +
  585 + create_table "search_terms", :force => true do |t|
  586 + t.string "term"
  587 + t.integer "context_id"
  588 + t.string "context_type"
  589 + t.string "asset", :default => "all"
  590 + t.float "score", :default => 0.0
  591 + t.float "relevance_score", :default => 0.0
  592 + t.float "occurrence_score", :default => 0.0
  593 + end
  594 +
  595 + add_index "search_terms", ["asset"], :name => "index_search_terms_on_asset"
  596 + add_index "search_terms", ["occurrence_score"], :name => "index_search_terms_on_occurrence_score"
  597 + add_index "search_terms", ["relevance_score"], :name => "index_search_terms_on_relevance_score"
  598 + add_index "search_terms", ["score"], :name => "index_search_terms_on_score"
  599 + add_index "search_terms", ["term"], :name => "index_search_terms_on_term"
  600 +
559 601 create_table "sessions", :force => true do |t|
560 602 t.string "session_id", :null => false
561 603 t.text "data"
... ... @@ -635,23 +677,25 @@ ActiveRecord::Schema.define(:version =&gt; 20140808185510) do
635 677 create_table "users", :force => true do |t|
636 678 t.string "login"
637 679 t.string "email"
638   - t.string "crypted_password", :limit => 40
639   - t.string "salt", :limit => 40
  680 + t.string "crypted_password", :limit => 40
  681 + t.string "salt", :limit => 40
640 682 t.datetime "created_at"
641 683 t.datetime "updated_at"
642 684 t.string "remember_token"
643 685 t.datetime "remember_token_expires_at"
644 686 t.text "terms_of_use"
645   - t.string "terms_accepted", :limit => 1
  687 + t.string "terms_accepted", :limit => 1
646 688 t.integer "environment_id"
647 689 t.string "password_type"
648   - t.boolean "enable_email", :default => false
649   - t.string "last_chat_status", :default => ""
650   - t.string "chat_status", :default => ""
  690 + t.boolean "enable_email", :default => false
  691 + t.string "last_chat_status", :default => ""
  692 + t.string "chat_status", :default => ""
651 693 t.datetime "chat_status_at"
652   - t.string "activation_code", :limit => 40
  694 + t.string "activation_code", :limit => 40
653 695 t.datetime "activated_at"
654 696 t.string "return_to"
  697 + t.string "private_token"
  698 + t.datetime "private_token_generated_at"
655 699 end
656 700  
657 701 create_table "validation_infos", :force => true do |t|
... ...
debian/control
... ... @@ -7,7 +7,6 @@ Build-Depends:
7 7 debhelper (>= 7.0.50~),
8 8 po4a,
9 9 ruby-gettext,
10   - ruby-gettext-rails,
11 10 ruby-sqlite3,
12 11 rake,
13 12 rails3 (>= 3.2.6-1~),
... ...
public/javascripts/inputosaurus.js 0 → 100644
... ... @@ -0,0 +1,523 @@
  1 +/**
  2 + * Inputosaurus Text
  3 + *
  4 + * Must be instantiated on an <input> element
  5 + * Allows multiple input items. Each item is represented with a removable tag that appears to be inside the input area.
  6 + *
  7 + * @requires:
  8 + *
  9 + * jQuery 1.7+
  10 + * jQueryUI 1.8+ Core
  11 + *
  12 + * @version 0.1.6
  13 + * @author Dan Kielp <dan@sproutsocial.com>
  14 + * @created October 3,2012
  15 + *
  16 + */
  17 +
  18 +
  19 +(function($) {
  20 +
  21 + var inputosaurustext = {
  22 +
  23 + version: "0.1.6",
  24 +
  25 + eventprefix: "inputosaurus",
  26 +
  27 + options: {
  28 +
  29 + // bindable events
  30 + //
  31 + // 'change' - triggered whenever a tag is added or removed (should be similar to binding the the change event of the instantiated input
  32 + // 'keyup' - keyup event on the newly created input
  33 +
  34 + // while typing, the user can separate values using these delimiters
  35 + // the value tags are created on the fly when an inputDelimiter is detected
  36 + inputDelimiters : [',', ';'],
  37 +
  38 + // this separator is used to rejoin all input items back to the value of the original <input>
  39 + outputDelimiter : ',',
  40 +
  41 + allowDuplicates : false,
  42 +
  43 + parseOnBlur : false,
  44 +
  45 + // optional wrapper for widget
  46 + wrapperElement : null,
  47 +
  48 + width : null,
  49 +
  50 + // simply passing an autoComplete source (array, string or function) will instantiate autocomplete functionality
  51 + autoCompleteSource : '',
  52 +
  53 + // When forcing users to select from the autocomplete list, allow them to press 'Enter' to select an item if it's the only option left.
  54 + activateFinalResult : false,
  55 +
  56 + // manipulate and return the input value after parseInput() parsing
  57 + // the array of tag names is passed and expected to be returned as an array after manipulation
  58 + parseHook : null,
  59 +
  60 + // define a placeholder to display when the input is empty
  61 + placeholder: null,
  62 +
  63 + // when you check for duplicates it check for the case
  64 + caseSensitiveDuplicates: false
  65 + },
  66 +
  67 + _create: function() {
  68 + var widget = this,
  69 + els = {},
  70 + o = widget.options,
  71 + placeholder = o.placeholder || this.element.attr('placeholder') || null;
  72 +
  73 + this._chosenValues = [];
  74 +
  75 + // Create the elements
  76 + els.ul = $('<ul class="inputosaurus-container">');
  77 + els.input = $('<input type="text" />');
  78 + els.inputCont = $('<li class="inputosaurus-input inputosaurus-required"></li>');
  79 + els.origInputCont = $('<li class="inputosaurus-input-hidden inputosaurus-required">');
  80 +
  81 + // define starting placeholder
  82 + if (placeholder) {
  83 + o.placeholder = placeholder;
  84 + els.input.attr('placeholder', o.placeholder);
  85 + if (o.width) {
  86 + els.input.css('min-width', o.width - 50);
  87 + }
  88 + }
  89 +
  90 + o.wrapperElement && o.wrapperElement.append(els.ul);
  91 + this.element.replaceWith(o.wrapperElement || els.ul);
  92 + els.origInputCont.append(this.element).hide();
  93 +
  94 + els.inputCont.append(els.input);
  95 + els.ul.append(els.inputCont);
  96 + els.ul.append(els.origInputCont);
  97 +
  98 + o.width && els.ul.css('width', o.width);
  99 +
  100 + this.elements = els;
  101 +
  102 + widget._attachEvents();
  103 +
  104 + // if instantiated input already contains a value, parse that junk
  105 + if($.trim(this.element.val())){
  106 + els.input.val( this.element.val() );
  107 + this.parseInput();
  108 + }
  109 +
  110 + this._instAutocomplete();
  111 + },
  112 +
  113 + _instAutocomplete : function() {
  114 + if(this.options.autoCompleteSource){
  115 + var widget = this;
  116 +
  117 + this.elements.input.autocomplete({
  118 + position : {
  119 + of : this.elements.ul
  120 + },
  121 + source : this.options.autoCompleteSource,
  122 + minLength : 1,
  123 + select : function(ev, ui){
  124 + ev.preventDefault();
  125 + widget.elements.input.val(ui.item.value);
  126 + widget.parseInput();
  127 + },
  128 + open : function() {
  129 + // Older versions of jQueryUI have a different namespace
  130 + var auto = $(this).data('ui-autocomplete') || $(this).data('autocomplete');
  131 + var menu = auto.menu,
  132 + $menuItems;
  133 +
  134 +
  135 + // zIndex will force the element on top of anything (like a dialog it's in)
  136 + menu.element.zIndex && menu.element.zIndex($(this).zIndex() + 1);
  137 + menu.element.width(widget.elements.ul.outerWidth());
  138 +
  139 + // auto-activate the result if it's the only one
  140 + if(widget.options.activateFinalResult){
  141 + $menuItems = menu.element.find('li');
  142 +
  143 + // activate single item to allow selection upon pressing 'Enter'
  144 + if($menuItems.size() === 1){
  145 + menu[menu.activate ? 'activate' : 'focus']($.Event('click'), $menuItems);
  146 + }
  147 + }
  148 + }
  149 + });
  150 + }
  151 + },
  152 +
  153 + _autoCompleteMenuPosition : function() {
  154 + var widget;
  155 + if(this.options.autoCompleteSource){
  156 + widget = this.elements.input.data('ui-autocomplete') || this.elements.input.data('autocomplete');
  157 + widget && widget.menu.element.position({
  158 + of: this.elements.ul,
  159 + my: 'left top',
  160 + at: 'left bottom',
  161 + collision: 'none'
  162 + });
  163 + }
  164 + },
  165 +
  166 + /*_closeAutoCompleteMenu : function() {
  167 + if(this.options.autoCompleteSource){
  168 + this.elements.input.autocomplete('close');
  169 + }
  170 + },*/
  171 +
  172 + parseInput : function(ev) {
  173 + var widget = (ev && ev.data.widget) || this,
  174 + val,
  175 + delimiterFound = false,
  176 + values = [];
  177 +
  178 + val = widget.elements.input.val();
  179 +
  180 + val && (delimiterFound = widget._containsDelimiter(val));
  181 +
  182 + if(delimiterFound !== false){
  183 + values = val.split(delimiterFound);
  184 + } else if(!ev || ev.which === $.ui.keyCode.ENTER && !$('.ui-menu-item.ui-state-focus').size() && !$('.ui-menu-item .ui-state-focus').size() && !$('#ui-active-menuitem').size()){
  185 + values.push(val);
  186 + ev && ev.preventDefault();
  187 +
  188 + // prevent autoComplete menu click from causing a false 'blur'
  189 + } else if(ev.type === 'blur' && !$('#ui-active-menuitem').size()){
  190 + values.push(val);
  191 + }
  192 +
  193 + $.isFunction(widget.options.parseHook) && (values = widget.options.parseHook(values));
  194 +
  195 + if(values.length){
  196 + widget._setChosen(values);
  197 + widget.elements.input.val('');
  198 + widget._resizeInput();
  199 + }
  200 +
  201 + widget._resetPlaceholder();
  202 + },
  203 +
  204 + _inputFocus : function(ev) {
  205 + var widget = ev.data.widget || this;
  206 +
  207 + widget.elements.input.value || (widget.options.autoCompleteSource.length && widget.elements.input.autocomplete('search', ''));
  208 + },
  209 +
  210 + _inputKeypress : function(ev) {
  211 + var widget = ev.data.widget || this;
  212 +
  213 + ev.type === 'keyup' && widget._trigger('keyup', ev, widget);
  214 +
  215 + switch(ev.which){
  216 + case $.ui.keyCode.BACKSPACE:
  217 + ev.type === 'keydown' && widget._inputBackspace(ev);
  218 + break;
  219 +
  220 + case $.ui.keyCode.LEFT:
  221 + ev.type === 'keydown' && widget._inputBackspace(ev);
  222 + break;
  223 +
  224 + default :
  225 + widget.parseInput(ev);
  226 + widget._resizeInput(ev);
  227 + }
  228 +
  229 + // reposition autoComplete menu as <ul> grows and shrinks vertically
  230 + if(widget.options.autoCompleteSource){
  231 + setTimeout(function(){widget._autoCompleteMenuPosition.call(widget);}, 200);
  232 + }
  233 + },
  234 +
  235 + // the input dynamically resizes based on the length of its value
  236 + _resizeInput : function(ev) {
  237 + var widget = (ev && ev.data.widget) || this,
  238 + maxWidth = widget.elements.ul.width(),
  239 + val = widget.elements.input.val(),
  240 + txtWidth = 25 + val.length * 8;
  241 +
  242 + widget.elements.input.width(txtWidth < maxWidth ? txtWidth : maxWidth);
  243 + },
  244 +
  245 + // resets placeholder on representative input
  246 + _resetPlaceholder: function () {
  247 + var placeholder = this.options.placeholder,
  248 + input = this.elements.input,
  249 + width = this.options.width || 'inherit';
  250 + if (placeholder && this.element.val().length === 0) {
  251 + input.attr('placeholder', placeholder).css('min-width', width - 50)
  252 + }else {
  253 + input.attr('placeholder', '').css('min-width', 'inherit')
  254 + }
  255 + },
  256 +
  257 + // if our input contains no value and backspace has been pressed, select the last tag
  258 + _inputBackspace : function(ev) {
  259 + var widget = (ev && ev.data.widget) || this;
  260 + lastTag = widget.elements.ul.find('li:not(.inputosaurus-required):last');
  261 +
  262 + // IE goes back in history if the event isn't stopped
  263 + ev.stopPropagation();
  264 +
  265 + if((!$(ev.currentTarget).val() || (('selectionStart' in ev.currentTarget) && ev.currentTarget.selectionStart === 0 && ev.currentTarget.selectionEnd === 0)) && lastTag.size()){
  266 + ev.preventDefault();
  267 + lastTag.find('a').focus();
  268 + }
  269 +
  270 + },
  271 +
  272 + _editTag : function(ev) {
  273 + var widget = (ev && ev.data.widget) || this,
  274 + tagName = '',
  275 + $closest = $(ev.currentTarget).closest('li'),
  276 + tagKey = $closest.data('ui-inputosaurus') || $closest.data('inputosaurus');
  277 +
  278 + if(!tagKey){
  279 + return true;
  280 + }
  281 +
  282 + ev.preventDefault();
  283 +
  284 + $.each(widget._chosenValues, function(i,v) {
  285 + v.key === tagKey && (tagName = v.value);
  286 + });
  287 +
  288 + widget.elements.input.val(tagName);
  289 +
  290 + widget._removeTag(ev);
  291 + widget._resizeInput(ev);
  292 + },
  293 +
  294 + _tagKeypress : function(ev) {
  295 + var widget = ev.data.widget;
  296 + switch(ev.which){
  297 +
  298 + case $.ui.keyCode.BACKSPACE:
  299 + ev && ev.preventDefault();
  300 + ev && ev.stopPropagation();
  301 + $(ev.currentTarget).trigger('click');
  302 + break;
  303 +
  304 + // 'e' - edit tag (removes tag and places value into visible input
  305 + case 69:
  306 + widget._editTag(ev);
  307 + break;
  308 +
  309 + case $.ui.keyCode.LEFT:
  310 + ev.type === 'keydown' && widget._prevTag(ev);
  311 + break;
  312 +
  313 + case $.ui.keyCode.RIGHT:
  314 + ev.type === 'keydown' && widget._nextTag(ev);
  315 + break;
  316 +
  317 + case $.ui.keyCode.DOWN:
  318 + ev.type === 'keydown' && widget._focus(ev);
  319 + break;
  320 + }
  321 + },
  322 +
  323 + // select the previous tag or input if no more tags exist
  324 + _prevTag : function(ev) {
  325 + var widget = (ev && ev.data.widget) || this,
  326 + tag = $(ev.currentTarget).closest('li'),
  327 + previous = tag.prev();
  328 +
  329 + if(previous.is('li')){
  330 + previous.find('a').focus();
  331 + } else {
  332 + widget._focus();
  333 + }
  334 + },
  335 +
  336 + // select the next tag or input if no more tags exist
  337 + _nextTag : function(ev) {
  338 + var widget = (ev && ev.data.widget) || this,
  339 + tag = $(ev.currentTarget).closest('li'),
  340 + next = tag.next();
  341 +
  342 + if(next.is('li:not(.inputosaurus-input)')){
  343 + next.find('a').focus();
  344 + } else {
  345 + widget._focus();
  346 + }
  347 + },
  348 +
  349 + // return the inputDelimiter that was detected or false if none were found
  350 + _containsDelimiter : function(tagStr) {
  351 +
  352 + var found = false;
  353 +
  354 + $.each(this.options.inputDelimiters, function(k,v) {
  355 + if(tagStr.indexOf(v) !== -1){
  356 + found = v;
  357 + }
  358 + });
  359 +
  360 + return found;
  361 + },
  362 +
  363 + _setChosen : function(valArr) {
  364 + var self = this;
  365 +
  366 + if(!$.isArray(valArr)){
  367 + return false;
  368 + }
  369 +
  370 + $.each(valArr, function(k,v) {
  371 + var exists = false,
  372 + obj = {
  373 + key : '',
  374 + value : ''
  375 + };
  376 +
  377 + v = $.trim(v);
  378 +
  379 + $.each(self._chosenValues, function(kk,vv) {
  380 + if(!self.options.caseSensitiveDuplicates){
  381 + vv.value.toLowerCase() === v.toLowerCase() && (exists = true);
  382 + }
  383 + else{
  384 + vv.value === v && (exists = true);
  385 + }
  386 + });
  387 +
  388 + if(v !== '' && (!exists || self.options.allowDuplicates)){
  389 +
  390 + obj.key = 'mi_' + Math.random().toString( 16 ).slice( 2, 10 );
  391 + obj.value = v;
  392 + self._chosenValues.push(obj);
  393 +
  394 + self._renderTags();
  395 + }
  396 + });
  397 + self._setValue(self._buildValue());
  398 + },
  399 +
  400 + _buildValue : function() {
  401 + var widget = this,
  402 + value = '';
  403 +
  404 + $.each(this._chosenValues, function(k,v) {
  405 + value += value.length ? widget.options.outputDelimiter + v.value : v.value;
  406 + });
  407 +
  408 + return value;
  409 + },
  410 +
  411 + _setValue : function(value) {
  412 + var val = this.element.val();
  413 +
  414 + if(val !== value){
  415 + this.element.val(value);
  416 + this._trigger('change');
  417 + }
  418 + },
  419 +
  420 + // @name text for tag
  421 + // @className optional className for <li>
  422 + _createTag : function(name, key, className) {
  423 + className = className ? ' class="' + className + '"' : '';
  424 +
  425 + if(name !== undefined){
  426 + return $('<li' + className + ' data-inputosaurus="' + key + '"><span>' + name + '</span> <a href="javascript:void(0);" class="ficon">&#x2716;</a></li>');
  427 + }
  428 + },
  429 +
  430 + _renderTags : function() {
  431 + var self = this;
  432 +
  433 + this.elements.ul.find('li:not(.inputosaurus-required)').remove();
  434 +
  435 + $.each(this._chosenValues, function(k,v) {
  436 + var el = self._createTag(v.value, v.key);
  437 + self.elements.ul.find('li.inputosaurus-input').before(el);
  438 + });
  439 + },
  440 +
  441 + _removeTag : function(ev) {
  442 + var $closest = $(ev.currentTarget).closest('li'),
  443 + key = $closest.data('ui-inputosaurus') || $closest.data('inputosaurus'),
  444 + indexFound = false,
  445 + widget = (ev && ev.data.widget) || this;
  446 +
  447 +
  448 + $.each(widget._chosenValues, function(k,v) {
  449 + if(key === v.key){
  450 + indexFound = k;
  451 + }
  452 + });
  453 +
  454 + indexFound !== false && widget._chosenValues.splice(indexFound, 1);
  455 +
  456 + widget._setValue(widget._buildValue());
  457 +
  458 + $(ev.currentTarget).closest('li').remove();
  459 + widget.elements.input.focus();
  460 + },
  461 +
  462 + _focus : function(ev) {
  463 + var widget = (ev && ev.data.widget) || this,
  464 + $closest = $(ev.target).closest('li'),
  465 + $data = $closest.data('ui-inputosaurus') || $closest.data('inputosaurus');
  466 +
  467 + if(!ev || !$data){
  468 + widget.elements.input.focus();
  469 + }
  470 + },
  471 +
  472 + _tagFocus : function(ev) {
  473 + $(ev.currentTarget).parent()[ev.type === 'focusout' ? 'removeClass' : 'addClass']('inputosaurus-selected');
  474 + },
  475 +
  476 + refresh : function() {
  477 + var delim = this.options.outputDelimiter,
  478 + val = this.element.val(),
  479 + values = [];
  480 +
  481 + values.push(val);
  482 + delim && (values = val.split(delim));
  483 +
  484 + if(values.length){
  485 + this._chosenValues = [];
  486 +
  487 + $.isFunction(this.options.parseHook) && (values = this.options.parseHook(values));
  488 +
  489 + this._setChosen(values);
  490 + this._renderTags();
  491 + this.elements.input.val('');
  492 + this._resizeInput();
  493 + }
  494 + },
  495 +
  496 + _attachEvents : function() {
  497 + var widget = this;
  498 +
  499 + this.elements.input.on('keyup.inputosaurus', {widget : widget}, this._inputKeypress);
  500 + this.elements.input.on('keydown.inputosaurus', {widget : widget}, this._inputKeypress);
  501 + this.elements.input.on('change.inputosaurus', {widget : widget}, this._inputKeypress);
  502 + this.elements.input.on('focus.inputosaurus', {widget : widget}, this._inputFocus);
  503 + this.options.parseOnBlur && this.elements.input.on('blur.inputosaurus', {widget : widget}, this.parseInput);
  504 +
  505 + this.elements.ul.on('click.inputosaurus', {widget : widget}, this._focus);
  506 + this.elements.ul.on('click.inputosaurus', 'a', {widget : widget}, this._removeTag);
  507 + this.elements.ul.on('dblclick.inputosaurus', 'li', {widget : widget}, this._editTag);
  508 + this.elements.ul.on('focus.inputosaurus', 'a', {widget : widget}, this._tagFocus);
  509 + this.elements.ul.on('blur.inputosaurus', 'a', {widget : widget}, this._tagFocus);
  510 + this.elements.ul.on('keydown.inputosaurus', 'a', {widget : widget}, this._tagKeypress);
  511 + },
  512 +
  513 + _destroy: function() {
  514 + this.elements.input.unbind('.inputosaurus');
  515 +
  516 + this.elements.ul.replaceWith(this.element);
  517 +
  518 + }
  519 + };
  520 +
  521 + $.widget("ui.inputosaurus", inputosaurustext);
  522 +})(jQuery);
  523 +
... ...
public/stylesheets/inputosaurus.css 0 → 100644
... ... @@ -0,0 +1,76 @@
  1 +.inputosaurus-container .ui-autocomplete-input {
  2 + background-color: #ffffff;
  3 +}
  4 +
  5 +.inputosaurus-container {
  6 + /*background-color: #fff;*/
  7 + /*border: 1px solid #bcbec0;*/
  8 + margin: 0;
  9 + padding: 0;
  10 + display: inline-block;
  11 + cursor: text;
  12 + /**font-size: 14px;**/
  13 + /**font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;**/
  14 + max-height: 300px;
  15 + overflow: hidden;
  16 + overflow-y: auto;
  17 +}
  18 +.inputosaurus-container li {
  19 + display: block;
  20 + float: left;
  21 + overflow: hidden;
  22 + margin: 2px 2px 0;
  23 + padding: 2px 3px;
  24 + white-space: nowrap;
  25 + overflow: hidden;
  26 + -o-text-overflow: ellipsis;
  27 + -ms-text-overflow: ellipsis;
  28 + text-overflow: ellipsis;
  29 + background-color: #e5eff7;
  30 + border: #a9cae4 solid 1px;
  31 + -webkit-border-radius: 2px;
  32 + -moz-border-radius: 2px;
  33 + border-radius: 2px;
  34 + color: #5b9bcd;
  35 + -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.75) inset;
  36 + -moz-box-shadow: 0 1px 0 rgba(255,255,255,0.75) inset;
  37 + box-shadow: 0 1px 0 rgba(255,255,255,0.75) inset;
  38 + line-height: 20px;
  39 + cursor: default;
  40 +}
  41 +.inputosaurus-container li.inputosaurus-selected { background-color: #bdd6eb; }
  42 +.inputosaurus-container li a {
  43 + font-size: 16px;
  44 + color: #5b9bcd;
  45 + padding: 1px;
  46 + text-decoration: none;
  47 + outline: none;
  48 +}
  49 +.inputosaurus-container .inputosaurus-input {
  50 + border: none;
  51 + box-shadow: none;
  52 +}
  53 +.inputosaurus-container .inputosaurus-input input {
  54 + border: none;
  55 + height: 23px;
  56 + font-size: 14px;
  57 + line-height: 20px;
  58 + color: #555;
  59 + margin: 0;
  60 + outline: none;
  61 + padding: 0 0 1px 1px;
  62 + width: 25px;
  63 + -webkit-box-shadow: none;
  64 + -moz-box-shadow: none;
  65 + box-shadow: none;
  66 + background-image: none;
  67 + text-indent: 0px;
  68 +}
  69 +
  70 +.inputosaurus-container .inputosaurus-input input:hover {
  71 + -webkit-box-shadow: none;
  72 + -moz-box-shadow: none;
  73 + box-shadow: none;
  74 + background-color: #f0f0f0;
  75 +}
  76 +.inputosaurus-input-hidden { display: none; }
... ...
script/noosfero-plugins
... ... @@ -77,8 +77,7 @@ run(){
77 77  
78 78 _enable(){
79 79 plugin="$1"
80   - cd $enabled_plugins_dir
81   - source="../../plugins/$plugin"
  80 + source="$available_plugins_dir/$plugin"
82 81 target="$enabled_plugins_dir/$plugin"
83 82 base="$base_plugins_dir/$plugin"
84 83 run "$source/before_enable.rb"
... ... @@ -101,15 +100,11 @@ _enable(){
101 100 fi
102 101 fi
103 102 if [ "$installation_ok" = true ] && [ "$dependencies_ok" = true ]; then
104   - ln -s "$source" "$plugin"
  103 + ln -s "$source" "$target"
105 104 plugins_public_dir="$NOOSFERO_DIR/public/plugins"
106 105 plugins_features_dir="$NOOSFERO_DIR/features/plugins"
107   - cd $plugins_public_dir
108   - test -d "$source/public" && ln -s "$source/public" "$plugin"
109   - if [ -d "$NOOSFERO_DIR/features" ]; then
110   - cd $plugins_features_dir
111   - test -d "$source/features" && ln -s "$source/features" "$plugin"
112   - fi
  106 + test -d "$target/public" && ln -s "$target/public" "$plugins_public_dir/$plugin"
  107 + test -d "$NOOSFERO_DIR/features" && test -d "$target/features" && ln -s "$target/features" "$plugins_features_dir/$plugin"
113 108 _say "$plugin enabled"
114 109 run "$source/after_enable.rb"
115 110 needs_migrate=true
... ...
script/quick-start
... ... @@ -85,7 +85,7 @@ fi
85 85 run rake db:schema:load
86 86 run rake db:data:minimal
87 87 run rake db:test:prepare
88   -run rails runner 'Environment.default.enable("skip_new_user_email_confirmation")'
  88 +rails runner 'Environment.default.enable("skip_new_user_email_confirmation")'
89 89  
90 90 # FIXME compile translations depends on ruby-gettext-rails, please see debian/control
91 91 # run rake noosfero:translations:compile
... ...
test/functional/cms_controller_test.rb
... ... @@ -1807,6 +1807,23 @@ class CmsControllerTest &lt; ActionController::TestCase
1807 1807 assert_template 'cms/publish'
1808 1808 end
1809 1809  
  1810 + should 'response of search_tags be json' do
  1811 + get :search_tags, :profile => profile.identifier, :term => 'linux'
  1812 + assert_equal 'application/json', @response.content_type
  1813 + end
  1814 +
  1815 + should 'return empty json if does not find tag' do
  1816 + get :search_tags, :profile => profile.identifier, :term => 'linux'
  1817 + assert_equal "[]", @response.body
  1818 + end
  1819 +
  1820 + should 'return tags found' do
  1821 + tag = mock; tag.stubs(:name).returns('linux')
  1822 + ActsAsTaggableOn::Tag.stubs(:find).returns([tag])
  1823 + get :search_tags, :profile => profile.identifier, :term => 'linux'
  1824 + assert_equal '[{"label":"linux","value":"linux"}]', @response.body
  1825 + end
  1826 +
1810 1827 protected
1811 1828  
1812 1829 # FIXME this is to avoid adding an extra dependency for a proper JSON parser.
... ...
test/functional/environment_design_controller_test.rb
... ... @@ -6,9 +6,7 @@ class EnvironmentDesignController; def rescue_action(e) raise e end; end
6 6  
7 7 class EnvironmentDesignControllerTest < ActionController::TestCase
8 8  
9   - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
10   - # the Noosfero core soon, see ActionItem3045
11   - ALL_BLOCKS = [ArticleBlock, LoginBlock, EnvironmentStatisticsBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ]
  9 + ALL_BLOCKS = [ArticleBlock, LoginBlock, RecentDocumentsBlock, EnterprisesBlock, CommunitiesBlock, SellersSearchBlock, LinkListBlock, FeedReaderBlock, SlideshowBlock, HighlightsBlock, FeaturedProductsBlock, CategoriesBlock, RawHTMLBlock, TagsBlock ]
12 10  
13 11 def setup
14 12 @controller = EnvironmentDesignController.new
... ... @@ -77,18 +75,6 @@ class EnvironmentDesignControllerTest &lt; ActionController::TestCase
77 75 assert_tag :tag => 'p', :attributes => { :id => 'no_portal_community' }
78 76 end
79 77  
80   - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
81   - # the Noosfero core soon, see ActionItem3045
82   - should 'be able to edit EnvironmentStatisticsBlock' do
83   - login_as(create_admin_user(Environment.default))
84   - b = EnvironmentStatisticsBlock.create!
85   - e = Environment.default
86   - e.boxes.create!
87   - e.boxes.first.blocks << b
88   - get :edit, :id => b.id
89   - assert_tag :tag => 'input', :attributes => { :id => 'block_title' }
90   - end
91   -
92 78 should 'be able to edit EnterprisesBlock' do
93 79 login_as(create_admin_user(Environment.default))
94 80 b = EnterprisesBlock.create!
... ...
test/unit/box_test.rb
... ... @@ -33,9 +33,6 @@ class BoxTest &lt; ActiveSupport::TestCase
33 33 assert blocks.include?('categories-block')
34 34 assert blocks.include?('communities-block')
35 35 assert blocks.include?('enterprises-block')
36   - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
37   - # the Noosfero core soon, see ActionItem3045
38   - assert blocks.include?('environment-statistics-block')
39 36 assert blocks.include?('fans-block')
40 37 assert blocks.include?('favorite-enterprises-block')
41 38 assert blocks.include?('feed-reader-block')
... ... @@ -64,9 +61,6 @@ class BoxTest &lt; ActiveSupport::TestCase
64 61 assert blocks.include?('communities-block')
65 62 assert blocks.include?('disabled-enterprise-message-block')
66 63 assert blocks.include?('enterprises-block')
67   - # TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
68   - # the Noosfero core soon, see ActionItem3045
69   - assert blocks.include?('environment-statistics-block')
70 64 assert blocks.include?('fans-block')
71 65 assert blocks.include?('favorite-enterprises-block')
72 66 assert blocks.include?('featured-products-block')
... ...
test/unit/environment_statistics_block_test.rb
... ... @@ -1,99 +0,0 @@
1   -# TODO EnvironmentStatisticsBlock is DEPRECATED and will be removed from
2   -# the Noosfero core soon, see ActionItem3045
3   -
4   -require File.dirname(__FILE__) + '/../test_helper'
5   -
6   -class EnvironmentStatisticsBlockTest < ActiveSupport::TestCase
7   -
8   - should 'inherit from Block' do
9   - assert_kind_of Block, EnvironmentStatisticsBlock.new
10   - end
11   -
12   - should 'describe itself' do
13   - assert_not_equal Block.description, EnvironmentStatisticsBlock.description
14   - end
15   -
16   - should 'provide a default title' do
17   - owner = mock
18   - owner.expects(:name).returns('my environment')
19   -
20   - block = EnvironmentStatisticsBlock.new
21   - block.expects(:owner).returns(owner)
22   - assert_equal 'Statistics for my environment', block.title
23   - end
24   -
25   - should 'generate statistics' do
26   - env = create(Environment)
27   - user1 = create_user('testuser1', :environment_id => env.id)
28   - user2 = create_user('testuser2', :environment_id => env.id)
29   -
30   - fast_create(Enterprise, :environment_id => env.id)
31   - fast_create(Community, :environment_id => env.id)
32   -
33   - block = EnvironmentStatisticsBlock.new
34   - env.boxes.first.blocks << block
35   -
36   - content = block.content
37   -
38   - assert_match(/One enterprise/, content)
39   - assert_match(/2 users/, content)
40   - assert_match(/One community/, content)
41   - end
42   -
43   - should 'generate statistics including private profiles' do
44   - env = create(Environment)
45   - user1 = create_user('testuser1', :environment_id => env.id)
46   - user2 = create_user('testuser2', :environment_id => env.id)
47   - user3 = create_user('testuser3', :environment_id => env.id)
48   - p = user3.person; p.public_profile = false; p.save!
49   -
50   - fast_create(Enterprise, :environment_id => env.id)
51   - fast_create(Enterprise, :environment_id => env.id, :public_profile => false)
52   -
53   - fast_create(Community, :environment_id => env.id)
54   - fast_create(Community, :environment_id => env.id, :public_profile => false)
55   -
56   - block = EnvironmentStatisticsBlock.new
57   - env.boxes.first.blocks << block
58   -
59   - content = block.content
60   -
61   - assert_match /2 enterprises/, content
62   - assert_match /3 users/, content
63   - assert_match /2 communities/, content
64   - end
65   -
66   - should 'generate statistics but not for not visible profiles' do
67   - env = create(Environment)
68   - user1 = create_user('testuser1', :environment_id => env.id)
69   - user2 = create_user('testuser2', :environment_id => env.id)
70   - user3 = create_user('testuser3', :environment_id => env.id)
71   - p = user3.person; p.visible = false; p.save!
72   -
73   - fast_create(Enterprise, :environment_id => env.id)
74   - fast_create(Enterprise, :environment_id => env.id, :visible => false)
75   -
76   - fast_create(Community, :environment_id => env.id)
77   - fast_create(Community, :environment_id => env.id, :visible => false)
78   -
79   - block = EnvironmentStatisticsBlock.new
80   - env.boxes.first.blocks << block
81   -
82   - content = block.content
83   -
84   - assert_match /One enterprise/, content
85   - assert_match /2 users/, content
86   - assert_match /One community/, content
87   - end
88   -
89   - should 'not display enterprises if disabled' do
90   - env = fast_create(Environment)
91   - env.enable('disable_asset_enterprises', false)
92   -
93   - block = EnvironmentStatisticsBlock.new
94   - block.stubs(:owner).returns(env)
95   -
96   - assert_no_match /enterprises/i, block.content
97   - end
98   -
99   -end