Compare commits

...

114 Commits

Author SHA1 Message Date
yafeilee a2a1bd2b14 add beian 2019-05-24 15:13:31 +08:00
yafeilee 4ec5f33ed3 update deploy info 2019-05-23 17:26:47 +08:00
yafeilee 429a3b29df update mina ng puma 2019-05-11 10:39:42 +08:00
yafeilee 784eb72264 update about page 2019-05-11 10:32:00 +08:00
yafeilee a10730782a update about page 2019-04-23 11:10:49 +08:00
yafeilee ccf2619632 update puma 2019-04-23 11:00:55 +08:00
yafeilee 3fa8b966b7 update about page 2019-04-23 10:38:21 +08:00
yafeilee dcdb43f601 fix travis ci 2019-04-23 10:28:09 +08:00
yafeilee 46653b9154 fix error 2019-04-22 22:07:17 +08:00
yafeilee 0bc4493710 update gemfile 2019-04-22 18:06:29 +08:00
yafeilee efcf826b25 update home index 2018-12-21 15:37:32 +08:00
yafeilee 87b3d659a2 update gemfile versions 2018-12-18 11:00:27 +08:00
yafeilee 1d71f7cb61 Update gemfile 2018-09-30 10:24:07 +08:00
yafeilee df3622f5de update gems 2018-07-06 00:20:58 +08:00
yafeilee a2873718bb add blog search feature 2018-05-26 18:31:22 +08:00
yafeilee 9de1aa67d4 add simplecov 2018-05-19 18:47:38 +08:00
yafeilee 8431d42a6a update readme 2018-05-19 17:41:39 +08:00
yafeilee e81ea9b25f trying fix travis ci 2018-05-19 17:30:25 +08:00
yafeilee 8d278adaa2 refactor js 2018-05-19 17:16:30 +08:00
yafeilee 78c4f0efd9 fix depreciation message 2018-05-19 17:07:08 +08:00
yafeilee b038f4cce2 upgrade from 5.1 to 5.2 2018-05-19 15:11:27 +08:00
yafeilee 7e0aba298d fix xss 2018-03-23 14:18:45 +08:00
yafeilee fabb1d642b update text 2018-01-29 00:16:01 +08:00
yafeilee a58ae723a7 add wechat_qrcode 2018-01-28 23:49:37 +08:00
yafeilee 8d3666cc5c add en deploy file 2018-01-21 03:07:07 +08:00
yafeilee 5367b01b10 upgrade gems 2018-01-21 02:48:47 +08:00
yafeilee 9bf7ad5b6d update profile 2018-01-21 02:41:55 +08:00
yafeilee a27ac6278c update deploy info 2018-01-21 02:01:09 +08:00
yafeilee 2129ab60fb fix upload 500 error 2017-07-20 16:05:12 +08:00
yafeilee 2f24cc14db drop unicorn and use puma 2017-05-23 11:06:02 +08:00
yafeilee 74a2d01f8e fix deploy file 2017-05-13 09:59:29 +08:00
yafeilee 24b0273643 update mina version 2017-05-01 13:37:07 +08:00
yafeilee c36f7e5477 Merge branch 'rails51' 2017-04-30 19:48:40 +08:00
yafeilee fee635b556 upgrade to rails5.1 2017-04-30 19:48:31 +08:00
yafeilee 1982ba33b5 Remove useless route 2016-11-08 14:56:00 +08:00
yafeilee 2c72356c9c Modify about home page 2016-11-07 21:26:17 +08:00
yafeilee 65daae4668 Remove useless route 2016-11-07 14:58:04 +08:00
yafeilee 6faad25f2d Ignore exception with null session 2016-08-13 20:56:18 +08:00
yafeilee ca7b1e3de2 Reject request with csrf attack 2016-08-12 15:03:27 +08:00
yafeilee 6f29e536d7 Trying to fix comment attack 2016-08-12 14:18:28 +08:00
yafeilee 22ca31bbb5 Add defer instead of async 2016-08-07 12:03:58 +08:00
yafeilee 712275037f Trying to fix loading js async bug 2016-08-07 11:44:45 +08:00
yafeilee 3e2b0a137e Speed up js load time 2016-08-07 02:55:06 +08:00
yafeilee d75b5fce18 Fixed rspec warning info, refactor spec 2016-08-07 00:52:35 +08:00
yafeilee 0132f5c1d4 Update ruby version 2016-08-06 23:18:34 +08:00
yafeilee d8fdc0dd20 Add cdn 2016-08-06 02:29:42 +08:00
yafeilee e7fb967d55 warning -> warn 2016-07-29 09:24:25 +08:00
yafeilee c0ae414e8f Add browser_warrior 2016-07-29 01:25:17 +08:00
yafeilee b27c3d4a14 Update ruby version to 2.3.1 2016-07-29 01:11:29 +08:00
yafei lee 9c3902f9ba Merge pull request #49 from jimmy0017/dev 2016-07-29 00:04:50 +08:00
Jimmy Lin fc63d8fe42 Put footer's website and year into application.yml 2016-07-28 15:57:05 +02:00
Jimmy Lin 8442751b5f Change travis.yml to ruby 2.3.1 2016-07-28 14:26:13 +02:00
Jimmy Lin 432956aae9 Change footer's design by to www url 2016-07-28 14:17:13 +02:00
Jimmy Lin 6944c55aee Update to rails 5 2016-07-28 12:28:40 +02:00
yafeilee 91fc33cb23 remove quiet assets because rails5 has support it 2016-06-23 11:18:26 +08:00
yafeilee 14e9b70206 Update rails version, add mina logs 2016-05-15 18:12:20 +08:00
yafeilee 7bec3b85f8 Modify jiaoluo url 2016-05-13 23:37:46 +08:00
yafeilee 4083ec4b6b locales yml update 2016-05-03 12:25:23 +08:00
yafeilee da83ca8dd0 fixing admin posts bug 2016-05-03 02:44:46 +08:00
yafeilee f615325a2d Img tag css 2016-04-29 23:41:09 +08:00
yafeilee 31af9ab6d0 Fixing comment attack by bot 2016-04-29 23:37:52 +08:00
yafeilee 4de197f4c4 Update README 2016-04-28 17:20:38 +08:00
yafeilee 5f90e48078 Add screenshots link 2016-04-28 17:14:38 +08:00
yafeilee a2c5e9c677 Update README & Add wblog screenshots 2016-04-28 17:04:22 +08:00
yafeilee 990889e09b Update index page 2016-04-28 15:47:57 +08:00
yafeilee 2aa033e0b0 Cable config 2016-04-28 12:21:43 +08:00
yafeilee f0d9191ffc Production cable allow config 2016-04-28 12:02:36 +08:00
yafeilee 61236c2d09 Production cable config 2016-04-28 11:30:50 +08:00
yafeilee db6a585467 Merge branch 'actioncable' 2016-04-28 11:28:30 +08:00
yafeilee 560229b101 Add new feature #46 2016-04-28 11:28:15 +08:00
yafeilee 70e9e1c43f Rename *.js.coffee -> *.coffee 2016-04-28 11:18:01 +08:00
yafeilee 386a6f89a3 About page for small screen 2016-04-27 17:01:01 +08:00
yafeilee 26eb22b895 Fixing unsubscribe feature 2016-04-27 16:12:56 +08:00
yafeilee f543fad37d Admin subscribe manage 2016-04-27 15:42:44 +08:00
yafeilee 218548c3b4 Subscribe button text 2016-04-27 14:47:04 +08:00
yafeilee 0f83d3b127 Fixing unscrible link 2016-04-27 14:09:47 +08:00
yafeilee 1e2255e14f Blog index page for small screen 2016-04-27 14:05:47 +08:00
yafeilee 5f292edafc Blog content css adjust 2016-04-27 12:48:13 +08:00
yafeilee 45072bef37 Blog content css adjust 2016-04-27 12:36:38 +08:00
yafeilee d92ac0ce51 Merge branch 'subscribe' 2016-04-27 12:23:54 +08:00
yafeilee 8203757d1e Fix subscribe system for rails5 2016-04-27 12:22:37 +08:00
yafeilee 91e9201bea About page css adjust 2016-04-27 10:21:26 +08:00
yafeilee eb2886e686 Update Gemfile 2016-04-26 17:50:41 +08:00
yafeilee 9b68bbde0b Subscribe feature refactor 2016-04-26 00:20:14 +08:00
yafeilee deaa69f89e Travis yml 2016-04-25 14:46:33 +08:00
yafeilee a956a91501 Travis ci 2016-04-25 14:37:17 +08:00
yafeilee ca48a639c5 Travis ci 2016-04-25 14:36:42 +08:00
yafeilee 468364302d Remove database yml and just leave example file 2016-04-25 14:30:15 +08:00
yafeilee 53aaa7aa00 Fix travis install gem 2016-04-25 14:21:01 +08:00
yafeilee e9e89f19f3 Style css adjust 2016-04-25 14:11:51 +08:00
yafeilee e700fdac7e travis ci 2016-04-25 13:03:41 +08:00
yafeilee 33b7f6666d Fix test cases 2016-04-25 12:27:32 +08:00
yafeilee 6ec662459f Remove useless js 2016-04-25 11:47:06 +08:00
yafeilee 7139ab7429 Remove layout ga 2016-04-25 11:34:36 +08:00
yafeilee 1dfafd5c6b Support turoblinks5 2016-04-25 11:16:51 +08:00
yafeilee 179a79675c ga 2016-04-25 11:16:05 +08:00
yafeilee 276f37c081 Fix about page 2016-04-25 00:26:31 +08:00
yafeilee 55b0380865 English index page 2016-04-25 00:23:13 +08:00
yafeilee d8a2e96c78 Change to deploy master branch back 2016-04-25 00:14:48 +08:00
yafeilee 650e1951a8 Merge branch 'new_design' 2016-04-25 00:14:02 +08:00
yafeilee 86fdce2b43 English deploy file 2016-04-24 23:51:39 +08:00
yafeilee c041191aea deploy file update 2016-04-24 17:04:17 +08:00
yafeilee dee8533df9 Deploy file update 2016-04-24 15:58:24 +08:00
yafeilee e0346dca01 Deploy 2016-04-24 15:22:24 +08:00
yafeilee 033339182b Adjust comment and qrcode feature 2016-04-24 00:43:05 +08:00
yafeilee c749707d98 Refactor comment and qrcode 2016-04-23 14:47:50 +08:00
yafeilee 6bf723a3f7 about page refactor 2016-04-21 23:58:24 +08:00
yafeilee e1060b93af Like feature done 2016-04-21 22:11:18 +08:00
yafeilee 27314da99a Add turbolinks, remove useless angularjs code 2016-04-21 21:01:52 +08:00
yafeilee 02f09f1d38 Adjust paginator window argument 2016-04-21 18:12:30 +08:00
yafeilee 3615504b76 refactor admin post blog feature without angularjs 2016-04-21 18:07:55 +08:00
yafeilee 368467dfad Some topbar style improve 2016-04-20 23:36:51 +08:00
yafeilee 859d6bbf22 Refactor database with pg and foudation6( stage 1 ) 2016-04-20 22:29:29 +08:00
yafeilee a36c272e28 Upgrade rails4 -> rails5.beta3 2016-04-20 16:03:27 +08:00
216 changed files with 4059 additions and 3501 deletions

5
.gitignore vendored
View File

