From af3a2f883f6a6bafb2560c562fa41601f400582a Mon Sep 17 00:00:00 2001 From: emyuu Date: Wed, 6 Jul 2016 13:33:51 +0200 Subject: [PATCH 1/5] modified the create_version function and edited the view so that editing older versions works and the diff dropdown works. also modified all relevant tests that didn't pass anymore because of the changes. The parentlist also got modified and works as intended now. --- app/models/post.rb | 35 ++++++++++++++++++++---- app/views/posts/_options.html.erb | 18 ++++++++---- app/views/posts/parentlist.html.erb | 13 +++++++-- test/functional/posts_controller_test.rb | 24 ++++++++-------- test/unit/post_test.rb | 17 ++++++------ 5 files changed, 74 insertions(+), 33 deletions(-) diff --git a/app/models/post.rb b/app/models/post.rb index bcccf31..af7e1ac 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -63,11 +63,28 @@ def all_comments end def create_version(params) - child = self.class.new(params) - child.parent_id = self.id - self.newest = false - self.save - return child + if self.newest != true + children_ids = self.children.map{|x| x.id} + newest_post = Post.where(:id => children_ids, "newest" => true).first + former_newest_params = newest_post.serializable_hash + former_newest_params.delete("id") + former_newest_params["newest"] = false + former_newest = self.class.new(former_newest_params) + former_newest.save + newest_post.update_attributes(params) + newest_post.parent_id = former_newest.id + return newest_post + else + old_params = self.serializable_hash + old_params.delete("id") + parent = self.class.new(old_params) + parent.newest = false + self.newest = true + parent.save + self.parent_id = parent.id + self.update_attributes(params) + return self + end end def self.new_from_file(params, data) @@ -136,8 +153,14 @@ def diff_to_parent(post_id = nil) end end + def children + child = Post.where(:parent_id => self.id).first + return [] if child.nil? + return child.children + [child] + end + def parents return [] if parent_id.nil? - return parent.parents + [parent] + return [parent] + parent.parents end end diff --git a/app/views/posts/_options.html.erb b/app/views/posts/_options.html.erb index 8be4cd4..bfb26aa 100644 --- a/app/views/posts/_options.html.erb +++ b/app/views/posts/_options.html.erb @@ -16,10 +16,11 @@ <% end %> <% end %> - <% if @post.parent.present? && - !current_page?(root_url) && !current_page?(posts_path) %> + <% if !current_page?(root_url) && !current_page?(posts_path) %> + <%= !@post.parent.nil? ? parent_placeholder = + @post.parent.id.to_s : parent_placeholder = "" %> <%= text_field_tag(:diff_id, nil, :class => "input-sm", - :placeholder => @post.parent.id.to_s) %> + :placeholder => parent_placeholder) %> <%= button_tag(:type => "submit", :class => "btn btn-mini", :title => "Diff") do %> @@ -30,13 +31,20 @@ <% end %> diff --git a/app/views/posts/parentlist.html.erb b/app/views/posts/parentlist.html.erb index 869f60a..8b94fb1 100644 --- a/app/views/posts/parentlist.html.erb +++ b/app/views/posts/parentlist.html.erb @@ -1,6 +1,15 @@
- <% @post.parents.each.with_index(1) do |parent, id| %> -
  • <%= link_to(diff_post_path(@post, :diff_id => parent.id)){"Version: #{id}"} %>
  • + <% children = @post.children %> + <% parents = @post.parents %> + <% children.each.with_index(0) do |child, id| %> +
  • <%= link_to(diff_post_path(@post, + :diff_id => child.id)){"Version: #{parents.size + 1 + children.size - id}"} %>
  • + <% end %> +
  • <%= link_to(diff_post_path(@post, + :diff_id => @post.id)) { "Version: #{parents.size + 1} (self)"} %>
  • + <% parents.each.with_index(0) do |parent, id| %> +
  • <%= link_to(diff_post_path(@post, + :diff_id => parent.id)){"Version: #{parents.size - id}"} %>
  • <% end %>
    diff --git a/test/functional/posts_controller_test.rb b/test/functional/posts_controller_test.rb index ee2452d..1c6b950 100644 --- a/test/functional/posts_controller_test.rb +++ b/test/functional/posts_controller_test.rb @@ -80,18 +80,18 @@ class PostsControllerTest < ActionController::TestCase assert_nil assigns(:post).parent_id end - test "should update post with a new post and old post as parent" do + test "should update post with an old post and a new post as parent" do put :update, id: @post, post: { content: @post.content + "new", title: @post.title } - assert_redirected_to post_path(assigns(:post)) - assert_not_equal assigns(:post).id, @post.id - assert_equal assigns(:post).parent_id, @post.id - @post.reload - assert_equal false, @post.newest + assert_redirected_to post_path(@post) + assert_equal assigns(:post).id, @post.id + assert_not_equal assigns(:post).parent_id, @post.parent_id + assigns(:post).reload + assert_equal true, @post.newest assert_equal true, assigns(:post).newest end - test "should update post with a new post from file and old post as parent" do + test "should update post with an old post from file and a new post as parent" do test_image = "test/fixtures/test.txt" file = Rack::Test::UploadedFile.new(test_image, "text/plain") @@ -101,11 +101,11 @@ class PostsControllerTest < ActionController::TestCase author: @post.author, upload_file: file } - assert_redirected_to post_path(assigns(:post)) - assert_not_equal assigns(:post).id, @post.id - assert_equal assigns(:post).parent_id, @post.id - @post.reload - assert_equal false, @post.newest + assert_redirected_to post_path(@post) + assert_equal assigns(:post).id, @post.id + assert_not_equal assigns(:post).parent_id, @post.parent_id + assigns(:post).reload + assert_equal true, @post.newest assert_equal true, assigns(:post).newest end diff --git a/test/unit/post_test.rb b/test/unit/post_test.rb index e2d800a..6d6b6ad 100644 --- a/test/unit/post_test.rb +++ b/test/unit/post_test.rb @@ -64,15 +64,16 @@ class PostTest < ActiveSupport::TestCase assert_equal parent, child.parent end - test "should make new version of post" do - assert_not_nil parent = Post.find(1) - assert_not_nil child = parent.create_version({ + test "should stay the same post after creating new version" do + assert_not_nil old_post = Post.find(1) + content_before = old_post.content + old_post.content = old_post.content + "new" + assert_not_nil new_post = old_post.create_version({ :content => "far out"}) - assert_not_equal child, parent - assert child.save - assert_equal parent.id, child.parent_id - assert_not_equal true, parent.newest - assert_equal true, child.newest + assert_equal new_post, old_post + assert_not_equal old_post.content, content_before + assert_not_equal new_post.content, content_before + assert_not_equal new_post.content, new_post.parent.content end test "collect all parent ids" do From 90efb0575189cd7893b58adbfb6a1f6c51f648b0 Mon Sep 17 00:00:00 2001 From: emyuu Date: Thu, 4 Aug 2016 15:42:40 +0200 Subject: [PATCH 2/5] added rudimentary tagging function, still needs to be worked on. --- Gemfile | 13 +- Gemfile.lock | 14 + app/assets/javascripts/application.js | 5 + app/assets/javascripts/posts.js | 19 + app/assets/javascripts/posts.js.coffee | 8 + app/assets/javascripts/tags.js | 6 + app/assets/stylesheets/application.css.scss | 2 + app/controllers/posts_controller.rb | 5 + app/controllers/tags_controller.rb | 18 + app/models/post.rb | 10 + .../acts_as_taggable_on/tags/._tag.html.erb | 1 + app/views/posts/_form.html.erb | 15 +- app/views/posts/_post.html.erb | 4 +- app/views/posts/show.html.erb | 3 + app/views/tags/index.html.erb | 1 + app/views/tags/show.html.erb | 7 + config/application.rb | 3 +- config/initializers/acts_as_taggable_on.rb | 2 + config/routes.rb | 6 + ...on_migration.acts_as_taggable_on_engine.rb | 31 + ...ique_indices.acts_as_taggable_on_engine.rb | 20 + ...ache_to_tags.acts_as_taggable_on_engine.rb | 15 + ...ggable_index.acts_as_taggable_on_engine.rb | 10 + ...or_tag_names.acts_as_taggable_on_engine.rb | 10 + db/schema.rb | 22 +- lib/extended/tag_extend.rb | 12 + .../assets/javascripts/jquery.tokeninput.js | 1106 +++++++++++++++++ vendor/assets/stylesheets/token-input.css | 127 ++ vendor/cache/actionmailer-4.0.1.gem | Bin 0 -> 23040 bytes vendor/cache/actionpack-4.0.1.gem | Bin 0 -> 335872 bytes vendor/cache/activemodel-4.0.1.gem | Bin 0 -> 46080 bytes vendor/cache/activerecord-4.0.1.gem | Bin 0 -> 305152 bytes .../activerecord-deprecated_finders-1.0.3.gem | Bin 0 -> 14848 bytes vendor/cache/activesupport-4.0.1.gem | Bin 0 -> 310272 bytes vendor/cache/acts-as-taggable-on-3.5.0.gem | Bin 0 -> 49152 bytes vendor/cache/arel-4.0.1.gem | Bin 0 -> 52736 bytes vendor/cache/atomic-1.1.14.gem | Bin 0 -> 16896 bytes vendor/cache/bcrypt-ruby-3.1.2.gem | Bin 0 -> 42496 bytes vendor/cache/bootstrap-sass-2.3.2.0.gem | Bin 0 -> 81920 bytes vendor/cache/builder-3.1.4.gem | Bin 0 -> 26112 bytes vendor/cache/charlock_holmes-0.6.9.4.gem | Bin 0 -> 651776 bytes vendor/cache/coffee-rails-4.0.0.gem | Bin 0 -> 8704 bytes vendor/cache/coffee-script-2.2.0.gem | Bin 0 -> 5120 bytes vendor/cache/coffee-script-source-1.6.2.gem | Bin 0 -> 51200 bytes vendor/cache/daemons-1.1.9.gem | Bin 0 -> 32768 bytes vendor/cache/devise-3.0.0.rc.gem | Bin 0 -> 151040 bytes vendor/cache/diffy-2.1.4.gem | Bin 0 -> 14336 bytes vendor/cache/erubis-2.7.0.gem | Bin 0 -> 173056 bytes vendor/cache/escape_utils-0.2.4.gem | Bin 0 -> 23040 bytes vendor/cache/eventmachine-1.0.3.gem | Bin 0 -> 225792 bytes vendor/cache/execjs-1.4.0.gem | Bin 0 -> 15360 bytes vendor/cache/github-linguist-2.3.4.gem | Bin 0 -> 174080 bytes vendor/cache/hike-1.2.3.gem | Bin 0 -> 8704 bytes vendor/cache/i18n-0.6.5.gem | Bin 0 -> 62464 bytes vendor/cache/jquery-rails-3.0.1.gem | Bin 0 -> 185856 bytes vendor/cache/mail-2.5.4.gem | Bin 0 -> 272384 bytes vendor/cache/mime-types-1.25.gem | Bin 0 -> 60928 bytes vendor/cache/minitest-4.7.5.gem | Bin 0 -> 57856 bytes vendor/cache/multi_json-1.8.2.gem | Bin 0 -> 29696 bytes vendor/cache/orm_adapter-0.4.0.gem | Bin 0 -> 14848 bytes vendor/cache/polyglot-0.3.3.gem | Bin 0 -> 6656 bytes vendor/cache/posix-spawn-0.3.6.gem | Bin 0 -> 25600 bytes vendor/cache/protected_attributes-1.1.3.gem | Bin 0 -> 15360 bytes vendor/cache/rack-1.5.2.gem | Bin 0 -> 216576 bytes vendor/cache/rack-test-0.6.2.gem | Bin 0 -> 22528 bytes vendor/cache/rails-4.0.1.gem | Bin 0 -> 1548800 bytes vendor/cache/rails-assets-chosen-1.6.1.gem | Bin 0 -> 18432 bytes vendor/cache/rails-assets-jquery-3.1.0.gem | Bin 0 -> 145408 bytes .../cache/rails-jquery-tokeninput-0.2.6.gem | Bin 0 -> 26112 bytes vendor/cache/railties-4.0.1.gem | Bin 0 -> 117248 bytes vendor/cache/rake-10.1.0.gem | Bin 0 -> 123904 bytes vendor/cache/redcarpet-3.3.4.gem | Bin 0 -> 60416 bytes vendor/cache/sass-3.2.9.gem | Bin 0 -> 259584 bytes vendor/cache/sass-rails-4.0.0.gem | Bin 0 -> 91648 bytes vendor/cache/sprockets-2.10.0.gem | Bin 0 -> 42496 bytes vendor/cache/sprockets-rails-2.0.1.gem | Bin 0 -> 11264 bytes vendor/cache/sqlite3-1.3.7.gem | Bin 0 -> 57856 bytes vendor/cache/thin-1.6.1.gem | Bin 0 -> 58368 bytes vendor/cache/thor-0.18.1.gem | Bin 0 -> 83456 bytes vendor/cache/thread_safe-0.1.3.gem | Bin 0 -> 136704 bytes vendor/cache/tilt-1.4.1.gem | Bin 0 -> 42496 bytes vendor/cache/treetop-1.4.15.gem | Bin 0 -> 78336 bytes vendor/cache/tzinfo-0.3.38.gem | Bin 0 -> 312832 bytes vendor/cache/uglifier-2.1.1.gem | Bin 0 -> 72192 bytes vendor/cache/warden-1.2.3.gem | Bin 0 -> 30720 bytes vendor/cache/yajl-ruby-1.1.0.gem | Bin 0 -> 557056 bytes 86 files changed, 1488 insertions(+), 7 deletions(-) create mode 100644 app/assets/javascripts/posts.js.coffee create mode 100644 app/assets/javascripts/tags.js create mode 100644 app/controllers/tags_controller.rb create mode 100644 app/views/acts_as_taggable_on/tags/._tag.html.erb create mode 100644 app/views/tags/index.html.erb create mode 100644 app/views/tags/show.html.erb create mode 100644 config/initializers/acts_as_taggable_on.rb create mode 100644 db/migrate/20160707134101_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb create mode 100644 db/migrate/20160707134102_add_missing_unique_indices.acts_as_taggable_on_engine.rb create mode 100644 db/migrate/20160707134103_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb create mode 100644 db/migrate/20160707134104_add_missing_taggable_index.acts_as_taggable_on_engine.rb create mode 100644 db/migrate/20160707134105_change_collation_for_tag_names.acts_as_taggable_on_engine.rb create mode 100644 lib/extended/tag_extend.rb create mode 100755 vendor/assets/javascripts/jquery.tokeninput.js create mode 100644 vendor/assets/stylesheets/token-input.css create mode 100644 vendor/cache/actionmailer-4.0.1.gem create mode 100644 vendor/cache/actionpack-4.0.1.gem create mode 100644 vendor/cache/activemodel-4.0.1.gem create mode 100644 vendor/cache/activerecord-4.0.1.gem create mode 100644 vendor/cache/activerecord-deprecated_finders-1.0.3.gem create mode 100644 vendor/cache/activesupport-4.0.1.gem create mode 100644 vendor/cache/acts-as-taggable-on-3.5.0.gem create mode 100644 vendor/cache/arel-4.0.1.gem create mode 100644 vendor/cache/atomic-1.1.14.gem create mode 100644 vendor/cache/bcrypt-ruby-3.1.2.gem create mode 100644 vendor/cache/bootstrap-sass-2.3.2.0.gem create mode 100644 vendor/cache/builder-3.1.4.gem create mode 100644 vendor/cache/charlock_holmes-0.6.9.4.gem create mode 100644 vendor/cache/coffee-rails-4.0.0.gem create mode 100644 vendor/cache/coffee-script-2.2.0.gem create mode 100644 vendor/cache/coffee-script-source-1.6.2.gem create mode 100644 vendor/cache/daemons-1.1.9.gem create mode 100644 vendor/cache/devise-3.0.0.rc.gem create mode 100644 vendor/cache/diffy-2.1.4.gem create mode 100644 vendor/cache/erubis-2.7.0.gem create mode 100644 vendor/cache/escape_utils-0.2.4.gem create mode 100644 vendor/cache/eventmachine-1.0.3.gem create mode 100644 vendor/cache/execjs-1.4.0.gem create mode 100644 vendor/cache/github-linguist-2.3.4.gem create mode 100644 vendor/cache/hike-1.2.3.gem create mode 100644 vendor/cache/i18n-0.6.5.gem create mode 100644 vendor/cache/jquery-rails-3.0.1.gem create mode 100644 vendor/cache/mail-2.5.4.gem create mode 100644 vendor/cache/mime-types-1.25.gem create mode 100644 vendor/cache/minitest-4.7.5.gem create mode 100644 vendor/cache/multi_json-1.8.2.gem create mode 100644 vendor/cache/orm_adapter-0.4.0.gem create mode 100644 vendor/cache/polyglot-0.3.3.gem create mode 100644 vendor/cache/posix-spawn-0.3.6.gem create mode 100644 vendor/cache/protected_attributes-1.1.3.gem create mode 100644 vendor/cache/rack-1.5.2.gem create mode 100644 vendor/cache/rack-test-0.6.2.gem create mode 100644 vendor/cache/rails-4.0.1.gem create mode 100644 vendor/cache/rails-assets-chosen-1.6.1.gem create mode 100644 vendor/cache/rails-assets-jquery-3.1.0.gem create mode 100644 vendor/cache/rails-jquery-tokeninput-0.2.6.gem create mode 100644 vendor/cache/railties-4.0.1.gem create mode 100644 vendor/cache/rake-10.1.0.gem create mode 100644 vendor/cache/redcarpet-3.3.4.gem create mode 100644 vendor/cache/sass-3.2.9.gem create mode 100644 vendor/cache/sass-rails-4.0.0.gem create mode 100644 vendor/cache/sprockets-2.10.0.gem create mode 100644 vendor/cache/sprockets-rails-2.0.1.gem create mode 100644 vendor/cache/sqlite3-1.3.7.gem create mode 100644 vendor/cache/thin-1.6.1.gem create mode 100644 vendor/cache/thor-0.18.1.gem create mode 100644 vendor/cache/thread_safe-0.1.3.gem create mode 100644 vendor/cache/tilt-1.4.1.gem create mode 100644 vendor/cache/treetop-1.4.15.gem create mode 100644 vendor/cache/tzinfo-0.3.38.gem create mode 100644 vendor/cache/uglifier-2.1.1.gem create mode 100644 vendor/cache/warden-1.2.3.gem create mode 100644 vendor/cache/yajl-ruby-1.1.0.gem diff --git a/Gemfile b/Gemfile index 1c8ec24..8f525bf 100644 --- a/Gemfile +++ b/Gemfile @@ -12,8 +12,16 @@ gem 'railties' gem 'coffee-rails' gem 'bootstrap-sass', "2.3.2.0" gem 'uglifier' - gem 'thin' +gem 'devise', '3.0.0.rc' +gem 'jquery-rails' +gem 'acts-as-taggable-on' +gem 'rails-jquery-tokeninput' +gem 'protected_attributes' + +source 'https://rails-assets.org' do + gem 'rails-assets-chosen' +end # Language detection gem "github-linguist", "~> 2.3.4" , require: "linguist" @@ -21,5 +29,4 @@ gem "github-linguist", "~> 2.3.4" , require: "linguist" # Syntax highlighter gem "pygments.rb", git: "https://github.com/tmm1/pygments.rb", branch: "master" gem 'diffy' -gem 'jquery-rails' -gem 'devise', '3.0.0.rc' + diff --git a/Gemfile.lock b/Gemfile.lock index 4981f2a..aaa3b8d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,6 +9,7 @@ GIT GEM remote: https://rubygems.org/ + remote: https://rails-assets.org/ specs: actionmailer (4.0.1) actionpack (= 4.0.1) @@ -34,6 +35,8 @@ GEM multi_json (~> 1.3) thread_safe (~> 0.1) tzinfo (~> 0.3.37) + acts-as-taggable-on (3.5.0) + activerecord (>= 3.2, < 5) arel (4.0.1) atomic (1.1.14) bcrypt-ruby (3.1.2) @@ -79,6 +82,8 @@ GEM orm_adapter (0.4.0) polyglot (0.3.3) posix-spawn (0.3.6) + protected_attributes (1.1.3) + activemodel (>= 4.0.1, < 5.0) rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) @@ -90,6 +95,11 @@ GEM bundler (>= 1.3.0, < 2.0) railties (= 4.0.1) sprockets-rails (~> 2.0.0) + rails-assets-chosen (1.6.1) + rails-assets-jquery (>= 1.4.4) + rails-assets-jquery (3.1.0) + rails-jquery-tokeninput (0.2.6) + jquery-rails (>= 2) railties (4.0.1) actionpack (= 4.0.1) activesupport (= 4.0.1) @@ -135,14 +145,18 @@ PLATFORMS ruby DEPENDENCIES + acts-as-taggable-on bootstrap-sass (= 2.3.2.0) coffee-rails devise (= 3.0.0.rc) diffy github-linguist (~> 2.3.4) jquery-rails + protected_attributes pygments.rb! rails (~> 4.0.1) + rails-assets-chosen! + rails-jquery-tokeninput railties redcarpet sass-rails diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 5dcb43d..f2ae6c6 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,8 +12,13 @@ // //= require jquery //= require jquery_ujs +//= require jquery.tokeninput //= require_tree . // Loads all Bootstrap javascripts //= require bootstrap // + +//Needed for tag auto completition +//= require chosen +// diff --git a/app/assets/javascripts/posts.js b/app/assets/javascripts/posts.js index f890d83..b49cb95 100644 --- a/app/assets/javascripts/posts.js +++ b/app/assets/javascripts/posts.js @@ -81,5 +81,24 @@ $(document).ready(function() { target.select(); }, 100); }); + + function callUrl() { + return "/tags.json" /*+ ???.filter($("tester").text())*/; + } + $('#post_tag_list_tokens').tokenInput(callUrl, { + minChars: 2, + allowFreeTagging: true, + preventDuplicates: true, + prePopulate: $('#post_tag_list_tokens').data('load') +// onResult: function (tag) { +// if ($.isEmptyObject(tag)) { +// return [{ id: '0', name: $("tester").text() }]; +// } +// else { +// return tag; +// } +// } + }); }); + diff --git a/app/assets/javascripts/posts.js.coffee b/app/assets/javascripts/posts.js.coffee new file mode 100644 index 0000000..dce8845 --- /dev/null +++ b/app/assets/javascripts/posts.js.coffee @@ -0,0 +1,8 @@ +#jQuery -> +# $('#post_tag_list_tokens').tokenInput '/posts/tags.json', +# minChars: 2 +# allowCustomEntry: true +# preventDuplicates: true +# prePopulate: $('#post_tag_list_tokens').data('load') + + diff --git a/app/assets/javascripts/tags.js b/app/assets/javascripts/tags.js new file mode 100644 index 0000000..749c512 --- /dev/null +++ b/app/assets/javascripts/tags.js @@ -0,0 +1,6 @@ +//$ -> +// $("#post_tags").tokenInput "/posts/tags.json", +// prePopulate: $("#post_tags").data("pre") +// preventDuplicates: true +// noResultsText: "No results, press space key to create a new tag." +// animateDropdown: false diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index fb3e57f..f49e327 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -9,7 +9,9 @@ * compiled file, but it's generally better to create a new file per style scope. * *= require_self + *= require token-input *= require_tree . + *= require chosen */ /* compensate the overlap caused by bootstrap top navigation, min. 40px at the top! */ diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index deb7973..64c47a8 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -2,6 +2,7 @@ class PostsController < ApplicationController protect_from_forgery before_filter :verify_api_key, only: [:create] + before_filter :find_tags, only: [:new, :create, :edit, :update] skip_before_filter :verify_authenticity_token, if: :json_request? skip_before_filter :verify_api_key, unless: :json_request? @@ -282,6 +283,10 @@ def verify_api_key end end + def find_tags + @post_tags = params[:id].present? ? Post.find(params[:id]).tags.token_input_tags : [] + end + protected def json_request? diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb new file mode 100644 index 0000000..0edb08d --- /dev/null +++ b/app/controllers/tags_controller.rb @@ -0,0 +1,18 @@ +class TagsController < ApplicationController + def index + @tags = ActsAsTaggableOn::Tag.all.token_input_tags + render :json => @tags + end + + def show + @tag = ActsAsTaggableOn::Tag.find(params[:id]) + @posts = Post.tagged_with(@tag.name) + end + +# def filter +# @tag = ActsAsTaggableOn::Tag.filter(params[:query]) +# respond_to do |format| +# format.html { render :filter } +# end +# end +end diff --git a/app/models/post.rb b/app/models/post.rb index af7e1ac..17249e8 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -1,5 +1,6 @@ class Post < ActiveRecord::Base require 'tempfile' + #require 'acts-as-taggable-on' def self.MaxUploadSize 102400 # 100kb @@ -22,6 +23,11 @@ def self.MaxUploadSize :foreign_key => :parent_id, :dependent => :destroy belongs_to :parent, :class_name => "Post" + acts_as_taggable + + attr_reader :tag_list_tokens + attr_accessible :name, :tag_list_tokens, :title, :author, :content + attr_accessor :uploaded_file def self.file_extensions @@ -163,4 +169,8 @@ def parents return [] if parent_id.nil? return [parent] + parent.parents end + + def tag_list_tokens=(tokens) + self.tag_list = tokens.gsub("'", "") + end end diff --git a/app/views/acts_as_taggable_on/tags/._tag.html.erb b/app/views/acts_as_taggable_on/tags/._tag.html.erb new file mode 100644 index 0000000..fcef4ab --- /dev/null +++ b/app/views/acts_as_taggable_on/tags/._tag.html.erb @@ -0,0 +1 @@ +<%= link_to tag.name, tag_path(tag) %> diff --git a/app/views/posts/_form.html.erb b/app/views/posts/_form.html.erb index 84ac167..c2de8a2 100644 --- a/app/views/posts/_form.html.erb +++ b/app/views/posts/_form.html.erb @@ -45,7 +45,19 @@ :id => "inputContentType")%> - + +
    + +
    + <%= f.text_field :tag_list_tokens, data: {load: @post_tags} %> +
    +
    + + + +
    @@ -70,6 +82,7 @@ :class => "input-xxlarge" } %>
    +
    <%= f.submit :class => "btn pull-right" %>
    diff --git a/app/views/posts/_post.html.erb b/app/views/posts/_post.html.erb index 993e27c..3ce4b3f 100644 --- a/app/views/posts/_post.html.erb +++ b/app/views/posts/_post.html.erb @@ -19,7 +19,9 @@ - <%= @post.updated_at.strftime('%a, %d %b %Y %H:%M:%S') %> -<%= render 'options' %> + + +<%= render 'posts/options' %>

    diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb index 642b7db..39c6050 100644 --- a/app/views/posts/show.html.erb +++ b/app/views/posts/show.html.erb @@ -9,6 +9,9 @@

    - <%= @post.author %>

    <% end %> + <%-# render @post.tags -%> + <%= raw @post.tag_list.join(', ') %> + <%= render 'options' %> diff --git a/app/views/tags/show.html.erb b/app/views/tags/show.html.erb new file mode 100644 index 0000000..3ca4488 --- /dev/null +++ b/app/views/tags/show.html.erb @@ -0,0 +1,7 @@ +

    <%= @tag.name %>

    + <% unless @posts.blank?%> +
    + <%=render :partial => "posts/post", :collection => @posts%> +
    + <% end %> + diff --git a/config/application.rb b/config/application.rb index e13407b..88ca996 100644 --- a/config/application.rb +++ b/config/application.rb @@ -17,8 +17,9 @@ class Application < Rails::Application # Custom directories with classes and modules you want to be autoloadable. # config.autoload_paths += %W(#{config.root}/extras) + config.autoload_paths += %W(#{config.root}/lib/**/) - # Only load the plugins named here, in the order given (default is alphabetical). + # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. # config.plugins = [ :exception_notification, :ssl_requirement, :all ] diff --git a/config/initializers/acts_as_taggable_on.rb b/config/initializers/acts_as_taggable_on.rb new file mode 100644 index 0000000..2354feb --- /dev/null +++ b/config/initializers/acts_as_taggable_on.rb @@ -0,0 +1,2 @@ +require_relative '../../lib/extended/tag_extend.rb' +ActsAsTaggableOn::Tag.send(:include, TagExtend) diff --git a/config/routes.rb b/config/routes.rb index af2f4eb..7f3e702 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,6 +6,9 @@ resources :posts, :path => :p do resources :comments resources :linecomments +# collection do +# get :tags, as: :tags +# end member do get :diff get :like @@ -14,8 +17,11 @@ get :parentlist end end + resources :posts, :as => :p + resources :tags, only: [:index, :show] + #get "posts/tags" => "posts#tags", :as => :tags get '/help' => 'posts#help', :as => :help get '/search' => 'posts#search', :as => :search root :to => 'posts#index' diff --git a/db/migrate/20160707134101_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb b/db/migrate/20160707134101_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000..6bbd559 --- /dev/null +++ b/db/migrate/20160707134101_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb @@ -0,0 +1,31 @@ +# This migration comes from acts_as_taggable_on_engine (originally 1) +class ActsAsTaggableOnMigration < ActiveRecord::Migration + def self.up + create_table :tags do |t| + t.string :name + end + + create_table :taggings do |t| + t.references :tag + + # You should make sure that the column created is + # long enough to store the required class names. + t.references :taggable, polymorphic: true + t.references :tagger, polymorphic: true + + # Limit is created to prevent MySQL error on index + # length for MyISAM table type: http://bit.ly/vgW2Ql + t.string :context, limit: 128 + + t.datetime :created_at + end + + add_index :taggings, :tag_id + add_index :taggings, [:taggable_id, :taggable_type, :context] + end + + def self.down + drop_table :taggings + drop_table :tags + end +end diff --git a/db/migrate/20160707134102_add_missing_unique_indices.acts_as_taggable_on_engine.rb b/db/migrate/20160707134102_add_missing_unique_indices.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000..4ca676f --- /dev/null +++ b/db/migrate/20160707134102_add_missing_unique_indices.acts_as_taggable_on_engine.rb @@ -0,0 +1,20 @@ +# This migration comes from acts_as_taggable_on_engine (originally 2) +class AddMissingUniqueIndices < ActiveRecord::Migration + def self.up + add_index :tags, :name, unique: true + + remove_index :taggings, :tag_id + remove_index :taggings, [:taggable_id, :taggable_type, :context] + add_index :taggings, + [:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type], + unique: true, name: 'taggings_idx' + end + + def self.down + remove_index :tags, :name + + remove_index :taggings, name: 'taggings_idx' + add_index :taggings, :tag_id + add_index :taggings, [:taggable_id, :taggable_type, :context] + end +end diff --git a/db/migrate/20160707134103_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb b/db/migrate/20160707134103_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000..8edb508 --- /dev/null +++ b/db/migrate/20160707134103_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb @@ -0,0 +1,15 @@ +# This migration comes from acts_as_taggable_on_engine (originally 3) +class AddTaggingsCounterCacheToTags < ActiveRecord::Migration + def self.up + add_column :tags, :taggings_count, :integer, default: 0 + + ActsAsTaggableOn::Tag.reset_column_information + ActsAsTaggableOn::Tag.find_each do |tag| + ActsAsTaggableOn::Tag.reset_counters(tag.id, :taggings) + end + end + + def self.down + remove_column :tags, :taggings_count + end +end diff --git a/db/migrate/20160707134104_add_missing_taggable_index.acts_as_taggable_on_engine.rb b/db/migrate/20160707134104_add_missing_taggable_index.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000..71f2d7f --- /dev/null +++ b/db/migrate/20160707134104_add_missing_taggable_index.acts_as_taggable_on_engine.rb @@ -0,0 +1,10 @@ +# This migration comes from acts_as_taggable_on_engine (originally 4) +class AddMissingTaggableIndex < ActiveRecord::Migration + def self.up + add_index :taggings, [:taggable_id, :taggable_type, :context] + end + + def self.down + remove_index :taggings, [:taggable_id, :taggable_type, :context] + end +end diff --git a/db/migrate/20160707134105_change_collation_for_tag_names.acts_as_taggable_on_engine.rb b/db/migrate/20160707134105_change_collation_for_tag_names.acts_as_taggable_on_engine.rb new file mode 100644 index 0000000..bfb06bc --- /dev/null +++ b/db/migrate/20160707134105_change_collation_for_tag_names.acts_as_taggable_on_engine.rb @@ -0,0 +1,10 @@ +# This migration comes from acts_as_taggable_on_engine (originally 5) +# This migration is added to circumvent issue #623 and have special characters +# work properly +class ChangeCollationForTagNames < ActiveRecord::Migration + def up + if ActsAsTaggableOn::Utils.using_mysql? + execute("ALTER TABLE tags MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;") + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 6287fcd..33810df 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20140202010251) do +ActiveRecord::Schema.define(version: 20160707134105) do create_table "apikeys", force: true do |t| t.string "key" @@ -60,6 +60,26 @@ t.string "content_type", default: "None" end + create_table "taggings", force: true do |t| + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context", limit: 128 + t.datetime "created_at" + end + + add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true + add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context" + + create_table "tags", force: true do |t| + t.string "name" + t.integer "taggings_count", default: 0 + end + + add_index "tags", ["name"], name: "index_tags_on_name", unique: true + create_table "users", force: true do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false diff --git a/lib/extended/tag_extend.rb b/lib/extended/tag_extend.rb new file mode 100644 index 0000000..a069938 --- /dev/null +++ b/lib/extended/tag_extend.rb @@ -0,0 +1,12 @@ +module TagExtend + extend ActiveSupport::Concern + + included do + scope :by_tag_name, -> name { where("name like ?", "%#{name}%") } + + def self.token_input_tags + where(nil).map{|t| {id: t.name, name: t.name}} + end + end +end + diff --git a/vendor/assets/javascripts/jquery.tokeninput.js b/vendor/assets/javascripts/jquery.tokeninput.js new file mode 100755 index 0000000..4b69d82 --- /dev/null +++ b/vendor/assets/javascripts/jquery.tokeninput.js @@ -0,0 +1,1106 @@ +/* + * jQuery Plugin: Tokenizing Autocomplete Text Entry + * Version 1.6.2 + * + * Copyright (c) 2009 James Smith (http://loopj.com) + * Licensed jointly under the GPL and MIT licenses, + * choose which one suits your project best! + * + */ +;(function ($) { + var DEFAULT_SETTINGS = { + // Search settings + method: "GET", + queryParam: "q", + searchDelay: 300, + minChars: 1, + propertyToSearch: "name", + jsonContainer: null, + contentType: "json", + excludeCurrent: false, + excludeCurrentParameter: "x", + + // Prepopulation settings + prePopulate: null, + processPrePopulate: false, + + // Display settings + hintText: "Type in a search term", + noResultsText: "No results", + searchingText: "Searching...", + deleteText: "×", + animateDropdown: true, + placeholder: null, + theme: null, + zindex: 999, + resultsLimit: null, + + enableHTML: false, + + resultsFormatter: function(item) { + var string = item[this.propertyToSearch]; + return "
  • " + (this.enableHTML ? string : _escapeHTML(string)) + "
  • "; + }, + + tokenFormatter: function(item) { + var string = item[this.propertyToSearch]; + return "
  • " + (this.enableHTML ? string : _escapeHTML(string)) + "

  • "; + }, + + // Tokenization settings + tokenLimit: null, + tokenDelimiter: ",", + preventDuplicates: false, + tokenValue: "id", + + // Behavioral settings + allowFreeTagging: false, + allowTabOut: false, + autoSelectFirstResult: false, + + // Callbacks + onResult: null, + onCachedResult: null, + onAdd: null, + onFreeTaggingAdd: null, + onDelete: null, + onReady: null, + + // Other settings + idPrefix: "token-input-", + + // Keep track if the input is currently in disabled mode + disabled: false + }; + + // Default classes to use when theming + var DEFAULT_CLASSES = { + tokenList : "token-input-list", + token : "token-input-token", + tokenReadOnly : "token-input-token-readonly", + tokenDelete : "token-input-delete-token", + selectedToken : "token-input-selected-token", + highlightedToken : "token-input-highlighted-token", + dropdown : "token-input-dropdown", + dropdownItem : "token-input-dropdown-item", + dropdownItem2 : "token-input-dropdown-item2", + selectedDropdownItem : "token-input-selected-dropdown-item", + inputToken : "token-input-input-token", + focused : "token-input-focused", + disabled : "token-input-disabled" + }; + + // Input box position "enum" + var POSITION = { + BEFORE : 0, + AFTER : 1, + END : 2 + }; + + // Keys "enum" + var KEY = { + BACKSPACE : 8, + TAB : 9, + ENTER : 13, + ESCAPE : 27, + SPACE : 32, + PAGE_UP : 33, + PAGE_DOWN : 34, + END : 35, + HOME : 36, + LEFT : 37, + UP : 38, + RIGHT : 39, + DOWN : 40, + NUMPAD_ENTER : 108, + COMMA : 188 + }; + + var HTML_ESCAPES = { + '&' : '&', + '<' : '<', + '>' : '>', + '"' : '"', + "'" : ''', + '/' : '/' + }; + + var HTML_ESCAPE_CHARS = /[&<>"'\/]/g; + + function coerceToString(val) { + return String((val === null || val === undefined) ? '' : val); + } + + function _escapeHTML(text) { + return coerceToString(text).replace(HTML_ESCAPE_CHARS, function(match) { + return HTML_ESCAPES[match]; + }); + } + + // Additional public (exposed) methods + var methods = { + init: function(url_or_data_or_function, options) { + var settings = $.extend({}, DEFAULT_SETTINGS, options || {}); + + return this.each(function () { + $(this).data("settings", settings); + $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings)); + }); + }, + clear: function() { + this.data("tokenInputObject").clear(); + return this; + }, + add: function(item) { + this.data("tokenInputObject").add(item); + return this; + }, + remove: function(item) { + this.data("tokenInputObject").remove(item); + return this; + }, + get: function() { + return this.data("tokenInputObject").getTokens(); + }, + toggleDisabled: function(disable) { + this.data("tokenInputObject").toggleDisabled(disable); + return this; + }, + setOptions: function(options){ + $(this).data("settings", $.extend({}, $(this).data("settings"), options || {})); + return this; + }, + destroy: function () { + if (this.data("tokenInputObject")) { + this.data("tokenInputObject").clear(); + var tmpInput = this; + var closest = this.parent(); + closest.empty(); + tmpInput.show(); + closest.append(tmpInput); + return tmpInput; + } + } + }; + + // Expose the .tokenInput function to jQuery as a plugin + $.fn.tokenInput = function (method) { + // Method calling and initialization logic + if (methods[method]) { + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else { + return methods.init.apply(this, arguments); + } + }; + + // TokenList class for each input + $.TokenList = function (input, url_or_data, settings) { + // + // Initialization + // + + // Configure the data source + if (typeof(url_or_data) === "string" || typeof(url_or_data) === "function") { + // Set the url to query against + $(input).data("settings").url = url_or_data; + + // If the URL is a function, evaluate it here to do our initalization work + var url = computeURL(); + + // Make a smart guess about cross-domain if it wasn't explicitly specified + if ($(input).data("settings").crossDomain === undefined && typeof url === "string") { + if(url.indexOf("://") === -1) { + $(input).data("settings").crossDomain = false; + } else { + $(input).data("settings").crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]); + } + } + } else if (typeof(url_or_data) === "object") { + // Set the local data to search through + $(input).data("settings").local_data = url_or_data; + } + + // Build class names + if($(input).data("settings").classes) { + // Use custom class names + $(input).data("settings").classes = $.extend({}, DEFAULT_CLASSES, $(input).data("settings").classes); + } else if($(input).data("settings").theme) { + // Use theme-suffixed default class names + $(input).data("settings").classes = {}; + $.each(DEFAULT_CLASSES, function(key, value) { + $(input).data("settings").classes[key] = value + "-" + $(input).data("settings").theme; + }); + } else { + $(input).data("settings").classes = DEFAULT_CLASSES; + } + + // Save the tokens + var saved_tokens = []; + + // Keep track of the number of tokens in the list + var token_count = 0; + + // Basic cache to save on db hits + var cache = new $.TokenList.Cache(); + + // Keep track of the timeout, old vals + var timeout; + var input_val; + + // Create a new text input an attach keyup events + var input_box = $("") + .css({ + outline: "none" + }) + .attr("id", $(input).data("settings").idPrefix + input.id) + .focus(function () { + if ($(input).data("settings").disabled) { + return false; + } else + if ($(input).data("settings").tokenLimit === null || $(input).data("settings").tokenLimit !== token_count) { + show_dropdown_hint(); + } + token_list.addClass($(input).data("settings").classes.focused); + }) + .blur(function () { + hide_dropdown(); + + if ($(input).data("settings").allowFreeTagging) { + add_freetagging_tokens(); + } + + $(this).val(""); + token_list.removeClass($(input).data("settings").classes.focused); + }) + .bind("keyup keydown blur update", resize_input) + .keydown(function (event) { + var previous_token; + var next_token; + + switch(event.keyCode) { + case KEY.LEFT: + case KEY.RIGHT: + case KEY.UP: + case KEY.DOWN: + if(this.value.length === 0) { + previous_token = input_token.prev(); + next_token = input_token.next(); + + if((previous_token.length && previous_token.get(0) === selected_token) || + (next_token.length && next_token.get(0) === selected_token)) { + // Check if there is a previous/next token and it is selected + if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) { + deselect_token($(selected_token), POSITION.BEFORE); + } else { + deselect_token($(selected_token), POSITION.AFTER); + } + } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) { + // We are moving left, select the previous token if it exists + select_token($(previous_token.get(0))); + } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) { + // We are moving right, select the next token if it exists + select_token($(next_token.get(0))); + } + } else { + var dropdown_item = null; + + if (event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) { + dropdown_item = $(dropdown).find('li').first(); + + if (selected_dropdown_item) { + dropdown_item = $(selected_dropdown_item).next(); + } + } else { + dropdown_item = $(dropdown).find('li').last(); + + if (selected_dropdown_item) { + dropdown_item = $(selected_dropdown_item).prev(); + } + } + + select_dropdown_item(dropdown_item); + } + + break; + + case KEY.BACKSPACE: + previous_token = input_token.prev(); + + if (this.value.length === 0) { + if (selected_token) { + delete_token($(selected_token)); + hiddenInput.change(); + } else if(previous_token.length) { + select_token($(previous_token.get(0))); + } + + return false; + } else if($(this).val().length === 1) { + hide_dropdown(); + } else { + // set a timeout just long enough to let this function finish. + setTimeout(function(){ do_search(); }, 5); + } + break; + + case KEY.TAB: + case KEY.ENTER: + case KEY.NUMPAD_ENTER: + case KEY.COMMA: + if(selected_dropdown_item) { + add_token($(selected_dropdown_item).data("tokeninput")); + hiddenInput.change(); + } else { + if ($(input).data("settings").allowFreeTagging) { + if($(input).data("settings").allowTabOut && $(this).val() === "") { + return true; + } else { + add_freetagging_tokens(); + } + } else { + $(this).val(""); + if($(input).data("settings").allowTabOut) { + return true; + } + } + event.stopPropagation(); + event.preventDefault(); + } + return false; + + case KEY.ESCAPE: + hide_dropdown(); + return true; + + default: + if (String.fromCharCode(event.which)) { + // set a timeout just long enough to let this function finish. + setTimeout(function(){ do_search(); }, 5); + } + break; + } + }); + + // Keep reference for placeholder + if (settings.placeholder) { + input_box.attr("placeholder", settings.placeholder); + } + + // Keep a reference to the original input box + var hiddenInput = $(input) + .hide() + .val("") + .focus(function () { + focusWithTimeout(input_box); + }) + .blur(function () { + input_box.blur(); + + //return the object to this can be referenced in the callback functions. + return hiddenInput; + }) + ; + + // Keep a reference to the selected token and dropdown item + var selected_token = null; + var selected_token_index = 0; + var selected_dropdown_item = null; + + // The list to store the token items in + var token_list = $("