├── .gitignore ├── .idea ├── .generators ├── .name ├── .rakeTasks ├── dataSources.ids ├── dataSources.xml ├── encodings.xml ├── misc.xml ├── modules.xml ├── scopes │ └── scope_settings.xml └── vcs.xml ├── Capfile ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app ├── assets │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── application.js │ │ ├── html5shiv.min.js │ │ └── respond.min.js │ └── stylesheets │ │ ├── application.css │ │ └── register.css ├── controllers │ ├── application_controller.rb │ ├── articles_controller.rb │ ├── blogs_controller.rb │ ├── comments_controller.rb │ ├── concerns │ │ └── .keep │ └── users_controller.rb ├── helpers │ └── application_helper.rb ├── mailers │ ├── .keep │ └── user_mailer.rb ├── models │ ├── .keep │ ├── article.rb │ ├── article_comment.rb │ ├── article_star.rb │ ├── article_view.rb │ ├── blog_info.rb │ ├── category.rb │ ├── concerns │ │ └── .keep │ ├── user.rb │ └── user_active.rb └── views │ ├── articles │ ├── _article.html.haml │ ├── _article_sidebar.html.haml │ ├── _articles.html.haml │ ├── _comment.html.haml │ ├── _comment_form.html.haml │ ├── _comments.html.haml │ ├── _error.html.haml │ ├── _form.html.haml │ ├── _toolbar_upload_img.html.haml │ ├── edit.html.haml │ ├── index.html.haml │ ├── index.js.haml │ ├── new.html.haml │ ├── show.html.haml │ ├── show.js.haml │ └── star.js.haml │ ├── blogs │ ├── _set_sidebar.html.haml │ ├── about.html.haml │ ├── change_password.html.haml │ ├── index.html.haml │ ├── set.html.haml │ ├── set_blog.html.haml │ └── upload_img.js.haml │ ├── comments │ ├── create.js.haml │ ├── destroy.js.haml │ ├── edit.js.haml │ └── update.js.haml │ ├── kaminari │ ├── _first_page.html.haml │ ├── _gap.html.haml │ ├── _last_page.html.haml │ ├── _next_page.html.haml │ ├── _page.html.haml │ ├── _paginator.html.haml │ └── _prev_page.html.haml │ ├── layouts │ ├── _footer.html.haml │ ├── application.html.haml │ └── register.html.haml │ ├── user_mailer │ ├── auth_mail.html.haml │ └── forget_password.html.haml │ └── users │ ├── _form.html.haml │ ├── change_pw.html.haml │ ├── forget_password.html.haml │ ├── forget_password_confirm.html.haml │ ├── login.html.haml │ ├── register.html.haml │ └── send_active_mail.html.haml ├── bin ├── bundle ├── rails └── rake ├── config.ru ├── config ├── application.rb ├── blog.yml.example ├── boot.rb ├── database.yml.example ├── deploy.rb ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── 1_settings.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── session_store.rb │ ├── social_share_button.rb │ └── wrap_parameters.rb ├── locales │ ├── article.zh-CN.yml │ ├── article_comment.zh-CN.yml │ ├── blog_info.zh-CN.yml │ ├── en.yml │ ├── pagination.zh-CN.yml │ ├── social_share_button.zh-CN.yml │ ├── user.zh-CN.yml │ └── zh-CN.yml ├── routes.rb ├── secrets.yml.example ├── unicorn.rb.example └── unicorn_capistrano.rb ├── db ├── migrate │ ├── 20140825134700_create_users.rb │ ├── 20140826093600_create_articles.rb │ ├── 20140826093700_create_categories.rb │ ├── 20140826125700_add_source_column_to_articles.rb │ ├── 20140826132200_add_category_id_column_to_articles.rb │ ├── 20140827095400_create_article_stars.rb │ ├── 20140827125400_create_article_views.rb │ ├── 20140827134600_create_article_comments.rb │ ├── 20140831174700_add_nickname_and_avatar_column_to_users.rb │ ├── 20140831194600_create_blog_info.rb │ ├── 20140908084800_add_activation_column_to_users.rb │ └── 20140908085400_create_user_actives.rb ├── schema.rb └── seeds.rb ├── deployment ├── init.d │ └── blog └── nginx │ └── nginx.conf ├── doc ├── articles_list.png ├── index.png ├── new_article.png ├── set1.png ├── set2.png └── show_article.png ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── gavatar.jpg ├── images │ └── .gitkeep └── robots.txt ├── test ├── controllers │ └── .keep ├── fixtures │ └── .keep ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ └── .keep └── test_helper.rb └── vendor └── assets ├── javascripts └── .keep └── stylesheets └── .keep /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/*.log 16 | /tmp 17 | /config/*.yml 18 | /config/unicorn.rb 19 | /public/assets/* 20 | /.idea/* 21 | /public/images/* 22 | -------------------------------------------------------------------------------- /.idea/.generators: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | blog -------------------------------------------------------------------------------- /.idea/.rakeTasks: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /.idea/dataSources.ids: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mysql 6 | com.mysql.jdbc.Driver 7 | jdbc:mysql://127.0.0.1/blog_development 8 | root 9 | df9bdf98df99df9edf9fdf9c 10 | 11 | 12 | 13 | mysql 14 | com.mysql.jdbc.Driver 15 | jdbc:mysql://127.0.0.1/blog_test 16 | root 17 | df9bdf98df99df9edf9fdf9c 18 | 19 | 20 | 21 | mysql 22 | com.mysql.jdbc.Driver 23 | jdbc:mysql://127.0.0.1/blog_production 24 | root 25 | df9bdf98df99df9edf9fdf9c 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | # Load DSL and set up stages 2 | require "capistrano/setup" 3 | 4 | # Include default deployment tasks 5 | require "capistrano/deploy" 6 | 7 | # Include tasks from other gems included in your Gemfile 8 | # 9 | # For documentation on these, see for example: 10 | # 11 | # https://github.com/capistrano/rvm 12 | # https://github.com/capistrano/rbenv 13 | # https://github.com/capistrano/chruby 14 | # https://github.com/capistrano/bundler 15 | # https://github.com/capistrano/rails 16 | # https://github.com/capistrano/passenger 17 | # 18 | require 'capistrano/rvm' 19 | # require 'capistrano/rbenv' 20 | # require 'capistrano/chruby' 21 | require 'capistrano/bundler' 22 | require 'capistrano/rails/assets' 23 | require 'capistrano/rails/migrations' 24 | require 'capistrano3/unicorn' 25 | 26 | # Load custom tasks from `lib/capistrano/tasks` if you have any defined 27 | Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r } 28 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://ruby.taobao.org' 2 | ruby '2.1.2' 3 | 4 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 5 | gem 'rails', '4.1.5' 6 | # Use mysql as the database for Active Record 7 | gem 'mysql2' 8 | # Use SCSS for stylesheets 9 | gem 'sass-rails', '~> 4.0.3' 10 | # Use Uglifier as compressor for JavaScript assets 11 | gem 'uglifier', '>= 1.3.0' 12 | # Use CoffeeScript for .js.coffee assets and views 13 | gem 'coffee-rails', '~> 4.0.0' 14 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes 15 | gem 'therubyracer', platforms: :ruby 16 | #Use kaminari page 17 | gem 'kaminari' 18 | #Use settingslogic 19 | gem 'settingslogic' 20 | # Use jquery as the JavaScript library 21 | gem 'jquery-rails' 22 | # Use haml template 23 | gem 'haml-rails' 24 | # Use bootstrap css 25 | gem 'bootstrap-sass' 26 | gem 'font-awesome-sass-rails', '~> 3.0.2.2' 27 | gem 'twitter-bootstrap-rails-confirm', github: 'fxhover/twitter-bootstrap-rails-confirm', branch: 'bootstrap3' 28 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 29 | gem 'turbolinks' 30 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 31 | gem 'jbuilder', '~> 2.0' 32 | # bundle exec rake doc:rails generates the API under doc/api. 33 | gem 'sdoc', '~> 0.4.0', group: :doc 34 | 35 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 36 | gem 'spring', group: :development 37 | 38 | # Use ActiveModel has_secure_password 39 | gem 'bcrypt', '~> 3.1.7' 40 | 41 | # Use unicorn as the app server 42 | gem 'unicorn' 43 | 44 | group :development do 45 | gem 'thin' 46 | end 47 | 48 | #Avatar 49 | gem 'gravatar_image_tag' 50 | 51 | #markdown 52 | gem 'markdown-toolbar', git: 'git@github.com:fxhover/markdown-toolbar.git' 53 | gem 'redcarpet' 54 | 55 | #file upload 56 | gem 'simple_fileupload' 57 | gem 'remotipart' 58 | 59 | #share article 60 | gem 'social-share-button' 61 | 62 | # Use Capistrano for deployment 63 | group :development do 64 | gem 'capistrano', require: false 65 | gem 'capistrano-rvm', require: false 66 | gem 'capistrano-rails', require: false 67 | gem 'capistrano-bundler', require: false 68 | gem 'capistrano3-unicorn', require: false 69 | end 70 | 71 | # Use debugger 72 | # gem 'debugger', group: [:development, :test] 73 | 74 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/fxhover/twitter-bootstrap-rails-confirm.git 3 | revision: 92dd750dd04f492526aca894c5ec1b321a6e25a9 4 | branch: bootstrap3 5 | specs: 6 | twitter-bootstrap-rails-confirm (1.0.2) 7 | 8 | GIT 9 | remote: git@github.com:fxhover/markdown-toolbar.git 10 | revision: 5c755649d647fabc953347c2e03a2203cf7d52ff 11 | specs: 12 | markdown-toolbar (0.2.4) 13 | 14 | GEM 15 | remote: https://ruby.taobao.org/ 16 | specs: 17 | actionmailer (4.1.5) 18 | actionpack (= 4.1.5) 19 | actionview (= 4.1.5) 20 | mail (~> 2.5.4) 21 | actionpack (4.1.5) 22 | actionview (= 4.1.5) 23 | activesupport (= 4.1.5) 24 | rack (~> 1.5.2) 25 | rack-test (~> 0.6.2) 26 | actionview (4.1.5) 27 | activesupport (= 4.1.5) 28 | builder (~> 3.1) 29 | erubis (~> 2.7.0) 30 | activemodel (4.1.5) 31 | activesupport (= 4.1.5) 32 | builder (~> 3.1) 33 | activerecord (4.1.5) 34 | activemodel (= 4.1.5) 35 | activesupport (= 4.1.5) 36 | arel (~> 5.0.0) 37 | activesupport (4.1.5) 38 | i18n (~> 0.6, >= 0.6.9) 39 | json (~> 1.7, >= 1.7.7) 40 | minitest (~> 5.1) 41 | thread_safe (~> 0.1) 42 | tzinfo (~> 1.1) 43 | airbrussh (1.1.0) 44 | sshkit (>= 1.6.1, != 1.7.0) 45 | arel (5.0.1.20140414130214) 46 | bcrypt (3.1.7) 47 | bootstrap-sass (3.2.0.1) 48 | sass (~> 3.2) 49 | builder (3.2.2) 50 | capistrano (3.6.1) 51 | airbrussh (>= 1.0.0) 52 | capistrano-harrow 53 | i18n 54 | rake (>= 10.0.0) 55 | sshkit (>= 1.9.0) 56 | capistrano-bundler (1.1.4) 57 | capistrano (~> 3.1) 58 | sshkit (~> 1.2) 59 | capistrano-harrow (0.5.3) 60 | capistrano-rails (1.1.7) 61 | capistrano (~> 3.1) 62 | capistrano-bundler (~> 1.1) 63 | capistrano-rvm (0.1.2) 64 | capistrano (~> 3.0) 65 | sshkit (~> 1.2) 66 | capistrano3-unicorn (0.2.1) 67 | capistrano (~> 3.1, >= 3.1.0) 68 | coffee-rails (4.0.1) 69 | coffee-script (>= 2.2.0) 70 | railties (>= 4.0.0, < 5.0) 71 | coffee-script (2.3.0) 72 | coffee-script-source 73 | execjs 74 | coffee-script-source (1.7.1) 75 | daemons (1.1.9) 76 | erubis (2.7.0) 77 | eventmachine (1.0.3) 78 | execjs (2.2.1) 79 | font-awesome-sass-rails (3.0.2.2) 80 | railties (>= 3.1.1) 81 | sass-rails (>= 3.1.1) 82 | gravatar_image_tag (1.2.0) 83 | haml (4.0.5) 84 | tilt 85 | haml-rails (0.5.3) 86 | actionpack (>= 4.0.1) 87 | activesupport (>= 4.0.1) 88 | haml (>= 3.1, < 5.0) 89 | railties (>= 4.0.1) 90 | hike (1.2.3) 91 | i18n (0.6.11) 92 | jbuilder (2.1.3) 93 | activesupport (>= 3.0.0, < 5) 94 | multi_json (~> 1.2) 95 | jquery-rails (3.1.1) 96 | railties (>= 3.0, < 5.0) 97 | thor (>= 0.14, < 2.0) 98 | json (1.8.1) 99 | kaminari (0.16.1) 100 | actionpack (>= 3.0.0) 101 | activesupport (>= 3.0.0) 102 | kgio (2.9.2) 103 | libv8 (3.16.14.3) 104 | mail (2.5.4) 105 | mime-types (~> 1.16) 106 | treetop (~> 1.4.8) 107 | mime-types (1.25.1) 108 | minitest (5.4.0) 109 | multi_json (1.10.1) 110 | mysql2 (0.3.16) 111 | net-scp (1.2.1) 112 | net-ssh (>= 2.6.5) 113 | net-ssh (3.2.0) 114 | polyglot (0.3.5) 115 | rack (1.5.2) 116 | rack-test (0.6.2) 117 | rack (>= 1.0) 118 | rails (4.1.5) 119 | actionmailer (= 4.1.5) 120 | actionpack (= 4.1.5) 121 | actionview (= 4.1.5) 122 | activemodel (= 4.1.5) 123 | activerecord (= 4.1.5) 124 | activesupport (= 4.1.5) 125 | bundler (>= 1.3.0, < 2.0) 126 | railties (= 4.1.5) 127 | sprockets-rails (~> 2.0) 128 | railties (4.1.5) 129 | actionpack (= 4.1.5) 130 | activesupport (= 4.1.5) 131 | rake (>= 0.8.7) 132 | thor (>= 0.18.1, < 2.0) 133 | raindrops (0.13.0) 134 | rake (10.3.2) 135 | rdoc (4.1.1) 136 | json (~> 1.4) 137 | redcarpet (3.1.2) 138 | ref (1.0.5) 139 | remotipart (1.2.1) 140 | sass (3.2.19) 141 | sass-rails (4.0.3) 142 | railties (>= 4.0.0, < 5.0) 143 | sass (~> 3.2.0) 144 | sprockets (~> 2.8, <= 2.11.0) 145 | sprockets-rails (~> 2.0) 146 | sdoc (0.4.1) 147 | json (~> 1.7, >= 1.7.7) 148 | rdoc (~> 4.0) 149 | settingslogic (2.0.9) 150 | simple_fileupload (0.0.1) 151 | social-share-button (0.1.8) 152 | coffee-rails 153 | sass-rails 154 | spring (1.1.3) 155 | sprockets (2.11.0) 156 | hike (~> 1.2) 157 | multi_json (~> 1.0) 158 | rack (~> 1.0) 159 | tilt (~> 1.1, != 1.3.0) 160 | sprockets-rails (2.1.3) 161 | actionpack (>= 3.0) 162 | activesupport (>= 3.0) 163 | sprockets (~> 2.8) 164 | sshkit (1.11.2) 165 | net-scp (>= 1.1.2) 166 | net-ssh (>= 2.8.0) 167 | therubyracer (0.12.1) 168 | libv8 (~> 3.16.14.0) 169 | ref 170 | thin (1.6.2) 171 | daemons (>= 1.0.9) 172 | eventmachine (>= 1.0.0) 173 | rack (>= 1.0.0) 174 | thor (0.19.1) 175 | thread_safe (0.3.4) 176 | tilt (1.4.1) 177 | treetop (1.4.15) 178 | polyglot 179 | polyglot (>= 0.3.1) 180 | turbolinks (2.3.0) 181 | coffee-rails 182 | tzinfo (1.2.2) 183 | thread_safe (~> 0.1) 184 | uglifier (2.5.3) 185 | execjs (>= 0.3.0) 186 | json (>= 1.8.0) 187 | unicorn (4.8.3) 188 | kgio (~> 2.6) 189 | rack 190 | raindrops (~> 0.7) 191 | 192 | PLATFORMS 193 | ruby 194 | 195 | DEPENDENCIES 196 | bcrypt (~> 3.1.7) 197 | bootstrap-sass 198 | capistrano 199 | capistrano-bundler 200 | capistrano-rails 201 | capistrano-rvm 202 | capistrano3-unicorn 203 | coffee-rails (~> 4.0.0) 204 | font-awesome-sass-rails (~> 3.0.2.2) 205 | gravatar_image_tag 206 | haml-rails 207 | jbuilder (~> 2.0) 208 | jquery-rails 209 | kaminari 210 | markdown-toolbar! 211 | mysql2 212 | rails (= 4.1.5) 213 | redcarpet 214 | remotipart 215 | sass-rails (~> 4.0.3) 216 | sdoc (~> 0.4.0) 217 | settingslogic 218 | simple_fileupload 219 | social-share-button 220 | spring 221 | therubyracer 222 | thin 223 | turbolinks 224 | twitter-bootstrap-rails-confirm! 225 | uglifier (>= 1.3.0) 226 | unicorn 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Blog演示页面 2 | 3 | ##首页 4 | 5 | ![首页](https://github.com/fxhover/blog/raw/master/doc/index.png) 6 | 7 | ##博文列表 8 | 9 | ![文章列表](https://github.com/fxhover/blog/raw/master/doc/articles_list.png) 10 | 11 | ##写博客页面 12 | 13 | ![写博客](https://github.com/fxhover/blog/raw/master/doc/new_article.png) 14 | 15 | ##文章详情页面 16 | 17 | ![文章详情](https://github.com/fxhover/blog/raw/master/doc/show_article.png) 18 | 19 | ##设置页面 20 | 21 | ![设置头像](https://github.com/fxhover/blog/raw/master/doc/set1.png) 22 | 23 | ![设置博客](https://github.com/fxhover/blog/raw/master/doc/set2.png) 24 | 25 | #Blog部署文档# 26 | 27 | ##环境 28 | git、ruby 2.1.2、rails 4.1.5、nginx 1.2+、mysql 5.0+ 29 | 30 | ##下载代码## 31 | 32 | git clone git@github.com:fxhover/blog.git 33 | 34 | ##安装ruby依赖类库## 35 | 36 | bundle exec bundle install 37 | 38 | ##配置文件## 39 | 40 | cd blog 41 | 42 | cp config/database.yml.example config/database.yml 修改数据库配置信息 43 | 44 | cp config/secrets.yml.example config/secrets.yml 45 | 46 | cp config/blog.yml.example config/blog.yml 修改好博客配置信息 47 | 48 | cp config/unicorn.rb.example config/unicorn.rb 修改unicorn配置文件,配置应用目录等信息 49 | 50 | ##执行数据脚本## 51 | 52 | RAILS_ENV=production bundle exec rake db:create 创建数据库 53 | 54 | RAILS_ENV=production bundle exec rake db:migrate 创建表及索引 55 | 56 | RAILS_ENV=production bundle exec rake db:seed 创建默认分类和管理员账号,编辑 db/seeds.rb可以修改默认分类和管理员账号密码,然后再执行 57 | 58 | ##预编译前端资源## 59 | 60 | RAILS_ENV=production bundle exec rake assets:clean 61 | 62 | RAILS_ENV=production bundle exec rake assets:precompile 63 | 64 | ##启动## 65 | 66 | ###创建服务用到的文件夹### 67 | mkdir -p tmp/pids 68 | 69 | mkdir -p log 70 | 71 | mkdir -p tmp/sockets 72 | 73 | ###拷贝启动服务文件### 74 | 75 | sudo cp deployment/init.d/blog /etc/init.d 编辑文件中的app目录和sudo user的配置 76 | 77 | ###启动blog服务### 78 | 79 | sudo service blog start 80 | 81 | sudo service blog stop/restart 停止或者重启服务 82 | 83 | ###nginx配置### 84 | 85 | 以下列出部分配置,完整配置见 deployment/nginx/nginx.conf文件 86 | 87 | upstream blog { 88 | server unix:/home/fangxiang/blog/tmp/sockets/blog.socket; 89 | } 90 | server { 91 | listen 80; 92 | server_name blog.com; 93 | root /home/fangxiang/blog/public; 94 | try_files $uri/index.html $uri.html $uri @user1; 95 | location @user1 { 96 | proxy_redirect off; 97 | proxy_set_header Host $host; 98 | proxy_set_header X-Forwarded-Host $host; 99 | proxy_set_header X-Forwarded-Server $host; 100 | proxy_set_header X-Real-Ip $remote_addr; 101 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 102 | proxy_buffering on; 103 | proxy_pass http://blog; 104 | } 105 | location ~ ^(/assets) { 106 | access_log off; 107 | expires max; 108 | } 109 | 110 | } 111 | 112 | ###重启nginx### 113 | 114 | sudo service nginx restart 115 | ###绑定hosts 116 | 117 | 127.0.0.1 blog.com 118 | 119 | ###访问### 120 | 121 | http://blog.com 122 | 123 | ##使用Capistrano部署 124 | 125 | ``` 126 | bundle install 127 | 128 | bundle exec cap production deploy:check 129 | 130 | cp ./config/*.yml /var/www/blog/shared/config/ 131 | 132 | cp ./config/unicorn_capistrano.rb /var/www/blog/shared/config/unicorn.rb 133 | 134 | bundle exec cap production deploy 135 | ``` 136 | 137 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require bootstrap 17 | //= require twitter/bootstrap/rails/confirm 18 | //= require html5shiv.min.js 19 | //= require respond.min.js 20 | //= require markdown-toolbar 21 | //= require jquery.remotipart 22 | //= require social-share-button 23 | //= require_tree . 24 | 25 | $.fn.twitter_bootstrap_confirmbox.defaults = { 26 | title: '提示', 27 | cancel: '取消', 28 | cancel_class: 'btn cancel', 29 | proceed: '确定', 30 | proceed_class: 'btn proceed btn-primary' 31 | } 32 | 33 | //markdown preview 34 | function preview(text_area_id, content_id){ 35 | content = $('#' + text_area_id).val(); 36 | $.post('/blogs/preview', {content: content}, function(data){ 37 | if (data.status){ 38 | $('#' + content_id).html(data.message); 39 | } 40 | }); 41 | } -------------------------------------------------------------------------------- /app/assets/javascripts/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.2",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b)}(this,document); -------------------------------------------------------------------------------- /app/assets/javascripts/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl 2 | * Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT 3 | * */ 4 | 5 | !function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b img{ 12 | display:none; 13 | } 14 | 15 | /*overwrite bt style*/ 16 | .form-control:focus { 17 | border-color: #a6a6a6; 18 | outline: none; 19 | -webkit-box-shadow: none; 20 | box-shadow: none; 21 | } 22 | 23 | .content { 24 | } 25 | 26 | .content > h1{ 27 | height: 1px; 28 | text-indent: -9999px; 29 | } 30 | 31 | /* ---------------------- */ 32 | .brand { 33 | display: block; 34 | margin: auto auto; 35 | width: 250px; 36 | height: 30px; 37 | background-size: 185px 36px; 38 | background-position: center; 39 | background-repeat: no-repeat; 40 | } 41 | 42 | .brand:hover { 43 | text-decoration: none; 44 | } 45 | 46 | .brand-third { 47 | display: block; 48 | margin: 0px auto; 49 | background-position: center; 50 | background-repeat: no-repeat; 51 | } 52 | 53 | .brand-xdf h1,.brand-github h1, .brand-evernote h1 { 54 | text-indent: -9999px; 55 | margin-top: 0px; 56 | } 57 | 58 | .brand h1 { 59 | margin-top: 0px; 60 | text-align: center; 61 | } 62 | 63 | 64 | .binding > div { 65 | float: left; 66 | height: 40px; 67 | background-repeat: no-repeat; 68 | } 69 | 70 | .binding .cooperator.github{ 71 | width: 92px; 72 | background-image: url(https://dn-st.qbox.me/pages/images/github@2x.jpg); 73 | background-position: left center; 74 | background-size: 92px 36px; 75 | } 76 | 77 | .binding .cooperator.mingdao{ 78 | width: 60px; 79 | background-image: url(https://dn-st.qbox.me/pages/images/mingdao@2x.jpg); 80 | background-position: -4px bottom; 81 | background-size: 60px 36px; 82 | } 83 | 84 | .binding .arrow{ 85 | width: 27px; 86 | height: 38px; 87 | margin-left: 16px; 88 | background-image: url(https://dn-st.qbox.me/pages/images/arrow@2x.png); 89 | background-position: left center; 90 | background-size: 27px 25px; 91 | } 92 | 93 | .binding .teambition { 94 | width: 185px; 95 | margin-left: 20px; 96 | background-position: left top; 97 | background-size: 185px 36px; 98 | } 99 | 100 | .slide-wrapper { 101 | width: 300px; 102 | overflow: hidden; 103 | } 104 | 105 | .slide-wrapper .slider{ 106 | position: relative; 107 | width: 620px; 108 | min-height: 500px; 109 | /*white-space: nowrap;*/ 110 | -webkit-transition: -webkit-transform 0.3s ease-in-out; 111 | -moz-transition: -moz-transform 0.3s ease-in-out; 112 | -o-transition: -o-transform 0.3s ease-in-out; 113 | transition: transform 0.3s ease-in-out; 114 | } 115 | 116 | .slide-wrapper .slider.active{ 117 | -webkit-transform: translate3d(-320px,0,0); 118 | -moz-transform: translate3d(-320px,0,0); 119 | -o-transform: translate3d(-320px,0,0); 120 | transform: translate3d(-320px,0,0); 121 | } 122 | 123 | .slide-wrapper .slider .form-unit.create{ 124 | position: absolute; 125 | left: 0; 126 | opacity: 100; 127 | } 128 | 129 | .slide-wrapper .slider.active .form-unit.create{ 130 | opacity: 0; 131 | } 132 | 133 | .slide-wrapper .slider .form-unit.bind{ 134 | opacity: 0; 135 | position: absolute; 136 | left: 320px; 137 | } 138 | 139 | .slide-wrapper .slider.active .form-unit.bind{ 140 | opacity: 100; 141 | } 142 | 143 | .slide-wrapper .form-unit{ 144 | display: inline-block; 145 | vertical-align: top; 146 | margin-right: 20px; 147 | -webkit-transition: opacity 0.6s ease-in-out; 148 | -moz-transition: opacity 0.6s ease-in-out; 149 | -o-transition: opacity 0.6s ease-in-out; 150 | transition: opacity 0.6s ease-in-out; 151 | } 152 | 153 | .form-unit { 154 | width: 480px; 155 | margin: 30px auto; 156 | padding: 50px; 157 | box-shadow: 0 0 10px 4px rgba(0, 0, 0, .04); 158 | background-color: white; 159 | } 160 | 161 | .form-unit > h2 { 162 | text-align: center; 163 | font-family: 'Open Sans', arial; 164 | -webkit-font-smoothing: antialiased; color: #555; 165 | font-size: 24px; 166 | margin-top: 0px; 167 | margin-bottom: 30px; 168 | } 169 | 170 | .form-unit > h3 { 171 | text-align: center; 172 | font-family: 'Open Sans', arial; 173 | -webkit-font-smoothing: antialiased; 174 | color: #555; 175 | font-size: 18px; 176 | font-weight: 400; 177 | margin: 25px 0; 178 | } 179 | 180 | 181 | .form-unit.info{ 182 | width: 500px; 183 | margin-top: 100px; 184 | text-align: left; 185 | } 186 | .form-unit.info .success-icon{ 187 | /*display: inline-block;*/ 188 | float: left; 189 | width: 100px; 190 | height: 100px; 191 | background-image: url('https://dn-st.qbox.me/pages/images/success-info-icon.png'); 192 | background-repeat: no-repeat; 193 | background-position: left 23px; 194 | background-size: 70px 71px; 195 | } 196 | .form-unit.info .error-icon{ 197 | /*display: inline-block;*/ 198 | float: left; 199 | width: 100px; 200 | height: 100px; 201 | background-image: url('https://dn-st.qbox.me/pages/images/error-info-icon.png'); 202 | background-repeat: no-repeat; 203 | background-position: left 23px; 204 | background-size: 70px 71px; 205 | } 206 | 207 | .form-unit.info .info-content{ 208 | float: left; 209 | width: 400px; 210 | } 211 | .form-unit .info-content h1{ 212 | margin-top: 20px; 213 | font-size: 34px; 214 | color: #66819d; 215 | font-weight: normal; 216 | } 217 | .form-unit .info-content h2 { 218 | margin-bottom: 20px; 219 | font-size: 18px; 220 | font-weight: normal; 221 | font-family: Helvetica; 222 | color: #a7b5c4; 223 | } 224 | 225 | .form-unit .button-wrapper .btn{ 226 | display: inline-block; 227 | width: 160px; 228 | height: 40px; 229 | margin-right: 10px; 230 | padding: 0; 231 | line-height: 40px; 232 | font-size: 16px; 233 | color: #66819d; 234 | } 235 | 236 | .form-unit .button-wrapper .btn-primary{ 237 | color: #fff; 238 | text-shadow: 0 1px #111; 239 | } 240 | .form-unit .error-block{ 241 | position: relative; 242 | display: inline-block; 243 | padding: 5px 7px; 244 | margin-bottom: 13px; 245 | color: #d58a8a; 246 | border: 1px solid #d58a8a; 247 | border-radius: 3px; 248 | background-color: #fff4f4; 249 | } 250 | 251 | .third-accounts-login { 252 | margin-top: 30px; 253 | margin-bottom: -20px; 254 | color: #aaa; 255 | } 256 | 257 | .third-accounts-login a { 258 | text-indent: -10000px; 259 | display: inline-block; 260 | margin-right: 10px; 261 | height: 22px; 262 | width: 22px; 263 | } 264 | 265 | .third-accounts-login a.github { 266 | background-position:0 -66px; 267 | } 268 | 269 | .third-accounts-login a.evernote { 270 | background-position:0 -22px; 271 | } 272 | 273 | .third-accounts-login a.github:hover { 274 | background-position:0 -44px; 275 | } 276 | 277 | .third-accounts-login a.evernote:hover { 278 | background-position:0 0px; 279 | } 280 | 281 | .production-list { 282 | text-align: center; 283 | margin-top: 100px; 284 | } 285 | 286 | .production-list ul { 287 | list-style: none; 288 | padding-left: 0px; 289 | } 290 | 291 | .production-list li { 292 | display: inline-block; 293 | } 294 | 295 | .production-list li a { 296 | text-decoration: none; 297 | } 298 | 299 | .production-list li { 300 | display: inline-block; 301 | width: 230px; 302 | } 303 | 304 | .production-list li div { 305 | text-indent: -9999px; 306 | width: 40px; 307 | height: 40px; 308 | margin: 0 5px; 309 | background-size: 80px 40px; 310 | margin: auto; 311 | } 312 | 313 | .production-list li p { 314 | color: gray; 315 | } 316 | 317 | .production-list li h4 { 318 | color: #383838; 319 | } 320 | 321 | .production-list .teambition-app, .production-list .today-app { 322 | -webkit-transform: translate3d(0, 0, 0); 323 | transform: translate3d(0, 0, 0); 324 | -webkit-transition: box-shadow ease-in-out 218ms,-webkit-transform 218ms ease-in-out; 325 | transition: box-shadow ease-in-out 218ms,transform 218ms ease-in-out; 326 | } 327 | 328 | .production-list .teambition-app:hover, .production-list .today-app:hover { 329 | -webkit-transform: translate3d(0, -3px, 0); 330 | transform: translate3d(0, -3px, 0); 331 | } 332 | 333 | .production-list .teambition-app { 334 | box-shadow: 0 0 3px #ccc; 335 | border-radius: 3px; 336 | } 337 | 338 | .production-list .today-app { 339 | background-position: -40px 0; 340 | } 341 | 342 | .auth-form .form-field { 343 | position: relative; 344 | } 345 | 346 | .auth-form .form-field .icon { 347 | position: absolute; 348 | font-size: 20px; 349 | top: 10px; 350 | left: 12px; 351 | color: #c0c0c0; 352 | 353 | } 354 | 355 | .auth-form .form-control { 356 | -webkit-box-shadow: none; 357 | box-shadow: none; 358 | } 359 | 360 | .auth-form input { 361 | height: 48px; 362 | width: 100%; 363 | margin-bottom: 20px; 364 | font-size: 16px; 365 | } 366 | 367 | .auth-form input:focus + .icon { 368 | color: gray; 369 | } 370 | 371 | 372 | 373 | .auth-form .email { 374 | padding-left: 50px; 375 | } 376 | 377 | 378 | .auth-form .password { 379 | padding-left: 50px; 380 | } 381 | 382 | .auth-form .name { 383 | padding-left: 50px; 384 | } 385 | 386 | .auth-form label { 387 | position: absolute; 388 | left: 350px; 389 | top: 50px; 390 | color: #b94a48; 391 | } 392 | .auth-form .btn { 393 | padding: 12px; 394 | font-size: 18px; 395 | border-radius: 4px; 396 | width: 100%; 397 | margin: 10px 0; 398 | display: inline-block; 399 | outline: none; 400 | } 401 | .auth-form .btn:focus { 402 | outline: none; 403 | } 404 | /*认证 授权*/ 405 | .authorize-unit .btn { 406 | width: 49%; 407 | display:inline-block; 408 | } 409 | 410 | .authorize-unit .btn-link { 411 | color: #aaa; 412 | } 413 | 414 | .authorize-unit .authorize-data { 415 | color: #838383; 416 | text-align: center; 417 | margin-top: -15px; 418 | margin-bottom: 0px; 419 | } 420 | 421 | .authorize-unit .avatar { 422 | display: block; 423 | width: 100px; 424 | height: 100px; 425 | background-size: 100px 100px; 426 | background-color: #eee; 427 | margin: 30px auto; 428 | margin-bottom: 0px; 429 | } 430 | 431 | .authorize-unit h3 { 432 | text-align: center; 433 | margin-top: 10px; 434 | } 435 | 436 | .authorize-unit .account-email { 437 | text-align: center; 438 | margin-top: 8px; 439 | } 440 | 441 | .auth-form .action-wrapper > a, 442 | .auth-form .action-wrapper > span { 443 | color: #aaa; 444 | font-size: 14px; 445 | } 446 | .action-wrapper { 447 | margin-top: 10px; 448 | text-align: right; 449 | } 450 | .action-wrapper > a:hover { 451 | text-decoration: none; 452 | } 453 | 454 | .cooperator-wrapper{ 455 | position: absolute; 456 | left: 400px; 457 | top: 200px; 458 | padding-left: 30px; 459 | } 460 | 461 | .cooperator-wrapper::before{ 462 | content: ''; 463 | position: absolute; 464 | height: 130px; 465 | width: 1px; 466 | background-color: #ddd; 467 | left: 0; 468 | top: 30px; 469 | } 470 | 471 | .cooperator-wrapper > p{ 472 | opacity: 0; 473 | margin-bottom: 10px; 474 | font-size: 13px; 475 | font-weight: normal; 476 | color: #a6a6a6; 477 | } 478 | 479 | .cooperator-wrapper a.btn{ 480 | display: block; 481 | width: 200px; 482 | height: 50px; 483 | line-height: 34px; 484 | margin-bottom: 30px; 485 | color: #808080; 486 | text-align: left; 487 | background-image: -webkit-linear-gradient(top, #fefefe, #fcfcfc); 488 | background-image: -moz-linear-gradient(top, #fefefe, #fcfcfc); 489 | background-image: -o-linear-gradient(top, #fefefe, #fcfcfc); 490 | background-image: -linear-gradient(top, #fefefe, #fcfcfc); 491 | 492 | } 493 | 494 | .cooperator-wrapper a.btn:hover{ 495 | border: 1px solid #bbb; 496 | } 497 | 498 | .cooperator-wrapper a.btn i.icon{ 499 | display: inline-block; 500 | width: 25px; 501 | height: 25px; 502 | margin: -4px 15px 0 5px; 503 | background-size: 25px 25px; 504 | vertical-align: middle; 505 | } 506 | 507 | .cooperator-wrapper a.btn i.icon.github-icon{ 508 | background-image: url(https://dn-st.qbox.me/pages/images/icon-github@2x.jpg); 509 | } 510 | 511 | .cooperator-wrapper a.btn i.icon.mingdao-icon{ 512 | background-image: url(https://dn-st.qbox.me/pages/images/icon-mingdao@2x.jpg); 513 | } 514 | 515 | /* members list */ 516 | 517 | .member-list-container { 518 | margin-top: 30px; 519 | } 520 | .member-list { 521 | list-style: none; 522 | padding-left: 0; 523 | margin-top: 20px; 524 | margin-bottom: 0; 525 | } 526 | 527 | .member { 528 | position: relative; 529 | padding-left: 50px; 530 | margin-top: 26px; 531 | } 532 | 533 | .member div { 534 | position: absolute; 535 | left: 0; 536 | } 537 | 538 | .member h5 { 539 | margin-top: 0px; 540 | margin-bottom: 4px; 541 | } 542 | 543 | .member p { 544 | color: #a0a0a0; 545 | } 546 | 547 | .img-36 { 548 | display: inline-block; 549 | width: 36px; 550 | height: 36px; 551 | background-size: 36px 36px; 552 | background-color: #eee 553 | } 554 | 555 | .img-48 { 556 | display: inline-block; 557 | width: 48px; 558 | height: 48px; 559 | background-size: 48px 48px; 560 | background-color: #eee 561 | } 562 | 563 | .img-60 { 564 | display: inline-block; 565 | width: 60px; 566 | height: 60px; 567 | background-size: 60px 60px; 568 | background-color: #eee 569 | } 570 | 571 | .use-loged-account { 572 | color: #838383; 573 | margin-top: 30px; 574 | margin-bottom: 28px; 575 | } 576 | 577 | 578 | .yourself { 579 | margin-bottom: 20px; 580 | background-color: #f5f6f7; 581 | width: 100%; 582 | padding: 20px; 583 | padding-left: 100px; 584 | border-radius: 4px; 585 | } 586 | 587 | .yourself .avatar { 588 | left: 20px; 589 | } 590 | 591 | .yourself h4 { 592 | margin-top: 5px; 593 | margin-bottom: 10px; 594 | } 595 | 596 | .notify-box { 597 | width: 700px; 598 | margin: 200px auto; 599 | background-color: white; 600 | height: 250px; 601 | padding: 55px; 602 | border-radius: 6px; 603 | box-shadow: 0 0 10px 4px rgba(0, 0, 0, .05); 604 | position: relative; 605 | } 606 | 607 | .notify-box .btn { 608 | padding: 10px 15px; 609 | font-size: 14px; 610 | border-radius: 2px; 611 | margin: 10px 0; 612 | } 613 | 614 | 615 | .notify-box .info, 616 | .notify-box .success, 617 | .notify-box .error { 618 | position: absolute; 619 | height: 80px; 620 | width: 80px; 621 | } 622 | 623 | .notify-box .info .icon, 624 | .notify-box .success .icon, 625 | .notify-box .error .icon { 626 | font-size: 80px; 627 | position: absolute; 628 | top: -20px; 629 | } 630 | 631 | .notify-box .info .icon { 632 | color: #428bca; 633 | } 634 | 635 | .notify-box .success .icon { 636 | color: #6AB83A; 637 | } 638 | 639 | .notify-box .error .icon { 640 | color: #d9534f; 641 | } 642 | 643 | .notify-box .message { 644 | margin-left: 135px; 645 | } 646 | 647 | .notify-box .message h3 { 648 | margin-top: 0px; 649 | margin-bottom: 15px; 650 | } 651 | 652 | .notify-box .message p { 653 | font-size: 16px; 654 | margin-bottom: 20px; 655 | color: #838383; 656 | } 657 | 658 | @media (max-width: 650px) { 659 | 660 | .notify-box { 661 | width: auto; 662 | margin: 100px 30px; 663 | height:auto; 664 | background-color: white; 665 | padding: 10px; 666 | box-shadow: none; 667 | position: relative; 668 | min-width: 240px; 669 | } 670 | 671 | .notify-box .info, 672 | .notify-box .success, 673 | .notify-box .error { 674 | height: 24px; 675 | width: 24px; 676 | } 677 | 678 | .notify-box .info .icon, 679 | .notify-box .success .icon, 680 | .notify-box .error .icon { 681 | font-size: 24px; 682 | top: 0px; 683 | } 684 | 685 | .notify-box .message { 686 | margin-left: 0px; 687 | } 688 | 689 | .notify-box .message h3 { 690 | margin-top: 8px; 691 | margin-bottom: 15px; 692 | margin-left: 30px; 693 | font-size: 18px; 694 | } 695 | 696 | .notify-box .message p { 697 | font-size: 16px; 698 | margin-bottom: 5px; 699 | color: #838383; 700 | } 701 | 702 | .notify-box .message .btn { 703 | display: block; 704 | margin: 10px auto; 705 | width: 60%; 706 | } 707 | } 708 | 709 | 710 | @media (max-width: 580px) { 711 | 712 | html, body { 713 | height: 100%; 714 | width: 100%; 715 | } 716 | 717 | body { 718 | background-position: center bottom; 719 | background-size: 200%; 720 | } 721 | 722 | .form-unit { 723 | width: 250px; 724 | padding: 0; 725 | box-shadow: none; 726 | background-color: none; 727 | margin-top: 100px; 728 | } 729 | 730 | .form-unit h3 { 731 | font-size: 15px; 732 | } 733 | 734 | .production-list { 735 | display: none; 736 | } 737 | 738 | } 739 | 740 | @media (min-height: 800px) { 741 | body { 742 | background-position-y: 250px; 743 | } 744 | .form-unit { 745 | margin: 110px auto; 746 | } 747 | } 748 | 749 | .btn-lg { 750 | width: 100%; 751 | } -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class ApplicationController < ActionController::Base 3 | # Prevent CSRF attacks by raising an exception. 4 | # For APIs, you may want to use :null_session instead. 5 | protect_from_forgery with: :exception 6 | 7 | helper_method :current_user, :is_logined?, :current_user_is_admin?, :get_categories_options, :current_user_can_star?, 8 | :current_user_can_edit_comment?, :markdown_parser 9 | 10 | rescue_from Exception, with: :error_500 unless Rails.env.development? 11 | rescue_from ActiveRecord::RecordNotFound, with: :error_404 unless Rails.env.development? 12 | 13 | before_filter :current_user 14 | 15 | def current_user 16 | @current_user = nil 17 | if session[:user_id].present? 18 | @current_user = User.find(session[:user_id]) 19 | redirect_to logout_path(referer) unless @current_user 20 | end 21 | @current_user 22 | end 23 | 24 | def current_user_is_admin? 25 | @current_user && @current_user.admin 26 | end 27 | 28 | def current_user_can_star?(article) 29 | return false unless @current_user 30 | !article.article_stars.find_by(user_id: @current_user.id).present? 31 | end 32 | 33 | def current_user_can_edit_comment?(comment) 34 | return false unless @current_user 35 | comment.user_id == @current_user.id 36 | end 37 | 38 | def get_categories_options 39 | options = [] 40 | Category.order('articles_count desc').each {|c| options << [c.name, c.id]} 41 | options 42 | end 43 | 44 | def markdown_parser(content) 45 | markdown = Redcarpet::Markdown.new Redcarpet::Render::HTML, autolink: true, tables: true 46 | markdown.render content 47 | end 48 | 49 | def referer 50 | request.env['HTTP_REFERER'] || root_path 51 | end 52 | 53 | def is_logined? 54 | session[:user_id].present? && session[:user_id] 55 | end 56 | 57 | def require_login 58 | redirect_to root_path unless is_logined? 59 | end 60 | 61 | def error_500 62 | render file: File.join(Rails.root, 'public/500.html'), status: 500, layout: false 63 | end 64 | 65 | def error_404 66 | render file: File.join(Rails.root, 'public/404.html'), status: 404, layout: false 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /app/controllers/articles_controller.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class ArticlesController < ApplicationController 3 | before_filter :check_current_user_is_admin, only: [:new, :create, :edit, :update] 4 | before_filter :article, only: [:show, :edit, :update, :destroy, :star] 5 | 6 | def index 7 | res = Article 8 | order = 'updated_at desc' 9 | if params[:sort].present? 10 | order = case params[:sort] 11 | when 'star' 12 | 'star_count desc' 13 | when 'comments' 14 | 'comments_count desc' 15 | else 16 | 'updated_at desc' 17 | end 18 | end 19 | if params[:c].present? 20 | category = Category.find params[:c] 21 | res = res.where("category_id=#{category.id}") if category 22 | end 23 | res = res.where("title like ?", "%#{params[:keyword]}%") if params[:keyword].present? 24 | @articles = res.order(order).page(params[:page]).per(Settings.blog.aritcle_page_size) 25 | respond_to do |format| 26 | format.html 27 | format.js 28 | end 29 | end 30 | 31 | def new 32 | @article = Article.new 33 | end 34 | 35 | def create 36 | @article = Article.new params.require(:article).permit(:title, :tags, :source, :content) 37 | @article.user_id = @current_user.id 38 | if params[:article].present? && params[:article][:category_id].present? 39 | @article.category_id = params[:article][:category_id] 40 | elsif params[:article].present? && params[:article][:category_name].present? 41 | category = Category.find_or_create params[:article][:category_name] 42 | @article.category_id = category.id 43 | end 44 | if @article.save 45 | redirect_to article_path(@article) 46 | else 47 | render 'new' 48 | end 49 | end 50 | 51 | def show 52 | @article.add_view request.remote_ip, @current_user, params.inspect 53 | @comment = @article.comments.new 54 | @comments = @article.comments.order('updated_at asc').page(params[:page]).per(Settings.blog.comments_page_size) 55 | respond_to do |format| 56 | format.html 57 | format.js 58 | end 59 | end 60 | 61 | def edit 62 | 63 | end 64 | 65 | def update 66 | attributes = params.require(:article).permit(:title, :tags, :source, :content) 67 | if params[:article].present? && params[:article][:category_id].present? 68 | attributes[:category_id] = params[:article][:category_id] 69 | elsif params[:article].present? && params[:article][:category_name].present? 70 | category = Category.find_or_create params[:article][:category_name] 71 | attributes[:category_id] = category.id 72 | end 73 | if @article.update_attributes attributes 74 | redirect_to article_path(@article) 75 | else 76 | render 'edit' 77 | end 78 | end 79 | 80 | def destroy 81 | if @article.destroy 82 | redirect_to articles_path 83 | else 84 | flash[:error] = '删除失败' 85 | redirect_to article_path(@article) 86 | end 87 | end 88 | 89 | def star 90 | if current_user_can_star? @article 91 | @result = {status: false, message: '', star_count: 0} 92 | star = @article.article_stars.new user_id: @current_user.id 93 | if star.save 94 | @result[:star_count] = (@article.star_count || 0) + 1 95 | @result[:status] = true 96 | else 97 | @result[:message] = '称赞失败' 98 | end 99 | respond_to do |format| 100 | format.js 101 | end 102 | end 103 | end 104 | 105 | protected 106 | 107 | def check_current_user_is_admin 108 | redirect_to root_path unless (@current_user && @current_user.admin) 109 | end 110 | 111 | def article 112 | @article = Article.find params[:id] 113 | end 114 | end -------------------------------------------------------------------------------- /app/controllers/blogs_controller.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class BlogsController < ApplicationController 3 | before_filter :require_login, only: [:set, :set_userinfo, :upload_img, :set_blog, :update_blog, :change_password, :update_password] 4 | 5 | def index 6 | @articles = Article.order('updated_at desc').limit(10) 7 | @new_comments = ArticleComment.order('updated_at desc').limit(10) 8 | end 9 | 10 | def set 11 | 12 | end 13 | 14 | def set_userinfo 15 | user = User.find @current_user.id 16 | user.nick_name = params[:user][:nick_name] if params[:user][:nick_name].present? 17 | begin 18 | if params[:user][:avatar].present? 19 | upload_info = upload_picture params[:user][:avatar] 20 | user.avatar = "/images/#{upload_info[:real_file_name]}" 21 | end 22 | rescue UploadException => e 23 | flash.now[:error] = e.message 24 | render 'set' 25 | else 26 | if user.save 27 | redirect_to set_blogs_path 28 | else 29 | flash.now[:error] = user.errors.full_messages.first 30 | render 'set' 31 | end 32 | end 33 | end 34 | 35 | def set_blog 36 | @blog = BlogInfo.first 37 | @blog = BlogInfo.new unless @blog 38 | end 39 | 40 | def update_blog 41 | param_hash = params.require(:blog).permit(:name, :blog_title, :email, :description) 42 | @blog = BlogInfo.first 43 | if @blog.present? 44 | result = @blog.update_attributes param_hash 45 | else 46 | @blog = BlogInfo.new param_hash 47 | result = @blog.save 48 | end 49 | if result 50 | redirect_to set_blogs_path 51 | else 52 | render 'set_blog' 53 | end 54 | end 55 | 56 | def about 57 | @blog = BlogInfo.first 58 | end 59 | 60 | def change_password 61 | 62 | end 63 | 64 | def update_password 65 | @user = User.find @current_user.id 66 | if @user.check_password(params[:user][:old_password]) 67 | @user.password = params[:user][:password] || '' 68 | @user.password_confirmation = params[:user][:password_confirmation] || '' 69 | if @user.save 70 | flash[:success] = '修改密码成功,请重新登录' 71 | redirect_to login_path 72 | else 73 | render 'change_password' 74 | end 75 | else 76 | flash.now[:error] = '原密码错误' 77 | render 'change_password' 78 | end 79 | end 80 | 81 | def upload_img 82 | @result = {status: false, message: '', text_id: params[:upload][:text_id] || ''} 83 | begin 84 | if params[:upload].present? && params[:upload][:img].present? && remotipart_submitted? 85 | upload_info = upload_picture params[:upload][:img] 86 | @result[:status] = true 87 | @result[:message] = "![#{upload_info[:file_name]}](/images/#{upload_info[:real_file_name]})" 88 | end 89 | rescue UploadException => e 90 | @result[:message] = e.message 91 | end 92 | respond_to do |format| 93 | format.js 94 | end 95 | end 96 | 97 | def preview 98 | result = {status: false, message: ''} 99 | if params[:content] 100 | result[:status] = true 101 | result[:message] = ActionController::Base.helpers.sanitize(markdown_parser(params[:content])) 102 | end 103 | render json: result.to_json 104 | end 105 | 106 | protected 107 | 108 | def upload_picture(file) 109 | upload_path = File.join Rails.root, 'public/images' 110 | upload = SimpleFileupload.new upload_path:upload_path, max_size: 1024*1024*2, type: 'image' 111 | upload_info = upload.upload file 112 | end 113 | end -------------------------------------------------------------------------------- /app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class CommentsController < ApplicationController 3 | before_filter :article, only: [:create, :edit, :update, :destroy] 4 | before_filter :require_login 5 | before_filter :get_comment, only: [:edit, :update, :destroy] 6 | 7 | def create 8 | @result = {status: false, message: ''} 9 | @comment = @article.comments.new content: params[:article_comment][:content] 10 | @comment.user_id = @current_user.id 11 | if @comment.save 12 | @result[:status] = true 13 | else 14 | @result[:message] = @comment.errors.full_messages.first 15 | end 16 | respond_to do |format| 17 | format.js 18 | end 19 | end 20 | 21 | def edit 22 | respond_to do |format| 23 | format.js 24 | end 25 | end 26 | 27 | def update 28 | @result = {status: false, message: ''} 29 | if params[:article_comment].present? && params[:article_comment][:content].present? 30 | @comment.content = params[:article_comment][:content] 31 | if @comment.save 32 | @result[:status] = true 33 | else 34 | @result[:message] = '编辑评论失败' 35 | end 36 | end 37 | respond_to do |format| 38 | format.js 39 | end 40 | end 41 | 42 | def destroy 43 | @result = {status: false, message: ''} 44 | if @comment.destroy 45 | @result[:status] = true 46 | else 47 | @result[:message] = '删除评论失败' 48 | end 49 | respond_to do |format| 50 | format.js 51 | end 52 | end 53 | 54 | protected 55 | 56 | def article 57 | @article = Article.find params[:article_id] 58 | end 59 | 60 | def get_comment 61 | @comment = @article.comments.find params[:id] 62 | end 63 | end -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class UsersController < ApplicationController 3 | def register 4 | @user = User.new 5 | render 'register', layout: 'register' 6 | end 7 | 8 | def register_confirm 9 | @user = User.new params.require(:user).permit(:username,:email,:password,:password_confirmation) 10 | if @user.save 11 | #to_login @user 12 | session[:wait_active_email] = @user.email 13 | redirect_to send_active_mail_users_path 14 | else 15 | render 'register', layout: 'register' 16 | end 17 | end 18 | 19 | def login 20 | return redirect_to(login_path(from: referer)) unless params[:from].present? 21 | @user = User.new 22 | render 'login', layout: 'register' 23 | end 24 | 25 | def login_confirm 26 | @user = User.find_by username: params[:user][:username] 27 | unless @user.activation? 28 | session[:wait_active_email] = @user.email 29 | flash.now[:error] = "用户还没有激活,点此发送激活邮件。" 30 | return render 'login', layout: 'register' 31 | end 32 | if @user && @user.check_password(params[:user][:password]) 33 | to_login @user 34 | @user.update_attribute :last_login_time, DateTime.now 35 | redirect_to (params[:from].present? ? params[:from] : root_path) 36 | else 37 | flash[:error] = '用户名或密码错误' 38 | render 'login', layout: 'register' 39 | end 40 | rescue 41 | flash[:error] = '用户名或密码错误' 42 | render 'login', layout: 'register' 43 | end 44 | 45 | def logout 46 | session[:user_id] = nil 47 | redirect_to referer 48 | end 49 | 50 | def send_active_mail 51 | @result = {status: false, message: ''} 52 | if session[:wait_active_email] 53 | wait_user = User.find_by email: session[:wait_active_email] 54 | if wait_user.activation? 55 | flash[:success] = '您的账号已经激活,请登录' 56 | return redirect_to login_path 57 | end 58 | if wait_user.present? 59 | token = UserActive.generate_token 60 | active = UserActive.new user_id: wait_user.id, type_name: 'Active', token: token 61 | url = File.join(Settings.blog.domain, "users/activation?token=#{token}") 62 | if active.save && UserMailer.auth_mail(session[:wait_active_email], url).deliver 63 | @result[:status] = true 64 | @result[:message] = session[:wait_active_email] 65 | end 66 | end 67 | end 68 | end 69 | 70 | def activation 71 | active = UserActive.where(type_name: 'Active', token: params[:token], used: 0).order('created_at desc').first 72 | if active && !active.used? 73 | user = User.find active.user_id 74 | user.activation = 1 75 | active.used = 1 76 | if user.save && active.save 77 | flash[:success] = '激活成功,请登录' 78 | redirect_to login_path 79 | else 80 | flash[:error] = '激活失败,请重试,如果还不能激活,请联系管理员' 81 | redirect_to login_path 82 | end 83 | else 84 | flash[:error] = 'token不存在' 85 | redirect_to root_path 86 | end 87 | end 88 | 89 | def forget_password 90 | render 'forget_password', layout: 'register' 91 | end 92 | 93 | def forget_password_confirm 94 | @result = {status: false, message: ''} 95 | if params[:user][:email].present? 96 | user = User.find_by email: params[:user][:email] 97 | return redirect_to(root_path) unless user 98 | token = UserActive.generate_token 99 | active = UserActive.new user_id: user.id, token: token, type_name: 'ForgetPassword' 100 | url = File.join(Settings.blog.domain, "users/change_pw?token=#{token}") 101 | if active.save && UserMailer.forget_password(user.email, url).deliver 102 | @result[:status] = true 103 | @result[:message] = user.email 104 | else 105 | flash.now[:error] = '邮件发送失败,请重试' 106 | render 'forget_password', layout: 'register' 107 | end 108 | end 109 | end 110 | 111 | def change_pw 112 | @token = params[:token] 113 | active = UserActive.where(type_name: 'ForgetPassword', token: @token, used: 0).order('created_at desc').first 114 | return redirect_to(root_path) unless active 115 | if Time.now.to_i - active.created_at.to_i > 600 116 | return redirect_to root_path 117 | end 118 | render 'change_pw', layout: 'register' 119 | end 120 | 121 | def change_pw_confirm 122 | @token = params[:user][:token] 123 | active = UserActive.where(type_name: 'ForgetPassword', token: @token, used: 0).order('created_at desc').first 124 | return redirect_to(root_path) unless active 125 | @user = User.find active.user_id 126 | return redirect_to(root_path) unless @user 127 | @user.password = params[:user][:password] || '' 128 | @user.password_confirmation = params[:user][:password_confirmation] 129 | if @user.save 130 | flash[:success] = '重置密码成功,请登录' 131 | redirect_to login_path 132 | else 133 | flash.now[:error] = @user.errors.full_messages.first 134 | render 'change_pw', layout: 'register' 135 | end 136 | end 137 | 138 | protected 139 | 140 | def to_login(user) 141 | session[:user_id] = user.id 142 | end 143 | end -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | module ApplicationHelper 3 | def avatar(user) 4 | user.avatar.present? ? image_tag(user.avatar) : (gravatar_image_tag user.email, alt: user.username) 5 | end 6 | 7 | def avatar_url(user) 8 | user.avatar.present? ? user.avatar : (gravatar_image_url user.email, alt: user.username) 9 | end 10 | 11 | def get_categories 12 | Category.order('articles_count desc') 13 | end 14 | 15 | def get_tags(article) 16 | tags = article.tags || '' 17 | tags.split(',') 18 | end 19 | 20 | def get_articles_count 21 | Article.count 22 | end 23 | 24 | def blog_title 25 | blog = BlogInfo.first 26 | blog.present? ? blog.blog_title : 'Blog' 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/app/mailers/.keep -------------------------------------------------------------------------------- /app/mailers/user_mailer.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class UserMailer < ActionMailer::Base 3 | helper ApplicationHelper 4 | 5 | default from: Settings.mailer.user_name 6 | 7 | def auth_mail(email, url) 8 | @url = url 9 | mail(subject: "用户邮箱激活邮件", to: email, date: Time.now) 10 | end 11 | 12 | def forget_password(email, url) 13 | @url = url 14 | mail(subject: '找回密码', to: email, date: Time.now) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/app/models/.keep -------------------------------------------------------------------------------- /app/models/article.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class Article < ActiveRecord::Base 3 | attr_accessor :category_name 4 | belongs_to :category, counter_cache: 'articles_count' 5 | belongs_to :user 6 | has_many :article_stars 7 | has_many :article_views 8 | has_many :comments, class_name: 'ArticleComment' 9 | 10 | validates :title, length: {minimum: 10, maximum: 50} 11 | validates :tags, presence: true 12 | validates :source, allow_blank: true, format: {with: /[a-zA-Z0-9-]+\.[a-zA-Z0-9]+/} 13 | validates :content, length: {minimum: 10} 14 | 15 | validate :validate_category 16 | 17 | def validate_category 18 | errors.add(:category_id, '分类不正确') unless Category.find(self.category_id) 19 | rescue 20 | errors.add(:category_id, '分类不正确') 21 | return false 22 | else 23 | return true 24 | end 25 | 26 | def add_view(ip, current_user, param_string) 27 | return false if (self.article_views.where("created_at >= '#{DateTime.now - 10.minute}' and ip='#{ip}'").count > 0) 28 | view = self.article_views.new ip: ip, param_string: param_string 29 | view.user_id = current_user.id if current_user 30 | view.save 31 | end 32 | 33 | end -------------------------------------------------------------------------------- /app/models/article_comment.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class ArticleComment < ActiveRecord::Base 3 | belongs_to :article, counter_cache: 'comments_count' 4 | belongs_to :user 5 | 6 | validates :content, length: {minimum: 10} 7 | end -------------------------------------------------------------------------------- /app/models/article_star.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class ArticleStar < ActiveRecord::Base 3 | belongs_to :article, counter_cache: 'star_count' 4 | 5 | end -------------------------------------------------------------------------------- /app/models/article_view.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class ArticleView < ActiveRecord::Base 3 | belongs_to :article, counter_cache: 'view_count' 4 | 5 | end -------------------------------------------------------------------------------- /app/models/blog_info.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class BlogInfo < ActiveRecord::Base 3 | self.table_name = 'blog_info' 4 | 5 | validates :name, length: {minimum: 2, maximum: 20} 6 | validates :email, format: {with: /\A[a-zA-Z0-9\-]+@[a-zA-Z0-9-]+\.(org|com|cn|io|net|cc|me)\z/} 7 | end -------------------------------------------------------------------------------- /app/models/category.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class Category < ActiveRecord::Base 3 | has_many :articles 4 | 5 | class << self 6 | def find_or_create(name) 7 | category = self.find_by name: name 8 | unless category 9 | category = Category.new name: name 10 | category.save! 11 | end 12 | category 13 | end 14 | end 15 | end -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class User < ActiveRecord::Base 3 | has_secure_password 4 | 5 | has_many :articles 6 | has_many :comments, class_name: 'ArticleComment' 7 | 8 | before_save :set_admin 9 | validates :username, length: {minimum: 5, maximum: 20}, uniqueness: true 10 | validates :email, format: {with: /\A[a-zA-Z0-9\-]+@[a-zA-Z0-9-]+\.(org|com|cn|io|net|cc|me)\z/}, uniqueness: true 11 | validates :password, length: {minimum: 6}, confirmation: true, if: :need_valid_password? 12 | validates :nick_name, length: {minimum: 2, maximum: 20}, uniqueness: true, if: 'nick_name.present?' 13 | 14 | def nickname 15 | self.nick_name || self.username 16 | end 17 | 18 | def set_admin 19 | self.admin = 0 unless self.admin.present? 20 | end 21 | 22 | def check_password(password) 23 | self.authenticate(password) 24 | end 25 | 26 | def update_last_reply_time 27 | self.update_attribute last_reply_time: DateTime.now 28 | end 29 | 30 | def can_reply? 31 | (DateTime.now.to_i - self.last_reply_time.to_i) > 60 32 | end 33 | 34 | def need_valid_password? 35 | new_record? || password.present? 36 | end 37 | end -------------------------------------------------------------------------------- /app/models/user_active.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class UserActive < ActiveRecord::Base 3 | before_save :set_used 4 | 5 | def set_used 6 | self.used = 0 if self.used.nil? 7 | end 8 | 9 | class << self 10 | def generate_token 11 | SecureRandom.urlsafe_base64 + Time.now.to_i.to_s 12 | end 13 | end 14 | end -------------------------------------------------------------------------------- /app/views/articles/_article.html.haml: -------------------------------------------------------------------------------- 1 | .article_content 2 | %h3 3 | - if article.source.empty? 4 | [原创] 5 | = link_to article.title, article_path(article), target: '_blank' 6 | - get_tags(article).each do |tag| 7 | %span.label.label-default 8 | = tag 9 | %p 10 | %span.icon.icon-time(data-toggle="tooltip" data-placement="bottom" title="发表时间") 11 | = article.updated_at.strftime('%Y-%m-%d') 12 | %span.icon.icon-eye-open(data-toggle="tooltip" data-placement="bottom" title="浏览数") 13 | = article.view_count || 0 14 | %span.icon.icon-thumbs-up(data-toggle="tooltip" data-placement="bottom" title="称赞数") 15 | = article.star_count || 0 16 | %span.icon.icon-comments(data-toggle="tooltip" data-placement="bottom" title="评论数") 17 | = article.comments_count || 0 18 | - unless article.source.empty? 19 | %p 20 | 转载: 21 | = article.source 22 | 23 | .row-fluid.article_des 24 | %p 25 | = article.content[0..300] 26 | -------------------------------------------------------------------------------- /app/views/articles/_article_sidebar.html.haml: -------------------------------------------------------------------------------- 1 | .text-left.ui-sortable 2 | %h4 文章分类 3 | %ul.nav.bs-docs-sidenav 4 | %li 5 | = link_to articles_path, remote: true do 6 | 全部 7 | %span.badge 8 | = "#{get_articles_count}篇" 9 | - get_categories.each do |category| 10 | %li 11 | = link_to articles_path(c: category.id), remote: true do 12 | = category.name 13 | %span.badge 14 | = "#{category.articles_count || 0}篇" 15 | 16 | -------------------------------------------------------------------------------- /app/views/articles/_articles.html.haml: -------------------------------------------------------------------------------- 1 | - unless @articles.present? 2 | .jumbotron 3 | %h2 什么也没有~ 4 | - @articles.each do |article| 5 | = render partial: 'articles/article', locals: {article: article} 6 | 7 | = paginate @articles, remote: true if params[:controller] == 'articles' -------------------------------------------------------------------------------- /app/views/articles/_comment.html.haml: -------------------------------------------------------------------------------- 1 | .panel.panel-dfault{id: "comment_#{comment.id}"} 2 | .panel-body 3 | .row 4 | .col-md-1.text-center 5 | %span.comment_avatar 6 | = avatar comment.user 7 | = comment.user.nickname 8 | .comment_content.col-md-9{id: "comment_#{comment.id}_body"} 9 | = raw sanitize(markdown_parser(comment.content)) 10 | .row.comment_footer.text-right 11 | %span.icon.icon-time(data-toggle="tooltip" data-placement="bottom" title="评论时间") 12 | = "#{comment.updated_at.strftime('%Y-%m-%d %H:%M:%S')}" 13 | - if current_user_can_edit_comment?(comment) 14 | = link_to edit_article_comment_path(@article, comment), remote: true do 15 | %span.icon.icon-edit 16 | - if current_user_is_admin? || current_user_can_edit_comment?(comment) 17 | = link_to article_comment_path(@article, comment), method: 'delete', remote: true, "data-confirm"=> '确定删除吗?' do 18 | %span.icon.icon-trash 19 | -------------------------------------------------------------------------------- /app/views/articles/_comment_form.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'articles/toolbar_upload_img', locals: {text_id: 'article_comment_content'} 2 | - if defined?(new_comment) && new_comment 3 | - @comment = @article.comments.new 4 | .comment_form 5 | - url = @comment.new_record? ? article_comments_path(@article) : article_comment_path(@article, @comment) 6 | = form_for @comment, url: url, role: 'form', remote: true do |f| 7 | .error#error_message 8 | .form-group 9 | %ul.nav.nav-tabs#preview_tab{role: "tablist"} 10 | %li.active 11 | = link_to (@comment.new_record? ? '发表评论' : '编辑评论'), '#edit', role: 'tab', 'data-toggle'=>'tab' 12 | %li 13 | = link_to '预览', '#preview', role: 'tab', 'data-toggle'=>'tab' 14 | .tab-content 15 | .tab-pane.active#edit 16 | = f.text_area :content, placeholder: '评论内容', class: 'form-control' 17 | .tab-pane#preview 18 | .form-group 19 | = f.submit '提交', class: 'btn btn-success btn-lg btn-block' 20 | 21 | :javascript 22 | $(function(){ 23 | $('#preview_tab a[href="#preview"]').click(function(e){ 24 | e.preventDefault(); 25 | $(this).tab('show'); 26 | preview('article_comment_content', 'preview'); 27 | }); 28 | }); -------------------------------------------------------------------------------- /app/views/articles/_comments.html.haml: -------------------------------------------------------------------------------- 1 | - unless @comments.present? 2 | .jumbotron#no_comments 3 | %h2 还没有评论~ 4 | - @comments.each do |comment| 5 | = render partial: 'comment', locals: {comment: comment} 6 | #paginate 7 | = paginate @comments, remote: true -------------------------------------------------------------------------------- /app/views/articles/_error.html.haml: -------------------------------------------------------------------------------- 1 | - errors = @article.errors.full_messages 2 | - if errors.any? 3 | .alert.alert-danger 4 | %a.close{"data-dismiss"=>"alert"} × 5 | %span 6 | = errors.first 7 | - elsif flash[:error].present? 8 | .alert.alert-danger 9 | %a.close{"data-dismiss"=>"alert"} × 10 | %span 11 | = flash[:error] -------------------------------------------------------------------------------- /app/views/articles/_form.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'toolbar_upload_img', locals: {text_id: 'article_content'} 2 | 3 | = form_for @article, role: 'form' do |f| 4 | .form-group 5 | %label{for: 'article_title'} 文章标题 6 | = f.text_field :title, placeholder: '文章标题', class: 'form-control' 7 | .form-group 8 | %label{for: 'article_category_id'} 文章分类 9 | = select_tag "article[category_id]", options_for_select(get_categories_options, @article.category_id), include_blank: true, class: 'form-control' 10 | 或者新建一个分类: 11 | = f.text_field :category_name, placeholder: '分类名称', class: 'form-control' 12 | .form-group 13 | %label{for: 'article_tags'} 标签 14 | = f.text_field :tags, placeholder: '文章标签,多个用英文“,”隔开', class: 'form-control' 15 | .form-group 16 | %label{for: 'article_source'} 文章来源 17 | = f.text_field :source, placeholder: '文章来源,为空则为原创', class: 'form-control' 18 | .form-group 19 | %ul.nav.nav-tabs#preview_tab{role: "tablist"} 20 | %li.active 21 | = link_to '文章内容', '#edit', role: 'tab', 'data-toggle'=>'tab' 22 | %li 23 | = link_to '预览', '#preview', role: 'tab', 'data-toggle'=>'tab' 24 | .tab-content 25 | .tab-pane.active#edit 26 | = f.text_area :content, placeholder: '文章内容', class: 'form-control' 27 | .tab-pane#preview 28 | .form-group 29 | = f.submit '提交', class: 'btn btn-success btn-lg btn-block' 30 | 31 | :javascript 32 | $(function(){ 33 | $('#preview_tab a[href="#preview"]').click(function(e){ 34 | e.preventDefault(); 35 | $(this).tab('show'); 36 | preview('article_content', 'preview'); 37 | }); 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /app/views/articles/_toolbar_upload_img.html.haml: -------------------------------------------------------------------------------- 1 | = form_for :upload, url: upload_img_blogs_path, enctype: "multipart/form-data", class: 'form-horizontal', role: 'form', remote: true do |f| 2 | .modal.fade#upload_modal 3 | .modal-dialog 4 | .modal-content 5 | .modal-header 6 | %button.close{"data-dismiss"=>"modal"} 7 | %span{"aria-hidden"=>"true"} 8 | × 9 | %span.sr-only 10 | Close 11 | %h4 上传图片 12 | .modal-body 13 | .form-group.row.text-center 14 | .error.text-center#error 15 | %label.col-sm-2.control-label.text-right{for: "upload_img"} 图片: 16 | .col-sm-8.text-left 17 | = token_tag 18 | = f.hidden_field :text_id, value: text_id 19 | = f.file_field :img 20 | .modal-footer 21 | %button.btn.btn-default{"data-dismiss"=>"modal"} 关闭 22 | = submit_tag '上传', class: 'btn btn-primary' 23 | 24 | 25 | :javascript 26 | $(function(){ 27 | new MarkdownToolbar($('##{text_id}')); 28 | $('.mdt_button_image').unbind('click'); 29 | $('.mdt_button_image').click(function(){ 30 | $('#upload_modal').modal('show'); 31 | }); 32 | }); -------------------------------------------------------------------------------- /app/views/articles/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h3 编辑博文 2 | = render partial: 'error' 3 | = render partial: 'form' -------------------------------------------------------------------------------- /app/views/articles/index.html.haml: -------------------------------------------------------------------------------- 1 | .row.inner.edge 2 | .col-md-9.layout-main 3 | .tab-nav 4 | -# %h2.common-title 博文列表 5 | %nav.sub-tab.row 6 | %span.col-md-6.text-left 7 | %h3 文章列表 8 | %span.col-md-6.text-right.sort_navbar 9 | - params_hash = params.dup 10 | - params_hash.delete :page 11 | %span.icon.icon-circle-arrow-down(data-toggle="tooltip" data-placement="bottom" title="排序") 12 | = link_to '按时间', articles_path(params_hash.merge({sort: 'time'})), remote: true, class: "#{'current' if params[:sort].nil? || params[:sort] == 'time'}" 13 | = link_to '按称赞', articles_path(params_hash.merge({sort: 'star'})), remote: true, class: "#{'current' if params[:sort].present? && params[:sort] == 'star'}" 14 | = link_to '按评论', articles_path(params_hash.merge({sort: 'comments'})), remote: true, class: "#{'current' if params[:sort].present? && params[:sort] == 'comments'}" 15 | .article-list#article-list 16 | = render partial: 'articles' 17 | 18 | .sidebar.col-md-3.layout-secondary.hidden-xs.hidden-sm 19 | = render partial: 'article_sidebar' 20 | 21 | :javascript 22 | $(function(){ 23 | $('.sort_navbar a').click(function(){ 24 | $(this).addClass('current').siblings().removeClass('current'); 25 | }); 26 | }); -------------------------------------------------------------------------------- /app/views/articles/index.js.haml: -------------------------------------------------------------------------------- 1 | :plain 2 | $('#article-list').html("#{escape_javascript(render partial: 'articles')}"); -------------------------------------------------------------------------------- /app/views/articles/new.html.haml: -------------------------------------------------------------------------------- 1 | 2 | %h3 撰写博文 3 | = render partial: 'error' 4 | = render partial: 'form' -------------------------------------------------------------------------------- /app/views/articles/show.html.haml: -------------------------------------------------------------------------------- 1 | .article_show 2 | = render partial: 'error' 3 | %h3.article-title 4 | - if @article.source.empty? 5 | [原创] 6 | = @article.title 7 | - if current_user_is_admin? 8 | = link_to edit_article_path(@article) do 9 | %span.icon.icon-edit 10 | = link_to article_path(@article), method: 'delete', "data-confirm"=> "确定删除吗?" do 11 | %span.icon.icon-trash 12 | 13 | 14 | %p 15 | - get_tags(@article).each do |tag| 16 | %span.label.label-default 17 | = tag 18 | %p 19 | %span.icon 20 | 分类: 21 | = link_to @article.category.name, articles_path(c: @article.category_id) 22 | %span.icon.icon-time(data-toggle="tooltip" data-placement="bottom" title="发表时间") 23 | = @article.updated_at.strftime('%Y-%m-%d %H:%M') 24 | %span.icon.icon-eye-open(data-toggle="tooltip" data-placement="bottom" title="浏览") 25 | = @article.view_count || 1 26 | - if current_user_can_star?(@article) 27 | = link_to star_article_path(@article), method: :post, remote: 'true' do 28 | %span.icon.icon-thumbs-up#article_star(data-toggle="tooltip" data-placement="bottom" title="称赞") 29 | = @article.star_count || 0 30 | - else 31 | %span.icon.icon-thumbs-up(data-toggle="tooltip" data-placement="bottom" title="称赞数") 32 | = @article.star_count || 0 33 | %span.icon.icon-comments(data-toggle="tooltip" data-placement="bottom" title="评论") 34 | = @article.comments_count || 0 35 | %span 36 | = social_share_button_tag(@article.title) 37 | - unless @article.source.empty? 38 | %p 39 | 转载: 40 | = @article.source 41 | 42 | .panel.panel-dfault 43 | .panel-body#article_content_show 44 | -#= raw markdown_parser(@article.content) 45 | 46 | %h3 评论: 47 | #comments_content 48 | = render partial: 'comments' 49 | 50 | - if is_logined? 51 | #comment_form 52 | = render partial: 'comment_form' 53 | - else 54 | .jumbotron 55 | %h2 56 | = link_to '登录', login_path 57 | 后可评论 58 | 59 | :javascript 60 | $(function(){ 61 | $('#article_content_show').html("#{escape_javascript sanitize(markdown_parser(@article.content))}"); 62 | }); -------------------------------------------------------------------------------- /app/views/articles/show.js.haml: -------------------------------------------------------------------------------- 1 | :plain 2 | $('#comments_content').html("#{escape_javascript(render partial: 'comments')}"); -------------------------------------------------------------------------------- /app/views/articles/star.js.haml: -------------------------------------------------------------------------------- 1 | - if @result[:status] 2 | :plain 3 | $('#article_star').html(" #{@result[:star_count]}"); 4 | $('#article_star').parent('a').attr('href', 'javascript:void(0);').addClass('disabled'); -------------------------------------------------------------------------------- /app/views/blogs/_set_sidebar.html.haml: -------------------------------------------------------------------------------- 1 | - if defined?(obj) && obj.present? && obj.errors.any? 2 | .alert.alert-danger 3 | %a.close{"data-dismiss"=>"alert"} × 4 | %span 5 | = obj.errors.full_messages.first 6 | - if flash[:error].present? 7 | .alert.alert-danger 8 | %a.close{"data-dismiss"=>"alert"} × 9 | %span 10 | = flash[:error] 11 | .col-md-2 12 | %ul.nav.nav-pills.nav-stacked{role: 'tablist'} 13 | %li{role: 'presentation', class: "#{'active' if params[:action] == 'set'}"} 14 | = link_to '个人信息设置', set_blogs_path 15 | - if current_user_is_admin? 16 | %li{role: 'presentation', class: "#{'active' if params[:action] == 'set_blog'}"} 17 | = link_to '博客设置', set_blog_blogs_path 18 | %li{role: 'presentation'} 19 | = link_to '修改密码', change_password_blogs_path 20 | -------------------------------------------------------------------------------- /app/views/blogs/about.html.haml: -------------------------------------------------------------------------------- 1 | .text-left 2 | %p 3 | %h2 博主 4 | = @blog ? @blog.name : '未设置' 5 | %p 6 | %h2 email 7 | = @blog ? (link_to @blog.email, "mailto:#{@blog.email}") : '未设置' 8 | %p 9 | %h2 简介 10 | = @blog ? @blog.description : '未设置' -------------------------------------------------------------------------------- /app/views/blogs/change_password.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | %h3.set_title 设置 3 | = render 'set_sidebar', obj: @user 4 | .col-md-8 5 | = form_for :user, method: 'post', url: update_password_blogs_path, role: 'form' do |f| 6 | .form-group 7 | %label{for: 'user_username'} 用户名 8 | = f.text_field :username, value: @current_user.username, placeholder: '用户名', class: 'form-control username', disabled: 'disabled' 9 | .form-group 10 | %label{for: 'user_password'} 旧密码 11 | = f.password_field :old_password, placeholder: '旧密码', class: 'form-control password', required: true 12 | .form-group 13 | %label{for: 'user_password'} 新密码 14 | = f.password_field :password, placeholder: '密码,最少6位', class: 'form-control password', required: true 15 | .form-group 16 | %label{for: 'user_password_confirmation'} 确认密码 17 | = f.password_field :password_confirmation, placeholder: '确认密码', class: 'form-control password', required: true 18 | = submit_tag '修改', class: 'btn btn-primary set-btn' -------------------------------------------------------------------------------- /app/views/blogs/index.html.haml: -------------------------------------------------------------------------------- 1 | .row.inner.edge 2 | .col-md-9.layout-main 3 | .article-list#article-list 4 | = render partial: 'articles/articles' 5 | .col-md-3 6 | %h3 最新评论 7 | - if @new_comments.present? 8 | %ul.list-group.new_comments 9 | - @new_comments.each do |comment| 10 | %li.text-left 11 | = link_to "#{comment.user.nickname}: #{comment.content[0..18]}...", article_path(comment.article_id) + "#comment_#{comment.id}", target: '_blank', class: 'list-group-item' 12 | -------------------------------------------------------------------------------- /app/views/blogs/set.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | %h3.set_title 设置 3 | = render 'set_sidebar' 4 | .col-md-8 5 | = form_for :user, url: set_userinfo_blogs_path, role: 'form' do |f| 6 | .form-group 7 | %label{for: 'user_nick_name'} 昵称 8 | = f.text_field :nick_name, value: @current_user.nickname, placeholder: '昵称', class: 'form-control', size: 10 9 | .form-group 10 | %label{for: 'user_avatar'} 头像 11 | .row.text-left 12 | .col-md-2#img_preview 13 | %img#set_user_avatar{src: "#{avatar_url(@current_user)}"} 14 | .col-md-8.avatar-field 15 | = f.file_field :avatar 16 | .form-group 17 | = f.submit '修改', class: 'btn btn-primary set-btn' 18 | 19 | 20 | :javascript 21 | function previewImage(file){ 22 | var MAXWIDTH = 80; 23 | var MAXHEIGHT = 80; 24 | var div = document.getElementById('img_preview'); 25 | if (file.files && file.files[0]) 26 | { 27 | div.innerHTML = ''; 28 | var img = document.getElementById('imghead'); 29 | img.onload = function(){ 30 | var rect = clacImgZoomParam(MAXWIDTH, MAXHEIGHT, img.offsetWidth, img.offsetHeight); 31 | img.height = rect.height; 32 | img.style.marginTop = rect.top+'px'; 33 | } 34 | var reader = new FileReader(); 35 | reader.onload = function(evt){img.src = evt.target.result;} 36 | reader.readAsDataURL(file.files[0]); 37 | } 38 | else 39 | { 40 | var sFilter='filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="'; 41 | file.select(); 42 | var src = document.selection.createRange().text; 43 | div.innerHTML = ''; 44 | var img = document.getElementById('imghead'); 45 | img.filters.item('DXImageTransform.Microsoft.AlphaImageLoader').src = src; 46 | var rect = clacImgZoomParam(MAXWIDTH, MAXHEIGHT, img.offsetWidth, img.offsetHeight); 47 | status =('rect:'+rect.top+','+rect.left+','+rect.width+','+rect.height); 48 | div.innerHTML = "
"; 49 | } 50 | } 51 | 52 | function clacImgZoomParam( maxWidth, maxHeight, width, height ){ 53 | var param = {top:0, left:0, width:width, height:height}; 54 | if( width>maxWidth || height>maxHeight ) 55 | { 56 | rateWidth = width / maxWidth; 57 | rateHeight = height / maxHeight; 58 | 59 | if( rateWidth > rateHeight ) 60 | { 61 | param.width = maxWidth; 62 | param.height = Math.round(height / rateWidth); 63 | }else 64 | { 65 | param.width = Math.round(width / rateHeight); 66 | param.height = maxHeight; 67 | } 68 | } 69 | 70 | param.left = Math.round((maxWidth - param.width) / 2); 71 | param.top = Math.round((maxHeight - param.height) / 2); 72 | return param; 73 | } 74 | 75 | $(function(){ 76 | $("#user_avatar").change(function() { 77 | previewImage(this); 78 | }); 79 | }); -------------------------------------------------------------------------------- /app/views/blogs/set_blog.html.haml: -------------------------------------------------------------------------------- 1 | .row 2 | %h3.set_title 设置 3 | = render 'set_sidebar', obj: @blog 4 | .col-md-8 5 | = form_for @blog, as: 'blog', url: update_blog_blogs_path, method: 'post', role: 'form' do |f| 6 | .form-group 7 | %label{for: 'blog_name'} 博主 8 | = f.text_field :name, placeholder: '博主名称', class: 'form-control' 9 | .form-group 10 | %label{for: 'blog_blog_title'} 博客名字 11 | = f.text_field :blog_title, placeholder: '博客名称', class: 'form-control' 12 | .form-group 13 | %label{for: 'blog_email'} 联系邮箱 14 | = f.text_field :email, placeholder: '联系邮箱', class: 'form-control' 15 | .form-group 16 | %label{for: 'blog_description'} 关于博主 17 | = f.text_area :description, placeholder: '关于博主', class: 'form-control blog-description' 18 | .form-group 19 | = f.submit '修改', class: 'btn btn-primary set-btn' -------------------------------------------------------------------------------- /app/views/blogs/upload_img.js.haml: -------------------------------------------------------------------------------- 1 | - if @result[:status] 2 | :plain 3 | var old_val = $("##{@result[:text_id]}").val(); 4 | $("##{@result[:text_id]}").val(old_val + "#{@result[:message]}"); 5 | $('#upload_modal').modal('hide'); 6 | - else 7 | :plain 8 | $('#error').text("#{@result[:message]}"); -------------------------------------------------------------------------------- /app/views/comments/create.js.haml: -------------------------------------------------------------------------------- 1 | - if @result[:status] 2 | :plain 3 | $("#article_comment_content").val(''); 4 | $('#no_comments').remove(); 5 | $('#error_message').html(""); 6 | $("#{escape_javascript(render partial: 'articles/comment', locals: {comment: @comment})}").insertBefore('#paginate'); 7 | - else 8 | :plain 9 | $('#error_message').html("#{@result[:message]}"); -------------------------------------------------------------------------------- /app/views/comments/destroy.js.haml: -------------------------------------------------------------------------------- 1 | - if @result[:status] 2 | :plain 3 | $("#comment_#{@comment.id}").remove(); -------------------------------------------------------------------------------- /app/views/comments/edit.js.haml: -------------------------------------------------------------------------------- 1 | :plain 2 | $("#comment_form").html("#{escape_javascript(render partial: 'articles/comment_form')}"); 3 | location.hash = "#comment_form"; -------------------------------------------------------------------------------- /app/views/comments/update.js.haml: -------------------------------------------------------------------------------- 1 | - if @result[:status] 2 | :plain 3 | $("#comment_#{@comment.id}_body").html("#{sanitize(@comment.content)}"); 4 | $('#article_comment_content').val(""); 5 | location.hash = "#comment_#{@comment.id}"; 6 | $("#comment_form").html("#{escape_javascript(render partial: 'articles/comment_form', locals: {new_comment: true})}"); -------------------------------------------------------------------------------- /app/views/kaminari/_first_page.html.haml: -------------------------------------------------------------------------------- 1 | -# Link to the "First" page 2 | -# available local variables 3 | -# url: url to the first page 4 | -# current_page: a page object for the currently displayed page 5 | -# total_pages: total number of pages 6 | -# per_page: number of items to fetch per page 7 | -# remote: data-remote 8 | %li.first 9 | = link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, :remote => remote 10 | -------------------------------------------------------------------------------- /app/views/kaminari/_gap.html.haml: -------------------------------------------------------------------------------- 1 | -# Non-link tag that stands for skipped pages... 2 | -# available local variables 3 | -# current_page: a page object for the currently displayed page 4 | -# total_pages: total number of pages 5 | -# per_page: number of items to fetch per page 6 | -# remote: data-remote 7 | %li.page 8 | %span.page.gap 9 | = t('views.pagination.truncate').html_safe 10 | -------------------------------------------------------------------------------- /app/views/kaminari/_last_page.html.haml: -------------------------------------------------------------------------------- 1 | -# Link to the "Last" page 2 | -# available local variables 3 | -# url: url to the last page 4 | -# current_page: a page object for the currently displayed page 5 | -# total_pages: total number of pages 6 | -# per_page: number of items to fetch per page 7 | -# remote: data-remote 8 | %li.last 9 | = link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, :remote => remote 10 | -------------------------------------------------------------------------------- /app/views/kaminari/_next_page.html.haml: -------------------------------------------------------------------------------- 1 | -# Link to the "Next" page 2 | -# available local variables 3 | -# url: url to the next page 4 | -# current_page: a page object for the currently displayed page 5 | -# total_pages: total number of pages 6 | -# per_page: number of items to fetch per page 7 | -# remote: data-remote 8 | %li.next 9 | = link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, :rel => 'next', :remote => remote 10 | -------------------------------------------------------------------------------- /app/views/kaminari/_page.html.haml: -------------------------------------------------------------------------------- 1 | -# Link showing page number 2 | -# available local variables 3 | -# page: a page object for "this" page 4 | -# url: url to this page 5 | -# current_page: a page object for the currently displayed page 6 | -# total_pages: total number of pages 7 | -# per_page: number of items to fetch per page 8 | -# remote: data-remote 9 | %li{:class => "page#{' active disabled' if page.current?}"} 10 | -#= link_to_unless page.current?, page, url, {:remote => remote, :rel => page.next? ? 'next' : page.prev? ? 'prev' : nil} 11 | - if page.current? 12 | = link_to page, '#' 13 | - else 14 | = link_to page, url, {:remote => remote, :rel => page.next? ? 'next' : page.prev? ? 'prev' : nil} -------------------------------------------------------------------------------- /app/views/kaminari/_paginator.html.haml: -------------------------------------------------------------------------------- 1 | -# The container tag 2 | -# available local variables 3 | -# current_page: a page object for the currently displayed page 4 | -# total_pages: total number of pages 5 | -# per_page: number of items to fetch per page 6 | -# remote: data-remote 7 | -# paginator: the paginator that renders the pagination tags inside 8 | = paginator.render do 9 | .text-center 10 | %ul.pagination.text-center 11 | = first_page_tag unless current_page.first? 12 | = prev_page_tag unless current_page.first? 13 | - each_page do |page| 14 | - if page.left_outer? || page.right_outer? || page.inside_window? 15 | = page_tag page 16 | - elsif !page.was_truncated? 17 | = gap_tag 18 | = next_page_tag unless current_page.last? 19 | = last_page_tag unless current_page.last? 20 | -------------------------------------------------------------------------------- /app/views/kaminari/_prev_page.html.haml: -------------------------------------------------------------------------------- 1 | -# Link to the "Previous" page 2 | -# available local variables 3 | -# url: url to the previous page 4 | -# current_page: a page object for the currently displayed page 5 | -# total_pages: total number of pages 6 | -# per_page: number of items to fetch per page 7 | -# remote: data-remote 8 | %li.prev 9 | = link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, :rel => 'prev', :remote => remote 10 | -------------------------------------------------------------------------------- /app/views/layouts/_footer.html.haml: -------------------------------------------------------------------------------- 1 | .row.text-center.footer 2 | Copyright © 2014 fxhover, All Rights Reserved -------------------------------------------------------------------------------- /app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{charset: "utf-8"} 5 | %meta{"http-equiv"=>"X-UA-Compatible", "content"=>"IE=edge"} 6 | %meta{"name"=>"viewport", "content"=>"width=device-width, initial-scale=1"} 7 | %title= blog_title 8 | = stylesheet_link_tag 'application', media: 'all' 9 | = javascript_include_tag 'application' 10 | = csrf_meta_tags 11 | 12 | %body 13 | .container.wrap_body 14 | %nav.navbar.navbar-default.navbar-fixed-top{role:'navigation'} 15 | .container.container-fluid 16 | .navbar-header 17 | = link_to blog_title, root_path, class: 'navbar-brand' 18 | .collspse.navbar-collapse.site-header 19 | %ul.nav.navbar-nav 20 | %li{class: "#{'active' if params[:controller] == 'articles' && params[:action] == 'index'}"} 21 | = link_to '文章列表', articles_path 22 | %li{class: "#{'active' if params[:controller] == 'blogs' && params[:action] == 'about'}"} 23 | = link_to '关于博主', about_blogs_path 24 | %ul.nav.navbar-nav.navbar-right 25 | - if is_logined? 26 | %li.dropdown 27 | .navbar-header.navbar-avatar 28 | = avatar @current_user 29 | = link_to '#', class: 'dropdown-toggle pull-right', "data-toggle"=>"dropdown" do 30 | = @current_user.nickname 31 | %span.caret 32 | %ul.dropdown-menu{role: 'menu'} 33 | %li 34 | = link_to '设置', set_blogs_path 35 | - if current_user_is_admin? 36 | %li 37 | = link_to '写博客', new_article_path 38 | %li 39 | = link_to '退出', logout_path 40 | - else 41 | %ul.navbar-button.navbar-right 42 | %li 43 | = link_to '登录', login_path, class: 'btn btn-default' 44 | %li 45 | = link_to '注册', register_path, class: 'btn btn-primary' 46 | = form_tag articles_path, method: 'get', class: 'navbar-form pull-right', role: 'search', id: 'search_form' do 47 | .form-group 48 | = text_field_tag 'keyword', params[:keyword], class: 'form-control', placeholder: '请输入关键字' 49 | %a#search_button 50 | %span.icon.icon-search 51 | .container.body_content 52 | = yield 53 | = render partial: 'layouts/footer' 54 | :javascript 55 | $(function(){ 56 | $('#search_button').click(function(){ 57 | $('#search_form').submit(); 58 | }); 59 | }); -------------------------------------------------------------------------------- /app/views/layouts/register.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{charset: "utf-8"} 5 | %meta{"http-equiv"=>"X-UA-Compatible", "content"=>"IE=edge"} 6 | %meta{"name"=>"viewport", "content"=>"width=device-width, initial-scale=1"} 7 | %title= blog_title 8 | = stylesheet_link_tag 'application', media: 'all' 9 | = stylesheet_link_tag 'register', media: 'all' 10 | = javascript_include_tag 'application' 11 | = javascript_include_tag 'http://cdn.bootcss.com/html5shiv/3.7.2/html5shiv.min.js' 12 | = javascript_include_tag 'http://cdn.bootcss.com/respond.js/1.4.2/respond.min.js' 13 | = csrf_meta_tags 14 | 15 | %body 16 | .container.wrap_body 17 | = yield 18 | -------------------------------------------------------------------------------- /app/views/user_mailer/auth_mail.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | = raw "您好,这是来自 #{blog_title} 的激活邮件,请点击链接:#{link_to @url, @url} 来激活您的用户,如果不能点击,请复制链接地址到浏览器打开进行激活。" 3 | %p 4 | = "感谢您对 #{blog_title} 的支持,祝您生活愉快!" -------------------------------------------------------------------------------- /app/views/user_mailer/forget_password.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | = raw "您好,这是来自 #{blog_title} 的找回密码邮件,10分钟之内有效,请点击链接:#{link_to @url, @url} 来重置您的密码,如果不能点击,请复制链接地址到浏览器打开进行激活。" 3 | %p 4 | = "感谢您对 #{blog_title} 的支持,祝您生活愉快!" -------------------------------------------------------------------------------- /app/views/users/_form.html.haml: -------------------------------------------------------------------------------- 1 | - method = action == 'update' ? 'put' : 'post' 2 | - submit_url = case action; when 'register'; register_confirm_users_path; when 'login'; login_confirm_users_path; when 'update'; user_path(@user); end 3 | = form_for @user, method: method, url: submit_url, role: 'form' do |f| 4 | .form-field.form-group 5 | = f.text_field :username, placeholder: '用户名', class: 'form-control username', required: true 6 | .form-field.form-group 7 | = f.text_field :email, placeholder: '邮箱', class: 'form-control email', required: true 8 | .form-field.form-group 9 | = f.password_field :password, placeholder: '密码,最少6位', class: 'form-control password', required: true 10 | .form-field.form-group 11 | = f.password_field :password_confirmation, placeholder: '确认密码', class: 'form-control password', required: true 12 | = submit_tag '注册', class: 'btn btn-primary btn-lg' 13 | .action-wrapper 14 | = link_to '已有账号?', login_path -------------------------------------------------------------------------------- /app/views/users/change_pw.html.haml: -------------------------------------------------------------------------------- 1 | %section.content 2 | .form-unit 3 | = link_to root_path, class: 'brand' do 4 | %h1= blog_title 5 | %h3 重置密码 6 | - if flash[:success].present? 7 | .alert.alert-success 8 | %a.close{"data-dismiss"=>"success"} × 9 | %span 10 | = flash[:success] 11 | - elsif flash[:error].present? 12 | .alert.alert-danger 13 | %a.close{"data-dismiss"=>"alert"} × 14 | %span= raw flash[:error] 15 | = form_for :user, url: change_pw_confirm_users_path, role: 'form' do |f| 16 | .form-field.form-group 17 | = f.hidden_field :token, value: @token 18 | = f.password_field :password, placeholder: '密码', class: 'form-control', required: true 19 | .form-field.form-group 20 | = f.password_field :password_confirmation, placeholder: '确认密码', class: 'form-control password', required: true 21 | = submit_tag '重置', class: 'btn btn-primary btn-lg' -------------------------------------------------------------------------------- /app/views/users/forget_password.html.haml: -------------------------------------------------------------------------------- 1 | %section.content 2 | .form-unit 3 | = link_to root_path, class: 'brand' do 4 | %h1= blog_title 5 | %h3 找回密码 6 | - if flash[:success].present? 7 | .alert.alert-success 8 | %a.close{"data-dismiss"=>"success"} × 9 | %span 10 | = flash[:success] 11 | - elsif flash[:error].present? 12 | .alert.alert-danger 13 | %a.close{"data-dismiss"=>"alert"} × 14 | %span= raw flash[:error] 15 | = form_for :user, url: forget_password_confirm_users_path, role: 'form' do |f| 16 | .form-field.form-group 17 | = f.text_field :email, placeholder: '注册邮箱', class: 'form-control', required: true 18 | = submit_tag '确定', class: 'btn btn-primary btn-lg' -------------------------------------------------------------------------------- /app/views/users/forget_password_confirm.html.haml: -------------------------------------------------------------------------------- 1 | %h2.text-center 2 | - if @result[:status] 3 | = "找回密码邮件发送成功,请登录邮箱#{@result[:message]}进行密码找回。" 4 | - else 5 | = "找回密码邮件发送失败" -------------------------------------------------------------------------------- /app/views/users/login.html.haml: -------------------------------------------------------------------------------- 1 | %section.content 2 | .form-unit 3 | = link_to root_path, class: 'brand' do 4 | %h1= blog_title 5 | %h3 用户登录 6 | - errors = @user ? @user.errors.full_messages : [] 7 | - if errors.any? 8 | .alert.alert-danger 9 | %a.close{"data-dismiss"=>"alert"} × 10 | %span 11 | = errors.first 12 | - if flash[:success].present? 13 | .alert.alert-success 14 | %a.close{"data-dismiss"=>"success"} × 15 | %span 16 | = flash[:success] 17 | - elsif flash[:error].present? 18 | .alert.alert-danger 19 | %a.close{"data-dismiss"=>"alert"} × 20 | %span= raw flash[:error] 21 | = form_for :user, url: login_confirm_users_path(from: params[:from].present? ? params[:from] : ''), role: 'form' do |f| 22 | .form-field.form-group 23 | = f.text_field :username, placeholder: '用户名', class: 'form-control username', required: true 24 | .form-field.form-group 25 | = f.password_field :password, placeholder: '密码', class: 'form-control password', required: true 26 | = submit_tag '登录', class: 'btn btn-primary btn-lg' 27 | .action-wrapper 28 | = link_to '没有账号?', register_path 29 | = link_to '忘记密码?', forget_password_users_path -------------------------------------------------------------------------------- /app/views/users/register.html.haml: -------------------------------------------------------------------------------- 1 | %section.content 2 | .form-unit 3 | = link_to root_path, class: 'brand' do 4 | %h1= blog_title 5 | %h3 注册账号 6 | - errors = @user.errors.full_messages 7 | - if errors.any? 8 | .alert.alert-danger 9 | %a.close{"data-dismiss"=>"alert"} × 10 | %span 11 | = errors.first 12 | = render partial: 'form', locals: {action: 'register'} -------------------------------------------------------------------------------- /app/views/users/send_active_mail.html.haml: -------------------------------------------------------------------------------- 1 | %h2.text-center 2 | - if @result[:status] 3 | = "激活邮件发送成功,请登录邮箱#{@result[:message]}进行激活。" 4 | - else 5 | = "激活邮件发送失败,#{link_to '点此', send_active_mail_users_path}重新发送" -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Blog 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | #Set autoload path 16 | config.autoload_paths += %W(#{config.root}/lib) 17 | 18 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 19 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 20 | config.time_zone = 'Beijing' 21 | config.active_record.default_timezone = :local 22 | 23 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 24 | config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '*.{rb,yml}').to_s] 25 | config.i18n.default_locale = "zh-CN" 26 | 27 | BLOG_CONFIG = YAML.load_file(File.join(Rails.root, 'config/blog.yml'))[Rails.env.to_s] 28 | config.action_mailer.delivery_method = :smtp 29 | config.action_mailer.perform_deliveries = true 30 | config.action_mailer.raise_delivery_errors = true 31 | config.action_mailer.default :charset => "utf-8" 32 | config.action_mailer.default_url_options = {host: BLOG_CONFIG['blog']['domain']} 33 | config.action_mailer.smtp_settings = { 34 | address: BLOG_CONFIG['mailer']['address'], 35 | port: BLOG_CONFIG['mailer']['port'], 36 | user_name: BLOG_CONFIG['mailer']['user_name'], 37 | password: BLOG_CONFIG['mailer']['password'], 38 | authentication: 'plain', 39 | enable_starttls_auto: false 40 | } 41 | 42 | config.action_view.sanitized_allowed_tags = ["table", "tr", "td", "strong", "em", "b", "i", "p", "code", "pre", "tt", "samp", "kbd", "var", "sub", "sup", "dfn", "cite", "big", "small", "address", "hr", "br", "div", "span", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "dl", "dt", "dd", "abbr", "acronym", "a", "img", "blockquote", "del", "ins"] 43 | config.action_view.sanitized_allowed_attributes = ["href", "src", "width", "height", "alt", "cite", "datetime", "title", "class", "name", "xml:lang", "abbr", "style"] 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /config/blog.yml.example: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | defaults: &defaults 3 | blog: 4 | domain: http://localhost:3000 5 | aritcle_page_size: 10 6 | comments_page_size: 10 7 | mailer: 8 | address: smtp.163.com 9 | port: 25 10 | user_name: email_name@163.com 11 | password: email_password 12 | 13 | development: 14 | <<: *defaults 15 | 16 | production: 17 | <<: *defaults -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /config/database.yml.example: -------------------------------------------------------------------------------- 1 | # MySQL. Versions 5.0+ are recommended. 2 | # 3 | # Install the MYSQL driver 4 | # gem install mysql2 5 | # 6 | # Ensure the MySQL gem is defined in your Gemfile 7 | # gem 'mysql2' 8 | # 9 | # And be sure to use new-style password hashing: 10 | # http://dev.mysql.com/doc/refman/5.0/en/old-client.html 11 | # 12 | default: &default 13 | adapter: mysql2 14 | encoding: utf8 15 | pool: 5 16 | username: root 17 | password: "123456" 18 | host: localhost 19 | 20 | development: 21 | <<: *default 22 | database: blog_development 23 | 24 | # Warning: The database defined as "test" will be erased and 25 | # re-generated from your development database when you run "rake". 26 | # Do not set this db to the same as development or production. 27 | test: 28 | <<: *default 29 | database: blog_test 30 | 31 | # As with config/secrets.yml, you never want to store sensitive information, 32 | # like your database password, in your source code. If your source code is 33 | # ever seen by anyone, they now have access to your database. 34 | # 35 | # Instead, provide the password as a unix environment variable when you boot 36 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database 37 | # for a full rundown on how to provide these environment variables in a 38 | # production deployment. 39 | # 40 | # On Heroku and other platform providers, you may have a full connection URL 41 | # available as an environment variable. For example: 42 | # 43 | # DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase" 44 | # 45 | # You can use this database configuration with: 46 | # 47 | # production: 48 | # url: <%= ENV['DATABASE_URL'] %> 49 | # 50 | production: 51 | <<: *default 52 | database: blog_production 53 | username: blog 54 | password: "123456" 55 | -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | # config valid only for current version of Capistrano 2 | lock '3.6.1' 3 | 4 | set :application, 'blog' 5 | set :repo_url, 'https://github.com/fxhover/blog.git' 6 | 7 | # Default branch is :master 8 | # ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp 9 | 10 | server '127.0.0.1', user: 'fangxiang', roles: %w{web app db} 11 | set :use_sudo, true 12 | 13 | # Default deploy_to directory is /var/www/my_app_name 14 | set :deploy_to, '/var/www/blog' 15 | 16 | # Default value for :scm is :git 17 | set :scm, :git 18 | set :branch, 'master' 19 | 20 | # Default value for :log_level is :debug 21 | set :log_level, :debug 22 | 23 | # Rvm 24 | set :rvm_type, :user # Defaults to: :auto 25 | set :rvm_ruby_version, '2.1.2' # Defaults to: 'default' 26 | 27 | # Default value for :format is :airbrussh. 28 | # set :format, :airbrussh 29 | 30 | # You can configure the Airbrussh format using :format_options. 31 | # These are the defaults. 32 | set :format_options, command_output: true, log_file: 'log/capistrano.log', color: :auto, truncate: :auto 33 | 34 | # Default value for :pty is false 35 | # set :pty, true 36 | 37 | # Default value for :linked_files is [] 38 | append :linked_files, 'config/database.yml', 'config/secrets.yml', 'config/blog.yml', 'config/unicorn.rb' 39 | 40 | # Default value for linked_dirs is [] 41 | append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'public/system', 'vendor/bundle' 42 | 43 | # Default value for default_env is {} 44 | #set :default_env, { path: "/opt/ruby/bin:$PATH" } 45 | 46 | # Default value for keep_releases is 5 47 | # set :keep_releases, 5 48 | 49 | # Rails environment 50 | set :rails_env, 'production' 51 | 52 | # Unicorn config file path 53 | set :unicorn_config_path, 'config/unicorn.rb' 54 | 55 | set :enable_ssl, false 56 | 57 | after 'deploy:publishing', 'deploy:restart' 58 | 59 | namespace :deploy do 60 | task :restart do 61 | invoke 'unicorn:reload' 62 | end 63 | end -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Adds additional error checking when serving assets at runtime. 31 | # Checks for improperly declared sprockets dependencies. 32 | # Raises helpful error messages. 33 | config.assets.raise_runtime_errors = true 34 | 35 | # Raises error for missing translations 36 | # config.action_view.raise_on_missing_translations = true 37 | end 38 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_assets = true 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.compress = true 27 | config.assets.js_compressor = :uglifier 28 | #config.assets.css_compressor = :sass 29 | 30 | # Do not fallback to assets pipeline if a precompiled asset is missed. 31 | config.assets.compile = false 32 | 33 | # Generate digests for assets URLs. 34 | config.assets.digest = true 35 | 36 | # `config.assets.precompile` has moved to config/initializers/assets.rb 37 | 38 | # Specifies the header that your server uses for sending files. 39 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | # config.force_ssl = true 44 | 45 | # Set to :debug to see everything in the log. 46 | config.log_level = :info 47 | 48 | # Prepend all log lines with the following tags. 49 | # config.log_tags = [ :subdomain, :uuid ] 50 | 51 | # Use a different logger for distributed setups. 52 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 53 | 54 | # Use a different cache store in production. 55 | # config.cache_store = :mem_cache_store 56 | 57 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 58 | # config.action_controller.asset_host = "http://assets.example.com" 59 | 60 | # Precompile additional assets. 61 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 62 | # config.assets.precompile += %w( search.js ) 63 | 64 | # Ignore bad email addresses and do not raise email delivery errors. 65 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 66 | # config.action_mailer.raise_delivery_errors = false 67 | 68 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 69 | # the I18n.default_locale when a translation cannot be found). 70 | config.i18n.fallbacks = true 71 | 72 | # Send deprecation notices to registered listeners. 73 | config.active_support.deprecation = :notify 74 | 75 | # Disable automatic flushing of the log to improve performance. 76 | # config.autoflush_log = false 77 | 78 | # Use default logging formatter so that PID and timestamp are not suppressed. 79 | config.log_formatter = ::Logger::Formatter.new 80 | 81 | # Do not dump schema after migrations. 82 | config.active_record.dump_schema_after_migration = false 83 | end 84 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | # Raises error for missing translations 38 | # config.action_view.raise_on_missing_translations = true 39 | end 40 | -------------------------------------------------------------------------------- /config/initializers/1_settings.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class Settings < Settingslogic 3 | source "#{Rails.root}/config/blog.yml" 4 | namespace Rails.env 5 | end -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Precompile additional assets. 7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 8 | # Rails.application.config.assets.precompile += %w( search.js ) 9 | 10 | #Rails.application.config.assets.paths << Rails.root.join('vendor', 'assets') 11 | 12 | Rails.application.config.assets.precompile += %w( register.css *.png) 13 | 14 | Rails.application.config.assets.precompile << Proc.new { |path| 15 | if path =~ /\.(css|js|png)\z/ 16 | full_path = Rails.application.assets.resolve(path).to_path 17 | app_assets_path = Rails.root.join('app', 'assets').to_path 18 | vendor_assets_path = Rails.root.join('vendor', 'assets').to_path 19 | if full_path.starts_with? app_assets_path or full_path.starts_with? vendor_assets_path 20 | puts "including asset: " + full_path 21 | true 22 | else 23 | puts "excluding asset: " + full_path 24 | false 25 | end 26 | else 27 | false 28 | end 29 | } 30 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_blog_session' 4 | -------------------------------------------------------------------------------- /config/initializers/social_share_button.rb: -------------------------------------------------------------------------------- 1 | SocialShareButton.configure do |config| 2 | config.allow_sites = %w(twitter facebook weibo douban tqq renren qq kaixin001 baidu) 3 | end 4 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/article.zh-CN.yml: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | zh-CN: 3 | activerecord: 4 | models: 5 | article: '文章' 6 | attributes: 7 | article: 8 | title: '标题' 9 | tags: '标签' 10 | category_id: '分类' 11 | source: '来源' 12 | content: '内容' 13 | -------------------------------------------------------------------------------- /config/locales/article_comment.zh-CN.yml: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | zh-CN: 3 | activerecord: 4 | models: 5 | article_comment: '评论' 6 | attributes: 7 | article_comment: 8 | content: '评论内容' 9 | -------------------------------------------------------------------------------- /config/locales/blog_info.zh-CN.yml: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | zh-CN: 3 | activerecord: 4 | models: 5 | blog_info: '博客' 6 | attributes: 7 | blog_info: 8 | blog_title: '博客名称' 9 | name: '博主名称' 10 | description: '关于' 11 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/locales/pagination.zh-CN.yml: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | zh-CN: 3 | views: 4 | pagination: 5 | first: "首页" 6 | previous: "上一页" 7 | truncate: "..." 8 | next: "下一页" 9 | last: "末页" 10 | -------------------------------------------------------------------------------- /config/locales/social_share_button.zh-CN.yml: -------------------------------------------------------------------------------- 1 | 'zh-CN': 2 | social_share_button: 3 | share_to: 分享到 %{name} 4 | weibo: 新浪微博 5 | twitter: Twitter 6 | facebook: Facebook 7 | douban: 豆瓣 8 | qq: QQ空间 9 | tqq: 腾讯微博 10 | delicious: Delicious 11 | baidu: 百度收藏 12 | kaixin001: 开心网 13 | renren: 人人网 14 | google_plus: Google+ 15 | google_bookmark: Google 收藏 16 | tumblr: Tumblr 17 | plurk: Plurk 18 | pinterest: Pinterest 19 | email: Email 20 | -------------------------------------------------------------------------------- /config/locales/user.zh-CN.yml: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | zh-CN: 3 | activerecord: 4 | models: 5 | user: '用户' 6 | attributes: 7 | user: 8 | username: '用户名' 9 | email: '邮箱' 10 | password: '密码' 11 | password_confirmation: '确认密码' 12 | nick_name: '昵称' -------------------------------------------------------------------------------- /config/locales/zh-CN.yml: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | zh-CN: 3 | activerecord: 4 | errors: 5 | messages: 6 | inclusion: "不包含于列表中" 7 | exclusion: "是保留关键字" 8 | invalid: "是无效的" 9 | confirmation: "与确认值不匹配" 10 | accepted: "必须是可被接受的" 11 | empty: "不能留空" 12 | blank: "不能为空字符" 13 | too_long: "过长(最长为 %{count} 个字符)" 14 | too_short: "过短(最短为 %{count} 个字符)" 15 | wrong_length: "长度非法(必须为 %{count} 个字符)" 16 | taken: "已经被使用" 17 | not_a_number: "不是数字" 18 | not_an_integer: "必须是整数" 19 | greater_than: "必须大于 %{count}" 20 | greater_than_or_equal_to: "必须大于或等于 %{count}" 21 | equal_to: "必须等于 %{count}" 22 | less_than: "必须小于 %{count}" 23 | less_than_or_equal_to: "必须小于或等于 %{count}" 24 | odd: "必须为单数" 25 | even: "必须为双数" 26 | record_invalid: "校验失败: %{errors}" 27 | taken: "已占用" 28 | validations: "校验失败 - %{errors}." 29 | models: 30 | picture: 31 | attributes: 32 | attachment: 33 | size_too_big: "图片太大" 34 | errors: 35 | messages: 36 | extension_white_list_error: "上传图片格式不正确!" 37 | 38 | date: 39 | abbr_day_names: 40 | - 日 41 | - 一 42 | - 二 43 | - 三 44 | - 四 45 | - 五 46 | - 六 47 | abbr_month_names: 48 | - 49 | - 1月 50 | - 2月 51 | - 3月 52 | - 4月 53 | - 5月 54 | - 6月 55 | - 7月 56 | - 8月 57 | - 9月 58 | - 10月 59 | - 11月 60 | - 12月 61 | day_names: 62 | - 星期日 63 | - 星期一 64 | - 星期二 65 | - 星期三 66 | - 星期四 67 | - 星期五 68 | - 星期六 69 | formats: 70 | default: ! '%Y-%m-%d' 71 | long: ! '%Y年%m月%d日' 72 | short: ! '%b%d日' 73 | month_names: 74 | - 75 | - 一月 76 | - 二月 77 | - 三月 78 | - 四月 79 | - 五月 80 | - 六月 81 | - 七月 82 | - 八月 83 | - 九月 84 | - 十月 85 | - 十一月 86 | - 十二月 87 | order: 88 | - :year 89 | - :month 90 | - :day 91 | datetime: 92 | distance_in_words: 93 | about_x_hours: 94 | one: 大约一小时 95 | other: 大约 %{count} 小时 96 | about_x_months: 97 | one: 大约一个月 98 | other: 大约 %{count} 个月 99 | about_x_years: 100 | one: 大约一年 101 | other: 大约 %{count} 年 102 | almost_x_years: 103 | one: 接近一年 104 | other: 接近 %{count} 年 105 | half_a_minute: 半分钟 106 | less_than_x_minutes: 107 | one: 不到一分钟 108 | other: 不到 %{count} 分钟 109 | less_than_x_seconds: 110 | one: 不到一秒 111 | other: 不到 %{count} 秒 112 | over_x_years: 113 | one: 一年多 114 | other: ! '%{count} 年多' 115 | x_days: '%{count} 天' 116 | x_minutes: '%{count} 分钟' 117 | x_months: '%{count} 个月' 118 | x_seconds: '%{count} 秒' 119 | prompts: 120 | day: 日 121 | hour: 时 122 | minute: 分 123 | month: 月 124 | second: 秒 125 | year: 年 126 | misc: 127 | make_sure: "确认" 128 | cancel: "取消" 129 | save: "保 存" 130 | remove: "移除" 131 | edit: "编辑" 132 | already_exist: "已经存在了。" 133 | return: "返回" 134 | colon: ":" 135 | delete: "删除" 136 | update: "更新" 137 | close: "关闭" 138 | reopen: "重新打开" 139 | preview: "预览" 140 | number: 141 | currency: 142 | format: 143 | delimiter: ! ',' 144 | format: ! '%u %n' 145 | precision: 2 146 | separator: . 147 | significant: false 148 | strip_insignificant_zeros: false 149 | unit: CN¥ 150 | format: 151 | delimiter: ! ',' 152 | precision: 3 153 | separator: . 154 | significant: false 155 | strip_insignificant_zeros: false 156 | human: 157 | decimal_units: 158 | format: ! '%n %u' 159 | units: 160 | billion: 十亿 161 | million: 百万 162 | quadrillion: 千兆 163 | thousand: 千 164 | trillion: 兆 165 | unit: '' 166 | format: 167 | delimiter: '' 168 | precision: 1 169 | significant: false 170 | strip_insignificant_zeros: false 171 | storage_units: 172 | format: ! '%n %u' 173 | units: 174 | byte: 175 | one: Byte 176 | other: Bytes 177 | gb: GB 178 | kb: KB 179 | mb: MB 180 | tb: TB 181 | percentage: 182 | format: 183 | delimiter: '' 184 | precision: 185 | format: 186 | delimiter: '' 187 | head: 188 | meta: 189 | content: "全球最大中文IT社区" 190 | header: 191 | search: "搜索" 192 | find: "发现" 193 | boke: "博客" 194 | help: "帮助" 195 | sys_notic: "系统通知" 196 | project_monber: "项目成员" 197 | notic_attenion: "关注通知" 198 | peace_request: "合并请求" 199 | commit: "提交" 200 | work_order: "Issue" 201 | account_set: "账号设置" 202 | new_project: "创建项目" 203 | new_project_doc: "创建文档" 204 | new_organization: "创建组织" 205 | logout: "退出" 206 | login: "登录" 207 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # The priority is based upon order of creation: first created -> highest priority. 3 | # See how all your routes lay out with "rake routes". 4 | 5 | # You can have the root of your site routed with "root" 6 | # root 'welcome#index' 7 | 8 | # Example of regular route: 9 | # get 'products/:id' => 'catalog#view' 10 | 11 | # Example of named route that can be invoked with purchase_url(id: product.id) 12 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase 13 | 14 | # Example resource route (maps HTTP verbs to controller actions automatically): 15 | # resources :products 16 | 17 | # Example resource route with options: 18 | # resources :products do 19 | # member do 20 | # get 'short' 21 | # post 'toggle' 22 | # end 23 | # 24 | # collection do 25 | # get 'sold' 26 | # end 27 | # end 28 | 29 | # Example resource route with sub-resources: 30 | # resources :products do 31 | # resources :comments, :sales 32 | # resource :seller 33 | # end 34 | 35 | # Example resource route with more complex sub-resources: 36 | # resources :products do 37 | # resources :comments 38 | # resources :sales do 39 | # get 'recent', on: :collection 40 | # end 41 | # end 42 | 43 | # Example resource route with concerns: 44 | # concern :toggleable do 45 | # post 'toggle' 46 | # end 47 | # resources :posts, concerns: :toggleable 48 | # resources :photos, concerns: :toggleable 49 | 50 | # Example resource route within a namespace: 51 | # namespace :admin do 52 | # # Directs /admin/products/* to Admin::ProductsController 53 | # # (app/controllers/admin/products_controller.rb) 54 | # resources :products 55 | # end 56 | 57 | root "blogs#index" 58 | 59 | resources :blogs, only:[:index] do 60 | collection do 61 | post 'upload_img' 62 | get 'set' 63 | post 'set_userinfo' 64 | get 'set_blog' 65 | post 'update_blog' 66 | get 'about' 67 | get 'change_password' 68 | post 'update_password' 69 | post 'preview' 70 | end 71 | 72 | end 73 | 74 | resources :articles do 75 | member do 76 | post 'star' 77 | end 78 | 79 | resources :comments 80 | end 81 | 82 | get 'register' => 'users#register' 83 | get 'login' => 'users#login' 84 | get 'logout' => 'users#logout' 85 | resources :users do 86 | collection do 87 | get 'register' 88 | post 'register_confirm' 89 | get 'login' 90 | post 'login_confirm' 91 | get 'logout' 92 | get 'send_active_mail' 93 | get 'activation' 94 | get 'forget_password' 95 | post 'forget_password_confirm' 96 | get 'change_pw' 97 | post 'change_pw_confirm' 98 | end 99 | end 100 | 101 | end 102 | -------------------------------------------------------------------------------- /config/secrets.yml.example: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 6938efe794286cdef1a2929153f78aa17f3005ab4e671e0ba21bf09057b448152f1725ca939d77d392bec467487e40e4d7e9e9ed9ba785e746e80c5dea106e0e 15 | 16 | test: 17 | secret_key_base: 38e245a613b00c6701f3525867eb0c2e0b9d8f1395d54a2d7a3c1e3f35480fa331272f5cf1a74a130a60ed922131a00851c09a5819330652cd73e85378d0ca0a 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /config/unicorn.rb.example: -------------------------------------------------------------------------------- 1 | # path to app dir 2 | app_dir = "/home/fangxiang/blog/" 3 | worker_processes 2 4 | working_directory app_dir 5 | 6 | # Load app into the master before forking workers for super-fast 7 | # worker spawn times 8 | preload_app true 9 | 10 | # nuke workers after 30 seconds (60 is the default) 11 | timeout 30 12 | 13 | # listen on a Unix domain socket and/or a TCP port, 14 | 15 | #listen 8080 # listen to port 8080 on all TCP interfaces 16 | #listen "127.0.0.1:8080" # listen to port 8080 on the loopback interface 17 | listen "#{app_dir}/tmp/sockets/blog.socket" 18 | 19 | pid "#{app_dir}/tmp/pids/unicorn.pid" 20 | stderr_path "#{app_dir}/log/unicorn.stderr.log" 21 | stdout_path "#{app_dir}/log/unicorn.stdout.log" 22 | 23 | # http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow 24 | if GC.respond_to?(:copy_on_write_friendly=) 25 | GC.copy_on_write_friendly = true 26 | end 27 | 28 | 29 | before_fork do |server, worker| 30 | # the following is highly recomended for Rails + "preload_app true" 31 | # as there's no need for the master process to hold a connection 32 | # defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! 33 | 34 | ## 35 | # When sent a USR2, Unicorn will suffix its pidfile with .oldbin and 36 | # immediately start loading up a new version of itself (loaded with a new 37 | # version of our app). When this new Unicorn is completely loaded 38 | # it will begin spawning workers. The first worker spawned will check to 39 | # see if an .oldbin pidfile exists. If so, this means we've just booted up 40 | # a new Unicorn and need to tell the old one that it can now die. To do so 41 | # we send it a QUIT. 42 | # 43 | # Using this method we get 0 downtime deploys. 44 | 45 | old_pid = "#{server.config[:pid]}.oldbin" 46 | 47 | if File.exists?(old_pid) && server.pid != old_pid 48 | begin 49 | sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU 50 | Process.kill(sig, File.read(old_pid).to_i) 51 | rescue Errno::ENOENT, Errno::ESRCH 52 | # someone else did our job for us 53 | end 54 | end 55 | end 56 | 57 | after_fork do |server, worker| 58 | # Unicorn master loads the app then forks off workers - because of the way 59 | # Unix forking works, we need to make sure we aren't using any of the parent's 60 | # sockets, e.g. db connection 61 | 62 | # defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection 63 | # Redis and Memcached would go here but their connections are established 64 | # on demand, so the master never opens a socket 65 | end 66 | 67 | -------------------------------------------------------------------------------- /config/unicorn_capistrano.rb: -------------------------------------------------------------------------------- 1 | # path to app dir 2 | worker_processes 2 3 | 4 | # Load app into the master before forking workers for super-fast 5 | # worker spawn times 6 | preload_app true 7 | 8 | # nuke workers after 30 seconds (60 is the default) 9 | timeout 30 10 | 11 | # listen on a Unix domain socket and/or a TCP port, 12 | 13 | listen 8088 # listen to port 8080 on all TCP interfaces 14 | #listen "127.0.0.1:8080" # listen to port 8080 on the loopback interface 15 | #listen "#{app_dir}/tmp/sockets/blog.socket" 16 | 17 | pid "tmp/pids/unicorn.pid" 18 | stderr_path "log/unicorn.stderr.log" 19 | stdout_path "log/unicorn.stdout.log" 20 | 21 | # http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow 22 | if GC.respond_to?(:copy_on_write_friendly=) 23 | GC.copy_on_write_friendly = true 24 | end 25 | 26 | 27 | before_fork do |server, worker| 28 | # the following is highly recomended for Rails + "preload_app true" 29 | # as there's no need for the master process to hold a connection 30 | # defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! 31 | 32 | ## 33 | # When sent a USR2, Unicorn will suffix its pidfile with .oldbin and 34 | # immediately start loading up a new version of itself (loaded with a new 35 | # version of our app). When this new Unicorn is completely loaded 36 | # it will begin spawning workers. The first worker spawned will check to 37 | # see if an .oldbin pidfile exists. If so, this means we've just booted up 38 | # a new Unicorn and need to tell the old one that it can now die. To do so 39 | # we send it a QUIT. 40 | # 41 | # Using this method we get 0 downtime deploys. 42 | 43 | old_pid = "#{server.config[:pid]}.oldbin" 44 | 45 | if File.exists?(old_pid) && server.pid != old_pid 46 | begin 47 | sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU 48 | Process.kill(sig, File.read(old_pid).to_i) 49 | rescue Errno::ENOENT, Errno::ESRCH 50 | # someone else did our job for us 51 | end 52 | end 53 | end 54 | 55 | after_fork do |server, worker| 56 | # Unicorn master loads the app then forks off workers - because of the way 57 | # Unix forking works, we need to make sure we aren't using any of the parent's 58 | # sockets, e.g. db connection 59 | 60 | # defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection 61 | # Redis and Memcached would go here but their connections are established 62 | # on demand, so the master never opens a socket 63 | end 64 | 65 | -------------------------------------------------------------------------------- /db/migrate/20140825134700_create_users.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class CreateUsers < ActiveRecord::Migration 3 | def change 4 | create_table :users do |t| 5 | t.string :username 6 | t.string :password_digest 7 | t.string :email 8 | t.boolean :admin 9 | t.datetime :last_login_time 10 | t.datetime :last_reply_time 11 | t.timestamps 12 | end 13 | add_index :users, :username 14 | add_index :users, :email 15 | end 16 | end -------------------------------------------------------------------------------- /db/migrate/20140826093600_create_articles.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class CreateArticles < ActiveRecord::Migration 3 | def change 4 | create_table :articles do |t| 5 | t.integer :user_id 6 | t.string :title 7 | t.string :tags 8 | t.text :content 9 | t.integer :view_count 10 | t.integer :star_count 11 | t.integer :comments_count 12 | t.timestamps 13 | end 14 | add_index :articles, :user_id 15 | add_index :articles, :title 16 | add_index :articles, :view_count 17 | add_index :articles, :star_count 18 | add_index :articles, :comments_count 19 | add_index :articles, :created_at 20 | end 21 | end -------------------------------------------------------------------------------- /db/migrate/20140826093700_create_categories.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class CreateCategories < ActiveRecord::Migration 3 | def change 4 | create_table :categories do |t| 5 | t.string :name 6 | t.integer :articles_count 7 | t.timestamps 8 | end 9 | add_index :categories, :articles_count 10 | end 11 | end -------------------------------------------------------------------------------- /db/migrate/20140826125700_add_source_column_to_articles.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class AddSourceColumnToArticles < ActiveRecord::Migration 3 | def change 4 | add_column :articles, :source, :string 5 | end 6 | end -------------------------------------------------------------------------------- /db/migrate/20140826132200_add_category_id_column_to_articles.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class AddCategoryIdColumnToArticles < ActiveRecord::Migration 3 | def change 4 | add_column :articles, :category_id, :integer 5 | end 6 | end -------------------------------------------------------------------------------- /db/migrate/20140827095400_create_article_stars.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class CreateArticleStars < ActiveRecord::Migration 3 | def change 4 | create_table :article_stars do |t| 5 | t.integer :article_id 6 | t.integer :user_id 7 | t.timestamps 8 | end 9 | add_index :article_stars, [:article_id, :user_id] 10 | end 11 | end -------------------------------------------------------------------------------- /db/migrate/20140827125400_create_article_views.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class CreateArticleViews < ActiveRecord::Migration 3 | def change 4 | create_table :article_views do |t| 5 | t.integer :article_id 6 | t.integer :user_id 7 | t.string :ip 8 | t.text :param_string 9 | t.timestamps 10 | end 11 | add_index :article_views, [:article_id, :ip, :created_at] 12 | end 13 | end -------------------------------------------------------------------------------- /db/migrate/20140827134600_create_article_comments.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class CreateArticleComments < ActiveRecord::Migration 3 | def change 4 | create_table :article_comments do |t| 5 | t.integer :article_id 6 | t.integer :user_id 7 | t.text :content 8 | t.timestamps 9 | end 10 | add_index :article_comments, :article_id 11 | add_index :article_comments, :user_id 12 | end 13 | end -------------------------------------------------------------------------------- /db/migrate/20140831174700_add_nickname_and_avatar_column_to_users.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class AddNicknameAndAvatarColumnToUsers < ActiveRecord::Migration 3 | def change 4 | add_column :users, :nick_name, :string 5 | add_column :users, :avatar, :string 6 | add_index :users, :nick_name 7 | end 8 | end -------------------------------------------------------------------------------- /db/migrate/20140831194600_create_blog_info.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class CreateBlogInfo < ActiveRecord::Migration 3 | def change 4 | create_table :blog_info do |t| 5 | t.string :name 6 | t.string :blog_title 7 | t.string :email 8 | t.text :description 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /db/migrate/20140908084800_add_activation_column_to_users.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class AddActivationColumnToUsers < ActiveRecord::Migration 3 | def change 4 | add_column :users, :activation, :boolean 5 | end 6 | end -------------------------------------------------------------------------------- /db/migrate/20140908085400_create_user_actives.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | class CreateUserActives < ActiveRecord::Migration 3 | def change 4 | create_table :user_actives do |t| 5 | t.integer :user_id 6 | t.string :type_name 7 | t.string :token 8 | t.boolean :used 9 | t.timestamps 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20140908085400) do 15 | 16 | create_table "aritle_views", force: true do |t| 17 | t.integer "article_id" 18 | t.integer "user_id" 19 | t.string "ip" 20 | t.datetime "created_at" 21 | t.datetime "updated_at" 22 | end 23 | 24 | create_table "article_comments", force: true do |t| 25 | t.integer "article_id" 26 | t.integer "user_id" 27 | t.text "content" 28 | t.datetime "created_at" 29 | t.datetime "updated_at" 30 | end 31 | 32 | add_index "article_comments", ["article_id"], name: "index_article_comments_on_article_id", using: :btree 33 | add_index "article_comments", ["user_id"], name: "index_article_comments_on_user_id", using: :btree 34 | 35 | create_table "article_stars", force: true do |t| 36 | t.integer "article_id" 37 | t.integer "user_id" 38 | t.datetime "created_at" 39 | t.datetime "updated_at" 40 | end 41 | 42 | add_index "article_stars", ["article_id", "user_id"], name: "index_article_stars_on_article_id_and_user_id", using: :btree 43 | 44 | create_table "article_views", force: true do |t| 45 | t.integer "article_id" 46 | t.integer "user_id" 47 | t.string "ip" 48 | t.text "param_string" 49 | t.datetime "created_at" 50 | t.datetime "updated_at" 51 | end 52 | 53 | add_index "article_views", ["article_id", "ip", "created_at"], name: "index_article_views_on_article_id_and_ip_and_created_at", using: :btree 54 | 55 | create_table "articles", force: true do |t| 56 | t.integer "user_id" 57 | t.string "title" 58 | t.string "tags" 59 | t.text "content" 60 | t.integer "view_count" 61 | t.integer "star_count" 62 | t.integer "comments_count" 63 | t.datetime "created_at" 64 | t.datetime "updated_at" 65 | t.string "source" 66 | t.integer "category_id" 67 | end 68 | 69 | add_index "articles", ["comments_count"], name: "index_articles_on_comments_count", using: :btree 70 | add_index "articles", ["created_at"], name: "index_articles_on_created_at", using: :btree 71 | add_index "articles", ["star_count"], name: "index_articles_on_star_count", using: :btree 72 | add_index "articles", ["title"], name: "index_articles_on_title", using: :btree 73 | add_index "articles", ["user_id"], name: "index_articles_on_user_id", using: :btree 74 | add_index "articles", ["view_count"], name: "index_articles_on_view_count", using: :btree 75 | 76 | create_table "blog_info", force: true do |t| 77 | t.string "name" 78 | t.string "blog_title" 79 | t.string "email" 80 | t.text "description" 81 | end 82 | 83 | create_table "categories", force: true do |t| 84 | t.string "name" 85 | t.integer "articles_count" 86 | t.datetime "created_at" 87 | t.datetime "updated_at" 88 | end 89 | 90 | add_index "categories", ["articles_count"], name: "index_categories_on_articles_count", using: :btree 91 | 92 | create_table "user_actives", force: true do |t| 93 | t.integer "user_id" 94 | t.string "type_name" 95 | t.string "token" 96 | t.boolean "used" 97 | t.datetime "created_at" 98 | t.datetime "updated_at" 99 | end 100 | 101 | create_table "users", force: true do |t| 102 | t.string "username" 103 | t.string "password_digest" 104 | t.string "email" 105 | t.boolean "admin" 106 | t.datetime "last_login_time" 107 | t.datetime "last_reply_time" 108 | t.datetime "created_at" 109 | t.datetime "updated_at" 110 | t.string "nick_name" 111 | t.string "avatar" 112 | t.boolean "activation" 113 | end 114 | 115 | add_index "users", ["email"], name: "index_users_on_email", using: :btree 116 | add_index "users", ["nick_name"], name: "index_users_on_nick_name", using: :btree 117 | add_index "users", ["username"], name: "index_users_on_username", using: :btree 118 | 119 | end 120 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | 9 | #添加管理员 10 | admin = User.new username: 'admin', password: 'adminqwe', password_confirmation: 'adminqwe', email: '736698959@qq.com', admin: true, activation: 1 11 | puts admin.save! ? 'add admin success.' : 'add admin fail!' 12 | 13 | #添加一个默认分类 14 | category = Category.new name: '默认分类' 15 | puts category.save! ? 'add category success.' : 'add category fail!' -------------------------------------------------------------------------------- /deployment/init.d/blog: -------------------------------------------------------------------------------- 1 | ### BEGIN INIT INFO 2 | # Provides: code 3 | # Required-Start: $local_fs $remote_fs $network $syslog redis-server 4 | # Required-Stop: $local_fs $remote_fs $network $syslog 5 | # Default-Start: 2 3 4 5 6 | # Default-Stop: 0 1 6 7 | # Short-Description: Code git repository management 8 | # Description: Code git repository management 9 | ### END INIT INFO 10 | 11 | 12 | APP_ROOT="/home/fangxiang/blog" 13 | DAEMON_OPTS="-c $APP_ROOT/config/unicorn.rb -E production" 14 | PID_PATH="$APP_ROOT/tmp/pids" 15 | UNICORN_PID="$PID_PATH/unicorn.pid" 16 | NAME="unicorn" 17 | DESC="CSDN Group service" 18 | 19 | check_pid(){ 20 | if [ -f $UNICORN_PID ]; then 21 | PID=`cat $UNICORN_PID` 22 | STATUS=`ps aux | grep $PID | grep -v grep | wc -l` 23 | else 24 | STATUS=0 25 | PID=0 26 | fi 27 | } 28 | 29 | start() { 30 | cd $APP_ROOT 31 | check_pid 32 | if [ "$PID" -ne 0 -a "$STATUS" -ne 0 ]; then 33 | # Program is running, exit with error code 1. 34 | echo "Error! $DESC $NAME is currently running!" 35 | exit 1 36 | else 37 | if [ `whoami` = root ]; then 38 | sudo -u fangxiang -H bash -l -c "nohup bundle exec unicorn $DAEMON_OPTS > /dev/null 2>&1 &" 39 | echo "$DESC started" 40 | fi 41 | fi 42 | } 43 | 44 | stop() { 45 | cd $APP_ROOT 46 | check_pid 47 | if [ "$PID" -ne 0 -a "$STATUS" -ne 0 ]; then 48 | ## Program is running, stop it. 49 | kill -QUIT `cat $UNICORN_PID` 50 | rm "$UNICORN_PID" >> /dev/null 51 | echo "$DESC stopped" 52 | else 53 | ## Program is not running, exit with error. 54 | echo "Error! $DESC not started!" 55 | exit 1 56 | fi 57 | } 58 | 59 | restart() { 60 | cd $APP_ROOT 61 | check_pid 62 | if [ "$PID" -ne 0 -a "$STATUS" -ne 0 ]; then 63 | echo "Restarting $DESC..." 64 | kill -USR2 `cat $UNICORN_PID` 65 | echo "$DESC restarted." 66 | else 67 | echo "Error, $NAME not running!" 68 | exit 1 69 | fi 70 | } 71 | 72 | status() { 73 | cd $APP_ROOT 74 | check_pid 75 | if [ "$PID" -ne 0 -a "$STATUS" -ne 0 ]; then 76 | echo "$DESC / Unicorn with PID $PID is running." 77 | else 78 | echo "$DESC is not running." 79 | exit 1 80 | fi 81 | } 82 | 83 | ## Check to see if we are running as root first. 84 | ## Found at http://www.cyberciti.biz/tips/shell-root-user-check-script.html 85 | if [ "$(id -u)" != "0" ]; then 86 | echo "This script must be run as root" 87 | exit 1 88 | fi 89 | 90 | case "$1" in 91 | start) 92 | start 93 | ;; 94 | stop) 95 | stop 96 | ;; 97 | restart) 98 | restart 99 | ;; 100 | reload|force-reload) 101 | echo -n "Reloading $NAME configuration: " 102 | kill -HUP `cat $PID` 103 | echo "done." 104 | ;; 105 | status) 106 | status 107 | ;; 108 | *) 109 | echo "Usage: sudo service code {start|stop|restart|reload}" >&2 110 | exit 1 111 | ;; 112 | esac 113 | 114 | exit 0 115 | 116 | -------------------------------------------------------------------------------- /deployment/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | #user nobody; 2 | worker_processes 1; 3 | 4 | #error_log logs/error.log; 5 | #error_log logs/error.log notice; 6 | #error_log logs/error.log info; 7 | 8 | #pid logs/nginx.pid; 9 | 10 | 11 | events { 12 | worker_connections 1024; 13 | } 14 | 15 | 16 | http { 17 | include mime.types; 18 | default_type application/octet-stream; 19 | 20 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 21 | # '$status $body_bytes_sent "$http_referer" ' 22 | # '"$http_user_agent" "$http_x_forwarded_for"'; 23 | 24 | #access_log logs/access.log main; 25 | 26 | sendfile on; 27 | #tcp_nopush on; 28 | 29 | #keepalive_timeout 0; 30 | keepalive_timeout 65; 31 | 32 | gzip on; 33 | gzip_types application/json; 34 | 35 | upstream blog { 36 | server unix:/home/fangxiang/blog/tmp/sockets/blog.socket; 37 | } 38 | server { 39 | listen 80; 40 | server_name blog.com; 41 | root /home/fangxiang/blog/public; 42 | try_files $uri/index.html $uri.html $uri @user1; 43 | location @user1 { 44 | proxy_redirect off; 45 | proxy_set_header Host $host; 46 | proxy_set_header X-Forwarded-Host $host; 47 | proxy_set_header X-Forwarded-Server $host; 48 | proxy_set_header X-Real-Ip $remote_addr; 49 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 50 | proxy_buffering on; 51 | proxy_pass http://blog; 52 | } 53 | location ~ ^(/assets) { 54 | access_log off; 55 | expires max; 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /doc/articles_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/doc/articles_list.png -------------------------------------------------------------------------------- /doc/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/doc/index.png -------------------------------------------------------------------------------- /doc/new_article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/doc/new_article.png -------------------------------------------------------------------------------- /doc/set1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/doc/set1.png -------------------------------------------------------------------------------- /doc/set2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/doc/set2.png -------------------------------------------------------------------------------- /doc/show_article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/doc/show_article.png -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 404 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

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

63 |
64 |

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

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

The change you wanted was rejected.

62 |

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

63 |
64 |

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

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 500 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

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

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/public/favicon.ico -------------------------------------------------------------------------------- /public/gavatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/public/gavatar.jpg -------------------------------------------------------------------------------- /public/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/public/images/.gitkeep -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/test/controllers/.keep -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/test/fixtures/.keep -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/test/helpers/.keep -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/test/models/.keep -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 7 | fixtures :all 8 | 9 | # Add more helper methods to be used by all tests here... 10 | end 11 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxhover/blog/50c615a7cb56adc596e635f3b0a20a185e81256a/vendor/assets/stylesheets/.keep --------------------------------------------------------------------------------