@ -8,8 +8,13 @@
*.swp
/public/uploads/*
/coverage
/config/application.yml
/config/mongoid.yml
/config/database.yml
/public/assets/*
*.old
*.bak
.byebug_history
.ruby-version

4
.rspec
View File

@ -1,2 +1,2 @@
--colour
--color
--require spec_helper

View File

@ -1,10 +1,9 @@
# http://about.travis-ci.org/docs/user/build-configuration/
env:
global:
- CC_TEST_REPORTER_ID=787a2f89b15c637323c7340d65ec17e898ac44480706b4b4122ea040c2a88f1d
language: ruby
#bundler_args: '--without production'
script: CODECLIMATE_REPO_TOKEN=7d6b988f58234bf1560305412d10d9530902beebdbe2fb938b6a934f1d352578 bundle exec rake
before_install:
- "cat /etc/timezone"
- "grep -i processor /proc/cpuinfo | wc -l"
@ -13,11 +12,20 @@ before_install:
- "bundle -v"
before_script:
- "bundle exec rake travis:before_script"
- "curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter"
- "chmod +x ./cc-test-reporter"
- "./cc-test-reporter before-build"
- "cp -f config/database.yml.example config/database.yml"
- "cp -f config/application.yml.example config/application.yml"
- "bundle exec rake db:drop db:create db:schema:load --trace 2>&1"
script: bundle exec rspec
after_script:
- "./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
services:
- mongodb
- postgresql
rvm:
- 2.1.0
- 2.2.0
- 2.3.0
- 2.5.3

60
Gemfile
View File

@ -1,63 +1,73 @@
source 'https://rubygems.org'
gem 'rails', '4.2.5.2'
gem 'sass-rails'
gem 'coffee-rails', '~> 4.1.0'
ruby '2.5.3'
gem 'rails', '~> 5.2.0'
gem 'sass-rails', '~> 5.0'
gem 'coffee-rails', '~> 4.2'
gem 'uglifier', '>= 2.7.2'
gem 'jquery-rails'
gem 'foundation-rails', '~> 5.5.1'
gem 'foundation-rails', '~> 6.2.1'
gem 'foundation-icons-sass-rails'
gem 'font-awesome-sass'
gem 'font-awesome-sass', '4.7.0'
gem 'carrierwave'
gem 'kaminari'
gem 'turbolinks', '~> 5.x'
gem 'js_cookie_rails'
gem 'rails-i18n', '~> 5.1'
gem 'jbuilder'
gem 'pg', '~> 0.18'
gem 'mongoid'
gem 'mongoid-tree'
gem 'mongoid-pagination'
gem 'redcarpet'
gem 'rouge'
gem 'slim-rails'
gem 'simple_form'
gem 'simple_form', '~> 4.0.0'
gem 'mini_magick'
gem 'carrierwave-mongoid'
gem 'html_truncator'
gem 'nokogiri'
gem 'angularjs-rails'
gem 'figaro'
gem 'rqrcode-with-patches', require: 'rqrcode'
gem 'chunky_png'
gem 'sidekiq'
gem 'redis-namespace'
gem 'rest-client'
gem 'unicorn'
gem 'newrelic_rpm'
gem 'mina', require: false
gem 'mina-multistage', require: false
gem 'mina-sidekiq', require: false
gem 'mina-unicorn', require: false
gem 'puma'
gem 'bootsnap', '>= 1.3.0', require: false
gem 'mina', '~> 1.2.2', require: false
gem 'mina-ng-puma', '~> 1.3.0', require: false
gem 'mina-multistage', '~> 1.0.3', require: false
gem 'mina-sidekiq', '~> 1.0.3', require: false
gem 'mina-logs', '~> 1.1.0', require: false
gem 'browser_warrior'
group :development do
gem 'spring'
gem 'quiet_assets'
gem 'guard'
gem 'guard-rails'
gem 'guard-rspec', require: false
gem 'guard-bundler', require: false
gem 'listen', '~> 3.x'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
gem 'rack-cors', :require => 'rack/cors'
end
group :test do
gem 'capybara'
gem 'mongoid-rspec', :require => false
gem 'database_cleaner'
gem 'rspec-sidekiq'
gem "codeclimate-test-reporter", group: :test, require: nil
gem 'simplecov'
gem 'simplecov-console'
end
group :test, :development do
gem "rspec-rails", ">= 2.8.1"
gem 'pry-rails'
gem 'pry-nav'
gem 'factory_girl_rails'
gem 'byebug'
gem 'factory_bot_rails'
gem 'rails-controller-testing'
end

View File

@ -1,338 +1,377 @@
GEM
remote: https://rubygems.org/
specs:
actionmailer (4.2.5.2)
actionpack (= 4.2.5.2)
actionview (= 4.2.5.2)
activejob (= 4.2.5.2)
actioncable (5.2.3)
actionpack (= 5.2.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailer (5.2.3)
actionpack (= 5.2.3)
actionview (= 5.2.3)
activejob (= 5.2.3)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.5.2)
actionview (= 4.2.5.2)
activesupport (= 4.2.5.2)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-dom-testing (~> 2.0)
actionpack (5.2.3)
actionview (= 5.2.3)
activesupport (= 5.2.3)
rack (~> 2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.5.2)
activesupport (= 4.2.5.2)
actionview (5.2.3)
activesupport (= 5.2.3)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (4.2.5.2)
activesupport (= 4.2.5.2)
globalid (>= 0.3.0)
activemodel (4.2.5.2)
activesupport (= 4.2.5.2)
builder (~> 3.1)
activerecord (4.2.5.2)
activemodel (= 4.2.5.2)
activesupport (= 4.2.5.2)
arel (~> 6.0)
activesupport (4.2.5.2)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.2.3)
activesupport (= 5.2.3)
globalid (>= 0.3.6)
activemodel (5.2.3)
activesupport (= 5.2.3)
activerecord (5.2.3)
activemodel (= 5.2.3)
activesupport (= 5.2.3)
arel (>= 9.0)
activestorage (5.2.3)
actionpack (= 5.2.3)
activerecord (= 5.2.3)
marcel (~> 0.3.1)
activesupport (5.2.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.4.0)
angularjs-rails (1.5.0)
arel (6.0.3)
bson (4.0.4)
builder (3.2.2)
capybara (2.6.2)
addressable (2.6.0)
public_suffix (>= 2.0.2, < 4.0)
ansi (1.5.0)
arel (9.0.0)
babel-source (5.8.35)
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
execjs (~> 2.0)
bootsnap (1.4.3)
msgpack (~> 1.0)
browser (2.5.3)
browser_warrior (0.10.0)
browser
rails (~> 5.0)
sass-rails (~> 5.0)
builder (3.2.3)
byebug (11.0.1)
capybara (3.17.0)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (~> 1.2)
xpath (~> 3.2)
carrierwave (1.3.1)
activemodel (>= 4.0.0)
activesupport (>= 4.0.0)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
carrierwave (0.10.0)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
mime-types (>= 1.16)
carrierwave-mongoid (0.8.1)
carrierwave (>= 0.8.0, < 0.11.0)
mongoid (>= 3.0, < 6.0)
mongoid-grid_fs (>= 1.3, < 3.0)
chunky_png (1.3.5)
codeclimate-test-reporter (0.5.0)
simplecov (>= 0.7.1, < 1.0.0)
coderay (1.1.1)
coffee-rails (4.1.1)
codeclimate-test-reporter (1.0.7)
simplecov
coderay (1.1.2)
coffee-rails (4.2.2)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.1.x)
railties (>= 4.0.0)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.10.0)
concurrent-ruby (1.0.1)
connection_pool (2.2.0)
database_cleaner (1.5.1)
diff-lcs (1.2.5)
docile (1.1.5)
domain_name (0.5.20160309)
coffee-script-source (1.12.2)
concurrent-ruby (1.1.5)
connection_pool (2.2.2)
crass (1.0.4)
database_cleaner (1.7.0)
diff-lcs (1.3)
docile (1.3.1)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
erubis (2.7.0)
execjs (2.6.0)
factory_girl (4.5.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.6.0)
factory_girl (~> 4.5.0)
railties (>= 3.0.0)
ffi (1.9.10)
erubi (1.8.0)
execjs (2.7.0)
factory_bot (5.0.2)
activesupport (>= 4.2.0)
factory_bot_rails (5.0.2)
factory_bot (~> 5.0.2)
railties (>= 4.2.0)
ffi (1.10.0)
figaro (1.1.1)
thor (~> 0.14)
font-awesome-sass (4.5.0)
font-awesome-sass (4.7.0)
sass (>= 3.2)
formatador (0.2.5)
foundation-icons-sass-rails (3.0.0)
railties (>= 3.1.1)
sass-rails (>= 3.1.1)
foundation-rails (5.5.3.2)
foundation-rails (6.2.4.0)
railties (>= 3.1.0)
sass (>= 3.3.0, < 3.5)
globalid (0.3.6)
activesupport (>= 4.1.0)
guard (2.13.0)
sprockets-es6 (>= 0.9.0)
globalid (0.4.2)
activesupport (>= 4.2.0)
guard (2.15.0)
formatador (>= 0.2.4)
listen (>= 2.7, <= 4.0)
lumberjack (~> 1.0)
listen (>= 2.7, < 4.0)
lumberjack (>= 1.0.12, < 2.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-bundler (2.1.0)
bundler (~> 1.0)
guard-bundler (2.2.1)
bundler (>= 1.3.0, < 3)
guard (~> 2.2)
guard-compat (~> 1.1)
guard-compat (1.2.1)
guard-rails (0.7.2)
guard-rails (0.8.1)
guard (~> 2.11)
guard-compat (~> 1.0)
guard-rspec (4.6.4)
guard-rspec (4.7.3)
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
html_truncator (0.4.1)
hirb (0.7.3)
html_truncator (0.4.2)
nokogiri (~> 1.5)
http-cookie (1.0.2)
http-cookie (1.0.3)
domain_name (~> 0.5)
i18n (0.7.0)
jbuilder (2.4.1)
activesupport (>= 3.0.0, < 5.1)
multi_json (~> 1.2)
jquery-rails (4.1.1)
i18n (1.6.0)
concurrent-ruby (~> 1.0)
jbuilder (2.8.0)
activesupport (>= 4.2.0)
multi_json (>= 1.2)
jquery-rails (4.3.3)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.3)
kgio (2.10.0)
listen (3.0.6)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9.7)
loofah (2.0.3)
js_cookie_rails (2.2.0)
railties (>= 3.1)
json (2.2.0)
kaminari (1.1.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.1.1)
kaminari-activerecord (= 1.1.1)
kaminari-core (= 1.1.1)
kaminari-actionview (1.1.1)
actionview
kaminari-core (= 1.1.1)
kaminari-activerecord (1.1.1)
activerecord
kaminari-core (= 1.1.1)
kaminari-core (1.1.1)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
loofah (2.2.3)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lumberjack (1.0.10)
mail (2.6.3)
mime-types (>= 1.16, < 3)
method_source (0.8.2)
mime-types (2.99.1)
mina (0.3.8)
lumberjack (1.0.13)
mail (2.7.1)
mini_mime (>= 0.1.1)
marcel (0.3.3)
mimemagic (~> 0.3.2)
method_source (0.9.2)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.0331)
mimemagic (0.3.3)
mina (1.2.3)
open4 (~> 1.3.4)
rake
mina-multistage (1.0.2)
mina (>= 0.2.1)
mina-sidekiq (0.3.1)
mina
mina-unicorn (0.4.0)
mini_magick (4.4.0)
mini_portile2 (2.0.0)
minitest (5.8.4)
mongo (2.2.4)
bson (~> 4.0)
mongoid (5.1.1)
activemodel (~> 4.0)
mongo (~> 2.1)
origin (~> 2.2)
tzinfo (>= 0.3.37)
mongoid-grid_fs (2.2.1)
mime-types (>= 1.0, < 3.0)
mongoid (>= 3.0, < 6.0)
mongoid-pagination (0.2.0)
activesupport
mongoid
mongoid-rspec (3.0.0)
mongoid (~> 5.0)
rake
rspec (~> 3.3)
mongoid-tree (2.0.1)
mongoid (>= 4.0, < 6.0)
multi_json (1.11.2)
mina-logs (1.1.0)
mina (>= 1.0.2)
mina-multistage (1.0.3)
mina (~> 1.0)
mina-ng-puma (1.3.0)
mina (~> 1.2.0)
puma (>= 2.13)
mina-sidekiq (1.0.3)
mina (>= 1.0.2)
mini_magick (4.9.3)
mini_mime (1.0.1)
mini_portile2 (2.4.0)
minitest (5.11.3)
msgpack (1.2.10)
multi_json (1.13.1)
nenv (0.3.0)
netrc (0.11.0)
newrelic_rpm (3.15.0.314)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
notiffany (0.0.8)
nio4r (2.3.1)
nokogiri (1.10.2)
mini_portile2 (~> 2.4.0)
notiffany (0.1.1)
nenv (~> 0.1)
shellany (~> 0.0)
open4 (1.3.4)
origin (2.2.0)
pry (0.10.3)
pg (0.21.0)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry-nav (0.2.4)
pry (>= 0.9.10, < 0.11.0)
pry-rails (0.3.4)
pry (>= 0.9.10)
quiet_assets (1.1.0)
railties (>= 3.1, < 5.0)
rack (1.6.4)
rack-cors (0.4.0)
rack-test (0.6.3)
rack (>= 1.0)
rails (4.2.5.2)
actionmailer (= 4.2.5.2)
actionpack (= 4.2.5.2)
actionview (= 4.2.5.2)
activejob (= 4.2.5.2)
activemodel (= 4.2.5.2)
activerecord (= 4.2.5.2)
activesupport (= 4.2.5.2)
bundler (>= 1.3.0, < 2.0)
railties (= 4.2.5.2)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.7)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
railties (4.2.5.2)
actionpack (= 4.2.5.2)
activesupport (= 4.2.5.2)
method_source (~> 0.9.0)
public_suffix (3.0.3)
puma (3.12.1)
rack (2.0.7)
rack-cors (1.0.3)
rack-protection (2.0.5)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.2.3)
actioncable (= 5.2.3)
actionmailer (= 5.2.3)
actionpack (= 5.2.3)
actionview (= 5.2.3)
activejob (= 5.2.3)
activemodel (= 5.2.3)
activerecord (= 5.2.3)
activestorage (= 5.2.3)
activesupport (= 5.2.3)
bundler (>= 1.3.0)
railties (= 5.2.3)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.4)
actionpack (>= 5.0.1.x)
actionview (>= 5.0.1.x)
activesupport (>= 5.0.1.x)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.0.4)
loofah (~> 2.2, >= 2.2.2)
rails-i18n (5.1.3)
i18n (>= 0.7, < 2)
railties (>= 5.0, < 6)
railties (5.2.3)
actionpack (= 5.2.3)
activesupport (= 5.2.3)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
raindrops (0.16.0)
rake (11.1.1)
rb-fsevent (0.9.7)
rb-inotify (0.9.7)
ffi (>= 0.5.0)
redcarpet (3.3.4)
redis (3.2.2)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
rest-client (1.8.0)
thor (>= 0.19.0, < 2.0)
rake (12.3.2)
rb-fsevent (0.10.3)
rb-inotify (0.10.0)
ffi (~> 1.0)
redcarpet (3.4.0)
redis (4.1.0)
redis-namespace (1.6.0)
redis (>= 3.0.4)
regexp_parser (1.4.0)
rest-client (2.0.2)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rouge (1.10.1)
rqrcode-with-patches (0.6.0)
chunky_png
rspec (3.4.0)
rspec-core (~> 3.4.0)
rspec-expectations (~> 3.4.0)
rspec-mocks (~> 3.4.0)
rspec-core (3.4.4)
rspec-support (~> 3.4.0)
rspec-expectations (3.4.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
rouge (3.3.0)
rspec (3.8.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-core (3.8.0)
rspec-support (~> 3.8.0)
rspec-expectations (3.8.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-mocks (3.4.1)
rspec-support (~> 3.8.0)
rspec-mocks (3.8.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-rails (3.4.2)
actionpack (>= 3.0, < 4.3)
activesupport (>= 3.0, < 4.3)
railties (>= 3.0, < 4.3)
rspec-core (~> 3.4.0)
rspec-expectations (~> 3.4.0)
rspec-mocks (~> 3.4.0)
rspec-support (~> 3.4.0)
rspec-sidekiq (2.2.0)
rspec (~> 3.0, >= 3.0.0)
rspec-support (~> 3.8.0)
rspec-rails (3.8.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-support (~> 3.8.0)
rspec-sidekiq (3.0.3)
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.4.1)
sass (3.4.21)
sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
rspec-support (3.8.0)
ruby_dep (1.5.0)
sass (3.4.25)
sass-rails (5.0.7)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
shellany (0.0.1)
sidekiq (4.1.1)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
redis (~> 3.2, >= 3.2.1)
simple_form (3.2.1)
actionpack (> 4, < 5.1)
activemodel (> 4, < 5.1)
simplecov (0.11.2)
docile (~> 1.1.0)
json (~> 1.8)
sidekiq (5.2.6)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
simple_form (4.0.1)
actionpack (>= 5.0)
activemodel (>= 5.0)
simplecov (0.16.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
slim (3.0.6)
temple (~> 0.7.3)
tilt (>= 1.3.3, < 2.1)
slim-rails (3.0.1)
actionmailer (>= 3.1, < 5.0)
actionpack (>= 3.1, < 5.0)
activesupport (>= 3.1, < 5.0)
railties (>= 3.1, < 5.0)
slim (~> 3.0)
slop (3.6.0)
spring (1.6.4)
sprockets (3.5.2)
simplecov-console (0.4.2)
ansi
hirb
simplecov
simplecov-html (0.10.2)
slim (4.0.1)
temple (>= 0.7.6, < 0.9)
tilt (>= 2.0.6, < 2.1)
slim-rails (3.2.0)
actionpack (>= 3.1)
railties (>= 3.1)
slim (>= 3.0, < 5.0)
spring (2.0.2)
activesupport (>= 4.2)
spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0)
spring (>= 1.2, < 3.0)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.0.4)
sprockets-es6 (0.9.2)
babel-source (>= 5.8.11)
babel-transpiler
sprockets (>= 3.0.0)
sprockets-rails (3.2.1)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
temple (0.7.6)
thor (0.19.1)
thread_safe (0.3.5)
tilt (2.0.2)
tzinfo (1.2.2)
temple (0.8.1)
thor (0.20.3)
thread_safe (0.3.6)
tilt (2.0.9)
turbolinks (5.2.0)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (2.7.2)
execjs (>= 0.3.0)
json (>= 1.8.0)
uglifier (4.1.20)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
unicorn (5.0.1)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
xpath (2.0.0)
nokogiri (~> 1.3)
unf_ext (0.0.7.6)
websocket-driver (0.7.0)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
PLATFORMS
ruby
DEPENDENCIES
angularjs-rails
bootsnap (>= 1.3.0)
browser_warrior
byebug
capybara
carrierwave-mongoid
chunky_png
carrierwave
codeclimate-test-reporter
coffee-rails (~> 4.1.0)
coffee-rails (~> 4.2)
database_cleaner
factory_girl_rails
factory_bot_rails
figaro
font-awesome-sass
font-awesome-sass (= 4.7.0)
foundation-icons-sass-rails
foundation-rails (~> 5.5.1)
foundation-rails (~> 6.2.1)
guard
guard-bundler
guard-rails
@ -340,33 +379,41 @@ DEPENDENCIES
html_truncator
jbuilder
jquery-rails
mina
mina-multistage
mina-sidekiq
mina-unicorn
js_cookie_rails
kaminari
listen (~> 3.x)
mina (~> 1.2.2)
mina-logs (~> 1.1.0)
mina-multistage (~> 1.0.3)
mina-ng-puma (~> 1.3.0)
mina-sidekiq (~> 1.0.3)
mini_magick
mongoid
mongoid-pagination
mongoid-rspec
mongoid-tree
newrelic_rpm
nokogiri
pry-nav
pry-rails
quiet_assets
pg (~> 0.18)
puma
rack-cors
rails (= 4.2.5.2)
rails (~> 5.2.0)
rails-controller-testing
rails-i18n (~> 5.1)
redcarpet
redis-namespace
rest-client
rouge
rqrcode-with-patches
rspec-rails (>= 2.8.1)
rspec-sidekiq
sass-rails
sass-rails (~> 5.0)
sidekiq
simple_form
simple_form (~> 4.0.0)
simplecov
simplecov-console
slim-rails
spring
spring-watcher-listen (~> 2.0.0)
turbolinks (~> 5.x)
uglifier (>= 2.7.2)
unicorn
RUBY VERSION
ruby 2.5.3p105
BUNDLED WITH
1.17.1

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) <2012-2014> <liyafei>
Copyright (c) <2012-2016> <liyafei>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

117
README.md
View File

@ -1,38 +1,43 @@
WBlog
=======
[![Build Status](https://travis-ci.org/windy/wblog.svg?branch=master)](https://travis-ci.org/windy/wblog)
[![Code Climate](https://codeclimate.com/github/windy/wblog.png)](https://codeclimate.com/github/windy/wblog)
[![Test Coverage](https://codeclimate.com/github/windy/wblog/coverage.png)](https://codeclimate.com/github/windy/wblog)
[![Maintainability](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/maintainability)](https://codeclimate.com/github/windy/wblog/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/test_coverage)](https://codeclimate.com/github/windy/wblog/test_coverage)
The missing open source blog system on Ruby on Rails.
WBlog is open source blog which built for mobile first, it's licenced on MIT, use it for free!
New: WBlog is using Ruby on Rails 5.2 now.
[中文说明文档](/README.zh-CN.md)
Characteristic:
* Awful reading feeling for reader
* Independent comment system, store data on your own sever
* With mardown supported, you can post both powerful and clean formatted article(s)
* Modern clean reading feelings
* Markdown support, give nice formatted articles
* Mobile first, responsive page for iPhone, iPad, iMac.
* Independent comment system, subscribe system, picture manage system
A demo came from my English blog: <http://en.yafeilee.me>
Another demo using my blog: <http://yafeilee.me>
Power Admin Dashboard: <http://en.yafeilee.me/admin>, user and password are configurable.
![screenshot](https://github.com/windy/wblog/raw/master/doc/wblog.gif)
Some [screenshots](#screenshots)
### System dependencies
* Ruby ( >= 2.0 )
* Mongodb ( >= 2.7 )
* Ruby ( = 2.3.1 )
* Postgresql ( >= 9.x )
* Nginx ( >= 1.4 )
### Features
* Responsive, iPhone, iPad, Notebook, PC, all are supported
* QR Code attached article, scan and share it
* Inpendent comment system, managed by yourself
* QR Code, Like button make your article easily sharing with your friends
* Inpendent comment system, subscribe system, that all belong to you
* Markdown supported, code highlight, especially for programmer, like you
* Personalize it, commercialize it, it all depends on you
@ -40,31 +45,37 @@ Power Admin Dashboard: <http://en.yafeilee.me/admin>, user and password are conf
Make it to the best Ruby on Rails Blog system in the world.
### Study it locally
### Running in development mode
WBlog MUST run in Linux or Mac, it depends on Mongodb database. You can run it like a Ruby on Rails project as usual:
WBlog MUST run in Linux or OSX. I assume you are using OS X 10.
You can run it like a Ruby on Rails project as usual:
0. Check dependencies
```shell
ruby -v
# 2.3.1
postgres --version
# 9.x.x
```
1. Clone it
`git clone git@github.com:windy/wblog.git`
`cd wblog `
`cd wblog`
2. Install dependencies & configure
```shell
# Install mongodb ( see how to install it on your platform )
# on Mac, you can install it like this:
brew install mongodb
```
```shell
gem install bundler
bundle install
cp config/application.yml.example config/application.yml
cp config/mongoid.yml.example config/mongoid.yml
cp config/database.yml.example config/database.yml
```
Update application.yml & mongoid.yml as you need
Update `application.yml` & `database.yml` 's content as you need
3. Start it
@ -72,35 +83,39 @@ WBlog MUST run in Linux or Mac, it depends on Mongodb database. You can run it l
rails s
```
Open browser with `http://localhost:3000`
If there is any error found, please check your database's user and password.
4. Post the first article
4. Post the first blog
visit: http://127.0.0.1:3000/admin, input your username and password that is just configurated in application.yml.
visit: http://localhost:3000/admin, input your username and password configurated in `application.yml`.
then, post a new article.
OK, That's all.
### Deployment
WBlog uses `mina` as automation deployment tool, uses `unicorn` as the Rack container.
WBlog uses `mina` as automation deployment tool, uses `puma` as the Rack container.
WBlog recommends `nginx` as reverse proxy server.
It will be very fast.
Ruby on Rails project deployment is another big topic, I would NOT talk it here.
Ruby on Rails project deployment is another topic, I would NOT talk it here.
You can read WBlog wiki for more information: [WBlog 的发布流程(Chinese only now)](https://github.com/windy/wblog/wiki)
### Stack
* Ruby on Rails 4.2.x / Ruby 2.x
* AngularJS
* Foundation 5
* Ruby on Rails 5.2.0
* Ruby 2.3.1
* Turbolinks 5 / SJR
* Foundation 6
* mina
* slim
* Mongodb
* puma
* Postgresql
## Related open source blog systems
@ -110,3 +125,45 @@ You can read WBlog wiki for more information: [WBlog 的发布流程(Chinese onl
* octopress( Github Pages ): <http://octopress.org/>
* middleman( Ruby Gem ): Another static blog system <https://github.com/middleman/middleman>
* robbin_site( Padrino ): <https://github.com/robbin/robbin_site>
## License
MIT.
### Screenshots
Home Page:
![screenshot home](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/home.png)
Home Page for mobile:
![screenshot home small](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/home-small.png)
Home Page Hover Status for mobile:
![screenshot home hover](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/home-small-hover.png)
Blog Show Page:
![screenshot post](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/post.png)
Blog Show Page Hover Status:
![screenshot post hover](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/post-hover.png)
Admin Login Page:
![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-login.png)
Admin Dashboard Page:
![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-dashboard.png)
Admin New Blog Page:
![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-post.png)
Admin Blogs Manage Page:
![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-posts.png)

View File

@ -1,65 +1,64 @@
WBlog
=======
[![Build Status](https://travis-ci.org/windy/wblog.svg?branch=master)](https://travis-ci.org/windy/wblog)
[![Code Climate](https://codeclimate.com/github/windy/wblog.png)](https://codeclimate.com/github/windy/wblog)
[![Test Coverage](https://codeclimate.com/github/windy/wblog/coverage.png)](https://codeclimate.com/github/windy/wblog)
[![Maintainability](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/maintainability)](https://codeclimate.com/github/windy/wblog/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/test_coverage)](https://codeclimate.com/github/windy/wblog/test_coverage)
为移动而生的 Ruby on Rails 开源博客. WBlog 基于 MIT 协议, 自由使用.
现已全面支持 Ruby on Rails 5.2 版本!!!
* 用户极为友好的阅读体验
* 自带干净的评论系统
* 简洁而不简单的发布博客流程
访问我的博客以体验: <http://yafeilee.me>
访问我的博客以体验: <http://yafeilee.com>
后台禁止爬虫, 使用: <http://yafeilee.me/admin> 访问, 用户名密码可配置.
后台禁止爬虫, 使用: <http://yafeilee.com/admin> 访问, 用户名密码可配置.
![screenshot](https://github.com/windy/wblog/raw/master/doc/wblog.gif)
截图如下: <#screenshots>
### 为什么重写 WBlog
老的 WBlog 是两年前构建的, 体验越来越差, 而个人不喜欢托管博客到其他的站点, 又没有合适的 Ruby on Rails 博客系统.
### WBlog 的设计目标
* 优先以手机用户体验为主
* 干净的评论系统
* 独立干净的评论系统
* 良好的博客语法高亮支持
* markdown, 简洁而不简单的后台
* 要独立站点
* 可邮件订阅
* Markdown 支持
* 尽可能独立
### 特色
* 自适应于所有屏幕终端, 方便微信分享与评论
* 优先考虑移动用户, 可方便使用二维码扫描与关注
* 优先支持移动端访问
* 响应式设计, 支持所有屏幕终端, 并且支持微信扫码继续阅读和分享
* 自带评论系统, 干净而方便
* markdown 支持, 博客语法高亮, 方便技术性博客
* 开源可商用, 个性化能力超强 ( 与非独立博客相比 )
* Markdown 支持, 博客语法高亮, 方便技术性博客
* 开源可商用, 定制能力强
### 期望
成为 `Ruby on Rails` 下最好用的独立博客建站系统
### 本地学习
### 开发环境
WBlog 是一个基本的博客系统, 使用它之前, 你需要准备一台 VPS 独立主机, 安装好 Ruby on Rails 与 Mongodb. 我希望你是熟悉 Ruby on Rails 的, 这样方便定制 WBlog, 现在 WBlog 还太小.
WBlog 是一个标准的 Ruby on Rails 应用. 开发环境依赖于:
假定你有环境后, 克隆本代码. 然后与往常的 Rails 项目一样, 先安装 mongodb
```shell
# Install mongodb ( see how to install it on your platform )
# on Mac, you can install it like this:
brew install mongodb
```
* Ruby ( = 2.3.1 )
* Postgresql ( >= 9.x )
配置 WBlog:
```shell
gem install bundler
bundle install
cp config/application.yml.example config/application.yml
cp config/mongoid.yml.example config/mongoid.yml
cp config/database.yml.example config/database.yml
```
根据你个人情况, 更新对应的 application.yml & mongoid.yml.
更新对应配置: application.yml & database.yml.
对于配置有不明白的地方, 可以来这里咨询.
就这样, 可以尝试启动了:
@ -67,20 +66,22 @@ WBlog 是一个基本的博客系统, 使用它之前, 你需要准备一台 VPS
rails s
```
登录 http://localhsot:3000/admin 来发布第一篇博客.
### 发布应用
WBlog 采用了 `mina` 作为自动化发布工具, 使用 `nginx`, `unicorn` 为相关容器.
WBlog 采用了 `mina` 作为自动化发布工具, 使用 `nginx`, `puma` 为相关容器.
对应的发布流程在: [WBlog 的发布流程](https://github.com/windy/wblog/wiki)
### 技术栈
* Ruby on Rails 4.1.8 / Ruby 2.0
* AngularJS
* Foundation 5
* Ruby on Rails 5.2.0
* Ruby 2.3.1
* Foundation 6
* mina
* slim
* Mongodb
* Postgresql
## Ruby 相关开源博客推荐
@ -90,3 +91,41 @@ WBlog 采用了 `mina` 作为自动化发布工具, 使用 `nginx`, `unicorn`
* octopress( Github Pages ): <http://octopress.org/>
* middleman( Ruby Gem, Static ): <https://github.com/middleman/middleman>
* robbin_site( Padrino ): <https://github.com/robbin/robbin_site>
### Screenshots
首页:
![screenshot home](https://github.com/windy/wblog/raw/master/doc/wblog_s/home.png)
小屏首页:
![screenshot home small](https://github.com/windy/wblog/raw/master/doc/wblog_s/home-small.png)
展开的小屏首页:
![screenshot home hover](https://github.com/windy/wblog/raw/master/doc/wblog_s/home-small-hover.png)
博客详情页:
![screenshot post](https://github.com/windy/wblog/raw/master/doc/wblog_s/post.png)
展开的博客详情页:
![screenshot post hover](https://github.com/windy/wblog/raw/master/doc/wblog_s/post-hover.png)
管理员登录页:
![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-login.png)
管理页面板:
![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-dashboard.png)
发布新博客页:
![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-post.png)
博客管理页:
![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-posts.png)

BIN
app/assets/images/download-bg.jpg Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 85 KiB

BIN
app/assets/images/intro-bg.jpg Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 36 KiB

BIN
app/assets/images/mp.jpg Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
app/assets/images/weixin.jpg Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,13 @@
$(document).on 'turbolinks:load', ->
if $('.about-page').length > 0
$(window).scroll ()->
if $(this).scrollTop() > 0
$('.top-bar-wrapper').addClass('active')
else
$('.top-bar-wrapper').removeClass('active')
$('#about-top-bar').ddscrollSpy
highlightclass: 'active'
$('.intro').ddscrollSpy
highlightclass: 'active'

View File

@ -3,24 +3,42 @@
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
#
$(document).ready ->
$(document).on 'turbolinks:load', ->
$('a#upload_photo').click ->
$('input[type=file]').show().focus().click().hide()
false
$('#tabs').on 'change.zf.tabs', ()->
if $('#preview:visible').length > 0
$('#preview').text('Loading...')
$.ajax
url: '/admin/posts/preview'
type: 'POST'
data:
content: $('#content-input').val()
success: (data)->
$('#preview').html(data)
$('a.tag').click (event)->
event.preventDefault()
new_labels = $(this).text()
if $('#labels').val() == ''
labels = new_labels
else
labels = $('#labels').val() + ", #{new_labels}"
$('#labels').val(labels)
opt =
type: 'POST'
url: "/photos"
success: (data,status,xhr)->
txtBox = $("#post_content")
txtBox = $("#content-input")
caret_pos = txtBox.caret('pos')
src_merged = "\n" + data + "\n"
source = txtBox.val()
before_text = source.slice(0, caret_pos)
txtBox.val(before_text + src_merged + source.slice(caret_pos+1, source.count))
txtBox.caret('pos',caret_pos + src_merged.length)
txtBox.scope().content = txtBox.val()
txtBox.focus()
$('input[type=file]').fileUpload opt

View File

@ -1,589 +0,0 @@
/**
* x is a value between 0 and 1, indicating where in the animation you are.
*/
var duScrollDefaultEasing = function (x) {
'use strict';
if(x < 0.5) {
return Math.pow(x*2, 2)/2;
}
return 1-Math.pow((1-x)*2, 2)/2;
};
angular.module('duScroll', [
'duScroll.scrollspy',
'duScroll.smoothScroll',
'duScroll.scrollContainer',
'duScroll.spyContext',
'duScroll.scrollHelpers'
])
//Default animation duration for smoothScroll directive
.value('duScrollDuration', 350)
//Scrollspy debounce interval, set to 0 to disable
.value('duScrollSpyWait', 100)
//Wether or not multiple scrollspies can be active at once
.value('duScrollGreedy', false)
//Default offset for smoothScroll directive
.value('duScrollOffset', 0)
//Default easing function for scroll animation
.value('duScrollEasing', duScrollDefaultEasing);
angular.module('duScroll.scrollHelpers', ['duScroll.requestAnimation'])
.run(["$window", "$q", "cancelAnimation", "requestAnimation", "duScrollEasing", "duScrollDuration", "duScrollOffset", function($window, $q, cancelAnimation, requestAnimation, duScrollEasing, duScrollDuration, duScrollOffset) {
'use strict';
var proto = angular.element.prototype;
var isDocument = function(el) {
return (typeof HTMLDocument !== 'undefined' && el instanceof HTMLDocument) || (el.nodeType && el.nodeType === el.DOCUMENT_NODE);
};
var isElement = function(el) {
return (typeof HTMLElement !== 'undefined' && el instanceof HTMLElement) || (el.nodeType && el.nodeType === el.ELEMENT_NODE);
};
var unwrap = function(el) {
return isElement(el) || isDocument(el) ? el : el[0];
};
proto.scrollTo = function(left, top, duration, easing) {
var aliasFn;
if(angular.isElement(left)) {
aliasFn = this.scrollToElement;
} else if(duration) {
aliasFn = this.scrollToAnimated;
}
if(aliasFn) {
return aliasFn.apply(this, arguments);
}
var el = unwrap(this);
if(isDocument(el)) {
return $window.scrollTo(left, top);
}
el.scrollLeft = left;
el.scrollTop = top;
};
var scrollAnimation, deferred;
proto.scrollToAnimated = function(left, top, duration, easing) {
if(duration && !easing) {
easing = duScrollEasing;
}
var startLeft = this.scrollLeft(),
startTop = this.scrollTop(),
deltaLeft = Math.round(left - startLeft),
deltaTop = Math.round(top - startTop);
var startTime = null;
var el = this;
var cancelOnEvents = 'scroll mousedown mousewheel touchmove keydown';
var cancelScrollAnimation = function($event) {
if (!$event || $event.which > 0) {
el.unbind(cancelOnEvents, cancelScrollAnimation);
cancelAnimation(scrollAnimation);
deferred.reject();
scrollAnimation = null;
}
};
if(scrollAnimation) {
cancelScrollAnimation();
}
deferred = $q.defer();
if(!deltaLeft && !deltaTop) {
deferred.resolve();
return deferred.promise;
}
var animationStep = function(timestamp) {
if (startTime === null) {
startTime = timestamp;
}
var progress = timestamp - startTime;
var percent = (progress >= duration ? 1 : easing(progress/duration));
el.scrollTo(
startLeft + Math.ceil(deltaLeft * percent),
startTop + Math.ceil(deltaTop * percent)
);
if(percent < 1) {
scrollAnimation = requestAnimation(animationStep);
} else {
el.unbind(cancelOnEvents, cancelScrollAnimation);
scrollAnimation = null;
deferred.resolve();
}
};
//Fix random mobile safari bug when scrolling to top by hitting status bar
el.scrollTo(startLeft, startTop);
el.bind(cancelOnEvents, cancelScrollAnimation);
scrollAnimation = requestAnimation(animationStep);
return deferred.promise;
};
proto.scrollToElement = function(target, offset, duration, easing) {
var el = unwrap(this);
if(!angular.isNumber(offset) || isNaN(offset)) {
offset = duScrollOffset;
}
var top = this.scrollTop() + unwrap(target).getBoundingClientRect().top - offset;
if(isElement(el)) {
top -= el.getBoundingClientRect().top;
}
return this.scrollTo(0, top, duration, easing);
};
var overloaders = {
scrollLeft: function(value, duration, easing) {
if(angular.isNumber(value)) {
return this.scrollTo(value, this.scrollTop(), duration, easing);
}
var el = unwrap(this);
if(isDocument(el)) {
return $window.scrollX || document.documentElement.scrollLeft || document.body.scrollLeft;
}
return el.scrollLeft;
},
scrollTop: function(value, duration, easing) {
if(angular.isNumber(value)) {
return this.scrollTo(this.scrollTop(), value, duration, easing);
}
var el = unwrap(this);
if(isDocument(el)) {
return $window.scrollY || document.documentElement.scrollTop || document.body.scrollTop;
}
return el.scrollTop;
}
};
proto.scrollToElementAnimated = function(target, offset, duration, easing) {
return this.scrollToElement(target, offset, duration || duScrollDuration, easing);
};
proto.scrollTopAnimated = function(top, duration, easing) {
return this.scrollTop(top, duration || duScrollDuration, easing);
};
proto.scrollLeftAnimated = function(left, duration, easing) {
return this.scrollLeft(left, duration || duScrollDuration, easing);
};
//Add duration and easing functionality to existing jQuery getter/setters
var overloadScrollPos = function(superFn, overloadFn) {
return function(value, duration, easing) {
if(duration) {
return overloadFn.apply(this, arguments);
}
return superFn.apply(this, arguments);
};
};
for(var methodName in overloaders) {
proto[methodName] = (proto[methodName] ? overloadScrollPos(proto[methodName], overloaders[methodName]) : overloaders[methodName]);
}
}]);
//Adapted from https://gist.github.com/paulirish/1579671
angular.module('duScroll.polyfill', [])
.factory('polyfill', ["$window", function($window) {
'use strict';
var vendors = ['webkit', 'moz', 'o', 'ms'];
return function(fnName, fallback) {
if($window[fnName]) {
return $window[fnName];
}
var suffix = fnName.substr(0, 1).toUpperCase() + fnName.substr(1);
for(var key, i = 0; i < vendors.length; i++) {
key = vendors[i]+suffix;
if($window[key]) {
return $window[key];
}
}
return fallback;
};
}]);
angular.module('duScroll.requestAnimation', ['duScroll.polyfill'])
.factory('requestAnimation', ["polyfill", "$timeout", function(polyfill, $timeout) {
'use strict';
var lastTime = 0;
var fallback = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = $timeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
return polyfill('requestAnimationFrame', fallback);
}])
.factory('cancelAnimation', ["polyfill", "$timeout", function(polyfill, $timeout) {
'use strict';
var fallback = function(promise) {
$timeout.cancel(promise);
};
return polyfill('cancelAnimationFrame', fallback);
}]);
angular.module('duScroll.spyAPI', ['duScroll.scrollContainerAPI'])
.factory('spyAPI', ["$rootScope", "$timeout", "scrollContainerAPI", "duScrollGreedy", "duScrollSpyWait", function($rootScope, $timeout, scrollContainerAPI, duScrollGreedy, duScrollSpyWait) {
'use strict';
var createScrollHandler = function(context) {
var timer = false, queued = false;
var handler = function() {
queued = false;
var container = context.container,
containerEl = container[0],
containerOffset = 0;
if (typeof HTMLElement !== 'undefined' && containerEl instanceof HTMLElement || containerEl.nodeType && containerEl.nodeType === containerEl.ELEMENT_NODE) {
containerOffset = containerEl.getBoundingClientRect().top;
}
var i, currentlyActive, toBeActive, spies, spy, pos;
spies = context.spies;
currentlyActive = context.currentlyActive;
toBeActive = undefined;
for(i = 0; i < spies.length; i++) {
spy = spies[i];
pos = spy.getTargetPosition();
if (!pos) continue;
if(pos.top + spy.offset - containerOffset < 20 && (pos.top*-1 + containerOffset) < pos.height) {
if(!toBeActive || toBeActive.top < pos.top) {
toBeActive = {
top: pos.top,
spy: spy
};
}
}
}
if(toBeActive) {
toBeActive = toBeActive.spy;
}
if(currentlyActive === toBeActive || (duScrollGreedy && !toBeActive)) return;
if(currentlyActive) {
currentlyActive.$element.removeClass('active');
$rootScope.$broadcast('duScrollspy:becameInactive', currentlyActive.$element);
}
if(toBeActive) {
toBeActive.$element.addClass('active');
$rootScope.$broadcast('duScrollspy:becameActive', toBeActive.$element);
}
context.currentlyActive = toBeActive;
};
if(!duScrollSpyWait) {
return handler;
}
//Debounce for potential performance savings
return function() {
if(!timer) {
handler();
timer = $timeout(function() {
timer = false;
if(queued) {
handler();
}
}, duScrollSpyWait, false);
} else {
queued = true;
}
};
};
var contexts = {};
var createContext = function($scope) {
var id = $scope.$id;
var context = {
spies: []
};
context.handler = createScrollHandler(context);
contexts[id] = context;
$scope.$on('$destroy', function() {
destroyContext($scope);
});
return id;
};
var destroyContext = function($scope) {
var id = $scope.$id;
var context = contexts[id], container = context.container;
if(container) {
container.off('scroll', context.handler);
}
delete contexts[id];
};
var defaultContextId = createContext($rootScope);
var getContextForScope = function(scope) {
if(contexts[scope.$id]) {
return contexts[scope.$id];
}
if(scope.$parent) {
return getContextForScope(scope.$parent);
}
return contexts[defaultContextId];
};
var getContextForSpy = function(spy) {
var context, contextId, scope = spy.$element.scope();
if(scope) {
return getContextForScope(scope);
}
//No scope, most likely destroyed
for(contextId in contexts) {
context = contexts[contextId];
if(context.spies.indexOf(spy) !== -1) {
return context;
}
}
};
var isElementInDocument = function(element) {
while (element.parentNode) {
element = element.parentNode;
if (element === document) {
return true;
}
}
return false;
};
var addSpy = function(spy) {
var context = getContextForSpy(spy);
if (!context) return;
context.spies.push(spy);
if (!context.container || !isElementInDocument(context.container)) {
if(context.container) {
context.container.off('scroll', context.handler);
}
context.container = scrollContainerAPI.getContainer(spy.$element.scope());
context.container.on('scroll', context.handler).triggerHandler('scroll');
}
};
var removeSpy = function(spy) {
var context = getContextForSpy(spy);
if(spy === context.currentlyActive) {
context.currentlyActive = null;
}
var i = context.spies.indexOf(spy);
if(i !== -1) {
context.spies.splice(i, 1);
}
};
return {
addSpy: addSpy,
removeSpy: removeSpy,
createContext: createContext,
destroyContext: destroyContext,
getContextForScope: getContextForScope
};
}]);
angular.module('duScroll.scrollContainerAPI', [])
.factory('scrollContainerAPI', ["$document", function($document) {
'use strict';
var containers = {};
var setContainer = function(scope, element) {
var id = scope.$id;
containers[id] = element;
return id;
};
var getContainerId = function(scope) {
if(containers[scope.$id]) {
return scope.$id;
}
if(scope.$parent) {
return getContainerId(scope.$parent);
}
return;
};
var getContainer = function(scope) {
var id = getContainerId(scope);
return id ? containers[id] : $document;
};
var removeContainer = function(scope) {
var id = getContainerId(scope);
if(id) {
delete containers[id];
}
};
return {
getContainerId: getContainerId,
getContainer: getContainer,
setContainer: setContainer,
removeContainer: removeContainer
};
}]);
angular.module('duScroll.smoothScroll', ['duScroll.scrollHelpers', 'duScroll.scrollContainerAPI'])
.directive('duSmoothScroll', ["duScrollDuration", "duScrollOffset", "scrollContainerAPI", function(duScrollDuration, duScrollOffset, scrollContainerAPI) {
'use strict';
return {
link : function($scope, $element, $attr) {
$element.on('click', function(e) {
if(!$attr.href || $attr.href.indexOf('#') === -1) return;
var target = document.getElementById($attr.href.replace(/.*(?=#[^\s]+$)/, '').substring(1));
if(!target || !target.getBoundingClientRect) return;
if (e.stopPropagation) e.stopPropagation();
if (e.preventDefault) e.preventDefault();
var offset = $attr.offset ? parseInt($attr.offset, 10) : duScrollOffset;
var duration = $attr.duration ? parseInt($attr.duration, 10) : duScrollDuration;
var container = scrollContainerAPI.getContainer($scope);
container.scrollToElement(
angular.element(target),
isNaN(offset) ? 0 : offset,
isNaN(duration) ? 0 : duration
);
});
}
};
}]);
angular.module('duScroll.spyContext', ['duScroll.spyAPI'])
.directive('duSpyContext', ["spyAPI", function(spyAPI) {
'use strict';
return {
restrict: 'A',
scope: true,
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink($scope, iElement, iAttrs, controller) {
spyAPI.createContext($scope);
}
};
}
};
}]);
angular.module('duScroll.scrollContainer', ['duScroll.scrollContainerAPI'])
.directive('duScrollContainer', ["scrollContainerAPI", function(scrollContainerAPI){
'use strict';
return {
restrict: 'A',
scope: true,
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink($scope, iElement, iAttrs, controller) {
iAttrs.$observe('duScrollContainer', function(element) {
if(angular.isString(element)) {
element = document.getElementById(element);
}
element = (angular.isElement(element) ? angular.element(element) : iElement);
scrollContainerAPI.setContainer($scope, element);
$scope.$on('$destroy', function() {
scrollContainerAPI.removeContainer($scope);
});
});
}
};
}
};
}]);
angular.module('duScroll.scrollspy', ['duScroll.spyAPI'])
.directive('duScrollspy', ["spyAPI", "duScrollOffset", "$timeout", "$rootScope", function(spyAPI, duScrollOffset, $timeout, $rootScope) {
'use strict';
var Spy = function(targetElementOrId, $element, offset) {
if(angular.isElement(targetElementOrId)) {
this.target = targetElementOrId;
} else if(angular.isString(targetElementOrId)) {
this.targetId = targetElementOrId;
}
this.$element = $element;
this.offset = offset;
};
Spy.prototype.getTargetElement = function() {
if (!this.target && this.targetId) {
this.target = document.getElementById(this.targetId);
}
return this.target;
};
Spy.prototype.getTargetPosition = function() {
var target = this.getTargetElement();
if(target) {
return target.getBoundingClientRect();
}
};
Spy.prototype.flushTargetCache = function() {
if(this.targetId) {
this.target = undefined;
}
};
return {
link: function ($scope, $element, $attr) {
var href = $attr.ngHref || $attr.href;
var targetId;
if (href && href.indexOf('#') !== -1) {
targetId = href.replace(/.*(?=#[^\s]+$)/, '').substring(1);
} else if($attr.duScrollspy) {
targetId = $attr.duScrollspy;
}
if(!targetId) return;
// Run this in the next execution loop so that the scroll context has a chance
// to initialize
$timeout(function() {
var spy = new Spy(targetId, $element, -($attr.offset ? parseInt($attr.offset, 10) : duScrollOffset));
spyAPI.addSpy(spy);
$scope.$on('$destroy', function() {
spyAPI.removeSpy(spy);
});
$scope.$on('$locationChangeSuccess', spy.flushTargetCache.bind(spy));
$rootScope.$on('$stateChangeSuccess', spy.flushTargetCache.bind(spy));
}, 0, false);
}
};
}]);

View File

@ -1,15 +0,0 @@
#= require angular
#= require angular-cookies
#= require angular-resource
#= require angular-sanitize
#= require angular-scroll
#= require_self
#= require_tree ./angularjs
@app = angular.module('app', ['ngCookies', 'ngSanitize', 'duScroll'])
@app.value('duScrollOffset', 30)
@app.config(["$httpProvider", (provider) ->
provider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
provider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
])

View File

@ -1,36 +0,0 @@
@app.controller 'AboutController', [ '$scope', '$timeout', '$http', ($scope, $timeout, $http)->
$scope.type = null
$scope.click = (v)->
if $scope.type == v
$scope.type = null
return
$scope.type = v
$scope.weixin_click = ->
$scope.weixin = !$scope.weixin
#
$scope.email = ''
$scope.subscribe_success = null
$scope.email_validate = ->
$scope.email.match(/@/)
$scope.subscribe = ()->
$http
url: '/subscribes'
method: 'POST'
params:
email: $scope.email
.success (res)->
if res.success
$scope.email = ''
$scope.subscribe_success = true
else
$scope.subscribe_success = false
$scope.subscribe_fail_msg = res.message
$timeout ->
$scope.subscribe_success = null
, 3000
]

View File

@ -1,14 +0,0 @@
@app.controller 'AboutScrollController', [ '$scope', '$document', ($scope, $document)->
about_id = angular.element(document.getElementById('about'))
$document.on 'scroll', ()=>
$scope.$apply()
$scope.is_top = ()->
$document.scrollTop() <= 0
$scope.to_about = ()->
$document.scrollToElementAnimated(about_id)
]

View File

@ -1,35 +0,0 @@
@app.controller 'AdminPostsController', [ '$scope', '$http', '$location', '$timeout', '$cookies', '$sce', ($scope, $http, $location, $timeout, $cookies, $sce)->
$scope.body_active = true
$scope.init = (id)->
url = '/admin/posts/' + id + '.json'
$http
url: url
method: 'GET'
.success (res)->
$scope.title = res.title
$scope.type = res.type
$scope.labels = res.labels
$scope.content = res.content
$scope.changeToBody = ->
$scope.body_active = true
$scope.changeToPreview = ->
$scope.body_active = false
$scope.previewHTML = 'Loading...'
$http.post '/admin/posts/preview', { content: $scope.content }
.success (res)->
$scope.previewHTML = res
$scope.trustAsPreviewHTML = ()->
$sce.trustAsHtml($scope.previewHTML)
$scope.addTag = (e)->
new_labels= $(e.target).text()
if $scope.labels
$scope.labels += ", #{new_labels}"
else
$scope.labels = new_labels
]

View File

@ -1,21 +0,0 @@
@app.controller 'AdminSessionsController', [ '$scope', '$http', '$timeout', '$cookies', ($scope, $http, $timeout, $cookies)->
url = '/admin/sessions'
$scope.login = ->
$http
url: url
method: 'POST'
data:
username: $scope.username
password: $scope.password
.success (res)->
if res.success
urlback = $cookies.urlback || '/admin'
window.location = urlback
else
$scope.password = ''
$scope.error_msg = res.message
$timeout ->
$scope.error_msg = null
, 5000
]

View File

@ -1,43 +0,0 @@
@app.controller 'ArchivesController',[ '$scope', '$http', '$location', '$timeout', '$cookies', ($scope, $http, $location, $timeout, $cookies)->
url = window.location.pathname + ".json"
start_with = $cookies.start_with if window.location.pathname == $cookies.start_with_type
$http
url: url
method: 'GET'
params:
start_with: start_with
all: true
.success (res)->
$scope.update_start_with(res.start_with)
$scope.posts = res.posts
$scope.no_more_flag = false
$scope.loading_flag = false
$scope.load = ()->
$scope.loading_flag = true
$http(
url: url
method: 'GET'
params:
start_with: $scope.start_with
type: $scope.type
).success (res)->
$scope.no_more_flag = true if res.posts.length == 0
$scope.update_start_with(res.start_with)
$scope.posts = $scope.posts.concat(res.posts)
$timeout ->
$scope.loading_flag = false
, 500
$timeout ->
$scope.no_more_flag = false
, 3000
$scope.visit = (id)->
"/blogs/" + id
$scope.update_start_with = (start_with)->
$scope.start_with = start_with
$cookies.start_with_type = window.location.pathname
$cookies.start_with = start_with
]

View File

@ -1,35 +0,0 @@
@app.controller 'CommentsController', ['$scope', '$http', '$location', '$timeout', '$cookies', ($scope, $http, $location, $timeout, $cookies)->
url = window.location.pathname + "/comments.json"
$scope.name = $cookies.name
$scope.email = $cookies.email
$http.get(url).success (data)->
$scope.comments = data
$scope.publish_success = null
$scope.submit = ->
$scope.submitting = true
$cookies.name = $scope.name
$cookies.email = $scope.email
comment = { content: $scope.content, name: $scope.name, email: $scope.email }
$http.post(url, comment)
.success (res)->
if res.success
$scope.publish_success = true
$scope.content = ''
$scope.comments.unshift(res.data)
else
$scope.publish_success = false
$scope.publish_fail_msg = res.message
.error (data, status)->
$scope.publish_success = false
$scope.publish_fail_msg = 'Network Error, Retry for a moment, Status Code: ' + status
.finally ->
$scope.submitting = false
$scope.timeout = $timeout ->
$timeout.cancel($scope.timeout)
$scope.publish_success = null
, 5*1000
]

View File

@ -1,9 +0,0 @@
@app.directive 'ngInitial', ->
restrict: 'A',
controller: [
'$scope', '$element', '$attrs', '$parse', ($scope, $element, $attrs, $parse)->
val = $attrs.ngInitial || $attrs.value || $attrs.$$element.text()
getter = $parse($attrs.ngModel)
setter = getter.assign
setter($scope, val)
]

View File

@ -1,38 +0,0 @@
@app.controller 'LikesController', ['$scope', '$http', '$location', '$cookies', ($scope, $http, $location, $cookies)->
url = window.location.pathname + "/likes"
$http.get url
.success (res)->
$scope.count = res.count
$scope.like = $cookies.like
if $scope.like
$http
url: window.location.pathname + "/likes/#{$scope.like}/is_liked"
method: 'GET'
.success (res)->
if res == true
$scope.is_liked = true
else
$scope.is_liked = false
else
$scope.is_liked = false
$scope.submit = ->
$http.post url
.success (res)->
if res.success
$scope.like = $cookies.like = res.id
$scope.count = res.count
$scope.is_liked = true
$scope.cancel = ->
$http.delete url + "/" + $scope.like
.success (res)->
$scope.count = res.count
$scope.is_liked = false
# anyway, clear cookie
delete $cookies["like"]
$scope.like = null
]

View File

@ -1,4 +0,0 @@
@app.controller 'QRCodesController', [ '$scope', ($scope)->
$scope.show = ->
$scope.qrcode = ! $scope.qrcode
]

View File

@ -1,10 +0,0 @@
@app.controller 'SubscribesController', [ '$scope', '$http', ($scope, $http)->
$scope.cancel = ()->
$http
url: '/subscribes/cancel'
method: 'POST'
params:
email: $scope.email
.success (res)->
window.location = '/'
]

View File

@ -1,13 +1,16 @@
//= require jquery
//= require jquery_ujs
//= require foundation/foundation
//= require foundation/foundation.alert
//= require foundation/foundation.topbar
//= require foundation/foundation.offcanvas
//= require foundation/foundation.magellan
//= require angularjs
//= require turbolinks
//= require action_cable
//= require foundation
//= require js.cookie
//= require 'jquery.html5-fileupload'
//= require jquery.atwho
//= require ddscrollspy
//= require qrcode
//= require cable
//= require_tree .
$(function(){ $(document).foundation(); });
$(document).on('turbolinks:load', function(){
$(document).foundation();
});

View File

@ -0,0 +1 @@
@App ||= {}

View File

@ -0,0 +1,12 @@
$(document).on 'turbolinks:load', ->
$('#alert-container .close-button').click ()->
$('#alert-container').hide()
if $('#blog-show-page').length > 0
window.App.cable ||= ActionCable.createConsumer()
if window.App.comment_channel
window.App.comment_channel.unsubscribe()
window.App.comment_channel = window.App.cable.subscriptions.create { channel: "CommentChannel", post_id: $('#blog-show-page').data('post_id') },
received: (data)->
if data['not'] != Cookies.get('cable_id')
$.get( $('#blog-show-page').data('url') )

View File

@ -0,0 +1,42 @@
class @GoogleAnalytics
@load: ->
# Google Analytics depends on a global _gaq array. window is the global scope.
window._gaq = []
window._gaq.push ["_setAccount", GoogleAnalytics.analyticsId()]
# Create a script element and insert it in the DOM
ga = document.createElement("script")
ga.type = "text/javascript"
ga.async = true
ga.src = ((if "https:" is document.location.protocol then "https://ssl" else "http://www")) + ".google-analytics.com/ga.js"
firstScript = document.getElementsByTagName("script")[0]
firstScript.parentNode.insertBefore ga, firstScript
if typeof Turbolinks isnt 'undefined' and Turbolinks.supported
document.addEventListener "turbolinks:load", (->
GoogleAnalytics.trackPageview()
), true
else
GoogleAnalytics.trackPageview()
@trackPageview: (url) ->
unless GoogleAnalytics.isLocalRequest()
if url
window._gaq.push ["_trackPageview", url]
else
window._gaq.push ["_trackPageview"]
window._gaq.push ["_trackPageLoadTime"]
@isLocalRequest: ->
GoogleAnalytics.documentDomainIncludes "local"
@documentDomainIncludes: (str) ->
document.domain.indexOf(str) isnt -1
@analyticsId: ->
'<%= ENV['GOOGLE'] %>'
<% if ENV['GOOGLE'] %>
GoogleAnalytics.load()
<% end %>

View File

@ -0,0 +1,19 @@
$(document).on 'turbolinks:load', ->
$('.like-button').click ->
if $(this).hasClass('liked')
$.ajax
url: $(this).data('url') + '/' + Cookies.get('like')
type: 'DELETE'
success: (res)=>
$(this).removeClass('liked')
$(this).children('.count').text(res.count)
Cookies.remove('like')
else
$.ajax
url: $(this).data('url')
type: 'POST'
success: (res)=>
$(this).addClass('liked')
$(this).children('.count').text(res.count)
Cookies.set('like', res.id)

View File

@ -0,0 +1,13 @@
$(document).on 'turbolinks:load', ->
if $('#qrcode-home').length > 0
$('#qrcode-home').empty()
new QRCode( $('#qrcode-home')[0], $('#qrcode-home').data('url') )
if $('#image-tag').length > 0
$('#image-tag').empty()
new QRCode( $('#image-tag')[0], $('#image-tag').data('url') )
$('#qrcode-link').click (event)->
event.preventDefault()
$('.social-share').toggle()

View File

@ -0,0 +1,567 @@
// Foundation for Sites Settings
// -----------------------------
//
// Table of Contents:
//
// 1. Global
// 2. Breakpoints
// 3. The Grid
// 4. Base Typography
// 5. Typography Helpers
// 6. Abide
// 7. Accordion
// 8. Accordion Menu
// 9. Badge
// 10. Breadcrumbs
// 11. Button
// 12. Button Group
// 13. Callout
// 14. Close Button
// 15. Drilldown
// 16. Dropdown
// 17. Dropdown Menu
// 18. Flex Video
// 19. Forms
// 20. Label
// 21. Media Object
// 22. Menu
// 23. Meter
// 24. Off-canvas
// 25. Orbit
// 26. Pagination
// 27. Progress Bar
// 28. Reveal
// 29. Slider
// 30. Switch
// 31. Table
// 32. Tabs
// 33. Thumbnail
// 34. Title Bar
// 35. Tooltip
// 36. Top Bar
@import 'util/util';
// 1. Global
// ---------
$global-font-size: 100%;
$global-width: rem-calc(1200);
$global-lineheight: 1.5;
$foundation-palette: (
primary: #2199e8,
secondary: #777,
success: #3adb76,
warning: #ffae00,
alert: #ec5840,
);
$light-gray: #e6e6e6;
$medium-gray: #cacaca;
$dark-gray: #8a8a8a;
$black: #333333;
$white: #fefefe;
$body-background: $white;
$body-font-color: $black;
$body-font-family: 'Helvetica Neue', 'Microsoft Yahei', Helvetica, Roboto, 'WenQuanYi Micro Hei', Arial, sans-serif;
$body-antialiased: true;
$global-margin: 1rem;
$global-padding: 1rem;
$global-weight-normal: normal;
$global-weight-bold: bold;
$global-radius: 0;
$global-text-direction: ltr;
$global-flexbox: false;
$print-transparent-backgrounds: true;
@include add-foundation-colors;
// 2. Breakpoints
// --------------
$breakpoints: (
small: 0,
medium: 640px,
large: 1024px,
xlarge: 1200px,
xxlarge: 1440px,
);
$breakpoint-classes: (small medium large);
// 3. The Grid
// -----------
$grid-row-width: $global-width;
$grid-column-count: 12;
$grid-column-gutter: (
small: 20px,
medium: 30px,
);
$grid-column-align-edge: true;
$block-grid-max: 8;
// 4. Base Typography
// ------------------
$header-font-family: $body-font-family;
$header-font-weight: $global-weight-normal;
$header-font-style: normal;
$font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace;
$header-sizes: (
small: (
'h1': 24,
'h2': 20,
'h3': 19,
'h4': 18,
'h5': 17,
'h6': 16,
),
medium: (
'h1': 48,
'h2': 40,
'h3': 31,
'h4': 25,
'h5': 20,
'h6': 16,
),
);
$header-color: inherit;
$header-lineheight: 1.4;
$header-margin-bottom: 0.5rem;
$header-text-rendering: optimizeLegibility;
$small-font-size: 80%;
$header-small-font-color: $medium-gray;
$paragraph-lineheight: 1.6;
$paragraph-margin-bottom: 1rem;
$paragraph-text-rendering: optimizeLegibility;
$code-color: $black;
$code-font-family: $font-family-monospace;
$code-font-weight: $global-weight-normal;
$code-background: $light-gray;
$code-border: 1px solid $medium-gray;
$code-padding: rem-calc(2 5 1);
$anchor-color: $primary-color;
$anchor-color-hover: scale-color($anchor-color, $lightness: -14%);
$anchor-text-decoration: none;
$anchor-text-decoration-hover: none;
$hr-width: $global-width;
$hr-border: 1px solid $medium-gray;
$hr-margin: rem-calc(20) auto;
$list-lineheight: $paragraph-lineheight;
$list-margin-bottom: $paragraph-margin-bottom;
$list-style-type: disc;
$list-style-position: outside;
$list-side-margin: 1.25rem;
$list-nested-side-margin: 1.25rem;
$defnlist-margin-bottom: 1rem;
$defnlist-term-weight: $global-weight-bold;
$defnlist-term-margin-bottom: 0.3rem;
$blockquote-color: $dark-gray;
$blockquote-padding: rem-calc(9 20 0 19);
$blockquote-border: 1px solid $medium-gray;
$cite-font-size: rem-calc(13);
$cite-color: $dark-gray;
$keystroke-font: $font-family-monospace;
$keystroke-color: $black;
$keystroke-background: $light-gray;
$keystroke-padding: rem-calc(2 4 0);
$keystroke-radius: $global-radius;
$abbr-underline: 1px dotted $black;
// 5. Typography Helpers
// ---------------------
$lead-font-size: $global-font-size * 1.25;
$lead-lineheight: 1.6;
$subheader-lineheight: 1.4;
$subheader-color: $dark-gray;
$subheader-font-weight: $global-weight-normal;
$subheader-margin-top: 0.2rem;
$subheader-margin-bottom: 0.5rem;
$stat-font-size: 2.5rem;
// 6. Abide
// --------
$abide-inputs: true;
$abide-labels: true;
$input-background-invalid: map-get($foundation-palette, alert);
$form-label-color-invalid: map-get($foundation-palette, alert);
$input-error-color: map-get($foundation-palette, alert);
$input-error-font-size: rem-calc(12);
$input-error-font-weight: $global-weight-bold;
// 7. Accordion
// ------------
$accordion-background: $white;
$accordion-plusminus: true;
$accordion-item-color: foreground($accordion-background, $primary-color);
$accordion-item-background-hover: $light-gray;
$accordion-item-padding: 1.25rem 1rem;
$accordion-content-background: $white;
$accordion-content-border: 1px solid $light-gray;
$accordion-content-color: foreground($accordion-background, $primary-color);
$accordion-content-padding: 1rem;
// 8. Accordion Menu
// -----------------
$accordionmenu-arrows: true;
$accordionmenu-arrow-color: $primary-color;
// 9. Badge
// --------
$badge-background: $primary-color;
$badge-color: foreground($badge-background);
$badge-padding: 0.3em;
$badge-minwidth: 2.1em;
$badge-font-size: 0.6rem;
// 10. Breadcrumbs
// ---------------
$breadcrumbs-margin: 0 0 $global-margin 0;
$breadcrumbs-item-font-size: rem-calc(11);
$breadcrumbs-item-color: $primary-color;
$breadcrumbs-item-color-current: $black;
$breadcrumbs-item-color-disabled: $medium-gray;
$breadcrumbs-item-margin: 0.75rem;
$breadcrumbs-item-uppercase: true;
$breadcrumbs-item-slash: true;
// 11. Button
// ----------
$button-padding: 1rem 2rem 1.0625rem 2rem;
$button-margin: 0 0 $global-margin 0;
$button-fill: solid;
$button-background: $primary-color;
$button-background-hover: scale-color($button-background, $lightness: -15%);
$button-color: $white;
$button-color-alt: $black;
$button-radius: $global-radius;
$button-sizes: (
tiny: 0.6rem,
small: 0.75rem,
default: 0.9rem,
large: 1.25rem,
);
$button-opacity-disabled: 0.25;
// 12. Button Group
// ----------------
$buttongroup-margin: 1rem;
$buttongroup-spacing: 1px;
$buttongroup-child-selector: '.button';
$buttongroup-expand-max: 6;
// 13. Callout
// -----------
$callout-background: $white;
$callout-background-fade: 85%;
$callout-border: 1px solid rgba($black, 0.25);
$callout-margin: 0 0 1rem 0;
$callout-padding: 1rem;
$callout-font-color: $body-font-color;
$callout-font-color-alt: $body-background;
$callout-radius: $global-radius;
$callout-link-tint: 30%;
// 14. Close Button
// ----------------
$closebutton-position: right top;
$closebutton-offset-horizontal: 1rem;
$closebutton-offset-vertical: 0.5rem;
$closebutton-size: 2em;
$closebutton-lineheight: 1;
$closebutton-color: $dark-gray;
$closebutton-color-hover: $black;
// 15. Drilldown
// -------------
$drilldown-transition: transform 0.15s linear;
$drilldown-arrows: true;
$drilldown-arrow-color: $primary-color;
$drilldown-background: $white;
// 16. Dropdown
// ------------
$dropdown-padding: 1rem;
$dropdown-border: 1px solid $medium-gray;
$dropdown-font-size: 1rem;
$dropdown-width: 300px;
$dropdown-radius: $global-radius;
$dropdown-sizes: (
tiny: 100px,
small: 200px,
large: 400px,
);
// 17. Dropdown Menu
// -----------------
$dropdownmenu-arrows: true;
$dropdownmenu-arrow-color: $anchor-color;
$dropdownmenu-min-width: 200px;
$dropdownmenu-background: $white;
$dropdownmenu-border: 1px solid $medium-gray;
// 18. Flex Video
// --------------
$flexvideo-margin-bottom: rem-calc(16);
$flexvideo-ratio: 4 by 3;
$flexvideo-ratio-widescreen: 16 by 9;
// 19. Forms
// ---------
$fieldset-border: 1px solid $medium-gray;
$fieldset-padding: rem-calc(20);
$fieldset-margin: rem-calc(18 0);
$legend-padding: rem-calc(0 3);
$form-spacing: rem-calc(16);
$helptext-color: $black;
$helptext-font-size: rem-calc(13);
$helptext-font-style: italic;
$input-prefix-color: $black;
$input-prefix-background: $light-gray;
$input-prefix-border: 1px solid $medium-gray;
$input-prefix-padding: 1rem;
$form-label-color: $black;
$form-label-font-size: rem-calc(14);
$form-label-font-weight: $global-weight-normal;
$form-label-line-height: 1.8;
$select-background: $white;
$select-triangle-color: $dark-gray;
$select-radius: $global-radius;
$input-color: $black;
$input-placeholder-color: $medium-gray;
$input-font-family: inherit;
$input-font-size: rem-calc(16);
$input-background: $white;
$input-background-focus: $white;
$input-background-disabled: $light-gray;
$input-border: 1px solid $medium-gray;
$input-border-focus: 1px solid $dark-gray;
$input-shadow: inset 0 1px 2px rgba($black, 0.1);
$input-shadow-focus: 0 0 5px $medium-gray;
$input-cursor-disabled: not-allowed;
$input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
$input-number-spinners: true;
$input-radius: $global-radius;
// 20. Label
// ---------
$label-background: $primary-color;
$label-color: foreground($label-background);
$label-font-size: 0.8rem;
$label-padding: 0.33333rem 0.5rem;
$label-radius: $global-radius;
// 21. Media Object
// ----------------
$mediaobject-margin-bottom: $global-margin;
$mediaobject-section-padding: $global-padding;
$mediaobject-image-width-stacked: 100%;
// 22. Menu
// --------
$menu-margin: 0;
$menu-margin-nested: 1rem;
$menu-item-padding: 0.7rem 1rem;
$menu-item-color-active: $white;
$menu-item-background-active: map-get($foundation-palette, primary);
$menu-icon-spacing: 0.25rem;
// 23. Meter
// ---------
$meter-height: 1rem;
$meter-radius: $global-radius;
$meter-background: $medium-gray;
$meter-fill-good: $success-color;
$meter-fill-medium: $warning-color;
$meter-fill-bad: $alert-color;
// 24. Off-canvas
// --------------
$offcanvas-size: 250px;
$offcanvas-background: #474747;
$offcanvas-zindex: -1;
$offcanvas-transition-length: 0.5s;
$offcanvas-transition-timing: ease;
$offcanvas-fixed-reveal: true;
$offcanvas-exit-background: rgba($white, 0.25);
$maincontent-class: 'off-canvas-content';
$maincontent-shadow: 0 0 10px rgba($black, 0.5);
// 25. Orbit
// ---------
$orbit-bullet-background: $medium-gray;
$orbit-bullet-background-active: $dark-gray;
$orbit-bullet-diameter: 1.2rem;
$orbit-bullet-margin: 0.1rem;
$orbit-bullet-margin-top: 0.8rem;
$orbit-bullet-margin-bottom: 0.8rem;
$orbit-caption-background: rgba($black, 0.5);
$orbit-caption-padding: 1rem;
$orbit-control-background-hover: rgba($black, 0.5);
$orbit-control-padding: 1rem;
$orbit-control-zindex: 10;
// 26. Pagination
// --------------
$pagination-font-size: rem-calc(14);
$pagination-margin-bottom: $global-margin;
$pagination-item-color: $black;
$pagination-item-padding: rem-calc(3 10);
$pagination-item-spacing: rem-calc(1);
$pagination-radius: $global-radius;
$pagination-item-background-hover: $light-gray;
$pagination-item-background-current: $primary-color;
$pagination-item-color-current: foreground($pagination-item-background-current);
$pagination-item-color-disabled: $medium-gray;
$pagination-ellipsis-color: $black;
$pagination-mobile-items: false;
$pagination-arrows: true;
// 27. Progress Bar
// ----------------
$progress-height: 1rem;
$progress-background: $medium-gray;
$progress-margin-bottom: $global-margin;
$progress-meter-background: $primary-color;
$progress-radius: $global-radius;
// 28. Reveal
// ----------
$reveal-background: $white;
$reveal-width: 600px;
$reveal-max-width: $global-width;
$reveal-padding: $global-padding;
$reveal-border: 1px solid $medium-gray;
$reveal-radius: $global-radius;
$reveal-zindex: 1005;
$reveal-overlay-background: rgba($black, 0.45);
// 29. Slider
// ----------
$slider-width-vertical: 0.5rem;
$slider-transition: all 0.2s ease-in-out;
$slider-height: 0.5rem;
$slider-background: $light-gray;
$slider-fill-background: $medium-gray;
$slider-handle-height: 1.4rem;
$slider-handle-width: 1.4rem;
$slider-handle-background: $primary-color;
$slider-opacity-disabled: 0.25;
$slider-radius: $global-radius;
// 30. Switch
// ----------
$switch-background: $medium-gray;
$switch-background-active: $primary-color;
$switch-height: 2rem;
$switch-height-tiny: 1.5rem;
$switch-height-small: 1.75rem;
$switch-height-large: 2.5rem;
$switch-radius: $global-radius;
$switch-margin: $global-margin;
$switch-paddle-background: $white;
$switch-paddle-offset: 0.25rem;
$switch-paddle-radius: $global-radius;
$switch-paddle-transition: all 0.25s ease-out;
// 31. Table
// ---------
$table-background: $white;
$table-color-scale: 5%;
$table-border: 1px solid smart-scale($table-background, $table-color-scale);
$table-padding: rem-calc(8 10 10);
$table-hover-scale: 2%;
$table-row-hover: darken($table-background, $table-hover-scale);
$table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale);
$table-striped-background: smart-scale($table-background, $table-color-scale);
$table-stripe: even;
$table-head-background: smart-scale($table-background, $table-color-scale / 2);
$table-foot-background: smart-scale($table-background, $table-color-scale);
$table-head-font-color: $body-font-color;
$show-header-for-stacked: false;
// 32. Tabs
// --------
$tab-margin: 0;
$tab-background: $white;
$tab-background-active: $light-gray;
$tab-item-font-size: rem-calc(16);
$tab-item-background-hover: $white;
$tab-item-padding: 1.25rem 1.5rem;
$tab-expand-max: 6;
$tab-content-background: $white;
$tab-content-border: none;
$tab-content-color: foreground($tab-background, $primary-color);
$tab-content-padding: 1rem;
// 33. Thumbnail
// -------------
$thumbnail-border: solid 4px $white;
$thumbnail-margin-bottom: $global-margin;
$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);
$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);
$thumbnail-transition: box-shadow 200ms ease-out;
$thumbnail-radius: $global-radius;
// 34. Title Bar
// -------------
$titlebar-background: $black;
$titlebar-color: $white;
$titlebar-padding: 0.5rem;
$titlebar-text-font-weight: bold;
$titlebar-icon-color: $white;
$titlebar-icon-color-hover: $medium-gray;
$titlebar-icon-spacing: 0.25rem;
// 35. Tooltip
// -----------
$has-tip-font-weight: $global-weight-bold;
$has-tip-border-bottom: dotted 1px $dark-gray;
$tooltip-background-color: $black;
$tooltip-color: $white;
$tooltip-padding: 0.75rem;
$tooltip-font-size: $small-font-size;
$tooltip-pip-width: 0.75rem;
$tooltip-pip-height: $tooltip-pip-width * 0.866;
$tooltip-radius: $global-radius;
// 36. Top Bar
// -----------
$topbar-padding: 0.5rem;
$topbar-background: $black;
$topbar-submenu-background: $topbar-background;
$topbar-title-spacing: 1rem;
$topbar-input-width: 200px;
$topbar-unstack-breakpoint: medium;

