diff --git a/.gitignore b/.gitignore
index cce3516..30e9bc1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,77 +1,34 @@
-#----------------------------------------------------------------------------
-# Ignore these files when commiting to a git repository.
-#
# See http://help.github.com/ignore-files/ for more about ignoring files.
#
-# The original version of this file is found here:
-# https://github.com/RailsApps/rails-composer/blob/master/files/gitignore.txt
-#
-# Corrections? Improvements? Create a GitHub issue:
-# http://github.com/RailsApps/rails-composer/issues
-#----------------------------------------------------------------------------
-
-# bundler state
-/.bundle
-/vendor/bundle/
-/vendor/ruby/
-
-# minimal Rails specific artifacts
-db/*.sqlite3
-/log/*
-/tmp/*
-
-# various artifacts
-**.war
-*.rbc
-*.sassc
-.rspec
-.redcar/
-.sass-cache
-/config/config.yml
-/config/database.yml
-/coverage.data
-/coverage/
-/db/*.javadb/
-/db/*.sqlite3
-/doc/api/
-/doc/app/
-/doc/features.html
-/doc/specs.html
-/public/cache
-/public/stylesheets/compiled
-/public/system/*
-/spec/tmp/*
-/cache
-/capybara*
-/capybara-*.html
-/gems
-/specifications
-rerun.txt
-pickle-email-*.html
-
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile ~/.gitignore_global
-#
-# Here are some files you may want to ignore globally:
-
-# scm revert files
-**.orig
-# Mac finder artifacts
-.DS_Store
-
-# Netbeans project directory
-/nbproject/
+# Ignore bundler config
+/.bundle
-# RubyMine project files
-.idea
+# Ignore the default SQLite database.
+/db/*.sqlite3
-# Textmate project files
-/*.tmproj
+# Ignore all logfiles and tempfiles.
+/log/*.log
+/tmp/cache/
+/tmp/sessions/
+/tmp/pids/*.pid
+/tmp/sockets/*.sock
-# vim artifacts
-**.swp
+.DS_Store
+.cache_rake_t
+/config/database.yml
+/config/settings.yml
+/.sass-cache
+/public/uploads/
+/public/assets/
+/public/cad
+chromedriver.log
+/vendor/bundle/
+/*.sh
+.rvmrc
+/logfile
+config/application.yml
-# Ignore application configuration
-/config/application.yml
diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..53607ea
--- /dev/null
+++ b/.rspec
@@ -0,0 +1 @@
+--colour
diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 0000000..3e3c2f1
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+2.1.1
diff --git a/Gemfile b/Gemfile
index b3c361e..e28ae2a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,27 +1,85 @@
-source 'https://rubygems.org'
-gem 'rails', '3.2.12'
-gem 'sqlite3'
+source 'http://rubygems.org'
+
+gem 'rails', '3.2.17'
+gem 'honeypot-captcha'
+
+# Bundle edge Rails instead:
+# gem 'rails', :git => 'git://github.com/rails/rails.git'
+
+# gem 'pg'
+gem 'mysql2'
+
+# Gems used only for assets and not required
+# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
+
+ # gem 'therubyracer', '~> 0.10.2', :platforms => :ruby
gem 'uglifier', '>= 1.0.3'
end
+
gem 'jquery-rails'
-gem "unicorn", ">= 4.3.1"
-gem "rspec-rails", ">= 2.12.2", :group => [:development, :test]
-gem "database_cleaner", ">= 1.0.0.RC1", :group => :test
-gem "email_spec", ">= 1.4.0", :group => :test
-gem "factory_girl_rails", ">= 4.2.0", :group => [:development, :test]
-gem "bootstrap-sass", ">= 2.3.0.0"
-gem "devise", ">= 2.2.3"
-gem "cancan", ">= 1.6.9"
-gem "rolify", ">= 3.2.0"
-gem "simple_form", ">= 2.1.0"
-gem "quiet_assets", ">= 1.0.2", :group => :development
-gem "figaro", ">= 0.6.3"
-gem "better_errors", ">= 0.7.2", :group => :development
-gem "binding_of_caller", ">= 0.7.1", :group => :development, :platforms => [:mri_19, :rbx]
+gem 'jquery-ui-rails'
+
+# To use ActiveModel has_secure_password
+# gem 'bcrypt-ruby', '~> 3.0.0'
+
+# Use unicorn as the web server
+gem 'unicorn'
+
+# Deploy with Capistrano
+# gem 'capistrano'
+
+# To use debugger
+# gem 'ruby-debug19', :require => 'ruby-debug'
+
+group :production do
+ gem 'rabel-meta', '1.5'
+end
+
+group :test, :development do
+ gem 'rspec-rails', '~> 2.12.0'
+ gem 'debugger'
+end
+
+group :test do
+ gem 'rspec'
+ gem 'shoulda-matchers', '~> 1.5.2'
+ gem 'factory_girl_rails', '~> 3.5.0'
+ gem 'cucumber-rails', :require => false
+ gem 'database_cleaner'
+ gem 'capybara', '~> 2.0.1'
+ gem 'selenium-webdriver', '~> 2.37.0'
+end
+
+group :development do
+ gem 'quiet_assets', '~> 1.0.1'
+ gem 'awesome_print'
+ gem 'better_errors'
+end
+
+gem 'haml'
+gem 'devise'
+gem 'cancan'
+gem 'kaminari'
+gem 'carrierwave', "~> 0.6.2"
+gem 'carrierwave-upyun', '~> 0.1.6'
+gem 'rmagick'
+gem 'mime-types'
+gem 'redcarpet', '~> 3.1.2'
+gem 'coderay'
+gem 'kgio'
+gem 'dalli'
+gem 'acts_as_list'
+gem 'rails-settings-cached', '= 0.2.1'
+gem 'facebox-rails', '~> 0.1.3'
+gem 'default_value_for', '~> 2.0.2'
+gem 'bootstrap-sass', '~> 3.1.1'
+gem 'slim-rails', '~> 1.0.3'
+gem 'simple_form', '~> 2.0.4'
+gem 'foreman', '~> 0.61.0'
+gem 'thin', '~> 1.5.0'
+gem 'figaro'
gem 'masonry-rails'
-gem 'mysql2'
-gem 'redcarpet'
diff --git a/Gemfile.lock b/Gemfile.lock
index 0501012..743e657 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,12 +1,12 @@
GEM
- remote: https://rubygems.org/
+ remote: http://rubygems.org/
specs:
- actionmailer (3.2.12)
- actionpack (= 3.2.12)
- mail (~> 2.4.4)
- actionpack (3.2.12)
- activemodel (= 3.2.12)
- activesupport (= 3.2.12)
+ actionmailer (3.2.17)
+ actionpack (= 3.2.17)
+ mail (~> 2.5.4)
+ actionpack (3.2.17)
+ activemodel (= 3.2.17)
+ activesupport (= 3.2.17)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
@@ -14,85 +14,144 @@ GEM
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
- activemodel (3.2.12)
- activesupport (= 3.2.12)
+ activemodel (3.2.17)
+ activesupport (= 3.2.17)
builder (~> 3.0.0)
- activerecord (3.2.12)
- activemodel (= 3.2.12)
- activesupport (= 3.2.12)
+ activerecord (3.2.17)
+ activemodel (= 3.2.17)
+ activesupport (= 3.2.17)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
- activeresource (3.2.12)
- activemodel (= 3.2.12)
- activesupport (= 3.2.12)
- activesupport (3.2.12)
- i18n (~> 0.6)
+ activeresource (3.2.17)
+ activemodel (= 3.2.17)
+ activesupport (= 3.2.17)
+ activesupport (3.2.17)
+ i18n (~> 0.6, >= 0.6.4)
multi_json (~> 1.0)
- addressable (2.3.4)
- arel (3.0.2)
- bcrypt-ruby (3.0.1)
- better_errors (0.9.0)
+ acts_as_list (0.4.0)
+ activerecord (>= 3.0)
+ arel (3.0.3)
+ atomic (1.1.15)
+ awesome_print (1.2.0)
+ bcrypt (3.1.7)
+ bcrypt-ruby (3.1.5)
+ bcrypt (>= 3.1.3)
+ better_errors (1.1.0)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
- binding_of_caller (0.7.1)
- debug_inspector (>= 0.0.1)
- bootstrap-sass (2.3.1.3)
+ bootstrap-sass (3.1.1.1)
sass (~> 3.2)
+ bourne (1.5.0)
+ mocha (>= 0.13.2, < 0.15)
builder (3.0.4)
cancan (1.6.10)
- coderay (1.0.9)
+ capybara (2.0.3)
+ mime-types (>= 1.16)
+ nokogiri (>= 1.3.3)
+ rack (>= 1.0.0)
+ rack-test (>= 0.5.4)
+ selenium-webdriver (~> 2.0)
+ xpath (~> 1.0.0)
+ carrierwave (0.6.2)
+ activemodel (>= 3.2.0)
+ activesupport (>= 3.2.0)
+ carrierwave-upyun (0.1.6)
+ carrierwave (>= 0.5.7)
+ rest-client (>= 1.6.7)
+ childprocess (0.5.1)
+ ffi (~> 1.0, >= 1.0.11)
+ coderay (1.1.0)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script-source
execjs
- coffee-script-source (1.6.2)
- database_cleaner (1.0.1)
- debug_inspector (0.0.2)
- devise (2.2.4)
+ coffee-script-source (1.7.0)
+ columnize (0.3.6)
+ cucumber (1.3.11)
+ builder (>= 2.1.2)
+ diff-lcs (>= 1.1.3)
+ gherkin (~> 2.12)
+ multi_json (>= 1.7.5, < 2.0)
+ multi_test (>= 0.0.2)
+ cucumber-rails (1.4.0)
+ capybara (>= 1.1.2)
+ cucumber (>= 1.2.0)
+ nokogiri (>= 1.5.0)
+ rails (>= 3.0.0)
+ daemons (1.1.9)
+ dalli (2.7.0)
+ database_cleaner (1.2.0)
+ debugger (1.6.6)
+ columnize (>= 0.3.1)
+ debugger-linecache (~> 1.2.0)
+ debugger-ruby_core_source (~> 1.3.2)
+ debugger-linecache (1.2.0)
+ debugger-ruby_core_source (1.3.2)
+ default_value_for (2.0.3)
+ devise (3.2.3)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.1)
- railties (~> 3.1)
- warden (~> 1.2.1)
- diff-lcs (1.2.4)
- email_spec (1.4.0)
- launchy (~> 2.1)
- mail (~> 2.2)
+ railties (>= 3.2.6, < 5)
+ thread_safe (~> 0.1)
+ warden (~> 1.2.3)
+ diff-lcs (1.1.3)
erubis (2.7.0)
- execjs (1.4.0)
- multi_json (~> 1.0)
- factory_girl (4.2.0)
+ eventmachine (1.0.3)
+ execjs (2.0.2)
+ facebox-rails (0.1.3)
+ railties (~> 3.0)
+ thor (~> 0.14)
+ factory_girl (3.5.0)
activesupport (>= 3.0.0)
- factory_girl_rails (4.2.1)
- factory_girl (~> 4.2.0)
+ factory_girl_rails (3.5.0)
+ factory_girl (~> 3.5.0)
railties (>= 3.0.0)
- figaro (0.6.4)
+ ffi (1.9.3)
+ figaro (0.7.0)
bundler (~> 1.0)
rails (>= 3, < 5)
- hike (1.2.2)
- i18n (0.6.4)
+ foreman (0.61.0)
+ thor (>= 0.13.6)
+ gherkin (2.12.2)
+ multi_json (~> 1.3)
+ haml (4.0.5)
+ tilt
+ hike (1.2.3)
+ honeypot-captcha (1.0.1)
+ i18n (0.6.9)
journey (1.0.4)
- jquery-rails (3.0.0)
+ jquery-rails (3.1.0)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
- json (1.8.0)
- kgio (2.8.0)
- launchy (2.3.0)
- addressable (~> 2.3)
- mail (2.4.4)
- i18n (>= 0.4.0)
+ jquery-ui-rails (4.2.0)
+ railties (>= 3.2.16)
+ json (1.8.1)
+ kaminari (0.15.1)
+ actionpack (>= 3.0.0)
+ activesupport (>= 3.0.0)
+ kgio (2.9.2)
+ mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
- masonry-rails (0.2.0)
+ masonry-rails (0.2.1)
rails
- mime-types (1.23)
- multi_json (1.7.5)
- mysql2 (0.3.11)
- orm_adapter (0.4.0)
- polyglot (0.3.3)
+ metaclass (0.0.4)
+ mime-types (1.25.1)
+ mini_portile (0.5.2)
+ mocha (0.14.0)
+ metaclass (~> 0.0.1)
+ multi_json (1.9.0)
+ multi_test (0.0.3)
+ mysql2 (0.3.15)
+ nokogiri (1.6.1)
+ mini_portile (~> 0.5.0)
+ orm_adapter (0.5.0)
+ polyglot (0.3.4)
quiet_assets (1.0.2)
railties (>= 3.1, < 5.0)
+ rabel-meta (1.5)
rack (1.4.5)
rack-cache (1.2)
rack (>= 0.4)
@@ -100,92 +159,148 @@ GEM
rack
rack-test (0.6.2)
rack (>= 1.0)
- rails (3.2.12)
- actionmailer (= 3.2.12)
- actionpack (= 3.2.12)
- activerecord (= 3.2.12)
- activeresource (= 3.2.12)
- activesupport (= 3.2.12)
+ rails (3.2.17)
+ actionmailer (= 3.2.17)
+ actionpack (= 3.2.17)
+ activerecord (= 3.2.17)
+ activeresource (= 3.2.17)
+ activesupport (= 3.2.17)
bundler (~> 1.0)
- railties (= 3.2.12)
- railties (3.2.12)
- actionpack (= 3.2.12)
- activesupport (= 3.2.12)
+ railties (= 3.2.17)
+ rails-settings-cached (0.2.1)
+ rails (>= 3.0.0)
+ railties (3.2.17)
+ actionpack (= 3.2.17)
+ activesupport (= 3.2.17)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
- raindrops (0.11.0)
- rake (10.0.4)
+ raindrops (0.13.0)
+ rake (10.1.1)
rdoc (3.12.2)
json (~> 1.4)
- redcarpet (2.0.1)
- rolify (3.2.0)
- rspec-core (2.13.1)
- rspec-expectations (2.13.0)
- diff-lcs (>= 1.1.3, < 2.0)
- rspec-mocks (2.13.1)
- rspec-rails (2.13.2)
+ redcarpet (3.1.2)
+ rest-client (1.6.7)
+ mime-types (>= 1.16)
+ rmagick (2.13.2)
+ rspec (2.12.0)
+ rspec-core (~> 2.12.0)
+ rspec-expectations (~> 2.12.0)
+ rspec-mocks (~> 2.12.0)
+ rspec-core (2.12.2)
+ rspec-expectations (2.12.1)
+ diff-lcs (~> 1.1.3)
+ rspec-mocks (2.12.2)
+ rspec-rails (2.12.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
- rspec-core (~> 2.13.0)
- rspec-expectations (~> 2.13.0)
- rspec-mocks (~> 2.13.0)
- sass (3.2.9)
+ rspec-core (~> 2.12.0)
+ rspec-expectations (~> 2.12.0)
+ rspec-mocks (~> 2.12.0)
+ rubyzip (1.0.0)
+ sass (3.2.14)
sass-rails (3.2.6)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
- simple_form (2.1.0)
+ selenium-webdriver (2.37.0)
+ childprocess (>= 0.2.5)
+ multi_json (~> 1.0)
+ rubyzip (~> 1.0.0)
+ websocket (~> 1.0.4)
+ shoulda-matchers (1.5.6)
+ activesupport (>= 3.0.0)
+ bourne (~> 1.3)
+ simple_form (2.0.4)
actionpack (~> 3.0)
activemodel (~> 3.0)
+ slim (1.3.9)
+ temple (~> 0.6.3)
+ tilt (~> 1.3, >= 1.3.3)
+ slim-rails (1.0.3)
+ actionpack (~> 3.0)
+ activesupport (~> 3.0)
+ railties (~> 3.0)
+ slim (~> 1.0)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
- sqlite3 (1.3.7)
+ temple (0.6.7)
+ thin (1.5.1)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
thor (0.18.1)
+ thread_safe (0.2.0)
+ atomic (>= 1.1.7, < 2)
tilt (1.4.1)
- treetop (1.4.12)
+ treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
- tzinfo (0.3.37)
- uglifier (2.1.1)
+ tzinfo (0.3.38)
+ uglifier (2.4.0)
execjs (>= 0.3.0)
- multi_json (~> 1.0, >= 1.0.2)
- unicorn (4.6.2)
+ json (>= 1.8.0)
+ unicorn (4.8.2)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
- warden (1.2.1)
+ warden (1.2.3)
rack (>= 1.0)
+ websocket (1.0.7)
+ xpath (1.0.0)
+ nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
- better_errors (>= 0.7.2)
- binding_of_caller (>= 0.7.1)
- bootstrap-sass (>= 2.3.0.0)
- cancan (>= 1.6.9)
+ acts_as_list
+ awesome_print
+ better_errors
+ bootstrap-sass (~> 3.1.1)
+ cancan
+ capybara (~> 2.0.1)
+ carrierwave (~> 0.6.2)
+ carrierwave-upyun (~> 0.1.6)
+ coderay
coffee-rails (~> 3.2.1)
- database_cleaner (>= 1.0.0.RC1)
- devise (>= 2.2.3)
- email_spec (>= 1.4.0)
- factory_girl_rails (>= 4.2.0)
- figaro (>= 0.6.3)
+ cucumber-rails
+ dalli
+ database_cleaner
+ debugger
+ default_value_for (~> 2.0.2)
+ devise
+ facebox-rails (~> 0.1.3)
+ factory_girl_rails (~> 3.5.0)
+ figaro
+ foreman (~> 0.61.0)
+ haml
+ honeypot-captcha
jquery-rails
+ jquery-ui-rails
+ kaminari
+ kgio
masonry-rails
+ mime-types
mysql2
- quiet_assets (>= 1.0.2)
- rails (= 3.2.12)
- redcarpet
- rolify (>= 3.2.0)
- rspec-rails (>= 2.12.2)
+ quiet_assets (~> 1.0.1)
+ rabel-meta (= 1.5)
+ rails (= 3.2.17)
+ rails-settings-cached (= 0.2.1)
+ redcarpet (~> 3.1.2)
+ rmagick
+ rspec
+ rspec-rails (~> 2.12.0)
sass-rails (~> 3.2.3)
- simple_form (>= 2.1.0)
- sqlite3
+ selenium-webdriver (~> 2.37.0)
+ shoulda-matchers (~> 1.5.2)
+ simple_form (~> 2.0.4)
+ slim-rails (~> 1.0.3)
+ thin (~> 1.5.0)
uglifier (>= 1.0.3)
- unicorn (>= 4.3.1)
+ unicorn
diff --git a/License.txt b/License.txt
new file mode 100644
index 0000000..a794b82
--- /dev/null
+++ b/License.txt
@@ -0,0 +1,15 @@
+Rabel
+-----
+Rabel is released under the [MIT License](http://opensource.org/licenses/MIT).
+
+Project Babel 2
+---------------
+Copyright (c) 2010, Xin Liu All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the name of the OLIVIDA nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..9302eaa
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: bundle exec rails server thin -p $PORT -e $RAILS_ENV
diff --git a/README.md b/README.md
index 4f47030..cbb319d 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,5 @@
-# MakerLab Learning Management System
+## MakerLab Learning Managment System学习系统
-This is a simple learning management system like edx-platform,but more simple.No grading system.
+基于Rabel(https://github.com/daqing/rabel )整合了论坛、评论、收藏等功能的学习系统。
-Developed based on rails 3.2.12.
-# Todo
-
-Provide more info.
diff --git a/Rakefile b/Rakefile
index d152cfe..06f15ae 100644
--- a/Rakefile
+++ b/Rakefile
@@ -4,4 +4,4 @@
require File.expand_path('../config/application', __FILE__)
-MakerLabLMS::Application.load_tasks
+Rabel::Application.load_tasks
diff --git a/app/assets/images/admin/ads.png b/app/assets/images/admin/ads.png
new file mode 100644
index 0000000..fd6ce34
Binary files /dev/null and b/app/assets/images/admin/ads.png differ
diff --git a/app/assets/images/admin/bg.png b/app/assets/images/admin/bg.png
new file mode 100644
index 0000000..72fe137
Binary files /dev/null and b/app/assets/images/admin/bg.png differ
diff --git a/app/assets/images/admin/cagegories.png b/app/assets/images/admin/cagegories.png
new file mode 100644
index 0000000..57e9f0c
Binary files /dev/null and b/app/assets/images/admin/cagegories.png differ
diff --git a/app/assets/images/admin/cloud.png b/app/assets/images/admin/cloud.png
new file mode 100644
index 0000000..5ea8651
Binary files /dev/null and b/app/assets/images/admin/cloud.png differ
diff --git a/app/assets/images/admin/dashboard.png b/app/assets/images/admin/dashboard.png
new file mode 100644
index 0000000..28a272b
Binary files /dev/null and b/app/assets/images/admin/dashboard.png differ
diff --git a/app/assets/images/admin/guides.png b/app/assets/images/admin/guides.png
new file mode 100644
index 0000000..57e9f0c
Binary files /dev/null and b/app/assets/images/admin/guides.png differ
diff --git a/app/assets/images/admin/member.png b/app/assets/images/admin/member.png
new file mode 100644
index 0000000..a75cb3d
Binary files /dev/null and b/app/assets/images/admin/member.png differ
diff --git a/app/assets/images/admin/nodes.png b/app/assets/images/admin/nodes.png
new file mode 100644
index 0000000..600ecfa
Binary files /dev/null and b/app/assets/images/admin/nodes.png differ
diff --git a/app/assets/images/admin/pages.png b/app/assets/images/admin/pages.png
new file mode 100644
index 0000000..2ed77eb
Binary files /dev/null and b/app/assets/images/admin/pages.png differ
diff --git a/app/assets/images/admin/palette.png b/app/assets/images/admin/palette.png
new file mode 100644
index 0000000..58c1386
Binary files /dev/null and b/app/assets/images/admin/palette.png differ
diff --git a/app/assets/images/admin/reward_history.png b/app/assets/images/admin/reward_history.png
new file mode 100644
index 0000000..32d2f0c
Binary files /dev/null and b/app/assets/images/admin/reward_history.png differ
diff --git a/app/assets/images/admin/settings.png b/app/assets/images/admin/settings.png
new file mode 100644
index 0000000..e394329
Binary files /dev/null and b/app/assets/images/admin/settings.png differ
diff --git a/app/assets/images/admin/topics.png b/app/assets/images/admin/topics.png
new file mode 100644
index 0000000..57e9f0c
Binary files /dev/null and b/app/assets/images/admin/topics.png differ
diff --git a/app/assets/images/admin/users.png b/app/assets/images/admin/users.png
new file mode 100644
index 0000000..614779e
Binary files /dev/null and b/app/assets/images/admin/users.png differ
diff --git a/app/assets/images/bg.png b/app/assets/images/bg.png
new file mode 100644
index 0000000..48d02a0
Binary files /dev/null and b/app/assets/images/bg.png differ
diff --git a/app/assets/images/bg_blended.png b/app/assets/images/bg_blended.png
new file mode 100644
index 0000000..dde6034
Binary files /dev/null and b/app/assets/images/bg_blended.png differ
diff --git a/app/assets/images/bg_top_light.png b/app/assets/images/bg_top_light.png
new file mode 100644
index 0000000..8cdcab9
Binary files /dev/null and b/app/assets/images/bg_top_light.png differ
diff --git a/app/assets/images/dot_orange.png b/app/assets/images/dot_orange.png
new file mode 100644
index 0000000..d01ff1a
Binary files /dev/null and b/app/assets/images/dot_orange.png differ
diff --git a/app/assets/images/ghost.png b/app/assets/images/ghost.png
new file mode 100644
index 0000000..4923584
Binary files /dev/null and b/app/assets/images/ghost.png differ
diff --git a/app/assets/images/heart-gray.png b/app/assets/images/heart-gray.png
new file mode 100644
index 0000000..7591e35
Binary files /dev/null and b/app/assets/images/heart-gray.png differ
diff --git a/app/assets/images/heart.png b/app/assets/images/heart.png
new file mode 100644
index 0000000..8244c02
Binary files /dev/null and b/app/assets/images/heart.png differ
diff --git a/app/assets/images/loading.gif b/app/assets/images/loading.gif
new file mode 100644
index 0000000..e5ca1c1
Binary files /dev/null and b/app/assets/images/loading.gif differ
diff --git a/app/assets/images/mobile/bg_section.png b/app/assets/images/mobile/bg_section.png
new file mode 100644
index 0000000..a47c6f9
Binary files /dev/null and b/app/assets/images/mobile/bg_section.png differ
diff --git a/app/assets/images/mobile/eject.png b/app/assets/images/mobile/eject.png
new file mode 100644
index 0000000..fdc376f
Binary files /dev/null and b/app/assets/images/mobile/eject.png differ
diff --git a/app/assets/images/mobile/eject48.png b/app/assets/images/mobile/eject48.png
new file mode 100644
index 0000000..2cf35d6
Binary files /dev/null and b/app/assets/images/mobile/eject48.png differ
diff --git a/app/assets/images/mobile/gear.png b/app/assets/images/mobile/gear.png
new file mode 100644
index 0000000..3171a00
Binary files /dev/null and b/app/assets/images/mobile/gear.png differ
diff --git a/app/assets/images/mobile/gear48.png b/app/assets/images/mobile/gear48.png
new file mode 100644
index 0000000..cf26846
Binary files /dev/null and b/app/assets/images/mobile/gear48.png differ
diff --git a/app/assets/images/qbar.png b/app/assets/images/qbar.png
new file mode 100644
index 0000000..5f6a9a8
Binary files /dev/null and b/app/assets/images/qbar.png differ
diff --git a/app/assets/images/rails.png b/app/assets/images/rails.png
new file mode 100644
index 0000000..d5edc04
Binary files /dev/null and b/app/assets/images/rails.png differ
diff --git a/app/assets/images/reply_button.png b/app/assets/images/reply_button.png
new file mode 100644
index 0000000..7ba4ef6
Binary files /dev/null and b/app/assets/images/reply_button.png differ
diff --git a/app/assets/images/rss.png b/app/assets/images/rss.png
new file mode 100644
index 0000000..1cf1953
Binary files /dev/null and b/app/assets/images/rss.png differ
diff --git a/app/assets/images/star.png b/app/assets/images/star.png
new file mode 100644
index 0000000..3609d07
Binary files /dev/null and b/app/assets/images/star.png differ
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index ce31fdc..4351f96 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -1,18 +1,14 @@
-// This is a manifest file that'll be compiled into application.js, which will include all the files
-// listed below.
-//
-// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
-//
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// the compiled file.
-//
-// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
-// GO AFTER THE REQUIRES BELOW.
-//
//= require jquery
//= require jquery_ujs
+//= require jquery.ui.sortable
+//= require jquery.ui.datepicker
+//= require jquery.ui.datepicker-zh-CN
//= require bootstrap
-//= require_tree .
-//= require masonry/jquery.masonry
+//= require jquery.ui.effect-highlight
+//= require jquery_elastic
+//= require jquery.facebox
+//= require jquery_at_caret
+//= require jquery_smooth_scroll
+//= require rabel
//= require masonry/jquery.imagesloaded.min
+//= require masonry/jquery.masonry
diff --git a/app/assets/javascripts/articles.js.coffee b/app/assets/javascripts/articles.js.coffee
deleted file mode 100644
index 7615679..0000000
--- a/app/assets/javascripts/articles.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/categories.js.coffee b/app/assets/javascripts/categories.js.coffee
deleted file mode 100644
index 7615679..0000000
--- a/app/assets/javascripts/categories.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/downloads.js.coffee b/app/assets/javascripts/downloads.js.coffee
deleted file mode 100644
index 7615679..0000000
--- a/app/assets/javascripts/downloads.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/guides.js.coffee b/app/assets/javascripts/guides.js.coffee
deleted file mode 100644
index 7615679..0000000
--- a/app/assets/javascripts/guides.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/home.js.coffee b/app/assets/javascripts/home.js.coffee
deleted file mode 100644
index 7615679..0000000
--- a/app/assets/javascripts/home.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/i_fileupload.js b/app/assets/javascripts/i_fileupload.js
new file mode 100644
index 0000000..99f9585
--- /dev/null
+++ b/app/assets/javascripts/i_fileupload.js
@@ -0,0 +1,3 @@
+//= require jquery.ui.widget
+//= require jquery.iframe-transport
+//= require jquery.fileupload
diff --git a/app/assets/javascripts/products.js.coffee b/app/assets/javascripts/products.js.coffee
deleted file mode 100644
index 7615679..0000000
--- a/app/assets/javascripts/products.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/rabel.js.coffee b/app/assets/javascripts/rabel.js.coffee
new file mode 100644
index 0000000..ba4a4bc
--- /dev/null
+++ b/app/assets/javascripts/rabel.js.coffee
@@ -0,0 +1,88 @@
+window.rabel = {}
+window.rabel.trackEvent = (category, action, label) ->
+ try
+ _gaq.push ['_trackEvent', category, action, label]
+ catch error
+
+jQuery ($) ->
+ window.rabel.sortable = (selector, update_path, options) ->
+ options ||= {}
+ settings =
+ stop: (event, ui) ->
+ $.post(update_path, $(this).sortable('serialize', {key: 'position[]'}), ->
+ $(ui.item).parent().effect('highlight')
+ )
+ $.extend(settings, options)
+ $(selector).sortable(settings)
+
+ $(".highlight").mouseenter ->
+ $(this).css('background', '#f0f0f0')
+ .mouseleave ->
+ $(this).css('background', '')
+ $("textarea").elastic()
+ $("form.navbar-form").submit () ->
+ search_input = $("#q");
+ query = search_input.val()
+ return if query.length == 0
+ domain = search_input.data('domain')
+ window.open window.rabel.search_engine_url + "site:#{domain}%20#{query}"
+ false
+
+ focus_comment_box = ->
+ $("#comment_content").focus()
+
+ $(".fix_cell").find(".cell:last").addClass("inner").removeClass("cell")
+ $(".mention_button").click ->
+ mention = $(this).data('mention')
+ current_content = $("#comment_content").val()
+ new_content = ''
+ if current_content.length > 0
+ new_content = current_content + "\n" + mention + ' '
+ else
+ new_content = mention + ' '
+ focus_comment_box().val(new_content)
+ $(".jump_to_comment").click ->
+ $.smoothScroll({speed: 800, scrollTarget: '#comment_content'})
+ focus_comment_box()
+ $(".back_to_top").click ->
+ $.smoothScroll({speed: 700, scrollTarget: '#Top'})
+
+ $("a.preview").click ->
+ ref_obj = $("#" + $(this).data('ref'))
+ preview_content = ref_obj.val()
+ if preview_content.length == 0
+ ref_obj.focus()
+ return
+
+ type = $(this).data('type')
+ $.post("/topics/preview.text", {content: preview_content, type: type}, (data) ->
+ ref_obj.hide()
+ $("#preview").html(data).show()
+ $("#preview").css('border', '1px dotted #ccc')
+ $("#preview").css('background', 'lightyellow')
+ $("#preview").css('padding', '10px')
+ $("a.preview").addClass('current_label')
+ $(".cancel_preview").removeClass('current_label')
+ )
+ $("a.cancel_preview").click ->
+ content_id = $(this).data('ref')
+ ref_obj = $("#" + content_id)
+ $("#preview").hide()
+ ref_obj.show()
+ ref_obj.focus()
+ $(this).addClass('current_label')
+ $("a.preview").removeClass('current_label')
+
+ $(".track_event").click ->
+ window.rabel.trackEvent($(this).data('category'), $(this).data('action'), $(this).data('label'))
+
+ $(".hoverable").mouseenter ->
+ $(this).find('.hover_action').fadeIn()
+ .mouseleave ->
+ $(this).find('.hover_action').fadeOut()
+
+ if window.location.hash.length > 0
+ hashbang = window.location.hash.split('/')
+ if hashbang[0] == '#!' and hashbang[1] == 'click'
+ $("##{hashbang[2]}").click()
+
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
new file mode 100644
index 0000000..9ff8534
--- /dev/null
+++ b/app/assets/stylesheets/application.css
@@ -0,0 +1,10 @@
+/*
+ * = require_self
+ * = require bootstrap
+ * = require jquery.facebox
+ * = require core
+ *= require 'masonry/fluid'
+ *= require 'masonry/gutters'
+ *= require 'masonry/transitions'
+ *
+ */
diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss
deleted file mode 100644
index 37206ea..0000000
--- a/app/assets/stylesheets/application.css.scss
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * This is a manifest file that'll be compiled into application.css, which will include all the files
- * listed below.
- *
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
- * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
- *
- * You're free to add application-wide styles to this file and they'll appear at the top of the
- * compiled file, but it's generally better to create a new file per style scope.
- *
- *= require_self
- *= require_tree .
- *= require 'masonry/fluid'
- *= require 'masonry/basic'
- *= require 'masonry/centered'
- *= require 'masonry/gutters'
- *= require 'masonry/infinitescroll'
- *= require 'masonry/right-to-left'
- *= require 'masonry/transitions'
-*/
-
-.content {
- background-color: #eee;
- padding: 20px;
- margin: 0 -20px; /* negative indent the amount of the padding to maintain the grid system */
- -webkit-border-radius: 0 0 6px 6px;
- -moz-border-radius: 0 0 6px 6px;
- border-radius: 0 0 6px 6px;
- -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
- -moz-box-shadow: 0 1px 2px rgba(0,0,0,.15);
- box-shadow: 0 1px 2px rgba(0,0,0,.15);
-}
diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.scss b/app/assets/stylesheets/bootstrap_and_overrides.css.scss
deleted file mode 100644
index f176172..0000000
--- a/app/assets/stylesheets/bootstrap_and_overrides.css.scss
+++ /dev/null
@@ -1,131 +0,0 @@
-@import "bootstrap";
-body { padding-top: 60px; }
-@import "bootstrap-responsive";
-
-.box-guide {
- border: 1px solid #ccc;
- width: 215px;
- padding: 0px 10px;
- background: white;
- margin: 5px 0;
-
- h5,p {
- color: black;
- }
-}
-
-.half {
- width: 49%;
- float: left;
-}
-
-.box-guide:hover {
- background: #eee;
-}
-
-#masonry-container {
- padding: 5px;
- margin-bottom: 20px;
- border-radius: 5px;
- clear: both;
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
-}
-
-.clearfix:before, .clearfix:after { content: ""; display: table; }
-.clearfix:after { clear: both; }
-.clearfix { zoom: 1; }
-
-.sidebar {
- width: 250px;
- float: left;
- background-color: #FFF;
- border: 1px solid #E7E7E7;
-
- h1{
- font-size: 2em;
- margin:0 10px 0px 10px;
- }
-
- .subtitle {
- margin:0 10px 10px 10px;
- color: #aaa;
- }
-}
-
-.sidenav {
- width: 230px;
- margin: 10px 10px 10px 10px;
- padding: 0;
- background-color: #FFF;
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
- -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
- -moz-box-shadow: 0 1px 4px rgba(0,0,0,.065);
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
-
- li {
- a {
- margin:0 0 -1px;
- padding: 8px 14px;
- border: 1px solid #E5E5E5;
- }
- }
- .icon-chevron-right {
- float: right;
- margin-top: 2px;
- margin-right: -6px;
- opacity: .25;
- }
-}
-
-.article {
- background-color: white;
- padding: 10px;
-
- .content-head{
- border-bottom: 1px solid #ddd;
-
- p {
- margin-right: 20px;
- margin-top: 10px;
- }
- }
-
- .article-content {
- margin-top: 5px;
- }
-}
-
-.float-left {
- float: left;
-}
-
-.float-right {
- float: right;
-}
-
-.gray {
- color: gray;
-}
-
-.center {
- text-align: center;
-}
-
-.article-footer {
- font-size: 1.2em;
- border-top: 1px solid #ddd;
- margin-top: 10px;
- padding-top: 5px;
-}
-
-.category {
- margin: 5px 5px;
-}
-
-input, textarea, .uneditable-input {
-width: 806px;
-}
diff --git a/app/assets/stylesheets/core.css.scss b/app/assets/stylesheets/core.css.scss
new file mode 100644
index 0000000..a9142e9
--- /dev/null
+++ b/app/assets/stylesheets/core.css.scss
@@ -0,0 +1,219 @@
+#wrap {
+ background-color: #E0E0E0;
+ padding-top: 20px;
+ border-bottom: 1px solid #CCC;
+}
+
+#page-main { padding-bottom: 20px; }
+
+.box {
+ background: white;
+ border-radius: 3px;
+ box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.15);
+ border-bottom: 1px solid #E2E2E9;
+ margin-bottom: 20px;
+
+ .cell, .box-header {
+ padding: 10px;
+ border-bottom: 1px solid #F0F0F0;
+ }
+
+ .inner {
+ padding: 10px;
+ }
+
+ .header {
+ padding: 10px;
+ border-bottom: 1px solid #E2E2E2;
+ }
+}
+
+
+.snow { color: #E2E2E2; }
+.sep3 { height: 3px; }
+.sep5 { height: 5px; }
+.sep10 { height: 10px; }
+
+td.avatar {
+ text-align: center;
+ vertical-align: top;
+ width: 48px;
+}
+
+.fr { float: right; }
+
+#footer {
+ #footer-main {
+ line-height: 150%;
+ padding: 10px;
+ }
+
+ ul.page-links {
+ margin: 10px 0 20px 0;
+ font-weight: bold;
+
+ li {
+ display: inline;
+ }
+
+ li.snow {
+ padding: 0 5px;
+ }
+ }
+
+ .copywrite {
+ margin-bottom: 15px;
+ }
+}
+
+a.action_label {
+ border: 1px solid #CCC;
+ border-top-left-radius: 6px;
+ border-top-right-radius: 6px;
+ padding: 5px 8px 2px;
+ color: #666;
+ border-bottom: none;
+ text-decoration: none;
+}
+
+a.action_label:hover {
+ background-color: #ccc;
+ color: #333;
+}
+
+.topic {
+ .item_title {
+ padding-left: 58px;
+ }
+
+ .topic-meta {
+ font-size: 11px;
+ margin-top: 5px;
+
+ .muted {
+ padding: 0 5px;
+ }
+ }
+}
+
+.item_node {
+ background: -moz-linear-gradient(center top , #FFFFFF 0%, #F3F3F3 50%, #EDEDED 51%, #FFFFFF 100%) repeat scroll 0 0 transparent;
+ background: -webkit-linear-gradient(top , #FFFFFF 0%, #F3F3F3 50%, #EDEDED 51%, #FFFFFF 100%) repeat scroll 0 0 transparent;
+ border: 1px solid #E5E5E5;
+ border-radius: 4px 4px 4px 4px;
+ display: inline-block;
+ font-size: 12px;
+ line-height: 12px;
+ margin: 0 5px 5px 0;
+ padding: 4px 10px;
+}
+
+
+td.with_separator {
+ border-right: 1px solid #ccc;
+}
+
+.center { text-align: center; }
+
+.content {
+ font-size: 14px;
+ line-height: 180%;
+ color: #000;
+ overflow: hidden;
+ word-break: break-word;
+
+ h2 { padding: 10px 0px 10px 0px; }
+}
+
+.additional {
+ margin-top: 20px;
+ font-size: 85%;
+}
+
+a.current_label {
+ background-color: #E3E3E3;
+}
+
+.sort_item {
+ padding: 5px;
+ border-bottom: 1px dotted #CCC;
+}
+
+.sort_item:hover { cursor: pointer; }
+.sort_actions { margin-top: 10px; }
+
+img.external {
+ width: auto;
+ height: auto;
+ margin-bottom: 10px;
+ max-width: 570px;
+}
+
+.fileupload-btn {
+ position: relative;
+
+ input#fileupload {
+ position: absolute;
+ top: 0;
+ right: 0;
+ opacity: 0;
+ font-size: 23px;
+ direction: ltr;
+ cursor: pointer;
+
+ }
+}
+
+span.guillemet {
+ color: silver;
+}
+
+span.left-guillemet {
+ margin-right: 3px;
+}
+
+span.right-guillemet {
+ margin-left: 3px;
+}
+
+.relative-container {
+ position: relative;
+}
+
+.centered {
+ margin: 0 auto;
+ position: absolute;
+ left: 50%;
+}
+
+.custom_brand {
+ float: left;
+ margin-right: 20px;
+}
+
+ol li {
+ margin: 5px 0;
+}
+
+ul.nav li {
+ list-style-type: none;
+}
+
+.btn-new-topic {
+ position: relative;
+ top: -3px;
+}
+
+.navbar {
+ margin-bottom: 0;
+ border-bottom: 1px solid #ccc;
+}
+
+.user_list{
+ .user{
+ text-align: center;
+ margin-top: 20px;
+ margin-left: 20px;
+ float: left;
+}
+}
diff --git a/app/assets/stylesheets/downloads.css.scss b/app/assets/stylesheets/downloads.css.scss
deleted file mode 100644
index d2c8841..0000000
--- a/app/assets/stylesheets/downloads.css.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the downloads controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/home.css.scss b/app/assets/stylesheets/home.css.scss
deleted file mode 100644
index fc47dcd..0000000
--- a/app/assets/stylesheets/home.css.scss
+++ /dev/null
@@ -1,99 +0,0 @@
-// Place all the styles related to the home controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
-
-.showcases-header{
- width: 60%;
- text-align: center;
- margin: 60px 0 10px;
- font-weight: 200;
- margin-bottom: 40px;
- display: block;
- margin-left: auto;
- margin-right: auto;
-}
-
-.page-header{
- padding-bottom: 9px;
- border-bottom: 1px solid #EEE;
-}
-
-.row {
- margin-right: -15px;
- margin-left: -15px;
-}
-.item {
- height:360px;
-}
-
-.masthead-bg{
- position: absolute;
- top: 0;
- left: 0;
- min-width: 100%;
- height: 460px;
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4),0 0 30px rgba(0, 0, 0, 0.075);
- background-repeat: repeat-x;
- background-image: -webkit-linear-gradient(45deg, #02010F, #4699A8);
- background-image: -moz-linear-gradient(45deg, #02010F, #4699A8);
- background-image: linear-gradient(45deg, #02010F, #4699A8);
-}
-
-.carousel {
-position: relative;
-margin-bottom: 0px;
-margin-left: -70px;
-margin-right: -70px;
-margin-top: -40px;
-line-height: 1;
-}
-
-.carousel-caption {
- background-color:transparent;
- position: absolute;
-padding-top: 20px;
-color: #FFF;
-text-align: center;
-text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
-z-index: 10;
-bottom: 40px;
-left: 15%;
-right: 15%;
- p {
- margin-bottom: 20px;
- font-size: 21px;
- line-height: 1.4;
- margin: 0 0 10px;
- }
-}
-
-.masthead h1 {
-font-size: 100px;
-line-height: 1;
-letter-spacing: -2px;
-font-weight: bold;
-}
-
-.masthead h2 {
-font-size: 30px;
-font-weight: 200;
-line-height: 1.25;
-}
-
-.bc-social {
-padding: 15px 0;
-text-align: center;
-}
-
-.bc-social-buttons {
-margin-left: 0;
-margin-bottom: 0;
-padding-left: 0;
-list-style: none;
-li {
-display: inline-block;
-padding: 5px 8px;
-line-height: 1;
-font-size: 16px;
-}
-}
diff --git a/app/assets/stylesheets/products.css.scss b/app/assets/stylesheets/products.css.scss
deleted file mode 100644
index 89e2e8d..0000000
--- a/app/assets/stylesheets/products.css.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the products controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/admin/advertisements_controller.rb b/app/controllers/admin/advertisements_controller.rb
new file mode 100644
index 0000000..75bcc20
--- /dev/null
+++ b/app/controllers/admin/advertisements_controller.rb
@@ -0,0 +1,51 @@
+# encoding: utf-8
+class Admin::AdvertisementsController < Admin::BaseController
+ before_filter :find_ad, :only => [:edit, :update, :destroy]
+
+ def index
+ @ads = Advertisement.order('start_date DESC').page(params[:page]).per(4)
+ @title = '广告位'
+ end
+
+ def new
+ @ad = Advertisement.new
+ @title = '添加新广告'
+ end
+
+ def create
+ @ad = Advertisement.new(params[:advertisement])
+ if @ad.save
+ redirect_to admin_advertisements_path
+ else
+ flash[:error] = '添加新广告失败'
+ render :new
+ end
+ end
+
+ def edit
+ @title = '修改广告'
+ end
+
+ def update
+ if @ad.update_attributes(params[:advertisement])
+ redirect_to admin_advertisements_path
+ else
+ flash[:error] = '修改广告失败'
+ render :edit
+ end
+ end
+
+ def destroy
+ if @ad.destroy
+ flash[:success] = '删除成功'
+ else
+ flash[:success] = '删除失败'
+ end
+ redirect_to admin_advertisements_path
+ end
+
+ private
+ def find_ad
+ @ad = Advertisement.find(params[:id])
+ end
+end
diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb
new file mode 100644
index 0000000..763ecf1
--- /dev/null
+++ b/app/controllers/admin/base_controller.rb
@@ -0,0 +1,26 @@
+# encoding: utf-8
+class Admin::BaseController < ApplicationController
+ include Admin::BaseHelper
+ before_filter :authenticate_user!
+ before_filter do |c|
+ raise CanCan::AccessDenied unless current_user.can_manage_site?
+ end
+
+ before_filter do |c|
+ add_title_item '管理后台'
+ end
+
+ layout 'admin'
+
+ def method_missing(method)
+ if method =~ /^find_parent_(.*)$/
+ model = $1.classify.constantize
+ instance_variable_set("@#{$1}".to_sym, model.find(params["#{$1}_id".to_sym]))
+ elsif method =~ /^find_(.*)$/
+ model = $1.classify.constantize
+ instance_variable_set("@#{$1}".to_sym, model.find(params[:id]))
+ else
+ super
+ end
+ end
+end
diff --git a/app/controllers/admin/categories_controller.rb b/app/controllers/admin/categories_controller.rb
new file mode 100644
index 0000000..6970d7a
--- /dev/null
+++ b/app/controllers/admin/categories_controller.rb
@@ -0,0 +1,61 @@
+# encoding: utf-8
+class Admin::CategoriesController < Admin::BaseController
+
+ def index
+ @categories = Category.all
+ @title = '教程分类'
+ end
+
+ def new
+ @category = Category.new
+ respond_to do |format|
+ format.js {
+ @title = '添加教程分类'
+ render :show_form
+ }
+ end
+ end
+
+ def create
+ @category = Category.new(params[:category])
+ respond_to do |format|
+ if @category.save
+ format.js
+ else
+ format.js { render :show_form }
+ end
+ end
+ end
+
+ def edit
+ @category = Category.find(params[:id])
+ respond_to do |format|
+ format.js {
+ @title = '修改分类'
+ render :show_form
+ }
+ end
+ end
+
+ def update
+ @category = Category.find(params[:id])
+ respond_to do |format|
+ if @category.update_attributes(params[:category])
+ format.js { render :js => 'window.location.reload()' }
+ else
+ format.js { render :show_form }
+ end
+ end
+ end
+
+ def destroy
+ @category = Category.find(params[:id])
+ respond_to do |format|
+ if @category.can_delete? and @category.destroy
+ format.js
+ else
+ format.js { render :text => :error, :status => :unprocessable_entity }
+ end
+ end
+ end
+end
diff --git a/app/controllers/admin/cloud_files_controller.rb b/app/controllers/admin/cloud_files_controller.rb
new file mode 100644
index 0000000..920d8b7
--- /dev/null
+++ b/app/controllers/admin/cloud_files_controller.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+class Admin::CloudFilesController < Admin::BaseController
+ def index
+ @title = '云硬盘'
+ @files = CloudFile.order('id DESC').page(params[:page])
+ end
+
+ def new
+ @file = CloudFile.new
+ @title = '上传新文件'
+ end
+
+ def create
+ @file = CloudFile.new(params[:cloud_file])
+
+ if @file.save
+ redirect_to admin_cloud_files_path
+ else
+ @title = '上传新文件'
+ render :new
+ end
+ end
+
+ def destroy
+ @file = CloudFile.find(params[:id])
+
+ if @file.destroy
+ flash[:success] = '删除成功'
+ else
+ flash[:error] = '删除失败'
+ end
+ redirect_to admin_cloud_files_path
+ end
+end
diff --git a/app/controllers/admin/guides_controller.rb b/app/controllers/admin/guides_controller.rb
new file mode 100644
index 0000000..0fe87b7
--- /dev/null
+++ b/app/controllers/admin/guides_controller.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+class Admin::GuidesController < Admin::BaseController
+ def index
+ @guides = Guide.order('created_at DESC').page(params[:page])
+ @title = '学习教程'
+ end
+
+ def destroy
+ @guide = Guide.find(params[:id])
+ if @guide.destroy
+ redirect_to admin_guides_path
+ else
+ redirect_to admin_root_path
+ end
+ end
+end
diff --git a/app/controllers/admin/nodes_controller.rb b/app/controllers/admin/nodes_controller.rb
new file mode 100644
index 0000000..6939280
--- /dev/null
+++ b/app/controllers/admin/nodes_controller.rb
@@ -0,0 +1,81 @@
+# encoding: utf-8
+class Admin::NodesController < Admin::BaseController
+ before_filter :find_parent_plane, :except => [:sort, :destroy, :move, :move_to]
+ before_filter :find_node, :only => [:move, :move_to, :destroy]
+
+ def new
+ @node = @plane.nodes.new
+ respond_to do |format|
+ format.js {
+ @title = '添加节点'
+ render :show_form
+ }
+ end
+ end
+
+ def create
+ @node = @plane.nodes.build(params[:node])
+ respond_to do |format|
+ if @node.save
+ format.js
+ else
+ format.js { render :show_form }
+ end
+ end
+ end
+
+ def edit
+ @node = @plane.nodes.find(params[:id])
+ respond_to do |format|
+ format.js {
+ @title = '修改节点'
+ render :show_form
+ }
+ end
+ end
+
+ def update
+ @node = @plane.nodes.find(params[:id])
+ respond_to do |format|
+ if @node.update_attributes(params[:node])
+ format.js
+ else
+ format.js { render :show_form }
+ end
+ end
+ end
+
+ def sort
+ params[:position].each_with_index do |id, pos|
+ Node.update(id, :position => pos)
+ end
+
+ respond_to do |format|
+ format.js { head :ok }
+ end
+ end
+
+ def move
+ respond_to do |f|
+ f.js
+ end
+ end
+
+ def move_to
+ respond_to do |f|
+ f.js {
+ render :text => :error, :status => :unprocessable_entity unless @node.update_attributes(params[:node])
+ }
+ end
+ end
+
+ def destroy
+ respond_to do |format|
+ if @node.can_delete? and @node.destroy
+ format.js
+ else
+ format.js { render :text => :error, :status => :unprocessable_entity }
+ end
+ end
+ end
+end
diff --git a/app/controllers/admin/notifications_controller.rb b/app/controllers/admin/notifications_controller.rb
new file mode 100644
index 0000000..43688a2
--- /dev/null
+++ b/app/controllers/admin/notifications_controller.rb
@@ -0,0 +1,8 @@
+class Admin::NotificationsController < Admin::BaseController
+ # only delete read notifications
+ def clear
+ Notification.where(:unread => false).delete_all
+
+ redirect_to admin_root_path
+ end
+end
diff --git a/app/controllers/admin/pages_controller.rb b/app/controllers/admin/pages_controller.rb
new file mode 100644
index 0000000..3db9e4f
--- /dev/null
+++ b/app/controllers/admin/pages_controller.rb
@@ -0,0 +1,59 @@
+# encoding: utf-8
+class Admin::PagesController < Admin::BaseController
+ before_filter :find_page, :only => [:edit, :update, :destroy]
+
+ def index
+ @pages = Page.default_order
+ @title = '页面'
+ end
+
+ def new
+ @page = Page.new
+ @title = '创建新页面'
+ render :action
+ end
+
+ def create
+ @page = Page.new(params[:page])
+ if @page.save
+ redirect_to admin_pages_path
+ else
+ @title = '创建新页面'
+ render :action
+ end
+ end
+
+ def edit
+ @title = '修改页面'
+ render :action
+ end
+
+ def update
+ if @page.update_attributes(params[:page])
+ redirect_to admin_pages_path
+ else
+ @title = '编辑页面'
+ render :action
+ end
+ end
+
+ def destroy
+ if @page.destroy
+ redirect_to admin_pages_path
+ else
+ flash[:error] = '删除页面出错'
+ redirect_to admin_root_path
+ end
+ end
+
+ def sort
+ params[:position].each_with_index do |id, pos|
+ Page.update(id, :position => pos)
+ end
+
+ respond_to do |format|
+ format.js { head :ok }
+ end
+ end
+
+end
diff --git a/app/controllers/admin/planes_controller.rb b/app/controllers/admin/planes_controller.rb
new file mode 100644
index 0000000..aeab125
--- /dev/null
+++ b/app/controllers/admin/planes_controller.rb
@@ -0,0 +1,75 @@
+# encoding: utf-8
+class Admin::PlanesController < Admin::BaseController
+ before_filter :find_plane, :only => [:edit, :update, :destroy]
+
+ def index
+ @planes = Plane.default_order
+ @title = '位面节点'
+ end
+
+ def new
+ @title = '添加位面'
+ @plane = Plane.new
+ respond_to do |format|
+ format.js { render :show_form }
+ end
+ end
+
+ def create
+ @plane = Plane.new(params[:plane])
+ respond_to do |format|
+ if @plane.save
+ format.js
+ else
+ format.js { render :show_form }
+ end
+ end
+ end
+
+ def edit
+ respond_to do |format|
+ format.js {
+ @title = '修改位面'
+ render :show_form
+ }
+ end
+ end
+
+ def update
+ respond_to do |format|
+ if @plane.update_attributes(params[:plane])
+ format.js { render :js => 'window.location.reload()' }
+ else
+ format.js { render :show_form }
+ end
+ end
+ end
+
+ def destroy
+ respond_to do |format|
+ if @plane.can_delete? and @plane.destroy
+ format.js
+ else
+ format.js { render :json => {:error => 'delete plane failed'}, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ def sort
+ if params[:position].present?
+ params[:position].each_with_index do |id, pos|
+ Plane.update(id, :position => pos)
+ end
+
+ respond_to do |f|
+ f.js { head :ok }
+ end
+ else
+ respond_to do |f|
+ f.js {
+ @planes = Plane.default_order
+ }
+ end
+ end
+ end
+end
diff --git a/app/controllers/admin/rewards_controller.rb b/app/controllers/admin/rewards_controller.rb
new file mode 100644
index 0000000..1d193d6
--- /dev/null
+++ b/app/controllers/admin/rewards_controller.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+class Admin::RewardsController < Admin::BaseController
+ before_filter :find_parent_user, :except => [:index]
+
+ def index
+ @rewards = Reward.order('created_at DESC').page(params[:page])
+ @title = '奖励记录'
+ end
+
+ def new
+ respond_to do |f|
+ f.js {
+ @reward_type = params[:reward_type].present? ? params[:reward_type] : Reward::TYPE_GRANT
+ @reward = @user.rewards.build(:reward_type => @reward_type, :amount_str => '0')
+ }
+ end
+ end
+
+ def create
+ respond_to do |f|
+ f.js {
+ @reward = @user.rewards.build(params[:reward])
+ @reward_type = @reward.reward_type
+ @reward.admin_user = current_user
+
+ result = Reward.transaction do
+ @reward.save && @user.update_attributes({:reward => @user.reward + @reward.amount}, :as => current_user.permission_role)
+ end
+
+ render :new and return unless result
+ }
+ end
+ end
+end
diff --git a/app/controllers/admin/site_settings_controller.rb b/app/controllers/admin/site_settings_controller.rb
new file mode 100644
index 0000000..c110c34
--- /dev/null
+++ b/app/controllers/admin/site_settings_controller.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+class Admin::SiteSettingsController < Admin::BaseController
+ def show
+ @settings = Siteconf
+ @title = '基本设置'
+ end
+
+ def appearance
+ @settings = Siteconf
+ @title = '外观'
+ end
+
+ def update
+ params[:settings].each_key do |key|
+ Siteconf.send("#{key}=", params[:settings][key])
+ end
+ flash[:success] = '保存成功'
+ redirect_to :back
+ end
+end
diff --git a/app/controllers/admin/topics_controller.rb b/app/controllers/admin/topics_controller.rb
new file mode 100644
index 0000000..dfaa593
--- /dev/null
+++ b/app/controllers/admin/topics_controller.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+class Admin::TopicsController < Admin::BaseController
+ def index
+ @topics = Topic.order('created_at DESC').page(params[:page])
+ @title = '讨论话题'
+ end
+
+ def destroy
+ @topic = Topic.find(params[:id])
+ if @topic.destroy
+ redirect_to admin_topics_path
+ else
+ redirect_to admin_root_path
+ end
+ end
+end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
new file mode 100644
index 0000000..9be69ea
--- /dev/null
+++ b/app/controllers/admin/users_controller.rb
@@ -0,0 +1,88 @@
+# encoding: utf-8
+class Admin::UsersController < Admin::BaseController
+ before_filter :find_user, :only => [:edit, :update, :toggle_admin, :toggle_blocked, :destroy]
+
+ def index
+ if params[:nickname].present?
+ @user = User.find_by_nickname(params[:nickname])
+ if @user.present?
+ @users = [@user]
+ else
+ @users = []
+ end
+ @users = Kaminari.paginate_array(@users).page(params[:page])
+ else
+ @users = User.order('created_at DESC, id DESC').page(params[:page])
+ end
+ @title = '用户'
+ end
+
+ def edit
+ authorize! :edit_info, @user
+
+ @title = '修改用户信息'
+ end
+
+ def update
+ authorize! :edit_info, @user
+
+ if params[:user][:password].empty?
+ params[:user].delete(:password)
+ params[:user].delete(:password_confirmation)
+ end
+
+ if @user.update_attributes(params[:user], :as => current_user.permission_role)
+ redirect_to admin_users_path + "?nickname=#{@user.nickname}"
+ else
+ render :edit
+ end
+ end
+
+ def toggle_admin
+ respond_to do |format|
+ if current_user.can_manage_site?
+ if @user.admin?
+ @user.acts_as_normal_user
+ else
+ @user.acts_as_admin
+ end
+ if @user.save
+ format.js
+ else
+ format.js { render :json => {:error => 'toggle admin failed'}, :status => :unprocessable_entity }
+ end
+ else
+ format.js { render :json => {:error => 'no permission'}, :status => :forbidden }
+ end
+ end
+ end
+
+ def toggle_blocked
+ respond_to do |format|
+ if current_user.can_manage_site?
+ if @user.toggle!(:blocked)
+ format.js
+ else
+ format.js { render :json => {:error => 'toggle admin failed'}, :status => :unprocessable_entity }
+ end
+ else
+ format.js { render :json => {:error => 'no permission'}, :status => :forbidden }
+ end
+ end
+ end
+
+ def destroy
+ if @user.destroy
+ flash[:success] = "会员 #{@user.nickname} 删除成功。"
+ redirect_to root_path
+ else
+ flash[:error] = "会员 #{@user.nickname} 删除失败。"
+ redirect_to member_path(@user.nickname)
+ end
+ end
+
+ private
+ def find_user
+ @user = User.find(params[:id])
+ end
+end
diff --git a/app/controllers/admin/welcome_admin_controller.rb b/app/controllers/admin/welcome_admin_controller.rb
new file mode 100644
index 0000000..4f33d1c
--- /dev/null
+++ b/app/controllers/admin/welcome_admin_controller.rb
@@ -0,0 +1,7 @@
+# encoding: utf-8
+class Admin::WelcomeAdminController < Admin::BaseController
+ def index
+ @title = '运行状态'
+ @notifications_to_clear = Notification.where(:unread => false).count
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index f9a089f..66b1617 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,8 +1,125 @@
+# encoding: utf-8
class ApplicationController < ActionController::Base
protect_from_forgery
+ include ApplicationHelper
+ include BootstrapHelper
+
+ layout :find_layout
rescue_from CanCan::AccessDenied do |exception|
- redirect_to root_path, :alert => exception.message
+ exception.default_message = t('tips.no_permission')
+ redirect_to root_url, :alert => exception.message
+ end
+
+ rescue_from ActiveRecord::RecordNotFound do |exception|
+ case request.format.to_sym
+ when :html, :mobile
+ @title = '404: Not Found'
+ @note = '您要访问的页面不存在。'
+ @exception = exception
+ render 'welcome/exception' and return
+ when :js
+ render :json => {:error => 'record not found'}, :status => :not_found and return
+ end
+ end
+
+ rescue_from NoMethodError, RuntimeError do |exception|
+ logger.error exception.message
+ exception.backtrace.each { |line| logger.error line }
+
+ case request.format.to_sym
+ when :html, :mobile
+ @title = '500: Internal Error'
+ @note = '不好意思,系统运行遇到了错误。'
+ @exception = exception
+ render 'welcome/exception' and return
+ when :js
+ render :json => {:error => exception.inspect}, :status => :internal_server_error and return
+ end
+ end
+
+ before_filter :init
+ before_filter :detect_mobile_client
+
+ def custom_path(model)
+ if model.is_a? Topic
+ t_path(model.id)
+ elsif model.is_a? Node
+ go_path(model.key)
+ else
+ model
+ end
+ end
+
+ def method_missing(method, *args, &block)
+ if method =~ /^find_(.*)able/
+ such_able = "@#{$1}able"
+ params.each do |name, value|
+ if name =~ /(.+)_id$/
+ instance_variable_set(such_able.to_sym, $1.classify.constantize.find(value)) and return
+ end
+ end
+ else
+ super
+ end
+ end
+
+ def init
+ count_unread_notification
+ initialize_breadcrumbs_and_title
+
+ @seo_description = ''
+ ActionMailer::Base.default_url_options[:host] = Figaro.env.RABEL_HOST_NAME
+ end
+
+ def initialize_breadcrumbs_and_title
+ unless request.format.to_sym == :js
+ basic_name = append_notification_count(Siteconf.site_name)
+ @title_items = [basic_name]
+ @breadcrumbs = [%(#{Siteconf.site_name} )]
+ end
+ end
+
+ def mobile_device?
+ request.format == :mobile
end
+ private
+ # Overwriting the sign_out redirect path method
+ def after_sign_out_path_for(resource_or_scope)
+ goodbye_path
+ end
+
+ def find_layout
+ if mobile_device?
+ 'application'
+ else
+ 'app'
+ end
+ end
+
+ def count_unread_notification
+ unless request.format.to_sym == :js or params[:controller] == 'notifications'
+ @unread_count = current_user.try(:unread_notification_count) || 0
+ else
+ @unread_count = 0
+ end
+ @show_notification_count = true
+ end
+
+ # From Teambox
+ def detect_mobile_client
+ ua = request.env['HTTP_USER_AGENT']
+ session[:posting_device] = ua[/(iPod|iPad|iPhone|Android)/i] if ua.present?
+ end
+
+ def store_location
+ # NOTE:
+ # This is a hack
+ # It works as long as devise doesn't change its source code
+ # See: https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb#L172
+ session[:user_return_to] = request.fullpath
+ end
+
end
+
diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb
index ee9c854..9ee61b6 100644
--- a/app/controllers/articles_controller.rb
+++ b/app/controllers/articles_controller.rb
@@ -1,31 +1,60 @@
class ArticlesController < ApplicationController
- load_and_authorize_resource
+ before_filter :authenticate_user!, :except => [:show, :index]
+ before_filter :get_guide, :except => [:show]
+ before_filter :except => [:index, :show] do |c|
+ auth_admin
+ end
# GET /articles
# GET /articles.json
- before_filter :get_guide
-
def index
- @articles = @guide.articles.all
- @category = Category.find(@guide.category_id)
-
respond_to do |format|
- format.html # index.html.erb
- format.json { render json: @articles }
+ format.html {
+ @title = @guide.title
+ @articles = @guide.articles.cached_all()
+ @seo_description = @title
+ }
+# format.atom {
+# @feed_items = Topic.recent_topics(Siteconf::HOMEPAGE_TOPICS)
+# @last_update = @feed_items.first.updated_at unless @feed_items.empty?
+# render :layout => false
+# }
end
end
# GET /articles/1
# GET /articles/1.json
def show
- @article = Article.find(params[:id])
- @articles = @guide.articles
- @next_article = @articles.where("id > #{params[:id]}").first
- @pre_article = @articles.where("id < #{params[:id]}").last
- @category = Category.find(@guide.category_id)
+ raise ActiveRecord::RecordNotFound.new if params[:id].to_i.to_s != params[:id]
+
+ @article = Article.find_cached(params[:id])
+ @current_article_id = @article.id
+ store_location
+ # NOTE
+ # We can't use @topic.increment!(:hit) here,
+ # because updated_at is part of the cache key
+ ActiveRecord::Base.connection.execute("UPDATE articles SET hit = hit + 1 WHERE articles.id = #{@article.id}")
+
+ @title = @article.title
+ @guide = @article.cached_assoc_object(:guide)
+
+ @total_comments = @article.comments_count
+ @total_pages = (@total_comments * 1.0 / Siteconf.pagination_comments.to_i).ceil
+ @current_page = params[:p].nil? ? @total_pages : params[:p].to_i
+ @per_page = Siteconf.pagination_comments.to_i
+ @comments = @article.cached_assoc_pagination(:comments, @current_page, @per_page, 'created_at', Rabel::Model::ORDER_ASC)
+
+ @new_comment = @article.comments.new
+
+ @canonical_path = "/guides/#{@guide.id}/articles/#{params[:id]}"
+ @canonical_path += "?p=#{@current_page}" if @total_pages > 1
+ @seo_description = "#{@guide.title} - #{@guide.user.nickname} - #{@article.title}"
+
+ @prev_article = @article.prev_article(@guide)
+ @next_article = @article.next_article(@guide)
respond_to do |format|
- format.html # show.html.erb
- format.json { render json: @article }
+ format.html
+ format.mobile
end
end
@@ -48,16 +77,14 @@ def edit
# POST /articles
# POST /articles.json
def create
- @article = Article.new(params[:article])
- @article.guide_id = params[:guide_id]
+ @article = @guide.articles.new(params[:article])
+ @article.user = current_user
respond_to do |format|
if @article.save
- format.html { redirect_to @guide, notice: 'Article was successfully created.' }
- format.json { render json: @guide, status: :created, location: @article }
+ format.html { redirect_to @guide}
else
- format.html { render action: "new" }
- format.json { render json: @article.errors, status: :unprocessable_entity }
+ render :new
end
end
end
@@ -66,11 +93,10 @@ def create
# PUT /articles/1.json
def update
@article = Article.find(params[:id])
- @article.guide_id = params[:guide_id]
respond_to do |format|
if @article.update_attributes(params[:article])
- format.html { redirect_to [@guide,@article], notice: 'Article was successfully updated.' }
+ format.html { redirect_to @article, notice: 'Article was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
@@ -86,12 +112,11 @@ def destroy
@article.destroy
respond_to do |format|
- format.html { redirect_to @guide }
+ format.html { redirect_to articles_url }
format.json { head :no_content }
end
end
-
private
def get_guide
diff --git a/app/controllers/bookmarks_controller.rb b/app/controllers/bookmarks_controller.rb
new file mode 100644
index 0000000..56ea554
--- /dev/null
+++ b/app/controllers/bookmarks_controller.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+class BookmarksController < ApplicationController
+ before_filter :authenticate_user!
+ before_filter :find_bookmarkable, :only => :create
+
+ def create
+ @bookmark = @bookmarkable.bookmarks.build
+ @bookmark.user = current_user
+ flash[:error] = '收藏失败' unless @bookmark.save
+ redirect_to custom_path(@bookmarkable)
+ end
+
+ def destroy
+ @bookmark = Bookmark.find(params[:id])
+ authorize! :update, @bookmark
+ if @bookmark.destroy
+ redirect_to custom_path(@bookmark.bookmarkable)
+ else
+ flash[:error] = '取消收藏失败'
+ redirect_to root_path
+ end
+ end
+
+end
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index 6a2ed54..869cd27 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -1,5 +1,4 @@
class CategoriesController < ApplicationController
- load_and_authorize_resource
# GET /categories
# GET /categories.json
def index
@@ -15,7 +14,6 @@ def index
# GET /categories/1.json
def show
@category = Category.find(params[:id])
- @guides = @category.guides
respond_to do |format|
format.html # show.html.erb
diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb
new file mode 100644
index 0000000..cfe2b8d
--- /dev/null
+++ b/app/controllers/comments_controller.rb
@@ -0,0 +1,47 @@
+# encoding: utf-8
+class CommentsController < ApplicationController
+ before_filter :authenticate_user!
+ before_filter :find_commentable, :only => [:create]
+ before_filter :find_comment_and_auth, :only => [:edit, :update, :destroy]
+
+ def create
+ redirect_to root_path, :notice => I18n.t('tips.comments_closed') and return if @commentable.try(:comments_closed)
+ @comment = @commentable.comments.build(params[:comment])
+ @comment.user = current_user
+ @comment.posting_device = session[:posting_device] if session[:posting_device].present?
+ flash[:else] = '添加回复失败' unless @comment.save
+ redirect_to custom_path(@commentable)
+ end
+
+ def edit
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def update
+ respond_to do |format|
+ if @comment.update_attributes(params[:comment])
+ format.js
+ else
+ render :json => :error, :status => :unprocessable_entity
+ end
+ end
+ end
+
+ def destroy
+ respond_to do |format|
+ if @comment.destroy
+ format.js
+ else
+ render :json => :error, :status => :unprocessable_entity
+ end
+ end
+ end
+
+ private
+ def find_comment_and_auth
+ render :text => :error, :status => :not_found and return unless current_user.can_manage_site?
+ @comment = Comment.find(params[:id])
+ end
+end
diff --git a/app/controllers/downloads_controller.rb b/app/controllers/downloads_controller.rb
deleted file mode 100644
index 1eefa0a..0000000
--- a/app/controllers/downloads_controller.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-class DownloadsController < ApplicationController
- load_and_authorize_resource
- # GET /downloads
- # GET /downloads.json
- def index
- @downloads = Download.all
-
- respond_to do |format|
- format.html # index.html.erb
- format.json { render json: @downloads }
- end
- end
-
- # GET /downloads/1
- # GET /downloads/1.json
- def show
- @download = Download.find(params[:id])
-
- respond_to do |format|
- format.html # show.html.erb
- format.json { render json: @download }
- end
- end
-
- # GET /downloads/new
- # GET /downloads/new.json
- def new
- @download = Download.new
-
- respond_to do |format|
- format.html # new.html.erb
- format.json { render json: @download }
- end
- end
-
- # GET /downloads/1/edit
- def edit
- @download = Download.find(params[:id])
- end
-
- # POST /downloads
- # POST /downloads.json
- def create
- @download = Download.new(params[:download])
-
- respond_to do |format|
- if @download.save
- format.html { redirect_to @download, notice: 'Download was successfully created.' }
- format.json { render json: @download, status: :created, location: @download }
- else
- format.html { render action: "new" }
- format.json { render json: @download.errors, status: :unprocessable_entity }
- end
- end
- end
-
- # PUT /downloads/1
- # PUT /downloads/1.json
- def update
- @download = Download.find(params[:id])
-
- respond_to do |format|
- if @download.update_attributes(params[:download])
- format.html { redirect_to @download, notice: 'Download was successfully updated.' }
- format.json { head :no_content }
- else
- format.html { render action: "edit" }
- format.json { render json: @download.errors, status: :unprocessable_entity }
- end
- end
- end
-
- # DELETE /downloads/1
- # DELETE /downloads/1.json
- def destroy
- @download = Download.find(params[:id])
- @download.destroy
-
- respond_to do |format|
- format.html { redirect_to downloads_url }
- format.json { head :no_content }
- end
- end
-end
diff --git a/app/controllers/guides_controller.rb b/app/controllers/guides_controller.rb
index 107fc59..12d151e 100644
--- a/app/controllers/guides_controller.rb
+++ b/app/controllers/guides_controller.rb
@@ -1,29 +1,74 @@
class GuidesController < ApplicationController
- load_and_authorize_resource
+ before_filter :authenticate_user!, :except => [:show, :index]
+ before_filter :find_guide_and_auth, :only => [:edit_title,:update_title, :move, :destroy]
+ before_filter :only => [:new_from_home, :create_from_home] do |c|
+ auth_admin
+ end
+
# GET /guides
# GET /guides.json
def index
- @guides = Guide.order("created_at DESC")
- @categories = Category.all
-
respond_to do |format|
- format.html # index.html.erb
- format.json { render json: @guides }
+ format.html {
+ per_page = Siteconf::HOMEPAGE_TOPICS
+ @title = '最新学习教程'
+ if params[:page].present?
+ current_page = params[:page].to_i
+ @title += " (第 #{current_page} 页)"
+ else
+ current_page = 1
+ end
+
+ if params[:c].present?
+ @category = Category.find_cached(params[:c])
+ @guides = @category.guides.cached_pagination(current_page, per_page, 'updated_at')
+ #@guides = @category.guides.where(:publish => true).cached_pagination(current_page, per_page, 'updated_at')
+ @title = @category.title
+ else
+ @guides = Guide.cached_pagination(current_page, per_page, 'updated_at')
+ #if current_user.can_manage_site?
+ # @guides = Guide.cached_pagination(current_page, per_page, 'updated_at')
+ #else
+ # @guides = Guide.where(:publish => true).cached_pagination(current_page, per_page, 'updated_at')
+ #end
+ end
+
+ @canonical_path = guides_path
+ @canonical_path += "?page=#{current_page}" if current_page > 1
+
+ @seo_description = @title
+ }
+ format.atom {
+ @feed_items = Article.cached_all('updated_at DESC').take(Siteconf::HOMEPAGE_TOPICS)
+ @last_update = @feed_items.first.updated_at unless @feed_items.empty?
+ render :layout => false
+ }
end
end
# GET /guides/1
# GET /guides/1.json
def show
- @guide = Guide.find(params[:id])
- @articles = @guide.articles
- @first_article = @articles.first
- @next_article = @articles.first(2).last
- @category = Category.find(@guide.category_id)
+ raise ActiveRecord::RecordNotFound.new if params[:id].to_i.to_s != params[:id]
+
+ @guide = Guide.find_cached(params[:id])
+ store_location
+ # NOTE
+ # We can't use @topic.increment!(:hit) here,
+ # because updated_at is part of the cache key
+ # ActiveRecord::Base.connection.execute("UPDATE topics SET hit = hit + 1 WHERE topics.id = #{@topic.id}")
+
+ @title = @guide.title
+ @category = @guide.cached_assoc_object(:category)
+ @current_article_id = @guide.articles.first.id
+
+ @total_bookmarks = @guide.bookmarks.count
+
+ @seo_description = "#{@category.title} - #{@guide.user.nickname} - #{@guide.title}"
respond_to do |format|
- format.html # show.html.erb
- format.json { render json: @guide }
+ format.html
+ format.mobile
end
end
@@ -43,21 +88,37 @@ def edit
@guide = Guide.find(params[:id])
end
+ def new_from_home
+ @guide = Guide.new
+ end
# POST /guides
# POST /guides.json
def create
+ category_id = params[:guide].delete(:category_id)
@guide = Guide.new(params[:guide])
- @guide.user_id = current_user.id
+ @guide.category = Category.find(category_id) if category_id.present?
+ @guide.user = current_user
+ if @guide.save
+ redirect_to @guide
+ else
+ render :new
+ end
+ end
- respond_to do |format|
- if @guide.save
- @article = @guide.articles.new()
- format.html { redirect_to [@guide,@article], notice: 'Guide was successfully created.' }
- format.json { render json: @guide, status: :created, location: @guide }
+ def create_from_home
+ category_id = params[:guide].delete(:category_id)
+ @guide = Guide.new(params[:guide])
+ @guide.category = Category.find(category_id) if category_id.present?
+ @guide.user = current_user
+
+ if @guide.save
+ unless @guide.articles.any?
+ redirect_to new_guide_article_path(@guide)
else
- format.html { render action: "new" }
- format.json { render json: @guide.errors, status: :unprocessable_entity }
+ redirect_to guide_path(@guide)
end
+ else
+ render :new_from_home
end
end
@@ -65,20 +126,55 @@ def create
# PUT /guides/1.json
def update
@guide = Guide.find(params[:id])
- @guide.user_id = current_user.id
-
- respond_to do |format|
+ if params[:new_category_id].present?
+ # move to new node
+ @new_category = Category.find(params[:new_category_id])
+ respond_to do |format|
+ format.js {
+ if @new_category.present?
+ @guide.category = @new_category
+ if @guide.save
+ render :js => "window.location.reload()"
+ else
+ render :js => "$.facebox('移动教程失败')"
+ end
+ else
+ render :js => "$.facebox('分类不存在')"
+ end
+ }
+ end
+ else
if @guide.update_attributes(params[:guide])
- @article = @guide.articles.new()
- format.html { redirect_to [@guide,@article], notice: 'Guide was successfully updated.' }
- format.json { head :no_content }
+ redirect_to @guide
else
- format.html { render action: "edit" }
- format.json { render json: @guide.errors, status: :unprocessable_entity }
+ flash[:error] = '之前的更新有误,请编辑后再提交'
+ render :edit
end
end
end
+ def edit_title
+ respond_to do |f|
+ f.js
+ end
+ end
+
+ def update_title
+ respond_to do |f|
+ f.js {
+ unless @guide.update_attributes(params[:guide])
+ render :text => :error, :status => :unprocessable_entity
+ end
+ }
+ end
+ end
+
+ def move
+ respond_to do |format|
+ format.js
+ end
+ end
+
# DELETE /guides/1
# DELETE /guides/1.json
def destroy
@@ -90,4 +186,14 @@ def destroy
format.json { head :no_content }
end
end
+
+ private
+ def find_category
+ @category = Category.find(params[:category_id])
+ end
+
+ def find_guide_and_auth
+ @guide = Guide.find(params[:id])
+ authorize! :update, @guide, :message => '你没有权限管理此教程'
+ end
end
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
deleted file mode 100644
index e60f16c..0000000
--- a/app/controllers/home_controller.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class HomeController < ApplicationController
- def index
- @guides = Guide.all
- end
-end
diff --git a/app/controllers/nodes_controller.rb b/app/controllers/nodes_controller.rb
new file mode 100644
index 0000000..5d5ab4f
--- /dev/null
+++ b/app/controllers/nodes_controller.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+class NodesController < ApplicationController
+ def show
+ @node = Node.find_by_attr_cached!(:key, params[:key])
+ @title = @node.name
+
+ if params[:p].present?
+ @page_num = params[:p].to_i
+ @title += " (第 #{@page_num} 页)"
+ else
+ @page_num = 1
+ end
+
+ @total_topics = @node.topics_count
+ @total_pages = (@total_topics * 1.0 / Siteconf.pagination_topics.to_i).ceil
+ @next_page_num = (@page_num < @total_pages) ? @page_num + 1 : 0
+ @prev_page_num = (@page_num > 1) ? @page_num - 1 : 0
+ @topics = @node.cached_assoc_pagination(:topics, @page_num, Siteconf.pagination_topics.to_i, 'updated_at')
+
+ @canonical_path = "/go/#{params[:key]}"
+ @canonical_path += "?p=#{@page_num}" if @page_num > 1
+ @seo_description = "#{@node.name} - #{@node.introduction}"
+
+ respond_to do |format|
+ format.html
+ format.mobile { add_breadcrumb @node.name }
+ end
+ end
+end
diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb
new file mode 100644
index 0000000..b9e880a
--- /dev/null
+++ b/app/controllers/notifications_controller.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+class NotificationsController < ApplicationController
+ before_filter :authenticate_user!
+
+ def index
+ @notifications = current_user.notifications.where(:unread => true).order('created_at DESC').limit(100).all
+ current_user.notifications.update_all(:unread => false)
+ @unread_count = 0
+
+ @title = '提醒系统'
+
+ respond_to do |format|
+ format.html
+ format.mobile {
+ add_breadcrumb(@title)
+ @show_notification_count = false
+ }
+ end
+ end
+
+ def read
+ @notification = current_user.notifications.find(params[:id])
+ if @notification.present?
+ redirect_to @notification.notifiable.notifiable_path
+ else
+ redirect_to root_path, :error => '无法处理之前的请求'
+ end
+ end
+end
diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb
new file mode 100644
index 0000000..f718821
--- /dev/null
+++ b/app/controllers/pages_controller.rb
@@ -0,0 +1,16 @@
+class PagesController < ApplicationController
+ def show
+ if current_user && current_user.can_manage_site?
+ @page = Page.find_by_key!(params[:key])
+ else
+ @page = Page.find_by_attr_cached!(:key, params[:key], :published => true)
+ end
+ @title = @page.title
+
+ if @page.content.size > 100
+ @seo_description = @page.content.slice(0, 100) + '...'
+ else
+ @seo_description = @page.content
+ end
+ end
+end
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
deleted file mode 100644
index dc3ea51..0000000
--- a/app/controllers/products_controller.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-class ProductsController < ApplicationController
- load_and_authorize_resource
- # GET /products
- # GET /products.json
- def index
- @products = Product.all
-
- respond_to do |format|
- format.html # index.html.erb
- format.json { render json: @products }
- end
- end
-
- # GET /products/1
- # GET /products/1.json
- def show
- @product = Product.find(params[:id])
-
- respond_to do |format|
- format.html # show.html.erb
- format.json { render json: @product }
- end
- end
-
- # GET /products/new
- # GET /products/new.json
- def new
- @product = Product.new
-
- respond_to do |format|
- format.html # new.html.erb
- format.json { render json: @product }
- end
- end
-
- # GET /products/1/edit
- def edit
- @product = Product.find(params[:id])
- end
-
- # POST /products
- # POST /products.json
- def create
- @product = Product.new(params[:product])
-
- respond_to do |format|
- if @product.save
- format.html { redirect_to @product, notice: 'Product was successfully created.' }
- format.json { render json: @product, status: :created, location: @product }
- else
- format.html { render action: "new" }
- format.json { render json: @product.errors, status: :unprocessable_entity }
- end
- end
- end
-
- # PUT /products/1
- # PUT /products/1.json
- def update
- @product = Product.find(params[:id])
-
- respond_to do |format|
- if @product.update_attributes(params[:product])
- format.html { redirect_to @product, notice: 'Product was successfully updated.' }
- format.json { head :no_content }
- else
- format.html { render action: "edit" }
- format.json { render json: @product.errors, status: :unprocessable_entity }
- end
- end
- end
-
- # DELETE /products/1
- # DELETE /products/1.json
- def destroy
- @product = Product.find(params[:id])
- @product.destroy
-
- respond_to do |format|
- format.html { redirect_to products_url }
- format.json { head :no_content }
- end
- end
-end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
new file mode 100644
index 0000000..6c4bbb3
--- /dev/null
+++ b/app/controllers/registrations_controller.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+class RegistrationsController < Devise::RegistrationsController
+ def create
+ build_resource params[:user]
+
+ if resource.verify_captcha(session[:captcha]) and resource.save
+ if resource.active_for_authentication?
+ set_flash_message :notice, :signed_up if is_navigational_format?
+ sign_in(resource_name, resource)
+ respond_with resource, :location => after_sign_up_path_for(resource)
+ else
+ set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format?
+ expire_session_data_after_sign_in!
+ respond_with resource, :location => after_inactive_sign_up_path_for(resource)
+ end
+ else
+ clean_up_passwords resource
+ respond_with resource
+ end
+ end
+end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
new file mode 100644
index 0000000..9464dfa
--- /dev/null
+++ b/app/controllers/sessions_controller.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+class SessionsController < Devise::SessionsController
+ def new
+ @title = '登入'
+ @seo_description = @title
+
+ super
+ end
+
+ def create
+ old_session = session.dup
+ resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
+ sign_in(resource_name, resource)
+ reset_session
+ session.reverse_merge!(old_session)
+ if mobile_device?
+ redirect_to root_path
+ else
+ respond_with resource, :location => after_sign_in_path_for(resource)
+ end
+ end
+end
diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb
new file mode 100644
index 0000000..b9dd7b8
--- /dev/null
+++ b/app/controllers/topics_controller.rb
@@ -0,0 +1,204 @@
+# encoding: utf-8
+class TopicsController < ApplicationController
+ before_filter :authenticate_user!, :except => [:show, :index]
+ before_filter :find_node, :except => [:show, :index, :preview, :toggle_comments_closed, :toggle_sticky, :new_from_home, :create_from_home]
+ before_filter :find_topic_and_auth, :only => [:edit_title,:update_title,
+ :edit, :update, :move, :destroy]
+ before_filter :only => [:toggle_comments_closed, :toggle_sticky] do |c|
+ auth_admin
+ end
+
+ def index
+ respond_to do |format|
+ format.html {
+ per_page = Siteconf::HOMEPAGE_TOPICS
+ @title = '全站最新话题更改记录'
+ if params[:page].present?
+ current_page = params[:page].to_i
+ @title += " (第 #{current_page} 页)"
+ else
+ current_page = 1
+ end
+
+ @topics = Topic.cached_pagination(current_page, per_page, 'updated_at')
+
+ @canonical_path = topics_path
+ @canonical_path += "?page=#{current_page}" if current_page > 1
+
+ @seo_description = @title
+ }
+ format.atom {
+ @feed_items = Topic.recent_topics(Siteconf::HOMEPAGE_TOPICS)
+ @last_update = @feed_items.first.updated_at unless @feed_items.empty?
+ render :layout => false
+ }
+ end
+ end
+
+ def show
+ raise ActiveRecord::RecordNotFound.new if params[:id].to_i.to_s != params[:id]
+
+ @topic = Topic.find_cached(params[:id])
+ store_location
+ # NOTE
+ # We can't use @topic.increment!(:hit) here,
+ # because updated_at is part of the cache key
+ ActiveRecord::Base.connection.execute("UPDATE topics SET hit = hit + 1 WHERE topics.id = #{@topic.id}")
+
+ @title = @topic.title
+ @node = @topic.cached_assoc_object(:node)
+
+ @total_comments = @topic.comments_count
+ @total_pages = (@total_comments * 1.0 / Siteconf.pagination_comments.to_i).ceil
+ @current_page = params[:p].nil? ? @total_pages : params[:p].to_i
+ @per_page = Siteconf.pagination_comments.to_i
+ @comments = @topic.cached_assoc_pagination(:comments, @current_page, @per_page, 'created_at', Rabel::Model::ORDER_ASC)
+
+ @new_comment = @topic.comments.new
+ @total_bookmarks = @topic.bookmarks.count
+
+ @canonical_path = "/t/#{params[:id]}"
+ @canonical_path += "?p=#{@current_page}" if @total_pages > 1
+ @seo_description = "#{@node.name} - #{@topic.user.nickname} - #{@topic.title}"
+
+ @prev_topic = @topic.prev_topic(@node)
+ @next_topic = @topic.next_topic(@node)
+
+ respond_to do |format|
+ format.html
+ format.mobile
+ end
+ end
+
+ def new
+ @topic = @node.topics.new
+
+ respond_to do |format|
+ format.html
+ format.mobile
+ end
+ end
+
+ def new_from_home
+ @topic = Topic.new
+ end
+
+ def create
+ @topic = @node.topics.new(params[:topic], :as => current_user.permission_role)
+ @topic.user = current_user
+ if @topic.save
+ redirect_to t_path(@topic.id)
+ else
+ render :new
+ end
+ end
+
+ def create_from_home
+ node_id = params[:topic].delete(:node_id)
+ @topic = Topic.new(params[:topic], :as => current_user.permission_role)
+ @topic.node = Node.find(node_id) if node_id.present?
+ @topic.user = current_user
+
+ if @topic.save
+ redirect_to t_path(@topic.id)
+ else
+ render :new_from_home
+ end
+ end
+
+ def edit_title
+ respond_to do |f|
+ f.js
+ end
+ end
+
+ def update_title
+ respond_to do |f|
+ f.js {
+ unless @topic.update_attributes(params[:topic])
+ render :text => :error, :status => :unprocessable_entity
+ end
+ }
+ end
+ end
+
+ def edit
+ end
+
+ def update
+ if params[:new_node_id].present?
+ # move to new node
+ @new_node = Node.find(params[:new_node_id])
+ respond_to do |format|
+ format.js {
+ if @new_node.present?
+ @topic.node = @new_node
+ if @topic.save
+ render :js => "window.location.reload()"
+ else
+ render :js => "$.facebox('移动帖子失败')"
+ end
+ else
+ render :js => "$.facebox('节点不存在')"
+ end
+ }
+ end
+ else
+ if @topic.update_attributes(params[:topic], :as => current_user.permission_role)
+ redirect_to t_path(@topic.id)
+ else
+ flash[:error] = '之前的更新有误,请编辑后再提交'
+ render :edit
+ end
+ end
+ end
+
+ def move
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def destroy
+ if @topic.destroy
+ redirect_to root_path, :notice => '帖子删除成功'
+ else
+ raise RuntimeError.new('删除帖子出错')
+ end
+ end
+
+ def preview
+ @type = ['topic', 'comment', 'page'].delete params[:type]
+ respond_to do |f|
+ if @type.present?
+ f.text { @content = params[:content] }
+ else
+ render :text => :error, :status => :unprocessable_entity
+ end
+ end
+ end
+
+ def toggle_comments_closed
+ @topic = Topic.find(params[:topic_id])
+ @topic.toggle!(:comments_closed)
+ @topic.touch
+ redirect_to t_path(@topic.id)
+ end
+
+ def toggle_sticky
+ @topic = Topic.find(params[:topic_id])
+ @topic.toggle!(:sticky)
+ @topic.touch
+ redirect_to t_path(@topic.id)
+ end
+
+ private
+ def find_node
+ @node = Node.find(params[:node_id])
+ end
+
+ def find_topic_and_auth
+ @topic = @node.topics.find(params[:id])
+ authorize! :update, @topic, :message => '你没有权限管理此主题'
+ end
+end
diff --git a/app/controllers/upyun_images_controller.rb b/app/controllers/upyun_images_controller.rb
new file mode 100644
index 0000000..498dee1
--- /dev/null
+++ b/app/controllers/upyun_images_controller.rb
@@ -0,0 +1,31 @@
+class UpyunImagesController < ApplicationController
+ before_filter :authenticate_user!
+
+ def create
+ redirect_to root_path if Figaro.env.RABEL_UPYUN_SWITCH != 'on'
+
+ respond_to do |f|
+ f.json {
+ result = []
+
+ params[:upyun_image][:asset].each do |file|
+ img = UpyunImage.new(:asset => file)
+ img.filename = file.original_filename
+ img.user = current_user
+ if img.save
+ result << {
+ :name => file.original_filename,
+ :size => img.size,
+ :url => img.asset.url,
+ :thumbnail_url => img.asset.url,
+ :delete_url => upyun_image_path(img),
+ :delete_type => 'DELETE'
+ }
+ end
+ end
+
+ render :json => result
+ }
+ end
+ end
+end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 536002e..a8853fb 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,33 +1,149 @@
+# encoding: utf-8
class UsersController < ApplicationController
- before_filter :authenticate_user!
+ before_filter :authenticate_user!, :except => [:index, :show, :topics, :my_topics]
+ layout "app", :except => [:index]
def index
- authorize! :index, @user, :message => 'Not authorized as an administrator.'
- @users = User.all
+ @hot_users = User.order("comments_count DESC, topics_count DESC").limit(50)
+ @new_users = User.order("created_at DESC ").limit(50)
+ @title = "最活跃及最新会员"
+ @canonical_path = "/users"
+ @seo_description = "#{@title} - #{Siteconf.site_name}"
+ render layout: "base"
end
-
def show
- @user = User.find(params[:id])
- end
-
- def update
- authorize! :update, @user, :message => 'Not authorized as an administrator.'
- @user = User.find(params[:id])
- if @user.update_attributes(params[:user], :as => :admin)
- redirect_to users_path, :notice => "User updated."
+ @user = User.find_by_attr_cached!(:nickname, params[:nickname])
+ store_location
+
+ @title = @user.nickname
+ @canonical_path = "/member/#{@title}"
+
+ @signature = @user.account.signature
+ @weibo_link = cannonical_url(@user.account.weibo_link)
+ @personal_website = cannonical_url(@user.account.personal_website)
+ @location = @user.account.location
+ @introduction = @user.account.introduction
+
+ @nickname_tip = (@user == current_user) ? '我' : @user.nickname
+ @seo_description = "#{@user.nickname} - #{@signature}"
+
+ respond_to do |format|
+ format.html
+ format.mobile { add_breadcrumb @user.nickname }
+ end
+ end
+
+ def guides
+ @user = User.find_by_attr_cached!(:nickname, params[:nickname])
+
+ @current_page = params[:page].present? ? params[:page] : 1
+ @guides = @user.cached_assoc_pagination(:guides, @current_page, 20, 'created_at')
+
+ @title = "#{@user.nickname} 创建的所有学习教程"
+ @seo_description = "#{@title} - #{Siteconf.site_name}"
+ end
+ def topics
+ @user = User.find_by_attr_cached!(:nickname, params[:nickname])
+
+ @current_page = params[:page].present? ? params[:page] : 1
+ @topics = @user.cached_assoc_pagination(:topics, @current_page, 20, 'created_at')
+
+ @title = "#{@user.nickname} 创建的所有主题"
+ @seo_description = "#{@title} - #{Siteconf.site_name}"
+ end
+
+ def edit
+ @user = current_user
+ @user.build_account unless @user.account.present?
+ @title = '设置'
+
+ respond_to do |format|
+ format.html
+ format.mobile
+ end
+ end
+
+ def update_account
+ @user = current_user
+ if @user.update_without_password(params[:user])
+ flash[:success] = '个人设置成功更新'
+ sign_in :user, current_user, :bypass => true
+ redirect_to settings_path
+ else
+ flash[:error] = '个人设置保存失败'
+ render :edit
+ end
+ end
+
+ def update_password
+ @user = current_user
+ if @user.update_with_password(params[:user])
+ flash[:success] = '密码已成功更新,下次请用新密码登录'
+ sign_in :user, current_user, :bypass => true
+ redirect_to settings_path
+ else
+ flash[:error] = '密码更新失败'
+ render :edit
+ end
+ end
+
+ def update_avatar
+ @user = current_user
+ if params[:user].present?
+ if @user.update_without_password(params[:user])
+ flash[:success] = '头像更新成功'
+ redirect_to settings_path + '#avatar'
+ else
+ flash[:error] = '头像更新失败'
+ render :edit
+ end
else
- redirect_to users_path, :alert => "Unable to update user."
+ redirect_to settings_path + '#avatar', :notice => '请选择要上传的头像'
end
end
-
- def destroy
- authorize! :destroy, @user, :message => 'Not authorized as an administrator.'
- user = User.find(params[:id])
- unless user == current_user
- user.destroy
- redirect_to users_path, :notice => "User deleted."
+
+ def my_topics
+ if params[:nickname].present?
+ @user = User.find_by_attr_cached!(:nickname, params[:nickname])
+ @my_topics = @user.bookmarked_topics
+ @my_guides = @user.bookmarked_guides
+ @title = @user.nickname + ' 的收藏'
else
- redirect_to users_path, :notice => "Can't delete yourself."
+ @my_topics = current_user.bookmarked_topics
+ @my_guides = current_user.bookmarked_guides
+ @title = '我的收藏'
+ end
+
+ respond_to do |format|
+ format.html
+ format.mobile { add_breadcrumb(@title) }
+ end
+ end
+
+ def my_following
+ @my_followed_users = current_user.followed_users
+ @followed_topic_timeline = current_user.followed_topic_timeline
+ @title = '我的特别关注'
+
+ respond_to do |format|
+ format.html
+ format.mobile { add_breadcrumb(@title) }
+ end
+ end
+
+ def follow
+ @followed_user = User.find_by_nickname!(params[:nickname])
+ unless current_user.following?(@followed_user)
+ flash[:error] = '加入特别关注失败' unless current_user.follow(@followed_user)
+ end
+ redirect_to member_path(params[:nickname])
+ end
+
+ def unfollow
+ @followed_user = User.find_by_nickname!(params[:nickname])
+ if current_user.following?(@followed_user)
+ flash[:error] = '取消特别关注失败' unless current_user.unfollow(@followed_user)
end
+ redirect_to member_path(params[:nickname])
end
-end
\ No newline at end of file
+end
diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb
new file mode 100644
index 0000000..e271e53
--- /dev/null
+++ b/app/controllers/welcome_controller.rb
@@ -0,0 +1,59 @@
+# encoding: utf-8
+class WelcomeController < ApplicationController
+
+ layout 'welcome'
+
+ def index
+ @topics = Topic.home_topics(Siteconf::HOMEPAGE_TOPICS)
+ @guides = Guide.cached_all('updated_at DESC').take(15)
+ @sticky_topics = Topic.sticky_topics
+ @canonical_path = '/'
+ @full_title = site_intro
+ @seo_description = Siteconf.seo_description
+
+ respond_to do |format|
+ format.html
+ format.mobile { @planes = Plane.all }
+ end
+ end
+
+ def goodbye
+ @title = '登出'
+
+ respond_to do |format|
+ format.html
+ format.mobile { add_breadcrumb @title }
+ end
+ end
+
+ def captcha
+ head :ok and return unless Siteconf.show_captcha?
+
+ respond_to do |format|
+ format.gif {
+ session[:captcha] = Rabel::Captcha.random_code
+ send_data Rabel::Captcha.image(session[:captcha]), :type => 'image/gif', :disposition => 'ineline'
+ }
+ end
+ end
+
+ def sitemap
+ respond_to do |f|
+ f.xml {
+ max = 50000
+ @lastmod = [
+ Topic.order('updated_at DESC').first.try(:updated_at),
+ Guide.order('updated_at DESC').first.try(:updated_at),
+ Article.order('updated_at DESC').first.try(:updated_at),
+ Page.only_published.order('updated_at DESC').first.try(:updated_at),
+ Comment.order('updated_at DESC').first.try(:updated_at),
+ ].compact.max
+
+ @pages = Page.only_published.all
+ @topics = Topic.select('id, comments_count, updated_at').order('created_at DESC').limit(max - @pages.size - 1 - 11000)
+ @guides = Guide.select('id, updated_at').order('created_at DESC').limit(1000)
+ @articles = Article.select('id,comments_count,guide_id, updated_at').order('created_at DESC').limit(10000)
+ }
+ end
+ end
+end
diff --git a/app/helpers/admin/base_helper.rb b/app/helpers/admin/base_helper.rb
new file mode 100644
index 0000000..c8b62a5
--- /dev/null
+++ b/app/helpers/admin/base_helper.rb
@@ -0,0 +1,53 @@
+# encoding: utf-8
+module Admin::BaseHelper
+ def prepare_resource(resource)
+ r = [:admin]
+ if resource.is_a? Array
+ r += resource
+ else
+ r << resource
+ end
+ r
+ end
+
+ def admin_create_button(text, resource, options={})
+ default_option = {:class => 'btn btn-sm btn-info'}
+ link_to text, new_polymorphic_path(prepare_resource(resource)), default_option.merge(options)
+ end
+
+ def admin_edit_button(text, resource, options={})
+ default_option = {:class => 'btn btn-sm'}
+ link_to text, edit_polymorphic_path(prepare_resource(resource)), default_option.merge(options)
+ end
+
+ def admin_delete_button(resource, options={})
+ default_option = {:class => 'btn btn-sm btn-danger', :method => :delete, :data => {:confirm => '真的要删除吗?'}}
+ link_to '删除', prepare_resource(resource), default_option.merge(options)
+ end
+
+ def dashboard_menu
+ [
+ {
+ :name => '社区管理',
+ :items => [
+ ['运行状态', 'dashboard', admin_root_path],
+ ['基本设置', 'settings', admin_site_settings_path],
+ ['外观', 'palette', admin_appearance_path],
+ ['用户', 'users', admin_users_path],
+ ['位面节点', 'nodes', admin_planes_path],
+ ['教程分类', 'cagegories', admin_categories_path],
+ ['学习教程', 'guides', admin_guides_path],
+ ['讨论话题', 'topics', admin_topics_path],
+ ['页面', 'pages', admin_pages_path],
+ ['广告位', 'ads', admin_advertisements_path],
+ ['文件上传', 'cloud', admin_cloud_files_path],
+ ['奖励记录', 'reward_history', admin_rewards_path],
+ ]
+ }
+ ]
+ end
+
+ def page_publish_status(page)
+ content_tag(:span, page.published? ? '已发布' : '草稿', :class => page.published? ? 'published' : 'draft')
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 58b4588..e8b934e 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,25 +1,221 @@
+# encoding: utf-8
module ApplicationHelper
+ def site_intro
+ append_notification_count(Siteconf.site_name + ' - ' + Siteconf.short_intro)
+ end
+
+ def append_notification_count(title)
+ return title if @unread_count == 0
+ title + " (#{@unread_count})"
+ end
+
+ def title
+ return @full_title if @full_title.present?
+ add_title_item @title if @title.present?
+ @title_items.join(' - ')
+ end
+
+ def add_title_item(item)
+ @title_items.unshift item unless request.format.to_sym == :js
+ end
+
+ def build_navigation(items, class_name='gray')
+ items.unshift(link_to(Siteconf.site_name, root_path, :class => :rabel))
+ items.join(' › ').html_safe
+ end
+
+ def add_breadcrumb(item)
+ @breadcrumbs << item
+ end
+
+ def build_breadcrumbs
+ result_html =
+ case @breadcrumbs.length
+ when 1
+ ''
+ else
+ @breadcrumbs.join(' › ')
+ end
+ result_html.html_safe
+ end
+
+ def build_admin_navigation(items, class_name='fade')
+ items.unshift(link_to('管理后台', admin_root_path))
+ build_navigation(items, class_name)
+ end
+
+ def edit_topic_navigation(node, topic)
+ build_navigation([
+ link_to(node.name, go_path(node.key)),
+ link_to(topic.title, t_path(topic.id)),
+ '编辑'
+ ], 'bigger')
+ end
+
+ def format_page(page_content)
+ if Siteconf.allow_markdown_in_pages?
+ parse_markdown(page_content)
+ else
+ format_content(page_content)
+ end
+ end
+
+ def format_topic(topic_content)
+ if Siteconf.allow_markdown_in_topics?
+ parse_markdown(topic_content)
+ else
+ format_content(topic_content)
+ end
+ end
+
+ def format_comment(comment_content)
+ if Siteconf.allow_markdown_in_comments?
+ parse_markdown(comment_content)
+ else
+ format_content(comment_content)
+ end
+ end
+
+ def format_content(text)
+ begin
+ text = Rabel::LinkEmailParser.parse_url(Rabel::Base.h(text)) do |link|
+ Rabel::Base.smart_url(link)
+ end
+ text = Rabel::LinkEmailParser.parse_email(text) do |address|
+ Rabel::Base.protect_at_symbol(address)
+ end
+
+ nl_to_br(Rabel::Base.decode_symbols(Rabel::Base.make_mention_links(text))).html_safe
+ rescue
+ h(text)
+ end
+ end
+
+ def nl_to_br(text)
+ text.gsub("\r\n", " ").gsub("\r", " ").gsub("\n", " ")
+ end
+
+ def parse_markdown(text)
+ begin
+ nl_to_br(Rabel::Base.decode_symbols(
+ Rabel::Base.make_mention_links(
+ MarkdownConverter.convert(text)
+ )
+ )).html_safe
+ rescue Exception => e
+ h(text)
+ end
+ end
+
+ def flash_messages
+ @flash_messages ||= flash.select {|type, message| message.length > 0}
+ end
+
+ def show_flash_messages
+ result = []
+ flash_messages.each do |type, message|
+ result << content_tag(:span, message, :class => "#{type}-message")
+ end
+ result.join(' ').html_safe
+ end
+
+ def show_mobile_messages
+ if flash_messages.any?
+ content_tag(:div, show_flash_messages, :class => :cell)
+ end
+ end
- def display_base_errors resource
- return '' if (resource.errors.empty?) or (resource.errors[:base].empty?)
- messages = resource.errors[:base].map { |msg| content_tag(:p, msg) }.join
- html = <<-HTML
-
- ×
- #{messages}
-
- HTML
- html.html_safe
- end
-
- def markdown(text)
- options = {
- :autolink => true,
- :space_after_headers => true,
- :no_intra_emphasis => true
+ def search_engines
+ {
+ :google => 'http://www.google.com.hk/search?q=',
+ :bing => 'http://cn.bing.com/search?q=',
+ :baidu => 'http://www.baidu.com/s?wd=',
+ :wenLu => 'http://www.wen.lu/?gws_rd=cr#q='
}
- markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, options)
- markdown.render(text).html_safe
end
+ def search_engine_url
+ search_engines[Siteconf.default_search_engine.to_sym]
+ end
+
+ def large_avatar(user)
+ image_tag user.avatar.url, :class => :large_avatar, :alt => "#{user.nickname} large avatar"
+ end
+
+ def medium_avatar(user)
+ image_tag user.avatar.url(:medium), :class => :medium_avatar, :alt => "#{user.nickname} medium avatar"
+ end
+
+ def mini_avatar(user)
+ image_tag user.avatar.url(:mini), :class => :mini_avatar, :alt => "#{user.nickname} mini avatar"
+ end
+
+ def hash_key_append(hash, key, value)
+ if hash[key].present?
+ hash[key] = "#{hash[key]} #{value}"
+ else
+ hash[key] = value
+ end
+ end
+
+ def nickname_profile_link(nickname, options={})
+ options[:title] = nickname
+ hash_key_append(options, :class, 'rabel profile_link')
+
+ link_to nickname, member_path(url_encode(nickname)), options
+ end
+
+ def user_profile_link(user, options={})
+ nickname_profile_link(user.nickname, options)
+ end
+
+ def user_profile_avatar_link(user, avatar_size, options={})
+ avatar_method = "#{avatar_size}_avatar"
+
+ options[:title] = user.nickname
+ hash_key_append(options, :class, 'profile_link')
+
+ link_to(member_path(url_encode(user.nickname)), options) { send(avatar_method, user) }
+ end
+
+ def page_real_url(page)
+ if page.content.start_with?('http')
+ page.content
+ elsif page.content.start_with?('/')
+ page.content
+ else
+ page_path(page.key)
+ end
+ end
+
+ def show_posting_device(comment)
+ content_tag(:span, " via #{comment.posting_device}".html_safe, class: :snow) if comment.posting_device.present?
+ end
+
+ def auth_admin(error_tip='tips.no_permission')
+ redirect_to root_path, :notice => t(error_tip) unless current_user.can_manage_site?
+ end
+
+ def cannonical_url(url)
+ return url if url.length == 0
+ url.start_with?('http://') ? url : 'http://' + url
+ end
+
+ def weibo_icon_for(weibo_link)
+ if weibo_link.include?('weibo.com')
+ 'sina_weibo'
+ elsif weibo_link.include?('t.qq.com')
+ 'tx_weibo'
+ else
+ 'twitter'
+ end
+ end
+
+ def proper_length(str, len)
+ if str.size > len
+ str[0..len] + ' ...'
+ else
+ str
+ end
+ end
end
diff --git a/app/helpers/articles_helper.rb b/app/helpers/articles_helper.rb
deleted file mode 100644
index 2968277..0000000
--- a/app/helpers/articles_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module ArticlesHelper
-end
diff --git a/app/helpers/bootstrap_helper.rb b/app/helpers/bootstrap_helper.rb
new file mode 100644
index 0000000..a01ba66
--- /dev/null
+++ b/app/helpers/bootstrap_helper.rb
@@ -0,0 +1,10 @@
+module BootstrapHelper
+ def current_nav(item)
+ @current_nav_item = item
+ end
+
+ def nav_item(title, path, options={})
+ css_class = (title == @current_nav_item) ? 'active' : ''
+ content_tag(:li, link_to(title, path, options), :class => css_class)
+ end
+end
diff --git a/app/helpers/categories_helper.rb b/app/helpers/categories_helper.rb
deleted file mode 100644
index e06f315..0000000
--- a/app/helpers/categories_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module CategoriesHelper
-end
diff --git a/app/helpers/downloads_helper.rb b/app/helpers/downloads_helper.rb
deleted file mode 100644
index c8cd007..0000000
--- a/app/helpers/downloads_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module DownloadsHelper
-end
diff --git a/app/helpers/guides_helper.rb b/app/helpers/guides_helper.rb
deleted file mode 100644
index 446f38a..0000000
--- a/app/helpers/guides_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module GuidesHelper
-end
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
deleted file mode 100644
index 23de56a..0000000
--- a/app/helpers/home_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module HomeHelper
-end
diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb
deleted file mode 100644
index ab5c42b..0000000
--- a/app/helpers/products_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module ProductsHelper
-end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 1352b85..b2b6412 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -2,12 +2,28 @@ class Ability
include CanCan::Ability
def initialize(user)
- user ||= User.new # guest user (not logged in)
- if user.has_role? :admin
- can :manage, :all
- else
- can :read, :all
+ user ||= User.new
+
+ can :update, Topic do |topic|
+ topic.allow_modification_by?(user)
+ end
+
+ can :update, Guide do |guide|
+ guide.allow_modification_by?(user)
+ end
+
+ can :update, Article do |article|
+ article.allow_modification_by?(user)
end
+
+ can :update, Bookmark do |bookmark|
+ bookmark.user == user
+ end
+
+ can :edit_info, User do |target_user|
+ user.root? or (user.can_manage_site? and (not target_user.root?))
+ end
+
# Define abilities for the passed in user here. For example:
#
# user ||= User.new # guest user (not logged in)
diff --git a/app/models/account.rb b/app/models/account.rb
new file mode 100644
index 0000000..90029dc
--- /dev/null
+++ b/app/models/account.rb
@@ -0,0 +1,20 @@
+class Account < ActiveRecord::Base
+ belongs_to :user
+
+ BASE_FIELDS = [:personal_website, :location, :signature, :introduction, :weibo_link]
+ attr_accessible *BASE_FIELDS
+ attr_accessible *BASE_FIELDS, :as => :admin
+
+ validates :signature, :length => {:maximum => 20}
+
+ before_create :set_default_value
+
+ private
+ def set_default_value
+ self.personal_website ||= ''
+ self.location ||= ''
+ self.signature ||= ''
+ self.introduction ||= ''
+ end
+
+end
diff --git a/app/models/advertisement.rb b/app/models/advertisement.rb
new file mode 100644
index 0000000..afee4b5
--- /dev/null
+++ b/app/models/advertisement.rb
@@ -0,0 +1,34 @@
+class Advertisement < ActiveRecord::Base
+ validates :link, :banner, :title, :words, :start_date, :expire_date, :duration, :presence => true
+ validates :duration, :numericality => {:only_integer => true, :less_than => 3650, :greater_than => 1}
+
+ attr_accessible :link, :banner, :title, :words, :start_date, :duration
+ mount_uploader :banner, AdBannerUploader
+ before_validation :set_expire_date
+
+ def self.available
+ num = available_condition.count
+ return Rabel::Model::EMPTY_DATASET if num == 0
+ ts = select('updated_at').order('updated_at DESC').first.try(:updated_at)
+ Rails.cache.fetch("#{self.model_name.collection}/available/#{num}-#{ts}") do
+ available_condition.order('start_date DESC').all
+ end
+ end
+
+ def self.available_condition
+ today = Time.zone.today
+ where(["start_date <= ? AND expire_date >= ?", today, today])
+ end
+
+ def showing?
+ today = Time.zone.today
+ today >= self.start_date and today <= self.expire_date
+ end
+
+ private
+ def set_expire_date
+ if self.start_date.present? and self.duration.present?
+ self.expire_date = self.start_date + self.duration.days
+ end
+ end
+end
diff --git a/app/models/article.rb b/app/models/article.rb
index b0df18f..07998d3 100644
--- a/app/models/article.rb
+++ b/app/models/article.rb
@@ -1,5 +1,88 @@
+# encoding: utf-8
class Article < ActiveRecord::Base
- attr_accessible :content, :guide_id, :title
+ include Notifiable
+ include Rabel::ActiveCache
+ DEFAULT_HIT = 0
+ default_value_for :hit, DEFAULT_HIT
+ default_value_for :content, ''
+ default_value_for :involved_at do
+ Time.zone.now
+ end
+
+ attr_accessible :title, :content, :complete, :guide_id
+ attr_accessible :title, :content, :complete, :guide_id, :comments_closed, :as => :admin
belongs_to :guide
+ belongs_to :user
+ has_many :comments, :as => :commentable, :dependent => :destroy
+ has_many :notifications, :as => :notifiable, :dependent => :destroy
+
+ validates :guide_id, :user_id, :title, :presence => true
+
+ after_create :send_notifications
+ after_save :update_guide
+
+ def last_comment
+ self.comments.order('created_at ASC').last
+ end
+
+ def allow_modification_by?(user)
+ user.can_manage_site?
+ end
+
+ def notifiable_title
+ self.guide.title + " - " + title
+ end
+
+ def notifiable_path
+ "/guides/#{self.guide.id}/articles/#{id}"
+ end
+
+ def self.latest_involved_topics(num)
+ order('involved_at DESC').limit(num).all
+ end
+
+ def self.recent_topics(num)
+ ts = select('updated_at').order('updated_at DESC').first.try(:updated_at)
+ return Rabel::Model::EMPTY_DATASET unless ts.present?
+ Rails.cache.fetch("topics/recent/#{self.count}/#{num}-#{ts}") do
+ order('involved_at DESC').limit(num).all
+ end
+ end
+
+ def mention_check_text
+ self.title + self.content
+ end
+
+ def mentioned_users
+ mentioned_names = self.mention_check_text.scan(Notifiable::MENTION_REGEXP).collect {|matched| matched.first}.uniq
+ mentioned_names.delete(self.user.nickname)
+ mentioned_names.map { |name| User.find_by_nickname(name) }.compact
+ end
+
+ def prev_article(guide)
+ guide.articles.where(['id < ?', self.id]).order('created_at DESC').first
+ end
+
+ def next_article(guide)
+ guide.articles.where(['id > ?', self.id]).order('created_at ASC').first
+ end
+
+ private
+
+ def send_notifications
+ mentioned_users.each do |user|
+ Notification.notify(
+ user,
+ self,
+ self.user,
+ Notification::ACTION_TOPIC,
+ self.content
+ )
+ end
+ end
+
+ def update_guide
+ self.guide.touch
+ end
end
diff --git a/app/models/bookmark.rb b/app/models/bookmark.rb
new file mode 100644
index 0000000..ef26695
--- /dev/null
+++ b/app/models/bookmark.rb
@@ -0,0 +1,7 @@
+class Bookmark < ActiveRecord::Base
+ validates :user_id, :bookmarkable_type, :bookmarkable_id, :presence => true
+ attr_protected :user_id, :bookmarkable_type, :bookmarkable_id
+
+ belongs_to :user
+ belongs_to :bookmarkable, :polymorphic => true
+end
diff --git a/app/models/category.rb b/app/models/category.rb
index bf2acd6..ca155d1 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -1,5 +1,12 @@
+# encoding: utf-8
class Category < ActiveRecord::Base
- attr_accessible :title
+ include Rabel::ActiveCache
+ attr_accessible :title, :presence => true
+
+ has_many :guides, :dependent => :destroy
+
+ def can_delete?
+ self.guides.count == 0
+ end
- has_many :guides
end
diff --git a/app/models/cloud_file.rb b/app/models/cloud_file.rb
new file mode 100644
index 0000000..7773c08
--- /dev/null
+++ b/app/models/cloud_file.rb
@@ -0,0 +1,14 @@
+class CloudFile < ActiveRecord::Base
+ attr_accessible :name, :asset
+
+ mount_uploader :asset, CloudFileUploader
+
+ validates :name, :asset, :presence => true
+
+ before_create :save_metadata
+ private
+ def save_metadata
+ self.content_type = asset.file.content_type
+ self.file_size = asset.file.size
+ end
+end
diff --git a/app/models/comment.rb b/app/models/comment.rb
new file mode 100644
index 0000000..1d85547
--- /dev/null
+++ b/app/models/comment.rb
@@ -0,0 +1,77 @@
+class Comment < ActiveRecord::Base
+ include Rabel::ActiveCache
+
+ belongs_to :user
+ belongs_to :commentable, :polymorphic => true, :counter_cache => true
+
+ validates :user_id, :commentable_id, :commentable_type, :content, :presence => true
+ attr_accessible :content
+
+ after_create :touch_parent_model
+ after_create :send_notifications
+ after_destroy :update_last_reply
+
+ def mentioned_users
+ mentioned_names = self.content.scan(Notifiable::MENTION_REGEXP).collect {|matched| matched.first}.uniq
+ mentioned_names.delete(self.user.nickname)
+ mentioned_names.delete(self.commentable.user.nickname)
+ mentioned_names.map { |name| User.find_by_nickname(name) }.compact
+ end
+
+ private
+ def touch_parent_model
+ created_date = commentable.created_at.to_date
+ if commentable.has_attribute?(:last_replied_by) and commentable.has_attribute?(:last_replied_at)
+ commentable.last_replied_by = self.user.nickname
+ commentable.last_replied_at = self.created_at
+ commentable.save
+ end
+
+ if commentable.has_attribute?(:involved_at)
+ commentable.touch(:involved_at) if created_date.months_since(6) > Time.zone.today
+ else
+ commentable.touch
+ end
+ end
+
+ def send_notifications
+ # send notification to commentable owner
+ # unless the comment was created by the same owner
+ send_notification_to(
+ self.commentable.user,
+ Notification::ACTION_REPLY) unless self.user == self.commentable.user
+ send_notification_to_mentioned_users
+ end
+
+ def send_notification_to(user, action)
+ Notification.notify(
+ user,
+ self.commentable,
+ self.user,
+ action,
+ self.content
+ )
+ end
+
+ def send_notification_to_mentioned_users
+ mentioned_users.each do |user|
+ send_notification_to(user, Notification::ACTION_MENTION)
+ end
+ end
+
+ def update_last_reply
+ if commentable.has_attribute?(:last_replied_by) and commentable.has_attribute?(:last_replied_at)
+ if commentable.comments_count == 0
+ commentable.last_replied_by = ''
+ commentable.last_replied_at = ''
+ else
+ last_comment = commentable.try(:last_comment)
+ if last_comment.present?
+ commentable.last_replied_by = last_comment.user.nickname
+ commentable.last_replied_at = last_comment.created_at
+ end
+ end
+ commentable.save
+ end
+ end
+end
diff --git a/app/models/download.rb b/app/models/download.rb
deleted file mode 100644
index f4f0ec1..0000000
--- a/app/models/download.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Download < ActiveRecord::Base
- attr_accessible :description, :title
-end
diff --git a/app/models/following.rb b/app/models/following.rb
new file mode 100644
index 0000000..8646cb5
--- /dev/null
+++ b/app/models/following.rb
@@ -0,0 +1,9 @@
+# FIXME
+# Three indexes were added: :user_id, :followed_user_id and [:user_id, :followed_user_id]
+# Need to know if all indexes are needed
+class Following < ActiveRecord::Base
+ attr_accessible :followed_user_id
+
+ belongs_to :follower, :class_name => 'User', :foreign_key => 'user_id'
+ belongs_to :followed_user, :class_name => 'User'
+end
diff --git a/app/models/guide.rb b/app/models/guide.rb
index 0431b9e..0630898 100644
--- a/app/models/guide.rb
+++ b/app/models/guide.rb
@@ -1,8 +1,53 @@
+# encoding: utf-8
class Guide < ActiveRecord::Base
- attr_accessible :category_id, :img, :overview, :subtitle, :title, :user_id
+ include Notifiable
+ include Rabel::ActiveCache
+ attr_accessible :category_id, :feature_id, :img, :overview, :publish, :subtitle, :title, :user_id
belongs_to :user
belongs_to :category
- has_many :articles
+ has_many :articles, :dependent => :destroy
+ has_many :notifications, :as => :notifiable, :dependent => :destroy
+ has_many :bookmarks, :as => :bookmarkable, :dependent => :destroy
+
+ validates :category_id, :user_id, :title, :presence => true
+
+ after_create :send_notifications
+
+ def notifiable_title
+ title
+ end
+
+ def notifiable_path
+ "/guides/#{id}"
+ end
+
+ def mention_check_text
+ self.title + self.overview
+ end
+
+ def mentioned_users
+ mentioned_names = self.mention_check_text.scan(Notifiable::MENTION_REGEXP).collect {|matched| matched.first}.uniq
+ mentioned_names.delete(self.user.nickname)
+ mentioned_names.map { |name| User.find_by_nickname(name) }.compact
+ end
+
+ def allow_modification_by?(user)
+ (self.user == user) || user.can_manage_site?
+ end
+
+ private
+
+ def send_notifications
+ mentioned_users.each do |user|
+ Notification.notify(
+ user,
+ self,
+ self.user,
+ Notification::ACTION_TOPIC,
+ self.overview
+ )
+ end
+ end
end
diff --git a/app/models/node.rb b/app/models/node.rb
new file mode 100644
index 0000000..a043184
--- /dev/null
+++ b/app/models/node.rb
@@ -0,0 +1,24 @@
+# encoding: utf-8
+class Node < ActiveRecord::Base
+ include Sortable
+ include Rabel::ActiveCache
+
+ has_many :topics
+ has_many :bookmarks, :as => :bookmarkable, :dependent => :destroy
+ belongs_to :plane, :touch => true
+
+ validates :name, :plane_id, :key, :presence => true
+ validates :key, :uniqueness => true, :format => {:with => /[a-zA-Z0-9_-]+/, :message => I18n.t('tips.node_key_format')}
+ validate :node_key_should_not_contain_slash
+
+ attr_accessible :plane_id, :name, :key, :custom_css, :custom_html, :introduction, :position, :quiet
+
+ def can_delete?
+ self.topics_count == 0
+ end
+
+ private
+ def node_key_should_not_contain_slash
+ errors.add(:key, "不能包含斜线(/)") if self.key.present? and self.key.include?('/')
+ end
+end
diff --git a/app/models/notifiable.rb b/app/models/notifiable.rb
new file mode 100644
index 0000000..1b60a68
--- /dev/null
+++ b/app/models/notifiable.rb
@@ -0,0 +1,16 @@
+module Notifiable
+ MENTION_REGEXP = /@([a-zA-Z0-9_\-\p{han}]+)/u
+
+ def notifiable_title
+ default_implementation
+ end
+
+ def notifiable_path
+ default_implementation
+ end
+
+ private
+ def default_implementation
+ raise "Notifiable callback was not implemented."
+ end
+end
diff --git a/app/models/notification.rb b/app/models/notification.rb
new file mode 100644
index 0000000..2585f3e
--- /dev/null
+++ b/app/models/notification.rb
@@ -0,0 +1,44 @@
+# encoding: utf-8
+class Notification < ActiveRecord::Base
+ ACTION_MENTION = 'mention'
+ ACTION_REPLY = 'reply'
+ ACTION_TOPIC = 'topic'
+ ACTION_REWARD = 'reward'
+
+ belongs_to :user
+ belongs_to :action_user, :class_name => 'User'
+ belongs_to :notifiable, :polymorphic => true
+
+ attr_accessible :content, :action
+
+ # Notify user
+ def self.notify(user, notifiable, action_user, action, content)
+ nf = Notification.new(:action => action, :content => content)
+ nf.notifiable = notifiable
+ nf.user = user
+ nf.action_user = action_user
+ nf.save
+ end
+
+ def action_info_prefix
+ case self.action
+ when ACTION_MENTION
+ '在回复'
+ when ACTION_REPLY
+ '在'
+ when ACTION_TOPIC
+ '在话题'
+ end
+ end
+
+ def action_info_suffix
+ case self.action
+ when ACTION_MENTION
+ '时提到'
+ when ACTION_REPLY
+ '里回复'
+ when ACTION_TOPIC
+ '中提到'
+ end
+ end
+end
diff --git a/app/models/page.rb b/app/models/page.rb
new file mode 100644
index 0000000..176b5d1
--- /dev/null
+++ b/app/models/page.rb
@@ -0,0 +1,14 @@
+# encoding: utf-8
+class Page < ActiveRecord::Base
+ include Sortable
+ include Rabel::ActiveCache
+
+ acts_as_list
+
+ validates :key, :title, :content, :presence => true
+ attr_accessible :key, :title, :content, :published, :position
+
+ def self.only_published
+ where(:published => true)
+ end
+end
diff --git a/app/models/plane.rb b/app/models/plane.rb
new file mode 100644
index 0000000..e07d195
--- /dev/null
+++ b/app/models/plane.rb
@@ -0,0 +1,13 @@
+class Plane < ActiveRecord::Base
+ include Rabel::ActiveCache
+ include Sortable
+
+ validates :name, :presence => true
+ has_many :nodes, :order => Node.default_order_str
+
+ attr_accessible :name, :position
+
+ def can_delete?
+ self.nodes.count == 0
+ end
+end
diff --git a/app/models/product.rb b/app/models/product.rb
deleted file mode 100644
index 12baedd..0000000
--- a/app/models/product.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Product < ActiveRecord::Base
- attr_accessible :descrption, :img, :name
-end
diff --git a/app/models/reward.rb b/app/models/reward.rb
new file mode 100644
index 0000000..89aa691
--- /dev/null
+++ b/app/models/reward.rb
@@ -0,0 +1,56 @@
+# encoding: utf-8
+class Reward < ActiveRecord::Base
+ TYPE_GRANT = 'grant'
+ TYPE_REVOKE = 'revoke'
+
+ attr_accessor :amount_str, :reward_type
+ attr_accessible :reason, :amount, :amount_str, :reward_type
+
+ validates :user_id, :admin_user_id, :amount, :reason, :presence => true
+ validates :reward_type, :amount_str, :presence => true, :on => :create
+ validates :reward_type, :inclusion => { :in => [TYPE_GRANT, TYPE_REVOKE] }, :on => :create
+
+ validate :amount_rules, :on => :create
+
+ belongs_to :user
+ belongs_to :admin_user, :class_name => 'User'
+
+ before_validation :set_amount
+ before_create :set_balance
+ after_create :notify_user
+
+ private
+ def set_amount
+ if self.reward_type == TYPE_GRANT
+ self.amount = self.amount_str.to_i.abs
+ else
+ self.amount = self.amount_str.to_i.abs * -1
+ end
+ end
+
+ def set_balance
+ self.balance = self.user.reward + self.amount
+ end
+
+ def notify_user
+ Notification.notify(
+ self.user,
+ self,
+ self.admin_user,
+ Notification::ACTION_REWARD,
+ self.reason
+ )
+ end
+
+ def amount_rules
+ if self.amount.present?
+ if self.amount == 0
+ errors.add(:amount_str, "不能为零")
+ end
+
+ if self.reward_type == TYPE_REVOKE and self.amount.abs > self.user.reward
+ errors.add(:amount_str, "扣除金额不能超过可用余额")
+ end
+ end
+ end
+end
diff --git a/app/models/role.rb b/app/models/role.rb
deleted file mode 100644
index 145baa7..0000000
--- a/app/models/role.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class Role < ActiveRecord::Base
- has_and_belongs_to_many :users, :join_table => :users_roles
- belongs_to :resource, :polymorphic => true
-
- scopify
-end
diff --git a/app/models/siteconf.rb b/app/models/siteconf.rb
new file mode 100644
index 0000000..f30bcfc
--- /dev/null
+++ b/app/models/siteconf.rb
@@ -0,0 +1,57 @@
+class Siteconf < RailsSettings::CachedSettings
+ def self.boolean_attributes(*args)
+ args.each do |m|
+ self.instance_eval <<-CODE
+ def #{m}?
+ Siteconf.send(:#{m}) == 'on'
+ end
+ CODE
+ end
+ end
+
+ HOMEPAGE_TOPICS = 15
+ attr_accessible :var
+
+ boolean_attributes :show_captcha, :show_community_stats,
+ :allow_markdown_in_topics,
+ :allow_markdown_in_comments,
+ :allow_markdown_in_pages
+
+ class << self
+ def seo_keywords_str
+ self.seo_keywords.join(',')
+ end
+
+ def seo_keywords_str=(str)
+ self.seo_keywords = str.split(',')
+ end
+
+ def marketing_str
+ self.marketing.join(',') rescue ''
+ end
+
+ def marketing_str=(str)
+ self.marketing = str.split(',')
+ end
+
+ def nav_position_top?
+ self.nav_position == 'top'
+ end
+
+ def nav_position_sidebar?
+ self.nav_position == 'sidebar'
+ end
+
+ def nav_position_bottom?
+ self.nav_position == 'bottom'
+ end
+
+ def topic_editable_period
+ self.topic_editable_period_str.to_i.minutes
+ end
+
+ def simple_topic_list_style?
+ self.topic_list_style == 'simple'
+ end
+ end
+end
diff --git a/app/models/sortable.rb b/app/models/sortable.rb
new file mode 100644
index 0000000..0da86b8
--- /dev/null
+++ b/app/models/sortable.rb
@@ -0,0 +1,23 @@
+module Sortable
+ def self.included(base)
+ base.class_eval do
+ extend ClassMethods
+
+ after_create :set_default_position
+ private
+ def set_default_position
+ self.update_column(:position, id)
+ end
+ end
+ end
+
+ module ClassMethods
+ def default_order
+ order(default_order_str)
+ end
+
+ def default_order_str
+ 'position ASC'
+ end
+ end
+end
diff --git a/app/models/topic.rb b/app/models/topic.rb
new file mode 100644
index 0000000..c71bb1c
--- /dev/null
+++ b/app/models/topic.rb
@@ -0,0 +1,116 @@
+class Topic < ActiveRecord::Base
+ include Notifiable
+ include Rabel::ActiveCache
+
+ DEFAULT_HIT = 0
+ default_value_for :hit, DEFAULT_HIT
+ default_value_for :content, ''
+ default_value_for :involved_at do
+ Time.zone.now
+ end
+
+ belongs_to :node, :touch => true, :counter_cache => true
+ belongs_to :user
+ has_many :comments, :as => :commentable, :dependent => :destroy
+ has_many :bookmarks, :as => :bookmarkable, :dependent => :destroy
+ has_many :notifications, :as => :notifiable, :dependent => :destroy
+
+ validates :node_id, :user_id, :title, :presence => true
+
+ attr_accessible :title, :content
+ attr_accessible :title, :content, :comments_closed, :sticky, :as => :admin
+
+ after_create :send_notifications
+
+ def last_comment
+ self.comments.order('created_at ASC').last
+ end
+
+ def locked?
+ Time.now - self.created_at > Siteconf.topic_editable_period
+ end
+
+ def allow_modification_by?(user)
+ (!locked? && self.user == user) || user.can_manage_site?
+ end
+
+ def notifiable_title
+ title
+ end
+
+ def notifiable_path
+ "/t/#{id}"
+ end
+
+ def self.sticky_topics
+ ts = select('updated_at').with_sticky(true).order('updated_at DESC').first.try(:updated_at)
+ return Rabel::Model::EMPTY_DATASET unless ts.present?
+ count = with_sticky(true).count
+ Rails.cache.fetch("topics/sticky/#{ts}-#{count}") do
+ with_sticky(true).order('updated_at DESC').all
+ end
+ end
+
+ def self.home_topics(num)
+ ts = select('updated_at').order('updated_at DESC').first.try(:updated_at)
+ return Rabel::Model::EMPTY_DATASET unless ts.present?
+ node_ts = Node.select('updated_at').order('updated_at DESC').first.try(:updated_at)
+ Rails.cache.fetch("topics/homepage/#{self.count}/#{num}-#{ts}/#{node_ts}") do
+ excluded_nodes = Node.where(:quiet => true).pluck(:id)
+ if excluded_nodes.any?
+ where("node_id NOT in (?)", excluded_nodes).with_sticky(false).latest_involved_topics(num)
+ else
+ with_sticky(false).latest_involved_topics(num)
+ end
+ end
+ end
+
+ def self.with_sticky(sticky)
+ where(:sticky => sticky)
+ end
+
+ def self.latest_involved_topics(num)
+ order('involved_at DESC').limit(num).all
+ end
+
+ def self.recent_topics(num)
+ ts = select('updated_at').order('updated_at DESC').first.try(:updated_at)
+ return Rabel::Model::EMPTY_DATASET unless ts.present?
+ Rails.cache.fetch("topics/recent/#{self.count}/#{num}-#{ts}") do
+ order('involved_at DESC').limit(num).all
+ end
+ end
+
+ def mention_check_text
+ self.title + self.content
+ end
+
+ def mentioned_users
+ mentioned_names = self.mention_check_text.scan(Notifiable::MENTION_REGEXP).collect {|matched| matched.first}.uniq
+ mentioned_names.delete(self.user.nickname)
+ mentioned_names.map { |name| User.find_by_nickname(name) }.compact
+ end
+
+ def prev_topic(node)
+ node.topics.where(['id < ?', self.id]).order('created_at DESC').first
+ end
+
+ def next_topic(node)
+ node.topics.where(['id > ?', self.id]).order('created_at ASC').first
+ end
+
+ private
+
+ def send_notifications
+ mentioned_users.each do |user|
+ Notification.notify(
+ user,
+ self,
+ self.user,
+ Notification::ACTION_TOPIC,
+ self.content
+ )
+ end
+ end
+
+end
diff --git a/app/models/upyun_image.rb b/app/models/upyun_image.rb
new file mode 100644
index 0000000..f360404
--- /dev/null
+++ b/app/models/upyun_image.rb
@@ -0,0 +1,19 @@
+class UpyunImage < ActiveRecord::Base
+ belongs_to :user
+
+ attr_accessible :asset
+
+ mount_uploader :asset, UpyunImageUploader
+
+ validates :asset, :content_type, :size, :filename, :presence => true
+ validates_integrity_of :asset
+
+ before_validation :set_metadata
+
+ private
+ def set_metadata
+ self.content_type = asset.file.content_type
+ self.size = asset.file.size
+ end
+
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index fab53fb..14e17fc 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,14 +1,205 @@
+# encoding:utf-8
+require 'carrierwave/orm/activerecord'
+
class User < ActiveRecord::Base
- rolify
+ include Rabel::ActiveCache
# Include default devise modules. Others available are:
- # :token_authenticatable, :confirmable,
- # :lockable, :timeoutable and :omniauthable
+ # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
-
# Setup accessible (or protected) attributes for your model
- attr_accessible :role_ids, :as => :admin
- attr_accessible :name, :email, :password, :password_confirmation, :remember_me
+ attr_accessor :captcha
+
+ BASE_FIELDS = [:nickname, :email, :password, :password_confirmation,
+ :remember_me, :avatar, :account_attributes, :captcha
+ ]
+
+ attr_accessible *BASE_FIELDS
+ attr_accessible *(BASE_FIELDS + [:reward]), :as => :admin
+
+ mount_uploader :avatar, AvatarUploader
+
+ validates :nickname, :presence => true, :uniqueness => true, :length => {:maximum => 12}
+ validate :nickname_cannot_contain_invalid_characters
+
+ has_one :account, :dependent => :destroy
+ accepts_nested_attributes_for :account
+
+ has_many :topics, :dependent => :destroy
+ has_many :comments, :dependent => :destroy
+ has_many :bookmarks, :dependent => :destroy
+ has_many :notifications, :dependent => :destroy
+ has_many :rewards
+
+ has_many :guides, :dependent => :destroy
+ has_many :articles
+
+ has_many :follower_relationships, :class_name => 'Following', :foreign_key => 'followed_user_id', :dependent => :destroy
+ has_many :followed_relationships, :class_name => 'Following', :foreign_key => 'user_id', :dependent => :destroy
+
+ has_many :followers, :through => :follower_relationships
+ has_many :followed_users, :through => :followed_relationships
+
+ before_create :create_acount
+
+ def latest_created_topics
+ self.topics.order('created_at DESC').limit(10)
+ end
+
+ def latest_created_guides
+ self.guides.order('created_at DESC').limit(10)
+ end
+
+ def latest_comments
+ self.comments.order('created_at DESC').limit(10)
+ end
+
+ def bookmarked?(bookmarkable)
+ bookmarkable.bookmarks.where(:user_id => self.id).exists?
+ end
+
+ def bookmark_of(bookmarkable)
+ bookmarkable.bookmarks.where(:user_id => self.id).first
+ end
+
+ def bookmarked_nodes_count
+ self.bookmarks.where(:bookmarkable_type => 'Node').count
+ end
+
+ def bookmarked_nodes
+ ids = self.bookmarks.select(:bookmarkable_id).where(:bookmarkable_type => 'Node').collect(&:bookmarkable_id)
+ Node.find(ids)
+ end
+
+ def bookmarked_topics_count
+ self.bookmarks.where(:bookmarkable_type => 'Topic').count
+ end
+
+ def bookmarked_guides_count
+ self.bookmarks.where(:bookmarkable_type => 'Guide').count
+ end
+
+ def bookmarked_guides
+ ids = self.bookmarks.select(:bookmarkable_id).where(:bookmarkable_type => 'Guide').collect(&:bookmarkable_id)
+ Guide.find(ids)
+ end
+
+ def bookmarked_topics
+ ids = self.bookmarks.select(:bookmarkable_id).where(:bookmarkable_type => 'Topic').collect(&:bookmarkable_id)
+ Topic.find(ids)
+ end
+
+ def recent_followers
+ follower_ids = self.follower_relationships.order('created_at DESC').limit(10).pluck(:user_id)
+ follower_ids.map { |uid| User.find_cached(uid) }
+ end
- has_many :guides
+ def follow(user)
+ self.followed_users << user and true
+ end
+
+ def unfollow(user)
+ user.followers.delete(self) and true
+ end
+
+ def following?(user)
+ self.followed_relationships.where(:followed_user_id => user.id).exists?
+ end
+
+ def followed_by?(user)
+ self.follower_relationships.where(:user_id => user.id).exists?
+ end
+
+ def follower_count
+ @follower_count ||= self.follower_relationships.count
+ end
+
+ def followed_user_count
+ @followed_user_count ||= self.followed_relationships.count
+ end
+
+ def follower_ids
+ @follower_ids ||= self.follower_relationships.collect(&:user_id)
+ end
+
+ def followed_user_ids
+ @followed_user_ids ||= self.followed_relationships.collect(&:followed_user_id)
+ end
+
+ def followed_topic_timeline
+ # FIXME: cache this result
+ Topic.where(:user_id => self.followed_user_ids).order('created_at DESC').limit(10)
+ end
+
+ def root?
+ self.id == 1 || self.role == 'root'
+ end
+
+ def permission_role
+ can_manage_site? ? :admin : :default
+ end
+
+ def admin?
+ self.role == 'admin'
+ end
+
+ def acts_as_admin
+ self.role = 'admin'
+ end
+
+ def acts_as_normal_user
+ self.role = 'user'
+ end
+
+ def can_manage_site?
+ root? || admin?
+ end
+
+ def unread_notification_count
+ self.notifications.where(:unread => true).count
+ end
+
+ def active_for_authentication?
+ super && not_blocked?
+ end
+
+ def not_blocked?
+ not self.blocked?
+ end
+
+ def inactive_message
+ not_blocked? ? super : :blocked
+ end
+
+ def verify_captcha(correct_captcha)
+ return true unless Siteconf.show_captcha?
+ if self.captcha.downcase == correct_captcha.downcase
+ true
+ else
+ self.errors.add(:captcha, "验证码不正确")
+ false
+ end
+ end
+
+ def has_avatar?
+ self.read_attribute(:avatar).present?
+ end
+
+ private
+ def create_acount
+ self.build_account if self.account.nil?
+ end
+
+ def nickname_cannot_contain_invalid_characters
+ if self.nickname.present? and (self.nickname.include?('@') or
+ self.nickname.include?('-') or
+ self.nickname.include?(' ') or
+ self.nickname.include?('.') or
+ self.nickname.include?('/') or
+ self.nickname.include?('\\')
+ )
+ errors.add(:nickname, "不能包含@, 横线, 斜线, 句点或空格")
+ end
+ end
end
+
diff --git a/app/uploaders/ad_banner_uploader.rb b/app/uploaders/ad_banner_uploader.rb
new file mode 100644
index 0000000..e254084
--- /dev/null
+++ b/app/uploaders/ad_banner_uploader.rb
@@ -0,0 +1,53 @@
+# encoding: utf-8
+
+class AdBannerUploader < CarrierWave::Uploader::Base
+ include UploaderHelper
+ include PictureExtensionWhiteList
+ include CarrierWave::MimeTypes
+ process :set_content_type
+
+ # Include RMagick or MiniMagick support:
+ include CarrierWave::RMagick
+ # include CarrierWave::MiniMagick
+
+ # Choose what kind of storage to use for this uploader:
+ storage :file
+ # storage :fog
+
+ # Override the directory where uploaded files will be stored.
+ # This is a sensible default for uploaders that are meant to be mounted:
+ #def store_dir
+ # "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ #end
+
+ # Provide a default URL as a default if there hasn't been a file uploaded:
+ def default_url
+ "/banner/default.gif"
+ end
+
+ # Process files as they are uploaded:
+ process :resize_to_fit => [120, 90]
+ # process :scale => [200, 300]
+ #
+ # def scale(width, height)
+ # # do something
+ # end
+
+ # Create different versions of your uploaded files:
+ # version :thumb do
+ # process :scale => [50, 50]
+ # end
+
+ # Add a white list of extensions which are allowed to be uploaded.
+ # For images you might use something like this:
+ # def extension_white_list
+ # %w(jpg jpeg gif png)
+ # end
+
+ # Override the filename of the uploaded files:
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
+ # def filename
+ # "something.jpg" if original_filename
+ # end
+
+end
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
new file mode 100644
index 0000000..1c57f2d
--- /dev/null
+++ b/app/uploaders/avatar_uploader.rb
@@ -0,0 +1,58 @@
+# encoding: utf-8
+class AvatarUploader < CarrierWave::Uploader::Base
+ include UploaderHelper
+ include PictureExtensionWhiteList
+ include CarrierWave::MimeTypes
+ process :set_content_type
+
+ # Include RMagick or MiniMagick support:
+ include CarrierWave::RMagick
+ # include CarrierWave::MiniMagick
+
+ # Choose what kind of storage to use for this uploader:
+ storage :file
+ # storage :fog
+
+ # Override the directory where uploaded files will be stored.
+ # This is a sensible default for uploaders that are meant to be mounted:
+ # def store_dir
+ # "uploads/#{model.class.to_s.underscore}_#{mounted_as}/#{model.id % 100}/#{model.id % 1000}"
+ # end
+
+
+ # Provide a default URL as a default if there hasn't been a file uploaded:
+ def default_url
+ "/avatar/" + [version_name, "default.png"].compact.join('_')
+ end
+
+ # Process files as they are uploaded:
+ process :resize_to_fit => [72, 72]
+ #
+ # def scale(width, height)
+ # # do something
+ # end
+
+ # Create different versions of your uploaded files:
+ version :medium do
+ process :resize_to_fit => [48, 48]
+ end
+
+ version :mini do
+ process :resize_to_fit => [24, 24]
+ end
+
+ # Add a white list of extensions which are allowed to be uploaded.
+ # For images you might use something like this:
+ # def extension_white_list
+ # %w(jpg jpeg gif png)
+ # end
+
+ # Override the filename of the uploaded files:
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
+ # def filename
+ # if original_filename.present?
+ # hashed_name = Digest::MD5.hexdigest(original_filename)[5..10]
+ # "#{hashed_name}.#{file.extension}"
+ # end
+ # end
+end
diff --git a/app/uploaders/cloud_file_uploader.rb b/app/uploaders/cloud_file_uploader.rb
new file mode 100644
index 0000000..2ee2cad
--- /dev/null
+++ b/app/uploaders/cloud_file_uploader.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+
+class CloudFileUploader < CarrierWave::Uploader::Base
+ include UploaderHelper
+
+ # Include RMagick or MiniMagick support:
+ # include CarrierWave::RMagick
+ # include CarrierWave::MiniMagick
+
+ # Choose what kind of storage to use for this uploader:
+ storage :file
+ # storage :fog
+
+ # Override the directory where uploaded files will be stored.
+ # This is a sensible default for uploaders that are meant to be mounted:
+
+ # Provide a default URL as a default if there hasn't been a file uploaded:
+ # def default_url
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
+ # end
+
+ # Process files as they are uploaded:
+ # process :scale => [200, 300]
+ #
+ # def scale(width, height)
+ # # do something
+ # end
+
+ # Create different versions of your uploaded files:
+ # version :thumb do
+ # process :scale => [50, 50]
+ # end
+
+ # Add a white list of extensions which are allowed to be uploaded.
+ # For images you might use something like this:
+ # def extension_white_list
+ # %w(jpg jpeg gif png)
+ # end
+
+ # Override the filename of the uploaded files:
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
+ # def filename
+ # "something.jpg" if original_filename
+ # end
+
+end
diff --git a/app/uploaders/picture_extension_white_list.rb b/app/uploaders/picture_extension_white_list.rb
new file mode 100644
index 0000000..7fbf9c0
--- /dev/null
+++ b/app/uploaders/picture_extension_white_list.rb
@@ -0,0 +1,5 @@
+module PictureExtensionWhiteList
+ def extension_white_list
+ %w(jpg jpeg gif png)
+ end
+end
diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb
new file mode 100644
index 0000000..445b3e6
--- /dev/null
+++ b/app/uploaders/uploader_helper.rb
@@ -0,0 +1,19 @@
+require 'digest'
+require 'carrierwave/processing/mime_types'
+
+module UploaderHelper
+ def store_dir
+ "uploads/#{model.class.to_s.underscore}_#{mounted_as}/#{model.id % 100}/#{model.id % 1000}"
+ end
+
+ def cache_dir
+ "/tmp"
+ end
+
+ def filename
+ if original_filename.present?
+ hashed_name = Digest::MD5.hexdigest(File.dirname(current_path))[5..15]
+ "#{hashed_name}.#{file.extension}"
+ end
+ end
+end
diff --git a/app/uploaders/upyun_image_uploader.rb b/app/uploaders/upyun_image_uploader.rb
new file mode 100644
index 0000000..164b294
--- /dev/null
+++ b/app/uploaders/upyun_image_uploader.rb
@@ -0,0 +1,65 @@
+# encoding: utf-8
+
+class UpyunImageUploader < CarrierWave::Uploader::Base
+ include PictureExtensionWhiteList
+ include UploaderHelper
+
+ # Include RMagick or MiniMagick support:
+ include CarrierWave::RMagick
+ # include CarrierWave::MiniMagick
+
+ # Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
+ # include Sprockets::Helpers::RailsHelper
+ # include Sprockets::Helpers::IsolatedHelper
+
+ # Choose what kind of storage to use for this uploader:
+ # storage :file
+ # storage :fog
+ storage :upyun
+
+ self.upyun_username = Figaro.env.RABEL_UPYUN_OP_NAME
+ self.upyun_password = Figaro.env.RABEL_UPYUN_OP_PASSWORD
+ self.upyun_bucket = Figaro.env.RABEL_UPYUN_BUCKET
+ self.upyun_bucket_domain = Figaro.env.RABEL_UPYUN_BUCKET_DOMAIN
+
+ process :resize_to_limit => [570, 0]
+
+ # Override the directory where uploaded files will be stored.
+ # This is a sensible default for uploaders that are meant to be mounted:
+ # def store_dir
+ # "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ # end
+
+ # Provide a default URL as a default if there hasn't been a file uploaded:
+ # def default_url
+ # # For Rails 3.1+ asset pipeline compatibility:
+ # # asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
+ #
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
+ # end
+
+ # Process files as they are uploaded:
+ # process :scale => [200, 300]
+ #
+ # def scale(width, height)
+ # # do something
+ # end
+
+ # Create different versions of your uploaded files:
+ # version :thumb do
+ # process :scale => [50, 50]
+ # end
+
+ # Add a white list of extensions which are allowed to be uploaded.
+ # For images you might use something like this:
+ # def extension_white_list
+ # %w(jpg jpeg gif png)
+ # end
+
+ # Override the filename of the uploaded files:
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
+ # def filename
+ # "something.jpg" if original_filename
+ # end
+
+end
diff --git a/app/views/admin/advertisements/_advertisement.html.haml b/app/views/admin/advertisements/_advertisement.html.haml
new file mode 100644
index 0000000..27a0c78
--- /dev/null
+++ b/app/views/admin/advertisements/_advertisement.html.haml
@@ -0,0 +1,34 @@
+.box.advertisement.span5
+ .cell
+ .pull-right
+ = admin_edit_button('修改', advertisement)
+ = advertisement.title
+ = render 'shared/ad', :ad => advertisement
+ .cell
+ %table.table.table-bordered
+ %tr
+ %td
+ 开始日期
+ %td
+ = advertisement.start_date
+ %tr
+ %td
+ 结束日期
+ %td
+ = advertisement.expire_date
+ %tr
+ %td
+ 持续时间
+ %td
+ = advertisement.duration
+ %span.gray 天
+ .clear-fix
+ .inner
+ .pull-right
+ = admin_delete_button(advertisement)
+ - if advertisement.showing?
+ .label.label-success 展示中
+ - elsif advertisement.expire_date < today
+ .label.label-danger 已过期
+ - else
+ .label.label-info 已预约
diff --git a/app/views/admin/advertisements/_form.html.haml b/app/views/admin/advertisements/_form.html.haml
new file mode 100644
index 0000000..27d5a7e
--- /dev/null
+++ b/app/views/admin/advertisements/_form.html.haml
@@ -0,0 +1,14 @@
+= simple_form_for [:admin, ad], :html => {:multipart => true} do |f|
+ = f.input :title, :label => '广告标题'
+ = f.input :words, :as => :text, :label => '广告语', :input_html => {:rows => 2}
+ = f.input :link, :label => '广告链接'
+ .control-group
+ .controls
+ = image_tag f.object.banner.url
+ = f.input :banner, :label => '宣传图片'
+ = f.input :start_date, :label => '开始日期'
+ = f.input :duration, :label => '持续时间 (天)', :input_html => {:class => 'input-sm'}
+
+ .form-actions
+ = f.submit '保存', :class => 'btn btn-sm btn-primary'
+
diff --git a/app/views/admin/advertisements/edit.html.haml b/app/views/admin/advertisements/edit.html.haml
new file mode 100644
index 0000000..0d1b24d
--- /dev/null
+++ b/app/views/admin/advertisements/edit.html.haml
@@ -0,0 +1,5 @@
+.box
+ .cell
+ = build_admin_navigation([link_to('广告位', admin_advertisements_path), @title])
+ .inner
+ = render 'form', :ad => @ad
diff --git a/app/views/admin/advertisements/index.html.haml b/app/views/admin/advertisements/index.html.haml
new file mode 100644
index 0000000..015eba5
--- /dev/null
+++ b/app/views/admin/advertisements/index.html.haml
@@ -0,0 +1,11 @@
+.box
+ .inner
+ .fr
+ = admin_create_button('添加新广告', :advertisement)
+ = build_admin_navigation([@title])
+
+.row
+ = render @ads, :today => Time.zone.today
+
+= paginate @ads
+
diff --git a/app/views/admin/advertisements/new.html.haml b/app/views/admin/advertisements/new.html.haml
new file mode 100644
index 0000000..0d1b24d
--- /dev/null
+++ b/app/views/admin/advertisements/new.html.haml
@@ -0,0 +1,5 @@
+.box
+ .cell
+ = build_admin_navigation([link_to('广告位', admin_advertisements_path), @title])
+ .inner
+ = render 'form', :ad => @ad
diff --git a/app/views/admin/categories/_category.html.haml b/app/views/admin/categories/_category.html.haml
new file mode 100644
index 0000000..8b3f46f
--- /dev/null
+++ b/app/views/admin/categories/_category.html.haml
@@ -0,0 +1,10 @@
+.box.plane{:id => category.html_id}
+ .cell
+ .fr
+ = admin_edit_button('修改分类', category, :remote => true, :class => 'dark')
+ %span.gray /
+ - if category.can_delete?
+ = admin_delete_button(category, :remote => true)
+ = link_to category.title, "#{guides_url}?c=#{category.id}"
+// = render plane.nodes.default_order
+.sep10
diff --git a/app/views/admin/categories/_form.html.haml b/app/views/admin/categories/_form.html.haml
new file mode 100644
index 0000000..96d2498
--- /dev/null
+++ b/app/views/admin/categories/_form.html.haml
@@ -0,0 +1,6 @@
+= simple_form_for [:admin, category], :remote => true do |f|
+ %legend= @title
+ = f.input :title, :label => '名称', :input_html => {:autofocus => true}
+ .form-actions
+ = f.submit '保存', :class => 'btn btn-small btn-primary'
+
diff --git a/app/views/admin/categories/_sort_plane.html.haml b/app/views/admin/categories/_sort_plane.html.haml
new file mode 100644
index 0000000..634d3b6
--- /dev/null
+++ b/app/views/admin/categories/_sort_plane.html.haml
@@ -0,0 +1 @@
+.sort_item{:id => plane.html_id}= plane.name
diff --git a/app/views/admin/categories/_sort_planes.html.haml b/app/views/admin/categories/_sort_planes.html.haml
new file mode 100644
index 0000000..f1727a8
--- /dev/null
+++ b/app/views/admin/categories/_sort_planes.html.haml
@@ -0,0 +1,4 @@
+%strong 位面拖动排序
+= render :partial => 'sort_plane', :collection => planes, :as => :plane, :formats => :html
+.sort_actions.center
+ = link_to '完成排序', 'javascript:window.location.reload()', :class => 'btn btn-small btn-primary'
diff --git a/app/views/admin/categories/create.js.haml b/app/views/admin/categories/create.js.haml
new file mode 100644
index 0000000..f7895c6
--- /dev/null
+++ b/app/views/admin/categories/create.js.haml
@@ -0,0 +1,2 @@
+$("#planes").append("#{escape_javascript render(@category)}")
+$.facebox.close()
diff --git a/app/views/admin/categories/destroy.js.haml b/app/views/admin/categories/destroy.js.haml
new file mode 100644
index 0000000..1c9ca3d
--- /dev/null
+++ b/app/views/admin/categories/destroy.js.haml
@@ -0,0 +1 @@
+$("##{@category.html_id}").hide()
diff --git a/app/views/admin/categories/index.html.haml b/app/views/admin/categories/index.html.haml
new file mode 100644
index 0000000..f3e6e02
--- /dev/null
+++ b/app/views/admin/categories/index.html.haml
@@ -0,0 +1,13 @@
+- content_for :template_js do
+ :plain
+ rabel.sortable(".plane", "#{sort_admin_nodes_path}")
+
+.box
+ .inner
+ .fr
+ .btn-group
+ = admin_create_button('添加分类', :category, :remote => true)
+ = build_admin_navigation([@title])
+
+#planes
+ = render @categories
diff --git a/app/views/admin/categories/show_form.js.haml b/app/views/admin/categories/show_form.js.haml
new file mode 100644
index 0000000..bdd69d7
--- /dev/null
+++ b/app/views/admin/categories/show_form.js.haml
@@ -0,0 +1 @@
+$.facebox("#{escape_javascript(render('form', :category => @category))}");
diff --git a/app/views/admin/categories/sort.js.haml b/app/views/admin/categories/sort.js.haml
new file mode 100644
index 0000000..21eed78
--- /dev/null
+++ b/app/views/admin/categories/sort.js.haml
@@ -0,0 +1,2 @@
+$.facebox("#{escape_javascript render('sort_planes', :planes => @planes, :formats => :html)}");
+rabel.sortable("#facebox .content", "#{sort_admin_planes_path}")
diff --git a/app/views/admin/cloud_files/_cloud_file.html.haml b/app/views/admin/cloud_files/_cloud_file.html.haml
new file mode 100644
index 0000000..e7fe5d5
--- /dev/null
+++ b/app/views/admin/cloud_files/_cloud_file.html.haml
@@ -0,0 +1,10 @@
+%tr.highlight
+ %td= cloud_file.name
+ %td
+ .input-append
+ = text_field_tag '', cloud_file.asset.url, :class => :span4
+ = link_to '查看', cloud_file.asset.url, :target => :_blank, :class => :btn
+ %td= number_to_human_size(cloud_file.file_size)
+ %td= cloud_file.content_type
+ %td
+ = admin_delete_button(cloud_file)
diff --git a/app/views/admin/cloud_files/_form.html.haml b/app/views/admin/cloud_files/_form.html.haml
new file mode 100644
index 0000000..3c56a5c
--- /dev/null
+++ b/app/views/admin/cloud_files/_form.html.haml
@@ -0,0 +1,5 @@
+= simple_form_for [:admin, @file], :multipart => true do |f|
+ = f.input :asset, :label => '选择要上传的文件'
+ = f.input :name, :label => '简要描述'
+ .form-actions
+ = f.submit '上传', :class => 'btn btn-sm btn-primary'
diff --git a/app/views/admin/cloud_files/index.html.haml b/app/views/admin/cloud_files/index.html.haml
new file mode 100644
index 0000000..5bc730f
--- /dev/null
+++ b/app/views/admin/cloud_files/index.html.haml
@@ -0,0 +1,18 @@
+.box
+ .cell
+ .fr
+ = admin_create_button('上传文件', :cloud_file)
+ = build_admin_navigation([@title])
+ .cell
+ %table.table
+ %thead
+ %tr
+ %th 简要描述
+ %th 访问路径
+ %th 大小
+ %th 文件类型
+ %th 操作
+ %tbody
+ = render @files
+ .inner
+ = paginate @files
diff --git a/app/views/admin/cloud_files/new.html.haml b/app/views/admin/cloud_files/new.html.haml
new file mode 100644
index 0000000..927bb0a
--- /dev/null
+++ b/app/views/admin/cloud_files/new.html.haml
@@ -0,0 +1,5 @@
+.box
+ .cell
+ = build_admin_navigation([link_to('文件上传', admin_cloud_files_path), @title])
+ .inner
+ = render 'form'
diff --git a/app/views/admin/comments/_comment.html.haml b/app/views/admin/comments/_comment.html.haml
new file mode 100644
index 0000000..a02dabc
--- /dev/null
+++ b/app/views/admin/comments/_comment.html.haml
@@ -0,0 +1,14 @@
+- commentable = comment.commentable
+.cell.comment_header.muted
+ .pull-right.timeago
+ = time_ago_in_words(comment.created_at)
+ = comment.user.nickname
+ 回复了
+ = user_profile_link commentable.user
+ 创建的话题
+ %span.chevron ›
+ = link_to commentable.notifiable_title, commentable.notifiable_path, :class => :rabel
+.inner
+ .reply_content
+ = format_content comment.content
+
diff --git a/app/views/admin/guides/_table_view.html.haml b/app/views/admin/guides/_table_view.html.haml
new file mode 100644
index 0000000..1317f98
--- /dev/null
+++ b/app/views/admin/guides/_table_view.html.haml
@@ -0,0 +1,26 @@
+%table.topics.table
+ %thead
+ %tr
+ %th.w50 ID
+ %th.auto{:align => :left} 分类
+ %th.auto{:align => :left} 标题
+ %th.auto{:align => :left} 收藏数
+ %th.auto{:align => :right} 创建时间
+ %th.w100 操作
+ %tbody
+ - guides.each do |guide|
+ %tr.highlight
+ %td.w50
+ %strong.green
+ = guide.id
+ %td.auto
+ = link_to guide.category.title, "#{guides_url}?c=#{guide.category.id}"
+ %td.auto
+ = link_to guide.title, guide
+ %td.auto
+ = guide.bookmarks.count
+ %td.auto{:align => :right}
+ %small= time_ago_in_words(guide.created_at)
+ %td.w100
+ = link_to '编辑', edit_guide_path(guide), :class => 'btn btn-sm'
+ = link_to '删除', admin_guide_path(guide), :class => 'btn btn-sm btn-danger', :method => :delete, :data => {:confirm => t(:delete_confirm)}
diff --git a/app/views/admin/guides/index.html.haml b/app/views/admin/guides/index.html.haml
new file mode 100644
index 0000000..72e4eda
--- /dev/null
+++ b/app/views/admin/guides/index.html.haml
@@ -0,0 +1,8 @@
+.box
+ .cell
+ = build_admin_navigation([@title])
+ .cell
+ = render 'table_view', :guides => @guides
+ .inner{:align => :center}
+ = paginate @guides
+
diff --git a/app/views/admin/nodes/_form.html.haml b/app/views/admin/nodes/_form.html.haml
new file mode 100644
index 0000000..3ee9ddc
--- /dev/null
+++ b/app/views/admin/nodes/_form.html.haml
@@ -0,0 +1,18 @@
+= simple_form_for [:admin, plane, node], :remote => true, :html => {:class => 'node'} do |f|
+ %legend= @title
+ = f.input :name, :label => '名称'
+ .form-group
+ = f.label :key, 'URL 路径'
+ .input-group
+ %span.input-group-addon /go/
+ = f.text_field :key, :class => 'form-control'
+ = f.input :introduction, :label => '一句话简介'
+ = f.input :custom_css, :label => '自定义 CSS', :input_html => {:rows => 5}
+ = f.input :custom_html, :label => '自定义侧栏内容', :input_html => {:rows => 5}
+ .form-group
+ .checkbox
+ %label
+ = f.check_box :quiet
+ 禁止本节点话题出现在首页
+ .form-actions
+ = f.submit '保存', :class => 'btn btn-sm btn-primary'
diff --git a/app/views/admin/nodes/_move_form.js.haml b/app/views/admin/nodes/_move_form.js.haml
new file mode 100644
index 0000000..e00b1a1
--- /dev/null
+++ b/app/views/admin/nodes/_move_form.js.haml
@@ -0,0 +1,6 @@
+= simple_form_for [:admin, @node], :url => move_to_admin_node_path(@node), :remote => true do |f|
+ %legend 移动到新位面
+ = f.select :plane_id, options_from_collection_for_select(Plane.all - [@node.plane], :id, :name), :class => 'form-control'
+ .form-actions
+ = f.submit '开始移动', :class => 'btn btn-sm btn-primary'
+
diff --git a/app/views/admin/nodes/_node.html.haml b/app/views/admin/nodes/_node.html.haml
new file mode 100644
index 0000000..9e32e83
--- /dev/null
+++ b/app/views/admin/nodes/_node.html.haml
@@ -0,0 +1,8 @@
+.cell.node{:id => node.html_id}
+ .fr
+ = admin_edit_button('修改节点', [node.plane, node], {:remote => true, :id => "edit_#{node.html_id}"})
+ = link_to '移动', move_admin_node_path(node), :remote => true, :class => 'btn btn-sm'
+ = admin_delete_button(node, :remote => true) if node.can_delete?
+ = link_to node.name, go_path(node.key)
+ - if node.quiet
+ = image_tag 'ghost.png', :align => :top, :title => t('tips.quiet_node')
diff --git a/app/views/admin/nodes/create.js.haml b/app/views/admin/nodes/create.js.haml
new file mode 100644
index 0000000..59ca438
--- /dev/null
+++ b/app/views/admin/nodes/create.js.haml
@@ -0,0 +1,3 @@
+$("##{@plane.html_id}").append("#{escape_javascript render(@node) }")
+$("##{@plane.html_id} .cell:first .fr").hide()
+$.facebox.close()
diff --git a/app/views/admin/nodes/destroy.js.haml b/app/views/admin/nodes/destroy.js.haml
new file mode 100644
index 0000000..06c6d5e
--- /dev/null
+++ b/app/views/admin/nodes/destroy.js.haml
@@ -0,0 +1,4 @@
+$("##{@node.html_id}").hide()
+- if @node.plane.can_delete?
+ $("##{@node.plane.html_id}").replaceWith("#{escape_javascript render(@node.plane)}")
+
diff --git a/app/views/admin/nodes/move.js.haml b/app/views/admin/nodes/move.js.haml
new file mode 100644
index 0000000..a3236af
--- /dev/null
+++ b/app/views/admin/nodes/move.js.haml
@@ -0,0 +1 @@
+$.facebox("#{escape_javascript(render 'move_form')}")
diff --git a/app/views/admin/nodes/move_to.js.haml b/app/views/admin/nodes/move_to.js.haml
new file mode 100644
index 0000000..200b232
--- /dev/null
+++ b/app/views/admin/nodes/move_to.js.haml
@@ -0,0 +1 @@
+window.location.href = "#{admin_planes_path}"
diff --git a/app/views/admin/nodes/show_form.js.haml b/app/views/admin/nodes/show_form.js.haml
new file mode 100644
index 0000000..72e4139
--- /dev/null
+++ b/app/views/admin/nodes/show_form.js.haml
@@ -0,0 +1,2 @@
+$.facebox("#{escape_javascript(render('form', :plane => @plane, :node => @node))}");
+$("textarea").elastic()
diff --git a/app/views/admin/nodes/update.js.haml b/app/views/admin/nodes/update.js.haml
new file mode 100644
index 0000000..20df923
--- /dev/null
+++ b/app/views/admin/nodes/update.js.haml
@@ -0,0 +1,6 @@
+- target_id = '#' + @node.html_id
+$.facebox.close()
+$("#{target_id}").replaceWith("#{escape_javascript(render @node)}");
+setTimeout(function() {
+$("#{target_id}").effect('highlight')
+}, 500)
diff --git a/app/views/admin/pages/_form.html.haml b/app/views/admin/pages/_form.html.haml
new file mode 100644
index 0000000..4207cbc
--- /dev/null
+++ b/app/views/admin/pages/_form.html.haml
@@ -0,0 +1,25 @@
+= simple_form_for [:admin, page] do |f|
+ = f.input :title, :label => '标题'
+ .form-group
+ = f.label :key, '英文标题'
+ .input-group
+ %span.input-group-addon /page/
+ = f.text_field :key, :class => 'form-control'
+ %span.help-block 只允许英文字符和下划线
+
+ .form-group
+ = f.label :content, '页面内容'
+ = render 'shared/preview_widget', :ref => :page_content, :type => :page
+ = f.text_area :content, :class => 'form-control', :rows => 5
+ .form-group
+ = f.label :published, '发布状态'
+ .radio
+ %label
+ = f.radio_button 'published', true
+ %span.published 立刻发布
+ %label
+ = f.radio_button 'published', false
+ %span.draft 保存为草稿
+ .form-actions
+ = f.submit '保存', :class => 'btn btn-sm btn-primary'
+
diff --git a/app/views/admin/pages/_page.html.haml b/app/views/admin/pages/_page.html.haml
new file mode 100644
index 0000000..9f59fb7
--- /dev/null
+++ b/app/views/admin/pages/_page.html.haml
@@ -0,0 +1,11 @@
+%tr{:id => page.html_id}
+ %td{:align => :right, :width => 200}
+ = link_to page_real_url(page), page_real_url(page)
+ %td{:align => :center, :width => :auto}
+ = page.title
+ %td{:align => :center, :width => :auto}
+ = page_publish_status(page)
+ %td{:align => :left}
+ = admin_edit_button('修改页面', page)
+ = admin_delete_button(page)
+
diff --git a/app/views/admin/pages/action.html.haml b/app/views/admin/pages/action.html.haml
new file mode 100644
index 0000000..0bbabde
--- /dev/null
+++ b/app/views/admin/pages/action.html.haml
@@ -0,0 +1,5 @@
+.box
+ .cell
+ = build_admin_navigation([link_to('页面管理', admin_pages_path), @title])
+ .inner
+ = render 'form', :page => @page
diff --git a/app/views/admin/pages/index.html.haml b/app/views/admin/pages/index.html.haml
new file mode 100644
index 0000000..d1275a9
--- /dev/null
+++ b/app/views/admin/pages/index.html.haml
@@ -0,0 +1,27 @@
+- content_for :template_js do
+ :plain
+ rabel.sortable("table tbody", "#{sort_admin_pages_path}", {
+ helper: function(e, ui) {
+ ui.children().each(function() {
+ $(this).width($(this).width());
+ });
+ return ui;
+ }
+ })
+
+.box
+ .cell
+ .fr
+ = admin_create_button('创建新页面', :page)
+ = build_admin_navigation([@title])
+ .inner
+ %table.table
+ %thead
+ %tr
+ %th{:align => :right, :width => 100} URL
+ %th{:align => :center, :width => :auto} 标题
+ %th{:align => :center, :width => :auto} 发布状态
+ %th{:align => :left} 操作
+ %tbody
+ = render @pages
+
diff --git a/app/views/admin/planes/_form.html.haml b/app/views/admin/planes/_form.html.haml
new file mode 100644
index 0000000..70ffd90
--- /dev/null
+++ b/app/views/admin/planes/_form.html.haml
@@ -0,0 +1,6 @@
+= simple_form_for [:admin, plane], :remote => true do |f|
+ %legend= @title
+ = f.input :name, :label => '名称', :input_html => {:autofocus => true}
+ .form-actions
+ = f.submit '保存', :class => 'btn btn-small btn-primary'
+
diff --git a/app/views/admin/planes/_plane.html.haml b/app/views/admin/planes/_plane.html.haml
new file mode 100644
index 0000000..199be04
--- /dev/null
+++ b/app/views/admin/planes/_plane.html.haml
@@ -0,0 +1,11 @@
+.box.plane{:id => plane.html_id}
+ .cell
+ .fr
+ = admin_edit_button('修改位面', plane, :remote => true, :class => 'dark')
+ %span.gray /
+ = admin_create_button('添加节点', [plane, :node], {:class => 'dark', :remote => true})
+ - if plane.can_delete?
+ = admin_delete_button(plane, :remote => true)
+ = plane.name
+ = render plane.nodes.default_order
+.sep10
diff --git a/app/views/admin/planes/_sort_plane.html.haml b/app/views/admin/planes/_sort_plane.html.haml
new file mode 100644
index 0000000..634d3b6
--- /dev/null
+++ b/app/views/admin/planes/_sort_plane.html.haml
@@ -0,0 +1 @@
+.sort_item{:id => plane.html_id}= plane.name
diff --git a/app/views/admin/planes/_sort_planes.html.haml b/app/views/admin/planes/_sort_planes.html.haml
new file mode 100644
index 0000000..f1727a8
--- /dev/null
+++ b/app/views/admin/planes/_sort_planes.html.haml
@@ -0,0 +1,4 @@
+%strong 位面拖动排序
+= render :partial => 'sort_plane', :collection => planes, :as => :plane, :formats => :html
+.sort_actions.center
+ = link_to '完成排序', 'javascript:window.location.reload()', :class => 'btn btn-small btn-primary'
diff --git a/app/views/admin/planes/create.js.haml b/app/views/admin/planes/create.js.haml
new file mode 100644
index 0000000..49af127
--- /dev/null
+++ b/app/views/admin/planes/create.js.haml
@@ -0,0 +1,2 @@
+$("#planes").append("#{escape_javascript render(@plane)}")
+$.facebox.close()
diff --git a/app/views/admin/planes/destroy.js.haml b/app/views/admin/planes/destroy.js.haml
new file mode 100644
index 0000000..45cbf26
--- /dev/null
+++ b/app/views/admin/planes/destroy.js.haml
@@ -0,0 +1 @@
+$("##{@plane.html_id}").hide()
diff --git a/app/views/admin/planes/index.html.haml b/app/views/admin/planes/index.html.haml
new file mode 100644
index 0000000..be668ed
--- /dev/null
+++ b/app/views/admin/planes/index.html.haml
@@ -0,0 +1,14 @@
+- content_for :template_js do
+ :plain
+ rabel.sortable(".plane", "#{sort_admin_nodes_path}")
+
+.box
+ .inner
+ .fr
+ .btn-group
+ = admin_create_button('添加位面', :plane, :remote => true)
+ = link_to '位面排序', sort_admin_planes_path, :class => 'btn btn-sm btn-info', :remote => true
+ = build_admin_navigation([@title])
+
+#planes
+ = render @planes
diff --git a/app/views/admin/planes/show_form.js.haml b/app/views/admin/planes/show_form.js.haml
new file mode 100644
index 0000000..7d29641
--- /dev/null
+++ b/app/views/admin/planes/show_form.js.haml
@@ -0,0 +1 @@
+$.facebox("#{escape_javascript(render('form', :plane => @plane))}");
diff --git a/app/views/admin/planes/sort.js.haml b/app/views/admin/planes/sort.js.haml
new file mode 100644
index 0000000..21eed78
--- /dev/null
+++ b/app/views/admin/planes/sort.js.haml
@@ -0,0 +1,2 @@
+$.facebox("#{escape_javascript render('sort_planes', :planes => @planes, :formats => :html)}");
+rabel.sortable("#facebox .content", "#{sort_admin_planes_path}")
diff --git a/app/views/admin/rewards/_form.html.haml b/app/views/admin/rewards/_form.html.haml
new file mode 100644
index 0000000..8029aab
--- /dev/null
+++ b/app/views/admin/rewards/_form.html.haml
@@ -0,0 +1,25 @@
+= simple_form_for [:admin, @user, @reward], :remote => true do |f|
+ %legend
+ - if @reward_type == Reward::TYPE_GRANT
+ 奖励给
+ = @user.nickname
+ - else
+ 从
+ = @user.nickname
+ 帐户中扣除
+
+ .control-group
+ = f.label :amount_str, Siteconf.reward_title, :class => 'control-label'
+ .controls
+ = f.text_field :amount_str, :autofocus => true, :class => 'input-small'
+ - if @reward_type == Reward::TYPE_REVOKE
+ %span.gray 可用余额
+ .label.label-info= @user.reward
+ = f.input :reason, :label => '理由', :input_html => {:placeholder => '必填', :rows => 2, :class => :span4}
+ = f.hidden_field :reward_type
+ - if @reward_type == 'grant'
+ - @button_name = '发放奖励'
+ - else
+ - @button_name = "扣除#{Siteconf.reward_title}"
+ .form-actions
+ = f.submit @button_name, :class => 'btn btn-small btn-primary'
diff --git a/app/views/admin/rewards/_reward.html.haml b/app/views/admin/rewards/_reward.html.haml
new file mode 100644
index 0000000..e0bc3ba
--- /dev/null
+++ b/app/views/admin/rewards/_reward.html.haml
@@ -0,0 +1,16 @@
+%tr.highlight
+ %td.d
+ %small.gray= l reward.created_at, :format => :long
+ %td.d
+ %strong= user_profile_link(reward.user)
+ %td.d
+ - if reward.amount > 0
+ %strong.green.quantity= reward.amount
+ - else
+ %strong.red.quantity= reward.amount
+ %td.d
+ = reward.balance
+ %td.d
+ = user_profile_link(reward.admin_user)
+ %td.d.last
+ .gray= reward.reason
diff --git a/app/views/admin/rewards/create.js.haml b/app/views/admin/rewards/create.js.haml
new file mode 100644
index 0000000..3497d3a
--- /dev/null
+++ b/app/views/admin/rewards/create.js.haml
@@ -0,0 +1,8 @@
+:plain
+ var balance = $("#reward_balance")
+ if (balance.length > 0) {
+ balance.html("#{@user.reward}");
+ $.facebox.close();
+ } else {
+ window.location.reload();
+ }
diff --git a/app/views/admin/rewards/index.html.haml b/app/views/admin/rewards/index.html.haml
new file mode 100644
index 0000000..d890ab1
--- /dev/null
+++ b/app/views/admin/rewards/index.html.haml
@@ -0,0 +1,17 @@
+.box
+ .cell
+ = build_admin_navigation([@title])
+ .cell
+ %table.table.table-bordered
+ %tr
+ %td.h{:width => 110} 时间
+ %td.h{:width => 80} 帐户
+ %td.h{:width => 60} 数额
+ %td.h{:width => 60} 当前余额
+ %td.h{:width => 80} 管理员
+ %td.h.last{:width => :auto} 理由
+
+ = render @rewards
+ .inner
+ = paginate @rewards
+
diff --git a/app/views/admin/rewards/new.js.haml b/app/views/admin/rewards/new.js.haml
new file mode 100644
index 0000000..c6a072b
--- /dev/null
+++ b/app/views/admin/rewards/new.js.haml
@@ -0,0 +1 @@
+$.facebox('#{escape_javascript render('form')}');
diff --git a/app/views/admin/site_settings/appearance.html.haml b/app/views/admin/site_settings/appearance.html.haml
new file mode 100644
index 0000000..251e339
--- /dev/null
+++ b/app/views/admin/site_settings/appearance.html.haml
@@ -0,0 +1,42 @@
+.box
+ .cell
+ = build_admin_navigation([@title])
+ .inner
+ = form_for :site_settings, :url => admin_site_settings_path, :method => :put do |f|
+ .form-group
+ = label_tag 'settings[theme]', '设计风格'
+ = select_tag 'settings[theme]', options_for_select([['默认风格', :rabel]], @settings.theme), :class => 'form-control'
+
+ .form-group
+ = label_tag 'settings[nav_position]', '节点导航位置'
+ = select_tag 'settings[nav_position]', options_for_select([['头部', 'top'], ['侧边栏', 'sidebar'], ['底部', 'bottom']], @settings.nav_position), :class => 'form-control'
+
+ .form-group
+ = label_tag 'settings[topic_list_style]', '帖子列表风格'
+ = select_tag 'settings[topic_list_style]', options_for_select([['极简', :simple], ['丰富', :complex]], @settings.topic_list_style), :class => 'form-control'
+
+ .form-group
+ = label_tag 'settings[custom_logo]', '自定义Logo'
+ = text_field_tag 'settings[custom_logo]', @settings.custom_logo, :class => :sl, :placeholder => t('tips.custom_logo_path'), :class => 'form-control'
+ %small.help-inline 推荐尺寸: 100 x 40
+
+ .form-group
+ = label_tag 'settings[global_banner]', '自定义Banner'
+ = text_field_tag 'settings[global_banner]', @settings.global_banner, :class => :sl, :placeholder => t('tips.banner_path'), :class => 'form-control'
+ %small.help-inline 推荐尺寸: 960 x 145
+
+ .form-group
+ = label_tag 'settings[custom_css]', '自定义CSS'
+ %span.gray.help-block <style type="text/css">
+ = text_area_tag 'settings[custom_css]', @settings.custom_css, :class => 'form-control', :rows => 5
+ .gray </style>
+
+ .form-group
+ = label_tag 'settings[custom_js]', '自定义JavaScript'
+ .controls
+ %span.gray.help-block <script type="text/javascript">
+ = text_area_tag 'settings[custom_js]', @settings.custom_js, :class => 'form-control', :rows => 5
+ .gray </script>
+
+ .form-actions
+ = f.submit '保存', :class => 'btn btn-small btn-primary'
diff --git a/app/views/admin/site_settings/show.html.haml b/app/views/admin/site_settings/show.html.haml
new file mode 100644
index 0000000..a6927b2
--- /dev/null
+++ b/app/views/admin/site_settings/show.html.haml
@@ -0,0 +1,136 @@
+.box
+ .cell
+ = build_admin_navigation([@title])
+ .inner
+ = form_for :site_settings, :url => admin_site_settings_path, :method => :put do |f|
+ .form-group
+ = label_tag 'settings[site_name]', '网站名称'
+ = text_field_tag 'settings[site_name]', @settings.site_name, :class => 'form-control'
+ %span.help-block 必填
+
+ .form-group
+ = label_tag 'settings[welcome_tip]', '欢迎信息'
+ = text_field_tag 'settings[welcome_tip]', @settings.welcome_tip, :class => 'form-control'
+ %span.help-block 支持 HTML
+
+ .form-group
+ = label_tag 'settings[short_intro]', '简短介绍'
+ = text_field_tag 'settings[short_intro]', @settings.short_intro, :class => 'form-control'
+ %span.help-block 网站简短介绍, 显示在右侧边栏
+
+ .form-group
+ = label_tag 'settings[marketing_str]', '市场宣传关键字'
+ = text_field_tag 'settings[marketing_str]', @settings.marketing_str, :class => 'form-control'
+ %span.help-block 用英文逗号(,)隔开
+
+ .form-group
+ = label_tag 'settings[ga_id]', 'Google Analytics ID'
+ = text_field_tag 'settings[ga_id]', @settings.ga_id, :class => 'form-control'
+ %span.help-block 例如: UA-12345678-01
+
+ .form-group
+ = label_tag 'settings[default_search_engine]', '站内搜索引擎'
+ = select_tag 'settings[default_search_engine]', options_for_select([['谷歌', 'google'], ['百度', 'baidu'], ['必应', 'bing'],['WenLu','wenLu']], @settings.default_search_engine), :class => 'form-control'
+
+ .form-group
+ = label_tag 'settings[show_captcha]', '注册验证码'
+ .radio
+ %label
+ = radio_button_tag 'settings[show_captcha]', 'on', @settings.show_captcha?
+ 开启
+ %label
+ = radio_button_tag 'settings[show_captcha]', 'off', !@settings.show_captcha?
+ 关闭
+
+ .form-group
+ = label_tag 'settings[show_community_stats]', '社区运行状态'
+ .radio
+ %label
+ = radio_button_tag 'settings[show_community_stats]', 'on', @settings.show_community_stats?
+ 显示
+ %label
+ = radio_button_tag 'settings[show_community_stats]', 'off', !@settings.show_community_stats?
+ 隐藏
+
+ .form-group
+ = label_tag 'settings[allow_markdown_in_topics]', '话题允许 Markdown'
+ .radio
+ %label
+ = radio_button_tag 'settings[allow_markdown_in_topics]', 'on', @settings.allow_markdown_in_topics?
+ 允许
+ %label
+ = radio_button_tag 'settings[allow_markdown_in_topics]', 'off', !@settings.allow_markdown_in_topics?
+ 关闭
+
+ .form-group
+ = label_tag 'settings[allow_markdown_in_comments]', '回复允许 Markdown', :class => 'control-label'
+ .radio
+ %label
+ = radio_button_tag 'settings[allow_markdown_in_comments]', 'on', @settings.allow_markdown_in_comments?
+ 允许
+ %label
+ = radio_button_tag 'settings[allow_markdown_in_comments]', 'off', !@settings.allow_markdown_in_comments?
+ 关闭
+ .form-group
+ = label_tag 'settings[allow_markdown_in_pages]', '页面允许 Markdown'
+ .radio
+ %label
+ = radio_button_tag 'settings[allow_markdown_in_pages]', 'on', @settings.allow_markdown_in_pages?
+ 允许
+ %label
+ = radio_button_tag 'settings[allow_markdown_in_pages]', 'off', !@settings.allow_markdown_in_pages?
+ 关闭
+ .form-group
+ = label_tag 'settings[custom_head_tags]', '自定义Head标签'
+ = text_area_tag 'settings[custom_head_tags]', @settings.custom_head_tags, :class => 'form-control', :rows => 5
+ %span.help-block 可以添加<meta>, <script>, <style>等头部标签
+ .form-group
+ = label_tag 'settings[seo_description]', 'SEO 描述'
+ = text_area_tag 'settings[seo_description]', @settings.seo_description, :class => 'form-control'
+ %span.help-block 用于HTML meta标签
+ .form-group
+ = label_tag 'settings[splash]', 'Hero Unit'
+ = text_area_tag 'settings[splash]', @settings.splash, :class => 'form-control', :rows => 5
+ %span.help-block 支持HTML
+ .form-group
+ = label_tag 'settings[global_sidebar_block]', '全局侧边栏'
+ = text_area_tag 'settings[global_sidebar_block]', @settings.global_sidebar_block, :class => 'form-control', :rows => 5
+ %span.help-block 支持HTML
+ .form-group
+ = label_tag 'settings[footer]', '页面底部 [普通版]'
+ = text_area_tag 'settings[footer]', @settings.footer, :class => 'form-control', :rows => 5
+ %span.help-block 支持HTML
+ .form-group
+ = label_tag 'settings[mobile_footer]', '页面底部 [移动版]'
+ = text_area_tag 'settings[mobile_footer]', @settings.mobile_footer, :class => 'form-control', :rows => 5
+ %span.help-block 支持HTML
+ .form-group
+ = label_tag 'settings[sticky_topics_heading]', '置顶话题提示'
+ = text_field_tag 'settings[sticky_topics_heading]', @settings.sticky_topics_heading, :class => 'form-control'
+ .form-group
+ = label_tag 'settings[latest_topics_heading]', '最新话题提示'
+ = text_field_tag 'settings[latest_topics_heading]', @settings.latest_topics_heading, :class => 'form-control'
+ .form-group
+ = label_tag 'settings[reward_title]', '奖励名称'
+ = text_field_tag 'settings[reward_title]', @settings.reward_title, :class => 'form-control'
+ %span.help-block 例如: 银币,金币,积分,优惠券,代金券,蓝钻, Q币等
+ .form-group
+ = label_tag 'settings[topic_editable_period_str]', '话题编辑限制'
+ .input-group
+ = text_field_tag 'settings[topic_editable_period_str]', @settings.topic_editable_period_str, :class => 'form-control'
+ %span.input-group-addon 分钟
+ %span.help-block 允许用户修改话题的时间
+ .form-group
+ = label_tag 'settings[pagination_topics]', '节点话题'
+ .input-group
+ = text_field_tag 'settings[pagination_topics]', @settings.pagination_topics, :class => 'form-control'
+ %span.input-group-addon / 页
+ .form-group
+ = label_tag 'settings[pagination_comments]', '回复'
+ .input-group
+ = text_field_tag 'settings[pagination_comments]', @settings.pagination_comments, :class => 'form-control input-sm'
+ %span.input-group-addon / 页
+
+ .form-actions
+ = f.submit '保存', :class => 'btn btn-small btn-primary'
+
diff --git a/app/views/admin/topics/_table_view.html.haml b/app/views/admin/topics/_table_view.html.haml
new file mode 100644
index 0000000..52590a2
--- /dev/null
+++ b/app/views/admin/topics/_table_view.html.haml
@@ -0,0 +1,35 @@
+%table.topics.table
+ %thead
+ %tr
+ %th.w50 ID
+ %th.auto{:align => :left} 节点
+ %th.auto{:align => :left} 标题
+ %th.auto{:align => :left} 作者
+ %th.auto{:align => :right} 回复数
+ %th.auto{:align => :right} 收藏数
+ %th.auto{:align => :right} 浏览量
+ %th.auto{:align => :right} 创建时间
+ %th.w100 操作
+ %tbody
+ - topics.each do |topic|
+ %tr.highlight
+ %td.w50
+ %strong.green
+ = topic.id
+ %td.auto
+ = link_to topic.node.name, go_path(topic.node.key)
+ %td.auto
+ = link_to topic.title, t_path(topic.id)
+ %td.auto
+ = user_profile_link(topic.user)
+ %td.auto{:align => :right}
+ = topic.comments_count
+ %td.auto{:align => :right}
+ = topic.bookmarks.count
+ %td.auto{:align => :right}
+ = topic.hit
+ %td.auto{:align => :right}
+ %small= time_ago_in_words(topic.created_at)
+ %td.w100
+ = link_to '编辑', edit_node_topic_path(topic.node, topic), :class => 'btn btn-sm'
+ = link_to '删除', admin_topic_path(topic), :class => 'btn btn-sm btn-danger', :method => :delete, :data => {:confirm => t(:delete_confirm)}
diff --git a/app/views/admin/topics/index.html.haml b/app/views/admin/topics/index.html.haml
new file mode 100644
index 0000000..9cd8a16
--- /dev/null
+++ b/app/views/admin/topics/index.html.haml
@@ -0,0 +1,8 @@
+.box
+ .cell
+ = build_admin_navigation([@title])
+ .cell
+ = render 'table_view', :topics => @topics
+ .inner{:align => :center}
+ = paginate @topics
+
diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml
new file mode 100644
index 0000000..c5cccc5
--- /dev/null
+++ b/app/views/admin/users/_user.html.haml
@@ -0,0 +1,38 @@
+%tr.highlight{:id => user.html_id}
+ %td{:align => :right}= user.id
+ %td.auto{:align => :left}
+ - if user.blocked?
+ %del= user.nickname
+ .label.label-warning 已屏蔽
+ - else
+ %strong
+ = user_profile_link(user, :class => :black)
+ %td.w50{:align => :left}
+ - if user.root?
+ %strong.green ROOT
+ - elsif user.admin?
+ = image_tag 'star.png'
+ - else
+ %span.gray 普通
+
+ %td.auto{:align => :left}= user.sign_in_count
+ %td.auto{:align => :left}= user.bookmarks.count
+ %td.auto{:align => :left}= user.last_sign_in_at
+ %td.auto{:align => :left}= user.email
+ %td.auto{:align => :right}
+ = user.reward
+ %td.center
+ - if can? :edit_info, user
+ = link_to '修改用户信息', edit_admin_user_path(user), :class => 'btn btn-sm'
+
+ - if current_user.can_manage_site? and user != current_user and (not user.root?)
+ - if user.admin?
+ = link_to '取消管理权限', toggle_admin_admin_user_path(user), :method => :put, :remote => true, :class => 'btn btn-sm'
+ - else
+ = link_to '提升为管理员', toggle_admin_admin_user_path(user), :method => :put, :remote => true, :class => 'btn btn-sm'
+
+ - if user.blocked?
+ = link_to '取消屏蔽', toggle_blocked_admin_user_path(user), :method => :put, :remote => true, :class => 'btn btn-sm'
+ - else
+ = link_to '屏蔽', toggle_blocked_admin_user_path(user), :method => :put, :remote => true, :class => 'btn btn-sm btn-danger'
+
diff --git a/app/views/admin/users/edit.html.haml b/app/views/admin/users/edit.html.haml
new file mode 100644
index 0000000..b09aa44
--- /dev/null
+++ b/app/views/admin/users/edit.html.haml
@@ -0,0 +1,15 @@
+.box
+ .cell
+ .pull-right= link_to '返回用户管理', admin_users_path + "?nickname=#{url_encode @user.nickname}", :class => :btn
+ = build_admin_navigation([@title])
+ .cell
+ = simple_form_for [:admin, @user] do |f|
+ = f.input :nickname, :label => '用户名'
+ = f.input :email
+ = f.input :password, :label => '新密码'
+ = f.input :password_confirmation, :label => '确认新密码'
+ = render 'users/account_detail_form', :f => f
+ = f.input :reward, :label => Siteconf.reward_title
+ .form-actions
+ = f.submit '更新用户信息', :class => 'btn btn-small btn-primary'
+
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
new file mode 100644
index 0000000..d1994d1
--- /dev/null
+++ b/app/views/admin/users/index.html.haml
@@ -0,0 +1,26 @@
+.box
+ .cell
+ .fr
+ = form_for '', :url => admin_users_path, :method => :get, :html => {:class => 'form-inline'} do
+ .input-append
+ = text_field_tag :nickname, params[:nickname], :class => 'form-control input-sm', :placeholder => '用户昵称'
+ = submit_tag '搜索', :class => 'btn btn-default btn-sm'
+
+ = build_admin_navigation([@title])
+ .cell
+ %table.table
+ %thead
+ %tr
+ %th{:align => :right} ID
+ %th.w50{:align => :left} 昵称
+ %th.auto{:align => :left} 角色
+ %th.auto{:align => :left} 登录次数
+ %th.auto{:align => :left} 收藏数
+ %th.auto{:align => :left} 最近登录
+ %th.auto{:align => :left} Email
+ %th.auto{:align => :right}= Siteconf.reward_title
+ %th 操作
+ %tbody
+ = render @users
+ .inner{:align => :center}
+ = paginate @users
diff --git a/app/views/admin/users/toggle_admin.js.haml b/app/views/admin/users/toggle_admin.js.haml
new file mode 100644
index 0000000..029f4ab
--- /dev/null
+++ b/app/views/admin/users/toggle_admin.js.haml
@@ -0,0 +1,2 @@
+$("##{@user.html_id}").replaceWith("#{escape_javascript(render('user', :user => @user))}");
+$("##{@user.html_id}").effect('highlight');
diff --git a/app/views/admin/users/toggle_blocked.js.haml b/app/views/admin/users/toggle_blocked.js.haml
new file mode 100644
index 0000000..029f4ab
--- /dev/null
+++ b/app/views/admin/users/toggle_blocked.js.haml
@@ -0,0 +1,2 @@
+$("##{@user.html_id}").replaceWith("#{escape_javascript(render('user', :user => @user))}");
+$("##{@user.html_id}").effect('highlight');
diff --git a/app/views/admin/welcome_admin/index.html.haml b/app/views/admin/welcome_admin/index.html.haml
new file mode 100644
index 0000000..689bdef
--- /dev/null
+++ b/app/views/admin/welcome_admin/index.html.haml
@@ -0,0 +1,62 @@
+.row
+ .col-md-6
+ .box
+ .cell
+ 最新用户
+ .inner
+ %table.topics.table
+ %thead
+ %tr
+ %th.auto{:align => :left} 用户名
+ %th.auto{:align => :left} Email
+ %th.w100 注册时间
+ %tbody
+ - User.order('created_at DESC').limit(5).each do |user|
+ %tr
+ %td.auto
+ = user.nickname
+ %td.auto
+ = user.email
+ %td.w100
+ %small.gray= time_ago_in_words(user.created_at)
+ .col-md-6
+ .box
+ .cell
+ 社区运行状态
+ .inner
+ %table.table
+ %thead
+ %tr
+ %th 注册会员
+ %th 话题
+ %th 回复
+ %tr
+ %td
+ %strong= User.cached_count
+ %td
+ %strong= Topic.cached_count
+ %td
+ %strong= Comment.cached_count
+
+- if @notifications_to_clear > 0
+ .row
+ .box
+ .cell
+ 系统清理
+ .inner
+ %table.table.table-bordered
+ %tr
+ %td{:align => :right, :width => '40%'}
+ %span.gray 可清理提醒
+ %td{:align => :left}
+ .pull-right= link_to '删除已读提醒', clear_admin_notifications_path, :method => :delete, :class => 'btn btn-mini btn-info' if @notifications_to_clear > 0
+ %strong= @notifications_to_clear
+
+.row
+ .box.span8
+ .cell
+ 最新回复
+ .inner
+ = render Comment.order('created_at DESC').limit(5)
+
+
diff --git a/app/views/articles/_article.html.erb b/app/views/articles/_article.html.erb
deleted file mode 100644
index 16d1338..0000000
--- a/app/views/articles/_article.html.erb
+++ /dev/null
@@ -1,7 +0,0 @@
-
-<%= link_to [guide,article] do %>
-
- <%= article.title %>
-<% end %>
-
-
diff --git a/app/views/articles/_article.html.haml b/app/views/articles/_article.html.haml
new file mode 100644
index 0000000..74a13b3
--- /dev/null
+++ b/app/views/articles/_article.html.haml
@@ -0,0 +1,5 @@
+- article_user = article.cached_assoc_object(:user)
+- article_guide = article.cached_assoc_object(:guide)
+- comments_count = article.comments_count
+= render "articles/content_article", :article_user => article_user, :article_guide => article_guide, :article => article
+
diff --git a/app/views/articles/_article.mobile.haml b/app/views/articles/_article.mobile.haml
new file mode 100644
index 0000000..7b59552
--- /dev/null
+++ b/app/views/articles/_article.mobile.haml
@@ -0,0 +1,24 @@
+- topic_user = topic.user
+- comment_count = topic.comments_count
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td.avatar{:valign => :top}
+ = user_profile_avatar_link(topic_user, :medium)
+ %td{:valign => :top, :style => 'padding-left: 8px;'}
+ %span.fade
+ = user_profile_link(topic_user)
+ in
+ = link_to topic.node.name, go_path(topic.node.key)
+ - if comment_count > 0
+ %small
+ 收到
+ = comment_count
+ 回复
+ .sep5
+ %span.bigger
+ = link_to topic.title, t_path(topic.id)
+ %span.created
+ = time_ago_in_words(topic.created_at)
+
+
diff --git a/app/views/articles/_back_to_node.html.haml b/app/views/articles/_back_to_node.html.haml
new file mode 100644
index 0000000..15b1353
--- /dev/null
+++ b/app/views/articles/_back_to_node.html.haml
@@ -0,0 +1,4 @@
+%span.fade
+ %span.chevron ‹
+ 返回
+ = link_to node.name, go_path(node.key)
diff --git a/app/views/articles/_complex_article.html.haml b/app/views/articles/_complex_article.html.haml
new file mode 100644
index 0000000..39f4d08
--- /dev/null
+++ b/app/views/articles/_complex_article.html.haml
@@ -0,0 +1,24 @@
+- comments_count = article.comments_count
+- last_replied_by = article.last_replied_by
+.cell.topic{:class => article_user.can_manage_site? ? 'admin' : ''}
+ .avatar.pull-left
+ = user_profile_avatar_link(article_user, :medium)
+ .item_title
+ - if comments_count > 0
+ .pull-right
+ .badge.badge-info= comments_count
+ %h2.topic_title
+ = link_to article_guide.title + "-" + article.title, guide_article_path(article_guide,article), :class => 'rabel topic'
+ .topic-meta
+ = link_to article_guide.title, article_guide, :class => :node
+ %span.muted •
+ = user_profile_link(article_user, :class => :dark)
+ %span.muted •
+ - if comments_count > 0
+ = time_ago_in_words(article.last_replied_at)
+ %span.muted •
+ 最后回复来自
+ = nickname_profile_link(article.user.nickname)
+ - else
+ = time_ago_in_words(article.created_at)
+
diff --git a/app/views/articles/_content_article.html.haml b/app/views/articles/_content_article.html.haml
new file mode 100644
index 0000000..f180a6a
--- /dev/null
+++ b/app/views/articles/_content_article.html.haml
@@ -0,0 +1,22 @@
+- comments_count = article.comments_count
+- last_replied_by = article.last_replied_by
+.cell.topic{:class => article_user.can_manage_site? ? 'admin' : ''}
+ - if comments_count > 0
+ .pull-right
+ .badge.badge-info= comments_count
+ %h2.topic_title
+ = link_to article.title, guide_article_path(article_guide,article), :class => 'rabel topic'
+ .content.topic_content= format_topic(article.content)
+ .topic-meta
+ = link_to article_guide.title, article_guide, :class => :node
+ %span.muted •
+ = user_profile_link(article_user, :class => :dark)
+ %span.muted •
+ - if comments_count > 0
+ = time_ago_in_words(article.last_replied_at)
+ %span.muted •
+ 最后回复来自
+ = nickname_profile_link(article.user.nickname)
+ - else
+ = time_ago_in_words(article.created_at)
+
diff --git a/app/views/articles/_form.html.erb b/app/views/articles/_form.html.erb
deleted file mode 100644
index 76c476a..0000000
--- a/app/views/articles/_form.html.erb
+++ /dev/null
@@ -1,12 +0,0 @@
-<%= simple_form_for([@guide,@article]) do |f| %>
- <%= f.error_notification %>
-
-
- <%= f.input :title %>
- <%= f.input :content %>
-
-
-
- <%= f.button :submit %>
-
-<% end %>
diff --git a/app/views/articles/_form.html.haml b/app/views/articles/_form.html.haml
new file mode 100644
index 0000000..0c4b534
--- /dev/null
+++ b/app/views/articles/_form.html.haml
@@ -0,0 +1,8 @@
+.alert.alert-info 如果标题已经包含你想说的话,内容可以留空
+= simple_form_for [guide, article] do |f|
+ %a{:name => 'new_article'}/
+ = f.input :title, :label => '标题', :input_html => {:maxlength => 150, :class => :span6}
+ = render 'shared/fileupload_widget', :textarea_selector => "textarea#topic_content"
+ = render 'shared/preview_widget', :ref => :topic_content, :type => :topic
+ = f.input :content, :input_html => {:rows => 10, :style => 'width: 98%;'}, :label => false, :placeholder => '文章内容'
+ = f.submit (article.new_record? ? '创建新文章' : '提交修改'), :class => 'btn btn-primary', :data => {:disable_with => t('tips.submitting')}
diff --git a/app/views/articles/_move_form.js.haml b/app/views/articles/_move_form.js.haml
new file mode 100644
index 0000000..af057ad
--- /dev/null
+++ b/app/views/articles/_move_form.js.haml
@@ -0,0 +1,6 @@
+= form_for [@node, @topic], :remote => :true do |f|
+ = label_tag '移动到新节点'
+ %br
+ = select_tag "new_node_id", option_groups_from_collection_for_select(Plane.default_order.all, :nodes, :name, :id, :name, f.object.node.id)
+ %br
+ = f.submit '开始移动', :class => 'btn btn-small'
diff --git a/app/views/articles/_profile_article.html.haml b/app/views/articles/_profile_article.html.haml
new file mode 100644
index 0000000..1408b00
--- /dev/null
+++ b/app/views/articles/_profile_article.html.haml
@@ -0,0 +1,22 @@
+- topic_node = topic.node
+- comments_count = topic.comments_count
+- last_replied_by = topic.last_replied_by
+.cell.topic{:class => topic_user.can_manage_site? ? 'admin' : ''}
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td{:valign => :middle, :width => :auto}
+ %span.bigger
+ = link_to topic.title, t_path(topic.id), :class => 'rabel topic'
+ .topic-meta
+ = link_to topic_node.name, go_path(topic_node.key), :class => :node
+ - if comments_count > 0
+ •
+ = time_ago_in_words(topic.last_replied_at)
+ •
+ 最后回复来自
+ = nickname_profile_link(last_replied_by)
+ - else
+ •
+ = time_ago_in_words(topic.created_at)
+ %td{:valign => :middle, :width => 40, :align => :right}
+ .badge.badge-info= comments_count
diff --git a/app/views/articles/_profile_article.mobile.haml b/app/views/articles/_profile_article.mobile.haml
new file mode 100644
index 0000000..808ebf4
--- /dev/null
+++ b/app/views/articles/_profile_article.mobile.haml
@@ -0,0 +1,11 @@
+.cell
+ %table{:cellpadding => 5, :cellspacing => 0, :border => 0}
+ %tr
+ %td{:width => 80, :align => :right}
+ = link_to topic.node.name, go_path(topic.node.key)
+ %td{:align => :left}
+ = link_to topic.title, t_path(topic.id)
+ %small.fade
+ 收到
+ = topic.comments_count
+ 回复
diff --git a/app/views/articles/_simple_article.html.haml b/app/views/articles/_simple_article.html.haml
new file mode 100644
index 0000000..28032b2
--- /dev/null
+++ b/app/views/articles/_simple_article.html.haml
@@ -0,0 +1,12 @@
+- comments_count = article.comments_count
+-#.cell{:class => article_user.can_manage_site? ? 'admin' : ''}
+-# %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+-# %tr
+-# %td{:valign => :top, :style => 'padding-left: 12px'}
+-# - if comments_count > 0
+-# .fr
+-# = link_to comments_count, [article_guide,article], :class => "list-group-item"
+-# .sep3
+-# %span{:style => 'font-size: 12px; line-height: 100%'}
+-# = link_to article.title, [article_guide,article], :class => 'rabel topic'
+=link_to ("#{article.title}"+" "+content_tag(:span,"#{comments_count}",:class => "#{comments_count>0? 'article_count':''} badge")).html_safe,[article_guide,article], :class => "list-group-item #{current_article_id == article.id ? 'active':''}"
diff --git a/app/views/articles/_table_view.html.haml b/app/views/articles/_table_view.html.haml
new file mode 100644
index 0000000..5f117bc
--- /dev/null
+++ b/app/views/articles/_table_view.html.haml
@@ -0,0 +1,27 @@
+%table{:cellpadding => 5, :cellspacing => 0, :border => 0, :width => '100%', :class => :topics}
+ %tr
+ %th{:align => :right, :width => 50} 回复
+ %th{:align => :left, :width => :auto} 标题
+ %th{:align => :left, :width => 200, :colspan => 2} 最后回复时间
+ - i = 1
+ - topics.each do |topic|
+ - class_name = (i % 2 == 0) ? 'even' : 'odd'
+ - i += 1
+ %tr
+ %td{:align => :right, :width => 50, :class => "#{class_name} lend"}
+ - comments_count = topic.comments_count
+ - if comments_count > 0
+ %strong
+ %span.green= comments_count
+ - else
+ %span.snow 0
+ %td{:align => :left, :width => :auto, :class => class_name}
+ = link_to topic.title, t_path(topic.id)
+ - last_comment = topic.last_comment
+ - last_comment = topic if last_comment.nil?
+ %td{:align => :left, :width => 80, :class => class_name}
+ = user_profile_link(last_comment.user, :class => :dark)
+ %td{:align => :left, :width => 120, :class => "#{class_name} rend"}
+ %small.fade= last_comment.created_at.strftime("%Y-%m-%d %H:%M:%S")
+
+
diff --git a/app/views/articles/_title_form.html.haml b/app/views/articles/_title_form.html.haml
new file mode 100644
index 0000000..d091384
--- /dev/null
+++ b/app/views/articles/_title_form.html.haml
@@ -0,0 +1,7 @@
+= form_for [@node, @topic], :url => update_title_node_topic_path(@node, @topic), :remote => true do |f|
+ %a{:name => 'new_topic'}
+ = f.label :title, '标题'
+ .sep5
+ = f.text_area :title, :class => :span4, :rows => 2, :autofocus => true
+ .sep5
+ = f.submit '提交修改', :class => 'btn btn-small', :data => {:disable_with => t('tips.submitting')}
diff --git a/app/views/articles/edit.html.erb b/app/views/articles/edit.html.erb
deleted file mode 100644
index f8560b1..0000000
--- a/app/views/articles/edit.html.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-Editing article
-
-<%= render 'form' %>
-
-<%= link_to 'Show', [@guide,@article] %> |
-<%= link_to 'Back', @guide %>
diff --git a/app/views/articles/edit.html.haml b/app/views/articles/edit.html.haml
new file mode 100644
index 0000000..0b8e962
--- /dev/null
+++ b/app/views/articles/edit.html.haml
@@ -0,0 +1,30 @@
+- unless current_user.can_manage_site?
+ .box
+ .cell
+ %span.fade 修改功能指南
+ .inner
+ 在新主题创建后的
+ = Siteconf.topic_editable_period_str
+ 分钟内,可以自由编辑。
+ .sep5
+ %strong
+ 距离本主题的编辑权限关闭还有
+ %span.orange= (@article.created_at + Siteconf.topic_editable_period - Time.now).round
+ 秒
+ - content_for :template_js do
+ :plain
+ var second = parseInt($("span.orange").text());
+ var countdown_id = setInterval(function() {
+ if (second == 0) {
+ $('#Rightbar strong').text('此主题编辑权限已经关闭');
+ clearInterval(countdown_id);
+ return;
+ }
+ second = second - 1;
+ $("span.orange").text(second);
+ }, 1000);
+.box
+ .cell
+ = build_navigation([link_to(@guide.title,@guide), link_to(@article.title,guide_article_path(@guide,@article))])
+ .cell
+ = render 'form', :guide => @guide, :article => @article, :comments_closed => @article.comments_closed?
diff --git a/app/views/articles/edit_title.js.haml b/app/views/articles/edit_title.js.haml
new file mode 100644
index 0000000..9c06c83
--- /dev/null
+++ b/app/views/articles/edit_title.js.haml
@@ -0,0 +1 @@
+$.facebox("#{escape_javascript(render('title_form'))}")
diff --git a/app/views/articles/index.atom.builder b/app/views/articles/index.atom.builder
new file mode 100644
index 0000000..dacb1b2
--- /dev/null
+++ b/app/views/articles/index.atom.builder
@@ -0,0 +1,18 @@
+atom_feed :language => 'zh-CN' do |feed|
+ feed.title Siteconf.site_name
+ feed.updated @last_update
+
+ @feed_items.each do |item|
+ topic_url = t_url(item.id)
+ feed.entry(item, :url => topic_url ) do |entry|
+ entry.url topic_url
+ entry.title item.title
+ entry.content parse_markdown(item.content), :type => 'html'
+ entry.updated item.updated_at.strftime("%Y-%m-%dT%H:%M:%SZ")
+
+ entry.author do |author|
+ author.name item.user.nickname
+ end
+ end
+ end
+end
diff --git a/app/views/articles/index.html.erb b/app/views/articles/index.html.erb
deleted file mode 100644
index 0b86434..0000000
--- a/app/views/articles/index.html.erb
+++ /dev/null
@@ -1,59 +0,0 @@
-
-<% content_for(:title) { "MakerLab:"+@guide.title.to_s } %>
-
-
- <%= link_to "All",root_path %> >
- <%= link_to @category.title, @category %> >
- <%= link_to @guide.title, @guide %>
-
-
-
-
-
-<% @articles.each do |article| %>
-
-
<%= article.title %>
-
- By <%= link_to @guide.user.name, @guide.user %>
-
-
-
- <%= markdown(article.content) %>
-
-<% end %>
-
-
-
diff --git a/app/views/articles/index.html.haml b/app/views/articles/index.html.haml
new file mode 100644
index 0000000..04e643d
--- /dev/null
+++ b/app/views/articles/index.html.haml
@@ -0,0 +1,4 @@
+= render 'guides/show/guide_sidebar'
+.box
+ .cell= build_navigation([@title])
+ = render @articles
diff --git a/app/views/articles/move.js.haml b/app/views/articles/move.js.haml
new file mode 100644
index 0000000..1264037
--- /dev/null
+++ b/app/views/articles/move.js.haml
@@ -0,0 +1 @@
+$.facebox("#{escape_javascript(render('move_form'))}")
diff --git a/app/views/articles/new.html.erb b/app/views/articles/new.html.erb
deleted file mode 100644
index 1d191ab..0000000
--- a/app/views/articles/new.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-New article
-
-<%= render 'form' %>
-
-<%= link_to 'Back', guide_articles_path %>
diff --git a/app/views/articles/new.html.haml b/app/views/articles/new.html.haml
new file mode 100644
index 0000000..99ce6d7
--- /dev/null
+++ b/app/views/articles/new.html.haml
@@ -0,0 +1,7 @@
+= render 'guides/show/guide_sidebar'
+.box
+ .cell
+ = build_navigation [link_to(@guide.title, @guide)]
+ .inner
+ %h2 创建新文章
+ = render 'form', :guide => @guide, :article => @article, :comments_closed => false
diff --git a/app/views/articles/new.mobile.haml b/app/views/articles/new.mobile.haml
new file mode 100644
index 0000000..e1ed7aa
--- /dev/null
+++ b/app/views/articles/new.mobile.haml
@@ -0,0 +1,14 @@
+- add_breadcrumb link_to(@node.name, go_path(@node.key), :class => :black)
+- add_breadcrumb '新建主题'
+.cell
+ = form_for [@node, @topic] do |f|
+ = f.text_field :title, :class => :sll
+ .sep5
+ = f.text_area :content, :class => :mll
+ .sep5
+ - if current_user.can_manage_site?
+ = check_box_tag :comments_closed
+ = label_tag :comments_closed, '禁止回复'
+ .sep5
+ = f.submit '创建新主题', :class => :btn
+
diff --git a/app/views/articles/new_from_home.html.slim b/app/views/articles/new_from_home.html.slim
new file mode 100644
index 0000000..e442cc7
--- /dev/null
+++ b/app/views/articles/new_from_home.html.slim
@@ -0,0 +1,17 @@
+.box
+ .cell
+ h2 创建新话题
+ .inner
+ = simple_form_for @topic, :url => create_from_home_topics_path do |f|
+ = f.input :title, :label => '标题', :input_html => {:maxlength => 150, :class => :span6}
+ = render 'shared/fileupload_widget', :textarea_selector => "textarea#topic_content"
+ = render 'shared/preview_widget', :ref => :topic_content, :type => :topic
+ = f.input :content, :input_html => {:rows => 10, :style => 'width: 98%;'}, :label => false, :placeholder => '话题内容'
+ = f.association :node, :label => false, :collection => Plane.all, :as => :grouped_select, :group_method => :nodes, :prompt => '选择一个节点'
+ - if current_user.can_manage_site?
+ .checkbox
+ = f.input :sticky, :inline_label => '保持置顶', :label => false
+ .checkbox
+ = f.input :comments_closed, :inline_label => '禁止回复', :label => false
+ = f.submit '创建新话题', :class => 'btn btn-primary btn-inverse', :data => {:disable_with => t('tips.submitting')}
+
diff --git a/app/views/articles/preview.text.haml b/app/views/articles/preview.text.haml
new file mode 100644
index 0000000..4f96708
--- /dev/null
+++ b/app/views/articles/preview.text.haml
@@ -0,0 +1,2 @@
+- format_method = "format_#{@type}"
+= send(format_method, @content)
diff --git a/app/views/articles/show.html.erb b/app/views/articles/show.html.erb
deleted file mode 100644
index 677082a..0000000
--- a/app/views/articles/show.html.erb
+++ /dev/null
@@ -1,89 +0,0 @@
-
-<% content_for(:title) { "MakerLab:"+@guide.title.to_s + "-" + @article.title.to_s } %>
-
-
- <%= link_to "All",root_path %> >
- <%= link_to @category.title, @category %> >
- <%= link_to @guide.title, @guide %> >
- <%= link_to @article.title, [@guide,@article] %>
-
-
-
-
-
-
-
-
<%= @articles.find(params[:id]).title %>
-
- <% if can? :update, @guide %>
- <%= link_to 'Edit this article', edit_guide_article_path(@guide,@article) %> |
- <% end %>
- By <%= link_to @guide.user.name, @guide.user %>
-
-
-
-
- <%= markdown(@articles.find(params[:id]).content) %>
-
-
-
-
- <%= link_to "< " + @pre_article.title,[@guide,@pre_article] if @pre_article %>
-
-
- <%= link_to @next_article.title + " >",[@guide,@next_article] if @next_article %>
-
-
-
- <%= "last edited at : " + @article.updated_at.to_s %>
-
-
-
-
-
-
-
-
diff --git a/app/views/articles/show.html.haml b/app/views/articles/show.html.haml
new file mode 100644
index 0000000..715cbe0
--- /dev/null
+++ b/app/views/articles/show.html.haml
@@ -0,0 +1,58 @@
+= render 'guides/show/guide_sidebar'
+= render 'topics/show/manage'
+
+- content_for :template_js do
+ :plain
+ var creating_comment = false;
+
+ $("textarea#comment_content").keydown(function(e) {
+ if (e.ctrlKey && e.keyCode == 13) {
+ if (creating_comment) return;
+ creating_comment = true
+ $("input[type=submit]").click();
+ }
+ });
+
+- article_user = @article.cached_assoc_object(:user)
+
+.box
+ %article
+ .header
+ .pull-right
+ = user_profile_avatar_link(article_user, :large)
+ = build_navigation([link_to("学习系统", guides_path, :class => :rabel),link_to(@guide.category.title, "#{guides_url}?c=#{@guide.category.id}", :class => :rabel),link_to(@guide.title,@guide, :class => :rabel)], 'bigger')
+ .sep10
+ %h1#topic_title
+ = Rabel::Base.make_mention_links(Rabel::Base.h(@article.title)).html_safe
+ %small.topic-meta
+ By
+ = user_profile_link(article_user, :class => :dark)
+ at
+ = time_ago_in_words(@article.created_at)
+ ,
+ = @article.hit
+ 次浏览
+ .clearfix
+ .inner
+ .content.topic_content= format_topic(@article.content)
+ .inner
+ - if @next_article.present?
+ .pull-right
+ = link_to proper_length(@next_article.title, 15), @next_article, :class => :rabel, :rel => :prev
+ %span.guillemet.right-guillemet »
+
+ - if @prev_article.present?
+ .pull-left
+ %span.guillemet.left-guillemet «
+ = link_to proper_length(@prev_article.title, 15), @prev_article, :class => :rabel, :rel => :next
+ .clearfix
+
+ = render 'shared/box_tip', :tip => "本文最后更新于:" + time_ago_in_words(@article.updated_at)
+= render 'articles/show/comments' if @comments.any?
+
+- if @article.comments_closed?
+ = render 'shared/box_tip', :tip => t('tips.comments_closed')
+- elsif @comments.empty?
+ = render 'shared/box_tip', :tip => '目前尚无回复'
+
+= render 'articles/show/comment_form' unless @article.comments_closed?
diff --git a/app/views/articles/show.mobile.haml b/app/views/articles/show.mobile.haml
new file mode 100644
index 0000000..a9840ae
--- /dev/null
+++ b/app/views/articles/show.mobile.haml
@@ -0,0 +1,52 @@
+- add_breadcrumb link_to(@node.name, go_path(@node.key), :class => :black)
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td.avatar{:valign => :top}
+ = user_profile_avatar_link(@topic.user, :medium)
+ %td{:valign => :top, :style => 'padding-left: 5px;'}
+ %h1= @topic.title
+ .sep5
+ %span.fade
+ By
+ = user_profile_link(@topic.user)
+ = time_ago_in_words(@topic.created_at)
+ - if @topic.hit > 0
+ = " - #{@topic.hit}次点击"
+ .sep10
+ = format_topic @topic.content
+.cell
+ .fr
+ - if @total_bookmarks > 0
+ %small.fade= "已有 #{@total_bookmarks} 人收藏"
+
+ - if current_user
+ - if current_user.bookmarked?(@topic)
+ = link_to '取消收藏', current_user.bookmark_of(@topic), :method => :delete
+
+ = image_tag 'heart.png', :align => :top
+ - else
+ = link_to '加入收藏', topic_bookmarks_path(@topic), :method => :post
+
+ %strong.fade
+ - if @topic.comments_closed?
+ = t('tips.comments_closed')
+ - else
+ 共收到
+ = @topic.comments_count
+ 条回复
+
+#replies
+ = render @comments
+ - if @total_pages > 1
+ .cell{:align => :center}
+ = paginate @comments, :param_name => :p, :window => 2
+
+- if user_signed_in? and not @topic.comments_closed?
+ .cell
+ %span.fade 现在就添加一条回复
+ = form_for [@topic, @new_comment] do |f|
+ .sep5
+ = f.text_area :content, :class => :mll
+ .sep5
+ = f.submit '发送'
diff --git a/app/views/articles/show/_bookmark_button.html.haml b/app/views/articles/show/_bookmark_button.html.haml
new file mode 100644
index 0000000..e01f126
--- /dev/null
+++ b/app/views/articles/show/_bookmark_button.html.haml
@@ -0,0 +1,8 @@
+- if user_signed_in?
+ .inner
+ .fr{:align => :right}
+ - if current_user.bookmarked?(@topic)
+ = link_to '取消收藏', current_user.bookmark_of(@topic), :method => :delete, :class => 'op cancel'
+ - else
+ = link_to '加入收藏', topic_bookmarks_path(@topic), :method => :post, :class => :op
+
diff --git a/app/views/articles/show/_bookmarked_users.html.haml b/app/views/articles/show/_bookmarked_users.html.haml
new file mode 100644
index 0000000..2bffce9
--- /dev/null
+++ b/app/views/articles/show/_bookmarked_users.html.haml
@@ -0,0 +1,8 @@
+- if @total_bookmarks > 0
+ - content_for :rightbar do
+ .box
+ .box-header
+ 收藏此话题的成员
+ .inner
+ - @topic.bookmarks.each do |b|
+ = user_profile_avatar_link(b.user, :mini)
diff --git a/app/views/articles/show/_comment_form.html.haml b/app/views/articles/show/_comment_form.html.haml
new file mode 100644
index 0000000..f4cdb4a
--- /dev/null
+++ b/app/views/articles/show/_comment_form.html.haml
@@ -0,0 +1,15 @@
+- if user_signed_in?
+ = form_for [@article, @new_comment] do |f|
+ .box
+ .box-header
+ .fr
+ ⬆
+ = link_to '回到顶部', 'javascript:void(0);', :class => 'dark back_to_top'
+ 现在就添加一条回复
+ .inner
+ = render 'shared/fileupload_widget', :textarea_selector => "textarea#comment_content"
+ = render 'shared/preview_widget', :ref => :comment_content, :type => :comment
+ = f.text_area :content, :rows => 5, :style => 'width: 98%;'
+ .sep10
+ = f.submit '发送', :class => 'btn btn-sm btn-success', :data => {:disable_with => t('tips.submitting')}
+ %small.gray 支持 Ctrl + Enter 快捷键
diff --git a/app/views/articles/show/_comments.html.haml b/app/views/articles/show/_comments.html.haml
new file mode 100644
index 0000000..8e1dd80
--- /dev/null
+++ b/app/views/articles/show/_comments.html.haml
@@ -0,0 +1,14 @@
+%section
+ .box
+ .box-header
+ .fr
+ - if @article.comments_closed?
+ 回复权限关闭
+ - else
+ ⬇
+ = link_to '跳到回复', 'javascript:void(0);', :class => 'dark jump_to_comment'
+ = "#{@total_comments} 回复"
+ #replies{:class => "#{'fix_cell' if @total_pages == 1}"}
+ = render @comments
+ - if @total_pages > 1
+ = paginate @comments, :param_name => :p
diff --git a/app/views/articles/show/_manage.html.haml b/app/views/articles/show/_manage.html.haml
new file mode 100644
index 0000000..2946442
--- /dev/null
+++ b/app/views/articles/show/_manage.html.haml
@@ -0,0 +1,18 @@
+- if can? :update, @article
+ - content_for :rightbar do
+ .box
+ .box-header
+ 话题管理
+ .cell
+ = link_to '修改标题', edit_title_node_topic_path(@node, @topic), :remote => true, :class => :btn
+ = link_to '编辑全部', edit_node_topic_path(@node, @topic), :class => :btn
+ .cell
+ = link_to '移动到新节点', move_node_topic_path(@node, @topic), :remote => true, :class => :btn
+ - if current_user.can_manage_site?
+ .cell
+ - toggle_comments_closed_tip = @topic.comments_closed? ? '允许回复' : '禁止回复'
+ = link_to toggle_comments_closed_tip, topic_toggle_comments_closed_path(@topic), :method => :put, :class => :btn
+ - toggle_sticky_tip = @topic.sticky? ? '取消置顶' : '置顶此话题'
+ = link_to toggle_sticky_tip, topic_toggle_sticky_path(@topic), :method => :put, :class => :btn
+ .inner
+ = link_to '删除此话题', node_topic_path(@node, @topic), :method => :delete, :data => {:confirm => t(:delete_confirm)}, :class => 'btn btn-sm btn-danger'
diff --git a/app/views/articles/update_title.js.haml b/app/views/articles/update_title.js.haml
new file mode 100644
index 0000000..20825d3
--- /dev/null
+++ b/app/views/articles/update_title.js.haml
@@ -0,0 +1,2 @@
+$("#article_title").html("#{escape_javascript(@article.title)}");
+$.facebox.close();
diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb
deleted file mode 100644
index 771d10e..0000000
--- a/app/views/categories/_form.html.erb
+++ /dev/null
@@ -1,11 +0,0 @@
-<%= simple_form_for(@category) do |f| %>
- <%= f.error_notification %>
-
-
- <%= f.input :title %>
-
-
-
- <%= f.button :submit %>
-
-<% end %>
diff --git a/app/views/categories/edit.html.erb b/app/views/categories/edit.html.erb
deleted file mode 100644
index fce22f7..0000000
--- a/app/views/categories/edit.html.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-Editing category
-
-<%= render 'form' %>
-
-<%= link_to 'Show', @category %> |
-<%= link_to 'Back', categories_path %>
diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb
deleted file mode 100644
index f9faa11..0000000
--- a/app/views/categories/index.html.erb
+++ /dev/null
@@ -1,17 +0,0 @@
-Listing categories
-
-<% @categories.each do |category| %>
-
- <%= link_to category.title, category %> |
- <% if can? :update,@categories %>
-
<%= link_to 'Edit', edit_category_path(category) %> |
- <%= link_to 'Destroy', category, method: :delete, data: { confirm: 'Are you sure?' } %>
- <% end %>
-
-<% end %>
-
-
-
-<% if can? :update,@categories %>
- <%= link_to 'New Category', new_category_path %>
-<% end %>
diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb
deleted file mode 100644
index a84b5d3..0000000
--- a/app/views/categories/new.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-New category
-
-<%= render 'form' %>
-
-<%= link_to 'Back', categories_path %>
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
deleted file mode 100644
index 3db6f06..0000000
--- a/app/views/categories/show.html.erb
+++ /dev/null
@@ -1,43 +0,0 @@
-
-<% content_for(:title) { "MakerLab学习系统-"+@category.title.to_s } %>
-
-
- <%= link_to "All",root_path %> >
- <%= @category.title %>
-
-<% if can? :update,@category %>
- <%= link_to 'New Guide', new_guide_path %>
-<% end %>
-
-
- <% @guides.each do |guide| %>
-
- <%= link_to guide do %>
-
<%= guide.title %>
-
<%= guide.subtitle %>
- <%= image_tag guide.img unless guide.img.empty? %>
-
<%= guide.overview %>
- <% end %>
-
- <% end %>
-
-
-
-
-
-
-
-
-<%= link_to 'Edit', edit_category_path(@category) %> |
-<%= link_to 'Back', categories_path %>
diff --git a/app/views/comments/_comment.html.haml b/app/views/comments/_comment.html.haml
new file mode 100644
index 0000000..2ae0470
--- /dev/null
+++ b/app/views/comments/_comment.html.haml
@@ -0,0 +1,28 @@
+- comment_user = comment.cached_assoc_object(:user)
+%article
+ .cell.reply.hoverable{:id => comment.html_id, :class => comment_user.can_manage_site? ? 'admin' : ''}
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td{:valign => :top, :width => 48}
+ = user_profile_avatar_link(comment_user, :medium)
+ %td{:width => 10}
+ %td{:width => :auto, :valign => :top}
+ .fr
+ - if user_signed_in? and current_user.can_manage_site?
+ %small.hover_action
+ = link_to 'EDIT', edit_comment_path(comment), :class => 'rabel edit', :remote => :true
+ = link_to 'DEL', comment_path(comment), :method => :delete, :data => {:confirm => I18n.t(:delete_confirm)}, :class => 'rabel', :remote => true
+ %small.snow
+ - _num = comment_counter + 1
+ - if @total_pages > 1
+ = "##{Siteconf.pagination_comments.to_i * (@current_page.to_i - 1) + _num} -"
+ - else
+ = "##{_num} -"
+ = time_ago_in_words(comment.created_at)
+ - if user_signed_in?
+ = image_tag 'reply_button.png', :align => :absmiddle, :border => 0, :class => 'clickable mention_button', :data => {:mention => "@#{comment_user.nickname}"}
+ = user_profile_link(comment_user, :class => :dark)
+ = show_posting_device(comment)
+ .sep5
+ .content.reply_content= format_comment comment.content
+
diff --git a/app/views/comments/_comment.mobile.haml b/app/views/comments/_comment.mobile.haml
new file mode 100644
index 0000000..7634c15
--- /dev/null
+++ b/app/views/comments/_comment.mobile.haml
@@ -0,0 +1,19 @@
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td.avatar_mini{:valign => :top}
+ = mini_avatar(comment.user)
+ %td{:style => 'padding-left: 5px;', :valign => :top}
+ .fr
+ %span.ago
+ - _num = comment_counter + 1
+ - if @total_pages > 1
+ = "##{Siteconf.pagination_comments.to_i * (@page_num.to_i - 1) + _num} -"
+ - else
+ = "##{_num} -"
+ = time_ago_in_words(comment.created_at)
+ = user_profile_link(comment.user)
+ = show_posting_device(comment)
+ .sep5
+ = format_comment comment.content
+
diff --git a/app/views/comments/_form.js.haml b/app/views/comments/_form.js.haml
new file mode 100644
index 0000000..2655818
--- /dev/null
+++ b/app/views/comments/_form.js.haml
@@ -0,0 +1,5 @@
+= simple_form_for @comment, :remote => true do |f|
+ %legend 修改回复
+ = f.input :content, :input_html => {:autofocus => true, :rows => 3, :class => :span5}, :label => false
+ .form-actions
+ = f.submit '保存', :class => 'btn btn-small btn-primary'
diff --git a/app/views/comments/_profile_comment.html.haml b/app/views/comments/_profile_comment.html.haml
new file mode 100644
index 0000000..3ae4097
--- /dev/null
+++ b/app/views/comments/_profile_comment.html.haml
@@ -0,0 +1,14 @@
+- commentable = comment.commentable
+.cell.comment_header.muted
+ .pull-right.timeago
+ = time_ago_in_words(comment.created_at)
+ 回复了
+ = user_profile_link commentable.user
+ 创建的话题
+ %span.chevron ›
+ = link_to commentable.notifiable_title, commentable.notifiable_path, :class => :rabel
+.inner
+ .reply_content
+ = format_content comment.content
+
+.sep5
diff --git a/app/views/comments/destroy.js.haml b/app/views/comments/destroy.js.haml
new file mode 100644
index 0000000..b5dc437
--- /dev/null
+++ b/app/views/comments/destroy.js.haml
@@ -0,0 +1,2 @@
+$("##{@comment.html_id}").effect('highlight');
+$("##{@comment.html_id}").slideUp();
diff --git a/app/views/comments/edit.js.haml b/app/views/comments/edit.js.haml
new file mode 100644
index 0000000..7b9debe
--- /dev/null
+++ b/app/views/comments/edit.js.haml
@@ -0,0 +1,2 @@
+$.facebox("#{escape_javascript(render('form'))}");
+$("textarea").elastic()
diff --git a/app/views/comments/update.js.haml b/app/views/comments/update.js.haml
new file mode 100644
index 0000000..e464a67
--- /dev/null
+++ b/app/views/comments/update.js.haml
@@ -0,0 +1,2 @@
+$("##{@comment.html_id}").find('.reply_content').html('#{escape_javascript(parse_markdown(@comment.content))}');
+$.facebox.close();
diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb
new file mode 100644
index 0000000..b7ae403
--- /dev/null
+++ b/app/views/devise/confirmations/new.html.erb
@@ -0,0 +1,12 @@
+Resend confirmation instructions
+
+<%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %>
+ <%= devise_error_messages! %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.submit "Resend confirmation instructions" %>
+<% end %>
+
+<%= render :partial => "devise/shared/links" %>
\ No newline at end of file
diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb
new file mode 100644
index 0000000..a6ea8ca
--- /dev/null
+++ b/app/views/devise/mailer/confirmation_instructions.html.erb
@@ -0,0 +1,5 @@
+Welcome <%= @resource.email %>!
+
+You can confirm your account through the link below:
+
+<%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %>
diff --git a/app/views/devise/mailer/reset_password_instructions.html.haml b/app/views/devise/mailer/reset_password_instructions.html.haml
new file mode 100644
index 0000000..36df096
--- /dev/null
+++ b/app/views/devise/mailer/reset_password_instructions.html.haml
@@ -0,0 +1,7 @@
+- reset_url = edit_password_url(@resource, :reset_password_token => @resource.reset_password_token)
+Hi #{@resource.nickname}:
+%br/
+%p 请点击下面的链接重新设置你的密码:
+%p=link_to reset_url, reset_url
+%p= Siteconf.site_name
+%p 如果本次密码重设请求不是由你发起,你可以安全地忽略本邮件。
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb
new file mode 100644
index 0000000..2263c21
--- /dev/null
+++ b/app/views/devise/mailer/unlock_instructions.html.erb
@@ -0,0 +1,7 @@
+Hello <%= @resource.email %>!
+
+Your account has been locked due to an excessive amount of unsuccessful sign in attempts.
+
+Click the link below to unlock your account:
+
+<%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %>
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
new file mode 100644
index 0000000..89ccc0d
--- /dev/null
+++ b/app/views/devise/passwords/edit.html.haml
@@ -0,0 +1,24 @@
+- @title = '重新设置密码'
+.box
+ .cell
+ = build_navigation([@title])
+ .inner
+ = form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f|
+ = devise_error_messages!
+ = f.hidden_field :reset_password_token
+ %table.form
+ %tr
+ %td.left
+ = f.label :password, '新密码'
+ %td.right
+ = f.password_field :password, :class => :sl
+ %tr
+ %td.left
+ = f.label :password_confirmation, '请再输入一次'
+ %td.right
+ = f.password_field :password_confirmation, :class => :sl
+ %tr
+ %td.left
+ %td.right
+ = f.submit '继续', :class => 'btn btn-small'
+
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
new file mode 100644
index 0000000..a282e2d
--- /dev/null
+++ b/app/views/devise/passwords/new.html.haml
@@ -0,0 +1,13 @@
+- @title = '重新设置密码'
+.box
+ .cell
+ = build_navigation([@title])
+ .inner
+ = simple_form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post, :class => 'form-horizontal' }) do |f|
+ = f.input :nickname, :label => '用户名', :input_html => {:autofocus => true}
+ = f.input :email, :label => '注册邮箱'
+ .form-actions
+ = f.submit @title, :class => 'btn btn-small btn-primary'
+ .additional
+ 24 小时内,至多可以重新设置密码 2 次。
+
diff --git a/app/views/devise/registrations/_form.html.haml b/app/views/devise/registrations/_form.html.haml
new file mode 100644
index 0000000..4c54ef5
--- /dev/null
+++ b/app/views/devise/registrations/_form.html.haml
@@ -0,0 +1,14 @@
+= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
+ - f.object.captcha = ''
+ = f.input :nickname, :label => '用户名', :input_html => {:autofocus => true}
+ = f.input :email, :label => '电子邮件'
+ = f.input :password, :label => '密码'
+ = f.input :password_confirmation, :label => '密码确认'
+ - if Siteconf.show_captcha?
+ = f.input :captcha, :label => '验证码'
+ .control-group
+ .controls
+ = image_tag captcha_path(:format => :gif), :class => :captcha
+ .form-actions
+ = f.submit '加入社区', :class => 'btn btn-small btn-primary'
+
diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb
index 4ac1775..90b73bd 100644
--- a/app/views/devise/registrations/edit.html.erb
+++ b/app/views/devise/registrations/edit.html.erb
@@ -1,14 +1,25 @@
Edit <%= resource_name.to_s.humanize %>
-<%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => 'form-vertical' }) do |f| %>
- <%= f.error_notification %>
- <%= display_base_errors resource %>
- <%= f.input :name, :autofocus => true %>
- <%= f.input :email, :required => true %>
- <%= f.input :password, :autocomplete => "off", :hint => "leave it blank if you don't want to change it", :required => false %>
- <%= f.input :password_confirmation, :required => false %>
- <%= f.input :current_password, :hint => "we need your current password to confirm your changes", :required => true %>
- <%= f.button :submit, 'Update', :class => 'btn-primary' %>
+
+<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
+ <%= devise_error_messages! %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.label :password %> (leave blank if you don't want to change it)
+ <%= f.password_field :password %>
+
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation %>
+
+ <%= f.label :current_password %> (we need your current password to confirm your changes)
+ <%= f.password_field :current_password %>
+
+ <%= f.submit "Update" %>
<% end %>
+
Cancel my account
-Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :data => { :confirm => "Are you sure?" }, :method => :delete %>.
+
+Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :data => {:confirm => "Are you sure?"}, :method => :delete %>.
+
<%= link_to "Back", :back %>
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb
deleted file mode 100644
index 3772015..0000000
--- a/app/views/devise/registrations/new.html.erb
+++ /dev/null
@@ -1,11 +0,0 @@
-Sign up
-<%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => {:class => 'form-vertical' }) do |f| %>
- <%= f.error_notification %>
- <%= display_base_errors resource %>
- <%= f.input :name, :autofocus => true %>
- <%= f.input :email, :required => true %>
- <%= f.input :password, :required => true %>
- <%= f.input :password_confirmation, :required => true %>
- <%= f.button :submit, 'Sign up', :class => 'btn-primary' %>
-<% end %>
-<%= render "devise/shared/links" %>
diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml
new file mode 100644
index 0000000..cc8b9e3
--- /dev/null
+++ b/app/views/devise/registrations/new.html.haml
@@ -0,0 +1,7 @@
+- @title = '注册'
+- @seo_description = @title
+.box
+ .cell
+ = build_navigation([@title])
+ .inner
+ = render 'devise/registrations/form'
diff --git a/app/views/devise/registrations/new.mobile.haml b/app/views/devise/registrations/new.mobile.haml
new file mode 100644
index 0000000..623b508
--- /dev/null
+++ b/app/views/devise/registrations/new.mobile.haml
@@ -0,0 +1,3 @@
+- add_breadcrumb '新用户注册'
+.cell
+ = render :partial => 'devise/registrations/form', :formats => :html
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb
deleted file mode 100644
index 06d5422..0000000
--- a/app/views/devise/sessions/new.html.erb
+++ /dev/null
@@ -1,8 +0,0 @@
-Sign in
-<%= simple_form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => {:class => 'form-vertical' }) do |f| %>
- <%= f.input :email, :autofocus => true %>
- <%= f.input :password %>
- <%= f.input :remember_me, :as => :boolean if devise_mapping.rememberable? %>
- <%= f.button :submit, "Sign in", :class => 'btn-primary' %>
-<% end %>
-<%= render "devise/shared/links" %>
diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.erb
similarity index 79%
rename from app/views/devise/shared/_links.html.erb
rename to app/views/devise/shared/_links.erb
index 33b1120..d2b23ef 100644
--- a/app/views/devise/shared/_links.html.erb
+++ b/app/views/devise/shared/_links.erb
@@ -2,12 +2,12 @@
<%= link_to "Sign in", new_session_path(resource_name) %>
<% end -%>
-<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
- <%= link_to "Sign up", new_registration_path(resource_name) %>
-<% end -%>
+<%# if devise_mapping.registerable? && controller_name != 'registrations' %>
+ <%# link_to "注册", new_registration_path(resource_name) %>
+<%# end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
- <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+ <%= link_to "找回登录密码", new_password_path(resource_name) %>
<% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb
new file mode 100644
index 0000000..c6cdcfe
--- /dev/null
+++ b/app/views/devise/unlocks/new.html.erb
@@ -0,0 +1,12 @@
+Resend unlock instructions
+
+<%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %>
+ <%= devise_error_messages! %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.submit "Resend unlock instructions" %>
+<% end %>
+
+<%= render :partial => "devise/shared/links" %>
\ No newline at end of file
diff --git a/app/views/downloads/_form.html.erb b/app/views/downloads/_form.html.erb
deleted file mode 100644
index a90971a..0000000
--- a/app/views/downloads/_form.html.erb
+++ /dev/null
@@ -1,12 +0,0 @@
-<%= simple_form_for(@download) do |f| %>
- <%= f.error_notification %>
-
-
- <%= f.input :title %>
- <%= f.input :description %>
-
-
-
- <%= f.button :submit %>
-
-<% end %>
diff --git a/app/views/downloads/edit.html.erb b/app/views/downloads/edit.html.erb
deleted file mode 100644
index 5ae0d79..0000000
--- a/app/views/downloads/edit.html.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-Editing download
-
-<%= render 'form' %>
-
-<%= link_to 'Show', @download %> |
-<%= link_to 'Back', downloads_path %>
diff --git a/app/views/downloads/index.html.erb b/app/views/downloads/index.html.erb
deleted file mode 100644
index 56e2496..0000000
--- a/app/views/downloads/index.html.erb
+++ /dev/null
@@ -1,16 +0,0 @@
-
-<% @downloads.each do |download| %>
- <%= download.title %>
- <%= markdown(download.description) %>
- <% if can? :update,@downloads %>
- <%= link_to 'Show', download %>
- <%= link_to 'Edit', edit_download_path(download) %>
- <%= link_to 'Destroy', download, method: :delete, data: { confirm: 'Are you sure?' } %>
- <% end %>
-<% end %>
-
-
-
-<% if can? :update,@downloads %>
- <%= link_to 'New Download', new_download_path %>
-<% end %>
diff --git a/app/views/downloads/new.html.erb b/app/views/downloads/new.html.erb
deleted file mode 100644
index 6b01afb..0000000
--- a/app/views/downloads/new.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-New download
-
-<%= render 'form' %>
-
-<%= link_to 'Back', downloads_path %>
diff --git a/app/views/downloads/show.html.erb b/app/views/downloads/show.html.erb
deleted file mode 100644
index 96e3e7e..0000000
--- a/app/views/downloads/show.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-<%= notice %>
-
-
- Title:
- <%= @download.title %>
-
-
-
- Description:
- <%= @download.description %>
-
-
-
-<%= link_to 'Edit', edit_download_path(@download) %> |
-<%= link_to 'Back', downloads_path %>
diff --git a/app/views/guides/_back_to_node.html.haml b/app/views/guides/_back_to_node.html.haml
new file mode 100644
index 0000000..15b1353
--- /dev/null
+++ b/app/views/guides/_back_to_node.html.haml
@@ -0,0 +1,4 @@
+%span.fade
+ %span.chevron ‹
+ 返回
+ = link_to node.name, go_path(node.key)
diff --git a/app/views/guides/_complex_guide.html.haml b/app/views/guides/_complex_guide.html.haml
new file mode 100644
index 0000000..4d8a52e
--- /dev/null
+++ b/app/views/guides/_complex_guide.html.haml
@@ -0,0 +1,22 @@
+.box-guide
+ .cell.topic{:class => guide_user.can_manage_site? ? 'admin' : ''}
+ .item_title2
+ %h2.guide_title
+ = link_to guide.title, guide_path(guide), :class => 'rabel topic'
+ %h5.guide-subtitle
+ =link_to guide.subtitle, guide_path(guide)
+ =link_to guide_path(guide) do
+ = image_tag guide.img,:class => "img-thumbnail" unless guide.img.nil?
+ .guide-overview
+ =link_to guide.overview, guide_path(guide)
+ .list-group
+ - guide.articles.each do |article|
+ = render "articles/simple_article", :article_user => guide_user, :article_guide => guide, :article => article, :current_article_id => 0
+ .topic-meta
+ = link_to guide_node.title, "#{guides_url}?c=#{guide_node.id}", :class => :category
+ %span.muted •
+ = user_profile_link(guide_user, :class => :dark)
+ - if user_signed_in? && current_user.can_manage_site?
+ %span.muted •
+ = link_to "编辑",edit_guide_path(guide), :class => :dark
+
diff --git a/app/views/guides/_form.html.erb b/app/views/guides/_form.html.erb
deleted file mode 100644
index fa3fadd..0000000
--- a/app/views/guides/_form.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-<%= simple_form_for(@guide) do |f| %>
- <%= f.error_notification %>
-
-
- <%= f.input :title %>
- <%= f.association :category %>
- <%= f.input :subtitle %>
- <%= f.input :overview, as: :text %>
- <%= f.input :img %>
-
-
-
- <%= f.button :submit %>
-
-<% end %>
diff --git a/app/views/guides/_form.html.haml b/app/views/guides/_form.html.haml
new file mode 100644
index 0000000..8fc9e02
--- /dev/null
+++ b/app/views/guides/_form.html.haml
@@ -0,0 +1,13 @@
+.alert.alert-info 如果标题已经包含你想说的话,内容可以留空
+= simple_form_for guide do |f|
+ %a{:name => 'new_topic'}/
+ = f.input :title, :label => '标题', :input_html => {:maxlength => 150, :class => :span6}
+ = render 'shared/fileupload_widget', :textarea_selector => "textarea#topic_content"
+ = render 'shared/preview_widget', :ref => :topic_content, :type => :topic
+ = f.input :subtitle, :input_html => {:rows => 10, :style => 'width: 98%;'}, :label => false, :placeholder => '话题内容'
+ = f.input :overview,:as => :text, :input_html => {:rows => 10, :style => 'width: 98%;'}, :label => false, :placeholder => '话题内容'
+ = f.input :img, :input_html => {:rows => 10, :style => 'width: 98%;'}, :label => false, :placeholder => '预览图片'
+ = f.association :category, :label => false, :prompt => '选择一个分类'
+ .check_box
+ = f.input :publish, :label => '是否马上发布'
+ = f.submit (guide.new_record? ? '创建新教程' : '提交修改'), :class => 'btn btn-primary', :data => {:disable_with => t('tips.submitting')}
diff --git a/app/views/guides/_guide.html.haml b/app/views/guides/_guide.html.haml
new file mode 100644
index 0000000..0b9eacb
--- /dev/null
+++ b/app/views/guides/_guide.html.haml
@@ -0,0 +1,5 @@
+- guide_user = guide.cached_assoc_object(:user)
+- guide_node = guide.cached_assoc_object(:category)
+- guide_articles = guide.articles
+= render "guides/#{Siteconf.topic_list_style}_guide", :guide_user => guide_user, :guide_node => guide_node, :guide => guide, :guide_articles => guide_articles
+
diff --git a/app/views/guides/_guide.mobile.haml b/app/views/guides/_guide.mobile.haml
new file mode 100644
index 0000000..d6a56cf
--- /dev/null
+++ b/app/views/guides/_guide.mobile.haml
@@ -0,0 +1,18 @@
+- guide_user = guide.user
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td.avatar{:valign => :top}
+ = user_profile_avatar_link(guide_user, :medium)
+ %td{:valign => :top, :style => 'padding-left: 8px;'}
+ %span.fade
+ = user_profile_link(guide_user)
+ in
+ = link_to guide.category.title, guide.category
+ .sep5
+ %span.bigger
+ = link_to guide.title, guide
+ %span.created
+ = time_ago_in_words(guide.created_at)
+
+
diff --git a/app/views/guides/_move_form.js.haml b/app/views/guides/_move_form.js.haml
new file mode 100644
index 0000000..1e192ee
--- /dev/null
+++ b/app/views/guides/_move_form.js.haml
@@ -0,0 +1,6 @@
+= form_for @guide, :remote => :true do |f|
+ = label_tag '移动到新分类'
+ %br
+ = select_tag "new_category_id", options_for_select(Category.all.map { |category| [category.title, category.id] })
+ %br
+ = f.submit '开始移动', :class => 'btn btn-small'
diff --git a/app/views/guides/_profile_guide.html.haml b/app/views/guides/_profile_guide.html.haml
new file mode 100644
index 0000000..243b75b
--- /dev/null
+++ b/app/views/guides/_profile_guide.html.haml
@@ -0,0 +1,13 @@
+- guide_node = guide.category
+.cell.topic{:class => guide_user.can_manage_site? ? 'admin' : ''}
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td{:valign => :middle, :width => :auto}
+ %span.bigger
+ = link_to guide.title, guide, :class => 'rabel topic'
+ .topic-meta
+ = link_to guide_node.title, "#{guides_url}?c=#{guide_node.id}", :class => :node
+ %span.muted •
+ = "最后更新于 "
+ = time_ago_in_words(guide.updated_at)
+ %td{:valign => :middle, :width => 40, :align => :right}
diff --git a/app/views/guides/_profile_guide.mobile.haml b/app/views/guides/_profile_guide.mobile.haml
new file mode 100644
index 0000000..b19f5bc
--- /dev/null
+++ b/app/views/guides/_profile_guide.mobile.haml
@@ -0,0 +1,7 @@
+.cell
+ %table{:cellpadding => 5, :cellspacing => 0, :border => 0}
+ %tr
+ %td{:width => 80, :align => :right}
+ = link_to guide.category.title, guide.catetory
+ %td{:align => :left}
+ = link_to guide.title, topic
diff --git a/app/views/guides/_simple_guide.html.haml b/app/views/guides/_simple_guide.html.haml
new file mode 100644
index 0000000..f041214
--- /dev/null
+++ b/app/views/guides/_simple_guide.html.haml
@@ -0,0 +1,10 @@
+.cell.topic{:class => guide_user.can_manage_site? ? 'admin' : ''}
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td{:valign => :top, :class => :avatar}
+ = user_profile_avatar_link(guide_user, :mini)
+ %td{:valign => :top, :style => 'padding-left: 12px'}
+ .sep3
+ %span.bigger{:style => 'font-size: 16px; line-height: 130%'}
+ = link_to guide.title, guide, :class => 'rabel topic'
+
diff --git a/app/views/guides/_table_view.html.haml b/app/views/guides/_table_view.html.haml
new file mode 100644
index 0000000..5f117bc
--- /dev/null
+++ b/app/views/guides/_table_view.html.haml
@@ -0,0 +1,27 @@
+%table{:cellpadding => 5, :cellspacing => 0, :border => 0, :width => '100%', :class => :topics}
+ %tr
+ %th{:align => :right, :width => 50} 回复
+ %th{:align => :left, :width => :auto} 标题
+ %th{:align => :left, :width => 200, :colspan => 2} 最后回复时间
+ - i = 1
+ - topics.each do |topic|
+ - class_name = (i % 2 == 0) ? 'even' : 'odd'
+ - i += 1
+ %tr
+ %td{:align => :right, :width => 50, :class => "#{class_name} lend"}
+ - comments_count = topic.comments_count
+ - if comments_count > 0
+ %strong
+ %span.green= comments_count
+ - else
+ %span.snow 0
+ %td{:align => :left, :width => :auto, :class => class_name}
+ = link_to topic.title, t_path(topic.id)
+ - last_comment = topic.last_comment
+ - last_comment = topic if last_comment.nil?
+ %td{:align => :left, :width => 80, :class => class_name}
+ = user_profile_link(last_comment.user, :class => :dark)
+ %td{:align => :left, :width => 120, :class => "#{class_name} rend"}
+ %small.fade= last_comment.created_at.strftime("%Y-%m-%d %H:%M:%S")
+
+
diff --git a/app/views/guides/_title_form.html.haml b/app/views/guides/_title_form.html.haml
new file mode 100644
index 0000000..37a0d46
--- /dev/null
+++ b/app/views/guides/_title_form.html.haml
@@ -0,0 +1,7 @@
+= form_for @guide, :remote => true do |f|
+ %a{:name => 'new_guide'}
+ = f.label :title, '标题'
+ .sep5
+ = f.text_area :title, :class => :span4, :rows => 2, :autofocus => true
+ .sep5
+ = f.submit '提交修改', :class => 'btn btn-small', :data => {:disable_with => t('tips.submitting')}
diff --git a/app/views/guides/edit.html.erb b/app/views/guides/edit.html.erb
deleted file mode 100644
index 86826cd..0000000
--- a/app/views/guides/edit.html.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-Editing guide
-
-<%= render 'form' %>
-
-<%= link_to 'Show', @guide %> |
-<%= link_to 'Back', guides_path %>
diff --git a/app/views/guides/edit.html.haml b/app/views/guides/edit.html.haml
new file mode 100644
index 0000000..35187f3
--- /dev/null
+++ b/app/views/guides/edit.html.haml
@@ -0,0 +1,30 @@
+- unless current_user.can_manage_site?
+ .box
+ .cell
+ %span.fade 修改功能指南
+ .inner
+ 在新主题创建后的
+ = Siteconf.topic_editable_period_str
+ 分钟内,可以自由编辑。
+ .sep5
+ %strong
+ 距离本主题的编辑权限关闭还有
+ %span.orange= (@guide.created_at + Siteconf.topic_editable_period - Time.now).round
+ 秒
+ - content_for :template_js do
+ :plain
+ var second = parseInt($("span.orange").text());
+ var countdown_id = setInterval(function() {
+ if (second == 0) {
+ $('#Rightbar strong').text('此主题编辑权限已经关闭');
+ clearInterval(countdown_id);
+ return;
+ }
+ second = second - 1;
+ $("span.orange").text(second);
+ }, 1000);
+.box
+ .cell
+ = build_navigation([@guide])
+ .cell
+ = render 'form', :guide => @guide
diff --git a/app/views/guides/edit_title.js.haml b/app/views/guides/edit_title.js.haml
new file mode 100644
index 0000000..9c06c83
--- /dev/null
+++ b/app/views/guides/edit_title.js.haml
@@ -0,0 +1 @@
+$.facebox("#{escape_javascript(render('title_form'))}")
diff --git a/app/views/guides/index.atom.builder b/app/views/guides/index.atom.builder
new file mode 100644
index 0000000..8e81b26
--- /dev/null
+++ b/app/views/guides/index.atom.builder
@@ -0,0 +1,19 @@
+atom_feed :language => 'zh-CN' do |feed|
+ feed.title Siteconf.site_name
+ feed.updated @last_update
+
+ @feed_items.each do |item|
+ guide_url = guide_article_url(item.guide,item)
+ guide_title = item.guide.title + " - "
+ feed.entry(item, :url => guide_url ) do |entry|
+ entry.url guide_url
+ entry.title guide_title + item.title
+ entry.content parse_markdown(item.content), :type => 'html'
+ entry.updated item.updated_at.strftime("%Y-%m-%dT%H:%M:%SZ")
+
+ entry.author do |author|
+ author.name item.user.nickname
+ end
+ end
+ end
+end
diff --git a/app/views/guides/index.html.erb b/app/views/guides/index.html.erb
deleted file mode 100644
index 8644062..0000000
--- a/app/views/guides/index.html.erb
+++ /dev/null
@@ -1,50 +0,0 @@
-
-<% content_for(:title) { "MakerLab学习系统-列表" } %>
-
-Listing guides
-
- Cateories:
-
-
-<% @categories.each do |category| %>
-
- <%= link_to category.title, category %> |
- <% if can? :update,category %>
-
<%= link_to 'Edit', edit_category_path(category) %> |
- <%= link_to 'Destroy', category, method: :delete, data: { confirm: 'Are you sure?' } %>
- <% end %>
-
-<% end %>
-
-<% if can? :update,@guides %>
- <%= link_to 'New Guide', new_guide_path %>
-<% end %>
-
-
- <% @guides.each do |guide| %>
-
- <%= link_to guide do %>
-
<%= guide.title %>
-
<%= guide.subtitle %>
- <%= image_tag guide.img unless guide.img.empty? %>
-
<%= markdown(guide.overview) %>
- <% end %>
-
- <% end %>
-
-
-
-
-
-
diff --git a/app/views/guides/index.html.haml b/app/views/guides/index.html.haml
new file mode 100644
index 0000000..3164e56
--- /dev/null
+++ b/app/views/guides/index.html.haml
@@ -0,0 +1,27 @@
+- content_for :rightbar do
+ = render 'shared/categories'
+.box
+ .cell
+ = build_navigation([link_to("学习系统",guides_path, :class => :rabel),@title])
+ - if user_signed_in? && current_user.can_manage_site?
+ .pull-right
+ = link_to '创建新教程', new_from_home_guides_path, :class => 'btn btn-success btn-sm btn-new-topic'
+.clearfix
+#masonry-container.clearfix.transitions-enabled.has-gutters
+ = render @guides
+.box
+ .inner{:align => :center}
+ = paginate @guides
+
+:javascript
+ var $container = $('#masonry-container');
+ $container.imagesLoaded( function(){
+
+ $container.masonry({
+ itemSelector: '.box-guide',
+ columnWidth:0,
+ gutterWidth: 5
+ });
+
+ });
+
diff --git a/app/views/guides/move.js.haml b/app/views/guides/move.js.haml
new file mode 100644
index 0000000..1264037
--- /dev/null
+++ b/app/views/guides/move.js.haml
@@ -0,0 +1 @@
+$.facebox("#{escape_javascript(render('move_form'))}")
diff --git a/app/views/guides/new.html.erb b/app/views/guides/new.html.erb
deleted file mode 100644
index 25ad71e..0000000
--- a/app/views/guides/new.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-New guide
-
-<%= render 'form' %>
-
-<%= link_to 'Back', guides_path %>
diff --git a/app/views/guides/new.html.haml b/app/views/guides/new.html.haml
new file mode 100644
index 0000000..95fbec0
--- /dev/null
+++ b/app/views/guides/new.html.haml
@@ -0,0 +1,6 @@
+.box
+ .cell
+ = build_navigation [link_to("学习系统", guides_path)]
+ .inner
+ %h2 创建新教程
+ = render 'form', :guide => @guide, :topic => @guide
diff --git a/app/views/guides/new.mobile.haml b/app/views/guides/new.mobile.haml
new file mode 100644
index 0000000..a6d0f96
--- /dev/null
+++ b/app/views/guides/new.mobile.haml
@@ -0,0 +1,10 @@
+- add_breadcrumb link_to("学习系统", guides_path, :class => :black)
+- add_breadcrumb '新建主题'
+.cell
+ = form_for @guide do |f|
+ = f.text_field :title, :class => :sll
+ .sep5
+ = f.text_area :overview, :class => :mll
+ .sep5
+ = f.submit '创建新教程', :class => :btn
+
diff --git a/app/views/guides/new_from_home.html.slim b/app/views/guides/new_from_home.html.slim
new file mode 100644
index 0000000..b59715e
--- /dev/null
+++ b/app/views/guides/new_from_home.html.slim
@@ -0,0 +1,14 @@
+.box
+ .cell
+ h2 创建新教程
+ .inner
+ = simple_form_for @guide, :url => create_from_home_guides_path do |f|
+ = f.input :title, :label => '标题', :input_html => {:maxlength => 150, :class => :span6}
+ = render 'shared/fileupload_widget', :textarea_selector => "textarea#topic_content"
+ = render 'shared/preview_widget', :ref => :topic_content, :type => :topic
+ = f.input :subtitle, :input_html => {:rows => 10, :style => 'width: 98%;'}, :label => false, :placeholder => '副标题'
+ = f.input :overview, :as => :text, :input_html => {:rows => 10, :style => 'width: 98%;'}, :label => false, :placeholder => '预览'
+ = f.input :img, :input_html => {:rows => 10, :style => 'width: 98%;'}, :label => false, :placeholder => '预览图片'
+ = f.association :category, :label => false, :prompt => '选择一个分类'
+ = f.submit '创建新教程', :class => 'btn btn-primary btn-inverse', :data => {:disable_with => t('tips.submitting')}
+
diff --git a/app/views/guides/preview.text.haml b/app/views/guides/preview.text.haml
new file mode 100644
index 0000000..e05feea
--- /dev/null
+++ b/app/views/guides/preview.text.haml
@@ -0,0 +1,2 @@
+- format_method = "format_#{@type}"
+= send(format_method, @overview)
diff --git a/app/views/guides/show.html.erb b/app/views/guides/show.html.erb
deleted file mode 100644
index e1058dc..0000000
--- a/app/views/guides/show.html.erb
+++ /dev/null
@@ -1,77 +0,0 @@
-
-<% content_for(:title) { @guide.title.to_s } %>
-
-
- <%= link_to "All",root_path %> >
- <%= link_to @category.title, @category %> >
- <%= link_to @guide.title, @guide %>
-
-
-
-
-
-
-
-
<%= @first_article.try(:title) || "Please add new article" %>
-
- <% if can? :update, @guide %>
- <%= link_to 'Edit this article', edit_guide_article_path(@guide,@first_article) if@first_article %> |
- <% end %>
- By <%= link_to @guide.user.name, @guide.user %>
-
-
-
- <%= markdown(@first_article.try(:content)) || "Please add new article" %>
-
-
-
-
- <%= link_to @next_article.title + " >",[@guide,@next_article] if @next_article %>
-
-
-
- <%= "last edited at : " + @first_article.try(:updated_at).to_s %>
-
-
-
-
-
-
diff --git a/app/views/guides/show.html.haml b/app/views/guides/show.html.haml
new file mode 100644
index 0000000..999f36e
--- /dev/null
+++ b/app/views/guides/show.html.haml
@@ -0,0 +1,46 @@
+= render 'guides/show/guide_sidebar'
+= render 'guides/show/manage'
+
+- content_for :template_js do
+ :plain
+ var creating_comment = false;
+
+ $("textarea#comment_content").keydown(function(e) {
+ if (e.ctrlKey && e.keyCode == 13) {
+ if (creating_comment) return;
+ creating_comment = true
+ $("input[type=submit]").click();
+ }
+ });
+
+- guide_user = @guide.cached_assoc_object(:user)
+- first_article = @guide.articles.cached_all().first
+- next_article = first_article.next_article(@guide)
+
+.box
+ %article
+ .header
+ .pull-right
+ = user_profile_avatar_link(guide_user, :large)
+ = build_navigation([link_to("学习系统", guides_path, :class => :rabel),link_to(@category.title, "#{guides_url}?c=#{@category.id}", :class => :rabel),link_to(@guide.title,@guide, :class => :rabel)], 'bigger')
+ .sep10
+ %h1#topic_title
+ = Rabel::Base.make_mention_links(Rabel::Base.h( first_article.try(:title))).html_safe
+ %small.topic-meta
+ By
+ = user_profile_link(guide_user, :class => :dark)
+ at
+ = time_ago_in_words(@guide.created_at)
+ .clearfix
+ .inner
+ .content.topic_content= format_topic(first_article.content)
+ .inner
+ - if next_article.present?
+ .pull-right
+ = link_to proper_length(next_article.title, 15), [@guide,next_article], :class => :rabel, :rel => :prev
+ %span.guillemet.right-guillemet »
+
+ .clearfix
+ = render 'shared/box_tip', :tip => "教程最后更新于:" + time_ago_in_words(@guide.updated_at)
+ = render 'shared/box_tip', :tip => link_to("发表评论或问题",guide_article_path(@guide,first_article)+"#comment_content")
+
diff --git a/app/views/guides/show.mobile.haml b/app/views/guides/show.mobile.haml
new file mode 100644
index 0000000..7ffc76f
--- /dev/null
+++ b/app/views/guides/show.mobile.haml
@@ -0,0 +1,27 @@
+- add_breadcrumb link_to(@category.title, @category, :class => :black)
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td.avatar{:valign => :top}
+ = user_profile_avatar_link(@guide.user, :medium)
+ %td{:valign => :top, :style => 'padding-left: 5px;'}
+ %h1= @guide.title
+ .sep5
+ %span.fade
+ By
+ = user_profile_link(@guide.user)
+ = time_ago_in_words(@guide.created_at)
+ .sep10
+ = format_topic @guide.overview
+.cell
+ .fr
+ - if @total_bookmarks > 0
+ %small.fade= "已有 #{@total_bookmarks} 人收藏"
+
+ - if current_user
+ - if current_user.bookmarked?(@guide)
+ = link_to '取消收藏', current_user.bookmark_of(@guide), :method => :delete
+
+ = image_tag 'heart.png', :align => :top
+ - else
+ = link_to '加入收藏', topic_bookmarks_path(@guide), :method => :post
diff --git a/app/views/guides/show/_bookmark_button.html.haml b/app/views/guides/show/_bookmark_button.html.haml
new file mode 100644
index 0000000..21bbb25
--- /dev/null
+++ b/app/views/guides/show/_bookmark_button.html.haml
@@ -0,0 +1,8 @@
+- if user_signed_in?
+ .inner
+ .fr{:align => :right}
+ - if current_user.bookmarked?(@guide)
+ = link_to '取消收藏', current_user.bookmark_of(@guide), :method => :delete, :class => 'op cancel'
+ - else
+ = link_to '收藏', topic_bookmarks_path(@guide), :method => :post, :class => :op
+
diff --git a/app/views/guides/show/_bookmarked_users.html.haml b/app/views/guides/show/_bookmarked_users.html.haml
new file mode 100644
index 0000000..8ff8fb5
--- /dev/null
+++ b/app/views/guides/show/_bookmarked_users.html.haml
@@ -0,0 +1,8 @@
+- if @total_bookmarks > 0
+ - content_for :rightbar do
+ .box
+ .box-header
+ 收藏此话题的成员
+ .inner
+ - @guide.bookmarks.each do |b|
+ = user_profile_avatar_link(b.user, :mini)
diff --git a/app/views/guides/show/_comment_form.html.haml b/app/views/guides/show/_comment_form.html.haml
new file mode 100644
index 0000000..0dd2418
--- /dev/null
+++ b/app/views/guides/show/_comment_form.html.haml
@@ -0,0 +1,15 @@
+- if user_signed_in?
+ = form_for [@topic, @new_comment] do |f|
+ .box
+ .box-header
+ .fr
+ ⬆
+ = link_to '回到顶部', 'javascript:void(0);', :class => 'dark back_to_top'
+ 现在就添加一条回复
+ .inner
+ = render 'shared/fileupload_widget', :textarea_selector => "textarea#comment_content"
+ = render 'shared/preview_widget', :ref => :comment_content, :type => :comment
+ = f.text_area :content, :rows => 5, :style => 'width: 98%;'
+ .sep10
+ = f.submit '发送', :class => 'btn btn-small', :data => {:disable_with => t('tips.submitting')}
+ %small.gray 支持 Ctrl + Enter 快捷键
diff --git a/app/views/guides/show/_comments.html.haml b/app/views/guides/show/_comments.html.haml
new file mode 100644
index 0000000..dcaee67
--- /dev/null
+++ b/app/views/guides/show/_comments.html.haml
@@ -0,0 +1,14 @@
+%section
+ .box
+ .box-header
+ .fr
+ - if @topic.comments_closed?
+ 回复权限关闭
+ - else
+ ⬇
+ = link_to '跳到回复', 'javascript:void(0);', :class => 'dark jump_to_comment'
+ = "#{@total_comments} 回复"
+ #replies{:class => "#{'fix_cell' if @total_pages == 1}"}
+ = render @comments
+ - if @total_pages > 1
+ = paginate @comments, :param_name => :p
diff --git a/app/views/guides/show/_guide_sidebar.html.haml b/app/views/guides/show/_guide_sidebar.html.haml
new file mode 100644
index 0000000..aaecf3a
--- /dev/null
+++ b/app/views/guides/show/_guide_sidebar.html.haml
@@ -0,0 +1,20 @@
+- content_for :rightbar do
+ .box{:data => {:spy => @guide.articles.count >9 ? "":"affix",:offset_top => "80"}}
+ %h2.box-header
+ #{@guide.title}
+ .cell
+ #{@guide.subtitle}
+ - @guide.articles.each do |article|
+ = render "articles/simple_article", :article_user => @guide.user, :article_guide => @guide, :article => article, :current_article_id => @current_article_id
+ .list-group
+ = link_to "单页显示",guide_articles_path(@guide), :class => "list-group-item"
+ - if user_signed_in? && current_user.can_manage_site?
+ = link_to "创建新文章",new_guide_article_path(@guide), :class => "list-group-item"
+ - if user_signed_in?
+ - if current_user.bookmarked?(@guide)
+ = link_to " 取消收藏 #{@guide.bookmarks.count} ".html_safe, current_user.bookmark_of(@guide), :method => :delete, :class => 'btn btn-default btn-lg btn-block'
+ - else
+ = link_to " 收藏此教程 #{@guide.bookmarks.count} ".html_safe, guide_bookmarks_path(@guide), :method => :post, :class => 'btn btn-info btn-lg btn-block'
+ = link_to " 有疑问?去论坛发布话题".html_safe, new_from_home_topics_path, :target => :_blank, :class => 'btn btn-info btn-lg btn-block'
+ - else
+ = link_to " 收藏此教程 #{@guide.bookmarks.count} ".html_safe, new_user_session_path, :class => 'btn btn-primary btn-lg btn-block'
diff --git a/app/views/guides/show/_manage.html.haml b/app/views/guides/show/_manage.html.haml
new file mode 100644
index 0000000..d370906
--- /dev/null
+++ b/app/views/guides/show/_manage.html.haml
@@ -0,0 +1,12 @@
+- if can? :update, @guide
+ - content_for :rightbar do
+ .box
+ .box-header
+ 教程管理
+ .cell
+ = link_to '修改标题', edit_title_guide_path( @guide), :remote => true, :class => :btn
+ = link_to '编辑全部', edit_guide_path(@guide), :class => :btn
+ .cell
+ = link_to '移动到新分类', move_guide_path(@guide), :remote => true, :class => :btn
+ .inner
+ = link_to '删除此话题', guide_path(@guide), :method => :delete, :data => {:confirm => t(:delete_confirm)}, :class => 'btn btn-sm btn-danger'
diff --git a/app/views/guides/update_title.js.haml b/app/views/guides/update_title.js.haml
new file mode 100644
index 0000000..c538fd3
--- /dev/null
+++ b/app/views/guides/update_title.js.haml
@@ -0,0 +1,2 @@
+$("#guide_title").html("#{escape_javascript(@guide.title)}");
+$.facebox.close();
diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb
deleted file mode 100644
index 802b0d8..0000000
--- a/app/views/home/index.html.erb
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- STduino系列产品是基于STM32 ARM芯片的与Arduino系列开发板在编程语言、硬件接口、编程IDE都兼容的开发板。
-
-
-
-
-
-
-
-
-
-
-
- 没有任何电子和编程基础?没问题,我们的图文、视频教程,让你在没有任何基础的情况下也可以快速入门,开始自己下创意项目。
-
-
-
-
-
-
-
-
-
-
-
- MakerLab社区:真正属于创客们的社区,你可以发表你的想法和问题。分享的创意和项目。跟与你一样的创客们交流。
-
-
-
-
-
-
-
-
-
-
diff --git a/app/views/kaminari/_first_page.html.haml b/app/views/kaminari/_first_page.html.haml
new file mode 100644
index 0000000..2c79293
--- /dev/null
+++ b/app/views/kaminari/_first_page.html.haml
@@ -0,0 +1,9 @@
+-# Link to the "First" page
+-# available local variables
+-# url: url to the first page
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%li.first
+ = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, :remote => remote, :class => :rabel
diff --git a/app/views/kaminari/_first_page.mobile.haml b/app/views/kaminari/_first_page.mobile.haml
new file mode 100644
index 0000000..fee8112
--- /dev/null
+++ b/app/views/kaminari/_first_page.mobile.haml
@@ -0,0 +1,9 @@
+-# Link to the "First" page
+-# available local variables
+-# url: url to the first page
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%span.first
+ = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, :remote => remote
diff --git a/app/views/kaminari/_gap.html.haml b/app/views/kaminari/_gap.html.haml
new file mode 100644
index 0000000..1ff7dc2
--- /dev/null
+++ b/app/views/kaminari/_gap.html.haml
@@ -0,0 +1,9 @@
+-# Non-link tag that stands for skipped pages...
+-# available local variables
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%li
+ %span.page.gap
+ = raw(t 'views.pagination.truncate')
diff --git a/app/views/kaminari/_gap.mobile.haml b/app/views/kaminari/_gap.mobile.haml
new file mode 100644
index 0000000..f82f185
--- /dev/null
+++ b/app/views/kaminari/_gap.mobile.haml
@@ -0,0 +1,8 @@
+-# Non-link tag that stands for skipped pages...
+-# available local variables
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%span.page.gap
+ = raw(t 'views.pagination.truncate')
diff --git a/app/views/kaminari/_last_page.html.haml b/app/views/kaminari/_last_page.html.haml
new file mode 100644
index 0000000..d0097bf
--- /dev/null
+++ b/app/views/kaminari/_last_page.html.haml
@@ -0,0 +1,9 @@
+-# Link to the "Last" page
+-# available local variables
+-# url: url to the last page
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%li.last
+ = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {:remote => remote, :class => :rabel}
diff --git a/app/views/kaminari/_last_page.mobile.haml b/app/views/kaminari/_last_page.mobile.haml
new file mode 100644
index 0000000..6e41d23
--- /dev/null
+++ b/app/views/kaminari/_last_page.mobile.haml
@@ -0,0 +1,9 @@
+-# Link to the "Last" page
+-# available local variables
+-# url: url to the last page
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%span.last
+ = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {:remote => remote}
diff --git a/app/views/kaminari/_next_page.html.haml b/app/views/kaminari/_next_page.html.haml
new file mode 100644
index 0000000..a59ed0f
--- /dev/null
+++ b/app/views/kaminari/_next_page.html.haml
@@ -0,0 +1,9 @@
+-# Link to the "Next" page
+-# available local variables
+-# url: url to the next page
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%li.next
+ = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, :rel => 'next', :remote => remote, :class => :rabel
diff --git a/app/views/kaminari/_next_page.mobile.haml b/app/views/kaminari/_next_page.mobile.haml
new file mode 100644
index 0000000..e87ab4e
--- /dev/null
+++ b/app/views/kaminari/_next_page.mobile.haml
@@ -0,0 +1,9 @@
+-# Link to the "Next" page
+-# available local variables
+-# url: url to the next page
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%span.next
+ = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, :rel => 'next', :remote => remote
diff --git a/app/views/kaminari/_page.html.haml b/app/views/kaminari/_page.html.haml
new file mode 100644
index 0000000..8774f07
--- /dev/null
+++ b/app/views/kaminari/_page.html.haml
@@ -0,0 +1,14 @@
+-# Link showing page number
+-# available local variables
+-# page: a page object for "this" page
+-# url: url to this page
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+- if page.current?
+ %li.active
+ %span= page
+- else
+ %li
+ = link_to page, url, {:remote => remote, :rel => page.next? ? 'next' : page.prev? ? 'prev' : nil, :class => :rabel}
diff --git a/app/views/kaminari/_page.mobile.haml b/app/views/kaminari/_page.mobile.haml
new file mode 100644
index 0000000..55c85ea
--- /dev/null
+++ b/app/views/kaminari/_page.mobile.haml
@@ -0,0 +1,10 @@
+-# Link showing page number
+-# available local variables
+-# page: a page object for "this" page
+-# url: url to this page
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%span{:class => "k_page#{' current' if page.current?}"}
+ = link_to_unless page.current?, page, url, {:remote => remote, :rel => page.next? ? 'next' : page.prev? ? 'prev' : nil}
diff --git a/app/views/kaminari/_paginator.html.haml b/app/views/kaminari/_paginator.html.haml
new file mode 100644
index 0000000..75af9bf
--- /dev/null
+++ b/app/views/kaminari/_paginator.html.haml
@@ -0,0 +1,19 @@
+-# The container tag
+-# available local variables
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+-# paginator: the paginator that renders the pagination tags inside
+= paginator.render do
+ .pagination.pagination-centered.pagination-small
+ %ul
+ = first_page_tag unless current_page.first?
+ = prev_page_tag unless current_page.first?
+ - each_page do |page|
+ - if page.left_outer? || page.right_outer? || page.inside_window?
+ = page_tag page
+ - elsif !page.was_truncated?
+ = gap_tag
+ = next_page_tag unless current_page.last?
+ = last_page_tag unless current_page.last?
diff --git a/app/views/kaminari/_paginator.mobile.haml b/app/views/kaminari/_paginator.mobile.haml
new file mode 100644
index 0000000..3ebb206
--- /dev/null
+++ b/app/views/kaminari/_paginator.mobile.haml
@@ -0,0 +1,18 @@
+-# The container tag
+-# available local variables
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+-# paginator: the paginator that renders the pagination tags inside
+= paginator.render do
+ .pagination
+ = first_page_tag unless current_page.first?
+ = prev_page_tag unless current_page.first?
+ - each_page do |page|
+ - if page.left_outer? || page.right_outer? || page.inside_window?
+ = page_tag page
+ - elsif !page.was_truncated?
+ = gap_tag
+ = next_page_tag unless current_page.last?
+ = last_page_tag unless current_page.last?
diff --git a/app/views/kaminari/_prev_page.html.haml b/app/views/kaminari/_prev_page.html.haml
new file mode 100644
index 0000000..e67af82
--- /dev/null
+++ b/app/views/kaminari/_prev_page.html.haml
@@ -0,0 +1,9 @@
+-# Link to the "Previous" page
+-# available local variables
+-# url: url to the previous page
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%li.prev
+ = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, :rel => 'prev', :remote => remote, :class => :rabel
diff --git a/app/views/kaminari/_prev_page.mobile.haml b/app/views/kaminari/_prev_page.mobile.haml
new file mode 100644
index 0000000..13f0d8a
--- /dev/null
+++ b/app/views/kaminari/_prev_page.mobile.haml
@@ -0,0 +1,9 @@
+-# Link to the "Previous" page
+-# available local variables
+-# url: url to the previous page
+-# current_page: a page object for the currently displayed page
+-# num_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%span.prev
+ = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, :rel => 'prev', :remote => remote
diff --git a/app/views/layouts/_messages.html.erb b/app/views/layouts/_messages.html.erb
deleted file mode 100644
index 060b04b..0000000
--- a/app/views/layouts/_messages.html.erb
+++ /dev/null
@@ -1,8 +0,0 @@
-<% flash.each do |name, msg| %>
- <% if msg.is_a?(String) %>
- ">
-
×
- <%= content_tag :div, msg, :id => "flash_#{name}" %>
-
- <% end %>
-<% end %>
diff --git a/app/views/layouts/_navigation.html.erb b/app/views/layouts/_navigation.html.erb
deleted file mode 100644
index 0fa16ea..0000000
--- a/app/views/layouts/_navigation.html.erb
+++ /dev/null
@@ -1,41 +0,0 @@
-<%= link_to "MakerLab创客实验室", "http://www.makerlab.me", :class => 'brand' %>
-
-
- 首页
-
-
- 学习
-
-
- 产品
-
-
- 下载
-
-
- 论坛
-
- <% if user_signed_in? %>
-
- <%= link_to 'Logout', destroy_user_session_path, :method=>'delete' %>
-
- <% elsif false %>
-
- <%= link_to 'Login', new_user_session_path %>
-
- <% end %>
- <% if user_signed_in? %>
-
- <%= link_to 'Edit account', edit_user_registration_path %>
-
- <% if current_user.has_role? :admin %>
-
- <%= link_to 'Admin', users_path %>
-
- <% end %>
- <% elsif false %>
-
- <%= link_to 'Sign up', new_user_registration_path %>
-
- <% end %>
-
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
new file mode 100644
index 0000000..34184ac
--- /dev/null
+++ b/app/views/layouts/admin.html.haml
@@ -0,0 +1,17 @@
+- content_for :content do
+ .col-md-3
+ - dashboard_menu.each do |m|
+ .box.fix_cell
+ .cell
+ %strong.gray= m[:name]
+ - m[:items].each do |item|
+ .cell
+ = image_tag "admin/#{item.second}.png", :align => :absmiddle, :valign => :middle
+
+ = link_to item.first, item.last, :class => (:current_item if @title == item.first)
+ = yield :rightbar
+ .col-md-9
+ = render 'shared/alert'
+ = yield
+
+= render :template => 'layouts/base'
diff --git a/app/views/layouts/app.html.haml b/app/views/layouts/app.html.haml
new file mode 100644
index 0000000..b93bf50
--- /dev/null
+++ b/app/views/layouts/app.html.haml
@@ -0,0 +1,13 @@
+- content_for :content do
+ .col-md-9.col-lg-9.col-sm-8
+ = render 'shared/alert'
+ = yield
+
+- content_for :sidebar do
+ .col-md-3.col-lg-3.col-sm-4#Rightbar
+ = render 'shared/sidebar_box_lms'
+ = yield :rightbar
+ = render :partial => 'shared/ad', :collection => Advertisement.available, :as => :ad
+
+= render :template => 'layouts/base'
+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
deleted file mode 100644
index f34bfe6..0000000
--- a/app/views/layouts/application.html.erb
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
- <%= content_for?(:title) ? yield(:title) : "Maker Lab" %>
- ">
- <%= stylesheet_link_tag "application", :media => "all" %>
- <%= javascript_include_tag "application" %>
- <%= csrf_meta_tags %>
- <%= yield(:head) %>
-
-
-
-
-
- <%= render 'layouts/navigation' %>
-
-
-
-
-
-
-
-
- <%= render 'layouts/messages' %>
- <%= yield %>
-
-
-
-
-
-
-
-
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
new file mode 100644
index 0000000..22b0735
--- /dev/null
+++ b/app/views/layouts/application.html.haml
@@ -0,0 +1,25 @@
+!!! 5
+%html{:lang => 'zh-CN'}
+ %head
+ = render 'shared/head'
+ %body
+ #Top
+ = render 'shared/top', :link_class => :top
+ #Wrapper
+ - if Siteconf.global_banner.present?
+ .sep10
+ #Banner
+ = image_tag Siteconf.global_banner, :alt => :banner
+ .sep10
+ - else
+ #Content
+ #Sidebar
+ #Rightbar
+ = render 'shared/sidebar_box_lms'
+ = yield :rightbar
+ = render :partial => 'shared/ad', :collection => Advertisement.available, :as => :ad
+ #Main
+ = render 'shared/content'
+ .c
+ = render 'shared/footer'
+
diff --git a/app/views/layouts/application.mobile.haml b/app/views/layouts/application.mobile.haml
new file mode 100644
index 0000000..cbf1a5e
--- /dev/null
+++ b/app/views/layouts/application.mobile.haml
@@ -0,0 +1,38 @@
+!!! 5
+%html{:lang => 'zh-CN'}
+ %head
+ = render :partial => 'shared/meta', :formats => :html
+ = stylesheet_link_tag "i_mobile"
+ = javascript_include_tag "application"
+ %body
+ #Top
+ #Member
+ - if user_signed_in?
+ %span.nickname= current_user.nickname
+ = user_profile_avatar_link(current_user, :mini, :class => :icon)
+
+ = link_to settings_path, :class => :icon do
+ #GearIcon
+
+ = link_to destroy_user_session_path, :method => :delete, :class => :icon do
+ #EjectIcon
+ - else
+ = link_to '登入', new_user_session_path, :class => :white
+ |
+ = link_to '注册', new_user_registration_path, :class => :white
+ = link_to Siteconf.site_name, root_path, :id => :Logo
+ #Main
+ - if @unread_count > 0 and @show_notification_count
+ .section 提醒
+ .cell
+ = image_tag 'dot_orange.png', :class => :dot_icon, :align => :absmiddle
+ %strong
+ = link_to "#{@unread_count} 条未读提醒", notifications_path, :class => :notification
+ - if @breadcrumbs.length > 1
+ .section
+ .fr= yield :nav_right
+ = build_breadcrumbs
+ = yield
+ .cell_bottom
+ = Siteconf.mobile_footer.html_safe
+ = render :partial => 'shared/google_analytics', :formats => :html
diff --git a/app/views/layouts/base.html.slim b/app/views/layouts/base.html.slim
new file mode 100644
index 0000000..d230fff
--- /dev/null
+++ b/app/views/layouts/base.html.slim
@@ -0,0 +1,63 @@
+doctype html
+html
+ head
+ = render 'shared/meta'
+ = stylesheet_link_tag 'application', :media => 'all'
+ = yield :head
+ - if Siteconf.theme != 'default'
+ = stylesheet_link_tag "#{Siteconf.theme}/i_theme"
+ - if Siteconf.custom_css.present?
+ style type='text/css' = Siteconf.custom_css.html_safe
+ = javascript_include_tag 'application'
+ link rel='apple-touch-icon' href='/apple-touch-icon.png'
+
+ = yield :final_head
+ body#rabel
+ .navbar.navbar-default
+ .container
+ .navbar-header
+ button.navbar-toggle(
+ data-toggle='collapse'
+ data-target='#navbar-collapse')
+ span.icon-bar
+ span.icon-bar
+ span.icon-bar
+ - if Siteconf.custom_logo.present?
+ = link_to root_path, :class => :custom_brand do
+ = image_tag Siteconf.custom_logo, :class => :custom_logo, :alt => Siteconf.site_name
+ - else
+ = link_to Siteconf.site_name, root_path, class: 'navbar-brand'
+
+ .collapse.navbar-collapse#navbar-collapse
+ ul.nav.navbar-nav.navbar-left
+ = nav_item(t(:homepage), root_path)
+ = nav_item("学习", guides_path)
+ = nav_item("论坛", topics_path)
+ = nav_item("活跃会员", users_path)
+ form.navbar-form.navbar-left
+ .form-group
+ input#q.form-control type='text' name='q' maxlength=40 data-domain=ENV['RABEL_HOST_NAME'] placeholder=t(:search)
+ ul.nav.navbar-nav.navbar-right
+ - if user_signed_in?
+ - if current_user.can_manage_site?
+ li= nav_item t(:dashboard), admin_root_path
+ li
+ =link_to ("0? "danger":"default"}\">#{@unread_count} ").html_safe,notifications_path, :title => "您收到的消息"
+ li.dropdown
+ =link_to ("#{current_user.nickname}"+content_tag(:span,"",class:"caret")).html_safe,(member_path(url_encode(current_user.nickname))), :data => {:toggle=>"dropdown",:target => "#"}
+ ul.dropdown-menu
+ li= nav_item "我的收藏 #{current_user.bookmarks.count} ".html_safe, my_topics_path(current_user.nickname)
+ li= nav_item "个人主页", member_path(url_encode(current_user.nickname))
+ li= nav_item '个人设置', settings_path
+ li= link_to t(:sign_out), destroy_user_session_path, :method => :delete
+ - else
+ = nav_item(t(:sign_up), new_user_registration_path)
+ = nav_item(t(:sign_in), new_user_session_path)
+
+ #wrap
+ .container#page-main
+ .row
+ = yield :sidebar
+ = content_for?(:content) ? yield(:content) : yield
+ = render 'shared/footer'
+
diff --git a/app/views/layouts/welcome.html.haml b/app/views/layouts/welcome.html.haml
new file mode 100644
index 0000000..72f6680
--- /dev/null
+++ b/app/views/layouts/welcome.html.haml
@@ -0,0 +1,13 @@
+- content_for :content do
+ .col-md-6.col-lg-6.col-sm-6
+ = render 'shared/alert'
+ = yield
+
+- content_for :sidebar do
+ .col-md-6.col-lg-6.col-sm-6#Rightbar
+ = render 'shared/sidebar_box_lms'
+ = yield :rightbar
+ = render :partial => 'shared/ad', :collection => Advertisement.available, :as => :ad
+
+= render :template => 'layouts/base'
+
diff --git a/app/views/nodes/_bookmark_node.html.haml b/app/views/nodes/_bookmark_node.html.haml
new file mode 100644
index 0000000..3b19a9f
--- /dev/null
+++ b/app/views/nodes/_bookmark_node.html.haml
@@ -0,0 +1,6 @@
+%tr
+ %td{:width => '50', :align => :right, :valign => :middle}
+ .howmany= node.topics_count
+ %td{:width => :auto, :align => :left}
+ %h3= link_to node.name, go_path(node.key)
+
diff --git a/app/views/nodes/_bookmark_node.mobile.haml b/app/views/nodes/_bookmark_node.mobile.haml
new file mode 100644
index 0000000..c86348a
--- /dev/null
+++ b/app/views/nodes/_bookmark_node.mobile.haml
@@ -0,0 +1 @@
+.cell= link_to node.name, go_path(node.key)
diff --git a/app/views/nodes/_custom_fields.html.haml b/app/views/nodes/_custom_fields.html.haml
new file mode 100644
index 0000000..972dad1
--- /dev/null
+++ b/app/views/nodes/_custom_fields.html.haml
@@ -0,0 +1,12 @@
+- if node.custom_css.present?
+ - content_for :final_head do
+ %style{:type => 'text/css'}
+ = node.custom_css.html_safe
+
+- if node.custom_html.present?
+ - content_for :rightbar do
+ .box
+ .inner
+ = node.custom_html.html_safe
+
+
diff --git a/app/views/nodes/_item_node.html.haml b/app/views/nodes/_item_node.html.haml
new file mode 100644
index 0000000..60c1449
--- /dev/null
+++ b/app/views/nodes/_item_node.html.haml
@@ -0,0 +1 @@
+= link_to item_node.name, go_path(item_node.key), :class => :item_node
diff --git a/app/views/nodes/_node.html.haml b/app/views/nodes/_node.html.haml
new file mode 100644
index 0000000..b620548
--- /dev/null
+++ b/app/views/nodes/_node.html.haml
@@ -0,0 +1 @@
+= link_to node.name, go_path(node.key), :class => "rabel item_node node_#{node.key}"
diff --git a/app/views/nodes/_node.mobile.haml b/app/views/nodes/_node.mobile.haml
new file mode 100644
index 0000000..7371086
--- /dev/null
+++ b/app/views/nodes/_node.mobile.haml
@@ -0,0 +1,2 @@
+= link_to node.name, go_path(node.key), :style => 'font-size: 14px;'
+
diff --git a/app/views/nodes/_paginator.html.haml b/app/views/nodes/_paginator.html.haml
new file mode 100644
index 0000000..7ec12c9
--- /dev/null
+++ b/app/views/nodes/_paginator.html.haml
@@ -0,0 +1,15 @@
+- if @total_pages > 1
+ %ul.pager
+ - if @prev_page_num > 0
+ %li.previous
+ - if @prev_page_num == 1
+ = link_to '← 上一页', go_path(@node.key)
+ - else
+ = link_to '← 上一页', go_path(@node.key) + "?p=#{@prev_page_num}"
+ %li.center
+ %span.gray= "#{@page_num}/#{@total_pages}"
+
+ - if @next_page_num > 0
+ %li.next
+ = link_to '下一页 →', go_path(@node.key) + "?p=#{@next_page_num}"
+
diff --git a/app/views/nodes/show.html.haml b/app/views/nodes/show.html.haml
new file mode 100644
index 0000000..6b69105
--- /dev/null
+++ b/app/views/nodes/show.html.haml
@@ -0,0 +1,40 @@
+- if user_signed_in?
+ - content_for :template_js do
+ $("a.super").click(function() { $("textarea.mll:first").focus() });
+
+- if user_signed_in? and current_user.can_manage_site?
+ - content_for :rightbar do
+ .box
+ .box-header
+ 节点管理
+ .cell
+ = link_to "管理 #{@node.name} 节点", admin_planes_path + "#!/click/edit_#{@node.html_id}", :class => 'btn btn-sm btn-default'
+ - if @node.quiet
+ .inner
+ = image_tag 'ghost.png', :align => :top, :title => t('tips.quiet_node')
+ = t('tips.quiet_node')
+
+= render 'custom_fields', :node => @node
+
+.box
+ .box-header
+ - if user_signed_in?
+ .fr
+ = link_to '创建新话题', '#new_topic', :class => 'btn btn-sm btn-success'
+ = build_navigation([@node.name], 'bigger')
+ - if @node.introduction.present?
+ .sep10
+ %span.gray= @node.introduction
+ .sep10
+ = render @topics
+ .inner
+ = render 'paginator'
+
+- if user_signed_in?
+ .box
+ .box-header
+ 创建新话题
+ .inner
+ = render 'topics/form', :node => @node, :topic => @node.topics.new, :comments_closed => false
+
+
diff --git a/app/views/nodes/show.mobile.haml b/app/views/nodes/show.mobile.haml
new file mode 100644
index 0000000..f3ad124
--- /dev/null
+++ b/app/views/nodes/show.mobile.haml
@@ -0,0 +1,16 @@
+- content_for :nav_right do
+ = @total_topics
+ 个主题
+
+- if user_signed_in?
+ .cell
+ = link_to new_node_topic_path(@node), :class => :btn do
+ %input{:type => :button, :value => '创建新主题'}
+
+= render @topics
+- if @total_pages > 1
+ .cell{:align => :center}
+ = render :partial => 'paginator', :formats => :html
+
+- if @node.custom_html.present?
+ .cell= @node.custom_html.html_safe
diff --git a/app/views/notifications/_notification.mobile.haml b/app/views/notifications/_notification.mobile.haml
new file mode 100644
index 0000000..143692f
--- /dev/null
+++ b/app/views/notifications/_notification.mobile.haml
@@ -0,0 +1,5 @@
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ = render :partial => 'shared/notification', :formats => :html, :locals => {:notification => notification}
+
+
diff --git a/app/views/notifications/index.html.haml b/app/views/notifications/index.html.haml
new file mode 100644
index 0000000..c54b8a9
--- /dev/null
+++ b/app/views/notifications/index.html.haml
@@ -0,0 +1,7 @@
+- content_for :sidebar do
+ .col-md-3.col-lg-3.col-sm-4#Rightbar
+ = render 'shared/sidebar_box'
+.box
+ .cell
+ = build_navigation([@title])
+ = render :partial => 'shared/notification', :collection => @notifications
diff --git a/app/views/notifications/index.mobile.haml b/app/views/notifications/index.mobile.haml
new file mode 100644
index 0000000..49b506e
--- /dev/null
+++ b/app/views/notifications/index.mobile.haml
@@ -0,0 +1 @@
+= render @notifications
diff --git a/app/views/pages/_nav.html.haml b/app/views/pages/_nav.html.haml
new file mode 100644
index 0000000..b82df16
--- /dev/null
+++ b/app/views/pages/_nav.html.haml
@@ -0,0 +1,6 @@
+- if nav.content.start_with?('http')
+ %li= link_to nav.title, nav.content, :class => 'dark nav', :target => :_blank
+- elsif nav.content.start_with?('/')
+ %li= link_to nav.title, nav.content, :class => 'dark nav'
+- else
+ %li= link_to nav.title, page_path(nav.key), :class => 'dark nav'
diff --git a/app/views/pages/_nav_ruler.html.haml b/app/views/pages/_nav_ruler.html.haml
new file mode 100644
index 0000000..8fc7474
--- /dev/null
+++ b/app/views/pages/_nav_ruler.html.haml
@@ -0,0 +1 @@
+%li.snow ·
diff --git a/app/views/pages/show.html.haml b/app/views/pages/show.html.haml
new file mode 100644
index 0000000..381bb01
--- /dev/null
+++ b/app/views/pages/show.html.haml
@@ -0,0 +1,13 @@
+.box
+ .inner
+ - if current_user && current_user.can_manage_site?
+ .fr
+ = link_to '修改', edit_admin_page_path(@page), :class => 'btn btn-small'
+ .page
+ %article
+ %h1.page-header
+ = @title
+ - unless @page.published
+ %small (草稿)
+ = format_page @page.content
+
diff --git a/app/views/pages/show.mobile.haml b/app/views/pages/show.mobile.haml
new file mode 100644
index 0000000..85eb3e7
--- /dev/null
+++ b/app/views/pages/show.mobile.haml
@@ -0,0 +1,4 @@
+- add_breadcrumb(@title)
+.cell
+ %h3= @title
+ = format_page @page.content
diff --git a/app/views/planes/_plane.html.haml b/app/views/planes/_plane.html.haml
new file mode 100644
index 0000000..ac1ddf8
--- /dev/null
+++ b/app/views/planes/_plane.html.haml
@@ -0,0 +1,7 @@
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td{:align => :right, :width => 100}
+ = plane.name
+ %td{:style => 'line-height: 200%; padding-left: 15px;'}
+ = render plane.cached_assoc_collection(:nodes, Node.default_order_str, 20)
diff --git a/app/views/planes/_plane.mobile.haml b/app/views/planes/_plane.mobile.haml
new file mode 100644
index 0000000..00b758a
--- /dev/null
+++ b/app/views/planes/_plane.mobile.haml
@@ -0,0 +1,7 @@
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0}
+ %tr
+ %td{:align => :right, :width => 60}
+ %span.fade= plane.name
+ %td{:style => "line-height: 200%; padding-left: 10px;"}
+ = render plane.nodes.default_order.limit(20)
diff --git a/app/views/products/_form.html.erb b/app/views/products/_form.html.erb
deleted file mode 100644
index 1f88f65..0000000
--- a/app/views/products/_form.html.erb
+++ /dev/null
@@ -1,13 +0,0 @@
-<%= simple_form_for(@product) do |f| %>
- <%= f.error_notification %>
-
-
- <%= f.input :name %>
- <%= f.input :img %>
- <%= f.input :descrption %>
-
-
-
- <%= f.button :submit %>
-
-<% end %>
diff --git a/app/views/products/edit.html.erb b/app/views/products/edit.html.erb
deleted file mode 100644
index 39f7f4f..0000000
--- a/app/views/products/edit.html.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-Editing product
-
-<%= render 'form' %>
-
-<%= link_to 'Show', @product %> |
-<%= link_to 'Back', products_path %>
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
deleted file mode 100644
index 69bced4..0000000
--- a/app/views/products/index.html.erb
+++ /dev/null
@@ -1,21 +0,0 @@
-Listing products
-
-
-<% @products.each do |product| %>
-
- <%= image_tag product.img unless product.img.empty? %>
-
<%= product.name %>
-
<%= markdown(product.descrption) %>
- <% if can? :update,@products %>
- <%= link_to 'Show', product %>
- <%= link_to 'Edit', edit_product_path(product) %>
- <%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %>
- <% end %>
-
-<% end %>
-
-
-
-<% if can? :update,@products %>
- <%= link_to 'New Product', new_product_path %>
-<% end %>
diff --git a/app/views/products/new.html.erb b/app/views/products/new.html.erb
deleted file mode 100644
index e5e0037..0000000
--- a/app/views/products/new.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-New product
-
-<%= render 'form' %>
-
-<%= link_to 'Back', products_path %>
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb
deleted file mode 100644
index cf45e3e..0000000
--- a/app/views/products/show.html.erb
+++ /dev/null
@@ -1,20 +0,0 @@
-<%= notice %>
-
-
- Name:
- <%= @product.name %>
-
-
-
- Img:
- <%= @product.img %>
-
-
-
- Descrption:
- <%= @product.descrption %>
-
-
-
-<%= link_to 'Edit', edit_product_path(@product) %> |
-<%= link_to 'Back', products_path %>
diff --git a/app/views/sessions/new.html.haml b/app/views/sessions/new.html.haml
new file mode 100644
index 0000000..6a405cd
--- /dev/null
+++ b/app/views/sessions/new.html.haml
@@ -0,0 +1,15 @@
+.box
+ .cell= build_navigation(['登入'])
+ .inner
+ = simple_form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f|
+ = devise_error_messages!
+ = f.input :nickname, :label => '用户名', :input_html => {:autofocus => true}
+ = f.input :password, :label => '密码'
+ - if devise_mapping.rememberable?
+ .hide= f.check_box :remember_me, :checked => true
+ .form-actions
+ = f.submit '登入', :class => 'btn btn-small btn-primary'
+ .additional
+ .gray 登录后 cookie 会被记住一年
+ = render :partial => "devise/shared/links"
+
diff --git a/app/views/sessions/new.mobile.haml b/app/views/sessions/new.mobile.haml
new file mode 100644
index 0000000..68ddc0c
--- /dev/null
+++ b/app/views/sessions/new.mobile.haml
@@ -0,0 +1,24 @@
+- @title = '登入'
+- add_breadcrumb @title
+.cell
+ = form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f|
+ = devise_error_messages!
+ %table{:cellpadding => 5, :cellspacing => 0, :border => 0}
+ %tr
+ %td{:width => 80, :align => :right} 用户名
+ %td{:width => 200, :align => :left}
+ = f.text_field :nickname, :class => :sl, :autofocus => true
+ %tr
+ %td{:width => 80, :align => :right} 密码
+ %td{:width => 200, :align => :left}
+ = f.password_field :password, :class => :sl
+ %tr
+ %td{:width => 80, :align => :right}
+ %td{:width => 200, :align => :left}
+ - if devise_mapping.rememberable?
+ .hide= f.check_box :remember_me, :checked => true
+ = f.submit '登入', :class => 'btn btn-small'
+ %tr
+ %td{:width => 80, :align => :right}
+ %td{:width => 200, :align => :left}
+ %span.fade 登录后 cookie 会被记住一年
diff --git a/app/views/shared/_ad.html.haml b/app/views/shared/_ad.html.haml
new file mode 100644
index 0000000..84a6416
--- /dev/null
+++ b/app/views/shared/_ad.html.haml
@@ -0,0 +1,12 @@
+.box
+ .inner
+ %span{:style => 'font-weight: bold; font-size: 10px; color: #e2e2e2;'} Promotion
+ .sep10
+ = link_to ad.link, :class => :track_event, :target => '_blank', :data => {:category => :ad, :action => :click, :label => ad.title} do
+ = image_tag ad.banner.url, :border => 0, :width => 120, :height => 90
+ .sep5
+ %span{:style => 'font-size: 11px; color: #666;'}
+ %strong{:style => 'color: #000;'}
+ = link_to ad.title, ad.link, :target => '_blank', :class => 'black track_event', :data => {:category => :ad, :action => :click, :label => ad.title}
+ .sep3
+ %span{:style => 'font-size: 12px;'}= ad.words
diff --git a/app/views/shared/_alert.html.haml b/app/views/shared/_alert.html.haml
new file mode 100644
index 0000000..9494c28
--- /dev/null
+++ b/app/views/shared/_alert.html.haml
@@ -0,0 +1,6 @@
+- if flash[:notice].present?
+ %p.alert.alert-info= notice
+- if flash[:success].present?
+ %p.alert.alert-success= flash[:success]
+- if flash[:alert].present?
+ %p.alert.alert-error= flash[:alert]
diff --git a/app/views/shared/_box_tip.html.haml b/app/views/shared/_box_tip.html.haml
new file mode 100644
index 0000000..016d200
--- /dev/null
+++ b/app/views/shared/_box_tip.html.haml
@@ -0,0 +1,3 @@
+.glass
+ .inner.center
+ = tip
diff --git a/app/views/shared/_categories.html.haml b/app/views/shared/_categories.html.haml
new file mode 100644
index 0000000..a48bd07
--- /dev/null
+++ b/app/views/shared/_categories.html.haml
@@ -0,0 +1,6 @@
+.box
+ .box-header
+ 学习教程分类
+ .inner
+ -Category.cached_all().each do |category|
+ =link_to category.title,"#{guides_url}?c=#{category.id}", :class => "rabel item_node"
diff --git a/app/views/shared/_community_stats.html.haml b/app/views/shared/_community_stats.html.haml
new file mode 100644
index 0000000..0b68880
--- /dev/null
+++ b/app/views/shared/_community_stats.html.haml
@@ -0,0 +1,20 @@
+.box
+ .box-header
+ 社区运行状态
+ .inner
+ %table.table.table-striped
+ %tr
+ %td
+ %span.gray 注册会员
+ %td
+ %strong= User.cached_count
+ %tr
+ %td
+ %span.gray 话题
+ %td
+ %strong= Topic.cached_count
+ %tr
+ %td
+ %span.gray 回复
+ %td
+ %strong= Comment.cached_count
diff --git a/app/views/shared/_content.html.haml b/app/views/shared/_content.html.haml
new file mode 100644
index 0000000..802ff29
--- /dev/null
+++ b/app/views/shared/_content.html.haml
@@ -0,0 +1,6 @@
+- if flash_messages.any?
+ .box
+ .inner
+ = show_flash_messages
+= yield
+
diff --git a/app/views/shared/_fileupload_widget.html.haml b/app/views/shared/_fileupload_widget.html.haml
new file mode 100644
index 0000000..87d793b
--- /dev/null
+++ b/app/views/shared/_fileupload_widget.html.haml
@@ -0,0 +1,32 @@
+- if ENV['RABEL_UPYUN_SWITCH'] == 'on'
+ - content_for :final_head do
+ = javascript_include_tag 'i_fileupload'
+
+ - content_for :template_js do
+ :plain
+ var textarea = $("#{textarea_selector}");
+ var _tip = $("#upload-tip");
+ $('#fileupload').fileupload({
+ dataType: 'json',
+ autoUpload: true,
+ url: '/upyun_images.json',
+ maxFileSize: 1024 * 1024 * 5,
+ acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
+ start: function(e) {
+ _tip.html('图片上传中 ···');
+ },
+ done: function (e, data) {
+ $.each(data.result, function (index, file) {
+ new_pic = "\n" + file.url;
+ textarea.atCaret('insert', new_pic);
+ });
+ },
+ always: function(e) {
+ _tip.html("上传图片");
+ },
+ });
+
+ .pull-right
+ %a.fileupload-btn.action_label
+ %span#upload-tip 上传图片
+ %input#fileupload{:type => 'file', :multiple => true, :name => "upyun_image[asset][]"}
diff --git a/app/views/shared/_footer.html.haml b/app/views/shared/_footer.html.haml
new file mode 100644
index 0000000..5068a22
--- /dev/null
+++ b/app/views/shared/_footer.html.haml
@@ -0,0 +1,21 @@
+#footer
+ .container#footer-main
+ %ul.page-links
+ = render :partial => 'pages/nav', :collection => Page.only_published.default_order, :spacer_template => 'pages/nav_ruler'
+ .copywrite
+ = Siteconf.footer.html_safe
+ %small.muted
+ Powered by
+ = link_to "MakerLab 创客实验室",root_path
+ = "学习系统 基于Rabel"
+
+= render 'shared/google_analytics'
+
+:javascript
+ window.rabel.search_engine_url = "#{search_engine_url}";
+ jQuery(function($) {
+ #{yield :template_js}
+ });
+
+:javascript
+ #{Siteconf.custom_js.html_safe}
diff --git a/app/views/shared/_google_analytics.html.haml b/app/views/shared/_google_analytics.html.haml
new file mode 100644
index 0000000..5e72fce
--- /dev/null
+++ b/app/views/shared/_google_analytics.html.haml
@@ -0,0 +1,12 @@
+- if Rails.env.production? and Siteconf.ga_id.present?
+ %script{:type => 'text/javascript'}
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', '#{Siteconf.ga_id}']);
+ _gaq.push(['_trackPageview']);
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+- else
+ / Google Analytics tracking code goes here
diff --git a/app/views/shared/_head.html.haml b/app/views/shared/_head.html.haml
new file mode 100644
index 0000000..6a10463
--- /dev/null
+++ b/app/views/shared/_head.html.haml
@@ -0,0 +1,9 @@
+= render 'shared/meta'
+= stylesheet_link_tag "application"
+= yield :head
+- if Siteconf.theme != 'default'
+ = stylesheet_link_tag "#{Siteconf.theme}/i_theme"
+- if Siteconf.custom_css.present?
+ %style{:type => 'text/css'}= Siteconf.custom_css.html_safe
+= javascript_include_tag "application"
+= yield :final_head
diff --git a/app/views/shared/_meta.html.haml b/app/views/shared/_meta.html.haml
new file mode 100644
index 0000000..4d9c493
--- /dev/null
+++ b/app/views/shared/_meta.html.haml
@@ -0,0 +1,11 @@
+%meta{:name => 'description', :content => @seo_description}
+%meta{:charset => 'UTF-8'}
+%meta{:name => 'HandheldFriendly', :content => 'True'}
+%meta{:name => 'viewport', :content => 'width=device-width, initial-scale=1.0'}
+%title= title
+= Siteconf.custom_head_tags.html_safe
+= favicon_link_tag
+= auto_discovery_link_tag :atom, topics_path(:atom)
+- if @canonical_path.present?
+ %link{:rel => :canonical, :href => "http://#{ENV['RABEL_HOST_NAME']}#{@canonical_path}"}
+= csrf_meta_tags
diff --git a/app/views/shared/_my_fav.html.haml b/app/views/shared/_my_fav.html.haml
new file mode 100644
index 0000000..029e095
--- /dev/null
+++ b/app/views/shared/_my_fav.html.haml
@@ -0,0 +1,16 @@
+%tr
+ %td.with_separator{:width => '34%', :align => :center}
+ = link_to my_topics_path(current_user.nickname), :class => :dark, :style => 'display: block;' do
+ %span.bigger= current_user.bookmarked_guides_count
+ .sep3
+ %span.gray 我的学习
+ %td.with_separator{:width => '34%', :align => :center}
+ = link_to my_topics_path(current_user.nickname), :class => :dark, :style => 'display: block;' do
+ %span.bigger= current_user.bookmarked_topics_count
+ .sep3
+ %span.gray 话题收藏
+ %td{:width => '33%', :align => :center}
+ = link_to my_following_path, :class => :dark, :style => 'display: block;' do
+ %span.bigger= current_user.followed_user_count
+ .sep3
+ %span.gray 特别关注
diff --git a/app/views/shared/_notification.html.haml b/app/views/shared/_notification.html.haml
new file mode 100644
index 0000000..9f949d2
--- /dev/null
+++ b/app/views/shared/_notification.html.haml
@@ -0,0 +1,26 @@
+- action_user = notification.action_user
+.cell
+ %table{:width => '100%'}
+ %tr
+ %td{:width => 32, :align => :left, :valign => :top}
+ = user_profile_avatar_link(action_user, :mini)
+ %td{:valign => :top}
+ %span.gray
+ %strong= user_profile_link(action_user)
+ - if notification.action == Notification::ACTION_REWARD
+ - if notification.notifiable.amount > 0
+ %span.green 奖励给你
+ - else
+ %span.red 从你的帐户中扣除了
+ %strong= notification.notifiable.amount.abs
+ = Siteconf.reward_title
+ - else
+ = notification.action_info_prefix
+ = link_to notification.notifiable.notifiable_title, read_notification_path(notification), :class => :rabel
+ = notification.action_info_suffix + '了你'
+ %span.snow
+ = time_ago_in_words(notification.created_at)
+ .sep5
+ - if notification.content.present?
+ .payload= parse_markdown(notification.content)
+
diff --git a/app/views/shared/_preview_widget.html.haml b/app/views/shared/_preview_widget.html.haml
new file mode 100644
index 0000000..8467611
--- /dev/null
+++ b/app/views/shared/_preview_widget.html.haml
@@ -0,0 +1,4 @@
+#preview-widget
+ = link_to '编辑', 'javascript:void(0);', :class => 'action_label cancel_preview current_label', :data => {:ref => ref}
+ = link_to '预览', 'javascript:void(0);', :class => 'action_label preview', :data => {:ref => ref, :type => type}
+ #preview
diff --git a/app/views/shared/_rss.html.haml b/app/views/shared/_rss.html.haml
new file mode 100644
index 0000000..6693de6
--- /dev/null
+++ b/app/views/shared/_rss.html.haml
@@ -0,0 +1,2 @@
+= image_tag 'rss.png', :align => :absmiddle
+= link_to 'RSS', topics_path(:atom), :target => '_blank', :class => :dark
diff --git a/app/views/shared/_sidebar_box.html.haml b/app/views/shared/_sidebar_box.html.haml
new file mode 100644
index 0000000..a94b918
--- /dev/null
+++ b/app/views/shared/_sidebar_box.html.haml
@@ -0,0 +1,37 @@
+- if user_signed_in?
+ .box
+ .cell
+ %table
+ %tr
+ %td{:width => 48, :valign => :top}
+ = user_profile_avatar_link(current_user, :medium)
+ %td{:width => 10, :valign => :top}
+ %td{:width => :auto, :valign => :left}
+ .profile-link= user_profile_link(current_user)
+ .signature= current_user.account.signature
+ .sep10
+ %table{:width => '100%'}
+ = render 'shared/my_fav'
+ - unless current_user.has_avatar?
+ .cell
+ .muted
+ %a{:href => settings_path + "#avatar", :class => 'btn btn-sm btn-info btn-block'} 立刻上传个性头像~
+ - if @unread_count > 0
+ .inner.muted
+ = image_tag 'dot_orange.png', :class => :icon, :align => :top
+ = link_to "#{@unread_count} 条未读提醒", notifications_path
+- else
+ .box
+ .cell
+ = "#{Siteconf.site_name} — #{Siteconf.short_intro}"
+ .inner
+ .sep5
+ .center
+ = link_to '现在注册', new_user_registration_path, :class => 'btn btn-small'
+ .sep5
+ .sep10
+ 已注册用户请
+ = link_to '登入', new_user_session_path, :class => :rabel
+
+- content_for :rightbar do
+ = Siteconf.global_sidebar_block.html_safe
diff --git a/app/views/shared/_sidebar_box_lms.html.haml b/app/views/shared/_sidebar_box_lms.html.haml
new file mode 100644
index 0000000..5607401
--- /dev/null
+++ b/app/views/shared/_sidebar_box_lms.html.haml
@@ -0,0 +1,15 @@
+- unless user_signed_in?
+ .box
+ .cell
+ = "#{Siteconf.site_name} — #{Siteconf.short_intro}"
+ .inner
+ .sep5
+ .center
+ = link_to '现在注册', new_user_registration_path, :class => 'btn btn-small'
+ .sep5
+ .sep10
+ 已注册用户请
+ = link_to '登入', new_user_session_path, :class => :rabel
+
+- content_for :rightbar do
+ = Siteconf.global_sidebar_block.html_safe
diff --git a/app/views/shared/_top.html.haml b/app/views/shared/_top.html.haml
new file mode 100644
index 0000000..33ab8d8
--- /dev/null
+++ b/app/views/shared/_top.html.haml
@@ -0,0 +1,23 @@
+#TopMain
+ - if Siteconf.custom_logo.present?
+ = link_to root_path, :class => 'logo custom_logo' do
+ = image_tag Siteconf.custom_logo, :alt => :logo
+ - else
+ = link_to Siteconf.site_name, root_path, :class => 'logo text_logo'
+ #Navigation
+ %ul
+ %li= link_to '首页', root_path, :class => link_class
+ %li= link_to '学习', guides_path, :class => link_class
+ %li= link_to '论坛', topics_path, :class => link_class
+ - if user_signed_in?
+ %li= user_profile_link(current_user, :class => link_class)
+ %li= link_to '个人设置', settings_path, :class => link_class
+ - if current_user.can_manage_site?
+ %li= link_to '管理后台', admin_root_path, :class => link_class
+ %li= link_to '登出', destroy_user_session_path, :method => :delete, :class => link_class
+ - else
+ %li= link_to '注册', new_user_registration_path, :class => link_class
+ %li= link_to '登入', new_user_session_path, :class => link_class
+ #Search
+ .search_input
+ %input#q{:type => 'text', :maxlength => 40, :name => 'q', :data => {:domain => Siteconf.site_host}}
diff --git a/app/views/topics/_back_to_node.html.haml b/app/views/topics/_back_to_node.html.haml
new file mode 100644
index 0000000..15b1353
--- /dev/null
+++ b/app/views/topics/_back_to_node.html.haml
@@ -0,0 +1,4 @@
+%span.fade
+ %span.chevron ‹
+ 返回
+ = link_to node.name, go_path(node.key)
diff --git a/app/views/topics/_complex_topic.html.haml b/app/views/topics/_complex_topic.html.haml
new file mode 100644
index 0000000..56bef9e
--- /dev/null
+++ b/app/views/topics/_complex_topic.html.haml
@@ -0,0 +1,24 @@
+- comments_count = topic.comments_count
+- last_replied_by = topic.last_replied_by
+.cell.topic{:class => topic_user.can_manage_site? ? 'admin' : ''}
+ .avatar.pull-left
+ = user_profile_avatar_link(topic_user, :medium)
+ .item_title
+ - if comments_count > 0
+ .pull-right
+ .badge.badge-info= comments_count
+ %h2.topic_title
+ = link_to topic.title, t_path(topic.id), :class => 'rabel topic'
+ .topic-meta
+ = link_to topic_node.name, go_path(topic_node.key), :class => :node
+ %span.muted •
+ = user_profile_link(topic_user, :class => :dark)
+ %span.muted •
+ - if comments_count > 0
+ = time_ago_in_words(topic.last_replied_at)
+ %span.muted •
+ 最后回复来自
+ = nickname_profile_link(last_replied_by)
+ - else
+ = time_ago_in_words(topic.created_at)
+
diff --git a/app/views/topics/_form.html.haml b/app/views/topics/_form.html.haml
new file mode 100644
index 0000000..fd9003d
--- /dev/null
+++ b/app/views/topics/_form.html.haml
@@ -0,0 +1,13 @@
+.alert.alert-info 如果标题已经包含你想说的话,内容可以留空
+= simple_form_for [node, topic] do |f|
+ %a{:name => 'new_topic'}/
+ = f.input :title, :label => '标题', :input_html => {:maxlength => 150, :class => :span6}
+ = render 'shared/fileupload_widget', :textarea_selector => "textarea#topic_content"
+ = render 'shared/preview_widget', :ref => :topic_content, :type => :topic
+ = f.input :content, :input_html => {:rows => 10, :style => 'width: 98%;'}, :label => false, :placeholder => '话题内容'
+ - if current_user.can_manage_site?
+ .checkbox
+ = f.input :sticky, :inline_label => '保持置顶', :label => false
+ .checkbox
+ = f.input :comments_closed, :inline_label => '禁止回复', :label => false
+ = f.submit (topic.new_record? ? '创建新话题' : '提交修改'), :class => 'btn btn-primary', :data => {:disable_with => t('tips.submitting')}
diff --git a/app/views/topics/_move_form.js.haml b/app/views/topics/_move_form.js.haml
new file mode 100644
index 0000000..af057ad
--- /dev/null
+++ b/app/views/topics/_move_form.js.haml
@@ -0,0 +1,6 @@
+= form_for [@node, @topic], :remote => :true do |f|
+ = label_tag '移动到新节点'
+ %br
+ = select_tag "new_node_id", option_groups_from_collection_for_select(Plane.default_order.all, :nodes, :name, :id, :name, f.object.node.id)
+ %br
+ = f.submit '开始移动', :class => 'btn btn-small'
diff --git a/app/views/topics/_profile_topic.html.haml b/app/views/topics/_profile_topic.html.haml
new file mode 100644
index 0000000..1408b00
--- /dev/null
+++ b/app/views/topics/_profile_topic.html.haml
@@ -0,0 +1,22 @@
+- topic_node = topic.node
+- comments_count = topic.comments_count
+- last_replied_by = topic.last_replied_by
+.cell.topic{:class => topic_user.can_manage_site? ? 'admin' : ''}
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td{:valign => :middle, :width => :auto}
+ %span.bigger
+ = link_to topic.title, t_path(topic.id), :class => 'rabel topic'
+ .topic-meta
+ = link_to topic_node.name, go_path(topic_node.key), :class => :node
+ - if comments_count > 0
+ •
+ = time_ago_in_words(topic.last_replied_at)
+ •
+ 最后回复来自
+ = nickname_profile_link(last_replied_by)
+ - else
+ •
+ = time_ago_in_words(topic.created_at)
+ %td{:valign => :middle, :width => 40, :align => :right}
+ .badge.badge-info= comments_count
diff --git a/app/views/topics/_profile_topic.mobile.haml b/app/views/topics/_profile_topic.mobile.haml
new file mode 100644
index 0000000..808ebf4
--- /dev/null
+++ b/app/views/topics/_profile_topic.mobile.haml
@@ -0,0 +1,11 @@
+.cell
+ %table{:cellpadding => 5, :cellspacing => 0, :border => 0}
+ %tr
+ %td{:width => 80, :align => :right}
+ = link_to topic.node.name, go_path(topic.node.key)
+ %td{:align => :left}
+ = link_to topic.title, t_path(topic.id)
+ %small.fade
+ 收到
+ = topic.comments_count
+ 回复
diff --git a/app/views/topics/_simple_topic.html.haml b/app/views/topics/_simple_topic.html.haml
new file mode 100644
index 0000000..d0b21d1
--- /dev/null
+++ b/app/views/topics/_simple_topic.html.haml
@@ -0,0 +1,14 @@
+- comments_count = topic.comments_count
+.cell.topic{:class => topic_user.can_manage_site? ? 'admin' : ''}
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td{:valign => :top, :class => :avatar}
+ = user_profile_avatar_link(topic_user, :mini)
+ %td{:valign => :top, :style => 'padding-left: 12px'}
+ - if comments_count > 0
+ .fr
+ = link_to comments_count, t_path(topic.id), :class => :count
+ .sep3
+ %span.bigger{:style => 'font-size: 16px; line-height: 130%'}
+ = link_to topic.title, t_path(topic.id), :class => 'rabel topic'
+
diff --git a/app/views/topics/_table_view.html.haml b/app/views/topics/_table_view.html.haml
new file mode 100644
index 0000000..5f117bc
--- /dev/null
+++ b/app/views/topics/_table_view.html.haml
@@ -0,0 +1,27 @@
+%table{:cellpadding => 5, :cellspacing => 0, :border => 0, :width => '100%', :class => :topics}
+ %tr
+ %th{:align => :right, :width => 50} 回复
+ %th{:align => :left, :width => :auto} 标题
+ %th{:align => :left, :width => 200, :colspan => 2} 最后回复时间
+ - i = 1
+ - topics.each do |topic|
+ - class_name = (i % 2 == 0) ? 'even' : 'odd'
+ - i += 1
+ %tr
+ %td{:align => :right, :width => 50, :class => "#{class_name} lend"}
+ - comments_count = topic.comments_count
+ - if comments_count > 0
+ %strong
+ %span.green= comments_count
+ - else
+ %span.snow 0
+ %td{:align => :left, :width => :auto, :class => class_name}
+ = link_to topic.title, t_path(topic.id)
+ - last_comment = topic.last_comment
+ - last_comment = topic if last_comment.nil?
+ %td{:align => :left, :width => 80, :class => class_name}
+ = user_profile_link(last_comment.user, :class => :dark)
+ %td{:align => :left, :width => 120, :class => "#{class_name} rend"}
+ %small.fade= last_comment.created_at.strftime("%Y-%m-%d %H:%M:%S")
+
+
diff --git a/app/views/topics/_title_form.html.haml b/app/views/topics/_title_form.html.haml
new file mode 100644
index 0000000..d091384
--- /dev/null
+++ b/app/views/topics/_title_form.html.haml
@@ -0,0 +1,7 @@
+= form_for [@node, @topic], :url => update_title_node_topic_path(@node, @topic), :remote => true do |f|
+ %a{:name => 'new_topic'}
+ = f.label :title, '标题'
+ .sep5
+ = f.text_area :title, :class => :span4, :rows => 2, :autofocus => true
+ .sep5
+ = f.submit '提交修改', :class => 'btn btn-small', :data => {:disable_with => t('tips.submitting')}
diff --git a/app/views/topics/_topic.html.haml b/app/views/topics/_topic.html.haml
new file mode 100644
index 0000000..7676bf6
--- /dev/null
+++ b/app/views/topics/_topic.html.haml
@@ -0,0 +1,5 @@
+- topic_user = topic.cached_assoc_object(:user)
+- topic_node = topic.cached_assoc_object(:node)
+- comments_count = topic.comments_count
+= render "topics/#{Siteconf.topic_list_style}_topic", :topic_user => topic_user, :topic_node => topic_node, :topic => topic
+
diff --git a/app/views/topics/_topic.mobile.haml b/app/views/topics/_topic.mobile.haml
new file mode 100644
index 0000000..7b59552
--- /dev/null
+++ b/app/views/topics/_topic.mobile.haml
@@ -0,0 +1,24 @@
+- topic_user = topic.user
+- comment_count = topic.comments_count
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td.avatar{:valign => :top}
+ = user_profile_avatar_link(topic_user, :medium)
+ %td{:valign => :top, :style => 'padding-left: 8px;'}
+ %span.fade
+ = user_profile_link(topic_user)
+ in
+ = link_to topic.node.name, go_path(topic.node.key)
+ - if comment_count > 0
+ %small
+ 收到
+ = comment_count
+ 回复
+ .sep5
+ %span.bigger
+ = link_to topic.title, t_path(topic.id)
+ %span.created
+ = time_ago_in_words(topic.created_at)
+
+
diff --git a/app/views/topics/edit.html.haml b/app/views/topics/edit.html.haml
new file mode 100644
index 0000000..600b6d3
--- /dev/null
+++ b/app/views/topics/edit.html.haml
@@ -0,0 +1,31 @@
+- unless @topic.locked? or current_user.can_manage_site?
+ .box
+ .cell
+ %span.fade 修改功能指南
+ .inner
+ 在新主题创建后的
+ = Siteconf.topic_editable_period_str
+ 分钟内,可以自由编辑。
+ .sep5
+ %strong
+ 距离本主题的编辑权限关闭还有
+ %span.orange= (@topic.created_at + Siteconf.topic_editable_period - Time.now).round
+ 秒
+ - content_for :template_js do
+ :plain
+ var second = parseInt($("span.orange").text());
+ var countdown_id = setInterval(function() {
+ if (second == 0) {
+ $('#Rightbar strong').text('此主题编辑权限已经关闭');
+ clearInterval(countdown_id);
+ return;
+ }
+ second = second - 1;
+ $("span.orange").text(second);
+ }, 1000);
+.box
+ .cell
+ = edit_topic_navigation(@node, @topic)
+ .cell
+ = render 'form', :node => @node, :topic => @topic, :comments_closed => @topic.comments_closed?
+ .inner= render 'back_to_node', :node => @node
diff --git a/app/views/topics/edit_title.js.haml b/app/views/topics/edit_title.js.haml
new file mode 100644
index 0000000..9c06c83
--- /dev/null
+++ b/app/views/topics/edit_title.js.haml
@@ -0,0 +1 @@
+$.facebox("#{escape_javascript(render('title_form'))}")
diff --git a/app/views/topics/index.atom.builder b/app/views/topics/index.atom.builder
new file mode 100644
index 0000000..dacb1b2
--- /dev/null
+++ b/app/views/topics/index.atom.builder
@@ -0,0 +1,18 @@
+atom_feed :language => 'zh-CN' do |feed|
+ feed.title Siteconf.site_name
+ feed.updated @last_update
+
+ @feed_items.each do |item|
+ topic_url = t_url(item.id)
+ feed.entry(item, :url => topic_url ) do |entry|
+ entry.url topic_url
+ entry.title item.title
+ entry.content parse_markdown(item.content), :type => 'html'
+ entry.updated item.updated_at.strftime("%Y-%m-%dT%H:%M:%SZ")
+
+ entry.author do |author|
+ author.name item.user.nickname
+ end
+ end
+ end
+end
diff --git a/app/views/topics/index.html.haml b/app/views/topics/index.html.haml
new file mode 100644
index 0000000..33ac827
--- /dev/null
+++ b/app/views/topics/index.html.haml
@@ -0,0 +1,10 @@
+- content_for :rightbar do
+ = render 'welcome/rightbar_planes'
+.box
+ .cell
+ = build_navigation([@title])
+ .pull-right
+ = link_to '创建新话题', new_from_home_topics_path, :class => 'btn btn-success btn-sm btn-new-topic'
+ = render @topics
+ .inner{:align => :center}
+ = paginate @topics
diff --git a/app/views/topics/move.js.haml b/app/views/topics/move.js.haml
new file mode 100644
index 0000000..1264037
--- /dev/null
+++ b/app/views/topics/move.js.haml
@@ -0,0 +1 @@
+$.facebox("#{escape_javascript(render('move_form'))}")
diff --git a/app/views/topics/new.html.haml b/app/views/topics/new.html.haml
new file mode 100644
index 0000000..a7c694f
--- /dev/null
+++ b/app/views/topics/new.html.haml
@@ -0,0 +1,6 @@
+.box
+ .cell
+ = build_navigation [link_to(@node.name, go_path(@node.key))]
+ .inner
+ %h2 创建新话题
+ = render 'form', :node => @node, :topic => @topic, :comments_closed => false
diff --git a/app/views/topics/new.mobile.haml b/app/views/topics/new.mobile.haml
new file mode 100644
index 0000000..e1ed7aa
--- /dev/null
+++ b/app/views/topics/new.mobile.haml
@@ -0,0 +1,14 @@
+- add_breadcrumb link_to(@node.name, go_path(@node.key), :class => :black)
+- add_breadcrumb '新建主题'
+.cell
+ = form_for [@node, @topic] do |f|
+ = f.text_field :title, :class => :sll
+ .sep5
+ = f.text_area :content, :class => :mll
+ .sep5
+ - if current_user.can_manage_site?
+ = check_box_tag :comments_closed
+ = label_tag :comments_closed, '禁止回复'
+ .sep5
+ = f.submit '创建新主题', :class => :btn
+
diff --git a/app/views/topics/new_from_home.html.slim b/app/views/topics/new_from_home.html.slim
new file mode 100644
index 0000000..65ae715
--- /dev/null
+++ b/app/views/topics/new_from_home.html.slim
@@ -0,0 +1,20 @@
+- content_for :sidebar do
+ .col-md-3.col-lg-3.col-sm-4#Rightbar
+ = render 'shared/sidebar_box'
+.box
+ .cell
+ h2 创建新话题
+ .inner
+ = simple_form_for @topic, :url => create_from_home_topics_path do |f|
+ = f.input :title, :label => '标题', :input_html => {:maxlength => 150, :class => :span6}
+ = render 'shared/fileupload_widget', :textarea_selector => "textarea#topic_content"
+ = render 'shared/preview_widget', :ref => :topic_content, :type => :topic
+ = f.input :content, :input_html => {:rows => 10, :style => 'width: 98%;'}, :label => false, :placeholder => '话题内容'
+ = f.association :node, :label => false, :collection => Plane.all, :as => :grouped_select, :group_method => :nodes, :prompt => '选择一个节点'
+ - if current_user.can_manage_site?
+ .checkbox
+ = f.input :sticky, :inline_label => '保持置顶', :label => false
+ .checkbox
+ = f.input :comments_closed, :inline_label => '禁止回复', :label => false
+ = f.submit '创建新话题', :class => 'btn btn-primary btn-inverse', :data => {:disable_with => t('tips.submitting')}
+
diff --git a/app/views/topics/preview.text.haml b/app/views/topics/preview.text.haml
new file mode 100644
index 0000000..4f96708
--- /dev/null
+++ b/app/views/topics/preview.text.haml
@@ -0,0 +1,2 @@
+- format_method = "format_#{@type}"
+= send(format_method, @content)
diff --git a/app/views/topics/show.html.haml b/app/views/topics/show.html.haml
new file mode 100644
index 0000000..652d5cd
--- /dev/null
+++ b/app/views/topics/show.html.haml
@@ -0,0 +1,67 @@
+- content_for :rightbar do
+ = render 'welcome/rightbar_planes'
+= render 'nodes/custom_fields', :node => @node
+= render 'topics/show/bookmarked_users'
+= render 'topics/show/manage'
+
+- content_for :template_js do
+ :plain
+ var creating_comment = false;
+
+ $("textarea#comment_content").keydown(function(e) {
+ if (e.ctrlKey && e.keyCode == 13) {
+ if (creating_comment) return;
+ creating_comment = true
+ $("input[type=submit]").click();
+ }
+ });
+
+- topic_user = @topic.cached_assoc_object(:user)
+
+.box
+ %article
+ .header
+ .pull-right
+ = user_profile_avatar_link(topic_user, :large)
+ = build_navigation([link_to(@node.name, go_path(@node.key), :class => :rabel)], 'bigger')
+ .sep10
+ %h1#topic_title
+ = Rabel::Base.make_mention_links(Rabel::Base.h(@topic.title)).html_safe
+ %small.topic-meta
+ By
+ = user_profile_link(topic_user, :class => :dark)
+ at
+ = time_ago_in_words(@topic.created_at)
+ ,
+ = @topic.hit
+ 次浏览
+ .clearfix
+ .inner
+ .content.topic_content= format_topic(@topic.content)
+ - if user_signed_in?
+ .pull-right
+ - if current_user.bookmarked?(@topic)
+ = link_to '取消收藏', current_user.bookmark_of(@topic), :method => :delete, :class => 'btn btn-xs btn-info unbookmark'
+ - else
+ = link_to '加入收藏', topic_bookmarks_path(@topic), :method => :post, :class => 'btn btn-xs btn-info bookmark'
+ .clearfix
+ .inner
+ - if @next_topic.present?
+ .pull-right
+ = link_to proper_length(@next_topic.title, 15), t_path(@next_topic.id), :class => :rabel, :rel => :prev
+ %span.guillemet.right-guillemet »
+
+ - if @prev_topic.present?
+ .pull-left
+ %span.guillemet.left-guillemet «
+ = link_to proper_length(@prev_topic.title, 15), t_path(@prev_topic.id), :class => :rabel, :rel => :next
+ .clearfix
+
+= render 'topics/show/comments' if @comments.any?
+
+- if @topic.comments_closed?
+ = render 'shared/box_tip', :tip => t('tips.comments_closed')
+- elsif @comments.empty?
+ = render 'shared/box_tip', :tip => '目前尚无回复'
+
+= render 'topics/show/comment_form' unless @topic.comments_closed?
diff --git a/app/views/topics/show.mobile.haml b/app/views/topics/show.mobile.haml
new file mode 100644
index 0000000..a9840ae
--- /dev/null
+++ b/app/views/topics/show.mobile.haml
@@ -0,0 +1,52 @@
+- add_breadcrumb link_to(@node.name, go_path(@node.key), :class => :black)
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td.avatar{:valign => :top}
+ = user_profile_avatar_link(@topic.user, :medium)
+ %td{:valign => :top, :style => 'padding-left: 5px;'}
+ %h1= @topic.title
+ .sep5
+ %span.fade
+ By
+ = user_profile_link(@topic.user)
+ = time_ago_in_words(@topic.created_at)
+ - if @topic.hit > 0
+ = " - #{@topic.hit}次点击"
+ .sep10
+ = format_topic @topic.content
+.cell
+ .fr
+ - if @total_bookmarks > 0
+ %small.fade= "已有 #{@total_bookmarks} 人收藏"
+
+ - if current_user
+ - if current_user.bookmarked?(@topic)
+ = link_to '取消收藏', current_user.bookmark_of(@topic), :method => :delete
+
+ = image_tag 'heart.png', :align => :top
+ - else
+ = link_to '加入收藏', topic_bookmarks_path(@topic), :method => :post
+
+ %strong.fade
+ - if @topic.comments_closed?
+ = t('tips.comments_closed')
+ - else
+ 共收到
+ = @topic.comments_count
+ 条回复
+
+#replies
+ = render @comments
+ - if @total_pages > 1
+ .cell{:align => :center}
+ = paginate @comments, :param_name => :p, :window => 2
+
+- if user_signed_in? and not @topic.comments_closed?
+ .cell
+ %span.fade 现在就添加一条回复
+ = form_for [@topic, @new_comment] do |f|
+ .sep5
+ = f.text_area :content, :class => :mll
+ .sep5
+ = f.submit '发送'
diff --git a/app/views/topics/show/_bookmark_button.html.haml b/app/views/topics/show/_bookmark_button.html.haml
new file mode 100644
index 0000000..e01f126
--- /dev/null
+++ b/app/views/topics/show/_bookmark_button.html.haml
@@ -0,0 +1,8 @@
+- if user_signed_in?
+ .inner
+ .fr{:align => :right}
+ - if current_user.bookmarked?(@topic)
+ = link_to '取消收藏', current_user.bookmark_of(@topic), :method => :delete, :class => 'op cancel'
+ - else
+ = link_to '加入收藏', topic_bookmarks_path(@topic), :method => :post, :class => :op
+
diff --git a/app/views/topics/show/_bookmarked_users.html.haml b/app/views/topics/show/_bookmarked_users.html.haml
new file mode 100644
index 0000000..2bffce9
--- /dev/null
+++ b/app/views/topics/show/_bookmarked_users.html.haml
@@ -0,0 +1,8 @@
+- if @total_bookmarks > 0
+ - content_for :rightbar do
+ .box
+ .box-header
+ 收藏此话题的成员
+ .inner
+ - @topic.bookmarks.each do |b|
+ = user_profile_avatar_link(b.user, :mini)
diff --git a/app/views/topics/show/_comment_form.html.haml b/app/views/topics/show/_comment_form.html.haml
new file mode 100644
index 0000000..156dc18
--- /dev/null
+++ b/app/views/topics/show/_comment_form.html.haml
@@ -0,0 +1,15 @@
+- if user_signed_in?
+ = form_for [@topic, @new_comment] do |f|
+ .box
+ .box-header
+ .fr
+ ⬆
+ = link_to '回到顶部', 'javascript:void(0);', :class => 'dark back_to_top'
+ 现在就添加一条回复
+ .inner
+ = render 'shared/fileupload_widget', :textarea_selector => "textarea#comment_content"
+ = render 'shared/preview_widget', :ref => :comment_content, :type => :comment
+ = f.text_area :content, :rows => 5, :style => 'width: 98%;'
+ .sep10
+ = f.submit '发送', :class => 'btn btn-sm btn-success', :data => {:disable_with => t('tips.submitting')}
+ %small.gray 支持 Ctrl + Enter 快捷键
diff --git a/app/views/topics/show/_comments.html.haml b/app/views/topics/show/_comments.html.haml
new file mode 100644
index 0000000..dcaee67
--- /dev/null
+++ b/app/views/topics/show/_comments.html.haml
@@ -0,0 +1,14 @@
+%section
+ .box
+ .box-header
+ .fr
+ - if @topic.comments_closed?
+ 回复权限关闭
+ - else
+ ⬇
+ = link_to '跳到回复', 'javascript:void(0);', :class => 'dark jump_to_comment'
+ = "#{@total_comments} 回复"
+ #replies{:class => "#{'fix_cell' if @total_pages == 1}"}
+ = render @comments
+ - if @total_pages > 1
+ = paginate @comments, :param_name => :p
diff --git a/app/views/topics/show/_manage.html.haml b/app/views/topics/show/_manage.html.haml
new file mode 100644
index 0000000..7ede5fc
--- /dev/null
+++ b/app/views/topics/show/_manage.html.haml
@@ -0,0 +1,18 @@
+- if can? :update, @topic
+ - content_for :rightbar do
+ .box
+ .box-header
+ 话题管理
+ .cell
+ = link_to '修改标题', edit_title_node_topic_path(@node, @topic), :remote => true, :class => :btn
+ = link_to '编辑全部', edit_node_topic_path(@node, @topic), :class => :btn
+ .cell
+ = link_to '移动到新节点', move_node_topic_path(@node, @topic), :remote => true, :class => :btn
+ - if current_user.can_manage_site?
+ .cell
+ - toggle_comments_closed_tip = @topic.comments_closed? ? '允许回复' : '禁止回复'
+ = link_to toggle_comments_closed_tip, topic_toggle_comments_closed_path(@topic), :method => :put, :class => :btn
+ - toggle_sticky_tip = @topic.sticky? ? '取消置顶' : '置顶此话题'
+ = link_to toggle_sticky_tip, topic_toggle_sticky_path(@topic), :method => :put, :class => :btn
+ .inner
+ = link_to '删除此话题', node_topic_path(@node, @topic), :method => :delete, :data => {:confirm => t(:delete_confirm)}, :class => 'btn btn-sm btn-danger'
diff --git a/app/views/topics/update_title.js.haml b/app/views/topics/update_title.js.haml
new file mode 100644
index 0000000..e8c74f1
--- /dev/null
+++ b/app/views/topics/update_title.js.haml
@@ -0,0 +1,2 @@
+$("#topic_title").html("#{escape_javascript(@topic.title)}");
+$.facebox.close();
diff --git a/app/views/users/_account_detail_form.html.haml b/app/views/users/_account_detail_form.html.haml
new file mode 100644
index 0000000..a15232c
--- /dev/null
+++ b/app/views/users/_account_detail_form.html.haml
@@ -0,0 +1,7 @@
+= f.fields_for :account do |fields|
+ = fields.input :personal_website, :label => '个人网站'
+ = fields.input :location, :label => '所在地'
+ = fields.input :weibo_link, :label => '微博地址', :input_html => {:class => :span4}
+ = fields.input :signature, :label => '签名', :input_html => {:class => :span4}
+ = fields.input :introduction, :label => '个人简介', :input_html => {:rows => 5, :class => :span4}
+
diff --git a/app/views/users/_account_form.html.haml b/app/views/users/_account_form.html.haml
new file mode 100644
index 0000000..ab6f94a
--- /dev/null
+++ b/app/views/users/_account_form.html.haml
@@ -0,0 +1,7 @@
+= simple_form_for @user, :url => update_account_path do |f|
+ = f.input :nickname, :disabled => true
+ = f.input :email
+ = render :partial => 'account_detail_form', :locals => {:f => f}, :formats => :html
+
+ .form-actions
+ = f.submit '保存设置', :class => 'btn btn-small btn-primary'
diff --git a/app/views/users/_followed_ruler.html.haml b/app/views/users/_followed_ruler.html.haml
new file mode 100644
index 0000000..1e6259a
--- /dev/null
+++ b/app/views/users/_followed_ruler.html.haml
@@ -0,0 +1,3 @@
+%tr
+ %td.ruler{:colspan => 2}
+ .ruler
diff --git a/app/views/users/_followed_user.html.haml b/app/views/users/_followed_user.html.haml
new file mode 100644
index 0000000..fdd0ec9
--- /dev/null
+++ b/app/views/users/_followed_user.html.haml
@@ -0,0 +1,8 @@
+- nickname = followed_user.nickname
+%li
+ .thumbnail
+ = link_to member_path(url_encode(nickname)), :title => nickname do
+ = large_avatar(followed_user)
+ .sep5
+ .caption.center
+ = nickname
diff --git a/app/views/users/_password_form.html.haml b/app/views/users/_password_form.html.haml
new file mode 100644
index 0000000..9c7248d
--- /dev/null
+++ b/app/views/users/_password_form.html.haml
@@ -0,0 +1,9 @@
+= simple_form_for @user, :url => update_password_path do |f|
+ %strong.fade 如果你不想更改密码,请留空以下输入框。
+ .sep5
+ = f.input :current_password, :label => '当前密码'
+ = f.input :password, :label => '新密码'
+ = f.input :password_confirmation, :label => '新密码确认'
+ .form-actions
+ = f.submit '修改密码', :class => 'btn btn-small btn-primary'
+
diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb
deleted file mode 100644
index 14b6afd..0000000
--- a/app/views/users/_user.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-
- <%= simple_form_for user, :url => user_path(user), :html => {:method => :put, :class => 'form-horizontal' } do |f| %>
-
-
- <%= f.input :role_ids, :collection => Role.all, :as => :radio_buttons, :label_method => lambda {|t| t.name.titleize}, :label => false, :item_wrapper_class => 'inline', checked: user.role_ids.first %>
-
-
- <% end %>
-
diff --git a/app/views/users/_user.mobile.haml b/app/views/users/_user.mobile.haml
new file mode 100644
index 0000000..08934cb
--- /dev/null
+++ b/app/views/users/_user.mobile.haml
@@ -0,0 +1,9 @@
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td.avatar{:valign => :top}
+ = user_profile_avatar_link(user, :medium)
+ %td{:style => 'padding-left: 8px;', :valign => :top}
+ %h1= user_profile_link(user)
+ .sep5
+ %p= user.account.signature
diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml
new file mode 100644
index 0000000..2042bd1
--- /dev/null
+++ b/app/views/users/edit.html.haml
@@ -0,0 +1,35 @@
+- content_for :sidebar do
+ .col-md-3.col-lg-3.col-sm-4#Rightbar
+ = render 'shared/sidebar_box'
+.box
+ .cell
+ = build_navigation(['设置'])
+ .inner
+ = render 'account_form', :btn_class => 'btn btn-small'
+
+%a{:name => :avatar}
+.box
+ .box-header
+ 头像
+ = simple_form_for @user, :url => update_avatar_path, :html => {:multipart => true, :class => 'edit_user'} do |f|
+ .cell
+ .control-group
+ .control-label 当前头像
+ .controls
+ = large_avatar(current_user)
+
+ = medium_avatar(current_user)
+
+ = mini_avatar(current_user)
+ .inner
+ = f.input :avatar, :label => '选择图片文件'
+ .form-actions
+ = f.submit '上传新头像', :class => 'btn btn-small btn-primary'
+ %span.help-block 推荐使用正方形的透明 PNG 图片以获得最佳效果。
+
+.box
+ .box-header
+ 修改密码
+ .inner
+ = render 'users/password_form', :btn_class => 'btn btn-small'
+
diff --git a/app/views/users/edit.mobile.haml b/app/views/users/edit.mobile.haml
new file mode 100644
index 0000000..e3b0e8e
--- /dev/null
+++ b/app/views/users/edit.mobile.haml
@@ -0,0 +1,8 @@
+- add_breadcrumb @title
+= show_mobile_messages
+.cell
+ = render :partial => 'users/account_form', :formats => :html, :locals => {:btn_class => ''}
+.section 安全
+.cell
+ = render :partial => 'users/password_form', :formats => :html, :locals => {:btn_class => ''}
+= show_mobile_messages
diff --git a/app/views/users/guides.html.haml b/app/views/users/guides.html.haml
new file mode 100644
index 0000000..7ef6e34
--- /dev/null
+++ b/app/views/users/guides.html.haml
@@ -0,0 +1,10 @@
+- content_for :sidebar do
+ .col-md-3.col-lg-3.col-sm-4#Rightbar
+ = render 'shared/sidebar_box'
+.box
+ .cell
+ = build_navigation([@title])
+ .cell
+ = render :partial => 'guides/profile_guide', :collection => @guides, :as => :guide, :locals => {:guide_user => @user}
+ = paginate @guides if @guides.any?
+
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb
deleted file mode 100644
index b55c38f..0000000
--- a/app/views/users/index.html.erb
+++ /dev/null
@@ -1,30 +0,0 @@
-Users
-
-
-
-
- Username
- Email
- Registered
- Role
-
-
-
-
-
- <% @users.each do |user| %>
-
- <%= link_to user.name, user %>
- <%= user.email %>
- <%= user.created_at.to_date %>
- <%= user.roles.first.name.titleize unless user.roles.first.nil? %>
-
- Change role
- <%= render user %>
-
- <%= link_to("Delete user", user_path(user), :data => { :confirm => "Are you sure?" }, :method => :delete, :class => 'btn btn-mini') unless user == current_user %>
-
- <% end %>
-
-
-
diff --git a/app/views/users/index.html.haml b/app/views/users/index.html.haml
new file mode 100644
index 0000000..de38ba6
--- /dev/null
+++ b/app/views/users/index.html.haml
@@ -0,0 +1,18 @@
+.box.user_list.clearfix
+ .box-header
+ %h3.center TOP 50 最活跃会员
+ -@hot_users.each do |user|
+ .user
+ .avatar
+ = user_profile_avatar_link(user, :large)
+ .name
+ = user_profile_link(user)
+.box.user_list.clearfix
+ .box-header
+ %h3.center 最新注册会员
+ -@new_users.each do |user|
+ .user
+ .avatar
+ = user_profile_avatar_link(user, :large)
+ .name
+ = user_profile_link(user)
diff --git a/app/views/users/my_following.html.haml b/app/views/users/my_following.html.haml
new file mode 100644
index 0000000..84ed088
--- /dev/null
+++ b/app/views/users/my_following.html.haml
@@ -0,0 +1,11 @@
+.box
+ .cell
+ = build_navigation(['我的特别关注'])
+ = render @followed_topic_timeline
+.box
+ .box-header
+ 我关注的社区成员
+ .inner
+ %ul.thumbnails
+ = render :partial => 'users/followed_user', :collection => @my_followed_users, :spacer_template => 'users/followed_ruler'
+
diff --git a/app/views/users/my_following.mobile.haml b/app/views/users/my_following.mobile.haml
new file mode 100644
index 0000000..28788e5
--- /dev/null
+++ b/app/views/users/my_following.mobile.haml
@@ -0,0 +1,3 @@
+= render @my_followed_users, :as => :user
+.section 讨论主题
+= render @followed_topic_timeline
diff --git a/app/views/users/my_topics.html.haml b/app/views/users/my_topics.html.haml
new file mode 100644
index 0000000..72fa3a1
--- /dev/null
+++ b/app/views/users/my_topics.html.haml
@@ -0,0 +1,13 @@
+- content_for :sidebar do
+ .col-md-3.col-lg-3.col-sm-4#Rightbar
+ = render 'shared/sidebar_box'
+.box
+ .box-header
+ = @user.nickname
+ 收藏的学习
+ = render :partial => 'guides/profile_guide', :collection => @user.bookmarked_guides, :as => :guide, :locals => {:guide_user => @user}
+.box
+ .box-header
+ = @user.nickname
+ 收藏的话题
+ = render :partial => 'topics/profile_topic', :collection => @user.bookmarked_topics, :as => :topic, :locals => {:topic_user => @user}
diff --git a/app/views/users/my_topics.mobile.haml b/app/views/users/my_topics.mobile.haml
new file mode 100644
index 0000000..cdd8b30
--- /dev/null
+++ b/app/views/users/my_topics.mobile.haml
@@ -0,0 +1,2 @@
+= render @my_topics
+= render @my_guides
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
deleted file mode 100644
index 929c257..0000000
--- a/app/views/users/show.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-User
-User: <%= @user.name %>
-Email: <%= @user.email if @user.email %>
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
new file mode 100644
index 0000000..ad6bf3d
--- /dev/null
+++ b/app/views/users/show.html.haml
@@ -0,0 +1,134 @@
+- content_for :sidebar do
+ .col-md-3.col-lg-3.col-sm-4#Rightbar
+ = render 'shared/sidebar_box'
+ - if @user.follower_count > 0
+ .box
+ .box-header
+ = "关注#{@nickname_tip}的人"
+ %span.gray= "(#{@user.follower_count})"
+ .inner
+ - @user.recent_followers.each do |follower|
+ = user_profile_avatar_link(follower, :mini)
+ - if user_signed_in? and current_user.can_manage_site? and (not @user.root?) and @user != current_user
+ .box
+ .box-header
+ %a{:name => :spam}
+ SPAM 处理
+ .inner
+ %p 如果该用户违反了社区行为准则,您可以删除此会员。
+ %p 与该会员相关的所有信息,也会一并删除。
+ .inner
+ = link_to '删除此会员', admin_user_path(@user), :method => :delete, :data => {:confirm => t('delete_confirm')}, :class => 'btn btn-small btn-danger'
+ - if user_signed_in? and current_user.can_manage_site?
+ .box
+ .box-header
+ 管理
+ .inner
+ .btn-group
+ = link_to "奖励#{Siteconf.reward_title}", new_admin_user_reward_path(@user), :class => 'btn btn-mini', :remote => true
+ = link_to "扣除#{Siteconf.reward_title}", new_admin_user_reward_path(@user) + "?reward_type=#{Reward::TYPE_REVOKE}", :class => 'btn btn-mini', :remote => true
+ = link_to '管理此用户', admin_users_path + "?nickname=#{url_encode(@user.nickname)}", :class => 'btn btn-mini'
+
+.box
+ %div{:class => (@introduction.length > 0) ? 'cell' : 'inner'}
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td{:width => 73, :valign => :top, :align => :center}
+ = large_avatar(@user)
+ %td{:width => 10, :valign => :top}
+ %td{:width => :auto, :align => :left, :valign => :top}
+ .fr
+ .sep3
+ - if @user.reward > 0
+ %span.gray
+ %strong#reward_balance= @user.reward
+ = Siteconf.reward_title
+ - if user_signed_in? and @user != current_user
+ - if current_user.following?(@user)
+ = link_to '取消特别关注', unfollow_user_path(@user.nickname), :method => :post, :class => 'btn btn-small btn-warning unfollow'
+ - else
+ = link_to '加入特别关注', follow_user_path(@user.nickname), :method => :post, :class => 'btn btn-small btn-inverse follow'
+ %h2{:style => 'padding: 0px; margin: 0px; font-size: 22px; line-height: 22px;'}
+ = @user.nickname
+ - if @signature.length > 0
+ .sep5
+ %span.gray.bigger= @signature
+ .sep5
+ %span.snow
+ = Siteconf.site_name
+ 第
+ = @user.id
+ 号会员, 加入于
+ = @user.created_at.strftime("%Y-%m-%d %H:%M:%S %p")
+ .sep10
+ %table{:cellpadding => 2, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td{:width => '50%'}
+ - if @weibo_link.length > 0
+ %span{:style => 'line-height: 16px;'}
+ = image_tag "icon/#{weibo_icon_for(@weibo_link)}.png", :align => :absmiddle
+
+ = link_to @weibo_link, @weibo_link, :target => :_blank, :rel => 'nofollow external', :class => :rabel
+ %tr
+ %td{:width => '50%'}
+ - if @personal_website.length > 0
+ %span{:style => 'line-height: 16px;'}
+ = image_tag 'icon/mobileme.png', :align => :absmiddle
+
+ = link_to @personal_website, @personal_website, :target => '_blank', :rel => 'nofollow external', :class => :rabel
+ %tr
+ %td{:width => '50%'}
+ - if @location.length > 0
+ %span{:style => 'line-height: 16px;'}
+ = image_tag 'icon/location.png', :align => :absmiddle
+
+ = link_to @location, "http://www.google.com/maps?q=#{@location}", :target => '_blank', :rel => 'nofollow external', :class => :rabel
+
+ - if @introduction.length > 0
+ .inner= parse_markdown @introduction
+
+
+.box
+ .box-header
+ = @user.nickname
+ 收藏的学习
+ = render :partial => 'guides/profile_guide', :collection => @user.bookmarked_guides, :as => :guide, :locals => {:guide_user => @user}
+ - if @user.bookmarked_topics.any?
+ .inner
+ %span.chevron »
+ %small= link_to "#{@user.nickname} 收藏的更多学习", my_topics_path(@user.nickname), :class => :rabel
+.box
+ .box-header
+ = @user.nickname
+ 收藏的话题
+ = render :partial => 'topics/profile_topic', :collection => @user.bookmarked_topics, :as => :topic, :locals => {:topic_user => @user}
+ - if @user.bookmarked_topics.any?
+ .inner
+ %span.chevron »
+ %small= link_to "#{@user.nickname} 收藏的更多主题", my_topics_path(@user.nickname), :class => :rabel
+.box
+ .box-header
+ = @user.nickname
+ 最近创建的学习教程
+ = render :partial => 'guides/profile_guide', :collection => @user.latest_created_guides, :as => :guide, :locals => {:guide_user => @user}
+ - if @user.guides.any?
+ .inner
+ %span.chevron »
+ %small= link_to "#{@user.nickname} 创建的更多学习教程", member_guides_path(@user.nickname), :class => :rabel
+.box
+ .box-header
+ = @user.nickname
+ 最近创建的话题
+ = render :partial => 'topics/profile_topic', :collection => @user.latest_created_topics, :as => :topic, :locals => {:topic_user => @user}
+ - if @user.topics.any?
+ .inner
+ %span.chevron »
+ %small= link_to "#{@user.nickname} 创建的更多主题", member_topics_path(@user.nickname), :class => :rabel
+
+.box
+ .box-header
+ = @user.nickname
+ 最近的回复
+ = render :partial => 'comments/profile_comment', :collection => @user.latest_comments, :as => :comment
+
+
diff --git a/app/views/users/show.mobile.haml b/app/views/users/show.mobile.haml
new file mode 100644
index 0000000..11141f9
--- /dev/null
+++ b/app/views/users/show.mobile.haml
@@ -0,0 +1,35 @@
+- content_for :nav_right do
+ 第
+ = @user.id
+ 号会员,于
+ = @user.created_at.strftime("%Y-%m-%d")
+ 加入
+
+.cell
+ %table{:cellpadding => 0, :cellspacing => 0, :border => 0, :width => '100%'}
+ %tr
+ %td.avatar{:valign => :top}
+ = medium_avatar(@user)
+ %td{:style => 'padding-left: 8px;', :valign => :top}
+ %h1= @user.nickname
+ .sep10
+ - if @personal_website.present?
+ 个人网站
+ = link_to @personal_website, @personal_website, :target => '_blank'
+ .sep10
+ - if @twitter_id.present?
+ Twitter
+ = "@#{@twitter_id}"
+
+- if user_signed_in? and current_user != @user
+ .cell
+ - if current_user.following?(@user)
+ .fr
+ = link_to '取消特别关注', unfollow_user_path(@user.nickname), :method => :post
+ %strong.fade
+ 已关注
+ - else
+ = link_to '加入特别关注', follow_user_path(@user.nickname), :method => :post
+
+= render :partial => 'topics/profile_topic', :collection => @user.latest_created_topics, :as => :topic
+
diff --git a/app/views/users/topics.html.haml b/app/views/users/topics.html.haml
new file mode 100644
index 0000000..c89ddd4
--- /dev/null
+++ b/app/views/users/topics.html.haml
@@ -0,0 +1,10 @@
+- content_for :sidebar do
+ .col-md-3.col-lg-3.col-sm-4#Rightbar
+ = render 'shared/sidebar_box'
+.box
+ .cell
+ = build_navigation([@title])
+ .cell
+ = render :partial => 'topics/profile_topic', :collection => @topics, :as => :topic, :locals => {:topic_user => @user}
+ = paginate @topics if @topics.any?
+
diff --git a/app/views/welcome/_home_guides.html.haml b/app/views/welcome/_home_guides.html.haml
new file mode 100644
index 0000000..bc717dc
--- /dev/null
+++ b/app/views/welcome/_home_guides.html.haml
@@ -0,0 +1,17 @@
+.box#guides_index
+ .cell{:align => 'left'}
+ -if user_signed_in? && current_user.can_manage_site?
+ .pull-right
+ = link_to '创建新教程', new_from_home_guides_path, :class => 'btn btn-success btn-sm btn-new-topic'
+ .welcome
+ = "学习系统最新更新"
+
+ - @guides.each do |guide|
+ = render "guides/profile_guide", :guide_user => guide.user, :guide_node => guide.category, :guide => guide, :guide_articles => guide.articles
+ .inner
+ .pull-right
+ = image_tag 'rss.png', :align => :absmiddle
+ = link_to 'RSS', guides_path(:atom), :target => '_blank', :class => :dark
+
+ %span.chevron »
+ = link_to '更多新教程', guides_path , :class => :rabel
diff --git a/app/views/welcome/_home_planes.html.haml b/app/views/welcome/_home_planes.html.haml
new file mode 100644
index 0000000..94175c4
--- /dev/null
+++ b/app/views/welcome/_home_planes.html.haml
@@ -0,0 +1,5 @@
+.box.fix_cell#planes
+ .box-header
+ %Strong= Siteconf.site_name
+ \/ 节点导航
+ = render Plane.cached_all(Plane.default_order_str)
diff --git a/app/views/welcome/_home_topics.html.haml b/app/views/welcome/_home_topics.html.haml
new file mode 100644
index 0000000..53da51c
--- /dev/null
+++ b/app/views/welcome/_home_topics.html.haml
@@ -0,0 +1,21 @@
+.box#topics_index
+ .cell{:align => 'left'}
+ .pull-right
+ = link_to '创建新话题', new_from_home_topics_path, :class => 'btn btn-success btn-sm btn-new-topic'
+ .welcome
+ = Siteconf.welcome_tip.html_safe
+ - if @sticky_topics.any?
+ - if Siteconf.sticky_topics_heading.present?
+ #sticky_topics.cell.topics_heading= Siteconf.sticky_topics_heading
+ = render @sticky_topics
+
+ - if @sticky_topics.any? and Siteconf.latest_topics_heading.present?
+ #latest_topics.cell.topics_heading= Siteconf.latest_topics_heading
+
+ = render @topics
+ .inner
+ .pull-right= render 'shared/rss'
+
+ - if Topic.cached_count > Siteconf::HOMEPAGE_TOPICS
+ %span.chevron »
+ = link_to '更多新主题', topics_path + '?page=2', :class => :rabel
diff --git a/app/views/welcome/_rightbar_plane.html.haml b/app/views/welcome/_rightbar_plane.html.haml
new file mode 100644
index 0000000..7be914f
--- /dev/null
+++ b/app/views/welcome/_rightbar_plane.html.haml
@@ -0,0 +1,4 @@
+.cell
+ %span.gray= plane.name
+ .sep5
+ = render plane.cached_assoc_collection(:nodes, Node.default_order_str, 20)
diff --git a/app/views/welcome/_rightbar_planes.html.haml b/app/views/welcome/_rightbar_planes.html.haml
new file mode 100644
index 0000000..87f875d
--- /dev/null
+++ b/app/views/welcome/_rightbar_planes.html.haml
@@ -0,0 +1,5 @@
+.box.fix_cell
+ .box-header
+ 节点导航
+ = render :partial => 'welcome/rightbar_plane', :collection => Plane.cached_all(Plane.default_order_str), :as => :plane
+
diff --git a/app/views/welcome/exception.html.haml b/app/views/welcome/exception.html.haml
new file mode 100644
index 0000000..6278a71
--- /dev/null
+++ b/app/views/welcome/exception.html.haml
@@ -0,0 +1,15 @@
+.box
+ .cell
+ = build_navigation([@title], 'bigger')
+ .cell{:align => :center}
+ %h1.fade= @note
+ - unless Rails.env.production?
+ %p{:align => :left}
+ = @exception.message
+ = @exception.backtrace.join(' ').html_safe
+ .inner
+ %span.fade
+ %span.chevron ‹
+ 返回
+ = link_to '首页', root_path
+ .sep5
diff --git a/app/views/welcome/exception.mobile.haml b/app/views/welcome/exception.mobile.haml
new file mode 100644
index 0000000..70ad728
--- /dev/null
+++ b/app/views/welcome/exception.mobile.haml
@@ -0,0 +1,7 @@
+- add_breadcrumb('页面不存在')
+.cell
+ %h2.fade= @title
+ - unless Rails.env.production?
+ %p{:align => :left}
+ = @exception.message
+ = @exception.backtrace.join(' ').html_safe
diff --git a/app/views/welcome/goodbye.html.haml b/app/views/welcome/goodbye.html.haml
new file mode 100644
index 0000000..a68f6aa
--- /dev/null
+++ b/app/views/welcome/goodbye.html.haml
@@ -0,0 +1,10 @@
+.box
+ .cell
+ = build_navigation([@title])
+ .inner
+ = "你已经成功从 #{Siteconf.site_name} 退出。没有任何个人信息留在这台设备上。"
+ .sep10
+ .sep5
+ = link_to '重新登入', new_user_session_path, :class => 'btn btn-small'
+ .sep5
+
diff --git a/app/views/welcome/goodbye.mobile.haml b/app/views/welcome/goodbye.mobile.haml
new file mode 100644
index 0000000..b785a04
--- /dev/null
+++ b/app/views/welcome/goodbye.mobile.haml
@@ -0,0 +1,4 @@
+.cell
+ = "你已经成功从 #{Siteconf.site_name} 登出。没有任何个人信息留在这台设备上。"
+ .sep10
+ = link_to '重新登入', new_user_session_path
diff --git a/app/views/welcome/index.html.haml b/app/views/welcome/index.html.haml
new file mode 100644
index 0000000..14b408e
--- /dev/null
+++ b/app/views/welcome/index.html.haml
@@ -0,0 +1,7 @@
+- content_for :rightbar do
+ = render 'home_guides'
+ = render 'shared/categories'
+
+= render 'home_topics'
+= render 'home_planes'
+
diff --git a/app/views/welcome/index.mobile.haml b/app/views/welcome/index.mobile.haml
new file mode 100644
index 0000000..f755ce0
--- /dev/null
+++ b/app/views/welcome/index.mobile.haml
@@ -0,0 +1,12 @@
+.section 置顶话题
+#sticky_topics= render @sticky_topics
+.section 最新讨论
+#topics_index= render @topics
+
+- if current_user
+ .section 我的收藏
+ %table{:cellpadding => 10, :cellspacing => 0, :border => 0, :width => '100%'}
+ = render :partial => 'shared/my_fav', :formats => :html
+
+.section 节点导航
+= render @planes
diff --git a/app/views/welcome/sitemap.xml.builder b/app/views/welcome/sitemap.xml.builder
new file mode 100644
index 0000000..1cd0e73
--- /dev/null
+++ b/app/views/welcome/sitemap.xml.builder
@@ -0,0 +1,55 @@
+xml.instruct!
+xml.urlset :"xmlns" => "http://www.sitemaps.org/schemas/sitemap/0.9" do
+ if @lastmod.present?
+ xml.url do
+ xml.loc(root_url)
+ xml.lastmod(@lastmod.strftime('%Y-%m-%d'))
+ xml.changefreq('hourly')
+ xml.priority('1.0')
+ end
+ @topics.each do |topic|
+ xml.url do
+ xml.loc(t_url(topic.id))
+ if topic.comments_count > 0
+ lastmod = topic.last_comment.updated_at
+ else
+ lastmod = topic.updated_at
+ end
+ xml.lastmod(lastmod.strftime('%Y-%m-%d'))
+ xml.changefreq('daily')
+ xml.priority('0.9')
+ end
+ end
+ @guides.each do |guide|
+ xml.url do
+ xml.loc(guide_url(guide.id))
+ lastmod = guide.updated_at
+ xml.lastmod(lastmod.strftime('%Y-%m-%d'))
+ xml.changefreq('daily')
+ xml.priority('0.9')
+ end
+ end
+ @articles.each do |article|
+ xml.url do
+ xml.loc(guide_article_url(article.guide,article))
+ if article.comments_count > 0
+ lastmod = article.last_comment.updated_at
+ else
+ lastmod = article.updated_at
+ end
+ xml.lastmod(lastmod.strftime('%Y-%m-%d'))
+ xml.changefreq('daily')
+ xml.priority('0.9')
+ end
+ end
+ @pages.each do |page|
+ xml.url do
+ xml.loc(page_url(page.key))
+ xml.lastmod(page.updated_at.strftime('%Y-%m-%d'))
+ xml.changefreq('monthly')
+ xml.priority('0.8')
+ end
+ end
+ end
+end
+
diff --git a/config.ru b/config.ru
index 6d9c4fe..e1d9784 100644
--- a/config.ru
+++ b/config.ru
@@ -1,4 +1,4 @@
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
-run MakerLabLMS::Application
+run Rabel::Application
diff --git a/config/application.example.yml b/config/application.example.yml
deleted file mode 100644
index f5cb8a4..0000000
--- a/config/application.example.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-# Add account credentials and API keys here.
-# See http://railsapps.github.io/rails-environment-variables.html
-# This file should be listed in .gitignore to keep your settings secret!
-# Each entry sets a local environment variable and overrides ENV variables in the Unix shell.
-# For example, setting:
-# GMAIL_USERNAME: Your_Gmail_Username
-# makes 'Your_Gmail_Username' available as ENV["GMAIL_USERNAME"]
-# Add application configuration variables here, as shown below.
-#
-GMAIL_USERNAME: Your_Username
-GMAIL_PASSWORD: Your_Password
-ADMIN_NAME: First User
-ADMIN_EMAIL: user@example.com
-ADMIN_PASSWORD: changeme
-ROLES: [admin, user, VIP]
diff --git a/config/application.rb b/config/application.rb
index b9d0e01..886ce11 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -1,6 +1,12 @@
require File.expand_path('../boot', __FILE__)
-require 'rails/all'
+# Pick the frameworks you want:
+require "active_record/railtie"
+require "action_controller/railtie"
+require "action_mailer/railtie"
+require "active_resource/railtie"
+require "sprockets/railtie"
+# require "rails/test_unit/railtie"
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
@@ -9,29 +15,17 @@
# Bundler.require(:default, :assets, Rails.env)
end
-module MakerLabLMS
- class Application < Rails::Application
-
- # don't generate RSpec tests for views and helpers
- config.generators do |g|
-
- g.test_framework :rspec, fixture: true
- g.fixture_replacement :factory_girl, dir: 'spec/factories'
-
-
- g.view_specs false
- g.helper_specs false
- end
+I18n.enforce_available_locales = true
+module Rabel
+ class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
- # config.autoload_paths += %W(#{config.root}/extras)
config.autoload_paths += %W(#{config.root}/lib)
-
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
@@ -45,7 +39,8 @@ class Application < Rails::Application
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
- # config.i18n.default_locale = :de
+ config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.yml')]
+ config.i18n.default_locale = :zh
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
@@ -53,24 +48,18 @@ class Application < Rails::Application
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password, :password_confirmation]
- # Enable escaping HTML in JSON.
- config.active_support.escape_html_entities_in_json = true
-
- # Use SQL instead of Active Record's schema dumper when creating the database.
- # This is necessary if your schema can't be completely dumped by the schema dumper,
- # like if you have constraints or database-specific column types
- # config.active_record.schema_format = :sql
-
- # Enforce whitelist mode for mass assignment.
- # This will create an empty whitelist of attributes available for mass-assignment for all models
- # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
- # parameters by using an attr_accessible or attr_protected declaration.
- config.active_record.whitelist_attributes = true
-
# Enable the asset pipeline
config.assets.enabled = true
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
+
+ config.assets.paths += %W(#{config.root}/themes/images #{config.root}/themes/stylesheets #{config.root}/themes/javascripts)
+
+ # Enable whitelist mass assignment protection by default
+ config.active_record.whitelist_attributes = true
+
+ # Don't access the DB when precompiling the assets
+ config.assets.initialize_on_precompile = false
end
end
diff --git a/config/application.yml.example b/config/application.yml.example
new file mode 100644
index 0000000..a4551a4
--- /dev/null
+++ b/config/application.yml.example
@@ -0,0 +1,42 @@
+# 网站域名,比如 'www.apple.com' 或者 'twitter.com'
+RABEL_HOST_NAME: 'example.com'
+
+# 系统邮件的发件地址,比如 'no-replya@twitter.com'
+RABEL_SYSTEM_EMAIL: 'hello@example.com'
+
+# 推荐使用 'rake secret' 命令
+# 生成 secret token
+RABEL_SECRET_TOKEN: ''
+
+# 请使用'网站名+ 随机数'替换 'example'
+# 比如替换成 '_twitter_9527_session'
+RABEL_SESSION_KEY: '_example_session'
+
+# Devise secret key
+#
+# 推荐使用 'rake secret' 命令来生成
+RABEL_DEVISE_SECRET_KEY: ''
+
+# 缓存配置,默认情况下不需要修改
+RABEL_MEMCACHED_SERVER: '127.0.0.1:11011'
+RABEL_MEMCACHED_USERNAME: ''
+RABEL_MEMCACHED_PASSWORD: ''
+RABEL_MEMCACHED_NAMESPACE: 'example'
+
+# 又拍云存储(www.upyun.com)配置
+
+# 修改为 'on',开启图片上传功能
+RABEL_UPYUN_SWITCH: 'off'
+
+# 操作员名称
+RABEL_UPYUN_OP_NAME: 'example'
+
+# 操作员密码
+RABEL_UPYUN_OP_PASSWORD: 'example'
+
+# 空间名称
+RABEL_UPYUN_BUCKET: 'example'
+
+# 空间域名
+RABEL_UPYUN_BUCKET_DOMAIN: 'example.b0.upaiyun.com'
+
diff --git a/config/cucumber.yml b/config/cucumber.yml
new file mode 100644
index 0000000..19b288d
--- /dev/null
+++ b/config/cucumber.yml
@@ -0,0 +1,8 @@
+<%
+rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
+rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
+std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip"
+%>
+default: <%= std_opts %> features
+wip: --tags @wip:3 --wip features
+rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip
diff --git a/config/database.yml.example b/config/database.yml.example
deleted file mode 100644
index dacbede..0000000
--- a/config/database.yml.example
+++ /dev/null
@@ -1,28 +0,0 @@
-# SQLite version 3.x
-# gem install sqlite3
-#
-# Ensure the SQLite 3 gem is defined in your Gemfile
-# gem 'sqlite3'
-development:
- adapter: sqlite3
- database: db/development.sqlite3
- pool: 5
- timeout: 5000
-
-# Warning: The database defined as "test" will be erased and
-# re-generated from your development database when you run "rake".
-# Do not set this db to the same as development or production.
-test:
- adapter: sqlite3
- database: db/test.sqlite3
- pool: 5
- timeout: 5000
-
-production:
- adapter: mysql2
- encoding: utf8
- reconnect: false
- database: MakerLabLMS_production
- pool: 5
- username: root
- password: root
diff --git a/config/database.yml.mysql b/config/database.yml.mysql
new file mode 100644
index 0000000..3bd5198
--- /dev/null
+++ b/config/database.yml.mysql
@@ -0,0 +1,35 @@
+# MySQL. Versions 4.1 and 5.0 are recommended.
+#
+# Install the MYSQL driver
+# gem install mysql2
+#
+# Ensure the MySQL gem is defined in your Gemfile
+# gem 'mysql2'
+#
+# And be sure to use new-style password hashing:
+# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
+default: &default
+ adapter: mysql2
+ encoding: utf8
+ reconnect: false
+ pool: 5
+ username: root
+ password:
+ socket: /tmp/mysql.sock
+ # host: 127.0.0.1
+ # port: 3306
+
+development:
+ <<: *default
+ database: rabel_development
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ <<: *default
+ database: rabel_test
+
+production:
+ <<: *default
+ database: rabel_production
diff --git a/config/database.yml.pg b/config/database.yml.pg
new file mode 100644
index 0000000..65de88e
--- /dev/null
+++ b/config/database.yml.pg
@@ -0,0 +1,47 @@
+# PostgreSQL. Versions 7.4 and 8.x are supported.
+#
+# Install the pg driver:
+# gem install pg
+# On Mac OS X with macports:
+# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
+# On Windows:
+# gem install pg
+# Choose the win32 build.
+# Install PostgreSQL and put its /bin directory on your path.
+#
+# Configure Using Gemfile
+# gem 'pg'
+#
+default: &default
+ adapter: postgresql
+ encoding: unicode
+ pool: 5
+ username: postgres
+ password:
+ host: localhost
+ port: 5432
+
+ # Schema search path. The server defaults to $user,public
+ #schema_search_path: myapp,sharedapp,public
+
+ # Minimum log levels, in increasing order:
+ # debug5, debug4, debug3, debug2, debug1,
+ # log, notice, warning, error, fatal, and panic
+ # The server defaults to notice.
+ #min_messages: warning
+
+development:
+ <<: *default
+ database: rabel_development
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ <<: *default
+ database: rabel_test
+
+production:
+ <<: *default
+ database: rabel_production
+
diff --git a/config/environment.rb b/config/environment.rb
index 5f2934b..41af2c0 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -2,4 +2,4 @@
require File.expand_path('../application', __FILE__)
# Initialize the rails application
-MakerLabLMS::Application.initialize!
+Rabel::Application.initialize!
diff --git a/config/environments/development.rb b/config/environments/development.rb
index a8a6229..74ec71c 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -1,8 +1,8 @@
-MakerLabLMS::Application.configure do
+Rabel::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# In the development environment your application's code is reloaded on
- # every request. This slows down response time but is perfect for development
+ # every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
@@ -13,25 +13,9 @@
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
- # ActionMailer Config
+ # Don't care if the mailer can't send
+ config.action_mailer.raise_delivery_errors = false
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
- config.action_mailer.delivery_method = :smtp
- # change to true to allow email to be sent during development
- config.action_mailer.perform_deliveries = false
- config.action_mailer.raise_delivery_errors = true
- config.action_mailer.default :charset => "utf-8"
-
- config.action_mailer.smtp_settings = {
- address: "smtp.gmail.com",
- port: 587,
- domain: "example.com",
- authentication: "plain",
- enable_starttls_auto: true,
- user_name: ENV["GMAIL_USERNAME"],
- password: ENV["GMAIL_PASSWORD"]
- }
-
-
# Print deprecation notices to the Rails logger
config.active_support.deprecation = :log
@@ -39,16 +23,17 @@
# Only use best-standards-support built into browsers
config.action_dispatch.best_standards_support = :builtin
- # Raise exception on mass assignment protection for Active Record models
- config.active_record.mass_assignment_sanitizer = :strict
-
- # Log the query plan for queries taking more than this (works
- # with SQLite, MySQL, and PostgreSQL)
- config.active_record.auto_explain_threshold_in_seconds = 0.5
-
# Do not compress assets
config.assets.compress = false
# Expands the lines which load the assets
config.assets.debug = true
+ config.assets.logger = false
+
+ # Raise exception on mass assignment protection for Active Record models
+ config.active_record.mass_assignment_sanitizer = :strict
+
+ # Log the query plan for queries taking more than this (works
+ # with SQLite, MySQL, and PostgreSQL)
+ config.active_record.auto_explain_threshold_in_seconds = 0.5
end
diff --git a/config/environments/production.rb b/config/environments/production.rb
index a089cb4..28ba073 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -1,4 +1,4 @@
-MakerLabLMS::Application.configure do
+Rabel::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# Code is not reloaded between requests
@@ -20,7 +20,7 @@
# Generate digests for assets URLs
config.assets.digest = true
- # Defaults to nil and saved in location specified by config.assets.prefix
+ # Defaults to Rails.root.join("public/assets")
# config.assets.manifest = YOUR_PATH
# Specifies the header that your server uses for sending files
@@ -33,23 +33,28 @@
# See everything in the log (default is :info)
# config.log_level = :debug
- # Prepend all log lines with the following tags
- # config.log_tags = [ :subdomain, :uuid ]
-
# Use a different logger for distributed setups
- # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
+ # config.logger = SyslogLogger.new
# Use a different cache store in production
- # config.cache_store = :mem_cache_store
+ config.cache_store = :dalli_store,
+ Figaro.env.RABEL_MEMCACHED_SERVER,
+ {
+ :namespace => Figaro.env.RABEL_MEMCACHED_NAMESPACE,
+ :username => Figaro.env.RABEL_MEMCACHED_USERNAME,
+ :password => Figaro.env.RABEL_MEMCACHED_PASSWORD,
+ :compress => true
+ }
# Enable serving of images, stylesheets, and JavaScripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
- # config.assets.precompile += %w( search.js )
+ config.assets.precompile += [ Proc.new {|path| File.basename(path).start_with?('i_')} ]
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
+ config.action_mailer.delivery_method = :sendmail
# Enable threaded mode
# config.threadsafe!
@@ -60,28 +65,4 @@
# Send deprecation notices to registered listeners
config.active_support.deprecation = :notify
-
- config.action_mailer.default_url_options = { :host => 'example.com' }
- # ActionMailer Config
- # Setup for production - deliveries, no errors raised
- config.action_mailer.delivery_method = :smtp
- config.action_mailer.perform_deliveries = true
- config.action_mailer.raise_delivery_errors = false
- config.action_mailer.default :charset => "utf-8"
-
- config.action_mailer.smtp_settings = {
- address: "smtp.gmail.com",
- port: 587,
- domain: "example.com",
- authentication: "plain",
- enable_starttls_auto: true,
- user_name: ENV["GMAIL_USERNAME"],
- password: ENV["GMAIL_PASSWORD"]
- }
-
-
-
- # Log the query plan for queries taking more than this (works
- # with SQLite, MySQL, and PostgreSQL)
- # config.active_record.auto_explain_threshold_in_seconds = 0.5
end
diff --git a/config/environments/test.rb b/config/environments/test.rb
index ddb96fe..23e69a9 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -1,10 +1,10 @@
-MakerLabLMS::Application.configure do
+Rabel::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# The test environment is used exclusively to run your application's
- # test suite. You never need to work with it otherwise. Remember that
+ # test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
- # and recreated between test runs. Don't rely on the data there!
+ # and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Configure static asset server for tests with Cache-Control for performance
@@ -29,13 +29,15 @@
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
- # Raise exception on mass assignment protection for Active Record models
- config.active_record.mass_assignment_sanitizer = :strict
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
- # ActionMailer Config
- config.action_mailer.default_url_options = { :host => 'example.com' }
-
+ # Raise exception on mass assignment protection for Active Record models
+ config.active_record.mass_assignment_sanitizer = :strict
+ config.cache_store = :null_store
end
diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb
new file mode 100644
index 0000000..9bb62f6
--- /dev/null
+++ b/config/initializers/carrierwave.rb
@@ -0,0 +1 @@
+CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index f19610a..1b433e1 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -1,10 +1,10 @@
-# Use this hook to configure devise mailer, warden hooks and so forth.
-# Many of these configuration options can be set straight in your model.
+# Use this hook to configure devise mailer, warden hooks and so forth. The first
+# four configuration values can also be set straight in your models.
Devise.setup do |config|
# ==> Mailer Configuration
# Configure the e-mail address which will be shown in Devise::Mailer,
# note that it will be overwritten if you use your own mailer class with default "from" parameter.
- config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com"
+ config.mailer_sender = Figaro.env.RABEL_SYSTEM_EMAIL
# Configure the class responsible to send e-mails.
# config.mailer = "Devise::Mailer"
@@ -23,7 +23,7 @@
# session. If you need permissions, you should implement that in a before filter.
# You can also supply a hash where the value is a boolean determining whether
# or not authentication should be aborted when the value is not present.
- # config.authentication_keys = [ :email ]
+ config.authentication_keys = [ :nickname ]
# Configure parameters from the request object used for authentication. Each entry
# given should be a request method and it will automatically be passed to the
@@ -35,27 +35,17 @@
# Configure which authentication keys should be case-insensitive.
# These keys will be downcased upon creating or modifying a user and when used
# to authenticate or find a user. Default is :email.
- config.case_insensitive_keys = [ :email ]
+ config.case_insensitive_keys = []
# Configure which authentication keys should have whitespace stripped.
# These keys will have whitespace before and after removed upon creating or
# modifying a user and when used to authenticate or find a user. Default is :email.
- config.strip_whitespace_keys = [ :email ]
+ config.strip_whitespace_keys = [ :nickname ]
# Tell if authentication through request.params is enabled. True by default.
- # It can be set to an array that will enable params authentication only for the
- # given strategies, for example, `config.params_authenticatable = [:database]` will
- # enable it only for database (email + password) authentication.
# config.params_authenticatable = true
- # Tell if authentication through HTTP Auth is enabled. False by default.
- # It can be set to an array that will enable http authentication only for the
- # given strategies, for example, `config.http_authenticatable = [:token]` will
- # enable it only for token authentication. The supported strategies are:
- # :database = Support basic authentication with authentication key + password
- # :token = Support basic authentication with token authentication key
- # :token_options = Support token authentication with options as defined in
- # http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html
+ # Tell if authentication through HTTP Basic Auth is enabled. False by default.
# config.http_authenticatable = false
# If http headers should be returned for AJAX requests. True by default.
@@ -69,13 +59,6 @@
# Does not affect registerable.
# config.paranoid = true
- # By default Devise will store the user in session. You can skip storage for
- # :http_auth and :token_auth by adding those symbols to the array below.
- # Notice that if you are skipping storage for all authentication paths, you
- # may want to disable generating routes to Devise's sessions controller by
- # passing :skip => :sessions to `devise_for` in your config/routes.rb
- config.skip_session_storage = [:http_auth]
-
# ==> Configuration for :database_authenticatable
# For bcrypt, this is the cost for hashing the password and defaults to 10. If
# using other encryptors, it sets how many times you want the password re-encrypted.
@@ -86,7 +69,7 @@
config.stretches = Rails.env.test? ? 1 : 10
# Setup a pepper to generate the encrypted password.
- # config.pepper = "ec64db3d152e6986899f2035619ad650226f16cbac0d7137078f9bf4fe1b5f4f2490267b82a91a9332691d178d0351b7ec26a59358b5a53012d1677e6145a87c"
+ # config.pepper = "3870d8a048ba70be1b1ec9e6f1bac1e6bd93a96067647e7faa5fb1cf78a7309d3e5d5b8153ce6fc0ce3f3025fab48c25505f8b9f86fc99c43bc4fa77fdb88cc0"
# ==> Configuration for :confirmable
# A period that the user is allowed to access the website even without
@@ -96,40 +79,29 @@
# the user cannot access the website without confirming his account.
# config.allow_unconfirmed_access_for = 2.days
- # A period that the user is allowed to confirm their account before their
- # token becomes invalid. For example, if set to 3.days, the user can confirm
- # their account within 3 days after the mail was sent, but on the fourth day
- # their account can't be confirmed with the token any more.
- # Default is nil, meaning there is no restriction on how long a user can take
- # before confirming their account.
- # config.confirm_within = 3.days
-
- # If true, requires any email changes to be confirmed (exactly the same way as
- # initial account confirmation) to be applied. Requires additional unconfirmed_email
- # db field (see migrations). Until confirmed new email is stored in
- # unconfirmed email column, and copied to email column on successful confirmation.
- config.reconfirmable = true
-
# Defines which key will be used when confirming an account
# config.confirmation_keys = [ :email ]
# ==> Configuration for :rememberable
# The time the user will be remembered without asking for credentials again.
- # config.remember_for = 2.weeks
+ config.remember_for = 1.year
+
+ # If true, a valid remember token can be re-used between multiple browsers.
+ # config.remember_across_browsers = true
# If true, extends the user's remember period when remembered via cookie.
# config.extend_remember_period = false
# Options to be passed to the created cookie. For instance, you can set
# :secure => true in order to force SSL only cookies.
- # config.rememberable_options = {}
+ # config.cookie_options = {}
# ==> Configuration for :validatable
- # Range for password length. Default is 8..128.
- config.password_length = 8..128
+ # Range for password length. Default is 6..128.
+ # config.password_length = 6..128
# Email regex used to validate email formats. It simply asserts that
- # one (and only one) @ exists in the given string. This is mainly
+ # an one (and only one) @ exists in the given string. This is mainly
# to give user feedback and not to assert the e-mail validity.
# config.email_regexp = /\A[^@]+@[^@]+\z/
@@ -138,9 +110,6 @@
# time the user will be asked for credentials again. Default is 30 minutes.
# config.timeout_in = 30.minutes
- # If true, expires auth token on session timeout.
- # config.expire_auth_token_on_timeout = false
-
# ==> Configuration for :lockable
# Defines which strategy will be used to lock an account.
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
@@ -167,27 +136,29 @@
# ==> Configuration for :recoverable
#
# Defines which key will be used when recovering the password for an account
- # config.reset_password_keys = [ :email ]
+ config.reset_password_keys = [ :nickname, :email ]
# Time interval you can reset your password with a reset password key.
# Don't put a too small interval or your users won't have the time to
# change their passwords.
- config.reset_password_within = 6.hours
+ config.reset_password_within = 12.hours
# ==> Configuration for :encryptable
# Allow you to use another encryption algorithm besides bcrypt (default). You can use
# :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
# :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
# and :restful_authentication_sha1 (then you should set stretches to 10, and copy
- # REST_AUTH_SITE_KEY to pepper).
- #
- # Require the `devise-encryptable` gem when using anything other than bcrypt
+ # REST_AUTH_SITE_KEY to pepper)
# config.encryptor = :sha512
# ==> Configuration for :token_authenticatable
# Defines name of the authentication token params key
# config.token_authentication_key = :auth_token
+ # If true, authentication through token does not store user in session and needs
+ # to be supplied on each request. Useful if you are using the token as API token.
+ # config.stateless_token = false
+
# ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
@@ -198,8 +169,9 @@
# devise role declared in your routes (usually :user).
# config.default_scope = :user
- # Set this configuration to false if you want /users/sign_out to sign out
- # only the current scope. By default, Devise signs out all scopes.
+ # Configure sign_out behavior.
+ # Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope).
+ # The default is true, which means any logout action will sign out all active scopes.
# config.sign_out_all_scopes = true
# ==> Navigation configuration
@@ -210,8 +182,9 @@
# If you have any extra navigational formats, like :iphone or :mobile, you
# should add them to the navigational formats lists.
#
- # The "*/*" below is required to match Internet Explorer requests.
- # config.navigational_formats = ["*/*", :html]
+ # The :"*/*" and "*/*" formats below is required to match Internet
+ # Explorer requests.
+ config.navigational_formats = [:"*/*", "*/*", :html, :mobile]
# The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :delete
@@ -226,21 +199,12 @@
# change the failure app, you can configure them inside the config.warden block.
#
# config.warden do |manager|
+ # manager.failure_app = AnotherApp
# manager.intercept_401 = false
# manager.default_strategies(:scope => :user).unshift :some_external_strategy
# end
- # ==> Mountable engine configurations
- # When using Devise inside an engine, let's call it `MyEngine`, and this engine
- # is mountable, there are some extra configurations to be taken into account.
- # The following options are available, assuming the engine is mounted as:
- #
- # mount MyEngine, at: "/my_engine"
- #
- # The router that invoked `devise_for`, in the example above, would be:
- # config.router_name = :my_engine
- #
- # When using omniauth, Devise cannot automatically set Omniauth path,
- # so you need to do it manually. For the users scope, it would be:
- # config.omniauth_path_prefix = "/my_engine/users/auth"
+ # Secret key for devise
+ config.secret_key = Figaro.env.RABEL_DEVISE_SECRET_KEY
end
+
diff --git a/config/initializers/form_error.rb b/config/initializers/form_error.rb
new file mode 100644
index 0000000..af17b04
--- /dev/null
+++ b/config/initializers/form_error.rb
@@ -0,0 +1,11 @@
+ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
+ if html_tag =~ /
+ #{instance.error_message.join(',')}).html_safe
+ else
+ %(#{html_tag}
+ #{instance.error_message} ).html_safe
+ end
+end
diff --git a/config/initializers/generators.rb b/config/initializers/generators.rb
deleted file mode 100644
index 4b3d83a..0000000
--- a/config/initializers/generators.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-Rails.application.config.generators do |g|
-end
diff --git a/config/initializers/i18n.rb b/config/initializers/i18n.rb
new file mode 100644
index 0000000..e260c12
--- /dev/null
+++ b/config/initializers/i18n.rb
@@ -0,0 +1,8 @@
+module I18n
+ def self.custom_handler(exception, key, locale, options)
+ # custom error handling logic
+ locale.to_s.humanize
+ end
+end
+
+I18n.exception_handler = :custom_handler
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index 5d8d9be..9e8b013 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -8,8 +8,3 @@
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
-#
-# These inflection rules are supported but not enabled by default:
-# ActiveSupport::Inflector.inflections do |inflect|
-# inflect.acronym 'RESTful'
-# end
diff --git a/config/initializers/kaminari.rb b/config/initializers/kaminari.rb
new file mode 100644
index 0000000..2d2152a
--- /dev/null
+++ b/config/initializers/kaminari.rb
@@ -0,0 +1,9 @@
+module Kaminari
+ module Helpers
+ class Tag
+ def page_url_for(page)
+ @template.url_for @params.merge(@param_name => (page < 1 ? nil : page))
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/config/initializers/markdown.rb b/config/initializers/markdown.rb
new file mode 100644
index 0000000..c25070c
--- /dev/null
+++ b/config/initializers/markdown.rb
@@ -0,0 +1,147 @@
+module Redcarpet
+ module Render
+ class RabelTopicRender < HTML
+ def initialize(options={})
+ super options.merge({:xhtml => true,
+ :filter_html => false,
+ :hard_wrap => true})
+ end
+
+ def block_code(code, language)
+ begin
+ result = CodeRay.scan(code, language || :text).div(:tab_width => 2).sub("\n", '')
+ i = result.rindex("\n")
+ result = result[0..i-1] + result[i+1..-1]
+ Rabel::Base.protect_at_symbol result.gsub("\n", " ")
+ rescue Exception => e
+ "~~~~#{Rabel::Base.h language}#{Rabel::Base.h code}"
+ end
+ end
+
+ def block_quote(quote)
+ %(#{quote} )
+ end
+
+ def block_html(raw_html)
+ #Rabel::Base.h raw_html
+ raw_html
+ end
+
+ def header(text, header_level)
+ %(#{Rabel::Base.h text} )
+ end
+
+ def hrule()
+ %( )
+ end
+
+ def list(contents, list_type)
+ case list_type
+ when :unordered
+ %()
+ when :ordered
+ %(#{contents} )
+ end
+ end
+
+ def list_item(text, list_type)
+ %(#{text} )
+ end
+
+ def paragraph(text)
+ %(#{text}
)
+ end
+
+ def table(header, body)
+ %(#{header.gsub('td', 'th')} #{body}
)
+ end
+
+ def table_row(content)
+ %(#{content} )
+ end
+
+ def table_cell(content, alignment)
+ %(#{content} )
+ end
+
+ def autolink(link, link_type)
+ case link_type
+ when :url
+ Rabel::Base.smart_url(link)
+ when :email
+ Rabel::Base.email_link(Rabel::Base.protect_at_symbol(link))
+ end
+ end
+
+ def codespan(code)
+ %(#{Rabel::Base.protect_at_symbol(Rabel::Base.h(code))})
+ end
+
+ def double_emphasis(text)
+ %(#{text} )
+ end
+
+ def emphasis(text)
+ "#{text} "
+ end
+
+ def image(link, title, alt_text)
+ %( )
+ end
+
+ def linebreak()
+ " "
+ end
+
+ def link(link, title, content)
+ content = link unless content.present?
+ Rabel::Base.external_link(content, link)
+ end
+
+ def raw_html(raw_html)
+ #Rabel::Base.h raw_html
+ raw_html.html_safe
+ end
+
+ def triple_emphasis(text)
+ %(***#{text}*** )
+ end
+
+ def strikethrough(text)
+ %(#{text})
+ end
+
+ def superscript(text)
+ %(#{text} )
+ end
+ end
+ end
+end
+
+class MarkdownConverter
+ include Singleton
+
+ def self.convert(text)
+ self.instance.convert(text)
+ end
+
+ def convert(text)
+ @converter.render(text)
+ end
+
+ private
+ def initialize
+ @converter = Redcarpet::Markdown.new(
+ Redcarpet::Render::RabelTopicRender.new,
+ {
+ :no_intra_emphasis => true,
+ :fenced_code_blocks => true,
+ :autolink => true,
+ :strikethrough => true,
+ :space_after_headers => true,
+ :tables => true,
+ :superscript => true,
+ }
+ )
+ end
+end
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index 72aca7e..b7ad632 100644
--- a/config/initializers/mime_types.rb
+++ b/config/initializers/mime_types.rb
@@ -2,4 +2,4 @@
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
-# Mime::Type.register_alias "text/html", :iphone
+Mime::Type.register_alias "text/html", :mobile
diff --git a/config/initializers/monkey_patch.rb b/config/initializers/monkey_patch.rb
new file mode 100644
index 0000000..1657d18
--- /dev/null
+++ b/config/initializers/monkey_patch.rb
@@ -0,0 +1,11 @@
+# configure custom format
+ActionController::Responder.class_eval do
+ alias :to_mobile :to_html
+end
+
+ActiveRecord::Base.class_eval <<-CODE
+ def html_id
+ self.class.name.downcase + "_" + self.id.to_s
+ end
+CODE
+
diff --git a/config/initializers/rolify.rb b/config/initializers/rolify.rb
deleted file mode 100644
index c8df67a..0000000
--- a/config/initializers/rolify.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-Rolify.configure do |config|
- # By default ORM adapter is ActiveRecord. uncomment to use mongoid
- # config.use_mongoid
-
- # Dynamic shortcuts for User class (user.is_admin? like methods). Default is: false
- # Enable this feature _after_ running rake db:migrate as it relies on the roles table
- # config.use_dynamic_shortcuts
-end
\ No newline at end of file
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index 5202546..4ef7128 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -4,4 +4,4 @@
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-MakerLabLMS::Application.config.secret_token = 'fc1c1b9714cbaa2a77355c530285ec628fcddc1055b60546e6d792cb36c513a8c9d37986c418ae6c2e937739ac4094d2bf452a5db037b1040bba77ad5de2bcfa'
+Rabel::Application.config.secret_token = Figaro.env.RABEL_SECRET_TOKEN
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 9dfc924..9b8874d 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -1,8 +1,8 @@
# Be sure to restart your server when you modify this file.
-MakerLabLMS::Application.config.session_store :cookie_store, key: '_makerLabLMS_session'
+Rabel::Application.config.session_store :cookie_store, key: Figaro.env.RABEL_SESSION_KEY
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rails generate session_migration")
-# MakerLabLMS::Application.config.session_store :active_record_store
+# Rabel::Application.config.session_store :active_record_store
diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb
index e3f8d09..534340e 100644
--- a/config/initializers/simple_form.rb
+++ b/config/initializers/simple_form.rb
@@ -1,3 +1,27 @@
+inputs = %w[
+ CollectionSelectInput
+ DateTimeInput
+ FileInput
+ GroupedCollectionSelectInput
+ NumericInput
+ PasswordInput
+ RangeInput
+ StringInput
+ TextInput
+]
+
+inputs.each do |input_type|
+ superclass = "SimpleForm::Inputs::#{input_type}".constantize
+
+ new_class = Class.new(superclass) do
+ def input_html_classes
+ super.push('form-control')
+ end
+ end
+
+ Object.const_set(input_type, new_class)
+end
+
# Use this setup block to configure all options available in SimpleForm.
SimpleForm.setup do |config|
# Wrappers are used by the form builder to generate a
@@ -5,6 +29,64 @@
# wrapper, change the order or even add your own to the
# stack. The options given below are used to wrap the
# whole input.
+
+ config.wrappers :bootstrap3, tag: 'div', class: 'form-group', error_class: 'has-error',
+ defaults: { input_html: { class: 'default_class' } } do |b|
+
+ b.use :html5
+ b.use :min_max
+ b.use :maxlength
+ b.use :placeholder
+
+ b.optional :pattern
+ b.optional :readonly
+
+ b.use :label_input
+ b.use :hint, wrap_with: { tag: 'span', class: 'help-block' }
+ b.use :error, wrap_with: { tag: 'span', class: 'help-block has-error' }
+ end
+
+ config.wrappers :prepend, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
+ b.use :html5
+ b.use :placeholder
+ b.wrapper tag: 'div', class: 'controls' do |input|
+ input.wrapper tag: 'div', class: 'input-group' do |prepend|
+ prepend.use :label, :wrap_with => {class: 'input-group-addon'} ###Please note setting class here fro the label does not currently work (let me know if you know a workaround as this is the final hurdle)
+ prepend.use :input
+ end
+ input.use :hint, wrap_with: { tag: 'span', class: 'help-block' }
+ input.use :error, wrap_with: { tag: 'span', class: 'help-block has-error' }
+ end
+ end
+
+ config.wrappers :append, tag: 'div', class: 'form-group', error_class: 'has-error' do |b|
+ b.use :html5
+ b.use :placeholder
+ b.wrapper tag: 'div', class: 'controls' do |input|
+ input.wrapper tag: 'div', class: 'input-group' do |prepend|
+ prepend.use :input
+ prepend.use :label, :wrap_with => {class: 'input-group-addon'} ###Please note setting class here fro the label does not currently work (let me know if you know a workaround as this is the final hurdle)
+ end
+ input.use :hint, wrap_with: { tag: 'span', class: 'help-block' }
+ input.use :error, wrap_with: { tag: 'span', class: 'help-block has-error' }
+ end
+ end
+
+ config.wrappers :checkbox, tag: :div, class: "checkbox", error_class: "has-error" do |b|
+
+ # Form extensions
+ b.use :html5
+
+ # Form components
+ b.wrapper tag: :label do |ba|
+ ba.use :input
+ ba.use :label_text
+ end
+
+ b.use :hint, wrap_with: { tag: :p, class: "help-block" }
+ b.use :error, wrap_with: { tag: :span, class: "help-block text-danger" }
+ end
+
config.wrappers :default, :class => :input,
:hint_class => :field_with_hint, :error_class => :field_with_errors do |b|
## Extensions enabled by default
@@ -46,7 +128,7 @@
end
# The default wrapper to be used by the FormBuilder.
- config.default_wrapper = :default
+ config.default_wrapper = :bootstrap3
# Define the way to render check boxes / radio buttons with labels.
# Defaults to :nested for bootstrap config.
diff --git a/config/initializers/simple_form_bootstrap.rb b/config/initializers/simple_form_bootstrap.rb
deleted file mode 100644
index 1a22967..0000000
--- a/config/initializers/simple_form_bootstrap.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# Use this setup block to configure all options available in SimpleForm.
-SimpleForm.setup do |config|
- config.wrappers :bootstrap, :tag => 'div', :class => 'control-group', :error_class => 'error' do |b|
- b.use :html5
- b.use :placeholder
- b.use :label
- b.wrapper :tag => 'div', :class => 'controls' do |ba|
- ba.use :input
- ba.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
- ba.use :hint, :wrap_with => { :tag => 'p', :class => 'help-block' }
- end
- end
-
- config.wrappers :prepend, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
- b.use :html5
- b.use :placeholder
- b.use :label
- b.wrapper :tag => 'div', :class => 'controls' do |input|
- input.wrapper :tag => 'div', :class => 'input-prepend' do |prepend|
- prepend.use :input
- end
- input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
- input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
- end
- end
-
- config.wrappers :append, :tag => 'div', :class => "control-group", :error_class => 'error' do |b|
- b.use :html5
- b.use :placeholder
- b.use :label
- b.wrapper :tag => 'div', :class => 'controls' do |input|
- input.wrapper :tag => 'div', :class => 'input-append' do |append|
- append.use :input
- end
- input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' }
- input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' }
- end
- end
-
- # Wrappers for forms and inputs using the Twitter Bootstrap toolkit.
- # Check the Bootstrap docs (http://twitter.github.com/bootstrap)
- # to learn about the different styles for forms and inputs,
- # buttons and other elements.
- config.default_wrapper = :bootstrap
-end
diff --git a/config/initializers/siteconf.rb b/config/initializers/siteconf.rb
new file mode 100644
index 0000000..07a5798
--- /dev/null
+++ b/config/initializers/siteconf.rb
@@ -0,0 +1,32 @@
+# encoding: utf-8
+
+Siteconf.defaults[:site_name] = 'Rabel' # 站点名称,如: Rabel
+Siteconf.defaults[:welcome_tip] = '欢迎访问Rabel ' # 网站欢迎语, 支持html标签
+Siteconf.defaults[:splash] = ''
+Siteconf.defaults[:ga_id] = '' # Google Analytics ID
+Siteconf.defaults[:default_search_engine] = 'google' # 默认搜索引擎,可从下面的搜索引擎列表中选择
+Siteconf.defaults[:seo_description] = 'Rabel - 新一代简洁社区软件' # SEO 描述
+Siteconf.defaults[:short_intro] = '新一代简洁社区软件' # 网站简短介绍, 显示在右侧边栏
+Siteconf.defaults[:footer] = '© 2012 Rabel
'
+Siteconf.defaults[:mobile_footer] = '© 2012 Rabel'
+Siteconf.defaults[:custom_css] = '' # 全局自定义CSS
+Siteconf.defaults[:custom_js] = '' # 全局自定义JavaScript
+Siteconf.defaults[:custom_head_tags] = '' # 自定义Head标签
+Siteconf.defaults[:pagination_topics] = "25"
+Siteconf.defaults[:pagination_comments] = "100"
+Siteconf.defaults[:nav_position] = 'bottom'
+Siteconf.defaults[:show_captcha] = 'off'
+Siteconf.defaults[:custom_logo] = ''
+Siteconf.defaults[:global_banner] = ''
+Siteconf.defaults[:theme] = 'rabel'
+Siteconf.defaults[:global_sidebar_block] = ''
+Siteconf.defaults[:show_community_stats] = 'on'
+Siteconf.defaults[:allow_markdown_in_topics] = 'on'
+Siteconf.defaults[:allow_markdown_in_comments] = 'on'
+Siteconf.defaults[:allow_markdown_in_pages] = 'on'
+Siteconf.defaults[:topic_editable_period_str] = '5'
+Siteconf.defaults[:reward_title] = '银币'
+Siteconf.defaults[:sticky_topics_heading] = '置顶话题'
+Siteconf.defaults[:latest_topics_heading] = '最新讨论'
+Siteconf.defaults[:topic_list_style] = 'complex'
+
diff --git a/config/initializers/version.rb b/config/initializers/version.rb
new file mode 100644
index 0000000..f260a0b
--- /dev/null
+++ b/config/initializers/version.rb
@@ -0,0 +1,14 @@
+module Rabel
+ module VERSION #:nodoc:
+ MAJOR = 1
+ MINOR = 6
+ TINY = 0
+ PRE = 'dev'
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ end
+
+ def self.version
+ self::VERSION::STRING
+ end
+end
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index d01f375..cc383cb 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -1,59 +1,60 @@
-# Additional translations at https://github.com/plataformatec/devise/wiki/I18n
+# Additional translations at http://github.com/plataformatec/devise/wiki/I18n
en:
- devise:
- confirmations:
- confirmed: "Your account was successfully confirmed. You are now signed in."
- send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes."
- send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes."
- failure:
- already_authenticated: "You are already signed in."
- inactive: "Your account was not activated yet."
- invalid: "Invalid email or password."
- invalid_token: "Invalid authentication token."
- locked: "Your account is locked."
- not_found_in_database: "Invalid email or password."
- timeout: "Your session expired, please sign in again to continue."
- unauthenticated: "You need to sign in or sign up before continuing."
- unconfirmed: "You have to confirm your account before continuing."
- mailer:
- confirmation_instructions:
- subject: "Confirmation instructions"
- reset_password_instructions:
- subject: "Reset password instructions"
- unlock_instructions:
- subject: "Unlock Instructions"
- omniauth_callbacks:
- failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
- success: "Successfully authenticated from %{kind} account."
- passwords:
- no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
- send_instructions: "You will receive an email with instructions about how to reset your password in a few minutes."
- send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
- updated: "Your password was changed successfully. You are now signed in."
- updated_not_active: "Your password was changed successfully."
- registrations:
- destroyed: "Bye! Your account was successfully cancelled. We hope to see you again soon."
- signed_up: "Welcome! You have signed up successfully."
- signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
- signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
- signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
- update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address."
- updated: "You updated your account successfully."
- sessions:
- signed_in: "Signed in successfully."
- signed_out: "Signed out successfully."
- unlocks:
- send_instructions: "You will receive an email with instructions about how to unlock your account in a few minutes."
- send_paranoid_instructions: "If your account exists, you will receive an email with instructions about how to unlock it in a few minutes."
- unlocked: "Your account has been unlocked successfully. Please sign in to continue."
errors:
messages:
- already_confirmed: "was already confirmed, please try signing in"
- confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
expired: "has expired, please request a new one"
not_found: "not found"
+ already_confirmed: "was already confirmed, please try signing in"
not_locked: "was not locked"
not_saved:
one: "1 error prohibited this %{resource} from being saved:"
other: "%{count} errors prohibited this %{resource} from being saved:"
+
+ devise:
+ failure:
+ already_authenticated: 'You are already signed in.'
+ unauthenticated: 'You need to sign in or sign up before continuing.'
+ unconfirmed: 'You have to confirm your account before continuing.'
+ locked: 'Your account is locked.'
+ invalid: 'Invalid email or password.'
+ invalid_token: 'Invalid authentication token.'
+ timeout: 'Your session expired, please sign in again to continue.'
+ inactive: 'Your account was not activated yet.'
+ not_found_in_database: 'Invalid email or password.'
+ sessions:
+ signed_in: 'Signed in successfully.'
+ signed_out: 'Signed out successfully.'
+ passwords:
+ send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.'
+ updated: 'Your password was changed successfully. You are now signed in.'
+ updated_not_active: 'Your password was changed successfully.'
+ send_paranoid_instructions: "If your e-mail exists on our database, you will receive a password recovery link on your e-mail"
+ confirmations:
+ send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.'
+ send_paranoid_instructions: 'If your e-mail exists on our database, you will receive an email with instructions about how to confirm your account in a few minutes.'
+ confirmed: 'Your account was successfully confirmed. You are now signed in.'
+ registrations:
+ signed_up: 'Welcome! You have signed up successfully.'
+ updated: 'You updated your account successfully.'
+ destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.'
+ signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.'
+ signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.'
+ signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.'
+ unlocks:
+ send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
+ unlocked: 'Your account was successfully unlocked. You are now signed in.'
+ send_paranoid_instructions: 'If your account exists, you will receive an email with instructions about how to unlock it in a few minutes.'
+ omniauth_callbacks:
+ success: 'Successfully authorized from %{kind} account.'
+ failure: 'Could not authorize you from %{kind} because "%{reason}".'
+ mailer:
+ confirmation_instructions:
+ subject: 'Confirmation instructions'
+ reset_password_instructions:
+ subject: 'Reset password instructions'
+ unlock_instructions:
+ subject: 'Unlock Instructions'
+ users:
+ user:
+ updated: Settings Updated
diff --git a/config/locales/devise.zh.yml b/config/locales/devise.zh.yml
new file mode 100644
index 0000000..024b5d7
--- /dev/null
+++ b/config/locales/devise.zh.yml
@@ -0,0 +1,63 @@
+# Additional translations at http://github.com/plataformatec/devise/wiki/I18n
+
+zh:
+ errors:
+ messages:
+ expired: "has expired, please request a new one"
+ not_found: "不存在"
+ already_confirmed: "was already confirmed, please try signing in"
+ not_locked: "was not locked"
+ not_saved:
+ one: 请解决以下错误之后再继续
+ other: 请解决以下错误之后再继续
+
+ devise:
+ failure:
+ already_authenticated: ''
+ unauthenticated: '请先登录或注册'
+ unconfirmed: 'You have to confirm your account before continuing.'
+ locked: 'Your account is locked.'
+ invalid: '无效的用户名或密码'
+ invalid_token: 'Invalid authentication token.'
+ timeout: 'Your session expired, please sign in again to continue.'
+ inactive: 'Your account was not activated yet.'
+ not_found_in_database: '无效的用户名或密码'
+ user:
+ blocked: '管理员取消了您的登录权限。'
+ sessions:
+ signed_in: ''
+ signed_out: ''
+ passwords:
+ send_instructions: '现在请去查看你的注册邮箱,其中有帮助你重新设置密码的链接。'
+ updated: '你已经成功修改了密码。'
+ updated_not_active: 'Your password was changed successfully.'
+ send_paranoid_instructions: "If your e-mail exists on our database, you will receive a password recovery link on your e-mail"
+ confirmations:
+ send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.'
+ send_paranoid_instructions: 'If your e-mail exists on our database, you will receive an email with instructions about how to confirm your account in a few minutes.'
+ confirmed: 'Your account was successfully confirmed. You are now signed in.'
+ registrations:
+ signed_up: ''
+ updated: '密码已成功更新,下次请用新密码登录'
+ destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.'
+ signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.'
+ signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.'
+ signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.'
+ unlocks:
+ send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
+ unlocked: 'Your account was successfully unlocked. You are now signed in.'
+ send_paranoid_instructions: 'If your account exists, you will receive an email with instructions about how to unlock it in a few minutes.'
+ omniauth_callbacks:
+ success: 'Successfully authorized from %{kind} account.'
+ failure: 'Could not authorize you from %{kind} because "%{reason}".'
+ mailer:
+ confirmation_instructions:
+ subject: 'Confirmation instructions'
+ reset_password_instructions:
+ subject: '重新设置密码'
+ unlock_instructions:
+ subject: 'Unlock Instructions'
+ users:
+ user:
+ updated: 个人设置成功更新
+
diff --git a/config/locales/simple_form.zh.yml b/config/locales/simple_form.zh.yml
new file mode 100644
index 0000000..762eca0
--- /dev/null
+++ b/config/locales/simple_form.zh.yml
@@ -0,0 +1,26 @@
+zh:
+ simple_form:
+ "yes": 'Yes'
+ "no": 'No'
+ required:
+ text: '必填'
+ mark: '*'
+ # You can uncomment the line below if you need to overwrite the whole required html.
+ # When using html, text and mark won't be used.
+ html: ''
+ error_notification:
+ default_message: "Please review the problems below:"
+ # Labels and hints examples
+ # labels:
+ # defaults:
+ # password: 'Password'
+ # user:
+ # new:
+ # email: 'E-mail to sign in.'
+ # edit:
+ # email: 'E-mail.'
+ # hints:
+ # defaults:
+ # username: 'User name to sign in.'
+ # password: 'No special characters, please.'
+
diff --git a/config/locales/views/layout.zh.yml b/config/locales/views/layout.zh.yml
new file mode 100644
index 0000000..503bd3c
--- /dev/null
+++ b/config/locales/views/layout.zh.yml
@@ -0,0 +1,7 @@
+zh:
+ homepage: '首页'
+ sign_in: '登入'
+ sign_up: '注册'
+ sign_out: '退出'
+ search: '搜索话题'
+ dashboard: '管理后台'
diff --git a/config/locales/zh.yml b/config/locales/zh.yml
new file mode 100644
index 0000000..d7f02dc
--- /dev/null
+++ b/config/locales/zh.yml
@@ -0,0 +1,242 @@
+# Sample localization file for English. Add more files in this directory for other locales.
+# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+# Chinese (China) translations for Ruby on Rails
+# by tsechingho (http://github.com/tsechingho)
+
+zh:
+ date:
+ formats:
+ default: "%Y-%m-%d"
+ short: "%b%d日"
+ long: "%Y年%b%d日"
+ day_names: [星期一, 星期二, 星期三, 星期四, 星期五, 星期六, 星期日]
+ abbr_day_names: [一, 二, 三, 四, 五, 六, 日]
+ month_names: [~, 一月, 二月, 三月, 四月, 五月, 六月, 七月, 八月, 九月, 十月, 十一月, 十二月]
+ abbr_month_names: [~, 1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月]
+ order:
+ - :year
+ - :month
+ - :day
+
+ time:
+ formats:
+ default: "%Y年%b%d日 %A %H:%M:%S %Z"
+ short: "%b%d日 %H:%M"
+ long: "%Y年%b%d日 %H:%M"
+ am: "上午"
+ pm: "下午"
+
+ datetime:
+ distance_in_words:
+ half_a_minute: "半分钟前"
+ less_than_x_seconds:
+ one: "不到一秒前"
+ other: "不到 %{count} 秒前"
+ x_seconds:
+ one: "一秒前"
+ other: "%{count} 秒前"
+ less_than_x_minutes:
+ one: "不到一分钟前"
+ other: "不到 %{count} 分钟前"
+ x_minutes:
+ one: "一分钟前"
+ other: "%{count} 分钟前"
+ about_x_hours:
+ one: "一小时前"
+ other: "%{count} 小时前"
+ x_days:
+ one: "一天前"
+ other: "%{count} 天前"
+ about_x_months:
+ one: "一个月前"
+ other: "%{count} 个月前"
+ x_months:
+ one: "一个月前"
+ other: "%{count} 个月前"
+ about_x_years:
+ one: "一年前"
+ other: "%{count} 年前"
+ over_x_years:
+ one: "一年前"
+ other: "%{count} 年前"
+ almost_x_years:
+ one: "一年前"
+ other: "%{count} 年前"
+ prompts:
+ year: "年"
+ month: "月"
+ day: "日"
+ hour: "时"
+ minute: "分"
+ second: "秒"
+
+ number:
+ format:
+ separator: "."
+ delimiter: ","
+ precision: 3
+ significant: false
+ strip_insignificant_zeros: false
+ currency:
+ format:
+ format: "%u %n"
+ unit: "CN¥"
+ separator: "."
+ delimiter: ","
+ precision: 2
+ significant: false
+ strip_insignificant_zeros: false
+ percentage:
+ format:
+ delimiter: ""
+ precision:
+ format:
+ delimiter: ""
+ human:
+ format:
+ delimiter: ""
+ precision: 1
+ significant: false
+ strip_insignificant_zeros: false
+ storage_units:
+ format: "%n %u"
+ units:
+ byte:
+ one: "Byte"
+ other: "Bytes"
+ kb: "KB"
+ mb: "MB"
+ gb: "GB"
+ tb: "TB"
+ decimal_units:
+ format: "%n %u"
+ units:
+ # 10^-21 zepto, 10^-24 yocto
+ atto: "渺" # 10^-18
+ femto: "飞" # 10^-15 毫微微
+ pico: "漠" # 10^-12 微微
+ nano: "奈" # 10^-9 毫微
+ micro: "微" # 10^-6
+ mili: "毫" # 10^-3 milli
+ centi: "厘" # 10^-2
+ deci: "分" # 10^-1
+ unit: ""
+ ten:
+ one: "十"
+ other: "十" # 10^1
+ hundred: "百" # 10^2
+ thousand: "千" # 10^3 kilo
+ million: "百万" # 10^6 mega
+ billion: "十亿" # 10^9 giga
+ trillion: "兆" # 10^12 tera
+ quadrillion: "千兆" # 10^15 peta
+ # 10^18 exa, 10^21 zetta, 10^24 yotta
+
+ support:
+ array:
+ words_connector: ", "
+ two_words_connector: " 和 "
+ last_word_connector: ", 和 "
+ select:
+ prompt: "请选择"
+
+ activerecord:
+ modules:
+ user: 用户
+ attributes:
+ user:
+ nickname: 用户名
+ email: 电子邮件
+ password: 密码
+ password_confirmation: 密码确认
+ errors:
+ template: # ~ 2.3.5 backward compatible
+ header:
+ one: "有 1 个错误需要修正。"
+ other: "有 %{count} 个错误需要修正。"
+ body: "如下字段出现错误:"
+ full_messages:
+ format: "%{attribute} %{message}"
+ messages:
+ inclusion: "不包含于列表中"
+ exclusion: "是保留关键字"
+ invalid: "是无效的"
+ confirmation: "与确认值不匹配"
+ accepted: "必须是可被接受的"
+ empty: "不能留空"
+ blank: "不能为空字符"
+ too_long: "过长(最长为 %{count} 个字符)"
+ too_short: "过短(最短为 %{count} 个字符)"
+ wrong_length: "长度非法(必须为 %{count} 个字符)"
+ not_a_number: "不是数字"
+ not_an_integer: "必须是整数"
+ greater_than: "必须大于 %{count}"
+ greater_than_or_equal_to: "必须大于或等于 %{count}"
+ equal_to: "必须等于 %{count}"
+ less_than: "必须小于 %{count}"
+ less_than_or_equal_to: "必须小于或等于 %{count}"
+ odd: "必须为单数"
+ even: "必须为双数"
+ taken: "已经被使用"
+ record_invalid: "校验失败: %{errors}"
+
+ activemodel:
+ errors:
+ template:
+ header:
+ one: "有 1 个错误发生导致「%{model}」无法被保存。"
+ other: "有 %{count} 个错误发生导致「%{model}」无法被保存。"
+ body: "如下字段出现错误:"
+
+ errors:
+ format: "%{attribute} %{message}"
+ messages:
+ inclusion: "不包含于列表中"
+ exclusion: "是保留关键字"
+ invalid: "是无效的"
+ confirmation: "与确认值不匹配"
+ accepted: "必须是可被接受的"
+ empty: "不能留空"
+ blank: "不能为空字符"
+ too_long: "过长(最长为 %{count} 个字符)"
+ too_short: "过短(最短为 %{count} 个字符)"
+ wrong_length: "长度非法(必须为 %{count} 个字符)"
+ not_a_number: "不是数字"
+ not_an_integer: "必须是整数"
+ greater_than: "必须大于 %{count}"
+ greater_than_or_equal_to: "必须大于或等于 %{count}"
+ equal_to: "必须等于 %{count}"
+ less_than: "必须小于 %{count}"
+ less_than_or_equal_to: "必须小于或等于 %{count}"
+ odd: "必须为单数"
+ even: "必须为双数"
+ extension_white_list_error: "不支持此扩展名"
+
+ helpers:
+ select:
+ prompt: "请选择"
+ submit:
+ create: "新增%{model}"
+ update: "更新%{model}"
+ submit: "储存%{model}"
+
+ tips:
+ submitting: 正在提交
+ comments_closed: 该话题的回复权限已关闭
+ custom_logo_path: 请输入 Logo 相对路径
+ banner_path: 请输入 Banner 图片相对路径
+ quiet_node: 本节点话题禁登首页
+ no_permission: 你没有权限访问刚才的页面
+ node_key_format: "只允许英文字符,数字,下划线(_)和短横线(-)"
+ topic:
+ errors:
+ not_editable: 主题在创建5分钟后无法编辑
+ delete_confirm: 真的要删除吗?
+ views:
+ pagination:
+ first: "FIRST"
+ last: "LAST"
+ previous: "←"
+ next: "→"
+ truncate: "..."
diff --git a/config/nginx.conf b/config/nginx.conf
deleted file mode 100644
index 311ffe4..0000000
--- a/config/nginx.conf
+++ /dev/null
@@ -1,28 +0,0 @@
-upstream unicorn-makerlab {
- server unix:/tmp/unicorn.makerlab.sock fail_timeout=0;
-}
-
-server {
- listen 80 ;
- server_name makerlab.me www.makerlab.me learn.makerlab.me;
- root /home/rabel/MakerLabLMS/public;
- #root /vagrant
-
- location ^~ /assets/ {
- gzip_static on;
- expires max;
- add_header Cache-Control public;
- }
-
- try_files $uri/index.html $uri @unicorn;
- location @unicorn {
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header Host $http_host;
- proxy_redirect off;
- proxy_pass http://unicorn-makerlab;
- }
-
- error_page 500 502 503 504 /500.html;
- client_max_body_size 4G;
- keepalive_timeout 10;
-}
diff --git a/config/routes.rb b/config/routes.rb
index dc3d155..4b53726 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,25 +1,167 @@
-MakerLabLMS::Application.routes.draw do
+Rabel::Application.routes.draw do
+ resources :articles do
+ resources :comments
+ end
- resources :downloads
+ resources :guides do
+ resources :bookmarks
+ resources :articles
+ get :new_from_home, :on => :collection
+ post :create_from_home, :on => :collection
+ member do
+ get :move
+ get :edit_title
+ put :update_title
+ end
+ end
- resources :products
+ devise_for :users, :controllers => {:sessions => "sessions", :registrations => "registrations"}
+ get 'settings' => 'users#edit'
+ get 'member/:nickname' => 'users#show', :as => :member
+ get 'member/:nickname/topics' => 'users#topics', :as => :member_topics
+ get 'member/:nickname/guides' => 'users#guides', :as => :member_guides
+ post 'member/:nickname/follow' => 'users#follow', :as => :follow_user
+ post 'member/:nickname/unfollow' => 'users#unfollow', :as => :unfollow_user
+ put 'users/update_account' => 'users#update_account', :as => :update_account
+ put 'users/update_password' => 'users#update_password', :as => :update_password
+ put 'users/update_avatar' => 'users#update_avatar', :as => :update_avatar
+ get 'go/:key' => 'nodes#show', :as => :go
+ get 't/:id' => 'topics#show', :as => :t
+ get '/topics/:id' => redirect('/t/%{id}'), :constraints => { :id => /\d+/ }
+ get '/categories/:id' => redirect('/guides/?c=%{id}'), :constraints => { :id => /\d+/ }
- resources :categories
+ get 'member/:nickname/marks' => 'users#my_topics', :as => :my_topics
+ get 'my/following' => 'users#my_following', :as => :my_following
+ get 'page/:key' => 'pages#show', :as => :page
+ get 'goodbye' => 'welcome#goodbye'
+ get 'captcha' => 'welcome#captcha'
+ get 'sitemap' => 'welcome#sitemap'
+ get 'users' => 'users#index'
- resources :guides do
- resources :articles
+ resources :nodes do
+ resources :topics do
+ member do
+ get :move
+ get :edit_title
+ put :update_title
+ end
+ end
+ end
+ resources :topics do
+ resources :comments
+ resources :bookmarks
+ post :preview, :on => :collection
+ put :toggle_comments_closed
+ put :toggle_sticky
+ get :new_from_home, :on => :collection
+ # post :create_from_home, :on => :collection
end
- match '/' => 'guides#index', :constraints => { :subdomain => 'learn' }
- root :to => "home#index"
+ resources :comments, :bookmarks, :upyun_images
+
+ resources :notifications do
+ get :read, :on => :member
+ end
+
+ namespace :admin do
+
+ resources :categories
+ resources :guides
+
+ resources :planes do
+ resources :nodes
+ post :sort, :on => :collection
+ get :sort, :on => :collection
+ end
+
+ resources :nodes do
+ post :sort, :on => :collection
+ get :move, :on => :member
+ put :move_to, :on => :member
+ end
+
+ resources :users do
+ member do
+ put :toggle_admin
+ put :toggle_blocked
+ end
+ resources :rewards
+ end
- authenticated :user do
- root :to => 'guides#index'
+ resources :pages do
+ post :sort, :on => :collection
+ end
+
+ resource :site_settings
+ resources :topics, :advertisements, :cloud_files, :rewards
+
+ resources :notifications do
+ delete :clear, :on => :collection
+ end
+
+ get 'appearance' => 'site_settings#appearance'
+
+ root :to => 'welcome_admin#index'
end
- devise_for :users
- resources :users
+ root :to => 'welcome#index'
+ # The priority is based upon order of creation:
+ # first created -> highest priority.
+
+ # Sample of regular route:
+ # match 'products/:id' => 'catalog#view'
+ # Keep in mind you can assign values other than :controller and :action
+
+ # Sample of named route:
+ # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
+ # This route can be invoked with purchase_url(:id => product.id)
+
+ # Sample resource route (maps HTTP verbs to controller actions automatically):
+ # resources :products
+
+ # Sample resource route with options:
+ # resources :products do
+ # member do
+ # get 'short'
+ # post 'toggle'
+ # end
+ #
+ # collection do
+ # get 'sold'
+ # end
+ # end
+
+ # Sample resource route with sub-resources:
+ # resources :products do
+ # resources :comments, :sales
+ # resource :seller
+ # end
+
+ # Sample resource route with more complex sub-resources
+ # resources :products do
+ # resources :comments
+ # resources :sales do
+ # get 'recent', :on => :collection
+ # end
+ # end
+
+ # Sample resource route within a namespace:
+ # namespace :admin do
+ # # Directs /admin/products/* to Admin::ProductsController
+ # # (app/controllers/admin/products_controller.rb)
+ # resources :products
+ # end
+
+ # You can have the root of your site routed with "root"
+ # just remember to delete public/index.html.
+ # root :to => 'welcome#index'
+
+ # See how all your routes lay out with "rake routes"
+
+ # This is a legacy wild controller route that's not recommended for RESTful applications.
+ # Note: This route will make all actions in every controller accessible via GET requests.
+ # match ':controller(/:action(/:id(.:format)))'
end
diff --git a/config/unicorn.rb b/config/unicorn.rb
index 18c764e..d40835d 100644
--- a/config/unicorn.rb
+++ b/config/unicorn.rb
@@ -1,10 +1,104 @@
-root = "/home/rabel/MakerLabLMS/"
-#root = "/vagrant"
-working_directory root
-pid "#{root}/tmp/pids/unicorn.pid"
-stderr_path "#{root}/log/unicorn.log"
-stdout_path "#{root}/log/unicorn.log"
-
-listen "/tmp/unicorn.makerlab.sock"
+# Sample verbose configuration file for Unicorn (not Rack)
+#
+# This configuration file documents many features of Unicorn
+# that may not be needed for some applications. See
+# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
+# for a much simpler configuration file.
+#
+# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
+# documentation.
+
+# Application root dir
+APP_HOME = File.dirname(File.dirname(File.expand_path(__FILE__)))
+
+# Use at least one worker per core if you're on a dedicated server,
+# more will usually help for _short_ waits on databases/caches.
worker_processes 2
+
+# Since Unicorn is never exposed to outside clients, it does not need to
+# run on the standard HTTP port (80), there is no reason to start Unicorn
+# as root unless it's from system init scripts.
+# If running the master process as root and the workers as an unprivileged
+# user, do this to switch euid/egid in the workers (also chowns logs):
+# user "unprivileged_user", "unprivileged_group"
+
+# Help ensure your application will always spawn in the symlinked
+# "current" directory that Capistrano sets up.
+working_directory APP_HOME # available in 0.94.0+
+
+# listen on both a Unix domain socket and a TCP port,
+# we use a shorter backlog for quicker failover when busy
+listen "#{APP_HOME}/tmp/sockets/unicorn.sock", :backlog => 64
+
+# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 30
+
+# feel free to point this anywhere accessible on the filesystem
+pid "#{APP_HOME}/tmp/pids/unicorn.pid"
+
+# By default, the Unicorn logger will write to stderr.
+# Additionally, ome applications/frameworks log to stderr or stdout,
+# so prevent them from going to /dev/null when daemonized here:
+stderr_path "#{APP_HOME}/log/unicorn.stderr.log"
+stdout_path "#{APP_HOME}/log/unicorn.stdout.log"
+
+# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
+# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
+preload_app true
+GC.respond_to?(:copy_on_write_friendly=) and
+ GC.copy_on_write_friendly = true
+
+# Enable this flag to have unicorn test client connections by writing the
+# beginning of the HTTP headers before calling the application. This
+# prevents calling the application for connections that have disconnected
+# while queued. This is only guaranteed to detect clients on the same
+# host unicorn runs on, and unlikely to detect disconnects even on a
+# fast LAN.
+check_client_connection false
+
+before_fork do |server, worker|
+ # the following is highly recomended for Rails + "preload_app true"
+ # as there's no need for the master process to hold a connection
+ defined?(ActiveRecord::Base) and
+ ActiveRecord::Base.connection.disconnect!
+
+ # The following is only recommended for memory/DB-constrained
+ # installations. It is not needed if your system can house
+ # twice as many worker_processes as you have configured.
+ #
+ # # This allows a new master process to incrementally
+ # # phase out the old master process with SIGTTOU to avoid a
+ # # thundering herd (especially in the "preload_app false" case)
+ # # when doing a transparent upgrade. The last worker spawned
+ # # will then kill off the old master process with a SIGQUIT.
+ # old_pid = "#{server.config[:pid]}.oldbin"
+ # if old_pid != server.pid
+ # begin
+ # sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
+ # Process.kill(sig, File.read(old_pid).to_i)
+ # rescue Errno::ENOENT, Errno::ESRCH
+ # end
+ # end
+ #
+ # Throttle the master from forking too quickly by sleeping. Due
+ # to the implementation of standard Unix signal handlers, this
+ # helps (but does not completely) prevent identical, repeated signals
+ # from being lost when the receiving process is busy.
+ # sleep 1
+end
+
+after_fork do |server, worker|
+ # per-process listener ports for debugging/admin/migrations
+ # addr = "127.0.0.1:#{9293 + worker.nr}"
+ # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
+
+ # the following is *required* for Rails + "preload_app true",
+ defined?(ActiveRecord::Base) and
+ ActiveRecord::Base.establish_connection
+
+ # if preload_app is true, then you may also want to check and
+ # restart any other shared sockets/descriptors such as Memcached,
+ # and Redis. TokyoCabinet file handles are safe to reuse
+ # between any number of forked children (assuming your kernel
+ # correctly implements pread()/pwrite() system calls)
+end
diff --git a/db/migrate/20111207114903_devise_create_users.rb b/db/migrate/20111207114903_devise_create_users.rb
new file mode 100644
index 0000000..6ff8138
--- /dev/null
+++ b/db/migrate/20111207114903_devise_create_users.rb
@@ -0,0 +1,54 @@
+class DeviseCreateUsers < ActiveRecord::Migration
+ def self.up
+ create_table(:users) do |t|
+ # Database authenticatable
+ t.string :email, :null => false, :default => ""
+ t.string :encrypted_password, :null => false, :default => ""
+
+ # Recoverable
+ t.string :reset_password_token
+ t.datetime :reset_password_sent_at
+
+ # Rememberable
+ t.datetime :remember_created_at
+
+ # Trackable
+ t.integer :sign_in_count, :default => 0
+ t.datetime :current_sign_in_at
+ t.datetime :last_sign_in_at
+ t.string :current_sign_in_ip
+ t.string :last_sign_in_ip
+
+ # Encryptable
+ # t.string :password_salt
+
+ # Confirmable
+ # t.string :confirmation_token
+ # t.datetime :confirmed_at
+ # t.datetime :confirmation_sent_at
+ # t.string :unconfirmed_email # Only if using reconfirmable
+
+
+ # Lockable
+ # t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
+ # t.string :unlock_token # Only if unlock strategy is :email or :both
+ # t.datetime :locked_at
+
+
+ # Token authenticatable
+ # t.string :authentication_token
+
+ t.timestamps
+ end
+
+ add_index :users, :email, :unique => true
+ add_index :users, :reset_password_token, :unique => true
+ # add_index :users, :confirmation_token, :unique => true
+ # add_index :users, :unlock_token, :unique => true
+ # add_index :users, :authentication_token, :unique => true
+ end
+
+ def self.down
+ drop_table :users
+ end
+end
diff --git a/db/migrate/20111207115533_add_nickname_to_users.rb b/db/migrate/20111207115533_add_nickname_to_users.rb
new file mode 100644
index 0000000..c837acb
--- /dev/null
+++ b/db/migrate/20111207115533_add_nickname_to_users.rb
@@ -0,0 +1,6 @@
+class AddNicknameToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :nickname, :string
+ add_index :users, :nickname, :unique => true
+ end
+end
diff --git a/db/migrate/20111208133931_create_accounts.rb b/db/migrate/20111208133931_create_accounts.rb
new file mode 100644
index 0000000..437016a
--- /dev/null
+++ b/db/migrate/20111208133931_create_accounts.rb
@@ -0,0 +1,14 @@
+class CreateAccounts < ActiveRecord::Migration
+ def change
+ create_table :accounts do |t|
+ t.integer :user_id
+ t.string :personal_website
+ t.string :location
+ t.string :signature
+ t.text :introduction
+ t.string :twitter_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20111208134052_add_index_to_accounts.rb b/db/migrate/20111208134052_add_index_to_accounts.rb
new file mode 100644
index 0000000..d03d4b9
--- /dev/null
+++ b/db/migrate/20111208134052_add_index_to_accounts.rb
@@ -0,0 +1,5 @@
+class AddIndexToAccounts < ActiveRecord::Migration
+ def change
+ add_index :accounts, :user_id
+ end
+end
diff --git a/db/migrate/20111209053551_create_nodes.rb b/db/migrate/20111209053551_create_nodes.rb
new file mode 100644
index 0000000..09f48d4
--- /dev/null
+++ b/db/migrate/20111209053551_create_nodes.rb
@@ -0,0 +1,12 @@
+class CreateNodes < ActiveRecord::Migration
+ def change
+ create_table :nodes do |t|
+ t.string :name
+ t.string :key
+ t.string :introduction
+ t.text :custom_html
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20111209053640_add_index_to_nodes.rb b/db/migrate/20111209053640_add_index_to_nodes.rb
new file mode 100644
index 0000000..0e85b00
--- /dev/null
+++ b/db/migrate/20111209053640_add_index_to_nodes.rb
@@ -0,0 +1,5 @@
+class AddIndexToNodes < ActiveRecord::Migration
+ def change
+ add_index :nodes, :key, :unique => true
+ end
+end
diff --git a/db/migrate/20111209054915_create_topics.rb b/db/migrate/20111209054915_create_topics.rb
new file mode 100644
index 0000000..d745be4
--- /dev/null
+++ b/db/migrate/20111209054915_create_topics.rb
@@ -0,0 +1,13 @@
+class CreateTopics < ActiveRecord::Migration
+ def change
+ create_table :topics do |t|
+ t.integer :node_id
+ t.integer :user_id
+ t.string :title
+ t.text :content
+ t.integer :hit
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20111209054959_add_index_to_topics.rb b/db/migrate/20111209054959_add_index_to_topics.rb
new file mode 100644
index 0000000..accb50e
--- /dev/null
+++ b/db/migrate/20111209054959_add_index_to_topics.rb
@@ -0,0 +1,6 @@
+class AddIndexToTopics < ActiveRecord::Migration
+ def change
+ add_index :topics, :node_id
+ add_index :topics, :user_id
+ end
+end
diff --git a/db/migrate/20111209060937_create_comments.rb b/db/migrate/20111209060937_create_comments.rb
new file mode 100644
index 0000000..f74b404
--- /dev/null
+++ b/db/migrate/20111209060937_create_comments.rb
@@ -0,0 +1,12 @@
+class CreateComments < ActiveRecord::Migration
+ def change
+ create_table :comments do |t|
+ t.text :content
+ t.integer :user_id
+ t.string :commentable_type
+ t.integer :commentable_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20111209061125_add_index_to_comments.rb b/db/migrate/20111209061125_add_index_to_comments.rb
new file mode 100644
index 0000000..12b9342
--- /dev/null
+++ b/db/migrate/20111209061125_add_index_to_comments.rb
@@ -0,0 +1,6 @@
+class AddIndexToComments < ActiveRecord::Migration
+ def change
+ add_index :comments, :user_id
+ add_index :comments, [:commentable_id, :commentable_type]
+ end
+end
diff --git a/db/migrate/20111209080245_create_planes.rb b/db/migrate/20111209080245_create_planes.rb
new file mode 100644
index 0000000..224ee39
--- /dev/null
+++ b/db/migrate/20111209080245_create_planes.rb
@@ -0,0 +1,9 @@
+class CreatePlanes < ActiveRecord::Migration
+ def change
+ create_table :planes do |t|
+ t.string :name
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20111209080432_add_plane_id_to_nodes.rb b/db/migrate/20111209080432_add_plane_id_to_nodes.rb
new file mode 100644
index 0000000..ccf1c21
--- /dev/null
+++ b/db/migrate/20111209080432_add_plane_id_to_nodes.rb
@@ -0,0 +1,6 @@
+class AddPlaneIdToNodes < ActiveRecord::Migration
+ def change
+ add_column :nodes, :plane_id, :integer
+ add_index :nodes, :plane_id
+ end
+end
diff --git a/db/migrate/20111217060752_create_bookmarks.rb b/db/migrate/20111217060752_create_bookmarks.rb
new file mode 100644
index 0000000..21d383c
--- /dev/null
+++ b/db/migrate/20111217060752_create_bookmarks.rb
@@ -0,0 +1,11 @@
+class CreateBookmarks < ActiveRecord::Migration
+ def change
+ create_table :bookmarks do |t|
+ t.integer :user_id
+ t.string :bookmarkable_type
+ t.integer :bookmarkable_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20111217061017_add_index_to_bookmarks.rb b/db/migrate/20111217061017_add_index_to_bookmarks.rb
new file mode 100644
index 0000000..81b680a
--- /dev/null
+++ b/db/migrate/20111217061017_add_index_to_bookmarks.rb
@@ -0,0 +1,8 @@
+class AddIndexToBookmarks < ActiveRecord::Migration
+ def change
+ change_table :bookmarks do |t|
+ t.index :user_id
+ t.index [:bookmarkable_id, :bookmarkable_type]
+ end
+ end
+end
diff --git a/db/migrate/20111220135343_create_followings.rb b/db/migrate/20111220135343_create_followings.rb
new file mode 100644
index 0000000..6f64b24
--- /dev/null
+++ b/db/migrate/20111220135343_create_followings.rb
@@ -0,0 +1,10 @@
+class CreateFollowings < ActiveRecord::Migration
+ def change
+ create_table :followings do |t|
+ t.integer :user_id
+ t.integer :followed_user_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20111220135535_add_index_to_followings.rb b/db/migrate/20111220135535_add_index_to_followings.rb
new file mode 100644
index 0000000..4e802cb
--- /dev/null
+++ b/db/migrate/20111220135535_add_index_to_followings.rb
@@ -0,0 +1,7 @@
+class AddIndexToFollowings < ActiveRecord::Migration
+ def change
+ add_index :followings, :user_id
+ add_index :followings, :followed_user_id
+ add_index :followings, [:user_id, :followed_user_id], :unique => true
+ end
+end
diff --git a/db/migrate/20111224055101_add_avatar_to_users.rb b/db/migrate/20111224055101_add_avatar_to_users.rb
new file mode 100644
index 0000000..7b4de37
--- /dev/null
+++ b/db/migrate/20111224055101_add_avatar_to_users.rb
@@ -0,0 +1,5 @@
+class AddAvatarToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :avatar, :string
+ end
+end
diff --git a/db/migrate/20111224105955_create_pages.rb b/db/migrate/20111224105955_create_pages.rb
new file mode 100644
index 0000000..ff2d1ba
--- /dev/null
+++ b/db/migrate/20111224105955_create_pages.rb
@@ -0,0 +1,11 @@
+class CreatePages < ActiveRecord::Migration
+ def change
+ create_table :pages do |t|
+ t.string :key
+ t.string :title
+ t.text :content
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20111224111225_add_index_to_pages.rb b/db/migrate/20111224111225_add_index_to_pages.rb
new file mode 100644
index 0000000..b30ac84
--- /dev/null
+++ b/db/migrate/20111224111225_add_index_to_pages.rb
@@ -0,0 +1,5 @@
+class AddIndexToPages < ActiveRecord::Migration
+ def change
+ add_index :pages, :key
+ end
+end
diff --git a/db/migrate/20111224111624_add_admin_to_users.rb b/db/migrate/20111224111624_add_admin_to_users.rb
new file mode 100644
index 0000000..0249df7
--- /dev/null
+++ b/db/migrate/20111224111624_add_admin_to_users.rb
@@ -0,0 +1,6 @@
+class AddAdminToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :admin, :boolean, :default => false
+ add_index :users, :admin
+ end
+end
diff --git a/db/migrate/20111230121459_create_notifications.rb b/db/migrate/20111230121459_create_notifications.rb
new file mode 100644
index 0000000..b3b5a3b
--- /dev/null
+++ b/db/migrate/20111230121459_create_notifications.rb
@@ -0,0 +1,15 @@
+class CreateNotifications < ActiveRecord::Migration
+ def change
+ create_table :notifications do |t|
+ t.integer :user_id
+ t.string :notifiable_type
+ t.integer :notifiable_id
+ t.text :content
+ t.integer :action_user_id
+ t.string :action
+ t.boolean :unread, :default => true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20111230121950_add_index_to_notifications.rb b/db/migrate/20111230121950_add_index_to_notifications.rb
new file mode 100644
index 0000000..70fff16
--- /dev/null
+++ b/db/migrate/20111230121950_add_index_to_notifications.rb
@@ -0,0 +1,8 @@
+class AddIndexToNotifications < ActiveRecord::Migration
+ def change
+ change_table :notifications do |t|
+ t.index :user_id
+ t.index :unread
+ end
+ end
+end
diff --git a/db/migrate/20111230165404_add_more_index_to_notifications.rb b/db/migrate/20111230165404_add_more_index_to_notifications.rb
new file mode 100644
index 0000000..fb8f210
--- /dev/null
+++ b/db/migrate/20111230165404_add_more_index_to_notifications.rb
@@ -0,0 +1,7 @@
+class AddMoreIndexToNotifications < ActiveRecord::Migration
+ def change
+ change_table :notifications do |t|
+ t.index [:notifiable_id, :notifiable_type]
+ end
+ end
+end
diff --git a/db/migrate/20120104115917_add_published_to_pages.rb b/db/migrate/20120104115917_add_published_to_pages.rb
new file mode 100644
index 0000000..1c098c8
--- /dev/null
+++ b/db/migrate/20120104115917_add_published_to_pages.rb
@@ -0,0 +1,6 @@
+class AddPublishedToPages < ActiveRecord::Migration
+ def change
+ add_column :pages, :published, :boolean, :default => false
+ add_index :pages, :published
+ end
+end
diff --git a/db/migrate/20120106055211_add_role_to_users.rb b/db/migrate/20120106055211_add_role_to_users.rb
new file mode 100644
index 0000000..7b80564
--- /dev/null
+++ b/db/migrate/20120106055211_add_role_to_users.rb
@@ -0,0 +1,7 @@
+class AddRoleToUsers < ActiveRecord::Migration
+ def change
+ remove_column :users, :admin
+ add_column :users, :role, :string
+ add_index :users, :role
+ end
+end
diff --git a/db/migrate/20120107134203_create_advertisements.rb b/db/migrate/20120107134203_create_advertisements.rb
new file mode 100644
index 0000000..89b4c07
--- /dev/null
+++ b/db/migrate/20120107134203_create_advertisements.rb
@@ -0,0 +1,15 @@
+class CreateAdvertisements < ActiveRecord::Migration
+ def change
+ create_table :advertisements do |t|
+ t.string :link
+ t.string :banner
+ t.string :title
+ t.string :words
+ t.date :start_date
+ t.date :expire_date
+ t.integer :duration
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20120107134636_add_index_to_advertisements.rb b/db/migrate/20120107134636_add_index_to_advertisements.rb
new file mode 100644
index 0000000..1788e0f
--- /dev/null
+++ b/db/migrate/20120107134636_add_index_to_advertisements.rb
@@ -0,0 +1,8 @@
+class AddIndexToAdvertisements < ActiveRecord::Migration
+ def change
+ change_table :advertisements do |t|
+ t.index :start_date
+ t.index :expire_date
+ end
+ end
+end
diff --git a/db/migrate/20120205011606_add_position_to_pages.rb b/db/migrate/20120205011606_add_position_to_pages.rb
new file mode 100644
index 0000000..76bb95b
--- /dev/null
+++ b/db/migrate/20120205011606_add_position_to_pages.rb
@@ -0,0 +1,6 @@
+class AddPositionToPages < ActiveRecord::Migration
+ def change
+ add_column :pages, :position, :integer
+ add_index :pages, :position
+ end
+end
diff --git a/db/migrate/20120206084156_add_position_to_nodes.rb b/db/migrate/20120206084156_add_position_to_nodes.rb
new file mode 100644
index 0000000..6a3feb1
--- /dev/null
+++ b/db/migrate/20120206084156_add_position_to_nodes.rb
@@ -0,0 +1,6 @@
+class AddPositionToNodes < ActiveRecord::Migration
+ def change
+ add_column :nodes, :position, :integer
+ add_index :nodes, :position
+ end
+end
diff --git a/db/migrate/20120217131718_add_blocked_to_users.rb b/db/migrate/20120217131718_add_blocked_to_users.rb
new file mode 100644
index 0000000..0f60138
--- /dev/null
+++ b/db/migrate/20120217131718_add_blocked_to_users.rb
@@ -0,0 +1,5 @@
+class AddBlockedToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :blocked, :boolean, :default => false
+ end
+end
diff --git a/db/migrate/20120304140424_create_settings.rb b/db/migrate/20120304140424_create_settings.rb
new file mode 100644
index 0000000..c4685c5
--- /dev/null
+++ b/db/migrate/20120304140424_create_settings.rb
@@ -0,0 +1,17 @@
+class CreateSettings < ActiveRecord::Migration
+ def self.up
+ create_table :settings do |t|
+ t.string :var, :null => false
+ t.text :value, :null => true
+ t.integer :thing_id, :null => true
+ t.string :thing_type, :limit => 30, :null => true
+ t.timestamps
+ end
+
+ add_index :settings, [ :thing_type, :thing_id, :var ], :unique => true
+ end
+
+ def self.down
+ drop_table :settings
+ end
+end
diff --git a/db/migrate/20120316074203_add_updated_at_index_to_topics.rb b/db/migrate/20120316074203_add_updated_at_index_to_topics.rb
new file mode 100644
index 0000000..6bb6994
--- /dev/null
+++ b/db/migrate/20120316074203_add_updated_at_index_to_topics.rb
@@ -0,0 +1,5 @@
+class AddUpdatedAtIndexToTopics < ActiveRecord::Migration
+ def change
+ add_index :topics, :updated_at
+ end
+end
diff --git a/db/migrate/20120316134205_add_involved_at_to_topics.rb b/db/migrate/20120316134205_add_involved_at_to_topics.rb
new file mode 100644
index 0000000..4381e69
--- /dev/null
+++ b/db/migrate/20120316134205_add_involved_at_to_topics.rb
@@ -0,0 +1,16 @@
+class AddInvolvedAtToTopics < ActiveRecord::Migration
+ def up
+ add_column :topics, :involved_at, :datetime
+ add_index :topics, :involved_at
+ remove_index :topics, :updated_at
+ Topic.all.each do |topic|
+ topic.update_column(:involved_at, topic.updated_at)
+ end
+ end
+
+ def down
+ remove_index :topics, :involved_at
+ remove_column :topics, :involved_at
+ add_index :topics, :updated_at
+ end
+end
diff --git a/db/migrate/20120331142416_add_created_at_index_to_comments.rb b/db/migrate/20120331142416_add_created_at_index_to_comments.rb
new file mode 100644
index 0000000..5140fae
--- /dev/null
+++ b/db/migrate/20120331142416_add_created_at_index_to_comments.rb
@@ -0,0 +1,5 @@
+class AddCreatedAtIndexToComments < ActiveRecord::Migration
+ def change
+ add_index :comments, :created_at
+ end
+end
diff --git a/db/migrate/20120404070333_add_updated_at_index_to_comments.rb b/db/migrate/20120404070333_add_updated_at_index_to_comments.rb
new file mode 100644
index 0000000..870123d
--- /dev/null
+++ b/db/migrate/20120404070333_add_updated_at_index_to_comments.rb
@@ -0,0 +1,5 @@
+class AddUpdatedAtIndexToComments < ActiveRecord::Migration
+ def change
+ add_index :comments, :updated_at
+ end
+end
diff --git a/db/migrate/20120404100838_add_updated_at_index_to_planes.rb b/db/migrate/20120404100838_add_updated_at_index_to_planes.rb
new file mode 100644
index 0000000..a10518f
--- /dev/null
+++ b/db/migrate/20120404100838_add_updated_at_index_to_planes.rb
@@ -0,0 +1,5 @@
+class AddUpdatedAtIndexToPlanes < ActiveRecord::Migration
+ def change
+ add_index :planes, :updated_at
+ end
+end
diff --git a/db/migrate/20120404101032_add_updated_at_index_to_nodes.rb b/db/migrate/20120404101032_add_updated_at_index_to_nodes.rb
new file mode 100644
index 0000000..cb6edfd
--- /dev/null
+++ b/db/migrate/20120404101032_add_updated_at_index_to_nodes.rb
@@ -0,0 +1,5 @@
+class AddUpdatedAtIndexToNodes < ActiveRecord::Migration
+ def change
+ add_index :nodes, :updated_at
+ end
+end
diff --git a/db/migrate/20120405045224_add_comments_count_to_topics.rb b/db/migrate/20120405045224_add_comments_count_to_topics.rb
new file mode 100644
index 0000000..977ebaf
--- /dev/null
+++ b/db/migrate/20120405045224_add_comments_count_to_topics.rb
@@ -0,0 +1,5 @@
+class AddCommentsCountToTopics < ActiveRecord::Migration
+ def change
+ add_column :topics, :comments_count, :integer, :null => false, :default => 0
+ end
+end
diff --git a/db/migrate/20120405064209_set_comments_count_on_topics.rb b/db/migrate/20120405064209_set_comments_count_on_topics.rb
new file mode 100644
index 0000000..dc01715
--- /dev/null
+++ b/db/migrate/20120405064209_set_comments_count_on_topics.rb
@@ -0,0 +1,13 @@
+class SetCommentsCountOnTopics < ActiveRecord::Migration
+ def up
+ Topic.find_each do |t|
+ Topic.reset_counters(t.id, :comments)
+ end
+ end
+
+ def down
+ Topic.find_each do |t|
+ t.update_attribute(:comments_count, 0)
+ end
+ end
+end
diff --git a/db/migrate/20120405071656_add_topics_count_to_nodes.rb b/db/migrate/20120405071656_add_topics_count_to_nodes.rb
new file mode 100644
index 0000000..68b2fed
--- /dev/null
+++ b/db/migrate/20120405071656_add_topics_count_to_nodes.rb
@@ -0,0 +1,5 @@
+class AddTopicsCountToNodes < ActiveRecord::Migration
+ def change
+ add_column :nodes, :topics_count, :integer, :null => false, :default => 0
+ end
+end
diff --git a/db/migrate/20120405072538_set_topics_count_on_nodes.rb b/db/migrate/20120405072538_set_topics_count_on_nodes.rb
new file mode 100644
index 0000000..0acb4b8
--- /dev/null
+++ b/db/migrate/20120405072538_set_topics_count_on_nodes.rb
@@ -0,0 +1,13 @@
+class SetTopicsCountOnNodes < ActiveRecord::Migration
+ def up
+ Node.find_each do |node|
+ Node.reset_counters(node.id, :topics)
+ end
+ end
+
+ def down
+ Node.find_each do |node|
+ node.update_attribute(:topics_count, 0)
+ end
+ end
+end
diff --git a/db/migrate/20120427122531_add_position_to_planes.rb b/db/migrate/20120427122531_add_position_to_planes.rb
new file mode 100644
index 0000000..111a052
--- /dev/null
+++ b/db/migrate/20120427122531_add_position_to_planes.rb
@@ -0,0 +1,5 @@
+class AddPositionToPlanes < ActiveRecord::Migration
+ def change
+ add_column :planes, :position, :integer, :null => false, :default => 0
+ end
+end
diff --git a/db/migrate/20120428141950_create_cloud_files.rb b/db/migrate/20120428141950_create_cloud_files.rb
new file mode 100644
index 0000000..3a76780
--- /dev/null
+++ b/db/migrate/20120428141950_create_cloud_files.rb
@@ -0,0 +1,12 @@
+class CreateCloudFiles < ActiveRecord::Migration
+ def change
+ create_table :cloud_files do |t|
+ t.string :content_type
+ t.integer :file_size
+ t.string :asset
+ t.string :name
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20120622053738_add_created_at_index_to_followings.rb b/db/migrate/20120622053738_add_created_at_index_to_followings.rb
new file mode 100644
index 0000000..01a1a9b
--- /dev/null
+++ b/db/migrate/20120622053738_add_created_at_index_to_followings.rb
@@ -0,0 +1,5 @@
+class AddCreatedAtIndexToFollowings < ActiveRecord::Migration
+ def change
+ add_index :followings, :created_at
+ end
+end
diff --git a/db/migrate/20120623133345_add_posting_device_to_comments.rb b/db/migrate/20120623133345_add_posting_device_to_comments.rb
new file mode 100644
index 0000000..410ef89
--- /dev/null
+++ b/db/migrate/20120623133345_add_posting_device_to_comments.rb
@@ -0,0 +1,5 @@
+class AddPostingDeviceToComments < ActiveRecord::Migration
+ def change
+ add_column :comments, :posting_device, :string, null: false, default: ''
+ end
+end
diff --git a/db/migrate/20120624064847_add_comments_closed_to_topics.rb b/db/migrate/20120624064847_add_comments_closed_to_topics.rb
new file mode 100644
index 0000000..fa6b681
--- /dev/null
+++ b/db/migrate/20120624064847_add_comments_closed_to_topics.rb
@@ -0,0 +1,5 @@
+class AddCommentsClosedToTopics < ActiveRecord::Migration
+ def change
+ add_column :topics, :comments_closed, :boolean, null: false, default: false
+ end
+end
diff --git a/db/migrate/20120624124831_add_quiet_to_nodes.rb b/db/migrate/20120624124831_add_quiet_to_nodes.rb
new file mode 100644
index 0000000..f10f9b3
--- /dev/null
+++ b/db/migrate/20120624124831_add_quiet_to_nodes.rb
@@ -0,0 +1,6 @@
+class AddQuietToNodes < ActiveRecord::Migration
+ def change
+ add_column :nodes, :quiet, :boolean, null: false, default: false
+ add_index :nodes, :quiet
+ end
+end
diff --git a/db/migrate/20120625011328_add_sticky_to_topics.rb b/db/migrate/20120625011328_add_sticky_to_topics.rb
new file mode 100644
index 0000000..ed5b75f
--- /dev/null
+++ b/db/migrate/20120625011328_add_sticky_to_topics.rb
@@ -0,0 +1,6 @@
+class AddStickyToTopics < ActiveRecord::Migration
+ def change
+ add_column :topics, :sticky, :boolean, default: false
+ add_index :topics, :sticky
+ end
+end
diff --git a/db/migrate/20120626085113_add_reward_to_users.rb b/db/migrate/20120626085113_add_reward_to_users.rb
new file mode 100644
index 0000000..596bc8f
--- /dev/null
+++ b/db/migrate/20120626085113_add_reward_to_users.rb
@@ -0,0 +1,5 @@
+class AddRewardToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :reward, :integer, :default => 0
+ end
+end
diff --git a/db/migrate/20120627121746_create_rewards.rb b/db/migrate/20120627121746_create_rewards.rb
new file mode 100644
index 0000000..eb26c28
--- /dev/null
+++ b/db/migrate/20120627121746_create_rewards.rb
@@ -0,0 +1,14 @@
+class CreateRewards < ActiveRecord::Migration
+ def change
+ create_table :rewards do |t|
+ t.integer :admin_user_id, default: 0
+ t.integer :user_id, default: 0
+ t.integer :amount, default: 0
+ t.integer :balance, default: 0
+
+ t.text :reason
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20120627194105_add_custom_css_to_nodes.rb b/db/migrate/20120627194105_add_custom_css_to_nodes.rb
new file mode 100644
index 0000000..743df49
--- /dev/null
+++ b/db/migrate/20120627194105_add_custom_css_to_nodes.rb
@@ -0,0 +1,5 @@
+class AddCustomCssToNodes < ActiveRecord::Migration
+ def change
+ add_column :nodes, :custom_css, :text
+ end
+end
diff --git a/db/migrate/20120722021548_add_last_replied_by_to_topics.rb b/db/migrate/20120722021548_add_last_replied_by_to_topics.rb
new file mode 100644
index 0000000..dd927e2
--- /dev/null
+++ b/db/migrate/20120722021548_add_last_replied_by_to_topics.rb
@@ -0,0 +1,12 @@
+class AddLastRepliedByToTopics < ActiveRecord::Migration
+ def up
+ add_column :topics, :last_replied_by, :string, default: ''
+ Topic.find_each do |topic|
+ topic.update_column(:last_replied_by, topic.last_comment.user.nickname) if topic.comments_count > 0
+ end
+ end
+
+ def down
+ remove_column :topics, :last_replied_by
+ end
+end
diff --git a/db/migrate/20120722031529_add_weibo_link_to_accounts.rb b/db/migrate/20120722031529_add_weibo_link_to_accounts.rb
new file mode 100644
index 0000000..5ab173f
--- /dev/null
+++ b/db/migrate/20120722031529_add_weibo_link_to_accounts.rb
@@ -0,0 +1,14 @@
+class AddWeiboLinkToAccounts < ActiveRecord::Migration
+ def up
+ add_column :accounts, :weibo_link, :string, default: ''
+ User.find_each do |user|
+ user.account.update_column(:weibo_link, "http://twitter.com/#{user.account.twitter_id}") if user.account.twitter_id.present?
+ end
+ remove_column :accounts, :twitter_id
+ end
+
+ def down
+ add_column :accounts, :twitter_id
+ remove_column :accounts, :weibo_link
+ end
+end
diff --git a/db/migrate/20120727092241_add_last_replied_at_to_topics.rb b/db/migrate/20120727092241_add_last_replied_at_to_topics.rb
new file mode 100644
index 0000000..21b05b4
--- /dev/null
+++ b/db/migrate/20120727092241_add_last_replied_at_to_topics.rb
@@ -0,0 +1,11 @@
+class AddLastRepliedAtToTopics < ActiveRecord::Migration
+ def change
+ add_column :topics, :last_replied_at, :datetime
+ Topic.find_each do |topic|
+ if topic.comments_count > 0
+ topic.last_replied_at = topic.last_comment.created_at
+ topic.save
+ end
+ end
+ end
+end
diff --git a/db/migrate/20121203081304_create_upyun_images.rb b/db/migrate/20121203081304_create_upyun_images.rb
new file mode 100644
index 0000000..801c4eb
--- /dev/null
+++ b/db/migrate/20121203081304_create_upyun_images.rb
@@ -0,0 +1,9 @@
+class CreateUpyunImages < ActiveRecord::Migration
+ def change
+ create_table :upyun_images do |t|
+ t.string :asset
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20121204031612_add_user_id_to_upyun_images.rb b/db/migrate/20121204031612_add_user_id_to_upyun_images.rb
new file mode 100644
index 0000000..8a2076f
--- /dev/null
+++ b/db/migrate/20121204031612_add_user_id_to_upyun_images.rb
@@ -0,0 +1,6 @@
+class AddUserIdToUpyunImages < ActiveRecord::Migration
+ def change
+ add_column :upyun_images, :user_id, :integer
+ add_index :upyun_images, :user_id
+ end
+end
diff --git a/db/migrate/20121204032057_add_size_and_filename_and_content_type_to_upyun_images.rb b/db/migrate/20121204032057_add_size_and_filename_and_content_type_to_upyun_images.rb
new file mode 100644
index 0000000..cf080f5
--- /dev/null
+++ b/db/migrate/20121204032057_add_size_and_filename_and_content_type_to_upyun_images.rb
@@ -0,0 +1,7 @@
+class AddSizeAndFilenameAndContentTypeToUpyunImages < ActiveRecord::Migration
+ def change
+ add_column :upyun_images, :size, :integer
+ add_column :upyun_images, :filename, :string
+ add_column :upyun_images, :content_type, :string
+ end
+end
diff --git a/db/migrate/20130604055828_devise_create_users.rb b/db/migrate/20130604055828_devise_create_users.rb
deleted file mode 100644
index 2099d99..0000000
--- a/db/migrate/20130604055828_devise_create_users.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-class DeviseCreateUsers < ActiveRecord::Migration
- def change
- create_table(:users) do |t|
- ## Database authenticatable
- t.string :email, :null => false, :default => ""
- t.string :encrypted_password, :null => false, :default => ""
-
- ## Recoverable
- t.string :reset_password_token
- t.datetime :reset_password_sent_at
-
- ## Rememberable
- t.datetime :remember_created_at
-
- ## Trackable
- t.integer :sign_in_count, :default => 0
- t.datetime :current_sign_in_at
- t.datetime :last_sign_in_at
- t.string :current_sign_in_ip
- t.string :last_sign_in_ip
-
- ## Confirmable
- # t.string :confirmation_token
- # t.datetime :confirmed_at
- # t.datetime :confirmation_sent_at
- # t.string :unconfirmed_email # Only if using reconfirmable
-
- ## Lockable
- # t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
- # t.string :unlock_token # Only if unlock strategy is :email or :both
- # t.datetime :locked_at
-
- ## Token authenticatable
- # t.string :authentication_token
-
-
- t.timestamps
- end
-
- add_index :users, :email, :unique => true
- add_index :users, :reset_password_token, :unique => true
- # add_index :users, :confirmation_token, :unique => true
- # add_index :users, :unlock_token, :unique => true
- # add_index :users, :authentication_token, :unique => true
- end
-end
diff --git a/db/migrate/20130604055830_add_name_to_users.rb b/db/migrate/20130604055830_add_name_to_users.rb
deleted file mode 100644
index bac750e..0000000
--- a/db/migrate/20130604055830_add_name_to_users.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class AddNameToUsers < ActiveRecord::Migration
- def change
- add_column :users, :name, :string
- end
-end
diff --git a/db/migrate/20130604055836_rolify_create_roles.rb b/db/migrate/20130604055836_rolify_create_roles.rb
deleted file mode 100644
index 999c94a..0000000
--- a/db/migrate/20130604055836_rolify_create_roles.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-class RolifyCreateRoles < ActiveRecord::Migration
- def change
- create_table(:roles) do |t|
- t.string :name
- t.references :resource, :polymorphic => true
-
- t.timestamps
- end
-
- create_table(:users_roles, :id => false) do |t|
- t.references :user
- t.references :role
- end
-
- add_index(:roles, :name)
- add_index(:roles, [ :name, :resource_type, :resource_id ])
- add_index(:users_roles, [ :user_id, :role_id ])
- end
-end
diff --git a/db/migrate/20140115135254_create_products.rb b/db/migrate/20140115135254_create_products.rb
deleted file mode 100644
index 16d0596..0000000
--- a/db/migrate/20140115135254_create_products.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class CreateProducts < ActiveRecord::Migration
- def change
- create_table :products do |t|
- t.string :name
- t.string :img
- t.text :descrption
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20140115150740_create_downloads.rb b/db/migrate/20140115150740_create_downloads.rb
deleted file mode 100644
index 9f90d10..0000000
--- a/db/migrate/20140115150740_create_downloads.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class CreateDownloads < ActiveRecord::Migration
- def change
- create_table :downloads do |t|
- t.string :title
- t.text :description
-
- t.timestamps
- end
- end
-end
diff --git a/db/migrate/20130901152910_create_categories.rb b/db/migrate/20140603123140_create_categories.rb
similarity index 100%
rename from db/migrate/20130901152910_create_categories.rb
rename to db/migrate/20140603123140_create_categories.rb
diff --git a/db/migrate/20130901150730_create_guides.rb b/db/migrate/20140603124310_create_guides.rb
similarity index 83%
rename from db/migrate/20130901150730_create_guides.rb
rename to db/migrate/20140603124310_create_guides.rb
index ace7d6c..46c9565 100644
--- a/db/migrate/20130901150730_create_guides.rb
+++ b/db/migrate/20140603124310_create_guides.rb
@@ -7,6 +7,8 @@ def change
t.string :subtitle
t.string :overview
t.string :img
+ t.boolean :publish
+ t.integer :feature_id
t.timestamps
end
diff --git a/db/migrate/20130901154403_create_articles.rb b/db/migrate/20140603124659_create_articles.rb
similarity index 88%
rename from db/migrate/20130901154403_create_articles.rb
rename to db/migrate/20140603124659_create_articles.rb
index ed73293..db9fea8 100644
--- a/db/migrate/20130901154403_create_articles.rb
+++ b/db/migrate/20140603124659_create_articles.rb
@@ -4,6 +4,7 @@ def change
t.string :title
t.integer :guide_id
t.text :content
+ t.string :complete
t.timestamps
end
diff --git a/db/migrate/20140603151147_add_comment_count_status_to_article.rb b/db/migrate/20140603151147_add_comment_count_status_to_article.rb
new file mode 100644
index 0000000..1f0605a
--- /dev/null
+++ b/db/migrate/20140603151147_add_comment_count_status_to_article.rb
@@ -0,0 +1,6 @@
+class AddCommentCountStatusToArticle < ActiveRecord::Migration
+ def change
+ add_column :articles, :comments_count, :integer, :default => 0
+ add_column :articles, :comments_closed, :boolean, :default => false
+ end
+end
diff --git a/db/migrate/20140604120603_add_last_involved_to_articles.rb b/db/migrate/20140604120603_add_last_involved_to_articles.rb
new file mode 100644
index 0000000..1ae3702
--- /dev/null
+++ b/db/migrate/20140604120603_add_last_involved_to_articles.rb
@@ -0,0 +1,26 @@
+class AddLastInvolvedToArticles < ActiveRecord::Migration
+ def up
+ add_column :articles, :involved_at, :datetime
+ add_column :articles, :last_replied_by, :string, :default => ""
+ add_column :articles, :last_replied_at, :datetime
+ add_column :articles, :hit, :integer
+ add_column :articles, :user_id, :integer
+ add_index :articles, :involved_at
+ add_index :articles, :guide_id
+ add_index :articles, :user_id
+ Article.all.each do |article|
+ article.update_column(:involved_at, article.updated_at)
+ end
+ end
+
+ def down
+ remove_index :articles, :involved_at
+ remove_index :articles, :guide_id
+ remove_index :articles, :user_id
+ remove_column :articles, :hit
+ remove_column :articles, :user_id
+ remove_column :articles, :involved_at
+ remove_column :articles, :last_replied_by
+ remove_column :articles, :last_replied_at
+ end
+end
diff --git a/db/migrate/20140818051125_add_comments_count_to_user.rb b/db/migrate/20140818051125_add_comments_count_to_user.rb
new file mode 100644
index 0000000..a03b9c6
--- /dev/null
+++ b/db/migrate/20140818051125_add_comments_count_to_user.rb
@@ -0,0 +1,8 @@
+class AddCommentsCountToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :comments_count, :integer, :default => 0
+ User.find_each do |user|
+ user.update_column(:comments_count, user.comments.count)
+ end
+ end
+end
diff --git a/db/migrate/20140818051920_add_topics_count_to_user.rb b/db/migrate/20140818051920_add_topics_count_to_user.rb
new file mode 100644
index 0000000..28ccdba
--- /dev/null
+++ b/db/migrate/20140818051920_add_topics_count_to_user.rb
@@ -0,0 +1,8 @@
+class AddTopicsCountToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :topics_count, :integer, :default => 0
+ User.find_each do |user|
+ user.update_column(:topics_count, user.topics.count)
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 21a1ada..22e20de 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,29 +11,109 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20140115150740) do
+ActiveRecord::Schema.define(:version => 20140818051920) do
+
+ create_table "accounts", :force => true do |t|
+ t.integer "user_id"
+ t.string "personal_website"
+ t.string "location"
+ t.string "signature"
+ t.text "introduction"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "weibo_link", :default => ""
+ end
+
+ add_index "accounts", ["user_id"], :name => "index_accounts_on_user_id"
+
+ create_table "advertisements", :force => true do |t|
+ t.string "link"
+ t.string "banner"
+ t.string "title"
+ t.string "words"
+ t.date "start_date"
+ t.date "expire_date"
+ t.integer "duration"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "advertisements", ["expire_date"], :name => "index_advertisements_on_expire_date"
+ add_index "advertisements", ["start_date"], :name => "index_advertisements_on_start_date"
create_table "articles", :force => true do |t|
t.string "title"
t.integer "guide_id"
t.text "content"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.string "complete"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "comments_count", :default => 0
+ t.boolean "comments_closed", :default => false
+ t.datetime "involved_at"
+ t.string "last_replied_by", :default => ""
+ t.datetime "last_replied_at"
+ t.integer "hit"
+ t.integer "user_id"
+ end
+
+ add_index "articles", ["guide_id"], :name => "index_articles_on_guide_id"
+ add_index "articles", ["involved_at"], :name => "index_articles_on_involved_at"
+ add_index "articles", ["user_id"], :name => "index_articles_on_user_id"
+
+ create_table "bookmarks", :force => true do |t|
+ t.integer "user_id"
+ t.string "bookmarkable_type"
+ t.integer "bookmarkable_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
+ add_index "bookmarks", ["bookmarkable_id", "bookmarkable_type"], :name => "index_bookmarks_on_bookmarkable_id_and_bookmarkable_type"
+ add_index "bookmarks", ["user_id"], :name => "index_bookmarks_on_user_id"
+
create_table "categories", :force => true do |t|
t.string "title"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
- create_table "downloads", :force => true do |t|
- t.string "title"
- t.text "description"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ create_table "cloud_files", :force => true do |t|
+ t.string "content_type"
+ t.integer "file_size"
+ t.string "asset"
+ t.string "name"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "comments", :force => true do |t|
+ t.text "content"
+ t.integer "user_id"
+ t.string "commentable_type"
+ t.integer "commentable_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "posting_device", :default => "", :null => false
+ end
+
+ add_index "comments", ["commentable_id", "commentable_type"], :name => "index_comments_on_commentable_id_and_commentable_type"
+ add_index "comments", ["created_at"], :name => "index_comments_on_created_at"
+ add_index "comments", ["updated_at"], :name => "index_comments_on_updated_at"
+ add_index "comments", ["user_id"], :name => "index_comments_on_user_id"
+
+ create_table "followings", :force => true do |t|
+ t.integer "user_id"
+ t.integer "followed_user_id"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
+ add_index "followings", ["created_at"], :name => "index_followings_on_created_at"
+ add_index "followings", ["followed_user_id"], :name => "index_followings_on_followed_user_id"
+ add_index "followings", ["user_id", "followed_user_id"], :name => "index_followings_on_user_id_and_followed_user_id", :unique => true
+ add_index "followings", ["user_id"], :name => "index_followings_on_user_id"
+
create_table "guides", :force => true do |t|
t.string "title"
t.integer "user_id"
@@ -41,32 +121,128 @@
t.string "subtitle"
t.string "overview"
t.string "img"
+ t.boolean "publish"
+ t.integer "feature_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
- create_table "products", :force => true do |t|
+ create_table "nodes", :force => true do |t|
t.string "name"
- t.string "img"
- t.text "descrption"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.string "key"
+ t.string "introduction"
+ t.text "custom_html"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "plane_id"
+ t.integer "position"
+ t.integer "topics_count", :default => 0, :null => false
+ t.boolean "quiet", :default => false, :null => false
+ t.text "custom_css"
+ end
+
+ add_index "nodes", ["key"], :name => "index_nodes_on_key", :unique => true
+ add_index "nodes", ["plane_id"], :name => "index_nodes_on_plane_id"
+ add_index "nodes", ["position"], :name => "index_nodes_on_position"
+ add_index "nodes", ["quiet"], :name => "index_nodes_on_quiet"
+ add_index "nodes", ["updated_at"], :name => "index_nodes_on_updated_at"
+
+ create_table "notifications", :force => true do |t|
+ t.integer "user_id"
+ t.string "notifiable_type"
+ t.integer "notifiable_id"
+ t.text "content"
+ t.integer "action_user_id"
+ t.string "action"
+ t.boolean "unread", :default => true
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "notifications", ["notifiable_id", "notifiable_type"], :name => "index_notifications_on_notifiable_id_and_notifiable_type"
+ add_index "notifications", ["unread"], :name => "index_notifications_on_unread"
+ add_index "notifications", ["user_id"], :name => "index_notifications_on_user_id"
+
+ create_table "pages", :force => true do |t|
+ t.string "key"
+ t.string "title"
+ t.text "content"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.boolean "published", :default => false
+ t.integer "position"
end
- create_table "roles", :force => true do |t|
+ add_index "pages", ["key"], :name => "index_pages_on_key"
+ add_index "pages", ["position"], :name => "index_pages_on_position"
+ add_index "pages", ["published"], :name => "index_pages_on_published"
+
+ create_table "planes", :force => true do |t|
t.string "name"
- t.integer "resource_id"
- t.string "resource_type"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "position", :default => 0, :null => false
+ end
+
+ add_index "planes", ["updated_at"], :name => "index_planes_on_updated_at"
+
+ create_table "rewards", :force => true do |t|
+ t.integer "admin_user_id", :default => 0
+ t.integer "user_id", :default => 0
+ t.integer "amount", :default => 0
+ t.integer "balance", :default => 0
+ t.text "reason"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
- add_index "roles", ["name", "resource_type", "resource_id"], :name => "index_roles_on_name_and_resource_type_and_resource_id"
- add_index "roles", ["name"], :name => "index_roles_on_name"
+ create_table "settings", :force => true do |t|
+ t.string "var", :null => false
+ t.text "value"
+ t.integer "thing_id"
+ t.string "thing_type", :limit => 30
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "settings", ["thing_type", "thing_id", "var"], :name => "index_settings_on_thing_type_and_thing_id_and_var", :unique => true
+
+ create_table "topics", :force => true do |t|
+ t.integer "node_id"
+ t.integer "user_id"
+ t.string "title"
+ t.text "content"
+ t.integer "hit"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.datetime "involved_at"
+ t.integer "comments_count", :default => 0, :null => false
+ t.boolean "comments_closed", :default => false, :null => false
+ t.boolean "sticky", :default => false
+ t.string "last_replied_by", :default => ""
+ t.datetime "last_replied_at"
+ end
+
+ add_index "topics", ["involved_at"], :name => "index_topics_on_involved_at"
+ add_index "topics", ["node_id"], :name => "index_topics_on_node_id"
+ add_index "topics", ["sticky"], :name => "index_topics_on_sticky"
+ add_index "topics", ["user_id"], :name => "index_topics_on_user_id"
+
+ create_table "upyun_images", :force => true do |t|
+ t.string "asset"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "user_id"
+ t.integer "size"
+ t.string "filename"
+ t.string "content_type"
+ end
+
+ add_index "upyun_images", ["user_id"], :name => "index_upyun_images_on_user_id"
create_table "users", :force => true do |t|
- t.string "email", :default => "", :null => false
- t.string "encrypted_password", :default => "", :null => false
+ t.string "email", :default => "", :null => false
+ t.string "encrypted_password", :default => "", :null => false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
@@ -75,19 +251,20 @@
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
- t.string "name"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "nickname"
+ t.string "avatar"
+ t.string "role"
+ t.boolean "blocked", :default => false
+ t.integer "reward", :default => 0
+ t.integer "comments_count", :default => 0
+ t.integer "topics_count", :default => 0
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
+ add_index "users", ["nickname"], :name => "index_users_on_nickname", :unique => true
add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
-
- create_table "users_roles", :id => false, :force => true do |t|
- t.integer "user_id"
- t.integer "role_id"
- end
-
- add_index "users_roles", ["user_id", "role_id"], :name => "index_users_roles_on_user_id_and_role_id"
+ add_index "users", ["role"], :name => "index_users_on_role"
end
diff --git a/db/seeds.rb b/db/seeds.rb
index cdd37a3..03946b0 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
@@ -5,14 +7,41 @@
#
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
# Mayor.create(name: 'Emanuel', city: cities.first)
-# Environment variables (ENV['...']) are set in the file config/application.yml.
-# See http://railsapps.github.io/rails-environment-variables.html
-puts 'ROLES'
-YAML.load(ENV['ROLES']).each do |role|
- Role.find_or_create_by_name({ :name => role }, :without_protection => true)
- puts 'role: ' << role
+
+def create_seeds_data
+ password = 'project_rabel'
+ user = User.create(:nickname => 'root', :email => 'root@rabelapp.com', :password => password, :password_confirmation => password)
+ plane = Plane.create(:name => '生活')
+ node = plane.nodes.create(:name => '电影', :key => 'movie',
+ :introduction => '一起看电影',
+ :custom_html => '推荐书目 ')
+ topic = node.topics.new(:title => '那继续晒一下韩国电影吧',
+ :content => '至少这个世纪前十年棒子电影那是相当的强势啊,水准很高,精品很多啊。')
+ topic.user = user
+ topic.hit = 1
+ topic.save
+
+ comment = topic.comments.new(:content => '大叔')
+ comment.user = user
+ comment.save
+
+ user2 = User.create(:nickname => 'DHH', :email => 'dhh@example.com', :password => password, :password_confirmation => password)
+ p2 = Plane.create(:name => 'Web框架')
+ node2 = p2.nodes.create(:name => 'Rails 3', :key => 'rails3', :introduction => 'Ruby on Rails')
+ topic2 = node2.topics.new(:title => 'Rails is not for beginners', :content => 'Rails is cool, but not for beginners')
+ topic2.user = user2
+ topic2.hit = 100
+ topic2.save
+
+ user3 = User.create(:nickname => 'Linus', :email => 'linus@linux.org', :password => password, :password_confirmation => password)
+ p3 = Plane.create(:name => 'Web基础架构')
+ node3 = p3.nodes.create(:name => 'C', :key => 'c_programming')
+ topic3 = node3.topics.new(:title => 'Linux 3.0 released')
+ topic3.user = user3
+ topic3.save
+
+ Page.create(:key => 'about', :title => '关于我们', :content => '这是一个用rabel搭建的现代社区')
end
-puts 'DEFAULT USERS'
-user = User.find_or_create_by_email :name => ENV['ADMIN_NAME'].dup, :email => ENV['ADMIN_EMAIL'].dup, :password => ENV['ADMIN_PASSWORD'].dup, :password_confirmation => ENV['ADMIN_PASSWORD'].dup
-puts 'user: ' << user.name
-user.add_role :admin
\ No newline at end of file
+
+create_seeds_data unless Rails.env.production?
+
diff --git a/deploy/auto_start_unicorn.sh b/deploy/auto_start_unicorn.sh
new file mode 100755
index 0000000..f4b919f
--- /dev/null
+++ b/deploy/auto_start_unicorn.sh
@@ -0,0 +1,39 @@
+# create unicorn upstart script for this website
+DIR=$( cd "$( dirname "$0" )" && pwd )
+source $DIR/shared.sh
+
+if [[ -z "$1" || -z "$2" ]]; then
+ log_error "USAGE: $0 [site_name] [/path/to/app/home]"
+ log_info "--- example ---"
+ log_info "$0 rabelapp /home/rabel/sites/rabelapp"
+ exit 1;
+fi
+
+site_name="$1"
+app_home="$2"
+tmp_file="$app_home/tmp/$site_name.conf"
+
+# create rvm wrapper
+if [[ ! -f "$HOME/.rvm/bin/bootup_unicorn" ]]; then
+ rvm wrapper `rvm current` bootup unicorn
+fi
+
+cat > $tmp_file <"
+description "$site_name running on Nginx/Unicorn"
+
+start on runlevel [2]
+stop on runlevel [016]
+
+kill signal QUIT
+
+respawn
+respawn limit 3 10
+
+exec $HOME/.rvm/bin/bootup_unicorn -c $app_home/config/unicorn.rb -E production
+
+respawn
+EOF
+
+sudo mv $tmp_file /etc/init/
+log_info "done"
diff --git a/deploy/centos_6.4_install.sh b/deploy/centos_6.4_install.sh
new file mode 100755
index 0000000..c61ac92
--- /dev/null
+++ b/deploy/centos_6.4_install.sh
@@ -0,0 +1,41 @@
+DIR=$( cd "$( dirname "$0" )" && pwd )
+source $DIR/shared.sh
+
+if [ -z "$1" -o -z "$2" ]; then
+ log_error "USAGE: $0 [mysql_password] [domain]"
+ log_info "--- example ---"
+ log_info "$0 e17c92a rabelapp.com"
+ exit
+fi
+
+MYSQL_PASSWD="$1"
+MAIL_DOMAIN="$2"
+
+# Enable EPEL
+sudo su -c 'rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm'
+sudo yum update -y
+sudo yum install -y unzip curl wget vim
+
+# Install deps
+sudo yum install -y gcc-c++ patch readline readline-devel zlib zlib-devel \
+libyaml-devel libffi-devel openssl-devel make bzip2 autoconf automake \
+libtool bison gsfonts iconv-devel git \
+mysql-libs mysql-devel ImageMagick-devel
+
+# Install RVM
+curl -L https://get.rvm.io | bash -s stable
+source ~/.rvm/scripts/rvm
+rvm install 1.9.3-p327
+rvm use 1.9.3-p327 --default
+
+sudo yum install -y postfix ImageMagick ImageMagick-perl nginx memcached
+
+# Install MySQL Server
+sudo yum install -y mysql-server
+sudo /etc/init.d/mysqld restart
+
+# Set MySQL root password
+/usr/bin/mysqladmin -u root password "$MYSQL_PASSWD"
+
+# Set mail domain for postfix
+sudo su -c "sed -i -e '/^#mydomain/a mydomain = $MAIL_DOMAIN' /etc/postfix/main.cf"
diff --git a/deploy/install_bundle.sh b/deploy/install_bundle.sh
new file mode 100755
index 0000000..a3f7bae
--- /dev/null
+++ b/deploy/install_bundle.sh
@@ -0,0 +1 @@
+bundle install --without development test
diff --git a/deploy/kill_old_unicorn.sh b/deploy/kill_old_unicorn.sh
new file mode 100755
index 0000000..e89af95
--- /dev/null
+++ b/deploy/kill_old_unicorn.sh
@@ -0,0 +1,12 @@
+# send QUIT signal to old unicorn master
+DIR=$( cd "$( dirname "$0" )" && pwd )
+source $DIR/shared.sh
+
+if [[ -f "./tmp/pids/unicorn.pid.oldbin" ]]; then
+ oldpid=`cat ./tmp/pids/unicorn.pid.oldbin`
+ log_info "send QUIT signal to pid=$oldpid"
+ sudo kill -s QUIT $oldpid
+else
+ log_error "ERROR: unicorn master(old) not found."
+fi
+
diff --git a/deploy/migrate_database.sh b/deploy/migrate_database.sh
new file mode 100755
index 0000000..37b8e04
--- /dev/null
+++ b/deploy/migrate_database.sh
@@ -0,0 +1 @@
+RAILS_ENV=production bundle exec rake db:migrate
diff --git a/deploy/nginx-example.conf b/deploy/nginx-example.conf
new file mode 100755
index 0000000..f6fcb43
--- /dev/null
+++ b/deploy/nginx-example.conf
@@ -0,0 +1,56 @@
+server_names_hash_bucket_size 64;
+
+upstream EXAMPLE_app_server {
+ server unix:RABEL_HOME/tmp/sockets/unicorn.sock;
+}
+
+server {
+ # un-comment below to enable IPv6 support
+ # listen [::]:80 default ipv6only=on;
+ listen 80;
+
+ set $APP_HOME_PUBLIC RABEL_HOME/public;
+
+ server_name HOST_NAME;
+
+ # un-comment below to enable host rewrite
+ # if ($host = 'OTHER_HOST_NAME') {
+ # rewrite ^(.*)$ http://REWRITE_HOST_NAME$1 permanent;
+ # }
+
+ root $APP_HOME_PUBLIC;
+ index index.html index.htm;
+
+ client_max_body_size 4M;
+
+ location ~ ^/(assets|uploads|avatar|favicon) {
+ access_log off;
+ expires 1y;
+ add_header Cache-Control public;
+ break;
+ }
+
+ location / {
+ try_files $uri @EXAMPLE_backend;
+ }
+
+ location @EXAMPLE_backend {
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header Host $http_host;
+ proxy_redirect off;
+ client_max_body_size 4M;
+ client_body_buffer_size 128K;
+ proxy_pass http://EXAMPLE_app_server;
+ }
+
+ error_page 404 /404.html;
+
+ # redirect server error pages to the static page /50x.html
+ #
+ error_page 500 502 503 504 /500.html;
+ location = /500.html {
+ root $APP_HOME_PUBLIC;
+ }
+}
+
diff --git a/deploy/precompile_assets.sh b/deploy/precompile_assets.sh
new file mode 100755
index 0000000..9dff951
--- /dev/null
+++ b/deploy/precompile_assets.sh
@@ -0,0 +1 @@
+RAILS_ENV=production bundle exec rake assets:precompile
diff --git a/deploy/reload_unicorn.sh b/deploy/reload_unicorn.sh
new file mode 100755
index 0000000..bc583dd
--- /dev/null
+++ b/deploy/reload_unicorn.sh
@@ -0,0 +1,13 @@
+# send USR2 signal to unicorn master process
+DIR=$( cd "$( dirname "$0" )" && pwd )
+source $DIR/shared.sh
+
+if [[ -f "./tmp/pids/unicorn.pid" ]]; then
+ pid=`cat ./tmp/pids/unicorn.pid`
+ log_info "send USR2 signal to pid=$pid"
+ sudo kill -s USR2 $pid
+ log_warning '!!! do NOT forget to kill the old unicorn process !!!'
+else
+ log_error "ERROR: unicorn master process not found"
+fi
+
diff --git a/deploy/set_heroku_env.rb b/deploy/set_heroku_env.rb
new file mode 100755
index 0000000..cae7ca0
--- /dev/null
+++ b/deploy/set_heroku_env.rb
@@ -0,0 +1,6 @@
+require 'rubygems'
+require 'yaml'
+
+envs = YAML.load_file('./config/application.yml')
+kv = envs.map {|k, v| "#{k}=\"#{v}\""}
+`heroku config:add #{kv.join(' ')}`
diff --git a/deploy/setup_database_once.sh b/deploy/setup_database_once.sh
new file mode 100755
index 0000000..403c15f
--- /dev/null
+++ b/deploy/setup_database_once.sh
@@ -0,0 +1 @@
+RAILS_ENV=production bundle exec rake db:setup
diff --git a/deploy/shared.sh b/deploy/shared.sh
new file mode 100644
index 0000000..9cb380f
--- /dev/null
+++ b/deploy/shared.sh
@@ -0,0 +1,13 @@
+# shared library
+log_error() {
+ echo -e "\033[1;31m$1\033[0m"
+}
+
+log_info() {
+ echo -e "\033[1;32m$1\033[0m"
+}
+
+log_warning() {
+ echo -e "\033[1;41m$1\033[0m"
+}
+
diff --git a/deploy/start_unicorn_manually.sh b/deploy/start_unicorn_manually.sh
new file mode 100755
index 0000000..d382dae
--- /dev/null
+++ b/deploy/start_unicorn_manually.sh
@@ -0,0 +1,13 @@
+# start unicorn master process manually
+DIR=$( cd "$( dirname "$0" )" && pwd )
+source $DIR/shared.sh
+
+bundle exec unicorn -c `pwd`/config/unicorn.rb -E production -D
+
+if [[ "$?" == 0 ]]; then
+ log_info "unicorn master started."
+else
+ log_error "master failed to start."
+ tail -n 60 ./log/unicorn.stderr.log | less
+fi
+
diff --git a/deploy/stop_unicorn_manually.sh b/deploy/stop_unicorn_manually.sh
new file mode 100755
index 0000000..9ab84bf
--- /dev/null
+++ b/deploy/stop_unicorn_manually.sh
@@ -0,0 +1,12 @@
+# send QUIT signal to unicorn master process
+DIR=$( cd "$( dirname "$0" )" && pwd )
+source $DIR/shared.sh
+
+if [[ -f "./tmp/pids/unicorn.pid" ]]; then
+ pid=`cat ./tmp/pids/unicorn.pid`
+ log_info "send QUIT signal to pid=$pid"
+ sudo kill -s QUIT $pid
+else
+ log_error "ERROR: unicorn master process not found"
+fi
+
diff --git a/deploy/ubuntu_12.04_install.sh b/deploy/ubuntu_12.04_install.sh
new file mode 100755
index 0000000..04d76f4
--- /dev/null
+++ b/deploy/ubuntu_12.04_install.sh
@@ -0,0 +1,57 @@
+DIR=$( cd "$( dirname "$0" )" && pwd )
+source $DIR/shared.sh
+
+if [ -z "$1" -o -z "$2" ]; then
+ log_error "USAGE: $0 [mysql_password] [domain]"
+ log_info "--- example ---"
+ log_info "$0 e17c92a rabelapp.com"
+ exit
+fi
+
+MYSQL_PASSWD="$1"
+MAIL_ADDRESS="$2"
+
+RUBY_VERSION="2.0.0-p247"
+
+sudo apt-get update
+sudo apt-get install -y unzip curl aptitude vim debconf-utils
+
+# Install RVM
+curl -L get.rvm.io | bash -s stable
+source_rvm="source ~/.rvm/scripts/rvm"
+echo "$source_rvm" >> ~/.bashrc
+echo "$source_rvm" | bash
+
+# Install deps
+sudo apt-get install -y build-essential openssl \
+libreadline6 libreadline6-dev \
+curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev \
+libsqlite3-0 libxml2-dev libxslt-dev \
+autoconf libc6-dev ncurses-dev automake libtool bison g++ \
+libmysqlclient-dev
+
+# Install RMagic deps
+sudo apt-get install -y graphicsmagick-libmagick-dev-compat
+sudo apt-get install -y libmagickwand-dev
+
+# Install Ruby
+~/.rvm/bin/rvm install $RUBY_VERSION
+echo "$source_rvm" | bash
+rvm use $RUBY_VERSION --default
+
+sudo aptitude install -y memcached imagemagick nodejs nginx
+
+cat <认识电影
+ And I am on the node page
+ Then I should see 认识电影
+
+ Scenario: show node introduction
+ Given a node exists with introduction: A cool node
+ And I am on the node page
+ Then I should see A cool node
diff --git a/features/pages.feature b/features/pages.feature
new file mode 100644
index 0000000..cb111fb
--- /dev/null
+++ b/features/pages.feature
@@ -0,0 +1,20 @@
+Feature: Pages
+ Scenario: visit page
+ Given a page exists with title: FAQ
+ And I am on the page page
+ Then page title should contain FAQ
+ And I should see FAQ
+ Scenario: visit draft page
+ Given an root exists
+ And a page is in draft with title: FAQ
+ And I am on the page page
+ Then it should display a record not found message
+ Given I have logged in as devin
+ And I am on the page page
+ Then it should display a record not found message
+ When I logout
+ Given as an admin, I have logged in as zhiming
+ And I am on the page page
+ Then page title should contain FAQ
+ And I should see FAQ
+
diff --git a/features/passwords.feature b/features/passwords.feature
new file mode 100644
index 0000000..762d066
--- /dev/null
+++ b/features/passwords.feature
@@ -0,0 +1,9 @@
+Feature: Passwords
+ Scenario: reset password
+ Given an user exists
+ And I am on the password reset page
+ Then I should see 重新设置密码
+ And I should see 用户名
+ And I should see 注册邮箱
+ When I fill in the password reset form
+ Then I should see 现在请去查看你的注册邮箱,其中有帮助你重新设置密码的链接
diff --git a/features/session.feature b/features/session.feature
new file mode 100644
index 0000000..de8e6cc
--- /dev/null
+++ b/features/session.feature
@@ -0,0 +1,17 @@
+Feature: Session
+
+ Scenario: sign in
+ Given an user exists with nickname: devin
+ And I am on the login page
+ Then it should display the login form
+ When I fill in devin's credentials
+ Then I should be signed in
+
+ Scenario: sign out
+ Given I have logged in as devin
+ And I am on the home page
+ Then I should see 退出
+ When I click the link 退出
+ Then I should be redirected to the goodbye page
+ And I should see 退出
+ And I should see 没有任何个人信息留在这台设备上
diff --git a/features/step_definitions/ad.rb b/features/step_definitions/ad.rb
new file mode 100644
index 0000000..e4b5a8b
--- /dev/null
+++ b/features/step_definitions/ad.rb
@@ -0,0 +1,9 @@
+# encoding: utf-8
+Given /^an advertisement exists with title: (.*)$/ do |title|
+ FactoryGirl.create(:advertisement, :title => title)
+end
+
+When /^I provide new ad title$/ do
+ fill_in "advertisement[title]", :with => 'Rabel 1.0 Preview'
+ click_button '保存'
+end
diff --git a/features/step_definitions/base.rb b/features/step_definitions/base.rb
new file mode 100644
index 0000000..2b8fc6d
--- /dev/null
+++ b/features/step_definitions/base.rb
@@ -0,0 +1,271 @@
+# encoding: utf-8
+
+def path_to(page)
+ case page
+ when 'the home'
+ root_path
+ when 'the login'
+ new_user_session_path
+ when 'the signout'
+ destroy_user_session_path
+ when 'the register'
+ new_user_registration_path
+ when 'the settings'
+ settings_path
+ when /(.*)'s profile/
+ nickname = $1
+ if nickname == 'the user'
+ user = User.first
+ else
+ user = User.find_by_nickname(nickname)
+ end
+ member_path(user.nickname)
+ when 'the topic'
+ topic = Topic.first
+ t_path(topic.id)
+ when 'the node'
+ node = Node.first
+ go_path(node.key)
+ when 'the topic creation'
+ node = Node.first
+ new_node_topic_path(node)
+ when 'my following'
+ my_following_path
+ when 'my bookmarked topics'
+ my_topics_path
+ when 'my bookmarked nodes'
+ my_nodes_path
+ when 'the admin pages'
+ admin_pages_path
+ when 'the admin users'
+ admin_users_path
+ when 'the admin topics'
+ admin_topics_path
+ when 'the admin planes'
+ admin_planes_path
+ when 'the admin ads'
+ admin_advertisements_path
+ when 'the admin page creation'
+ new_admin_page_path
+ when 'the admin edit page'
+ page = Page.first
+ edit_admin_page_path(page)
+ when 'the page'
+ page = Page.first
+ page_path(page.key)
+ when 'the goodbye'
+ goodbye_path
+ when 'the password reset'
+ new_user_password_path
+ when 'the notifications'
+ notifications_path
+ else
+ raise 'Unknown page'
+ end
+end
+
+Given /^I am on (.*) page$/ do |page|
+ visit path_to(page)
+end
+
+When /^wait (\d+)s$/ do |i|
+ sleep i.to_i
+end
+
+When /^debug$/ do
+ debugger
+end
+
+Given /^I logout$/ do
+ within('.navbar') do
+ click_link '退出'
+ end
+end
+
+When /^I try to register$/ do
+ visit new_user_registration_path
+end
+
+Given /^I am not authenticated$/ do
+ steps %Q(Given I am on the home page)
+ steps %Q(Then I should not see 退出)
+end
+
+Given /^an? (.*) exists with nickname: (.*)$/ do |type, nickname|
+ FactoryGirl.create(type.to_sym, :nickname => nickname)
+end
+
+Given /^I have logged in as (.*)$/ do |nickname|
+ user = User.find_by_nickname(nickname)
+ steps %Q(Given an user exists with nickname: #{nickname}) unless user.present?
+ visit new_user_session_path
+ fill_in 'user_nickname', :with => nickname
+ fill_in 'user_password', :with => ENV['RABEL_TEST_DEFAULT_PASSWORD']
+ click_button '登入'
+end
+
+Then /^I should not see (.*)$/ do |text|
+ page.should have_no_text(text)
+end
+
+Then /^I should see (.*)$/ do |text|
+ page.should have_text(text)
+end
+
+Then /^I should be redirected to (.*) page$/ do |page|
+ current_path.should == path_to(page)
+end
+
+Given /^a node exists with name: (.*)$/ do |name|
+ FactoryGirl.create(:node, :name => name)
+end
+
+Given /^a topic exists with title:(.*)$/ do |title|
+ FactoryGirl.create(:topic, :title => title)
+end
+
+Given /^a topic under the node exists with title:(.*)$/ do |title|
+ node = Node.first
+ FactoryGirl.create(:topic, :node => node, :title => title)
+end
+
+Given /^a topic of (.*) exists with title:(.*)$/ do |nickname, title|
+ if nickname == 'the user' or nickname == 'me'
+ user = User.first
+ else
+ user = User.find_by_nickname(nickname)
+ end
+ FactoryGirl.create(:topic, :user => user, :title => title)
+end
+
+Given /^a locked topic of (.*) exists with title:(.*)$/ do |nickname, title|
+ if nickname == 'the user' or nickname == 'me'
+ user = User.first
+ else
+ user = User.find_by_nickname(nickname)
+ end
+ FactoryGirl.create(:locked_topic, :user => user, :title => title)
+end
+
+Given /^a comment exists with content:(.*)$/ do |content|
+ topic = Topic.first
+ FactoryGirl.create(:comment, :commentable => topic, :content => content)
+end
+
+Then /^page title should contain (.*)$/ do |title|
+ page.find(:xpath, '//title').native.text.should have_content(title)
+end
+
+Then /^page title should not contain (.*)$/ do |title|
+ page.find(:xpath, '//title').native.text.should_not have_content(title)
+end
+
+When /^I click the link (.*)$/ do |link|
+ click_link(link)
+end
+
+When /^I click the button (.*)$/ do |button|
+ click_button(button)
+end
+
+When /^I click the mobile button (.*)$/ do |button|
+ find_button(button).find(:xpath, '..').click
+end
+
+Then /^I can see that (.*) was mentioned in the comment$/ do |nickname|
+ within('.reply_content') do
+ page.should have_link(nickname)
+ end
+end
+
+Then /^it should display personal homepage of (.*)$/ do |nickname|
+ steps %Q(Then page title should contain #{nickname})
+ steps %Q(Then I should see #{nickname})
+ steps %Q(Then I should see 最近创建的话题)
+ steps %Q(Then I should see 最近的回复)
+end
+
+Then /^it should display button (.*)$/ do |text|
+ page.should have_button(text)
+end
+
+Given /^as an admin, I have logged in as (.*)$/ do |nickname|
+ steps %Q(Given an admin exists with nickname: #{nickname})
+ steps %Q(Given I have logged in as #{nickname})
+end
+
+Given /^as root, I have logged in as (.*)$/ do |nickname|
+ steps %Q(Given a root exists with nickname: #{nickname})
+ steps %Q(Given I have logged in as #{nickname})
+end
+
+Given /^(a|an) (.*) exists$/ do |i, model|
+ FactoryGirl.create(model)
+end
+
+Then /^an alert message is shown as (.*)$/ do |text|
+ assert page.driver.browser.switch_to.alert.text.include?(text)
+end
+
+When /^I confirm the alert message$/ do
+ page.driver.browser.switch_to.alert.accept
+end
+
+Then /^it should display the search form$/ do
+ page.should have_css("input#q")
+end
+
+When /^I search for (.*)$/ do |query|
+ fill_in "q", :with => query
+ find("#q").native.send_key(:enter)
+end
+
+Then /^it will use configured search engine$/ do
+ page.driver.browser.window_handles.length.should == 2
+ new_window = page.driver.browser.window_handles.last
+ page.driver.browser.switch_to.window new_window
+ current_url.should include(search_engine_url)
+ current_url.should include('site')
+end
+
+Then /^it should display (\d+) notification(s?)$/ do |n, i|
+ page.all("#page-main .col-md-9 table tr").size.should == n.to_i
+end
+
+Given /^a notification exists with user: (.*)$/ do |nickname|
+ user = User.find_by_nickname(nickname)
+ FactoryGirl.create(:notification, :user => user)
+end
+
+Then /^it should display link (.*)$/ do |link_text|
+ page.should have_link(link_text)
+end
+
+Then /^it should not display link (.*)$/ do |link_text|
+ page.should have_no_link(link_text)
+end
+
+Then /^it should not display button (.*)$/ do |link_text|
+ page.should have_no_button(link_text)
+end
+
+Then /^I should be able to post a new comment$/ do
+ steps %Q(When I add comment: 我爱北京天安门)
+ steps %Q(Then I should be redirected to the topic page)
+ steps %Q(And I should see 我爱北京天安门)
+end
+
+Given /^my user agent is: (.*)$/ do |ua|
+ page.driver.header('USER_AGENT', ua)
+end
+
+Then /^it should display a record not found message$/ do
+ steps %Q(Then I should see 404)
+end
+
+Given /^I have (\d+) unread notification$/ do |num|
+ user = User.first
+ num.to_i.times do
+ FactoryGirl.create(:notification, :user => user)
+ end
+end
diff --git a/features/step_definitions/bookmark.rb b/features/step_definitions/bookmark.rb
new file mode 100644
index 0000000..4cb4ae0
--- /dev/null
+++ b/features/step_definitions/bookmark.rb
@@ -0,0 +1,35 @@
+Given /^I have bookmarked this node$/ do
+ user = User.first
+ node = Node.first
+ FactoryGirl.create(:bookmark, :user => user, :bookmarkable => node)
+end
+
+Given /^I have bookmarked this topic$/ do
+ user = User.first
+ topic = Topic.first
+ FactoryGirl.create(:bookmark, :user => user, :bookmarkable => topic)
+end
+
+Given /^I have bookmarked (\d+) nodes$/ do |n|
+ u = User.first
+ n.to_i.times do
+ node = FactoryGirl.create(:node)
+ FactoryGirl.create(:bookmark, :user => u, :bookmarkable => node)
+ end
+end
+
+Then /^it should display (\d+) bookmarked nodes$/ do |n|
+ page.should have_css('td tr', :count => n.to_i)
+end
+
+Given /^I have bookmarked (\d+) topics$/ do |n|
+ u = User.first
+ n.to_i.times do
+ topic = FactoryGirl.create(:topic)
+ FactoryGirl.create(:bookmark, :user => u, :bookmarkable => topic)
+ end
+end
+
+Then /^it should display (\d+) bookmarked topics$/ do |n|
+ page.should have_css('td.avatar', :count => n.to_i)
+end
diff --git a/features/step_definitions/comments.rb b/features/step_definitions/comments.rb
new file mode 100644
index 0000000..e61aaa0
--- /dev/null
+++ b/features/step_definitions/comments.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+#
+When /^I add comment:(.*)$/ do |comment|
+ fill_in "comment[content]", :with => comment
+ click_button '发送'
+end
+
+Then /^there should be link (.*) in reply$/ do |link|
+ within(".reply") do
+ page.should have_link(link)
+ end
+end
+
+Then /^there's no link (.*) in reply$/ do |link|
+ within(".reply") do
+ page.should have_no_link(link)
+ end
+end
diff --git a/features/step_definitions/email_steps.rb b/features/step_definitions/email_steps.rb
deleted file mode 100644
index 12bcb3f..0000000
--- a/features/step_definitions/email_steps.rb
+++ /dev/null
@@ -1,206 +0,0 @@
-# Commonly used email steps
-#
-# To add your own steps make a custom_email_steps.rb
-# The provided methods are:
-#
-# last_email_address
-# reset_mailer
-# open_last_email
-# visit_in_email
-# unread_emails_for
-# mailbox_for
-# current_email
-# open_email
-# read_emails_for
-# find_email
-#
-# General form for email scenarios are:
-# - clear the email queue (done automatically by email_spec)
-# - execute steps that sends an email
-# - check the user received an/no/[0-9] emails
-# - open the email
-# - inspect the email contents
-# - interact with the email (e.g. click links)
-#
-# The Cucumber steps below are setup in this order.
-
-module EmailHelpers
- def current_email_address
- # Replace with your a way to find your current email. e.g @current_user.email
- # last_email_address will return the last email address used by email spec to find an email.
- # Note that last_email_address will be reset after each Scenario.
- last_email_address || "example@example.com"
- end
-end
-
-World(EmailHelpers)
-
-#
-# Reset the e-mail queue within a scenario.
-# This is done automatically before each scenario.
-#
-
-Given /^(?:a clear email queue|no emails have been sent)$/ do
- reset_mailer
-end
-
-#
-# Check how many emails have been sent/received
-#
-
-Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails?$/ do |address, amount|
- unread_emails_for(address).size.should == parse_email_count(amount)
-end
-
-Then /^(?:I|they|"([^"]*?)") should have (an|no|\d+) emails?$/ do |address, amount|
- mailbox_for(address).size.should == parse_email_count(amount)
-end
-
-Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails? with subject "([^"]*?)"$/ do |address, amount, subject|
- unread_emails_for(address).select { |m| m.subject =~ Regexp.new(Regexp.escape(subject)) }.size.should == parse_email_count(amount)
-end
-
-Then /^(?:I|they|"([^"]*?)") should receive (an|no|\d+) emails? with subject \/([^"]*?)\/$/ do |address, amount, subject|
- unread_emails_for(address).select { |m| m.subject =~ Regexp.new(subject) }.size.should == parse_email_count(amount)
-end
-
-Then /^(?:I|they|"([^"]*?)") should receive an email with the following body:$/ do |address, expected_body|
- open_email(address, :with_text => expected_body)
-end
-
-#
-# Accessing emails
-#
-
-# Opens the most recently received email
-When /^(?:I|they|"([^"]*?)") opens? the email$/ do |address|
- open_email(address)
-end
-
-When /^(?:I|they|"([^"]*?)") opens? the email with subject "([^"]*?)"$/ do |address, subject|
- open_email(address, :with_subject => subject)
-end
-
-When /^(?:I|they|"([^"]*?)") opens? the email with subject \/([^"]*?)\/$/ do |address, subject|
- open_email(address, :with_subject => Regexp.new(subject))
-end
-
-When /^(?:I|they|"([^"]*?)") opens? the email with text "([^"]*?)"$/ do |address, text|
- open_email(address, :with_text => text)
-end
-
-When /^(?:I|they|"([^"]*?)") opens? the email with text \/([^"]*?)\/$/ do |address, text|
- open_email(address, :with_text => Regexp.new(text))
-end
-
-#
-# Inspect the Email Contents
-#
-
-Then /^(?:I|they) should see "([^"]*?)" in the email subject$/ do |text|
- current_email.should have_subject(text)
-end
-
-Then /^(?:I|they) should see \/([^"]*?)\/ in the email subject$/ do |text|
- current_email.should have_subject(Regexp.new(text))
-end
-
-Then /^(?:I|they) should see "([^"]*?)" in the email body$/ do |text|
- current_email.default_part_body.to_s.should include(text)
-end
-
-Then /^(?:I|they) should see \/([^"]*?)\/ in the email body$/ do |text|
- current_email.default_part_body.to_s.should =~ Regexp.new(text)
-end
-
-Then /^(?:I|they) should see the email delivered from "([^"]*?)"$/ do |text|
- current_email.should be_delivered_from(text)
-end
-
-Then /^(?:I|they) should see "([^\"]*)" in the email "([^"]*?)" header$/ do |text, name|
- current_email.should have_header(name, text)
-end
-
-Then /^(?:I|they) should see \/([^\"]*)\/ in the email "([^"]*?)" header$/ do |text, name|
- current_email.should have_header(name, Regexp.new(text))
-end
-
-Then /^I should see it is a multi\-part email$/ do
- current_email.should be_multipart
-end
-
-Then /^(?:I|they) should see "([^"]*?)" in the email html part body$/ do |text|
- current_email.html_part.body.to_s.should include(text)
-end
-
-Then /^(?:I|they) should see "([^"]*?)" in the email text part body$/ do |text|
- current_email.text_part.body.to_s.should include(text)
-end
-
-#
-# Inspect the Email Attachments
-#
-
-Then /^(?:I|they) should see (an|no|\d+) attachments? with the email$/ do |amount|
- current_email_attachments.size.should == parse_email_count(amount)
-end
-
-Then /^there should be (an|no|\d+) attachments? named "([^"]*?)"$/ do |amount, filename|
- current_email_attachments.select { |a| a.filename == filename }.size.should == parse_email_count(amount)
-end
-
-Then /^attachment (\d+) should be named "([^"]*?)"$/ do |index, filename|
- current_email_attachments[(index.to_i - 1)].filename.should == filename
-end
-
-Then /^there should be (an|no|\d+) attachments? of type "([^"]*?)"$/ do |amount, content_type|
- current_email_attachments.select { |a| a.content_type.include?(content_type) }.size.should == parse_email_count(amount)
-end
-
-Then /^attachment (\d+) should be of type "([^"]*?)"$/ do |index, content_type|
- current_email_attachments[(index.to_i - 1)].content_type.should include(content_type)
-end
-
-Then /^all attachments should not be blank$/ do
- current_email_attachments.each do |attachment|
- attachment.read.size.should_not == 0
- end
-end
-
-Then /^show me a list of email attachments$/ do
- EmailSpec::EmailViewer::save_and_open_email_attachments_list(current_email)
-end
-
-#
-# Interact with Email Contents
-#
-
-When /^(?:I|they) follow "([^"]*?)" in the email$/ do |link|
- visit_in_email(link)
-end
-
-When /^(?:I|they) click the first link in the email$/ do
- click_first_link_in_email
-end
-
-#
-# Debugging
-# These only work with Rails and OSx ATM since EmailViewer uses RAILS_ROOT and OSx's 'open' command.
-# Patches accepted. ;)
-#
-
-Then /^save and open current email$/ do
- EmailSpec::EmailViewer::save_and_open_email(current_email)
-end
-
-Then /^save and open all text emails$/ do
- EmailSpec::EmailViewer::save_and_open_all_text_emails
-end
-
-Then /^save and open all html emails$/ do
- EmailSpec::EmailViewer::save_and_open_all_html_emails
-end
-
-Then /^save and open all raw emails$/ do
- EmailSpec::EmailViewer::save_and_open_all_raw_emails
-end
diff --git a/features/step_definitions/mobile.rb b/features/step_definitions/mobile.rb
new file mode 100644
index 0000000..e498c39
--- /dev/null
+++ b/features/step_definitions/mobile.rb
@@ -0,0 +1,30 @@
+# encoding: utf-8
+
+Then /^it should display a logout icon$/ do
+ page.should have_css("#EjectIcon")
+end
+
+When /^I click the logout icon$/ do
+ find("#EjectIcon").find(:xpath, '..').click
+end
+
+Then /^it should display a settings icon$/ do
+ page.should have_css("#GearIcon")
+end
+
+Then /^it should display the version number$/ do
+ steps %Q(Then I should see #{Rabel.version})
+end
+
+When /^I read the notifications$/ do
+ find(".notification").click
+end
+
+Then /^it should display mark all as read buttun$/ do
+ page.should have_css('a.btn')
+ page.should have_button('全部标记为已读')
+end
+
+When /^I mark all notifications as read$/ do
+ find_button('全部标记为已读').find(:xpath, '..').click
+end
diff --git a/features/step_definitions/node.rb b/features/step_definitions/node.rb
new file mode 100644
index 0000000..39411d2
--- /dev/null
+++ b/features/step_definitions/node.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+
+Then /^it should display a compact topic creation form$/ do
+ page.should have_css('form.new_topic')
+ page.should have_css('input.sll')
+ page.should have_css('textarea.mll')
+end
+
+Then /^it should not display a compact topic creation form$/ do
+ page.should_not have_css('form.new_topic')
+ page.should_not have_css('input.sll')
+ page.should_not have_css('textarea.mll')
+end
+
+When /^I provide topic creation information$/ do
+ fill_in "topic[title]", :with => 'hi'
+ fill_in "topic[content]", :with => 'Rails is cool!'
+ click_button '创建'
+end
+
+Given /^the node has topics of (\d+) pages$/ do |page_count|
+ node = Node.first
+ topic_count = Siteconf.pagination_topics.to_i * page_count.to_i
+ topic_count.times do
+ FactoryGirl.create(:topic, :node => node)
+ end
+end
+
+Then /^it should display one page topics$/ do
+ page.should have_css(".avatar", :count => Siteconf.pagination_topics.to_i)
+end
+
+Given /^a node exists with introduction: (.*)$/ do |intro|
+ FactoryGirl.create(:node, :introduction => intro)
+end
+
+Then /^it should display the node form$/ do
+ page.should have_css('form.node')
+end
+
+When /^I try to provide node info with name: (.*)$/ do |name|
+ fill_in "node[key]", :with => 'node_key_1'
+ fill_in "node[name]", :with => name
+ click_button '保存'
+end
+
diff --git a/features/step_definitions/page.rb b/features/step_definitions/page.rb
new file mode 100644
index 0000000..9f816c1
--- /dev/null
+++ b/features/step_definitions/page.rb
@@ -0,0 +1,37 @@
+# encoding: utf-8
+When /^I fill in the page form$/ do
+ fill_in "page[key]", :with => 'faq'
+ fill_in "page[title]", :with => 'hi'
+ fill_in "page[content]", :with => 'ok'
+ click_button '保存'
+end
+
+Given /^(\d+) pages exist$/ do |n|
+ n.to_i.times do
+ FactoryGirl.create(:page)
+ end
+end
+
+Then /^(\d+) page nav links shold be shown$/ do |n|
+ page.all('#footer .page-links a.nav').size.should == n.to_i
+end
+
+Given /^a page exists with title: (.*)$/ do |title|
+ FactoryGirl.create(:page, :title => title)
+end
+
+Given /^a page is published$/ do
+ FactoryGirl.create(:page, :published => true)
+end
+
+Given /^a page is in draft$/ do
+ FactoryGirl.create(:page, :published => false)
+end
+
+Given /^a page is in draft with title: (.*)$/ do |title|
+ FactoryGirl.create(:page, :published => false, :title => title)
+end
+
+Then /^it should only display (\d+) page$/ do |num|
+ all("#footer .page-links a.nav").size.should == num.to_i
+end
diff --git a/features/step_definitions/password.rb b/features/step_definitions/password.rb
new file mode 100644
index 0000000..212d7b0
--- /dev/null
+++ b/features/step_definitions/password.rb
@@ -0,0 +1,7 @@
+# encoding: utf-8
+When /^I fill in the password reset form$/ do
+ u = User.first
+ fill_in "user[nickname]", :with => u.nickname
+ fill_in "user[email]", :with => u.email
+ click_button '重新设置密码'
+end
diff --git a/features/step_definitions/plane.rb b/features/step_definitions/plane.rb
new file mode 100644
index 0000000..997cb3b
--- /dev/null
+++ b/features/step_definitions/plane.rb
@@ -0,0 +1,22 @@
+# encoding: utf-8
+Then /^it should display the plane creation form$/ do
+ page.should have_css('form.new_plane')
+ steps %Q(And I should see 名称)
+ steps %Q(And it should display button 保存)
+end
+
+Then /^it should display the plane editing form$/ do
+ page.should have_css('form.edit_plane')
+ steps %Q(And I should see 名称)
+ steps %Q(And it should display button 保存)
+end
+
+When /^I provide the plane name: (.*)$/ do |name|
+ fill_in "plane[name]", :with => name
+ click_button '保存'
+end
+
+Given /^a plane exists with name: (.*)$/ do |name|
+ FactoryGirl.create(:plane, :name => name)
+end
+
diff --git a/features/step_definitions/session.rb b/features/step_definitions/session.rb
new file mode 100644
index 0000000..d70ad89
--- /dev/null
+++ b/features/step_definitions/session.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+When /^I try to sign in$/ do
+ click_link '登入'
+end
+
+Then /^it should display the login form$/ do
+ page.should have_css('form')
+ page.should have_content('登入')
+ page.should have_content('用户名')
+ page.should have_content('密码')
+ page.should have_content('找回登录密码')
+end
+
+When /^I fill in (.*)\'s credentials$/ do |nickname|
+ fill_in 'user_nickname', :with => nickname
+ fill_in 'user_password', :with => ENV['RABEL_TEST_DEFAULT_PASSWORD']
+ click_button '登入'
+end
+
+Then /^I should be signed in$/ do
+ page.should have_no_content('登入')
+end
diff --git a/features/step_definitions/topic.rb b/features/step_definitions/topic.rb
new file mode 100644
index 0000000..ef593c3
--- /dev/null
+++ b/features/step_definitions/topic.rb
@@ -0,0 +1,121 @@
+# encoding: utf-8
+
+Then /^it should display a topic creation form$/ do
+ page.should have_css('form.new_topic')
+ page.should have_css('input#topic_title')
+ page.should have_css('textarea#topic_content')
+end
+
+Then /^it should display a topic edit form$/ do
+ page.should have_css('form.edit_topic')
+ page.should have_css('input#topic_title')
+ page.should have_css('textarea#topic_content')
+end
+
+Then /^it should display a comment form$/ do
+ page.should have_css('form.new_comment')
+ page.should have_content('现在就添加一条回复')
+end
+
+Then /^it should not display a comment form$/ do
+ page.should have_no_css('form.new_comment')
+ page.should have_no_content('现在就添加一条回复')
+end
+
+Given /^the topic has comments of (\d+) pages \((\d+) per page\)$/ do |pages, per_page|
+ topic = Topic.first
+ Siteconf.pagination_comments = per_page.to_i
+ (per_page.to_i * pages.to_i).times do
+ FactoryGirl.create(:comment, :commentable => topic)
+ end
+end
+
+Then /^it should display the pagination links$/ do
+ within(".pagination") do
+ page.should have_css('.first')
+ page.should have_css('.active')
+ end
+end
+
+Then /^it should display the mobile pagination links$/ do
+ within(".pagination") do
+ page.should have_css('.current')
+ page.should have_css('.k_page')
+ end
+end
+
+Then /^the current page is the last page$/ do
+ last_child = find(".pagination .active span")
+ last_child.tag_name.should == 'span'
+end
+
+Then /^the current mobile page is the last page$/ do
+ last_child = find(".pagination .current")
+ last_child.tag_name.should == 'span'
+end
+
+def str_to_num(str)
+ case str
+ when 'first'
+ 1
+ when 'second'
+ 2
+ when 'third'
+ 3
+ end
+end
+
+When /^I click the (.*) page$/ do |page|
+ within(".pagination") do
+ click_link str_to_num(page).to_s
+ end
+end
+
+Then /^the current page should be the (.*) page$/ do |page|
+ find(".pagination .active span").text.should == str_to_num(page).to_s
+end
+
+Then /^the current mobile page should be the (.*) page$/ do |page|
+ find(".pagination .current").text.should == str_to_num(page).to_s
+end
+
+Given /^a node exists with custom html: (.*)$/ do |html|
+ FactoryGirl.create(:node, :custom_html => html)
+end
+
+Then /^it should display custom widget: (.*)$/ do |content|
+ within("#Rightbar") do
+ page.should have_content(content)
+ end
+end
+
+Then /^it should display a mention button$/ do
+ page.should have_css('img.mention_button')
+end
+
+When /^I click the mention button$/ do
+ find("img.mention_button:first").click
+end
+
+Then /^the commenter user name should appear in the comment box$/ do
+ mention_button = find("img.mention_button:first")
+ find("#comment_content").value.should have_content(mention_button['data-mention'])
+end
+
+Then /^it should not display any mention buttons$/ do
+ page.should have_no_css("img.mention_button")
+end
+
+When /^I post a comment with content: (.*)$/ do |content|
+ fill_in "comment[content]", :with => content
+ click_button '发送'
+end
+
+Given /^I have subscribed to the topic feed$/ do
+ visit "/topics.atom"
+end
+
+Then /^it should display topic feeds$/ do
+ doc = Nokogiri::XML(page.driver.response.body)
+ doc.css('entry').size.should > 0
+end
diff --git a/features/step_definitions/user.rb b/features/step_definitions/user.rb
new file mode 100644
index 0000000..336a66a
--- /dev/null
+++ b/features/step_definitions/user.rb
@@ -0,0 +1,63 @@
+# encoding: utf-8
+
+Then /^it should display the registration form$/ do
+ page.should have_css('form')
+ page.should have_content('注册')
+ page.should have_content('用户名')
+ page.should have_content('密码')
+ page.should have_content('电子邮件')
+end
+
+When /^I provide the necessary registration infomation$/ do
+ fill_in 'user_nickname', :with => 'Rabel_1'
+ fill_in 'user_password', :with => ENV['RABEL_TEST_DEFAULT_PASSWORD']
+ fill_in 'user_password_confirmation', :with => ENV['RABEL_TEST_DEFAULT_PASSWORD']
+ fill_in 'user_email', :with => 'rabel_1@rabel.com'
+ click_button '注册'
+end
+
+When /^I try to edit my profile settings$/ do
+ visit settings_path
+end
+
+Then /^it should display the settings form$/ do
+ page.should have_css('form.edit_user')
+
+ # account
+ page.should have_content('用户名')
+ page.should have_content('电子邮件')
+ page.should have_content('个人网站')
+ page.should have_content('所在地')
+ page.should have_content('签名')
+ page.should have_content('个人简介')
+
+ # password
+ page.should have_content('当前密码')
+ page.should have_content('新密码')
+ page.should have_content('新密码确认')
+end
+
+When /^I provide account info$/ do
+ fill_in 'user[account_attributes][personal_website]', :with => 'http://rabelapp.com'
+ click_button '保存设置'
+end
+
+When /^I provide new password$/ do
+ new_password = "new_#{ENV['RABEL_TEST_DEFAULT_PASSWORD']}"
+ fill_in 'user[current_password]', :with => ENV['RABEL_TEST_DEFAULT_PASSWORD']
+ fill_in 'user[password]', :with => new_password
+ fill_in 'user[password_confirmation]', :with => new_password
+ click_button '修改密码'
+end
+
+Given /^(.*) has followed (.*)$/ do |who, nicknames|
+ actor = User.find_by_nickname(who)
+ nicknames.split(', ').each do |nickname|
+ user = User.find_by_nickname(nickname)
+ unless user.present?
+ user = FactoryGirl.create(:user, :nickname => nickname)
+ end
+ actor.follow(user)
+ end
+end
+
diff --git a/features/support/env.rb b/features/support/env.rb
new file mode 100644
index 0000000..5e86ada
--- /dev/null
+++ b/features/support/env.rb
@@ -0,0 +1,76 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+ENV['RAILS_ENV'] = 'test'
+ENV['RABEL_TEST_DEFAULT_PASSWORD'] = '123456'
+ENV['RABEL_HOST_NAME'] = '127.0.0.1'
+require 'cucumber/rails'
+
+# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In
+# order to ease the transition to Capybara we set the default here. If you'd
+# prefer to use XPath just remove this line and adjust any selectors in your
+# steps to use the XPath syntax.
+Capybara.default_selector = :css
+Capybara.register_driver :selenium do |app|
+ Capybara::Selenium::Driver.new(app, :browser => :firefox)
+end
+
+Capybara.register_driver :mobile do |app|
+ Capybara::RackTest::Driver.new(app, :headers => {'HTTP_USER_AGENT' => 'iPhone'})
+end
+
+Before('@mobile') do
+ Capybara.current_driver = :mobile
+end
+
+After('@mobile') do
+ Capybara.use_default_driver
+end
+
+# By default, any exception happening in your Rails application will bubble up
+# to Cucumber so that your scenario will fail. This is a different from how
+# your application behaves in the production environment, where an error page will
+# be rendered instead.
+#
+# Sometimes we want to override this default behaviour and allow Rails to rescue
+# exceptions and display an error page (just like when the app is running in production).
+# Typical scenarios where you want to do this is when you test your error pages.
+# There are two ways to allow Rails to rescue exceptions:
+#
+# 1) Tag your scenario (or feature) with @allow-rescue
+#
+# 2) Set the value below to true. Beware that doing this globally is not
+# recommended as it will mask a lot of errors for you!
+#
+ActionController::Base.allow_rescue = false
+
+# Remove/comment out the lines below if your app doesn't have a database.
+# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead.
+begin
+ DatabaseCleaner.strategy = :transaction
+rescue NameError
+ raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
+end
+
+# You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios.
+# See the DatabaseCleaner documentation for details. Example:
+#
+# Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do
+# DatabaseCleaner.strategy = :truncation, {:except => %w[widgets]}
+# end
+#
+# Before('~@no-txn', '~@selenium', '~@culerity', '~@celerity', '~@javascript') do
+# DatabaseCleaner.strategy = :transaction
+# end
+#
+
+# Possible values are :truncation and :transaction
+# The :transaction strategy is faster, but might give you threading problems.
+# See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature
+Cucumber::Rails::Database.javascript_strategy = :truncation
+
+World(FactoryGirl::Syntax::Methods)
+
+Siteconf.captcha = 'off'
diff --git a/features/support/named_routes_fix.rb b/features/support/named_routes_fix.rb
new file mode 100644
index 0000000..add468f
--- /dev/null
+++ b/features/support/named_routes_fix.rb
@@ -0,0 +1,23 @@
+World(ApplicationHelper)
+
+module RoutingFixHelper
+ def member_path(nickname)
+ "/member/#{nickname}"
+ end
+
+ def go_path(key)
+ "/go/#{key}"
+ end
+
+ def t_path(id)
+ "/t/#{id}"
+ end
+
+ def my_following_path
+ '/my/following'
+ end
+
+ def page_path(key)
+ "/page/#{key}"
+ end
+end
diff --git a/features/topics.feature b/features/topics.feature
new file mode 100644
index 0000000..bdf34ba
--- /dev/null
+++ b/features/topics.feature
@@ -0,0 +1,162 @@
+Feature: Topics
+
+ Scenario: visit topic page when logged in
+ Given a topic exists with title: 那继续晒一下韩国电影吧
+ And a comment exists with content: 大叔
+ And I have logged in as devin
+ And I am on the topic page
+ Then page title should contain 那继续晒一下韩国电影吧
+ And I should see 那继续晒一下韩国电影吧
+ And I should see 大叔
+ And it should display a comment form
+ And I should not see 编辑全部
+ And I should see Ctrl + Enter
+
+ @javascript
+ Scenario: visit topic page as anonymous user
+ Given a topic exists with title: 那继续晒一下韩国电影吧
+ And a comment exists with content: 大叔
+ And I am not authenticated
+ When I am on the topic page
+ Then I should see 那继续晒一下韩国电影吧
+ And I should see 大叔
+ And it should not display a comment form
+ And I should not see 加入收藏
+ And I should not see 取消收藏
+ And it should not display any mention buttons
+
+ Scenario: visit topic that without comments
+ Given a topic exists with title: 那继续晒一下韩国电影吧
+ And I am not authenticated
+ When I am on the topic page
+ Then I should see 那继续晒一下韩国电影吧
+ And I should see 目前尚无回复
+ And I should not see 直到
+ And it should not display a comment form
+
+ Scenario: visit a topic created by myself
+ Given I have logged in as devin
+ And a topic of me exists with title: 那继续晒一下韩国电影吧
+ And I am on the topic page
+ Then I should see 编辑全部
+
+ Scenario: edit topic
+ Given a root exists
+ And an user exists with nickname: devin
+ And I have logged in as devin
+ And a topic of devin exists with title: Hi
+ And I am on the topic page
+ Then I should see 编辑全部
+ When I click the link 编辑全部
+ Then it should display a topic edit form
+
+ Scenario: normal user can't edit locked topics
+ Given a root exists
+ And an user exists with nickname: devin
+ And I have logged in as devin
+ And a locked topic of devin exists with title: Hi
+ And I am on the topic page
+ Then it should not display link 编辑全部
+
+ Scenario: admin can edit locked topics
+ Given a locked_topic exists
+ And as an admin, I have logged in as devin
+ When I am on the topic page
+ Then it should display link 编辑全部
+ When I click the link 编辑全部
+ Then it should display a topic edit form
+
+ Scenario: admin can move or delete topics
+ Given a locked_topic exists
+ And as an admin, I have logged in as devin
+ When I am on the topic page
+ Then it should display link 移动
+ And it should display link 删除
+
+ Scenario: admin can close comments for topic
+ Given as an admin, I have logged in as devin
+ And a node exists
+ When I am on the node page
+ Then I should see 禁止回复
+
+ Scenario: topic comments pagination
+ Given a topic exists with title: 那继续晒一下韩国电影吧
+ And the topic has comments of 3 pages (2 per page)
+ And I am on the topic page
+ Then it should display the pagination links
+ And the current page is the last page
+ When I click the second page
+ Then the current page should be the second page
+ When I click the first page
+ Then the current page should be the first page
+
+ Scenario: show topic comment sequence id on multi pages
+ Given a topic exists with title: 那继续晒一下韩国电影吧
+ And the topic has comments of 3 pages (2 per page)
+ And I am on the topic page
+ Then I should see #5
+ And I should see #6
+ When I click the second page
+ Then I should see #3
+ And I should see #4
+ When I click the first page
+ Then I should see #1
+ And I should see #2
+
+ Scenario: show topic comment sequence id on one page
+ Given a topic exists with title: 那继续晒一下韩国电影吧
+ And the topic has comments of 1 pages (2 per page)
+ And I am on the topic page
+ Then I should see #1
+ And I should see #2
+
+ Scenario: show custom rightbar widget
+ Given a node exists with custom html: 认识电影
+ And a topic under the node exists with title: Hi
+ And I am on the topic page
+ Then it should display custom widget: 认识电影
+
+ @javascript
+ Scenario: mention someone using mention button as authenticated user
+ Given I have logged in as devin
+ And a topic exists with title: Hi
+ And a comment exists with content: 大叔
+ And I am on the topic page
+ Then it should display a mention button
+ When I click the mention button
+ Then the commenter user name should appear in the comment box
+
+ Scenario: visit topic with comments that have mentions
+ Given a topic exists with title: Hi
+ And an user exists with nickname: daqing
+ And a comment exists with content: @daqing 大叔
+ And I am on the topic page
+ Then I can see that daqing was mentioned in the comment
+ When I click the link daqing
+ Then it should display personal homepage of daqing
+
+ Scenario: reply topic will create notification
+ Given an user exists with nickname: nana
+ And a topic of nana exists with title: Rails is cool
+ And I have logged in as devin
+ And I am on the topic page
+ When I add comment: 我爱北京天安门
+ Then I should be redirected to the topic page
+ Given I logout
+ And I have logged in as nana
+ And I am on the home page
+ Then page title should contain 1
+ When I click the link 1 条未读提醒
+ Then page title should contain 提醒系统
+ And I should not see 全部标记为已读
+ And it should display 1 notification
+ And I should see Rails is cool
+ When I click the link Rails is cool
+ Then I should see Rails is cool
+ And page title should not contain 1 条未读提醒
+ And I should not see 1 条未读提醒
+
+ Scenario: topic feed
+ Given a topic exists
+ And I have subscribed to the topic feed
+ Then it should display topic feeds
diff --git a/features/users.feature b/features/users.feature
new file mode 100644
index 0000000..71eda65
--- /dev/null
+++ b/features/users.feature
@@ -0,0 +1,87 @@
+Feature: Users
+
+ Scenario: edit settings when signed out
+ Given I am not authenticated
+ And I am on the settings page
+ Then I should be redirected to the login page
+
+ Scenario: edit settings after logged in
+ Given I have logged in as devin
+ And I am on the settings page
+ Then it should display the settings form
+ And page title should contain 设置
+ When I provide account info
+ Then I should see 个人设置成功更新
+
+ Scenario: change password
+ Given I have logged in as devin
+ And I am on the settings page
+ Then it should display the settings form
+ When I provide new password
+ Then I should see 密码已成功更新,下次请用新密码登录
+
+ Scenario: personal homepage
+ Given an user exists with nickname: devin
+ And I am not authenticated
+ And a topic of the user exists with title: Rails is cool
+ And I am on the user's profile page
+ Then page title should contain devin
+ And I should see devin
+ And I should not see 加入特别关注
+ And I should see Rails is cool
+
+ Scenario: visit other's personal homepage as authenticated user
+ Given an user exists with nickname: dhh
+ And an user exists with nickname: nana
+ And an user exists with nickname: zhiming
+ And I have logged in as devin
+ And zhiming has followed dhh
+ And a topic of the user exists with title: Rails is cool
+ And I am on dhh's profile page
+ Then page title should contain dhh
+ And I should see dhh
+ And I should see 加入特别关注
+ And I should see Rails is cool
+ Given nana has followed dhh
+ And I am on dhh's profile page
+ Then I should see 关注dhh的人
+
+ Scenario: visit my personal homepage as authenticated user
+ Given I have logged in as devin
+ And a topic of the user exists with title: Rails is cool
+ And I am on devin's profile page
+ Then page title should contain devin
+ And I should see devin
+ And I should not see 加入特别关注
+ And I should see Rails is cool
+
+ Scenario: follow and unfollow user
+ Given an user exists with nickname: dhh
+ And I have logged in as devin
+ And I am on dhh's profile page
+ Then I should see 加入特别关注
+ When I click the link 加入特别关注
+ Then I should see 取消特别关注
+ When I click the link 取消特别关注
+ Then I should see 加入特别关注
+
+ Scenario: my following page
+ Given I have logged in as devin
+ And devin has followed dhh, linus
+ And a topic of dhh exists with title: Rails is not for beginners
+ And a topic of linus exists with title: C++ is bullshit
+ And I am on my following page
+ Then I should see 我的特别关注
+ And page title should contain 我的特别关注
+ And I should see dhh
+ And I should see linus
+ And I should see Rails is not for beginners
+ And I should see C++ is bullshit
+
+ Scenario: change avatar
+ Given I have logged in as devin
+ And I am on the settings page
+ Then I should see 头像
+ And I should see 当前头像
+ And it should display button 上传新头像
+
diff --git a/lib/active_support/cache/null_store.rb b/lib/active_support/cache/null_store.rb
new file mode 100644
index 0000000..e4fb58d
--- /dev/null
+++ b/lib/active_support/cache/null_store.rb
@@ -0,0 +1,30 @@
+module ActiveSupport
+ module Cache
+ class NullStore < Store
+ def read(key)
+ nil
+ end
+
+ def fetch(key, options={})
+ yield
+ end
+
+ def delete(key)
+ true
+ end
+
+ def clear
+ true
+ end
+
+ def write(key, value)
+ true
+ end
+
+ def increment(key, value=1, options={})
+ true
+ end
+ end
+ end
+end
+
diff --git a/lib/assets/images/icon/location.png b/lib/assets/images/icon/location.png
new file mode 100644
index 0000000..e4f8aa9
Binary files /dev/null and b/lib/assets/images/icon/location.png differ
diff --git a/lib/assets/images/icon/mobileme.png b/lib/assets/images/icon/mobileme.png
new file mode 100644
index 0000000..81e3091
Binary files /dev/null and b/lib/assets/images/icon/mobileme.png differ
diff --git a/lib/assets/images/icon/sina_weibo.png b/lib/assets/images/icon/sina_weibo.png
new file mode 100644
index 0000000..dd9c20b
Binary files /dev/null and b/lib/assets/images/icon/sina_weibo.png differ
diff --git a/lib/assets/images/icon/twitter.png b/lib/assets/images/icon/twitter.png
new file mode 100644
index 0000000..6d85d73
Binary files /dev/null and b/lib/assets/images/icon/twitter.png differ
diff --git a/lib/assets/images/icon/tx_weibo.png b/lib/assets/images/icon/tx_weibo.png
new file mode 100644
index 0000000..56c0866
Binary files /dev/null and b/lib/assets/images/icon/tx_weibo.png differ
diff --git a/lib/rabel/active_cache.rb b/lib/rabel/active_cache.rb
new file mode 100644
index 0000000..6a39905
--- /dev/null
+++ b/lib/rabel/active_cache.rb
@@ -0,0 +1,122 @@
+module Rabel
+ module ActiveCache
+ def self.included(base)
+ base.class_eval do
+ extend ClassMethods
+ end
+ end
+
+ def cached_assoc_collection(assoc, order, limit)
+ ts = send(assoc).select('updated_at').order('updated_at DESC').first.try(:updated_at)
+ return Rabel::Model::EMPTY_DATASET unless ts.present?
+ total = send(assoc).count
+ cache_keys = [
+ self.class.model_name.collection,
+ self.id,
+ assoc,
+ order,
+ limit,
+ ts,
+ total
+ ]
+
+ Rails.cache.fetch(cache_keys.join('/')) do
+ send(assoc).order(order).limit(limit).all
+ end
+ end
+
+ def cached_assoc_pagination(assoc, current_page, per_page, order_column, order_type=Rabel::Model::ORDER_DESC)
+ raise ArgumentError, "Invalid order type: #{order_type}" unless Rabel::Model::ORDER_SPEC.include?(order_type)
+
+ ts = send(assoc).select("updated_at").order("updated_at DESC").first.try(:updated_at)
+ return Kaminari.paginate_array(Rabel::Model::EMPTY_DATASET).page(0) unless ts.present?
+
+ total = send(assoc).count
+ cache_keys = [
+ self.class.model_name.collection,
+ id,
+ assoc,
+ "#{current_page}:#{per_page}",
+ "#{order_column}:#{order_type}",
+ "#{total}-#{ts}"
+ ]
+
+ result = Rails.cache.fetch(cache_keys.join('/')) do
+ send(assoc).order("#{order_column} #{order_type}").page(current_page).per(per_page).all
+ end
+
+ Kaminari.paginate_array(result, :total_count => total).page(current_page).per(per_page)
+ end
+
+ def cached_assoc_object(assoc)
+ target_class = assoc.to_s.camelize.constantize
+ target_class.find_cached(self.send("#{assoc}_id"))
+ end
+
+ module ClassMethods
+ def find_cached(id)
+ ts = select('updated_at').where(:id => id).first.try(:updated_at)
+ raise ActiveRecord::RecordNotFound, "Couldn't find #{model_name} with id=#{id}" unless ts.present?
+
+ Rails.cache.fetch("#{model_name.collection}/#{id}-#{ts}") do
+ find(id)
+ end
+ end
+
+ def find_by_attr_cached(key, value, where_options={})
+ where_options.reverse_merge!(key => value)
+ ts = select('updated_at').where(where_options).first.try(:updated_at)
+ return nil unless ts.present?
+
+ cache_keys = [model_name.collection]
+ cache_keys += where_options.map {|k, v| "#{k}:#{v}"}
+ cache_keys << ts
+
+ Rails.cache.fetch(cache_keys.join('/')) do
+ where(key => value).first
+ end
+ end
+
+ def find_by_attr_cached!(key, value, where_options={})
+ result = find_by_attr_cached(key, value, where_options)
+ raise ActiveRecord::RecordNotFound, "Couldn't find #{model_name} with #{key}=#{value}" unless result.present?
+
+ result
+ end
+
+ def cached_pagination(page, per_page, order_column, order_type=Rabel::Model::ORDER_DESC)
+ raise ArgumentError, "Invalid order type: #{order_type}" unless Rabel::Model::ORDER_SPEC.include?(order_type)
+ ts = select(order_column).order("#{order_column} DESC").first.try(order_column.to_sym)
+ return Kaminari.paginate_array(Rabel::Model::EMPTY_DATASET).page(0) unless ts.present?
+
+ total = self.count
+ cache_keys = [
+ self.model_name.collection,
+ "#{page}:#{per_page}",
+ "#{order_column}:#{order_type}",
+ "#{total}-#{ts}",
+ ]
+
+ result = Rails.cache.fetch(cache_keys.join('/')) do
+ order("#{order_column} #{order_type}").page(page).per(per_page).all
+ end
+
+ Kaminari.paginate_array(result, :total_count => total).page(page).per(per_page)
+ end
+
+ def cached_count
+ Rails.cache.fetch("#{self.model_name.collection}/count", :expires_in => 5.minutes) do
+ self.count
+ end
+ end
+
+ def cached_all(order_str='')
+ ts = select('updated_at').order('updated_at DESC').first.try(:updated_at)
+ return Rabel::Model::EMPTY_DATASET unless ts.present?
+ Rails.cache.fetch("#{self.model_name.collection}/all-#{ts}") do
+ self.order(order_str).all
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rabel/base.rb b/lib/rabel/base.rb
new file mode 100644
index 0000000..815206a
--- /dev/null
+++ b/lib/rabel/base.rb
@@ -0,0 +1,70 @@
+# encoding: utf-8
+
+require 'cgi'
+
+module Rabel
+ module Base
+ def self.make_mention_links(text)
+ text.gsub(Notifiable::MENTION_REGEXP) do
+ if $1.present?
+ %(@#{$1} )
+ else
+ "@#{$1}"
+ end
+ end
+ end
+
+ def self.h(text)
+ CGI.escapeHTML(text)
+ end
+
+ def self.smart_url(link)
+ if link =~ /http:\/\/v.youku.com\/v_show\/id_(.*)\.html/
+ self.embed_video("http://player.youku.com/player.php/sid/#{$1}/v.swf")
+ elsif link =~ /http:\/\/www.tudou.com\/programs\/view\/([a-zA-Z0-9\-_]+)\/?/
+ self.embed_video("http://www.tudou.com/v/#{$1}/v.swf")
+ elsif link =~ /xiami\.com\/widget\/?(.*)\/albumPlayer/
+ embed_music(link, 235, 346)
+ elsif link =~ /xiami\.com\/widget\/?(.*)\/singlePlayer/
+ embed_music(link, 257, 33)
+ elsif link =~ /\.(jpg|jpeg|png|gif)$/i
+ self.show_image(link)
+ else
+ self.external_link(link, link)
+ end
+ end
+
+ def self.protect_at_symbol(text)
+ begin
+ text.gsub("@", "%AT%")
+ rescue
+ text
+ end
+ end
+
+ def self.decode_symbols(text)
+ text.gsub("%AT%", "@").gsub("%N%", " ")
+ end
+
+ def self.email_link(address)
+ %(#{address} )
+ end
+
+ def self.external_link(content, url)
+ %(#{content} )
+ end
+
+ def self.show_image(url)
+ %( )
+ end
+
+ def self.embed_video(url)
+ %( )
+ end
+
+ def self.embed_music(url, width, height)
+ %( )
+ end
+ end
+end
+
diff --git a/lib/rabel/captcha.rb b/lib/rabel/captcha.rb
new file mode 100644
index 0000000..5993aef
--- /dev/null
+++ b/lib/rabel/captcha.rb
@@ -0,0 +1,34 @@
+module Rabel
+ class Captcha
+ def self.random_code(length=4)
+ chars = ('b'..'z').to_a - ['l', 'o'] + ('2'..'9').to_a
+ rand_chars = []
+ length.times do
+ rand_chars << chars[rand(chars.length)]
+ end
+ rand_chars.join
+ end
+
+ def self.image(code)
+ canvas = Magick::Image.new(40 * code.length, 40)
+
+ draw = Magick::Draw.new
+ draw.font_family = 'times'
+ draw.pointsize = 20
+ start_pos = 10
+ code.each_char do |char|
+ draw.annotate(canvas, 0, 0, start_pos, 22 + rand(10), char) {
+ rotation = rand(10) > 5 ? 0.7 : -1.1
+ self.font_weight = rand(10) > 5 ? Magick::NormalWeight : Magick::BoldWeight
+ self.fill = '#731f43'
+ self.affine = Magick::AffineMatrix.new(1.7, 0.8, rotation, 1.7, rand(8), rand(5))
+ self.text_antialias(true)
+ }
+ start_pos += 40
+ end
+ result_image = canvas.to_blob { self.format = 'gif' }
+ canvas.destroy!
+ result_image
+ end
+ end
+end
diff --git a/lib/rabel/link_email_parser.rb b/lib/rabel/link_email_parser.rb
new file mode 100644
index 0000000..754df66
--- /dev/null
+++ b/lib/rabel/link_email_parser.rb
@@ -0,0 +1,66 @@
+module Rabel
+ class LinkEmailParser
+ AUTO_LINK_RE = %r{
+ (?: ((?:ed2k|ftp|http|https|irc|mailto|news|gopher|nntp|telnet|webcal|xmpp|callto|feed|svn|urn|aim|rsync|tag|ssh|sftp|rtsp|afs):)// | www\. )
+ [^(\s|@|[\u4e00-\u9fa5]|[\uff00-\uffef]|[\u2e80-\u2eff]|[\u3000-\u303f]|[\u31c0-\u31ef])<]+
+ }x
+
+ # regexps for determining context, used high-volume
+ AUTO_LINK_CRE = [/<[^>]+$/, /^[^>]*>/, //i, /<\/a>/i]
+
+ AUTO_EMAIL_RE = /[\w.!#\$%+-]+@[\w-]+(?:\.[\w-]+)+/
+
+ BRACKETS = { ']' => '[', ')' => '(', '}' => '{' }
+
+ WORD_PATTERN = RUBY_VERSION < '1.9' ? '\w' : '\p{Word}'
+
+ # Turns all urls into clickable links. If a block is given, each url
+ # is yielded and the result is used as the link text.
+ def self.parse_url(text)
+ text.gsub(AUTO_LINK_RE) do
+ scheme, href = $1, $&
+ punctuation = []
+
+ if auto_linked?($`, $')
+ # do not change string; URL is already linked
+ href
+ else
+ # don't include trailing punctuation character as part of the URL
+ while href.sub!(/[^#{WORD_PATTERN}\/-]$/, '')
+ punctuation.push $&
+ if opening = BRACKETS[punctuation.last] and href.scan(opening).size > href.scan(punctuation.last).size
+ href << punctuation.pop
+ break
+ end
+ end
+
+ href = 'http://' + href unless scheme
+ result = block_given? ? yield(href) : href
+ if result == href
+ %( #{href} )
+ else
+ result
+ end
+ end
+ end
+ end
+
+
+ # Turns all email addresses into clickable links. If a block is given,
+ # each email is yielded and the result is used as the link text.
+ def self.parse_email(text)
+ text.gsub(AUTO_EMAIL_RE) do
+ text = $&
+ new_text = (block_given?) ? yield(text) : text
+ Rabel::Base.email_link(new_text)
+ end
+ end
+
+ # Detects already linked context or position in the middle of a tag
+ def self.auto_linked?(left, right)
+ (left =~ AUTO_LINK_CRE[0] and right =~ AUTO_LINK_CRE[1]) or
+ (left.rindex(AUTO_LINK_CRE[2]) and $' !~ AUTO_LINK_CRE[3])
+ end
+ end
+end
+
diff --git a/lib/rabel/model.rb b/lib/rabel/model.rb
new file mode 100644
index 0000000..6135efa
--- /dev/null
+++ b/lib/rabel/model.rb
@@ -0,0 +1,8 @@
+module Rabel
+ module Model
+ EMPTY_DATASET = []
+ ORDER_ASC = 'ASC'
+ ORDER_DESC = 'DESC'
+ ORDER_SPEC = [ORDER_ASC, ORDER_DESC]
+ end
+end
diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake
new file mode 100644
index 0000000..83f7947
--- /dev/null
+++ b/lib/tasks/cucumber.rake
@@ -0,0 +1,65 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+
+
+unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks
+
+vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
+$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil?
+
+begin
+ require 'cucumber/rake/task'
+
+ namespace :cucumber do
+ Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
+ t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'default'
+ end
+
+ Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
+ t.binary = vendored_cucumber_bin
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'wip'
+ end
+
+ Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t|
+ t.binary = vendored_cucumber_bin
+ t.fork = true # You may get faster startup if you set this to false
+ t.profile = 'rerun'
+ end
+
+ desc 'Run all features'
+ task :all => [:ok, :wip]
+
+ task :statsetup do
+ require 'rails/code_statistics'
+ ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features')
+ ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features')
+ end
+ end
+ desc 'Alias for cucumber:ok'
+ task :cucumber => 'cucumber:ok'
+
+ task :default => :cucumber
+
+ task :features => :cucumber do
+ STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
+ end
+
+ # In case we don't have ActiveRecord, append a no-op task that we can depend upon.
+ task 'db:test:prepare' do
+ end
+
+ task :stats => 'cucumber:statsetup'
+rescue LoadError
+ desc 'cucumber rake task not available (cucumber not installed)'
+ task :cucumber do
+ abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
+ end
+end
+
+end
diff --git a/lib/templates/erb/scaffold/_form.html.erb b/lib/templates/erb/scaffold/_form.html.erb
deleted file mode 100644
index 201a069..0000000
--- a/lib/templates/erb/scaffold/_form.html.erb
+++ /dev/null
@@ -1,13 +0,0 @@
-<%%= simple_form_for(@<%= singular_table_name %>) do |f| %>
- <%%= f.error_notification %>
-
-
- <%- attributes.each do |attribute| -%>
- <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %>
- <%- end -%>
-
-
-
- <%%= f.button :submit %>
-
-<%% end %>
diff --git a/lib/templates/slim/scaffold/_form.html.slim b/lib/templates/slim/scaffold/_form.html.slim
new file mode 100644
index 0000000..a2ff775
--- /dev/null
+++ b/lib/templates/slim/scaffold/_form.html.slim
@@ -0,0 +1,10 @@
+= simple_form_for(@<%= singular_table_name %>) do |f|
+ = f.error_notification
+
+ .form-inputs
+<%- attributes.each do |attribute| -%>
+ = f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %>
+<%- end -%>
+
+ .form-actions
+ = f.button :submit
diff --git a/vendor/assets/javascripts/.gitkeep b/log/.gitkeep
similarity index 100%
rename from vendor/assets/javascripts/.gitkeep
rename to log/.gitkeep
diff --git a/public/500.html b/public/500.html
index f3648a0..b80307f 100644
--- a/public/500.html
+++ b/public/500.html
@@ -20,6 +20,7 @@
We're sorry, but something went wrong.
+
We've been notified about this issue and we'll take a look at it shortly.