├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Capfile ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE.md ├── README.md ├── Rakefile ├── Vagrantfile ├── app ├── assets │ ├── images │ │ ├── .keep │ │ └── rails.png │ ├── javascripts │ │ ├── application.js.coffee │ │ ├── campo.js.coffee │ │ ├── locales │ │ │ ├── en.js.coffee │ │ │ └── zh-CN.js.coffee │ │ └── plugins │ │ │ ├── comments.js.coffee │ │ │ ├── likes.js.coffee │ │ │ ├── markdown_area.js.coffee │ │ │ ├── pagination_popover.js.coffee │ │ │ ├── turboform.js.coffee │ │ │ ├── validator.js.coffee │ │ │ └── visible_to.js.coffee │ └── stylesheets │ │ ├── application.css.scss │ │ ├── bootstrap_customize.css.scss │ │ ├── comments.css.scss │ │ ├── highlight.css.scss │ │ ├── markdown.css.scss │ │ ├── notifications.css.scss │ │ ├── subscriptions.css.scss │ │ ├── topics.css.scss │ │ └── users.css.scss ├── controllers │ ├── admin │ │ ├── application_controller.rb │ │ ├── attachments_controller.rb │ │ ├── categories_controller.rb │ │ ├── comments_controller.rb │ │ ├── dashboard_controller.rb │ │ ├── friend_sites_controller.rb │ │ ├── topics_controller.rb │ │ └── users_controller.rb │ ├── application_controller.rb │ ├── attachments_controller.rb │ ├── comments_controller.rb │ ├── concerns │ │ └── .keep │ ├── likes_controller.rb │ ├── markdown_controller.rb │ ├── notifications_controller.rb │ ├── qunit_controller.rb │ ├── sessions_controller.rb │ ├── settings │ │ ├── accounts_controller.rb │ │ ├── application_controller.rb │ │ ├── notifications_controller.rb │ │ ├── passwords_controller.rb │ │ └── profiles_controller.rb │ ├── subscriptions_controller.rb │ ├── topics_controller.rb │ ├── users │ │ ├── application_controller.rb │ │ ├── comments_controller.rb │ │ ├── confirmations_controller.rb │ │ ├── passwords_controller.rb │ │ └── topics_controller.rb │ └── users_controller.rb ├── helpers │ ├── admin │ │ ├── comments_helper.rb │ │ └── topics_helper.rb │ ├── application_helper.rb │ ├── comments_helper.rb │ ├── markdown_helper.rb │ ├── notifications_helper.rb │ ├── timeago_helper.rb │ ├── topics_helper.rb │ └── useres_helper.rb ├── jobs │ └── comment_notification_job.rb ├── mailers │ ├── .keep │ ├── notification_mailer.rb │ └── user_mailer.rb ├── models │ ├── .keep │ ├── attachment.rb │ ├── category.rb │ ├── comment.rb │ ├── concerns │ │ ├── .keep │ │ ├── likeable.rb │ │ ├── subscribable.rb │ │ └── trashable.rb │ ├── friend_site.rb │ ├── like.rb │ ├── notification.rb │ ├── subscription.rb │ ├── topic.rb │ └── user.rb ├── uploaders │ ├── avatar_uploader.rb │ └── file_uploader.rb └── views │ ├── admin │ ├── attachments │ │ ├── destroy.js.erb │ │ └── index.html.slim │ ├── categories │ │ ├── _form.html.slim │ │ ├── index.html.slim │ │ ├── new.html.slim │ │ └── show.html.slim │ ├── comments │ │ ├── index.html.slim │ │ └── show.html.slim │ ├── dashboard │ │ └── show.html.slim │ ├── friend_sites │ │ ├── _form.html.slim │ │ ├── edit.html.slim │ │ └── index.html.slim │ ├── topics │ │ ├── index.html.slim │ │ └── show.html.slim │ └── users │ │ ├── index.html.slim │ │ └── show.html.slim │ ├── comments │ ├── _comment.html.slim │ ├── _form.html.slim │ ├── cancel.js.erb │ ├── create.js.erb │ ├── edit.js.erb │ ├── trash.js.erb │ └── update.js.erb │ ├── kaminari │ └── campo │ │ ├── _gap.html.slim │ │ ├── _next_page.html.slim │ │ ├── _page.html.slim │ │ ├── _paginator.html.slim │ │ └── _prev_page.html.slim │ ├── layouts │ ├── admin.html.slim │ └── application.html.slim │ ├── likes │ ├── create.js.erb │ └── destroy.js.erb │ ├── markdown │ ├── _area.html.slim │ └── preview.html.slim │ ├── notification_mailer │ ├── comment.html.slim │ ├── comment.text.erb │ ├── mention.html.slim │ └── mention.text.erb │ ├── notifications │ ├── clear.js.erb │ ├── destroy.js.erb │ ├── index.html.slim │ ├── mark.js.erb │ └── notification │ │ ├── _comment.html.slim │ │ └── _mention.html.slim │ ├── qunit │ └── index.html.slim │ ├── sessions │ ├── access_limiter.html.slim │ └── new.html.slim │ ├── settings │ ├── _sidebar.html.slim │ ├── accounts │ │ └── show.html.slim │ ├── notifications │ │ └── show.html.slim │ ├── passwords │ │ └── show.html.slim │ └── profiles │ │ └── show.html.slim │ ├── share │ ├── _flash_messages.html.slim │ ├── _form_error_messages.html.slim │ └── _user_confirm_required.html.slim │ ├── subscriptions │ ├── _subscription.html.slim │ └── update.js.erb │ ├── topics │ ├── _form.html.slim │ ├── _search_form.html.slim │ ├── _topic.html.slim │ ├── create.js.erb │ ├── edit.html.slim │ ├── feed.builder │ ├── index.html.slim │ ├── new.html.slim │ ├── search.html.slim │ ├── show.html.slim │ └── update.js.erb │ ├── user_mailer │ ├── confirmation.html.slim │ ├── confirmation.text.erb │ ├── password_reset.html.slim │ └── password_reset.text.erb │ └── users │ ├── _profile.html.slim │ ├── _sidebar.html.slim │ ├── comments │ ├── _comment.html.slim │ └── index.html.slim │ ├── confirmations │ ├── create.js.erb │ ├── limiter.js.erb │ ├── new.html.slim │ └── show.html.slim │ ├── index.html.slim │ ├── new.html.slim │ ├── passwords │ ├── edit.html.slim │ ├── new.html.slim │ └── show.html.slim │ └── topics │ └── index.html.slim ├── bin ├── bundle ├── rails ├── rake └── spring ├── config.ru ├── config ├── application.rb ├── boot.rb ├── config.example.yml ├── database.example.yml ├── deploy.rb ├── deploy │ ├── production.rb │ └── vagrant.rb ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── i18n-tasks.yml ├── initializers │ ├── 00_config.rb │ ├── backtrace_silencers.rb │ ├── elasticsearch.rb │ ├── filter_parameter_logging.rb │ ├── impression.rb │ ├── inflections.rb │ ├── kaminari_config.rb │ ├── mailer.rb │ ├── mime_types.rb │ ├── resque.rb │ ├── session_store.rb │ ├── social_share_button.rb │ └── wrap_parameters.rb ├── locales │ ├── en.yml │ ├── social_share_button.en.yml │ ├── social_share_button.zh-CN.yml │ ├── social_share_button.zh-TW.yml │ └── zh-CN.yml ├── nginx.conf ├── nginx.example.conf ├── resque.example.sh ├── routes.rb └── secrets.example.yml ├── db ├── migrate │ ├── 20140103111354_create_users.rb │ ├── 20140104125105_create_topics.rb │ ├── 20140121065043_create_notifications.rb │ ├── 20140130122905_create_comments.rb │ ├── 20140204053314_create_likes.rb │ ├── 20140204164212_create_subscriptions.rb │ ├── 20140208060708_create_categories.rb │ ├── 20140218070616_create_attachments.rb │ ├── 20140310070632_remove_lower_column.rb │ ├── 20140405074043_remove_password_reset_token_in_users.rb │ ├── 20140412065000_add_confirmed_to_users.rb │ ├── 20140412113810_add_comment_notification_settings_to_users.rb │ ├── 20140612075008_create_friend_sites.rb │ ├── 20140620090118_add_counts_to_users.rb │ ├── 20140623033657_create_impressions_table.rb │ └── 20140623065349_add_impressions_count_to_topics.rb ├── seeds.rb └── structure.sql ├── lib ├── assets │ └── .keep └── tasks │ ├── .keep │ ├── elasticseach.rake │ ├── resque.rake │ └── test.rake ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── robots.txt ├── swiftguide-cn.png └── upyun_logo.png ├── script └── setup.sh ├── test ├── controllers │ ├── .keep │ ├── admin │ │ ├── application_controller_test.rb │ │ ├── attachments_controller_test.rb │ │ ├── categories_controller_test.rb │ │ ├── comments_controller_test.rb │ │ ├── dashboard_controller_test.rb │ │ ├── friend_sites_controller_test.rb │ │ ├── topics_controller_test.rb │ │ └── users_controller_test.rb │ ├── attachments_controller_test.rb │ ├── comments_controller_test.rb │ ├── likes_controller_test.rb │ ├── markdown_controller_test.rb │ ├── notifications_controller_test.rb │ ├── sessions_controller_test.rb │ ├── settings │ │ ├── accounts_controller_test.rb │ │ ├── application_controller_test.rb │ │ ├── notifications_controller_test.rb │ │ ├── passwords_controller_test.rb │ │ └── profiles_controller_test.rb │ ├── subscriptions_controller_test.rb │ ├── topics_controller_test.rb │ ├── users │ │ ├── application_controller_test.rb │ │ ├── comments_controller_test.rb │ │ ├── confirmations_controller_test.rb │ │ ├── passwords_controller_test.rb │ │ └── topics_controller_test.rb │ └── users_controller_test.rb ├── factories │ ├── attachments.rb │ ├── categories.rb │ ├── comments.rb │ ├── friend_sites.rb │ ├── likes.rb │ ├── notifications.rb │ ├── subscriptions.rb │ ├── topics.rb │ └── users.rb ├── fixtures │ └── .keep ├── helpers │ ├── .keep │ ├── admin │ │ ├── comments_helper_test.rb │ │ └── topics_helper_test.rb │ ├── application_helper_test.rb │ ├── comments_helper_test.rb │ ├── markdown_helper_test.rb │ ├── notifications_helper_test.rb │ ├── timeago_helper_test.rb │ ├── topics_helper_test.rb │ └── useres_helper_test.rb ├── integration │ └── .keep ├── javascripts │ ├── likes_test.js.coffee │ ├── test_helper.js.coffee │ └── visible_to_test.js.coffee ├── jobs │ └── comment_notification_job_test.rb ├── mailers │ ├── .keep │ ├── notification_mailer_test.rb │ ├── previews │ │ ├── notification_mailer_preview.rb │ │ └── user_mailer_preview.rb │ └── user_mailer_test.rb ├── models │ ├── .keep │ ├── attachment_test.rb │ ├── category_test.rb │ ├── comment_test.rb │ ├── friend_site_test.rb │ ├── like_test.rb │ ├── notification_test.rb │ ├── subscription_test.rb │ ├── topic_test.rb │ └── user_test.rb └── test_helper.rb └── vendor └── assets ├── javascripts ├── .keep ├── jquery.autosize.js ├── jquery.timeago.js ├── jquery.validate.js ├── locales │ └── jquery.timeago.zh-CN.js ├── nprogress.js └── qunit.js └── stylesheets ├── .keep ├── nprogress.css └── qunit.css /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/*.log 16 | /tmp 17 | 18 | # Ignore database and secrets config. 19 | /config/database.yml 20 | /config/secrets.yml 21 | /config/config.yml 22 | /config/resque.sh 23 | 24 | # Ignore uploads. 25 | /public/uploads 26 | 27 | /public/assets 28 | 29 | # Ignore Vagrant config. 30 | /.vagrant 31 | 32 | # Ignore deploy config. 33 | /config/deploy 34 | 35 | *.swo 36 | *.swp 37 | 38 | dump.rdb 39 | 40 | campo.sublime* 41 | 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - "2.1.1" 5 | 6 | bundler_args: --without production development 7 | 8 | services: 9 | - redis-server 10 | - elasticsearch 11 | - memcached 12 | 13 | before_script: 14 | - psql -c 'create database campo_test;' -U postgres 15 | - cp config/config.example.yml config/config.yml 16 | - cp config/secrets.example.yml config/secrets.yml 17 | - cp config/database.example.yml config/database.yml 18 | - "sed -i 's/username:/username: postgres/g' config/database.yml" 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## master 2 | 3 | - Add elasticseach host config in config.example.yml 4 | - Add Email confirmation module 5 | - Add comment/mention email notification, add notification settings page 6 | 7 | ## 3.0.0 (2014-3-06) 8 | -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | # Load DSL and Setup Up Stages 2 | require 'capistrano/setup' 3 | 4 | # Includes default deployment tasks 5 | require 'capistrano/deploy' 6 | 7 | unless ARGV.any? { |task| task =~ /provision/ } 8 | require 'capistrano/rvm' 9 | require 'capistrano/bundler' 10 | require 'capistrano/rails/assets' 11 | require 'capistrano/rails/migrations' 12 | end 13 | 14 | # Loads custom tasks from `lib/capistrano/tasks' if you have any defined. 15 | Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } 16 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard 'livereload' do 5 | watch(%r{app/views/.+\.(slim)$}) 6 | watch(%r{app/helpers/.+\.rb}) 7 | watch(%r{public/.+\.(css|js|html)}) 8 | watch(%r{config/locales/.+\.yml}) 9 | # Rails Assets Pipeline 10 | watch(%r{(app|vendor)(/assets/\w+/(.+\.(css|js|coffee|scss|gif|png|jpg))).*}) { |m| "/assets/#{m[3]}" } 11 | 12 | # JS testing 13 | watch(%r{test/javascripts/(.+\.(js)).*}) { |m| "/assets/#{m[1]}" } 14 | end 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2014 [Rei](mailto:chloerei@gmail.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This is the codebase of [Swiftist.org](http://swiftist.org) 2 | 3 | 4 | [Swiftist.org](http://swiftist.org) is based on @Rei's Campo, which is a lightweight forum application, base on Ruby on Rails. 5 | 6 | It's good for: 7 | 8 | - Build user forum for your website. 9 | - As code base, customize develop for your business. 10 | 11 | 12 | ## Build with 13 | 14 | - Ruby 2.1 and Rails 4.1.1 15 | - Postgresql 9.1+ for databsae 16 | - elasticsearch 1.0+ for full text searching 17 | - Redis for backgound jobs 18 | - memcached for cache 19 | - postfix or other email service(through SMTP) for sending system email 20 | 21 | The best deploy environment is Ubuntu 12.04 LTS, require at least 1G memory. 22 | 23 | You don't need to install all denpendency manual, an auto-install scirpt will be show below. 24 | 25 | 26 | ## License 27 | 28 | MIT License. 29 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = "2" 6 | 7 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 8 | config.vm.box = "ubuntu/precise64" 9 | 10 | config.vm.provider :virtualbox do |vb| 11 | vb.customize ["modifyvm", :id, "--memory", "1024"] 12 | end 13 | 14 | config.vm.network :private_network, ip: '192.168.33.10' 15 | 16 | # Fix postgresql default encoding 17 | config.vm.provision "shell", inline: <<-EOF 18 | update-locale LC_ALL="en_US.utf8" 19 | LC_ALL=en_US.UTF-8 20 | EOF 21 | end 22 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/app/assets/images/rails.png -------------------------------------------------------------------------------- /app/assets/javascripts/application.js.coffee: -------------------------------------------------------------------------------- 1 | #= require jquery 2 | #= require jquery_ujs 3 | #= require turbolinks 4 | #= require bootstrap 5 | #= require jquery.autosize 6 | #= require jquery.validate 7 | #= require jquery.timeago 8 | #= require nprogress 9 | #= require campo 10 | #= require jquery.atwho 11 | #= require_tree ./plugins 12 | #= require social-share-button 13 | 14 | 15 | $ -> 16 | console.log "对源代码感兴趣?更多关于本站技术栈信息请参考: http://swiftist.org/topics/11" 17 | 18 | $(document).on 'page:update', -> 19 | $('[data-behaviors~=autosize]').autosize() 20 | 21 | $("time[data-behaviors~=timeago]").timeago() 22 | 23 | $(document).on 'page:fetch', -> 24 | NProgress.start() 25 | $(document).on 'page:change', -> 26 | NProgress.done() 27 | $(document).on 'page:restore', -> 28 | NProgress.remove() 29 | -------------------------------------------------------------------------------- /app/assets/javascripts/campo.js.coffee: -------------------------------------------------------------------------------- 1 | @campo = {} 2 | -------------------------------------------------------------------------------- /app/assets/javascripts/locales/en.js.coffee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/app/assets/javascripts/locales/en.js.coffee -------------------------------------------------------------------------------- /app/assets/javascripts/locales/zh-CN.js.coffee: -------------------------------------------------------------------------------- 1 | #= require locales/jquery.timeago.zh-CN 2 | -------------------------------------------------------------------------------- /app/assets/javascripts/plugins/comments.js.coffee: -------------------------------------------------------------------------------- 1 | $(document).on 'click', '.comment [data-reply-to]', -> 2 | textarea = $('#new_comment textarea') 3 | textarea.focus() 4 | textarea.val(textarea.val() + $(this).data('reply-to')) 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/plugins/likes.js.coffee: -------------------------------------------------------------------------------- 1 | campo.Likes = 2 | updateLike: (likeable, id, liked = true) -> 3 | if liked 4 | $("#like-for-#{likeable}-#{id}").addClass('liked').data('method', 'delete') 5 | else 6 | $("#like-for-#{likeable}-#{id}").removeClass('liked').data('method', 'post') 7 | 8 | updateLikes: (likeable, ids, liked = true) -> 9 | for id in ids 10 | @updateLike(likeable, id, liked) 11 | -------------------------------------------------------------------------------- /app/assets/javascripts/plugins/markdown_area.js.coffee: -------------------------------------------------------------------------------- 1 | $(document).on 'focus', '.markdown-area textarea', -> 2 | $(this).closest('.markdown-area').addClass('focus') 3 | 4 | $(document).on 'blur', '.markdown-area textarea', -> 5 | $(this).closest('.markdown-area').removeClass('focus') 6 | 7 | $(document).on 'click', '.markdown-area a[data-behaviors~="preview"]', (e) -> 8 | area = $(this).closest('.markdown-area') 9 | preview = area.find('.preview') 10 | textarea = area.find('textarea') 11 | preview.html('') 12 | preview.css height: textarea.css('height') 13 | 14 | $.ajax 15 | url: '/markdown/preview' 16 | data: { body: textarea.val() } 17 | type: 'POST' 18 | success: (data) -> 19 | preview.html(data) 20 | preview.css height: 'auto' 21 | 22 | $(document).on 'change', '.markdown-area .file-upload input[type=file]', (event) -> 23 | textarea = $(this).closest('.markdown-area').find('textarea') 24 | 25 | $.each event.target.files, -> 26 | formData = new FormData() 27 | formData.append 'attachment[file]', this 28 | fileName = this.name 29 | imageTag = "![#{fileName}]()" 30 | 31 | pos = textarea[0].selectionStart 32 | before = textarea.val().slice(0, pos) 33 | after = textarea.val().slice(pos, -1) 34 | before = before + "\n" if before != '' 35 | after = "\n" + after if after != '' 36 | textarea.val(before + imageTag + after).trigger('autosize.resize') 37 | textarea[0].selectionStart = (before + imageTag).length 38 | 39 | $.ajax 40 | url: '/attachments' 41 | type: 'POST' 42 | dataType: 'json' 43 | processData: false 44 | contentType: false 45 | data: formData 46 | success: (data) -> 47 | pos = textarea[0].selectionStart 48 | imagePos = textarea.val().indexOf(imageTag) 49 | textarea.val(textarea.val().replace(imageTag, "![#{fileName}](#{data.url})")).trigger('autosize.resize') 50 | if imagePos < pos 51 | textarea[0].selectionStart = textarea[0].selectionEnd = pos + data.url.length 52 | else 53 | textarea[0].selectionStart = textarea[0].selectionEnd = pos 54 | 55 | # Clear input, or ujs submit will be abort. 56 | $(this).replaceWith($(this).val('').clone()) 57 | -------------------------------------------------------------------------------- /app/assets/javascripts/plugins/pagination_popover.js.coffee: -------------------------------------------------------------------------------- 1 | $(document).popover 2 | selector: '[data-behaviors~=pagination-popover]' 3 | content: -> 4 | $(this).siblings('.popover').find('.popover-content').html() 5 | html: true 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/plugins/turboform.js.coffee: -------------------------------------------------------------------------------- 1 | $(document).on 'submit', 'form[method=get]:not([data-remote])', (event) -> 2 | event.preventDefault() 3 | symbol = if @.action.indexOf('?') == -1 then '?' else '&' 4 | Turbolinks.visit @.action + symbol + $(@).serialize() 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/plugins/validator.js.coffee: -------------------------------------------------------------------------------- 1 | $.validator.setDefaults 2 | highlight: (element) -> 3 | $(element).closest(".form-group").addClass "has-error" 4 | 5 | unhighlight: (element) -> 6 | $(element).closest(".form-group").removeClass "has-error" 7 | 8 | errorElement: "span" 9 | errorClass: "help-block" 10 | errorPlacement: (error, element) -> 11 | if element.closest('.markdown-area').length 12 | error.insertAfter(element.closest('.markdown-area')) 13 | else if element.parent('.input-group').length 14 | error.insertAfter(element.parent()) 15 | else 16 | error.insertAfter(element) 17 | 18 | jQuery.validator.addMethod "format", ((value, element, param) -> 19 | @optional(element) or param.test(value) 20 | ), "Please fix this field." 21 | -------------------------------------------------------------------------------- /app/assets/javascripts/plugins/visible_to.js.coffee: -------------------------------------------------------------------------------- 1 | campo.VisibleTo = 2 | # data-visible-to="options" 3 | # 4 | # options: 5 | # - user: Visible to logined user 6 | # Check if campo.currentUser exists. 7 | # 8 | # - creator: Visible to creator 9 | # Find closest element witch has data-creator-id, 10 | # and compare with campo.currentUser.id. 11 | # 12 | # - no-creator: Visible to anyone except creator 13 | check: -> 14 | $('[data-visible-to]').each -> 15 | $element = $(this) 16 | rules = $element.data('visible-to').split(/\s/) 17 | 18 | if 'user' in rules 19 | if !campo.currentUser? 20 | return $element.remove() 21 | 22 | if 'creator' in rules 23 | creator_id = $element.closest('[data-creator-id]').data('creator-id') 24 | if (!campo.currentUser?) or (campo.currentUser.id != creator_id) 25 | return $element.remove() 26 | 27 | if 'no-creator' in rules 28 | creator_id = $element.closest('[data-creator-id]').data('creator-id') 29 | if campo.currentUser? and (campo.currentUser.id == creator_id) 30 | return $element.remove() 31 | 32 | $(document).on 'page:update', -> 33 | campo.VisibleTo.check() 34 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | *= require font-awesome 3 | */ 4 | 5 | 6 | $font-size-base: 15px; 7 | 8 | @import "bootstrap"; 9 | @import "highlight"; 10 | @import "nprogress"; 11 | @import "bootstrap_customize"; 12 | @import "users"; 13 | @import "topics"; 14 | @import "comments"; 15 | @import "notifications"; 16 | @import "subscriptions"; 17 | @import "markdown"; 18 | @import "social-share-button"; 19 | @import "jquery.atwho"; 20 | 21 | body { 22 | background: #eee; 23 | } 24 | html, body{ 25 | height: 100%; 26 | .main{ 27 | height: 100%; 28 | min-height: 100%; 29 | height: auto !important; 30 | } 31 | } 32 | 33 | .text-hide-overflow { 34 | overflow: hidden; 35 | text-overflow: ellipsis; 36 | } 37 | 38 | footer{ 39 | background: white; 40 | padding: 20px 0; 41 | line-height: 1.5em; 42 | a{ 43 | color: #999999; 44 | &:hover{ 45 | color: #bbb; 46 | } 47 | } 48 | .copyright{ 49 | } 50 | .friend-links{ 51 | .link-item{ 52 | margin-right: 20px; 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /app/assets/stylesheets/comments.css.scss: -------------------------------------------------------------------------------- 1 | .list-group-item.comment { 2 | .list-group-item-heading { 3 | margin-bottom: $line-height-computed / 2; 4 | color: $gray-light; 5 | 6 | a { 7 | color: $gray-light; 8 | } 9 | 10 | .comment-user { 11 | b { 12 | color: $gray-dark; 13 | } 14 | 15 | &:hover { 16 | text-decoration: none; 17 | 18 | b { 19 | color: $link-hover-color; 20 | text-decoration: underline; 21 | } 22 | } 23 | } 24 | } 25 | 26 | .list-group-item-actions { 27 | .liked { 28 | color: $brand-danger; 29 | 30 | &:hover { 31 | color: $brand-danger; 32 | } 33 | } 34 | } 35 | 36 | @media (max-width: $screen-xs) { 37 | .list-group-item-avatar { 38 | img { 39 | height: 24px; 40 | width: 24px; 41 | } 42 | } 43 | 44 | .list-group-item-avatar + .list-group-item-content { 45 | margin-left: 0; 46 | } 47 | 48 | .list-group-item-heading { 49 | margin-left: 34px; 50 | line-height: 24px; 51 | } 52 | } 53 | } 54 | 55 | .commentable { 56 | } 57 | -------------------------------------------------------------------------------- /app/assets/stylesheets/markdown.css.scss: -------------------------------------------------------------------------------- 1 | .markdown-area { 2 | border: 1px solid #ccc; 3 | border-radius: 5px; 4 | overflow: hidden; 5 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075); 6 | @include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s); 7 | 8 | &.focus { 9 | $color: $input-border-focus; 10 | $color-rgba: rgba(red($color), green($color), blue($color), .6); 11 | border-color: $color; 12 | @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px $color-rgba); 13 | 14 | } 15 | 16 | .nav-tabs { 17 | padding: $padding-base-horizontal $padding-base-horizontal 0; 18 | 19 | li a { 20 | padding: $padding-base-vertical $padding-base-horizontal; 21 | } 22 | } 23 | 24 | textarea { 25 | margin: 0; 26 | padding: $padding-base-vertical $padding-base-horizontal; 27 | border: none; 28 | resize: vertical; 29 | height: 8em; 30 | @include box-shadow(none); 31 | 32 | &:focus { 33 | border: none; 34 | @include box-shadow(none); 35 | } 36 | } 37 | 38 | .file-upload { 39 | background: $gray-lighter; 40 | position: relative; 41 | 42 | input[type=file] { 43 | position: absolute; 44 | width: 100%; 45 | top: 0; 46 | bottom: 0; 47 | outline: 0; 48 | opacity: 0; 49 | cursor: pointer; 50 | } 51 | 52 | .text { 53 | padding: $padding-base-vertical/2 $padding-base-horizontal; 54 | } 55 | 56 | &:hover { 57 | a { 58 | text-decoration: underline; 59 | } 60 | } 61 | } 62 | 63 | .preview { 64 | min-height: 8em; 65 | padding: $padding-base-vertical $padding-base-horizontal; 66 | } 67 | } 68 | 69 | .has-error .markdown-area { 70 | border-color: $state-danger-text; 71 | 72 | &.focus { 73 | border-color: darken($state-danger-text, 10%); 74 | @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten($state-danger-text, 20%)); 75 | } 76 | 77 | textarea { 78 | @include box-shadow(none); 79 | 80 | &:focus { 81 | border: none; 82 | @include box-shadow(none); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/assets/stylesheets/notifications.css.scss: -------------------------------------------------------------------------------- 1 | .notifications-bell { 2 | position: relative; 3 | 4 | .badge { 5 | background: $brand-primary; 6 | 7 | @media (min-width: $grid-float-breakpoint) { 8 | position: absolute; 9 | top: 5px; 10 | right: 5px; 11 | } 12 | } 13 | } 14 | 15 | .notification { 16 | &.read { 17 | .list-group-item-link { 18 | color: #999; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/assets/stylesheets/subscriptions.css.scss: -------------------------------------------------------------------------------- 1 | .dropdown-menu-subscription { 2 | width: 360px; 3 | 4 | li a { 5 | white-space: normal; 6 | } 7 | 8 | .subscription-status { 9 | float: left; 10 | display: none; 11 | } 12 | 13 | li:hover, 14 | li.selected { 15 | .subscription-status { 16 | display: block; 17 | } 18 | } 19 | 20 | .subscription-description { 21 | margin-left: 25px; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/assets/stylesheets/topics.css.scss: -------------------------------------------------------------------------------- 1 | .topics-header { 2 | @media (max-width: $grid-float-breakpoint) { 3 | .row > div { 4 | margin-bottom: $line-height-computed / 2; 5 | 6 | &:last-child { 7 | margin-bottom: 0; 8 | } 9 | } 10 | } 11 | } 12 | 13 | .form-topic .markdown-area { 14 | textarea { 15 | height: 12em; 16 | } 17 | 18 | .preview { 19 | min-height: 12em; 20 | } 21 | } 22 | 23 | .friends-sites{ 24 | .title{ 25 | border-bottom: 1px solid #eee; 26 | padding-bottom: 10px; 27 | } 28 | .site-item{ 29 | height: 30px; 30 | a{ 31 | line-height: 30px; 32 | font-size: 16px; 33 | text-decoration: none; 34 | &:hover{ 35 | color: orange; 36 | } 37 | } 38 | } 39 | } 40 | 41 | .social-share-button{ 42 | padding-top: 8px; 43 | } 44 | -------------------------------------------------------------------------------- /app/assets/stylesheets/users.css.scss: -------------------------------------------------------------------------------- 1 | #user-confirm-required { 2 | padding: $line-height-computed 0 0; 3 | 4 | .alert { 5 | margin: 0; 6 | } 7 | 8 | .modal-header { 9 | border-bottom: none; 10 | } 11 | } 12 | 13 | .user-profile { 14 | background: white; 15 | text-align: center; 16 | } 17 | 18 | .user-profile-avatar { 19 | margin: ($line-height-computed / 2) 0; 20 | 21 | img { 22 | width: 80px; 23 | height: 80px; 24 | } 25 | } 26 | 27 | .user-profile-name { 28 | font-size: 1.2em; 29 | font-weight: bold; 30 | margin-bottom: $line-height-computed / 2; 31 | } 32 | 33 | .user-image-uploader { 34 | @include clearfix(); 35 | 36 | .image { 37 | width: 96px; 38 | height: 96px; 39 | float: left; 40 | } 41 | 42 | .uploader { 43 | margin-left: 110px; 44 | } 45 | } 46 | 47 | .top-users{ 48 | border-radius: 5px; 49 | background: white; 50 | margin-top: 20px; 51 | .header{ 52 | padding: 10px; 53 | width: 100%; 54 | } 55 | ul.user-list{ 56 | padding: 0 20px; 57 | list-style: none; 58 | margin-left: -30px; 59 | li.user-item{ 60 | margin: 0 0 20px 30px; 61 | height: 77px; 62 | width: 70px; 63 | .avatar{ 64 | width: 50px; 65 | height: 50px; 66 | margin-bottom: 20px; 67 | margin: 0 auto; 68 | img{ 69 | width: 50px; 70 | } 71 | } 72 | .user-name{ 73 | margin-top: 5px; 74 | font-size: 0.8em; 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/controllers/admin/application_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::ApplicationController < ApplicationController 2 | layout 'admin' 3 | before_action :login_required, :admin_required 4 | 5 | private 6 | 7 | def admin_required 8 | raise AccessDenied unless current_user.admin? 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/admin/attachments_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::AttachmentsController < Admin::ApplicationController 2 | def index 3 | @attachments = Attachment.includes(:user).order(id: :desc).page(params[:page]) 4 | end 5 | 6 | def destroy 7 | @attachment = Attachment.find params[:id] 8 | @attachment.destroy 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/admin/categories_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::CategoriesController < Admin::ApplicationController 2 | before_action :find_category, only: [:show, :edit, :update, :destroy] 3 | 4 | def index 5 | @categories = Category.order(topics_count: :desc) 6 | end 7 | 8 | def show 9 | end 10 | 11 | def new 12 | @category = Category.new 13 | end 14 | 15 | def create 16 | @category = Category.new category_params 17 | 18 | if @category.save 19 | flash[:success] = I18n.t('admin.categories.flashes.successfully_created') 20 | redirect_to admin_category_path(@category) 21 | else 22 | render :new 23 | end 24 | end 25 | 26 | def edit 27 | end 28 | 29 | def update 30 | if @category.update_attributes category_params 31 | flash[:success] = I18n.t('admin.categories.flashes.successfully_updated') 32 | redirect_to admin_category_path(@category) 33 | else 34 | render :edit 35 | end 36 | end 37 | 38 | def destroy 39 | @category.destroy 40 | flash[:success] = I18n.t('admin.categories.flashes.successfully_destroy') 41 | redirect_to admin_categories_path 42 | end 43 | 44 | private 45 | 46 | def category_params 47 | params.require(:category).permit(:name, :slug, :description) 48 | end 49 | 50 | def find_category 51 | @category = Category.find params[:id] 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /app/controllers/admin/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::CommentsController < Admin::ApplicationController 2 | before_action :find_comment, except: [:index, :trashed] 3 | 4 | def index 5 | @comments = Comment.includes(:user, :commentable).order(id: :desc).page(params[:page]) 6 | end 7 | 8 | def trashed 9 | @comments = Comment.trashed.includes(:user, :commentable).order(id: :desc).page(params[:page]) 10 | render :index 11 | end 12 | 13 | def show 14 | end 15 | 16 | def update 17 | if @comment.update_attributes params.require(:comment).permit(:body) 18 | flash[:success] = I18n.t('admin.comments.flashes.successfully_updated') 19 | redirect_to admin_comment_path(@comment) 20 | else 21 | render :show 22 | end 23 | end 24 | 25 | def trash 26 | @comment.trash 27 | flash[:success] = I18n.t('admin.comments.flashes.successfully_trashed') 28 | redirect_to admin_comment_path(@comment) 29 | end 30 | 31 | def restore 32 | @comment.restore 33 | flash[:success] = I18n.t('admin.comments.flashes.successfully_restored') 34 | redirect_to admin_comment_path(@comment) 35 | end 36 | 37 | private 38 | 39 | def find_comment 40 | @comment = Comment.with_trashed.find params[:id] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /app/controllers/admin/dashboard_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::DashboardController < Admin::ApplicationController 2 | def show 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/admin/friend_sites_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::FriendSitesController < Admin::ApplicationController 2 | before_action :set_friend_site, only: [ :edit, :update, :destroy] 3 | def index 4 | @friend_site = FriendSite.new 5 | @friend_sites = FriendSite.all 6 | end 7 | 8 | def create 9 | FriendSite.create friend_site_params 10 | redirect_to admin_friend_sites_path 11 | end 12 | 13 | def edit 14 | end 15 | 16 | def update 17 | @friend_site.update_attributes friend_site_params 18 | redirect_to admin_friend_sites_path 19 | end 20 | 21 | def destroy 22 | @friend_site.destroy 23 | redirect_to admin_friend_sites_path 24 | end 25 | 26 | private 27 | def friend_site_params 28 | params.require(:friend_site).permit(:name, :url) 29 | end 30 | def set_friend_site 31 | @friend_site = FriendSite.find(params[:id]) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/controllers/admin/topics_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::TopicsController < Admin::ApplicationController 2 | before_action :find_topic, only: [:show, :update, :trash, :restore] 3 | 4 | def index 5 | @topics = Topic.includes(:user).order(id: :desc).page(params[:page]) 6 | end 7 | 8 | def trashed 9 | @topics = Topic.trashed.includes(:user).order(id: :desc).page(params[:page]) 10 | render :index 11 | end 12 | 13 | def show 14 | end 15 | 16 | def update 17 | if @topic.update_attributes params.require(:topic).permit(:title, :category_id, :body) 18 | flash[:success] = I18n.t('admin.topics.flashes.successfully_updated') 19 | redirect_to admin_topic_url(@topic) 20 | else 21 | render :show 22 | end 23 | end 24 | 25 | def trash 26 | @topic.trash 27 | flash[:success] = I18n.t('admin.topics.flashes.successfully_trashed') 28 | redirect_to admin_topic_path(@topic) 29 | end 30 | 31 | def restore 32 | @topic.restore 33 | flash[:success] = I18n.t('admin.topics.flashes.successfully_restored') 34 | redirect_to admin_topic_path(@topic) 35 | end 36 | 37 | def top 38 | topic = Topic.find params[:topic_id] 39 | topic.top_it 40 | redirect_to admin_topic_path(topic) 41 | end 42 | def cancel_top 43 | topic = Topic.find params[:topic_id] 44 | topic.cancel_top_it 45 | redirect_to admin_topic_path(topic) 46 | end 47 | 48 | private 49 | 50 | def find_topic 51 | @topic = Topic.with_trashed.find params[:id] 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /app/controllers/admin/users_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::UsersController < Admin::ApplicationController 2 | before_action :find_user, except: [:index, :locked] 3 | 4 | def index 5 | @users = User.unlocked.order(id: :desc).page(params[:page]) 6 | end 7 | 8 | def locked 9 | @users = User.locked.order(id: :desc).page(params[:page]) 10 | render :index 11 | end 12 | 13 | def show 14 | end 15 | 16 | def update 17 | if @user.update_attributes params.require(:user).permit(:name, :username, :email, :confirmed, :bio, :avatar, :remove_avatar) 18 | flash[:success] = I18n.t('admin.users.flashes.successfully_updated') 19 | redirect_to admin_user_url(@user) 20 | else 21 | render :show 22 | end 23 | end 24 | 25 | def destroy 26 | @user.destroy 27 | flash[:success] = I18n.t('admin.users.flashes.successfully_destroy') 28 | redirect_to admin_users_path 29 | end 30 | 31 | def lock 32 | @user.lock 33 | flash[:success] = I18n.t('admin.users.flashes.successfully_locked') 34 | redirect_to admin_user_url(@user) 35 | end 36 | 37 | def unlock 38 | @user.unlock 39 | flash[:success] = I18n.t('admin.users.flashes.successfully_unlocked') 40 | redirect_to admin_user_url(@user) 41 | end 42 | 43 | private 44 | 45 | def find_user 46 | @user = User.find params[:id] 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /app/controllers/attachments_controller.rb: -------------------------------------------------------------------------------- 1 | class AttachmentsController < ApplicationController 2 | before_action :login_required 3 | 4 | def create 5 | @attachment = current_user.attachments.create params.require(:attachment).permit(:file) 6 | 7 | render json: { url: @attachment.file.url } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class CommentsController < ApplicationController 2 | before_action :login_required, :no_locked_required 3 | before_action :find_comment, only: [:edit, :cancel, :update, :trash] 4 | 5 | def create 6 | resource, id = request.path.split('/')[1, 2] 7 | @commentable = resource.singularize.classify.constantize.find(id) 8 | @comment = @commentable.comments.new params.require(:comment).permit(:body).merge(user: current_user) 9 | if @comment.save 10 | Resque.enqueue(CommentNotificationJob, @comment.id) 11 | end 12 | end 13 | 14 | def edit 15 | end 16 | 17 | def cancel 18 | end 19 | 20 | def update 21 | @comment.update_attributes params.require(:comment).permit(:body) 22 | end 23 | 24 | def trash 25 | @comment.trash 26 | end 27 | 28 | private 29 | 30 | def find_comment 31 | @comment = current_user.comments.find params[:id] 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/likes_controller.rb: -------------------------------------------------------------------------------- 1 | class LikesController < ApplicationController 2 | before_action :login_required, :no_locked_required, :find_likeable 3 | 4 | def create 5 | @likeable.likes.find_or_create_by user: current_user 6 | end 7 | 8 | def destroy 9 | @likeable.likes.where(user: current_user).destroy_all 10 | end 11 | 12 | private 13 | 14 | def find_likeable 15 | resource, id = request.path.split('/')[1, 2] 16 | @likeable = resource.singularize.classify.constantize.find(id) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/markdown_controller.rb: -------------------------------------------------------------------------------- 1 | class MarkdownController < ApplicationController 2 | def preview 3 | render layout: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/notifications_controller.rb: -------------------------------------------------------------------------------- 1 | class NotificationsController < ApplicationController 2 | before_action :login_required 3 | after_action :mark_all_as_read, only: [:index] 4 | 5 | def index 6 | @notifications = current_user.notifications.includes(:subject).order(id: :desc).page(params[:page]) 7 | end 8 | 9 | def destroy 10 | @notification = current_user.notifications.find params[:id] 11 | @notification.destroy 12 | end 13 | 14 | def clear 15 | current_user.notifications.delete_all 16 | end 17 | 18 | private 19 | 20 | def mark_all_as_read 21 | current_user.notifications.unread.update_all(read: true, updated_at: Time.now.utc) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/controllers/qunit_controller.rb: -------------------------------------------------------------------------------- 1 | class QunitController < ApplicationController 2 | layout false 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | before_action :no_login_required, :access_limiter, only: [:new, :create] 3 | 4 | def new 5 | store_location params[:return_to] 6 | end 7 | 8 | def create 9 | login = params[:login].downcase 10 | @user = if login.include?('@') 11 | User.where('lower(email) = ?', login).first 12 | else 13 | User.where('lower(username) = ?', login).first 14 | end 15 | 16 | if @user && @user.authenticate(params[:password]) 17 | login_as @user 18 | remember_me 19 | redirect_back_or_default root_url 20 | else 21 | flash[:warning] = I18n.t('sessions.flashes.incorrect_user_name_or_password') 22 | redirect_to login_url 23 | end 24 | end 25 | 26 | def destroy 27 | logout 28 | redirect_to root_url 29 | end 30 | 31 | private 32 | 33 | def access_limiter 34 | key = "sessions:limiter:#{request.remote_ip}" 35 | if $redis.get(key).to_i > 4 36 | render :access_limiter 37 | elsif action_name != 'new' # get login page not incr limiter 38 | $redis.incr(key) 39 | if $redis.ttl(key) == -1 40 | $redis.expire(key, 60) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /app/controllers/settings/accounts_controller.rb: -------------------------------------------------------------------------------- 1 | class Settings::AccountsController < Settings::ApplicationController 2 | before_action :current_password_required, only: [:update] 3 | 4 | def show 5 | end 6 | 7 | def update 8 | if @user.update_attributes params.require(:user).permit(:username, :email, :locale) 9 | flash[:success] = I18n.t('settings.accounts.flashes.successfully_updated') 10 | redirect_to settings_account_path 11 | else 12 | render :show 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/settings/application_controller.rb: -------------------------------------------------------------------------------- 1 | class Settings::ApplicationController < ApplicationController 2 | before_action :login_required, :set_user 3 | 4 | private 5 | 6 | def set_user 7 | @user = current_user 8 | end 9 | 10 | def current_password_required 11 | unless params[:current_password] && @user.authenticate(params[:current_password]) 12 | flash.now[:warning] = I18n.t('settings.flashes.incorrect_current_password') 13 | render :show 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/controllers/settings/notifications_controller.rb: -------------------------------------------------------------------------------- 1 | class Settings::NotificationsController < Settings::ApplicationController 2 | def show 3 | end 4 | 5 | def update 6 | if @user.update_attributes params.require(:user).permit(:send_mention_email, :send_mention_web, :send_comment_email, :send_comment_web) 7 | flash[:success] = I18n.t('settings.notifications.flashes.successfully_updated') 8 | redirect_to settings_notifications_url 9 | else 10 | render :show 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/settings/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | class Settings::PasswordsController < Settings::ApplicationController 2 | before_action :current_password_required, only: [:update] 3 | 4 | def show 5 | end 6 | 7 | def update 8 | if @user.update_attributes params.require(:user).permit(:password, :password_confirmation) 9 | flash[:success] = I18n.t('settings.passwords.flashes.successfully_updated') 10 | redirect_to settings_password_url 11 | else 12 | render :show 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/settings/profiles_controller.rb: -------------------------------------------------------------------------------- 1 | class Settings::ProfilesController < Settings::ApplicationController 2 | def show 3 | end 4 | 5 | def update 6 | if @user.update_attributes params.require(:user).permit(:name, :bio, :avatar, :remove_avatar) 7 | flash[:success] = I18n.t('settings.profiles.flashes.successfully_updated') 8 | redirect_to settings_profile_url 9 | else 10 | render :show 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/subscriptions_controller.rb: -------------------------------------------------------------------------------- 1 | class SubscriptionsController < ApplicationController 2 | before_action :login_required, :no_locked_required, :find_subscribable 3 | 4 | def update 5 | case params[:status] 6 | when 'subscribed' 7 | @subscribable.subscribe_by(current_user) 8 | when 'ignored' 9 | @subscribable.ignore_by(current_user) 10 | end 11 | end 12 | 13 | def destroy 14 | @subscribable.subscriptions.where(user: current_user).destroy_all 15 | render :update 16 | end 17 | 18 | private 19 | 20 | def find_subscribable 21 | resource, id = request.path.split('/')[1, 2] 22 | @subscribable = resource.singularize.classify.constantize.find(id) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/controllers/users/application_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::ApplicationController < ApplicationController 2 | before_action :find_user 3 | 4 | private 5 | 6 | def find_user 7 | @user = User.where('lower(username) = ?', params[:username].downcase).first! 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/users/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::CommentsController < Users::ApplicationController 2 | def index 3 | @comments = @user.comments.includes(:commentable).order(id: :desc).page(params[:page]) 4 | end 5 | 6 | def likes 7 | @comments = @user.like_comments.includes(:commentable, :user).order(id: :desc).page(params[:page]) 8 | render :index 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/users/confirmations_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::ConfirmationsController < ApplicationController 2 | before_action :login_required, :no_confirmed_required 3 | before_action :access_limiter, only: [:create] 4 | 5 | def new 6 | store_location params[:return_to] 7 | end 8 | 9 | def show 10 | if params[:token].present? 11 | @user = User.find_by_confirmation_token(params[:token]) 12 | if @user && @user == current_user 13 | @user.confirm 14 | flash[:success] = I18n.t('users.confirmations.confirm_success') 15 | redirect_back_or_default root_url 16 | end 17 | end 18 | end 19 | 20 | def create 21 | UserMailer.confirmation(current_user.id).deliver 22 | end 23 | 24 | private 25 | 26 | def no_confirmed_required 27 | if current_user.confirmed? 28 | redirect_to root_url 29 | end 30 | end 31 | 32 | def access_limiter 33 | key = "verifies:limiter:#{request.remote_ip}" 34 | if $redis.get(key).to_i > 0 35 | render :limiter 36 | else 37 | $redis.incr(key) 38 | if $redis.ttl(key) == -1 39 | $redis.expire(key, 60) 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /app/controllers/users/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::PasswordsController < ApplicationController 2 | before_action :no_login_required 3 | before_action :check_token, only: [:edit, :update] 4 | 5 | def show 6 | end 7 | 8 | def new 9 | end 10 | 11 | def create 12 | if @user = User.find_by("lower(email) = ?", params[:email].downcase) 13 | UserMailer.password_reset(@user.id).deliver 14 | redirect_to users_password_path 15 | else 16 | flash.now[:warning] = I18n.t('passwords.flashes.user_email_not_found') 17 | render :new 18 | end 19 | end 20 | 21 | def edit 22 | end 23 | 24 | def update 25 | if @user.update_attributes params.require(:user).permit(:password, :password_confirmation) 26 | flash[:success] = I18n.t('passwords.flashes.successfully_update') 27 | redirect_to login_url 28 | else 29 | render :edit 30 | end 31 | end 32 | 33 | private 34 | 35 | def check_token 36 | unless @user = User.find_by_password_reset_token(params[:token]) 37 | flash[:warning] = I18n.t('passwords.flashes.token_invalid') 38 | redirect_to new_users_password_path 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/controllers/users/topics_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::TopicsController < Users::ApplicationController 2 | def index 3 | @topics = @user.topics.includes(:category).order(id: :desc).page(params[:page]) 4 | end 5 | 6 | def likes 7 | @topics = @user.like_topics.includes(:category, :user).order(id: :desc).page(params[:page]) 8 | render :index 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :no_login_required, only: [:new, :create] 3 | 4 | def index 5 | @users = User.top 50 6 | end 7 | 8 | def new 9 | store_location params[:return_to] 10 | @user = User.new 11 | end 12 | 13 | def create 14 | @user = User.new params.require(:user).permit(:username, :email, :name, :password).merge(locale: locale) 15 | if @user.save 16 | login_as @user 17 | UserMailer.confirmation(@user.id).deliver 18 | redirect_back_or_default root_url 19 | else 20 | render :new 21 | end 22 | end 23 | 24 | def check_email 25 | respond_to do |format| 26 | format.json do 27 | render json: !User.where('lower(email) = ?', params[:user][:email].downcase).where.not(id: params[:id]).exists? 28 | end 29 | end 30 | end 31 | 32 | def check_username 33 | respond_to do |format| 34 | format.json do 35 | render json: !User.where('lower(username) = ?', params[:user][:username].downcase).where.not(id: params[:id]).exists? 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/helpers/admin/comments_helper.rb: -------------------------------------------------------------------------------- 1 | module Admin::CommentsHelper 2 | def link_to_commentable(commentable) 3 | case commentable 4 | when Topic 5 | link_to truncate(commentable.title), admin_topic_path(commentable) 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/helpers/admin/topics_helper.rb: -------------------------------------------------------------------------------- 1 | module Admin::TopicsHelper 2 | def link_to_admin_topic(topic) 3 | if topic.nil? 4 | "Topic had been destroy" 5 | else 6 | link_to topic.title, admin_topic_path(topic) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def return_to_path(path) 3 | case path 4 | when '/', /^\/login/, /^\/signup/ 5 | nil 6 | else 7 | path 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/helpers/comments_helper.rb: -------------------------------------------------------------------------------- 1 | module CommentsHelper 2 | def comment_link(comment, options = {}) 3 | options[:only_path] = true unless options[:only_path] == false 4 | case comment.commentable 5 | when Topic 6 | topic_path(comment.commentable, comment_id: comment.id, anchor: "comment-#{comment.id}", only_path: options[:only_path]) 7 | end 8 | end 9 | 10 | def comment_title(comment) 11 | case comment.commentable 12 | when Topic 13 | comment.commentable.title 14 | else 15 | t 'helpers.comments.deleted_entry' 16 | end 17 | end 18 | 19 | def comment_replace_path(comment) 20 | case comment.commentable 21 | when Topic 22 | topic_last_path(@comment.commentable) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/helpers/markdown_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rouge/plugins/redcarpet' 2 | 3 | module MarkdownHelper 4 | class HTMLRender < Redcarpet::Render::HTML 5 | include Rouge::Plugins::Redcarpet 6 | def block_html(raw_html) 7 | if raw_html =~ /^$/ 8 | raw_html 9 | end 10 | end 11 | end 12 | 13 | class TextReplaceVisitor 14 | def visit(node) 15 | if %w(a pre code).include?(node.name) 16 | return 17 | elsif node.text? 18 | node.replace(process(node.content)) 19 | else 20 | node.children.each do |child| 21 | child.accept(self) 22 | end 23 | end 24 | end 25 | 26 | def process(text) 27 | # link mention 28 | # @username => @username 29 | text.gsub!(/@([a-z0-9][a-z0-9-]*)/i) { |match| 30 | %Q|#{match}| 31 | } 32 | 33 | # link comments 34 | # #123 => #123 35 | text.gsub!(/#(\d+)/) { |match| 36 | %Q|#{match}| 37 | } 38 | 39 | text 40 | end 41 | end 42 | 43 | def markdown_text_replace(html) 44 | doc = Nokogiri::HTML.fragment(html) 45 | doc.accept(TextReplaceVisitor.new) 46 | doc.to_html 47 | end 48 | 49 | def markdown(text) 50 | renderer = HTMLRender.new(hard_wrap: true, 51 | filter_html: true, 52 | link_attributes: { rel: 'nofollow' }) 53 | 54 | markdown = Redcarpet::Markdown.new(renderer, 55 | autolink: true, 56 | tables: true, 57 | space_after_headers: true, 58 | fenced_code_blocks: true) 59 | 60 | markdown.render(text) 61 | end 62 | 63 | def markdown_format(text) 64 | sanitize(markdown_text_replace(markdown(text)), 65 | tags: %w(table th tr td thead tbody p br img h1 h2 h3 h4 blockquote pre code strong em a ul ol li span iframe), 66 | attributes: %w(href src class title alt target rel height width frameborder allowfullscreen)) 67 | end 68 | 69 | def markdown_area(form, name, options = {}) 70 | render partial: 'markdown/area', locals: options.merge(form: form, name: name) 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /app/helpers/notifications_helper.rb: -------------------------------------------------------------------------------- 1 | module NotificationsHelper 2 | def unread_notifications_count 3 | if login? 4 | current_user.notifications.unread.count 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/helpers/timeago_helper.rb: -------------------------------------------------------------------------------- 1 | module TimeagoHelper 2 | def time_ago_tag(time) 3 | time_tag time, data: { behaviors: 'timeago' } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/helpers/topics_helper.rb: -------------------------------------------------------------------------------- 1 | module TopicsHelper 2 | def topic_last_path(topic) 3 | topic_path(topic, page: (topic.total_pages if topic.total_pages > 1), anchor: (topic.comments_count if topic.comments_count > 0)) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/helpers/useres_helper.rb: -------------------------------------------------------------------------------- 1 | module UseresHelper 2 | def user_link(user) 3 | user_root_path(username: user.username) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/jobs/comment_notification_job.rb: -------------------------------------------------------------------------------- 1 | class CommentNotificationJob 2 | @queue = 'notification' 3 | 4 | def self.perform(comment_id) 5 | comment = Comment.find comment_id 6 | create_mention_notification(comment) 7 | create_comment_notification(comment) 8 | end 9 | 10 | def self.create_mention_notification(comment) 11 | users = comment.mention_users - [comment.user] 12 | if comment.commentable.respond_to? :ignored_users 13 | users = users - comment.commentable.ignored_users 14 | end 15 | 16 | users.each do |user| 17 | if user.send_mention_web? 18 | Notification.create(user: user, 19 | subject: comment, 20 | name: 'mention') 21 | end 22 | 23 | if user.confirmed? && user.send_mention_email? 24 | NotificationMailer.mention(user.id, comment.id).deliver 25 | end 26 | end 27 | end 28 | 29 | def self.create_comment_notification(comment) 30 | if comment.commentable.respond_to? :subscribed_users 31 | users = comment.commentable.subscribed_users - comment.mention_users - [comment.user] 32 | 33 | users.each do |user| 34 | if user.send_comment_web? 35 | Notification.create(user: user, 36 | subject: comment, 37 | name: 'comment') 38 | end 39 | 40 | if user.confirmed? && user.send_comment_email? 41 | NotificationMailer.comment(user.id, comment.id).deliver 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/app/mailers/.keep -------------------------------------------------------------------------------- /app/mailers/notification_mailer.rb: -------------------------------------------------------------------------------- 1 | class NotificationMailer < ActionMailer::Base 2 | include Resque::Mailer 3 | 4 | helper :markdown, :comments 5 | 6 | def mention(user_id, comment_id) 7 | @user = User.find user_id 8 | @comment = Comment.find comment_id 9 | I18n.locale = @user.locale 10 | headers(message_id: "#{@comment.commentable_type.downcase.pluralize}/#{@comment.commentable_id}/#{@comment.id}@#{CONFIG['host']}", 11 | in_reply_to: "#{@comment.commentable_type.downcase.pluralize}/#{@comment.commentable_id}@#{CONFIG['host']}") 12 | mail(from: "#{@comment.user.name} ", 13 | to: @user.email, 14 | subject: "#{@comment.commentable.title} ##{@comment.commentable.id}") 15 | end 16 | 17 | def comment(user_id, comment_id) 18 | @user = User.find user_id 19 | @comment = Comment.find comment_id 20 | I18n.locale = @user.locale 21 | headers(message_id: "#{@comment.commentable_type.downcase.pluralize}/#{@comment.commentable_id}/#{@comment.id}@#{CONFIG['host']}", 22 | in_reply_to: "#{@comment.commentable_type.downcase.pluralize}/#{@comment.commentable_id}@#{CONFIG['host']}") 23 | mail(from: "#{@comment.user.name} ", 24 | to: @user.email, 25 | subject: "#{@comment.commentable.title} ##{@comment.commentable.id}") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/mailers/user_mailer.rb: -------------------------------------------------------------------------------- 1 | class UserMailer < ActionMailer::Base 2 | include Resque::Mailer 3 | 4 | helper :markdown 5 | 6 | def password_reset(user_id) 7 | @user = User.find(user_id) 8 | I18n.locale = @user.locale 9 | mail(to: @user.email, 10 | subject: I18n.t('user_mailer.password_reset.subject')) 11 | end 12 | 13 | def confirmation(user_id) 14 | @user = User.find(user_id) 15 | I18n.locale = @user.locale 16 | mail(to: @user.email, 17 | subject: I18n.t('user_mailer.confirmation.subject')) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/app/models/.keep -------------------------------------------------------------------------------- /app/models/attachment.rb: -------------------------------------------------------------------------------- 1 | class Attachment < ActiveRecord::Base 2 | mount_uploader :file, FileUploader 3 | 4 | belongs_to :user 5 | end 6 | -------------------------------------------------------------------------------- /app/models/category.rb: -------------------------------------------------------------------------------- 1 | class Category < ActiveRecord::Base 2 | has_many :topics, dependent: :nullify 3 | 4 | validates :name, presence: true 5 | validates :slug, presence: true, format: { with: /\A[a-zA-Z0-9-]+\z/ }, uniqueness: { case_sensitive: false } 6 | end 7 | -------------------------------------------------------------------------------- /app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ActiveRecord::Base 2 | include Likeable 3 | include Trashable 4 | include MarkdownHelper 5 | 6 | has_many :notifications, as: 'subject', dependent: :delete_all 7 | belongs_to :user, counter_cache: true 8 | belongs_to :commentable, polymorphic: true, counter_cache: true, touch: true 9 | 10 | validates :commentable_type, inclusion: { in: %w(Topic) } 11 | validates :commentable, :user, presence: true 12 | validates :body, presence: true 13 | 14 | after_trash :decrement_counter_cache, :delete_all_notifications 15 | after_restore :increment_counter_cache 16 | after_destroy :increment_counter_cache, if: :trashed? 17 | 18 | def increment_counter_cache 19 | if commentable.has_attribute? :comments_count 20 | commentable.class.update_counters commentable.id, comments_count: 1 21 | end 22 | User.update_counters user.id, comments_count: 1 23 | end 24 | 25 | def decrement_counter_cache 26 | if commentable.has_attribute? :comments_count 27 | commentable.class.update_counters commentable.id, comments_count: -1 28 | end 29 | User.update_counters user.id, comments_count: -1 30 | end 31 | 32 | def delete_all_notifications 33 | notifications.delete_all 34 | end 35 | 36 | def page(per = Comment.default_per_page) 37 | @page ||= ((commentable.comments.where("id < ?", id).count) / per + 1) 38 | end 39 | 40 | def mention_users 41 | return @menton_users if defined?(@menton_users) 42 | 43 | doc = Nokogiri::HTML.fragment(markdown(body)) 44 | usernames = doc.search('text()').map { |node| 45 | unless node.ancestors('a, pre, code').any? 46 | node.text.scan(/@([a-z0-9][a-z0-9-]*)/i).flatten 47 | end 48 | }.flatten.compact.uniq 49 | 50 | @menton_users = User.where(username: usernames) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/concerns/likeable.rb: -------------------------------------------------------------------------------- 1 | module Likeable 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | has_many :likes, as: 'likeable', dependent: :delete_all 6 | end 7 | 8 | def liked_by?(user) 9 | likes.where(user: user).exists? 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/concerns/subscribable.rb: -------------------------------------------------------------------------------- 1 | module Subscribable 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | has_many :subscriptions, as: 'subscribable', dependent: :delete_all 6 | has_many :subscribed_users, -> { where(subscriptions: { status: Subscription.statuses['subscribed'] }) }, through: :subscriptions, source: :user 7 | has_many :ignored_users, -> { where(subscriptions: { status: Subscription.statuses['ignored'] }) }, through: :subscriptions, source: :user 8 | end 9 | 10 | def subscribe_by(user) 11 | subscriptions.find_or_initialize_by(user: user).subscribed! 12 | end 13 | 14 | def subscribed_by?(user) 15 | subscriptions.subscribed.where(user: user).exists? 16 | end 17 | 18 | def ignore_by(user) 19 | subscriptions.find_or_initialize_by(user: user).ignored! 20 | end 21 | 22 | def ignored_by?(user) 23 | subscriptions.ignored.where(user: user).exists? 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/concerns/trashable.rb: -------------------------------------------------------------------------------- 1 | module Trashable 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | default_scope { where(trashed: false) } 6 | scope :trashed, -> { unscope(where: :trashed).where(trashed: true) } 7 | scope :with_trashed, -> { unscope(where: :trashed) } 8 | 9 | define_model_callbacks :trash, :restore 10 | end 11 | 12 | def trash 13 | run_callbacks(:trash) do 14 | update_attribute :trashed, true 15 | end 16 | end 17 | 18 | def restore 19 | run_callbacks(:restore) do 20 | update_attribute :trashed, false 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/models/friend_site.rb: -------------------------------------------------------------------------------- 1 | class FriendSite < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/models/like.rb: -------------------------------------------------------------------------------- 1 | class Like < ActiveRecord::Base 2 | belongs_to :user 3 | belongs_to :likeable, polymorphic: true, counter_cache: true 4 | 5 | validates :user, :likeable, presence: true 6 | end 7 | -------------------------------------------------------------------------------- /app/models/notification.rb: -------------------------------------------------------------------------------- 1 | class Notification < ActiveRecord::Base 2 | belongs_to :user 3 | belongs_to :subject, polymorphic: true 4 | 5 | scope :named, -> (name) { where(name: name) } 6 | scope :unread, -> { where(read: false) } 7 | end 8 | -------------------------------------------------------------------------------- /app/models/subscription.rb: -------------------------------------------------------------------------------- 1 | class Subscription < ActiveRecord::Base 2 | enum status: [ :subscribed, :ignored ] 3 | 4 | belongs_to :user 5 | belongs_to :subscribable, polymorphic: true, counter_cache: true 6 | 7 | validates :user, :subscribable, presence: true 8 | end 9 | -------------------------------------------------------------------------------- /app/models/topic.rb: -------------------------------------------------------------------------------- 1 | class Topic < ActiveRecord::Base 2 | include Likeable 3 | include Trashable 4 | include Subscribable 5 | include Elasticsearch::Model 6 | include Elasticsearch::Model::Callbacks 7 | 8 | TOP_HOT_VALUE = 100000000000000000000 9 | 10 | belongs_to :user, counter_cache: true 11 | belongs_to :category, counter_cache: true 12 | has_many :comments, as: 'commentable' 13 | is_impressionable :counter_cache => true 14 | 15 | validates :title, :body, presence: true 16 | 17 | after_create :update_hot, :owner_subscribe 18 | after_touch :update_hot 19 | 20 | after_trash :decrement_counter_cache 21 | after_restore :increment_counter_cache 22 | # Fix double desc counter 23 | after_destroy :increment_counter_cache, if: :trashed? 24 | 25 | def visit_count 26 | impressionist_count(:filter=>:all) 27 | end 28 | 29 | def increment_counter_cache 30 | if category 31 | Category.update_counters category.id, topics_count: 1 32 | end 33 | User.update_counters user.id, topics_count: 1 34 | end 35 | 36 | def decrement_counter_cache 37 | if category 38 | Category.update_counters category.id, topics_count: -1 39 | end 40 | User.update_counters user.id, topics_count: -1 41 | end 42 | 43 | def calculate_hot 44 | # order = Math.log10([comments_count, 1].max) 45 | # order + created_at.to_f / 45000 * 2 * 30 46 | 47 | updated_at.to_f / 45000 48 | end 49 | 50 | def update_hot 51 | # reload because comments_count has been cache in associations 52 | reload 53 | update_attribute :hot, calculate_hot if hot != TOP_HOT_VALUE 54 | end 55 | 56 | def owner_subscribe 57 | subscribe_by user 58 | end 59 | 60 | def total_pages 61 | (comments_count.to_f / Comment.default_per_page).ceil 62 | end 63 | 64 | def top_it 65 | self.hot = TOP_HOT_VALUE 66 | self.save 67 | end 68 | def cancel_top_it 69 | update_attribute :hot, calculate_hot 70 | end 71 | 72 | def more_like_this(num = 5) 73 | Topic.search( 74 | query: { 75 | more_like_this: { 76 | fields: ['title', 'body'], 77 | like_text: title + '\n' + body 78 | } 79 | }, 80 | filter: { 81 | and: [ 82 | { term: { trashed: false } }, 83 | { not: { term: { id: id } } } 84 | ] 85 | } 86 | ).limit(num).records.to_a rescue [] 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /app/uploaders/avatar_uploader.rb: -------------------------------------------------------------------------------- 1 | class AvatarUploader < CarrierWave::Uploader::Base 2 | include CarrierWave::MiniMagick 3 | 4 | storage :file 5 | 6 | def store_dir 7 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" 8 | end 9 | 10 | VERSION_SIZES = { 11 | normal: 48, 12 | bigger: 96 13 | } 14 | 15 | def default_url 16 | if model.respond_to? :gravatar_url 17 | model.gravatar_url(size: VERSION_SIZES[version_name]) 18 | end 19 | end 20 | 21 | VERSION_SIZES.each do |version_name, size| 22 | version version_name do 23 | process resize_to_fill: [size, size] 24 | end 25 | end 26 | 27 | def extension_white_list 28 | %w(jpg jpeg gif png) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/uploaders/file_uploader.rb: -------------------------------------------------------------------------------- 1 | class FileUploader < CarrierWave::Uploader::Base 2 | include CarrierWave::MiniMagick 3 | 4 | storage :file 5 | 6 | def store_dir 7 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" 8 | end 9 | 10 | def extension_white_list 11 | %w(jpg jpeg gif png) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/views/admin/attachments/destroy.js.erb: -------------------------------------------------------------------------------- 1 | $('#attachment-<%= @attachment.id %>').fadeOut(function() { 2 | $(this).remove(); 3 | }); 4 | -------------------------------------------------------------------------------- /app/views/admin/attachments/index.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.attachments' 2 | 3 | .main 4 | .container 5 | .section 6 | .section-heading 7 | h3.section-title 8 | = t '.attachments' 9 | 10 | .section-body 11 | .row 12 | - @attachments.in_groups_of(3, false) do |group| 13 | - group.each do |attachment| 14 | .col-md-4 id="attachment-#{attachment.id}" 15 | .thumbnail 16 | a href=attachment.file.url 17 | img src=attachment.file.url alt=attachment['file'] 18 | .caption 19 | h4.text-hide-overflow = attachment['file'] 20 | a.text-muted href=admin_user_path(attachment.user) 21 | = attachment.user.name 22 | a.pull-right.text-muted href=admin_attachment_path(attachment) data-remote="true" data-method="delete" data-confirm=t('.delete_confirm') 23 | i.fa.fa-times 24 | .clearfix 25 | 26 | .clearfix 27 | .pull-right 28 | = paginate @attachments, theme: 'campo' 29 | -------------------------------------------------------------------------------- /app/views/admin/categories/_form.html.slim: -------------------------------------------------------------------------------- 1 | = form_for category, url: url_for([:admin, category]), html: { class: 'category-form' } do |f| 2 | = render 'share/flash_messages' 3 | = render 'share/form_error_messages', form: f 4 | .form-group 5 | = f.label :name 6 | = f.text_field :name, class: 'form-control', tabIndex: 1 7 | .form-group 8 | = f.label :slug 9 | = f.text_field :slug, class: 'form-control', tabIndex: 2 10 | .form-group 11 | = f.label :description 12 | = markdown_area f, :description, tabIndex: 3 13 | - unless @category.new_record? 14 | .form-group 15 | = f.label :topics_count 16 | = f.text_field :topics_count, class: 'form-control', disabled: true 17 | .clearfix 18 | - if category.new_record? 19 | = f.submit t('.create_category'), class: 'btn btn-success', tabIndex: 4 20 | - else 21 | = f.submit t('.save_changes'), class: 'btn btn-success', tabIndex: 4 22 | .pull-right 23 | a.btn.btn-danger href=admin_category_path(@category) data-method="delete" data-confirm=t('.permanently_delete_confirm') 24 | = t '.permanently_delete' 25 | 26 | javascript: 27 | $('form.category-form').validate({ 28 | rules: { 29 | 'category[name]': { 30 | required: true 31 | }, 32 | 'category[slug]': { 33 | required: true 34 | } 35 | }, 36 | messages: { 37 | 'category[name]': { 38 | required: '#{@category.errors.generate_message :name, :blank}' 39 | }, 40 | 'category[slug]': { 41 | required: '#{@category.errors.generate_message :slug, :blank}' 42 | } 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /app/views/admin/categories/index.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.categories' 2 | 3 | .main 4 | .container 5 | = render 'share/flash_messages' 6 | 7 | .row 8 | .col-md-8.col-md-push-2 9 | .panel 10 | .panel-heading.claerfix 11 | .pull-right 12 | a.btn.btn-default.btn-success href=new_admin_category_path 13 | = t '.new_category' 14 | h2.panel-title 15 | = t '.categories' 16 | .panel-body 17 | .list-group.list-group-campo 18 | - @categories.each do |category| 19 | a.list-group-item href=admin_category_path(category) 20 | .list-group-item-heading 21 | b = category.name 22 | ' 23 | span.text-muted = category.slug 24 | .text-muted 25 | = t '.topics_count', count: category.topics_count 26 | -------------------------------------------------------------------------------- /app/views/admin/categories/new.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.new_category' 2 | 3 | .main 4 | .container 5 | .row 6 | .col-md-8.col-md-push-2 7 | .panel 8 | .panel-heading 9 | h3.panel-title = t '.new_category' 10 | .panel-body 11 | = render 'form', category: @category 12 | -------------------------------------------------------------------------------- /app/views/admin/categories/show.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = @category.name 2 | 3 | .main 4 | .container 5 | .row 6 | .col-md-8.col-md-push-2 7 | .panel 8 | .panel-heading 9 | h3.panel-title = @category.name 10 | .panel-body 11 | = render 'form', category: @category 12 | -------------------------------------------------------------------------------- /app/views/admin/comments/index.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.comments' 2 | 3 | .sub-navbar 4 | ul.nav.sub-navbar-nav 5 | li class=('active' if action_name == 'index') 6 | a href=admin_comments_path 7 | = t '.normal' 8 | li class=('active' if action_name == 'trashed') 9 | a href=trashed_admin_comments_path 10 | = t '.trashed' 11 | 12 | .main 13 | .container 14 | = render 'share/flash_messages' 15 | 16 | .row 17 | .col-md-8.col-md-push-2 18 | .panel 19 | .panel-heading 20 | h2.panel-title 21 | = t '.comments' 22 | .panel-body 23 | .list-group.list-group-campo 24 | - @comments.each do |comment| 25 | a.list-group-item href=admin_comment_path(comment) 26 | .list-group-item-heading 27 | = comment.id 28 | = '. ' 29 | b = truncate comment.body 30 | .text-muted 31 | = comment.user.name 32 | = ' · ' 33 | = time_ago_tag comment.created_at 34 | .panel-footer.clearfix 35 | .pull-right 36 | = paginate @comments, theme: 'campo' 37 | -------------------------------------------------------------------------------- /app/views/admin/comments/show.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.comments' 2 | 3 | .main 4 | .container 5 | .row 6 | .col-md-8.col-md-push-2 7 | .panel 8 | .panel-heading 9 | h3.panel-title = t '.comments' 10 | .panel-body 11 | = form_for [:admin, @comment], html: { class: 'comment-form' } do |f| 12 | = render 'share/flash_messages' 13 | = render 'share/form_error_messages', form: f 14 | .form-group 15 | = f.label :commentable 16 | p 17 | = link_to_commentable @comment.commentable 18 | .form-group 19 | = f.label :user 20 | p 21 | a href=admin_user_path(@comment.user) 22 | = @comment.user.name 23 | .form-group 24 | = markdown_area f, :body, tabIndex: 1 25 | .form-group 26 | = f.label :created_at 27 | = f.text_field :created_at, class: 'form-control', disabled: true 28 | .clearfix 29 | = f.submit t('.save_changes'), class: 'btn btn-success', tabIndex: 2 30 | .pull-right 31 | - if @comment.trashed? 32 | a.btn.btn-default href=restore_admin_comment_path(@comment) data-method="patch" 33 | = t '.restore' 34 | - else 35 | a.btn.btn-default href=trash_admin_comment_path(@comment) data-method="delete" data-confirm=t('.delete_confirm') 36 | = t '.delete' 37 | 38 | javascript: 39 | $('form.comment-form').validate({ 40 | rules: { 41 | 'comment[body]': { 42 | required: true 43 | } 44 | }, 45 | messages: { 46 | 'comment[body]': { 47 | required: '#{@comment.errors.generate_message :body, :blank}' 48 | } 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /app/views/admin/dashboard/show.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .panel 4 | .panel-heading 5 | h2.panel-title 6 | = t '.dashboard' 7 | .panel-body 8 | -------------------------------------------------------------------------------- /app/views/admin/friend_sites/_form.html.slim: -------------------------------------------------------------------------------- 1 | = form_for @friend_site, url: url_for([:admin, @friend_site]), html: { class: 'category-form' } do |f| 2 | = f.text_field :name, class: 'form-control', placeholder: "网站名", tabIndex: 1 3 | br 4 | = f.text_field :url, class: 'form-control', placeholder: "网站地址", tabIndex: 1 5 | br 6 | = f.submit "保存友情链接", class: 'btn btn-success' -------------------------------------------------------------------------------- /app/views/admin/friend_sites/edit.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .col-md-9 4 | .panel 5 | .panel-heading 6 | h3.panel-title = "友情链接管理" 7 | .panel-body 8 | =render 'form' 9 | 10 | -------------------------------------------------------------------------------- /app/views/admin/friend_sites/index.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .col-md-9 4 | .panel 5 | .panel-body 6 | =render 'form' 7 | .panel 8 | .panel-heading 9 | h3.panel-title = "已添加链接" 10 | 11 | .panel-body 12 | - @friend_sites.each do |f| 13 | .row 14 | .col-md-3 15 | h4 16 | =f.name 17 | .col-md-6 18 | h4 19 | =f.url 20 | .col-md-1.5 21 | h4 22 | = link_to "编辑", edit_admin_friend_site_path(f) 23 | .col-md-1.5 24 | h4 25 | = link_to "删除", admin_friend_site_path(f), method: :delete, data: { confirm: 'Are you sure?' } 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/views/admin/topics/index.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.topics' 2 | 3 | .sub-navbar 4 | ul.nav.sub-navbar-nav 5 | li class=('active' if action_name == 'index') 6 | a href=admin_topics_path 7 | = t '.normal' 8 | li class=('active' if action_name == 'trashed') 9 | a href=trashed_admin_topics_path 10 | =t '.trashed' 11 | 12 | .main 13 | .container 14 | = render 'share/flash_messages' 15 | 16 | .row 17 | .col-md-8.col-md-push-2 18 | .panel 19 | .panel-heading 20 | h2.panel-title 21 | = t '.topics' 22 | .panel-body 23 | .list-group.list-group-campo 24 | - @topics.each do |topic| 25 | a.list-group-item href=admin_topic_path(topic) 26 | .list-group-item-heading 27 | = topic.id 28 | = '. ' 29 | b = truncate topic.title 30 | .text-muted 31 | = topic.user.name 32 | = ' · ' 33 | = time_ago_tag topic.created_at 34 | = ' · ' 35 | = topic.comments_count 36 | ' 37 | | comments 38 | .panel-footer.clearfix 39 | .pull-right 40 | = paginate @topics, theme: 'campo' 41 | -------------------------------------------------------------------------------- /app/views/admin/users/index.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.users' 2 | 3 | .sub-navbar 4 | ul.nav.sub-navbar-nav 5 | li class=('active' if action_name == 'index') 6 | a href=admin_users_path 7 | = t '.normal' 8 | li class=('active' if action_name == 'locked') 9 | a href=locked_admin_users_path 10 | = t '.locked' 11 | 12 | .main 13 | .container 14 | = render 'share/flash_messages' 15 | 16 | .row 17 | .col-md-8.col-md-push-2 18 | .panel 19 | .panel-heading 20 | h2.panel-title 21 | = t '.users' 22 | .panel-body 23 | .list-group.list-group-campo 24 | - @users.each do |user| 25 | a.list-group-item href=admin_user_path(user) 26 | .list-group-item-heading 27 | = user.id 28 | = '. ' 29 | b = user.name 30 | ' 31 | span.text-muted = "@#{user.username}" 32 | .text-muted 33 | = user.email 34 | = ' · ' 35 | = time_ago_tag user.created_at 36 | .panel-footer.clearfix 37 | .pull-right 38 | = paginate @users, theme: 'campo' 39 | -------------------------------------------------------------------------------- /app/views/comments/_comment.html.slim: -------------------------------------------------------------------------------- 1 | - cache [comment, locale] do 2 | li.list-group-item.comment id="comment-#{comment.id}" data-creator-id=comment.user_id 3 | a.list-group-item-avatar href=user_link(comment.user) 4 | img.img-rounded alt="avatar" src=comment.user.avatar.normal.url 5 | .list-group-item-content 6 | .list-group-item-heading 7 | a.comment-user href=user_link(comment.user) data-name=comment.user.username 8 | b = comment.user.name 9 | ' 10 | = "@#{comment.user.username}" 11 | = ' · ' 12 | a href=comment_link(comment) 13 | = time_ago_tag comment.created_at 14 | article.comment-body 15 | = markdown_format comment.body 16 | .list-group-item-actions.clearfix 17 | .pull-right 18 | a.btn data-reply-to="@#{comment.user.username} ##{comment.id} " 19 | i.fa.fa-reply 20 | a.btn id="like-for-comment-#{comment.id}" href=comment_like_path(comment) data-remote="true" data-method="post" 21 | i.fa.fa-heart 22 | ' 23 | span.count 24 | - if comment.likes_count > 0 25 | = comment.likes_count 26 | a.btn href=edit_comment_path(comment) data-remote="true" data-visible-to="creator" 27 | i.fa.fa-pencil 28 | a.btn href=trash_comment_path(comment) data-remote="true" data-method="delete" data-visible-to="creator" data-confirm=t('.delete_confirm') 29 | i.fa.fa-trash-o 30 | -------------------------------------------------------------------------------- /app/views/comments/_form.html.slim: -------------------------------------------------------------------------------- 1 | .list-group-item-avatar 2 | img.img-rounded alt="avatar" src=comment.user.avatar.normal.url 3 | .list-group-item-content 4 | .list-group-item-heading 5 | a.comment-user href=user_link(comment.user) 6 | b = comment.user.name 7 | ' 8 | = "@#{comment.user.username}" 9 | = ' · ' 10 | a href=comment_link(comment) 11 | = time_ago_tag comment.created_at 12 | = form_for comment, remote: true do |f| 13 | .form-group 14 | = markdown_area f, :body 15 | = f.submit t('.save_changes'), class: 'btn btn-success', 'data-disable-with' => t('.save_changes') 16 | ' 17 | a.btn.btn-default href=cancel_comment_path(comment) data-remote="true" 18 | = t '.cancel' 19 | 20 | javascript: 21 | $('#edit_comment_#{comment.id}').validate({ 22 | rules: { 23 | 'comment[body]': { 24 | required: true 25 | } 26 | }, 27 | messages: { 28 | 'comment[body]': { 29 | required: '#{@comment.errors.generate_message :body, :blank}' 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /app/views/comments/cancel.js.erb: -------------------------------------------------------------------------------- 1 | $('#comment-<%= @comment.id %>').replaceWith('<%= j render @comment %>'); 2 | <% if current_user.likes.where(likeable: @comment).exists? %> 3 | campo.Likes.updateLike('comment', <%= @comment.id %>); 4 | <% end %> 5 | -------------------------------------------------------------------------------- /app/views/comments/create.js.erb: -------------------------------------------------------------------------------- 1 | <% if @comment.errors.empty? %> 2 | $('#comments-for-<%= @comment.commentable_type.downcase %>-<%= @comment.commentable_id %>').append('<%= j render @comment %>').find('.empty-message').remove(); 3 | $('#new_comment textarea').val(''); 4 | $('#new_comment .preview').val(''); 5 | $('#new_comment [data-toggle="tab"]:first').tab('show'); 6 | history.replaceState(history.state, '', '<%= comment_replace_path(@comment) %>'); 7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/views/comments/edit.js.erb: -------------------------------------------------------------------------------- 1 | $('#comment-<%= @comment.id %>').html('<%= j render 'form', comment: @comment %>'); 2 | -------------------------------------------------------------------------------- /app/views/comments/trash.js.erb: -------------------------------------------------------------------------------- 1 | $('#comment-<%= @comment.id %>').slideUp(function() { 2 | $(this).remove(); 3 | }); 4 | -------------------------------------------------------------------------------- /app/views/comments/update.js.erb: -------------------------------------------------------------------------------- 1 | <% if @comment.errors.empty? %> 2 | $('#comment-<%= @comment.id %>').replaceWith('<%= j render @comment %>'); 3 | <% if @comment.liked_by?(current_user) %> 4 | campo.Likes.updateLike('comment', <%= @comment.id %>); 5 | <% end %> 6 | <% end %> 7 | -------------------------------------------------------------------------------- /app/views/kaminari/campo/_gap.html.slim: -------------------------------------------------------------------------------- 1 | li.page.gap 2 | a.btn.disabled ... 3 | -------------------------------------------------------------------------------- /app/views/kaminari/campo/_next_page.html.slim: -------------------------------------------------------------------------------- 1 | a.btn.btn-default href=url class=('disabled' if current_page.last?) 2 | span.glyphicon.glyphicon-chevron-right 3 | -------------------------------------------------------------------------------- /app/views/kaminari/campo/_page.html.slim: -------------------------------------------------------------------------------- 1 | li.page 2 | a.btn href=url class=('active' if page.current?) 3 | = page 4 | -------------------------------------------------------------------------------- /app/views/kaminari/campo/_paginator.html.slim: -------------------------------------------------------------------------------- 1 | = paginator.render do 2 | nav.pagination-campo 3 | a.btn data-behaviors="pagination-popover" data-placement="top" data-toggle="button" 4 | = I18n.t 'karminari.campo.paginator.page', page: "#{current_page} / #{total_pages}" 5 | .popover 6 | .popover-content 7 | ul.pages 8 | - each_page do |page| 9 | - if page.left_outer? || page.right_outer? || page.inside_window? 10 | == page_tag page 11 | - elsif !page.was_truncated? 12 | == gap_tag 13 | .btn-group 14 | == prev_page_tag 15 | == next_page_tag 16 | -------------------------------------------------------------------------------- /app/views/kaminari/campo/_prev_page.html.slim: -------------------------------------------------------------------------------- 1 | a.btn.btn-default href=url class=('disabled' if current_page.first?) 2 | span.glyphicon.glyphicon-chevron-left 3 | -------------------------------------------------------------------------------- /app/views/layouts/admin.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title 5 | = @page_title ? "#{@page_title} - #{t('.admin', name: CONFIG['title'])}" : t('.admin', name: CONFIG['title']) 6 | = stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true 7 | = javascript_include_tag "application", "data-turbolinks-track" => true 8 | meta name="viewport" content="width=device-width, initial-scale=1.0" 9 | = csrf_meta_tags 10 | - if login? 11 | javascript: 12 | campo.currentUser = { 13 | id: #{current_user.id} 14 | }; 15 | 16 | body 17 | nav.navbar.navbar-campo.navbar-static-top.navbar-inverse 18 | .container 19 | .navbar-header 20 | a.navbar-brand href=admin_root_path 21 | = t '.admin', name: CONFIG['title'] 22 | ul.nav.navbar-nav.navbar-right 23 | li 24 | a href=root_path 25 | i.fa.fa-sign-out 26 | li.navbar-toggle 27 | a.tiem type="button" data-toggle="collapse" data-target="#navbar" 28 | i.fa.fa-bars 29 | #navbar.collapse.navbar-collapse 30 | ul.nav.navbar-nav 31 | li class=('active' if controller_name == 'users') 32 | a href=admin_users_path 33 | = t '.users' 34 | li class=('active' if controller_name == 'categories') 35 | a href=admin_categories_path 36 | = t '.categories' 37 | li class=('active' if controller_name == 'topics') 38 | a href=admin_topics_path 39 | = t '.topics' 40 | li class=('active' if controller_name == 'comments') 41 | a href=admin_comments_path 42 | = t '.comments' 43 | li class=('active' if controller_name == 'attachments') 44 | a href=admin_attachments_path 45 | = t '.attachments' 46 | li class=('active' if controller_name == 'friend_sites') 47 | a href=admin_friend_sites_path 48 | = t '.friend_sites' 49 | 50 | = yield 51 | -------------------------------------------------------------------------------- /app/views/likes/create.js.erb: -------------------------------------------------------------------------------- 1 | campo.Likes.updateLike('<%= @likeable.class.name.downcase %>', <%= @likeable.id %>); 2 | $('#like-for-<%= @likeable.class.name.downcase %>-<%= @likeable.id %> .count').text('<%= @likeable.likes.count %>'); 3 | -------------------------------------------------------------------------------- /app/views/likes/destroy.js.erb: -------------------------------------------------------------------------------- 1 | campo.Likes.updateLike('<%= @likeable.class.name.downcase %>', <%= @likeable.id %>, false); 2 | $('#like-for-<%= @likeable.class.name.downcase %>-<%= @likeable.id %> .count').text('<%= @likeable.likes.count > 0 ? @likeable.likes.count : nil %>'); 3 | -------------------------------------------------------------------------------- /app/views/markdown/_area.html.slim: -------------------------------------------------------------------------------- 1 | - resource = form.object 2 | - resource_name = resource.class.name.downcase 3 | - write_pane_id = "write_#{resource_name}_#{resource.try(:id) || 'new'}" 4 | - preview_pane_id = "preview_#{resource_name}_#{resource.try(:id) || 'new'}" 5 | - tabIndex ||= nil 6 | 7 | .markdown-area 8 | ul.nav.nav-tabs 9 | li.active 10 | a href="##{write_pane_id}" data-toggle="tab" 11 | = t '.write' 12 | li 13 | a href="##{preview_pane_id}" data-toggle="tab" data-behaviors="preview" 14 | = t '.preview' 15 | .tab-content 16 | .tab-pane.active id=write_pane_id 17 | = form.text_area name, class: 'form-control', data: { behaviors: 'autosize' }, tabIndex: tabIndex 18 | .file-upload 19 | = file_field_tag :file, multiple: true, tabIndex: 99 20 | .text.text-muted 21 | = t '.attach_images_by' 22 | ' 23 | a = t '.select_them' 24 | .tab-pane id=preview_pane_id 25 | article.preview 26 | -------------------------------------------------------------------------------- /app/views/markdown/preview.html.slim: -------------------------------------------------------------------------------- 1 | = markdown_format params[:body] 2 | -------------------------------------------------------------------------------- /app/views/notification_mailer/comment.html.slim: -------------------------------------------------------------------------------- 1 | = sanitize markdown @comment.body 2 | 3 | p 4 | a href=comment_link(@comment, only_path: false) 5 | = t '.view_it_on_web' 6 | -------------------------------------------------------------------------------- /app/views/notification_mailer/comment.text.erb: -------------------------------------------------------------------------------- 1 | <%= @comment.body %> 2 | 3 | --- 4 | 5 | <%= t '.view_it_on_web' %> <%= comment_link(@comment, only_path: false) %> 6 | -------------------------------------------------------------------------------- /app/views/notification_mailer/mention.html.slim: -------------------------------------------------------------------------------- 1 | = sanitize markdown @comment.body 2 | 3 | p 4 | a href=comment_link(@comment, only_path: false) 5 | = t '.view_it_on_web' 6 | -------------------------------------------------------------------------------- /app/views/notification_mailer/mention.text.erb: -------------------------------------------------------------------------------- 1 | <%= @comment.body %> 2 | 3 | --- 4 | 5 | <%= t '.view_it_on_web' %> <%= comment_link(@comment, only_path: false) %> 6 | -------------------------------------------------------------------------------- /app/views/notifications/clear.js.erb: -------------------------------------------------------------------------------- 1 | Turbolinks.visit('<%= notifications_path %>'); 2 | -------------------------------------------------------------------------------- /app/views/notifications/destroy.js.erb: -------------------------------------------------------------------------------- 1 | $('#notification_<%= @notification.id %>').slideUp(function () { $(this).remove() }); 2 | -------------------------------------------------------------------------------- /app/views/notifications/index.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .row 4 | .col-md-8.col-md-push-2 5 | .panel 6 | .panel-heading 7 | .pull-right 8 | a.btn.btn-default href=settings_notifications_path 9 | i.fa.fa-cog 10 | ' 11 | a.btn.btn-default href=clear_notifications_path data-remote="true" data-method="delete" data-confirm=t('.delete_all_confirm') 12 | = t '.delete_all' 13 | h3.panel-title 14 | = t '.notifications' 15 | .panel-body 16 | ul.list-group.list-group-campo 17 | - if @notifications.any? 18 | - @notifications.each do |notification| 19 | = render "notifications/notification/#{notification.name}", notification: notification 20 | - else 21 | li.list-group-item.text-center.text-muted.empty-message 22 | = t '.no_notifications' 23 | .panel-footer.clearfix 24 | .pull-right 25 | = paginate @notifications, theme: 'campo' 26 | -------------------------------------------------------------------------------- /app/views/notifications/mark.js.erb: -------------------------------------------------------------------------------- 1 | Turbolinks.visit('<%= notifications_path %>'); 2 | -------------------------------------------------------------------------------- /app/views/notifications/notification/_comment.html.slim: -------------------------------------------------------------------------------- 1 | - cache [notification, locale] do 2 | - comment = notification.subject 3 | li.list-group-item.notification id="notification_#{notification.id}" class=('read' if notification.read) 4 | a.btn.list-group-item-remove href=notification_path(notification) data-remote="true" data-method="delete" 5 | i.fa.fa-times 6 | 7 | a.list-group-item-link href=comment_link(comment) 8 | .list-group-item-avatar 9 | img.img-rounded alt="avatar" src=comment.user.avatar.normal.url 10 | .list-group-item-content 11 | .list-group-item-heading 12 | b = comment.user.name 13 | ' 14 | = t '.commented_on' 15 | ' 16 | b = comment_title(comment) 17 | ' 18 | = time_ago_tag comment.created_at 19 | .text-muted 20 | = truncate comment.body, length: 140 21 | -------------------------------------------------------------------------------- /app/views/notifications/notification/_mention.html.slim: -------------------------------------------------------------------------------- 1 | - cache [notification, locale] do 2 | - comment = notification.subject 3 | li.list-group-item.notification id="notification_#{notification.id}" class=('read' if notification.read) 4 | a.btn.list-group-item-remove href=notification_path(notification) data-remote="true" data-method="delete" 5 | i.fa.fa-times 6 | 7 | a.list-group-item-link href=comment_link(comment) 8 | .list-group-item-avatar 9 | img.img-rounded alt="avatar" src=comment.user.avatar.normal.url 10 | .list-group-item-content 11 | .list-group-item-heading 12 | b = comment.user.name 13 | ' 14 | = t '.mentioned_you_on' 15 | ' 16 | b = comment_title(comment) 17 | ' 18 | = time_ago_tag comment.created_at 19 | .text-muted 20 | = truncate comment.body, length: 140 21 | -------------------------------------------------------------------------------- /app/views/qunit/index.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | = stylesheet_link_tag "qunit" 5 | = javascript_include_tag "test_helper" 6 | body data-no-turbolink=true 7 | #qunit 8 | #qunit-fixture 9 | -------------------------------------------------------------------------------- /app/views/sessions/access_limiter.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .row 4 | .col-md-6.col-md-offset-3 5 | .panel 6 | .panel-heading 7 | h3.panel-title 8 | = t '.try_too_many_times' 9 | .panel-body 10 | = render 'share/flash_messages' 11 | = t '.are_you' 12 | ' 13 | a href=new_users_password_path 14 | = t '.forgot_password' 15 | -------------------------------------------------------------------------------- /app/views/sessions/new.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .row 4 | .col-md-6.col-md-offset-3 5 | .panel 6 | .panel-heading 7 | h3.panel-title 8 | = t '.sign_in' 9 | .panel-body 10 | = form_tag login_path 11 | = render 'share/flash_messages' 12 | .form-group 13 | = label_tag t('.login') 14 | = text_field_tag :login, params[:login], placeholder: t('.username_or_email'), tabindex: 1, class: 'form-control' 15 | .form-group 16 | = label_tag t('.password') 17 | = password_field_tag :password, nil, placeholder: t('.password'), tabindex: 2, class: 'form-control' 18 | = submit_tag t('.sign_in'), class: 'btn btn-success', tabindex: 5 19 | hr 20 | a href=new_users_password_path 21 | = t '.forgot_password' 22 | -------------------------------------------------------------------------------- /app/views/settings/_sidebar.html.slim: -------------------------------------------------------------------------------- 1 | .list-group 2 | a.list-group-item class=('active' if controller_name == 'accounts') href=settings_account_path 3 | .pull-right 4 | i.fa.fa-chevron-right 5 | = t '.account' 6 | a.list-group-item class=('active' if controller_name == 'passwords') href=settings_password_path 7 | .pull-right 8 | i.fa.fa-chevron-right 9 | = t '.password' 10 | a.list-group-item class=('active' if controller_name == 'profiles') href=settings_profile_path 11 | .pull-right 12 | i.fa.fa-chevron-right 13 | = t '.profile' 14 | a.list-group-item class=('active' if controller_name == 'notifications') href=settings_notifications_path 15 | .pull-right 16 | i.fa.fa-chevron-right 17 | = t '.notifications' 18 | -------------------------------------------------------------------------------- /app/views/settings/notifications/show.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.notifications_settings' 2 | 3 | .main 4 | .container 5 | .row 6 | .col-md-3 7 | = render 'settings/sidebar' 8 | .col-md-9 9 | .panel 10 | .panel-heading 11 | h3.panel-title 12 | = t '.notifications' 13 | .panel-body 14 | = form_for @user, url: settings_notifications_path, html: { class: 'form-horizontal user-form' } do |f| 15 | = render 'share/flash_messages' 16 | .form-group 17 | label.control-label.col-sm-3 18 | = t '.comment' 19 | .col-sm-6 20 | label.checkbox-inline 21 | = f.check_box :send_comment_web 22 | = t '.web' 23 | label.checkbox-inline 24 | = f.check_box :send_comment_email 25 | = t '.email' 26 | .help-block 27 | = t '.when_receive_new_comment_on_watching_items' 28 | .form-group 29 | label.control-label.col-sm-3 30 | = t '.mention' 31 | .col-sm-6 32 | label.checkbox-inline 33 | = f.check_box :send_mention_web 34 | = t '.web' 35 | label.checkbox-inline 36 | = f.check_box :send_mention_email 37 | = t '.email' 38 | .help-block 39 | = t '.when_someone_mention_you_with_username' 40 | .form-group 41 | .col-sm-6.col-sm-push-3 42 | = f.submit t('.save_changes'), class: 'btn btn-primary', tabIndex: 5 43 | -------------------------------------------------------------------------------- /app/views/settings/passwords/show.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.password_settings' 2 | 3 | .main 4 | .container 5 | .row 6 | .col-md-3 7 | = render 'settings/sidebar' 8 | .col-md-9 9 | .panel 10 | .panel-heading 11 | h3.panel-title 12 | = t '.password' 13 | .panel-body 14 | = form_for @user, url: settings_password_path, html: { class: 'form-horizontal password-form' } do |f| 15 | = render 'share/flash_messages' 16 | = render 'share/form_error_messages', form: f 17 | .form-group 18 | = label_tag :current_password, t('.current_password'), class: 'control-label col-sm-3' 19 | .col-sm-6 20 | = password_field_tag :current_password, nil, class: 'form-control', tabIndex: 1 21 | .form-group 22 | = f.label :password, class: 'control-label col-sm-3' 23 | .col-sm-6 24 | = f.password_field :password, class: 'form-control', tabIndex: 2 25 | .form-group 26 | = f.label :password_confirmation, class: 'control-label col-sm-3' 27 | .col-sm-6 28 | = f.password_field :password_confirmation, class: 'form-control', tabIndex: 3 29 | .form-group 30 | .col-sm-6.col-sm-push-3 31 | = f.submit t('.save_changes'), class: 'btn btn-primary', tabIndex: 4 32 | 33 | javascript: 34 | $('form.password-form').validate({ 35 | rules: { 36 | 'current_password': 'required', 37 | 'user[password]': 'required', 38 | 'user[password_confirmation]': { 39 | equalTo: '#user_password' 40 | } 41 | }, 42 | messages: { 43 | 'current_password': '#{@user.errors.generate_message :password, :blank}', 44 | 'user[password]': '#{@user.errors.generate_message :password, :blank}', 45 | 'user[password_confirmation]': { 46 | equalTo: '#{@user.errors.generate_message :password_confirmation, :confirmation}' 47 | } 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /app/views/settings/profiles/show.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.profile_settings' 2 | 3 | .main 4 | .container 5 | .row 6 | .col-md-3 7 | = render 'settings/sidebar' 8 | .col-md-9 9 | .panel 10 | .panel-heading 11 | h3.panel-title 12 | = t '.profile' 13 | .panel-body 14 | = form_for @user, url: settings_profile_path, html: { multipart: true, class: 'form-horizontal user-form' } do |f| 15 | = render 'share/flash_messages' 16 | = render 'share/form_error_messages', form: f 17 | .form-group 18 | = f.label :avatar, class: 'control-label col-sm-3' 19 | .col-sm-6.user-image-uploader 20 | .image 21 | img.img-rounded alt="avatar" src=current_user.avatar.bigger.url 22 | .uploader 23 | = f.file_field :avatar, tabIndex: 1 24 | .checkbox 25 | label 26 | = f.check_box :remove_avatar, tabIndex: 2 27 | ' 28 | | Remove avatar 29 | .form-group 30 | = f.label :name, class: 'control-label col-sm-3' 31 | .col-sm-6 32 | = f.text_field :name, class: 'form-control', tabIndex: 3 33 | .form-group 34 | = f.label :bio, class: 'control-label col-sm-3' 35 | .col-sm-6 36 | = f.text_area :bio, class: 'form-control', tabIndex: 4 37 | .form-group 38 | .col-sm-6.col-sm-push-3 39 | = f.submit t('.save_changes'), class: 'btn btn-primary', tabIndex: 5 40 | 41 | javascript: 42 | $('form.user-form').validate({ 43 | rules: { 44 | 'user[name]': { 45 | required: true, 46 | } 47 | }, 48 | messages: { 49 | 'user[name]': { 50 | required: '#{@user.errors.generate_message :name, :blank}' 51 | } 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /app/views/share/_flash_messages.html.slim: -------------------------------------------------------------------------------- 1 | - [:success, :info, :warning, :danger].each do |type| 2 | - if flash[type] 3 | .alert class="alert-#{type}" 4 | a.close data-dismiss="alert" 5 | | × 6 | = flash[type] 7 | -------------------------------------------------------------------------------- /app/views/share/_form_error_messages.html.slim: -------------------------------------------------------------------------------- 1 | - if form.object.errors.any? 2 | .alert.alert-warning 3 | a.close data-dismiss="alert" 4 | | × 5 | - form.object.errors.full_messages.each do |message| 6 | p = message 7 | -------------------------------------------------------------------------------- /app/views/share/_user_confirm_required.html.slim: -------------------------------------------------------------------------------- 1 | #user-confirm-required 2 | .container 3 | .alert.alert-warning 4 | p 5 | = t '.description', email: current_user.email 6 | p 7 | a.btn.btn-default href=users_confirmation_path data-remote="true" data-method="post" 8 | = t '.resend_email' 9 | ' 10 | a.btn href=settings_account_path 11 | = t '.change_email' 12 | 13 | #user-confirm-required-modal.modal.fade 14 | .modal-dialog 15 | .modal-content 16 | .modal-header 17 | button.close data-dismiss="modal" 18 | | × 19 | .modal-title 20 | -------------------------------------------------------------------------------- /app/views/subscriptions/_subscription.html.slim: -------------------------------------------------------------------------------- 1 | - subscription = subscribable.subscriptions.find_by(user: current_user) 2 | .btn-group id="subscription-for-#{subscribable.class.name.downcase}-#{subscribable.id}" 3 | .btn.btn-default.dropdown-toggle data-toggle="dropdown" 4 | - if subscription.nil? 5 | i.fa.fa-eye 6 | ' 7 | = t '.watch' 8 | ' 9 | i.fa.fa-caret-down 10 | - elsif subscription.subscribed? 11 | i.fa.fa-eye-slash 12 | ' 13 | = t '.unwatch' 14 | ' 15 | i.fa.fa-caret-down 16 | - elsif subscription.ignored? 17 | i.fa.fa-ban 18 | ' 19 | = t '.stop_ignoring' 20 | ' 21 | i.fa.fa-caret-down 22 | ul.dropdown-menu.dropdown-menu-subscription 23 | li class=('selected' if subscription.nil?) 24 | a href=polymorphic_url([subscribable, :subscription]) data-remote="true" data-method="delete" 25 | .subscription-status 26 | i.fa.fa-check 27 | .subscription-description 28 | label = t '.not_watching' 29 | p = t '.you_only_receive_notifications_if_you_are_mentioned' 30 | li class=('selected' if subscription && subscription.subscribed?) 31 | a href=polymorphic_url([subscribable, :subscription], status: 'subscribed') data-remote="true" data-method="put" 32 | .subscription-status 33 | i.fa.fa-check 34 | .subscription-description 35 | label = t '.watching' 36 | p = t '.you_will_receive_notifications_for_all_comments' 37 | li class=('selected' if subscription && subscription.ignored?) 38 | a href=polymorphic_url([subscribable, :subscription], status: 'ignored') data-remote="true" data-method="put" 39 | .subscription-status 40 | i.fa.fa-check 41 | .subscription-description 42 | label = t '.ignoring' 43 | p = t '.you_do_not_receive_any_notifications' 44 | -------------------------------------------------------------------------------- /app/views/subscriptions/update.js.erb: -------------------------------------------------------------------------------- 1 | $('#subscription-for-<%= @subscribable.class.name.downcase %>-<%= @subscribable.id %>').replaceWith('<%= j render 'subscription', subscribable: @subscribable %>'); 2 | -------------------------------------------------------------------------------- /app/views/topics/_form.html.slim: -------------------------------------------------------------------------------- 1 | = form_for @topic, remote: true, html: { class: 'topic-form' } do |f| 2 | .row 3 | .col-md-9 4 | .form-group 5 | = f.text_field :title, class: 'form-control', placeholder: t('.title'), tabIndex: 1 6 | .col-md-3 7 | .form-group 8 | = f.collection_select :category_id, Category.order(topics_count: :desc), :id, :name, { include_blank: t('.no_category')}, class: 'form-control', tabIndex: 2 9 | .form-group 10 | = markdown_area f, :body, tabIndex: 3 11 | - if topic.new_record? 12 | = f.submit t('.create_topic'), class: 'btn btn-success', tabIndex: 4, 'data-disable-with' => t('.create_topic') 13 | - else 14 | = f.submit t('.save_changes'), class: 'btn btn-success', tabIndex: 4, 'data-disable-with' => t('.save_changes') 15 | ' 16 | a.btn.btn-default href=topic_path(@topic) tabIndex="5" 17 | = t '.cancel' 18 | 19 | - form_id = @topic.new_record? ? 'new_topic' : "edit_topic_#{@topic.id}" 20 | 21 | javascript: 22 | $('##{form_id}').validate({ 23 | rules: { 24 | 'topic[title]': { 25 | required: true 26 | }, 27 | 'topic[body]': { 28 | required: true 29 | } 30 | }, 31 | messages: { 32 | 'topic[title]': { 33 | required: '#{@topic.errors.generate_message :title, :blank}' 34 | }, 35 | 'topic[body]': { 36 | required: '#{@topic.errors.generate_message :body, :blank}' 37 | } 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /app/views/topics/_search_form.html.slim: -------------------------------------------------------------------------------- 1 | .panel 2 | .panel-body 3 | = form_tag search_topics_path, method: 'get', class: 'search-form', data: { behaviors: 'turboform' } do 4 | .input-group 5 | = text_field_tag :q, params[:q], class: 'form-control', tabIndex: 1, autocomplete: 'off', autofocus: params[:q].present? 6 | .input-group-btn 7 | button.btn.btn-default type='submit' tabIndex="2" 8 | i.fa.fa-search 9 | -------------------------------------------------------------------------------- /app/views/topics/_topic.html.slim: -------------------------------------------------------------------------------- 1 | - cache [topic, locale] do 2 | a.list-group-item.topic href=topic_last_path(topic) 3 | .list-group-item-avatar 4 | img.img-rounded alt="avatar" src=topic.user.avatar.normal.url 5 | .list-group-item-content 6 | .list-group-item-heading 7 | b = topic.title 8 | -if topic.hot == Topic::TOP_HOT_VALUE 9 | b.pull-right = t '.top' 10 | .text-muted 11 | = topic.user.name 12 | = ' · ' 13 | = time_ago_tag topic.updated_at 14 | - if topic.category 15 | = ' · ' 16 | = topic.category.name 17 | = ' · ' 18 | = t '.comments_count', count: topic.comments_count 19 | -------------------------------------------------------------------------------- /app/views/topics/create.js.erb: -------------------------------------------------------------------------------- 1 | <% if @topic.errors.empty? %> 2 | Turbolinks.visit('<%= topic_path(@topic) %>'); 3 | <% end %> 4 | -------------------------------------------------------------------------------- /app/views/topics/edit.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.edit_topic' 2 | 3 | .main 4 | .container 5 | .row 6 | .col-md-9 7 | .panel 8 | .panel-heading 9 | h3.panel-title = t '.edit_topic' 10 | .panel-body 11 | = render 'form', topic: @topic 12 | -------------------------------------------------------------------------------- /app/views/topics/feed.builder: -------------------------------------------------------------------------------- 1 | xml.instruct! :xml, :version => "1.0" 2 | xml.rss :version => "2.0" do 3 | xml.channel do 4 | xml.title "Swiftist.org" 5 | xml.link root_url 6 | xml.description "Swift最优质,最专业开发者社区" 7 | for topic in @topics 8 | xml.item do 9 | xml.title topic.title 10 | xml.description markdown_format(topic.body) 11 | xml.pubDate topic.created_at.strftime("%a, %d %b %Y %H:%M:%S %z") 12 | xml.author topic.user.name 13 | xml.link topic_url topic 14 | xml.guid topic_url topic 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/views/topics/new.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.new_topic' 2 | 3 | .main 4 | .container 5 | .row 6 | .col-md-9 7 | .panel 8 | .panel-heading 9 | h3.panel-title = t '.new_topic' 10 | 11 | .panel-body 12 | = render 'form', topic: @topic 13 | -------------------------------------------------------------------------------- /app/views/topics/search.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = "#{t '.search'} #{params[:q]}" 2 | 3 | .main 4 | .container 5 | .row 6 | .col-md-9 7 | .panel 8 | .panel-heading 9 | h3.panel-title 10 | = t '.search' 11 | ' 12 | = params[:q] 13 | .panel-body 14 | .list-group.list-group-campo 15 | - if @topics.any? 16 | = render @topics 17 | - else 18 | .list-group-item.text-center.text-muted.empty-message 19 | = t '.no_matching_results' 20 | - if @topics.total_pages > 1 21 | .panel-footer.clearfix 22 | .pull-right 23 | = paginate @topics, theme: 'campo' 24 | .col-md-3 25 | = render 'search_form' 26 | -------------------------------------------------------------------------------- /app/views/topics/update.js.erb: -------------------------------------------------------------------------------- 1 | <% if @topic.errors.empty? %> 2 | Turbolinks.visit('<%= topic_path(@topic) %>'); 3 | <% end %> 4 | -------------------------------------------------------------------------------- /app/views/user_mailer/confirmation.html.slim: -------------------------------------------------------------------------------- 1 | = sanitize markdown t '.text_body', url: users_confirmation_url(token: @user.confirmation_token), host: CONFIG['host'] 2 | -------------------------------------------------------------------------------- /app/views/user_mailer/confirmation.text.erb: -------------------------------------------------------------------------------- 1 | <%= t '.text_body', url: users_confirmation_url(token: @user.confirmation_token), host: CONFIG['host'] %> 2 | -------------------------------------------------------------------------------- /app/views/user_mailer/password_reset.html.slim: -------------------------------------------------------------------------------- 1 | = sanitize markdown t '.text_body', url: edit_users_password_url(token: @user.password_reset_token), host: CONFIG['host'] 2 | -------------------------------------------------------------------------------- /app/views/user_mailer/password_reset.text.erb: -------------------------------------------------------------------------------- 1 | <%= t '.text_body', url: edit_users_password_url(email: @user.email, token: @user.password_reset_token), host: CONFIG['host'] %> 2 | -------------------------------------------------------------------------------- /app/views/users/_profile.html.slim: -------------------------------------------------------------------------------- 1 | .panel.user-profile 2 | .panel-body 3 | .user-profile-avatar 4 | img.img-rounded alt="avatar" src=user.avatar.bigger.url 5 | .user-profile-name 6 | = user.name 7 | ' 8 | span.text-muted = "@#{user.username}" 9 | .user-profile-name 10 | = "第#{user.id}位会员" 11 | ' 12 | .user-profile-bio 13 | = simple_format user.bio 14 | 15 | - if login? and user == current_user 16 | .panel-footer.clearfix 17 | .pull-right 18 | a.btn.btn-default href=settings_profile_path 19 | = t '.edit_profile' 20 | -------------------------------------------------------------------------------- /app/views/users/_sidebar.html.slim: -------------------------------------------------------------------------------- 1 | .list-group 2 | a.list-group-item class=('active' if controller_name == 'topics') href=user_topics_path 3 | .pull-right 4 | i.fa.fa-chevron-right 5 | = t '.topics' 6 | a.list-group-item class=('active' if controller_name == 'comments') href=user_comments_path 7 | .pull-right 8 | i.fa.fa-chevron-right 9 | = t '.comments' 10 | -------------------------------------------------------------------------------- /app/views/users/comments/_comment.html.slim: -------------------------------------------------------------------------------- 1 | - cache [comment, locale] do 2 | a.list-group-item.commentable href=comment_link(comment) 3 | .list-group-item-avatar 4 | img.img-rounded alt="avatar" src=comment.user.avatar.normal.url 5 | .list-group-item-content 6 | .list-group-item-heading 7 | b = comment_title(comment) 8 | span.text-muted 9 | = ' · ' 10 | = time_ago_tag comment.created_at 11 | .text-muted 12 | = truncate comment.body, length: 140 13 | -------------------------------------------------------------------------------- /app/views/users/comments/index.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.user_s_comments', name: @user.name 2 | 3 | .main 4 | .container 5 | .row 6 | .col-md-3 7 | = render 'users/profile', user: @user 8 | = render 'users/sidebar' 9 | .col-md-9 10 | #comments.panel 11 | .panel-heading.clearfix 12 | .pull-right 13 | ul.nav.nav-pills 14 | li class=('active' if action_name == 'index') 15 | a href=user_comments_path 16 | = t '.publish' 17 | li class=('active' if action_name == 'likes') 18 | a href=likes_user_comments_path 19 | = t '.likes' 20 | h3.panel-title 21 | = t '.comments' 22 | .panel-body 23 | .list-group.list-group-campo 24 | - if @comments.any? 25 | = render @comments 26 | - else 27 | .list-group-item.text-center.text-muted.empty-message 28 | = t '.no_comment_yet' 29 | - if @comments.total_pages > 1 30 | .panel-footer.clearfix 31 | .pull-right 32 | = paginate @comments, theme: 'campo', params: { anchor: 'comments' } 33 | -------------------------------------------------------------------------------- /app/views/users/confirmations/create.js.erb: -------------------------------------------------------------------------------- 1 | $('#user-confirm-required-modal').find('.modal-title').text('<%= t '.resend_success' %>').end().modal(); 2 | -------------------------------------------------------------------------------- /app/views/users/confirmations/limiter.js.erb: -------------------------------------------------------------------------------- 1 | $('#user-confirm-required-modal').find('.modal-title').text('<%= t '.resend_limiter' %>').end().modal(); 2 | -------------------------------------------------------------------------------- /app/views/users/confirmations/new.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .row 4 | .col-md-8.col-md-push-2 5 | .panel 6 | .panel-heading 7 | h3.panel-title 8 | = t '.need_confirm_email' 9 | .panel-body 10 | = t '.click_resend_button_to_resend_confirmation_email' 11 | -------------------------------------------------------------------------------- /app/views/users/confirmations/show.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .row 4 | .col-md-8.col-md-push-2 5 | .panel 6 | .panel-heading 7 | h3.panel-title 8 | = t '.confirm_fail' 9 | .panel-body 10 | = t '.confirmation_toke_invalid_or_expired_please_resend' 11 | -------------------------------------------------------------------------------- /app/views/users/index.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .top-users 4 | .header.text-center 5 | | TOP50 活跃会员 6 | ul.user-list.clearfix 7 | - @users.each do |user| 8 | li.user-item.pull-left 9 | .avatar 10 | = link_to user_root_path(user.username) do 11 | img.img-rounded alt="avatar" src=user.avatar.normal.url 12 | .user-name.text-center 13 | = link_to user.name, user_root_path(user.username) 14 | -------------------------------------------------------------------------------- /app/views/users/passwords/edit.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .row 4 | .col-md-6.col-md-offset-3 5 | .panel 6 | .panel-heading 7 | h3.panel-title 8 | = t '.password_reset' 9 | .panel-body 10 | = form_for @user, url: users_password_path, html: { class: 'password-form' } do |f| 11 | = render 'share/flash_messages' 12 | = render 'share/form_error_messages', form: f 13 | = hidden_field_tag :email, params[:email] 14 | = hidden_field_tag :token, params[:token] 15 | .form-group 16 | = f.label :password, class: 'control-label' 17 | = f.password_field :password, class: 'form-control' 18 | .form-group 19 | = f.label :password_confirmation, class: 'control-label' 20 | = f.password_field :password_confirmation, class: 'form-control' 21 | .form-group 22 | = submit_tag t('.save_changes'), class: 'btn btn-default' 23 | 24 | javascript: 25 | $('form.password-form').validate({ 26 | rules: { 27 | 'user[password]': { 28 | required: true 29 | }, 30 | 'user[password_confirmation]': { 31 | equalTo: '#user_password' 32 | } 33 | }, 34 | messages: { 35 | 'user[password]': { 36 | required: '#{@user.errors.generate_message :password, :blank}' 37 | }, 38 | 'user[password_confirmation]': { 39 | equalTo: '#{@user.errors.generate_message :password_confirmation, :confirmation}' 40 | } 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /app/views/users/passwords/new.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .row 4 | .col-md-6.col-md-offset-3 5 | .panel 6 | .panel-heading 7 | h3.panel-title 8 | = t '.password_reset' 9 | .panel-body 10 | = form_tag users_password_path do 11 | = render 'share/flash_messages' 12 | .form-group 13 | = label_tag :email, t('.email'), class: 'control-label' 14 | = text_field_tag :email, nil, placeholder: t('.your_email'), class: 'form-control' 15 | .form-group 16 | = submit_tag t('.send_reset_email'), class: 'btn btn-default' 17 | -------------------------------------------------------------------------------- /app/views/users/passwords/show.html.slim: -------------------------------------------------------------------------------- 1 | .main 2 | .container 3 | .row 4 | .col-md-6.col-md-offset-3 5 | .panel 6 | .panel-heading 7 | h3.panel-title 8 | = t '.password_reset' 9 | .panel-body 10 | = t '.password_reset_email_has_been_sent_message' 11 | -------------------------------------------------------------------------------- /app/views/users/topics/index.html.slim: -------------------------------------------------------------------------------- 1 | - @page_title = t '.user_s_topics', name: @user.name 2 | 3 | .main 4 | .container 5 | .row 6 | .col-md-3 7 | = render 'users/profile', user: @user 8 | = render 'users/sidebar' 9 | .col-md-9 10 | #topics.panel 11 | .panel-heading.clearfix 12 | .pull-right 13 | ul.nav.nav-pills 14 | li class=('active' if action_name == 'index') 15 | a href=user_topics_path 16 | = t '.publish' 17 | li class=('active' if action_name == 'likes') 18 | a href=likes_user_topics_path 19 | = t '.likes' 20 | h3.panel-title 21 | = t '.topics' 22 | .panel-body 23 | .list-group.list-group-campo 24 | - if @topics.any? 25 | = render partial: 'topics/topic', collection: @topics 26 | - else 27 | .list-group-item.text-center.text-muted.empty-message 28 | = t '.no_topic_yet' 29 | - if @topics.total_pages > 1 30 | .panel-footer.clearfix 31 | .pull-right 32 | = paginate @topics, theme: 'campo', params: { anchor: 'topics' } 33 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | require_relative '../config/boot' 8 | require 'rails/commands' 9 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | require_relative '../config/boot' 7 | require 'rake' 8 | Rake.application.run 9 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast 4 | # It gets overwritten when you run the `spring binstub` command 5 | 6 | unless defined?(Spring) 7 | require "rubygems" 8 | require "bundler" 9 | 10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m) 11 | ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR) 12 | ENV["GEM_HOME"] = "" 13 | Gem.paths = ENV 14 | 15 | gem "spring", match[1] 16 | require "spring/binstub" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | require 'elasticsearch/rails/instrumentation' 5 | 6 | # Require the gems listed in Gemfile, including any gems 7 | # you've limited to :test, :development, or :production. 8 | Bundler.require(*Rails.groups) 9 | 10 | module Campo 11 | class Application < Rails::Application 12 | # Settings in config/environments/* take precedence over those specified here. 13 | # Application configuration should go into files in config/initializers 14 | # -- all .rb files in that directory are automatically loaded. 15 | 16 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 17 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 18 | # config.time_zone = 'Central Time (US & Canada)' 19 | 20 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 21 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 22 | config.i18n.default_locale = 'zh-CN' 23 | config.i18n.available_locales = [:en, 'zh-CN'] 24 | # Wait for fix https://github.com/rails/rails/issues/13164 25 | I18n.config.enforce_available_locales = false 26 | 27 | config.generators do |g| 28 | g.assets false 29 | g.helper false 30 | end 31 | 32 | config.active_record.schema_format = :sql 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /config/config.example.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | title: Campo 3 | host: localhost:3000 4 | 5 | mailer: 6 | # For localhost postfix 7 | smtp_settings: 8 | address: localhost 9 | port: 25 10 | enable_starttls_auto: false 11 | #domain: 12 | #user_name: 13 | #password: 14 | #authentication: 15 | default_options: 16 | from: no-reply@example.com 17 | default_url_options: 18 | host: localhost:3000 19 | 20 | redis: 21 | host: 127.0.0.1 22 | port: 6379 23 | db: 0 24 | 25 | elasticsearch: 26 | host: localhost:9200 27 | 28 | development: 29 | <<: *default 30 | admin_emails: 31 | - admin@example.com 32 | 33 | test: 34 | <<: *default 35 | admin_emails: 36 | - admin@example.com 37 | 38 | production: 39 | <<: *default 40 | 41 | admin_emails: 42 | # - admin@example.com 43 | -------------------------------------------------------------------------------- /config/database.example.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 8.2 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see rails configuration guide 21 | # http://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: 5 23 | username: 24 | password: 25 | 26 | development: 27 | <<: *default 28 | database: campo_development 29 | 30 | # Connect on a TCP socket. Omitted by default since the client uses a 31 | # domain socket that doesn't need configuration. Windows does not have 32 | # domain sockets, so uncomment these lines. 33 | #host: localhost 34 | 35 | # The TCP port the server listens on. Defaults to 5432. 36 | # If your server runs on a different port number, change accordingly. 37 | #port: 5432 38 | 39 | # Schema search path. The server defaults to $user,public 40 | #schema_search_path: myapp,sharedapp,public 41 | 42 | # Minimum log levels, in increasing order: 43 | # debug5, debug4, debug3, debug2, debug1, 44 | # log, notice, warning, error, fatal, and panic 45 | # Defaults to warning. 46 | #min_messages: notice 47 | 48 | # Warning: The database defined as "test" will be erased and 49 | # re-generated from your development database when you run "rake". 50 | # Do not set this db to the same as development or production. 51 | test: 52 | <<: *default 53 | database: campo_test 54 | 55 | production: 56 | <<: *default 57 | database: campo_production 58 | -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | # config valid only for Capistrano 3.1 2 | lock '3.1.0' 3 | 4 | set :application, 'campo' 5 | set :repo_url, 'git@github.com:chloerei/campo.git' 6 | set :deploy_to, -> { "/var/www/#{fetch(:application)}" } 7 | set :rails_env, 'production' 8 | 9 | set :linked_files, %w{config/database.yml config/config.yml config/secrets.yml} 10 | set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/uploads} 11 | 12 | namespace :deploy do 13 | desc "Restart unicorn and resque" 14 | task :restart do 15 | invoke 'deploy:passenger:restart' 16 | invoke 'deploy:resque:restart' 17 | end 18 | after :publishing, :restart 19 | 20 | namespace :passenger do 21 | task :restart do 22 | on roles(:app) do 23 | execute :touch, "#{release_path}/tmp/restart.txt" 24 | end 25 | end 26 | end 27 | 28 | namespace :resque do 29 | %w( start stop restart ).each do |action| 30 | desc "#{action} resque worker" 31 | task action do 32 | on roles(:app) do 33 | execute :service, :resque, action 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | # Simple Role Syntax 2 | # ================== 3 | # Supports bulk-adding hosts to roles, the primary 4 | # server in each group is considered to be the first 5 | # unless any hosts have the primary property set. 6 | # Don't declare `role :all`, it's a meta role 7 | role :app, %w{deploy@example.com} 8 | role :web, %w{deploy@example.com} 9 | role :db, %w{deploy@example.com} 10 | 11 | # Extended Server Syntax 12 | # ====================== 13 | # This can be used to drop a more detailed server 14 | # definition into the server list. The second argument 15 | # something that quacks like a hash can be used to set 16 | # extended properties on the server. 17 | server 'example.com', user: 'deploy', roles: %w{web app}, my_property: :my_value 18 | 19 | # you can set custom ssh options 20 | # it's possible to pass any option but you need to keep in mind that net/ssh understand limited list of options 21 | # you can see them in [net/ssh documentation](http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start) 22 | # set it globally 23 | # set :ssh_options, { 24 | # keys: %w(/home/rlisowski/.ssh/id_rsa), 25 | # forward_agent: false, 26 | # auth_methods: %w(password) 27 | # } 28 | # and/or per server 29 | # server 'example.com', 30 | # user: 'user_name', 31 | # roles: %w{web app}, 32 | # ssh_options: { 33 | # user: 'user_name', # overrides user setting above 34 | # keys: %w(/home/user_name/.ssh/id_rsa), 35 | # forward_agent: false, 36 | # auth_methods: %w(publickey password) 37 | # # password: 'please use keys' 38 | # } 39 | # setting per server overrides global ssh_options 40 | 41 | set :rails_env, 'production' 42 | -------------------------------------------------------------------------------- /config/deploy/vagrant.rb: -------------------------------------------------------------------------------- 1 | server '192.168.33.10', user: 'vagrant', roles: %w{web app db} 2 | 3 | set :ssh_options, { 4 | keys: '~/.vagrant.d/insecure_private_key' 5 | } 6 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = true 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # test javascript 31 | config.assets.paths << Rails.root.join('test', 'javascripts') 32 | 33 | # LiveReload, user localhost because livereload can't listen file changed 34 | # event in vagrant sync folder, so run `guard start` in host machine. 35 | config.middleware.use Rack::LiveReload, host: 'localhost' 36 | end 37 | 38 | # Slim pretty output 39 | Slim::Engine.set_default_options pretty: true, sort_attrs: true 40 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = "public, max-age=3600" 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | end 37 | -------------------------------------------------------------------------------- /config/i18n-tasks.yml: -------------------------------------------------------------------------------- 1 | base_locale: en 2 | locales: [en, zh-CN] 3 | 4 | search: 5 | include: 6 | - '*.rb' 7 | - '*.slim' 8 | - '*.text.erb' 9 | - '*.js.erb' 10 | 11 | ignore_unused: 12 | - activerecord.* 13 | -------------------------------------------------------------------------------- /config/initializers/00_config.rb: -------------------------------------------------------------------------------- 1 | # Load config/config.yml, store to CONFIG 2 | CONFIG = YAML.load(File.read(File.expand_path('../../config.yml', __FILE__)))[Rails.env] 3 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/elasticsearch.rb: -------------------------------------------------------------------------------- 1 | Elasticsearch::Model.client = Elasticsearch::Client.new host: CONFIG['elasticsearch']['host'], log: true, logger: Rails.logger 2 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/impression.rb: -------------------------------------------------------------------------------- 1 | # Use this hook to configure impressionist parameters 2 | #Impressionist.setup do |config| 3 | # Define ORM. Could be :active_record (default), :mongo_mapper or :mongoid 4 | # config.orm = :active_record 5 | #end 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/kaminari_config.rb: -------------------------------------------------------------------------------- 1 | Kaminari.configure do |config| 2 | config.default_per_page = 20 3 | # config.max_per_page = nil 4 | config.window = 2 5 | # config.outer_window = 0 6 | config.left = 1 7 | config.right = 1 8 | # config.page_method_name = :page 9 | # config.param_name = :page 10 | end 11 | -------------------------------------------------------------------------------- /config/initializers/mailer.rb: -------------------------------------------------------------------------------- 1 | # Only accept symbolize_keys here 2 | ActionMailer::Base.smtp_settings = CONFIG['mailer']['smtp_settings'].symbolize_keys 3 | ActionMailer::Base.default_url_options = CONFIG['mailer']['default_url_options'].symbolize_keys 4 | ActionMailer::Base.default_options = CONFIG['mailer']['default_options'].symbolize_keys 5 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/initializers/resque.rb: -------------------------------------------------------------------------------- 1 | require 'resque/server' 2 | 3 | $redis = Redis.new(host: CONFIG['redis']['host'], 4 | port: CONFIG['redis']['port'], 5 | db: CONFIG['redis']['db'], 6 | driver: :hiredis) 7 | 8 | Resque.redis = $redis 9 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_campo_session' 4 | -------------------------------------------------------------------------------- /config/initializers/social_share_button.rb: -------------------------------------------------------------------------------- 1 | SocialShareButton.configure do |config| 2 | config.allow_sites = %w(weibo douban tqq renren qq twitter facebook google_plus) 3 | end -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/social_share_button.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | social_share_button: 3 | share_to: Share to %{name} 4 | weibo: Sina Weibo 5 | twitter: Twitter 6 | facebook: Facebook 7 | douban: Douban 8 | qq: Qzone 9 | tqq: Tqq 10 | delicious: Delicious 11 | baidu: Baidu.com 12 | kaixin001: Kaixin001.com 13 | renren: Renren.com 14 | google_plus: Google+ 15 | google_bookmark: Google Bookmark 16 | tumblr: Tumblr 17 | plurk: Plurk 18 | pinterest: Pinterest 19 | email: Email 20 | -------------------------------------------------------------------------------- /config/locales/social_share_button.zh-CN.yml: -------------------------------------------------------------------------------- 1 | 'zh-CN': 2 | social_share_button: 3 | share_to: 分享到 %{name} 4 | weibo: 新浪微博 5 | twitter: Twitter 6 | facebook: Facebook 7 | douban: 豆瓣 8 | qq: QQ空间 9 | tqq: 腾讯微博 10 | delicious: Delicious 11 | baidu: 百度收藏 12 | kaixin001: 开心网 13 | renren: 人人网 14 | google_plus: Google+ 15 | google_bookmark: Google 收藏 16 | tumblr: Tumblr 17 | plurk: Plurk 18 | pinterest: Pinterest 19 | email: Email 20 | -------------------------------------------------------------------------------- /config/locales/social_share_button.zh-TW.yml: -------------------------------------------------------------------------------- 1 | 'zh-TW': 2 | social_share_button: 3 | share_to: 分享到 %{name} 4 | weibo: 新浪微博 5 | twitter: Twitter 6 | facebook: Facebook 7 | douban: 豆瓣 8 | qq: QQ空間 9 | tqq: 腾讯微博 10 | delicious: Delicious 11 | baidu: 百度收藏 12 | kaixin001: 開心網 13 | renren: 人人網 14 | google_plus: Google+ 15 | google_bookmark: Google 收藏 16 | tumblr: Tumblr 17 | plurk: 噗浪 18 | pinterest: Pinterest 19 | email: Email 20 | -------------------------------------------------------------------------------- /config/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default deferred; 3 | # server_name example.com; 4 | root /var/www/campo/current/public; 5 | passenger_enabled on; 6 | } 7 | -------------------------------------------------------------------------------- /config/nginx.example.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default deferred; 3 | # server_name example.com; 4 | root /var/www/campo/current/public; 5 | passenger_enabled on; 6 | } 7 | -------------------------------------------------------------------------------- /config/resque.example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: Campo 5 | # Required-Start: $all 6 | # Required-Stop: $network $local_fs $syslog 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: Start the Campo resque worker at boot 10 | # Description: Enable Campo at boot time. 11 | ### END INIT INFO 12 | 13 | set -e 14 | set -u 15 | 16 | APP_ROOT=/var/www/campo/current 17 | USER=deploy 18 | 19 | TIMEOUT=${TIMEOUT-60} 20 | PIDFILE="$APP_ROOT/tmp/pids/resque.%d.pid" 21 | QUEUES="*" 22 | COUNT=1 23 | 24 | run () { 25 | if [ "$(id -un)" = "$USER" ]; then 26 | eval $1 27 | else 28 | su -c "$1" - $USER 29 | fi 30 | } 31 | 32 | case "$1" in 33 | start) 34 | for i in $(seq 1 $COUNT); do 35 | pidfile=$(printf "$PIDFILE" $i) 36 | 37 | if test -s "$pidfile" && run "kill -0 `cat $pidfile`"; then 38 | echo "Worker `cat $pidfile` alread running" 39 | else 40 | run "cd $APP_ROOT; ~/.rvm/bin/rvm default do bundle exec rake environment resque:work QUEUE=$QUEUES PIDFILE=$pidfile TERM_CHILD=1 BACKGROUND=yes RAILS_ENV=production > /dev/null 2>&1" 41 | echo "Start worker `cat $pidfile`" 42 | fi 43 | done 44 | ;; 45 | stop) 46 | for i in $(seq 1 $COUNT); do 47 | pidfile=$(printf "$PIDFILE" $i) 48 | 49 | if test -s "$pidfile" && run "kill -QUIT `cat $pidfile`"; then 50 | echo "Stop worker `cat $pidfile`" 51 | run "rm $pidfile" 52 | fi 53 | done 54 | ;; 55 | restart|reload) 56 | $0 stop 57 | $0 start 58 | ;; 59 | *) 60 | echo >&2 "Usage: $0 " 61 | exit 1 62 | ;; 63 | esac 64 | -------------------------------------------------------------------------------- /config/secrets.example.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 4a5cd11e0132dada7a95e33fa20be21cc1e078c92756d19af2fc4dc1326595fe5753eb23552fab6ab3f855ffccc72d7faf3b891b9f71206c0fef9955276f4a0e 15 | 16 | test: 17 | secret_key_base: f37a378152ddf54f255d7853e694543072e5e47f6b461516bd1a64c5c58e86671f45b87c415277c46dce62c4b74bcbb53167f7d1f5dbd2d5d638914c0b443c9a 18 | 19 | production: 20 | secret_key_base: ea801ab6da8977bb3ca0c9199bf1f111fed04c9afd778adc0a297c711a05255d3e8cccf2e34dbbaf866f7a1cf371244589b527caa088dd30fa2c1b9575400471 21 | -------------------------------------------------------------------------------- /db/migrate/20140103111354_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :name 5 | t.string :email 6 | t.string :email_lower 7 | t.string :username 8 | t.string :username_lower 9 | t.string :password_digest 10 | t.text :bio 11 | t.string :avatar 12 | t.string :locale 13 | t.datetime :locked_at 14 | t.string :password_reset_token 15 | t.datetime :password_reset_token_created_at 16 | 17 | t.timestamps 18 | end 19 | 20 | add_index :users, :email, unique: true 21 | add_index :users, :email_lower, unique: true 22 | add_index :users, :username, unique: true 23 | add_index :users, :username_lower, unique: true 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20140104125105_create_topics.rb: -------------------------------------------------------------------------------- 1 | class CreateTopics < ActiveRecord::Migration 2 | def change 3 | create_table :topics do |t| 4 | t.belongs_to :user, index: true 5 | t.belongs_to :category, index: true 6 | t.string :title 7 | t.text :body 8 | t.float :hot, default: 0.0 9 | t.integer :comments_count, default: 0 10 | t.integer :likes_count, default: 0 11 | t.integer :subscriptions_count, default: 0 12 | t.boolean :trashed, default: false 13 | 14 | t.timestamps 15 | end 16 | 17 | add_index :topics, :hot 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20140121065043_create_notifications.rb: -------------------------------------------------------------------------------- 1 | class CreateNotifications < ActiveRecord::Migration 2 | def change 3 | create_table :notifications do |t| 4 | t.belongs_to :user, index: true 5 | t.belongs_to :subject, polymorphic: true, index: true 6 | t.string :name 7 | t.boolean :read, default: false 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20140130122905_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration 2 | def change 3 | create_table :comments do |t| 4 | t.belongs_to :user, index: true 5 | t.belongs_to :commentable, polymorphic: true, index: true 6 | t.text :body 7 | t.integer :likes_count, default: 0 8 | t.boolean :trashed, default: false 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20140204053314_create_likes.rb: -------------------------------------------------------------------------------- 1 | class CreateLikes < ActiveRecord::Migration 2 | def change 3 | create_table :likes do |t| 4 | t.belongs_to :user, index: true 5 | t.belongs_to :likeable, polymorphic: true, index: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20140204164212_create_subscriptions.rb: -------------------------------------------------------------------------------- 1 | class CreateSubscriptions < ActiveRecord::Migration 2 | def change 3 | create_table :subscriptions do |t| 4 | t.belongs_to :user, index: true 5 | t.belongs_to :subscribable, polymorphic: true, index: true 6 | t.integer :status, default: 0 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20140208060708_create_categories.rb: -------------------------------------------------------------------------------- 1 | class CreateCategories < ActiveRecord::Migration 2 | def change 3 | create_table :categories do |t| 4 | t.string :name 5 | t.string :slug 6 | t.string :slug_lower 7 | t.text :description 8 | t.integer :topics_count, default: 0 9 | 10 | t.timestamps 11 | end 12 | 13 | add_index :categories, :slug, unique: true 14 | add_index :categories, :slug_lower, unique: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20140218070616_create_attachments.rb: -------------------------------------------------------------------------------- 1 | class CreateAttachments < ActiveRecord::Migration 2 | def change 3 | create_table :attachments do |t| 4 | t.belongs_to :user, index: true 5 | t.string :file 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20140310070632_remove_lower_column.rb: -------------------------------------------------------------------------------- 1 | class RemoveLowerColumn < ActiveRecord::Migration 2 | def change 3 | remove_column :users, :username_lower 4 | remove_column :users, :email_lower 5 | remove_index :users, :username 6 | remove_index :users, :email 7 | execute "CREATE UNIQUE INDEX index_users_on_lowercase_username ON users USING btree (lower(username))" 8 | execute "CREATE UNIQUE INDEX index_users_on_lowercase_email ON users USING btree (lower(email))" 9 | 10 | remove_column :categories, :slug_lower 11 | remove_index :categories, :slug 12 | execute "CREATE UNIQUE INDEX index_categories_on_lowercase_slug ON categories USING btree (lower(slug))" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20140405074043_remove_password_reset_token_in_users.rb: -------------------------------------------------------------------------------- 1 | class RemovePasswordResetTokenInUsers < ActiveRecord::Migration 2 | def change 3 | remove_column :users, :password_reset_token 4 | remove_column :users, :password_reset_token_created_at 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20140412065000_add_confirmed_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddConfirmedToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :confirmed, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20140412113810_add_comment_notification_settings_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddCommentNotificationSettingsToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :send_comment_email, :boolean, default: true 4 | add_column :users, :send_comment_web, :boolean, default: true 5 | add_column :users, :send_mention_email, :boolean, default: true 6 | add_column :users, :send_mention_web, :boolean, default: true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20140612075008_create_friend_sites.rb: -------------------------------------------------------------------------------- 1 | class CreateFriendSites < ActiveRecord::Migration 2 | def change 3 | create_table :friend_sites do |t| 4 | t.string :name 5 | t.string :url 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20140620090118_add_counts_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddCountsToUsers < ActiveRecord::Migration 2 | def up 3 | add_column :users, :topics_count, :integer, default: 0 4 | add_column :users, :comments_count, :integer, default: 0 5 | 6 | User.reset_column_information 7 | 8 | User.find_each do |user| 9 | User.reset_counters user.id, :topics 10 | User.reset_counters user.id, :comments 11 | end 12 | end 13 | def down 14 | remove_column :users, :topics_count 15 | remove_column :users, :comments_count 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20140623033657_create_impressions_table.rb: -------------------------------------------------------------------------------- 1 | class CreateImpressionsTable < ActiveRecord::Migration 2 | def self.up 3 | create_table :impressions, :force => true do |t| 4 | t.string :impressionable_type 5 | t.integer :impressionable_id 6 | t.integer :user_id 7 | t.string :controller_name 8 | t.string :action_name 9 | t.string :view_name 10 | t.string :request_hash 11 | t.string :ip_address 12 | t.string :session_hash 13 | t.text :message 14 | t.text :referrer 15 | t.timestamps 16 | end 17 | add_index :impressions, [:impressionable_type, :message, :impressionable_id], :name => "impressionable_type_message_index", :unique => false, :length => {:message => 255 } 18 | add_index :impressions, [:impressionable_type, :impressionable_id, :request_hash], :name => "poly_request_index", :unique => false 19 | add_index :impressions, [:impressionable_type, :impressionable_id, :ip_address], :name => "poly_ip_index", :unique => false 20 | add_index :impressions, [:impressionable_type, :impressionable_id, :session_hash], :name => "poly_session_index", :unique => false 21 | add_index :impressions, [:controller_name,:action_name,:request_hash], :name => "controlleraction_request_index", :unique => false 22 | add_index :impressions, [:controller_name,:action_name,:ip_address], :name => "controlleraction_ip_index", :unique => false 23 | add_index :impressions, [:controller_name,:action_name,:session_hash], :name => "controlleraction_session_index", :unique => false 24 | add_index :impressions, :user_id 25 | end 26 | 27 | def self.down 28 | drop_table :impressions 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /db/migrate/20140623065349_add_impressions_count_to_topics.rb: -------------------------------------------------------------------------------- 1 | class AddImpressionsCountToTopics < ActiveRecord::Migration 2 | def change 3 | add_column :topics, :impressions_count, :integer, default: 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/lib/tasks/.keep -------------------------------------------------------------------------------- /lib/tasks/elasticseach.rake: -------------------------------------------------------------------------------- 1 | require 'elasticsearch/rails/tasks/import' 2 | -------------------------------------------------------------------------------- /lib/tasks/resque.rake: -------------------------------------------------------------------------------- 1 | require 'resque/tasks' 2 | 3 | task "resque:setup" => :environment 4 | -------------------------------------------------------------------------------- /lib/tasks/test.rake: -------------------------------------------------------------------------------- 1 | namespace :test do 2 | Rails::TestTask.new(:jobs => 'test:prepare') do |t| 3 | t.pattern = 'test/jobs/**/*_test.rb' 4 | end 5 | end 6 | 7 | Rake::Task[:test].enhance { Rake::Task["test:jobs"].invoke } 8 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /public/swiftguide-cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/public/swiftguide-cn.png -------------------------------------------------------------------------------- /public/upyun_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/public/upyun_logo.png -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/admin/application_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Admin::ApplicationControllerTest < ActionController::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/controllers/admin/attachments_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Admin::AttachmentsControllerTest < ActionController::TestCase 4 | def setup 5 | login_as create(:admin) 6 | end 7 | 8 | test "should get index" do 9 | create :attachment 10 | 11 | get :index 12 | assert_response :success, @response.body 13 | end 14 | 15 | test "should delete attachment" do 16 | attachment = create(:attachment) 17 | 18 | assert_difference "Attachment.count", -1 do 19 | xhr :delete, :destroy, id: attachment 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/controllers/admin/categories_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Admin::CategoriesControllerTest < ActionController::TestCase 4 | def setup 5 | login_as create(:admin) 6 | end 7 | 8 | test "should get index" do 9 | create(:category) 10 | get :index 11 | assert_response :success, @response.body 12 | end 13 | 14 | test "should get show" do 15 | get :show, id: create(:category) 16 | assert_response :success, @response.body 17 | end 18 | 19 | test "should get new page" do 20 | get :new 21 | assert_response :success, @response.body 22 | end 23 | 24 | test "should create category" do 25 | assert_difference "Category.count" do 26 | post :create, category: attributes_for(:category) 27 | end 28 | end 29 | 30 | test "should update category" do 31 | category = create(:category) 32 | patch :update, id: category, category: { name: 'change' } 33 | assert_equal 'change', category.reload.name 34 | end 35 | 36 | test "should destroy category" do 37 | category = create(:category) 38 | assert_difference "Category.count", -1 do 39 | delete :destroy, id: category 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/controllers/admin/comments_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Admin::CommentsControllerTest < ActionController::TestCase 4 | def setup 5 | login_as create(:admin) 6 | end 7 | 8 | test "should get index" do 9 | create :comment 10 | get :index 11 | assert_response :success, @response.body 12 | end 13 | 14 | test "should get show page" do 15 | comment = create(:comment) 16 | get :show, id: comment 17 | assert_response :success, @response.body 18 | end 19 | 20 | test "should update comment" do 21 | comment = create(:comment) 22 | patch :update, id: comment, comment: { body: 'change' } 23 | assert_equal 'change', comment.reload.body 24 | end 25 | 26 | test "should destroy comment" do 27 | comment = create(:comment) 28 | assert_difference "Comment.trashed.count" do 29 | delete :trash, id: comment 30 | end 31 | assert_redirected_to admin_comment_path(comment) 32 | end 33 | 34 | test "should restore comment" do 35 | comment = create(:comment) 36 | comment.trash 37 | assert_difference "Comment.trashed.count", -1 do 38 | patch :restore, id: comment 39 | end 40 | assert_redirected_to admin_comment_path(comment) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/controllers/admin/dashboard_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Admin::DashboardControllerTest < ActionController::TestCase 4 | test "should raise if no admin" do 5 | login_as create(:user) 6 | assert_raise(ApplicationController::AccessDenied) do 7 | get :show 8 | end 9 | end 10 | 11 | test "should get show page" do 12 | login_as create(:admin) 13 | get :show 14 | assert_response :success, @response.body 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/controllers/admin/friend_sites_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Admin::FriendSitesControllerTest < ActionController::TestCase 4 | test "should get create" do 5 | get :create 6 | assert_response :success 7 | end 8 | 9 | test "should get update" do 10 | get :update 11 | assert_response :success 12 | end 13 | 14 | test "should get destory" do 15 | get :destory 16 | assert_response :success 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /test/controllers/admin/topics_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Admin::TopicsControllerTest < ActionController::TestCase 4 | def setup 5 | login_as create(:admin) 6 | end 7 | 8 | test "should get index" do 9 | create :topic 10 | get :index 11 | assert_response :success, @response.body 12 | end 13 | 14 | test "should get show page" do 15 | topic = create(:topic) 16 | get :show, id: topic 17 | assert_response :success, @response.body 18 | end 19 | 20 | test "should update topic" do 21 | topic = create(:topic) 22 | patch :update, id: topic, topic: { title: 'change' } 23 | assert_equal 'change', topic.reload.title 24 | end 25 | 26 | test "should destroy topic" do 27 | topic = create(:topic) 28 | assert_difference "Topic.trashed.count" do 29 | delete :trash, id: topic 30 | end 31 | assert_redirected_to admin_topic_path(topic) 32 | end 33 | 34 | test "should restore topic" do 35 | topic = create(:topic) 36 | topic.trash 37 | assert_difference "Topic.trashed.count", -1 do 38 | patch :restore, id: topic 39 | end 40 | assert_redirected_to admin_topic_path(topic) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/controllers/admin/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Admin::UsersControllerTest < ActionController::TestCase 4 | def setup 5 | login_as create(:admin) 6 | end 7 | 8 | test "should get index" do 9 | get :index 10 | assert_response :success, @response.body 11 | end 12 | 13 | test "should get show page" do 14 | user = create(:user) 15 | get :show, id: user 16 | assert_response :success, @response.body 17 | end 18 | 19 | test "shuold update user" do 20 | user = create(:user) 21 | patch :update, id: user, user: { name: 'change' } 22 | assert_equal 'change', user.reload.name 23 | end 24 | 25 | test "should destroy user" do 26 | user = create(:user) 27 | assert_difference "User.count", -1 do 28 | delete :destroy, id: user 29 | end 30 | end 31 | 32 | test "should lock user" do 33 | user = create(:user) 34 | 35 | patch :lock, id: user 36 | assert user.reload.locked? 37 | end 38 | 39 | test "should unlock user" do 40 | user = create(:user) 41 | user.lock 42 | 43 | delete :unlock, id: user 44 | assert !user.reload.locked? 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/controllers/attachments_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AttachmentsControllerTest < ActionController::TestCase 4 | def setup 5 | login_as create(:user) 6 | end 7 | 8 | test "should create attachment" do 9 | assert_difference "current_user.attachments.count" do 10 | post :create, attachment: { file: upload_file('app/assets/images/rails.png') } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/controllers/comments_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CommentsControllerTest < ActionController::TestCase 4 | test "should create comment" do 5 | login_as create(:user) 6 | topic = create(:topic) 7 | 8 | assert_difference "topic.comments.count" do 9 | xhr :post, :create, topic_id: topic, comment: { body: 'body' } 10 | end 11 | end 12 | 13 | test "should create notification job after create comment" do 14 | login_as create(:user) 15 | topic = create(:topic) 16 | 17 | assert_difference "Resque.size(:notification)" do 18 | xhr :post, :create, topic_id: topic, comment: { body: 'body' } 19 | end 20 | end 21 | 22 | test "should edit comment" do 23 | comment = create(:comment) 24 | login_as comment.user 25 | 26 | xhr :get, :edit, id: comment 27 | assert_response :success, @response.body 28 | end 29 | 30 | test "should cancel edit" do 31 | comment = create(:comment) 32 | login_as comment.user 33 | 34 | xhr :get, :cancel, id: comment 35 | assert_response :success, @response.body 36 | end 37 | 38 | test "shuold update comment" do 39 | comment = create(:comment) 40 | login_as comment.user 41 | 42 | xhr :patch, :update, id: comment, comment: { body: 'change' } 43 | assert_equal 'change', comment.reload.body 44 | end 45 | 46 | test "should trash comment" do 47 | comment = create(:comment) 48 | login_as comment.user 49 | 50 | assert_difference "Comment.trashed.count" do 51 | xhr :delete, :trash, id: comment 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/controllers/likes_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class LikesControllerTest < ActionController::TestCase 4 | test "should like comment" do 5 | comment = create(:comment) 6 | login_as create(:user) 7 | 8 | assert_difference "comment.likes.count" do 9 | xhr :post, :create, comment_id: comment 10 | end 11 | end 12 | 13 | test "should unlike comment" do 14 | user = create(:user) 15 | comment = create(:comment) 16 | create :like, likeable: comment, user: user 17 | login_as user 18 | 19 | assert_difference "comment.likes.count", -1 do 20 | xhr :delete, :destroy, comment_id: comment.id 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/controllers/markdown_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MarkdownControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/notifications_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class NotificationsControllerTest < ActionController::TestCase 4 | test "should get index" do 5 | user = create(:user) 6 | create(:notification, user: user, subject: create(:comment), name: 'comment') 7 | create(:notification, user: user, subject: create(:comment), name: 'mention') 8 | 9 | login_as user 10 | get :index 11 | assert_response :success, @response.body 12 | end 13 | 14 | test "should destroy notification" do 15 | user = create(:user) 16 | notification = create(:notification, user: user) 17 | login_as user 18 | assert_difference "user.notifications.count", -1 do 19 | xhr :delete, :destroy, id: notification 20 | end 21 | end 22 | 23 | test "should mark all as read after get index" do 24 | user = create(:user) 25 | login_as user 26 | 3.times { create :notification, user: user } 27 | assert_equal 3, user.notifications.unread.count 28 | get :index 29 | assert_equal 0, user.notifications.unread.count 30 | end 31 | 32 | test "should destroy all notification" do 33 | user = create(:user) 34 | login_as user 35 | 3.times { create :notification, user: user } 36 | xhr :delete, :clear 37 | assert_equal 0, user.notifications.count 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/controllers/sessions_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SessionsControllerTest < ActionController::TestCase 4 | def setup 5 | # reset access limiter 6 | $redis.flushdb 7 | end 8 | 9 | test "should get login page" do 10 | get :new 11 | assert_response :success, @response.body 12 | end 13 | 14 | test "should store_location" do 15 | get :new, return_to: '/foo' 16 | assert_equal '/foo', session[:return_to] 17 | end 18 | 19 | test "should create session" do 20 | create(:user, username: 'Username', password: '12345678') 21 | assert !login? 22 | post :create, login: 'Username', password: '12345678' 23 | assert login? 24 | end 25 | 26 | test "should destroy session" do 27 | login_as create(:user) 28 | delete :destroy 29 | assert !login? 30 | end 31 | 32 | test "should redirect back after login" do 33 | session[:return_to] = '/foo' 34 | create(:user, username: 'username', password: '12345678') 35 | post :create, login: 'Username', password: '12345678' 36 | assert_redirected_to '/foo' 37 | end 38 | 39 | test "should access limit" do 40 | ip = '1.2.3.4' 41 | key = "sessions:limiter:#{ip}" 42 | request.headers['REMOTE_ADDR'] = ip 43 | assert_equal nil, $redis.get(key) 44 | post :create, login: 'Username', password: '12345678' 45 | assert_equal 1, $redis.get(key).to_i 46 | $redis.set(key, 5) 47 | post :create, login: 'Username', password: '12345678' 48 | assert_template :access_limiter 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/controllers/settings/accounts_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Settings::AccountsControllerTest < ActionController::TestCase 4 | def setup 5 | login_as create(:user, password: '123456') 6 | end 7 | 8 | test "should get settings account page" do 9 | get :show 10 | assert_response :success, @response.body 11 | end 12 | 13 | test "should update settings account" do 14 | patch :update, user: { username: 'change' }, current_password: '123456' 15 | assert_equal 'change', current_user.reload.username 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/controllers/settings/application_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Settings::ApplicationControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/settings/notifications_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Settings::NotificationsControllerTest < ActionController::TestCase 4 | def setup 5 | login_as create(:user) 6 | end 7 | 8 | test "should get show page" do 9 | get :show 10 | assert_response :success, @response.body 11 | end 12 | 13 | test "should update notification settings" do 14 | post :update, user: { send_mention_web: false } 15 | assert_equal false, current_user.reload.send_mention_web? 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/controllers/settings/passwords_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Settings::PasswordsControllerTest < ActionController::TestCase 4 | def setup 5 | login_as create(:user, password: '123456') 6 | end 7 | 8 | test "should get settings password page" do 9 | get :show 10 | assert_response :success, @response.body 11 | end 12 | 13 | test "should update passowrd" do 14 | patch :update, current_password: '123456', user: { password: '654321', password_confirmation: '654321' } 15 | assert current_user.reload.authenticate '654321' 16 | end 17 | 18 | test "should not update password without current_password" do 19 | patch :update, user: { password: '654321', password_confirmation: '654321' } 20 | assert !current_user.reload.authenticate('654321') 21 | 22 | patch :update, current_password: 'wrongpassword', user: { password: '654321', password_confirmation: '654321' } 23 | assert !current_user.reload.authenticate('654321') 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/controllers/settings/profiles_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Settings::ProfilesControllerTest < ActionController::TestCase 4 | def setup 5 | login_as create(:user) 6 | end 7 | 8 | test "should get profile page" do 9 | get :show 10 | assert_response :success, @response.body 11 | end 12 | 13 | test "should update profile" do 14 | patch :update, user: { name: 'change' } 15 | assert_equal 'change', current_user.reload.name 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/controllers/subscriptions_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SubscriptionsControllerTest < ActionController::TestCase 4 | test "should subscribe topic" do 5 | topic = create(:topic) 6 | user = create(:user) 7 | login_as user 8 | 9 | assert_difference "topic.subscribed_users.count" do 10 | xhr :put, :update, topic_id: topic, status: 'subscribed' 11 | end 12 | assert topic.subscribed_by? user 13 | assert !topic.ignored_by?(user) 14 | 15 | assert_difference "topic.ignored_users.count" do 16 | xhr :put, :update, topic_id: topic, status: 'ignored' 17 | end 18 | assert !topic.subscribed_by?(user) 19 | assert topic.ignored_by? user 20 | end 21 | 22 | test "should destroy topic subscripton" do 23 | topic = create(:topic) 24 | user = create(:user) 25 | login_as user 26 | 27 | topic.subscribe_by user 28 | 29 | assert_difference "topic.subscriptions.count", -1 do 30 | xhr :delete, :destroy, topic_id: topic 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/controllers/topics_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TopicsControllerTest < ActionController::TestCase 4 | test "should get index page" do 5 | 3.times { create(:topic) } 6 | get :index 7 | assert_response :success, @response.body 8 | end 9 | 10 | test "should get show page" do 11 | get :show, id: create(:topic) 12 | assert_response :success, @response.body 13 | end 14 | 15 | test "should redirect to comment page" do 16 | topic = create(:topic) 17 | (Comment.default_per_page + 1).times { create :comment, commentable: topic, user: topic.user } 18 | comment = topic.comments.order(id: :asc).last 19 | get :show, id: topic, comment_id: comment 20 | assert_equal comment.page, assigns(:comments).current_page 21 | end 22 | 23 | test "should get new page" do 24 | login_as create(:user) 25 | get :new 26 | assert_response :success, @response.body 27 | end 28 | 29 | test "should create topic" do 30 | login_as create(:user) 31 | assert_difference "Topic.count" do 32 | xhr :post, :create, topic: attributes_for(:topic) 33 | end 34 | topic = Topic.last 35 | assert_equal topic.user, topic.user 36 | end 37 | 38 | test "should edit topic" do 39 | topic = create(:topic) 40 | login_as topic.user 41 | get :edit, id: topic 42 | assert_response :success, @response.body 43 | end 44 | 45 | test "should update topic" do 46 | topic = create(:topic) 47 | login_as topic.user 48 | xhr :patch, :update, id: topic, topic: { title: 'change', body: 'change' } 49 | topic.reload 50 | assert_equal 'change', topic.title 51 | assert_equal 'change', topic.body 52 | end 53 | 54 | test "should not create topic for locked user" do 55 | user = create(:user) 56 | user.lock 57 | login_as user 58 | assert_no_difference "Topic.count" do 59 | assert_raise(ApplicationController::AccessDenied) do 60 | post :create, topic: attributes_for(:topic) 61 | end 62 | end 63 | end 64 | 65 | test "should trash topic" do 66 | topic = create(:topic) 67 | login_as topic.user 68 | 69 | assert_difference "Topic.trashed.count" do 70 | delete :trash, id: topic 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /test/controllers/users/application_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Users::ApplicationControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/users/comments_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Users::CommentsControllerTest < ActionController::TestCase 4 | def setup 5 | @user = create(:user) 6 | end 7 | 8 | test "should get index" do 9 | create(:comment, user: @user) 10 | get :index, username: @user.username 11 | assert_response :success, @response.body 12 | end 13 | 14 | test "should get likes" do 15 | create(:like, user: @user, likeable: create(:comment)) 16 | get :likes, username: @user.username 17 | assert_response :success, @response.body 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/controllers/users/confirmations_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Users::ConfirmationsControllerTest < ActionController::TestCase 4 | def setup 5 | $redis.flushdb 6 | end 7 | 8 | test "should redirect to root if already confirmed" do 9 | login_as create(:user, confirmed: true) 10 | get :new 11 | assert_redirected_to root_url 12 | end 13 | 14 | test "should get new page" do 15 | login_as create(:user, confirmed: false) 16 | get :new 17 | assert_response :success, @response.body 18 | end 19 | 20 | test "should confirm user if token valid" do 21 | login_as create(:user, confirmed: false) 22 | get :show, token: current_user.confirmation_token 23 | assert current_user.reload.confirmed? 24 | end 25 | 26 | test "should not confirm user if token invalid" do 27 | login_as create(:user, confirmed: false) 28 | get :show, token: current_user.confirmation_token[0..-2] 29 | assert !current_user.reload.confirmed? 30 | end 31 | 32 | test "should show" do 33 | login_as create(:user, confirmed: false) 34 | get :show 35 | assert_response :success, @response.body 36 | end 37 | 38 | test "should create confirm" do 39 | login_as create(:user, confirmed: false) 40 | xhr :post, :create 41 | assert ActionMailer::Base.deliveries.any? 42 | end 43 | 44 | test "should access limit" do 45 | login_as create(:user, confirmed: false) 46 | ip = '1.2.3.4' 47 | key = "verifies:limiter:#{ip}" 48 | request.headers['REMOTE_ADDR'] = ip 49 | assert_equal nil, $redis.get(key) 50 | assert_difference "ActionMailer::Base.deliveries.count" do 51 | xhr :post, :create 52 | assert_equal 1, $redis.get(key).to_i 53 | end 54 | 55 | assert_no_difference "ActionMailer::Base.deliveries.count" do 56 | xhr :post, :create 57 | assert_equal 1, $redis.get(key).to_i 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/controllers/users/passwords_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Users::PasswordsControllerTest < ActionController::TestCase 4 | test "should get new page" do 5 | get :new 6 | assert_response :success, @response.body 7 | end 8 | 9 | test "should create password reset email" do 10 | create :user, email: 'user@example.com' 11 | 12 | assert_difference "ActionMailer::Base.deliveries.size" do 13 | post :create, email: 'user@example.com' 14 | end 15 | assert_redirected_to users_password_path 16 | 17 | reset_email = ActionMailer::Base.deliveries.last 18 | assert_equal 'user@example.com', reset_email.to[0] 19 | end 20 | 21 | test "should not create password reset email if user no exists" do 22 | assert_no_difference "ActionMailer::Base.deliveries.size" do 23 | post :create, email: 'user@example.com' 24 | end 25 | assert_template :new 26 | end 27 | 28 | test "should not get edit page if token invalid" do 29 | user = create(:user) 30 | get :edit, email: user.email 31 | assert_redirected_to new_users_password_path 32 | end 33 | 34 | test "should not get edit page if token expired" do 35 | user = create(:user) 36 | get :edit, token: User.verifier_for('password-reset').generate([user.id, 2.hours.ago]) 37 | assert_redirected_to new_users_password_path 38 | end 39 | 40 | test "should get edit page" do 41 | user = create(:user) 42 | get :edit, email: user.email, token: user.password_reset_token 43 | assert_response :success, @response.body 44 | end 45 | 46 | test "should not update password if token invalid" do 47 | user = create(:user) 48 | post :update, email: user.email, user: { password: 'change', password_confirmation: 'change' } 49 | assert !user.reload.authenticate('change') 50 | end 51 | 52 | test "should update password" do 53 | user = create(:user) 54 | post :update, email: user.email, token: user.password_reset_token, user: { password: 'change', password_confirmation: 'change' } 55 | assert user.reload.authenticate('change') 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/controllers/users/topics_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Users::TopicsControllerTest < ActionController::TestCase 4 | def setup 5 | @user = create(:user) 6 | end 7 | 8 | test "should get index" do 9 | create(:topic, user: @user) 10 | get :index, username: @user.username 11 | assert_response :success, @response.body 12 | end 13 | 14 | test "should get likes" do 15 | create(:like, user: @user, likeable: create(:topic)) 16 | get :likes, username: @user.username 17 | assert_response :success, @response.body 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/controllers/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UsersControllerTest < ActionController::TestCase 4 | test "should get new page" do 5 | get :new 6 | assert_response :success, @response.body 7 | end 8 | 9 | test "should store_location" do 10 | get :new, return_to: '/foo' 11 | assert_equal '/foo', session[:return_to] 12 | end 13 | 14 | test "should create user" do 15 | assert !login? 16 | assert_difference ["User.count", "ActionMailer::Base.deliveries.size"] do 17 | post :create, user: attributes_for(:user) 18 | end 19 | assert login? 20 | end 21 | 22 | test "should create user with locale" do 23 | @request.headers['http_accept_language.parser'] = HttpAcceptLanguage::Parser.new('zh-CN') 24 | assert_difference "User.count" do 25 | post :create, user: attributes_for(:user) 26 | end 27 | assert_equal 'zh-CN', User.last.locale 28 | end 29 | 30 | test "should redirect back after signup" do 31 | session[:return_to] = '/foo' 32 | post :create, user: attributes_for(:user) 33 | assert_redirected_to '/foo' 34 | end 35 | 36 | test "should redirect to root if logined" do 37 | login_as create(:user) 38 | get :new 39 | assert_redirected_to root_url 40 | 41 | post :create, user: attributes_for(:user) 42 | assert_redirected_to root_url 43 | end 44 | 45 | test "should check email" do 46 | user = create(:user) 47 | 48 | # exist 49 | get :check_email, user: { email: user.email }, format: 'json' 50 | assert_equal 'false', @response.body 51 | 52 | # noexist 53 | get :check_email, user: { email: build(:user).email }, format: 'json' 54 | assert_equal 'true', @response.body 55 | end 56 | 57 | test "should check username" do 58 | user = create(:user) 59 | 60 | # exist 61 | get :check_username, user: { username: user.username }, format: 'json' 62 | assert_equal 'false', @response.body 63 | 64 | # noexist 65 | get :check_username, user: { username: build(:user).username }, format: 'json' 66 | assert_equal 'true', @response.body 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/factories/attachments.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :attachment do 3 | user 4 | file File.open('app/assets/images/rails.png') 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/factories/categories.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :category do 3 | sequence(:name) { |n| "name#{n}" } 4 | sequence(:slug) { |n| "slug#{n}" } 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/factories/comments.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :comment do 3 | user 4 | body "Content" 5 | association :commentable, factory: 'topic' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/factories/friend_sites.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryGirl.define do 4 | factory :friend_site do 5 | name "MyString" 6 | url "MyString" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/factories/likes.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :like do 3 | user 4 | association :likeable, factory: 'comment' 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/factories/notifications.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryGirl.define do 4 | factory :notification do 5 | user 6 | association :subject, factory: 'comment' 7 | name "comment" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/factories/subscriptions.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :subscription do 3 | user 4 | association :subscribable, factory: 'topic' 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/factories/topics.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryGirl.define do 4 | factory :topic do 5 | user 6 | title "MyString" 7 | body "MyString" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/factories/users.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :user do 3 | name 'name' 4 | sequence(:email) { |n| "user#{n}@example.com" } 5 | sequence(:username) { |n| "username#{n}" } 6 | password '12345678' 7 | bio 'bio' 8 | end 9 | 10 | factory :admin, parent: 'user' do 11 | email CONFIG['admin_emails'].first 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/test/fixtures/.keep -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/test/helpers/.keep -------------------------------------------------------------------------------- /test/helpers/admin/comments_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Admin::CommentsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/admin/topics_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Admin::TopicsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/application_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ApplicationHelperTest < ActionView::TestCase 4 | test "return_to should return right path" do 5 | assert_equal nil, return_to_path('/') 6 | assert_equal '/?page=2', return_to_path('/?page=2') 7 | assert_equal nil, return_to_path('/login') 8 | assert_equal nil, return_to_path('/signup') 9 | assert_equal '/topics', return_to_path('/topics') 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/helpers/comments_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CommentsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/markdown_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MarkdownHelperTest < ActionView::TestCase 4 | test "should link mentions" do 5 | assert_equal %q|

@username

|, markdown_text_replace('

@username

') 6 | assert_equal %q|@username|, markdown_text_replace(%q|@username|) 7 | assert_equal %q|
@username
|, markdown_text_replace(%q|
@username
|) 8 | assert_equal %q|@username|, markdown_text_replace(%q|@username|) 9 | end 10 | 11 | test "should link comments" do 12 | assert_equal %Q|

#1

|, markdown_text_replace('

#1

') 13 | assert_equal %q|#1|, markdown_text_replace(%q|#1|) 14 | assert_equal %q|
#1
|, markdown_text_replace(%q|
#1
|) 15 | assert_equal %q|#1|, markdown_text_replace(%q|#1|) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/helpers/notifications_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class NotificationsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/timeago_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TimeagoHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/topics_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TopicsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/useres_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UseresHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/test/integration/.keep -------------------------------------------------------------------------------- /test/javascripts/likes_test.js.coffee: -------------------------------------------------------------------------------- 1 | module('Likes') 2 | 3 | test 'should updateStatus', -> 4 | $fixture = $('#qunit-fixture') 5 | $fixture.append(" 6 | 7 | 8 | 9 | 10 | ") 11 | 12 | campo.Likes.updateLike('topic', 1) 13 | ok $fixture.find('#like-for-topic-1').hasClass('liked') 14 | 15 | campo.Likes.updateLikes('comment', [1,2]) 16 | ok $fixture.find('#like-for-comment-1').hasClass('liked') 17 | ok $fixture.find('#like-for-comment-2').hasClass('liked') 18 | ok !$fixture.find('#like-for-comment-3').hasClass('liked') 19 | -------------------------------------------------------------------------------- /test/javascripts/test_helper.js.coffee: -------------------------------------------------------------------------------- 1 | #= require application 2 | #= require qunit 3 | #= require_tree . 4 | -------------------------------------------------------------------------------- /test/javascripts/visible_to_test.js.coffee: -------------------------------------------------------------------------------- 1 | module('visible-to') 2 | 3 | test 'should remove element unless logined', -> 4 | $fixture = $('#qunit-fixture') 5 | $fixture.append(" 6 |
7 | ") 8 | 9 | campo.currentUser = { id: 1 } 10 | campo.VisibleTo.check() 11 | ok($fixture.find('div').length) 12 | 13 | campo.currentUser = null 14 | campo.VisibleTo.check() 15 | ok(!$fixture.find('div').length) 16 | 17 | test 'should remove element unless creator', -> 18 | $fixture = $('#qunit-fixture') 19 | $fixture.append(" 20 |
21 | ") 22 | 23 | campo.currentUser = { id: 1 } 24 | campo.VisibleTo.check() 25 | ok($fixture.find('div').length) 26 | 27 | campo.currentUser = { id: 2 } 28 | campo.VisibleTo.check() 29 | ok(!$fixture.find('div').length) 30 | 31 | test 'should remove element if creator', -> 32 | $fixture = $('#qunit-fixture') 33 | $fixture.append(" 34 |
35 | ") 36 | 37 | campo.currentUser = { id: 2 } 38 | campo.VisibleTo.check() 39 | ok($fixture.find('div').length) 40 | 41 | campo.currentUser = { id: 1 } 42 | campo.VisibleTo.check() 43 | ok(!$fixture.find('div').length) 44 | -------------------------------------------------------------------------------- /test/jobs/comment_notification_job_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CommentNotificationJobTest < ActiveSupport::TestCase 4 | test "should create mention notifications" do 5 | user = create(:user, confirmed: true) 6 | comment = create(:comment, body: "@#{user.username}") 7 | 8 | assert_difference ["user.notifications.named('mention').count", "ActionMailer::Base.deliveries.count"] do 9 | CommentNotificationJob.create_mention_notification(comment) 10 | end 11 | end 12 | 13 | test "should no create mention notification if use ignore" do 14 | user = create(:user, send_mention_web: false, send_mention_email: false) 15 | comment = create(:comment, body: "@#{user.username}") 16 | 17 | assert_no_difference ["user.notifications.named('mention').count", "ActionMailer::Base.deliveries.count"] do 18 | CommentNotificationJob.create_mention_notification(comment) 19 | end 20 | end 21 | 22 | test "shuold create comment notifications for subscribed_users" do 23 | user = create(:user, confirmed: true) 24 | topic = create(:topic, user: user) 25 | comment = create(:comment, commentable: topic) 26 | 27 | assert_difference ["user.notifications.named('comment').count", "ActionMailer::Base.deliveries.count"] do 28 | CommentNotificationJob.create_comment_notification(comment) 29 | end 30 | end 31 | 32 | test "shuold not create comment notifications for subscribed_users if user ignore" do 33 | user = create(:user, send_comment_web: false, send_comment_email: false) 34 | topic = create(:topic, user: user) 35 | comment = create(:comment, commentable: topic) 36 | 37 | assert_no_difference ["user.notifications.named('comment').count", "ActionMailer::Base.deliveries.count"] do 38 | CommentNotificationJob.create_comment_notification(comment) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/test/mailers/.keep -------------------------------------------------------------------------------- /test/mailers/notification_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class NotificationMailerTest < ActionMailer::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/mailers/previews/notification_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/notification_mailer 2 | class NotificationMailerPreview < ActionMailer::Preview 3 | def mention 4 | user = User.first || create(:user) 5 | comment = Comment.first || create(:comment, body: '@username') 6 | NotificationMailer.mention(user.id, comment.id) 7 | end 8 | 9 | def comment 10 | user = User.first || create(:user) 11 | comment = Comment.first || create(:comment, body: '@username') 12 | NotificationMailer.comment(user.id, comment.id) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/mailers/previews/user_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/user_mailer 2 | class UserMailerPreview < ActionMailer::Preview 3 | def password_reset 4 | user = User.first || FactoryGirl.create(:user) 5 | UserMailer.password_reset(user.id) 6 | end 7 | 8 | def confirmation 9 | user = User.first || FactoryGirl.create(:user) 10 | UserMailer.confirmation(user.id) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/mailers/user_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserMailerTest < ActionMailer::TestCase 4 | test "confirmation" do 5 | user = create(:user) 6 | email = UserMailer.confirmation(user.id).deliver 7 | assert ActionMailer::Base.deliveries.any? 8 | assert_equal [user.email], email.to 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/test/models/.keep -------------------------------------------------------------------------------- /test/models/attachment_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AttachmentTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/category_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CategoryTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/comment_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CommentTest < ActiveSupport::TestCase 4 | test "should increment commentable counter cache" do 5 | topic = create(:topic) 6 | create :comment, commentable: topic 7 | assert_equal 1, topic.reload.comments_count 8 | end 9 | 10 | test "should decrement commentable counter cache" do 11 | topic = create(:topic) 12 | comment = create :comment, commentable: topic 13 | comment.trash 14 | assert_equal 0, topic.reload.comments_count 15 | 16 | comment.restore 17 | assert_equal 1, topic.reload.comments_count 18 | 19 | comment.trash 20 | comment.destroy 21 | assert_equal 0, topic.reload.comments_count 22 | end 23 | 24 | test "should touch commentable" do 25 | time = 1.day.ago 26 | topic = create(:topic, updated_at: time) 27 | create(:comment, commentable: topic) 28 | assert topic.updated_at > time 29 | end 30 | 31 | test "should count page" do 32 | topic = create(:topic) 33 | 3.times { create :comment, commentable: topic } 34 | assert_equal 1, topic.comments.order(id: :asc).first.page(2) 35 | assert_equal 1, topic.comments.order(id: :asc).second.page(2) 36 | assert_equal 2, topic.comments.order(id: :asc).last.page(2) 37 | end 38 | 39 | test "should get mention_users" do 40 | user1 = create :user, username: 'user1' 41 | user2 = create :user, username: 'user2' 42 | comment = build(:comment, body: '@user1 @user2') 43 | assert_equal [user1, user2].sort, comment.mention_users.sort 44 | end 45 | 46 | test "should delete all related notifications after trash" do 47 | comment = create(:comment) 48 | create(:notification, subject: comment) 49 | 50 | assert_difference "Notification.count", -1 do 51 | comment.trash 52 | end 53 | end 54 | 55 | test "should delete all related notifications after destroy" do 56 | comment = create(:comment) 57 | create(:notification, subject: comment) 58 | 59 | assert_difference "Notification.count", -1 do 60 | comment.destroy 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/models/friend_site_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FriendSiteTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/like_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class LikeTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/notification_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class NotificationTest < ActiveSupport::TestCase 4 | test "should inc user unread_notifications_count" do 5 | user = create(:user) 6 | assert_difference "user.notifications.unread.count" do 7 | create(:notification, user: user) 8 | end 9 | 10 | assert_difference "user.notifications.unread.count", -1 do 11 | user.notifications.last.destroy 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/models/subscription_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SubscriptionTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/topic_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TopicTest < ActiveSupport::TestCase 4 | test "should update hot after create" do 5 | topic = create(:topic) 6 | assert topic.calculate_hot > 0 7 | end 8 | 9 | test "should update counter cache" do 10 | category = create(:category) 11 | topic = create(:topic, category: category) 12 | assert_equal 1, category.reload.topics_count 13 | topic.trash 14 | assert_equal 0, category.reload.topics_count 15 | topic.restore 16 | assert_equal 1, category.reload.topics_count 17 | topic.trash 18 | topic.destroy 19 | assert_equal 0, category.reload.topics_count 20 | end 21 | 22 | test "should update hot after comment change" do 23 | topic = create(:topic, comments_count: 1) 24 | hot = topic.hot 25 | comment = create(:comment, commentable: topic) 26 | assert topic.reload.hot > hot 27 | comment.destroy 28 | assert_equal hot, topic.hot 29 | end 30 | 31 | test "should trash" do 32 | topic = create(:topic) 33 | 34 | assert_difference "Topic.trashed.count" do 35 | assert_difference "Topic.count", -1 do 36 | topic.trash 37 | assert_equal true, topic.trashed? 38 | end 39 | end 40 | 41 | assert_difference "Topic.trashed.count", -1 do 42 | assert_difference "Topic.count" do 43 | topic.restore 44 | assert_equal false, topic.trashed? 45 | end 46 | end 47 | end 48 | 49 | test "should add user to subscribers" do 50 | topic = create(:topic) 51 | assert topic.subscribed_by?(topic.user) 52 | end 53 | 54 | test "should subscribe_by user" do 55 | topic = create(:topic) 56 | user = create(:user) 57 | topic.subscribe_by user 58 | assert topic.subscribed_by? user 59 | end 60 | 61 | test "should ignore_by user" do 62 | topic = create(:topic) 63 | user = create(:user) 64 | topic.ignore_by user 65 | assert topic.ignored_by? user 66 | end 67 | 68 | test "should update counter when change category" do 69 | old_category = create(:category) 70 | new_category = create(:category) 71 | topic = create(:topic, category: old_category) 72 | 73 | assert_difference "new_category.reload.topics_count" do 74 | assert_difference "old_category.reload.topics_count", -1 do 75 | topic.update_attributes category_id: new_category.id 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | test "should create user" do 5 | assert_not_nil create(:user) 6 | end 7 | 8 | test "should have remember token" do 9 | user = create(:user) 10 | assert_not_nil user.remember_token 11 | assert_equal user, User.find_by_remember_token(user.remember_token) 12 | assert_equal nil, User.find_by_remember_token('invalidtoken') 13 | end 14 | 15 | test "should genrate password_reset_token" do 16 | user = create(:user) 17 | assert_not_nil user.password_reset_token 18 | end 19 | 20 | test "should find_by_password_reset_token" do 21 | user = create(:user) 22 | token = user.password_reset_token 23 | assert_equal user, User.find_by_password_reset_token(token) 24 | end 25 | 26 | test "shuold generate confirmation token" do 27 | user = create(:user) 28 | assert_not_nil user.confirmation_token 29 | end 30 | 31 | test "should find_by_confirmation_token" do 32 | user = create(:user) 33 | token = user.confirmation_token 34 | assert_equal user, User.find_by_confirmation_token(token) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | if CONFIG['admin_emails'].blank? 6 | CONFIG['admin_emails'] = %w(admin@example.com) 7 | end 8 | 9 | class ActiveSupport::TestCase 10 | ActiveRecord::Migration.check_pending! 11 | 12 | include FactoryGirl::Syntax::Methods 13 | 14 | end 15 | 16 | class ActionController::TestCase 17 | # Delegate auth private method 18 | %w(login_as logout current_user login?).each do |method| 19 | define_method method do |*args| 20 | @controller.send method, *args 21 | end 22 | end 23 | 24 | def upload_file(path) 25 | ActionDispatch::Http::UploadedFile.new( 26 | :tempfile => File.open(path), 27 | :filename => File.basename(path) 28 | ) 29 | end 30 | end 31 | 32 | Topic.__elasticsearch__.create_index! 33 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/javascripts/locales/jquery.timeago.zh-CN.js: -------------------------------------------------------------------------------- 1 | // Simplified Chinese 2 | jQuery.timeago.settings.strings = { 3 | prefixAgo: null, 4 | prefixFromNow: "从现在开始", 5 | suffixAgo: "之前", 6 | suffixFromNow: null, 7 | seconds: "不到 1 分钟", 8 | minute: "大约 1 分钟", 9 | minutes: "%d 分钟", 10 | hour: "大约 1 小时", 11 | hours: "大约 %d 小时", 12 | day: "1 天", 13 | days: "%d 天", 14 | month: "大约 1 个月", 15 | months: "%d 月", 16 | year: "大约 1 年", 17 | years: "%d 年", 18 | numbers: [], 19 | wordSeparator: "" 20 | }; -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tengcong/campo/f8ad27798b13eadfe1e853eed8220f6b7c9ea04d/vendor/assets/stylesheets/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/nprogress.css: -------------------------------------------------------------------------------- 1 | /* Make clicks pass-through */ 2 | #nprogress { 3 | pointer-events: none; 4 | } 5 | 6 | #nprogress .bar { 7 | background: #29d; 8 | 9 | position: fixed; 10 | z-index: 1031; 11 | top: 0; 12 | left: 0; 13 | 14 | width: 100%; 15 | height: 2px; 16 | } 17 | 18 | /* Fancy blur effect */ 19 | #nprogress .peg { 20 | display: block; 21 | position: absolute; 22 | right: 0px; 23 | width: 100px; 24 | height: 100%; 25 | box-shadow: 0 0 10px #29d, 0 0 5px #29d; 26 | opacity: 1.0; 27 | 28 | -webkit-transform: rotate(3deg) translate(0px, -4px); 29 | -ms-transform: rotate(3deg) translate(0px, -4px); 30 | transform: rotate(3deg) translate(0px, -4px); 31 | } 32 | 33 | /* Remove these to get rid of the spinner */ 34 | #nprogress .spinner { 35 | display: block; 36 | position: fixed; 37 | z-index: 1031; 38 | top: 15px; 39 | right: 15px; 40 | } 41 | 42 | #nprogress .spinner-icon { 43 | width: 18px; 44 | height: 18px; 45 | box-sizing: border-box; 46 | 47 | border: solid 2px transparent; 48 | border-top-color: #29d; 49 | border-left-color: #29d; 50 | border-radius: 50%; 51 | 52 | -webkit-animation: nprogress-spinner 400ms linear infinite; 53 | animation: nprogress-spinner 400ms linear infinite; 54 | } 55 | 56 | @-webkit-keyframes nprogress-spinner { 57 | 0% { -webkit-transform: rotate(0deg); } 58 | 100% { -webkit-transform: rotate(360deg); } 59 | } 60 | @keyframes nprogress-spinner { 61 | 0% { transform: rotate(0deg); } 62 | 100% { transform: rotate(360deg); } 63 | } 64 | 65 | --------------------------------------------------------------------------------