View File

@ -1,8 +1,13 @@
.self-introduce {
margin-top: 2.2rem;
@media screen and (min-width: 64em) {
margin-top: 1.875rem;
}
h1, h2, h3, h4, h5, h6 {
border-bottom: 1px solid #dddddd;
}
p, .aboutme-index {
color: #5D5D5D;
}
}
@ -23,46 +28,69 @@
.about-page {
.fixed {
position: fixed;
top: 0;
width: 100%;
z-index: 99;
left: 0;
}
.responsive-button {
padding: 0.7rem 1rem;
}
a {
word-wrap: break-word;
}
@media only screen and (min-width: 40.063em) {
//@media only screen and (min-width: 40.063em) {
@media screen and (max-width: 39.9375em) {
.top-bar-wrapper {
background: 0 0;
transition: background .5s ease-in-out,padding .5s ease-in-out;
&.active {
background: #000;
border-bottom: 1px solid #666;
.top-bar {
margin: 0.5rem 0;
}
}
.top-bar {
background: 0 0;
margin: 1.5rem 0;
.name h1 {
font-size: 1.325rem;
}
}
.top-bar-section ul{
li, li a {
background: 0 0;
font-size: 1rem;
}
li a:hover {
background: #666;
}
li a.active {
background: #4C4C4C;
}
.top-bar, .top-bar-left, .top-bar-right {
width: auto !important;
}
}
}
.top-bar-wrapper {
background: 0 0;
transition: background .5s ease-in-out,padding .5s ease-in-out;
&.active {
background: #000;
border-bottom: 1px solid #666;
.top-bar {
margin: 0.5rem 0;
}
}
.top-bar {
background: 0 0;
margin: 1.5rem 0;
.name {
font-size: 1.325rem;
}
}
.top-bar ul{
& {
background: 0 0;
}
li, li a {
background: 0 0;
font-size: 1rem;
color: #fefefe;
}
li a:hover {
background: #666;
}
li a.active {
background: #4C4C4C;
}
}
}
//}
p {
font-size: 1.275rem;
@ -82,7 +110,7 @@
text-align: center;
.heading, .sub-heading {
color: #fff;
color: #eee;
}
.version {
@ -100,7 +128,7 @@
}
.circle {
color: #fff;
color: #eee;
width: 4rem;
height: 4rem;
font-size: 3rem;
@ -116,6 +144,24 @@
}
}
#about {
.wrapper {
.time {
color: #aaa;
margin-bottom: 0.5rem;
margin-top: 1.5rem;
}
p {
font-size: 1rem;
line-height: 2;
}
ul > li {
margin: 0.5rem 0;
}
}
}
#about, #skill {
background-color: #000;
color: #eee;
@ -162,6 +208,7 @@
background-color: rgba(63, 63, 63, 0.6);
list-style: none;
padding: 2rem 1rem;
margin-left: 0;
>li {
margin: 1rem 0;
}
@ -271,7 +318,7 @@
padding: 1.5rem;
background-color: #353535;
text-align: center;
color: #fff;
color: #eee;
font-size: 1.125rem;
}
}

