Full Code of windy/wblog for AI

master de727a0b63fa cached
236 files
266.0 KB
82.7k tokens
152 symbols
1 requests
Download .txt
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
=======
[![Build Status](https://travis-ci.org/windy/wblog.svg?branch=master)](https://travis-ci.org/windy/wblog)
[![Maintainability](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/maintainability)](https://codeclimate.com/github/windy/wblog/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/test_coverage)](https://codeclimate.com/github/windy/wblog/test_coverage)

The missing open source blog system on Ruby on Rails 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:

![screenshot home](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/home.png)

Home Page for mobile:

![screenshot home small](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/home-small.png)

Home Page Hover Status for mobile:

![screenshot home hover](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/home-small-hover.png)

Blog Show Page:

![screenshot post](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/post.png)

Blog Show Page Hover Status:

![screenshot post hover](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/post-hover.png)

Admin Login Page:

![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-login.png)

Admin Dashboard Page:

![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-dashboard.png)

Admin New Blog Page:

![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-post.png)

Admin Blogs Manage Page:

![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-posts.png)


================================================
FILE: README.zh-CN.md
================================================
WBlog
=======
[![Build Status](https://travis-ci.org/windy/wblog.svg?branch=master)](https://travis-ci.org/windy/wblog)
[![Maintainability](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/maintainability)](https://codeclimate.com/github/windy/wblog/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/test_coverage)](https://codeclimate.com/github/windy/wblog/test_coverage)

为移动而生的 Ruby on Rails 开源博客. WBlog 基于 MIT 协议, 自由使用.

~~现已全面支持 Ruby on Rails 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

首页:

![screenshot home](https://github.com/windy/wblog/raw/master/doc/wblog_s/home.png)

小屏首页:

![screenshot home small](https://github.com/windy/wblog/raw/master/doc/wblog_s/home-small.png)

展开的小屏首页:

![screenshot home hover](https://github.com/windy/wblog/raw/master/doc/wblog_s/home-small-hover.png)

博客详情页:

![screenshot post](https://github.com/windy/wblog/raw/master/doc/wblog_s/post.png)

展开的博客详情页:

![screenshot post hover](https://github.com/windy/wblog/raw/master/doc/wblog_s/post-hover.png)

管理员登录页:

![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-login.png)

管理页面板:

![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-dashboard.png)

发布新博客页:

![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-post.png)

博客管理页:

![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-posts.png)


================================================
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)
    "![](#{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, '&lt').replace(/>/g, '&gt').replace(/`/g, '&#96').replace(/"/g, '&quot').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)
              | &nbsp
              = 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'
              | &nbsp
              = link_to t('edit'), edit_admin_post_path(post), class: 'edit-post-link'
              | &nbsp
              = 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 &times;
          - elsif comment.errors.any?
            #alert-container.alert.alert-warning
              span.text #{comment.errors[:content].first}
              button class="close" type='button' data-dismiss='alert'
                span &times;
        = 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' &times;


================================================
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 &copy; 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
Download .txt
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
Download .txt
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[![Build Status](https://travis-ci.org/windy/wblog.svg?branch=master)](https://travis-ci.org/windy/wblog)\n"
  },
  {
    "path": "README.zh-CN.md",
    "chars": 2929,
    "preview": "WBlog\n=======\n[![Build Status](https://travis-ci.org/windy/wblog.svg?branch=master)](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.

Copied to clipboard!