Showing preview only (321K chars total). Download the full file or copy to clipboard to get everything.
Repository: windy/wblog
Branch: master
Commit: de727a0b63fa
Files: 236
Total size: 266.0 KB
Directory structure:
gitextract_s_ecaci2/
├── .1024
├── .ackrc
├── .browserslistrc
├── .gitattributes
├── .gitignore
├── .rspec
├── .ruby-version
├── .travis.yml
├── Gemfile
├── Procfile.dev
├── README.md
├── README.zh-CN.md
├── Rakefile
├── app/
│ ├── assets/
│ │ ├── builds/
│ │ │ └── .keep
│ │ └── stylesheets/
│ │ ├── about.scss
│ │ ├── aboutme_welcome.scss
│ │ ├── admin/
│ │ │ └── posts.scss
│ │ ├── admin.scss
│ │ ├── application.scss
│ │ ├── archives.scss
│ │ ├── blogs.scss
│ │ ├── bootstrap_custom.scss
│ │ ├── browserslist
│ │ ├── comments.scss
│ │ ├── fontawsome_custom.scss
│ │ ├── footer.scss
│ │ ├── head.scss
│ │ ├── highlight.scss
│ │ ├── libs/
│ │ │ └── markdown.scss
│ │ ├── like_and_weixin.scss
│ │ ├── new_year.scss
│ │ └── qrcodes.scss
│ ├── channels/
│ │ └── application_cable/
│ │ ├── channel.rb
│ │ └── connection.rb
│ ├── controllers/
│ │ ├── admin/
│ │ │ ├── accounts_controller.rb
│ │ │ ├── all_comments_controller.rb
│ │ │ ├── base_controller.rb
│ │ │ ├── comments_controller.rb
│ │ │ ├── dashboard_controller.rb
│ │ │ ├── labels_controller.rb
│ │ │ ├── posts_controller.rb
│ │ │ └── sessions_controller.rb
│ │ ├── application_controller.rb
│ │ ├── archives_controller.rb
│ │ ├── blogs_controller.rb
│ │ ├── comments_controller.rb
│ │ ├── concerns/
│ │ │ └── .keep
│ │ ├── home_controller.rb
│ │ ├── likes_controller.rb
│ │ └── photos_controller.rb
│ ├── helpers/
│ │ └── application_helper.rb
│ ├── javascript/
│ │ ├── about.js
│ │ ├── admin/
│ │ │ ├── posts.js
│ │ │ └── sidebar.js
│ │ ├── admin.js
│ │ ├── application.js
│ │ ├── base.js
│ │ ├── channels/
│ │ │ ├── consumer.js
│ │ │ └── index.js
│ │ ├── controllers/
│ │ │ ├── admin_label_controller.js
│ │ │ ├── index.js
│ │ │ ├── like_controller.js
│ │ │ └── qrcode_controller.js
│ │ ├── ga.js.erb
│ │ └── libs/
│ │ ├── add_jquery.js
│ │ ├── ddscrollspy.js
│ │ ├── jquery.atwho.js
│ │ ├── jquery.html5-fileupload.js
│ │ └── qrcode.js
│ ├── jobs/
│ │ └── application_job.rb
│ ├── mailers/
│ │ └── application_mailer.rb
│ ├── models/
│ │ ├── administrator.rb
│ │ ├── application_record.rb
│ │ ├── comment.rb
│ │ ├── concerns/
│ │ │ └── .keep
│ │ ├── label.rb
│ │ ├── like.rb
│ │ ├── photo.rb
│ │ └── post.rb
│ ├── uploaders/
│ │ └── photo_uploader.rb
│ └── views/
│ ├── admin/
│ │ ├── accounts/
│ │ │ └── edit.html.slim
│ │ ├── all_comments/
│ │ │ └── index.html.slim
│ │ ├── comments/
│ │ │ └── index.html.slim
│ │ ├── dashboard/
│ │ │ └── index.html.slim
│ │ ├── labels/
│ │ │ ├── _form.html.slim
│ │ │ ├── edit.html.slim
│ │ │ ├── index.html.slim
│ │ │ └── new.html.slim
│ │ ├── posts/
│ │ │ ├── _form.html.slim
│ │ │ ├── edit.html.slim
│ │ │ ├── index.html.slim
│ │ │ └── new.html.slim
│ │ └── sessions/
│ │ └── new.html.slim
│ ├── archives/
│ │ └── index.html.slim
│ ├── blogs/
│ │ ├── _comment.html.slim
│ │ ├── _post.html.slim
│ │ ├── _post_head.html.slim
│ │ ├── _qrcode.html.slim
│ │ ├── edit.html.slim
│ │ └── show.html.slim
│ ├── comments/
│ │ ├── _comment_content.html.slim
│ │ ├── _comment_pre.html.slim
│ │ └── create.html.slim
│ ├── common/
│ │ ├── _copyright.en.html.slim
│ │ ├── _copyright.html.slim
│ │ ├── _no_blog_here.en.html.slim
│ │ ├── _no_blog_here.html.slim
│ │ ├── _welcome.en.html.slim
│ │ ├── _welcome.html.slim
│ │ └── _welcome_new_year.html.slim
│ ├── home/
│ │ ├── _post_head.html.slim
│ │ ├── about.html.slim
│ │ └── index.html.slim
│ ├── kaminari/
│ │ ├── _first_page.html.slim
│ │ ├── _gap.html.slim
│ │ ├── _last_page.html.slim
│ │ ├── _next_page.html.slim
│ │ ├── _page.html.slim
│ │ ├── _paginator.html.slim
│ │ └── _prev_page.html.slim
│ ├── layouts/
│ │ ├── _footer.html.slim
│ │ ├── admin.html.slim
│ │ ├── application.html.slim
│ │ ├── mailer.html.erb
│ │ └── mailer.text.erb
│ └── shared/
│ └── admin/
│ ├── _flash_messages.html.slim
│ ├── _header.html.slim
│ └── _sidebar.html.slim
├── babel.config.js
├── bin/
│ ├── bundle
│ ├── dev
│ ├── rails
│ ├── rake
│ ├── setup
│ ├── spring
│ └── yarn
├── config/
│ ├── application.rb
│ ├── application.yml.example
│ ├── backup.rb.example
│ ├── boot.rb
│ ├── cable.yml
│ ├── credentials.yml.enc
│ ├── database.yml.example
│ ├── deploy/
│ │ └── production.rb
│ ├── deploy.rb
│ ├── environment.rb
│ ├── environments/
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers/
│ │ ├── application_controller_renderer.rb
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── browser_warrior.rb
│ │ ├── content_security_policy.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── kaminari_config.rb
│ │ ├── mime_types.rb
│ │ ├── permissions_policy.rb
│ │ ├── sidekiq.rb
│ │ ├── simple_form.rb
│ │ ├── simple_form_bootstrap.rb
│ │ └── wrap_parameters.rb
│ ├── locales/
│ │ ├── en.yml
│ │ ├── simple_form.en.yml
│ │ ├── simple_form.zh-CN.yml
│ │ └── zh-CN.yml
│ ├── logrotate.conf.example
│ ├── monit.conf.example
│ ├── nginx.conf.example
│ ├── nginx.ssl.conf.example
│ ├── puma.rb
│ ├── routes.rb
│ ├── secret.yml
│ ├── sidekiq.yml
│ ├── spring.rb
│ └── storage.yml
├── config.ru
├── db/
│ ├── migrate/
│ │ ├── 20160420082319_create_posts.rb
│ │ ├── 20160420082536_create_comments.rb
│ │ ├── 20160420082629_create_labels.rb
│ │ ├── 20160420082734_create_likes.rb
│ │ ├── 20160420082811_create_photos.rb
│ │ ├── 20160421035040_create_join_table_post_label.rb
│ │ ├── 20210614151036_create_active_storage_tables.active_storage.rb
│ │ ├── 20210614151102_create_administrators.rb
│ │ ├── 20250120142353_add_service_name_to_active_storage_blobs.active_storage.rb
│ │ ├── 20250120142354_create_active_storage_variant_records.active_storage.rb
│ │ └── 20250120142355_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb
│ ├── schema.rb
│ └── seeds.rb
├── doc/
│ └── .gitkeep
├── lib/
│ ├── assets/
│ │ └── .keep
│ ├── markdown.rb
│ ├── tasks/
│ │ └── .keep
│ └── templates/
│ └── slim/
│ └── scaffold/
│ └── _form.html.slim
├── log/
│ └── .keep
├── package.json
├── postcss.config.js
├── public/
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ └── robots.txt
├── spec/
│ ├── controllers/
│ │ ├── admin/
│ │ │ ├── comments_controller_spec.rb
│ │ │ ├── dashboard_controller_spec.rb
│ │ │ ├── posts_controller_spec.rb
│ │ │ └── sessions_controller_spec.rb
│ │ ├── archives_controller_spec.rb
│ │ ├── blogs_controller_spec.rb
│ │ ├── home_controller_spec.rb
│ │ └── likes_controller_spec.rb
│ ├── factories/
│ │ ├── comments.rb
│ │ ├── labels.rb
│ │ ├── posts.rb
│ │ └── subscribes.rb
│ ├── models/
│ │ ├── like_spec.rb
│ │ └── post_spec.rb
│ ├── rails_helper.rb
│ ├── spec_helper.rb
│ └── support/
│ ├── capybara.rb
│ ├── database_cleaner.rb
│ └── factory_bot.rb
├── storage/
│ └── .keep
├── test/
│ ├── application_system_test_case.rb
│ ├── channels/
│ │ └── application_cable/
│ │ └── connection_test.rb
│ ├── controllers/
│ │ └── .keep
│ ├── fixtures/
│ │ └── files/
│ │ └── .keep
│ ├── helpers/
│ │ └── .keep
│ ├── integration/
│ │ └── .keep
│ ├── mailers/
│ │ └── .keep
│ ├── models/
│ │ └── .keep
│ ├── system/
│ │ └── .keep
│ └── test_helper.rb
├── tmp/
│ └── .keep
└── vendor/
└── .keep
================================================
FILE CONTENTS
================================================
================================================
FILE: .1024
================================================
# .1024 Configuration file for project run commands, compilation and debug settings (optional);
# Any changes made will be auto-saved and take effect immediately.
# For more information, please refer to the documentation: https://docs.clacky.ai/clacky-workspace/configure
# Command to run when "Run" button clicked
run_commands: ['bin/dev']
# Command to install or update dependencies, will execute each time a new thread created to ensure dependencies up-to-date
dependency_command: gem install bundler && bundle install && yarn install && rails db:migrate
================================================
FILE: .ackrc
================================================
--ignore-file=ext:svg
--ignore-dir=public
--ignore-dir=tmp
--ignore-dir=node_modules
================================================
FILE: .browserslistrc
================================================
defaults
================================================
FILE: .gitattributes
================================================
# See https://git-scm.com/docs/gitattributes for more about git attribute files.
# Mark the database schema as having been generated.
db/schema.rb linguist-generated
# Mark the yarn lockfile as having been generated.
yarn.lock linguist-generated
# Mark any vendored files as having been vendored.
vendor/* linguist-vendored
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore uploaded files in development.
/storage/*
!/storage/.keep
/public/assets
# Ignore master key for decrypting credentials and more.
/config/master.key
/public/packs
/public/packs-test
/node_modules
/yarn-error.log
yarn-debug.log*
.yarn-integrity
*.swp
/public/uploads/*
/public/assets/*
/tags
/config/application.yml
/config/mongoid.yml
/config/database.yml
/config/master.key
**.orig
*.old
*.bak
*~
.env
.byebug_history
.DS_Store
/app/assets/builds/*
!/app/assets/builds/.keep
# Clacky-specific files
.1024*
!.1024
.breakpoints
================================================
FILE: .rspec
================================================
--color
--require spec_helper
--require rails_helper
================================================
FILE: .ruby-version
================================================
3.1.2
================================================
FILE: .travis.yml
================================================
# http://about.travis-ci.org/docs/user/build-configuration/
env:
global:
- CC_TEST_REPORTER_ID=787a2f89b15c637323c7340d65ec17e898ac44480706b4b4122ea040c2a88f1d
language: ruby
before_install:
- "cat /etc/timezone"
- "grep -i processor /proc/cpuinfo | wc -l"
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
- "gem install bundler"
- "bundle -v"
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:
- postgresql
rvm:
- 2.5.3
================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '3.1.2'
gem 'rails', '~> 7.1.4.2'
gem 'puma', '~> 4.x'
# gem 'puma-daemon', require: false
gem 'turbo-rails'
gem 'jbuilder', '~> 2.7'
gem 'bootsnap', '>= 1.4.4', require: false
gem 'stimulus-rails'
gem 'cssbundling-rails'
gem 'jsbundling-rails'
gem 'propshaft', '~> 1.1.0'
gem 'rexml', '~> 3.2', '>= 3.2.4'
group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
gem 'web-console', '>= 4.1.0'
gem 'rack-mini-profiler', '~> 2.0'
gem 'listen', '~> 3.9'
gem 'spring'
end
group :test do
gem 'capybara', '>= 3.26'
gem 'selenium-webdriver'
gem 'webdrivers'
end
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
gem 'pg', '>= 1.1'
gem 'concurrent-ruby', '1.3.4'
gem 'carrierwave'
gem 'redcarpet'
gem 'rouge'
gem 'mini_magick'
gem 'html_truncator'
gem 'nokogiri'
gem 'figaro'
gem 'simple_form', '~> 5.0'
gem 'slim-rails'
gem 'high_voltage', '~> 3.1'
# gem 'browser_warrior', '>= 0.11.0'
gem 'sidekiq', '~> 5'
gem 'bcrypt'
gem 'kaminari', github: 'kaminari/kaminari'
gem 'rails-i18n', '~> 7.0.10'
gem 'mina', '~> 1.2.2', require: false
gem 'mina-ng-puma', '>= 1.4.0', require: false
gem 'mina-multistage', require: false
gem 'mina-sidekiq', require: false
gem 'mina-logs', require: false
group :development do
gem 'rails_apps_testing'
gem 'faker'
end
group :development, :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
end
group :test do
gem 'database_cleaner'
gem 'launchy'
end
================================================
FILE: Procfile.dev
================================================
web: env RUBY_DEBUG_OPEN=true bin/rails server
css: yarn build:css --watch --poll
js: yarn build --watch
================================================
FILE: README.md
================================================
WBlog
=======
[](https://travis-ci.org/windy/wblog)
[](https://codeclimate.com/github/windy/wblog/maintainability)
[](https://codeclimate.com/github/windy/wblog/test_coverage)
The missing open source blog system on Ruby on Rails 7.x.
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 6.1 now.~~
New: WBlog has updated from webpacker to jsbundling & cssbundling with esbuild and sass.
New: WBlog is using Ruby on Rails 7.1 now.
[中文说明文档](/README.zh-CN.md)
Characteristic:
* 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 real example comes from my own blog( Chinese ): <https://yafeilee.com>
Some [screenshots](#screenshots)
### System dependencies
* Ruby ( >= 3.1.2 )
* Postgresql ( >= 9.x )
* Nginx ( >= 1.4 )
* node ( >= 1.18 )
### Features
* Responsive, iPhone, iPad, Notebook, PC, all are supported
* 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
### Goal
Make it to the best Ruby on Rails Blog system in the world.
### Running in development mode
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
# 3.1.2
postgres --version
# 9.x.x
npm -v
# 1.18.x
```
1. Clone it
`git clone git@github.com:windy/wblog.git`
`cd wblog`
2. Install dependencies & configure
```shell
# install rails dependencies
gem install bundler
bundle install
# install node dependencies
npm install yarn -g
yarn install
# copy and update project config file
cp config/application.yml.example config/application.yml
cp config/database.yml.example config/database.yml
```
Update `application.yml` & `database.yml` 's content as you need, then run setup:
```shell
bin/setup
```
3. Start it
one command:
```shell
bin/dev
```
It's all.
or using multi terminal:
```shell
# rails
bin/rails s
```
```shell
# js compile
bin/yarn build --watch
```
```shell
# css compile
bin/yarn build:css --watch
```
Open browser with `http://localhost:3000`
If there is any error found, please check your database's user and password( default is admin/admin )
4. Post the first blog
visit: http://localhost:3000/admin, input your username and password configurated in `db/seeds.rb`.
then, post a new article.
OK, That's all.
### Deployment
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 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 7.1
* Ruby 3.1.2
* Turbo
* Bootstrap 4
* mina
* slim
* puma
* Postgresql
## Related open source blog systems
* writings.io( Ruby on Rails 4.0.2 ): a multi users blog system <https://github.com/chloerei/writings>
* jekyll( Ruby Gem, Markdown): Static blog system <http://jekyllrb.com/>
* 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:

Home Page for mobile:

Home Page Hover Status for mobile:

Blog Show Page:

Blog Show Page Hover Status:

Admin Login Page:

Admin Dashboard Page:

Admin New Blog Page:

Admin Blogs Manage Page:

================================================
FILE: README.zh-CN.md
================================================
WBlog
=======
[](https://travis-ci.org/windy/wblog)
[](https://codeclimate.com/github/windy/wblog/maintainability)
[](https://codeclimate.com/github/windy/wblog/test_coverage)
为移动而生的 Ruby on Rails 开源博客. WBlog 基于 MIT 协议, 自由使用.
~~现已全面支持 Ruby on Rails 6.1 版本!!!~~
New: 现已经从 webpacker 升级至 jsbundling & cssbundling.
New: 现已全面升级到 Ruby on Rails 7.1 版本!!!
* 用户极为友好的阅读体验
* 自带干净的评论系统
* 简洁而不简单的发布博客流程
访问我的博客以体验: <https://yafeilee.com>
截图如下: <#screenshots>
### WBlog 的设计目标
* 优先以手机用户体验为主
* 独立干净的评论系统
* 良好的博客语法高亮支持
* 可邮件订阅
* Markdown 支持
* 尽可能独立
### 特色
* 优先支持移动端访问
* 响应式设计, 支持所有屏幕终端, 并且支持微信扫码继续阅读和分享
* 自带评论系统, 干净而方便
* Markdown 支持, 博客语法高亮, 方便技术性博客
* 开源可商用, 定制能力强
### 目标
`Ruby on Rails` 下最好用的独立博客建站系统
### 开发环境
WBlog 是一个标准的 Ruby on Rails 应用. 开发环境依赖于:
* Ruby ( = 3.1.2 )
* Postgresql ( >= 9.x )
* node ( >= 18 )
配置 WBlog:
```shell
# rails 依赖
gem install bundler
bundle install
# node 依赖
npm install yarn -g
yarn install
# 配置更新
cp config/application.yml.example config/application.yml
cp config/database.yml.example config/database.yml
```
更新对应配置: application.yml & database.yml.
就这样, 可以尝试启动了:
```shell
bin/dev
```
登录 http://localhsot:3000/admin 来发布第一篇博客.
### 发布应用
WBlog 采用了 `mina` 作为自动化发布工具, 使用 `nginx`, `puma` 为相关容器.
对应的发布流程在: [WBlog 的发布流程](https://github.com/windy/wblog/wiki)
### 技术栈
* Ruby on Rails 7.1
* Ruby 3.1.2
* Bootstrap 4
* mina
* slim
* Postgresql
## Ruby 相关开源博客推荐
* writings.io( Ruby on Rails 4.0.2 ): <https://github.com/chloerei/writings>
* jekyll( Ruby Gem, Markdown, Static ): <http://jekyllrb.com/>
* 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
首页:

小屏首页:

展开的小屏首页:

博客详情页:

展开的博客详情页:

管理员登录页:

管理页面板:

发布新博客页:

博客管理页:

================================================
FILE: Rakefile
================================================
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative "config/application"
Rails.application.load_tasks
================================================
FILE: app/assets/builds/.keep
================================================
================================================
FILE: app/assets/stylesheets/about.scss
================================================
.home-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;
color: #2199e8;
&:hover {
text-decoration: none;
opacity: 0.8;
}
}
//@media only screen and (min-width: 40.063em) {
@media screen and (max-width: 39.9375em) {
.top-bar-wrapper {
.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.3rem;
line-height: 1.8;
font-weight: 300;
}
.intro {
background-color: #7A7A7A;
background: url('intro-bg.jpg') no-repeat bottom center scroll;
background-size: cover;
height: 100vh;
width: 100%;
display: table;
.intro-heading {
display: table-cell;
vertical-align: middle;
text-align: center;
.heading, .sub-heading {
color: #eee;
}
.version {
color: #3FBFFF;
&:hover {
color: #BFEAFF;
}
}
.sub-heading {
margin-top: 2rem;
p {
line-height: 2;
}
}
.circle {
color: #eee;
width: 4rem;
height: 4rem;
font-size: 3rem;
border: 2px solid #eee;
display: inline-block;
border-radius: 50%;
margin-top: 2rem;
line-height: 1.2;
&:hover {
opacity: 0.7;
}
.fa {
font-size: 80%;
}
}
}
}
#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;
padding-top: 10rem;
padding-bottom: 10rem;
h1, h2 {
color: #eee;
text-align: center;
margin-bottom: 2rem;
}
}
#skill {
background: url('mp.jpg') no-repeat bottom center scroll;
background-color: #BABABA;
background-size: cover;
.skills {
color: #eeeeee;
background-color: rgba(63, 63, 63, 0.6);
padding: 2rem 2rem;
font-size: 1.125rem;
li {
margin: 1rem 0;
}
}
}
#work {
background: url('download-bg.jpg') no-repeat bottom center scroll;
background-size: cover;
padding-top: 10rem;
padding-bottom: 10rem;
h1, h2 {
color: #eee;
text-align: center;
margin-bottom: 2rem;
}
p {
color: #eee;
text-align: center;
}
.works {
color: #eee;
background-color: rgba(63, 63, 63, 0.6);
list-style: none;
padding: 2rem 2rem;
margin-left: 0;
>li {
margin: 1rem 0;
}
.project-description {
margin-bottom: 2rem;
font-size: 90%;
li {
margin: 0.5rem 0;
color: #aaa;
&:hover {
color: #ccc;
}
}
}
.name {
margin-right: 0.5rem;
font-size: 1.125rem;
font-weight: 600;
&:after {
margin-left: 0.5rem;
font-weight: 400;
content: '--';
}
}
.brief {
margin-right: 1rem;
color: #dfdfdf;
}
.link {
margin-right: 1rem;
color: #dfdfdf;
}
.time {
color: #aaa;
font-size: 95%;
}
}
}
#contact {
background: #000;
background-size: cover;
padding-top: 10rem;
padding-bottom: 2rem;
.modified-at {
padding-top: 8rem;
color: #aaa;
font-size: 1rem;
}
h1, h2 {
color: #eee;
text-align: center;
margin-bottom: 2rem;
}
p {
color: #eee;
text-align: center;
}
.mail_to {
margin-bottom: 2rem;
margin-top: 3rem;
}
.contact-ul {
text-align: center;
li {
display: inline-block;
}
li a {
display: inline-block;
padding: 0.5rem 2rem;
border: 1px solid #219AB3;
border-radius: 0.5rem;
color: #219AB3;
margin: 0.5rem 1rem;
transition: all .3s ease-in-out;
&:hover {
outline: 0;
color: #000;
background-color: #219ab3;
}
i {
margin-right: 0.5rem;
font-size: 1.1rem;
}
i.douban {
font-style: normal;
font-size: 95%;
vertical-align: baseline;
}
}
}
}
.footer {
border: none;
padding: 1.5rem;
background-color: #353535;
text-align: center;
color: #eee;
font-size: 1.125rem;
}
}
================================================
FILE: app/assets/stylesheets/aboutme_welcome.scss
================================================
.self-introduce {
@media screen and (min-width: 64em) {
margin-top: 1.875rem;
}
.box {
margin-bottom: 2rem;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 400;
margin-bottom: 1rem;
}
p, .aboutme-index {
color: #5D5D5D;
}
}
.aboutme-index {
list-style-type: none;
margin-left: 0;
padding-left: 0;
li {
margin-left: 0;
padding-bottom: 0.5rem;
span {
&:first-child{
margin-right: 0.5rem;
}
}
}
}
================================================
FILE: app/assets/stylesheets/admin/posts.scss
================================================
.admin-posts-edit-page, .admin-posts-index-page {
td {
i.fa {
margin-right: 0.2rem;
}
span {
margin-right: 0.5rem;
}
}
#content-input {
min-height: 30rem;
}
#preview {
padding: 0.5rem;
border: 1px solid #eee;
border-top: none;
margin-bottom: 0.5rem;
min-height: 5rem;
border-radius: 0.5rem;
}
#upload_photo {
float: right;
margin-top: -2rem;
}
}
================================================
FILE: app/assets/stylesheets/admin.scss
================================================
@use 'fontawsome_custom';
@use 'bootstrap_custom';
@use 'libs/markdown';
@use 'admin-lte/dist/css/adminlte';
@use 'admin/posts';
@use 'select2/dist/css/select2.css';
// @use '@ttskch/select2-bootstrap4-theme/dist/select2-bootstrap4.css';
.turbo-progress-bar {
height: 4px;
background-color: red;
}
.main-sidebar {
li.nav-item {
width: 100%;
a {
text-overflow: ellipsis;
}
}
}
.login-box {
margin: 7% auto;
}
================================================
FILE: app/assets/stylesheets/application.scss
================================================
@use 'bootstrap_custom';
@use 'fontawsome_custom';
@use 'libs/markdown';
@use 'aboutme_welcome';
@use 'archives';
@use 'blogs';
@use 'head';
@use 'qrcodes';
@use 'comments';
@use 'highlight';
@use 'footer';
@use 'like_and_weixin';
@use 'about';
@use 'new_year';
.turbo-progress-bar {
height: 2px;
background-color: red;
}
img {
display: block;
}
================================================
FILE: app/assets/stylesheets/archives.scss
================================================
.archives-field {
padding-top: 0.5rem;
margin-top: 1rem;
padding-bottom: 0.5rem;
i {
margin-right: 0.5rem;
}
span {
margin-right: 1rem;
}
.search-result-wrapper p {
font-size: 0.785rem;
color: #999;
}
.blog-title {
color: #111111;
&:hover {
color: red;
text-decoration: none;
}
border: none;
display: inline-block;
margin-bottom: 0.3rem;
em {
color: red;
font-style: normal;
}
}
li {
list-style: none;
color: #666666;
border-bottom: 1px dashed #CCCCCC;
margin-bottom: 1rem;
}
.tags-field {
font-size: 90%;
}
.load-more {
width: 100%;
text-align: center;
button {
background-color: transparent;
color: #333333;
border: 1px solid #DDDDDD;
&:focus {
outline-style: none;
}
}
}
.no-more-field p {
text-align: center;
color: #444444;
border-bottom: 1px solid #DDDDDD;
padding-bottom: 0.5rem;
padding-top: 1rem;
}
}
================================================
FILE: app/assets/stylesheets/blogs.scss
================================================
.home-index-page, .blogs-show-page {
.blog-title {
margin-top: 1rem;
line-height: 1.5;
}
.ptag {
margin-bottom: 0.5rem;
color: #888;
font-size: 95%;
span {
margin-left: 0.5rem;
&:first-child {
margin-left: 0;
}
}
.has-tip {
border: none;
}
}
.content {
padding-top: 1rem;
}
.read-more {
margin-top: 1rem;
display: inline-block;
padding: 1rem;
border: 1px solid #ccc;
color: #333;
&:hover {
color: #111;
border-color: #999;
text-decoration: none;
}
}
.published-at {
margin-top: 1rem;
color: #999;
@media screen and (min-width: 40.063em) {
text-align: right;
}
}
.blog-over {
margin-bottom: 1rem;
margin-top: 2rem;
border-bottom: 1px solid #DCDCDC;
}
.recent-title {
margin-top: 3rem;
font-weight: 400;
}
.recent-content {
padding-left: 1.2rem;
@media screen and (min-width: 64em) {
padding-bottom: 3rem;
}
li {
list-style: disc;
}
li a {
margin-left: -0.215rem;
}
}
#qrcode-home {
padding: 1rem 2rem 1rem 0;
}
.qrcode {
display: inline-block;
float: right;
margin-top: -4.215rem;
i {
margin-right: 0.5rem;
}
}
.qrcode-wrapper {
float: right;
margin-top: -2rem;
text-align: right;
table {
float: right;
}
p {
clear: both;
}
}
img {
width: 100%;
}
.social-share {
display: none;
}
.wechat_qrcode {
width: 200px;
margin-bottom: 20px;
}
}
================================================
FILE: app/assets/stylesheets/bootstrap_custom.scss
================================================
@use "sass:color";
$link-color: #2199e8 !default;
$link-decoration: none !default;
$link-hover-color: color.adjust($link-color, $lightness: -15%) !default;
$link-hover-decoration: none !default;
@forward 'bootstrap/scss/bootstrap';
================================================
FILE: app/assets/stylesheets/browserslist
================================================
last 2 versions
ie >= 9
Android >= 2.3
ios >= 7
================================================
FILE: app/assets/stylesheets/comments.scss
================================================
#alert-container {
border-radius: unset;
}
.comment-field {
background-color: #333333;
padding-top: 3rem;
padding-bottom: 1rem;
textarea {
min-height: 7rem;
}
input[type='text'], textarea {
border: 1px solid #333333;
background-color: #F7F7F7;
box-shadow: none;
width: 100%;
padding: 0.5rem;
color: #333;
margin-bottom: 1rem;
&:focus {
box-shadow: none;
background-color: white;
}
}
.has-tip {
color: #008cba;
}
.next {
float: right;
i {
margin-left: 0.275rem;
}
}
.prev i {
margin-right: 0.275rem;
margin-top: 0.5rem;
}
}
.comment-diag {
color: #EBEBEB;
border-top: 1px solid #5E5E5E;
padding-top: 1rem;
.created-at {
color: #b3b3b3;
}
.comment-content {
padding-bottom: 1rem;
padding-left: 0.275rem;
border-bottom: 1px dashed #8a8a8a;
margin-bottom: 0;
p {
margin-bottom: 0.325rem;
}
}
.name {
padding-top: 1rem;
padding-left: 0.275rem;
}
}
.comment-submit[disabled] {
opacity: 0.4;
cursor: not-allowed;
}
.comment-submit {
background-color: #008cba;
opacity: 0.8;
padding: 0.8rem 1.5rem;
margin-bottom: 1rem;
border: none;
color: white;
&:hover {
opacity: 1;
}
}
.comment-success {
color: #66FAB5;
}
.comment-fail {
color: #FF7A7A;
}
.comment-wrapper {
&:hover {
background-color: #444444;
}
.name {
color: #DDDDDD;
}
.comment-content {
word-wrap: break-word;
color: #DDDDDD;
word-break: break-all;
}
}
================================================
FILE: app/assets/stylesheets/fontawsome_custom.scss
================================================
$fa-font-path: '@fortawesome/fontawesome-free/webfonts';
@import '@fortawesome/fontawesome-free/scss/fontawesome';
@import '@fortawesome/fontawesome-free/scss/regular';
@import '@fortawesome/fontawesome-free/scss/solid';
@import '@fortawesome/fontawesome-free/scss/brands';
================================================
FILE: app/assets/stylesheets/footer.scss
================================================
.footer {
border-top: 1px solid #dddddd;
padding: 1rem 0 2rem;
text-align: right;
color: #666;
.link {
margin-right: 1rem;
}
.license {
margin-top: 1rem;
span {
margin-left: 0.5rem;
}
}
}
================================================
FILE: app/assets/stylesheets/head.scss
================================================
.my-navbar {
padding-left: 1.5rem;
padding-right: 1.5rem;
a.navbar-brand:hover {
color: #aaa;
}
.nav-link {
padding-right: 0.75rem !important;
padding-left: 0.75rem !important;
}
}
================================================
FILE: app/assets/stylesheets/highlight.scss
================================================
.highlight {
.hll { background-color: #49483e }
.c { color: #75715e } /* Comment */
.err { color: #960050; background-color: #1e0010 } /* Error */
.k { color: #66d9ef } /* Keyword */
.l { color: #ae81ff } /* Literal */
.n { color: #f8f8f2 } /* Name */
.o { color: #f92672 } /* Operator */
.p { color: #f8f8f2 } /* Punctuation */
.cm { color: #75715e } /* Comment.Multiline */
.cp { color: #75715e } /* Comment.Preproc */
.c1 { color: #75715e } /* Comment.Single */
.cs { color: #75715e } /* Comment.Special */
.ge { font-style: italic } /* Generic.Emph */
.gs { font-weight: bold } /* Generic.Strong */
.kc { color: #66d9ef } /* Keyword.Constant */
.kd { color: #66d9ef } /* Keyword.Declaration */
.kn { color: #f92672 } /* Keyword.Namespace */
.kp { color: #66d9ef } /* Keyword.Pseudo */
.kr { color: #66d9ef } /* Keyword.Reserved */
.kt { color: #66d9ef } /* Keyword.Type */
.ld { color: #e6db74 } /* Literal.Date */
.m { color: #ae81ff } /* Literal.Number */
.s { color: #e6db74 } /* Literal.String */
.na { color: #a6e22e } /* Name.Attribute */
.nb { color: #f8f8f2 } /* Name.Builtin */
.nc { color: #a6e22e } /* Name.Class */
.no { color: #66d9ef } /* Name.Constant */
.nd { color: #a6e22e } /* Name.Decorator */
.ni { color: #f8f8f2 } /* Name.Entity */
.ne { color: #a6e22e } /* Name.Exception */
.nf { color: #a6e22e } /* Name.Function */
.nl { color: #f8f8f2 } /* Name.Label */
.nn { color: #f8f8f2 } /* Name.Namespace */
.nx { color: #a6e22e } /* Name.Other */
.py { color: #f8f8f2 } /* Name.Property */
.nt { color: #f92672 } /* Name.Tag */
.nv { color: #f8f8f2 } /* Name.Variable */
.ow { color: #f92672 } /* Operator.Word */
.w { color: #f8f8f2 } /* Text.Whitespace */
.mf { color: #ae81ff } /* Literal.Number.Float */
.mh { color: #ae81ff } /* Literal.Number.Hex */
.mi { color: #ae81ff } /* Literal.Number.Integer */
.mo { color: #ae81ff } /* Literal.Number.Oct */
.sb { color: #e6db74 } /* Literal.String.Backtick */
.sc { color: #e6db74 } /* Literal.String.Char */
.sd { color: #e6db74 } /* Literal.String.Doc */
.s2 { color: #e6db74 } /* Literal.String.Double */
.se { color: #ae81ff } /* Literal.String.Escape */
.sh { color: #e6db74 } /* Literal.String.Heredoc */
.si { color: #e6db74 } /* Literal.String.Interpol */
.sx { color: #e6db74 } /* Literal.String.Other */
.sr { color: #e6db74 } /* Literal.String.Regex */
.s1 { color: #e6db74 } /* Literal.String.Single */
.ss { color: #e6db74 } /* Literal.String.Symbol */
.bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.vc { color: #f8f8f2 } /* Name.Variable.Class */
.vg { color: #f8f8f2 } /* Name.Variable.Global */
.vi { color: #f8f8f2 } /* Name.Variable.Instance */
.il { color: #ae81ff } /* Literal.Number.Integer.Long */
.gh { } /* Generic Heading & Diff Header */
.gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */
.gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */
.gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */
}
================================================
FILE: app/assets/stylesheets/libs/markdown.scss
================================================
.markdown {
word-wrap: break-word;
overflow: hidden;
color: #4f4444;
@media screen and (min-width: 40.063em) {
h1, h2, h3, h4, h5, h6 {
font-size: 1.875rem;
}
}
@media screen and (max-width: 40em) {
h1, h2, h3, h4, h5, h6 {
font-size: 1.2rem;
}
}
h1, h2, h3, h4, h5, h6 {
margin-bottom: 1.5rem;
font-weight: 400;
line-height: 1.5;
}
p {
margin-bottom: 1.5rem;
line-height: 1.8;
}
pre {
padding: 1rem 0.5rem;
margin: 1rem 0;
font-weight: 500;
line-height: 1.5;
white-space: pre;
word-wrap: normal;
overflow-x: auto;
background-color: #272822;
color: #f8f8f2;
border-radius: 5px;
> code {
border: none;
background-color: transparent;
margin: 0;
padding: 0;
}
}
code {
font-weight: 500;
border: none;
margin: 0;
background: #5d5e59;
padding: 0.2rem 0.5rem;
border-radius: 5px;
color: #f8f8f2;
display: inline-block;
margin: 0.3rem 0.2rem 0.3rem 0;
}
blockquote {
padding: 0.5625rem 1.25rem 0 1.1875rem;
border-left: 1px solid #cacaca;
color: #8a8a8a;
}
ol, ul, dl {
padding-left: 1.2rem;
li {
margin-bottom: 0.3rem;
}
}
}
================================================
FILE: app/assets/stylesheets/like_and_weixin.scss
================================================
.like-button {
color: #eaa296;
background-color: transparent;
border: 1px solid #e79385;
border-radius: 10rem;
padding: 0.5rem 1rem;
&:hover {
color: #e07662;
border-color: #e27d6b;
}
&:focus {
outline-style: none;
}
span {
margin-left: 0.23rem;
}
}
.like-button.liked {
background-color: #EAA296;
color: #FFF;
&:focus {
outline-style: none;
}
}
================================================
FILE: app/assets/stylesheets/new_year.scss
================================================
.new-year {
margin: 1rem 0;
text-align: center;
font-size: 1.1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.3);
strong {
color: white;
}
.close {
color: white;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
================================================
FILE: app/assets/stylesheets/qrcodes.scss
================================================
#image-tag {
float: right;
width: 200px;
margin-bottom: 1rem;
}
================================================
FILE: app/channels/application_cable/channel.rb
================================================
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
================================================
FILE: app/channels/application_cable/connection.rb
================================================
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
end
================================================
FILE: app/controllers/admin/accounts_controller.rb
================================================
class Admin::AccountsController < Admin::BaseController
def edit
end
def update
if current_admin.authenticate(params.require(:administrator)[:current_password])
if current_admin.update(admin_params)
admin_sign_out
redirect_to admin_login_path, notice: 'Account has been updated, please log in again'
else
render 'edit'
end
else
flash.now[:alert] = 'Old password is wrong, try again'
render 'edit'
end
end
private
def admin_params
params.require(:administrator).permit(:name, :password, :password_confirmation)
end
end
================================================
FILE: app/controllers/admin/all_comments_controller.rb
================================================
class Admin::AllCommentsController < Admin::BaseController
def index
@comments = Comment.order(created_at: :desc).page(params[:page]).per(25)
end
def destroy
comment = Comment.find(params[:id])
if comment.destroy
flash[:notice] = '删除评论成功'
redirect_to admin_all_comments_path
else
flash[:alert] = '删除失败'
redirect_to admin_all_comments_path
end
end
end
================================================
FILE: app/controllers/admin/base_controller.rb
================================================
class Admin::BaseController < ActionController::Base
layout 'admin'
protect_from_forgery with: :exception
before_action :authenticate_admin!
helper_method :current_admin
private
def authenticate_admin!
if current_admin.blank?
redirect_to admin_login_path
return
end
if current_admin.password_digest != session[:current_admin_token]
redirect_to admin_login_path, alert: 'Password was changed, please log in again'
return
end
end
def current_admin
@_current_admin ||= session[:current_admin_id] && Administrator.find_by(id: session[:current_admin_id])
end
def admin_sign_in(admin)
session[:current_admin_id] = admin.id
session[:current_admin_token] = admin.password_digest
end
def admin_sign_out
session[:current_admin_id] = nil
session[:current_admin_token] = nil
@_current_admin = nil
end
end
================================================
FILE: app/controllers/admin/comments_controller.rb
================================================
class Admin::CommentsController < Admin::BaseController
before_action do
@post = Post.find( params[:post_id] )
end
def index
@comments = @post.comments.order(created_at: :desc)
end
def destroy
comment = @post.comments.find(params[:id])
if comment.destroy
flash[:notice] = '删除评论成功'
redirect_to admin_post_comments_path(@post)
else
flash[:alert] = '删除失败'
redirect_to admin_post_comments_path(@post)
end
end
end
================================================
FILE: app/controllers/admin/dashboard_controller.rb
================================================
class Admin::DashboardController < Admin::BaseController
def index
@posts_count = Post.all.count
@comments_count = Comment.all.count
end
end
================================================
FILE: app/controllers/admin/labels_controller.rb
================================================
class Admin::LabelsController < Admin::BaseController
def index
@labels = Label.all.page(params[:page])
end
def new
@label = Label.new
end
def edit
@label = Label.find(params[:id])
end
def create
@label = Label.new(label_params)
if @label.save
redirect_to admin_labels_path, notice: '创建成功'
else
render :new
end
end
def update
@label = Label.find(params[:id])
@label.update!(label_params)
redirect_to admin_labels_path, notice: '更新成功'
end
def destroy
@label = Label.find(params[:id])
@label.destroy!
redirect_to admin_labels_path, notice: '删除成功'
end
private
def label_params
params.require(:label).permit(:name)
end
end
================================================
FILE: app/controllers/admin/posts_controller.rb
================================================
class Admin::PostsController < Admin::BaseController
def new
@post = Post.new
end
def edit
@post = Post.find( params[:id] )
end
def destroy
@post = Post.find( params[:id] )
if @post.destroy
flash[:notice] = '删除博客成功'
redirect_to admin_posts_path
else
flash[:error] = '删除博客失败'
redirect_to admin_posts_path
end
end
def index
@posts = Post.order(created_at: :desc).page(params[:page]).per(25)
end
def create
@post = Post.new( post_params )
if @post.save
flash[:notice] = '创建博客成功'
redirect_to admin_posts_path
else
flash.now[:error] = '创建失败'
render :new, status: 422
end
end
def update
@post = Post.find( params[:id] )
if @post.update( post_params )
flash[:notice] = '更新博客成功'
redirect_to admin_posts_path
else
flash[:error] = '更新博客失败'
render :edit
end
end
def preview
render plain: Post.render_html(params[:content] || "")
end
private
def post_params
params.require(:post).permit(:title, :content, label_ids: [])
end
end
================================================
FILE: app/controllers/admin/sessions_controller.rb
================================================
class Admin::SessionsController < Admin::BaseController
skip_before_action :authenticate_admin!, only: [:new, :create]
before_action do
@full_render = true
end
def new
end
def create
admin = Administrator.find_by(name: params[:name])
if admin && admin.authenticate(params[:password])
admin_sign_in(admin)
redirect_to admin_root_path
else
flash.now[:alert] = 'Username or password is wrong'
render 'new'
end
end
def destroy
admin_sign_out
redirect_to admin_login_path
end
end
================================================
FILE: app/controllers/application_controller.rb
================================================
class ApplicationController < ActionController::Base
end
================================================
FILE: app/controllers/archives_controller.rb
================================================
class ArchivesController < ApplicationController
def index
if (@q = params[:q]).blank?
@posts = Post.order(created_at: :desc).page(params[:page])
else
@q_size = Post.where('title like ?', "%#{@q}%").size
@posts = Post.where('title like ?', "%#{@q}%").order(created_at: :desc).page(params[:page])
end
end
end
================================================
FILE: app/controllers/blogs_controller.rb
================================================
class BlogsController < ApplicationController
def show
cookies[:cable_id] = SecureRandom.uuid
@post = Post.find(params[:id])
@post.visited
@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
end
def edit
@post = Post.find(params[:id])
redirect_to edit_admin_post_path(@post)
end
end
================================================
FILE: app/controllers/comments_controller.rb
================================================
class CommentsController < ApplicationController
layout false
def create
cookies[:name] = comment_params[:name]
cookies[:email] = comment_params[:email]
@post = Post.find( params[:blog_id] )
@comments = @post.comments.order(created_at: :desc)
# # 某些原因暂时关闭评论
flash.now[:notice] = '评论功能未开放'
return
@comment = @post.comments.build(comment_params)
if @comment.save
flash.now[:notice] = '发表成功'
# 重置评论
@comment = Comment.new
end
end
def refresh
@post = Post.find(params[:blog_id])
@comments = @post.comments.order(created_at: :desc)
end
private
def comment_params
params.require(:comment).permit(:content, :name, :email)
end
end
================================================
FILE: app/controllers/concerns/.keep
================================================
================================================
FILE: app/controllers/home_controller.rb
================================================
class HomeController < ApplicationController
def index
@newest = Post.order(created_at: :desc).first
@recent = Post.order(created_at: :desc).to_a[1..3]
end
def about
end
end
================================================
FILE: app/controllers/likes_controller.rb
================================================
class LikesController < ApplicationController
layout false
def index
post = Post.find( params[:blog_id] )
render :json=> { success: true, count: post.liked_count }
end
def create
post = Post.find( params[:blog_id] )
like = post.likes.build
if like.save
render :json=> { success: true, id: like.id.to_s, count: post.liked_count }
else
render :json=> { success: false, count: post.liked_count }
end
end
def destroy
post = Post.find( params[:blog_id] )
like = post.likes.find(params[:id])
if like.destroy
render :json=> { success: true, count: post.reload.liked_count }
else
render :json=> { success: false, count: post.reload.liked_count }
end
end
end
================================================
FILE: app/controllers/photos_controller.rb
================================================
class PhotosController < ApplicationController
def create
@photo = Photo.new(image: params["Filedata"])
@photo.save!
render plain: md_url(@photo.image.url)
end
private
def md_url(url)
""
end
end
================================================
FILE: app/helpers/application_helper.rb
================================================
module ApplicationHelper
# Generate `{controller}-{action}-page` class for body element
def body_class
path = controller_path.tr('/_', '-')
action_name_map = {
index: 'index',
new: 'edit',
edit: 'edit',
update: 'edit',
patch: 'edit',
create: 'edit',
destory: 'index'
}
mapped_action_name = action_name_map[action_name.to_sym] || action_name
body_class_page =
if controller.is_a?(HighVoltage::StaticPage) && params.key?(:id) && params[:id] !~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
id_name = params[:id].tr('_', '-') + '-page'
format('%s-%s', 'pages', id_name)
else
format('%s-%s-page', path, mapped_action_name)
end
body_class_page
end
# Admin active for helper
def admin_active_for(controller_name, navbar_name)
if controller_name.to_s == admin_root_path
return controller_name.to_s == navbar_name.to_s ? "active" : ""
end
navbar_name.to_s.include?(controller_name.to_s) ? 'active' : ''
end
def current_path
request.env['PATH_INFO']
end
def flash_class(level)
case level
when 'notice', 'success' then 'alert alert-success alert-dismissible'
when 'info' then 'alert alert-info alert-dismissible'
when 'warning' then 'alert alert-warning alert-dismissible'
when 'alert', 'error' then 'alert alert-danger alert-dismissible'
end
end
def format_time(time)
time.strftime("%Y-%m-%d %H:%M")
end
def format_date(time)
time.strftime("%Y.%m.%d")
end
def search_highlight(title, q)
return title if q.blank?
title.sub(q, "<em>#{q}</em>")
end
end
================================================
FILE: app/javascript/about.js
================================================
import './libs/ddscrollspy'
$(document).on('turbo:load', function(){
if($('.home-about-page').length === 0) { return; }
$(window).scroll(function(){
if($(this).scrollTop() > 0)
$('.top-bar-wrapper').addClass('active')
else
$('.top-bar-wrapper').removeClass('active')
})
$('#about-top-bar').ddscrollSpy({highlightclass: 'active'})
$('#about-anchor-link').click(function(e){
e.preventDefault()
$('html, body').animate({
scrollTop: $('#about').offset().top
},500)
})
})
================================================
FILE: app/javascript/admin/posts.js
================================================
$(document).on('turbo:load', function(){
$('a#upload_photo').click(function(){
$('input[type=file]').show().focus().click().hide()
return false
})
var opt = {
type: 'POST',
url: "/photos",
success: function(data,status,xhr){
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.focus()
}
}
$('input[type=file]').fileUpload(opt)
$('#preview-link').on('show.bs.tab', function(e){
$('#preview').text('Loading...')
$.ajax({
url: '/admin/posts/preview',
type: 'POST',
data: { content: $('#content-input').val() },
success: function(data){
$('#preview').html(data)
}
})
})
})
================================================
FILE: app/javascript/admin/sidebar.js
================================================
// (function() {
// window.App = window.App || {}
// window.App.adminSidebar = {
// saveSidebarScrollPosition: function() {
// var sidebar = this.page().find('.sidebar');
// var sidebarScrollTop = sidebar.scrollTop();
// localStorage.setItem('admin-SidebarScrollTop', sidebarScrollTop);
// },
// restoreSidebarScrollPosition: function() {
// var sidebar = this.page().find('.sidebar');
// var sidebarScrollTop = localStorage.getItem('admin-SidebarScrollTop');
// sidebar.scrollTop(sidebarScrollTop);
// },
// clearSidebarScrollPosition: function() {
// localStorage.setItem('admin-SidebarScrollTop', 0);
// },
// page: function() {
// return $('.admin-page');
// }
// };
// }).call(this);
// $(document).on('turbo:load', function() {
// var component = $('.admin-page');
// if (component.length > 0) {
// App.adminSidebar.restoreSidebarScrollPosition();
// }
// });
// $(document).on('turbo:before-render', function() {
// var component = $('.admin-page');
// if (component.length > 0) {
// App.adminSidebar.saveSidebarScrollPosition();
// } else {
// App.adminSidebar.clearSidebarScrollPosition();
// }
// });
================================================
FILE: app/javascript/admin.js
================================================
import './base'
import './libs/jquery.html5-fileupload'
import './libs/jquery.atwho'
import 'admin-lte'
import './admin/sidebar'
import './admin/posts'
================================================
FILE: app/javascript/application.js
================================================
// Entry point for the build script in your package.json
//
import './base'
import './about'
================================================
FILE: app/javascript/base.js
================================================
// base dependency library, it should be only shared by `admin.js` and `application.js`.
//
import './libs/add_jquery'
import 'bootstrap/dist/js/bootstrap'
import RailsUjs from '@rails/ujs'
import "@hotwired/turbo-rails"
import * as ActiveStorage from '@rails/activestorage'
// Turbo.session.drive = false
RailsUjs.start()
ActiveStorage.start()
import './channels'
import "./controllers"
$(document).on('turbo:load', function(){
$('[data-toggle="tooltip"]').tooltip()
})
================================================
FILE: app/javascript/channels/consumer.js
================================================
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.
import { createConsumer } from "@rails/actioncable"
export default createConsumer()
================================================
FILE: app/javascript/channels/index.js
================================================
// Load all the channels within this directory and all subdirectories.
// Channel files must be named *_channel.js.
================================================
FILE: app/javascript/controllers/admin_label_controller.js
================================================
import { Controller } from "@hotwired/stimulus"
import select2 from 'select2'
window.select2 = select2();
export default class extends Controller {
static targets = [ 'label' ]
connect() {
$(this.labelTarget).select2({
multiple: true,
tags: false,
})
}
disconnected() {
$(this.labelTarget).select2('destroy')
}
}
================================================
FILE: app/javascript/controllers/index.js
================================================
// Load all the controllers within this directory and all subdirectories.
// Controller files must be named *_controller.js or *_controller.ts.
import { Application } from "@hotwired/stimulus"
const application = Application.start()
import LikeController from "./like_controller"
application.register("like", LikeController)
import QrcodeController from "./qrcode_controller"
application.register("qrcode", QrcodeController)
import AdminLabelController from "./admin_label_controller"
application.register("admin-label", AdminLabelController)
================================================
FILE: app/javascript/controllers/like_controller.js
================================================
import { Controller } from "@hotwired/stimulus"
import Cookies from 'js-cookie'
export default class extends Controller {
static targets = [ 'button' ]
toggle(e) {
let button = $(this.buttonTarget)
if( button.hasClass('liked') ){
$.ajax({
url: button.data('url') + '/' + Cookies.get('like'),
type: 'DELETE',
success: function(res){
button.removeClass('liked')
button.children('.count').text(res.count)
Cookies.remove('like')
}
})
}else{
$.ajax({
url: button.data('url'),
type: 'POST',
success: function(res){
button.addClass('liked')
button.children('.count').text(res.count)
Cookies.set('like', res.id)
}
})
}
}
}
================================================
FILE: app/javascript/controllers/qrcode_controller.js
================================================
import { Controller } from "@hotwired/stimulus"
import '../libs/qrcode'
export default class extends Controller {
static targets = [ 'wrapper' ]
connect() {
}
greet(e) {
e.preventDefault()
$('#image-tag').empty()
new QRCode( $('#image-tag')[0], $('#image-tag').data('url') )
$(this.wrapperTarget).toggle()
}
}
================================================
FILE: app/javascript/ga.js.erb
================================================
<% if ENV['GA'].present? %>
var script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=<%= ENV['GA'] %>';
window.document.getElementsByTagName('head')[0].appendChild(script);
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
document.addEventListener('turbo:load', function(event){
if (typeof gtag === 'function') {
if(typeof gon !== 'undefined' && gon.user_id){
gtag('set', { 'user_id': gon.user_id })
}
gtag('config', '<%= ENV['GA'] %>', {
'page_location': event.data.url
});
}
});
<% end %>
================================================
FILE: app/javascript/libs/add_jquery.js
================================================
import $ from 'jquery'
window.jQuery = $
window.$ = $
================================================
FILE: app/javascript/libs/ddscrollspy.js
================================================
/*
* DD ScrollSpy Menu Script (c) Dynamic Drive (www.dynamicdrive.com)
* Last updated: Aug 1st, 14'
* Visit http://www.dynamicdrive.com/ for this script and 100s more.
*/
// Aug 1st, 14': Updated to v1.2, which supports showing a progress bar inside each menu item (except in iOS devices). Other minor improvements.
if (!Array.prototype.filter){
Array.prototype.filter = function(fun /*, thisp */){
"use strict";
if (this == null)
throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun != "function")
throw new TypeError();
var res = [];
var thisp = arguments[1];
for (var i = 0; i < len; i++){
if (i in t){
var val = t[i]; // in case fun mutates this
if (fun.call(thisp, val, i, t))
res.push(val);
}
}
return res;
};
}
(function($){
var defaults = {
spytarget: window,
scrolltopoffset: 0,
scrollbehavior: 'smooth',
scrollduration: 500,
highlightclass: 'selected',
enableprogress: '',
mincontentheight: 30
}
var isiOS = /iPhone|iPad|iPod/i.test(navigator.userAgent) // detect iOS devices
function inrange(el, range, field){ // check if "playing field" is inside range
var rangespan = range[1]-range[0], fieldspan = field[1]-field[0]
if ( (range[0]-field[0]) >= 0 && (range[0]-field[0]) < fieldspan ){ // if top of range is on field
return true
}
else{
if ( (range[0]-field[0]) <= 0 && (range[0]+rangespan) > field[0] ){ // if part of range overlaps field
return true
}
}
return false
}
$.fn.ddscrollSpy = function(options){
var $window = $(window)
var $body=(window.opera)? (document.compatMode=="CSS1Compat"? $('html') : $('body')) : $('html,body')
return this.each(function(){
var o = $.extend({}, defaults, options)
o.enableprogress = (isiOS)? '' : o.enableprogress // disable enableprogress in iOS
var targets = [], curtarget = ''
var cantscrollpastindex = -1 // index of target content that can't be scrolled past completely when scrollbar is at the end of the doc
var $spytarget = $( o.spytarget ).eq(0)
var spyheight = $spytarget.outerHeight()
var spyscrollheight = (o.spytarget == window)? $body.get(0).scrollHeight : $spytarget.get(0).scrollHeight
var $menu = $(this)
var totaltargetsheight = 0 // total height of target contents
function spyonmenuitems($menu){
var $menuitems = $menu.find('a[href^="#"]')
targets = []
curtarget = ''
totaltargetsheight = 0
$menuitems.each(function(i){
var $item = $(this)
var $target = $( $item.attr('href') )
var target = $target.get(0)
var $progress = null // progress DIV that gets dynamically added inside menu A element if o.enableprogress enabled
if ($target.length == 0) // if no matching links found
return true
$item
.off('click.goto')
.on('click.goto', function(e){
if ( o.spytarget == window && (o.scrollbehavior == 'jump' || !history.pushState))
window.location.hash = $item.attr('href')
if (o.scrollbehavior == 'smooth' || o.scrolltopoffset !=0){
var $scrollparent = (o.spytarget == window)? $body : $spytarget
var addoffset = 1 // add 1 pixel to scrollTop when scrolling to an element to make sure the browser always returns the correct target element (strange bug)
if (o.scrollbehavior == 'smooth' && (history.pushState || o.spytarget != window)){
$scrollparent.animate( {scrollTop: targets[i].offsettop + addoffset}, o.scrollduration, function(){
if (o.spytarget == window && history.pushState){
//history.pushState(null, null, $item.attr('href'))
}
})
}
else{
$scrollparent.prop('scrollTop', targets[i].offsettop + addoffset)
}
e.preventDefault()
}
})
if (o.enableprogress){ // if o.enableprogress enabled
if ($item.find('div.' + o.enableprogress).length == 0){ //if no progress DIV found inside menu item
$item.css({position: 'relative', overflow: 'hidden'}) // add some required style to parent A element
$('<div class="' + o.enableprogress + '" style="position:absolute; left: -100%" />').appendTo($item)
}
$progress = $item.find('div.' + o.enableprogress)
}
var targetoffset = (o.spytarget == window)? $target.offset().top : (target.offsetParent == o.spytarget)? target.offsetTop : target.offsetTop - o.spytarget.offsetTop
targetoffset += o.scrolltopoffset
var targetheight = ( parseInt($target.data('spyrange')) > 0 )? parseInt($target.data('spyrange')) : ( $target.outerHeight() || o.mincontentheight)
var offsetbottom = targetoffset + targetheight
if (cantscrollpastindex == -1 && offsetbottom > (spyscrollheight - spyheight)){ // determine index of first target which can't be scrolled past
cantscrollpastindex = i
}
targets.push( {$menuitem: $item, $des: $target, offsettop: targetoffset, height: targetheight, $progress: $progress, index: i} )
})
if (targets.length > 0)
totaltargetsheight = targets[targets.length-1].offsettop + targets[targets.length-1].height
}
function highlightitem(){
if (targets.length == 0)
return
var prevtarget = curtarget
var scrolltop = $spytarget.scrollTop()
var cantscrollpasttarget = false
var shortlist = targets.filter(function(el, index){ // filter target elements that are currently visible on screen
return inrange(el, [el.offsettop, el.offsettop + el.height], [scrolltop, scrolltop + spyheight])
})
if (shortlist.length > 0){
curtarget = shortlist.shift() // select the first element that's visible on screen
if (prevtarget && prevtarget != curtarget)
prevtarget.$menuitem.removeClass(o.highlightclass)
if (!curtarget.$menuitem.hasClass(o.highlightclass)) // if there was a previously selected menu link and it's not the same as current
curtarget.$menuitem.addClass(o.highlightclass) // highlight its menu item
if (curtarget.index >= cantscrollpastindex && scrolltop >= (spyscrollheight - spyheight)){ // if we're at target that can't be scrolled past and we're at end of document
if (o.enableprogress){ // if o.enableprogress enabled
for (var i=0; i<targets.length; i++){ // highlight everything
targets[i].$menuitem.find('div.' + o.enableprogress).css('left', 0)
}
}
curtarget.$menuitem.removeClass(o.highlightclass)
curtarget = targets[targets.length-1]
if (!curtarget.$menuitem.hasClass(o.highlightclass))
curtarget.$menuitem.addClass(o.highlightclass)
return
}
if (o.enableprogress){ // if o.enableprogress enabled
var scrollpct = ((scrolltop-curtarget.offsettop) / curtarget.height) * 100
curtarget.$menuitem.find('div.' + o.enableprogress).css('left', -100 + scrollpct + '%')
for (var i=0; i<targets.length; i++){
if (i < curtarget.index){
targets[i].$menuitem.find('div.' + o.enableprogress).css('left', 0)
}
else if (i > curtarget.index){
targets[i].$menuitem.find('div.' + o.enableprogress).css('left', '-100%')
}
}
}
}
else if (scrolltop > totaltargetsheight){ // if no target content visible on screen but scroll bar has scrolled past very last content already
if (o.enableprogress){ // if o.enableprogress enabled
curtarget.$menuitem.removeClass(o.highlightclass)
for (var i=0; i<targets.length; i++){
targets[i].$menuitem.find('div.' + o.enableprogress).css('left', 0)
}
}
}
}
function updatetargetpos(){
if (targets.length == 0)
return
var $menu = targets[0].$menu
spyheight = $spytarget.outerHeight()
spyscrollheight = (o.spytarget == window)? $body.get(0).scrollHeight : $spytarget.get(0).scrollHeight
totaltargetsheight = 0
cantscrollpastindex = -1
for (var i = 0; i < targets.length; i++){
var $target = targets[i].$des
var target = $target.get(0)
var targetoffset = (o.spytarget == window)? $target.offset().top : (target.offsetParent == o.spytarget)? target.offsetTop : target.offsetTop - o.spytarget.offsetTop
targetoffset += o.scrolltopoffset
targets[i].offsettop = targetoffset
targets[i].height = ( parseInt($target.data('spyrange')) > 0 )? parseInt($target.data('spyrange')) : ( $target.outerHeight() || o.mincontentheight)
if (o.enableprogress){ // if o.enableprogress enabled
var offsetbottom = targetoffset + targets[i].height // recalculate cantscrollpastindex
if (cantscrollpastindex == -1 && offsetbottom > (spyscrollheight - spyheight)){
cantscrollpastindex = i
}
}
}
totaltargetsheight = targets[targets.length-1].offsettop + targets[targets.length-1].height
}
spyonmenuitems($menu)
$menu.on('updatespy', function(){
spyonmenuitems($menu)
highlightitem()
})
$spytarget.on('scroll resize', function(){
highlightitem()
})
highlightitem()
$window.on('load resize', function(){
updatetargetpos()
})
}) // end return
}
})(jQuery);
================================================
FILE: app/javascript/libs/jquery.atwho.js
================================================
//
/*
Implement Github like autocomplete mentions
http://ichord.github.com/At.js
Copyright (c) 2013 chord.luo@gmail.com
Licensed under the MIT license.
*/
(function() {
(function(factory) {
if (typeof define === 'function' && define.amd) {
return define(['jquery'], factory);
} else {
return factory(window.jQuery);
}
})(function($) {
"use strict";
var Caret, Mirror, methods, pluginName;
pluginName = 'caret';
Caret = (function() {
function Caret($inputor) {
this.$inputor = $inputor;
this.domInputor = this.$inputor[0];
}
Caret.prototype.getPos = function() {
var end, endRange, inputor, len, normalizedValue, pos, range, start, textInputRange;
inputor = this.domInputor;
inputor.focus();
if (document.selection) {
/*
#assume we select "HATE" in the inputor such as textarea -> { }.
* start end-point.
* /
* < I really [HATE] IE > between the brackets is the selection range.
* \
* end end-point.
*/
range = document.selection.createRange();
pos = 0;
if (range && range.parentElement() === inputor) {
normalizedValue = inputor.value.replace(/\r\n/g, "\n");
/* SOMETIME !!!
"/r/n" is counted as two char.
one line is two, two will be four. balalala.
so we have to using the normalized one's length.;
*/
len = normalizedValue.length;
/*
<[ I really HATE IE ]>:
the whole content in the inputor will be the textInputRange.
*/
textInputRange = inputor.createTextRange();
/* _here must be the position of bookmark.
/
<[ I really [HATE] IE ]>
[---------->[ ] : this is what moveToBookmark do.
< I really [[HATE] IE ]> : here is result.
\ two brackets in should be in line.
*/
textInputRange.moveToBookmark(range.getBookmark());
endRange = inputor.createTextRange();
/* [--------------------->[] : if set false all end-point goto end.
< I really [[HATE] IE []]>
*/
endRange.collapse(false);
/*
___VS____
/ \
< I really [[HATE] IE []]>
\_endRange end-point.
" > -1" mean the start end-point will be the same or right to the end end-point
* simplelly, all in the end.
*/
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
start = end = len;
} else {
/*
I really |HATE] IE ]>
<-|
I really[ [HATE] IE ]>
<-[
I reall[y [HATE] IE ]>
will return how many unit have moved.
*/
start = -textInputRange.moveStart("character", -len);
end = -textInputRange.moveEnd("character", -len);
}
}
} else {
start = inputor.selectionStart;
}
return start;
};
Caret.prototype.setPos = function(pos) {
var inputor, range;
inputor = this.domInputor;
if (document.selection) {
range = inputor.createTextRange();
range.move("character", pos);
return range.select();
} else {
return inputor.setSelectionRange(pos, pos);
}
};
Caret.prototype.getPosition = function(pos) {
var $inputor, at_rect, format, h, html, mirror, start_range, x, y;
$inputor = this.$inputor;
format = function(value) {
return value.replace(/</g, '<').replace(/>/g, '>').replace(/`/g, '`').replace(/"/g, '"').replace(/\r\n|\r|\n/g, "<br />");
};
if (pos === void 0) {
pos = this.getPos();
}
start_range = $inputor.val().slice(0, pos);
html = "<span>" + format(start_range) + "</span>";
html += "<span id='caret'>|</span>";
mirror = new Mirror($inputor);
at_rect = mirror.create(html).rect();
x = at_rect.left - $inputor.scrollLeft();
y = at_rect.top - $inputor.scrollTop();
h = at_rect.height;
return {
left: x,
top: y,
height: h
};
};
Caret.prototype.getOffset = function(pos) {
var $inputor, h, offset, position, x, y;
$inputor = this.$inputor;
offset = $inputor.offset();
position = this.getPosition(pos);
x = offset.left + position.left;
y = offset.top + position.top;
h = position.height;
return {
left: x,
top: y,
height: h
};
};
Caret.prototype.getIEPosition = function(pos) {
var h, inputorOffset, offset, x, y;
offset = this.getIEOffset(pos);
inputorOffset = this.$inputor.offset();
x = offset.left - inputorOffset.left;
y = offset.top - inputorOffset.top;
h = offset.height;
return {
left: x,
top: y,
height: h
};
};
Caret.prototype.getIEOffset = function(pos) {
var h, range, x, y;
range = this.domInputor.createTextRange();
if (pos) {
range.move('character', pos);
}
x = range.boundingLeft + $inputor.scrollLeft();
y = range.boundingTop + $(window).scrollTop() + $inputor.scrollTop();
h = range.boundingHeight;
return {
left: x,
top: y,
height: h
};
};
return Caret;
})();
Mirror = (function() {
Mirror.prototype.css_attr = ["overflowY", "height", "width", "paddingTop", "paddingLeft", "paddingRight", "paddingBottom", "marginTop", "marginLeft", "marginRight", "marginBottom", "fontFamily", "borderStyle", "borderWidth", "wordWrap", "fontSize", "lineHeight", "overflowX", "text-align"];
function Mirror($inputor) {
this.$inputor = $inputor;
}
Mirror.prototype.mirrorCss = function() {
var css,
_this = this;
css = {
position: 'absolute',
left: -9999,
top: 0,
zIndex: -20000,
'white-space': 'pre-wrap'
};
$.each(this.css_attr, function(i, p) {
return css[p] = _this.$inputor.css(p);
});
return css;
};
Mirror.prototype.create = function(html) {
this.$mirror = $('<div></div>');
this.$mirror.css(this.mirrorCss());
this.$mirror.html(html);
this.$inputor.after(this.$mirror);
return this;
};
Mirror.prototype.rect = function() {
var $flag, pos, rect;
$flag = this.$mirror.find("#caret");
pos = $flag.position();
rect = {
left: pos.left,
top: pos.top,
height: $flag.height()
};
this.$mirror.remove();
return rect;
};
return Mirror;
})();
methods = {
pos: function(pos) {
if (pos) {
return this.setPos(pos);
} else {
return this.getPos();
}
},
position: function(pos) {
if (document.selection) {
return this.getIEPosition(pos);
} else {
return this.getPosition(pos);
}
},
offset: function(pos) {
if (document.selection) {
return this.getIEOffset(pos);
} else {
return this.getOffset(pos);
}
}
};
return $.fn.caret = function(method) {
var caret;
caret = new Caret(this);
if (methods[method]) {
return methods[method].apply(caret, Array.prototype.slice.call(arguments, 1));
} else {
return $.error("Method " + method + " does not exist on jQuery.caret");
}
};
});
}).call(this);
/*
Implement Github like autocomplete mentions
http://ichord.github.com/At.js
Copyright (c) 2013 chord.luo@gmail.com
Licensed under the MIT license.
*/
(function() {
var __slice = [].slice;
(function(factory) {
if (typeof define === 'function' && define.amd) {
return define(['jquery'], factory);
} else {
return factory(window.jQuery);
}
})(function($) {
var $CONTAINER, Api, App, Controller, DEFAULT_CALLBACKS, DEFAULT_TPL, KEY_CODE, Model, View;
App = (function() {
function App(inputor) {
this.current_flag = null;
this.controllers = {};
this.$inputor = $(inputor);
this.listen();
}
App.prototype.controller = function(key) {
return this.controllers[key || this.current_flag];
};
App.prototype.set_context_for = function(key) {
this.current_flag = key;
return this;
};
App.prototype.reg = function(flag, setting) {
var controller, _base;
controller = (_base = this.controllers)[flag] || (_base[flag] = new Controller(this, flag));
if (setting.alias) {
this.controllers[setting.alias] = controller;
}
controller.init(setting);
return this;
};
App.prototype.listen = function() {
var _this = this;
return this.$inputor.on('keyup.atwho', function(e) {
return _this.on_keyup(e);
}).on('keydown.atwho', function(e) {
return _this.on_keydown(e);
}).on('scroll.atwho', function(e) {
var _ref;
return (_ref = _this.controller()) != null ? _ref.view.hide() : void 0;
}).on('blur.atwho', function(e) {
var c;
if (c = _this.controller()) {
return c.view.hide(c.get_opt("display_timeout"));
}
});
};
App.prototype.dispatch = function() {
var _this = this;
return $.map(this.controllers, function(c) {
if (c.look_up()) {
return _this.set_context_for(c.key);
}
});
};
App.prototype.on_keyup = function(e) {
var _ref;
switch (e.keyCode) {
case KEY_CODE.ESC:
e.preventDefault();
if ((_ref = this.controller()) != null) {
_ref.view.hide();
}
break;
case KEY_CODE.DOWN:
case KEY_CODE.UP:
$.noop();
break;
default:
this.dispatch();
}
};
App.prototype.on_keydown = function(e) {
var view, _ref;
view = (_ref = this.controller()) != null ? _ref.view : void 0;
if (!(view && view.visible())) {
return;
}
switch (e.keyCode) {
case KEY_CODE.ESC:
e.preventDefault();
view.hide();
break;
case KEY_CODE.UP:
e.preventDefault();
view.prev();
break;
case KEY_CODE.DOWN:
e.preventDefault();
view.next();
break;
case KEY_CODE.TAB:
case KEY_CODE.ENTER:
if (!view.visible()) {
return;
}
e.preventDefault();
view.choose();
break;
default:
$.noop();
}
};
return App;
})();
Controller = (function() {
var uuid, _uuid;
_uuid = 0;
uuid = function() {
return _uuid += 1;
};
function Controller(app, key) {
this.app = app;
this.key = key;
this.$inputor = this.app.$inputor;
this.id = this.$inputor[0].id || uuid();
this.setting = null;
this.query = null;
this.pos = 0;
$CONTAINER.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>"));
this.model = new Model(this);
this.view = new View(this);
}
Controller.prototype.init = function(setting) {
this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
return this.model.reload(this.setting.data);
};
Controller.prototype.call_default = function() {
var args, func_name;
func_name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
try {
return DEFAULT_CALLBACKS[func_name].apply(this, args);
} catch (error) {
return $.error("" + error + " Or maybe At.js doesn't have function " + func_name);
}
};
Controller.prototype.trigger = function(name, data) {
var alias, event_name;
data.push(this);
alias = this.get_opt('alias');
event_name = alias ? "" + name + "-" + alias + ".atwho" : "" + name + ".atwho";
return this.$inputor.trigger(event_name, data);
};
Controller.prototype.callbacks = function(func_name) {
return this.get_opt("callbacks")[func_name] || DEFAULT_CALLBACKS[func_name];
};
Controller.prototype.get_opt = function(key, default_value) {
try {
return this.setting[key];
} catch (e) {
return null;
}
};
Controller.prototype.catch_query = function() {
var caret_pos, content, end, query, start, subtext;
content = this.$inputor.val();
caret_pos = this.$inputor.caret('pos');
subtext = content.slice(0, caret_pos);
query = this.callbacks("matcher").call(this, this.key, subtext, this.get_opt('start_with_space'));
if (typeof query === "string" && query.length <= this.get_opt('max_len', 20)) {
start = caret_pos - query.length;
end = start + query.length;
this.pos = start;
query = {
'text': query.toLowerCase(),
'head_pos': start,
'end_pos': end
};
this.trigger("matched", [this.key, query.text]);
} else {
this.view.hide();
}
return this.query = query;
};
Controller.prototype.rect = function() {
var c, scale_bottom;
c = this.$inputor.caret('offset', this.pos - 1);
scale_bottom = document.selection ? 0 : 2;
return {
left: c.left,
top: c.top,
bottom: c.top + c.height + scale_bottom
};
};
Controller.prototype.insert = function(str) {
var $inputor, source, start_str, text;
$inputor = this.$inputor;
str = '' + str;
source = $inputor.val();
start_str = source.slice(0, this.query['head_pos'] || 0);
text = "" + start_str + str + " " + (source.slice(this.query['end_pos'] || 0));
$inputor.val(text);
$inputor.caret('pos', start_str.length + str.length + 1);
return $inputor.change();
};
Controller.prototype.render_view = function(data) {
var search_key;
search_key = this.get_opt("search_key");
data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), search_key);
return this.view.render(data.slice(0, this.get_opt('limit')));
};
Controller.prototype.look_up = function() {
var query, _callback;
if (!(query = this.catch_query())) {
return;
}
_callback = function(data) {
if (data && data.length > 0) {
return this.render_view(data);
} else {
return this.view.hide();
}
};
this.model.query(query.text, $.proxy(_callback, this));
return query;
};
return Controller;
})();
Model = (function() {
var _storage;
_storage = {};
function Model(context) {
this.context = context;
this.key = this.context.key;
}
Model.prototype.saved = function() {
return this.fetch() > 0;
};
Model.prototype.query = function(query, callback) {
var data, search_key, _ref;
data = this.fetch();
search_key = this.context.get_opt("search_key");
callback(data = this.context.callbacks('filter').call(this.context, query, data, search_key));
if (!(data && data.length > 0)) {
return (_ref = this.context.callbacks('remote_filter')) != null ? _ref.call(this.context, query, callback) : void 0;
}
};
Model.prototype.fetch = function() {
return _storage[this.key] || [];
};
Model.prototype.save = function(data) {
return _storage[this.key] = this.context.callbacks("before_save").call(this.context, data || []);
};
Model.prototype.load = function(data) {
if (!(this.saved() || !data)) {
return this._load(data);
}
};
Model.prototype.reload = function(data) {
return this._load(data);
};
Model.prototype._load = function(data) {
var _this = this;
if (typeof data === "string") {
return $.ajax(data, {
dataType: "json"
}).done(function(data) {
return _this.save(data);
});
} else {
return this.save(data);
}
};
return Model;
})();
View = (function() {
function View(context) {
this.context = context;
this.key = this.context.key;
this.id = this.context.get_opt("alias") || ("at-view-" + (this.key.charCodeAt(0)));
this.$el = $("<div id='" + this.id + "' class='atwho-view'><ul id='" + this.id + "-ul' class='atwho-view-url'></ul></div>");
this.timeout_id = null;
this.context.$el.append(this.$el);
this.bind_event();
}
View.prototype.bind_event = function() {
var $menu,
_this = this;
$menu = this.$el.find('ul');
return $menu.on('mouseenter.view', 'li', function(e) {
$menu.find('.cur').removeClass('cur');
return $(e.currentTarget).addClass('cur');
}).on('click', function(e) {
_this.choose();
return e.preventDefault();
});
};
View.prototype.visible = function() {
return this.$el.is(":visible");
};
View.prototype.choose = function() {
var $li;
$li = this.$el.find(".cur");
this.context.insert(this.context.callbacks("before_insert").call(this.context, $li.data("value"), $li));
this.context.trigger("inserted", [$li]);
return this.hide();
};
View.prototype.reposition = function() {
var offset, rect;
rect = this.context.rect();
if (rect.bottom + this.$el.height() - $(window).scrollTop() > $(window).height()) {
rect.bottom = rect.top - this.$el.height();
}
offset = {
left: rect.left,
top: rect.bottom
};
this.$el.offset(offset);
return this.context.trigger("reposition", [offset]);
};
View.prototype.next = function() {
var cur, next;
cur = this.$el.find('.cur').removeClass('cur');
next = cur.next();
if (!next.length) {
next = this.$el.find('li:first');
}
return next.addClass('cur');
};
View.prototype.prev = function() {
var cur, prev;
cur = this.$el.find('.cur').removeClass('cur');
prev = cur.prev();
if (!prev.length) {
prev = this.$el.find('li:last');
}
return prev.addClass('cur');
};
View.prototype.show = function() {
if (!this.visible()) {
this.$el.show();
}
return this.reposition();
};
View.prototype.hide = function(time) {
var callback,
_this = this;
if (isNaN(time && this.visible())) {
return this.$el.hide();
} else {
callback = function() {
return _this.hide();
};
clearTimeout(this.timeout_id);
return this.timeout_id = setTimeout(callback, time);
}
};
View.prototype.render = function(list) {
var $li, $ul, item, li, tpl, _i, _len;
if (!$.isArray(list || list.length <= 0)) {
this.hide();
return;
}
this.$el.find('ul').empty();
$ul = this.$el.find('ul');
tpl = this.context.get_opt('tpl', DEFAULT_TPL);
for (_i = 0, _len = list.length; _i < _len; _i++) {
item = list[_i];
li = this.context.callbacks("tpl_eval").call(this.context, tpl, item);
$li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text));
$li.data("atwho-info", item);
$ul.append($li);
}
this.show();
return $ul.find("li:first").addClass("cur");
};
return View;
})();
KEY_CODE = {
DOWN: 40,
UP: 38,
ESC: 27,
TAB: 9,
ENTER: 13
};
DEFAULT_CALLBACKS = {
before_save: function(data) {
var item, _i, _len, _results;
if (!$.isArray(data)) {
return data;
}
_results = [];
for (_i = 0, _len = data.length; _i < _len; _i++) {
item = data[_i];
if ($.isPlainObject(item)) {
_results.push(item);
} else {
_results.push({
name: item
});
}
}
return _results;
},
matcher: function(flag, subtext, should_start_with_space) {
var match, regexp;
flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
if (should_start_with_space) {
flag = '(?:^|\\s)' + flag;
}
regexp = new RegExp(flag + '([A-Za-z0-9_\+\-]*)$|' + flag + '([^\\x00-\\xff]*)$', 'gi');
match = regexp.exec(subtext);
if (match) {
return match[2] || match[1];
} else {
return null;
}
},
filter: function(query, data, search_key) {
var item, _i, _len, _results;
_results = [];
for (_i = 0, _len = data.length; _i < _len; _i++) {
item = data[_i];
if (~item[search_key].toLowerCase().indexOf(query)) {
_results.push(item);
}
}
return _results;
},
remote_filter: null,
sorter: function(query, items, search_key) {
var item, _i, _len, _results;
if (!query) {
return items;
}
_results = [];
for (_i = 0, _len = items.length; _i < _len; _i++) {
item = items[_i];
item.atwho_order = item[search_key].toLowerCase().indexOf(query);
if (item.atwho_order > -1) {
_results.push(item);
}
}
return _results.sort(function(a, b) {
return a.atwho_order - b.atwho_order;
});
},
tpl_eval: function(tpl, map) {
try {
return tpl.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
return map[key];
});
} catch (error) {
return "";
}
},
highlighter: function(li, query) {
var regexp;
if (!query) {
return li;
}
regexp = new RegExp(">\\s*(\\w*)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig');
return li.replace(regexp, function(str, $1, $2, $3) {
return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
});
},
before_insert: function(value, $li) {
return value;
}
};
DEFAULT_TPL = "<li data-value='${name}'>${name}</li>";
Api = {
init: function(options) {
var $this, app;
app = ($this = $(this)).data("atwho");
if (!app) {
$this.data('atwho', (app = new App(this)));
}
return app.reg(options.at, options);
},
load: function(key, data) {
var c;
if (c = this.controller(key)) {
return c.model.load(data);
}
},
run: function() {
return this.dispatch();
}
};
$CONTAINER = $("<div id='atwho-container'></div>");
$.fn.atwho = function(method) {
var _args;
_args = arguments;
$('body').append($CONTAINER);
return this.filter('textarea, input').each(function() {
var app;
if (typeof method === 'object' || !method) {
return Api.init.apply(this, _args);
} else if (Api[method]) {
if (app = $(this).data('atwho')) {
return Api[method].apply(app, Array.prototype.slice.call(_args, 1));
}
} else {
return $.error("Method " + method + " does not exist on jQuery.caret");
}
});
};
return $.fn.atwho["default"] = {
at: void 0,
alias: void 0,
data: null,
tpl: DEFAULT_TPL,
callbacks: DEFAULT_CALLBACKS,
search_key: "name",
limit: 5,
max_len: 20,
start_with_space: true,
display_timeout: 300
};
});
}).call(this);
================================================
FILE: app/javascript/libs/jquery.html5-fileupload.js
================================================
/*
* jQuery HTML5 File Upload
*
* Author: timdream at gmail.com
* Web: http://timc.idv.tw/html5-file-upload/
*
* Ajax File Upload that use real xhr,
* built with getAsBinary, sendAsBinary, FormData, FileReader, ArrayBuffer, BlobBuilder and etc.
* works in Firefox 3, Chrome 5, Safari 5 and higher
*
* Image resizing and uploading currently works in Fx 3 and up, and Chrome 9 (dev) and up only.
* Extra settings will allow current Webkit users to upload the original image
* or send the resized image in base64 form.
*
* Usage:
* $.fileUploadSupported // a boolean value indicates if the browser is supported.
* $.imageUploadSupported // a boolean value indicates if the browser could resize image and upload in binary form.
* $.fileUploadAsBase64Supported // a boolean value indicate if the browser upload files in based64.
* $.imageUploadAsBase64Supported // a boolean value indicate if the browser could resize image and upload in based64.
* $('input[type=file]').fileUpload(ajaxSettings); //Make a input[type=file] select-and-send file upload widget
* $('#any-element').fileUpload(ajaxSettings); //Make a element receive dropped file
* //TBD $('form#fileupload').fileUpload(ajaxSettings); //Send a ajax form with file
* //TBD $('canvas').fileUpload(ajaxSettings); //Upload given canvas as if it's an png image.
*
* ajaxSettings is the object contains $.ajax settings that will be passed to.
* Available extended settings are:
* fileType:
* regexp check against filename extension; You should always checked it again on server-side.
* e.g. /^(gif|jpe?g|png|tiff?)$/i for images
* fileMaxSize:
* Maxium file size allowed in bytes. Use scientific notation for converience.
* e.g. 1E4 for 1KB, 1E8 for 1MB, 1E9 for 10MB.
* If you really care the difference between 1024 and 1000, use Math.pow(2, 10)
* fileError(info, textStatus, textDescription):
* callback function when there is any error preventing file upload to start,
* $.ajax and ajax events won't be called when error.
* Use $.noop to overwrite default alert function.
* imageMaxWidth, imageMaxHeight:
* Use any of the two settings to enable client-size image resizing.
* Image will be resized to fit into given rectangle.
* File size and type limit checking will be ignored.
* allowUploadOriginalImage:
* Set to true if you accept original image to be uploaded as a fallback
* when image resizing functionality is not availible (such as Webkit browsers).
* File size and type limit will be enforced.
* allowDataInBase64:
* Alternatively, you may wish to resize the image anyway and send the data
* in base64. The data will be 133% larger and you will need to process it further with
* server-side script.
* This setting might work with browsers which could read file but cannot send it in original
* binary (no known browser are designed this way though)
* forceResize:
* Set to true will cause the image being re-sampled even if the resized image
* has the same demension as the original one.
* imageType:
* Acceptable values are: 'jpeg', 'png', or 'auto'.
*
* TBD:
* ability to change settings after binding (you can unbind and bind again as a workaround)
* multipole file handling
* form intergation
*
*/
(function($) {
// Don't do logging if window.log function does not exist.
var log = window.console.log || $.noop;
// jQuery.ajax config
var config = {
fileError: function (info, textStatus, textDescription) {
window.alert(textDescription);
}
};
// Feature detection
// Read as binary string: FileReader API || Gecko-specific function (Fx3)
var canReadAsBinaryString = (window.FileReader || window.File.prototype.getAsBinary);
// Read file using FormData interface
var canReadFormData = !!(window.FormData);
// Read file into data: URL: FileReader API || Gecko-specific function (Fx3)
var canReadAsBase64 = (window.FileReader || window.File.prototype.getAsDataURL);
var canResizeImageToBase64 = !!(document.createElement('canvas').toDataURL);
var canResizeImageToBinaryString = canResizeImageToBase64 && window.atob;
var canResizeImageToFile = !!(document.createElement('canvas').mozGetAsFile);
// Send file in multipart/form-data with binary xhr (Gecko-specific function)
// || xhr.send(blob) that sends blob made with ArrayBuffer.
var canSendBinaryString = (
(window.XMLHttpRequest && window.XMLHttpRequest.prototype.sendAsBinary)
|| (window.ArrayBuffer && window.BlobBuilder)
);
// Send file as in FormData object
var canSendFormData = !!(window.FormData);
// Send image base64 data by extracting data: URL
var canSendImageInBase64 = !!(document.createElement('canvas').toDataURL);
var isSupported = (
(canReadAsBinaryString && canSendBinaryString)
|| (canReadFormData && canSendFormData)
);
var isImageSupported = (
canReadAsBase64 && (
(canResizeImageToBinaryString && canSendBinaryString)
|| (canResizeImageToFile && canSendFormData)
)
);
var isSupportedInBase64 = canReadAsBase64;
var isImageSupportedInBase64 = canReadAsBase64 && canResizeImageToBase64;
var dataURLtoBase64 = function (dataurl) {
return dataurl.substring(dataurl.indexOf(',')+1, dataurl.length);
}
// Step 1: check file info and attempt to read the file
// paramaters: Ajax settings, File object
var handleFile = function (settings, file) {
var info = {
// properties of standard File object || Gecko 1.9 properties
type: file.type || '', // MIME type
size: file.size || file.fileSize,
name: file.name || file.fileName
};
settings.resizeImage = !!(settings.imageMaxWidth || settings.imageMaxHeight);
if (settings.resizeImage && !isImageSupported && settings.allowUploadOriginalImage) {
log('WARN: Fall back to upload original un-resized image.');
settings.resizeImage = false;
}
if (settings.resizeImage) {
settings.imageMaxWidth = settings.imageMaxWidth || Infinity;
settings.imageMaxHeight = settings.imageMaxHeight || Infinity;
}
if (!settings.resizeImage) {
if (settings.fileType && settings.fileType.test) {
// Not using MIME types
if (!settings.fileType.test(info.name.substr(info.name.lastIndexOf('.')+1))) {
log('ERROR: Invalid Filetype.');
settings.fileError.call(this, info, 'INVALID_FILETYPE', 'Invalid filetype.');
return;
}
}
if (settings.fileMaxSize && file.size > settings.fileMaxSize) {
log('ERROR: File exceeds size limit.');
settings.fileError.call(this, info, 'FILE_EXCEEDS_SIZE_LIMIT', 'File exceeds size limit.');
return;
}
}
if (!settings.resizeImage && canReadFormData) {
log('INFO: Bypass file reading, insert file object into FormData object directly.');
handleForm(settings, 'file', file, info);
} else if (window.FileReader) {
log('INFO: Using FileReader to do asynchronously file reading.');
var reader = new FileReader();
reader.onerror = function (ev) {
if (ev.target.error) {
switch (ev.target.error) {
case 8:
log('ERROR: File not found.');
settings.fileError.call(this, info, 'FILE_NOT_FOUND', 'File not found.');
break;
case 24:
log('ERROR: File not readable.');
settings.fileError.call(this, info, 'IO_ERROR', 'File not readable.');
break;
case 18:
log('ERROR: File cannot be access due to security constrant.');
settings.fileError.call(this, info, 'SECURITY_ERROR', 'File cannot be access due to security constrant.');
break;
case 20: //User Abort
break;
}
}
}
if (!settings.resizeImage) {
if (canSendBinaryString) {
reader.onloadend = function (ev) {
var bin = ev.target.result;
handleForm(settings, 'bin', bin, info);
};
reader.readAsBinaryString(file);
} else if (settings.allowDataInBase64) {
reader.onloadend = function (ev) {
handleForm(
settings,
'base64',
dataURLtoBase64(ev.target.result),
info
);
};
reader.readAsDataURL(file);
} else {
log('ERROR: No available method to extract file; allowDataInBase64 not set.');
settings.fileError.call(this, info, 'NO_BIN_SUPPORT_AND_BASE64_NOT_SET', 'No available method to extract file; allowDataInBase64 not set.');
}
} else {
reader.onloadend = function (ev) {
var dataurl = ev.target.result;
handleImage(settings, dataurl, info);
};
reader.readAsDataURL(file);
}
} else if (window.File.prototype.getAsBinary) {
log('WARN: FileReader does not exist, UI will be blocked when reading big file.');
if (!settings.resizeImage) {
try {
var bin = file.getAsBinary();
} catch (e) {
log('ERROR: File not readable.');
settings.fileError.call(this, info, 'IO_ERROR', 'File not readable.');
return;
}
handleForm(settings, 'bin', bin, info);
} else {
try {
var bin = file.getAsDataURL();
} catch (e) {
log('ERROR: File not readable.');
settings.fileError.call(this, info, 'IO_ERROR', 'File not readable.');
return;
}
handleImage(settings, dataurl, info);
}
} else {
log('ERROR: No available method to extract file; this browser is not supported.');
settings.fileError.call(this, info, 'NOT_SUPPORT', 'ERROR: No available method to extract file; this browser is not supported.');
}
};
// step 1.5: inject file into <img>, paste the pixels into <canvas>,
// read the final image
var handleImage = function (settings, dataurl, info) {
var img = new Image();
img.onerror = function () {
log('ERROR: <img> failed to load, file is not a supported image format.');
settings.fileError.call(this, info, 'FILE_NOT_IMAGE', 'File is not a supported image format.');
};
img.onload = function () {
var ratio = Math.max(
img.width/settings.imageMaxWidth,
img.height/settings.imageMaxHeight,
1
);
var d = {
w: Math.floor(Math.max(img.width/ratio, 1)),
h: Math.floor(Math.max(img.height/ratio, 1))
}
log(
'INFO: Original image size: ' + img.width.toString(10) + 'x' + img.height.toString(10)
+ ', resized image size: ' + d.w + 'x' + d.h + '.'
);
if (!settings.forceResize && img.width === d.w && img.height === d.h) {
log('INFO: Image demension is the same, send the original file.');
if (canResizeImageToBinaryString) {
handleForm(
settings,
'bin',
window.atob(dataURLtoBase64(dataurl)),
info
);
} else if (settings.allowDataInBase64) {
handleForm(
settings,
'base64',
dataURLtoBase64(dataurl),
info
);
} else {
log('ERROR: No available method to send the original file; allowDataInBase64 not set.');
settings.fileError.call(this, info, 'NO_BIN_SUPPORT_AND_BASE64_NOT_SET', 'No available method to extract file; allowDataInBase64 not set.');
}
return;
}
var canvas = document.createElement('canvas');
canvas.setAttribute('width', d.w);
canvas.setAttribute('height', d.h);
canvas.getContext('2d').drawImage(
img,
0,
0,
img.width,
img.height,
0,
0,
d.w,
d.h
);
if (!settings.imageType || settings.imageType === 'auto') {
if (info.type === 'image/jpeg') settings.imageType = 'jpeg';
else settings.imageType = 'png';
}
var ninfo = {
type: 'image/' + settings.imageType,
name: info.name.substr(0, info.name.indexOf('.')) + '.resized.' + settings.imageType
};
if (canResizeImageToFile && canSendFormData) {
// Gecko 2 (Fx4) non-standard function
var nfile = canvas.mozGetAsFile(
ninfo.name,
'image/' + settings.imageType
);
ninfo.size = file.size || file.fileSize;
handleForm(
settings,
'file',
nfile,
ninfo
);
} else if (canResizeImageToBinaryString && canSendBinaryString) {
// Read the image as DataURL, convert it back to binary string.
var bin = window.atob(dataURLtoBase64(canvas.toDataURL('image/' + settings.imageType)));
ninfo.size = bin.length;
handleForm(
settings,
'bin',
bin,
ninfo
);
} else if (settings.allowDataInBase64 && canResizeImageToBase64 && canSendImageInBase64) {
handleForm(
settings,
'base64',
dataURLtoBase64(canvas.toDataURL('image/' + settings.imageType)),
ninfo
);
} else {
log('ERROR: No available method to extract image; allowDataInBase64 not set.');
settings.fileError.call(this, info, 'NO_BIN_SUPPORT_AND_BASE64_NOT_SET', 'No available method to extract file; allowDataInBase64 not set.');
}
}
img.src = dataurl;
}
// Step 2: construct form data and send the file
// paramaters: Ajax settings, File object, binary string of file || null, file info assoc array
var handleForm = function (settings, type, data, info) {
if (canSendFormData && type === 'file') {
// FormData API saves the day
log('INFO: Using FormData to construct form.');
var formdata = new FormData();
formdata.append('Filedata', data);
// Prevent jQuery form convert FormData object into string.
settings.processData = false;
// Prevent jQuery from overwrite automatically generated xhr content-Type header
// by unsetting the default contentType and inject data only right before xhr.send()
settings.contentType = null;
settings.__beforeSend = settings.beforeSend;
settings.beforeSend = function (xhr, s) {
s.data = formdata;
if (s.__beforeSend) return s.__beforeSend.call(this, xhr, s);
}
//settings.data = formdata;
} else if (canSendBinaryString && type === 'bin') {
log('INFO: Concat our own multipart/form-data data string.');
// A placeholder MIME type
if (!info.type) info.type = 'application/octet-stream';
if (/[^\x20-\x7E]/.test(info.name)) {
log('INFO: Filename contains non-ASCII code, do UTF8-binary string conversion.');
info.name_bin = unescape(encodeURIComponent(info.name));
}
//filtered out non-ASCII chars in filenames
// info.name = info.name.replace(/[^\x20-\x7E]/g, '_');
// multipart/form-data boundary
var bd = 'xhrupload-' + parseInt(Math.random()*(2 << 16));
settings.contentType = 'multipart/form-data; boundary=' + bd;
var formdata = '--' + bd + '\n' // RFC 1867 Format, simulate form file upload
+ 'content-disposition: form-data; name="Filedata";'
+ ' filename="' + (info.name_bin || info.name) + '"\n'
+ 'Content-Type: ' + info.type + '\n\n'
+ data + '\n\n'
+ '--' + bd + '--';
if (window.XMLHttpRequest.prototype.sendAsBinary) {
// Use xhr.sendAsBinary that takes binary string
log('INFO: Pass binary string to xhr.');
settings.data = formdata;
} else {
// make a blob
log('INFO: Convert binary string into Blob.');
var buf = new ArrayBuffer(formdata.length);
var view = new Uint8Array(buf);
$.each(
formdata,
function (i, o) {
view[i] = o.charCodeAt(0);
}
);
var bb = new BlobBuilder();
bb.append(buf);
var blob = bb.getBlob();
settings.processData = false;
settings.__beforeSend = settings.beforeSend;
settings.beforeSend = function (xhr, s) {
s.data = blob;
if (s.__beforeSend) return s.__beforeSend.call(this, xhr, s);
};
}
} else if (settings.allowDataInBase64 && type === 'base64') {
log('INFO: Concat our own multipart/form-data data string; send the file in base64 because binary xhr is not supported.');
// A placeholder MIME type
if (!info.type) info.type = 'application/octet-stream';
// multipart/form-data boundary
var bd = 'xhrupload-' + parseInt(Math.random()*(2 << 16));
settings.contentType = 'multipart/form-data; boundary=' + bd;
settings.data = '--' + bd + '\n' // RFC 1867 Format, simulate form file upload
+ 'content-disposition: form-data; name="Filedata";'
+ ' filename="' + encodeURIComponent(info.name) + '.base64"\n'
+ 'Content-Transfer-Encoding: base64\n' // Vaild MIME header, but won't work with PHP file upload handling.
+ 'Content-Type: ' + info.type + '\n\n'
+ data + '\n\n'
+ '--' + bd + '--';
} else {
log('ERROR: Data is not given in processable form.');
settings.fileError.call(this, info, 'INTERNAL_ERROR', 'Data is not given in processable form.');
return;
}
xhrupload(settings);
};
// Step 3: start sending out file
var xhrupload = function (settings) {
log('INFO: Sending file.');
if (typeof settings.data === 'string' && canSendBinaryString) {
log('INFO: Using xhr.sendAsBinary.');
settings.___beforeSend = settings.beforeSend;
settings.beforeSend = function (xhr, s) {
xhr.send = xhr.sendAsBinary;
if (s.___beforeSend) return s.___beforeSend.call(this, xhr, s);
}
}
$.ajax(settings);
};
$.fn.fileUpload = function(settings) {
this.each(function(i, el) {
if ($(el).is('input[type=file]')) {
log('INFO: binding onchange event to a input[type=file].');
$(el).bind(
'change',
function () {
if (!this.files.length) {
log('ERROR: no file selected.');
return;
} else if (this.files.length > 1) {
log('WARN: Multiple file upload not implemented yet, only first file will be uploaded.');
}
handleFile($.extend({}, config, settings), this.files[0]);
if (this.form.length === 1) {
this.form.reset();
} else {
log('WARN: Unable to reset file selection, upload won\'t be triggered again if user selects the same file.');
}
return;
}
);
}
if ($(el).is('form')) {
log('ERROR: <form> not implemented yet.');
} else {
log('INFO: binding ondrop event.');
$(el).bind(
'dragover', // dragover behavior should be blocked for drop to invoke.
function(ev) {
return false;
}
).bind(
'drop',
function (ev) {
if (!ev.originalEvent.dataTransfer.files) {
log('ERROR: No FileList object present; user might had dropped text.');
return false;
}
if (!ev.originalEvent.dataTransfer.files.length) {
log('ERROR: User had dropped a virual file (e.g. "My Computer")');
return false;
}
if (!ev.originalEvent.dataTransfer.files.length > 1) {
log('WARN: Multiple file upload not implemented yet, only first file will be uploaded.');
}
handleFile($.extend({}, config, settings), ev.originalEvent.dataTransfer.files[0]);
return false;
}
);
}
});
return this;
};
$.fileUploadSupported = isSupported;
$.imageUploadSupported = isImageSupported;
$.fileUploadAsBase64Supported = isSupportedInBase64;
$.imageUploadAsBase64Supported = isImageSupportedInBase64;
})(jQuery);
================================================
FILE: app/javascript/libs/qrcode.js
================================================
/**
* @fileoverview
* - Using the 'QRCode for Javascript library'
* - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
* - this library has no dependencies.
*
* @author davidshimjs
* @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
*/
var QRCode;
(function () {
//---------------------------------------------------------------------
// QRCode for JavaScript
//
// Copyright (c) 2009 Kazuhiko Arase
//
// URL: http://www.d-project.com/
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license.php
//
// The word "QR Code" is registered trademark of
// DENSO WAVE INCORPORATED
// http://www.denso-wave.com/qrcode/faqpatent-e.html
//
//---------------------------------------------------------------------
function QR8bitByte(data) {
this.mode = QRMode.MODE_8BIT_BYTE;
this.data = data;
this.parsedData = [];
// Added to support UTF-8 Characters
for (var i = 0, l = this.data.length; i < l; i++) {
var byteArray = [];
var code = this.data.charCodeAt(i);
if (code > 0x10000) {
byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[3] = 0x80 | (code & 0x3F);
} else if (code > 0x800) {
byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[2] = 0x80 | (code & 0x3F);
} else if (code > 0x80) {
byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
byteArray[1] = 0x80 | (code & 0x3F);
} else {
byteArray[0] = code;
}
this.parsedData.push(byteArray);
}
this.parsedData = Array.prototype.concat.apply([], this.parsedData);
if (this.parsedData.length != this.data.length) {
this.parsedData.unshift(191);
this.parsedData.unshift(187);
this.parsedData.unshift(239);
}
}
QR8bitByte.prototype = {
getLength: function (buffer) {
return this.parsedData.length;
},
write: function (buffer) {
for (var i = 0, l = this.parsedData.length; i < l; i++) {
buffer.put(this.parsedData[i], 8);
}
}
};
function QRCodeModel(typeNumber, errorCorrectLevel) {
this.typeNumber = typeNumber;
this.errorCorrectLevel = errorCorrectLevel;
this.modules = null;
this.moduleCount = 0;
this.dataCache = null;
this.dataList = [];
}
QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
this.modules[r][6]=(r%2==0);}
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
+buffer.getLengthInBits()
+">"
+totalDataCount*8
+")");}
if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD1,8);}
return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
if(r==0&&c==0){continue;}
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
while(n>=256){n-=255;}
return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
function _isSupportCanvas() {
return typeof CanvasRenderingContext2D != "undefined";
}
// android 2.x doesn't support Data-URI spec
function _getAndroid() {
var android = false;
var sAgent = navigator.userAgent;
if (/android/i.test(sAgent)) { // android
android = true;
var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i);
if (aMat && aMat[1]) {
android = parseFloat(aMat[1]);
}
}
return android;
}
var svgDrawer = (function() {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
this.clear();
function makeSVG(tag, attrs) {
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
return el;
}
var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
_el.appendChild(svg);
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorLight, "width": "100%", "height": "100%"}));
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"}));
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
if (oQRCode.isDark(row, col)) {
var child = makeSVG("use", {"x": String(col), "y": String(row)});
child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template")
svg.appendChild(child);
}
}
}
};
Drawing.prototype.clear = function () {
while (this._el.hasChildNodes())
this._el.removeChild(this._el.lastChild);
};
return Drawing;
})();
var useSVG = document.documentElement.tagName.toLowerCase() === "svg";
// Drawing in DOM by using Table tag
var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
var aHTML = ['<table style="border:0;border-collapse:collapse;">'];
for (var row = 0; row < nCount; row++) {
aHTML.push('<tr>');
for (var col = 0; col < nCount; col++) {
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
}
aHTML.push('</tr>');
}
aHTML.push('</table>');
_el.innerHTML = aHTML.join('');
// Fix the margin values as real size.
var elTable = _el.childNodes[0];
var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;
var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;
if (nLeftMarginTable > 0 && nTopMarginTable > 0) {
elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px";
}
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._el.innerHTML = '';
};
return Drawing;
})() : (function () { // Drawing in Canvas
function _onMakeImage() {
this._elImage.src = this._elCanvas.toDataURL("image/png");
this._elImage.style.display = "block";
this._elCanvas.style.display = "none";
}
// Android 2.1 bug workaround
// http://code.google.com/p/android/issues/detail?id=5141
if (this && this._android && this._android <= 2.1) {
var factor = 1 / window.devicePixelRatio;
var drawImage = CanvasRenderingContext2D.prototype.drawImage;
CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
if (("nodeName" in image) && /img/i.test(image.nodeName)) {
for (var i = arguments.length - 1; i >= 1; i--) {
arguments[i] = arguments[i] * factor;
}
} else if (typeof dw == "undefined") {
arguments[1] *= factor;
arguments[2] *= factor;
arguments[3] *= factor;
arguments[4] *= factor;
}
drawImage.apply(this, arguments);
};
}
/**
* Check whether the user's browser supports Data URI or not
*
* @private
* @param {Function} fSuccess Occurs if it supports Data URI
* @param {Function} fFail Occurs if it doesn't support Data URI
*/
function _safeSetDataURI(fSuccess, fFail) {
var self = this;
self._fFail = fFail;
self._fSuccess = fSuccess;
// Check it just once
if (self._bSupportDataURI === null) {
var el = document.createElement("img");
var fOnError = function() {
self._bSupportDataURI = false;
if (self._fFail) {
self._fFail.call(self);
}
};
var fOnSuccess = function() {
self._bSupportDataURI = true;
if (self._fSuccess) {
self._fSuccess.call(self);
}
};
el.onabort = fOnError;
el.onerror = fOnError;
el.onload = fOnSuccess;
el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data.
return;
} else if (self._bSupportDataURI === true && self._fSuccess) {
self._fSuccess.call(self);
} else if (self._bSupportDataURI === false && self._fFail) {
self._fFail.call(self);
}
};
/**
* Drawing QRCode by using canvas
*
* @constructor
* @param {HTMLElement} el
* @param {Object} htOption QRCode Options
*/
var Drawing = function (el, htOption) {
this._bIsPainted = false;
this._android = _getAndroid();
this._htOption = htOption;
this._elCanvas = document.createElement("canvas");
this._elCanvas.width = htOption.width;
this._elCanvas.height = htOption.height;
el.appendChild(this._elCanvas);
this._el = el;
this._oContext = this._elCanvas.getContext("2d");
this._bIsPainted = false;
this._elImage = document.createElement("img");
this._elImage.alt = "Scan me!";
this._elImage.style.display = "none";
this._el.appendChild(this._elImage);
this._bSupportDataURI = null;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _elImage = this._elImage;
var _oContext = this._oContext;
var _htOption = this._htOption;
var nCount = oQRCode.getModuleCount();
var nWidth = _htOption.width / nCount;
var nHeight = _htOption.height / nCount;
var nRoundedWidth = Math.round(nWidth);
var nRoundedHeight = Math.round(nHeight);
_elImage.style.display = "none";
this.clear();
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
var bIsDark = oQRCode.isDark(row, col);
var nLeft = col * nWidth;
var nTop = row * nHeight;
_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.lineWidth = 1;
_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.fillRect(nLeft, nTop, nWidth, nHeight);
// 안티 앨리어싱 방지 처리
_oContext.strokeRect(
Math.floor(nLeft) + 0.5,
Math.floor(nTop) + 0.5,
nRoundedWidth,
nRoundedHeight
);
_oContext.strokeRect(
Math.ceil(nLeft) - 0.5,
Math.ceil(nTop) - 0.5,
nRoundedWidth,
nRoundedHeight
);
}
}
this._bIsPainted = true;
};
/**
* Make the image from Canvas if the browser supports Data URI.
*/
Drawing.prototype.makeImage = function () {
if (this._bIsPainted) {
_safeSetDataURI.call(this, _onMakeImage);
}
};
/**
* Return whether the QRCode is painted or not
*
* @return {Boolean}
*/
Drawing.prototype.isPainted = function () {
return this._bIsPainted;
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);
this._bIsPainted = false;
};
/**
* @private
* @param {Number} nNumber
*/
Drawing.prototype.round = function (nNumber) {
if (!nNumber) {
return nNumber;
}
return Math.floor(nNumber * 1000) / 1000;
};
return Drawing;
})();
/**
* Get the type by string length
*
* @private
* @param {String} sText
* @param {Number} nCorrectLevel
* @return {Number} type
*/
function _getTypeNumber(sText, nCorrectLevel) {
var nType = 1;
var length = _getUTF8Length(sText);
for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
var nLimit = 0;
switch (nCorrectLevel) {
case QRErrorCorrectLevel.L :
nLimit = QRCodeLimitLength[i][0];
break;
case QRErrorCorrectLevel.M :
nLimit = QRCodeLimitLength[i][1];
break;
case QRErrorCorrectLevel.Q :
nLimit = QRCodeLimitLength[i][2];
break;
case QRErrorCorrectLevel.H :
nLimit = QRCodeLimitLength[i][3];
break;
}
if (length <= nLimit) {
break;
} else {
nType++;
}
}
if (nType > QRCodeLimitLength.length) {
throw new Error("Too long data");
}
return nType;
}
function _getUTF8Length(sText) {
var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
return replacedText.length + (replacedText.length != sText ? 3 : 0);
}
/**
* @class QRCode
* @constructor
* @example
* new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie");
*
* @example
* var oQRCode = new QRCode("test", {
* text : "http://naver.com",
* width : 128,
* height : 128
* });
*
* oQRCode.clear(); // Clear the QRCode.
* oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode.
*
* @param {HTMLElement|String} el target element or 'id' attribute of element.
* @param {Object|String} vOption
* @param {String} vOption.text QRCode link data
* @param {Number} [vOption.width=256]
* @param {Number} [vOption.height=256]
* @param {String} [vOption.colorDark="#000000"]
* @param {String} [vOption.colorLight="#ffffff"]
* @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]
*/
QRCode = function (el, vOption) {
this._htOption = {
width : 256,
height : 256,
typeNumber : 4,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRErrorCorrectLevel.H
};
if (typeof vOption === 'string') {
vOption = {
text : vOption
};
}
// Overwrites options
if (vOption) {
for (var i in vOption) {
this._htOption[i] = vOption[i];
}
}
if (typeof el == "string") {
el = document.getElementById(el);
}
if (this._htOption.useSVG) {
Drawing = svgDrawer;
}
this._android = _getAndroid();
this._el = el;
this._oQRCode = null;
this._oDrawing = new Drawing(this._el, this._htOption);
if (this._htOption.text) {
this.makeCode(this._htOption.text);
}
};
/**
* Make the QRCode
*
* @param {String} sText link data
*/
QRCode.prototype.makeCode = function (sText) {
this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);
this._oQRCode.addData(sText);
this._oQRCode.make();
this._el.title = sText;
this._oDrawing.draw(this._oQRCode);
this.makeImage();
};
/**
* Make the Image from Canvas element
* - It occurs automatically
* - Android below 3 doesn't support Data-URI spec.
*
* @private
*/
QRCode.prototype.makeImage = function () {
if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) {
this._oDrawing.makeImage();
}
};
/**
* Clear the QRCode
*/
QRCode.prototype.clear = function () {
this._oDrawing.clear();
};
/**
* @name QRCode.CorrectLevel
*/
QRCode.CorrectLevel = QRErrorCorrectLevel;
})();
window.QRCode = QRCode;
================================================
FILE: app/jobs/application_job.rb
================================================
class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
# Most jobs are safe to ignore if the underlying records are no longer available
# discard_on ActiveJob::DeserializationError
end
================================================
FILE: app/mailers/application_mailer.rb
================================================
class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com'
layout 'mailer'
end
================================================
FILE: app/models/administrator.rb
================================================
class Administrator < ApplicationRecord
validates :name, presence: true, uniqueness: true
has_secure_password
end
================================================
FILE: app/models/application_record.rb
================================================
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
================================================
FILE: app/models/comment.rb
================================================
class Comment < ApplicationRecord
belongs_to :post
validates :name, presence: true
validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
validates :content, presence: true, length: { minimum: 4 }
def reply_emails
Comment.where(post_id: self.post_id).collect(&:email).uniq - [ self.email ] - Subscribe.unsubscribe_list - [ ENV['ADMIN_USER'] ]
end
end
================================================
FILE: app/models/concerns/.keep
================================================
================================================
FILE: app/models/label.rb
================================================
class Label < ApplicationRecord
has_and_belongs_to_many :posts
validates :name, presence: true
end
================================================
FILE: app/models/like.rb
================================================
class Like < ApplicationRecord
belongs_to :post
end
================================================
FILE: app/models/photo.rb
================================================
class Photo < ApplicationRecord
mount_uploader :image, PhotoUploader
end
================================================
FILE: app/models/post.rb
================================================
require 'markdown'
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :labels
has_many :likes
validates :title, :presence=>true, :uniqueness=> true
validates :content, :presence=>true, :length => { :minimum=> 3 }
def content_html
self.class.render_html(self.content)
end
def self.render_html(content)
rd = CodeHTML.new
md = Redcarpet::Markdown.new(rd, autolink: true, fenced_code_blocks: true)
md.render(content)
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
# truncate content for meta description display
def meta_content
html = HTML_Truncator.truncate(content_html, 100, :length_in_chars => true, ellipsis: '')
# Easily get text for Nokogiri
html = '<div>' + html + '</div>'
Nokogiri.parse(html).text()
end
def labels_content( need_blank=false )
content = self.labels.collect { |label| label.name }.join(", ")
content = I18n.t('none') if content.blank? and !need_blank
content
end
def liked_count
self.likes.size
end
def liked_by?(like_id)
!! self.likes.where(id: like_id).first
end
end
================================================
FILE: app/uploaders/photo_uploader.rb
================================================
class PhotoUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
process :resize_to_limit => [1200,nil]
version :medium do
process :resize_to_limit => [640,nil]
end
version :small do
process :resize_to_limit => [400,nil]
end
def extension_white_list
%w(jpg jpeg gif png)
end
end
================================================
FILE: app/views/admin/accounts/edit.html.slim
================================================
- content_for :title do
| Account Setting
.card.card-primary
.card-header.with-border
h3.card-title
| Edit Account
= simple_form_for current_admin, url: admin_account_path do |f|
.card-body
= f.input :name, label: 'Administrator Name'
= f.input :current_password, label: 'Current Password', required: true
= f.input :password, label: 'New Password( Blank if not modify )'
= f.input :password_confirmation, label: 'Confirm New Password'
.card-footer
= f.submit 'Update Account', class: 'btn btn-primary'
================================================
FILE: app/views/admin/all_comments/index.html.slim
================================================
.card
.card-header
.float-sm-left
h3.card-title 评论管理
.float-sm-right
.card-body
table.table.table-hover
thead
tr
th 内容
th 所属文章
th 时间
th #{t('admin.posts_head.operation')}
tbody
- @comments.each do |comment|
tr
td #{comment.content}
td #{comment.post.title}
td #{format_time(comment.created_at)}
td
= link_to t('destroy'), admin_all_comment_path(comment), method: 'DELETE', 'data-confirm' => '确认删除?'
.card-footer
.float-sm-left
| 总计: #{@comments.total_count}
.float-sm-right
= paginate @comments
================================================
FILE: app/views/admin/comments/index.html.slim
================================================
.card
.card-header
.float-sm-left
h3.card-title
' #{t('admin.comments')}
| / #{@post.title}
.card-body
table.table.table-hover
thead
tr
th #{t('admin.comments_head.name')}
th #{t('admin.comments_head.email')}
th #{t('admin.comments_head.content')}
th #{t('admin.comments_head.created_at')}
th #{t('admin.comments_head.operation')}
tbody
- @comments.each do |comment|
tr
td #{comment.name}
td
= mail_to comment.email
td
= simple_format(comment.content)
td
= format_time(comment.created_at)
td
= link_to t('admin.comments_head.reply'), blog_path(@post, anchor: 'new_comment'), target: '_blank', class: 'edit-post-link'
'
= link_to t('admin.comments_head.destroy'), admin_post_comment_path(@post, comment), method: 'DELETE', 'data-confirm'=> '确认删除?'
================================================
FILE: app/views/admin/dashboard/index.html.slim
================================================
.container-fluid
.row
.col-lg-6
.card.card-primary.card-outline
.card-body
h5.card-title 博客数量: #{@posts_count}
p.card-text
.col-lg-6
.card.card-primary.card-outline
.card-body
h5.card-title 评论数量: #{@comments_count}
p.card-text
================================================
FILE: app/views/admin/labels/_form.html.slim
================================================
.row
.col-sm-6
= simple_form_for([:admin, @label], html: {novalidate: '' }) do |f|
= f.input :name
= f.button :submit
================================================
FILE: app/views/admin/labels/edit.html.slim
================================================
.card
.card-header
.float-sm-left
h3.card-title 修改标签
.card-body
= render 'form'
================================================
FILE: app/views/admin/labels/index.html.slim
================================================
.card
.card-header
.float-sm-left
h3.card-title 标签管理
.float-sm-right
= link_to '创建标签', new_admin_label_path
.card-body
table.table.table-hover
thead
tr
th 标签名
th 被引用次数
th 创建时间
th 操作
tbody
- @labels.each do |label|
tr
td = label.name
td = label.posts.count
td = format_time(label.created_at)
td
= link_to '编辑', edit_admin_label_path(label)
|  
= link_to '删除', admin_label_path(label), method: 'DELETE', 'data-confirm' => '确认删除?'
.card-footer
.float-sm-left
| 总计: #{@labels.total_count}
.float-sm-right
= paginate @labels
================================================
FILE: app/views/admin/labels/new.html.slim
================================================
.card
.card-header
.float-sm-left
h3.card-title 新建标签
.card-body
= render 'form'
================================================
FILE: app/views/admin/posts/_form.html.slim
================================================
.row data-controller='admin-label'
.col-sm-8
= simple_form_for([:admin, @post], html: {novalidate: '' }) do |f|
= f.input :title, label: t('admin.posts_attributes.title')
= f.association :labels, input_html: { data: { 'admin-label-target': 'label', 'labels-data': Label.all.map(&:name) } }, label: t('admin.posts_attributes.labels')
/ tabs and upload file field
ul.nav.nav-tabs#tabs
li.nav-item
a.nav-link.active data-toggle="tab" href="#content" #{t('admin.posts_attributes.content')}
li.nav-item
a.nav-link#preview-link data-toggle="tab" href="#preview" #{t('admin.posts_attributes.preview')}
= link_to t('admin.posts_attributes.upload_photo'), "#", id: 'upload_photo'
input type="file" style="display: none;"
.tab-content
.tab-pane.fade.show.active#content
= f.input :content, :as=> :text, :label => false, input_html: { id: 'content-input' }
.tab-pane.fade.markdown#preview
= f.button :submit
================================================
FILE: app/views/admin/posts/edit.html.slim
================================================
.card
.card-header
.float-sm-left
h3.card-title 修改博客
.card-body
= render 'form'
================================================
FILE: app/views/admin/posts/index.html.slim
================================================
.card
.card-header
.float-sm-left
h3.card-title #{t('admin.posts')}
.float-sm-right
= link_to '创建博客', new_admin_post_path
.card-body
table.table.table-hover
thead
tr
th #{t('admin.posts_head.title')}
th #{t('admin.posts_head.summary')}
th #{t('admin.posts_head.operation')}
tbody
- @posts.each do |post|
tr
td = link_to post.title, blog_path(post)
td
i.fa.fa-calendar
span #{format_time(post.created_at)}
i.fa.fa-list
span #{ post.labels_content }
i.fa.fa-eye
span #{ post.visited_count }
i.fa.fa-heart
span #{ post.liked_count }
td
= 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' => '确认删除?'
.card-footer
.float-sm-left
| 总计: #{@posts.total_count}
.float-sm-right
= paginate @posts
================================================
FILE: app/views/admin/posts/new.html.slim
================================================
.card
.card-header
.float-sm-left
h3.card-title #{t('admin.new_post')}
.card-body
= render 'form'
================================================
FILE: app/views/admin/sessions/new.html.slim
================================================
- content_for :title, 'Log in'
main
section.content-messages
= render 'shared/admin/flash_messages'
section.content
.login-box
.login-logo
span
| Dashboard
.card
.card-body.login-card-body
p.login-box-msg Log in
= form_tag admin_login_path do
.input-group.mb-3
= text_field_tag :name, nil, placeholder: 'Username', class: 'form-control'
.input-group-append.input-group-text
span.fas.fa-envelope
.input-group.mb-3
= password_field_tag :password, nil, placeholder: 'Password', class: 'form-control'
.input-group-append.input-group-text
span.fas.fa-lock
.row
.col-4
= submit_tag 'Log in', class: 'btn btn-primary btn-block btn-flat'
================================================
FILE: app/views/archives/index.html.slim
================================================
- content_for(:title) do
| #{t('title.timeline')}
.container
.row.justify-content-center
.col-sm-12.col-lg-9
ul.archives-field
.search-wrapper
= form_with url: archives_path, method: 'GET' do |f|
.form-group
= f.search_field :q, value: @q, placeholder: t('archive.search'), class: 'form-control'
- @posts.each do |post|
li
= link_to blog_path(post), class: 'blog-title' do
== search_highlight(post.title, @q)
p.tags-field
i.fa.fa-calendar
span
= format_date(post.created_at)
i.fa.fa-eye
span
= post.labels_content
i.fa.fa-torah
span
= post.visited_count
i.fa.fa-heart
span
= post.liked_count
- if @q.present?
.search-result-wrapper
p.text-muted 共 #{@q_size || 0} 条结果
= paginate @posts, q: @q
================================================
FILE: app/views/blogs/_comment.html.slim
================================================
turbo-frame#comment-frame
.container
.row.justify-content-center
.col-12.col-lg-9
- comment = @comment || Comment.new
= simple_form_for comment, url: blog_comments_path(post), remote: true do |f|
= f.text_area :content, placeholder: t('comment_placeholder.content')
.row
.col-12.col-lg-6
= f.text_field :name, value: cookies[:name], placeholder: t('comment_placeholder.name')
.row
.col-12.col-lg-6
= f.text_field :email, value: cookies[:email], placeholder: t('comment_placeholder.email')
button.comment-submit type='submit' data-disable-with=t('comment_placeholder.submitting') #{t('comment_placeholder.submit')}
- if info = flash.now[:notice]
#alert-container.alert.alert-success
span.text #{info}
// ai fix
button class="close" type='button' data-dismiss='alert'
span ×
- elsif comment.errors.any?
#alert-container.alert.alert-warning
span.text #{comment.errors[:content].first}
button class="close" type='button' data-dismiss='alert'
span ×
= render partial: 'comments/comment_content', locals: { comments: comments }
================================================
FILE: app/views/blogs/_post.html.slim
================================================
= render partial: 'post_head', locals: { post: post }
.content.markdown
== post.content_html
p.ptag.published-at
| #{t('announce_at')}
span #{format_date(post.created_at)}
= render 'common/copyright'
hr.blog-over
p data-controller='like'
button.like-button class="#{'liked' if post.liked_by?(cookies[:like])}" type='button' data-url=blog_likes_path(post) data-like-target='button' data-action='click->like#toggle'
span.count #{@likes_count}
span Like
.qrcode-controller data-controller='qrcode'
.qrcode
a#qrcode-link href='#' data-action='click->qrcode#greet'
i.fi-link
| #{t('qr_code')}
.social-share data-qrcode-target='wrapper'
.qrcode-wrapper
= render partial: "qrcode", locals: { str: blog_url(post) }
================================================
FILE: app/views/blogs/_post_head.html.slim
================================================
/ require: locals: { post : post }
h2.blog-title #{post.title}
p.ptag
span.fa.fa-list
span #{post.labels_content}
================================================
FILE: app/views/blogs/_qrcode.html.slim
================================================
.qrcode-image
#image-tag data-url=str
p #{t('qrcodetips')}
================================================
FILE: app/views/blogs/edit.html.slim
================================================
h1 Blogs#edit
p Find me in app/views/blogs/edit.html.slim
================================================
FILE: app/views/blogs/show.html.slim
================================================
- content_for(:meta) do
meta name="description" content=@post.meta_content
meta name="keywords" content=@post.labels_content
- content_for(:title) do
| #{@post.title}
/ data-url=refresh_blog_comments_path(@post) data-post_id=@post.id
.container.blog-wrapper
.row.justify-content-center
.col-xs-12.col-lg-9
= render partial: "post", :locals=> { post: @post }
.comment-field
= render partial: 'comment', locals: { comments: @comments, post: @post }
p
.container
.row.justify-content-center
.col-xs-12.col-lg-9
- if @prev
= link_to blog_path(@prev), class: 'prev' do
i.fa.fa-arrow-left
| 上一篇
- if @next
= link_to blog_path(@next), class: 'next' do
| 下一篇
i.fa.fa-arrow-right
================================================
FILE: app/views/comments/_comment_content.html.slim
================================================
.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 }
================================================
FILE: app/views/comments/_comment_pre.html.slim
================================================
.comment-content
= simple_format(comment.content)
================================================
FILE: app/views/comments/create.html.slim
================================================
= render partial: 'blogs/comment', locals: { post: @post, comments: @comments }
================================================
FILE: app/views/common/_copyright.en.html.slim
================================================
p.copyright.published-at.ptag
| © Creative Commons - ShareAlike - NonCommercial - Attribution
================================================
FILE: app/views/common/_copyright.html.slim
================================================
p.copyright.published-at.ptag
| © 自由转载 - 非商用 - 非衍生 - 保持署名
================================================
FILE: app/views/common/_no_blog_here.en.html.slim
================================================
h2.blog-title #{t('home.no_blog_here')}
p
| No post here, please visit
= link_to ' Manage Post ', new_admin_post_path
| to create the first post.
================================================
FILE: app/views/common/_no_blog_here.html.slim
================================================
h2.blog-title #{t('home.no_blog_here')}
p
| 这里还没有博客, 请访问
= link_to '管理页面', new_admin_post_path
| 来创建第一篇博客
================================================
FILE: app/views/common/_welcome.en.html.slim
================================================
/* adjust stylesheet: .self-introduce-index */
h4 WELCOME
p I'm Li Yafei, WinDy is my English name.
h4 ABOUT
ul.aboutme-index
li
span Industry:
span Web Development, Startups, Life
li
span Location:
span Nan Shan District, ShenZhen, China
li
span More:
span
= link_to 'About Me', about_path
================================================
FILE: app/views/common/_welcome.html.slim
================================================
/* 样式调整请找 stylesheet: .self-introduce-index */
.box
h4 欢迎
p 我是技术达人李亚飞
.box
h4 关于我
ul.aboutme-index
li
span 领域:
span 技术, 创业, 生活
li
span 位置:
span 中国 - 深圳 - 南山
li
span 更多:
span
= link_to '关于我', about_path
================================================
FILE: app/views/common/_welcome_new_year.html.slim
================================================
- if (1.days.from_now).strftime('%-m-%-d') =~ /^1-[123]$/
.new-year.alert.alert-success.alert-dismissible.fade.show role='alert'
strong 🎉 我的朋友,祝你元旦快乐!
button.close type='button' data-dismiss='alert' aria-label='Close'
span aria-hidden='true' ×
================================================
FILE: app/views/home/_post_head.html.slim
================================================
/ require: locals: { post : post }
h2.blog-title #{post.title}
p.ptag
span
i.fi-pricetag-multiple
span #{post.labels_content}
================================================
FILE: app/views/home/about.html.slim
================================================
- content_for(:title) do
| #{t('title.about')}
- content_for(:main) do
nav#about-top-bar class="navbar navbar-expand-lg navbar-dark bg-dark my-navbar fixed-top"
a.navbar-brand href=root_path 回到博客
button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-collapse"
span class="navbar-toggler-icon"
div class="collapse navbar-collapse" id="navbar-collapse"
ul.navbar-nav.ml-auto
li class="nav-item"
a.nav-link href='#about' 关于
li class="nav-item"
a.nav-link href='#skill' 技能
li class="nav-item"
a.nav-link href='#work' 作品
li class="nav-item"
a.nav-link href='#contact' 联系
#intro
header.intro.container-fluid
.row.intro-heading
.col-12
h1.heading 李亚飞
.sub-heading
p 懒,驱动人类进步的关键力量。
a.circle#about-anchor-link href='#about'
i.fa.fa-angle-double-down
section#about.container-fluid
.row.justify-content-center
.col-12.col-lg-8.col-xl-6
h1.title 关于我
p 我是李亚飞,坐标深圳,连续创业者,技术达人,有众多开源项目和多场技术分享,创立过 3 家以技术产品驱动的创业公司,现 ShowMeBug 产品创始人&至简天成科技CEO,前单麦科技联合创始人&CTO。
.wrapper
.time 2024.04-至今
p Clacky CEO
ul
li 全球首创:L3 Agentic AI CDE
li 公测地址:clacky.ai
.wrapper
.time 2019.03 ~ 2024.04
p ShowMeBug创始人,至简天成科技CEO
ul
li 中国数字化面试开拓者
li 以全栈产研为核心的团队
li 近百万用户,6000家企业客户
li 开箱即用地址:showmebug.com
.wrapper
.time 2016.04 ~ 2018.12
p 深圳百分之八十网络技术有限公司联合创始人 & CTO,旗下主要产品单麦小程序平台是帮助商家一键制作小程序的SAAS服务平台,我与另一位联合创始人一道创立并运营了整个公司与产品,我的主要职责包括共同决策产品方向,负责研发团队日常管理,参与市场部销售计划制定与支持工作。
ul
li 组建以敏捷开发,全栈工程师为核心的高效率研发团队
li 带领团队连续 52 次版本迭代,每周发布新版本
li 单麦小程序平台客户体验满意度90%以上,上线小程序几千家,服务用户15万+
li 单麦小程序平台成为微信小程序生态 TOP10 获奖者
li 2018 年 12 月并购退出
.wrapper
.time 2015
p SmartX 早期员工,SmartX 是位于中国的全球领先的超融合存储方案供应商,利用软件技术方案帮助企业大规模降低存储硬件的成本。作为早期创始员工,担任前端架构师角色,顺利保证 SmartX OS 的快速交付。
.wrapper
.time 2014
p Cywin.cn 联合创始人 & 技术负责人,Cywin.cn 是一家股权众筹平台,对标美国的 Angelist.co,帮助创业公司完成股权融资的交易平台。作为项目的发起人之一,负责产品的研发工作。
.wrapper
.time 2009 ~ 2014
p 深信服(2018年上市)是一家著名的网络安全公司。加入时研发规模600人,作为当时研发部成长最快的工程师之一,担任首界自动化产品线的主管兼技术负责人(现研发效能部门),带领技术团队进行研发质量和内部工具链研发方面的工作。
section#skill.container-fluid
.row.justify-content-center
.col-12.col-lg-8.col-xl-6
.skills
h1.title 开发技能栈
ul
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.container-fluid
.row.justify-content-center
.col-12.col-lg-8.col-xl-6
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.yafeilee.me' target='_blank' http://jiaoluo.yafeilee.me
ul.project-description
li 朋友的创业项目, 独立开发
li 现代的功能: 微信登录, 支付系统, 视频托管, 用户社区.
li Ruby on Rails 架构, 使用 Turoblinks, RJS 进行前端交互.
li 七牛云存储集成, 微信集成, 支付宝集成.
li
span.time 2014.3 - 2014.10
.project
span.name Cywin
span.brief 一个股权众筹的商业平台
span.link
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 月已经
a href='https://github.com/windy/cywin' target='_blank' 开源
| .
li
span.time 2014.2 - 2014.3
.project
span.name WBlog
span.brief Rails 社区正缺了一个独立博客建站系统
span.link
a href='https://github.com/windy/wblog' target='_blank' https://github.com/windy/wblog
ul.project-description
li Ruby on Rails 开源博客系统, 帮助更多的朋友构建高定制性的博客.
li 具备博客管理, 点赞, 评论, 二维码, 自适应等现代网站所有特性.
li
span.time 2011.x - 2013.10
.project
span.name ATM/ATT
span.brief 测试业界领先的关键字驱动的自动化测试平台
span.link 深信服内部产品
ul.project-description
li 作为 Leader 带领团队架构并开发.
li XMLRPC 作为数据交换, 平台具备任务调度, Agent 服务, 用例编写, 权限管理, Svn集成, 自动布署.
li 关键字驱动方案是测试业内领先的技术框架.
li 几乎所有服务使用 100% Ruby 代码完成.
section#contact
.grid-x
.small-12.large-8.large-offset-2.cell
h1.title 联系我
p 保持放松, 请随时邮件与我联系
p.mail_to
= mail_to 'lyfi2003@gmail.com'
ul.contact-ul
li
a href='https://github.com/windy' target='_blank'
i.fab.fa-github
| Github
li
a href='http://ruby-china.org/lyfi2003' target='_blank'
i.fa.fa-heart
| RubyChina
li
a href='http://www.douban.com/people/41759170/' target='_blank'
i.douban
| 豆
| Douban
p.modified-at
| 本页更新于 2025.03.01
.footer
.grid-x
.small-12.cell
div Copyright © 2012 - 2025 yafeilee.com
================================================
FILE: app/views/home/index.html.slim
================================================
- if ENV['INTRODUCE'].present?
- content_for(:meta) do
meta name="description" content="#{ENV['INTRODUCE'].dup.force_encoding('UTF-8')}"
- content_for(:title) do
| #{t('title.home')}
.container
.row
.col-sm-12.col-lg-8
- unless @newest
= render 'common/no_blog_here'
- else
= render partial: 'post_head', locals: { post: @newest }
.content.markdown
== @newest.sub_content
= link_to t('home.read'), blog_path(@newest), class: 'read-more'
p.published-at #{t('home.created_at')} #{format_date(@newest.created_at)}
- if @recent.present?
h4.recent-title #{t('home.recent')}
ul.recent-content
- @recent.each do |re|
li = link_to re.title, blog_path(re)
.col-lg-4.self-introduce
/ Adjust it in common/welcome
= render 'common/welcome'
h4 #{t('subscribes.title')}
.row
.col-12.col-md-6.col-lg-12
= image_tag 'wechat_qrcode.jpg', class: 'wechat_qrcode'
================================================
FILE: app/views/kaminari/_first_page.html.slim
================================================
li.page-item
= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link'
================================================
FILE: app/views/kaminari/_gap.html.slim
================================================
li.page-item.disabled
= link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link'
================================================
FILE: app/views/kaminari/_last_page.html.slim
================================================
li.page-item
= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, remote: remote, class: 'page-link'
================================================
FILE: app/views/kaminari/_next_page.html.slim
================================================
li.page-item
= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote, class: 'page-link'
================================================
FILE: app/views/kaminari/_page.html.slim
================================================
- if page.current?
li.page-item.active
= content_tag :a, page, data: { remote: remote }, rel: page.rel, class: 'page-link'
- else
li.page-item
= link_to page, url, remote: remote, rel: page.rel, class: 'page-link'
================================================
FILE: app/views/kaminari/_paginator.html.slim
================================================
= paginator.render do
nav
ul.pagination
== first_page_tag unless current_page.first?
== prev_page_tag unless current_page.first?
- each_page do |page|
- if page.left_outer? || page.right_outer? || page.inside_window?
== page_tag page
- elsif !page.was_truncated?
== gap_tag
== next_page_tag unless current_page.last?
== last_page_tag unless current_page.last?
================================================
FILE: app/views/kaminari/_prev_page.html.slim
================================================
li.page-item
= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote, class: 'page-link'
================================================
FILE: app/views/layouts/_footer.html.slim
================================================
.container
.row
.col-sm-12
.footer
div
span.link = ENV['SITE_ADDRESS']
span.time = ENV['SITE_YEAR']
.license
| Designed by
span
= link_to 'WinDy', 'http://yafeilee.com/about', target: '_blank'
.license
| Built with
span
= link_to 'wblog', 'https://github.com/windy/wblog', target: '_blank'
- if ENV['SITE_BEIAN'].present?
.license
= link_to ENV['SITE_BEIAN'], 'http://beian.miit.gov.cn/', target: '_blank'
================================================
FILE: app/views/layouts/admin.html.slim
================================================
doctype html
html
head
meta charset='utf-8'
meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"
meta name="renderer" content="webkit"
meta http-equiv="cleartype" content="on"
meta name="HandheldFriendly" content="True"
meta name="MobileOptimized" content="320"
meta name="turbo-cache-control" content="no-cache"
- if content_for?(:title)
title = format("%s - Dashboard", yield(:title))
- else
title Dashboard
= csrf_meta_tags
= action_cable_meta_tag
= content_for?(:head) ? yield(:head) : ''
= stylesheet_link_tag 'admin', media: 'all', 'data-turbo-track': 'reload'
/ = javascript_include_tag 'admin', 'data-turbo-track': 'reload'
= javascript_include_tag 'admin', 'data-turbo-track': Rails.env.production? ? "reload" : "", type: "module"
= favicon_link_tag asset_path("favicon.png")
body.layout-fixed.sidebar-expand-lg.bg-body-tertiary.app-loaded.sidebar-open.admin-page class=body_class
- if @full_render
= yield
- else
.app-wrapper
= render 'shared/admin/header'
= render 'shared/admin/sidebar'
main.app-main
.content-messages
= render 'shared/admin/flash_messages'
.app-content-header
.app-content
= yield
/ aside.control-sidebar.control-sidebar-dark
/ /! Control sidebar content goes here
/ .p-3
/ h5 Title
/ p Sidebar content
/ /! /.control-sidebar
/ /! Main Footer
.app-footer
/! To the right
.float-end.d-none.d-sm-inline
| Anything you want
/! Default to the left
strong
| Copyright © 2019 - All rights reserved.
| Theme by AdminLTE.io
================================================
FILE: app/views/layouts/application.html.slim
================================================
doctype html
html
head
meta charset='utf-8'
meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"
meta name="renderer" content="webkit"
meta http-equiv="cleartype" content="on"
meta name="HandheldFriendly" content="True"
meta name="MobileOptimized" content="320"
meta name="turbo-cache-control" content="no-cache"
- if content_for?(:meta)
= yield(:meta)
title
= content_for?(:title) ? yield(:title) + " | #{ENV['SITE_NAME']}" : ENV['SITE_NAME']
= csrf_meta_tags
= action_cable_meta_tag
= content_for?(:head) ? yield(:head) : ''
= stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload'
= javascript_include_tag 'application', 'data-turbo-track': Rails.env.production? ? "reload" : "", type: "module"
/ = javascript_include_tag 'application', 'data-turbo-track': Rails.env.production? ? "reload" : ""
/ = javascript_tag 'ga', 'data-turbo-track': 'reload'
= favicon_link_tag asset_path("favicon.png")
body class=body_class
- if content_for?(:main)
= yield(:main)
- else
nav class="navbar navbar-expand-lg navbar-dark bg-dark my-navbar"
a class="navbar-brand" href="/" #{ENV['SITE_NAME']}
button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-collapse"
span class="navbar-toggler-icon"
div class="collapse navbar-collapse" id="navbar-collapse"
ul.navbar-nav.ml-auto
li class="nav-item"
= link_to '首页', root_path, class: 'nav-link'
li class="nav-item"
= link_to '时间线', archives_path, class: 'nav-link'
li class="nav-item"
= link_to '关于', about_path, class: 'nav-link'
li class="nav-item"
a class="nav-link" href=archives_path
.fa.fa-search
main
= render 'common/welcome_new_year'
= yield
= render "layouts/footer"
================================================
FILE: app/views/layouts/mailer.html.erb
================================================
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Email styles need to be inline */
</style>
</head>
<body>
<%= yield %>
</body>
</html>
================================================
FILE: app/views/layouts/mailer.text.erb
================================================
<%= yield %>
================================================
FILE: app/views/shared/admin/_flash_messages.html.slim
================================================
- flash.each do |key, value|
= content_tag :div, class: flash_class(key) do
button.close[type="button" data-dismiss="alert"]
| ×
= value
================================================
FILE: app/views/shared/admin/_header.html.slim
================================================
nav class="app-header navbar navbar-expand bg-body"
/! Left navbar links
ul.navbar-nav
li.nav-item
a.nav-link data-lte-toggle='sidebar' data-widget="pushmenu" href="javascript:void(0)"
i.fas.fa-bars
/! SEARCH FORM
- if content_for?(:search)
= yield(:search)
/! Right navbar links
ul.navbar-nav.ml-auto
/! Messages Dropdown Menu
li.nav-item.dropdown.show
a.nav-link.dropdown-toggle href="javascript:void(0)" data-toggle="dropdown"
span
= current_admin.name
ul.dropdown-menu.dropdown-menu-right
li.user-header
= link_to 'Account Setting', edit_admin_account_path, class: 'dropdown-item'
.dropdown-divider
= link_to 'Logout', admin_logout_path, method: :delete, class: 'dropdown-item'
================================================
FILE: app/views/shared/admin/_sidebar.html.slim
================================================
/! Main Sidebar Container
aside.app-sidebar.bg-body-secondary.shadow data-bs-theme="dark"
.sidebar-brand
/! Brand Logo
a.brand-link href=admin_root_path
= image_tag 'logo.png', class: 'brand-image img-circle elevation-3'
span.brand-text.fw-light Dashboard
/! Sidebar
.sidebar-wrapper data-overlayscrollbars="host"
.os-size-observer
.os-size-observer-listener
/! Sidebar Menu
nav.mt-2
ul.nav.sidebar-menu.flex-column data-lte-toggle="treeview" data-accordion="false"
li.nav-header 数据
li.nav-item
= link_to admin_root_path, class: "nav-link #{admin_active_for(admin_root_path, current_path)}" do
i.nav-icon.fas.fa-tachometer-alt
span 数据中心
li.nav-header 博客
li.nav-item
= link_to admin_posts_path, class: "nav-link #{admin_active_for(admin_posts_path, current_path)}" do
i.nav-icon.fas.fa-pen-square
span 博客管理
li.nav-item
= link_to admin_all_comments_path, class: "nav-link #{admin_active_for(admin_all_comments_path, current_path)}" do
i.nav-icon.fas.fa-comment
span 评论管理
li.nav-header 标签
li.nav-item
= link_to admin_labels_path, class: "nav-link #{admin_active_for(admin_labels_path, current_path)}" do
i.nav-icon.fas.fa-list
span 标签管理
================================================
FILE: babel.config.js
================================================
module.exports = function(api) {
var validEnv = ['development', 'test', 'production']
var currentEnv = api.env()
var isDevelopmentEnv = api.env('development')
var isProductionEnv = api.env('production')
var isTestEnv = api.env('test')
if (!validEnv.includes(currentEnv)) {
throw new Error(
'Please specify a valid `NODE_ENV` or ' +
'`BABEL_ENV` environment variables. Valid values are "development", ' +
'"test", and "production". Instead, received: ' +
JSON.stringify(currentEnv) +
'.'
)
}
return {
presets: [
isTestEnv && [
'@babel/preset-env',
{
targets: {
node: 'current'
}
}
],
(isProductionEnv || isDevelopmentEnv) && [
'@babel/preset-env',
{
forceAllTransforms: true,
useBuiltIns: 'entry',
corejs: 3,
modules: false,
exclude: ['transform-typeof-symbol']
}
]
].filter(Boolean),
plugins: [
'babel-plugin-macros',
'@babel/plugin-syntax-dynamic-import',
isTestEnv && 'babel-plugin-dynamic-import-node',
'@babel/plugin-transform-destructuring',
[
'@babel/plugin-proposal-class-properties',
{
loose: true
}
],
[
'@babel/plugin-proposal-object-rest-spread',
{
useBuiltIns: true
}
],
[
'@babel/plugin-transform-runtime',
{
helpers: false
}
],
[
'@babel/plugin-transform-regenerator',
{
async: false
}
]
].filter(Boolean)
}
}
================================================
FILE: bin/bundle
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'bundle' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "rubygems"
m = Module.new do
module_function
def invoked_as_script?
File.expand_path($0) == File.expand_path(__FILE__)
end
def env_var_version
ENV["BUNDLER_VERSION"]
end
def cli_arg_version
return unless invoked_as_script? # don't want to hijack other binstubs
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
bundler_version = nil
update_index = nil
ARGV.each_with_index do |a, i|
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
bundler_version = a
end
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
bundler_version = $1
update_index = i
end
bundler_version
end
def gemfile
gemfile = ENV["BUNDLE_GEMFILE"]
return gemfile if gemfile && !gemfile.empty?
File.expand_path("../../Gemfile", __FILE__)
end
def lockfile
lockfile =
case File.basename(gemfile)
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
else "#{gemfile}.lock"
end
File.expand_path(lockfile)
end
def lockfile_version
return unless File.file?(lockfile)
lockfile_contents = File.read(lockfile)
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
Regexp.last_match(1)
end
def bundler_version
@bundler_version ||=
env_var_version || cli_arg_version ||
lockfile_version
end
def bundler_requirement
return "#{Gem::Requirement.default}.a" unless bundler_version
bundler_gem_version = Gem::Version.new(bundler_version)
requirement = bundler_gem_version.approximate_recommendation
return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
requirement += ".a" if bundler_gem_version.prerelease?
requirement
end
def load_bundler!
ENV["BUNDLE_GEMFILE"] ||= gemfile
activate_bundler
end
def activate_bundler
gem_error = activation_error_handling do
gem "bundler", bundler_requirement
end
return if gem_error.nil?
require_error = activation_error_handling do
require "bundler/version"
end
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
exit 42
end
def activation_error_handling
yield
nil
rescue StandardError, LoadError => e
e
end
end
m.load_bundler!
if m.invoked_as_script?
load Gem.bin_path("bundler", "bundle")
end
================================================
FILE: bin/dev
================================================
#!/usr/bin/env sh
if gem list --no-installed --exact --silent foreman; then
echo "Installing foreman..."
gem install foreman
fi
# Default to port 3000 if not specified
export PORT="${PORT:-3000}"
exec foreman start -f Procfile.dev --env /dev/null "$@"
================================================
FILE: bin/rails
================================================
#!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"
================================================
FILE: bin/rake
================================================
#!/usr/bin/env ruby
require_relative "../config/boot"
require "rake"
Rake.application.run
================================================
FILE: bin/setup
================================================
#!/usr/bin/env ruby
require 'fileutils'
# path to your application root.
APP_ROOT = File.expand_path('..', __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
end
FileUtils.chdir APP_ROOT do
# This script is a way to setup or update your development environment automatically.
# This script is idempotent, so that you can run it at anytime and get an expectable outcome.
# Add necessary setup steps to this file.
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
# Install JavaScript dependencies
system('bin/yarn')
# puts "\n== Copying sample files =="
unless File.exist?('config/database.yml')
FileUtils.cp 'config/database.yml.example', 'config/database.yml'
end
unless File.exist?('config/application.yml')
FileUtils.cp 'config/application.yml.example', 'config/application.yml'
end
puts "\n== Preparing database =="
system! 'bin/rails db:prepare'
puts "\n== Preparing db:seed =="
system! 'bin/rails db:seed'
puts "\n== Removing old logs and tempfiles =="
system! 'bin/rails log:clear tmp:clear'
puts "\n== Restarting application server =="
system! 'bin/rails restart'
end
================================================
FILE: bin/spring
================================================
#!/usr/bin/env ruby
if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"])
gem "bundler"
require "bundler"
# Load Spring without loading other gems in the Gemfile, for speed.
Bundler.locked_gems&.specs&.find { |spec| spec.name == "spring" }&.tap do |spring|
Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
gem "spring", spring.version
require "spring/binstub"
rescue Gem::LoadError
# Ignore when Spring is not installed.
end
end
================================================
FILE: bin/yarn
================================================
#!/usr/bin/env ruby
APP_ROOT = File.expand_path('..', __dir__)
Dir.chdir(APP_ROOT) do
yarn = ENV["PATH"].split(File::PATH_SEPARATOR).
select { |dir| File.expand_path(dir) != __dir__ }.
product(["yarn", "yarn.cmd", "yarn.ps1"]).
map { |dir, file| File.expand_path(file, dir) }.
find { |file| File.executable?(file) }
if yarn
exec yarn, *ARGV
else
$stderr.puts "Yarn executable was not detected in the system."
$stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
exit 1
end
end
================================================
FILE: config/application.rb
================================================
require_relative "boot"
require "rails/all"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module Wblog
class Application < Rails::Application
config.generators do |g|
g.test_framework :rspec,
fixtures: true,
view_specs: false,
helper_specs: false,
routing_specs: false,
controller_specs: false,
request_specs: false
g.fixture_replacement :factory_bot, dir: "spec/factories"
end
config.generators.assets = false
config.generators.helper = false
config.time_zone = 'Beijing'
config.i18n.available_locales = [:en, :'zh-CN']
config.i18n.default_locale = :'zh-CN'
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 7.0
# Please, add to the `ignore` list any other `lib` subdirectories that do
# not contain `.rb` files, or that should not be reloaded or eager loaded.
# Common ones are `templates`, `generators`, or `middleware`, for example.
config.autoload_lib(ignore: %w(assets tasks))
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
end
end
================================================
FILE: config/application.yml.example
================================================
# This section MUST be configured
SITE_NAME: "WinDy's Blog"
# domain name used by action cable
DOMAIN: ''
PROTOCOL: http
# sidekiq db
SIDEKIQ_DB: '1'
# google anlytics
GA: ''
# Rails secret token, use `rake secret` to generate new one here.
SECRET_KEY_BASE: 'e4122773d4324fce978c52cde790d84d14f7194f377aea41b7b8302d1d10150e6076a3b7e5e0c1f24ca330cf0a058482c95ea37908bba1722d0761ba5d4e566a'
#locale: en or zh-CN, zh-CN is default value
LOCALE: 'zh-CN'
# sidekiq redis namespace, if you configure two blog in on VPS, you should change it.
# default is wblog
REDIS_NAMESPACE: 'wblog'
# META description for SEO
INTRODUCE: '这是李亚飞的博客, 李亚飞是暂住在深圳的一名 Ruby 程序员, 这里是关于技术, 创业, 生活的思考'
#Website Address for footer display without http
SITE_ADDRESS: 'yafeilee.com'
#Website Year for footer display
SITE_YEAR: '© 2012 - 2019'
#Website beian for footer display
SITE_BEIAN: '粤ICP备19030132号-3'
# optional
# google analytics, blank it if you don't need
GOOGLE: ''
# CDN ( optional )
CDN: ''
# Email Setting, see more: /config/environments/production.rb
MAIL_SERVER: ''
DOMAIN_NAME: ''
MAIL_USERNAME: ''
MAIL_PASSWORD: ''
================================================
FILE: config/backup.rb.example
================================================
# encoding: utf-8
## backup gem example
## Howto:
## $ gem install backup
## $ backup generate:model --trigger wblog --archives --storages='local' --compressor='gzip'
## $ cp config/backup.rb.example ~/Backup/models/wblog.rb
## $ backup perform --trigger wblog
Model.new(:wblog, 'Description for wblog') do
database PostgreSQL do |db|
db.name = "wblog_production"
db.username = "postgres"
db.password = "postgres"
db.host = "localhost"
db.port = 5432
end
archive :rails_config do |archive|
archive.add "/data/www/wblog/shared/config/"
archive.add "/etc/nginx/conf.d/"
end
store_with Local do |local|
local.path = "/data/www/backups/"
local.keep = 5
end
# Use FTP instead of Local in production environments
#store_with FTP do |server|
#server.username = ""
#server.password = ""
#server.ip = ""
#server.port = 21
#server.path = "~/backups/"
#server.keep = 5
## server.keep = Time.now - 2592000 # Remove all backups older than 1 month.
#server.passive_mode = false
#end
compress_with Gzip
end
================================================
FILE: config/boot.rb
================================================
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require "bundler/setup" # Set up gems listed in the Gemfile.
require "bootsnap/setup" # Speed up boot time by caching expensive operations.
================================================
FILE: config/cable.yml
================================================
development:
adapter: async
test:
adapter: test
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: wblog_production
================================================
FILE: config/credentials.yml.enc
================================================
lNIHR59bC+oahS9eflbwKRqAEUPxasN91LCSNg875kdit4+EUfInV7wQEZ8bsnI+aZxxEF+Rbx9reUR42p5yCSJQCRv8BB6W0bwX65lPypdeivt+puy1SmcLdWWkBcLAyFUAQiVw1Qk+/XrZWqT/P1pIVX1fMBJ/cAIddTZvooIl2PXMEJYmGFUq0sjCYpfsZqrGB1R2WT/7Ks8YAiOG2nyzfaY1OrhRd7UkaCk37SolIYoyrf8gTgZ56mcEcBn9801YZ6hNAmdwUb66TeUMwq+HyJJtwPPDCnqOxlUjbEQVnlqJ27qTvatQj3z2w+NK6UzgYjoZ+m1TQlg0pXqnnemsf2PdWCpmlutUyNxnFZX/Zshdi9gLfXDvUtvtHsUDuWMX7HUQ5bFGNoLiKdRMhxkirxG9qDepDwuo--yAFRd2ZelQPqJupj--VZZY7ftgfjWkvrItf9JYig==
================================================
FILE: config/database.yml.example
================================================
development:
adapter: postgresql
host: localhost
encoding: unicode
database: wblog_development
pool: 5
username: postgres
password: postgres
template: template0
test:
adapter: postgresql
host: localhost
encoding: unicode
database: wblog_test
pool: 5
username: postgres
password: postgres
template: template0
production:
adapter: postgresql
host: localhost
encoding: unicode
database: wblog_production
pool: 16
username: postgres
password: postgres
template: template0
================================================
FILE: config/deploy/production.rb
================================================
set :domain, 'yafeilee.com'
set :deploy_to, '/data/www/wblog'
set :repository, 'git@github.com:windy/wblog.git'
set :branch, 'master'
set :user, 'ruby'
================================================
FILE: config/deploy.rb
================================================
STDOUT.sync = true
set :stages, %w(production)
set :default_stage, 'production'
require 'mina/bundler'
require 'mina/rails'
require 'mina/git'
require 'mina/rbenv'
require 'mina/puma'
require "mina_sidekiq/tasks"
require 'mina/logs'
require 'mina/multistage'
set :asset_dirs, fetch(:asset_dirs, []).push('app/javascript')
set :shared_dirs, fetch(:shared_dirs, []).push('log', 'public/uploads', 'public/packs', 'node_modules', 'storage')
set :shared_files, fetch(:shared_files, []).push('config/database.yml', 'config/application.yml')
set :puma_config, ->{ "#{fetch(:current_path)}/config/puma.rb" }
set :sidekiq_pid, ->{ "#{fetch(:shared_path)}/tmp/pids/sidekiq.pid" }
task :remote_environment do
invoke :'rbenv:load'
end
task :setup do
command %[mkdir -p "#{fetch(:shared_path)}/tmp/sockets"]
command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/tmp/sockets"]
command %[mkdir -p "#{fetch(:shared_path)}/tmp/pids"]
command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/tmp/pids"]
command %[mkdir -p "#{fetch(:shared_path)}/log"]
command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/log"]
command %[mkdir -p "#{fetch(:shared_path)}/public/uploads"]
command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/public/uploads"]
command %[mkdir -p "#{fetch(:shared_path)}/public/packs"]
command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/public/packs"]
command %[mkdir -p "#{fetch(:shared_path)}/node_modules"]
command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/node_modules"]
command %[mkdir -p "#{fetch(:shared_path)}/storage"]
command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/storage"]
command %[mkdir -p "#{fetch(:shared_path)}/config"]
command %[chmod g+rx,u+rwx "#{fetch(:shared_path)}/config"]
command %[touch "#{fetch(:shared_path)}/config/application.yml"]
command %[echo "-----> Be sure to edit '#{fetch(:shared_path)}/config/application.yml'"]
command %[touch "#{fetch(:shared_path)}/config/database.yml"]
command %[echo "-----> Be sure to edit '#{fetch(:shared_path)}/config/database.yml'"]
end
desc "Clear bootsnap cache"
task :clear_bootsnap do
command %[echo "Clear bootsnap cache..."]
command %[rm -rf "#{fetch(:shared_path)}/tmp/bootsnap-*"]
end
desc "Deploys the current version to the server."
task :deploy do
command %[echo "-----> Server: #{fetch(:domain)}"]
command %[echo "-----> Path: #{fetch(:deploy_to)}"]
command %[echo "-----> Branch: #{fetch(:branch)}"]
deploy do
invoke :'git:clone'
invoke :'deploy:link_shared_paths'
invoke :'bundle:install'
invoke :'rails:db_migrate'
invoke :clear_bootsnap
invoke :'rails:assets_precompile'
invoke :'deploy:cleanup'
on :launch do
invoke :'rbenv:load'
invoke :'sidekiq:quiet'
invoke :'puma:smart_restart'
invoke :'sidekiq:restart'
end
end
end
desc "Prepare the first deploy on server."
task :first_deploy do
comm
gitextract_s_ecaci2/
├── .1024
├── .ackrc
├── .browserslistrc
├── .gitattributes
├── .gitignore
├── .rspec
├── .ruby-version
├── .travis.yml
├── Gemfile
├── Procfile.dev
├── README.md
├── README.zh-CN.md
├── Rakefile
├── app/
│ ├── assets/
│ │ ├── builds/
│ │ │ └── .keep
│ │ └── stylesheets/
│ │ ├── about.scss
│ │ ├── aboutme_welcome.scss
│ │ ├── admin/
│ │ │ └── posts.scss
│ │ ├── admin.scss
│ │ ├── application.scss
│ │ ├── archives.scss
│ │ ├── blogs.scss
│ │ ├── bootstrap_custom.scss
│ │ ├── browserslist
│ │ ├── comments.scss
│ │ ├── fontawsome_custom.scss
│ │ ├── footer.scss
│ │ ├── head.scss
│ │ ├── highlight.scss
│ │ ├── libs/
│ │ │ └── markdown.scss
│ │ ├── like_and_weixin.scss
│ │ ├── new_year.scss
│ │ └── qrcodes.scss
│ ├── channels/
│ │ └── application_cable/
│ │ ├── channel.rb
│ │ └── connection.rb
│ ├── controllers/
│ │ ├── admin/
│ │ │ ├── accounts_controller.rb
│ │ │ ├── all_comments_controller.rb
│ │ │ ├── base_controller.rb
│ │ │ ├── comments_controller.rb
│ │ │ ├── dashboard_controller.rb
│ │ │ ├── labels_controller.rb
│ │ │ ├── posts_controller.rb
│ │ │ └── sessions_controller.rb
│ │ ├── application_controller.rb
│ │ ├── archives_controller.rb
│ │ ├── blogs_controller.rb
│ │ ├── comments_controller.rb
│ │ ├── concerns/
│ │ │ └── .keep
│ │ ├── home_controller.rb
│ │ ├── likes_controller.rb
│ │ └── photos_controller.rb
│ ├── helpers/
│ │ └── application_helper.rb
│ ├── javascript/
│ │ ├── about.js
│ │ ├── admin/
│ │ │ ├── posts.js
│ │ │ └── sidebar.js
│ │ ├── admin.js
│ │ ├── application.js
│ │ ├── base.js
│ │ ├── channels/
│ │ │ ├── consumer.js
│ │ │ └── index.js
│ │ ├── controllers/
│ │ │ ├── admin_label_controller.js
│ │ │ ├── index.js
│ │ │ ├── like_controller.js
│ │ │ └── qrcode_controller.js
│ │ ├── ga.js.erb
│ │ └── libs/
│ │ ├── add_jquery.js
│ │ ├── ddscrollspy.js
│ │ ├── jquery.atwho.js
│ │ ├── jquery.html5-fileupload.js
│ │ └── qrcode.js
│ ├── jobs/
│ │ └── application_job.rb
│ ├── mailers/
│ │ └── application_mailer.rb
│ ├── models/
│ │ ├── administrator.rb
│ │ ├── application_record.rb
│ │ ├── comment.rb
│ │ ├── concerns/
│ │ │ └── .keep
│ │ ├── label.rb
│ │ ├── like.rb
│ │ ├── photo.rb
│ │ └── post.rb
│ ├── uploaders/
│ │ └── photo_uploader.rb
│ └── views/
│ ├── admin/
│ │ ├── accounts/
│ │ │ └── edit.html.slim
│ │ ├── all_comments/
│ │ │ └── index.html.slim
│ │ ├── comments/
│ │ │ └── index.html.slim
│ │ ├── dashboard/
│ │ │ └── index.html.slim
│ │ ├── labels/
│ │ │ ├── _form.html.slim
│ │ │ ├── edit.html.slim
│ │ │ ├── index.html.slim
│ │ │ └── new.html.slim
│ │ ├── posts/
│ │ │ ├── _form.html.slim
│ │ │ ├── edit.html.slim
│ │ │ ├── index.html.slim
│ │ │ └── new.html.slim
│ │ └── sessions/
│ │ └── new.html.slim
│ ├── archives/
│ │ └── index.html.slim
│ ├── blogs/
│ │ ├── _comment.html.slim
│ │ ├── _post.html.slim
│ │ ├── _post_head.html.slim
│ │ ├── _qrcode.html.slim
│ │ ├── edit.html.slim
│ │ └── show.html.slim
│ ├── comments/
│ │ ├── _comment_content.html.slim
│ │ ├── _comment_pre.html.slim
│ │ └── create.html.slim
│ ├── common/
│ │ ├── _copyright.en.html.slim
│ │ ├── _copyright.html.slim
│ │ ├── _no_blog_here.en.html.slim
│ │ ├── _no_blog_here.html.slim
│ │ ├── _welcome.en.html.slim
│ │ ├── _welcome.html.slim
│ │ └── _welcome_new_year.html.slim
│ ├── home/
│ │ ├── _post_head.html.slim
│ │ ├── about.html.slim
│ │ └── index.html.slim
│ ├── kaminari/
│ │ ├── _first_page.html.slim
│ │ ├── _gap.html.slim
│ │ ├── _last_page.html.slim
│ │ ├── _next_page.html.slim
│ │ ├── _page.html.slim
│ │ ├── _paginator.html.slim
│ │ └── _prev_page.html.slim
│ ├── layouts/
│ │ ├── _footer.html.slim
│ │ ├── admin.html.slim
│ │ ├── application.html.slim
│ │ ├── mailer.html.erb
│ │ └── mailer.text.erb
│ └── shared/
│ └── admin/
│ ├── _flash_messages.html.slim
│ ├── _header.html.slim
│ └── _sidebar.html.slim
├── babel.config.js
├── bin/
│ ├── bundle
│ ├── dev
│ ├── rails
│ ├── rake
│ ├── setup
│ ├── spring
│ └── yarn
├── config/
│ ├── application.rb
│ ├── application.yml.example
│ ├── backup.rb.example
│ ├── boot.rb
│ ├── cable.yml
│ ├── credentials.yml.enc
│ ├── database.yml.example
│ ├── deploy/
│ │ └── production.rb
│ ├── deploy.rb
│ ├── environment.rb
│ ├── environments/
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers/
│ │ ├── application_controller_renderer.rb
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── browser_warrior.rb
│ │ ├── content_security_policy.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── kaminari_config.rb
│ │ ├── mime_types.rb
│ │ ├── permissions_policy.rb
│ │ ├── sidekiq.rb
│ │ ├── simple_form.rb
│ │ ├── simple_form_bootstrap.rb
│ │ └── wrap_parameters.rb
│ ├── locales/
│ │ ├── en.yml
│ │ ├── simple_form.en.yml
│ │ ├── simple_form.zh-CN.yml
│ │ └── zh-CN.yml
│ ├── logrotate.conf.example
│ ├── monit.conf.example
│ ├── nginx.conf.example
│ ├── nginx.ssl.conf.example
│ ├── puma.rb
│ ├── routes.rb
│ ├── secret.yml
│ ├── sidekiq.yml
│ ├── spring.rb
│ └── storage.yml
├── config.ru
├── db/
│ ├── migrate/
│ │ ├── 20160420082319_create_posts.rb
│ │ ├── 20160420082536_create_comments.rb
│ │ ├── 20160420082629_create_labels.rb
│ │ ├── 20160420082734_create_likes.rb
│ │ ├── 20160420082811_create_photos.rb
│ │ ├── 20160421035040_create_join_table_post_label.rb
│ │ ├── 20210614151036_create_active_storage_tables.active_storage.rb
│ │ ├── 20210614151102_create_administrators.rb
│ │ ├── 20250120142353_add_service_name_to_active_storage_blobs.active_storage.rb
│ │ ├── 20250120142354_create_active_storage_variant_records.active_storage.rb
│ │ └── 20250120142355_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb
│ ├── schema.rb
│ └── seeds.rb
├── doc/
│ └── .gitkeep
├── lib/
│ ├── assets/
│ │ └── .keep
│ ├── markdown.rb
│ ├── tasks/
│ │ └── .keep
│ └── templates/
│ └── slim/
│ └── scaffold/
│ └── _form.html.slim
├── log/
│ └── .keep
├── package.json
├── postcss.config.js
├── public/
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ └── robots.txt
├── spec/
│ ├── controllers/
│ │ ├── admin/
│ │ │ ├── comments_controller_spec.rb
│ │ │ ├── dashboard_controller_spec.rb
│ │ │ ├── posts_controller_spec.rb
│ │ │ └── sessions_controller_spec.rb
│ │ ├── archives_controller_spec.rb
│ │ ├── blogs_controller_spec.rb
│ │ ├── home_controller_spec.rb
│ │ └── likes_controller_spec.rb
│ ├── factories/
│ │ ├── comments.rb
│ │ ├── labels.rb
│ │ ├── posts.rb
│ │ └── subscribes.rb
│ ├── models/
│ │ ├── like_spec.rb
│ │ └── post_spec.rb
│ ├── rails_helper.rb
│ ├── spec_helper.rb
│ └── support/
│ ├── capybara.rb
│ ├── database_cleaner.rb
│ └── factory_bot.rb
├── storage/
│ └── .keep
├── test/
│ ├── application_system_test_case.rb
│ ├── channels/
│ │ └── application_cable/
│ │ └── connection_test.rb
│ ├── controllers/
│ │ └── .keep
│ ├── fixtures/
│ │ └── files/
│ │ └── .keep
│ ├── helpers/
│ │ └── .keep
│ ├── integration/
│ │ └── .keep
│ ├── mailers/
│ │ └── .keep
│ ├── models/
│ │ └── .keep
│ ├── system/
│ │ └── .keep
│ └── test_helper.rb
├── tmp/
│ └── .keep
└── vendor/
└── .keep
SYMBOL INDEX (152 symbols across 51 files)
FILE: app/channels/application_cable/channel.rb
type ApplicationCable (line 1) | module ApplicationCable
class Channel (line 2) | class Channel < ActionCable::Channel::Base
FILE: app/channels/application_cable/connection.rb
type ApplicationCable (line 1) | module ApplicationCable
class Connection (line 2) | class Connection < ActionCable::Connection::Base
FILE: app/controllers/admin/accounts_controller.rb
class Admin::AccountsController (line 1) | class Admin::AccountsController < Admin::BaseController
method edit (line 2) | def edit
method update (line 5) | def update
method admin_params (line 21) | def admin_params
FILE: app/controllers/admin/all_comments_controller.rb
class Admin::AllCommentsController (line 1) | class Admin::AllCommentsController < Admin::BaseController
method index (line 2) | def index
method destroy (line 6) | def destroy
FILE: app/controllers/admin/base_controller.rb
class Admin::BaseController (line 1) | class Admin::BaseController < ActionController::Base
method authenticate_admin! (line 12) | def authenticate_admin!
method current_admin (line 24) | def current_admin
method admin_sign_in (line 28) | def admin_sign_in(admin)
method admin_sign_out (line 33) | def admin_sign_out
FILE: app/controllers/admin/comments_controller.rb
class Admin::CommentsController (line 1) | class Admin::CommentsController < Admin::BaseController
method index (line 6) | def index
method destroy (line 10) | def destroy
FILE: app/controllers/admin/dashboard_controller.rb
class Admin::DashboardController (line 1) | class Admin::DashboardController < Admin::BaseController
method index (line 2) | def index
FILE: app/controllers/admin/labels_controller.rb
class Admin::LabelsController (line 1) | class Admin::LabelsController < Admin::BaseController
method index (line 2) | def index
method new (line 6) | def new
method edit (line 10) | def edit
method create (line 14) | def create
method update (line 23) | def update
method destroy (line 29) | def destroy
method label_params (line 36) | def label_params
FILE: app/controllers/admin/posts_controller.rb
class Admin::PostsController (line 1) | class Admin::PostsController < Admin::BaseController
method new (line 2) | def new
method edit (line 6) | def edit
method destroy (line 10) | def destroy
method index (line 21) | def index
method create (line 25) | def create
method update (line 37) | def update
method preview (line 49) | def preview
method post_params (line 54) | def post_params
FILE: app/controllers/admin/sessions_controller.rb
class Admin::SessionsController (line 1) | class Admin::SessionsController < Admin::BaseController
method new (line 8) | def new
method create (line 11) | def create
method destroy (line 22) | def destroy
FILE: app/controllers/application_controller.rb
class ApplicationController (line 1) | class ApplicationController < ActionController::Base
FILE: app/controllers/archives_controller.rb
class ArchivesController (line 1) | class ArchivesController < ApplicationController
method index (line 2) | def index
FILE: app/controllers/blogs_controller.rb
class BlogsController (line 1) | class BlogsController < ApplicationController
method show (line 2) | def show
method edit (line 12) | def edit
FILE: app/controllers/comments_controller.rb
class CommentsController (line 1) | class CommentsController < ApplicationController
method create (line 4) | def create
method refresh (line 23) | def refresh
method comment_params (line 29) | def comment_params
FILE: app/controllers/home_controller.rb
class HomeController (line 1) | class HomeController < ApplicationController
method index (line 2) | def index
method about (line 7) | def about
FILE: app/controllers/likes_controller.rb
class LikesController (line 1) | class LikesController < ApplicationController
method index (line 4) | def index
method create (line 9) | def create
method destroy (line 20) | def destroy
FILE: app/controllers/photos_controller.rb
class PhotosController (line 1) | class PhotosController < ApplicationController
method create (line 2) | def create
method md_url (line 9) | def md_url(url)
FILE: app/helpers/application_helper.rb
type ApplicationHelper (line 1) | module ApplicationHelper
function body_class (line 3) | def body_class
function admin_active_for (line 27) | def admin_active_for(controller_name, navbar_name)
function current_path (line 34) | def current_path
function flash_class (line 38) | def flash_class(level)
function format_time (line 47) | def format_time(time)
function format_date (line 51) | def format_date(time)
function search_highlight (line 55) | def search_highlight(title, q)
FILE: app/javascript/controllers/admin_label_controller.js
method connect (line 9) | connect() {
method disconnected (line 16) | disconnected() {
FILE: app/javascript/controllers/like_controller.js
method toggle (line 7) | toggle(e) {
FILE: app/javascript/controllers/qrcode_controller.js
method connect (line 6) | connect() {
method greet (line 9) | greet(e) {
FILE: app/javascript/libs/ddscrollspy.js
function inrange (line 49) | function inrange(el, range, field){ // check if "playing field" is insid...
function spyonmenuitems (line 77) | function spyonmenuitems($menu){
function highlightitem (line 130) | function highlightitem(){
function updatetargetpos (line 180) | function updatetargetpos(){
FILE: app/javascript/libs/jquery.atwho.js
function Caret (line 24) | function Caret($inputor) {
function Mirror (line 201) | function Mirror($inputor) {
function App (line 307) | function App(inputor) {
function Controller (line 421) | function Controller(app, key) {
function Model (line 545) | function Model(context) {
function View (line 600) | function View(context) {
FILE: app/javascript/libs/qrcode.js
function QR8bitByte (line 29) | function QR8bitByte(data) {
function QRCodeModel (line 78) | function QRCodeModel(typeNumber, errorCorrectLevel) {
function QRPolynomial (line 139) | function QRPolynomial(num,shift){if(num.length==undefined){throw new Err...
function QRRSBlock (line 146) | function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this...
function QRBitBuffer (line 149) | function QRBitBuffer(){this.buffer=[];this.length=0;}
function _isSupportCanvas (line 154) | function _isSupportCanvas() {
function _getAndroid (line 159) | function _getAndroid() {
function makeSVG (line 191) | function makeSVG(tag, attrs) {
function _onMakeImage (line 276) | function _onMakeImage() {
function _safeSetDataURI (line 310) | function _safeSetDataURI(fSuccess, fFail) {
function _getTypeNumber (line 469) | function _getTypeNumber(sText, nCorrectLevel) {
function _getUTF8Length (line 505) | function _getUTF8Length(sText) {
FILE: app/jobs/application_job.rb
class ApplicationJob (line 1) | class ApplicationJob < ActiveJob::Base
FILE: app/mailers/application_mailer.rb
class ApplicationMailer (line 1) | class ApplicationMailer < ActionMailer::Base
FILE: app/models/administrator.rb
class Administrator (line 1) | class Administrator < ApplicationRecord
FILE: app/models/application_record.rb
class ApplicationRecord (line 1) | class ApplicationRecord < ActiveRecord::Base
FILE: app/models/comment.rb
class Comment (line 1) | class Comment < ApplicationRecord
method reply_emails (line 8) | def reply_emails
FILE: app/models/label.rb
class Label (line 1) | class Label < ApplicationRecord
FILE: app/models/like.rb
class Like (line 1) | class Like < ApplicationRecord
FILE: app/models/photo.rb
class Photo (line 1) | class Photo < ApplicationRecord
FILE: app/models/post.rb
class Post (line 2) | class Post < ActiveRecord::Base
method content_html (line 11) | def content_html
method render_html (line 15) | def self.render_html(content)
method visited (line 21) | def visited
method sub_content (line 28) | def sub_content
method meta_content (line 33) | def meta_content
method labels_content (line 40) | def labels_content( need_blank=false )
method liked_count (line 46) | def liked_count
method liked_by? (line 50) | def liked_by?(like_id)
FILE: app/uploaders/photo_uploader.rb
class PhotoUploader (line 1) | class PhotoUploader < CarrierWave::Uploader::Base
method store_dir (line 6) | def store_dir
method extension_white_list (line 20) | def extension_white_list
FILE: config/application.rb
type Wblog (line 9) | module Wblog
class Application (line 10) | class Application < Rails::Application
FILE: config/routes.rb
class AdminConstraint (line 4) | class AdminConstraint
method matches? (line 5) | def matches?(request)
FILE: db/migrate/20160420082319_create_posts.rb
class CreatePosts (line 1) | class CreatePosts < ActiveRecord::Migration[6.1]
method change (line 2) | def change
FILE: db/migrate/20160420082536_create_comments.rb
class CreateComments (line 1) | class CreateComments < ActiveRecord::Migration[6.1]
method change (line 2) | def change
FILE: db/migrate/20160420082629_create_labels.rb
class CreateLabels (line 1) | class CreateLabels < ActiveRecord::Migration[6.1]
method change (line 2) | def change
FILE: db/migrate/20160420082734_create_likes.rb
class CreateLikes (line 1) | class CreateLikes < ActiveRecord::Migration[6.1]
method change (line 2) | def change
FILE: db/migrate/20160420082811_create_photos.rb
class CreatePhotos (line 1) | class CreatePhotos < ActiveRecord::Migration[6.1]
method change (line 2) | def change
FILE: db/migrate/20160421035040_create_join_table_post_label.rb
class CreateJoinTablePostLabel (line 1) | class CreateJoinTablePostLabel < ActiveRecord::Migration[6.1]
method change (line 2) | def change
FILE: db/migrate/20210614151036_create_active_storage_tables.active_storage.rb
class CreateActiveStorageTables (line 2) | class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
method change (line 3) | def change
FILE: db/migrate/20210614151102_create_administrators.rb
class CreateAdministrators (line 1) | class CreateAdministrators < ActiveRecord::Migration[6.1]
method change (line 2) | def change
FILE: db/migrate/20250120142353_add_service_name_to_active_storage_blobs.active_storage.rb
class AddServiceNameToActiveStorageBlobs (line 2) | class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
method up (line 3) | def up
method down (line 17) | def down
FILE: db/migrate/20250120142354_create_active_storage_variant_records.active_storage.rb
class CreateActiveStorageVariantRecords (line 2) | class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
method change (line 3) | def change
method primary_key_type (line 17) | def primary_key_type
method blobs_primary_key_type (line 22) | def blobs_primary_key_type
FILE: db/migrate/20250120142355_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb
class RemoveNotNullOnActiveStorageBlobsChecksum (line 2) | class RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migratio...
method change (line 3) | def change
FILE: lib/markdown.rb
class CodeHTML (line 4) | class CodeHTML < Redcarpet::Render::HTML
method initialize (line 7) | def initialize(extensions = {})
FILE: test/application_system_test_case.rb
class ApplicationSystemTestCase (line 3) | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
FILE: test/channels/application_cable/connection_test.rb
class ApplicationCable::ConnectionTest (line 3) | class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
FILE: test/test_helper.rb
class ActiveSupport::TestCase (line 5) | class ActiveSupport::TestCase
Condensed preview — 236 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (304K chars).
[
{
"path": ".1024",
"chars": 558,
"preview": "# .1024 Configuration file for project run commands, compilation and debug settings (optional);\n# Any changes made will "
},
{
"path": ".ackrc",
"chars": 85,
"preview": "--ignore-file=ext:svg\n--ignore-dir=public\n--ignore-dir=tmp\n--ignore-dir=node_modules\n"
},
{
"path": ".browserslistrc",
"chars": 9,
"preview": "defaults\n"
},
{
"path": ".gitattributes",
"chars": 327,
"preview": "# See https://git-scm.com/docs/gitattributes for more about git attribute files.\n\n# Mark the database schema as having b"
},
{
"path": ".gitignore",
"chars": 1032,
"preview": "# See https://help.github.com/articles/ignoring-files for more about ignoring files.\n#\n# If you find yourself ignoring t"
},
{
"path": ".rspec",
"chars": 53,
"preview": "--color\n--require spec_helper\n--require rails_helper\n"
},
{
"path": ".ruby-version",
"chars": 6,
"preview": "3.1.2\n"
},
{
"path": ".travis.yml",
"chars": 909,
"preview": "# http://about.travis-ci.org/docs/user/build-configuration/\nenv:\n global:\n - CC_TEST_REPORTER_ID=787a2f89b15c637323c"
},
{
"path": "Gemfile",
"chars": 1579,
"preview": "source 'https://rubygems.org'\ngit_source(:github) { |repo| \"https://github.com/#{repo}.git\" }\n\nruby '3.1.2'\ngem 'rails',"
},
{
"path": "Procfile.dev",
"chars": 105,
"preview": "web: env RUBY_DEBUG_OPEN=true bin/rails server\ncss: yarn build:css --watch --poll\njs: yarn build --watch\n"
},
{
"path": "README.md",
"chars": 5035,
"preview": "WBlog\n=======\n[](https://travis-ci.org/windy/wblog)\n"
},
{
"path": "README.zh-CN.md",
"chars": 2929,
"preview": "WBlog\n=======\n[](https://travis-ci.org/windy/wblog)\n"
},
{
"path": "Rakefile",
"chars": 227,
"preview": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they wil"
},
{
"path": "app/assets/builds/.keep",
"chars": 0,
"preview": ""
},
{
"path": "app/assets/stylesheets/about.scss",
"chars": 5399,
"preview": ".home-about-page {\n .fixed {\n position: fixed;\n top: 0;\n width: 100%;\n z-index: 99;\n left: 0;\n }\n\n .re"
},
{
"path": "app/assets/stylesheets/aboutme_welcome.scss",
"chars": 472,
"preview": ".self-introduce {\n @media screen and (min-width: 64em) {\n margin-top: 1.875rem;\n }\n\n .box {\n margin-bottom: 2re"
},
{
"path": "app/assets/stylesheets/admin/posts.scss",
"chars": 429,
"preview": ".admin-posts-edit-page, .admin-posts-index-page {\n td {\n i.fa {\n margin-right: 0.2rem;\n }\n span {\n m"
},
{
"path": "app/assets/stylesheets/admin.scss",
"chars": 440,
"preview": "@use 'fontawsome_custom';\n@use 'bootstrap_custom';\n@use 'libs/markdown';\n@use 'admin-lte/dist/css/adminlte';\n@use 'admin"
},
{
"path": "app/assets/stylesheets/application.scss",
"chars": 355,
"preview": "@use 'bootstrap_custom';\n@use 'fontawsome_custom';\n@use 'libs/markdown';\n\n@use 'aboutme_welcome';\n@use 'archives';\n@use "
},
{
"path": "app/assets/stylesheets/archives.scss",
"chars": 1023,
"preview": ".archives-field {\n padding-top: 0.5rem;\n margin-top: 1rem;\n padding-bottom: 0.5rem;\n\n i {\n margin-right: 0.5rem;\n"
},
{
"path": "app/assets/stylesheets/blogs.scss",
"chars": 1598,
"preview": ".home-index-page, .blogs-show-page {\n .blog-title {\n margin-top: 1rem;\n line-height: 1.5;\n }\n\n .ptag {\n marg"
},
{
"path": "app/assets/stylesheets/bootstrap_custom.scss",
"chars": 328,
"preview": "@use \"sass:color\";\n\n$link-color: #2199e8 !default;\n$link-decoration: "
},
{
"path": "app/assets/stylesheets/browserslist",
"chars": 48,
"preview": "last 2 versions\nie >= 9\nAndroid >= 2.3\nios >= 7\n"
},
{
"path": "app/assets/stylesheets/comments.scss",
"chars": 1556,
"preview": "#alert-container {\n border-radius: unset;\n}\n\n.comment-field {\n background-color: #333333;\n padding-top: 3rem;\n paddi"
},
{
"path": "app/assets/stylesheets/fontawsome_custom.scss",
"chars": 274,
"preview": "$fa-font-path: '@fortawesome/fontawesome-free/webfonts';\n@import '@fortawesome/fontawesome-free/scss/fontawesome';\n@impo"
},
{
"path": "app/assets/stylesheets/footer.scss",
"chars": 227,
"preview": ".footer {\n border-top: 1px solid #dddddd;\n padding: 1rem 0 2rem;\n text-align: right;\n color: #666;\n .link {\n mar"
},
{
"path": "app/assets/stylesheets/head.scss",
"chars": 207,
"preview": ".my-navbar {\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n\n a.navbar-brand:hover {\n color: #aaa;\n }\n\n .nav-lin"
},
{
"path": "app/assets/stylesheets/highlight.scss",
"chars": 3065,
"preview": ".highlight {\n .hll { background-color: #49483e }\n .c { color: #75715e } /* Comment */\n .err { color: #960050; backgro"
},
{
"path": "app/assets/stylesheets/libs/markdown.scss",
"chars": 1252,
"preview": ".markdown {\n\n word-wrap: break-word;\n overflow: hidden;\n color: #4f4444;\n\n @media screen and (min-width: 40.063em) {"
},
{
"path": "app/assets/stylesheets/like_and_weixin.scss",
"chars": 400,
"preview": ".like-button {\n color: #eaa296;\n background-color: transparent;\n border: 1px solid #e79385;\n border-radius: 10rem;\n"
},
{
"path": "app/assets/stylesheets/new_year.scss",
"chars": 350,
"preview": ".new-year {\n margin: 1rem 0;\n text-align: center;\n font-size: 1.1rem;\n background: linear-gradient(135deg, #667eea 0"
},
{
"path": "app/assets/stylesheets/qrcodes.scss",
"chars": 70,
"preview": "#image-tag {\n float: right;\n width: 200px;\n margin-bottom: 1rem;\n}\n"
},
{
"path": "app/channels/application_cable/channel.rb",
"chars": 79,
"preview": "module ApplicationCable\n class Channel < ActionCable::Channel::Base\n end\nend\n"
},
{
"path": "app/channels/application_cable/connection.rb",
"chars": 85,
"preview": "module ApplicationCable\n class Connection < ActionCable::Connection::Base\n end\nend\n"
},
{
"path": "app/controllers/admin/accounts_controller.rb",
"chars": 605,
"preview": "class Admin::AccountsController < Admin::BaseController\n def edit\n end\n\n def update\n if current_admin.authenticate"
},
{
"path": "app/controllers/admin/all_comments_controller.rb",
"chars": 404,
"preview": "class Admin::AllCommentsController < Admin::BaseController\n def index\n @comments = Comment.order(created_at: :desc)."
},
{
"path": "app/controllers/admin/base_controller.rb",
"chars": 892,
"preview": "class Admin::BaseController < ActionController::Base\n layout 'admin'\n\n protect_from_forgery with: :exception\n\n before"
},
{
"path": "app/controllers/admin/comments_controller.rb",
"chars": 471,
"preview": "class Admin::CommentsController < Admin::BaseController\n before_action do\n @post = Post.find( params[:post_id] )\n e"
},
{
"path": "app/controllers/admin/dashboard_controller.rb",
"chars": 153,
"preview": "class Admin::DashboardController < Admin::BaseController\n def index\n @posts_count = Post.all.count\n @comments_cou"
},
{
"path": "app/controllers/admin/labels_controller.rb",
"chars": 723,
"preview": "class Admin::LabelsController < Admin::BaseController\n def index\n @labels = Label.all.page(params[:page])\n end\n\n d"
},
{
"path": "app/controllers/admin/posts_controller.rb",
"chars": 1099,
"preview": "class Admin::PostsController < Admin::BaseController\n def new\n @post = Post.new\n end\n\n def edit\n @post = Post.f"
},
{
"path": "app/controllers/admin/sessions_controller.rb",
"chars": 549,
"preview": "class Admin::SessionsController < Admin::BaseController\n skip_before_action :authenticate_admin!, only: [:new, :create]"
},
{
"path": "app/controllers/application_controller.rb",
"chars": 57,
"preview": "class ApplicationController < ActionController::Base\nend\n"
},
{
"path": "app/controllers/archives_controller.rb",
"chars": 342,
"preview": "class ArchivesController < ApplicationController\n def index\n if (@q = params[:q]).blank?\n @posts = Post.order(c"
},
{
"path": "app/controllers/blogs_controller.rb",
"chars": 532,
"preview": "class BlogsController < ApplicationController\n def show\n cookies[:cable_id] = SecureRandom.uuid\n @post = Post.fin"
},
{
"path": "app/controllers/comments_controller.rb",
"chars": 715,
"preview": "class CommentsController < ApplicationController\n layout false\n\n def create\n cookies[:name] = comment_params[:name]"
},
{
"path": "app/controllers/concerns/.keep",
"chars": 0,
"preview": ""
},
{
"path": "app/controllers/home_controller.rb",
"chars": 191,
"preview": "class HomeController < ApplicationController\n def index\n @newest = Post.order(created_at: :desc).first\n @recent ="
},
{
"path": "app/controllers/likes_controller.rb",
"chars": 740,
"preview": "class LikesController < ApplicationController\n layout false\n\n def index\n post = Post.find( params[:blog_id] )\n r"
},
{
"path": "app/controllers/photos_controller.rb",
"chars": 233,
"preview": "class PhotosController < ApplicationController\n def create\n @photo = Photo.new(image: params[\"Filedata\"])\n @photo"
},
{
"path": "app/helpers/application_helper.rb",
"chars": 1632,
"preview": "module ApplicationHelper\n # Generate `{controller}-{action}-page` class for body element\n def body_class\n path = co"
},
{
"path": "app/javascript/about.js",
"chars": 521,
"preview": "import './libs/ddscrollspy'\n\n$(document).on('turbo:load', function(){\n if($('.home-about-page').length === 0) { return;"
},
{
"path": "app/javascript/admin/posts.js",
"chars": 948,
"preview": "$(document).on('turbo:load', function(){\n $('a#upload_photo').click(function(){\n $('input[type=file]').show().focus("
},
{
"path": "app/javascript/admin/sidebar.js",
"chars": 1228,
"preview": "// (function() {\n // window.App = window.App || {}\n // window.App.adminSidebar = {\n // saveSidebarScrollPosition: f"
},
{
"path": "app/javascript/admin.js",
"chars": 152,
"preview": "import './base'\nimport './libs/jquery.html5-fileupload'\nimport './libs/jquery.atwho'\nimport 'admin-lte'\nimport './admin/"
},
{
"path": "app/javascript/application.js",
"chars": 93,
"preview": "// Entry point for the build script in your package.json\n//\nimport './base'\nimport './about'\n"
},
{
"path": "app/javascript/base.js",
"chars": 477,
"preview": "// base dependency library, it should be only shared by `admin.js` and `application.js`.\n//\nimport './libs/add_jquery'\ni"
},
{
"path": "app/javascript/channels/consumer.js",
"chars": 270,
"preview": "// Action Cable provides the framework to deal with WebSockets in Rails.\n// You can generate new channels where WebSocke"
},
{
"path": "app/javascript/channels/index.js",
"chars": 116,
"preview": "// Load all the channels within this directory and all subdirectories.\n// Channel files must be named *_channel.js.\n"
},
{
"path": "app/javascript/controllers/admin_label_controller.js",
"chars": 351,
"preview": "import { Controller } from \"@hotwired/stimulus\"\nimport select2 from 'select2'\n\nwindow.select2 = select2();\n\nexport defau"
},
{
"path": "app/javascript/controllers/index.js",
"chars": 548,
"preview": "// Load all the controllers within this directory and all subdirectories.\n// Controller files must be named *_controller"
},
{
"path": "app/javascript/controllers/like_controller.js",
"chars": 787,
"preview": "import { Controller } from \"@hotwired/stimulus\"\nimport Cookies from 'js-cookie'\n\nexport default class extends Controller"
},
{
"path": "app/javascript/controllers/qrcode_controller.js",
"chars": 338,
"preview": "import { Controller } from \"@hotwired/stimulus\"\nimport '../libs/qrcode'\n\nexport default class extends Controller {\n sta"
},
{
"path": "app/javascript/ga.js.erb",
"chars": 666,
"preview": "<% if ENV['GA'].present? %>\n var script = document.createElement('script');\n script.src = 'https://www.googletagmanage"
},
{
"path": "app/javascript/libs/add_jquery.js",
"chars": 54,
"preview": "import $ from 'jquery'\nwindow.jQuery = $\nwindow.$ = $\n"
},
{
"path": "app/javascript/libs/ddscrollspy.js",
"chars": 9139,
"preview": "/*\n* DD ScrollSpy Menu Script (c) Dynamic Drive (www.dynamicdrive.com)\n* Last updated: Aug 1st, 14'\n* Visit http://www.d"
},
{
"path": "app/javascript/libs/jquery.atwho.js",
"chars": 25347,
"preview": "//\n/*\n Implement Github like autocomplete mentions\n http://ichord.github.com/At.js\n\n Copyright (c) 2013 chord.luo@gma"
},
{
"path": "app/javascript/libs/jquery.html5-fileupload.js",
"chars": 18876,
"preview": "/*\n * jQuery HTML5 File Upload\n *\n * Author: timdream at gmail.com\n * Web: http://timc.idv.tw/html5-file-upload/\n *\n "
},
{
"path": "app/javascript/libs/qrcode.js",
"chars": 33021,
"preview": "/**\n * @fileoverview\n * - Using the 'QRCode for Javascript library'\n * - Fixed dataset of 'QRCode for Javascript library"
},
{
"path": "app/jobs/application_job.rb",
"chars": 269,
"preview": "class ApplicationJob < ActiveJob::Base\n # Automatically retry jobs that encountered a deadlock\n # retry_on ActiveRecor"
},
{
"path": "app/mailers/application_mailer.rb",
"chars": 102,
"preview": "class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\nend\n"
},
{
"path": "app/models/administrator.rb",
"chars": 118,
"preview": "class Administrator < ApplicationRecord\n validates :name, presence: true, uniqueness: true\n has_secure_password\nend\n"
},
{
"path": "app/models/application_record.rb",
"chars": 78,
"preview": "class ApplicationRecord < ActiveRecord::Base\n self.abstract_class = true\nend\n"
},
{
"path": "app/models/comment.rb",
"chars": 412,
"preview": "class Comment < ApplicationRecord\n belongs_to :post\n\n validates :name, presence: true\n validates :email, presence: tr"
},
{
"path": "app/models/concerns/.keep",
"chars": 0,
"preview": ""
},
{
"path": "app/models/label.rb",
"chars": 103,
"preview": "class Label < ApplicationRecord\n has_and_belongs_to_many :posts\n validates :name, presence: true\nend\n"
},
{
"path": "app/models/like.rb",
"chars": 54,
"preview": "class Like < ApplicationRecord\n belongs_to :post\nend\n"
},
{
"path": "app/models/photo.rb",
"chars": 75,
"preview": "class Photo < ApplicationRecord\n mount_uploader :image, PhotoUploader\nend\n"
},
{
"path": "app/models/post.rb",
"chars": 1292,
"preview": "require 'markdown'\nclass Post < ActiveRecord::Base\n has_many :comments\n has_and_belongs_to_many :labels\n\n has_many :l"
},
{
"path": "app/uploaders/photo_uploader.rb",
"chars": 440,
"preview": "class PhotoUploader < CarrierWave::Uploader::Base\n include CarrierWave::MiniMagick\n\n storage :file\n\n def store_dir\n "
},
{
"path": "app/views/admin/accounts/edit.html.slim",
"chars": 558,
"preview": "- content_for :title do\n | Account Setting\n\n.card.card-primary\n .card-header.with-border\n h3.card-title\n | Edi"
},
{
"path": "app/views/admin/all_comments/index.html.slim",
"chars": 679,
"preview": ".card\n .card-header\n .float-sm-left\n h3.card-title 评论管理\n .float-sm-right\n .card-body\n table.table.table-"
},
{
"path": "app/views/admin/comments/index.html.slim",
"chars": 1014,
"preview": ".card\n .card-header\n .float-sm-left\n h3.card-title\n ' #{t('admin.comments')}\n | / #{@post.title}\n"
},
{
"path": "app/views/admin/dashboard/index.html.slim",
"chars": 305,
"preview": ".container-fluid\n .row\n .col-lg-6\n .card.card-primary.card-outline\n .card-body\n h5.card-title 博"
},
{
"path": "app/views/admin/labels/_form.html.slim",
"chars": 136,
"preview": ".row\n .col-sm-6\n = simple_form_for([:admin, @label], html: {novalidate: '' }) do |f|\n = f.input :name\n = f"
},
{
"path": "app/views/admin/labels/edit.html.slim",
"chars": 100,
"preview": ".card\n .card-header\n .float-sm-left\n h3.card-title 修改标签\n .card-body\n = render 'form'\n"
},
{
"path": "app/views/admin/labels/index.html.slim",
"chars": 742,
"preview": ".card\n .card-header\n .float-sm-left\n h3.card-title 标签管理\n .float-sm-right\n = link_to '创建标签', new_admin_l"
},
{
"path": "app/views/admin/labels/new.html.slim",
"chars": 98,
"preview": ".card\n .card-header\n .float-sm-left\n h3.card-title 新建标签\n .card-body\n = render 'form'\n"
},
{
"path": "app/views/admin/posts/_form.html.slim",
"chars": 1018,
"preview": ".row data-controller='admin-label'\n .col-sm-8\n = simple_form_for([:admin, @post], html: {novalidate: '' }) do |f|\n "
},
{
"path": "app/views/admin/posts/edit.html.slim",
"chars": 100,
"preview": ".card\n .card-header\n .float-sm-left\n h3.card-title 修改博客\n .card-body\n = render 'form'\n"
},
{
"path": "app/views/admin/posts/index.html.slim",
"chars": 1222,
"preview": ".card\n .card-header\n .float-sm-left\n h3.card-title #{t('admin.posts')}\n .float-sm-right\n = link_to '创建博"
},
{
"path": "app/views/admin/posts/new.html.slim",
"chars": 116,
"preview": ".card\n .card-header\n .float-sm-left\n h3.card-title #{t('admin.new_post')}\n .card-body\n = render 'form'\n"
},
{
"path": "app/views/admin/sessions/new.html.slim",
"chars": 855,
"preview": "- content_for :title, 'Log in'\n\nmain\n section.content-messages\n = render 'shared/admin/flash_messages'\n section.con"
},
{
"path": "app/views/archives/index.html.slim",
"chars": 1018,
"preview": "- content_for(:title) do\n | #{t('title.timeline')}\n.container\n .row.justify-content-center\n .col-sm-12.col-lg-9\n "
},
{
"path": "app/views/blogs/_comment.html.slim",
"chars": 1308,
"preview": "turbo-frame#comment-frame\n .container\n .row.justify-content-center\n .col-12.col-lg-9\n - comment = @comme"
},
{
"path": "app/views/blogs/_post.html.slim",
"chars": 772,
"preview": "= render partial: 'post_head', locals: { post: post }\n\n.content.markdown\n == post.content_html\n\np.ptag.published-at\n |"
},
{
"path": "app/views/blogs/_post_head.html.slim",
"chars": 118,
"preview": "/ require: locals: { post : post }\nh2.blog-title #{post.title}\np.ptag\n span.fa.fa-list\n span #{post.labels_content}\n"
},
{
"path": "app/views/blogs/_qrcode.html.slim",
"chars": 63,
"preview": ".qrcode-image\n #image-tag data-url=str\n p #{t('qrcodetips')}\n"
},
{
"path": "app/views/blogs/edit.html.slim",
"chars": 58,
"preview": "h1 Blogs#edit\np Find me in app/views/blogs/edit.html.slim\n"
},
{
"path": "app/views/blogs/show.html.slim",
"chars": 790,
"preview": "- content_for(:meta) do\n meta name=\"description\" content=@post.meta_content\n meta name=\"keywords\" content=@post.labels"
},
{
"path": "app/views/comments/_comment_content.html.slim",
"chars": 274,
"preview": ".comment-diag\n - comments.each do |comment|\n .comment-wrapper\n p.name\n | #{comment.name}\n | #{\" "
},
{
"path": "app/views/comments/_comment_pre.html.slim",
"chars": 52,
"preview": ".comment-content\n = simple_format(comment.content)\n"
},
{
"path": "app/views/comments/create.html.slim",
"chars": 80,
"preview": "= render partial: 'blogs/comment', locals: { post: @post, comments: @comments }\n"
},
{
"path": "app/views/common/_copyright.en.html.slim",
"chars": 96,
"preview": "p.copyright.published-at.ptag\n | © Creative Commons - ShareAlike - NonCommercial - Attribution\n"
},
{
"path": "app/views/common/_copyright.html.slim",
"chars": 60,
"preview": "p.copyright.published-at.ptag\n | © 自由转载 - 非商用 - 非衍生 - 保持署名\n"
},
{
"path": "app/views/common/_no_blog_here.en.html.slim",
"chars": 152,
"preview": "h2.blog-title #{t('home.no_blog_here')}\np\n | No post here, please visit\n = link_to ' Manage Post ', new_admin_post_pat"
},
{
"path": "app/views/common/_no_blog_here.html.slim",
"chars": 112,
"preview": "h2.blog-title #{t('home.no_blog_here')}\np\n | 这里还没有博客, 请访问\n = link_to '管理页面', new_admin_post_path\n | 来创建第一篇博客\n"
},
{
"path": "app/views/common/_welcome.en.html.slim",
"chars": 329,
"preview": "/* adjust stylesheet: .self-introduce-index */\nh4 WELCOME\np I'm Li Yafei, WinDy is my English name.\n\nh4 ABOUT\nul.aboutme"
},
{
"path": "app/views/common/_welcome.html.slim",
"chars": 267,
"preview": "/* 样式调整请找 stylesheet: .self-introduce-index */\n.box\n h4 欢迎\n p 我是技术达人李亚飞\n.box\n h4 关于我\n ul.aboutme-index\n li\n "
},
{
"path": "app/views/common/_welcome_new_year.html.slim",
"chars": 266,
"preview": "- if (1.days.from_now).strftime('%-m-%-d') =~ /^1-[123]$/\n .new-year.alert.alert-success.alert-dismissible.fade.show ro"
},
{
"path": "app/views/home/_post_head.html.slim",
"chars": 134,
"preview": "/ require: locals: { post : post }\nh2.blog-title #{post.title}\np.ptag\n span\n i.fi-pricetag-multiple\n span #{post.la"
},
{
"path": "app/views/home/about.html.slim",
"chars": 6302,
"preview": "- content_for(:title) do\n | #{t('title.about')}\n- content_for(:main) do\n nav#about-top-bar class=\"navbar navbar-expand"
},
{
"path": "app/views/home/index.html.slim",
"chars": 1026,
"preview": "- if ENV['INTRODUCE'].present?\n - content_for(:meta) do\n meta name=\"description\" content=\"#{ENV['INTRODUCE'].dup.for"
},
{
"path": "app/views/kaminari/_first_page.html.slim",
"chars": 126,
"preview": "li.page-item\n = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-"
},
{
"path": "app/views/kaminari/_gap.html.slim",
"chars": 94,
"preview": "li.page-item.disabled\n = link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link'\n"
},
{
"path": "app/views/kaminari/_last_page.html.slim",
"chars": 124,
"preview": "li.page-item\n = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, remote: remote, class: 'page-li"
},
{
"path": "app/views/kaminari/_next_page.html.slim",
"chars": 137,
"preview": "li.page-item\n = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote, cl"
},
{
"path": "app/views/kaminari/_page.html.slim",
"chars": 226,
"preview": "- if page.current?\n li.page-item.active\n = content_tag :a, page, data: { remote: remote }, rel: page.rel, class: 'pa"
},
{
"path": "app/views/kaminari/_paginator.html.slim",
"chars": 432,
"preview": "= paginator.render do\n nav\n ul.pagination\n == first_page_tag unless current_page.first?\n == prev_page_tag "
},
{
"path": "app/views/kaminari/_prev_page.html.slim",
"chars": 142,
"preview": "li.page-item\n = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remot"
},
{
"path": "app/views/layouts/_footer.html.slim",
"chars": 556,
"preview": ".container\n .row\n .col-sm-12\n .footer\n div\n span.link = ENV['SITE_ADDRESS']\n span.time"
},
{
"path": "app/views/layouts/admin.html.slim",
"chars": 1893,
"preview": "doctype html\nhtml\n head\n meta charset='utf-8'\n meta name=\"viewport\" content=\"width=device-width, initial-scale=1,"
},
{
"path": "app/views/layouts/application.html.slim",
"chars": 2077,
"preview": "doctype html\nhtml\n head\n meta charset='utf-8'\n meta name=\"viewport\" content=\"width=device-width, initial-scale=1,"
},
{
"path": "app/views/layouts/mailer.html.erb",
"chars": 229,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n <style>\n "
},
{
"path": "app/views/layouts/mailer.text.erb",
"chars": 13,
"preview": "<%= yield %>\n"
},
{
"path": "app/views/shared/admin/_flash_messages.html.slim",
"chars": 153,
"preview": "- flash.each do |key, value|\n = content_tag :div, class: flash_class(key) do\n button.close[type=\"button\" data-dismis"
},
{
"path": "app/views/shared/admin/_header.html.slim",
"chars": 792,
"preview": "nav class=\"app-header navbar navbar-expand bg-body\"\n /! Left navbar links\n ul.navbar-nav\n li.nav-item\n a.nav-l"
},
{
"path": "app/views/shared/admin/_sidebar.html.slim",
"chars": 1378,
"preview": "/! Main Sidebar Container\naside.app-sidebar.bg-body-secondary.shadow data-bs-theme=\"dark\"\n .sidebar-brand\n /! Brand "
},
{
"path": "babel.config.js",
"chars": 1668,
"preview": "module.exports = function(api) {\n var validEnv = ['development', 'test', 'production']\n var currentEnv = api.env()\n v"
},
{
"path": "bin/bundle",
"chars": 2945,
"preview": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n#\n# This file was generated by Bundler.\n#\n# The application 'bundle' "
},
{
"path": "bin/dev",
"chars": 259,
"preview": "#!/usr/bin/env sh\n\nif gem list --no-installed --exact --silent foreman; then\n echo \"Installing foreman...\"\n gem instal"
},
{
"path": "bin/rails",
"chars": 141,
"preview": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequ"
},
{
"path": "bin/rake",
"chars": 90,
"preview": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
},
{
"path": "bin/setup",
"chars": 1256,
"preview": "#!/usr/bin/env ruby\nrequire 'fileutils'\n\n# path to your application root.\nAPP_ROOT = File.expand_path('..', __dir__)\n\nde"
},
{
"path": "bin/spring",
"chars": 494,
"preview": "#!/usr/bin/env ruby\nif !defined?(Spring) && [nil, \"development\", \"test\"].include?(ENV[\"RAILS_ENV\"])\n gem \"bundler\"\n re"
},
{
"path": "bin/yarn",
"chars": 533,
"preview": "#!/usr/bin/env ruby\nAPP_ROOT = File.expand_path('..', __dir__)\nDir.chdir(APP_ROOT) do\n yarn = ENV[\"PATH\"].split(File::P"
},
{
"path": "config/application.rb",
"chars": 1497,
"preview": "require_relative \"boot\"\n\nrequire \"rails/all\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited "
},
{
"path": "config/application.yml.example",
"chars": 1112,
"preview": "# This section MUST be configured\nSITE_NAME: \"WinDy's Blog\"\n\n# domain name used by action cable\nDOMAIN: ''\nPROTOCOL: htt"
},
{
"path": "config/backup.rb.example",
"chars": 1210,
"preview": "# encoding: utf-8\n## backup gem example\n## Howto:\n## $ gem install backup\n## $ backup generate:model --trigger wblog --a"
},
{
"path": "config/boot.rb",
"chars": 207,
"preview": "ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)\n\nrequire \"bundler/setup\" # Set up gems listed in the G"
},
{
"path": "config/cable.yml",
"chars": 186,
"preview": "development:\n adapter: async\n\ntest:\n adapter: test\n\nproduction:\n adapter: redis\n url: <%= ENV.fetch(\"REDIS_URL\") { \""
},
{
"path": "config/credentials.yml.enc",
"chars": 464,
"preview": "lNIHR59bC+oahS9eflbwKRqAEUPxasN91LCSNg875kdit4+EUfInV7wQEZ8bsnI+aZxxEF+Rbx9reUR42p5yCSJQCRv8BB6W0bwX65lPypdeivt+puy1SmcL"
},
{
"path": "config/database.yml.example",
"chars": 545,
"preview": "development:\n adapter: postgresql\n host: localhost\n encoding: unicode\n database: wblog_development\n pool: "
},
{
"path": "config/deploy/production.rb",
"chars": 153,
"preview": "set :domain, 'yafeilee.com'\nset :deploy_to, '/data/www/wblog'\nset :repository, 'git@github.com:windy/wblog.git'\nset :br"
},
{
"path": "config/deploy.rb",
"chars": 3388,
"preview": "STDOUT.sync = true\nset :stages, %w(production)\nset :default_stage, 'production'\n\nrequire 'mina/bundler'\nrequire 'mina/ra"
},
{
"path": "config/environment.rb",
"chars": 128,
"preview": "# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.init"
},
{
"path": "config/environments/development.rb",
"chars": 2735,
"preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n # Settings specified here will take pre"
},
{
"path": "config/environments/production.rb",
"chars": 4196,
"preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n # Settings specified here will take pre"
},
{
"path": "config/environments/test.rb",
"chars": 2643,
"preview": "require \"active_support/core_ext/integer/time\"\n\n# The test environment is used exclusively to run your application's\n# t"
},
{
"path": "config/initializers/application_controller_renderer.rb",
"chars": 216,
"preview": "# Be sure to restart your server when you modify this file.\n\n# ActiveSupport::Reloader.to_prepare do\n# ApplicationCont"
},
{
"path": "config/initializers/assets.rb",
"chars": 741,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire"
},
{
"path": "config/initializers/backtrace_silencers.rb",
"chars": 540,
"preview": "# Be sure to restart your server when you modify this file.\n\n# You can add backtrace silencers for libraries that you're"
},
{
"path": "config/initializers/browser_warrior.rb",
"chars": 702,
"preview": "# BrowserWarrior.detect do |browser|\n # next true if Rails.env.test?\n # next true if browser.bot?\n\n # next true unles"
},
{
"path": "config/initializers/content_security_policy.rb",
"chars": 1071,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy.\n# See"
},
{
"path": "config/initializers/cookies_serializer.rb",
"chars": 244,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Specify a serializer for the signed and encrypted cookie "
},
{
"path": "config/initializers/filter_parameter_logging.rb",
"chars": 448,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Configure parameters to be partially matched (e.g. passw "
},
{
"path": "config/initializers/inflections.rb",
"chars": 649,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Infl"
},
{
"path": "config/initializers/kaminari_config.rb",
"chars": 353,
"preview": "# frozen_string_literal: true\n\nKaminari.configure do |config|\n # config.default_per_page = 25\n # config.max_per_page ="
},
{
"path": "config/initializers/mime_types.rb",
"chars": 156,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Add new mime types for use in respond_to blocks:\n# Mime::"
},
{
"path": "config/initializers/permissions_policy.rb",
"chars": 480,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide HTTP permissions policy. For f"
},
{
"path": "config/initializers/sidekiq.rb",
"chars": 201,
"preview": "Sidekiq.configure_server do |config|\n config.redis = { db: ENV['SIDEKIQ_DB'].presence || '1' }\nend\n\nSidekiq.configure_c"
},
{
"path": "config/initializers/simple_form.rb",
"chars": 7050,
"preview": "# frozen_string_literal: true\n#\n# Uncomment this and change the path if necessary to include your own\n# components.\n# Se"
},
{
"path": "config/initializers/simple_form_bootstrap.rb",
"chars": 20178,
"preview": "# frozen_string_literal: true\n\n# Please do not make direct changes to this file!\n# This generator is maintained by the c"
},
{
"path": "config/initializers/wrap_parameters.rb",
"chars": 485,
"preview": "# Be sure to restart your server when you modify this file.\n\n# This file contains settings for ActionController::ParamsW"
},
{
"path": "config/locales/en.yml",
"chars": 2764,
"preview": "en:\n comment: \"Comment\"\n edit: 'Edit'\n destroy: 'Destroy'\n announce_at: \"Published at \"\n qr_code: 'QR Code'\n visit"
},
{
"path": "config/locales/simple_form.en.yml",
"chars": 698,
"preview": "en:\n simple_form:\n \"yes\": 'Yes'\n \"no\": 'No'\n required:\n text: 'required'\n mark: '*'\n # You can "
},
{
"path": "config/locales/simple_form.zh-CN.yml",
"chars": 805,
"preview": "en:\n simple_form:\n \"yes\": '是'\n \"no\": '否'\n required:\n text: '必须的'\n mark: '*'\n # You can uncommen"
},
{
"path": "config/locales/zh-CN.yml",
"chars": 2474,
"preview": "zh-CN:\n comment: \"评论\"\n edit: '编辑'\n destroy: '删除'\n announce_at: \"发表于 \"\n qr_code: '二维码'\n visited_count: \"浏览数 \"\n noc"
},
{
"path": "config/logrotate.conf.example",
"chars": 237,
"preview": "# truncate your rails log every day\n# Usage:\n# `cp logrotate.conf.example /etc/logrotate.d/wblog`\n/data/www/wblog/curren"
},
{
"path": "config/monit.conf.example",
"chars": 2531,
"preview": "# Watch your rails app & sidekiq process and restart it automatically\n# Usage:\n# `sudo apt-get install monit -y`\n# `cp m"
},
{
"path": "config/nginx.conf.example",
"chars": 1025,
"preview": "upstream wblog {\n server unix:///data/www/wblog/shared/tmp/sockets/puma.sock fail_timeout=0;\n}\n\nserver {\n listen 80;\n "
},
{
"path": "config/nginx.ssl.conf.example",
"chars": 1801,
"preview": "upstream wblog {\n server unix:///data/www/wblog/shared/tmp/sockets/puma.sock fail_timeout=0;\n}\n\nserver {\n listen 80;\n "
},
{
"path": "config/puma.rb",
"chars": 492,
"preview": "# require 'puma/daemon'\n\nif ENV['RAILS_ENV'] == 'production'\n app_root = '/data/www/wblog/shared'\n pidfile \"#{app_root"
},
{
"path": "config/routes.rb",
"chars": 1342,
"preview": "require 'sidekiq/web'\nSidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base]\n\nclass AdminConstrai"
},
{
"path": "config/secret.yml",
"chars": 474,
"preview": "development:\n secret_key_base: df19bc95da417d0dc49e659249d64706168116e77fb348533c32d817cb0a36fe5b6e88303ad680448ddbea65"
},
{
"path": "config/sidekiq.yml",
"chars": 89,
"preview": ":concurrency: 5\n:queues:\n - critical\n - default\n - low\nproduction:\n :concurrency: 25\n"
},
{
"path": "config/spring.rb",
"chars": 136,
"preview": "%w(\n .ruby-version\n .rbenv-vars\n tmp/restart.txt\n tmp/caching-dev.txt\n config/application.yml\n).each { |path| Sprin"
},
{
"path": "config/storage.yml",
"chars": 1093,
"preview": "test:\n service: Disk\n root: <%= Rails.root.join(\"tmp/storage\") %>\n\nlocal:\n service: Disk\n root: <%= Rails.root.join("
},
{
"path": "config.ru",
"chars": 160,
"preview": "# This file is used by Rack-based servers to start the application.\n\nrequire_relative \"config/environment\"\n\nrun Rails.ap"
},
{
"path": "db/migrate/20160420082319_create_posts.rb",
"chars": 218,
"preview": "class CreatePosts < ActiveRecord::Migration[6.1]\n def change\n create_table :posts do |t|\n t.string :title\n "
},
{
"path": "db/migrate/20160420082536_create_comments.rb",
"chars": 227,
"preview": "class CreateComments < ActiveRecord::Migration[6.1]\n def change\n create_table :comments do |t|\n t.string :name\n"
},
{
"path": "db/migrate/20160420082629_create_labels.rb",
"chars": 154,
"preview": "class CreateLabels < ActiveRecord::Migration[6.1]\n def change\n create_table :labels do |t|\n t.string :name\n\n "
},
{
"path": "db/migrate/20160420082734_create_likes.rb",
"chars": 156,
"preview": "class CreateLikes < ActiveRecord::Migration[6.1]\n def change\n create_table :likes do |t|\n t.references :post\n\n "
},
{
"path": "db/migrate/20160420082811_create_photos.rb",
"chars": 155,
"preview": "class CreatePhotos < ActiveRecord::Migration[6.1]\n def change\n create_table :photos do |t|\n t.string :image\n\n "
},
{
"path": "db/migrate/20160421035040_create_join_table_post_label.rb",
"chars": 210,
"preview": "class CreateJoinTablePostLabel < ActiveRecord::Migration[6.1]\n def change\n create_join_table :posts, :labels do |t|\n"
},
{
"path": "db/migrate/20210614151036_create_active_storage_tables.active_storage.rb",
"chars": 1362,
"preview": "# This migration comes from active_storage (originally 20170806125915)\nclass CreateActiveStorageTables < ActiveRecord::M"
},
{
"path": "db/migrate/20210614151102_create_administrators.rb",
"chars": 253,
"preview": "class CreateAdministrators < ActiveRecord::Migration[6.1]\n def change\n create_table :administrators do |t|\n t.s"
},
{
"path": "db/migrate/20250120142353_add_service_name_to_active_storage_blobs.active_storage.rb",
"chars": 718,
"preview": "# This migration comes from active_storage (originally 20190112182829)\nclass AddServiceNameToActiveStorageBlobs < Active"
},
{
"path": "db/migrate/20250120142354_create_active_storage_variant_records.active_storage.rb",
"chars": 1103,
"preview": "# This migration comes from active_storage (originally 20191206030411)\nclass CreateActiveStorageVariantRecords < ActiveR"
},
{
"path": "db/migrate/20250120142355_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb",
"chars": 292,
"preview": "# This migration comes from active_storage (originally 20211119233751)\nclass RemoveNotNullOnActiveStorageBlobsChecksum <"
},
{
"path": "db/schema.rb",
"chars": 3878,
"preview": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the mig"
},
{
"path": "db/seeds.rb",
"chars": 1937,
"preview": "# This file should contain all the record creation needed to seed the database with default values.\n# The data can then "
},
{
"path": "doc/.gitkeep",
"chars": 1,
"preview": "\n"
},
{
"path": "lib/assets/.keep",
"chars": 0,
"preview": ""
},
{
"path": "lib/markdown.rb",
"chars": 239,
"preview": "require 'rouge'\nrequire 'rouge/plugins/redcarpet'\n\nclass CodeHTML < Redcarpet::Render::HTML\n include Rouge::Plugins::Re"
},
{
"path": "lib/tasks/.keep",
"chars": 0,
"preview": ""
},
{
"path": "lib/templates/slim/scaffold/_form.html.slim",
"chars": 373,
"preview": "= simple_form_for(@<%= singular_table_name %>) do |f|\n = f.error_notification\n = f.error_notification message: f.objec"
},
{
"path": "log/.keep",
"chars": 0,
"preview": ""
},
{
"path": "package.json",
"chars": 1135,
"preview": "{\n \"name\": \"wblog\",\n \"private\": true,\n \"dependencies\": {\n \"@fortawesome/fontawesome-free\": \"^5.15.4\",\n \"@hotwir"
},
{
"path": "postcss.config.js",
"chars": 224,
"preview": "module.exports = {\n plugins: [\n require('postcss-import'),\n require('postcss-flexbugs-fixes'),\n require('postc"
}
]
// ... and 36 more files (download for full content)
About this extraction
This page contains the full source code of the windy/wblog GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 236 files (266.0 KB), approximately 82.7k tokens, and a symbol index with 152 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.