View File

@ -1,3 +1,10 @@
.dash-title {
padding-top: 2rem;
}
#dashboard-topbar {
background-color: $light-gray;
ul {
background-color: $light-gray;
}
}

View File

@ -7,10 +7,14 @@
margin-bottom: 2rem;
}
#post_content {
#content-input {
min-height: 20rem;
}
.content-field {
padding: 0;
}
.preview {
min-height: 20rem;
border: 1px solid #DDDDDD;
@ -25,7 +29,7 @@
#upload_photo {
float: right;
margin-top: 2rem;
margin-top: 1.875rem;
}
tr {

View File

@ -0,0 +1,3 @@
.admin-subscribes-field {
}

View File

@ -1,3 +0,0 @@
.ng-dirty .ng-invalid, .ng-dirty .ng-required {
border: 1px solid #FF7A7A !important;
}

View File

@ -1,18 +1,25 @@
//.inner-wrap {
//min-height: 100%;
//}
@import 'font-awesome-sprockets';
@import 'font-awesome';
@import 'foundation_and_overrides';
@import 'foundation-icons';
@import 'aboutme_welcome';
@import 'archives';
@import 'aboutme_welcome';
@import 'archives';
@import 'markdown';
@import 'blogs';
@import 'head';
@import 'blogs';
@import 'head';
@import 'qrcodes';
@import 'angularjs';
@import 'comments';
@import 'comments';
@import 'highlight';
@import 'footer';
@import 'like_and_weixin';
@import 'admin/*';
.turbolinks-progress-bar {
height: 2px;
background-color: red;
}
img {
display: block;
}

View File

@ -11,12 +11,22 @@
margin-right: 1rem;
}
.search-result-wrapper p {
font-size: 0.785rem;
color: #999;
}
.blog-title {
color: #111111;
&:hover {
color: red;
}
border: none;
em {
color: red;
font-style: normal;
}
}
li {

View File

@ -1,6 +1,5 @@
.blog-title {
border-bottom: 1px solid #dddddd;
padding: 0.5rem 0;
margin-top: 1rem;
}
.ptag {
@ -19,7 +18,7 @@
}
.content {
padding-top: 0.5rem;
padding-top: 1rem;
}
.read-more {
@ -35,7 +34,7 @@
}
.published-at {
@media only screen and (min-width: 40.063em) {
@media screen and (min-width: 40.063em) {
text-align: right;
}
margin-top: 1rem;
@ -45,21 +44,23 @@
.blog-over {
margin-bottom: 1rem;
margin-top: 2rem;
}
.recent-title {
border-bottom: 1px solid #dddddd;
margin-bottom: 1rem;
border-bottom: 1px solid #DCDCDC;
}
.recent-content {
padding-bottom: 3rem;
@media screen and (min-width: 64em) {
padding-bottom: 3rem;
}
li {
list-style: disc;
}
padding-left: 0rem;
}
#qrcode-home {
padding: 1rem 2rem 1rem 0;
}
.qrcode {
display: inline-block;
float: right;
@ -81,24 +82,11 @@
}
}
.subscribe-ul {
list-style-type: none;
margin-left: 0;
li {
margin-left: 0;
margin-bottom: 0.5rem;
}
.rss-subscribe {
margin-top: 0.5rem;
}
.subscribe-success {
margin-left: 1rem;
color: green;
}
.subscribe-fail {
margin-left: 1rem;
color: red;
}
.social-share {
display: none;
}
.wechat_qrcode {
width: 200px;
margin-bottom: 20px;
}

View File

@ -1,3 +1,7 @@
#alert-container {
display: none;
}
.comment-field {
background-color: #333333;
padding-top: 3rem;
@ -43,14 +47,17 @@
color: #b3b3b3;
}
.comment-content {
padding-bottom: 2.275rem;
padding-bottom: 1rem;
padding-left: 0.275rem;
border-bottom: 1px dashed #8a8a8a;
margin-bottom: 0;
white-space: pre;
p {
margin-bottom: 0.325rem;
}
}
.name {
padding-top: 0.5rem;
padding-top: 1rem;
padding-left: 0.275rem;
}
}
@ -75,11 +82,11 @@
}
.comment-wrapper {
margin-bottom: 0.5rem;
&:hover {
background-color: #444444;
}
.name {
color: #DDDDDD;
}
@ -87,8 +94,7 @@
.comment-content {
word-wrap: break-word;
color: #DDDDDD;
white-space: pre-wrap;
word-break: break-all;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,39 @@
.middle-text {
text-align: center;
z-index: 999;
.my-topbar {
a {
color: #F7F7F7;
&:hover {
color: #999;
}
&.title {
font-weight: bold;
}
}
button {
outline: 0;
}
.title-bar-title {
margin-left: 0.5rem;
}
}
#offCanvas {
min-height: 100%;
a {
color: #F7F7F7;
&:hover {
background-color: #333;
}
}
label {
padding: 3px 20px;
background-color: #545454;
color: #B9B9B9;
text-transform: uppercase;
}
}
@ -16,3 +47,4 @@
cursor: default;
}
}

