├── .ackrc ├── .gitignore ├── .rspec ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── images │ │ ├── .keep │ │ ├── ali-pay.jpeg │ │ ├── alipay.png │ │ ├── arrows.png │ │ ├── bg-1.1.jpeg │ │ ├── bg-1.2.jpeg │ │ ├── bg-2.1.jpeg │ │ ├── bg-2.2.jpeg │ │ ├── bg-3.jpeg │ │ ├── bg-4.jpeg │ │ ├── bg-5.jpeg │ │ ├── bg-6.png │ │ ├── favicon.ico │ │ ├── loader.gif │ │ ├── new-sprites.png │ │ ├── wechat-1.jpeg │ │ ├── wechat-pay.jpeg │ │ └── zoom-icons.png │ ├── javascripts │ │ ├── admin.js │ │ ├── application.js │ │ ├── cable.js │ │ ├── channels │ │ │ └── .keep │ │ ├── googleanlytics.js │ │ ├── open-turn.coffee │ │ ├── photo.js │ │ ├── sb-admin.coffee │ │ ├── timeline-main.coffee │ │ └── upload.coffee │ └── stylesheets │ │ ├── admin.scss │ │ ├── application.scss │ │ ├── layout.scss │ │ ├── paginator.scss │ │ └── photo.scss ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── admin │ │ ├── articles_controller.rb │ │ ├── base_controller.rb │ │ ├── dashboard_controller.rb │ │ ├── photos_controller.rb │ │ └── resumes_controller.rb │ ├── application_controller.rb │ ├── articles_controller.rb │ ├── concerns │ │ └── .keep │ ├── home_controller.rb │ ├── photos_controller.rb │ └── sessions_controller.rb ├── helpers │ └── application_helper.rb ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── article.rb │ ├── base.rb │ ├── concerns │ │ └── .keep │ ├── image.rb │ ├── photo.rb │ └── resume.rb ├── uploaders │ └── image_uploader.rb └── views │ ├── admin │ ├── articles │ │ ├── _form.html.slim │ │ ├── edit.html.slim │ │ ├── index.html.slim │ │ └── new.html.slim │ ├── dashboard │ │ └── index.html.slim │ ├── photos │ │ ├── _form.html.slim │ │ ├── edit.html.slim │ │ ├── index.html.slim │ │ └── new.html.slim │ └── resumes │ │ └── edit.html.slim │ ├── articles │ ├── index.html.slim │ └── show.html.slim │ ├── home │ ├── about.html.slim │ ├── index.html.slim │ └── timeline.html.slim │ ├── kaminari │ ├── _first_page.html.slim │ ├── _gap.html.slim │ ├── _last_page.html.slim │ ├── _next_page.html.slim │ ├── _page.html.slim │ ├── _paginator.html.slim │ └── _prev_page.html.slim │ ├── layouts │ ├── _admin_navigation.html.slim │ ├── _footer.html.slim │ ├── _messages.html.slim │ ├── _navigation.html.slim │ ├── admin.html.slim │ ├── application.html.slim │ ├── mailer.html.erb │ ├── mailer.text.erb │ └── photo.html.slim │ ├── photos │ ├── _album-samples.html.slim │ ├── index.html.slim │ └── show.js.erb │ └── sessions │ └── new.html.slim ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring ├── update └── yarn ├── config.ru ├── config ├── application.rb ├── application.yml.example ├── backup.rb.example ├── boot.rb ├── cable.yml ├── database.yml.example ├── deploy.rb ├── deploy │ └── production.rb ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── browser_warrior.rb │ ├── carrierwave.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── kaminari_config.rb │ ├── mime_types.rb │ ├── sidekiq.rb │ ├── simple_form.rb │ ├── simple_form_bootstrap.rb │ ├── status_page.rb │ └── wrap_parameters.rb ├── locales │ ├── en.yml │ └── simple_form.en.yml ├── logrotate.conf.example ├── monit.conf.example ├── nginx.conf.example ├── nginx.ssl.conf.example ├── puma.rb ├── routes.rb ├── secrets.yml └── spring.rb ├── db ├── migrate │ ├── 20171204103444_create_bases.rb │ ├── 20171206112233_create_images.rb │ ├── 20171225033340_add_visit_to_base.rb │ └── 20171225034053_add_draft_to_base.rb ├── schema.rb └── seeds.rb ├── lib ├── assets │ └── .keep ├── tasks │ └── .keep └── templates │ └── slim │ └── scaffold │ └── _form.html.slim ├── log └── .keep ├── package.json ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── spec ├── factories │ ├── articles.rb │ └── images.rb ├── models │ ├── article_spec.rb │ └── image_spec.rb ├── rails_helper.rb ├── spec_helper.rb └── support │ ├── capybara.rb │ ├── database_cleaner.rb │ └── factory_girl.rb ├── tmp └── .keep └── vendor ├── .keep └── assets ├── javascripts ├── bootstrap.bundle.min.js ├── jquery-ui-1.8.20.custom.min.js ├── sb-admin.min.js ├── simditor-hotkeys.js ├── simditor-module.js ├── simditor-uploader.js ├── simditor.js ├── startbootstrap-clean-blog.js ├── timeline-modernizr.js ├── turn-hash.js ├── turn-jquery.min.js ├── turn-magazine1.js ├── turn-modernizr.min.js ├── turn-zoom.min.js ├── turn.html4.min.js └── turn.min.js └── stylesheets ├── bootstrap.css ├── jquery-ui.css ├── jquery.ui.html4.css ├── sb-admin.css ├── simditor.css ├── startbootstrap-clean-blog.css ├── timeline-style.scss └── turn-magazine.css /.ackrc: -------------------------------------------------------------------------------- 1 | --ignore-file=ext:log 2 | --ignore-dir=public 3 | --ignore-dir=tmp 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle 2 | 3 | /db/*.sqlite3 4 | 5 | /log/* 6 | /tmp/* 7 | !/log/.keep 8 | !/tmp/.keep 9 | 10 | /node_modules 11 | /yarn-error.log 12 | 13 | *.swp 14 | 15 | /public/uploads/* 16 | /public/assets/* 17 | 18 | /config/application.yml 19 | /config/database.yml 20 | **.orig 21 | *.old 22 | *.bak 23 | *~ 24 | .env 25 | .byebug_history 26 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --require rails_helper 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | git_source(:github) do |repo_name| 4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 5 | "https://github.com/#{repo_name}.git" 6 | end 7 | gem 'rails', '~> 5.1.4' 8 | gem 'puma', '~> 3.7' 9 | gem 'sass-rails', '~> 5.0' 10 | gem 'uglifier', '>= 1.3.0' 11 | gem 'coffee-rails', '~> 4.2' 12 | gem 'turbolinks', '~> 5' 13 | gem 'jbuilder', '~> 2.5' 14 | 15 | group :development, :test do 16 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] 17 | gem 'capybara', '~> 2.13' 18 | gem 'selenium-webdriver' 19 | end 20 | 21 | group :development do 22 | gem 'web-console', '>= 3.3.0' 23 | gem 'listen', '>= 3.0.5', '< 3.2' 24 | gem 'spring' 25 | gem 'spring-watcher-listen', '~> 2.0.0' 26 | end 27 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 28 | 29 | gem 'pg' 30 | gem 'figaro' 31 | gem 'jquery-rails' 32 | gem 'simple_form', '~> 3.5.0' 33 | gem 'font-awesome-sass' 34 | gem 'slim-rails' 35 | gem 'high_voltage', '~> 3.0.0' 36 | gem 'carrierwave' 37 | gem 'carrierwave-upyun' 38 | gem 'status-page' 39 | gem 'browser_warrior' 40 | gem 'redis-namespace' 41 | gem 'sidekiq' 42 | gem 'kaminari', '~> 1.0.1' 43 | gem 'rails-i18n', '~> 5.0.3' 44 | gem 'mina', '~> 1.2.2', require: false 45 | gem 'mina-puma', '~> 1.1.0', require: false 46 | gem 'mina-multistage', '~> 1.0.3', require: false 47 | gem 'mina-sidekiq', '~> 1.0.3', require: false 48 | gem 'mina-logs', '~> 1.1.0', require: false 49 | gem 'lograge' 50 | group :development do 51 | gem 'rails_apps_testing' 52 | end 53 | 54 | group :development, :test do 55 | gem 'rspec-rails' 56 | gem 'factory_girl_rails' 57 | gem 'pry' 58 | end 59 | 60 | group :test do 61 | gem 'database_cleaner' 62 | gem 'launchy' 63 | end 64 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.1.7) 5 | actionpack (= 5.1.7) 6 | nio4r (~> 2.0) 7 | websocket-driver (~> 0.6.1) 8 | actionmailer (5.1.7) 9 | actionpack (= 5.1.7) 10 | actionview (= 5.1.7) 11 | activejob (= 5.1.7) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.1.7) 15 | actionview (= 5.1.7) 16 | activesupport (= 5.1.7) 17 | rack (~> 2.0) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.1.7) 22 | activesupport (= 5.1.7) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.1.7) 28 | activesupport (= 5.1.7) 29 | globalid (>= 0.3.6) 30 | activemodel (5.1.7) 31 | activesupport (= 5.1.7) 32 | activerecord (5.1.7) 33 | activemodel (= 5.1.7) 34 | activesupport (= 5.1.7) 35 | arel (~> 8.0) 36 | activesupport (5.1.7) 37 | concurrent-ruby (~> 1.0, >= 1.0.2) 38 | i18n (>= 0.7, < 2) 39 | minitest (~> 5.1) 40 | tzinfo (~> 1.1) 41 | addressable (2.7.0) 42 | public_suffix (>= 2.0.2, < 5.0) 43 | arel (8.0.0) 44 | bindex (0.8.1) 45 | browser (4.2.0) 46 | browser_warrior (0.14.0) 47 | browser (>= 4.0, < 5) 48 | rails (>= 5.0, < 7) 49 | sass-rails 50 | builder (3.2.4) 51 | byebug (11.1.3) 52 | capybara (2.18.0) 53 | addressable 54 | mini_mime (>= 0.1.3) 55 | nokogiri (>= 1.3.3) 56 | rack (>= 1.0.0) 57 | rack-test (>= 0.5.4) 58 | xpath (>= 2.0, < 4.0) 59 | carrierwave (2.2.0) 60 | activemodel (>= 5.0.0) 61 | activesupport (>= 5.0.0) 62 | addressable (~> 2.6) 63 | image_processing (~> 1.1) 64 | mimemagic (>= 0.3.0) 65 | mini_mime (>= 0.1.3) 66 | ssrf_filter (~> 1.0) 67 | carrierwave-upyun (1.0.5) 68 | carrierwave (>= 1.0.0) 69 | faraday (>= 0.8.0) 70 | childprocess (3.0.0) 71 | coderay (1.1.3) 72 | coffee-rails (4.2.2) 73 | coffee-script (>= 2.2.0) 74 | railties (>= 4.0.0) 75 | coffee-script (2.4.1) 76 | coffee-script-source 77 | execjs 78 | coffee-script-source (1.12.2) 79 | concurrent-ruby (1.1.8) 80 | connection_pool (2.2.3) 81 | crass (1.0.6) 82 | database_cleaner (2.0.1) 83 | database_cleaner-active_record (~> 2.0.0) 84 | database_cleaner-active_record (2.0.0) 85 | activerecord (>= 5.a) 86 | database_cleaner-core (~> 2.0.0) 87 | database_cleaner-core (2.0.1) 88 | diff-lcs (1.4.4) 89 | erubi (1.10.0) 90 | execjs (2.7.0) 91 | factory_girl (4.9.0) 92 | activesupport (>= 3.0.0) 93 | factory_girl_rails (4.9.0) 94 | factory_girl (~> 4.9.0) 95 | railties (>= 3.0.0) 96 | faraday (1.3.0) 97 | faraday-net_http (~> 1.0) 98 | multipart-post (>= 1.2, < 3) 99 | ruby2_keywords 100 | faraday-net_http (1.0.1) 101 | ffi (1.14.2) 102 | figaro (1.2.0) 103 | thor (>= 0.14.0, < 2) 104 | font-awesome-sass (5.15.1) 105 | sassc (>= 1.11) 106 | globalid (0.4.2) 107 | activesupport (>= 4.2.0) 108 | high_voltage (3.0.0) 109 | i18n (0.9.5) 110 | concurrent-ruby (~> 1.0) 111 | image_processing (1.12.1) 112 | mini_magick (>= 4.9.5, < 5) 113 | ruby-vips (>= 2.0.17, < 3) 114 | jbuilder (2.11.2) 115 | activesupport (>= 5.0.0) 116 | jquery-rails (4.4.0) 117 | rails-dom-testing (>= 1, < 3) 118 | railties (>= 4.2.0) 119 | thor (>= 0.14, < 2.0) 120 | kaminari (1.0.1) 121 | activesupport (>= 4.1.0) 122 | kaminari-actionview (= 1.0.1) 123 | kaminari-activerecord (= 1.0.1) 124 | kaminari-core (= 1.0.1) 125 | kaminari-actionview (1.0.1) 126 | actionview 127 | kaminari-core (= 1.0.1) 128 | kaminari-activerecord (1.0.1) 129 | activerecord 130 | kaminari-core (= 1.0.1) 131 | kaminari-core (1.0.1) 132 | launchy (2.5.0) 133 | addressable (~> 2.7) 134 | listen (3.1.5) 135 | rb-fsevent (~> 0.9, >= 0.9.4) 136 | rb-inotify (~> 0.9, >= 0.9.7) 137 | ruby_dep (~> 1.2) 138 | lograge (0.11.2) 139 | actionpack (>= 4) 140 | activesupport (>= 4) 141 | railties (>= 4) 142 | request_store (~> 1.0) 143 | loofah (2.9.0) 144 | crass (~> 1.0.2) 145 | nokogiri (>= 1.5.9) 146 | mail (2.7.1) 147 | mini_mime (>= 0.1.1) 148 | method_source (1.0.0) 149 | mimemagic (0.3.5) 150 | mina (1.2.3) 151 | open4 (~> 1.3.4) 152 | rake 153 | mina-logs (1.1.0) 154 | mina (>= 1.0.2) 155 | mina-multistage (1.0.3) 156 | mina (~> 1.0) 157 | mina-puma (1.1.0) 158 | mina (~> 1.2.0) 159 | puma (>= 2.13) 160 | mina-sidekiq (1.0.3) 161 | mina (>= 1.0.2) 162 | mini_magick (4.11.0) 163 | mini_mime (1.0.2) 164 | mini_portile2 (2.5.0) 165 | minitest (5.14.4) 166 | multipart-post (2.1.1) 167 | nio4r (2.5.5) 168 | nokogiri (1.11.1) 169 | mini_portile2 (~> 2.5.0) 170 | racc (~> 1.4) 171 | open4 (1.3.4) 172 | pg (1.2.3) 173 | pry (0.14.0) 174 | coderay (~> 1.1) 175 | method_source (~> 1.0) 176 | public_suffix (4.0.6) 177 | puma (3.12.6) 178 | racc (1.5.2) 179 | rack (2.2.3) 180 | rack-test (1.1.0) 181 | rack (>= 1.0, < 3) 182 | rails (5.1.7) 183 | actioncable (= 5.1.7) 184 | actionmailer (= 5.1.7) 185 | actionpack (= 5.1.7) 186 | actionview (= 5.1.7) 187 | activejob (= 5.1.7) 188 | activemodel (= 5.1.7) 189 | activerecord (= 5.1.7) 190 | activesupport (= 5.1.7) 191 | bundler (>= 1.3.0) 192 | railties (= 5.1.7) 193 | sprockets-rails (>= 2.0.0) 194 | rails-dom-testing (2.0.3) 195 | activesupport (>= 4.2.0) 196 | nokogiri (>= 1.6) 197 | rails-html-sanitizer (1.3.0) 198 | loofah (~> 2.3) 199 | rails-i18n (5.0.4) 200 | i18n (~> 0.7) 201 | railties (~> 5.0) 202 | rails_apps_testing (0.3.13) 203 | railties (5.1.7) 204 | actionpack (= 5.1.7) 205 | activesupport (= 5.1.7) 206 | method_source 207 | rake (>= 0.8.7) 208 | thor (>= 0.18.1, < 2.0) 209 | rake (13.0.3) 210 | rb-fsevent (0.10.4) 211 | rb-inotify (0.10.1) 212 | ffi (~> 1.0) 213 | redis (4.2.5) 214 | redis-namespace (1.8.1) 215 | redis (>= 3.0.4) 216 | request_store (1.5.0) 217 | rack (>= 1.4) 218 | rspec-core (3.10.1) 219 | rspec-support (~> 3.10.0) 220 | rspec-expectations (3.10.1) 221 | diff-lcs (>= 1.2.0, < 2.0) 222 | rspec-support (~> 3.10.0) 223 | rspec-mocks (3.10.2) 224 | diff-lcs (>= 1.2.0, < 2.0) 225 | rspec-support (~> 3.10.0) 226 | rspec-rails (4.0.2) 227 | actionpack (>= 4.2) 228 | activesupport (>= 4.2) 229 | railties (>= 4.2) 230 | rspec-core (~> 3.10) 231 | rspec-expectations (~> 3.10) 232 | rspec-mocks (~> 3.10) 233 | rspec-support (~> 3.10) 234 | rspec-support (3.10.2) 235 | ruby-vips (2.0.17) 236 | ffi (~> 1.9) 237 | ruby2_keywords (0.0.4) 238 | ruby_dep (1.5.0) 239 | rubyzip (2.3.0) 240 | sass (3.7.4) 241 | sass-listen (~> 4.0.0) 242 | sass-listen (4.0.0) 243 | rb-fsevent (~> 0.9, >= 0.9.4) 244 | rb-inotify (~> 0.9, >= 0.9.7) 245 | sass-rails (5.0.7) 246 | railties (>= 4.0.0, < 6) 247 | sass (~> 3.1) 248 | sprockets (>= 2.8, < 4.0) 249 | sprockets-rails (>= 2.0, < 4.0) 250 | tilt (>= 1.1, < 3) 251 | sassc (2.4.0) 252 | ffi (~> 1.9) 253 | selenium-webdriver (3.142.7) 254 | childprocess (>= 0.5, < 4.0) 255 | rubyzip (>= 1.2.2) 256 | sidekiq (6.1.3) 257 | connection_pool (>= 2.2.2) 258 | rack (~> 2.0) 259 | redis (>= 4.2.0) 260 | simple_form (3.5.1) 261 | actionpack (> 4, < 5.2) 262 | activemodel (> 4, < 5.2) 263 | slim (4.1.0) 264 | temple (>= 0.7.6, < 0.9) 265 | tilt (>= 2.0.6, < 2.1) 266 | slim-rails (3.2.0) 267 | actionpack (>= 3.1) 268 | railties (>= 3.1) 269 | slim (>= 3.0, < 5.0) 270 | spring (2.1.1) 271 | spring-watcher-listen (2.0.1) 272 | listen (>= 2.7, < 4.0) 273 | spring (>= 1.2, < 3.0) 274 | sprockets (3.7.2) 275 | concurrent-ruby (~> 1.0) 276 | rack (> 1, < 3) 277 | sprockets-rails (3.2.2) 278 | actionpack (>= 4.0) 279 | activesupport (>= 4.0) 280 | sprockets (>= 3.0.0) 281 | ssrf_filter (1.0.7) 282 | status-page (0.1.5) 283 | rails (>= 4.2) 284 | temple (0.8.2) 285 | thor (1.1.0) 286 | thread_safe (0.3.6) 287 | tilt (2.0.10) 288 | turbolinks (5.2.1) 289 | turbolinks-source (~> 5.2) 290 | turbolinks-source (5.2.0) 291 | tzinfo (1.2.9) 292 | thread_safe (~> 0.1) 293 | uglifier (4.2.0) 294 | execjs (>= 0.3.0, < 3) 295 | web-console (3.7.0) 296 | actionview (>= 5.0) 297 | activemodel (>= 5.0) 298 | bindex (>= 0.4.0) 299 | railties (>= 5.0) 300 | websocket-driver (0.6.5) 301 | websocket-extensions (>= 0.1.0) 302 | websocket-extensions (0.1.5) 303 | xpath (3.2.0) 304 | nokogiri (~> 1.8) 305 | 306 | PLATFORMS 307 | ruby 308 | 309 | DEPENDENCIES 310 | browser_warrior 311 | byebug 312 | capybara (~> 2.13) 313 | carrierwave 314 | carrierwave-upyun 315 | coffee-rails (~> 4.2) 316 | database_cleaner 317 | factory_girl_rails 318 | figaro 319 | font-awesome-sass 320 | high_voltage (~> 3.0.0) 321 | jbuilder (~> 2.5) 322 | jquery-rails 323 | kaminari (~> 1.0.1) 324 | launchy 325 | listen (>= 3.0.5, < 3.2) 326 | lograge 327 | mina (~> 1.2.2) 328 | mina-logs (~> 1.1.0) 329 | mina-multistage (~> 1.0.3) 330 | mina-puma (~> 1.1.0) 331 | mina-sidekiq (~> 1.0.3) 332 | pg 333 | pry 334 | puma (~> 3.7) 335 | rails (~> 5.1.4) 336 | rails-i18n (~> 5.0.3) 337 | rails_apps_testing 338 | redis-namespace 339 | rspec-rails 340 | sass-rails (~> 5.0) 341 | selenium-webdriver 342 | sidekiq 343 | simple_form (~> 3.5.0) 344 | slim-rails 345 | spring 346 | spring-watcher-listen (~> 2.0.0) 347 | status-page 348 | turbolinks (~> 5) 349 | tzinfo-data 350 | uglifier (>= 1.3.0) 351 | web-console (>= 3.3.0) 352 | 353 | BUNDLED WITH 354 | 2.1.4 355 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Rina Blog 4 | 5 | > RBlog 项目是一个使用 [rails template of 80percent](https://github.com/80percent/rails-template) 模板创建的 Rails 项目, 引用了最新的技术框架,后端框架为: Rails 5.1.4, 前端框架用的 bootstrap 4, startbootstrap, 主要功能有: 后台管理, 数据统计, 文章管理,相册管理,个人简历管理, 时间线等功能 6 | 7 | ## Demo 8 | 9 | 项目 Demo 请访问: 10 | 11 | ## 核心技术框架 12 | 13 | * Ruby on Rails 5.1.4 14 | * bootstrap 4 15 | * font-awesome 16 | * figaro 17 | * postgres 18 | * slim 19 | * high_voltage 20 | * carriewave & upyun 21 | * sidekiq 22 | * kaminari 23 | * mina 24 | * puma 25 | * lograge 26 | * simditor 27 | * turn.js 28 | 29 | ## 系统依赖 30 | 31 | Rails 5.1.4 32 | 33 | ## 开发环境准备 34 | 35 | 第一步, 安装项目依赖 36 | 37 | $ bundle install 38 | 39 | 第二步, 启动服务 40 | 41 | $ rails s 42 | 43 | 第三步, 浏览器访问: http://localhost:3000 44 | 45 | 结束. 46 | 47 | ## 如何发布? 48 | 49 | 第一步, 配置nginx 50 | 51 | 先根据项目里的 config/deploy/production.rb, /config/deploy.rb, config/puma.rb, /config/nigix.conf 文件, 修改其中的配置, 然后将 /config/nigix.conf 文件复制到你的服务器上 nginx 所在目录的 /etc/nginx/conf.d 目录下, 命名为 xxx.conf 的文件. 然后重启 nginx. 52 | 53 | 第二步, 在服务器上初始化 54 | 55 | $ mina setup 56 | 57 | 第三步, 发布 58 | 59 | $ mina deploy 60 | 61 | ## 学习参考资料 62 | 63 | Rails文档: 64 | 65 | 使用模板创建Rails项目: 66 | 67 | ubuntu16.04安装rails 68 | 69 | simditor编辑器: 70 | 71 | startbootstrap-clean-blog前端样式: 72 | 73 | sb-admin前端样式: 74 | 75 | turnjs前端样式: 76 | 77 | timeline前端样式: 78 | 79 | 图片在线压缩: 80 | 81 | ## 引荐 Vue.js 项目 82 | 83 | 项目 Demo 请访问: 84 | 85 | 项目代码: 86 | 87 | ## 引荐 React.js 项目 88 | 89 | 项目 Demo 请访问: 90 | 91 | 项目代码: 92 | 93 | ## 贡献者 94 | 95 | * Rina 96 | 97 | ## Built with 98 | 99 | [rails template of 80percent](https://github.com/80percent/rails-template) 100 | 101 | ## LICENSE 102 | MIT 103 | -------------------------------------------------------------------------------- /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_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/ali-pay.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/ali-pay.jpeg -------------------------------------------------------------------------------- /app/assets/images/alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/alipay.png -------------------------------------------------------------------------------- /app/assets/images/arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/arrows.png -------------------------------------------------------------------------------- /app/assets/images/bg-1.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/bg-1.1.jpeg -------------------------------------------------------------------------------- /app/assets/images/bg-1.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/bg-1.2.jpeg -------------------------------------------------------------------------------- /app/assets/images/bg-2.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/bg-2.1.jpeg -------------------------------------------------------------------------------- /app/assets/images/bg-2.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/bg-2.2.jpeg -------------------------------------------------------------------------------- /app/assets/images/bg-3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/bg-3.jpeg -------------------------------------------------------------------------------- /app/assets/images/bg-4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/bg-4.jpeg -------------------------------------------------------------------------------- /app/assets/images/bg-5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/bg-5.jpeg -------------------------------------------------------------------------------- /app/assets/images/bg-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/bg-6.png -------------------------------------------------------------------------------- /app/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/favicon.ico -------------------------------------------------------------------------------- /app/assets/images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/loader.gif -------------------------------------------------------------------------------- /app/assets/images/new-sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/new-sprites.png -------------------------------------------------------------------------------- /app/assets/images/wechat-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/wechat-1.jpeg -------------------------------------------------------------------------------- /app/assets/images/wechat-pay.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/wechat-pay.jpeg -------------------------------------------------------------------------------- /app/assets/images/zoom-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/images/zoom-icons.png -------------------------------------------------------------------------------- /app/assets/javascripts/admin.js: -------------------------------------------------------------------------------- 1 | //= require turbolinks 2 | //= require jquery 3 | //= require rails-ujs 4 | //= require simditor-module 5 | //= require simditor-hotkeys 6 | //= require simditor-uploader 7 | //= require simditor 8 | //= require cable 9 | //= require sb-admin 10 | //= require upload 11 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | //= require jquery 2 | //= require rails-ujs 3 | //= require turbolinks 4 | //= require bootstrap.bundle.min 5 | //= require cable 6 | //= require startbootstrap-clean-blog 7 | //= require timeline-modernizr 8 | //= require timeline-main 9 | //= require googleanlytics 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/googleanlytics.js: -------------------------------------------------------------------------------- 1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 4 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 5 | ga('create', 'UA-69461736-1', 'auto'); 6 | ga('send', 'pageview'); 7 | -------------------------------------------------------------------------------- /app/assets/javascripts/open-turn.coffee: -------------------------------------------------------------------------------- 1 | $(document).on 'ready page:load', -> 2 | $('body').delegate 'a.icon.quit', "click", (event)-> 3 | event.preventDefault() 4 | $('body').removeClass('overflow-hidden') 5 | $('.album-samples').hide() 6 | $('.container.album-wrap').show() 7 | $('header.masthead').show() 8 | Hash.go('').update() 9 | -------------------------------------------------------------------------------- /app/assets/javascripts/photo.js: -------------------------------------------------------------------------------- 1 | //= require jquery 2 | //= require rails-ujs 3 | //= require cable 4 | //= require bootstrap.bundle.min 5 | //= require startbootstrap-clean-blog 6 | //= require turn-jquery.min 7 | //= require jquery-ui-1.8.20.custom.min 8 | //= require turn-modernizr.min 9 | //= require turn-hash 10 | //= require open-turn 11 | //= require turn-zoom.min 12 | //= require turn-magazine1 13 | //= require googleanlytics 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/sb-admin.coffee: -------------------------------------------------------------------------------- 1 | $(document).on 'turbolinks:load', -> 2 | $('body').delegate '#sidenavToggler', "click", (event)-> 3 | event.preventDefault() 4 | $("body").toggleClass("sidenav-toggled") 5 | $(".navbar-sidenav .nav-link-collapse").addClass("collapsed") 6 | $(".navbar-sidenav .sidenav-second-level, .navbar-sidenav .sidenav-third-level").removeClass("show") 7 | -------------------------------------------------------------------------------- /app/assets/javascripts/timeline-main.coffee: -------------------------------------------------------------------------------- 1 | $(document).on 'turbolinks:load', -> 2 | timelineBlocks = $('.cd-timeline-block') 3 | offset = 0.8 4 | #hide timeline blocks which are outside the viewport 5 | 6 | hideBlocks = (blocks, offset) -> 7 | blocks.each -> 8 | $(this).offset().top > $(window).scrollTop() + $(window).height() * offset and $(this).find('.cd-timeline-img, .cd-timeline-content').addClass('is-hidden') 9 | return 10 | return 11 | 12 | showBlocks = (blocks, offset) -> 13 | blocks.each -> 14 | $(this).offset().top <= $(window).scrollTop() + $(window).height() * offset and $(this).find('.cd-timeline-img').hasClass('is-hidden') and $(this).find('.cd-timeline-img, .cd-timeline-content').removeClass('is-hidden').addClass('bounce-in') 15 | return 16 | return 17 | 18 | hideBlocks timelineBlocks, offset 19 | #on scolling, show/animate timeline blocks when enter the viewport 20 | $(window).on 'scroll', -> 21 | if !window.requestAnimationFrame then setTimeout((-> 22 | showBlocks timelineBlocks, offset 23 | return 24 | ), 100) else window.requestAnimationFrame((-> 25 | showBlocks timelineBlocks, offset 26 | return 27 | )) 28 | return 29 | return 30 | -------------------------------------------------------------------------------- /app/assets/javascripts/upload.coffee: -------------------------------------------------------------------------------- 1 | $(document).on 'turbolinks:load', -> 2 | $('body').delegate '.simditor-body', 'paste', (event)-> 3 | pasteEvent = event.originalEvent 4 | if pasteEvent and pasteEvent.clipboardData and pasteEvent.clipboardData.items 5 | if pasteEvent.clipboardData.items[0].type.indexOf('image') isnt -1 6 | event.preventDefault() 7 | formData = new FormData() 8 | formData.append 'file', pasteEvent.clipboardData.items[0].getAsFile(), "image.png" 9 | $.ajax 10 | url: '/admin/upload' 11 | type: 'POST' 12 | data: formData 13 | dataType: 'JSON' 14 | processData: false 15 | contentType: false 16 | beforeSend: -> 17 | success: (e, status, res) -> 18 | src = res.responseJSON.file_path 19 | $(".simditor-body").children().last().append('

Image

').focus() 20 | return false 21 | error: (res) -> 22 | alert("上传失败") 23 | complete: -> 24 | -------------------------------------------------------------------------------- /app/assets/stylesheets/admin.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap"; 2 | @import "font-awesome-sprockets"; 3 | @import "font-awesome"; 4 | @import "simditor"; 5 | @import "sb-admin"; 6 | @import "paginator"; 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap"; 2 | @import "font-awesome-sprockets"; 3 | @import "font-awesome"; 4 | @import "startbootstrap-clean-blog"; 5 | @import "layout"; 6 | @import "timeline-style"; 7 | @import "paginator"; 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/layout.scss: -------------------------------------------------------------------------------- 1 | header.home-index-1 { 2 | background-image: image-url("bg-1.1.jpeg"); 3 | } 4 | header.home-index-2 { 5 | background-image: image-url("bg-1.2.jpeg"); 6 | } 7 | header.articles-index { 8 | background-image: image-url("bg-3.jpeg"); 9 | } 10 | header.articles-show-1 { 11 | background-image: image-url("bg-2.1.jpeg"); 12 | } 13 | header.articles-show-2 { 14 | background-image: image-url("bg-2.2.jpeg"); 15 | } 16 | header.home-about { 17 | background-image: image-url("bg-5.jpeg"); 18 | } 19 | header.home-timeline { 20 | background-image: image-url("bg-6.png"); 21 | } 22 | .content-markdown { 23 | word-wrap: break-word; 24 | img { 25 | width: 100%; 26 | } 27 | a { 28 | color: #0366d6; 29 | text-decoration: none; 30 | } 31 | blockquote { 32 | padding: 0 1em; 33 | color: #6a737d; 34 | border-left: 0.25em solid #dfe2e5; 35 | } 36 | h1, h2, h3, h4 { 37 | margin-bottom: 20px; 38 | padding-bottom: 0.3em; 39 | border-bottom: 1px solid #eaecef; 40 | } 41 | } 42 | body.front_end { 43 | background: #f9f9f0; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /app/assets/stylesheets/paginator.scss: -------------------------------------------------------------------------------- 1 | .pagination { 2 | display: inline-block; 3 | padding-left: 0; 4 | margin: 20px 0; 5 | border-radius: 4px 6 | } 7 | 8 | .pagination>li { 9 | display: inline 10 | } 11 | 12 | .pagination>li>a,.pagination>li>span { 13 | position: relative; 14 | float: left; 15 | padding: 6px 12px; 16 | line-height: 1.428571429; 17 | text-decoration: none; 18 | color: #337ab7; 19 | background-color: #fff; 20 | border: 1px solid #ddd; 21 | margin-left: -1px 22 | } 23 | 24 | .pagination>li:first-child>a,.pagination>li:first-child>span { 25 | margin-left: 0; 26 | border-bottom-left-radius: 4px; 27 | border-top-left-radius: 4px 28 | } 29 | 30 | .pagination>li:last-child>a,.pagination>li:last-child>span { 31 | border-bottom-right-radius: 4px; 32 | border-top-right-radius: 4px 33 | } 34 | 35 | .pagination>li>a:hover,.pagination>li>a:focus,.pagination>li>span:hover,.pagination>li>span:focus { 36 | z-index: 2; 37 | color: #23527c; 38 | background-color: #eeeeee; 39 | border-color: #ddd 40 | } 41 | 42 | .pagination>.active>a,.pagination>.active>a:hover,.pagination>.active>a:focus,.pagination>.active>span,.pagination>.active>span:hover,.pagination>.active>span:focus { 43 | z-index: 3; 44 | color: #fff; 45 | background-color: #337ab7; 46 | border-color: #337ab7; 47 | cursor: default 48 | } 49 | 50 | .pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus { 51 | color: #777777; 52 | background-color: #fff; 53 | border-color: #ddd; 54 | cursor: not-allowed 55 | } 56 | 57 | .pagination-lg>li>a,.pagination-lg>li>span { 58 | padding: 10px 16px; 59 | font-size: 18px; 60 | line-height: 1.3333333 61 | } 62 | 63 | .pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span { 64 | border-bottom-left-radius: 6px; 65 | border-top-left-radius: 6px 66 | } 67 | 68 | .pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span { 69 | border-bottom-right-radius: 6px; 70 | border-top-right-radius: 6px 71 | } 72 | 73 | .pagination-sm>li>a,.pagination-sm>li>span { 74 | padding: 5px 10px; 75 | font-size: 12px; 76 | line-height: 1.5 77 | } 78 | 79 | .pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span { 80 | border-bottom-left-radius: 3px; 81 | border-top-left-radius: 3px 82 | } 83 | 84 | .pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span { 85 | border-bottom-right-radius: 3px; 86 | border-top-right-radius: 3px 87 | } 88 | 89 | .pager { 90 | padding-left: 0; 91 | margin: 20px 0; 92 | list-style: none; 93 | text-align: center 94 | } 95 | 96 | .pager:before,.pager:after { 97 | content: " "; 98 | display: table 99 | } 100 | 101 | .pager:after { 102 | clear: both 103 | } 104 | 105 | .pager li { 106 | display: inline 107 | } 108 | 109 | .pager li>a,.pager li>span { 110 | display: inline-block; 111 | padding: 5px 14px; 112 | background-color: #fff; 113 | border: 1px solid #ddd; 114 | border-radius: 15px 115 | } 116 | 117 | .pager li>a:hover,.pager li>a:focus { 118 | text-decoration: none; 119 | background-color: #eeeeee 120 | } 121 | 122 | .pager .next>a,.pager .next>span { 123 | float: right 124 | } 125 | 126 | .pager .previous>a,.pager .previous>span { 127 | float: left 128 | } 129 | 130 | .pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span { 131 | color: #777777; 132 | background-color: #fff; 133 | cursor: not-allowed 134 | } 135 | -------------------------------------------------------------------------------- /app/assets/stylesheets/photo.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap"; 2 | @import "startbootstrap-clean-blog"; 3 | @import "font-awesome-sprockets"; 4 | @import "font-awesome"; 5 | @import "turn-magazine"; 6 | @import "jquery-ui"; 7 | 8 | body.photo-wrapper.overflow-hidden { 9 | overflow: hidden; 10 | } 11 | body.photo-wrapper { 12 | background-image: image-url("bg-4.jpeg"); 13 | overflow: scroll; 14 | 15 | /* Zoom In #1 */ 16 | .hover figure img { 17 | -webkit-transform: scale(1); 18 | transform: scale(1); 19 | -webkit-transition: .3s ease-in-out; 20 | transition: .3s ease-in-out; 21 | padding: 5%; 22 | width: 70%; 23 | } 24 | .hover img:hover { 25 | -webkit-transform: scale(1.3); 26 | transform: scale(1.3); 27 | padding: 0; 28 | } 29 | 30 | .album-wrap { 31 | margin-top: 120px; 32 | img { 33 | margin-left: 50%; 34 | margin-bottom: 120px; 35 | box-shadow: 0 10px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19) !important; 36 | } 37 | } 38 | 39 | .bar-wrap { 40 | .quit:hover { 41 | background-position: -66px 0; 42 | } 43 | .quit { 44 | background-position: -44px 0; 45 | cursor: default; 46 | right: 0; 47 | top: 55px; 48 | float: right; 49 | } 50 | a { 51 | transition: none; 52 | position: relative; 53 | } 54 | a.quit:hover { 55 | text-decoration: underline; 56 | } 57 | .icon { 58 | cursor:pointer; 59 | width: 22px; 60 | height: 22px; 61 | z-index: 900000; 62 | background-image: image-url('new-sprites.png'); 63 | display: inline-block; 64 | } 65 | } 66 | 67 | @media (max-width: 768px) { 68 | .album-wrap { 69 | .hover.column { 70 | text-align: center; 71 | } 72 | img { 73 | margin-left: 0%; 74 | } 75 | a { 76 | pointer-events: none; 77 | cursor: default; 78 | } 79 | .hover figure img { 80 | width: 72%; 81 | } 82 | } 83 | } 84 | } 85 | 86 | @charset "UTF-8"; 87 | .animated { 88 | animation-duration: 1s; 89 | animation-fill-mode: both; 90 | } 91 | 92 | @keyframes swing { 93 | 5% { 94 | transform: rotate3d(0, 0, 1, 25deg); 95 | } 96 | 97 | 20% { 98 | transform: rotate3d(0, 0, 1, 35deg); 99 | } 100 | 101 | 40% { 102 | transform: rotate3d(0, 0, 1, 15deg); 103 | } 104 | 105 | 60% { 106 | transform: rotate3d(0, 0, 1, 30deg); 107 | } 108 | 109 | 80% { 110 | transform: rotate3d(0, 0, 1, 20deg); 111 | } 112 | 113 | to { 114 | transform: rotate3d(0, 0, 1, 25deg); 115 | } 116 | } 117 | 118 | .swing { 119 | transform-origin: top left; 120 | animation-name: swing; 121 | } 122 | 123 | @media (max-width: 768px) { 124 | @keyframes swing { 125 | 20% { 126 | transform: rotate3d(0, 0, 1, 15deg); 127 | } 128 | 129 | 40% { 130 | transform: rotate3d(0, 0, 1, -10deg); 131 | } 132 | 133 | 60% { 134 | transform: rotate3d(0, 0, 1, 5deg); 135 | } 136 | 137 | 80% { 138 | transform: rotate3d(0, 0, 1, -5deg); 139 | } 140 | 141 | to { 142 | transform: rotate3d(0, 0, 1, 0deg); 143 | } 144 | } 145 | 146 | .swing { 147 | transform-origin: top center; 148 | animation-name: swing; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/admin/articles_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::ArticlesController < Admin::BaseController 2 | def index 3 | @articles = Article.all.order(created_at: 'DESC').page(params[:page]) 4 | end 5 | 6 | def new 7 | @article = Article.new 8 | end 9 | 10 | def create 11 | @article = Article.new(article_params) 12 | if @article.save 13 | redirect_to admin_articles_path 14 | else 15 | render 'new' 16 | end 17 | end 18 | 19 | def edit 20 | @article = Article.find(params[:id]) 21 | end 22 | 23 | def update 24 | @article = Article.find(params[:id]) 25 | if @article.update(article_params) 26 | flash[:notice] = '更新成功' 27 | redirect_to admin_articles_path 28 | else 29 | render 'edit' 30 | end 31 | end 32 | 33 | def destroy 34 | @article = Article.find(params[:id]) 35 | if @article.destroy 36 | flash[:notice] = '删除成功' 37 | else 38 | flash[:error] = "删除失败" 39 | end 40 | end 41 | 42 | def push 43 | @article = Article.find(params[:id]) 44 | if @article.push 45 | flash[:notice] = '发布成功' 46 | else 47 | flash[:error] = '发布失败' 48 | end 49 | redirect_to admin_articles_path 50 | end 51 | 52 | private 53 | def article_params 54 | params.require(:article).permit(:title, :subtitle, :content) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /app/controllers/admin/base_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::BaseController < ApplicationController 2 | layout 'admin' 3 | before_action :authenticate_user 4 | 5 | def authenticate_user 6 | unless session[:login] 7 | redirect_to new_session_path 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/admin/dashboard_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::DashboardController < Admin::BaseController 2 | def index 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/admin/photos_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::PhotosController < Admin::BaseController 2 | def index 3 | @photos = Photo.all.order(updated_at: 'DESC').page(params[:page]) 4 | end 5 | 6 | def new 7 | @photo = Photo.new 8 | end 9 | 10 | def create 11 | @photo = Photo.new(photo_params) 12 | if @photo.save 13 | redirect_to admin_photos_path 14 | else 15 | render 'new' 16 | end 17 | end 18 | 19 | def edit 20 | @photo = Photo.find(params[:id]) 21 | end 22 | 23 | def update 24 | @photo = Photo.find(params[:id]) 25 | if @photo.update(photo_params) 26 | flash[:notice] = '更新成功' 27 | redirect_to admin_photos_path 28 | else 29 | render 'edit' 30 | end 31 | end 32 | 33 | def destroy 34 | @photo = Photo.find(params[:id]) 35 | if @photo.destroy 36 | flash[:notice] = '删除成功' 37 | else 38 | flash[:error] = "删除失败, 原因: #{@photo.errors.messages.to_s}" 39 | end 40 | redirect_to admin_photos_path 41 | end 42 | 43 | def push 44 | @photo = Photo.find(params[:id]) 45 | if @photo.push 46 | flash[:notice] = '更新成功' 47 | else 48 | flash[:error] = '更新失败' 49 | end 50 | redirect_to admin_photos_path 51 | end 52 | 53 | def upload 54 | @image = Image.new(img: params[:file]) 55 | if @image.save 56 | render json: { success: true, msg: '上传成功', file_path: @image.img.url, status: 200 } 57 | else 58 | render json: { success: false, status: 501 } 59 | end 60 | end 61 | 62 | private 63 | def photo_params 64 | params.require(:photo).permit(:title, :subtitle, :content) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /app/controllers/admin/resumes_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::ResumesController < Admin::BaseController 2 | def edit 3 | @resume = Resume.first || Resume.create(title: '刘祯的简历', content: '内容') 4 | end 5 | 6 | def update 7 | @resume = Resume.first 8 | if @resume.update(params.require(:resume).permit(:title, :subtitle, :content)) 9 | flash[:notice] = '更新成功' 10 | redirect_to edit_admin_resume_path 11 | else 12 | flash[:notice] = '更新失败' 13 | render 'edit' 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery with: :exception 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/articles_controller.rb: -------------------------------------------------------------------------------- 1 | class ArticlesController < ApplicationController 2 | def show 3 | @article = Article.find(params[:id]) 4 | if @article.draft && !session[:login] 5 | flash[:errors] = '访问链接不存在' 6 | redirect_to root_path 7 | end 8 | @article.increment!(:visit, 1) 9 | end 10 | 11 | def index 12 | @articles = Article.pushed.order(created_at: 'DESC').page(params[:page]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | @articles = Article.pushed.order(created_at: 'DESC').limit(6) 4 | end 5 | 6 | def about 7 | @resume = Resume.first 8 | @resume.increment!(:visit, 1) 9 | end 10 | 11 | def timeline 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/photos_controller.rb: -------------------------------------------------------------------------------- 1 | class PhotosController < ApplicationController 2 | layout 'photo' 3 | def index 4 | @photos = Photo.pushed.order(created_at: 'DESC') 5 | end 6 | 7 | def show 8 | @photo = Photo.find(params[:id]) 9 | @photo.increment!(:visit, 1) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | layout false 3 | def new 4 | end 5 | 6 | def create 7 | if params[:session][:username] == ENV['USERNAME'] && params[:session][:password] == ENV['PASSWORD'] 8 | session[:login] = true 9 | flash[:notic] = '登录成功' 10 | redirect_to admin_root_path 11 | else 12 | flash.now[:error] = '用户名或密码错误' 13 | render 'new' 14 | end 15 | end 16 | 17 | def destroy 18 | session[:login] = false 19 | flash[:notic] = '退出成功' 20 | redirect_to new_session_path 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def format_date(datetime) 3 | datetime.strftime("%Y-%m-%d") if datetime.present? 4 | end 5 | 6 | def active_for(controller_name, navbar_name) 7 | if controller_name.to_s == admin_root_path 8 | return controller_name.to_s == navbar_name.to_s ? "active" : "" 9 | end 10 | navbar_name.to_s.include?(controller_name.to_s) ? 'active' : '' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/article.rb: -------------------------------------------------------------------------------- 1 | class Article < Base 2 | scope :pushed, -> { where(draft: false) } 3 | 4 | def push 5 | self.update(draft: !self.draft) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/base.rb: -------------------------------------------------------------------------------- 1 | class Base < ApplicationRecord 2 | validates :title, :content, presence: true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/image.rb: -------------------------------------------------------------------------------- 1 | require 'carrierwave/orm/activerecord' 2 | class Image < ApplicationRecord 3 | mount_uploader :img, ImageUploader 4 | end 5 | -------------------------------------------------------------------------------- /app/models/photo.rb: -------------------------------------------------------------------------------- 1 | class Photo < Base 2 | scope :pushed, -> { where(draft: false) } 3 | 4 | def images 5 | Nokogiri::HTML(self.content).xpath("//img").map do |img| 6 | img['src'] 7 | end 8 | end 9 | 10 | def push 11 | self.update(draft: !self.draft) 12 | end 13 | 14 | def cover 15 | images = Nokogiri::HTML(self.content).xpath("//img") 16 | images[0]['src'] if images.present? 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/models/resume.rb: -------------------------------------------------------------------------------- 1 | class Resume < Base 2 | end 3 | -------------------------------------------------------------------------------- /app/uploaders/image_uploader.rb: -------------------------------------------------------------------------------- 1 | class ImageUploader < CarrierWave::Uploader::Base 2 | storage :file 3 | 4 | def extension_whitelist 5 | %w(jpg jpeg gif png) 6 | end 7 | 8 | def store_dir 9 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" 10 | end 11 | 12 | def filename 13 | if super.present? 14 | @prefix ||= SecureRandom.uuid.gsub("-","") 15 | "#{@prefix}.#{file.extension.downcase}" 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/views/admin/articles/_form.html.slim: -------------------------------------------------------------------------------- 1 | .row 2 | .offset-md-2.col-md-8 3 | = simple_form_for [:admin, @article] do |f| 4 | = f.error_notification 5 | = f.input :title 6 | = f.input :subtitle 7 | = f.text_area :content, id: 'editor_content', class: 'simditor', autofocus: true 8 | = f.submit '提交', class: 'btn btn-primary' 9 | ' 10 | = link_to '取消', admin_articles_path 11 | 12 | javascript: 13 | new Simditor({ 14 | textarea: $('#editor_content'), 15 | toolbar: ['title', 'bold', 'italic', 'underline', 'strikethrough', 'fontScale', 'color', '|', 'ol', 'ul', '|', 'blockquote', 'code', 'table', 'link', 'image', 'hr', 'indent', 'outdent', 'alignment'] 16 | }); 17 | -------------------------------------------------------------------------------- /app/views/admin/articles/edit.html.slim: -------------------------------------------------------------------------------- 1 | h2.text-center 更新文章 2 | = render 'form' 3 | -------------------------------------------------------------------------------- /app/views/admin/articles/index.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title) do 2 | | 文章列表 3 | 4 | p = link_to '新建', new_admin_article_path, class: 'btn btn-info' 5 | table.table 6 | thead 7 | tr 8 | th ID 9 | th 标题 10 | th 访问数 11 | th 创建时间 12 | th 更新时间 13 | th 发布 14 | th 操作 15 | tbody 16 | - @articles.each do |article| 17 | tr 18 | td = article.id 19 | td = article.title 20 | td = article.visit 21 | td = format_date article.created_at 22 | td = format_date article.updated_at 23 | td 24 | = link_to '浏览', article_path(article), class: 'btn btn-info btn-sm', target: '_blank' 25 | ' 26 | - if article.draft 27 | = link_to "发布", push_admin_article_path(article), class: 'btn btn-warning btn-sm' 28 | - else 29 | = link_to "下架", push_admin_article_path(article), class: 'btn btn-danger btn-sm' 30 | td 31 | = link_to '更新', edit_admin_article_path(article), class: 'btn btn-info btn-sm' 32 | ' 33 | = link_to '删除', admin_article_path(article), method: 'DELETE', data: { confirm: "确定删除#{article.title}这篇文章?" }, class: 'btn btn-danger btn-sm' 34 | 35 | = paginate @articles 36 | -------------------------------------------------------------------------------- /app/views/admin/articles/new.html.slim: -------------------------------------------------------------------------------- 1 | h2.text-center 新建文章 2 | = render 'form' 3 | -------------------------------------------------------------------------------- /app/views/admin/dashboard/index.html.slim: -------------------------------------------------------------------------------- 1 | .container-fluid 2 | ol.breadcrumb 3 | li.breadcrumb-item 4 | a href='#' Dashboard 5 | .row 6 | .col-xl-3.col-sm-6.mb-3 7 | .card.text-white.bg-primary.o-hidden.h-100 8 | .card-body 9 | .card-body-icon 10 | i.fa.fa-fw.fa-table 11 | .mr-5 #{Article.count} Articles! 12 | = link_to admin_articles_path, class: 'card-footer text-white clearfix small z-1' do 13 | span.float-left View Details 14 | span.float-right 15 | i.fa.fa-angle-right 16 | .col-xl-3.col-sm-6.mb-3 17 | .card.text-white.bg-warning.o-hidden.h-100 18 | .card-body 19 | .card-body-icon 20 | i.fa.fa-fw.fa-address-book-o 21 | .mr-5 #{Photo.count} Photos! 22 | = link_to admin_photos_path, class: 'card-footer text-white clearfix small z-1' do 23 | span.float-left View Details 24 | span.float-right 25 | i.fa.fa-angle-right 26 | .col-xl-3.col-sm-6.mb-3 27 | .card.text-white.bg-success.o-hidden.h-100 28 | .card-body 29 | .card-body-icon 30 | i.fa.fa-fw.fa-comments 31 | .mr-5 26 New Messages! 32 | a.card-footer.text-white.clearfix.small.z-1 33 | span.float-left View Details 34 | span.float-right 35 | i.fa.fa-angle-right 36 | .col-xl-3.col-sm-6.mb-3 37 | .card.text-white.bg-danger.o-hidden.h-100 38 | .card-body 39 | .card-body-icon 40 | i.fa.fa-fw.fa-file-text-o 41 | .mr-5 #{Resume.first.visit} Resume Visited! 42 | = link_to edit_admin_resume_path, class: 'card-footer text-white clearfix small z-1' do 43 | span.float-left View Details 44 | span.float-right 45 | i.fa.fa-angle-right 46 | -------------------------------------------------------------------------------- /app/views/admin/photos/_form.html.slim: -------------------------------------------------------------------------------- 1 | .row 2 | .offset-md-2.col-md-8 3 | = simple_form_for [:admin, @photo] do |f| 4 | = f.error_notification 5 | = f.input :title 6 | = f.input :subtitle 7 | = f.text_area :content, id: 'editor_content', class: 'simditor', autofocus: true 8 | = f.submit '提交', class: 'btn btn-primary' 9 | ' 10 | = link_to '取消', admin_photos_path 11 | 12 | javascript: 13 | new Simditor({ 14 | textarea: $('#editor_content'), 15 | toolbar: ['title', 'bold', 'italic', 'underline', 'strikethrough', 'fontScale', 'color', '|', 'ol', 'ul', '|', 'blockquote', 'code', 'table', 'link', 'image', 'hr', 'indent', 'outdent', 'alignment'], 16 | upload: { 17 | url: '/admin/upload', //文件上传的接口地址 18 | params: null, //键值对,指定文件上传接口的额外参数,上传的时候随文件一起提交 19 | fileKey: 'file', //服务器端获取文件数据的参数名 20 | connectionCount: 3, 21 | leaveConfirm: '正在上传文件' 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /app/views/admin/photos/edit.html.slim: -------------------------------------------------------------------------------- 1 | h2.text-center 更新相册 2 | = render 'form' 3 | -------------------------------------------------------------------------------- /app/views/admin/photos/index.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title) do 2 | | 相册列表 3 | 4 | p = link_to '新建', new_admin_photo_path, class: 'btn btn-info' 5 | table.table 6 | thead 7 | tr 8 | th ID 9 | th 相册名称 10 | th 相册描述 11 | th 访问数 12 | th 创建时间 13 | th 更新时间 14 | th 操作 15 | tbody 16 | - @photos.each do |photo| 17 | tr 18 | td = photo.id 19 | td = photo.title 20 | td = photo.subtitle 21 | td = photo.visit 22 | td = format_date photo.created_at 23 | td = format_date photo.updated_at 24 | td 25 | = link_to '浏览', photos_path, class: 'btn btn-info btn-sm', target: '_blank' 26 | ' 27 | - if photo.draft 28 | = link_to "发布", push_admin_photo_path(photo), class: 'btn btn-warning btn-sm' 29 | - else 30 | = link_to "下架", push_admin_photo_path(photo), class: 'btn btn-danger btn-sm' 31 | td 32 | = link_to '更新', edit_admin_photo_path(photo), class: 'btn btn-info btn-sm' 33 | ' 34 | = link_to '删除', admin_photo_path(photo), method: 'DELETE', data: { confirm: "确定删除#{photo.title}这个相册?" }, class: 'btn btn-danger btn-sm' 35 | 36 | = paginate @photos 37 | -------------------------------------------------------------------------------- /app/views/admin/photos/new.html.slim: -------------------------------------------------------------------------------- 1 | h2.text-center 新建相册 2 | = render 'form' 3 | -------------------------------------------------------------------------------- /app/views/admin/resumes/edit.html.slim: -------------------------------------------------------------------------------- 1 | .row 2 | .offset-md-2.col-md-8 3 | = simple_form_for [:admin, @resume] do |f| 4 | = f.error_notification 5 | = f.input :title 6 | = f.input :subtitle 7 | = f.text_area :content, id: 'editor_content', class: 'simditor', autofocus: true 8 | = f.submit '提交', class: 'btn btn-primary' 9 | 10 | javascript: 11 | new Simditor({textarea: $('#editor_content')}); 12 | -------------------------------------------------------------------------------- /app/views/articles/index.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title) do 2 | | 所有文章 3 | header.masthead.articles-index 4 | .overlay 5 | .container 6 | .row 7 | .col-lg-8.col-md-10.mx-auto 8 | .site-heading 9 | h1 所有文章 10 | span.subheading 关于生活,技术 11 | .container 12 | .row 13 | .col-lg-8.col-md-10.mx-auto 14 | - @articles.each do |article| 15 | .post-preview 16 | = link_to article_path(article) do 17 | h2.post-title #{article.title} 18 | h3.post-subtitle #{article.subtitle} 19 | p.post-meta 20 | |Posted by 21 | ' 22 | = link_to 'Rina', article_path(article) 23 | ' 24 | |on #{format_date article.created_at} 25 | hr 26 | = paginate @articles 27 | -------------------------------------------------------------------------------- /app/views/articles/show.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title) do 2 | = @article.title 3 | 4 | header class="masthead articles-show-#{[*1,2].sample}" 5 | .overlay 6 | .container 7 | .row 8 | .col-lg-8.col-md-10.mx-auto 9 | .post-heading 10 | h1 = @article.title 11 | h2.subheading = @article.subtitle 12 | span.meta 13 | |Posted by 14 | ' 15 | = link_to 'Rina', '#' 16 | ' 17 | |on #{format_date @article.created_at} 18 | h3 = link_to '打赏支持', '#', 'data-toggle': "modal", 'data-target': "#PaysModal", class: 'btn btn-outline-white' 19 | 20 | .container 21 | .row 22 | .col-lg-8.col-md-10.mx-auto 23 | .content-markdown 24 | = @article.content.html_safe 25 | 26 | .footer.modal.fade#PaysModal tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" 27 | .modal-dialog.modal-lg role="document" 28 | .modal-content 29 | .modal-header 30 | button type="button" class="close" data-dismiss="modal" aria-label="Close" 31 | span aria-hidden="true" 32 | | × 33 | .message 34 | i.fa.fa-quote-left.pull-left 35 | span 如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作! 36 | i.fa.fa-quote-right.pull-right 37 | .modal-body.clearfix 38 | .row 39 | .col-md-2.col-lg-2.mx-auto 40 | .col-md-4.col-lg-4.mx-auto 41 | = image_tag 'ali-pay.jpg', width: '100%' 42 | .col-md-4.col-lg-4.mx-auto 43 | = image_tag 'wechat-pay.jpg', width: '100%' 44 | .col-md-2.col-lg-2.mx-auto 45 | -------------------------------------------------------------------------------- /app/views/home/about.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title) do 2 | | 关于刘祯 3 | header.masthead.home-about 4 | .container 5 | .row 6 | .col-lg-8.col-md-10.mx-auto 7 | .site-heading 8 | h1 关于刘祯 9 | span.subheading 工作经历 10 | .container 11 | .row 12 | .col-lg-8.col-md-10.mx-auto 13 | .content-markdown 14 | = @resume.content.html_safe 15 | -------------------------------------------------------------------------------- /app/views/home/index.html.slim: -------------------------------------------------------------------------------- 1 | header class="masthead home-index-#{[*1,2].sample}" 2 | .container 3 | .row 4 | .col-lg-8.col-md-10.mx-auto 5 | .site-heading 6 | h1 刘祯 - Rina 7 | span.subheading 坐标深圳 8 | .container 9 | .row 10 | .col-lg-8.col-md-10.mx-auto 11 | - @articles.each do |article| 12 | .post-preview 13 | = link_to article_path(article) do 14 | h2.post-title #{article.title} 15 | h3.post-subtitle #{article.subtitle} 16 | p.post-meta 17 | |Posted by 18 | ' 19 | = link_to 'Rina', article_path(article) 20 | ' 21 | |on #{format_date article.created_at} 22 | hr 23 | - unless @articles.blank? 24 | .clearfix 25 | = link_to 'Older Posts →', articles_path, class: 'btn btn-primary float-right' 26 | -------------------------------------------------------------------------------- /app/views/home/timeline.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title) do 2 | | 时间线 3 | header.masthead.home-timeline 4 | .container 5 | .row 6 | .col-lg-8.col-md-10.mx-auto 7 | .site-heading 8 | h1 时间线 9 | span.subheading 成长经历 10 | section.cd-container#cd-timeline 11 | .cd-timeline-block 12 | .cd-timeline-img.cd-picture 13 | .cd-timeline-content 14 | h2 博客系统2.0 15 | p 后台文章管理、相册管理、简历在线编辑、数据统计等 16 | = link_to 'Read more', 'https://github.com/liuzhenangel/RBlog', target: '_blank', class: 'cd-read-more' 17 | span.cd-date 现在 18 | .cd-timeline-block 19 | .cd-timeline-img.cd-location 20 | .cd-timeline-content 21 | h2 RubyChina - React 项目 22 | p react-ruby-china 项目是一个利用 react, react-dom, react-redux, react-router, redux, es6 和 redux-thunk 实现的 RubyChina 社区克隆项目, 主要目的是为了熟悉 react.js; 这是一个前后端分离项目, 前端主要是 react, react-dom, react-redux, react-router, redux, es6, redux-thunk, css 框架使用了 bootstrap. 后端利用 ruby-china 开放的 api. 支持响应式布局. 目前已有 290+ star 数. 23 | = link_to 'Read more', 'https://github.com/liuzhenangel/v2ex_frontend', target: '_blank', class: 'cd-read-more' 24 | span.cd-date 201610 25 | .cd-timeline-block 26 | .cd-timeline-img.cd-movie 27 | .cd-timeline-content 28 | h2 V2EX - vue.js 项目 29 | p V2EX 是一个利用 vue.js 和 v2ex api 实现的 V2EX 社区克隆项目, 主要目的是为了熟悉 vue.js; 这是一个前后端分离项目, 前端主要是 vue.js 和 vue-router, css 框架使用了 uikit. 后端利用 v2ex 开放的 api. 支持响应式布局. 目前已有 330+ star 数. 30 | = link_to 'Read more', 'https://github.com/liuzhenangel/v2ex_frontend', target: '_blank', class: 'cd-read-more' 31 | span.cd-date 201610 32 | .cd-timeline-block 33 | .cd-timeline-img.cd-picture 34 | .cd-timeline-content 35 | h2 君投天地 Co.Fund(自由职业) 36 | p 纽约的一个股权众筹的创业项目, 我作为主程负责开发了相关功能: 线上融资流程, 用户充值取现系统, 授权认证等; 前端使用了 AngularJS1.x, Foundation5 和 font-awesome, underscore, SASS 技术栈; 后端使用了 Rails; 收获了很多: https 布署, 全站 CDN 加速, 前后端分离的实践. 37 | = link_to 'Read more', 'https://co.fund', target: '_blank', class: 'cd-read-more' 38 | span.cd-date 201606 39 | .cd-timeline-block 40 | .cd-timeline-img.cd-location 41 | .cd-timeline-content 42 | h2 优融网络科技-Rails 高级工程师 43 | p UBOSS 商城, 是一个专注本地化的电商系统; 主要负责 后端逻辑 及 前端UI 工作; 主要开发功能: 运费模板, 单个或多个商品运费计算, 运费满减, 订单评价, 退款/退货售后, 团购管理(店铺管理/验证管理/前台用户&商家模块/收益管理/评价管理/商品管理) 等等. 44 | = link_to 'Read more', 'http://uboss.me/', target: '_blank', class: 'cd-read-more' 45 | span.cd-date 201509 46 | .cd-timeline-block 47 | .cd-timeline-img.cd-movie 48 | .cd-timeline-content 49 | h2 GODA(自由职业) 50 | p GODA 是一站式高端人才招聘的创业项目, 我负责了前期所有前后端开发及部署上线工作; 核心功能: 简历拍卖, 面试邀请, 后台审核, 接受/拒绝邀请, 邮件通知等; 收获: mina + unicorn + nignx 环境部署, RJS 前端交互. 51 | = link_to 'Read more', 'http://51goda.com/', target: '_blank', class: 'cd-read-more' 52 | span.cd-date 201506 53 | .cd-timeline-block 54 | .cd-timeline-img.cd-picture 55 | .cd-timeline-content 56 | h2 ECS - 一个发现,创造与分享城市一切美好事物的公益平台 57 | p 这是为朋友做的一个公益平台项目, 主要用于发布活动信息, 核心功能: 搜索, 点赞, 分享, 评论, 后台管理等功能; 前端使用了 Bootstrap, jquery, SASS, Coffee 技术栈; 后端使用了 Rails. 58 | = link_to 'Read more', 'http://ecssz.com/', target: '_blank', class: 'cd-read-more' 59 | span.cd-date 201504 60 | .cd-timeline-block 61 | .cd-timeline-img.cd-location 62 | .cd-timeline-content 63 | h2 首个个人博客 64 | p 这是我做的第一个技术博客, 具备博客管理, 点赞, 评论, 相册, 音乐盒等现代网站所有特性; 前端使用了 Bootstrap, jquery, SASS, Coffee 技术栈; 后端使用了 Rails. 65 | = link_to 'Read more', 'https://github.com/liuzhenangel/Rina_Blog', target: '_blank', class: 'cd-read-more' 66 | span.cd-date 201503 67 | .cd-timeline-block 68 | .cd-timeline-img.cd-movie 69 | .cd-timeline-content 70 | h2 深信服科技-测试开发工程师 71 | p 作为测试开发工程师, 与团队一起负责测试资源管理平台的开发及维护; 负责自动化框架的开发与维护; 完成一些内部工具的开发如: 企业内部搜索引擎的技术选型与开发. 72 | = link_to 'Read more', 'http://www.sangfor.com.cn/', target: '_blank', class: 'cd-read-more' 73 | span.cd-date 201303 74 | .cd-timeline-block 75 | .cd-timeline-img.cd-picture 76 | .cd-timeline-content 77 | h2 本科毕业 78 | p 毕业于中南林业科技大学(本科) 79 | span.cd-date 201309 80 | -------------------------------------------------------------------------------- /app/views/kaminari/_first_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.first?, raw(t 'views.pagination.first'), 3 | url, remote: remote 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_gap.html.slim: -------------------------------------------------------------------------------- 1 | li.disabled 2 | = link_to raw(t 'views.pagination.truncate'), '#' 3 | -------------------------------------------------------------------------------- /app/views/kaminari/_last_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.last?, raw(t 'views.pagination.last'), 3 | url, remote: remote 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_next_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.last?, raw(t 'views.pagination.next'), 3 | url, rel: 'next', remote: remote 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_page.html.slim: -------------------------------------------------------------------------------- 1 | li class="#{'active' if page.current?}" 2 | = link_to page, page.current? ? '#' : url, 3 | remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_paginator.html.slim: -------------------------------------------------------------------------------- 1 | = paginator.render do 2 | ul.pagination 3 | == first_page_tag unless current_page.first? 4 | == prev_page_tag unless current_page.first? 5 | 6 | - each_page do |page| 7 | - if page.left_outer? || page.right_outer? || page.inside_window? 8 | == page_tag page 9 | - elsif !page.was_truncated? 10 | == gap_tag 11 | 12 | == next_page_tag unless current_page.last? 13 | == last_page_tag unless current_page.last? 14 | -------------------------------------------------------------------------------- /app/views/kaminari/_prev_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), 3 | url, rel: 'prev', remote: remote 4 | -------------------------------------------------------------------------------- /app/views/layouts/_admin_navigation.html.slim: -------------------------------------------------------------------------------- 1 | nav.navbar.navbar-expand-lg.navbar-dark.bg-dark.fixed-top#mainNav 2 | = link_to "Rina's Blog", '#', class: 'navbar-brand' 3 | button.navbar-toggler.navbar-toggler-right data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" 4 | span.navbar-toggler-icon 5 | .collapse.navbar-collapse#navbarResponsive 6 | ul.navbar-nav.navbar-sidenav#exampleAccordion 7 | li class="nav-item #{active_for(admin_root_path, url_for)}" data-toggle="tooltip" data-placement="right" title="" data-original-title="Charts" 8 | = link_to admin_root_path, class: 'nav-link' do 9 | i.fa.fa-fw.fa-dashboard 10 | span.nav-link-text Dashboard 11 | li class="nav-item #{active_for(admin_articles_path, url_for)}" data-toggle="tooltip" data-placement="right" title="" data-original-title="文章" 12 | = link_to admin_articles_path, class: 'nav-link' do 13 | i.fa.fa-fw.fa-table 14 | span.nav-link-text 文章 15 | li class="nav-item #{active_for(admin_photos_path, url_for)}" data-toggle="tooltip" data-placement="right" title="" data-original-title="相册" 16 | = link_to admin_photos_path, class: 'nav-link' do 17 | i.fa.fa-fw.fa-address-book-o 18 | span.nav-link-text 相册 19 | li class="nav-item #{active_for(edit_admin_resume_path, url_for)}" data-toggle="tooltip" data-placement="right" title="" data-original-title="简历" 20 | = link_to edit_admin_resume_path, class: 'nav-link' do 21 | i.fa.fa-fw.fa-file-text-o 22 | span.nav-link-text 简历 23 | ul.navbar-nav.sidenav-toggler 24 | li.nav-item 25 | a.nav-link.text-center#sidenavToggler 26 | i.fa.fa-fw.fa-angle-left 27 | ul.navbar-nav.ml-auto 28 | li.nav-item 29 | = link_to signout_path, class: 'nav-link', 'data-toggle': "modal", 'data-target': '#exampleModal' do 30 | i.fa.fa-fw.fa-sign-out 31 | |Logout 32 | -------------------------------------------------------------------------------- /app/views/layouts/_footer.html.slim: -------------------------------------------------------------------------------- 1 | footer 2 | .container 3 | .row 4 | .col-lg-8.col-md-10.mx-auto 5 | ul.list-inline.text-center 6 | li.list-inline-item 7 | = link_to 'https://twitter.com/Rina_Liuzhen', target: '_blank' do 8 | span.fa-stack.fa-lg 9 | i.fa.fa-circle.fa-stack-2x 10 | i.fa.fa-twitter.fa-stack-1x.fa-inverse 11 | li.list-inline-item 12 | = link_to 'https://github.com/liuzhenangel/RBlog', target: '_blank' do 13 | span.fa-stack.fa-lg 14 | i.fa.fa-circle.fa-stack-2x 15 | i.fa.fa-github.fa-stack-1x.fa-inverse 16 | li.list-inline-item 17 | = link_to '#', 'data-toggle': "modal", 'data-target': "#WechatModal" do 18 | span.fa-stack.fa-lg 19 | i.fa.fa-circle.fa-stack-2x 20 | i.fa.fa-qrcode.fa-stack-1x.fa-inverse 21 | li.list-inline-item 22 | = link_to '#', 'data-toggle': "modal", 'data-target': "#WechatPayModal" do 23 | span.fa-stack.fa-lg 24 | i.fa.fa-circle.fa-stack-2x 25 | i.fa.fa-weixin.fa-stack-1x.fa-inverse 26 | li.list-inline-item 27 | = link_to '#', 'data-toggle': "modal", 'data-target': "#AlypayModal" do 28 | span.fa-stack.fa-lg 29 | = image_tag 'alipay.png', class: 'fa-alipay' 30 | p.copyright.text-muted Copyright © Rina's Blog 2017 31 | .footer.modal.fade#WechatModal tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" 32 | .modal-dialog role="document" 33 | .modal-content 34 | .modal-header 35 | button type="button" class="close" data-dismiss="modal" aria-label="Close" 36 | span aria-hidden="true" 37 | | × 38 | .message 39 | i.fa.fa-quote-left.pull-left 40 | span 扫码加我微信! 41 | i.fa.fa-quote-right.pull-right 42 | .modal-body.clearfix 43 | .row 44 | .col-md-2.col-lg-2.mx-auto 45 | .col-md-8.col-lg-8.mx-auto 46 | = image_tag 'wechat-1.jpg', width: '100%' 47 | .col-md-2.col-lg-2.mx-auto 48 | .footer.modal.fade#AlypayModal tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" 49 | .modal-dialog role="document" 50 | .modal-content 51 | .modal-header 52 | button type="button" class="close" data-dismiss="modal" aria-label="Close" 53 | span aria-hidden="true" 54 | | × 55 | .message 56 | i.fa.fa-quote-left.pull-left 57 | span 感谢打赏, 您的支持将鼓励我继续创作! 58 | i.fa.fa-quote-right.pull-right 59 | .modal-body.clearfix 60 | .row 61 | .col-md-2.col-lg-2.mx-auto 62 | .col-md-8.col-lg-8.mx-auto 63 | = image_tag 'ali-pay.jpg', width: '100%' 64 | .col-md-2.col-lg-2.mx-auto 65 | .footer.modal.fade#WechatPayModal tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" 66 | .modal-dialog role="document" 67 | .modal-content 68 | .modal-header 69 | button type="button" class="close" data-dismiss="modal" aria-label="Close" 70 | span aria-hidden="true" 71 | | × 72 | .message 73 | i.fa.fa-quote-left.pull-left 74 | span 感谢打赏, 您的支持将鼓励我继续创作! 75 | i.fa.fa-quote-right.pull-right 76 | .modal-body.clearfix 77 | .row 78 | .col-md-2.col-lg-2.mx-auto 79 | .col-md-8.col-lg-8.mx-auto 80 | = image_tag 'wechat-pay.jpg', width: '100%' 81 | .col-md-2.col-lg-2.mx-auto 82 | -------------------------------------------------------------------------------- /app/views/layouts/_messages.html.slim: -------------------------------------------------------------------------------- 1 | - flash.each do |name, msg| 2 | - if msg.is_a?(String) 3 | - alertname = (name.to_s == 'error' || name.to_s == 'alert' || name.to_s == 'errors') ? 'danger' : 'success' 4 | div class="alert alert-#{alertname}" 5 | button.close[type="button" data-dismiss="alert" aria-hidden="true"] × 6 | = content_tag :div, msg, :id => "flash_#{name}" 7 | -------------------------------------------------------------------------------- /app/views/layouts/_navigation.html.slim: -------------------------------------------------------------------------------- 1 | nav.navbar.navbar-expand-lg.navbar-light.fixed-top#mainNav 2 | .container 3 | = link_to "Rina's Blog", root_path, class: 'navbar-brand' 4 | button.navbar-toggler.navbar-toggler-right type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="true" aria-label="Toggle navigation" 5 | |Menu 6 | i.fa.fa-bars 7 | .collapse.navbar-collapse#navbarResponsive 8 | ul.navbar-nav.ml-auto 9 | li.nav-item 10 | = link_to '首页', root_path, class: 'nav-link' 11 | li.nav-item 12 | = link_to '所有', articles_path, class: 'nav-link' 13 | li.nav-item 14 | = link_to '相册', photos_path, class: 'nav-link' 15 | li.nav-item 16 | = link_to '关于', about_path, class: 'nav-link' 17 | li.nav-item 18 | = link_to '时间线', timeline_path, class: 'nav-link' 19 | -------------------------------------------------------------------------------- /app/views/layouts/admin.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta charset='utf-8' 5 | meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" 6 | meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1,IE=11" 7 | meta name="renderer" content="webkit" 8 | meta http-equiv="cleartype" content="on" 9 | meta name="HandheldFriendly" content="True" 10 | meta name="MobileOptimized" content="320" 11 | title 12 | = content_for?(:title) ? "#{yield(:title)} | Rina's Blog" : "Rina's Blog" 13 | = csrf_meta_tags 14 | = action_cable_meta_tag 15 | = content_for?(:head) ? yield(:head) : '' 16 | = stylesheet_link_tag 'admin', media: 'all', 'data-turbolinks-track': 'reload' 17 | = javascript_include_tag 'admin', 'data-turbolinks-track': 'reload' 18 | = favicon_link_tag "favicon.ico" 19 | body.fixed-nav.sticky-footer.bg-dark#page-top 20 | == render 'layouts/admin_navigation' 21 | .content-wrapper 22 | .container-fluid 23 | == render 'layouts/messages' 24 | == yield 25 | footer.sticky-footer 26 | .container 27 | .text-center 28 | small Copyright © Rina's Blog 2017 29 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta charset='utf-8' 5 | meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" 6 | meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" 7 | meta name="renderer" content="webkit" 8 | meta http-equiv="cleartype" content="on" 9 | meta name="HandheldFriendly" content="True" 10 | meta name="MobileOptimized" content="320" 11 | title 12 | = content_for?(:title) ? "#{yield(:title)} | Rina's Blog" : "Rina's Blog" 13 | = csrf_meta_tags 14 | = action_cable_meta_tag 15 | = content_for?(:head) ? yield(:head) : '' 16 | = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' 17 | = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' 18 | = favicon_link_tag "favicon.ico" 19 | 20 | body.front_end 21 | == render 'layouts/navigation' 22 | == yield 23 | == render 'layouts/footer' 24 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/layouts/photo.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta charset='utf-8' 5 | meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" 6 | meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" 7 | meta name="renderer" content="webkit" 8 | meta http-equiv="cleartype" content="on" 9 | meta name="HandheldFriendly" content="True" 10 | meta name="MobileOptimized" content="320" 11 | title 12 | = content_for?(:title) ? "#{yield(:title)} | Rina's Blog" : "Rina's Blog" 13 | = csrf_meta_tags 14 | = action_cable_meta_tag 15 | = content_for?(:head) ? yield(:head) : '' 16 | = stylesheet_link_tag 'photo', media: 'all' 17 | = javascript_include_tag 'photo' 18 | = favicon_link_tag "favicon.ico" 19 | 20 | body.photo-wrapper 21 | == render 'layouts/navigation' 22 | == yield 23 | == render 'layouts/footer' 24 | -------------------------------------------------------------------------------- /app/views/photos/_album-samples.html.slim: -------------------------------------------------------------------------------- 1 | .album-samples 2 | .container 3 | .bar-wrap 4 | .bar 5 | a.icon.quit 6 | #canvas 7 | .zoom-icon.zoom-icon-in 8 | .magazine-viewport 9 | .container 10 | .magazine 11 | /div ignore="1" class="next-button" 12 | /div ignore="1" class="previous-button" 13 | - images.each do |name| 14 | = image_tag name, class: 'image' 15 | .bottom 16 | #slider-bar.turnjs-slider 17 | #slider 18 | -------------------------------------------------------------------------------- /app/views/photos/index.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title) do 2 | | 相册 3 | .container.album-wrap 4 | - @photos.each_slice(3) do |photos| 5 | .row.photo-wrap 6 | - photos.each do |photo| 7 | - next unless photo.cover 8 | .col-lg-4.col-md-4.mx-auto 9 | = link_to photo_path(photo), remote: true do 10 | .hover.column 11 | figure 12 | = image_tag photo.cover, class: 'animated swing' 13 | .album-samples 14 | -------------------------------------------------------------------------------- /app/views/photos/show.js.erb: -------------------------------------------------------------------------------- 1 | $(window).scrollTop(0) 2 | $('.album-samples').replaceWith("<%= j render(partial: 'album-samples', locals: { images: @photo.images }) %>"); 3 | if (Modernizr.csstransforms) { 4 | $('head').append('<%= javascript_include_tag 'turn.min.js' %>') 5 | }else{ 6 | $('head').append('<%= javascript_include_tag 'turn.html4.min.js' %>') 7 | $('head').append('<%= stylesheet_link_tag 'jquery.ui.html4.css' %>') 8 | } 9 | 10 | function loadApp() { 11 | $('#canvas').fadeIn(1000); 12 | var flipbook = $('.magazine'); 13 | $('.album-samples').hide(); 14 | // Check if the CSS was already loaded 15 | if (flipbook.width()==0 || flipbook.height()==0) { 16 | setTimeout(loadApp, 10); 17 | return; 18 | } 19 | // Create the flipbook 20 | flipbook.turn({ 21 | // Magazine width 22 | // width: 800, 23 | // Magazine height 24 | // height: 400, 25 | // Duration in millisecond 26 | duration: 1000, 27 | // Enables gradients 28 | gradients: true, 29 | // Auto center this flipbook 30 | autoCenter: true, 31 | // Elevation from the edge of the flipbook when turning a page 32 | elevation: 50, 33 | // The number of pages 34 | //pages: 7, 35 | // Events 36 | when: { 37 | turning: function(event, page, view) { 38 | var book = $(this), 39 | currentPage = book.turn('page'), 40 | pages = book.turn('pages'); 41 | // Update the current URI 42 | Hash.go('page/' + page).update(); 43 | // Show and hide navigation buttons 44 | disableControls(page); 45 | }, 46 | turned: function(event, page, view) { 47 | disableControls(page); 48 | $(this).turn('center'); 49 | $('#slider').slider('value', getViewNumber($(this), page)); 50 | if (page==1) { 51 | $(this).turn('peel', 'br'); 52 | } 53 | }, 54 | missing: function (event, pages) { 55 | // Add pages that aren't in the magazine 56 | for (var i = 0; i < pages.length; i++) 57 | addPage(pages[i], $(this)); 58 | } 59 | } 60 | }); 61 | // Zoom.js 62 | $('.magazine-viewport').zoom({ 63 | flipbook: $('.magazine'), 64 | max: function() { 65 | return largeMagazineWidth()/$('.magazine').width(); 66 | }, 67 | when: { 68 | swipeLeft: function() { 69 | $(this).zoom('flipbook').turn('next'); 70 | }, 71 | swipeRight: function() { 72 | $(this).zoom('flipbook').turn('previous'); 73 | }, 74 | resize: function(event, scale, page, pageElement) { 75 | if (scale==1) 76 | loadSmallPage(page, pageElement); 77 | else 78 | loadLargePage(page, pageElement); 79 | }, 80 | zoomIn: function () { 81 | $('#slider-bar').hide(); 82 | $('.made').hide(); 83 | $('.magazine').removeClass('animated').addClass('zoom-in'); 84 | $('.zoom-icon').removeClass('zoom-icon-in').addClass('zoom-icon-out'); 85 | if (!window.escTip && !$.isTouch) { 86 | escTip = true; 87 | $('
', {'class': 'exit-message'}). 88 | html('
Press ESC to exit
'). 89 | appendTo($('body')). 90 | delay(2000). 91 | animate({opacity:0}, 500, function() { 92 | $(this).remove(); 93 | }); 94 | } 95 | }, 96 | zoomOut: function () { 97 | $('#slider-bar').fadeIn(); 98 | $('.exit-message').hide(); 99 | $('.made').fadeIn(); 100 | $('.zoom-icon').removeClass('zoom-icon-out').addClass('zoom-icon-in'); 101 | setTimeout(function(){ 102 | $('.magazine').addClass('animated').removeClass('zoom-in'); 103 | resizeViewport(); 104 | }, 0); 105 | 106 | } 107 | } 108 | }); 109 | // Zoom event 110 | if ($.isTouch) 111 | $('.magazine-viewport').bind('zoom.doubleTap', zoomTo); 112 | else 113 | $('.magazine-viewport').bind('zoom.tap', zoomTo); 114 | 115 | // Using arrow keys to turn the page 116 | $(document).keydown(function(e){ 117 | var previous = 37, next = 39, esc = 27; 118 | switch (e.keyCode) { 119 | case previous: 120 | // left arrow 121 | $('.magazine').turn('previous'); 122 | e.preventDefault(); 123 | break; 124 | case next: 125 | //right arrow 126 | $('.magazine').turn('next'); 127 | e.preventDefault(); 128 | break; 129 | case esc: 130 | $('.magazine-viewport').zoom('zoomOut'); 131 | e.preventDefault(); 132 | break; 133 | } 134 | }); 135 | // URIs - Format #/page/1 136 | Hash.on('^page\/([0-9]*)$', { 137 | yep: function(path, parts) { 138 | var page = parts[1]; 139 | if (page!==undefined) { 140 | if ($('.magazine').turn('is')) 141 | $('.magazine').turn('page', page); 142 | } 143 | }, 144 | nop: function(path) { 145 | if ($('.magazine').turn('is')) 146 | $('.magazine').turn('page', 1); 147 | } 148 | }); 149 | 150 | 151 | $(window).resize(function() { 152 | resizeViewport(); 153 | }).bind('orientationchange', function() { 154 | resizeViewport(); 155 | }); 156 | 157 | // Regions 158 | 159 | if ($.isTouch) { 160 | $('.magazine').bind('touchstart', regionClick); 161 | } else { 162 | $('.magazine').click(regionClick); 163 | } 164 | 165 | // Events for the next button 166 | 167 | $('.next-button').bind($.mouseEvents.over, function() { 168 | $(this).addClass('next-button-hover'); 169 | }).bind($.mouseEvents.out, function() { 170 | $(this).removeClass('next-button-hover'); 171 | }).bind($.mouseEvents.down, function() { 172 | $(this).addClass('next-button-down'); 173 | }).bind($.mouseEvents.up, function() { 174 | $(this).removeClass('next-button-down'); 175 | }).click(function() { 176 | $('.magazine').turn('next'); 177 | }); 178 | 179 | // Events for the next button 180 | $('.previous-button').bind($.mouseEvents.over, function() { 181 | $(this).addClass('previous-button-hover'); 182 | }).bind($.mouseEvents.out, function() { 183 | $(this).removeClass('previous-button-hover'); 184 | }).bind($.mouseEvents.down, function() { 185 | $(this).addClass('previous-button-down'); 186 | }).bind($.mouseEvents.up, function() { 187 | $(this).removeClass('previous-button-down'); 188 | }).click(function() { 189 | $('.magazine').turn('previous'); 190 | }); 191 | 192 | 193 | // Slider 194 | 195 | $( "#slider" ).slider({ 196 | min: 1, 197 | max: numberOfViews(flipbook), 198 | 199 | start: function(event, ui) { 200 | 201 | if (!window._thumbPreview) { 202 | _thumbPreview = $('
', {'class': 'thumbnail'}).html('
'); 203 | setPreview(ui.value); 204 | _thumbPreview.appendTo($(ui.handle)); 205 | } else 206 | setPreview(ui.value); 207 | 208 | moveBar(false); 209 | 210 | }, 211 | 212 | slide: function(event, ui) { 213 | 214 | setPreview(ui.value); 215 | 216 | }, 217 | 218 | stop: function() { 219 | 220 | if (window._thumbPreview) 221 | _thumbPreview.removeClass('show'); 222 | 223 | $('.magazine').turn('page', Math.max(1, $(this).slider('value')*2 - 2)); 224 | 225 | } 226 | }); 227 | resizeViewport(); 228 | $('.magazine').addClass('animated'); 229 | $('.album-samples').show() 230 | $('body').addClass('overflow-hidden') 231 | $('.container.album-wrap').hide() 232 | $('header.masthead').hide() 233 | $('.magazine').turn('page', 1) 234 | $('.magazine').turn('page', 2) 235 | } 236 | 237 | // Zoom icon 238 | 239 | $('.zoom-icon').bind('mouseover', function() { 240 | if ($(this).hasClass('zoom-icon-in')) 241 | $(this).addClass('zoom-icon-in-hover'); 242 | 243 | if ($(this).hasClass('zoom-icon-out')) 244 | $(this).addClass('zoom-icon-out-hover'); 245 | 246 | }).bind('mouseout', function() { 247 | 248 | if ($(this).hasClass('zoom-icon-in')) 249 | $(this).removeClass('zoom-icon-in-hover'); 250 | 251 | if ($(this).hasClass('zoom-icon-out')) 252 | $(this).removeClass('zoom-icon-out-hover'); 253 | 254 | }).bind('click', function() { 255 | if ($(this).hasClass('zoom-icon-in')) 256 | $('.magazine-viewport').zoom('zoomIn'); 257 | else if ($(this).hasClass('zoom-icon-out')) 258 | $('.magazine-viewport').zoom('zoomOut'); 259 | }); 260 | 261 | $('#canvas').hide(); 262 | yepnope({ 263 | complete: loadApp 264 | }); 265 | -------------------------------------------------------------------------------- /app/views/sessions/new.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta charset='utf-8' 5 | meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" 6 | meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" 7 | meta name="renderer" content="webkit" 8 | meta http-equiv="cleartype" content="on" 9 | meta name="HandheldFriendly" content="True" 10 | meta name="MobileOptimized" content="320" 11 | - if content_for?(:title) 12 | = yield(:title) 13 | - else 14 | title RBlog 15 | = csrf_meta_tags 16 | = action_cable_meta_tag 17 | = content_for?(:head) ? yield(:head) : '' 18 | = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' 19 | = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' 20 | = favicon_link_tag "favicon.ico" 21 | body 22 | == render 'layouts/messages' 23 | .container 24 | .row 25 | .col-lg-8.col-md-10.mx-auto 26 | h1.text-center 登录 27 | = form_for(:session, url: sessions_path, class: 'form-horizontal') do |f| 28 | .form-group 29 | label.col-form-label 用户名 30 | = f.text_field :username, class: 'form-control', placeholder: '用户名' 31 | .form-group 32 | label.col-form-label 密码 33 | = f.password_field :password, class: 'form-control', placeholder: '密码' 34 | = f.submit '登录', class: 'btn btn-large btn-primary' 35 | == render 'layouts/footer' 36 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # Install JavaScript dependencies if using Yarn 22 | # system('bin/yarn') 23 | 24 | 25 | # puts "\n== Copying sample files ==" 26 | # unless File.exist?('config/database.yml') 27 | # cp 'config/database.yml.sample', 'config/database.yml' 28 | # end 29 | 30 | puts "\n== Preparing database ==" 31 | system! 'bin/rails db:setup' 32 | 33 | puts "\n== Removing old logs and tempfiles ==" 34 | system! 'bin/rails log:clear tmp:clear' 35 | 36 | puts "\n== Restarting application server ==" 37 | system! 'bin/rails restart' 38 | end 39 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | VENDOR_PATH = File.expand_path('..', __dir__) 3 | Dir.chdir(VENDOR_PATH) do 4 | begin 5 | exec "yarnpkg #{ARGV.join(" ")}" 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 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 RBlog 10 | class Application < Rails::Application 11 | 12 | config.generators do |g| 13 | g.test_framework :rspec, 14 | fixtures: true, 15 | view_specs: false, 16 | helper_specs: false, 17 | routing_specs: false, 18 | controller_specs: false, 19 | request_specs: false 20 | g.fixture_replacement :factory_girl, dir: "spec/factories" 21 | end 22 | 23 | config.generators.assets = false 24 | config.generators.helper = false 25 | 26 | config.time_zone = 'Beijing' 27 | config.i18n.available_locales = [:en, :'zh-CN'] 28 | config.i18n.default_locale = :'zh-CN' 29 | 30 | config.lograge.enabled = true 31 | # Initialize configuration defaults for originally generated Rails version. 32 | config.load_defaults 5.1 33 | 34 | # Settings in config/environments/* take precedence over those specified here. 35 | # Application configuration should go into files in config/initializers 36 | # -- all .rb files in that directory are automatically loaded. 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /config/application.yml.example: -------------------------------------------------------------------------------- 1 | # Use `rake secret` to generate one key 2 | SECRET_KEY_BASE: '' 3 | 4 | # domain name used by action cable 5 | DOMAIN: '' 6 | PROTOCOL: http 7 | 8 | # UPYUN set 9 | UPYUN_USERNAME: '' 10 | UPYUN_PASSWORD: '' 11 | UPYUN_BUCKET: '' 12 | UPYUN_BUCKET_HOST: '' 13 | 14 | # sidekiq namespace 15 | SIDEKIQ_NAMESPACE: '' 16 | 17 | USERNAME: 'xxx' 18 | PASSWORD: 'xxx' 19 | -------------------------------------------------------------------------------- /config/backup.rb.example: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | ## backup gem example 3 | ## Howto: 4 | ## $ gem install backup 5 | ## $ backup generate:model --trigger RBlog --archives --storages='local' --compressor='gzip' 6 | ## $ cp config/backup.rb.example ~/Backup/models/RBlog.rb 7 | ## $ backup perform --trigger RBlog 8 | 9 | Model.new(:RBlog, 'Description for RBlog') do 10 | 11 | database PostgreSQL do |db| 12 | db.name = "RBlog_production" 13 | db.username = "postgres" 14 | db.password = "postgres" 15 | db.host = "localhost" 16 | db.port = 5432 17 | end 18 | 19 | archive :rails_config do |archive| 20 | archive.add "/data/www/RBlog/shared/config/application.yml" 21 | archive.add "/data/www/RBlog/shared/config/database.yml" 22 | end 23 | 24 | store_with Local do |local| 25 | local.path = "/data/www/backups/" 26 | local.keep = 5 27 | end 28 | 29 | compress_with Gzip 30 | end 31 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | channel_prefix: RBlog_production 11 | -------------------------------------------------------------------------------- /config/database.yml.example: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: postgresql 3 | host: localhost 4 | encoding: unicode 5 | database: RBlog_development 6 | pool: 5 7 | username: postgres 8 | password: postgres 9 | template: template0 10 | 11 | test: 12 | adapter: postgresql 13 | host: localhost 14 | encoding: unicode 15 | database: RBlog_test 16 | pool: 5 17 | username: postgres 18 | password: postgres 19 | template: template0 20 | 21 | production: 22 | adapter: postgresql 23 | host: localhost 24 | encoding: unicode 25 | database: RBlog_production 26 | pool: 50 27 | username: postgres 28 | password: postgres 29 | template: template0 30 | -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | set :stages, %w(production) 2 | set :default_stage, 'production' 3 | 4 | require 'mina/bundler' 5 | require 'mina/rails' 6 | require 'mina/git' 7 | require 'mina/rbenv' 8 | require 'mina/puma' 9 | require "mina_sidekiq/tasks" 10 | require 'mina/logs' 11 | require 'mina/multistage' 12 | 13 | set :shared_dirs, fetch(:shared_dirs, []).push('log', 'public/uploads', 'node_modules') 14 | set :shared_files, fetch(:shared_files, []).push('config/database.yml', 'config/application.yml') 15 | 16 | set :puma_config, ->{ "#{fetch(:current_path)}/config/puma.rb" } 17 | set :sidekiq_pid, ->{ "#{fetch(:shared_path)}/tmp/pids/sidekiq.pid" } 18 | 19 | task :remote_environment do 20 | invoke :'rbenv:load' 21 | end 22 | 23 | task :setup do 24 | command %[mkdir -p "#{fetch(:shared_path)}/tmp/sockets"] 25 | command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/tmp/sockets"] 26 | 27 | command %[mkdir -p "#{fetch(:shared_path)}/tmp/pids"] 28 | command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/tmp/pids"] 29 | 30 | command %[mkdir -p "#{fetch(:shared_path)}/log"] 31 | command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/log"] 32 | 33 | command %[mkdir -p "#{fetch(:shared_path)}/public/uploads"] 34 | command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/public/uploads"] 35 | 36 | command %[mkdir -p "#{fetch(:shared_path)}/node_modules"] 37 | command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/node_modules"] 38 | 39 | command %[mkdir -p "#{fetch(:shared_path)}/config"] 40 | command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/config"] 41 | 42 | command %[touch "#{fetch(:shared_path)}/config/application.yml"] 43 | command %[echo "-----> Be sure to edit '#{fetch(:shared_path)}/config/application.yml'"] 44 | 45 | command %[touch "#{fetch(:shared_path)}/config/database.yml"] 46 | command %[echo "-----> Be sure to edit '#{fetch(:shared_path)}/config/database.yml'"] 47 | end 48 | 49 | desc "Deploys the current version to the server." 50 | task :deploy do 51 | command %[echo "-----> Server: #{fetch(:domain)}"] 52 | command %[echo "-----> Path: #{fetch(:deploy_to)}"] 53 | command %[echo "-----> Branch: #{fetch(:branch)}"] 54 | 55 | deploy do 56 | invoke :'sidekiq:quiet' 57 | invoke :'git:clone' 58 | invoke :'deploy:link_shared_paths' 59 | invoke :'bundle:install' 60 | invoke :'rails:db_migrate' 61 | invoke :'rails:assets_precompile' 62 | invoke :'deploy:cleanup' 63 | 64 | on :launch do 65 | invoke :'rbenv:load' 66 | invoke :'puma:hard_restart' 67 | invoke :'sidekiq:restart' 68 | end 69 | end 70 | end 71 | 72 | desc "Prepare the first deploy on server." 73 | task :first_deploy do 74 | command %[echo "-----> Server: #{fetch(:domain)}"] 75 | command %[echo "-----> Path: #{fetch(:deploy_to)}"] 76 | command %[echo "-----> Branch: #{fetch(:branch)}"] 77 | 78 | deploy do 79 | invoke :'git:clone' 80 | invoke :'deploy:link_shared_paths' 81 | invoke :'bundle:install' 82 | invoke :'rails:assets_precompile' 83 | invoke :'deploy:cleanup' 84 | 85 | on :launch do 86 | invoke :'rbenv:load' 87 | invoke :'rails:db_create' 88 | invoke :'rails:db_migrate' 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | set :domain, 'blog.xiguamingpian.com' 2 | set :deploy_to, '/data/www/RBlog' 3 | set :repository, 'https://github.com/liuzhenangel/RBlog.git' 4 | set :branch, 'master' 5 | set :user, 'ruby' 6 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 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. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | config.action_mailer.perform_caching = false 33 | 34 | # Print deprecation notices to the Rails logger. 35 | config.active_support.deprecation = :log 36 | 37 | # Raise an error on page load if there are pending migrations. 38 | config.active_record.migration_error = :page_load 39 | 40 | # Debug mode disables concatenation and preprocessing of assets. 41 | # This option may cause significant delays in view rendering with a large 42 | # number of complex assets. 43 | config.assets.debug = true 44 | 45 | # Suppress logger output for asset requests. 46 | config.assets.quiet = true 47 | 48 | # Raises error for missing translations 49 | # config.action_view.raise_on_missing_translations = true 50 | 51 | # Use an evented file watcher to asynchronously detect changes in source code, 52 | # routes, locales, etc. This feature depends on the listen gem. 53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 54 | end 55 | -------------------------------------------------------------------------------- /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 | # Attempt to read encrypted secrets from `config/secrets.yml.enc`. 18 | # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or 19 | # `config/secrets.yml.key`. 20 | config.read_encrypted_secrets = true 21 | 22 | # Disable serving static files from the `/public` folder by default since 23 | # Apache or NGINX already handles this. 24 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 25 | 26 | # Compress JavaScripts and CSS. 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 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 34 | 35 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 36 | # config.action_controller.asset_host = 'http://assets.example.com' 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 | # Mount Action Cable outside main process or domain 43 | config.action_cable.allowed_request_origins = [ "#{ENV['PROTOCOL']}://#{ENV['DOMAIN']}" ] 44 | # config.action_cable.mount_path = nil 45 | # config.action_cable.url = 'wss://example.com/cable' 46 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 47 | 48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 49 | # config.force_ssl = true 50 | 51 | # Use the lowest log level to ensure availability of diagnostic information 52 | # when problems arise. 53 | config.log_level = :debug 54 | 55 | # Prepend all log lines with the following tags. 56 | config.log_tags = [ :request_id ] 57 | 58 | # Use a different cache store in production. 59 | # config.cache_store = :mem_cache_store 60 | 61 | # Use a real queuing backend for Active Job (and separate queues per environment) 62 | # config.active_job.queue_adapter = :resque 63 | # config.active_job.queue_name_prefix = "RBlog_#{Rails.env}" 64 | config.action_mailer.perform_caching = false 65 | 66 | # Ignore bad email addresses and do not raise email delivery errors. 67 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 68 | # config.action_mailer.raise_delivery_errors = false 69 | 70 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 71 | # the I18n.default_locale when a translation cannot be found). 72 | config.i18n.fallbacks = true 73 | 74 | # Send deprecation notices to registered listeners. 75 | config.active_support.deprecation = :notify 76 | 77 | # Use default logging formatter so that PID and timestamp are not suppressed. 78 | config.log_formatter = ::Logger::Formatter.new 79 | 80 | # Use a different logger for distributed setups. 81 | # require 'syslog/logger' 82 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 83 | 84 | if ENV["RAILS_LOG_TO_STDOUT"].present? 85 | logger = ActiveSupport::Logger.new(STDOUT) 86 | logger.formatter = config.log_formatter 87 | config.logger = ActiveSupport::TaggedLogging.new(logger) 88 | end 89 | 90 | # Do not dump schema after migrations. 91 | config.active_record.dump_schema_after_migration = false 92 | end 93 | -------------------------------------------------------------------------------- /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 public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /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 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | Rails.application.config.assets.precompile += %w( admin.js admin.scss photo.js photo.scss turn.min.js turn.html4.min.js jquery.ui.html4.css) 15 | -------------------------------------------------------------------------------- /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/browser_warrior.rb: -------------------------------------------------------------------------------- 1 | BrowserWarrior.detect do |browser| 2 | # See https://github.com/fnando/browser#usage for more usage 3 | if browser.ie?(6) or browser.ie?(7) or browser.ie?(8) 4 | # reject when false 5 | false 6 | else 7 | true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/initializers/carrierwave.rb: -------------------------------------------------------------------------------- 1 | CarrierWave.configure do |config| 2 | config.storage = :upyun 3 | config.upyun_username = ENV['UPYUN_USERNAME'] 4 | config.upyun_password = ENV['UPYUN_PASSWORD'] 5 | config.upyun_bucket = ENV['UPYUN_BUCKET'] 6 | config.upyun_bucket_host = ENV['UPYUN_BUCKET_HOST'] 7 | end 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /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/kaminari_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | Kaminari.configure do |config| 3 | config.default_per_page = 10 4 | # config.max_per_page = nil 5 | # config.window = 4 6 | # config.outer_window = 0 7 | # config.left = 0 8 | # config.right = 0 9 | # config.page_method_name = :page 10 | # config.param_name = :page 11 | # config.params_on_first_page = false 12 | end 13 | -------------------------------------------------------------------------------- /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/sidekiq.rb: -------------------------------------------------------------------------------- 1 | Sidekiq.configure_server do |config| 2 | config.redis = { :namespace => ENV['SIDEKIQ_NAMESPACE'] } 3 | end 4 | 5 | Sidekiq.configure_client do |config| 6 | config.redis = { :namespace => ENV['SIDEKIQ_NAMESPACE']} 7 | end 8 | -------------------------------------------------------------------------------- /config/initializers/simple_form.rb: -------------------------------------------------------------------------------- 1 | # Use this setup block to configure all options available in SimpleForm. 2 | SimpleForm.setup do |config| 3 | # Wrappers are used by the form builder to generate a 4 | # complete input. You can remove any component from the 5 | # wrapper, change the order or even add your own to the 6 | # stack. The options given below are used to wrap the 7 | # whole input. 8 | config.wrappers :default, class: :input, 9 | hint_class: :field_with_hint, error_class: :field_with_errors do |b| 10 | ## Extensions enabled by default 11 | # Any of these extensions can be disabled for a 12 | # given input by passing: `f.input EXTENSION_NAME => false`. 13 | # You can make any of these extensions optional by 14 | # renaming `b.use` to `b.optional`. 15 | 16 | # Determines whether to use HTML5 (:email, :url, ...) 17 | # and required attributes 18 | b.use :html5 19 | 20 | # Calculates placeholders automatically from I18n 21 | # You can also pass a string as f.input placeholder: "Placeholder" 22 | b.use :placeholder 23 | 24 | ## Optional extensions 25 | # They are disabled unless you pass `f.input EXTENSION_NAME => true` 26 | # to the input. If so, they will retrieve the values from the model 27 | # if any exists. If you want to enable any of those 28 | # extensions by default, you can change `b.optional` to `b.use`. 29 | 30 | # Calculates maxlength from length validations for string inputs 31 | # and/or database column lengths 32 | b.optional :maxlength 33 | 34 | # Calculate minlength from length validations for string inputs 35 | b.optional :minlength 36 | 37 | # Calculates pattern from format validations for string inputs 38 | b.optional :pattern 39 | 40 | # Calculates min and max from length validations for numeric inputs 41 | b.optional :min_max 42 | 43 | # Calculates readonly automatically from readonly attributes 44 | b.optional :readonly 45 | 46 | ## Inputs 47 | b.use :label_input 48 | b.use :hint, wrap_with: { tag: :span, class: :hint } 49 | b.use :error, wrap_with: { tag: :span, class: :error } 50 | 51 | ## full_messages_for 52 | # If you want to display the full error message for the attribute, you can 53 | # use the component :full_error, like: 54 | # 55 | # b.use :full_error, wrap_with: { tag: :span, class: :error } 56 | end 57 | 58 | # The default wrapper to be used by the FormBuilder. 59 | config.default_wrapper = :default 60 | 61 | # Define the way to render check boxes / radio buttons with labels. 62 | # Defaults to :nested for bootstrap config. 63 | # inline: input + label 64 | # nested: label > input 65 | config.boolean_style = :nested 66 | 67 | # Default class for buttons 68 | config.button_class = 'btn' 69 | 70 | # Method used to tidy up errors. Specify any Rails Array method. 71 | # :first lists the first message for each field. 72 | # Use :to_sentence to list all errors for each field. 73 | # config.error_method = :first 74 | 75 | # Default tag used for error notification helper. 76 | config.error_notification_tag = :div 77 | 78 | # CSS class to add for error notification helper. 79 | config.error_notification_class = 'error_notification' 80 | 81 | # ID to add for error notification helper. 82 | # config.error_notification_id = nil 83 | 84 | # Series of attempts to detect a default label method for collection. 85 | # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] 86 | 87 | # Series of attempts to detect a default value method for collection. 88 | # config.collection_value_methods = [ :id, :to_s ] 89 | 90 | # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. 91 | # config.collection_wrapper_tag = nil 92 | 93 | # You can define the class to use on all collection wrappers. Defaulting to none. 94 | # config.collection_wrapper_class = nil 95 | 96 | # You can wrap each item in a collection of radio/check boxes with a tag, 97 | # defaulting to :span. 98 | # config.item_wrapper_tag = :span 99 | 100 | # You can define a class to use in all item wrappers. Defaulting to none. 101 | # config.item_wrapper_class = nil 102 | 103 | # How the label text should be generated altogether with the required text. 104 | # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" } 105 | 106 | # You can define the class to use on all labels. Default is nil. 107 | # config.label_class = nil 108 | 109 | # You can define the default class to be used on forms. Can be overriden 110 | # with `html: { :class }`. Defaulting to none. 111 | # config.default_form_class = nil 112 | 113 | # You can define which elements should obtain additional classes 114 | # config.generate_additional_classes_for = [:wrapper, :label, :input] 115 | 116 | # Whether attributes are required by default (or not). Default is true. 117 | # config.required_by_default = true 118 | 119 | # Tell browsers whether to use the native HTML5 validations (novalidate form option). 120 | # These validations are enabled in SimpleForm's internal config but disabled by default 121 | # in this configuration, which is recommended due to some quirks from different browsers. 122 | # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, 123 | # change this configuration to true. 124 | config.browser_validations = false 125 | 126 | # Collection of methods to detect if a file type was given. 127 | # config.file_methods = [ :mounted_as, :file?, :public_filename ] 128 | 129 | # Custom mappings for input types. This should be a hash containing a regexp 130 | # to match as key, and the input type that will be used when the field name 131 | # matches the regexp as value. 132 | # config.input_mappings = { /count/ => :integer } 133 | 134 | # Custom wrappers for input types. This should be a hash containing an input 135 | # type as key and the wrapper that will be used for all inputs with specified type. 136 | # config.wrapper_mappings = { string: :prepend } 137 | 138 | # Namespaces where SimpleForm should look for custom input classes that 139 | # override default inputs. 140 | # config.custom_inputs_namespaces << "CustomInputs" 141 | 142 | # Default priority for time_zone inputs. 143 | # config.time_zone_priority = nil 144 | 145 | # Default priority for country inputs. 146 | # config.country_priority = nil 147 | 148 | # When false, do not use translations for labels. 149 | # config.translate_labels = true 150 | 151 | # Automatically discover new inputs in Rails' autoload path. 152 | # config.inputs_discovery = true 153 | 154 | # Cache SimpleForm inputs discovery 155 | # config.cache_discovery = !Rails.env.development? 156 | 157 | # Default class for inputs 158 | # config.input_class = nil 159 | 160 | # Define the default class of the input wrapper of the boolean input. 161 | config.boolean_label_class = 'checkbox' 162 | 163 | # Defines if the default input wrapper class should be included in radio 164 | # collection wrappers. 165 | # config.include_default_input_wrapper_class = true 166 | 167 | # Defines which i18n scope will be used in Simple Form. 168 | # config.i18n_scope = 'simple_form' 169 | end 170 | -------------------------------------------------------------------------------- /config/initializers/simple_form_bootstrap.rb: -------------------------------------------------------------------------------- 1 | # Use this setup block to configure all options available in SimpleForm. 2 | SimpleForm.setup do |config| 3 | config.error_notification_class = 'alert alert-danger' 4 | config.button_class = 'btn btn-default' 5 | config.boolean_label_class = nil 6 | 7 | config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 8 | b.use :html5 9 | b.use :placeholder 10 | b.optional :maxlength 11 | b.optional :minlength 12 | b.optional :pattern 13 | b.optional :min_max 14 | b.optional :readonly 15 | b.use :label, class: 'control-label' 16 | 17 | b.use :input, class: 'form-control' 18 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' } 19 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 20 | end 21 | 22 | config.wrappers :vertical_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 23 | b.use :html5 24 | b.use :placeholder 25 | b.optional :maxlength 26 | b.optional :minlength 27 | b.optional :readonly 28 | b.use :label, class: 'control-label' 29 | 30 | b.use :input 31 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' } 32 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 33 | end 34 | 35 | config.wrappers :vertical_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 36 | b.use :html5 37 | b.optional :readonly 38 | 39 | b.wrapper tag: 'div', class: 'checkbox' do |ba| 40 | ba.use :label_input 41 | end 42 | 43 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' } 44 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 45 | end 46 | 47 | config.wrappers :vertical_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 48 | b.use :html5 49 | b.optional :readonly 50 | b.use :label, class: 'control-label' 51 | b.use :input 52 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' } 53 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 54 | end 55 | 56 | config.wrappers :horizontal_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 57 | b.use :html5 58 | b.use :placeholder 59 | b.optional :maxlength 60 | b.optional :minlength 61 | b.optional :pattern 62 | b.optional :min_max 63 | b.optional :readonly 64 | b.use :label, class: 'col-sm-3 control-label' 65 | 66 | b.wrapper tag: 'div', class: 'col-sm-9' do |ba| 67 | ba.use :input, class: 'form-control' 68 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } 69 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 70 | end 71 | end 72 | 73 | config.wrappers :horizontal_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 74 | b.use :html5 75 | b.use :placeholder 76 | b.optional :maxlength 77 | b.optional :minlength 78 | b.optional :readonly 79 | b.use :label, class: 'col-sm-3 control-label' 80 | 81 | b.wrapper tag: 'div', class: 'col-sm-9' do |ba| 82 | ba.use :input 83 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } 84 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 85 | end 86 | end 87 | 88 | config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 89 | b.use :html5 90 | b.optional :readonly 91 | 92 | b.wrapper tag: 'div', class: 'col-sm-offset-3 col-sm-9' do |wr| 93 | wr.wrapper tag: 'div', class: 'checkbox' do |ba| 94 | ba.use :label_input 95 | end 96 | 97 | wr.use :error, wrap_with: { tag: 'span', class: 'help-block' } 98 | wr.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 99 | end 100 | end 101 | 102 | config.wrappers :horizontal_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 103 | b.use :html5 104 | b.optional :readonly 105 | 106 | b.use :label, class: 'col-sm-3 control-label' 107 | 108 | b.wrapper tag: 'div', class: 'col-sm-9' do |ba| 109 | ba.use :input 110 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } 111 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 112 | end 113 | end 114 | 115 | config.wrappers :inline_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 116 | b.use :html5 117 | b.use :placeholder 118 | b.optional :maxlength 119 | b.optional :minlength 120 | b.optional :pattern 121 | b.optional :min_max 122 | b.optional :readonly 123 | b.use :label, class: 'sr-only' 124 | 125 | b.use :input, class: 'form-control' 126 | b.use :error, wrap_with: { tag: 'span', class: 'help-block' } 127 | b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 128 | end 129 | 130 | config.wrappers :multi_select, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| 131 | b.use :html5 132 | b.optional :readonly 133 | b.use :label, class: 'control-label' 134 | b.wrapper tag: 'div', class: 'form-inline' do |ba| 135 | ba.use :input, class: 'form-control' 136 | ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } 137 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 138 | end 139 | end 140 | # Wrappers for forms and inputs using the Bootstrap toolkit. 141 | # Check the Bootstrap docs (http://getbootstrap.com) 142 | # to learn about the different styles for forms and inputs, 143 | # buttons and other elements. 144 | config.default_wrapper = :vertical_form 145 | config.wrapper_mappings = { 146 | check_boxes: :vertical_radio_and_checkboxes, 147 | radio_buttons: :vertical_radio_and_checkboxes, 148 | file: :vertical_file_input, 149 | boolean: :vertical_boolean, 150 | datetime: :multi_select, 151 | date: :multi_select, 152 | time: :multi_select 153 | } 154 | end 155 | -------------------------------------------------------------------------------- /config/initializers/status_page.rb: -------------------------------------------------------------------------------- 1 | StatusPage.configure do 2 | # Cache check status result 10 seconds 3 | self.interval = 10 4 | # Use service 5 | self.use :database 6 | self.use :cache 7 | self.use :redis 8 | self.use :sidekiq 9 | end 10 | -------------------------------------------------------------------------------- /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] 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/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 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | # include_blanks: 27 | # defaults: 28 | # age: 'Rather not say' 29 | # prompts: 30 | # defaults: 31 | # age: 'Select your age' 32 | -------------------------------------------------------------------------------- /config/logrotate.conf.example: -------------------------------------------------------------------------------- 1 | # truncate your rails log every day 2 | # Usage: 3 | # `cp logrotate.conf.example /etc/logrotate.d/RBlog` 4 | /data/www/RBlog/current/log/*.log { 5 | daily 6 | missingok 7 | rotate 7 8 | compress 9 | delaycompress 10 | notifempty 11 | copytruncate 12 | su ruby ruby 13 | } 14 | -------------------------------------------------------------------------------- /config/monit.conf.example: -------------------------------------------------------------------------------- 1 | # Watch your rails app & sidekiq process and restart it automatically 2 | # Usage: 3 | # `sudo apt-get install monit -y` 4 | # `cp monit.conf.example /etc/monit/conf.d/` 5 | # `service monit restart` 6 | check process RBlog_puma 7 | with pidfile /data/www/RBlog/shared/tmp/pids/puma.pid 8 | start program = "/bin/sh -c 'cd /data/www/RBlog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV="production" bundle exec puma -q -d -e production -C config/puma.rb'" as uid ruby and gid ruby with timeout 90 seconds 9 | stop program = "/bin/sh -c 'cd /data/www/RBlog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV="production" bundle exec pumactl -F /data/www/RBlog/current/config/puma.rb stop'" as uid ruby and gid ruby with timeout 90 seconds 10 | group RBlog 11 | 12 | check process RBlog_sidekiq 13 | with pidfile /data/www/RBlog/shared/tmp/pids/sidekiq.pid 14 | start program = "/bin/sh -c 'cd /data/www/RBlog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV="production" bundle exec sidekiq -d -e production -C /data/www/RBlog/current/config/sidekiq.yml -i 0 -P /data/www/RBlog/shared/tmp/pids/sidekiq.pid -L /data/www/RBlog/current/log/sidekiq.log'" as uid ruby and gid ruby with timeout 90 seconds 15 | stop program = "/bin/sh -c 'cd /data/www/RBlog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV="production" bundle exec sidekiqctl stop /data/www/RBlog/shared/tmp/pids/sidekiq.pid 11'" as uid ruby and gid ruby with timeout 90 seconds 16 | group RBlog 17 | 18 | #check process RBlog_clockwork 19 | # with pidfile /data/www/RBlog/shared/tmp/pids/clockworkd.clock.pid 20 | # start program = "/bin/sh -c 'cd /data/www/RBlog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV=production bundle exec clockworkd -c /data/www/RBlog/current/config/clock.rb -i clock -d /data/www/RBlog/current --pid-dir /data/www/RBlog/shared/tmp/pids --log --log-dir /data/www/RBlog/shared/log start'" as uid ruby and gid ruby with timeout 90 seconds 21 | # stop program = "/bin/sh -c 'cd /data/www/RBlog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV=production bundle exec clockworkd -c /data/www/RBlog/current/config/clock.rb -i clock -d /data/www/RBlog/current --pid-dir /data/www/RBlog/shared/tmp/pids --log --log-dir /data/www/RBlog/shared/log stop'" as uid ruby and gid ruby with timeout 90 seconds 22 | # group RBlog 23 | -------------------------------------------------------------------------------- /config/nginx.conf.example: -------------------------------------------------------------------------------- 1 | upstream RBlog { 2 | server unix:///data/www/RBlog/shared/tmp/sockets/puma.sock fail_timeout=0; 3 | } 4 | 5 | server { 6 | listen 80; 7 | server_name example.com; 8 | root /data/www/RBlog/current/public; 9 | 10 | location ^~ /assets/ { 11 | gzip_static on; 12 | expires max; 13 | add_header Cache-Control public; 14 | } 15 | 16 | location /cable { 17 | proxy_http_version 1.1; 18 | proxy_set_header Upgrade $http_upgrade; 19 | proxy_set_header Connection "Upgrade"; 20 | proxy_set_header Host $host; 21 | proxy_set_header X-Real-IP $remote_addr; 22 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 23 | proxy_pass http://RBlog; 24 | } 25 | 26 | location ~ ^/(uploads)/ { 27 | expires max; 28 | break; 29 | } 30 | 31 | 32 | try_files $uri/index.html $uri @RBlog; 33 | location @RBlog { 34 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 35 | proxy_set_header Host $http_host; 36 | proxy_set_header X-Real-IP $remote_addr; 37 | proxy_redirect off; 38 | proxy_pass http://RBlog; 39 | } 40 | 41 | error_page 500 502 503 504 /500.html; 42 | client_max_body_size 20M; 43 | keepalive_timeout 10; 44 | } 45 | -------------------------------------------------------------------------------- /config/nginx.ssl.conf.example: -------------------------------------------------------------------------------- 1 | upstream RBlog { 2 | server unix:///data/www/RBlog/shared/tmp/sockets/puma.sock fail_timeout=0; 3 | } 4 | 5 | server { 6 | listen 80; 7 | # FIXME: update your domain here 8 | server_name example.com; 9 | return 301 https://$server_name$request_uri; 10 | } 11 | 12 | server { 13 | listen 443 ssl; 14 | # FIXME: update your domain here 15 | server_name example.com; 16 | root /data/www/RBlog/current/public; 17 | 18 | ssl on; 19 | 20 | # FIXME: update your domain here 21 | ssl_certificate /etc/nginx/sslkeys/yourdomain.com.key.pem; 22 | ssl_certificate_key /etc/nginx/sslkeys/yourdomain.com.key; 23 | ssl_dhparam /etc/nginx/sslkeys/dhparam.pem; 24 | 25 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 26 | 27 | # ssl optimizations 28 | ssl_session_cache shared:SSL:30m; 29 | ssl_session_timeout 30m; 30 | add_header Strict-Transport-Security "max-age=31536000"; 31 | 32 | ssl_prefer_server_ciphers on; 33 | ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; 34 | 35 | location ^~ /assets/ { 36 | gzip_static on; 37 | expires max; 38 | add_header Cache-Control public; 39 | } 40 | 41 | location ~ ^/(uploads)/ { 42 | expires max; 43 | break; 44 | } 45 | 46 | location /cable { 47 | proxy_http_version 1.1; 48 | proxy_set_header Upgrade $http_upgrade; 49 | proxy_set_header Connection "Upgrade"; 50 | proxy_set_header X-Forwarded-Proto $scheme; 51 | proxy_set_header Host $host; 52 | proxy_set_header X-Real-IP $remote_addr; 53 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 54 | proxy_pass http://RBlog; 55 | } 56 | 57 | try_files $uri/index.html $uri @RBlog; 58 | location @RBlog { 59 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 60 | proxy_set_header X-Real-IP $remote_addr; 61 | proxy_set_header Host $http_host; 62 | proxy_set_header X-Forwarded-Proto $scheme; 63 | proxy_redirect off; 64 | proxy_pass http://RBlog; 65 | } 66 | 67 | error_page 500 502 503 504 /500; 68 | error_page 404 /404; 69 | client_max_body_size 20M; 70 | keepalive_timeout 10; 71 | } 72 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | if ENV['RAILS_ENV'] == 'production' 2 | app_root = '/data/www/RBlog/shared' 3 | pidfile "#{app_root}/tmp/pids/puma.pid" 4 | state_path "#{app_root}/tmp/pids/puma.state" 5 | bind "unix://#{app_root}/tmp/sockets/puma.sock" 6 | activate_control_app "unix://#{app_root}/tmp/sockets/pumactl.sock" 7 | daemonize true 8 | workers 2 9 | threads 8, 16 10 | preload_app! 11 | 12 | stdout_redirect "#{app_root}/log/puma_access.log", "#{app_root}/log/puma_error.log", true 13 | 14 | on_worker_boot do 15 | ActiveSupport.on_load(:active_record) do 16 | ActiveRecord::Base.establish_connection 17 | end 18 | end 19 | 20 | before_fork do 21 | ActiveRecord::Base.connection_pool.disconnect! 22 | end 23 | else 24 | plugin :tmp_restart 25 | end 26 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | require 'sidekiq/web' 2 | Sidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base] 3 | 4 | Rails.application.routes.draw do 5 | 6 | # write your routes here 7 | 8 | mount Sidekiq::Web => '/sidekiq' 9 | mount StatusPage::Engine => '/' 10 | #mount ActionCable.server => '/cable' 11 | root to: 'home#index' 12 | 13 | get 'about', to: 'home#about' 14 | get 'timeline', to: 'home#timeline' 15 | get 'signout', to: 'sessions#destroy' 16 | 17 | resources :sessions, only:[:new, :create] 18 | resources :articles, only: [:index, :show] 19 | resources :photos, only: [:index, :show] 20 | namespace :admin do 21 | root 'dashboard#index', as: 'root' 22 | post '/upload', to: 'photos#upload' 23 | resources :articles do 24 | member do 25 | get :push 26 | end 27 | end 28 | resources :photos do 29 | member do 30 | get :push 31 | end 32 | end 33 | resource :resume, only: [:edit, :update] 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails 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 | # Shared secrets are available across all environments. 14 | 15 | # shared: 16 | # api_key: a1B2c3D4e5F6 17 | 18 | # Environmental secrets are only available for that specific environment. 19 | 20 | development: 21 | secret_key_base: 264f8638f7627bbf554d116f9d951722ebdc12c34aa26692eca1041f6348ac4b68a88a28f7e879f601a9c3bac1acd140af9fa0fc379e5b31f513736bb63ad702 22 | 23 | test: 24 | secret_key_base: 9c29d7113a21ec69dec81e1681633fa8bccb939d9eaad373d4daae507795bb97c21a78fd78678fabc398e958c15a8f2b6c6c23ddab4b06b662383643eba80cd4 25 | 26 | # Do not keep production secrets in the unencrypted secrets file. 27 | # Instead, either read values from the environment. 28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets 29 | # and move the `production:` environment over there. 30 | 31 | production: 32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 33 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | config/application.yml 7 | ).each { |path| Spring.watch(path) } 8 | -------------------------------------------------------------------------------- /db/migrate/20171204103444_create_bases.rb: -------------------------------------------------------------------------------- 1 | class CreateBases < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :bases do |t| 4 | t.string :title 5 | t.string :subtitle 6 | t.text :content 7 | t.string :type 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20171206112233_create_images.rb: -------------------------------------------------------------------------------- 1 | class CreateImages < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :images do |t| 4 | t.string :img 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20171225033340_add_visit_to_base.rb: -------------------------------------------------------------------------------- 1 | class AddVisitToBase < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :bases, :visit, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171225034053_add_draft_to_base.rb: -------------------------------------------------------------------------------- 1 | class AddDraftToBase < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :bases, :draft, :boolean, default: true 4 | 5 | Article.all.update(draft: false) 6 | Photo.all.update(draft: false) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 20171225034053) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "plpgsql" 17 | 18 | create_table "bases", force: :cascade do |t| 19 | t.string "title" 20 | t.string "subtitle" 21 | t.text "content" 22 | t.string "type" 23 | t.datetime "created_at", null: false 24 | t.datetime "updated_at", null: false 25 | t.integer "visit" 26 | t.boolean "draft", default: true 27 | end 28 | 29 | create_table "images", force: :cascade do |t| 30 | t.string "img" 31 | t.datetime "created_at", null: false 32 | t.datetime "updated_at", null: false 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /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 rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/lib/tasks/.keep -------------------------------------------------------------------------------- /lib/templates/slim/scaffold/_form.html.slim: -------------------------------------------------------------------------------- 1 | = simple_form_for(@<%= singular_table_name %>) do |f| 2 | = f.error_notification 3 | 4 | .form-inputs 5 | <%- attributes.each do |attribute| -%> 6 | = f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> 7 | <%- end -%> 8 | 9 | .form-actions 10 | = f.button :submit 11 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RBlog", 3 | "private": true, 4 | "dependencies": {} 5 | } 6 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

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

63 |
64 |

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

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

The change you wanted was rejected.

62 |

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

63 |
64 |

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

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

We're sorry, but something went wrong.

62 |
63 |

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

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /spec/factories/articles.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :article do 3 | title "MyString" 4 | content "MyText" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/factories/images.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :image do 3 | img "MyString" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/article_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Article, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/image_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Image, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | require 'spec_helper' 3 | ENV['RAILS_ENV'] ||= 'test' 4 | require File.expand_path('../../config/environment', __FILE__) 5 | # Prevent database truncation if the environment is production 6 | abort("The Rails environment is running in production mode!") if Rails.env.production? 7 | require 'rspec/rails' 8 | # Add additional requires below this line. Rails is not loaded until this point! 9 | 10 | # Requires supporting ruby files with custom matchers and macros, etc, in 11 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 12 | # run as spec files by default. This means that files in spec/support that end 13 | # in _spec.rb will both be required and run as specs, causing the specs to be 14 | # run twice. It is recommended that you do not name files matching this glob to 15 | # end with _spec.rb. You can configure this pattern with the --pattern 16 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 17 | # 18 | # The following line is provided for convenience purposes. It has the downside 19 | # of increasing the boot-up time by auto-requiring all files in the support 20 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 21 | # require only the support files necessary. 22 | # 23 | Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 24 | 25 | # Checks for pending migrations and applies them before tests are run. 26 | # If you are not using ActiveRecord, you can remove this line. 27 | ActiveRecord::Migration.maintain_test_schema! 28 | 29 | RSpec.configure do |config| 30 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 31 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 32 | 33 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 34 | # examples within a transaction, remove the following line or assign false 35 | # instead of true. 36 | config.use_transactional_fixtures = false 37 | 38 | # RSpec Rails can automatically mix in different behaviours to your tests 39 | # based on their file location, for example enabling you to call `get` and 40 | # `post` in specs under `spec/controllers`. 41 | # 42 | # You can disable this behaviour by removing the line below, and instead 43 | # explicitly tag your specs with their type, e.g.: 44 | # 45 | # RSpec.describe UsersController, :type => :controller do 46 | # # ... 47 | # end 48 | # 49 | # The different available types are documented in the features, such as in 50 | # https://relishapp.com/rspec/rspec-rails/docs 51 | config.infer_spec_type_from_file_location! 52 | 53 | # Filter lines from Rails gems in backtraces. 54 | config.filter_rails_from_backtrace! 55 | # arbitrary gems may also be filtered via: 56 | # config.filter_gems_from_backtrace("gem name") 57 | end 58 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 16 | RSpec.configure do |config| 17 | # rspec-expectations config goes here. You can use an alternate 18 | # assertion/expectation library such as wrong or the stdlib/minitest 19 | # assertions if you prefer. 20 | config.expect_with :rspec do |expectations| 21 | # This option will default to `true` in RSpec 4. It makes the `description` 22 | # and `failure_message` of custom matchers include text for helper methods 23 | # defined using `chain`, e.g.: 24 | # be_bigger_than(2).and_smaller_than(4).description 25 | # # => "be bigger than 2 and smaller than 4" 26 | # ...rather than: 27 | # # => "be bigger than 2" 28 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 29 | end 30 | 31 | # rspec-mocks config goes here. You can use an alternate test double 32 | # library (such as bogus or mocha) by changing the `mock_with` option here. 33 | config.mock_with :rspec do |mocks| 34 | # Prevents you from mocking or stubbing a method that does not exist on 35 | # a real object. This is generally recommended, and will default to 36 | # `true` in RSpec 4. 37 | mocks.verify_partial_doubles = true 38 | end 39 | 40 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 41 | # have no way to turn it off -- the option exists only for backwards 42 | # compatibility in RSpec 3). It causes shared context metadata to be 43 | # inherited by the metadata hash of host groups and examples, rather than 44 | # triggering implicit auto-inclusion in groups with matching metadata. 45 | config.shared_context_metadata_behavior = :apply_to_host_groups 46 | 47 | # The settings below are suggested to provide a good initial experience 48 | # with RSpec, but feel free to customize to your heart's content. 49 | =begin 50 | # This allows you to limit a spec run to individual examples or groups 51 | # you care about by tagging them with `:focus` metadata. When nothing 52 | # is tagged with `:focus`, all examples get run. RSpec also provides 53 | # aliases for `it`, `describe`, and `context` that include `:focus` 54 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 55 | config.filter_run_when_matching :focus 56 | 57 | # Allows RSpec to persist some state between runs in order to support 58 | # the `--only-failures` and `--next-failure` CLI options. We recommend 59 | # you configure your source control system to ignore this file. 60 | config.example_status_persistence_file_path = "spec/examples.txt" 61 | 62 | # Limits the available syntax to the non-monkey patched syntax that is 63 | # recommended. For more details, see: 64 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 65 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 66 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 67 | config.disable_monkey_patching! 68 | 69 | # Many RSpec users commonly either run the entire suite or an individual 70 | # file, and it's useful to allow more verbose output when running an 71 | # individual spec file. 72 | if config.files_to_run.one? 73 | # Use the documentation formatter for detailed output, 74 | # unless a formatter has already been configured 75 | # (e.g. via a command-line flag). 76 | config.default_formatter = "doc" 77 | end 78 | 79 | # Print the 10 slowest examples and example groups at the 80 | # end of the spec run, to help surface which specs are running 81 | # particularly slow. 82 | config.profile_examples = 10 83 | 84 | # Run specs in random order to surface order dependencies. If you find an 85 | # order dependency and want to debug it, you can fix the order by providing 86 | # the seed, which is printed after each run. 87 | # --seed 1234 88 | config.order = :random 89 | 90 | # Seed global randomization in this process using the `--seed` CLI option. 91 | # Setting this allows you to use `--seed` to deterministically reproduce 92 | # test failures related to randomization by passing the same `--seed` value 93 | # as the one that triggered the failure. 94 | Kernel.srand config.seed 95 | =end 96 | end 97 | -------------------------------------------------------------------------------- /spec/support/capybara.rb: -------------------------------------------------------------------------------- 1 | Capybara.asset_host = 'http://localhost:3000' 2 | -------------------------------------------------------------------------------- /spec/support/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before(:suite) do 3 | DatabaseCleaner.clean_with(:truncation) 4 | end 5 | 6 | config.before(:each) do 7 | DatabaseCleaner.strategy = :transaction 8 | end 9 | 10 | config.before(:each, :js => true) do 11 | DatabaseCleaner.strategy = :truncation 12 | end 13 | 14 | config.before(:each) do 15 | DatabaseCleaner.start 16 | end 17 | 18 | config.append_after(:each) do 19 | DatabaseCleaner.clean 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/support/factory_girl.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.include FactoryGirl::Syntax::Methods 3 | end 4 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/tmp/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuzhenangel/RBlog/91bd8729b905fd28d75890cdeb206eb3becc42d5/vendor/.keep -------------------------------------------------------------------------------- /vendor/assets/javascripts/sb-admin.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - SB Admin v4.0.0-beta.2 (https://startbootstrap.com/template-overviews/sb-admin) 3 | * Copyright 2013-2017 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap-sb-admin/blob/master/LICENSE) 5 | */ 6 | !function(e){"use strict";e('.navbar-sidenav [data-toggle="tooltip"]').tooltip({template:''}),e("#sidenavToggler").click(function(o){o.preventDefault(),e("body").toggleClass("sidenav-toggled"),e(".navbar-sidenav .nav-link-collapse").addClass("collapsed"),e(".navbar-sidenav .sidenav-second-level, .navbar-sidenav .sidenav-third-level").removeClass("show")}),e(".navbar-sidenav .nav-link-collapse").click(function(o){o.preventDefault(),e("body").removeClass("sidenav-toggled")}),e("body.fixed-nav .navbar-sidenav, body.fixed-nav .sidenav-toggler, body.fixed-nav .navbar-collapse").on("mousewheel DOMMouseScroll",function(e){var o=e.originalEvent,a=o.wheelDelta||-o.detail;this.scrollTop+=30*(a<0?1:-1),e.preventDefault()}),e(document).scroll(function(){e(this).scrollTop()>100?e(".scroll-to-top").fadeIn():e(".scroll-to-top").fadeOut()}),e('[data-toggle="tooltip"]').tooltip(),e(document).on("click","a.scroll-to-top",function(o){var a=e(this);e("html, body").stop().animate({scrollTop:e(a.attr("href")).offset().top},1e3,"easeInOutExpo"),o.preventDefault()})}(jQuery); -------------------------------------------------------------------------------- /vendor/assets/javascripts/simditor-hotkeys.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module unless amdModuleId is set 4 | define('simple-hotkeys', ["jquery","simple-module"], function ($, SimpleModule) { 5 | return (root['hotkeys'] = factory($, SimpleModule)); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory(require("jquery"),require("simple-module")); 12 | } else { 13 | root.simple = root.simple || {}; 14 | root.simple['hotkeys'] = factory(jQuery,SimpleModule); 15 | } 16 | }(this, function ($, SimpleModule) { 17 | 18 | var Hotkeys, hotkeys, 19 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 20 | hasProp = {}.hasOwnProperty; 21 | 22 | Hotkeys = (function(superClass) { 23 | extend(Hotkeys, superClass); 24 | 25 | function Hotkeys() { 26 | return Hotkeys.__super__.constructor.apply(this, arguments); 27 | } 28 | 29 | Hotkeys.count = 0; 30 | 31 | Hotkeys.keyNameMap = { 32 | 8: "Backspace", 33 | 9: "Tab", 34 | 13: "Enter", 35 | 16: "Shift", 36 | 17: "Control", 37 | 18: "Alt", 38 | 19: "Pause", 39 | 20: "CapsLock", 40 | 27: "Esc", 41 | 32: "Spacebar", 42 | 33: "PageUp", 43 | 34: "PageDown", 44 | 35: "End", 45 | 36: "Home", 46 | 37: "Left", 47 | 38: "Up", 48 | 39: "Right", 49 | 40: "Down", 50 | 45: "Insert", 51 | 46: "Del", 52 | 91: "Meta", 53 | 93: "Meta", 54 | 48: "0", 55 | 49: "1", 56 | 50: "2", 57 | 51: "3", 58 | 52: "4", 59 | 53: "5", 60 | 54: "6", 61 | 55: "7", 62 | 56: "8", 63 | 57: "9", 64 | 65: "A", 65 | 66: "B", 66 | 67: "C", 67 | 68: "D", 68 | 69: "E", 69 | 70: "F", 70 | 71: "G", 71 | 72: "H", 72 | 73: "I", 73 | 74: "J", 74 | 75: "K", 75 | 76: "L", 76 | 77: "M", 77 | 78: "N", 78 | 79: "O", 79 | 80: "P", 80 | 81: "Q", 81 | 82: "R", 82 | 83: "S", 83 | 84: "T", 84 | 85: "U", 85 | 86: "V", 86 | 87: "W", 87 | 88: "X", 88 | 89: "Y", 89 | 90: "Z", 90 | 96: "0", 91 | 97: "1", 92 | 98: "2", 93 | 99: "3", 94 | 100: "4", 95 | 101: "5", 96 | 102: "6", 97 | 103: "7", 98 | 104: "8", 99 | 105: "9", 100 | 106: "Multiply", 101 | 107: "Add", 102 | 109: "Subtract", 103 | 110: "Decimal", 104 | 111: "Divide", 105 | 112: "F1", 106 | 113: "F2", 107 | 114: "F3", 108 | 115: "F4", 109 | 116: "F5", 110 | 117: "F6", 111 | 118: "F7", 112 | 119: "F8", 113 | 120: "F9", 114 | 121: "F10", 115 | 122: "F11", 116 | 123: "F12", 117 | 124: "F13", 118 | 125: "F14", 119 | 126: "F15", 120 | 127: "F16", 121 | 128: "F17", 122 | 129: "F18", 123 | 130: "F19", 124 | 131: "F20", 125 | 132: "F21", 126 | 133: "F22", 127 | 134: "F23", 128 | 135: "F24", 129 | 59: ";", 130 | 61: "=", 131 | 186: ";", 132 | 187: "=", 133 | 188: ",", 134 | 190: ".", 135 | 191: "/", 136 | 192: "`", 137 | 219: "[", 138 | 220: "\\", 139 | 221: "]", 140 | 222: "'" 141 | }; 142 | 143 | Hotkeys.aliases = { 144 | "escape": "esc", 145 | "delete": "del", 146 | "return": "enter", 147 | "ctrl": "control", 148 | "space": "spacebar", 149 | "ins": "insert", 150 | "cmd": "meta", 151 | "command": "meta", 152 | "wins": "meta", 153 | "windows": "meta" 154 | }; 155 | 156 | Hotkeys.normalize = function(shortcut) { 157 | var i, j, key, keyname, keys, len; 158 | keys = shortcut.toLowerCase().replace(/\s+/gi, "").split("+"); 159 | for (i = j = 0, len = keys.length; j < len; i = ++j) { 160 | key = keys[i]; 161 | keys[i] = this.aliases[key] || key; 162 | } 163 | keyname = keys.pop(); 164 | keys.sort().push(keyname); 165 | return keys.join("_"); 166 | }; 167 | 168 | Hotkeys.prototype.opts = { 169 | el: document 170 | }; 171 | 172 | Hotkeys.prototype._init = function() { 173 | this.id = ++this.constructor.count; 174 | this._map = {}; 175 | this._delegate = typeof this.opts.el === "string" ? document : this.opts.el; 176 | return $(this._delegate).on("keydown.simple-hotkeys-" + this.id, this.opts.el, (function(_this) { 177 | return function(e) { 178 | var ref; 179 | return (ref = _this._getHander(e)) != null ? ref.call(_this, e) : void 0; 180 | }; 181 | })(this)); 182 | }; 183 | 184 | Hotkeys.prototype._getHander = function(e) { 185 | var keyname, shortcut; 186 | if (!(keyname = this.constructor.keyNameMap[e.which])) { 187 | return; 188 | } 189 | shortcut = ""; 190 | if (e.altKey) { 191 | shortcut += "alt_"; 192 | } 193 | if (e.ctrlKey) { 194 | shortcut += "control_"; 195 | } 196 | if (e.metaKey) { 197 | shortcut += "meta_"; 198 | } 199 | if (e.shiftKey) { 200 | shortcut += "shift_"; 201 | } 202 | shortcut += keyname.toLowerCase(); 203 | return this._map[shortcut]; 204 | }; 205 | 206 | Hotkeys.prototype.respondTo = function(subject) { 207 | if (typeof subject === 'string') { 208 | return this._map[this.constructor.normalize(subject)] != null; 209 | } else { 210 | return this._getHander(subject) != null; 211 | } 212 | }; 213 | 214 | Hotkeys.prototype.add = function(shortcut, handler) { 215 | this._map[this.constructor.normalize(shortcut)] = handler; 216 | return this; 217 | }; 218 | 219 | Hotkeys.prototype.remove = function(shortcut) { 220 | delete this._map[this.constructor.normalize(shortcut)]; 221 | return this; 222 | }; 223 | 224 | Hotkeys.prototype.destroy = function() { 225 | $(this._delegate).off(".simple-hotkeys-" + this.id); 226 | this._map = {}; 227 | return this; 228 | }; 229 | 230 | return Hotkeys; 231 | 232 | })(SimpleModule); 233 | 234 | hotkeys = function(opts) { 235 | return new Hotkeys(opts); 236 | }; 237 | 238 | return hotkeys; 239 | 240 | })); 241 | 242 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/simditor-module.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module unless amdModuleId is set 4 | define('simple-module', ["jquery"], function (a0) { 5 | return (root['Module'] = factory(a0)); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory(require("jquery")); 12 | } else { 13 | root['SimpleModule'] = factory(jQuery); 14 | } 15 | }(this, function ($) { 16 | 17 | var Module, 18 | slice = [].slice; 19 | 20 | Module = (function() { 21 | Module.extend = function(obj) { 22 | var key, ref, val; 23 | if (!((obj != null) && typeof obj === 'object')) { 24 | return; 25 | } 26 | for (key in obj) { 27 | val = obj[key]; 28 | if (key !== 'included' && key !== 'extended') { 29 | this[key] = val; 30 | } 31 | } 32 | return (ref = obj.extended) != null ? ref.call(this) : void 0; 33 | }; 34 | 35 | Module.include = function(obj) { 36 | var key, ref, val; 37 | if (!((obj != null) && typeof obj === 'object')) { 38 | return; 39 | } 40 | for (key in obj) { 41 | val = obj[key]; 42 | if (key !== 'included' && key !== 'extended') { 43 | this.prototype[key] = val; 44 | } 45 | } 46 | return (ref = obj.included) != null ? ref.call(this) : void 0; 47 | }; 48 | 49 | Module.connect = function(cls) { 50 | if (typeof cls !== 'function') { 51 | return; 52 | } 53 | if (!cls.pluginName) { 54 | throw new Error('Module.connect: cannot connect plugin without pluginName'); 55 | return; 56 | } 57 | cls.prototype._connected = true; 58 | if (!this._connectedClasses) { 59 | this._connectedClasses = []; 60 | } 61 | this._connectedClasses.push(cls); 62 | if (cls.pluginName) { 63 | return this[cls.pluginName] = cls; 64 | } 65 | }; 66 | 67 | Module.prototype.opts = {}; 68 | 69 | function Module(opts) { 70 | var base, cls, i, instance, instances, len, name; 71 | this.opts = $.extend({}, this.opts, opts); 72 | (base = this.constructor)._connectedClasses || (base._connectedClasses = []); 73 | instances = (function() { 74 | var i, len, ref, results; 75 | ref = this.constructor._connectedClasses; 76 | results = []; 77 | for (i = 0, len = ref.length; i < len; i++) { 78 | cls = ref[i]; 79 | name = cls.pluginName.charAt(0).toLowerCase() + cls.pluginName.slice(1); 80 | if (cls.prototype._connected) { 81 | cls.prototype._module = this; 82 | } 83 | results.push(this[name] = new cls()); 84 | } 85 | return results; 86 | }).call(this); 87 | if (this._connected) { 88 | this.opts = $.extend({}, this.opts, this._module.opts); 89 | } else { 90 | this._init(); 91 | for (i = 0, len = instances.length; i < len; i++) { 92 | instance = instances[i]; 93 | if (typeof instance._init === "function") { 94 | instance._init(); 95 | } 96 | } 97 | } 98 | this.trigger('initialized'); 99 | } 100 | 101 | Module.prototype._init = function() {}; 102 | 103 | Module.prototype.on = function() { 104 | var args, ref; 105 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 106 | (ref = $(this)).on.apply(ref, args); 107 | return this; 108 | }; 109 | 110 | Module.prototype.one = function() { 111 | var args, ref; 112 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 113 | (ref = $(this)).one.apply(ref, args); 114 | return this; 115 | }; 116 | 117 | Module.prototype.off = function() { 118 | var args, ref; 119 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 120 | (ref = $(this)).off.apply(ref, args); 121 | return this; 122 | }; 123 | 124 | Module.prototype.trigger = function() { 125 | var args, ref; 126 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 127 | (ref = $(this)).trigger.apply(ref, args); 128 | return this; 129 | }; 130 | 131 | Module.prototype.triggerHandler = function() { 132 | var args, ref; 133 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 134 | return (ref = $(this)).triggerHandler.apply(ref, args); 135 | }; 136 | 137 | Module.prototype._t = function() { 138 | var args, ref; 139 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 140 | return (ref = this.constructor)._t.apply(ref, args); 141 | }; 142 | 143 | Module._t = function() { 144 | var args, key, ref, result; 145 | key = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; 146 | result = ((ref = this.i18n[this.locale]) != null ? ref[key] : void 0) || ''; 147 | if (!(args.length > 0)) { 148 | return result; 149 | } 150 | result = result.replace(/([^%]|^)%(?:(\d+)\$)?s/g, function(p0, p, position) { 151 | if (position) { 152 | return p + args[parseInt(position) - 1]; 153 | } else { 154 | return p + args.shift(); 155 | } 156 | }); 157 | return result.replace(/%%s/g, '%s'); 158 | }; 159 | 160 | Module.i18n = { 161 | 'zh-CN': {} 162 | }; 163 | 164 | Module.locale = 'zh-CN'; 165 | 166 | return Module; 167 | 168 | })(); 169 | 170 | return Module; 171 | 172 | })); 173 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/simditor-uploader.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module unless amdModuleId is set 4 | define('simple-uploader', ["jquery","simple-module"], function ($, SimpleModule) { 5 | return (root['uploader'] = factory($, SimpleModule)); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory(require("jquery"),require("simple-module")); 12 | } else { 13 | root.simple = root.simple || {}; 14 | root.simple['uploader'] = factory(jQuery,SimpleModule); 15 | } 16 | }(this, function ($, SimpleModule) { 17 | 18 | var Uploader, uploader, 19 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 20 | hasProp = {}.hasOwnProperty; 21 | 22 | Uploader = (function(superClass) { 23 | extend(Uploader, superClass); 24 | 25 | function Uploader() { 26 | return Uploader.__super__.constructor.apply(this, arguments); 27 | } 28 | 29 | Uploader.count = 0; 30 | 31 | Uploader.prototype.opts = { 32 | url: '', 33 | params: null, 34 | fileKey: 'upload_file', 35 | connectionCount: 3 36 | }; 37 | 38 | Uploader.prototype._init = function() { 39 | this.files = []; 40 | this.queue = []; 41 | this.id = ++Uploader.count; 42 | this.on('uploadcomplete', (function(_this) { 43 | return function(e, file) { 44 | _this.files.splice($.inArray(file, _this.files), 1); 45 | if (_this.queue.length > 0 && _this.files.length < _this.opts.connectionCount) { 46 | return _this.upload(_this.queue.shift()); 47 | } else if (_this.files.length === 0) { 48 | return _this.uploading = false; 49 | } 50 | }; 51 | })(this)); 52 | return $(window).on('beforeunload.uploader-' + this.id, (function(_this) { 53 | return function(e) { 54 | if (!_this.uploading) { 55 | return; 56 | } 57 | e.originalEvent.returnValue = _this._t('leaveConfirm'); 58 | return _this._t('leaveConfirm'); 59 | }; 60 | })(this)); 61 | }; 62 | 63 | Uploader.prototype.generateId = (function() { 64 | var id; 65 | id = 0; 66 | return function() { 67 | return id += 1; 68 | }; 69 | })(); 70 | 71 | Uploader.prototype.upload = function(file, opts) { 72 | var f, i, key, len; 73 | if (opts == null) { 74 | opts = {}; 75 | } 76 | if (file == null) { 77 | return; 78 | } 79 | if ($.isArray(file) || file instanceof FileList) { 80 | for (i = 0, len = file.length; i < len; i++) { 81 | f = file[i]; 82 | this.upload(f, opts); 83 | } 84 | } else if ($(file).is('input:file')) { 85 | key = $(file).attr('name'); 86 | if (key) { 87 | opts.fileKey = key; 88 | } 89 | this.upload($.makeArray($(file)[0].files), opts); 90 | } else if (!file.id || !file.obj) { 91 | file = this.getFile(file); 92 | } 93 | if (!(file && file.obj)) { 94 | return; 95 | } 96 | $.extend(file, opts); 97 | if (this.files.length >= this.opts.connectionCount) { 98 | this.queue.push(file); 99 | return; 100 | } 101 | if (this.triggerHandler('beforeupload', [file]) === false) { 102 | return; 103 | } 104 | this.files.push(file); 105 | this._xhrUpload(file); 106 | return this.uploading = true; 107 | }; 108 | 109 | Uploader.prototype.getFile = function(fileObj) { 110 | var name, ref, ref1; 111 | if (fileObj instanceof window.File || fileObj instanceof window.Blob) { 112 | name = (ref = fileObj.fileName) != null ? ref : fileObj.name; 113 | } else { 114 | return null; 115 | } 116 | return { 117 | id: this.generateId(), 118 | url: this.opts.url, 119 | params: this.opts.params, 120 | fileKey: this.opts.fileKey, 121 | name: name, 122 | size: (ref1 = fileObj.fileSize) != null ? ref1 : fileObj.size, 123 | ext: name ? name.split('.').pop().toLowerCase() : '', 124 | obj: fileObj 125 | }; 126 | }; 127 | 128 | Uploader.prototype._xhrUpload = function(file) { 129 | var formData, k, ref, v; 130 | formData = new FormData(); 131 | formData.append(file.fileKey, file.obj); 132 | formData.append("original_filename", file.name); 133 | if (file.params) { 134 | ref = file.params; 135 | for (k in ref) { 136 | v = ref[k]; 137 | formData.append(k, v); 138 | } 139 | } 140 | return file.xhr = $.ajax({ 141 | url: file.url, 142 | data: formData, 143 | processData: false, 144 | contentType: false, 145 | type: 'POST', 146 | headers: { 147 | 'X-File-Name': encodeURIComponent(file.name) 148 | }, 149 | xhr: function() { 150 | var req; 151 | req = $.ajaxSettings.xhr(); 152 | if (req) { 153 | req.upload.onprogress = (function(_this) { 154 | return function(e) { 155 | return _this.progress(e); 156 | }; 157 | })(this); 158 | } 159 | return req; 160 | }, 161 | progress: (function(_this) { 162 | return function(e) { 163 | if (!e.lengthComputable) { 164 | return; 165 | } 166 | return _this.trigger('uploadprogress', [file, e.loaded, e.total]); 167 | }; 168 | })(this), 169 | error: (function(_this) { 170 | return function(xhr, status, err) { 171 | return _this.trigger('uploaderror', [file, xhr, status]); 172 | }; 173 | })(this), 174 | success: (function(_this) { 175 | return function(result) { 176 | _this.trigger('uploadprogress', [file, file.size, file.size]); 177 | _this.trigger('uploadsuccess', [file, result]); 178 | return $(document).trigger('uploadsuccess', [file, result, _this]); 179 | }; 180 | })(this), 181 | complete: (function(_this) { 182 | return function(xhr, status) { 183 | return _this.trigger('uploadcomplete', [file, xhr.responseText]); 184 | }; 185 | })(this) 186 | }); 187 | }; 188 | 189 | Uploader.prototype.cancel = function(file) { 190 | var f, i, len, ref; 191 | if (!file.id) { 192 | ref = this.files; 193 | for (i = 0, len = ref.length; i < len; i++) { 194 | f = ref[i]; 195 | if (f.id === file * 1) { 196 | file = f; 197 | break; 198 | } 199 | } 200 | } 201 | this.trigger('uploadcancel', [file]); 202 | if (file.xhr) { 203 | file.xhr.abort(); 204 | } 205 | return file.xhr = null; 206 | }; 207 | 208 | Uploader.prototype.readImageFile = function(fileObj, callback) { 209 | var fileReader, img; 210 | if (!$.isFunction(callback)) { 211 | return; 212 | } 213 | img = new Image(); 214 | img.onload = function() { 215 | return callback(img); 216 | }; 217 | img.onerror = function() { 218 | return callback(); 219 | }; 220 | if (window.FileReader && FileReader.prototype.readAsDataURL && /^image/.test(fileObj.type)) { 221 | fileReader = new FileReader(); 222 | fileReader.onload = function(e) { 223 | return img.src = e.target.result; 224 | }; 225 | return fileReader.readAsDataURL(fileObj); 226 | } else { 227 | return callback(); 228 | } 229 | }; 230 | 231 | Uploader.prototype.destroy = function() { 232 | var file, i, len, ref; 233 | this.queue.length = 0; 234 | ref = this.files; 235 | for (i = 0, len = ref.length; i < len; i++) { 236 | file = ref[i]; 237 | this.cancel(file); 238 | } 239 | $(window).off('.uploader-' + this.id); 240 | return $(document).off('.uploader-' + this.id); 241 | }; 242 | 243 | Uploader.i18n = { 244 | 'zh-CN': { 245 | leaveConfirm: '正在上传文件,如果离开上传会自动取消' 246 | } 247 | }; 248 | 249 | Uploader.locale = 'zh-CN'; 250 | 251 | return Uploader; 252 | 253 | })(SimpleModule); 254 | 255 | uploader = function(opts) { 256 | return new Uploader(opts); 257 | }; 258 | 259 | return uploader; 260 | 261 | })); 262 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/startbootstrap-clean-blog.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | "use strict"; // Start of use strict 3 | 4 | // Floating label headings for the contact form 5 | $("body").on("input propertychange", ".floating-label-form-group", function(e) { 6 | $(this).toggleClass("floating-label-form-group-with-value", !!$(e.target).val()); 7 | }).on("focus", ".floating-label-form-group", function() { 8 | $(this).addClass("floating-label-form-group-with-focus"); 9 | }).on("blur", ".floating-label-form-group", function() { 10 | $(this).removeClass("floating-label-form-group-with-focus"); 11 | }); 12 | 13 | // Show the navbar when the page is scrolled up 14 | var MQL = 992; 15 | 16 | //primary navigation slide-in effect 17 | if ($(window).width() > MQL) { 18 | var headerHeight = $('#mainNav').height(); 19 | $(window).on('scroll', { 20 | previousTop: 0 21 | }, 22 | function() { 23 | var currentTop = $(window).scrollTop(); 24 | //check if user is scrolling up 25 | if (currentTop < this.previousTop) { 26 | //if scrolling up... 27 | if (currentTop > 0 && $('#mainNav').hasClass('is-fixed')) { 28 | $('#mainNav').addClass('is-visible'); 29 | } else { 30 | $('#mainNav').removeClass('is-visible is-fixed'); 31 | } 32 | } else if (currentTop > this.previousTop) { 33 | //if scrolling down... 34 | $('#mainNav').removeClass('is-visible'); 35 | if (currentTop > headerHeight && !$('#mainNav').hasClass('is-fixed')) $('#mainNav').addClass('is-fixed'); 36 | } 37 | this.previousTop = currentTop; 38 | }); 39 | } 40 | 41 | })(jQuery); // End of use strict 42 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/turn-hash.js: -------------------------------------------------------------------------------- 1 | /** 2 | * hash.js 3 | * 4 | * Copyright (C) 2012 Emmanuel Garcia 5 | * MIT Licensed 6 | * 7 | * **************************************** 8 | * 9 | * Hash.pushState(true); 10 | * 11 | * Hash.on('/page/([0-9]+)$', 12 | * {yep: function(path, parts) { }, nop: function() { }}, 13 | * 'Page $1'); 14 | * 15 | * Hash.go('/page/1'); 16 | **/ 17 | 18 | (function() { 19 | 20 | 'use strict'; 21 | 22 | var hashes = {}, 23 | regexp = {}, 24 | history = [], 25 | freq = 100, 26 | num = 0, 27 | pushState = false, 28 | timer = null, 29 | currentUrl = null, 30 | 31 | freeze = function(obj) { 32 | if (Object.freeze) return Object.freeze(obj); 33 | return obj; 34 | }, 35 | 36 | getHashParts = function() { 37 | return window.location.href.split('#'); 38 | }, 39 | 40 | startTimer = function() { 41 | 42 | if (!timer) 43 | timer = setInterval(function() { 44 | if (num>0 && currentUrl!=window.location.href) { 45 | currentUrl = window.location.href; 46 | window.Hash.check(); 47 | } 48 | }, freq); 49 | 50 | }, 51 | 52 | stopTimer = function() { 53 | 54 | if (timer) { 55 | clearInterval(timer); 56 | timer = null; 57 | } 58 | 59 | }; 60 | 61 | window.Hash = freeze({ 62 | 63 | pushState: function(yes) { 64 | 65 | if (window.history && window.history.pushState) 66 | pushState = yes; 67 | 68 | return this; 69 | }, 70 | 71 | fragment: function() { 72 | 73 | var hash = getHashParts(); 74 | return (pushState) ? 75 | window.location.pathname + ((hash[1]) ? '#' + hash[1] : '') 76 | : hash[1] || ''; 77 | 78 | }, 79 | 80 | get: function(path, params) { 81 | 82 | var p, fragment = '', parameters = []; 83 | 84 | for(p in params) { 85 | if (!Object.prototype.hasOwnProperty(p)) 86 | continue; 87 | parameters.push(encodeURIComponent(p) + '=' + encodeURIComponent(params[p])); 88 | } 89 | 90 | if (parameters.length>0) 91 | parameters = '?' + parameters.join('&'); 92 | 93 | return (pushState) ? path + parameters : 94 | getHashParts()[0] + '#' + path + parameters; 95 | 96 | }, 97 | 98 | go: function(hash, params) { 99 | 100 | if (this.fragment()!=hash) { 101 | var to = this.get(hash, params); 102 | 103 | if (pushState) 104 | window.history.pushState(null, document.title, to); 105 | else 106 | window.location.href = to; 107 | } 108 | 109 | return this; 110 | }, 111 | 112 | update: function () { 113 | 114 | currentUrl = window.location.href; 115 | return this; 116 | 117 | }, 118 | 119 | on: function(hash, callback, title) { 120 | 121 | if (!hashes[hash]) 122 | hashes[hash] = {title: title, listeners: []}; 123 | 124 | hashes[hash].listeners.push(callback); 125 | num++; 126 | startTimer(); 127 | 128 | return this; 129 | }, 130 | 131 | check: function() { 132 | 133 | var i, 134 | hash, 135 | parts, 136 | fragment = this.fragment(); 137 | 138 | 139 | for (hash in hashes) { 140 | if (!Object.prototype.hasOwnProperty.call(hashes, hash)) 141 | continue; 142 | 143 | hashes[hash].regexp = hashes[hash].regexp || new RegExp(hash); 144 | 145 | if ((parts = hashes[hash].regexp.exec(fragment))) { 146 | if (hashes[hash].title) 147 | document.title = hashes[hash].title; 148 | 149 | for (i = 0; i', {}); 10 | 11 | // Add the page to the flipbook 12 | if (book.turn('addPage', element, page)) { 13 | 14 | // Add the initial HTML 15 | // It will contain a loader indicator and a gradient 16 | element.html('
'); 17 | 18 | // Load the page 19 | loadPage(page, element); 20 | } 21 | 22 | } 23 | 24 | function loadPage(page, pageElement) { 25 | 26 | // Create an image element 27 | 28 | var img = $(''); 29 | 30 | img.mousedown(function(e) { 31 | e.preventDefault(); 32 | }); 33 | 34 | img.load(function() { 35 | 36 | // Set the size 37 | $(this).css({width: '100%', height: '100%'}); 38 | 39 | // Add the image to the page after loaded 40 | 41 | $(this).appendTo(pageElement); 42 | 43 | // Remove the loader indicator 44 | 45 | pageElement.find('.loader').remove(); 46 | }); 47 | 48 | // Load the page 49 | 50 | //img.attr('src', 'assets/pages/' + page + '.jpg'); 51 | 52 | loadRegions(page, pageElement); 53 | 54 | } 55 | 56 | // Zoom in / Zoom out 57 | 58 | function zoomTo(event) { 59 | 60 | setTimeout(function() { 61 | if ($('.magazine-viewport').data().regionClicked) { 62 | $('.magazine-viewport').data().regionClicked = false; 63 | } else { 64 | if ($('.magazine-viewport').zoom('value')==1) { 65 | $('.magazine-viewport').zoom('zoomIn', event); 66 | } else { 67 | $('.magazine-viewport').zoom('zoomOut'); 68 | } 69 | } 70 | }, 1); 71 | 72 | } 73 | 74 | 75 | 76 | // Load regions 77 | 78 | function loadRegions(page, element) { 79 | 80 | //$.getJSON('assets/pages/'+page+'-regions.json'). 81 | //done(function(data) { 82 | 83 | //$.each(data, function(key, region) { 84 | //addRegion(region, element); 85 | //}); 86 | //}); 87 | } 88 | 89 | // Add region 90 | 91 | function addRegion(region, pageElement) { 92 | 93 | var reg = $('
', {'class': 'region ' + region['class']}), 94 | options = $('.magazine').turn('options'), 95 | pageWidth = options.width/2, 96 | pageHeight = options.height; 97 | 98 | reg.css({ 99 | top: Math.round(region.y/pageHeight*100)+'%', 100 | left: Math.round(region.x/pageWidth*100)+'%', 101 | width: Math.round(region.width/pageWidth*100)+'%', 102 | height: Math.round(region.height/pageHeight*100)+'%' 103 | }).attr('region-data', $.param(region.data||'')); 104 | 105 | 106 | reg.appendTo(pageElement); 107 | } 108 | 109 | // Process click on a region 110 | 111 | function regionClick(event) { 112 | 113 | var region = $(event.target); 114 | 115 | if (region.hasClass('region')) { 116 | 117 | $('.magazine-viewport').data().regionClicked = true; 118 | 119 | setTimeout(function() { 120 | $('.magazine-viewport').data().regionClicked = false; 121 | }, 100); 122 | 123 | var regionType = $.trim(region.attr('class').replace('region', '')); 124 | 125 | return processRegion(region, regionType); 126 | 127 | } 128 | 129 | } 130 | 131 | // Process the data of every region 132 | 133 | function processRegion(region, regionType) { 134 | 135 | data = decodeParams(region.attr('region-data')); 136 | 137 | switch (regionType) { 138 | case 'link' : 139 | 140 | window.open(data.url); 141 | 142 | break; 143 | case 'zoom' : 144 | 145 | var regionOffset = region.offset(), 146 | viewportOffset = $('.magazine-viewport').offset(), 147 | pos = { 148 | x: regionOffset.left-viewportOffset.left, 149 | y: regionOffset.top-viewportOffset.top 150 | }; 151 | 152 | $('.magazine-viewport').zoom('zoomIn', pos); 153 | 154 | break; 155 | case 'to-page' : 156 | 157 | $('.magazine').turn('page', data.page); 158 | 159 | break; 160 | } 161 | 162 | } 163 | 164 | // Load large page 165 | 166 | function loadLargePage(page, pageElement) { 167 | 168 | var img = $(''); 169 | 170 | img.load(function() { 171 | 172 | var prevImg = pageElement.find('img'); 173 | $(this).css({width: '100%', height: '100%'}); 174 | $(this).appendTo(pageElement); 175 | prevImg.remove(); 176 | 177 | }); 178 | 179 | // Loadnew page 180 | 181 | //img.attr('src', 'pages/' + page + '-large.jpg'); 182 | } 183 | 184 | // Load small page 185 | 186 | function loadSmallPage(page, pageElement) { 187 | 188 | var img = pageElement.find('img'); 189 | 190 | img.css({width: '100%', height: '100%'}); 191 | 192 | img.unbind('load'); 193 | // Loadnew page 194 | 195 | //img.attr('src', 'pages/' + page + '.jpg'); 196 | } 197 | 198 | // http://code.google.com/p/chromium/issues/detail?id=128488 199 | 200 | function isChrome() { 201 | 202 | return navigator.userAgent.indexOf('Chrome')!=-1; 203 | 204 | } 205 | 206 | function disableControls(page) { 207 | if (page==1) 208 | $('.previous-button').hide(); 209 | else 210 | $('.previous-button').show(); 211 | 212 | if (page==$('.magazine').turn('pages')) 213 | $('.next-button').hide(); 214 | else 215 | $('.next-button').show(); 216 | } 217 | 218 | // Set the width and height for the viewport 219 | 220 | function resizeViewport() { 221 | 222 | var width = $(window).width(), 223 | height = $(window).height(), 224 | options = $('.magazine').turn('options'); 225 | 226 | $('.magazine').removeClass('animated'); 227 | 228 | $('.magazine-viewport').css({ 229 | width: width, 230 | height: height 231 | }). 232 | zoom('resize'); 233 | 234 | 235 | if ($('.magazine').turn('zoom')==1) { 236 | var bound = calculateBound({ 237 | width: options.width, 238 | height: options.height, 239 | boundWidth: Math.min(options.width, width), 240 | boundHeight: Math.min(options.height, height) 241 | }); 242 | 243 | if (bound.width%2!==0) 244 | bound.width-=1; 245 | 246 | 247 | if (bound.width!=$('.magazine').width() || bound.height!=$('.magazine').height()) { 248 | 249 | $('.magazine').turn('size', bound.width, bound.height); 250 | 251 | if ($('.magazine').turn('page')==1) 252 | $('.magazine').turn('peel', 'br'); 253 | 254 | $('.next-button').css({height: bound.height, backgroundPosition: '-38px '+(bound.height/2-32/2)+'px'}); 255 | $('.previous-button').css({height: bound.height, backgroundPosition: '-4px '+(bound.height/2-32/2)+'px'}); 256 | } 257 | 258 | $('.magazine').css({top: -bound.height/2, left: -bound.width/2}); 259 | } 260 | 261 | var magazineOffset = $('.magazine').offset(), 262 | boundH = height - magazineOffset.top - $('.magazine').height(), 263 | marginTop = (boundH - $('.thumbnails > div').height()) / 2; 264 | 265 | if (marginTop<0) { 266 | $('.thumbnails').css({height:1}); 267 | } else { 268 | $('.thumbnails').css({height: boundH}); 269 | $('.thumbnails > div').css({marginTop: marginTop}); 270 | } 271 | 272 | if (magazineOffset.top<$('.made').height()) 273 | $('.made').hide(); 274 | else 275 | $('.made').show(); 276 | 277 | $('.magazine').addClass('animated'); 278 | 279 | } 280 | 281 | 282 | // Number of views in a flipbook 283 | 284 | function numberOfViews(book) { 285 | return book.turn('pages') / 2 + 1; 286 | } 287 | 288 | // Current view in a flipbook 289 | 290 | function getViewNumber(book, page) { 291 | return parseInt((page || book.turn('page'))/2 + 1, 10); 292 | } 293 | 294 | function moveBar(yes) { 295 | if (Modernizr && Modernizr.csstransforms) { 296 | $('#slider .ui-slider-handle').css({zIndex: yes ? -1 : 10000}); 297 | } 298 | } 299 | 300 | function setPreview(view) { 301 | 302 | var previewWidth = 112, 303 | previewHeight = 73, 304 | previewSrc = 'pages/preview.jpg', 305 | preview = $(_thumbPreview.children(':first')), 306 | numPages = (view==1 || view==$('#slider').slider('option', 'max')) ? 1 : 2, 307 | width = (numPages==1) ? previewWidth/2 : previewWidth; 308 | 309 | _thumbPreview. 310 | addClass('no-transition'). 311 | css({width: width + 15, 312 | height: previewHeight + 15, 313 | top: -previewHeight - 30, 314 | left: ($($('#slider').children(':first')).width() - width - 15)/2 315 | }); 316 | 317 | preview.css({ 318 | width: width, 319 | height: previewHeight 320 | }); 321 | 322 | if (preview.css('background-image')==='' || 323 | preview.css('background-image')=='none') { 324 | 325 | preview.css({backgroundImage: 'url(' + previewSrc + ')'}); 326 | 327 | setTimeout(function(){ 328 | _thumbPreview.removeClass('no-transition'); 329 | }, 0); 330 | 331 | } 332 | 333 | preview.css({backgroundPosition: 334 | '0px -'+((view-1)*previewHeight)+'px' 335 | }); 336 | } 337 | 338 | // Width of the flipbook when zoomed in 339 | 340 | function largeMagazineWidth() { 341 | 342 | return 2214; 343 | 344 | } 345 | 346 | // decode URL Parameters 347 | 348 | function decodeParams(data) { 349 | 350 | var parts = data.split('&'), d, obj = {}; 351 | 352 | for (var i =0; id.boundWidth || bound.height>d.boundHeight) { 367 | 368 | var rel = bound.width/bound.height; 369 | 370 | if (d.boundWidth/rel>d.boundHeight && d.boundHeight*rel<=d.boundWidth) { 371 | 372 | bound.width = Math.round(d.boundHeight*rel); 373 | bound.height = d.boundHeight; 374 | 375 | } else { 376 | 377 | bound.width = d.boundWidth; 378 | bound.height = Math.round(d.boundWidth/rel); 379 | 380 | } 381 | } 382 | 383 | return bound; 384 | } 385 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/turn-modernizr.min.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.5.3 (Custom Build) | MIT & BSD 2 | * Build: http://www.modernizr.com/download/#-csstransforms-csstransforms3d-shiv-cssclasses-teststyles-testprop-testallprops-prefixes-domprefixes-load 3 | */ 4 | ;window.Modernizr=function(a,b,c){function z(a){j.cssText=a}function A(a,b){return z(m.join(a+";")+(b||""))}function B(a,b){return typeof a===b}function C(a,b){return!!~(""+a).indexOf(b)}function D(a,b){for(var d in a)if(j[a[d]]!==c)return b=="pfx"?a[d]:!0;return!1}function E(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:B(f,"function")?f.bind(d||b):f}return!1}function F(a,b,c){var d=a.charAt(0).toUpperCase()+a.substr(1),e=(a+" "+o.join(d+" ")+d).split(" ");return B(b,"string")||B(b,"undefined")?D(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),E(e,b,c))}var d="2.5.3",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var f,i,j,k=b.createElement("div"),l=b.body,m=l?l:b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),k.appendChild(j);return f=["­",""].join(""),k.id=h,(l?k:m).innerHTML+=f,m.appendChild(k),l||(m.style.background="",g.appendChild(m)),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},x={}.hasOwnProperty,y;!B(x,"undefined")&&!B(x.call,"undefined")?y=function(a,b){return x.call(a,b)}:y=function(a,b){return b in a&&B(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e});var G=function(a,c){var d=a.join(""),f=c.length;w(d,function(a,c){var d=b.styleSheets[b.styleSheets.length-1],g=d?d.cssRules&&d.cssRules[0]?d.cssRules[0].cssText:d.cssText||"":"",h=a.childNodes,i={};while(f--)i[h[f].id]=h[f];e.csstransforms3d=(i.csstransforms3d&&i.csstransforms3d.offsetLeft)===9&&i.csstransforms3d.offsetHeight===3},f,c)}([,["@media (",m.join("transform-3d),("),h,")","{#csstransforms3d{left:9px;position:absolute;height:3px;}}"].join("")],[,"csstransforms3d"]);q.csstransforms=function(){return!!F("transform")},q.csstransforms3d=function(){var a=!!F("perspective");return a&&"webkitPerspective"in g.style&&(a=e.csstransforms3d),a};for(var H in q)y(q,H)&&(v=H.toLowerCase(),e[v]=q[H](),t.push((e[v]?"":"no-")+v));return z(""),i=k=null,function(a,b){function g(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 h(){var a=k.elements;return typeof a=="string"?a.split(" "):a}function i(a){var b={},c=a.createElement,e=a.createDocumentFragment,f=e();a.createElement=function(a){var e=(b[a]||(b[a]=c(a))).cloneNode();return k.shivMethods&&e.canHaveChildren&&!d.test(a)?f.appendChild(e):e},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+h().join().replace(/\w+/g,function(a){return b[a]=c(a),f.createElement(a),'c("'+a+'")'})+");return n}")(k,f)}function j(a){var b;return a.documentShived?a:(k.shivCSS&&!e&&(b=!!g(a,"article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio{display:none}canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}mark{background:#FF0;color:#000}")),f||(b=!i(a)),b&&(a.documentShived=b),a)}var c=a.html5||{},d=/^<|^(?:button|form|map|select|textarea)$/i,e,f;(function(){var a=b.createElement("a");a.innerHTML="",e="hidden"in a,f=a.childNodes.length==1||function(){try{b.createElement("a")}catch(a){return!0}var c=b.createDocumentFragment();return typeof c.cloneNode=="undefined"||typeof c.createDocumentFragment=="undefined"||typeof c.createElement=="undefined"}()})();var k={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:j};a.html5=k,j(b)}(this,b),e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.testProp=function(a){return D([a])},e.testAllProps=F,e.testStyles=w,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return o.call(a)=="[object Function]"}function e(a){return typeof a=="string"}function f(){}function g(a){return!a||a=="loaded"||a=="complete"||a=="uninitialized"}function h(){var a=p.shift();q=1,a?a.t?m(function(){(a.t=="c"?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){a!="img"&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l={},o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};y[c]===1&&(r=1,y[c]=[],l=b.createElement(a)),a=="object"?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),a!="img"&&(r||y[c]===2?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i(b=="c"?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),p.length==1&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&o.call(a.opera)=="[object Opera]",l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return o.call(a)=="[object Array]"},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f div{ 219 | width:1050px; 220 | height:100px; 221 | margin:20px auto; 222 | } 223 | 224 | .thumbnails ul{ 225 | margin:0; 226 | padding:0; 227 | text-align:center; 228 | -webkit-transform:scale3d(0.5, 0.5, 1); 229 | -moz-transform:scale3d(0.5, 0.5, 1); 230 | -o-transform:scale3d(0.5, 0.5, 1); 231 | -ms-transform:scale3d(0.5, 0.5, 1); 232 | transform:scale3d(0.5, 0.5, 1); 233 | -webkit-transition:-webkit-transform ease-in-out 100ms; 234 | -moz-transition:-moz-transform ease-in-out 100ms; 235 | -ms-transition:-ms-transform ease-in-out 100ms; 236 | -o-transition:-o-transform ease-in-out 100ms; 237 | transition:transform ease-in-out 100ms; 238 | } 239 | 240 | .thumbanils-touch ul{ 241 | -webkit-transform:none; 242 | -moz-transform:none; 243 | -o-transform:none; 244 | -ms-transform:none; 245 | transform:none; 246 | } 247 | 248 | .thumbnails-hover ul{ 249 | -webkit-transform:scale3d(0.6, 0.6, 1); 250 | -moz-transform:scale3d(0.6, 0.6, 1); 251 | -o-transform:scale3d(0.6, 0.6, 1); 252 | -ms-transform:scale3d(0.6, 0.6, 1); 253 | transform:scale3d(0.6, 0.6, 1); 254 | } 255 | 256 | .thumbnails li{ 257 | list-style:none; 258 | display:inline-block; 259 | margin:0 5px; 260 | -webkit-box-shadow:0 0 10px #ccc; 261 | -moz-box-shadow:0 0 10px #ccc; 262 | -ms-box-shadow:0 0 10px #ccc; 263 | -o-box-shadow:0 0 10px #ccc; 264 | box-shadow:0 0 10px #ccc; 265 | -webkit-transition:-webkit-transform 60ms; 266 | -moz-transition:-webkit-transform 60ms; 267 | -o-transition:-webkit-transform 60ms; 268 | -ms-transition:-webkit-transform 60ms; 269 | transition:-webkit-transform 60ms; 270 | } 271 | 272 | .thumbnails li span{ 273 | display:none; 274 | } 275 | 276 | .thumbnails .current{ 277 | -webkit-box-shadow:0 0 10px red; 278 | -moz-box-shadow:0 0 10px red; 279 | -ms-box-shadow:0 0 10px red; 280 | -o-box-shadow:0 0 10px red; 281 | box-shadow:0 0 10px red; 282 | } 283 | 284 | .thumbnails .thumb-hover{ 285 | -webkit-transform:scale3d(1.3, 1.3, 1); 286 | -moz-transform:scale3d(1.3, 1.3, 1); 287 | -o-transform:scale3d(1.3, 1.3, 1); 288 | -ms-transform:scale3d(1.3, 1.3, 1); 289 | transform:scale3d(1.3, 1.3, 1); 290 | 291 | -webkit-box-shadow:0 0 10px #666; 292 | -moz-box-shadow:0 0 10px #666; 293 | -ms-box-shadow:0 0 10px #666; 294 | -o-box-shadow:0 0 10px #666; 295 | box-shadow:0 0 10px #666; 296 | } 297 | 298 | .thumbanils-touch .thumb-hover{ 299 | -webkit-transform:none; 300 | -moz-transform:none; 301 | -o-transform:none; 302 | -ms-transform:none; 303 | transform:none; 304 | } 305 | 306 | .thumbnails .thumb-hover span{ 307 | position:absolute; 308 | bottom:-30px; 309 | left:0; 310 | z-index:2; 311 | width:100%; 312 | height:30px; 313 | font:bold 15px arial; 314 | line-height:30px; 315 | color:#666; 316 | display:block; 317 | cursor:default; 318 | } 319 | 320 | .thumbnails img{ 321 | float:left; 322 | } 323 | 324 | .exit-message{ 325 | position: absolute; 326 | top:10px; 327 | left:0; 328 | width:100%; 329 | height:40px; 330 | z-index:10000; 331 | } 332 | 333 | .exit-message > div{ 334 | width:140px; 335 | height:30px; 336 | margin:auto; 337 | background:rgba(0,0,0,0.5); 338 | text-align:center; 339 | font:12px arial; 340 | line-height:30px; 341 | color:white; 342 | -webkit-border-radius:10px; 343 | -moz-border-radius:10px; 344 | -ms-border-radius:10px; 345 | -o-border-radius:10px; 346 | border-radius:10px; 347 | } 348 | 349 | .zoom-icon{ 350 | position:absolute; 351 | z-index:1000; 352 | width:22px; 353 | height:22px; 354 | top:10px; 355 | right:10px; 356 | background-image:image-url('zoom-icons.png'); 357 | background-size:88px 22px; 358 | } 359 | 360 | .zoom-icon-in{ 361 | background-position:0 0; 362 | cursor: pointer; 363 | } 364 | 365 | .zoom-icon-in.zoom-icon-in-hover{ 366 | background-position:-22px 0; 367 | cursor: pointer; 368 | } 369 | 370 | .zoom-icon-out{ 371 | background-position:-44px 0; 372 | } 373 | 374 | .zoom-icon-out.zoom-icon-out-hover{ 375 | background-position:-66px 0; 376 | cursor: pointer; 377 | } 378 | 379 | .bottom{ 380 | position:absolute; 381 | left:0; 382 | bottom:0; 383 | width:100%; 384 | } 385 | --------------------------------------------------------------------------------