View File

@ -1,25 +1,21 @@
.markdown {
//color: rgba(0,0,0,1);
word-wrap: break-word;
overflow: hidden;
@media only screen and (min-width: 40.063em) {
@media screen and (min-width: 40.063em) {
h1, h2, h3, h4, h5, h6 {
font-size: 1.5rem;
font-size: 1.875rem;
}
}
@media only screen and (max-width: 40em) {
@media screen and (max-width: 40em) {
h1, h2, h3, h4, h5, h6 {
font-size: 1.2875rem;
font-size: 1.2rem;
}
}
h1, h2, h3, h4, h5, h6 {
border-bottom: 1px solid #DDDDDD;
margin-bottom: 1rem;
}
pre {
@ -42,8 +38,6 @@
}
code {
//margin-left: 0.25rem;
//margin-right: 0.025rem;
font-weight: 500;
border: none;
background-color: transparent;

View File

@ -1,20 +1,5 @@
.qrcode-image {
table {
border-width: 0;
border-style: none;
border-color: #0000ff;
border-collapse: collapse;
}
td {
border-width: 0;
border-style: none;
border-color: #0000ff;
border-collapse: collapse;
padding: 0;
margin: 0;
width: 0.3rem;
height: 0.3rem;
}
td.black { background-color: #000; }
td.white { background-color: #fff; }
#image-tag {
float: right;
width: 200px;
margin-bottom: 1rem;
}

View File

@ -0,0 +1,5 @@
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end

View File

@ -0,0 +1,5 @@
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
end

View File

@ -0,0 +1,6 @@
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
class CommentChannel < ApplicationCable::Channel
def subscribed
stream_from "comment_post_#{params[:post_id]}"
end
end

View File

@ -10,16 +10,6 @@ class Admin::PostsController < ApplicationController
@post = Post.find( params[:id] )
end
def show
post = Post.find( params[:id] )
render :json=> {
title: post.title,
type: post.type,
labels: post.labels_content(true),
content: post.content
}
end
def destroy
@post = Post.find( params[:id] )
if @post.destroy
@ -32,7 +22,7 @@ class Admin::PostsController < ApplicationController
end
def index
@posts = Post.desc(:created_at)
@posts = Post.order(created_at: :desc).page(params[:page]).per(25)
end
def create
@ -71,7 +61,7 @@ class Admin::PostsController < ApplicationController
private
def initialize_or_create_labels(labels)
@post.labels = []
labels.split(",").each do |name|
labels.split(",").map { |i| i.strip }.uniq.each do |name|
label = Label.find_or_initialize_by(name: name.strip)
label.save!
@post.labels << label

View File

@ -6,15 +6,18 @@ class Admin::SessionsController < ApplicationController
def create
if ENV['ADMIN_USER'].blank?
render :json=> { success: false, message: t('admin.session.no_configuration') }
flash.now[:alert] = t('admin.session.no_configuration')
render :new
elsif ENV['ADMIN_USER'] != params[:username]
render :json=> { success: false, message: t('admin.session.username_error') }
flash.now[:alert] = t('admin.session.username_error')
render :new
elsif ENV['ADMIN_PASSWORD'] != params[:password]
render :json=> { success: false, message: t('admin.session.password_error') }
flash.now[:alert] = t('admin.session.password_error')
render :new
else
flash[:notice] = t('admin.session.login_success')
session[:login] = true
render :json=> { success: true }
redirect_to admin_root_path
end
end

View File

@ -0,0 +1,26 @@
class Admin::SubscribesController < ApplicationController
layout 'layouts/admin'
before_action :authericate_user!
def index
@subscribes = Subscribe.all.order(created_at: :desc).page(params[:page]).per(25)
end
def enable
@subscribe = Subscribe.find(params[:id])
@subscribe.enable = true
@subscribe.save!
redirect_to admin_subscribes_path, notice: "#{@subscribe.email} is enabled!"
end
def disable
@subscribe = Subscribe.find(params[:id])
@subscribe.enable = false
@subscribe.save!
redirect_to admin_subscribes_path, notice: "#{@subscribe.email} is disabled!"
end
end

View File

@ -1,60 +1,10 @@
class ArchivesController < ApplicationController
def index
type = map[params[:type]]
limit = 10
start_with = params[:start_with]
@posts = Post.desc(:created_at)
if type
@posts = @posts.where(type: type)
@type = type
end
# all 与 start_with 参数同在, 说明是要获取所有start_with之前的数据
if params[:all] and params[:start_with]
@posts = @posts.where(:created_at.gte => Time.at(start_with.to_i))
if (@q = params[:q]).blank?
@posts = Post.order(created_at: :desc).page(params[:page])
else
@posts = @posts.limit(limit)
@q_size = Post.where('title like ?', "%#{@q}%").size
@posts = Post.where('title like ?', "%#{@q}%").order(created_at: :desc).page(params[:page])
end
if !params[:all] and start_with
@posts = @posts.where(:created_at.lt => Time.at(start_with.to_i))
end
#update start_with
unless @posts.empty?
start_with = @posts[-1].created_at.to_i.to_s
end
respond_to do |format|
format.json do
render :json => {
posts: @posts.collect { |post| build_summary(post) },
start_with: start_with
}
end
format.html
end
end
private
def map
{
"life"=> "生活",
"tech"=> "技术",
"creator"=> "创业"
}
end
def build_summary(post)
{
title: post.title,
type: post.type_en,
created_at: format_date(post.created_at),
id: post.id.to_s,
liked_count: post.liked_count,
visited_count: post.visited_count,
labels: post.labels_content
}
end
end

View File

@ -2,8 +2,8 @@
class BlogsController < ApplicationController
def index
@newest = Post.desc(:created_at).first
@recent = Post.desc(:created_at).to_a[1..3]
@newest = Post.order(created_at: :desc).first
@recent = Post.order(created_at: :desc).to_a[1..3]
respond_to do |format|
format.html
format.json
@ -17,17 +17,19 @@ class BlogsController < ApplicationController
end
def show
cookies[:cable_id] = SecureRandom.uuid
@post = Post.find(params[:id])
@post.visited
@prev = Post.where(:created_at.lt => @post.created_at).desc(:created_at).where(:id.ne => @post.id).first
@next = Post.where(:created_at.gt => @post.created_at).asc(:created_at).where(:id.ne => @post.id).first
@comments = @post.comments
@prev = Post.where('created_at < ?', @post.created_at).order(created_at: :desc).first
@next = Post.where('created_at > ?', @post.created_at).order(created_at: :asc).first
@comments = @post.comments.order(created_at: :desc)
@likes_count = @post.likes.count
respond_to do |format|
format.html
format.json
end
end
def edit
@post = Post.find( params[:id] )
redirect_to edit_admin_post_path(@post)

View File

@ -1,6 +1,6 @@
class CommentsController < ApplicationController
layout false
helper_method :format_time
def index
@post = Post.find( params[:blog_id] )
res = @post.comments.desc(:created_at).collect { |comment| build_json(comment) }
@ -8,19 +8,33 @@ class CommentsController < ApplicationController
end
def create
@post = Post.find( params[:blog_id] )
comment = @post.comments.build(comment_params)
if comment.save
render :json=> { success: true, data: build_json(comment) }
else
render :json=> { success: false, message: comment.errors.full_messages.join(", ") }
unless request.xhr?
logger.warn "attack action detected: #{params.to_h}"
redirect_to root_path
return
end
cookies[:name] = comment_params[:name]
cookies[:email] = comment_params[:email]
@post = Post.find( params[:blog_id] )
@comment = @post.comments.build(comment_params)
if @comment.save
@comments = @post.comments.order(created_at: :desc)
ActionCable.server.broadcast "comment_post_#{@comment.post.id}", { not: cookies[:cable_id] }
render :create_ok
else
render :create_fail
end
end
def refresh
@post = Post.find(params[:blog_id])
@comments = @post.comments.order(created_at: :desc)
end
private
def comment_params
params.permit(:content, :name, :email)
params.require(:comment).permit(:content, :name, :email)
end
def build_json(comment)

View File

@ -6,15 +6,6 @@ class LikesController < ApplicationController
render :json=> { success: true, count: post.liked_count }
end
def is_liked
post = Post.find( params[:blog_id] )
if post.likes.where(id: params[:id]).first
render text: true
else
render text: false
end
end
def create
post = Post.find( params[:blog_id] )
like = post.likes.build

View File

@ -2,7 +2,7 @@ class PhotosController < ApplicationController
def create
@photo = Photo.new(image: params["Filedata"])
@photo.save!
render :text=> md_url(@photo.image.url)
render :plain => md_url(@photo.image.url)
end
def md_url(url)

View File

@ -3,24 +3,23 @@ class SubscribesController < ApplicationController
def index
end
def create
subscribe = Subscribe.find_or_initialize_by(email: params[:email])
subscribe.enable = true
def new
@subscribe = Subscribe.new
end
if subscribe.save
render :json => { success: true }
def create
@subscribe = Subscribe.find_or_initialize_by(email: subscribe_params[:email])
@subscribe.enable = true
if @subscribe.save
redirect_to subscribes_path, notice: '订阅成功'
else
render :json => { success: false, message: subscribe.errors.full_messages.join(", ")}
render :new
end
end
def cancel
subscribe = Subscribe.find_or_initialize_by(email: params[:email])
subscribe.enable = false
subscribe.save
flash[:notice] = "退订成功: #{params[:email]}"
render :json => { success: true }
def subscribe_params
params.require(:subscribe).permit(:email)
end
end

View File

@ -0,0 +1,21 @@
class UnsubscribesController < ApplicationController
def index
end
def new
@subscribe = Subscribe.new
end
def create
subscribe = Subscribe.find_or_initialize_by(email: subscribe_params[:email])
subscribe.enable = false
subscribe.save
flash[:notice] = "退订成功: #{subscribe_params[:email]}"
redirect_to unsubscribes_path
end
def subscribe_params
params.require(:subscribe).permit(:email)
end
end

View File

@ -1,2 +1,9 @@
module ApplicationHelper
# some format function is defined in app/controllers/application_controller.rb
#
def search_highlight(title, q)
return title if q.blank?
title.sub(q, "<em>#{q}</em>")
end
end

View File

@ -3,7 +3,7 @@ class CommentMailer < ActionMailer::Base
default from: "no-reply@#{domain}"
def new(comment_id, to)
def born(comment_id, to)
@comment = Comment.find(comment_id)
mail to: to, subject: '博主, 你的博客有新的评论'
end

View File

@ -3,7 +3,7 @@ class PostMailer < ActionMailer::Base
default from: "no-reply@#{domain}"
def new(post_id, to)
def born(post_id, to)
@post = Post.find(post_id)
mail to: to, subject: '客官, 新博客来了'
end

View File

@ -0,0 +1,3 @@
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end

View File

@ -1,24 +1,17 @@
class Comment
include Mongoid::Document
include Mongoid::Timestamps
field :name, :type => String
field :content, :type => String
field :email, :type=>String
class Comment < ApplicationRecord
belongs_to :post
validates_presence_of :post_id
validates :name, presence: true
validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, message: I18n.t('comment_attributes.email') }
validates :content, presence: true
validates_presence_of :post_id
def reply_emails
Comment.where(post_id: self.post_id).collect(&:email).uniq - [ self.email ] - Subscribe.unsubscribe_list
Comment.where(post_id: self.post_id).collect(&:email).uniq - [ self.email ] - Subscribe.unsubscribe_list - [ ENV['ADMIN_USER'] ]
end
after_create do
if ENV['MAIL_SERVER'].present? && ENV['ADMIN_USER'].present? && ENV['ADMIN_USER'] =~ /@/
after_commit on: :create do
if ENV['MAIL_SERVER'].present? && ENV['ADMIN_USER'].present? && ENV['ADMIN_USER'] =~ /@/ && ENV['ADMIN_USER'] != self.email
Rails.logger.info 'comment created, comment worker start'
NewCommentWorker.perform_async(self.id.to_s, ENV['ADMIN_USER'])
end

View File

@ -1,9 +1,4 @@
class Label
include Mongoid::Document
include Mongoid::Timestamps
field :name, :type => String
class Label < ApplicationRecord
has_and_belongs_to_many :posts
validates :name, presence: true
end

View File

@ -1,6 +1,4 @@
class Like
include Mongoid::Document
class Like < ApplicationRecord
belongs_to :post
validates_presence_of :post_id
end

View File

@ -1,7 +1,3 @@
class Photo
include Mongoid::Document
include Mongoid::Timestamps
field :image
class Photo < ApplicationRecord
mount_uploader :image, PhotoUploader
end

View File

@ -1,18 +1,5 @@
# encoding : utf-8
#
require 'markdown'
class Post
TECH = "技术"
LIFE = "生活"
CREATOR = "创业"
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Pagination
field :title, :type => String
field :content, :type => String
field :type, :type=> String
field :visited_count, :type=>Integer, :default=>0
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :labels
@ -20,9 +7,8 @@ class Post
validates :title, :presence=>true, :uniqueness=> true
validates :content, :presence=>true, :length => { :minimum=> 30 }
validates :type, :presence=>true, :inclusion => { :in => [ TECH, LIFE, CREATOR ] }
after_create do
after_commit on: :create do
if ENV['MAIL_SERVER'].present?
NewPostWorker.perform_async(self.id.to_s)
end
@ -38,35 +24,21 @@ class Post
md.render(content)
end
def type_en
map = {
'技术' => 'Tech',
'生活' => 'Life',
'创业' => 'Creator',
}
if I18n.locale == :en
map[type]
else
type
end
end
def visited
self.visited_count += 1
self.save
self.visited_count
end
# 显示给首页截断数据
# truncate content for home page display
def sub_content
HTML_Truncator.truncate(content_html, 300, length_in_chars: true)
end
# 显示给 meta description
# truncate content for meta description display
def meta_content
html = HTML_Truncator.truncate(content_html, 100, :length_in_chars => true, ellipsis: '')
# 加上 div 以方便 Nokogiri 获取 text()
# Easily get text for Nokogiri
html = '<div>' + html + '</div>'
Nokogiri.parse(html).text()
end
@ -80,4 +52,8 @@ class Post
def liked_count
self.likes.size
end
def liked_by?(like_id)
!! self.likes.where(id: like_id).first
end
end

View File

@ -1,8 +1,4 @@
class Subscribe
include Mongoid::Document
field :email, type: String
field :enable, type: Mongoid::Boolean, default: true
class Subscribe < ApplicationRecord
validates :email, presence: true, uniqueness: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, message: '地址无效' }
def self.subscribe_list

View File

@ -18,7 +18,7 @@
td
= mail_to comment.email
td
p.pre #{comment.content}
= simple_format(comment.content)
td
= format_time(comment.created_at)

View File

@ -2,14 +2,11 @@
= simple_form_for(@post, url: url, html: {novalidate: '' }) do |f|
.row
.large-6.columns
= f.input :title, label: t('admin.posts_attributes.title'), "ng-model"=>"title", input_html: { name: 'title' }
.row
.small-6.large-3.columns
= f.input :type, :as=>:select, :collection=> [ Post::TECH, Post::LIFE, Post::CREATOR ], label: t('admin.posts_attributes.type'), "ng-model"=>"type", input_html: { name: 'type' }
= f.input :title, label: t('admin.posts_attributes.title'), input_html: { name: 'title' }
.row
.small-12.large-6.columns
= label_tag :labels, t('admin.posts_attributes.labels')
= text_field_tag :labels, @post.labels_content(true), "ng-model"=>"labels", "ng-initial" => ''
= text_field_tag :labels, @post.labels_content(true)
.row
.small-12.columns
@ -17,22 +14,23 @@
| #{t('admin.posts_attributes.already_labels')}
span
- Label.all.each do |label|
a.tag href="#" ng-click="addTag($event)" #{label.name}
a.tag href="#" #{label.name}
/ tabs and upload file field
dl.tabs
dd ng-class="{ active: body_active }"
a href="" ng-click="changeToBody()" #{t('admin.posts_attributes.content')}
dd ng-class="{ active: !body_active }"
a href="#" ng-click="changeToPreview()" #{t('admin.posts_attributes.preview')}
ul.tabs#tabs data-tabs=''
li.tabs-title.is-active
a href="#content" #{t('admin.posts_attributes.content')}
li.tabs-title
a href="#preview" #{t('admin.posts_attributes.preview')}
= link_to t('admin.posts_attributes.upload_photo'), "#", :id=>'upload_photo'
input[type="file" style="display: none;"]
.content-field ng-show="body_active" ng-model= 'content'
= f.input :content, :as=> :text, :label => false, input_html: { name: 'content', "ng-model"=>'content', 'ng-initial'=>'' }
.tabs-content data-tabs-content='tabs'
.tabs-panel.content-field.is-active#content
= f.input :content, :as=> :text, :label => false, input_html: { name: 'content', id: 'content-input' }
.preview.markdown ng-hide="body_active" ng-bind-html=" trustAsPreviewHTML() "
.tabs-panel.preview.markdown#preview
.row
.small-12.large-6.columns.posts-button
button #{t('admin.posts_attributes.submit')}
button.button type='submit' #{t('admin.posts_attributes.submit')}

View File

@ -15,8 +15,6 @@
td.admin-post-summary-field
i.fi-calendar
span #{format_time(post.created_at)}
i.fi-list
span #{ post.type_en }
i.fi-pricetag-multiple
span #{ post.labels_content }
i.fi-torsos
@ -27,3 +25,4 @@
= link_to t('comment'), admin_post_comments_path(post.id), class: 'edit-post-link'
= link_to t('edit'), edit_admin_post_path(post), class: 'edit-post-link'
= link_to t('destroy'), admin_post_path(post), method: 'DELETE', 'data-confirm' => '确认删除?'
== paginate @posts

View File

@ -1,16 +1,12 @@
.row ng-controller="AdminSessionsController"
.small-12.large-8.columns
h3.blog-title #{t('admin.session.title')}
form ng-submit="login()"
= form_tag admin_sessions_path
.row
.small-12.large-8.columns
= label_tag 'username', t('admin.session.username')
= text_field_tag 'username', nil, placeholder: t('admin.session.username_placeholder'), "ng-model"=>"username"
= text_field_tag 'username', params[:username], placeholder: t('admin.session.username_placeholder')
= label_tag 'username', t('admin.session.password')
= password_field_tag 'password', nil, placeholder: t('admin.session.password_placeholder'), "ng-model"=>"password"
= password_field_tag 'password', params[:password], placeholder: t('admin.session.password_placeholder')
p
.alert-box.warning ng-show=" error_msg "
|{{ error_msg }}
button #{t('admin.session.login_button')}
button.button type='submit' #{t('admin.session.login_button')}

View File

@ -0,0 +1,25 @@
.row.admin-subscribes-field
.small-12.columns
h3.blog-title #{t('admin.subscribes') }
table width="100%"
thead
tr
th #{t('admin.subscribes_head.email')}
th #{t('admin.subscribes_head.enable')}
th #{t('admin.subscribes_head.created_at')}
th #{t('admin.subscribes_head.operation')}
tbody
- @subscribes.each do |subscribe|
tr
td
= mail_to subscribe.email
td
= subscribe.enable
td
= format_time(subscribe.created_at)
td
- if subscribe.enable
= link_to t('admin.subscribes_head.op.disable'), disable_admin_subscribe_path(subscribe), method: 'POST', data: { confirm: 'Confirm?' }
- else
= link_to t('admin.subscribes_head.op.enable'), enable_admin_subscribe_path(subscribe), method: 'POST', data: { confirm: 'Confirm?' }
== paginate @subscribes

View File

@ -1,30 +1,29 @@
- content_for(:title) do
| #{@type ? @type : t('title.timeline')}
.row ng-controller="ArchivesController" ng-cloak=""
| #{t('title.timeline')}
.row
.small-12.large-9.large-centered.columns
ul.archives-field ng-model="type" ng-init=" type= '#{@type}' "
li ng-repeat=" post in posts "
a.blog-title ng-href="{{ visit(post.id) }}"
|{{ post.title }}
p.tags-field
i.fi-calendar
span
|{{ post.created_at }}
i.fi-list-thumbnails
span
|{{ post.type }}
i.fi-pricetag-multiple
span
|{{ post.labels }}
i.fi-torsos
span
|{{ post.visited_count }}
i.fi-heart
span
|{{ post.liked_count }}
.no-more-field
p ng-show="no_more_flag" #{t('nocontent')}
.load-more
button.small ng-click="load()" ng-show="!loading_flag" Load More
button.small ng-show="loading_flag" Loading
ul.archives-field
.search-wrapper
= form_with url: archives_path, method: 'GET' do |f|
= f.search_field :q, value: @q, placeholder: t('archive.search')
- @posts.each do |post|
li
= link_to blog_path(post), class: 'blog-title' do
== search_highlight(post.title, @q)
p.tags-field
i.fi-calendar
span
= format_date(post.created_at)
i.fi-pricetag-multiple
span
= post.labels_content
i.fi-torsos
span
= post.visited_count
i.fi-heart
span
= post.liked_count
- if @q.present?
.search-result-wrapper
p.text-muted 共 #{@q_size || 0} 条结果
= paginate @posts, q: @q

View File

@ -1,21 +1,16 @@
.row ng-controller="CommentsController" ng-cloak=""
.row
.small-12.large-9.large-centered.columns
form novalidate='' name='form'
= form_for Comment.new, url: blog_comments_path(@post), remote: true do |f|
.row
.small-12.large-12.columns
= text_area_tag(:content, nil, placeholder: t('comment_placeholder.content'), 'ng-model'=> 'content', 'ng-required'=> true)
= f.text_area :content, placeholder: t('comment_placeholder.content')
.row
.small-12.large-6.columns
= text_field_tag(:name, nil, placeholder: t('comment_placeholder.name'), 'ng-model'=> 'name', 'ng-required'=> 'true')
= text_field_tag(:email, nil, placeholder: t('comment_placeholder.email'), 'ng-model'=> 'email', 'ng-pattern'=>"/^.+@.+$/", 'ng-required'=>"true")
button.comment-submit ng-click="submit()" ng-disabled="form.$invalid || submitting" {{ submitting && "#{t('comment_placeholder.submitting')}" || "#{t('comment_placeholder.submit')}" }}
p.comment-success ng-show="publish_success" #{t('comment_placeholder.publish_success')}
p.comment-fail ng-show="publish_success == false" #{t('comment_placeholder.publish_fail')}: {{ publish_fail_msg }}
.comment-diag
.comment-wrapper ng-repeat=" comment in comments "
p.name
|{{ comment.name + " • " }}
span.created-at
|{{ comment.created_at }}
/ ignore "white-space: pre" 's effect
<p class=comment-content>{{ comment.content }}</p>
= f.text_field :name, value: cookies[:name], placeholder: t('comment_placeholder.name')
= f.text_field :email, value: cookies[:email], placeholder: t('comment_placeholder.email')
button.button.comment-submit type='submit' data-disable-with=t('comment_placeholder.submitting') #{t('comment_placeholder.submit')}
#alert-container.alert.callout
span.text
button class="close-button" type='button'
span &times;
= render partial: 'comments/comment_content', locals: { comments: comments }

View File

@ -8,21 +8,16 @@ p.ptag.published-at
span #{format_date(post.created_at)}
= render 'common/copyright'
hr.blog-over
p ng-controller="LikesController" ng-cloak=""
button.like-button ng-show="! is_liked " ng-click="submit()"
|{{ count }}
p
button.button.like-button class="#{'liked' if post.liked_by?(cookies[:like])}" type='button' data-url=blog_likes_path(post)
span.count #{@likes_count}
span Like
button.like-button.liked ng-show=" is_liked " ng-click="cancel()"
|{{ count }}
span Like
div ng-controller = "QRCodesController"
.qrcode
a href="#" ng-model="qrcode" ng-init="qrcode=false" ng-click="show()"
i.fi-link
| #{t('qr_code')}
.qrcode
a#qrcode-link href="#"
i.fi-link
| #{t('qr_code')}
.social-share ng-show='qrcode'
.qrcode-wrapper
= render partial: "qrcode", locals: { str: blog_url(post) }
.social-share
.qrcode-wrapper
= render partial: "qrcode", locals: { str: blog_url(post) }

View File

@ -1,9 +1,6 @@
/ require: locals: { post : post }
h2.blog-title #{post.title}
p.ptag
span
i.fi-list-thumbnails
span #{post.type_en}
span
i.fi-pricetag-multiple
span #{post.labels_content}

View File

@ -1,3 +1,3 @@
.qrcode-image
= image_tag( qrcodes_path(str: str) )
#image-tag data-url=str
p #{t('qrcodetips')}

View File

@ -15,32 +15,16 @@
= link_to t('home.read'), blog_path(@newest), class: 'read-more'
p.published-at #{t('home.created_at')} #{format_date(@newest.created_at)}
h4.recent-title #{t('home.recent')}
ul.recent-content
- @recent.each do |re|
li = link_to "#{re.title}",blog_path(re)
- if @recent.present?
h4.recent-title #{t('home.recent')}
ul.recent-content
- @recent.each do |re|
li = link_to re.title,blog_path(re)
.large-3.columns.large-offset-1.self-introduce.self-introduce-index
/*Adjust it in common/welcome*/
= render 'common/welcome'
h4 #{t('subscribes.title')}
.row.ng-cloak ng-controller='AboutController'
.row
.small-12.medium-6.large-12.columns
ul.subscribe-ul
- if ENV['MAIL_SERVER'].present?
li
= link_to t('subscribes.email'), '', "ng-click"=>"click('email')"
.email-subscribe ng-show="type == 'email'"
= text_field_tag 'email', nil, placeholder: 'your@email.com', 'ng-model'=>'email'
button.small ng-click="subscribe()" ng-disabled="! email_validate()" #{t('subscribes.submit')}
span.subscribe-success ng-show="subscribe_success" #{t('subscribes.submit_success')}
span.subscribe-fail ng-show="subscribe_success == false" {{subscribe_fail_msg}}
li
= link_to t('subscribes.wechat'), '', "ng-click"=>"click('weixin')"
.weixin-subscribe ng-show="type == 'weixin'"
= render partial: "qrcode", locals: { str: root_url }
li
= link_to t('subscribes.rss'), '', "ng-click"=>"click('rss')"
.rss-subscribe ng-show="type == 'rss'"
= link_to rss_blogs_path do
- image_tag('rss.png')
= image_tag 'wechat_qrcode.jpg', class: 'wechat_qrcode'

View File

@ -1,14 +1,15 @@
- content_for(:meta) do
meta name="description" content="#{@post.meta_content}"
meta name="description" content=@post.meta_content
meta name="keywords" content=@post.labels_content
- content_for(:title) do
| #{@post.title}
.row.blog-wrapper
.row.blog-wrapper#blog-show-page data-url=refresh_blog_comments_path(@post) data-post_id=@post.id
.small-12.large-9.large-centered.columns
= render partial: "post", :locals=> { :post=> @post }
= render partial: "post", :locals=> { post: @post }
.comment-field
= render partial: 'comment', locals: { comments: @comments, post: @post }
p
.row
.small-12.large-9.large-centered.columns
- if @prev

View File

@ -1,12 +1,12 @@
p
| 你的博客有新的评论:
p
span 来自:
span 来自:
= mail_to @comment.email, @comment.name
p 评论内容:
p 评论内容:
pre
| #{@comment.content}
p
| 博客链接:
p
| 博客链接:
= link_to @comment.post.title, blog_url(@comment.post)

View File

@ -1,11 +1,11 @@
p 你评论过的博客有新的回复:
p 评论人: #{@comment.name}
p 评论内容:
p 评论内容:
pre #{@comment.content}
p
| 被评论博客:
| 被评论博客:
= link_to @comment.post.title, blog_url(@comment.post)
hr
p
| 退订:
= link_to unsubscribes_url, unsubscribes_url

View File

@ -7,3 +7,5 @@
被评论博客: <%= @comment.post.title %>
访问这里回复: <%= blog_url(@comment.post) %>
退订: <%= unsubscribes_url %>

View File

@ -0,0 +1,9 @@
.comment-diag
- comments.each do |comment|
.comment-wrapper
p.name
| #{comment.name}
| #{" • "}
span.created-at
| #{format_time(comment.created_at) }
= render partial: 'comments/comment_pre', locals: { comment: comment }

View File

@ -0,0 +1,2 @@
.comment-content
= simple_format(comment.content)

View File

@ -0,0 +1,2 @@
$('#alert-container .text').text(' <%= @comment.errors.full_messages.join(' ') %>' );
$('#alert-container').removeClass('success').addClass('alert').show();

View File

@ -0,0 +1,4 @@
$('#comment_content').val('');
$('#alert-container .text').text(' <%= t('comment_placeholder.publish_success') %>' );
$('#alert-container').removeClass('alert').addClass('success').show();
$('.comment-diag').replaceWith('<%= j render partial: 'comment_content', locals: { comments: @comments }%>');

View File

@ -0,0 +1 @@
$('.comment-diag').replaceWith('<%= j render partial: 'comment_content', locals: { comments: @comments }%>');

View File

@ -1,23 +0,0 @@
h2.blog-title ABOUT ME
p 我的名字是李亚飞( YaFei Lee ), 网名为 WinDy, 由于是我第一个网名, 所以一直使用至今.
p 我是 85 后成员, 目前生活在深圳, 计划长期在这里发展.
p 当年毕业于吉林大学计算机专业, 并在深信服工作约 5 年时间, 如今正在独立创业路中.
p 曾经对软件测试开发领域有浓厚兴趣, 如今对产品开发与公司运营兴趣较多. 平时多研究 Ruby on Rails, Web 开发等技术. 闲暇之余爱好中国象棋, 乒乓球, 读书.
p 最近正在读的书在这里: <a href='http://book.douban.com/people/41759170/collect'>豆瓣书单</a>
h4 近期动态
p 目前在独立创业路中, 正在互联网金融大浪中淘金.
p WinDy's Blog 开博也有 2 年以上了, 由于个人原因, 博客完全自主采用 Ruby on Rails 完成, 在 2014 年 3 月进行了重构, 以移动设备优先进行了设计.
p 闲暇之余, 开通了微信公众账号, 与 WinDy's Blog 不同, 这里不讲技术性内容, 主要面向生活感悟, 个人思考等, 欢迎关注.
= image_tag 'weixin.jpg'
p 也可以搜索 <strong>技术达人李亚飞</strong> 来找到我.
p 我对微信是重度使用者, 几乎不使用微博, 微信账号可以在我个别博客里发现, Enjoying!
h4 更多链接
ul
li
a href="https://github.com/windy" target="_blank" Github
li
a href="http://www.douban.com/people/41759170/" target="_blank" Douban
li
a href="http://www.linkedin.com/pub/yafei-lee/77/3b/505" target="_blank" LinkedIn

View File

@ -1,6 +1,6 @@
/* 样式调整请找 stylesheet: .self-introduce-index */
h4 欢迎
p 我是李亚飞, WinDy 是我的网名.
p 我是技术达人李亚飞
h4 关于我
ul.aboutme-index

View File

@ -1,5 +1,5 @@
- if (1.days.from_now).strftime('%-m-%-d') =~ /^1-[123]$/
.new-year
.alert-box data-alert=''
.callout.primary data-closable=''
| 我的朋友, 祝你元旦快乐
a href='#' class='close' &times;
button.close-button type='button' data-close='' &times;

View File

@ -1,25 +1,29 @@
- content_for(:title) do
| #{t('title.about')}
- content_for(:main) do
.about-page ng-app='app' ng-controller='AboutScrollController'
.top-bar-wrapper.contain-to-grid.fixed ng-class="{ active: ! is_top() }"
.row
.small-12.columns
nav.top-bar data-topbar='' role='navigation'
ul.title-area
li.name
h1
a href='/' Back to WinDy's Blog
section.top-bar-section
ul.right
.about-page
.top-bar-wrapper.contain-to-grid.fixed
nav#about-top-bar.top-bar
.top-bar-left
ul.menu
li
a.name href='/' Back to WinDy Blog
.top-bar-right
ul.menu
li
.responsive-button data-responsive-toggle="responsive-menu" data-hide-for="medium"
button class="menu-icon" type="button" data-toggle=''
#responsive-menu
.top-bar-right
ul.menu
li
a href='#about' du-smooth-scroll='' du-scrollspy='' ABOUT
a href='#about' ABOUT
li
a href='#skill' du-smooth-scroll='' du-scrollspy='' SKILL
a href='#skill' SKILL
li
a href='#work' du-smooth-scroll='' du-scrollspy='' PORTFOLIO
a href='#work' PORTFOLIO
li
a href='#contact' du-smooth-scroll='' du-scrollspy='' CONTACT
a href='#contact' CONTACT
#intro
header.intro
.intro-heading
@ -32,13 +36,13 @@
| Find a loved job rather than found a good job.
br
| Technology advance NOT by human's intelligence, but by their laziness.
a.circle href='#about' du-smooth-scroll=''
a.circle href='#about'
i.fa.fa-angle-double-down
section#about
.row
.small-12.large-9.large-centered.columns
h1.title About Me
p I'm Li yafei, located on Shenzhen, China. I'm a Ruby on Rails full-stack Developer.
p I'm Li yafei, located on the city of Shenzhen, in China. I'm a Ruby on Rails full-stack Developer.
p
| I graduated from Jinlin university at 2009, and then I worked for
a href='http://sangfor.com' &nbsp Sangfor Corporation &nbsp
@ -46,11 +50,16 @@
p
| At March 2014, I start a startup company named Cywin( Chinese name: 创业赢) with my partner.
p
| Cywin.cn is an investment crowdfunding platform for startups and investors which was built by me alone in six month.
a href='http://cywin.yafeilee.me' Cywin
'
| is an investment crowdfunding platform for startups and investors which was built by me alone in six month.
p
| Then it failed because the lack of resource below the line.
| But it failed at 2014.10 because of the lack of related resource.
p
| After that, I try to research and attempt remote work, I build a new Chinese remote team called 80%, which help people building a nice website, a mobile app, and etc.
| After that, I research remote work and jump in, I built a remote-work team called 80percent, which help people building nice websites, mobile apps, and so on.
p
' You can follow us at:
a href='http://80percent.io' 80percent website
section#skill
.row
.small-12.large-9.large-centered.columns
@ -61,11 +70,10 @@
li Ruby on Rails( expert )
li Linux / OSX( expert )
li Git( expert )
li AngularJS( skilled )
li AngularJS / React / VueJS / jQuery ( skilled )
li Bootstrap / Foundation 5( skilled )
li jQuery( skilled )
li HTML5
li CSS3
li HTML5 / CSS3
li Agile Development
li Testing Automation
li Deploying Automation
li PostgreSQL / Mysql / Mongodb
@ -74,46 +82,43 @@
.small-12.large-9.large-centered.columns
h1.title Portfolio
ul.works
li
span.time 2015.3 - 2016.x
.project
span.name Lina
span.brief an API provider based on Ruby on Rails
span.link
a href='http://linarb.org' target='_blank' http://linarb.org
ul.project-description
li Auto-generated API docs.
li zero learning costs for Railser.
li Qiniu cloud storage, Wechat Oauth, Alipay integration.
li
span.time 2014.12 - 2015.3
.project
span.name Jiaoluo
span.brief A MOOC education online system
span.link
a href='http://jiaoluo.it' target='_blank' http://jiaoluo.it
a href='http://jiaoluo.yafeilee.me' target='_blank' http://jiaoluo.yafeilee.me
ul.project-description
li Build it alone for a friend in two month.
li Awful cool modern UI.
li Primary features: Wechat Scan Login, Alipay payment, Video HTML5, community system.
li Based on Ruby on Rails, using Turoblinks & RJS for ajax.
li Qiniu cloud storage, Wechat Oauth, Alipay integration.
li
span.time 2014.12 - 2015.2
.project
span.name Lina
span.brief A RESTful API provider based on Ruby on Rails
span.link
a href='http://linarb.org' target='_blank' http://linarb.org( Chinese only )
ul.project-description
li An awesome API framework just like Grape.
li Lina can help you generate APIDOC, zero-learning costs for Railser.
li Lina is more powerful than Grape because it is by standing upon the shoulders of Giants.
li
| For more information, goto:
a href='http://linarb.org' target='_blank' http://linarb.org( Chinese only )
li Qiniu cloud storage( like aws storage ), Wechat Oauth ( like Facebook oauth ), Alipay integration( like paypal ).
li
span.time 2014.3 - 2014.10
.project
span.name Cywin
span.brief an investment crowdfunding platform for startups and investors
span.link
a href='http://cywin.cn' target='_blank' http://cywin.cn
a href='http://cywin.yafeilee.me' target='_blank' http://cywin.yafeilee.me
ul.project-description
li I build it myself, using stack of Ruby on Rails, AngularJS, Foundation 5.
li A lot of feature: login/auth system, notification, state machine, sunspot search, background job, logic code is large 50+ controllers, 120+ testcases.
li cost six month
li
| The code was open-source at March 2015, you can find it from
' The code was open-source at March 2015, you can find it from
a href='https://github.com/windy/cywin' target='_blank' github
| .
li
@ -124,7 +129,7 @@
span.link
a href='https://github.com/windy/wblog' target='_blank' https://github.com/windy/wblog
ul.project-description
li A modern blog system
li A modern blog system based on Ruby on Rails 5.0.
li Responsive page, like, share, comment system, dashboard.
li
span.time 2012 - 2013.10
@ -141,9 +146,9 @@
.small-12.large-9.large-centered.columns
h1.title Contact
p
| I'm working for building nice webapp & native app for you. Please contact me if you need help.
| I'm working on building nice web app & native app for clients. Please contact me if you need help.
p
| If you are a developer, I also would like to talk with you about technology direct and sth.
| If you are a developer, I also would like to talk with you about technology trend and so on.
p
| Please feel free to contact me.
p.mail_to
@ -162,8 +167,8 @@
i.fa.fa-twitter
| Twitter
p.modified-at
| Updated at 2015.4.8
| Updated at 2016.04.28
.footer
.row
.small-12.columns
div Copyright © 2012 - 2015 en.yafeilee.me
div Copyright © 2012 - 2016 en.yafeilee.me

View File

@ -1,93 +1,116 @@
- content_for(:title) do
| #{t('title.about')}
- content_for(:main) do
.about-page ng-app='app' ng-controller='AboutScrollController'
.top-bar-wrapper.contain-to-grid.fixed ng-class="{ active: ! is_top() }"
.row
.small-12.columns
nav.top-bar data-topbar='' role='navigation'
ul.title-area
li.name
h1
a href='/' 回到博客
section.top-bar-section
ul.right
li
a href='#about' du-smooth-scroll='' du-scrollspy='' 关于
li
a href='#skill' du-smooth-scroll='' du-scrollspy='' 技能
li
a href='#work' du-smooth-scroll='' du-scrollspy='' 作品
li
a href='#contact' du-smooth-scroll='' du-scrollspy='' 联系
.about-page
.top-bar-wrapper.contain-to-grid.fixed
nav#about-top-bar.top-bar
.top-bar-left
ul.menu
li
a.name href='/' 回到博客
.top-bar-right
ul.menu
li
.responsive-button data-responsive-toggle="responsive-menu" data-hide-for="medium"
button class="menu-icon" type="button" data-toggle=''
#responsive-menu
.top-bar-right
ul.menu
li
a href='#about' 关于
li
a href='#skill' 技能
li
a href='#work' 作品
li
a href='#contact' 联系
#intro
header.intro
.intro-heading
.row
.small-12.columns
h1.heading 李亚飞
a.version href='http://en.yafeilee.me/about' English Version
.sub-heading
p
| 懒, 是人类进步的动力
br
| 所谓技术, 就是让你的生活越来越懒
a.circle href='#about' du-smooth-scroll=''
p 懒,驱动人类进步的关键。
a.circle href='#about'
i.fa.fa-angle-double-down
section#about
.row
.small-12.large-9.large-centered.columns
h1.title 关于我
p 我是李亚飞, 一个在中国深圳的全栈开发工程师( Full Stack Developer ).
p
| 曾经在
a href='http://sangfor.com' 深信服
| 工作大约 5 年( 2009.6 - 2014.3 ). 在那里, 从一个菜鸟成长为一个资深工程师, 还有幸带领一个很酷的团队帮助公司进行自动化测试方向的研究与推进.
p
| 在 2014 年 3 月份, 与一个很不错的合伙人一起, 他负责业务, 我负责技术开发了创业赢(http://cywin.cn), 这是一个股权众筹平台, 帮助创业团队更好的融资, 但是在 10 月份的时候项目快速失败了.
p
| 2015 年 3 月, 我与几个伙伴正式组建了
a href='http://80percent.io' 80%
| 远程办公团队, 专注于为创业团队提供技术外包服务.
p
| 几个月来, 帮助了好几个项目( 天天投资365, 鲜品汇, 要有光等 )的成长.
p
| 2015 年 7 月, 重新加入了一家新的创业公司
a href='http://www.smartx.com' smartx
| , 目前作为前端全栈工程师入伙, 它有可能在未来二年成为中国另一个独角兽.
p
| 下一步, 未来请等待...
p 我是李亚飞,坐标深圳,连续创业者,技术达人,有众多开源项目和多场技术分享,联合创立 3 家技术产品驱动的公司,前单麦科技联合创始人&CTO现至简天成科技CEO。
.wrapper
.time 2019.03 ~ 现在
p 前公司已被某C轮公司收购再次创立了至简天成科技一家为创业者提供产品孵化的技术型公司。
ul
li 我们选择靠谱有潜力的创业公司。
li 创新的持股+现金的技术合作模式。
li 为创业伙伴打磨产品,利益共享长期发展。
li 现已有发展不错的创业合作伙伴。
.wrapper
.time 2016.04 ~ 2018.12
p 深圳百分之八十网络技术有限公司联合创始人 & CTO旗下主要产品单麦小程序平台是帮助商家一键制作小程序的SAAS服务平台我与另一位联合创始人一道创立并运营了整个公司与产品我的主要职责包括共同决策产品方向负责研发团队日常管理参与市场部销售计划制定与支持工作。
ul
li 组建以敏捷开发,全栈工程师为核心的高效率研发团队
li 带领团队连续 52 次版本迭代,每周发布新版本
li 单麦小程序平台客户体验满意度90%以上上线小程序几千家服务用户15万+
li 单麦小程序平台成为微信小程序生态 TOP10 获奖者
.wrapper
.time 2015
p SmartX 创始员工SmartX 是位于中国的全球领先的超融合存储方案供应商,利用软件技术方案帮助企业大规模降低存储硬件的成本。作为早期创始员工,担任前端架构师角色,顺利保证 SmartX OS 的快速交付。
.wrapper
.time 2014
p Cywin.cn 联合创始人 & 技术负责人Cywin.cn 是一家股权众筹平台,对标美国的 Angelist.co帮助创业公司完成股权融资的交易平台。作为项目的发起人之一负责产品的研发工作。
.wrapper
.time 2009 ~ 2014
p 深信服(已上市)是一家著名的 “华为系” 网络公司。加入时研发规模600人作为当时研发部成长最快的工程师之一担任自动化产品线的主管兼技术负责人带领技术团队进行质量管理方面的工作。
section#skill
.row
.small-12.large-9.large-centered.columns
h1.title 开发技能
h1.title 开发技能
.skills
ul
li Ruby on Rails( 熟练 )
li Linux / OSX( 熟练 )
li Git / Svn( 熟练 )
li AngularJS( 熟练 )
li Bootstrap / Foundation 5( 熟练 )
li jQuery( 熟练 )
li HTML5
li CSS3
li Ruby on Rails ( 精通 )
li Linux / OSX ( 非常熟悉 )
li Git / Svn ( 非常熟悉 )
li AngularJS / React / VueJS / ES6 / Jquery ( 非常熟悉 )
li Bootstrap / Foundation 6 ( 非常熟悉 )
li HTML5 ( 熟悉 )
li CSS3 ( 熟悉 )
li Agile Development
li Testing Automation
li Deploying Automation
li PostgreSQL / Mysql / Mongodb
section#work
.row
.small-12.large-9.large-centered.columns
h1.title 主要作品
h1.title 个人主要作品
ul.works
li
span.time 2015.3 - 2016.x
.project
span.name Lina
span.brief 一个专注于 API 接口编写的框架
span.link
a href='https://github.com/windy/lina' target='_blank' https://github.com/windy/lina
ul.project-description
li 自动生成文档
li 零学习成本, 集成 Rails 的 API 开发最佳实践
li 自动校验参数, 快速构建安全可靠的 API
li
' Github 地址:
a href='https://github.com/windy/lina' https://github.com/windy/lina
li
span.time 2014.12 - 2015.3
.project
span.name 青角落
span.brief 有趣的互联网人, 知识
span.link
a href='http://jiaoluo.it' target='_blank' http://jiaoluo.it
a href='http://jiaoluo.yafeilee.me' target='_blank' http://jiaoluo.yafeilee.me
ul.project-description
li 帮助朋友独立开发, 现代感极强的 UI 及 功能: 微信扫一扫, 支付宝, 视频托管, 社区.
li 朋友的创业项目, 独立开发
li 现代的功能: 微信登录, 支付系统, 视频托管, 用户社区.
li Ruby on Rails 架构, 使用 Turoblinks, RJS 进行前端交互.
li 七牛云存储集成, 微信集成, 支付宝集成.
li
@ -96,12 +119,12 @@
span.name Cywin
span.brief 一个股权众筹的商业平台
span.link
a href='http://cywin.yafeilee.me' target='_blank' http://cywin.yafeilee.me
a href='https://github.com/windy/cywin' target='_blank' https://github.com/windy/cywin
ul.project-description
li 全栈独立负责整个项目的后端, 前端, 架构在 Ruby on Rails, AngularJS, Foundation 5.
li 花费周期 6 个月, 上线.
li
| 代码在 2015 年 3 月已经
' 代码在 2015 年 3 月已经
a href='https://github.com/windy/cywin' target='_blank' 开源
| .
li
@ -115,7 +138,7 @@
li Ruby on Rails 开源博客系统, 帮助更多的朋友构建高定制性的博客.
li 具备博客管理, 点赞, 评论, 二维码, 自适应等现代网站所有特性.
li
span.time 2012 - 2013.10
span.time 2011.x - 2013.10
.project
span.name ATM/ATT
span.brief 测试业界领先的关键字驱动的自动化测试平台
@ -130,11 +153,18 @@
.small-12.large-9.large-centered.columns
h1.title 联系我
p
| 如果你正在创业或在创业规划中, 我也许可以提供一些技术性建议, 欢迎与我交流
| 对于技术开发者, 我可以帮助你指导前端开发技术发展的趋势预测, 给予职业发展规划.
br
| 对于创业者, 我擅长于精益创业分析, 帮助你快速落地项目, 少走技术弯路.
p
| 如果你想与我谈谈技术, 生活, 也可以与我随时与我交流
| 如果你是开发者, 可以找我谈谈技术发展, 程序员的生活, 或许能帮助更好的规划职业发展.
p
| 不要紧张, 请随时联系我
| 如果你是创业者, 可以找我谈谈你的创业规划, 技术困难, 我将尽力帮助你.
p
' 也欢迎到我们的公司主页了解更多信息:
a href='http://www.dao42.com' 至简天成官网
p
| 保持放松, 请随时邮件与我联系
p.mail_to
= mail_to 'lyfi2003@gmail.com'
ul.contact-ul
@ -152,8 +182,8 @@
| 豆
| Douban
p.modified-at
| 本页更新于 2015.7.7
| 本页更新于 2019.03.29
.footer
.row
.small-12.columns
div Copyright © 2012 - 2016 yafeilee.me
div Copyright © 2012 - 2019 yafeilee.me

View File

@ -0,0 +1,7 @@
/ Non-link tag that stands for skipped pages...
- available local variables
current_page : a page object for the currently displayed page
total_pages : total number of pages
per_page : number of items to fetch per page
remote : data-remote
li.ellipsis

View File

@ -0,0 +1,10 @@
/ Link to the "Last" page
- available local variables
url : url to the last page
current_page : a page object for the currently displayed page
total_pages : total number of pages
per_page : number of items to fetch per page
remote : data-remote
span.last
== link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, :remote => remote
'

View File

@ -0,0 +1,10 @@
/ Link to the "Next" page
- available local variables
url : url to the next page
current_page : a page object for the currently displayed page
total_pages : total number of pages
per_page : number of items to fetch per page
remote : data-remote
li.pagination-next class="#{'disabled' if current_page.last?}"
== link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, :rel => 'next', :remote => remote
'

View File

@ -0,0 +1,11 @@
/ Link showing page number
- available local variables
page : a page object for "this" page
url : url to this page
current_page : a page object for the currently displayed page
total_pages : total number of pages
per_page : number of items to fetch per page
remote : data-remote
li class="page#{' current' if page.current?}"
== link_to_unless page.current?, page, url, {:remote => remote, :rel => page.rel}
'

Some files were not shown because too many files have changed in this diff Show More