[
  {
    "path": ".1024",
    "content": "# .1024 Configuration file for project run commands, compilation and debug settings (optional);\n# Any changes made will be auto-saved and take effect immediately.\n# For more information, please refer to the documentation: https://docs.clacky.ai/clacky-workspace/configure\n\n# Command to run when \"Run\" button clicked\nrun_commands: ['bin/dev']\n# Command to install or update dependencies, will execute each time a new thread created to ensure dependencies up-to-date\ndependency_command: gem install bundler && bundle install && yarn install && rails db:migrate"
  },
  {
    "path": ".ackrc",
    "content": "--ignore-file=ext:svg\n--ignore-dir=public\n--ignore-dir=tmp\n--ignore-dir=node_modules\n"
  },
  {
    "path": ".browserslistrc",
    "content": "defaults\n"
  },
  {
    "path": ".gitattributes",
    "content": "# See https://git-scm.com/docs/gitattributes for more about git attribute files.\n\n# Mark the database schema as having been generated.\ndb/schema.rb linguist-generated\n\n# Mark the yarn lockfile as having been generated.\nyarn.lock linguist-generated\n\n# Mark any vendored files as having been vendored.\nvendor/* linguist-vendored\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files for more about ignoring files.\n#\n# If you find yourself ignoring temporary files generated by your text editor\n# or operating system, you probably want to add a global ignore instead:\n#   git config --global core.excludesfile '~/.gitignore_global'\n\n# Ignore bundler config.\n/.bundle\n\n# Ignore the default SQLite database.\n/db/*.sqlite3\n/db/*.sqlite3-journal\n\n# Ignore all logfiles and tempfiles.\n/log/*\n/tmp/*\n!/log/.keep\n!/tmp/.keep\n\n# Ignore uploaded files in development.\n/storage/*\n!/storage/.keep\n\n/public/assets\n\n# Ignore master key for decrypting credentials and more.\n/config/master.key\n\n/public/packs\n/public/packs-test\n/node_modules\n/yarn-error.log\nyarn-debug.log*\n.yarn-integrity\n\n*.swp\n\n/public/uploads/*\n/public/assets/*\n/tags\n\n/config/application.yml\n/config/mongoid.yml\n/config/database.yml\n/config/master.key\n**.orig\n*.old\n*.bak\n*~\n.env\n.byebug_history\n.DS_Store\n\n/app/assets/builds/*\n!/app/assets/builds/.keep\n\n# Clacky-specific files\n.1024*\n!.1024\n.breakpoints"
  },
  {
    "path": ".rspec",
    "content": "--color\n--require spec_helper\n--require rails_helper\n"
  },
  {
    "path": ".ruby-version",
    "content": "3.1.2\n"
  },
  {
    "path": ".travis.yml",
    "content": "# http://about.travis-ci.org/docs/user/build-configuration/\nenv:\n  global:\n    - CC_TEST_REPORTER_ID=787a2f89b15c637323c7340d65ec17e898ac44480706b4b4122ea040c2a88f1d\nlanguage: ruby\n\nbefore_install:\n  - \"cat /etc/timezone\"\n  - \"grep -i processor /proc/cpuinfo | wc -l\"\n  - \"echo 'gem: --no-ri --no-rdoc' > ~/.gemrc\"\n  - \"gem install bundler\"\n  - \"bundle -v\"\n\nbefore_script:\n  - \"curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter\"\n  - \"chmod +x ./cc-test-reporter\"\n  - \"./cc-test-reporter before-build\"\n  - \"cp -f config/database.yml.example config/database.yml\"\n  - \"cp -f config/application.yml.example config/application.yml\"\n  - \"bundle exec rake db:drop db:create db:schema:load --trace 2>&1\"\n\nscript: bundle exec rspec\n\nafter_script:\n  - \"./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT\"\n\nservices:\n  - postgresql\n\nrvm:\n  - 2.5.3\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\ngit_source(:github) { |repo| \"https://github.com/#{repo}.git\" }\n\nruby '3.1.2'\ngem 'rails', '~> 7.1.4.2'\ngem 'puma', '~> 4.x'\n# gem 'puma-daemon', require: false\ngem 'turbo-rails'\ngem 'jbuilder', '~> 2.7'\ngem 'bootsnap', '>= 1.4.4', require: false\ngem 'stimulus-rails'\n\ngem 'cssbundling-rails'\ngem 'jsbundling-rails'\ngem 'propshaft', '~> 1.1.0'\n\ngem 'rexml', '~> 3.2', '>= 3.2.4'\n\ngroup :development, :test do\n  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]\nend\n\ngroup :development do\n  gem 'web-console', '>= 4.1.0'\n  gem 'rack-mini-profiler', '~> 2.0'\n  gem 'listen', '~> 3.9'\n  gem 'spring'\nend\n\ngroup :test do\n  gem 'capybara', '>= 3.26'\n  gem 'selenium-webdriver'\n  gem 'webdrivers'\nend\ngem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]\ngem 'pg', '>= 1.1'\ngem 'concurrent-ruby', '1.3.4'\n\ngem 'carrierwave'\ngem 'redcarpet'\ngem 'rouge'\ngem 'mini_magick'\ngem 'html_truncator'\ngem 'nokogiri'\n\ngem 'figaro'\ngem 'simple_form', '~> 5.0'\ngem 'slim-rails'\ngem 'high_voltage', '~> 3.1'\n# gem 'browser_warrior', '>= 0.11.0'\ngem 'sidekiq', '~> 5'\ngem 'bcrypt'\ngem 'kaminari', github: 'kaminari/kaminari'\ngem 'rails-i18n', '~> 7.0.10'\ngem 'mina', '~> 1.2.2', require: false\ngem 'mina-ng-puma', '>= 1.4.0', require: false\ngem 'mina-multistage', require: false\ngem 'mina-sidekiq', require: false\ngem 'mina-logs', require: false\n\ngroup :development do\n  gem 'rails_apps_testing'\n  gem 'faker'\nend\n\ngroup :development, :test do\n  gem 'rspec-rails'\n  gem 'factory_bot_rails'\nend\n\ngroup :test do\n  gem 'database_cleaner'\n  gem 'launchy'\nend\n"
  },
  {
    "path": "Procfile.dev",
    "content": "web: env RUBY_DEBUG_OPEN=true bin/rails server\ncss: yarn build:css --watch --poll\njs: yarn build --watch\n"
  },
  {
    "path": "README.md",
    "content": "WBlog\n=======\n[![Build Status](https://travis-ci.org/windy/wblog.svg?branch=master)](https://travis-ci.org/windy/wblog)\n[![Maintainability](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/maintainability)](https://codeclimate.com/github/windy/wblog/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/test_coverage)](https://codeclimate.com/github/windy/wblog/test_coverage)\n\nThe missing open source blog system on Ruby on Rails 7.x.\n\nWBlog is open source blog which built for mobile first, it's licenced on MIT, use it for free!\n\n~~New: WBlog is using Ruby on Rails 6.1 now.~~\n\nNew: WBlog has updated from webpacker to jsbundling & cssbundling with esbuild and sass.\n\nNew: WBlog is using Ruby on Rails 7.1 now.\n\n[中文说明文档](/README.zh-CN.md)\n\nCharacteristic:\n\n* Modern clean reading feelings\n* Markdown support, give nice formatted articles\n* Mobile first, responsive page for iPhone, iPad, iMac.\n* Independent comment system, subscribe system, picture manage system\n\nA real example comes from my own blog( Chinese ): <https://yafeilee.com>\n\nSome [screenshots](#screenshots)\n\n### System dependencies\n\n* Ruby ( >= 3.1.2 )\n* Postgresql ( >= 9.x )\n* Nginx ( >= 1.4 )\n* node ( >= 1.18 )\n\n### Features\n\n* Responsive, iPhone, iPad, Notebook, PC, all are supported\n* QR Code, Like button make your article easily sharing with your friends\n* Inpendent comment system, subscribe system, that all belong to you\n* Markdown supported, code highlight, especially for programmer, like you\n* Personalize it, commercialize it, it all depends on you\n\n### Goal\n\nMake it to the best Ruby on Rails Blog system in the world.\n\n### Running in development mode\n\nWBlog MUST run in Linux or OSX. I assume you are using OS X 10.\n\nYou can run it like a Ruby on Rails project as usual:\n\n0. Check dependencies\n\n  ```shell\n  ruby -v\n  # 3.1.2\n  postgres  --version\n  # 9.x.x\n  npm -v\n  # 1.18.x\n  ```\n\n1. Clone it\n\n  `git clone git@github.com:windy/wblog.git`\n\n  `cd wblog`\n\n2. Install dependencies & configure\n\n  ```shell\n  # install rails dependencies\n  gem install bundler\n  bundle install\n  # install node dependencies\n  npm install yarn -g\n  yarn install\n  # copy and update project config file\n  cp config/application.yml.example config/application.yml\n  cp config/database.yml.example config/database.yml\n  ```\n\n  Update `application.yml` & `database.yml` 's content as you need, then run setup:\n\n  ```shell\n  bin/setup\n  ```\n\n3. Start it\n\n  one command:\n\n  ```shell\n  bin/dev\n  ```\n\n  It's all.\n\n  or using multi terminal:\n\n  ```shell\n  # rails\n  bin/rails s\n  ```\n\n  ```shell\n  # js compile\n  bin/yarn build --watch\n  ```\n\n  ```shell\n  # css compile\n  bin/yarn build:css --watch\n  ```\n\n  Open browser with `http://localhost:3000`\n\n  If there is any error found, please check your database's user and password( default is admin/admin )\n\n4. Post the first blog\n\n  visit: http://localhost:3000/admin, input your username and password configurated in `db/seeds.rb`.\n  then, post a new article.\n\nOK, That's all.\n\n### Deployment\n\nWBlog uses `mina` as automation deployment tool, uses `puma` as the Rack container.\n\nWBlog recommends `nginx` as reverse proxy server.\n\nIt will be very fast.\n\nRuby on Rails project deployment is another topic, I would NOT talk it here.\n\nYou can read WBlog wiki for more information: [WBlog 的发布流程(Chinese only now)](https://github.com/windy/wblog/wiki)\n\n### Stack\n\n* Ruby on Rails 7.1\n* Ruby 3.1.2\n* Turbo\n* Bootstrap 4\n* mina\n* slim\n* puma\n* Postgresql\n\n## Related open source blog systems\n\n* writings.io( Ruby on Rails 4.0.2 ): a multi users blog system <https://github.com/chloerei/writings>\n* jekyll( Ruby Gem, Markdown): Static blog system <http://jekyllrb.com/>\n* octopress( Github Pages ): <http://octopress.org/>\n* middleman( Ruby Gem ): Another static blog system <https://github.com/middleman/middleman>\n* robbin_site( Padrino ): <https://github.com/robbin/robbin_site>\n\n## License\n\nMIT.\n\n### Screenshots\n\nHome Page:\n\n![screenshot home](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/home.png)\n\nHome Page for mobile:\n\n![screenshot home small](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/home-small.png)\n\nHome Page Hover Status for mobile:\n\n![screenshot home hover](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/home-small-hover.png)\n\nBlog Show Page:\n\n![screenshot post](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/post.png)\n\nBlog Show Page Hover Status:\n\n![screenshot post hover](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/post-hover.png)\n\nAdmin Login Page:\n\n![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-login.png)\n\nAdmin Dashboard Page:\n\n![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-dashboard.png)\n\nAdmin New Blog Page:\n\n![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-post.png)\n\nAdmin Blogs Manage Page:\n\n![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s_en/admin-posts.png)\n"
  },
  {
    "path": "README.zh-CN.md",
    "content": "WBlog\n=======\n[![Build Status](https://travis-ci.org/windy/wblog.svg?branch=master)](https://travis-ci.org/windy/wblog)\n[![Maintainability](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/maintainability)](https://codeclimate.com/github/windy/wblog/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/545d8372a9dda70b77fe/test_coverage)](https://codeclimate.com/github/windy/wblog/test_coverage)\n\n为移动而生的 Ruby on Rails 开源博客. WBlog 基于 MIT 协议, 自由使用.\n\n~~现已全面支持 Ruby on Rails 6.1 版本!!!~~\n\nNew: 现已经从 webpacker 升级至 jsbundling & cssbundling.\n\nNew: 现已全面升级到 Ruby on Rails 7.1 版本！！！\n\n* 用户极为友好的阅读体验\n* 自带干净的评论系统\n* 简洁而不简单的发布博客流程\n\n访问我的博客以体验: <https://yafeilee.com>\n\n截图如下: <#screenshots>\n\n### WBlog 的设计目标\n\n* 优先以手机用户体验为主\n* 独立干净的评论系统\n* 良好的博客语法高亮支持\n* 可邮件订阅\n* Markdown 支持\n* 尽可能独立\n\n### 特色\n\n* 优先支持移动端访问\n* 响应式设计, 支持所有屏幕终端, 并且支持微信扫码继续阅读和分享\n* 自带评论系统, 干净而方便\n* Markdown 支持, 博客语法高亮, 方便技术性博客\n* 开源可商用, 定制能力强\n\n### 目标\n\n`Ruby on Rails` 下最好用的独立博客建站系统\n\n### 开发环境\n\nWBlog 是一个标准的 Ruby on Rails 应用. 开发环境依赖于:\n\n* Ruby ( = 3.1.2 )\n* Postgresql ( >= 9.x )\n* node ( >= 18 )\n\n配置 WBlog:\n\n  ```shell\n  # rails 依赖\n  gem install bundler\n  bundle install\n  # node 依赖\n  npm install yarn -g\n  yarn install\n  # 配置更新\n  cp config/application.yml.example config/application.yml\n  cp config/database.yml.example config/database.yml\n  ```\n\n  更新对应配置: application.yml & database.yml.\n\n就这样, 可以尝试启动了:\n\n  ```shell\n  bin/dev\n  ```\n\n登录 http://localhsot:3000/admin 来发布第一篇博客.\n\n### 发布应用\n\nWBlog 采用了 `mina` 作为自动化发布工具, 使用 `nginx`, `puma` 为相关容器.\n\n对应的发布流程在: [WBlog 的发布流程](https://github.com/windy/wblog/wiki)\n\n### 技术栈\n\n* Ruby on Rails 7.1\n* Ruby 3.1.2\n* Bootstrap 4\n* mina\n* slim\n* Postgresql\n\n\n## Ruby 相关开源博客推荐\n\n* writings.io( Ruby on Rails 4.0.2 ): <https://github.com/chloerei/writings>\n* jekyll( Ruby Gem, Markdown, Static ): <http://jekyllrb.com/>\n* octopress( Github Pages ): <http://octopress.org/>\n* middleman( Ruby Gem, Static ): <https://github.com/middleman/middleman>\n* robbin_site( Padrino ): <https://github.com/robbin/robbin_site>\n\n### Screenshots\n\n首页:\n\n![screenshot home](https://github.com/windy/wblog/raw/master/doc/wblog_s/home.png)\n\n小屏首页:\n\n![screenshot home small](https://github.com/windy/wblog/raw/master/doc/wblog_s/home-small.png)\n\n展开的小屏首页:\n\n![screenshot home hover](https://github.com/windy/wblog/raw/master/doc/wblog_s/home-small-hover.png)\n\n博客详情页:\n\n![screenshot post](https://github.com/windy/wblog/raw/master/doc/wblog_s/post.png)\n\n展开的博客详情页:\n\n![screenshot post hover](https://github.com/windy/wblog/raw/master/doc/wblog_s/post-hover.png)\n\n管理员登录页:\n\n![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-login.png)\n\n管理页面板:\n\n![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-dashboard.png)\n\n发布新博客页:\n\n![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-post.png)\n\n博客管理页:\n\n![screenshot admin](https://github.com/windy/wblog/raw/master/doc/wblog_s/admin-posts.png)\n"
  },
  {
    "path": "Rakefile",
    "content": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire_relative \"config/application\"\n\nRails.application.load_tasks\n"
  },
  {
    "path": "app/assets/builds/.keep",
    "content": ""
  },
  {
    "path": "app/assets/stylesheets/about.scss",
    "content": ".home-about-page {\n  .fixed {\n    position: fixed;\n    top: 0;\n    width: 100%;\n    z-index: 99;\n    left: 0;\n  }\n\n  .responsive-button {\n    padding: 0.7rem 1rem;\n  }\n\n  a {\n    word-wrap: break-word;\n    color: #2199e8;\n    &:hover {\n      text-decoration: none;\n      opacity: 0.8;\n    }\n  }\n  //@media only screen and (min-width: 40.063em) {\n  @media screen and (max-width: 39.9375em) {\n    .top-bar-wrapper {\n      .top-bar, .top-bar-left, .top-bar-right {\n        width: auto !important;\n      }\n    }\n  }\n  .top-bar-wrapper {\n    background: 0 0;\n    transition: background .5s ease-in-out,padding .5s ease-in-out;\n    &.active {\n      background: #000;\n      border-bottom: 1px solid #666;\n\n      .top-bar {\n        margin: 0.5rem 0;\n      }\n    }\n\n    .top-bar {\n      background: 0 0;\n      margin: 1.5rem 0;\n      .name {\n        font-size: 1.325rem;\n      }\n    }\n\n    .top-bar ul{\n      & {\n        background: 0 0;\n      }\n      li, li a {\n        background: 0 0;\n        font-size: 1rem;\n        color: #fefefe;\n      }\n\n      li a:hover {\n        background: #666;\n      }\n\n      li a.active {\n        background: #4C4C4C;\n      }\n    }\n  }\n  //}\n\n  p {\n    font-size: 1.3rem;\n    line-height: 1.8;\n    font-weight: 300;\n  }\n\n  .intro {\n    background-color: #7A7A7A;\n    background: url('intro-bg.jpg') no-repeat bottom center scroll;\n    background-size: cover;\n    height: 100vh;\n    width: 100%;\n    display: table;\n\n    .intro-heading {\n      display: table-cell;\n      vertical-align: middle;\n      text-align: center;\n\n      .heading, .sub-heading {\n        color: #eee;\n      }\n\n      .version {\n        color: #3FBFFF;\n        &:hover {\n          color: #BFEAFF;\n        }\n      }\n\n      .sub-heading {\n        margin-top: 2rem;\n        p {\n          line-height: 2;\n        }\n      }\n\n      .circle {\n        color: #eee;\n        width: 4rem;\n        height: 4rem;\n        font-size: 3rem;\n        border: 2px solid #eee;\n        display: inline-block;\n        border-radius: 50%;\n        margin-top: 2rem;\n        line-height: 1.2;\n        &:hover {\n          opacity: 0.7;\n        }\n\n        .fa {\n          font-size: 80%;\n        }\n      }\n    }\n  }\n\n  #about {\n    .wrapper {\n      .time {\n        color: #aaa;\n        margin-bottom: 0.5rem;\n        margin-top: 1.5rem;\n      }\n      p {\n        font-size: 1rem;\n        line-height: 2;\n      }\n\n      ul > li {\n        margin: 0.5rem 0;\n      }\n    }\n  }\n\n  #about, #skill {\n    background-color: #000;\n    color: #eee;\n    padding-top: 10rem;\n    padding-bottom: 10rem;\n    h1, h2 {\n      color: #eee;\n      text-align: center;\n      margin-bottom: 2rem;\n    }\n  }\n\n  #skill {\n    background: url('mp.jpg') no-repeat bottom center scroll;\n    background-color: #BABABA;\n    background-size: cover;\n    .skills {\n      color: #eeeeee;\n      background-color: rgba(63, 63, 63, 0.6);\n      padding: 2rem 2rem;\n      font-size: 1.125rem;\n      li {\n        margin: 1rem 0;\n      }\n    }\n  }\n\n  #work {\n    background: url('download-bg.jpg') no-repeat bottom center scroll;\n    background-size: cover;\n    padding-top: 10rem;\n    padding-bottom: 10rem;\n    h1, h2 {\n      color: #eee;\n      text-align: center;\n      margin-bottom: 2rem;\n    }\n\n    p {\n      color: #eee;\n      text-align: center;\n    }\n\n    .works {\n      color: #eee;\n      background-color: rgba(63, 63, 63, 0.6);\n      list-style: none;\n      padding: 2rem 2rem;\n      margin-left: 0;\n      >li {\n        margin: 1rem 0;\n      }\n\n      .project-description {\n        margin-bottom: 2rem;\n        font-size: 90%;\n        li {\n          margin: 0.5rem 0;\n          color: #aaa;\n          &:hover {\n            color: #ccc;\n          }\n        }\n      }\n\n      .name {\n        margin-right: 0.5rem;\n        font-size: 1.125rem;\n        font-weight: 600;\n        &:after {\n          margin-left: 0.5rem;\n          font-weight: 400;\n          content: '--';\n        }\n      }\n\n      .brief {\n        margin-right: 1rem;\n        color: #dfdfdf;\n      }\n\n      .link {\n        margin-right: 1rem;\n        color: #dfdfdf;\n      }\n\n      .time {\n        color: #aaa;\n        font-size: 95%;\n      }\n    }\n  }\n\n  #contact {\n    background: #000;\n    background-size: cover;\n    padding-top: 10rem;\n    padding-bottom: 2rem;\n    .modified-at {\n      padding-top: 8rem;\n      color: #aaa;\n      font-size: 1rem;\n    }\n    h1, h2 {\n      color: #eee;\n      text-align: center;\n      margin-bottom: 2rem;\n    }\n\n    p {\n      color: #eee;\n      text-align: center;\n    }\n\n    .mail_to {\n      margin-bottom: 2rem;\n      margin-top: 3rem;\n    }\n\n    .contact-ul {\n      text-align: center;\n      li {\n        display: inline-block;\n      }\n      li a {\n        display: inline-block;\n        padding: 0.5rem 2rem;\n        border: 1px solid #219AB3;\n        border-radius: 0.5rem;\n        color: #219AB3;\n        margin: 0.5rem 1rem;\n        transition: all .3s ease-in-out;\n\n        &:hover {\n          outline: 0;\n          color: #000;\n          background-color: #219ab3;\n        }\n\n        i {\n          margin-right: 0.5rem;\n          font-size: 1.1rem;\n        }\n\n        i.douban {\n          font-style: normal;\n          font-size: 95%;\n          vertical-align: baseline;\n        }\n      }\n    }\n  }\n\n  .footer {\n    border: none;\n    padding: 1.5rem;\n    background-color: #353535;\n    text-align: center;\n    color: #eee;\n    font-size: 1.125rem;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/aboutme_welcome.scss",
    "content": ".self-introduce {\n  @media screen and (min-width: 64em) {\n    margin-top: 1.875rem;\n  }\n\n  .box {\n    margin-bottom: 2rem;\n  }\n\n  h1, h2, h3, h4, h5, h6 {\n    font-weight: 400;\n    margin-bottom: 1rem;\n  }\n\n  p, .aboutme-index {\n    color: #5D5D5D;\n  }\n}\n\n.aboutme-index {\n  list-style-type: none;\n  margin-left: 0;\n  padding-left: 0;\n  li {\n    margin-left: 0;\n    padding-bottom: 0.5rem;\n    span {\n      &:first-child{\n        margin-right: 0.5rem;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/admin/posts.scss",
    "content": ".admin-posts-edit-page, .admin-posts-index-page {\n  td {\n    i.fa {\n      margin-right: 0.2rem;\n    }\n    span {\n      margin-right: 0.5rem;\n    }\n  }\n\n  #content-input {\n    min-height: 30rem;\n  }\n\n  #preview {\n    padding: 0.5rem;\n    border: 1px solid #eee;\n    border-top: none;\n    margin-bottom: 0.5rem;\n    min-height: 5rem;\n    border-radius: 0.5rem;\n  }\n\n  #upload_photo {\n    float: right;\n    margin-top: -2rem;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/admin.scss",
    "content": "@use 'fontawsome_custom';\n@use 'bootstrap_custom';\n@use 'libs/markdown';\n@use 'admin-lte/dist/css/adminlte';\n@use 'admin/posts';\n\n@use 'select2/dist/css/select2.css';\n// @use '@ttskch/select2-bootstrap4-theme/dist/select2-bootstrap4.css';\n\n.turbo-progress-bar {\n  height: 4px;\n  background-color: red;\n}\n\n.main-sidebar {\n  li.nav-item {\n    width: 100%;\n    a {\n      text-overflow: ellipsis;\n    }\n  }\n}\n\n.login-box {\n  margin: 7% auto;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/application.scss",
    "content": "@use 'bootstrap_custom';\n@use 'fontawsome_custom';\n@use 'libs/markdown';\n\n@use 'aboutme_welcome';\n@use 'archives';\n@use 'blogs';\n@use 'head';\n@use 'qrcodes';\n@use 'comments';\n@use 'highlight';\n@use 'footer';\n@use 'like_and_weixin';\n@use 'about';\n@use 'new_year';\n\n.turbo-progress-bar {\n  height: 2px;\n  background-color: red;\n}\n\nimg {\n  display: block;\n}\n"
  },
  {
    "path": "app/assets/stylesheets/archives.scss",
    "content": ".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  }\n\n  span {\n    margin-right: 1rem;\n  }\n\n  .search-result-wrapper p {\n    font-size: 0.785rem;\n    color: #999;\n  }\n\n  .blog-title {\n    color: #111111;\n    &:hover {\n      color: red;\n      text-decoration: none;\n    }\n    border: none;\n    display: inline-block;\n    margin-bottom: 0.3rem;\n\n    em {\n      color: red;\n      font-style: normal;\n    }\n  }\n\n  li {\n    list-style: none;\n    color: #666666;\n    border-bottom: 1px dashed #CCCCCC;\n    margin-bottom: 1rem;\n  }\n\n  .tags-field {\n    font-size: 90%;\n  }\n\n  .load-more {\n    width: 100%;\n    text-align: center;\n    button {\n      background-color: transparent;\n      color: #333333;\n      border: 1px solid #DDDDDD;\n      &:focus {\n        outline-style: none;\n      }\n    }\n  }\n\n  .no-more-field p {\n    text-align: center;\n    color: #444444;\n    border-bottom: 1px solid #DDDDDD;\n    padding-bottom: 0.5rem;\n    padding-top: 1rem;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/blogs.scss",
    "content": ".home-index-page, .blogs-show-page {\n  .blog-title {\n    margin-top: 1rem;\n    line-height: 1.5;\n  }\n\n  .ptag {\n    margin-bottom: 0.5rem;\n    color: #888;\n    font-size: 95%;\n    span {\n      margin-left: 0.5rem;\n      &:first-child {\n        margin-left: 0;\n      }\n    }\n    .has-tip {\n      border: none;\n    }\n  }\n\n  .content {\n    padding-top: 1rem;\n  }\n\n  .read-more {\n    margin-top: 1rem;\n    display: inline-block;\n    padding: 1rem;\n    border: 1px solid #ccc;\n    color: #333;\n\n    &:hover {\n      color: #111;\n      border-color: #999;\n      text-decoration: none;\n    }\n  }\n\n  .published-at {\n    margin-top: 1rem;\n    color: #999;\n\n    @media screen and (min-width: 40.063em) {\n      text-align: right;\n    }\n  }\n\n  .blog-over {\n    margin-bottom: 1rem;\n    margin-top: 2rem;\n    border-bottom: 1px solid #DCDCDC;\n  }\n\n  .recent-title {\n    margin-top: 3rem;\n    font-weight: 400;\n  }\n\n  .recent-content {\n    padding-left: 1.2rem;\n\n    @media screen and (min-width: 64em) {\n      padding-bottom: 3rem;\n    }\n    li {\n      list-style: disc;\n    }\n    li a {\n      margin-left: -0.215rem;\n    }\n  }\n\n  #qrcode-home {\n    padding: 1rem 2rem 1rem 0;\n  }\n\n  .qrcode {\n    display: inline-block;\n    float: right;\n    margin-top: -4.215rem;\n    i {\n      margin-right: 0.5rem;\n    }\n  }\n\n  .qrcode-wrapper {\n    float: right;\n    margin-top: -2rem;\n    text-align: right;\n    table {\n      float: right;\n    }\n    p {\n      clear: both;\n    }\n  }\n  img {\n    width: 100%;\n  }\n\n  .social-share {\n    display: none;\n  }\n\n  .wechat_qrcode {\n    width: 200px;\n    margin-bottom: 20px;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/bootstrap_custom.scss",
    "content": "@use \"sass:color\";\n\n$link-color:                              #2199e8 !default;\n$link-decoration:                         none !default;\n$link-hover-color:                        color.adjust($link-color, $lightness: -15%) !default;\n$link-hover-decoration:                   none !default;\n\n@forward 'bootstrap/scss/bootstrap';\n"
  },
  {
    "path": "app/assets/stylesheets/browserslist",
    "content": "last 2 versions\nie >= 9\nAndroid >= 2.3\nios >= 7\n"
  },
  {
    "path": "app/assets/stylesheets/comments.scss",
    "content": "#alert-container {\n  border-radius: unset;\n}\n\n.comment-field {\n  background-color: #333333;\n  padding-top: 3rem;\n  padding-bottom: 1rem;\n\n  textarea {\n    min-height: 7rem;\n  }\n\n  input[type='text'], textarea {\n    border: 1px solid #333333;\n    background-color: #F7F7F7;\n    box-shadow: none;\n    width: 100%;\n    padding: 0.5rem;\n    color: #333;\n    margin-bottom: 1rem;\n\n    &:focus {\n      box-shadow: none;\n      background-color: white;\n    }\n  }\n\n  .has-tip {\n    color: #008cba;\n  }\n\n  .next {\n    float: right;\n    i {\n      margin-left: 0.275rem;\n    }\n  }\n\n  .prev i {\n    margin-right: 0.275rem;\n    margin-top: 0.5rem;\n  }\n}\n\n.comment-diag {\n  color: #EBEBEB;\n  border-top: 1px solid #5E5E5E;\n  padding-top: 1rem;\n\n  .created-at {\n    color: #b3b3b3;\n  }\n  .comment-content {\n    padding-bottom: 1rem;\n    padding-left: 0.275rem;\n    border-bottom: 1px dashed #8a8a8a;\n    margin-bottom: 0;\n\n    p {\n      margin-bottom: 0.325rem;\n    }\n  }\n  .name {\n    padding-top: 1rem;\n    padding-left: 0.275rem;\n  }\n}\n\n.comment-submit[disabled] {\n  opacity: 0.4;\n  cursor: not-allowed;\n}\n\n.comment-submit {\n  background-color: #008cba;\n  opacity: 0.8;\n  padding: 0.8rem 1.5rem;\n  margin-bottom: 1rem;\n  border: none;\n  color: white;\n  &:hover {\n    opacity: 1;\n  }\n}\n\n\n.comment-success {\n  color: #66FAB5;\n}\n\n.comment-fail {\n  color: #FF7A7A;\n}\n\n.comment-wrapper {\n\n  &:hover {\n    background-color: #444444;\n  }\n\n  .name {\n    color: #DDDDDD;\n  }\n\n  .comment-content {\n    word-wrap: break-word;\n    color: #DDDDDD;\n    word-break: break-all;\n  }\n\n}\n"
  },
  {
    "path": "app/assets/stylesheets/fontawsome_custom.scss",
    "content": "$fa-font-path: '@fortawesome/fontawesome-free/webfonts';\n@import '@fortawesome/fontawesome-free/scss/fontawesome';\n@import '@fortawesome/fontawesome-free/scss/regular';\n@import '@fortawesome/fontawesome-free/scss/solid';\n@import '@fortawesome/fontawesome-free/scss/brands';\n"
  },
  {
    "path": "app/assets/stylesheets/footer.scss",
    "content": ".footer {\n  border-top: 1px solid #dddddd;\n  padding: 1rem 0 2rem;\n  text-align: right;\n  color: #666;\n  .link {\n    margin-right: 1rem;\n  }\n\n  .license {\n    margin-top: 1rem;\n    span {\n      margin-left: 0.5rem;\n    }\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/head.scss",
    "content": ".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-link {\n    padding-right: 0.75rem !important;\n    padding-left: 0.75rem !important;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/highlight.scss",
    "content": ".highlight {\n  .hll { background-color: #49483e }\n  .c { color: #75715e } /* Comment */\n  .err { color: #960050; background-color: #1e0010 } /* Error */\n  .k { color: #66d9ef } /* Keyword */\n  .l { color: #ae81ff } /* Literal */\n  .n { color: #f8f8f2 } /* Name */\n  .o { color: #f92672 } /* Operator */\n  .p { color: #f8f8f2 } /* Punctuation */\n  .cm { color: #75715e } /* Comment.Multiline */\n  .cp { color: #75715e } /* Comment.Preproc */\n  .c1 { color: #75715e } /* Comment.Single */\n  .cs { color: #75715e } /* Comment.Special */\n  .ge { font-style: italic } /* Generic.Emph */\n  .gs { font-weight: bold } /* Generic.Strong */\n  .kc { color: #66d9ef } /* Keyword.Constant */\n  .kd { color: #66d9ef } /* Keyword.Declaration */\n  .kn { color: #f92672 } /* Keyword.Namespace */\n  .kp { color: #66d9ef } /* Keyword.Pseudo */\n  .kr { color: #66d9ef } /* Keyword.Reserved */\n  .kt { color: #66d9ef } /* Keyword.Type */\n  .ld { color: #e6db74 } /* Literal.Date */\n  .m { color: #ae81ff } /* Literal.Number */\n  .s { color: #e6db74 } /* Literal.String */\n  .na { color: #a6e22e } /* Name.Attribute */\n  .nb { color: #f8f8f2 } /* Name.Builtin */\n  .nc { color: #a6e22e } /* Name.Class */\n  .no { color: #66d9ef } /* Name.Constant */\n  .nd { color: #a6e22e } /* Name.Decorator */\n  .ni { color: #f8f8f2 } /* Name.Entity */\n  .ne { color: #a6e22e } /* Name.Exception */\n  .nf { color: #a6e22e } /* Name.Function */\n  .nl { color: #f8f8f2 } /* Name.Label */\n  .nn { color: #f8f8f2 } /* Name.Namespace */\n  .nx { color: #a6e22e } /* Name.Other */\n  .py { color: #f8f8f2 } /* Name.Property */\n  .nt { color: #f92672 } /* Name.Tag */\n  .nv { color: #f8f8f2 } /* Name.Variable */\n  .ow { color: #f92672 } /* Operator.Word */\n  .w { color: #f8f8f2 } /* Text.Whitespace */\n  .mf { color: #ae81ff } /* Literal.Number.Float */\n  .mh { color: #ae81ff } /* Literal.Number.Hex */\n  .mi { color: #ae81ff } /* Literal.Number.Integer */\n  .mo { color: #ae81ff } /* Literal.Number.Oct */\n  .sb { color: #e6db74 } /* Literal.String.Backtick */\n  .sc { color: #e6db74 } /* Literal.String.Char */\n  .sd { color: #e6db74 } /* Literal.String.Doc */\n  .s2 { color: #e6db74 } /* Literal.String.Double */\n  .se { color: #ae81ff } /* Literal.String.Escape */\n  .sh { color: #e6db74 } /* Literal.String.Heredoc */\n  .si { color: #e6db74 } /* Literal.String.Interpol */\n  .sx { color: #e6db74 } /* Literal.String.Other */\n  .sr { color: #e6db74 } /* Literal.String.Regex */\n  .s1 { color: #e6db74 } /* Literal.String.Single */\n  .ss { color: #e6db74 } /* Literal.String.Symbol */\n  .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */\n  .vc { color: #f8f8f2 } /* Name.Variable.Class */\n  .vg { color: #f8f8f2 } /* Name.Variable.Global */\n  .vi { color: #f8f8f2 } /* Name.Variable.Instance */\n  .il { color: #ae81ff } /* Literal.Number.Integer.Long */\n\n  .gh { } /* Generic Heading & Diff Header */\n  .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */\n  .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */\n  .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */\n}\n"
  },
  {
    "path": "app/assets/stylesheets/libs/markdown.scss",
    "content": ".markdown {\n\n  word-wrap: break-word;\n  overflow: hidden;\n  color: #4f4444;\n\n  @media screen and (min-width: 40.063em) {\n    h1, h2, h3, h4, h5, h6 {\n      font-size: 1.875rem;\n    }\n  }\n\n  @media screen and (max-width: 40em) {\n    h1, h2, h3, h4, h5, h6 {\n      font-size: 1.2rem;\n    }\n  }\n\n  h1, h2, h3, h4, h5, h6 {\n    margin-bottom: 1.5rem;\n    font-weight: 400;\n    line-height: 1.5;\n  }\n\n  p {\n    margin-bottom: 1.5rem;\n    line-height: 1.8;\n  }\n\n  pre {\n    padding: 1rem 0.5rem;\n    margin: 1rem 0;\n    font-weight: 500;\n    line-height: 1.5;\n    white-space: pre;\n    word-wrap: normal;\n    overflow-x: auto;\n    background-color: #272822;\n    color: #f8f8f2;\n    border-radius: 5px;\n\n    > code {\n      border: none;\n      background-color: transparent;\n      margin: 0;\n      padding: 0;\n    }\n  }\n\n  code {\n    font-weight: 500;\n    border: none;\n    margin: 0;\n    background: #5d5e59;\n    padding: 0.2rem 0.5rem;\n    border-radius: 5px;\n    color: #f8f8f2;\n    display: inline-block;\n    margin: 0.3rem 0.2rem 0.3rem 0;\n  }\n\n  blockquote {\n    padding: 0.5625rem 1.25rem 0 1.1875rem;\n    border-left: 1px solid #cacaca;\n    color: #8a8a8a;\n  }\n  ol, ul, dl {\n    padding-left: 1.2rem;\n    li {\n      margin-bottom: 0.3rem;\n    }\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/like_and_weixin.scss",
    "content": ".like-button {\n  color:  #eaa296;\n  background-color: transparent;\n  border: 1px solid #e79385;\n  border-radius: 10rem;\n  padding: 0.5rem 1rem;\n\n  &:hover {\n    color: #e07662;\n    border-color: #e27d6b;\n  }\n  &:focus {\n    outline-style: none;\n  }\n  span {\n    margin-left: 0.23rem;\n  }\n\n}\n\n.like-button.liked {\n  background-color: #EAA296;\n  color: #FFF;\n  &:focus {\n    outline-style: none;\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/new_year.scss",
    "content": ".new-year {\n  margin: 1rem 0;\n  text-align: center;\n  font-size: 1.1rem;\n  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n  color: white;\n  border: none;\n  box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.3);\n\n  strong {\n    color: white;\n  }\n\n  .close {\n    color: white;\n    opacity: 0.8;\n\n    &:hover {\n      opacity: 1;\n    }\n  }\n}\n"
  },
  {
    "path": "app/assets/stylesheets/qrcodes.scss",
    "content": "#image-tag {\n  float: right;\n  width: 200px;\n  margin-bottom: 1rem;\n}\n"
  },
  {
    "path": "app/channels/application_cable/channel.rb",
    "content": "module ApplicationCable\n  class Channel < ActionCable::Channel::Base\n  end\nend\n"
  },
  {
    "path": "app/channels/application_cable/connection.rb",
    "content": "module ApplicationCable\n  class Connection < ActionCable::Connection::Base\n  end\nend\n"
  },
  {
    "path": "app/controllers/admin/accounts_controller.rb",
    "content": "class Admin::AccountsController < Admin::BaseController\n  def edit\n  end\n\n  def update\n    if current_admin.authenticate(params.require(:administrator)[:current_password])\n      if current_admin.update(admin_params)\n        admin_sign_out\n        redirect_to admin_login_path, notice: 'Account has been updated, please log in again'\n      else\n        render 'edit'\n      end\n    else\n      flash.now[:alert] = 'Old password is wrong, try again'\n      render 'edit'\n    end\n  end\n\n  private\n\n  def admin_params\n    params.require(:administrator).permit(:name, :password, :password_confirmation)\n  end\nend\n"
  },
  {
    "path": "app/controllers/admin/all_comments_controller.rb",
    "content": "class Admin::AllCommentsController < Admin::BaseController\n  def index\n    @comments = Comment.order(created_at: :desc).page(params[:page]).per(25)\n  end\n\n  def destroy\n    comment = Comment.find(params[:id])\n    if comment.destroy\n      flash[:notice] = '删除评论成功'\n      redirect_to admin_all_comments_path\n    else\n      flash[:alert] = '删除失败'\n      redirect_to admin_all_comments_path\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/admin/base_controller.rb",
    "content": "class Admin::BaseController < ActionController::Base\n  layout 'admin'\n\n  protect_from_forgery with: :exception\n\n  before_action :authenticate_admin!\n\n  helper_method :current_admin\n\n  private\n\n  def authenticate_admin!\n    if current_admin.blank?\n      redirect_to admin_login_path\n      return\n    end\n\n    if current_admin.password_digest != session[:current_admin_token]\n      redirect_to admin_login_path, alert: 'Password was changed, please log in again'\n      return\n    end\n  end\n\n  def current_admin\n    @_current_admin ||= session[:current_admin_id] && Administrator.find_by(id: session[:current_admin_id])\n  end\n\n  def admin_sign_in(admin)\n    session[:current_admin_id] = admin.id\n    session[:current_admin_token] = admin.password_digest\n  end\n\n  def admin_sign_out\n    session[:current_admin_id] = nil\n    session[:current_admin_token] = nil\n    @_current_admin = nil\n  end\nend\n"
  },
  {
    "path": "app/controllers/admin/comments_controller.rb",
    "content": "class Admin::CommentsController < Admin::BaseController\n  before_action do\n    @post = Post.find( params[:post_id] )\n  end\n\n  def index\n    @comments = @post.comments.order(created_at: :desc)\n  end\n\n  def destroy\n    comment = @post.comments.find(params[:id])\n    if comment.destroy\n      flash[:notice] = '删除评论成功'\n      redirect_to admin_post_comments_path(@post)\n    else\n      flash[:alert] = '删除失败'\n      redirect_to admin_post_comments_path(@post)\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/admin/dashboard_controller.rb",
    "content": "class Admin::DashboardController < Admin::BaseController\n  def index\n    @posts_count = Post.all.count\n    @comments_count = Comment.all.count\n  end\nend\n"
  },
  {
    "path": "app/controllers/admin/labels_controller.rb",
    "content": "class Admin::LabelsController < Admin::BaseController\n  def index\n    @labels = Label.all.page(params[:page])\n  end\n\n  def new\n    @label = Label.new\n  end\n\n  def edit\n    @label = Label.find(params[:id])\n  end\n\n  def create\n    @label = Label.new(label_params)\n    if @label.save\n      redirect_to admin_labels_path, notice: '创建成功'\n    else\n      render :new\n    end\n  end\n\n  def update\n    @label = Label.find(params[:id])\n    @label.update!(label_params)\n    redirect_to admin_labels_path, notice: '更新成功'\n  end\n\n  def destroy\n    @label = Label.find(params[:id])\n    @label.destroy!\n    redirect_to admin_labels_path, notice: '删除成功'\n  end\n\n  private\n  def label_params\n    params.require(:label).permit(:name)\n  end\nend\n"
  },
  {
    "path": "app/controllers/admin/posts_controller.rb",
    "content": "class Admin::PostsController < Admin::BaseController\n  def new\n    @post = Post.new\n  end\n\n  def edit\n    @post = Post.find( params[:id] )\n  end\n\n  def destroy\n    @post = Post.find( params[:id] )\n    if @post.destroy\n      flash[:notice] = '删除博客成功'\n      redirect_to admin_posts_path\n    else\n      flash[:error] = '删除博客失败'\n      redirect_to admin_posts_path\n    end\n  end\n\n  def index\n    @posts = Post.order(created_at: :desc).page(params[:page]).per(25)\n  end\n\n  def create\n    @post = Post.new( post_params )\n\n    if @post.save\n      flash[:notice] = '创建博客成功'\n      redirect_to admin_posts_path\n    else\n      flash.now[:error] = '创建失败'\n      render :new, status: 422\n    end\n  end\n\n  def update\n    @post = Post.find( params[:id] )\n\n    if @post.update( post_params )\n      flash[:notice] = '更新博客成功'\n      redirect_to admin_posts_path\n    else\n      flash[:error] = '更新博客失败'\n      render :edit\n    end\n  end\n\n  def preview\n    render plain: Post.render_html(params[:content] || \"\")\n  end\n\n  private\n  def post_params\n    params.require(:post).permit(:title, :content, label_ids: [])\n  end\nend\n"
  },
  {
    "path": "app/controllers/admin/sessions_controller.rb",
    "content": "class Admin::SessionsController < Admin::BaseController\n  skip_before_action :authenticate_admin!, only: [:new, :create]\n\n  before_action do\n    @full_render = true\n  end\n\n  def new\n  end\n\n  def create\n    admin = Administrator.find_by(name: params[:name])\n    if admin && admin.authenticate(params[:password])\n      admin_sign_in(admin)\n      redirect_to admin_root_path\n    else\n      flash.now[:alert] = 'Username or password is wrong'\n      render 'new'\n    end\n  end\n\n  def destroy\n    admin_sign_out\n    redirect_to admin_login_path\n  end\nend\n"
  },
  {
    "path": "app/controllers/application_controller.rb",
    "content": "class ApplicationController < ActionController::Base\nend\n"
  },
  {
    "path": "app/controllers/archives_controller.rb",
    "content": "class ArchivesController < ApplicationController\n  def index\n    if (@q = params[:q]).blank?\n      @posts = Post.order(created_at: :desc).page(params[:page])\n    else\n      @q_size = Post.where('title like ?', \"%#{@q}%\").size\n      @posts = Post.where('title like ?', \"%#{@q}%\").order(created_at: :desc).page(params[:page])\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/blogs_controller.rb",
    "content": "class BlogsController < ApplicationController\n  def show\n    cookies[:cable_id] = SecureRandom.uuid\n    @post = Post.find(params[:id])\n    @post.visited\n    @prev = Post.where('created_at < ?', @post.created_at).order(created_at: :desc).first\n    @next = Post.where('created_at > ?', @post.created_at).order(created_at: :asc).first\n    @comments = @post.comments.order(created_at: :desc)\n    @likes_count = @post.likes.count\n  end\n\n  def edit\n    @post = Post.find(params[:id])\n    redirect_to edit_admin_post_path(@post)\n  end\nend\n"
  },
  {
    "path": "app/controllers/comments_controller.rb",
    "content": "class CommentsController < ApplicationController\n  layout false\n\n  def create\n    cookies[:name] = comment_params[:name]\n    cookies[:email] = comment_params[:email]\n\n    @post = Post.find( params[:blog_id] )\n    @comments = @post.comments.order(created_at: :desc)\n\n    # # 某些原因暂时关闭评论\n    flash.now[:notice] = '评论功能未开放'\n    return\n\n    @comment = @post.comments.build(comment_params)\n    if @comment.save\n      flash.now[:notice] = '发表成功'\n      # 重置评论\n      @comment = Comment.new\n    end\n  end\n\n  def refresh\n    @post = Post.find(params[:blog_id])\n    @comments = @post.comments.order(created_at: :desc)\n  end\n\n  private\n  def comment_params\n    params.require(:comment).permit(:content, :name, :email)\n  end\nend\n"
  },
  {
    "path": "app/controllers/concerns/.keep",
    "content": ""
  },
  {
    "path": "app/controllers/home_controller.rb",
    "content": "class HomeController < ApplicationController\n  def index\n    @newest = Post.order(created_at: :desc).first\n    @recent = Post.order(created_at: :desc).to_a[1..3]\n  end\n\n  def about\n  end\nend\n"
  },
  {
    "path": "app/controllers/likes_controller.rb",
    "content": "class LikesController < ApplicationController\n  layout false\n\n  def index\n    post = Post.find( params[:blog_id] )\n    render :json=> { success: true, count: post.liked_count }\n  end\n\n  def create\n    post = Post.find( params[:blog_id] )\n    like = post.likes.build\n\n    if like.save\n      render :json=> { success: true, id: like.id.to_s, count: post.liked_count }\n    else\n      render :json=> { success: false, count: post.liked_count }\n    end\n  end\n\n  def destroy\n    post = Post.find( params[:blog_id] )\n    like = post.likes.find(params[:id])\n    if like.destroy\n      render :json=> { success: true, count: post.reload.liked_count }\n    else\n      render :json=> { success: false, count: post.reload.liked_count }\n    end\n  end\nend\n"
  },
  {
    "path": "app/controllers/photos_controller.rb",
    "content": "class PhotosController < ApplicationController\n  def create\n    @photo = Photo.new(image: params[\"Filedata\"])\n    @photo.save!\n    render plain: md_url(@photo.image.url)\n  end\n\n  private\n  def md_url(url)\n    \"![](#{url})\"\n  end\nend\n"
  },
  {
    "path": "app/helpers/application_helper.rb",
    "content": "module ApplicationHelper\n  # Generate `{controller}-{action}-page` class for body element\n  def body_class\n    path = controller_path.tr('/_', '-')\n    action_name_map = {\n      index: 'index',\n      new: 'edit',\n      edit: 'edit',\n      update: 'edit',\n      patch: 'edit',\n      create: 'edit',\n      destory: 'index'\n    }\n    mapped_action_name = action_name_map[action_name.to_sym] || action_name\n    body_class_page =\n      if controller.is_a?(HighVoltage::StaticPage) && params.key?(:id) && params[:id] !~ /\\A[-+]?[0-9]*\\.?[0-9]+\\Z/\n        id_name = params[:id].tr('_', '-') + '-page'\n        format('%s-%s', 'pages', id_name)\n      else\n        format('%s-%s-page', path, mapped_action_name)\n      end\n\n    body_class_page\n  end\n\n  # Admin active for helper\n  def admin_active_for(controller_name, navbar_name)\n    if controller_name.to_s == admin_root_path\n      return controller_name.to_s == navbar_name.to_s ? \"active\" : \"\"\n    end\n    navbar_name.to_s.include?(controller_name.to_s) ? 'active' : ''\n  end\n\n  def current_path\n    request.env['PATH_INFO']\n  end\n\n  def flash_class(level)\n    case level\n    when 'notice', 'success' then 'alert alert-success alert-dismissible'\n    when 'info' then 'alert alert-info alert-dismissible'\n    when 'warning' then 'alert alert-warning alert-dismissible'\n    when 'alert', 'error' then 'alert alert-danger alert-dismissible'\n    end\n  end\n\n  def format_time(time)\n    time.strftime(\"%Y-%m-%d %H:%M\")\n  end\n\n  def format_date(time)\n    time.strftime(\"%Y.%m.%d\")\n  end\n\n  def search_highlight(title, q)\n    return title if q.blank?\n\n    title.sub(q, \"<em>#{q}</em>\")\n  end\nend\n"
  },
  {
    "path": "app/javascript/about.js",
    "content": "import './libs/ddscrollspy'\n\n$(document).on('turbo:load', function(){\n  if($('.home-about-page').length === 0) { return; }\n\n  $(window).scroll(function(){\n    if($(this).scrollTop() > 0)\n      $('.top-bar-wrapper').addClass('active')\n    else\n      $('.top-bar-wrapper').removeClass('active')\n  })\n\n  $('#about-top-bar').ddscrollSpy({highlightclass: 'active'})\n\n  $('#about-anchor-link').click(function(e){\n    e.preventDefault()\n    $('html, body').animate({\n      scrollTop: $('#about').offset().top\n    },500)\n  })\n})\n"
  },
  {
    "path": "app/javascript/admin/posts.js",
    "content": "$(document).on('turbo:load', function(){\n  $('a#upload_photo').click(function(){\n    $('input[type=file]').show().focus().click().hide()\n    return false\n  })\n\n  var opt = {\n    type: 'POST',\n    url: \"/photos\",\n    success: function(data,status,xhr){\n      txtBox = $(\"#content-input\")\n      caret_pos = txtBox.caret('pos')\n      src_merged = \"\\n\" + data + \"\\n\"\n      source = txtBox.val()\n      before_text = source.slice(0, caret_pos)\n      txtBox.val(before_text + src_merged + source.slice(caret_pos+1, source.count))\n      txtBox.caret('pos',caret_pos + src_merged.length)\n      txtBox.focus()\n    }\n  }\n\n  $('input[type=file]').fileUpload(opt)\n\n  $('#preview-link').on('show.bs.tab', function(e){\n    $('#preview').text('Loading...')\n    $.ajax({\n      url: '/admin/posts/preview',\n      type: 'POST',\n      data: { content: $('#content-input').val() },\n      success: function(data){\n        $('#preview').html(data)\n      }\n    })\n  })\n})\n"
  },
  {
    "path": "app/javascript/admin/sidebar.js",
    "content": "// (function() {\n  // window.App = window.App || {}\n  // window.App.adminSidebar = {\n    // saveSidebarScrollPosition: function() {\n      // var sidebar = this.page().find('.sidebar');\n      // var sidebarScrollTop = sidebar.scrollTop();\n      // localStorage.setItem('admin-SidebarScrollTop', sidebarScrollTop);\n    // },\n\n    // restoreSidebarScrollPosition: function() {\n      // var sidebar = this.page().find('.sidebar');\n      // var sidebarScrollTop = localStorage.getItem('admin-SidebarScrollTop');\n      // sidebar.scrollTop(sidebarScrollTop);\n    // },\n\n    // clearSidebarScrollPosition: function() {\n      // localStorage.setItem('admin-SidebarScrollTop', 0);\n    // },\n\n    // page: function() {\n      // return $('.admin-page');\n    // }\n  // };\n// }).call(this);\n\n// $(document).on('turbo:load', function() {\n  // var component = $('.admin-page');\n  // if (component.length > 0) {\n    // App.adminSidebar.restoreSidebarScrollPosition();\n  // }\n// });\n\n// $(document).on('turbo:before-render', function() {\n  // var component = $('.admin-page');\n  // if (component.length > 0) {\n    // App.adminSidebar.saveSidebarScrollPosition();\n  // } else {\n    // App.adminSidebar.clearSidebarScrollPosition();\n  // }\n// });\n"
  },
  {
    "path": "app/javascript/admin.js",
    "content": "import './base'\nimport './libs/jquery.html5-fileupload'\nimport './libs/jquery.atwho'\nimport 'admin-lte'\nimport './admin/sidebar'\nimport './admin/posts'\n"
  },
  {
    "path": "app/javascript/application.js",
    "content": "// Entry point for the build script in your package.json\n//\nimport './base'\nimport './about'\n"
  },
  {
    "path": "app/javascript/base.js",
    "content": "// base dependency library, it should be only shared by `admin.js` and `application.js`.\n//\nimport './libs/add_jquery'\nimport 'bootstrap/dist/js/bootstrap'\n\nimport RailsUjs from '@rails/ujs'\nimport \"@hotwired/turbo-rails\"\nimport * as ActiveStorage from '@rails/activestorage'\n\n// Turbo.session.drive = false\nRailsUjs.start()\nActiveStorage.start()\n\nimport './channels'\nimport \"./controllers\"\n\n$(document).on('turbo:load', function(){\n  $('[data-toggle=\"tooltip\"]').tooltip()\n})\n"
  },
  {
    "path": "app/javascript/channels/consumer.js",
    "content": "// Action Cable provides the framework to deal with WebSockets in Rails.\n// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.\n\nimport { createConsumer } from \"@rails/actioncable\"\n\nexport default createConsumer()\n"
  },
  {
    "path": "app/javascript/channels/index.js",
    "content": "// 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",
    "content": "import { Controller } from \"@hotwired/stimulus\"\nimport select2 from 'select2'\n\nwindow.select2 = select2();\n\nexport default class extends Controller {\n  static targets = [ 'label' ]\n\n  connect() {\n    $(this.labelTarget).select2({\n      multiple: true,\n      tags: false,\n    })\n  }\n\n  disconnected() {\n    $(this.labelTarget).select2('destroy')\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/index.js",
    "content": "// Load all the controllers within this directory and all subdirectories.\n// Controller files must be named *_controller.js or *_controller.ts.\n\nimport { Application } from \"@hotwired/stimulus\"\n\nconst application = Application.start()\n\nimport LikeController from \"./like_controller\"\napplication.register(\"like\", LikeController)\n\nimport QrcodeController from \"./qrcode_controller\"\napplication.register(\"qrcode\", QrcodeController)\n\nimport AdminLabelController from \"./admin_label_controller\"\napplication.register(\"admin-label\", AdminLabelController)\n"
  },
  {
    "path": "app/javascript/controllers/like_controller.js",
    "content": "import { Controller } from \"@hotwired/stimulus\"\nimport Cookies from 'js-cookie'\n\nexport default class extends Controller {\n  static targets = [ 'button' ]\n\n  toggle(e) {\n    let button = $(this.buttonTarget)\n    if( button.hasClass('liked') ){\n      $.ajax({\n        url: button.data('url') + '/' + Cookies.get('like'),\n        type: 'DELETE',\n        success: function(res){\n          button.removeClass('liked')\n          button.children('.count').text(res.count)\n          Cookies.remove('like')\n        }\n      })\n    }else{\n      $.ajax({\n        url: button.data('url'),\n        type: 'POST',\n        success: function(res){\n          button.addClass('liked')\n          button.children('.count').text(res.count)\n          Cookies.set('like', res.id)\n        }\n      })\n    }\n  }\n}\n"
  },
  {
    "path": "app/javascript/controllers/qrcode_controller.js",
    "content": "import { Controller } from \"@hotwired/stimulus\"\nimport '../libs/qrcode'\n\nexport default class extends Controller {\n  static targets = [ 'wrapper' ]\n  connect() {\n  }\n\n  greet(e) {\n    e.preventDefault()\n    $('#image-tag').empty()\n    new QRCode( $('#image-tag')[0], $('#image-tag').data('url') )\n    $(this.wrapperTarget).toggle()\n  }\n}\n"
  },
  {
    "path": "app/javascript/ga.js.erb",
    "content": "<% if ENV['GA'].present? %>\n  var script = document.createElement('script');\n  script.src = 'https://www.googletagmanager.com/gtag/js?id=<%= ENV['GA'] %>';\n  window.document.getElementsByTagName('head')[0].appendChild(script);\n  window.dataLayer = window.dataLayer || [];\n  function gtag(){dataLayer.push(arguments);}\n  gtag('js', new Date());\n\n  document.addEventListener('turbo:load', function(event){\n    if (typeof gtag === 'function') {\n      if(typeof gon !== 'undefined' && gon.user_id){\n        gtag('set', { 'user_id': gon.user_id })\n      }\n      gtag('config', '<%= ENV['GA'] %>', {\n        'page_location': event.data.url\n      });\n    }\n  });\n<% end %>\n"
  },
  {
    "path": "app/javascript/libs/add_jquery.js",
    "content": "import $ from 'jquery'\nwindow.jQuery = $\nwindow.$ = $\n"
  },
  {
    "path": "app/javascript/libs/ddscrollspy.js",
    "content": "/*\n* DD ScrollSpy Menu Script (c) Dynamic Drive (www.dynamicdrive.com)\n* Last updated: Aug 1st, 14'\n* Visit http://www.dynamicdrive.com/ for this script and 100s more.\n*/\n\n// Aug 1st, 14': Updated to v1.2, which supports showing a progress bar inside each menu item (except in iOS devices). Other minor improvements.\n\nif (!Array.prototype.filter){\n  Array.prototype.filter = function(fun /*, thisp */){\n    \"use strict\";\n\n    if (this == null)\n      throw new TypeError();\n\n    var t = Object(this);\n    var len = t.length >>> 0;\n    if (typeof fun != \"function\")\n      throw new TypeError();\n\n    var res = [];\n    var thisp = arguments[1];\n    for (var i = 0; i < len; i++){\n      if (i in t){\n        var val = t[i]; // in case fun mutates this\n        if (fun.call(thisp, val, i, t))\n          res.push(val);\n      }\n    }\n\n    return res;\n  };\n}\n\n(function($){\n\n\tvar defaults = {\n\t\tspytarget: window,\n\t\tscrolltopoffset: 0,\n\t\tscrollbehavior: 'smooth',\n\t\tscrollduration: 500,\n\t\thighlightclass: 'selected',\n\t\tenableprogress: '',\n\t\tmincontentheight: 30\n\t}\n\n\tvar isiOS = /iPhone|iPad|iPod/i.test(navigator.userAgent) // detect iOS devices\n\n\tfunction inrange(el, range, field){ // check if \"playing field\" is inside range\n\t\tvar rangespan = range[1]-range[0], fieldspan = field[1]-field[0]\n\t\tif ( (range[0]-field[0]) >= 0 && (range[0]-field[0]) < fieldspan ){ // if top of range is on field\n\t\t\treturn true\n\t\t}\n\t\telse{\n\t\t\tif ( (range[0]-field[0]) <= 0 && (range[0]+rangespan) > field[0] ){ // if part of range overlaps field\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\t$.fn.ddscrollSpy = function(options){\n\t\tvar $window = $(window)\n\t\tvar $body=(window.opera)? (document.compatMode==\"CSS1Compat\"? $('html') : $('body')) : $('html,body')\n\n\n\t\treturn this.each(function(){\n\t\t\tvar o = $.extend({}, defaults, options)\n\t\t\to.enableprogress = (isiOS)? '' : o.enableprogress // disable enableprogress in iOS\n\t\t\tvar targets = [], curtarget = ''\n\t\t\tvar cantscrollpastindex = -1 // index of target content that can't be scrolled past completely when scrollbar is at the end of the doc\n\t\t\tvar $spytarget = $( o.spytarget ).eq(0)\n\t\t\tvar spyheight = $spytarget.outerHeight()\n\t\t\tvar spyscrollheight = (o.spytarget == window)? $body.get(0).scrollHeight : $spytarget.get(0).scrollHeight\n\t\t\tvar $menu = $(this)\n\t\t\tvar totaltargetsheight = 0 // total height of target contents\n\t\t\tfunction spyonmenuitems($menu){\n\t\t\t\tvar $menuitems = $menu.find('a[href^=\"#\"]')\n\t\t\t\ttargets = []\n\t\t\t\tcurtarget = ''\n\t\t\t\ttotaltargetsheight = 0\n\t\t\t\t$menuitems.each(function(i){\n\t\t\t\t\tvar $item = $(this)\n\t\t\t\t\tvar $target = $( $item.attr('href') )\n\t\t\t\t\tvar target = $target.get(0)\n\t\t\t\t\tvar $progress = null // progress DIV that gets dynamically added inside menu A element if o.enableprogress enabled\n\t\t\t\t\tif ($target.length == 0) // if no matching links found\n\t\t\t\t\t\treturn true\n\t\t\t\t\t$item\n\t\t\t\t\t\t.off('click.goto')\n\t\t\t\t\t\t.on('click.goto', function(e){\n\t\t\t\t\t\t\tif ( o.spytarget == window && (o.scrollbehavior == 'jump' || !history.pushState))\n\t\t\t\t\t\t\t\twindow.location.hash = $item.attr('href')\n\t\t\t\t\t\t\tif (o.scrollbehavior == 'smooth' || o.scrolltopoffset !=0){\n\t\t\t\t\t\t\t\tvar $scrollparent = (o.spytarget == window)? $body : $spytarget\n\t\t\t\t\t\t\t\tvar 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)\n\t\t\t\t\t\t\t\tif (o.scrollbehavior == 'smooth' && (history.pushState || o.spytarget != window)){\n\t\t\t\t\t\t\t\t\t$scrollparent.animate( {scrollTop: targets[i].offsettop + addoffset}, o.scrollduration, function(){\n\t\t\t\t\t\t\t\t\t\tif (o.spytarget == window && history.pushState){\n\t\t\t\t\t\t\t\t\t\t\t//history.pushState(null, null, $item.attr('href'))\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse{\n\t\t\t\t\t\t\t\t\t$scrollparent.prop('scrollTop', targets[i].offsettop + addoffset)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\te.preventDefault()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\tif (o.enableprogress){ // if o.enableprogress enabled\n\t\t\t\t\t\tif ($item.find('div.' + o.enableprogress).length == 0){ //if no progress DIV found inside menu item\n\t\t\t\t\t\t\t$item.css({position: 'relative', overflow: 'hidden'}) // add some required style to parent A element\n\t\t\t\t\t\t\t$('<div class=\"' + o.enableprogress + '\" style=\"position:absolute; left: -100%\" />').appendTo($item)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$progress = $item.find('div.' + o.enableprogress)\n\t\t\t\t\t}\n\t\t\t\t\tvar targetoffset = (o.spytarget == window)? $target.offset().top : (target.offsetParent == o.spytarget)? target.offsetTop : target.offsetTop - o.spytarget.offsetTop\n\t\t\t\t\ttargetoffset +=  o.scrolltopoffset\n\t\t\t\t\tvar targetheight = ( parseInt($target.data('spyrange')) > 0 )? parseInt($target.data('spyrange')) : ( $target.outerHeight() || o.mincontentheight)\n\t\t\t\t\tvar offsetbottom = targetoffset + targetheight\n\t\t\t\t\tif (cantscrollpastindex == -1 && offsetbottom > (spyscrollheight - spyheight)){ // determine index of first target which can't be scrolled past\n\t\t\t\t\t\tcantscrollpastindex = i\n\t\t\t\t\t}\n\t\t\t\t\ttargets.push( {$menuitem: $item, $des: $target, offsettop: targetoffset, height: targetheight, $progress: $progress, index: i} )\n\t\t\t\t})\n\t\t\t\tif (targets.length > 0)\n\t\t\t\t\ttotaltargetsheight = targets[targets.length-1].offsettop + targets[targets.length-1].height\n\t\t\t}\n\n\t\t\tfunction highlightitem(){\n\t\t\t\tif (targets.length == 0)\n\t\t\t\t\treturn\n\t\t\t\tvar prevtarget = curtarget\n\t\t\t\tvar scrolltop = $spytarget.scrollTop()\n\t\t\t\tvar cantscrollpasttarget = false\n\t\t\t\tvar shortlist = targets.filter(function(el, index){ // filter target elements that are currently visible on screen\n\t\t\t\t\treturn inrange(el, [el.offsettop, el.offsettop + el.height], [scrolltop, scrolltop + spyheight])\n\t\t\t\t})\n\t\t\t\tif (shortlist.length > 0){\n\t\t\t\t\tcurtarget = shortlist.shift() // select the first element that's visible on screen\n\t\t\t\t\tif (prevtarget && prevtarget != curtarget)\n\t\t\t\t\t\tprevtarget.$menuitem.removeClass(o.highlightclass)\n\t\t\t\t\tif (!curtarget.$menuitem.hasClass(o.highlightclass)) // if there was a previously selected menu link and it's not the same as current\n\t\t\t\t\t\tcurtarget.$menuitem.addClass(o.highlightclass) // highlight its menu item\n\t\t\t\t\tif (curtarget.index >= cantscrollpastindex && scrolltop >= (spyscrollheight - spyheight)){ // if we're at target that can't be scrolled past and we're at end of document\n\t\t\t\t\t\tif (o.enableprogress){ // if o.enableprogress enabled\n\t\t\t\t\t\t\tfor (var i=0; i<targets.length; i++){ // highlight everything\n\t\t\t\t\t\t\t\ttargets[i].$menuitem.find('div.' + o.enableprogress).css('left', 0)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurtarget.$menuitem.removeClass(o.highlightclass)\n\t\t\t\t\t\tcurtarget = targets[targets.length-1]\n\t\t\t\t\t\tif (!curtarget.$menuitem.hasClass(o.highlightclass))\n\t\t\t\t\t\t\tcurtarget.$menuitem.addClass(o.highlightclass)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif (o.enableprogress){ // if o.enableprogress enabled\n\t\t\t\t\t\tvar scrollpct = ((scrolltop-curtarget.offsettop) / curtarget.height) * 100\n\t\t\t\t\t\tcurtarget.$menuitem.find('div.' + o.enableprogress).css('left', -100 + scrollpct + '%')\n\t\t\t\t\t\tfor (var i=0; i<targets.length; i++){\n\t\t\t\t\t\t\tif (i < curtarget.index){\n\t\t\t\t\t\t\t\ttargets[i].$menuitem.find('div.' + o.enableprogress).css('left', 0)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (i > curtarget.index){\n\t\t\t\t\t\t\t\ttargets[i].$menuitem.find('div.' + o.enableprogress).css('left', '-100%')\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (scrolltop > totaltargetsheight){ // if no target content visible on screen but scroll bar has scrolled past very last content already\n\t\t\t\t\tif (o.enableprogress){ // if o.enableprogress enabled\n\t\t\t\t\t\tcurtarget.$menuitem.removeClass(o.highlightclass)\n\t\t\t\t\t\tfor (var i=0; i<targets.length; i++){\n\t\t\t\t\t\t\ttargets[i].$menuitem.find('div.' + o.enableprogress).css('left', 0)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction updatetargetpos(){\n\t\t\t\tif (targets.length == 0)\n\t\t\t\t\treturn\n\t\t\t\tvar $menu = targets[0].$menu\n\t\t\t\tspyheight = $spytarget.outerHeight()\n\t\t\t\tspyscrollheight = (o.spytarget == window)? $body.get(0).scrollHeight : $spytarget.get(0).scrollHeight\n\t\t\t\ttotaltargetsheight = 0\n\t\t\t\tcantscrollpastindex = -1\n\t\t\t\tfor (var i = 0; i < targets.length; i++){\n\t\t\t\t\tvar $target = targets[i].$des\n\t\t\t\t\tvar target = $target.get(0)\n\t\t\t\t\tvar targetoffset = (o.spytarget == window)? $target.offset().top : (target.offsetParent == o.spytarget)? target.offsetTop : target.offsetTop - o.spytarget.offsetTop\n\t\t\t\t\ttargetoffset += o.scrolltopoffset\n\t\t\t\t\ttargets[i].offsettop = targetoffset\n\t\t\t\t\ttargets[i].height = ( parseInt($target.data('spyrange')) > 0 )? parseInt($target.data('spyrange')) : ( $target.outerHeight() || o.mincontentheight)\n\t\t\t\t\tif (o.enableprogress){ // if o.enableprogress enabled\n\t\t\t\t\t\tvar offsetbottom = targetoffset + targets[i].height // recalculate cantscrollpastindex\n\t\t\t\t\t\tif (cantscrollpastindex == -1 && offsetbottom > (spyscrollheight - spyheight)){\n\t\t\t\t\t\t\tcantscrollpastindex = i\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttotaltargetsheight = targets[targets.length-1].offsettop + targets[targets.length-1].height\n\t\t\t}\n\n\t\t\tspyonmenuitems($menu)\n\n\t\t\t$menu.on('updatespy', function(){\n\t\t\t\tspyonmenuitems($menu)\n\t\t\t\thighlightitem()\n\t\t\t})\n\n\t\t\t$spytarget.on('scroll resize', function(){\n\t\t\t\thighlightitem()\n\t\t\t})\n\n\t\t\thighlightitem()\n\n\t\t\t$window.on('load resize', function(){\n\t\t\t\tupdatetargetpos()\n\t\t\t})\n\n\t\t}) // end return\n\t}\n\n})(jQuery);\n"
  },
  {
    "path": "app/javascript/libs/jquery.atwho.js",
    "content": "//\n/*\n  Implement Github like autocomplete mentions\n  http://ichord.github.com/At.js\n\n  Copyright (c) 2013 chord.luo@gmail.com\n  Licensed under the MIT license.\n*/\n\n\n(function() {\n  (function(factory) {\n    if (typeof define === 'function' && define.amd) {\n      return define(['jquery'], factory);\n    } else {\n      return factory(window.jQuery);\n    }\n  })(function($) {\n    \"use strict\";\n    var Caret, Mirror, methods, pluginName;\n\n    pluginName = 'caret';\n    Caret = (function() {\n      function Caret($inputor) {\n        this.$inputor = $inputor;\n        this.domInputor = this.$inputor[0];\n      }\n\n      Caret.prototype.getPos = function() {\n        var end, endRange, inputor, len, normalizedValue, pos, range, start, textInputRange;\n\n        inputor = this.domInputor;\n        inputor.focus();\n        if (document.selection) {\n          /*\n          #assume we select \"HATE\" in the inputor such as textarea -> { }.\n           *               start end-point.\n           *              /\n           * <  I really [HATE] IE   > between the brackets is the selection range.\n           *                   \\\n           *                    end end-point.\n          */\n\n          range = document.selection.createRange();\n          pos = 0;\n          if (range && range.parentElement() === inputor) {\n            normalizedValue = inputor.value.replace(/\\r\\n/g, \"\\n\");\n            /* SOMETIME !!!\n             \"/r/n\" is counted as two char.\n              one line is two, two will be four. balalala.\n              so we have to using the normalized one's length.;\n            */\n\n            len = normalizedValue.length;\n            /*\n               <[  I really HATE IE   ]>:\n                the whole content in the inputor will be the textInputRange.\n            */\n\n            textInputRange = inputor.createTextRange();\n            /*                 _here must be the position of bookmark.\n                             /\n               <[  I really [HATE] IE   ]>\n                [---------->[           ] : this is what moveToBookmark do.\n               <   I really [[HATE] IE   ]> : here is result.\n                              \\ two brackets in should be in line.\n            */\n\n            textInputRange.moveToBookmark(range.getBookmark());\n            endRange = inputor.createTextRange();\n            /*  [--------------------->[] : if set false all end-point goto end.\n              <  I really [[HATE] IE  []]>\n            */\n\n            endRange.collapse(false);\n            /*\n                            ___VS____\n                           /         \\\n             <   I really [[HATE] IE []]>\n                                      \\_endRange end-point.\n\n            \" > -1\" mean the start end-point will be the same or right to the end end-point\n                     * simplelly, all in the end.\n            */\n\n            if (textInputRange.compareEndPoints(\"StartToEnd\", endRange) > -1) {\n              start = end = len;\n            } else {\n              /*\n                      I really |HATE] IE   ]>\n                             <-|\n                    I really[ [HATE] IE   ]>\n                          <-[\n                  I reall[y  [HATE] IE   ]>\n\n                will return how many unit have moved.\n              */\n\n              start = -textInputRange.moveStart(\"character\", -len);\n              end = -textInputRange.moveEnd(\"character\", -len);\n            }\n          }\n        } else {\n          start = inputor.selectionStart;\n        }\n        return start;\n      };\n\n      Caret.prototype.setPos = function(pos) {\n        var inputor, range;\n\n        inputor = this.domInputor;\n        if (document.selection) {\n          range = inputor.createTextRange();\n          range.move(\"character\", pos);\n          return range.select();\n        } else {\n          return inputor.setSelectionRange(pos, pos);\n        }\n      };\n\n      Caret.prototype.getPosition = function(pos) {\n        var $inputor, at_rect, format, h, html, mirror, start_range, x, y;\n\n        $inputor = this.$inputor;\n        format = function(value) {\n          return value.replace(/</g, '&lt').replace(/>/g, '&gt').replace(/`/g, '&#96').replace(/\"/g, '&quot').replace(/\\r\\n|\\r|\\n/g, \"<br />\");\n        };\n        if (pos === void 0) {\n          pos = this.getPos();\n        }\n        start_range = $inputor.val().slice(0, pos);\n        html = \"<span>\" + format(start_range) + \"</span>\";\n        html += \"<span id='caret'>|</span>\";\n        mirror = new Mirror($inputor);\n        at_rect = mirror.create(html).rect();\n        x = at_rect.left - $inputor.scrollLeft();\n        y = at_rect.top - $inputor.scrollTop();\n        h = at_rect.height;\n        return {\n          left: x,\n          top: y,\n          height: h\n        };\n      };\n\n      Caret.prototype.getOffset = function(pos) {\n        var $inputor, h, offset, position, x, y;\n\n        $inputor = this.$inputor;\n        offset = $inputor.offset();\n        position = this.getPosition(pos);\n        x = offset.left + position.left;\n        y = offset.top + position.top;\n        h = position.height;\n        return {\n          left: x,\n          top: y,\n          height: h\n        };\n      };\n\n      Caret.prototype.getIEPosition = function(pos) {\n        var h, inputorOffset, offset, x, y;\n\n        offset = this.getIEOffset(pos);\n        inputorOffset = this.$inputor.offset();\n        x = offset.left - inputorOffset.left;\n        y = offset.top - inputorOffset.top;\n        h = offset.height;\n        return {\n          left: x,\n          top: y,\n          height: h\n        };\n      };\n\n      Caret.prototype.getIEOffset = function(pos) {\n        var h, range, x, y;\n\n        range = this.domInputor.createTextRange();\n        if (pos) {\n          range.move('character', pos);\n        }\n        x = range.boundingLeft + $inputor.scrollLeft();\n        y = range.boundingTop + $(window).scrollTop() + $inputor.scrollTop();\n        h = range.boundingHeight;\n        return {\n          left: x,\n          top: y,\n          height: h\n        };\n      };\n\n      return Caret;\n\n    })();\n    Mirror = (function() {\n      Mirror.prototype.css_attr = [\"overflowY\", \"height\", \"width\", \"paddingTop\", \"paddingLeft\", \"paddingRight\", \"paddingBottom\", \"marginTop\", \"marginLeft\", \"marginRight\", \"marginBottom\", \"fontFamily\", \"borderStyle\", \"borderWidth\", \"wordWrap\", \"fontSize\", \"lineHeight\", \"overflowX\", \"text-align\"];\n\n      function Mirror($inputor) {\n        this.$inputor = $inputor;\n      }\n\n      Mirror.prototype.mirrorCss = function() {\n        var css,\n          _this = this;\n\n        css = {\n          position: 'absolute',\n          left: -9999,\n          top: 0,\n          zIndex: -20000,\n          'white-space': 'pre-wrap'\n        };\n        $.each(this.css_attr, function(i, p) {\n          return css[p] = _this.$inputor.css(p);\n        });\n        return css;\n      };\n\n      Mirror.prototype.create = function(html) {\n        this.$mirror = $('<div></div>');\n        this.$mirror.css(this.mirrorCss());\n        this.$mirror.html(html);\n        this.$inputor.after(this.$mirror);\n        return this;\n      };\n\n      Mirror.prototype.rect = function() {\n        var $flag, pos, rect;\n\n        $flag = this.$mirror.find(\"#caret\");\n        pos = $flag.position();\n        rect = {\n          left: pos.left,\n          top: pos.top,\n          height: $flag.height()\n        };\n        this.$mirror.remove();\n        return rect;\n      };\n\n      return Mirror;\n\n    })();\n    methods = {\n      pos: function(pos) {\n        if (pos) {\n          return this.setPos(pos);\n        } else {\n          return this.getPos();\n        }\n      },\n      position: function(pos) {\n        if (document.selection) {\n          return this.getIEPosition(pos);\n        } else {\n          return this.getPosition(pos);\n        }\n      },\n      offset: function(pos) {\n        if (document.selection) {\n          return this.getIEOffset(pos);\n        } else {\n          return this.getOffset(pos);\n        }\n      }\n    };\n    return $.fn.caret = function(method) {\n      var caret;\n\n      caret = new Caret(this);\n      if (methods[method]) {\n        return methods[method].apply(caret, Array.prototype.slice.call(arguments, 1));\n      } else {\n        return $.error(\"Method \" + method + \" does not exist on jQuery.caret\");\n      }\n    };\n  });\n\n}).call(this);\n\n\n/*\n  Implement Github like autocomplete mentions\n  http://ichord.github.com/At.js\n\n  Copyright (c) 2013 chord.luo@gmail.com\n  Licensed under the MIT license.\n*/\n\n\n(function() {\n  var __slice = [].slice;\n\n  (function(factory) {\n    if (typeof define === 'function' && define.amd) {\n      return define(['jquery'], factory);\n    } else {\n      return factory(window.jQuery);\n    }\n  })(function($) {\n    var $CONTAINER, Api, App, Controller, DEFAULT_CALLBACKS, DEFAULT_TPL, KEY_CODE, Model, View;\n    App = (function() {\n\n      function App(inputor) {\n        this.current_flag = null;\n        this.controllers = {};\n        this.$inputor = $(inputor);\n        this.listen();\n      }\n\n      App.prototype.controller = function(key) {\n        return this.controllers[key || this.current_flag];\n      };\n\n      App.prototype.set_context_for = function(key) {\n        this.current_flag = key;\n        return this;\n      };\n\n      App.prototype.reg = function(flag, setting) {\n        var controller, _base;\n        controller = (_base = this.controllers)[flag] || (_base[flag] = new Controller(this, flag));\n        if (setting.alias) {\n          this.controllers[setting.alias] = controller;\n        }\n        controller.init(setting);\n        return this;\n      };\n\n      App.prototype.listen = function() {\n        var _this = this;\n        return this.$inputor.on('keyup.atwho', function(e) {\n          return _this.on_keyup(e);\n        }).on('keydown.atwho', function(e) {\n          return _this.on_keydown(e);\n        }).on('scroll.atwho', function(e) {\n          var _ref;\n          return (_ref = _this.controller()) != null ? _ref.view.hide() : void 0;\n        }).on('blur.atwho', function(e) {\n          var c;\n          if (c = _this.controller()) {\n            return c.view.hide(c.get_opt(\"display_timeout\"));\n          }\n        });\n      };\n\n      App.prototype.dispatch = function() {\n        var _this = this;\n        return $.map(this.controllers, function(c) {\n          if (c.look_up()) {\n            return _this.set_context_for(c.key);\n          }\n        });\n      };\n\n      App.prototype.on_keyup = function(e) {\n        var _ref;\n        switch (e.keyCode) {\n          case KEY_CODE.ESC:\n            e.preventDefault();\n            if ((_ref = this.controller()) != null) {\n              _ref.view.hide();\n            }\n            break;\n          case KEY_CODE.DOWN:\n          case KEY_CODE.UP:\n            $.noop();\n            break;\n          default:\n            this.dispatch();\n        }\n      };\n\n      App.prototype.on_keydown = function(e) {\n        var view, _ref;\n        view = (_ref = this.controller()) != null ? _ref.view : void 0;\n        if (!(view && view.visible())) {\n          return;\n        }\n        switch (e.keyCode) {\n          case KEY_CODE.ESC:\n            e.preventDefault();\n            view.hide();\n            break;\n          case KEY_CODE.UP:\n            e.preventDefault();\n            view.prev();\n            break;\n          case KEY_CODE.DOWN:\n            e.preventDefault();\n            view.next();\n            break;\n          case KEY_CODE.TAB:\n          case KEY_CODE.ENTER:\n            if (!view.visible()) {\n              return;\n            }\n            e.preventDefault();\n            view.choose();\n            break;\n          default:\n            $.noop();\n        }\n      };\n\n      return App;\n\n    })();\n    Controller = (function() {\n      var uuid, _uuid;\n\n      _uuid = 0;\n\n      uuid = function() {\n        return _uuid += 1;\n      };\n\n      function Controller(app, key) {\n        this.app = app;\n        this.key = key;\n        this.$inputor = this.app.$inputor;\n        this.id = this.$inputor[0].id || uuid();\n        this.setting = null;\n        this.query = null;\n        this.pos = 0;\n        $CONTAINER.append(this.$el = $(\"<div id='atwho-ground-\" + this.id + \"'></div>\"));\n        this.model = new Model(this);\n        this.view = new View(this);\n      }\n\n      Controller.prototype.init = function(setting) {\n        this.setting = $.extend({}, this.setting || $.fn.atwho[\"default\"], setting);\n        return this.model.reload(this.setting.data);\n      };\n\n      Controller.prototype.call_default = function() {\n        var args, func_name;\n        func_name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n        try {\n          return DEFAULT_CALLBACKS[func_name].apply(this, args);\n        } catch (error) {\n          return $.error(\"\" + error + \" Or maybe At.js doesn't have function \" + func_name);\n        }\n      };\n\n      Controller.prototype.trigger = function(name, data) {\n        var alias, event_name;\n        data.push(this);\n        alias = this.get_opt('alias');\n        event_name = alias ? \"\" + name + \"-\" + alias + \".atwho\" : \"\" + name + \".atwho\";\n        return this.$inputor.trigger(event_name, data);\n      };\n\n      Controller.prototype.callbacks = function(func_name) {\n        return this.get_opt(\"callbacks\")[func_name] || DEFAULT_CALLBACKS[func_name];\n      };\n\n      Controller.prototype.get_opt = function(key, default_value) {\n        try {\n          return this.setting[key];\n        } catch (e) {\n          return null;\n        }\n      };\n\n      Controller.prototype.catch_query = function() {\n        var caret_pos, content, end, query, start, subtext;\n        content = this.$inputor.val();\n        caret_pos = this.$inputor.caret('pos');\n        subtext = content.slice(0, caret_pos);\n        query = this.callbacks(\"matcher\").call(this, this.key, subtext, this.get_opt('start_with_space'));\n        if (typeof query === \"string\" && query.length <= this.get_opt('max_len', 20)) {\n          start = caret_pos - query.length;\n          end = start + query.length;\n          this.pos = start;\n          query = {\n            'text': query.toLowerCase(),\n            'head_pos': start,\n            'end_pos': end\n          };\n          this.trigger(\"matched\", [this.key, query.text]);\n        } else {\n          this.view.hide();\n        }\n        return this.query = query;\n      };\n\n      Controller.prototype.rect = function() {\n        var c, scale_bottom;\n        c = this.$inputor.caret('offset', this.pos - 1);\n        scale_bottom = document.selection ? 0 : 2;\n        return {\n          left: c.left,\n          top: c.top,\n          bottom: c.top + c.height + scale_bottom\n        };\n      };\n\n      Controller.prototype.insert = function(str) {\n        var $inputor, source, start_str, text;\n        $inputor = this.$inputor;\n        str = '' + str;\n        source = $inputor.val();\n        start_str = source.slice(0, this.query['head_pos'] || 0);\n        text = \"\" + start_str + str + \" \" + (source.slice(this.query['end_pos'] || 0));\n        $inputor.val(text);\n        $inputor.caret('pos', start_str.length + str.length + 1);\n        return $inputor.change();\n      };\n\n      Controller.prototype.render_view = function(data) {\n        var search_key;\n        search_key = this.get_opt(\"search_key\");\n        data = this.callbacks(\"sorter\").call(this, this.query.text, data.slice(0, 1001), search_key);\n        return this.view.render(data.slice(0, this.get_opt('limit')));\n      };\n\n      Controller.prototype.look_up = function() {\n        var query, _callback;\n        if (!(query = this.catch_query())) {\n          return;\n        }\n        _callback = function(data) {\n          if (data && data.length > 0) {\n            return this.render_view(data);\n          } else {\n            return this.view.hide();\n          }\n        };\n        this.model.query(query.text, $.proxy(_callback, this));\n        return query;\n      };\n\n      return Controller;\n\n    })();\n    Model = (function() {\n      var _storage;\n\n      _storage = {};\n\n      function Model(context) {\n        this.context = context;\n        this.key = this.context.key;\n      }\n\n      Model.prototype.saved = function() {\n        return this.fetch() > 0;\n      };\n\n      Model.prototype.query = function(query, callback) {\n        var data, search_key, _ref;\n        data = this.fetch();\n        search_key = this.context.get_opt(\"search_key\");\n        callback(data = this.context.callbacks('filter').call(this.context, query, data, search_key));\n        if (!(data && data.length > 0)) {\n          return (_ref = this.context.callbacks('remote_filter')) != null ? _ref.call(this.context, query, callback) : void 0;\n        }\n      };\n\n      Model.prototype.fetch = function() {\n        return _storage[this.key] || [];\n      };\n\n      Model.prototype.save = function(data) {\n        return _storage[this.key] = this.context.callbacks(\"before_save\").call(this.context, data || []);\n      };\n\n      Model.prototype.load = function(data) {\n        if (!(this.saved() || !data)) {\n          return this._load(data);\n        }\n      };\n\n      Model.prototype.reload = function(data) {\n        return this._load(data);\n      };\n\n      Model.prototype._load = function(data) {\n        var _this = this;\n        if (typeof data === \"string\") {\n          return $.ajax(data, {\n            dataType: \"json\"\n          }).done(function(data) {\n            return _this.save(data);\n          });\n        } else {\n          return this.save(data);\n        }\n      };\n\n      return Model;\n\n    })();\n    View = (function() {\n\n      function View(context) {\n        this.context = context;\n        this.key = this.context.key;\n        this.id = this.context.get_opt(\"alias\") || (\"at-view-\" + (this.key.charCodeAt(0)));\n        this.$el = $(\"<div id='\" + this.id + \"' class='atwho-view'><ul id='\" + this.id + \"-ul' class='atwho-view-url'></ul></div>\");\n        this.timeout_id = null;\n        this.context.$el.append(this.$el);\n        this.bind_event();\n      }\n\n      View.prototype.bind_event = function() {\n        var $menu,\n          _this = this;\n        $menu = this.$el.find('ul');\n        return $menu.on('mouseenter.view', 'li', function(e) {\n          $menu.find('.cur').removeClass('cur');\n          return $(e.currentTarget).addClass('cur');\n        }).on('click', function(e) {\n          _this.choose();\n          return e.preventDefault();\n        });\n      };\n\n      View.prototype.visible = function() {\n        return this.$el.is(\":visible\");\n      };\n\n      View.prototype.choose = function() {\n        var $li;\n        $li = this.$el.find(\".cur\");\n        this.context.insert(this.context.callbacks(\"before_insert\").call(this.context, $li.data(\"value\"), $li));\n        this.context.trigger(\"inserted\", [$li]);\n        return this.hide();\n      };\n\n      View.prototype.reposition = function() {\n        var offset, rect;\n        rect = this.context.rect();\n        if (rect.bottom + this.$el.height() - $(window).scrollTop() > $(window).height()) {\n          rect.bottom = rect.top - this.$el.height();\n        }\n        offset = {\n          left: rect.left,\n          top: rect.bottom\n        };\n        this.$el.offset(offset);\n        return this.context.trigger(\"reposition\", [offset]);\n      };\n\n      View.prototype.next = function() {\n        var cur, next;\n        cur = this.$el.find('.cur').removeClass('cur');\n        next = cur.next();\n        if (!next.length) {\n          next = this.$el.find('li:first');\n        }\n        return next.addClass('cur');\n      };\n\n      View.prototype.prev = function() {\n        var cur, prev;\n        cur = this.$el.find('.cur').removeClass('cur');\n        prev = cur.prev();\n        if (!prev.length) {\n          prev = this.$el.find('li:last');\n        }\n        return prev.addClass('cur');\n      };\n\n      View.prototype.show = function() {\n        if (!this.visible()) {\n          this.$el.show();\n        }\n        return this.reposition();\n      };\n\n      View.prototype.hide = function(time) {\n        var callback,\n          _this = this;\n        if (isNaN(time && this.visible())) {\n          return this.$el.hide();\n        } else {\n          callback = function() {\n            return _this.hide();\n          };\n          clearTimeout(this.timeout_id);\n          return this.timeout_id = setTimeout(callback, time);\n        }\n      };\n\n      View.prototype.render = function(list) {\n        var $li, $ul, item, li, tpl, _i, _len;\n        if (!$.isArray(list || list.length <= 0)) {\n          this.hide();\n          return;\n        }\n        this.$el.find('ul').empty();\n        $ul = this.$el.find('ul');\n        tpl = this.context.get_opt('tpl', DEFAULT_TPL);\n        for (_i = 0, _len = list.length; _i < _len; _i++) {\n          item = list[_i];\n          li = this.context.callbacks(\"tpl_eval\").call(this.context, tpl, item);\n          $li = $(this.context.callbacks(\"highlighter\").call(this.context, li, this.context.query.text));\n          $li.data(\"atwho-info\", item);\n          $ul.append($li);\n        }\n        this.show();\n        return $ul.find(\"li:first\").addClass(\"cur\");\n      };\n\n      return View;\n\n    })();\n    KEY_CODE = {\n      DOWN: 40,\n      UP: 38,\n      ESC: 27,\n      TAB: 9,\n      ENTER: 13\n    };\n    DEFAULT_CALLBACKS = {\n      before_save: function(data) {\n        var item, _i, _len, _results;\n        if (!$.isArray(data)) {\n          return data;\n        }\n        _results = [];\n        for (_i = 0, _len = data.length; _i < _len; _i++) {\n          item = data[_i];\n          if ($.isPlainObject(item)) {\n            _results.push(item);\n          } else {\n            _results.push({\n              name: item\n            });\n          }\n        }\n        return _results;\n      },\n      matcher: function(flag, subtext, should_start_with_space) {\n        var match, regexp;\n        flag = flag.replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|]/g, \"\\\\$&\");\n        if (should_start_with_space) {\n          flag = '(?:^|\\\\s)' + flag;\n        }\n        regexp = new RegExp(flag + '([A-Za-z0-9_\\+\\-]*)$|' + flag + '([^\\\\x00-\\\\xff]*)$', 'gi');\n        match = regexp.exec(subtext);\n        if (match) {\n          return match[2] || match[1];\n        } else {\n          return null;\n        }\n      },\n      filter: function(query, data, search_key) {\n        var item, _i, _len, _results;\n        _results = [];\n        for (_i = 0, _len = data.length; _i < _len; _i++) {\n          item = data[_i];\n          if (~item[search_key].toLowerCase().indexOf(query)) {\n            _results.push(item);\n          }\n        }\n        return _results;\n      },\n      remote_filter: null,\n      sorter: function(query, items, search_key) {\n        var item, _i, _len, _results;\n        if (!query) {\n          return items;\n        }\n        _results = [];\n        for (_i = 0, _len = items.length; _i < _len; _i++) {\n          item = items[_i];\n          item.atwho_order = item[search_key].toLowerCase().indexOf(query);\n          if (item.atwho_order > -1) {\n            _results.push(item);\n          }\n        }\n        return _results.sort(function(a, b) {\n          return a.atwho_order - b.atwho_order;\n        });\n      },\n      tpl_eval: function(tpl, map) {\n        try {\n          return tpl.replace(/\\$\\{([^\\}]*)\\}/g, function(tag, key, pos) {\n            return map[key];\n          });\n        } catch (error) {\n          return \"\";\n        }\n      },\n      highlighter: function(li, query) {\n        var regexp;\n        if (!query) {\n          return li;\n        }\n        regexp = new RegExp(\">\\\\s*(\\\\w*)(\" + query.replace(\"+\", \"\\\\+\") + \")(\\\\w*)\\\\s*<\", 'ig');\n        return li.replace(regexp, function(str, $1, $2, $3) {\n          return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';\n        });\n      },\n      before_insert: function(value, $li) {\n        return value;\n      }\n    };\n    DEFAULT_TPL = \"<li data-value='${name}'>${name}</li>\";\n    Api = {\n      init: function(options) {\n        var $this, app;\n        app = ($this = $(this)).data(\"atwho\");\n        if (!app) {\n          $this.data('atwho', (app = new App(this)));\n        }\n        return app.reg(options.at, options);\n      },\n      load: function(key, data) {\n        var c;\n        if (c = this.controller(key)) {\n          return c.model.load(data);\n        }\n      },\n      run: function() {\n        return this.dispatch();\n      }\n    };\n    $CONTAINER = $(\"<div id='atwho-container'></div>\");\n    $.fn.atwho = function(method) {\n      var _args;\n      _args = arguments;\n      $('body').append($CONTAINER);\n      return this.filter('textarea, input').each(function() {\n        var app;\n        if (typeof method === 'object' || !method) {\n          return Api.init.apply(this, _args);\n        } else if (Api[method]) {\n          if (app = $(this).data('atwho')) {\n            return Api[method].apply(app, Array.prototype.slice.call(_args, 1));\n          }\n        } else {\n          return $.error(\"Method \" + method + \" does not exist on jQuery.caret\");\n        }\n      });\n    };\n    return $.fn.atwho[\"default\"] = {\n      at: void 0,\n      alias: void 0,\n      data: null,\n      tpl: DEFAULT_TPL,\n      callbacks: DEFAULT_CALLBACKS,\n      search_key: \"name\",\n      limit: 5,\n      max_len: 20,\n      start_with_space: true,\n      display_timeout: 300\n    };\n  });\n\n}).call(this);\n"
  },
  {
    "path": "app/javascript/libs/jquery.html5-fileupload.js",
    "content": "/*\n *  jQuery HTML5 File Upload\n *\n *  Author: timdream at gmail.com\n *  Web: http://timc.idv.tw/html5-file-upload/\n *\n *  Ajax File Upload that use real xhr,\n *  built with getAsBinary, sendAsBinary, FormData, FileReader, ArrayBuffer, BlobBuilder and etc.\n *  works in Firefox 3, Chrome 5, Safari 5 and higher\n *\n *  Image resizing and uploading currently works in Fx 3 and up, and Chrome 9 (dev) and up only.\n *  Extra settings will allow current Webkit users to upload the original image\n *  or send the resized image in base64 form.\n *\n *  Usage:\n *   $.fileUploadSupported // a boolean value indicates if the browser is supported.\n *   $.imageUploadSupported // a boolean value indicates if the browser could resize image and upload in binary form.\n *   $.fileUploadAsBase64Supported // a boolean value indicate if the browser upload files in based64.\n *   $.imageUploadAsBase64Supported // a boolean value indicate if the browser could resize image and upload in based64.\n *   $('input[type=file]').fileUpload(ajaxSettings); //Make a input[type=file] select-and-send file upload widget\n *   $('#any-element').fileUpload(ajaxSettings); //Make a element receive dropped file\n *   //TBD $('form#fileupload').fileUpload(ajaxSettings); //Send a ajax form with file\n *   //TBD $('canvas').fileUpload(ajaxSettings); //Upload given canvas as if it's an png image.\n *\n *   ajaxSettings is the object contains $.ajax settings that will be passed to.\n *   Available extended settings are:\n *      fileType:\n *           regexp check against filename extension; You should always checked it again on server-side.\n *           e.g. /^(gif|jpe?g|png|tiff?)$/i for images\n *      fileMaxSize:\n *           Maxium file size allowed in bytes. Use scientific notation for converience.\n *           e.g. 1E4 for 1KB, 1E8 for 1MB, 1E9 for 10MB.\n *\t\t\t If you really care the difference between 1024 and 1000, use Math.pow(2, 10)\n *      fileError(info, textStatus, textDescription):\n *           callback function when there is any error preventing file upload to start,\n *           $.ajax and ajax events won't be called when error.\n *           Use $.noop to overwrite default alert function.\n *      imageMaxWidth, imageMaxHeight:\n *           Use any of the two settings to enable client-size image resizing.\n *           Image will be resized to fit into given rectangle.\n *           File size and type limit checking will be ignored.\n *      allowUploadOriginalImage:\n *           Set to true if you accept original image to be uploaded as a fallback\n *           when image resizing functionality is not availible (such as Webkit browsers).\n *           File size and type limit will be enforced.\n *      allowDataInBase64:\n *           Alternatively, you may wish to resize the image anyway and send the data\n *           in base64. The data will be 133% larger and you will need to process it further with\n *           server-side script.\n *           This setting might work with browsers which could read file but cannot send it in original\n *           binary (no known browser are designed this way though)\n *      forceResize:\n *           Set to true will cause the image being re-sampled even if the resized image\n *           has the same demension as the original one.\n *      imageType:\n *           Acceptable values are: 'jpeg', 'png', or 'auto'.\n *\n *  TBD:\n *   ability to change settings after binding (you can unbind and bind again as a workaround)\n *   multipole file handling\n *   form intergation\n *\n */\n\n(function($) {\n\t// Don't do logging if window.log function does not exist.\n\tvar log = window.console.log || $.noop;\n\n\t// jQuery.ajax config\n\tvar config = {\n\t\tfileError: function (info, textStatus, textDescription) {\n\t\t\twindow.alert(textDescription);\n\t\t}\n\t};\n\n\t// Feature detection\n\n\t// Read as binary string: FileReader API || Gecko-specific function (Fx3)\n\tvar canReadAsBinaryString = (window.FileReader || window.File.prototype.getAsBinary);\n\t// Read file using FormData interface\n\tvar canReadFormData = !!(window.FormData);\n\t// Read file into data: URL: FileReader API || Gecko-specific function (Fx3)\n\tvar canReadAsBase64 = (window.FileReader || window.File.prototype.getAsDataURL);\n\n\tvar canResizeImageToBase64 = !!(document.createElement('canvas').toDataURL);\n\tvar canResizeImageToBinaryString = canResizeImageToBase64 && window.atob;\n\tvar canResizeImageToFile = !!(document.createElement('canvas').mozGetAsFile);\n\n\t// Send file in multipart/form-data with binary xhr (Gecko-specific function)\n\t// || xhr.send(blob) that sends blob made with ArrayBuffer.\n\tvar canSendBinaryString = (\n\t\t(window.XMLHttpRequest && window.XMLHttpRequest.prototype.sendAsBinary)\n\t\t|| (window.ArrayBuffer && window.BlobBuilder)\n\t);\n\t// Send file as in FormData object\n\tvar canSendFormData = !!(window.FormData);\n\t// Send image base64 data by extracting data: URL\n\tvar canSendImageInBase64 = !!(document.createElement('canvas').toDataURL);\n\n\tvar isSupported = (\n\t\t(canReadAsBinaryString && canSendBinaryString)\n\t\t|| (canReadFormData && canSendFormData)\n\t);\n\tvar isImageSupported = (\n\t\tcanReadAsBase64 && (\n\t\t\t(canResizeImageToBinaryString && canSendBinaryString)\n\t\t\t|| (canResizeImageToFile && canSendFormData)\n\t\t)\n\t);\n\tvar isSupportedInBase64 = canReadAsBase64;\n\tvar isImageSupportedInBase64 = canReadAsBase64 && canResizeImageToBase64;\n\n\tvar dataURLtoBase64 = function (dataurl) {\n\t\treturn dataurl.substring(dataurl.indexOf(',')+1, dataurl.length);\n\t}\n\n\t// Step 1: check file info and attempt to read the file\n\t// paramaters: Ajax settings, File object\n\tvar handleFile = function (settings, file) {\n\t\tvar info = {\n\t\t\t// properties of standard File object || Gecko 1.9 properties\n\t\t\ttype: file.type || '', // MIME type\n\t\t\tsize: file.size || file.fileSize,\n\t\t\tname: file.name || file.fileName\n\t\t};\n\n\t\tsettings.resizeImage = !!(settings.imageMaxWidth || settings.imageMaxHeight);\n\n\t\tif (settings.resizeImage && !isImageSupported && settings.allowUploadOriginalImage) {\n\t\t\tlog('WARN: Fall back to upload original un-resized image.');\n\t\t\tsettings.resizeImage = false;\n\t\t}\n\n\t\tif (settings.resizeImage) {\n\t\t\tsettings.imageMaxWidth = settings.imageMaxWidth || Infinity;\n\t\t\tsettings.imageMaxHeight = settings.imageMaxHeight || Infinity;\n\t\t}\n\n\t\tif (!settings.resizeImage) {\n\t\t\tif (settings.fileType && settings.fileType.test) {\n\t\t\t\t// Not using MIME types\n\t\t\t\tif (!settings.fileType.test(info.name.substr(info.name.lastIndexOf('.')+1))) {\n\t\t\t\t\tlog('ERROR: Invalid Filetype.');\n\t\t\t\t\tsettings.fileError.call(this, info, 'INVALID_FILETYPE', 'Invalid filetype.');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (settings.fileMaxSize && file.size > settings.fileMaxSize) {\n\t\t\t\tlog('ERROR: File exceeds size limit.');\n\t\t\t\tsettings.fileError.call(this, info, 'FILE_EXCEEDS_SIZE_LIMIT', 'File exceeds size limit.');\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif (!settings.resizeImage && canReadFormData) {\n\t\t\tlog('INFO: Bypass file reading, insert file object into FormData object directly.');\n\t\t\thandleForm(settings, 'file', file, info);\n\t\t} else if (window.FileReader) {\n\t\t\tlog('INFO: Using FileReader to do asynchronously file reading.');\n\t\t\tvar reader = new FileReader();\n\t\t\treader.onerror = function (ev) {\n\t\t\t\tif (ev.target.error) {\n\t\t\t\t\tswitch (ev.target.error) {\n\t\t\t\t\t\tcase 8:\n\t\t\t\t\t\tlog('ERROR: File not found.');\n\t\t\t\t\t\tsettings.fileError.call(this, info, 'FILE_NOT_FOUND', 'File not found.');\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 24:\n\t\t\t\t\t\tlog('ERROR: File not readable.');\n\t\t\t\t\t\tsettings.fileError.call(this, info, 'IO_ERROR', 'File not readable.');\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 18:\n\t\t\t\t\t\tlog('ERROR: File cannot be access due to security constrant.');\n\t\t\t\t\t\tsettings.fileError.call(this, info, 'SECURITY_ERROR', 'File cannot be access due to security constrant.');\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 20: //User Abort\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!settings.resizeImage) {\n\t\t\t\tif (canSendBinaryString) {\n\t\t\t\t\treader.onloadend = function (ev) {\n\t\t\t\t\t\tvar bin = ev.target.result;\n\t\t\t\t\t\thandleForm(settings, 'bin', bin, info);\n\t\t\t\t\t};\n\t\t\t\t\treader.readAsBinaryString(file);\n\t\t\t\t} else if (settings.allowDataInBase64) {\n\t\t\t\t\treader.onloadend = function (ev) {\n\t\t\t\t\t\thandleForm(\n\t\t\t\t\t\t\tsettings,\n\t\t\t\t\t\t\t'base64',\n\t\t\t\t\t\t\tdataURLtoBase64(ev.target.result),\n\t\t\t\t\t\t\tinfo\n\t\t\t\t\t\t);\n\t\t\t\t\t};\n\t\t\t\t\treader.readAsDataURL(file);\n\t\t\t\t} else {\n\t\t\t\t\tlog('ERROR: No available method to extract file; allowDataInBase64 not set.');\n\t\t\t\t\tsettings.fileError.call(this, info, 'NO_BIN_SUPPORT_AND_BASE64_NOT_SET', 'No available method to extract file; allowDataInBase64 not set.');\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treader.onloadend = function (ev) {\n\t\t\t\t\tvar dataurl = ev.target.result;\n\t\t\t\t\thandleImage(settings, dataurl, info);\n\t\t\t\t};\n\t\t\t\treader.readAsDataURL(file);\n\t\t\t}\n\t\t} else if (window.File.prototype.getAsBinary) {\n\t\t\tlog('WARN: FileReader does not exist, UI will be blocked when reading big file.');\n\t\t\tif (!settings.resizeImage) {\n\t\t\t\ttry {\n\t\t\t\t\tvar bin = file.getAsBinary();\n\t\t\t\t} catch (e) {\n\t\t\t\t\tlog('ERROR: File not readable.');\n\t\t\t\t\tsettings.fileError.call(this, info, 'IO_ERROR', 'File not readable.');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\thandleForm(settings, 'bin', bin, info);\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tvar bin = file.getAsDataURL();\n\t\t\t\t} catch (e) {\n\t\t\t\t\tlog('ERROR: File not readable.');\n\t\t\t\t\tsettings.fileError.call(this, info, 'IO_ERROR', 'File not readable.');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\thandleImage(settings, dataurl, info);\n\t\t\t}\n\t\t} else {\n\t\t\tlog('ERROR: No available method to extract file; this browser is not supported.');\n\t\t\tsettings.fileError.call(this, info, 'NOT_SUPPORT', 'ERROR: No available method to extract file; this browser is not supported.');\n\t\t}\n\t};\n\n\t// step 1.5: inject file into <img>, paste the pixels into <canvas>,\n\t// read the final image\n\tvar handleImage = function (settings, dataurl, info) {\n\t\tvar img = new Image();\n\t\timg.onerror = function () {\n\t\t\tlog('ERROR: <img> failed to load, file is not a supported image format.');\n\t\t\tsettings.fileError.call(this, info, 'FILE_NOT_IMAGE', 'File is not a supported image format.');\n\t\t};\n\t\timg.onload = function () {\n\t\t\tvar ratio = Math.max(\n\t\t\t\timg.width/settings.imageMaxWidth,\n\t\t\t\timg.height/settings.imageMaxHeight,\n\t\t\t\t1\n\t\t\t);\n\t\t\tvar d = {\n\t\t\t\tw: Math.floor(Math.max(img.width/ratio, 1)),\n\t\t\t\th: Math.floor(Math.max(img.height/ratio, 1))\n\t\t\t}\n\t\t\tlog(\n\t\t\t\t'INFO: Original image size: ' + img.width.toString(10) + 'x' + img.height.toString(10)\n\t\t\t\t+ ', resized image size: ' + d.w + 'x' + d.h + '.'\n\t\t\t);\n\t\t\tif (!settings.forceResize && img.width === d.w && img.height === d.h) {\n\t\t\t\tlog('INFO: Image demension is the same, send the original file.');\n\t\t\t\tif (canResizeImageToBinaryString) {\n\t\t\t\t\thandleForm(\n\t\t\t\t\t\tsettings,\n\t\t\t\t\t\t'bin',\n\t\t\t\t\t\twindow.atob(dataURLtoBase64(dataurl)),\n\t\t\t\t\t\tinfo\n\t\t\t\t\t);\n\t\t\t\t} else if (settings.allowDataInBase64) {\n\t\t\t\t\thandleForm(\n\t\t\t\t\t\tsettings,\n\t\t\t\t\t\t'base64',\n\t\t\t\t\t\tdataURLtoBase64(dataurl),\n\t\t\t\t\t\tinfo\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tlog('ERROR: No available method to send the original file; allowDataInBase64 not set.');\n\t\t\t\t\tsettings.fileError.call(this, info, 'NO_BIN_SUPPORT_AND_BASE64_NOT_SET', 'No available method to extract file; allowDataInBase64 not set.');\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar canvas = document.createElement('canvas');\n\t\t\tcanvas.setAttribute('width', d.w);\n\t\t\tcanvas.setAttribute('height', d.h);\n\t\t\tcanvas.getContext('2d').drawImage(\n\t\t\t\timg,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\timg.width,\n\t\t\t\timg.height,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t\td.w,\n\t\t\t\td.h\n\t\t\t);\n\t\t\tif (!settings.imageType || settings.imageType === 'auto') {\n\t\t\t\tif (info.type === 'image/jpeg') settings.imageType = 'jpeg';\n\t\t\t\telse settings.imageType = 'png';\n\t\t\t}\n\n\t\t\tvar ninfo = {\n\t\t\t\ttype: 'image/' + settings.imageType,\n\t\t\t\tname: info.name.substr(0, info.name.indexOf('.')) + '.resized.' + settings.imageType\n\t\t\t};\n\n\t\t\tif (canResizeImageToFile && canSendFormData) {\n\t\t\t\t// Gecko 2 (Fx4) non-standard function\n\t\t\t\tvar nfile = canvas.mozGetAsFile(\n\t\t\t\t\tninfo.name,\n\t\t\t\t\t'image/' + settings.imageType\n\t\t\t\t);\n\t\t\t\tninfo.size = file.size || file.fileSize;\n\t\t\t\thandleForm(\n\t\t\t\t\tsettings,\n\t\t\t\t\t'file',\n\t\t\t\t\tnfile,\n\t\t\t\t\tninfo\n\t\t\t\t);\n\t\t\t} else if (canResizeImageToBinaryString && canSendBinaryString) {\n\t\t\t\t// Read the image as DataURL, convert it back to binary string.\n\t\t\t\tvar bin = window.atob(dataURLtoBase64(canvas.toDataURL('image/' + settings.imageType)));\n\t\t\t\tninfo.size = bin.length;\n\t\t\t\thandleForm(\n\t\t\t\t\tsettings,\n\t\t\t\t\t'bin',\n\t\t\t\t\tbin,\n\t\t\t\t\tninfo\n\t\t\t\t);\n\t\t\t} else if (settings.allowDataInBase64 && canResizeImageToBase64 && canSendImageInBase64) {\n\t\t\t\thandleForm(\n\t\t\t\t\tsettings,\n\t\t\t\t\t'base64',\n\t\t\t\t\tdataURLtoBase64(canvas.toDataURL('image/' + settings.imageType)),\n\t\t\t\t\tninfo\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlog('ERROR: No available method to extract image; allowDataInBase64 not set.');\n\t\t\t\tsettings.fileError.call(this, info, 'NO_BIN_SUPPORT_AND_BASE64_NOT_SET', 'No available method to extract file; allowDataInBase64 not set.');\n\t\t\t}\n\t\t}\n\t\timg.src = dataurl;\n\t}\n\t// Step 2: construct form data and send the file\n\t// paramaters: Ajax settings, File object, binary string of file || null, file info assoc array\n\tvar handleForm = function (settings, type, data, info) {\n\t\tif (canSendFormData && type === 'file') {\n\t\t\t// FormData API saves the day\n\t\t\tlog('INFO: Using FormData to construct form.');\n\t\t\tvar formdata = new FormData();\n\t\t\tformdata.append('Filedata', data);\n\t\t\t// Prevent jQuery form convert FormData object into string.\n\t\t\tsettings.processData = false;\n\t\t\t// Prevent jQuery from overwrite automatically generated xhr content-Type header\n\t\t\t// by unsetting the default contentType and inject data only right before xhr.send()\n\t\t\tsettings.contentType = null;\n\t\t\tsettings.__beforeSend = settings.beforeSend;\n\t\t\tsettings.beforeSend = function (xhr, s) {\n\t\t\t\ts.data = formdata;\n\t\t\t\tif (s.__beforeSend) return s.__beforeSend.call(this, xhr, s);\n\t\t\t}\n\t\t\t//settings.data = formdata;\n\t\t} else if (canSendBinaryString && type === 'bin') {\n\t\t\tlog('INFO: Concat our own multipart/form-data data string.');\n\n\t\t\t// A placeholder MIME type\n\t\t\tif (!info.type) info.type = 'application/octet-stream';\n\n\t\t\tif (/[^\\x20-\\x7E]/.test(info.name)) {\n\t\t\t\tlog('INFO: Filename contains non-ASCII code, do UTF8-binary string conversion.');\n\t\t\t\tinfo.name_bin = unescape(encodeURIComponent(info.name));\n\t\t\t}\n\n\t\t\t//filtered out non-ASCII chars in filenames\n\t\t\t// info.name = info.name.replace(/[^\\x20-\\x7E]/g, '_');\n\n\t\t\t// multipart/form-data boundary\n\t\t\tvar bd = 'xhrupload-' + parseInt(Math.random()*(2 << 16));\n\t\t\tsettings.contentType = 'multipart/form-data; boundary=' + bd;\n\t\t\tvar formdata = '--' + bd + '\\n' // RFC 1867 Format, simulate form file upload\n\t\t\t+ 'content-disposition: form-data; name=\"Filedata\";'\n\t\t\t+ ' filename=\"' + (info.name_bin || info.name) + '\"\\n'\n\t\t\t+ 'Content-Type: ' + info.type + '\\n\\n'\n\t\t\t+ data + '\\n\\n'\n\t\t\t+ '--' + bd + '--';\n\n\t\t\tif (window.XMLHttpRequest.prototype.sendAsBinary) {\n\t\t\t\t// Use xhr.sendAsBinary that takes binary string\n\t\t\t\tlog('INFO: Pass binary string to xhr.');\n\t\t\t\tsettings.data = formdata;\n\t\t\t} else {\n\t\t\t\t// make a blob\n\t\t\t\tlog('INFO: Convert binary string into Blob.');\n\t\t\t\tvar buf = new ArrayBuffer(formdata.length);\n\t\t\t\tvar view = new Uint8Array(buf);\n\t\t\t\t$.each(\n\t\t\t\t\tformdata,\n\t\t\t\t\tfunction (i, o) {\n\t\t\t\t\t\tview[i] = o.charCodeAt(0);\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tvar bb = new BlobBuilder();\n\t\t\t\tbb.append(buf);\n\t\t\t\tvar blob = bb.getBlob();\n\n\t\t\t\tsettings.processData = false;\n\t\t\t\tsettings.__beforeSend = settings.beforeSend;\n\t\t\t\tsettings.beforeSend = function (xhr, s) {\n\t\t\t\t\ts.data = blob;\n\t\t\t\t\tif (s.__beforeSend) return s.__beforeSend.call(this, xhr, s);\n\t\t\t\t};\n\t\t\t}\n\n\t\t} else if (settings.allowDataInBase64 && type === 'base64') {\n\t\t\tlog('INFO: Concat our own multipart/form-data data string; send the file in base64 because binary xhr is not supported.');\n\n\t\t\t// A placeholder MIME type\n\t\t\tif (!info.type) info.type = 'application/octet-stream';\n\n\t\t\t// multipart/form-data boundary\n\t\t\tvar bd = 'xhrupload-' + parseInt(Math.random()*(2 << 16));\n\t\t\tsettings.contentType = 'multipart/form-data; boundary=' + bd;\n\t\t\tsettings.data = '--' + bd + '\\n' // RFC 1867 Format, simulate form file upload\n\t\t\t+ 'content-disposition: form-data; name=\"Filedata\";'\n\t\t\t+ ' filename=\"' + encodeURIComponent(info.name) + '.base64\"\\n'\n\t\t\t+ 'Content-Transfer-Encoding: base64\\n' // Vaild MIME header, but won't work with PHP file upload handling.\n\t\t\t+ 'Content-Type: ' + info.type + '\\n\\n'\n\t\t\t+ data + '\\n\\n'\n\t\t\t+ '--' + bd + '--';\n\t\t} else {\n\t\t\tlog('ERROR: Data is not given in processable form.');\n\t\t\tsettings.fileError.call(this, info, 'INTERNAL_ERROR', 'Data is not given in processable form.');\n\t\t\treturn;\n\t\t}\n\t\txhrupload(settings);\n\t};\n\n\t// Step 3: start sending out file\n\tvar xhrupload = function (settings) {\n\t\tlog('INFO: Sending file.');\n\t\tif (typeof settings.data === 'string' && canSendBinaryString) {\n\t\t\tlog('INFO: Using xhr.sendAsBinary.');\n\t\t\tsettings.___beforeSend = settings.beforeSend;\n\t\t\tsettings.beforeSend = function (xhr, s) {\n\t\t\t\txhr.send = xhr.sendAsBinary;\n\t\t\t\tif (s.___beforeSend) return s.___beforeSend.call(this, xhr, s);\n\t\t\t}\n\t\t}\n\t\t$.ajax(settings);\n\t};\n\n\t$.fn.fileUpload = function(settings) {\n\t\tthis.each(function(i, el) {\n\t\t\tif ($(el).is('input[type=file]')) {\n\t\t\t\tlog('INFO: binding onchange event to a input[type=file].');\n\t\t\t\t$(el).bind(\n\t\t\t\t\t'change',\n\t\t\t\t\tfunction () {\n\t\t\t\t\t\tif (!this.files.length) {\n\t\t\t\t\t\t\tlog('ERROR: no file selected.');\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t} else if (this.files.length > 1) {\n\t\t\t\t\t\t\tlog('WARN: Multiple file upload not implemented yet, only first file will be uploaded.');\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandleFile($.extend({}, config, settings), this.files[0]);\n\n\t\t\t\t\t\tif (this.form.length === 1) {\n\t\t\t\t\t\t\tthis.form.reset();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlog('WARN: Unable to reset file selection, upload won\\'t be triggered again if user selects the same file.');\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif ($(el).is('form')) {\n\t\t\t\tlog('ERROR: <form> not implemented yet.');\n\t\t\t} else {\n\t\t\t\tlog('INFO: binding ondrop event.');\n\t\t\t\t$(el).bind(\n\t\t\t\t\t'dragover', // dragover behavior should be blocked for drop to invoke.\n\t\t\t\t\tfunction(ev) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t).bind(\n\t\t\t\t\t'drop',\n\t\t\t\t\tfunction (ev) {\n\t\t\t\t\t\tif (!ev.originalEvent.dataTransfer.files) {\n\t\t\t\t\t\t\tlog('ERROR: No FileList object present; user might had dropped text.');\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!ev.originalEvent.dataTransfer.files.length) {\n\t\t\t\t\t\t\tlog('ERROR: User had dropped a virual file (e.g. \"My Computer\")');\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!ev.originalEvent.dataTransfer.files.length > 1) {\n\t\t\t\t\t\t\tlog('WARN: Multiple file upload not implemented yet, only first file will be uploaded.');\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandleFile($.extend({}, config, settings), ev.originalEvent.dataTransfer.files[0]);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\treturn this;\n\t};\n\n\t$.fileUploadSupported = isSupported;\n\t$.imageUploadSupported = isImageSupported;\n\t$.fileUploadAsBase64Supported = isSupportedInBase64;\n\t$.imageUploadAsBase64Supported = isImageSupportedInBase64;\n\n})(jQuery);\n"
  },
  {
    "path": "app/javascript/libs/qrcode.js",
    "content": "/**\n * @fileoverview\n * - Using the 'QRCode for Javascript library'\n * - Fixed dataset of 'QRCode for Javascript library' for support full-spec.\n * - this library has no dependencies.\n *\n * @author davidshimjs\n * @see <a href=\"http://www.d-project.com/\" target=\"_blank\">http://www.d-project.com/</a>\n * @see <a href=\"http://jeromeetienne.github.com/jquery-qrcode/\" target=\"_blank\">http://jeromeetienne.github.com/jquery-qrcode/</a>\n */\nvar QRCode;\n\n(function () {\n\t//---------------------------------------------------------------------\n\t// QRCode for JavaScript\n\t//\n\t// Copyright (c) 2009 Kazuhiko Arase\n\t//\n\t// URL: http://www.d-project.com/\n\t//\n\t// Licensed under the MIT license:\n\t//   http://www.opensource.org/licenses/mit-license.php\n\t//\n\t// The word \"QR Code\" is registered trademark of\n\t// DENSO WAVE INCORPORATED\n\t//   http://www.denso-wave.com/qrcode/faqpatent-e.html\n\t//\n\t//---------------------------------------------------------------------\n\tfunction QR8bitByte(data) {\n\t\tthis.mode = QRMode.MODE_8BIT_BYTE;\n\t\tthis.data = data;\n\t\tthis.parsedData = [];\n\n\t\t// Added to support UTF-8 Characters\n\t\tfor (var i = 0, l = this.data.length; i < l; i++) {\n\t\t\tvar byteArray = [];\n\t\t\tvar code = this.data.charCodeAt(i);\n\n\t\t\tif (code > 0x10000) {\n\t\t\t\tbyteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);\n\t\t\t\tbyteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);\n\t\t\t\tbyteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);\n\t\t\t\tbyteArray[3] = 0x80 | (code & 0x3F);\n\t\t\t} else if (code > 0x800) {\n\t\t\t\tbyteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);\n\t\t\t\tbyteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);\n\t\t\t\tbyteArray[2] = 0x80 | (code & 0x3F);\n\t\t\t} else if (code > 0x80) {\n\t\t\t\tbyteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);\n\t\t\t\tbyteArray[1] = 0x80 | (code & 0x3F);\n\t\t\t} else {\n\t\t\t\tbyteArray[0] = code;\n\t\t\t}\n\n\t\t\tthis.parsedData.push(byteArray);\n\t\t}\n\n\t\tthis.parsedData = Array.prototype.concat.apply([], this.parsedData);\n\n\t\tif (this.parsedData.length != this.data.length) {\n\t\t\tthis.parsedData.unshift(191);\n\t\t\tthis.parsedData.unshift(187);\n\t\t\tthis.parsedData.unshift(239);\n\t\t}\n\t}\n\n\tQR8bitByte.prototype = {\n\t\tgetLength: function (buffer) {\n\t\t\treturn this.parsedData.length;\n\t\t},\n\t\twrite: function (buffer) {\n\t\t\tfor (var i = 0, l = this.parsedData.length; i < l; i++) {\n\t\t\t\tbuffer.put(this.parsedData[i], 8);\n\t\t\t}\n\t\t}\n\t};\n\n\tfunction QRCodeModel(typeNumber, errorCorrectLevel) {\n\t\tthis.typeNumber = typeNumber;\n\t\tthis.errorCorrectLevel = errorCorrectLevel;\n\t\tthis.modules = null;\n\t\tthis.moduleCount = 0;\n\t\tthis.dataCache = null;\n\t\tthis.dataList = [];\n\t}\n\n\tQRCodeModel.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);}\n\treturn 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;}}\n\tthis.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);}\n\tif(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}\n\tthis.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;}}\n\treturn 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();}}}\n\treturn qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}\n\tthis.modules[r][6]=(r%2==0);}\n\tfor(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}\n\tthis.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;}\n\tfor(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;}\n\tfor(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;}}\n\tfor(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;}}\n\tthis.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);}\n\tvar mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}\n\tthis.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}\n\trow+=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);}\n\tvar totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}\n\tif(buffer.getLengthInBits()>totalDataCount*8){throw new Error(\"code length overflow. (\"\n\t+buffer.getLengthInBits()\n\t+\">\"\n\t+totalDataCount*8\n\t+\")\");}\n\tif(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}\n\twhile(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}\n\twhile(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}\n\tbuffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}\n\tbuffer.put(QRCodeModel.PAD1,8);}\n\treturn 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];}\n\toffset+=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;}}\n\tvar totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}\n\tvar 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];}}}\n\tfor(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}\n\treturn 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)));}\n\treturn((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)));}\n\treturn(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}\n\treturn 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));}\n\treturn 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;}\n\tfor(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}\n\tif(r==0&&c==0){continue;}\n\tif(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}\n\tif(sameCount>5){lostPoint+=(3+sameCount-5);}}}\n\tfor(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;}}}\n\tfor(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;}}}\n\tfor(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;}}}\n\tvar darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}\n\tvar 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+\")\");}\n\treturn QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}\n\twhile(n>=256){n-=255;}\n\treturn 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;}\n\tfor(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];}\n\tfor(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}\n\tfunction QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+\"/\"+shift);}\n\tvar offset=0;while(offset<num.length&&num[offset]==0){offset++;}\n\tthis.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}\n\tQRPolynomial.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)));}}\n\treturn new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}\n\tvar 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);}\n\tfor(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}\n\treturn new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}\n\tQRRSBlock.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);}\n\tvar 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));}}\n\treturn 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;}\n\tQRBitBuffer.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);}\n\tif(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}\n\tthis.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]];\n\n\tfunction _isSupportCanvas() {\n\t\treturn typeof CanvasRenderingContext2D != \"undefined\";\n\t}\n\n\t// android 2.x doesn't support Data-URI spec\n\tfunction _getAndroid() {\n\t\tvar android = false;\n\t\tvar sAgent = navigator.userAgent;\n\n\t\tif (/android/i.test(sAgent)) { // android\n\t\t\tandroid = true;\n\t\t\tvar aMat = sAgent.toString().match(/android ([0-9]\\.[0-9])/i);\n\n\t\t\tif (aMat && aMat[1]) {\n\t\t\t\tandroid = parseFloat(aMat[1]);\n\t\t\t}\n\t\t}\n\n\t\treturn android;\n\t}\n\n\tvar svgDrawer = (function() {\n\n\t\tvar Drawing = function (el, htOption) {\n\t\t\tthis._el = el;\n\t\t\tthis._htOption = htOption;\n\t\t};\n\n\t\tDrawing.prototype.draw = function (oQRCode) {\n\t\t\tvar _htOption = this._htOption;\n\t\t\tvar _el = this._el;\n\t\t\tvar nCount = oQRCode.getModuleCount();\n\t\t\tvar nWidth = Math.floor(_htOption.width / nCount);\n\t\t\tvar nHeight = Math.floor(_htOption.height / nCount);\n\n\t\t\tthis.clear();\n\n\t\t\tfunction makeSVG(tag, attrs) {\n\t\t\t\tvar el = document.createElementNS('http://www.w3.org/2000/svg', tag);\n\t\t\t\tfor (var k in attrs)\n\t\t\t\t\tif (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);\n\t\t\t\treturn el;\n\t\t\t}\n\n\t\t\tvar svg = makeSVG(\"svg\" , {'viewBox': '0 0 ' + String(nCount) + \" \" + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});\n\t\t\tsvg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\", \"xmlns:xlink\", \"http://www.w3.org/1999/xlink\");\n\t\t\t_el.appendChild(svg);\n\n\t\t\tsvg.appendChild(makeSVG(\"rect\", {\"fill\": _htOption.colorLight, \"width\": \"100%\", \"height\": \"100%\"}));\n\t\t\tsvg.appendChild(makeSVG(\"rect\", {\"fill\": _htOption.colorDark, \"width\": \"1\", \"height\": \"1\", \"id\": \"template\"}));\n\n\t\t\tfor (var row = 0; row < nCount; row++) {\n\t\t\t\tfor (var col = 0; col < nCount; col++) {\n\t\t\t\t\tif (oQRCode.isDark(row, col)) {\n\t\t\t\t\t\tvar child = makeSVG(\"use\", {\"x\": String(col), \"y\": String(row)});\n\t\t\t\t\t\tchild.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", \"#template\")\n\t\t\t\t\t\tsvg.appendChild(child);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tDrawing.prototype.clear = function () {\n\t\t\twhile (this._el.hasChildNodes())\n\t\t\t\tthis._el.removeChild(this._el.lastChild);\n\t\t};\n\t\treturn Drawing;\n\t})();\n\n\tvar useSVG = document.documentElement.tagName.toLowerCase() === \"svg\";\n\n\t// Drawing in DOM by using Table tag\n\tvar Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {\n\t\tvar Drawing = function (el, htOption) {\n\t\t\tthis._el = el;\n\t\t\tthis._htOption = htOption;\n\t\t};\n\n\t\t/**\n\t\t * Draw the QRCode\n\t\t *\n\t\t * @param {QRCode} oQRCode\n\t\t */\n\t\tDrawing.prototype.draw = function (oQRCode) {\n            var _htOption = this._htOption;\n            var _el = this._el;\n\t\t\tvar nCount = oQRCode.getModuleCount();\n\t\t\tvar nWidth = Math.floor(_htOption.width / nCount);\n\t\t\tvar nHeight = Math.floor(_htOption.height / nCount);\n\t\t\tvar aHTML = ['<table style=\"border:0;border-collapse:collapse;\">'];\n\n\t\t\tfor (var row = 0; row < nCount; row++) {\n\t\t\t\taHTML.push('<tr>');\n\n\t\t\t\tfor (var col = 0; col < nCount; col++) {\n\t\t\t\t\taHTML.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>');\n\t\t\t\t}\n\n\t\t\t\taHTML.push('</tr>');\n\t\t\t}\n\n\t\t\taHTML.push('</table>');\n\t\t\t_el.innerHTML = aHTML.join('');\n\n\t\t\t// Fix the margin values as real size.\n\t\t\tvar elTable = _el.childNodes[0];\n\t\t\tvar nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;\n\t\t\tvar nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;\n\n\t\t\tif (nLeftMarginTable > 0 && nTopMarginTable > 0) {\n\t\t\t\telTable.style.margin = nTopMarginTable + \"px \" + nLeftMarginTable + \"px\";\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Clear the QRCode\n\t\t */\n\t\tDrawing.prototype.clear = function () {\n\t\t\tthis._el.innerHTML = '';\n\t\t};\n\n\t\treturn Drawing;\n\t})() : (function () { // Drawing in Canvas\n\t\tfunction _onMakeImage() {\n\t\t\tthis._elImage.src = this._elCanvas.toDataURL(\"image/png\");\n\t\t\tthis._elImage.style.display = \"block\";\n\t\t\tthis._elCanvas.style.display = \"none\";\n\t\t}\n\n\t\t// Android 2.1 bug workaround\n\t\t// http://code.google.com/p/android/issues/detail?id=5141\n\t\tif (this && this._android && this._android <= 2.1) {\n\t    \tvar factor = 1 / window.devicePixelRatio;\n\t        var drawImage = CanvasRenderingContext2D.prototype.drawImage;\n\t    \tCanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {\n\t    \t\tif ((\"nodeName\" in image) && /img/i.test(image.nodeName)) {\n\t\t        \tfor (var i = arguments.length - 1; i >= 1; i--) {\n\t\t            \targuments[i] = arguments[i] * factor;\n\t\t        \t}\n\t    \t\t} else if (typeof dw == \"undefined\") {\n\t    \t\t\targuments[1] *= factor;\n\t    \t\t\targuments[2] *= factor;\n\t    \t\t\targuments[3] *= factor;\n\t    \t\t\targuments[4] *= factor;\n\t    \t\t}\n\n\t        \tdrawImage.apply(this, arguments);\n\t    \t};\n\t\t}\n\n\t\t/**\n\t\t * Check whether the user's browser supports Data URI or not\n\t\t *\n\t\t * @private\n\t\t * @param {Function} fSuccess Occurs if it supports Data URI\n\t\t * @param {Function} fFail Occurs if it doesn't support Data URI\n\t\t */\n\t\tfunction _safeSetDataURI(fSuccess, fFail) {\n            var self = this;\n            self._fFail = fFail;\n            self._fSuccess = fSuccess;\n\n            // Check it just once\n            if (self._bSupportDataURI === null) {\n                var el = document.createElement(\"img\");\n                var fOnError = function() {\n                    self._bSupportDataURI = false;\n\n                    if (self._fFail) {\n                        self._fFail.call(self);\n                    }\n                };\n                var fOnSuccess = function() {\n                    self._bSupportDataURI = true;\n\n                    if (self._fSuccess) {\n                        self._fSuccess.call(self);\n                    }\n                };\n\n                el.onabort = fOnError;\n                el.onerror = fOnError;\n                el.onload = fOnSuccess;\n                el.src = \"data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==\"; // the Image contains 1px data.\n                return;\n            } else if (self._bSupportDataURI === true && self._fSuccess) {\n                self._fSuccess.call(self);\n            } else if (self._bSupportDataURI === false && self._fFail) {\n                self._fFail.call(self);\n            }\n\t\t};\n\n\t\t/**\n\t\t * Drawing QRCode by using canvas\n\t\t *\n\t\t * @constructor\n\t\t * @param {HTMLElement} el\n\t\t * @param {Object} htOption QRCode Options\n\t\t */\n\t\tvar Drawing = function (el, htOption) {\n    \t\tthis._bIsPainted = false;\n    \t\tthis._android = _getAndroid();\n\n\t\t\tthis._htOption = htOption;\n\t\t\tthis._elCanvas = document.createElement(\"canvas\");\n\t\t\tthis._elCanvas.width = htOption.width;\n\t\t\tthis._elCanvas.height = htOption.height;\n\t\t\tel.appendChild(this._elCanvas);\n\t\t\tthis._el = el;\n\t\t\tthis._oContext = this._elCanvas.getContext(\"2d\");\n\t\t\tthis._bIsPainted = false;\n\t\t\tthis._elImage = document.createElement(\"img\");\n\t\t\tthis._elImage.alt = \"Scan me!\";\n\t\t\tthis._elImage.style.display = \"none\";\n\t\t\tthis._el.appendChild(this._elImage);\n\t\t\tthis._bSupportDataURI = null;\n\t\t};\n\n\t\t/**\n\t\t * Draw the QRCode\n\t\t *\n\t\t * @param {QRCode} oQRCode\n\t\t */\n\t\tDrawing.prototype.draw = function (oQRCode) {\n            var _elImage = this._elImage;\n            var _oContext = this._oContext;\n            var _htOption = this._htOption;\n\n\t\t\tvar nCount = oQRCode.getModuleCount();\n\t\t\tvar nWidth = _htOption.width / nCount;\n\t\t\tvar nHeight = _htOption.height / nCount;\n\t\t\tvar nRoundedWidth = Math.round(nWidth);\n\t\t\tvar nRoundedHeight = Math.round(nHeight);\n\n\t\t\t_elImage.style.display = \"none\";\n\t\t\tthis.clear();\n\n\t\t\tfor (var row = 0; row < nCount; row++) {\n\t\t\t\tfor (var col = 0; col < nCount; col++) {\n\t\t\t\t\tvar bIsDark = oQRCode.isDark(row, col);\n\t\t\t\t\tvar nLeft = col * nWidth;\n\t\t\t\t\tvar nTop = row * nHeight;\n\t\t\t\t\t_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;\n\t\t\t\t\t_oContext.lineWidth = 1;\n\t\t\t\t\t_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;\n\t\t\t\t\t_oContext.fillRect(nLeft, nTop, nWidth, nHeight);\n\n\t\t\t\t\t// 안티 앨리어싱 방지 처리\n\t\t\t\t\t_oContext.strokeRect(\n\t\t\t\t\t\tMath.floor(nLeft) + 0.5,\n\t\t\t\t\t\tMath.floor(nTop) + 0.5,\n\t\t\t\t\t\tnRoundedWidth,\n\t\t\t\t\t\tnRoundedHeight\n\t\t\t\t\t);\n\n\t\t\t\t\t_oContext.strokeRect(\n\t\t\t\t\t\tMath.ceil(nLeft) - 0.5,\n\t\t\t\t\t\tMath.ceil(nTop) - 0.5,\n\t\t\t\t\t\tnRoundedWidth,\n\t\t\t\t\t\tnRoundedHeight\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis._bIsPainted = true;\n\t\t};\n\n\t\t/**\n\t\t * Make the image from Canvas if the browser supports Data URI.\n\t\t */\n\t\tDrawing.prototype.makeImage = function () {\n\t\t\tif (this._bIsPainted) {\n\t\t\t\t_safeSetDataURI.call(this, _onMakeImage);\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Return whether the QRCode is painted or not\n\t\t *\n\t\t * @return {Boolean}\n\t\t */\n\t\tDrawing.prototype.isPainted = function () {\n\t\t\treturn this._bIsPainted;\n\t\t};\n\n\t\t/**\n\t\t * Clear the QRCode\n\t\t */\n\t\tDrawing.prototype.clear = function () {\n\t\t\tthis._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);\n\t\t\tthis._bIsPainted = false;\n\t\t};\n\n\t\t/**\n\t\t * @private\n\t\t * @param {Number} nNumber\n\t\t */\n\t\tDrawing.prototype.round = function (nNumber) {\n\t\t\tif (!nNumber) {\n\t\t\t\treturn nNumber;\n\t\t\t}\n\n\t\t\treturn Math.floor(nNumber * 1000) / 1000;\n\t\t};\n\n\t\treturn Drawing;\n\t})();\n\n\t/**\n\t * Get the type by string length\n\t *\n\t * @private\n\t * @param {String} sText\n\t * @param {Number} nCorrectLevel\n\t * @return {Number} type\n\t */\n\tfunction _getTypeNumber(sText, nCorrectLevel) {\n\t\tvar nType = 1;\n\t\tvar length = _getUTF8Length(sText);\n\n\t\tfor (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {\n\t\t\tvar nLimit = 0;\n\n\t\t\tswitch (nCorrectLevel) {\n\t\t\t\tcase QRErrorCorrectLevel.L :\n\t\t\t\t\tnLimit = QRCodeLimitLength[i][0];\n\t\t\t\t\tbreak;\n\t\t\t\tcase QRErrorCorrectLevel.M :\n\t\t\t\t\tnLimit = QRCodeLimitLength[i][1];\n\t\t\t\t\tbreak;\n\t\t\t\tcase QRErrorCorrectLevel.Q :\n\t\t\t\t\tnLimit = QRCodeLimitLength[i][2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase QRErrorCorrectLevel.H :\n\t\t\t\t\tnLimit = QRCodeLimitLength[i][3];\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (length <= nLimit) {\n\t\t\t\tbreak;\n\t\t\t} else {\n\t\t\t\tnType++;\n\t\t\t}\n\t\t}\n\n\t\tif (nType > QRCodeLimitLength.length) {\n\t\t\tthrow new Error(\"Too long data\");\n\t\t}\n\n\t\treturn nType;\n\t}\n\n\tfunction _getUTF8Length(sText) {\n\t\tvar replacedText = encodeURI(sText).toString().replace(/\\%[0-9a-fA-F]{2}/g, 'a');\n\t\treturn replacedText.length + (replacedText.length != sText ? 3 : 0);\n\t}\n\n\t/**\n\t * @class QRCode\n\t * @constructor\n\t * @example\n\t * new QRCode(document.getElementById(\"test\"), \"http://jindo.dev.naver.com/collie\");\n\t *\n\t * @example\n\t * var oQRCode = new QRCode(\"test\", {\n\t *    text : \"http://naver.com\",\n\t *    width : 128,\n\t *    height : 128\n\t * });\n\t *\n\t * oQRCode.clear(); // Clear the QRCode.\n\t * oQRCode.makeCode(\"http://map.naver.com\"); // Re-create the QRCode.\n\t *\n\t * @param {HTMLElement|String} el target element or 'id' attribute of element.\n\t * @param {Object|String} vOption\n\t * @param {String} vOption.text QRCode link data\n\t * @param {Number} [vOption.width=256]\n\t * @param {Number} [vOption.height=256]\n\t * @param {String} [vOption.colorDark=\"#000000\"]\n\t * @param {String} [vOption.colorLight=\"#ffffff\"]\n\t * @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]\n\t */\n\tQRCode = function (el, vOption) {\n\t\tthis._htOption = {\n\t\t\twidth : 256,\n\t\t\theight : 256,\n\t\t\ttypeNumber : 4,\n\t\t\tcolorDark : \"#000000\",\n\t\t\tcolorLight : \"#ffffff\",\n\t\t\tcorrectLevel : QRErrorCorrectLevel.H\n\t\t};\n\n\t\tif (typeof vOption === 'string') {\n\t\t\tvOption\t= {\n\t\t\t\ttext : vOption\n\t\t\t};\n\t\t}\n\n\t\t// Overwrites options\n\t\tif (vOption) {\n\t\t\tfor (var i in vOption) {\n\t\t\t\tthis._htOption[i] = vOption[i];\n\t\t\t}\n\t\t}\n\n\t\tif (typeof el == \"string\") {\n\t\t\tel = document.getElementById(el);\n\t\t}\n\n\t\tif (this._htOption.useSVG) {\n\t\t\tDrawing = svgDrawer;\n\t\t}\n\n\t\tthis._android = _getAndroid();\n\t\tthis._el = el;\n\t\tthis._oQRCode = null;\n\t\tthis._oDrawing = new Drawing(this._el, this._htOption);\n\n\t\tif (this._htOption.text) {\n\t\t\tthis.makeCode(this._htOption.text);\n\t\t}\n\t};\n\n\t/**\n\t * Make the QRCode\n\t *\n\t * @param {String} sText link data\n\t */\n\tQRCode.prototype.makeCode = function (sText) {\n\t\tthis._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);\n\t\tthis._oQRCode.addData(sText);\n\t\tthis._oQRCode.make();\n\t\tthis._el.title = sText;\n\t\tthis._oDrawing.draw(this._oQRCode);\n\t\tthis.makeImage();\n\t};\n\n\t/**\n\t * Make the Image from Canvas element\n\t * - It occurs automatically\n\t * - Android below 3 doesn't support Data-URI spec.\n\t *\n\t * @private\n\t */\n\tQRCode.prototype.makeImage = function () {\n\t\tif (typeof this._oDrawing.makeImage == \"function\" && (!this._android || this._android >= 3)) {\n\t\t\tthis._oDrawing.makeImage();\n\t\t}\n\t};\n\n\t/**\n\t * Clear the QRCode\n\t */\n\tQRCode.prototype.clear = function () {\n\t\tthis._oDrawing.clear();\n\t};\n\n\t/**\n\t * @name QRCode.CorrectLevel\n\t */\n\tQRCode.CorrectLevel = QRErrorCorrectLevel;\n})();\n\nwindow.QRCode = QRCode;\n"
  },
  {
    "path": "app/jobs/application_job.rb",
    "content": "class ApplicationJob < ActiveJob::Base\n  # Automatically retry jobs that encountered a deadlock\n  # retry_on ActiveRecord::Deadlocked\n\n  # Most jobs are safe to ignore if the underlying records are no longer available\n  # discard_on ActiveJob::DeserializationError\nend\n"
  },
  {
    "path": "app/mailers/application_mailer.rb",
    "content": "class ApplicationMailer < ActionMailer::Base\n  default from: 'from@example.com'\n  layout 'mailer'\nend\n"
  },
  {
    "path": "app/models/administrator.rb",
    "content": "class Administrator < ApplicationRecord\n  validates :name, presence: true, uniqueness: true\n  has_secure_password\nend\n"
  },
  {
    "path": "app/models/application_record.rb",
    "content": "class ApplicationRecord < ActiveRecord::Base\n  self.abstract_class = true\nend\n"
  },
  {
    "path": "app/models/comment.rb",
    "content": "class Comment < ApplicationRecord\n  belongs_to :post\n\n  validates :name, presence: true\n  validates :email, presence: true, format: { with: /\\A([^@\\s]+)@((?:[-a-z0-9]+\\.)+[a-z]{2,})\\z/i }\n  validates :content, presence: true, length: { minimum: 4 }\n\n  def reply_emails\n    Comment.where(post_id: self.post_id).collect(&:email).uniq - [ self.email ] - Subscribe.unsubscribe_list - [ ENV['ADMIN_USER'] ]\n  end\nend\n"
  },
  {
    "path": "app/models/concerns/.keep",
    "content": ""
  },
  {
    "path": "app/models/label.rb",
    "content": "class Label < ApplicationRecord\n  has_and_belongs_to_many :posts\n  validates :name, presence: true\nend\n"
  },
  {
    "path": "app/models/like.rb",
    "content": "class Like < ApplicationRecord\n  belongs_to :post\nend\n"
  },
  {
    "path": "app/models/photo.rb",
    "content": "class Photo < ApplicationRecord\n  mount_uploader :image, PhotoUploader\nend\n"
  },
  {
    "path": "app/models/post.rb",
    "content": "require 'markdown'\nclass Post < ActiveRecord::Base\n  has_many :comments\n  has_and_belongs_to_many :labels\n\n  has_many :likes\n\n  validates :title, :presence=>true, :uniqueness=> true\n  validates :content, :presence=>true, :length => { :minimum=> 3 }\n\n  def content_html\n    self.class.render_html(self.content)\n  end\n\n  def self.render_html(content)\n    rd = CodeHTML.new\n    md = Redcarpet::Markdown.new(rd, autolink: true, fenced_code_blocks: true)\n    md.render(content)\n  end\n\n  def visited\n    self.visited_count += 1\n    self.save\n    self.visited_count\n  end\n\n  # truncate content for home page display\n  def sub_content\n    HTML_Truncator.truncate(content_html, 300, length_in_chars: true)\n  end\n\n  # truncate content for meta description display\n  def meta_content\n    html = HTML_Truncator.truncate(content_html, 100, :length_in_chars => true, ellipsis: '')\n    # Easily get text for Nokogiri\n    html = '<div>' + html + '</div>'\n    Nokogiri.parse(html).text()\n  end\n\n  def labels_content( need_blank=false )\n    content = self.labels.collect { |label| label.name }.join(\", \")\n    content = I18n.t('none') if content.blank? and !need_blank\n    content\n  end\n\n  def liked_count\n    self.likes.size\n  end\n\n  def liked_by?(like_id)\n    !! self.likes.where(id: like_id).first\n  end\nend\n"
  },
  {
    "path": "app/uploaders/photo_uploader.rb",
    "content": "class PhotoUploader < CarrierWave::Uploader::Base\n  include CarrierWave::MiniMagick\n\n  storage :file\n\n  def store_dir\n    \"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}\"\n  end\n\n  process :resize_to_limit => [1200,nil]\n\n  version :medium do\n    process :resize_to_limit => [640,nil]\n  end\n\n  version :small do\n    process :resize_to_limit => [400,nil]\n  end\n\n  def extension_white_list\n    %w(jpg jpeg gif png)\n  end\n\nend\n"
  },
  {
    "path": "app/views/admin/accounts/edit.html.slim",
    "content": "- content_for :title do\n  | Account Setting\n\n.card.card-primary\n  .card-header.with-border\n    h3.card-title\n      | Edit Account\n  = simple_form_for current_admin, url: admin_account_path do |f|\n    .card-body\n      = f.input :name, label: 'Administrator Name'\n      = f.input :current_password, label: 'Current Password', required: true\n      = f.input :password, label: 'New Password( Blank if not modify )'\n      = f.input :password_confirmation, label: 'Confirm New Password'\n    .card-footer\n      = f.submit 'Update Account', class: 'btn btn-primary'\n"
  },
  {
    "path": "app/views/admin/all_comments/index.html.slim",
    "content": ".card\n  .card-header\n    .float-sm-left\n      h3.card-title 评论管理\n    .float-sm-right\n  .card-body\n    table.table.table-hover\n      thead\n        tr\n          th 内容\n          th 所属文章\n          th 时间\n          th #{t('admin.posts_head.operation')}\n      tbody\n        - @comments.each do |comment|\n          tr\n            td #{comment.content}\n            td #{comment.post.title}\n            td #{format_time(comment.created_at)}\n            td\n              = link_to t('destroy'), admin_all_comment_path(comment), method: 'DELETE', 'data-confirm' => '确认删除?'\n  .card-footer\n    .float-sm-left\n      | 总计: #{@comments.total_count}\n    .float-sm-right\n      = paginate @comments\n"
  },
  {
    "path": "app/views/admin/comments/index.html.slim",
    "content": ".card\n  .card-header\n    .float-sm-left\n      h3.card-title\n        ' #{t('admin.comments')}\n        | / #{@post.title}\n  .card-body\n    table.table.table-hover\n      thead\n        tr\n          th #{t('admin.comments_head.name')}\n          th #{t('admin.comments_head.email')}\n          th #{t('admin.comments_head.content')}\n          th #{t('admin.comments_head.created_at')}\n          th #{t('admin.comments_head.operation')}\n      tbody\n        - @comments.each do |comment|\n          tr\n            td #{comment.name}\n            td\n              = mail_to comment.email\n            td\n              = simple_format(comment.content)\n            td\n              = format_time(comment.created_at)\n\n            td\n              = link_to t('admin.comments_head.reply'), blog_path(@post, anchor: 'new_comment'), target: '_blank', class: 'edit-post-link'\n              '\n              = link_to t('admin.comments_head.destroy'), admin_post_comment_path(@post, comment), method: 'DELETE', 'data-confirm'=> '确认删除?'\n"
  },
  {
    "path": "app/views/admin/dashboard/index.html.slim",
    "content": ".container-fluid\n  .row\n    .col-lg-6\n      .card.card-primary.card-outline\n        .card-body\n          h5.card-title 博客数量: #{@posts_count}\n          p.card-text\n    .col-lg-6\n      .card.card-primary.card-outline\n        .card-body\n          h5.card-title 评论数量: #{@comments_count}\n          p.card-text\n"
  },
  {
    "path": "app/views/admin/labels/_form.html.slim",
    "content": ".row\n  .col-sm-6\n    = simple_form_for([:admin, @label], html: {novalidate: '' }) do |f|\n      = f.input :name\n      = f.button :submit\n"
  },
  {
    "path": "app/views/admin/labels/edit.html.slim",
    "content": ".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",
    "content": ".card\n  .card-header\n    .float-sm-left\n      h3.card-title 标签管理\n    .float-sm-right\n      = link_to '创建标签', new_admin_label_path\n  .card-body\n    table.table.table-hover\n      thead\n        tr\n          th 标签名\n          th 被引用次数\n          th 创建时间\n          th 操作\n      tbody\n        - @labels.each do |label|\n          tr\n            td = label.name\n            td = label.posts.count\n            td = format_time(label.created_at)\n            td\n              = link_to '编辑', edit_admin_label_path(label)\n              | &nbsp\n              = link_to '删除', admin_label_path(label), method: 'DELETE', 'data-confirm' => '确认删除?'\n  .card-footer\n    .float-sm-left\n      | 总计: #{@labels.total_count}\n    .float-sm-right\n      = paginate @labels\n"
  },
  {
    "path": "app/views/admin/labels/new.html.slim",
    "content": ".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",
    "content": ".row data-controller='admin-label'\n  .col-sm-8\n    = simple_form_for([:admin, @post], html: {novalidate: '' }) do |f|\n      = f.input :title, label: t('admin.posts_attributes.title')\n      = f.association :labels, input_html: { data: { 'admin-label-target': 'label', 'labels-data': Label.all.map(&:name) } }, label: t('admin.posts_attributes.labels')\n\n      / tabs and upload file field\n      ul.nav.nav-tabs#tabs\n        li.nav-item\n          a.nav-link.active data-toggle=\"tab\" href=\"#content\" #{t('admin.posts_attributes.content')}\n        li.nav-item\n          a.nav-link#preview-link data-toggle=\"tab\" href=\"#preview\" #{t('admin.posts_attributes.preview')}\n      = link_to t('admin.posts_attributes.upload_photo'), \"#\", id: 'upload_photo'\n      input type=\"file\" style=\"display: none;\"\n\n      .tab-content\n        .tab-pane.fade.show.active#content\n          = f.input :content, :as=> :text, :label => false, input_html: { id: 'content-input' }\n\n        .tab-pane.fade.markdown#preview\n        = f.button :submit\n"
  },
  {
    "path": "app/views/admin/posts/edit.html.slim",
    "content": ".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",
    "content": ".card\n  .card-header\n    .float-sm-left\n      h3.card-title #{t('admin.posts')}\n    .float-sm-right\n      = link_to '创建博客', new_admin_post_path\n  .card-body\n    table.table.table-hover\n      thead\n        tr\n          th #{t('admin.posts_head.title')}\n          th #{t('admin.posts_head.summary')}\n          th #{t('admin.posts_head.operation')}\n      tbody\n        - @posts.each do |post|\n          tr\n            td = link_to post.title, blog_path(post)\n            td\n              i.fa.fa-calendar\n              span #{format_time(post.created_at)}\n              i.fa.fa-list\n              span #{ post.labels_content }\n              i.fa.fa-eye\n              span #{ post.visited_count }\n              i.fa.fa-heart\n              span #{ post.liked_count }\n            td\n              = link_to t('comment'), admin_post_comments_path(post.id), class: 'edit-post-link'\n              | &nbsp\n              = link_to t('edit'), edit_admin_post_path(post), class: 'edit-post-link'\n              | &nbsp\n              = link_to t('destroy'), admin_post_path(post), method: 'DELETE', 'data-confirm' => '确认删除?'\n  .card-footer\n    .float-sm-left\n      | 总计: #{@posts.total_count}\n    .float-sm-right\n      = paginate @posts\n"
  },
  {
    "path": "app/views/admin/posts/new.html.slim",
    "content": ".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",
    "content": "- content_for :title, 'Log in'\n\nmain\n  section.content-messages\n    = render 'shared/admin/flash_messages'\n  section.content\n    .login-box\n      .login-logo\n        span\n          | Dashboard\n      .card\n        .card-body.login-card-body\n          p.login-box-msg Log in\n          = form_tag admin_login_path do\n            .input-group.mb-3\n              = text_field_tag :name, nil, placeholder: 'Username', class: 'form-control'\n              .input-group-append.input-group-text\n                span.fas.fa-envelope\n            .input-group.mb-3\n              = password_field_tag :password, nil, placeholder: 'Password', class: 'form-control'\n              .input-group-append.input-group-text\n                span.fas.fa-lock\n            .row\n              .col-4\n                = submit_tag 'Log in', class: 'btn btn-primary btn-block btn-flat'\n"
  },
  {
    "path": "app/views/archives/index.html.slim",
    "content": "- content_for(:title) do\n  | #{t('title.timeline')}\n.container\n  .row.justify-content-center\n    .col-sm-12.col-lg-9\n      ul.archives-field\n        .search-wrapper\n          = form_with url: archives_path, method: 'GET' do |f|\n            .form-group\n              = f.search_field :q, value: @q, placeholder: t('archive.search'), class: 'form-control'\n        - @posts.each do |post|\n          li\n            = link_to blog_path(post), class: 'blog-title' do\n              == search_highlight(post.title, @q)\n            p.tags-field\n              i.fa.fa-calendar\n              span\n                = format_date(post.created_at)\n              i.fa.fa-eye\n              span\n                = post.labels_content\n              i.fa.fa-torah\n              span\n                = post.visited_count\n              i.fa.fa-heart\n              span\n                = post.liked_count\n        - if @q.present?\n          .search-result-wrapper\n            p.text-muted 共 #{@q_size || 0} 条结果\n      = paginate @posts, q: @q\n"
  },
  {
    "path": "app/views/blogs/_comment.html.slim",
    "content": "turbo-frame#comment-frame\n  .container\n    .row.justify-content-center\n      .col-12.col-lg-9\n        - comment = @comment || Comment.new\n        = simple_form_for comment, url: blog_comments_path(post), remote: true do |f|\n          = f.text_area :content, placeholder: t('comment_placeholder.content')\n          .row\n            .col-12.col-lg-6\n              = f.text_field :name, value: cookies[:name], placeholder: t('comment_placeholder.name')\n          .row\n            .col-12.col-lg-6\n              = f.text_field :email, value: cookies[:email], placeholder: t('comment_placeholder.email')\n          button.comment-submit type='submit' data-disable-with=t('comment_placeholder.submitting') #{t('comment_placeholder.submit')}\n          - if info = flash.now[:notice]\n            #alert-container.alert.alert-success\n              span.text #{info}\n              // ai fix\n              button class=\"close\" type='button' data-dismiss='alert'\n                span &times;\n          - elsif comment.errors.any?\n            #alert-container.alert.alert-warning\n              span.text #{comment.errors[:content].first}\n              button class=\"close\" type='button' data-dismiss='alert'\n                span &times;\n        = render partial: 'comments/comment_content', locals: { comments: comments }\n"
  },
  {
    "path": "app/views/blogs/_post.html.slim",
    "content": "= render partial: 'post_head', locals: { post: post }\n\n.content.markdown\n  == post.content_html\n\np.ptag.published-at\n  | #{t('announce_at')}\n  span #{format_date(post.created_at)}\n\n= render 'common/copyright'\nhr.blog-over\np data-controller='like'\n  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'\n    span.count #{@likes_count}\n    span Like\n  .qrcode-controller data-controller='qrcode'\n    .qrcode\n      a#qrcode-link href='#' data-action='click->qrcode#greet'\n        i.fi-link\n        | #{t('qr_code')}\n    .social-share data-qrcode-target='wrapper'\n      .qrcode-wrapper\n        = render partial: \"qrcode\", locals: { str: blog_url(post) }\n"
  },
  {
    "path": "app/views/blogs/_post_head.html.slim",
    "content": "/ 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",
    "content": ".qrcode-image\n  #image-tag data-url=str\n  p #{t('qrcodetips')}\n"
  },
  {
    "path": "app/views/blogs/edit.html.slim",
    "content": "h1 Blogs#edit\np Find me in app/views/blogs/edit.html.slim\n"
  },
  {
    "path": "app/views/blogs/show.html.slim",
    "content": "- content_for(:meta) do\n  meta name=\"description\" content=@post.meta_content\n  meta name=\"keywords\" content=@post.labels_content\n\n- content_for(:title) do\n  | #{@post.title}\n/ data-url=refresh_blog_comments_path(@post) data-post_id=@post.id\n.container.blog-wrapper\n  .row.justify-content-center\n    .col-xs-12.col-lg-9\n      = render partial: \"post\", :locals=> { post: @post }\n.comment-field\n  = render partial: 'comment', locals: { comments: @comments, post: @post }\n  p\n  .container\n    .row.justify-content-center\n      .col-xs-12.col-lg-9\n        - if @prev\n          = link_to blog_path(@prev), class: 'prev' do\n            i.fa.fa-arrow-left\n            | 上一篇\n        - if @next\n          = link_to blog_path(@next), class: 'next' do\n            | 下一篇\n            i.fa.fa-arrow-right\n"
  },
  {
    "path": "app/views/comments/_comment_content.html.slim",
    "content": ".comment-diag\n  - comments.each do |comment|\n    .comment-wrapper\n      p.name\n        | #{comment.name}\n        | #{\"  • \"}\n        span.created-at\n          | #{format_time(comment.created_at) }\n      = render partial: 'comments/comment_pre', locals: { comment: comment }\n"
  },
  {
    "path": "app/views/comments/_comment_pre.html.slim",
    "content": ".comment-content\n  = simple_format(comment.content)\n"
  },
  {
    "path": "app/views/comments/create.html.slim",
    "content": "= render partial: 'blogs/comment', locals: { post: @post, comments: @comments }\n"
  },
  {
    "path": "app/views/common/_copyright.en.html.slim",
    "content": "p.copyright.published-at.ptag\n  | © Creative Commons - ShareAlike - NonCommercial - Attribution\n"
  },
  {
    "path": "app/views/common/_copyright.html.slim",
    "content": "p.copyright.published-at.ptag\n  | © 自由转载 - 非商用 - 非衍生 - 保持署名\n"
  },
  {
    "path": "app/views/common/_no_blog_here.en.html.slim",
    "content": "h2.blog-title #{t('home.no_blog_here')}\np\n  | No post here, please visit\n  = link_to ' Manage Post ', new_admin_post_path\n  | to create the first post.\n"
  },
  {
    "path": "app/views/common/_no_blog_here.html.slim",
    "content": "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",
    "content": "/* adjust stylesheet: .self-introduce-index */\nh4 WELCOME\np I'm Li Yafei, WinDy is my English name.\n\nh4 ABOUT\nul.aboutme-index\n  li\n    span Industry:\n    span Web Development, Startups, Life\n  li\n    span Location:\n    span Nan Shan District, ShenZhen, China\n  li\n    span More:\n    span\n      =  link_to 'About Me', about_path\n"
  },
  {
    "path": "app/views/common/_welcome.html.slim",
    "content": "/* 样式调整请找 stylesheet: .self-introduce-index */\n.box\n  h4 欢迎\n  p 我是技术达人李亚飞\n.box\n  h4 关于我\n  ul.aboutme-index\n    li\n      span 领域:\n      span 技术, 创业, 生活\n    li\n      span 位置:\n      span 中国 - 深圳 - 南山\n    li\n      span 更多:\n      span\n        =  link_to '关于我', about_path\n"
  },
  {
    "path": "app/views/common/_welcome_new_year.html.slim",
    "content": "- if (1.days.from_now).strftime('%-m-%-d') =~ /^1-[123]$/\n  .new-year.alert.alert-success.alert-dismissible.fade.show role='alert'\n    strong 🎉 我的朋友，祝你元旦快乐！\n    button.close type='button' data-dismiss='alert' aria-label='Close'\n      span aria-hidden='true' &times;\n"
  },
  {
    "path": "app/views/home/_post_head.html.slim",
    "content": "/ require: locals: { post : post }\nh2.blog-title #{post.title}\np.ptag\n  span\n    i.fi-pricetag-multiple\n  span #{post.labels_content}\n"
  },
  {
    "path": "app/views/home/about.html.slim",
    "content": "- content_for(:title) do\n  | #{t('title.about')}\n- content_for(:main) do\n  nav#about-top-bar class=\"navbar navbar-expand-lg navbar-dark bg-dark my-navbar fixed-top\"\n    a.navbar-brand href=root_path 回到博客\n    button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#navbar-collapse\"\n      span class=\"navbar-toggler-icon\"\n    div class=\"collapse navbar-collapse\" id=\"navbar-collapse\"\n      ul.navbar-nav.ml-auto\n        li class=\"nav-item\"\n          a.nav-link href='#about' 关于\n        li class=\"nav-item\"\n          a.nav-link href='#skill' 技能\n        li class=\"nav-item\"\n          a.nav-link href='#work' 作品\n        li class=\"nav-item\"\n          a.nav-link href='#contact' 联系\n  #intro\n  header.intro.container-fluid\n    .row.intro-heading\n      .col-12\n        h1.heading 李亚飞\n        .sub-heading\n          p 懒，驱动人类进步的关键力量。\n        a.circle#about-anchor-link href='#about'\n          i.fa.fa-angle-double-down\n  section#about.container-fluid\n    .row.justify-content-center\n      .col-12.col-lg-8.col-xl-6\n        h1.title 关于我\n        p 我是李亚飞，坐标深圳，连续创业者，技术达人，有众多开源项目和多场技术分享，创立过 3 家以技术产品驱动的创业公司，现 ShowMeBug 产品创始人&至简天成科技CEO，前单麦科技联合创始人&CTO。\n        .wrapper\n          .time 2024.04-至今\n          p Clacky CEO\n          ul\n            li 全球首创：L3 Agentic AI CDE\n            li 公测地址：clacky.ai\n        .wrapper\n          .time 2019.03 ~ 2024.04\n          p ShowMeBug创始人，至简天成科技CEO\n          ul\n            li 中国数字化面试开拓者\n            li 以全栈产研为核心的团队\n            li 近百万用户，6000家企业客户\n            li 开箱即用地址：showmebug.com\n        .wrapper\n          .time 2016.04 ~ 2018.12\n          p 深圳百分之八十网络技术有限公司联合创始人 & CTO，旗下主要产品单麦小程序平台是帮助商家一键制作小程序的SAAS服务平台，我与另一位联合创始人一道创立并运营了整个公司与产品，我的主要职责包括共同决策产品方向，负责研发团队日常管理，参与市场部销售计划制定与支持工作。\n          ul\n            li 组建以敏捷开发，全栈工程师为核心的高效率研发团队\n            li 带领团队连续 52 次版本迭代，每周发布新版本\n            li 单麦小程序平台客户体验满意度90%以上，上线小程序几千家，服务用户15万+\n            li 单麦小程序平台成为微信小程序生态 TOP10 获奖者\n            li 2018 年 12 月并购退出\n        .wrapper\n          .time 2015\n          p SmartX 早期员工，SmartX 是位于中国的全球领先的超融合存储方案供应商，利用软件技术方案帮助企业大规模降低存储硬件的成本。作为早期创始员工，担任前端架构师角色，顺利保证 SmartX OS 的快速交付。\n        .wrapper\n          .time 2014\n          p Cywin.cn 联合创始人 & 技术负责人，Cywin.cn 是一家股权众筹平台，对标美国的 Angelist.co，帮助创业公司完成股权融资的交易平台。作为项目的发起人之一，负责产品的研发工作。\n        .wrapper\n          .time 2009 ~ 2014\n          p 深信服（2018年上市）是一家著名的网络安全公司。加入时研发规模600人，作为当时研发部成长最快的工程师之一，担任首界自动化产品线的主管兼技术负责人（现研发效能部门），带领技术团队进行研发质量和内部工具链研发方面的工作。\n\n  section#skill.container-fluid\n    .row.justify-content-center\n      .col-12.col-lg-8.col-xl-6\n        .skills\n          h1.title 开发技能栈\n          ul\n            li Ruby on Rails ( 精通 )\n            li Linux / OSX ( 非常熟悉 )\n            li Git / Svn ( 非常熟悉 )\n            li AngularJS / React / VueJS / ES6 / Jquery ( 非常熟悉 )\n            li Bootstrap / Foundation 6 ( 非常熟悉 )\n            li HTML5 ( 熟悉 )\n            li CSS3 ( 熟悉 )\n            li Agile Development\n            li Testing Automation\n            li Deploying Automation\n            li PostgreSQL / Mysql / Mongodb\n  section#work.container-fluid\n    .row.justify-content-center\n      .col-12.col-lg-8.col-xl-6\n        h1.title 个人主要作品\n        ul.works\n          li\n            span.time 2015.3 - 2016.x\n            .project\n              span.name Lina\n              span.brief 一个专注于 API 接口编写的框架\n              span.link\n                a href='https://github.com/windy/lina'  target='_blank' https://github.com/windy/lina\n            ul.project-description\n              li 自动生成文档\n              li 零学习成本, 集成 Rails 的 API 开发最佳实践\n              li 自动校验参数, 快速构建安全可靠的 API\n              li\n                ' Github 地址:\n                a href='https://github.com/windy/lina' https://github.com/windy/lina\n          li\n            span.time 2014.12 - 2015.3\n            .project\n              span.name 青角落\n              span.brief 有趣的互联网人, 知识\n              span.link\n                a href='http://jiaoluo.yafeilee.me'  target='_blank' http://jiaoluo.yafeilee.me\n            ul.project-description\n              li 朋友的创业项目, 独立开发\n              li 现代的功能: 微信登录, 支付系统, 视频托管, 用户社区.\n              li Ruby on Rails 架构, 使用 Turoblinks, RJS 进行前端交互.\n              li 七牛云存储集成, 微信集成, 支付宝集成.\n          li\n            span.time 2014.3 - 2014.10\n            .project\n              span.name Cywin\n              span.brief 一个股权众筹的商业平台\n              span.link\n                a href='https://github.com/windy/cywin'  target='_blank' https://github.com/windy/cywin\n            ul.project-description\n              li 全栈独立负责整个项目的后端, 前端, 架构在 Ruby on Rails, AngularJS, Foundation 5.\n              li 花费周期 6 个月, 上线.\n              li\n                ' 代码在 2015 年 3 月已经\n                a href='https://github.com/windy/cywin' target='_blank' 开源\n                | .\n          li\n            span.time 2014.2 - 2014.3\n            .project\n              span.name WBlog\n              span.brief Rails 社区正缺了一个独立博客建站系统\n              span.link\n                a href='https://github.com/windy/wblog' target='_blank' https://github.com/windy/wblog\n            ul.project-description\n              li Ruby on Rails 开源博客系统, 帮助更多的朋友构建高定制性的博客.\n              li 具备博客管理, 点赞, 评论, 二维码, 自适应等现代网站所有特性.\n          li\n            span.time 2011.x - 2013.10\n            .project\n              span.name ATM/ATT\n              span.brief 测试业界领先的关键字驱动的自动化测试平台\n              span.link 深信服内部产品\n            ul.project-description\n              li 作为 Leader 带领团队架构并开发.\n              li XMLRPC 作为数据交换, 平台具备任务调度, Agent 服务, 用例编写, 权限管理, Svn集成, 自动布署.\n              li 关键字驱动方案是测试业内领先的技术框架.\n              li 几乎所有服务使用 100% Ruby 代码完成.\n  section#contact\n    .grid-x\n      .small-12.large-8.large-offset-2.cell\n        h1.title 联系我\n        p 保持放松, 请随时邮件与我联系\n        p.mail_to\n          = mail_to 'lyfi2003@gmail.com'\n        ul.contact-ul\n          li\n            a href='https://github.com/windy' target='_blank'\n              i.fab.fa-github\n              | Github\n          li\n            a href='http://ruby-china.org/lyfi2003' target='_blank'\n              i.fa.fa-heart\n              | RubyChina\n          li\n            a href='http://www.douban.com/people/41759170/' target='_blank'\n              i.douban\n                | 豆\n              | Douban\n        p.modified-at\n          | 本页更新于 2025.03.01\n  .footer\n    .grid-x\n      .small-12.cell\n        div Copyright © 2012 - 2025 yafeilee.com"
  },
  {
    "path": "app/views/home/index.html.slim",
    "content": "- if ENV['INTRODUCE'].present?\n  - content_for(:meta) do\n    meta name=\"description\" content=\"#{ENV['INTRODUCE'].dup.force_encoding('UTF-8')}\"\n- content_for(:title) do\n  | #{t('title.home')}\n.container\n  .row\n    .col-sm-12.col-lg-8\n      - unless @newest\n        = render 'common/no_blog_here'\n      - else\n        = render partial: 'post_head', locals: { post: @newest }\n        .content.markdown\n          == @newest.sub_content\n          = link_to t('home.read'), blog_path(@newest), class: 'read-more'\n          p.published-at #{t('home.created_at')} #{format_date(@newest.created_at)}\n\n        - if @recent.present?\n          h4.recent-title #{t('home.recent')}\n          ul.recent-content\n            - @recent.each do |re|\n              li = link_to re.title, blog_path(re)\n\n    .col-lg-4.self-introduce\n      / Adjust it in common/welcome\n      = render 'common/welcome'\n      h4 #{t('subscribes.title')}\n      .row\n        .col-12.col-md-6.col-lg-12\n          = image_tag 'wechat_qrcode.jpg', class: 'wechat_qrcode'\n"
  },
  {
    "path": "app/views/kaminari/_first_page.html.slim",
    "content": "li.page-item\n  = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link'\n"
  },
  {
    "path": "app/views/kaminari/_gap.html.slim",
    "content": "li.page-item.disabled\n  = link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link'\n"
  },
  {
    "path": "app/views/kaminari/_last_page.html.slim",
    "content": "li.page-item\n  = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, remote: remote, class: 'page-link'\n"
  },
  {
    "path": "app/views/kaminari/_next_page.html.slim",
    "content": "li.page-item\n  = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote, class: 'page-link'\n"
  },
  {
    "path": "app/views/kaminari/_page.html.slim",
    "content": "- if page.current?\n  li.page-item.active\n    = content_tag :a, page, data: { remote: remote }, rel: page.rel, class: 'page-link'\n- else\n  li.page-item\n    = link_to page, url, remote: remote, rel: page.rel, class: 'page-link'\n"
  },
  {
    "path": "app/views/kaminari/_paginator.html.slim",
    "content": "= paginator.render do\n  nav\n    ul.pagination\n      == first_page_tag unless current_page.first?\n      == prev_page_tag unless current_page.first?\n      - each_page do |page|\n        - if page.left_outer? || page.right_outer? || page.inside_window?\n          == page_tag page\n        - elsif !page.was_truncated?\n          == gap_tag\n      == next_page_tag unless current_page.last?\n      == last_page_tag unless current_page.last?\n"
  },
  {
    "path": "app/views/kaminari/_prev_page.html.slim",
    "content": "li.page-item\n  = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote, class: 'page-link'\n"
  },
  {
    "path": "app/views/layouts/_footer.html.slim",
    "content": ".container\n  .row\n    .col-sm-12\n      .footer\n        div\n          span.link = ENV['SITE_ADDRESS']\n          span.time = ENV['SITE_YEAR']\n        .license\n          | Designed by\n          span\n            = link_to 'WinDy', 'http://yafeilee.com/about', target: '_blank'\n        .license\n          | Built with\n          span\n            = link_to 'wblog', 'https://github.com/windy/wblog', target: '_blank'\n        - if ENV['SITE_BEIAN'].present?\n          .license\n            = link_to ENV['SITE_BEIAN'], 'http://beian.miit.gov.cn/', target: '_blank'\n"
  },
  {
    "path": "app/views/layouts/admin.html.slim",
    "content": "doctype html\nhtml\n  head\n    meta charset='utf-8'\n    meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\"\n    meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\"\n    meta name=\"renderer\" content=\"webkit\"\n    meta http-equiv=\"cleartype\" content=\"on\"\n    meta name=\"HandheldFriendly\" content=\"True\"\n    meta name=\"MobileOptimized\" content=\"320\"\n    meta name=\"turbo-cache-control\" content=\"no-cache\"\n    - if content_for?(:title)\n      title = format(\"%s - Dashboard\", yield(:title))\n    - else\n      title Dashboard\n    = csrf_meta_tags\n    = action_cable_meta_tag\n    = content_for?(:head) ? yield(:head) : ''\n    = stylesheet_link_tag 'admin', media: 'all', 'data-turbo-track': 'reload'\n    / = javascript_include_tag 'admin', 'data-turbo-track': 'reload'\n    = javascript_include_tag 'admin', 'data-turbo-track': Rails.env.production? ? \"reload\" : \"\", type: \"module\"\n    = favicon_link_tag asset_path(\"favicon.png\")\n  body.layout-fixed.sidebar-expand-lg.bg-body-tertiary.app-loaded.sidebar-open.admin-page class=body_class\n    - if @full_render\n      = yield\n    - else\n      .app-wrapper\n        = render 'shared/admin/header'\n        = render 'shared/admin/sidebar'\n        main.app-main\n          .content-messages\n            = render 'shared/admin/flash_messages'\n          .app-content-header\n          .app-content\n            = yield\n        / aside.control-sidebar.control-sidebar-dark\n          / /! Control sidebar content goes here\n          / .p-3\n            / h5 Title\n            / p Sidebar content\n        / /! /.control-sidebar\n        / /! Main Footer\n        .app-footer\n          /! To the right\n          .float-end.d-none.d-sm-inline\n            | Anything you want\n          /! Default to the left\n          strong\n            | Copyright &copy; 2019 - All rights reserved.\n          | Theme by AdminLTE.io\n"
  },
  {
    "path": "app/views/layouts/application.html.slim",
    "content": "doctype html\nhtml\n  head\n    meta charset='utf-8'\n    meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\"\n    meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\"\n    meta name=\"renderer\" content=\"webkit\"\n    meta http-equiv=\"cleartype\" content=\"on\"\n    meta name=\"HandheldFriendly\" content=\"True\"\n    meta name=\"MobileOptimized\" content=\"320\"\n    meta name=\"turbo-cache-control\" content=\"no-cache\"\n    - if content_for?(:meta)\n      = yield(:meta)\n    title\n      = content_for?(:title) ? yield(:title) + \" | #{ENV['SITE_NAME']}\" : ENV['SITE_NAME']\n    = csrf_meta_tags\n    = action_cable_meta_tag\n    = content_for?(:head) ? yield(:head) : ''\n    = stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload'\n    = javascript_include_tag 'application', 'data-turbo-track': Rails.env.production? ? \"reload\" : \"\", type: \"module\"\n    / = javascript_include_tag 'application', 'data-turbo-track': Rails.env.production? ? \"reload\" : \"\"\n    / = javascript_tag 'ga', 'data-turbo-track': 'reload'\n    = favicon_link_tag asset_path(\"favicon.png\")\n  body class=body_class\n    - if content_for?(:main)\n      = yield(:main)\n    - else\n      nav class=\"navbar navbar-expand-lg navbar-dark bg-dark my-navbar\"\n        a class=\"navbar-brand\" href=\"/\" #{ENV['SITE_NAME']}\n        button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#navbar-collapse\"\n          span class=\"navbar-toggler-icon\"\n        div class=\"collapse navbar-collapse\" id=\"navbar-collapse\"\n          ul.navbar-nav.ml-auto\n            li class=\"nav-item\"\n              = link_to '首页', root_path, class: 'nav-link'\n            li class=\"nav-item\"\n              = link_to '时间线', archives_path, class: 'nav-link'\n            li class=\"nav-item\"\n              = link_to '关于', about_path, class: 'nav-link'\n            li class=\"nav-item\"\n              a class=\"nav-link\" href=archives_path\n                .fa.fa-search\n      main\n        = render 'common/welcome_new_year'\n        = yield\n        = render \"layouts/footer\"\n"
  },
  {
    "path": "app/views/layouts/mailer.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n    <style>\n      /* Email styles need to be inline */\n    </style>\n  </head>\n\n  <body>\n    <%= yield %>\n  </body>\n</html>\n"
  },
  {
    "path": "app/views/layouts/mailer.text.erb",
    "content": "<%= yield %>\n"
  },
  {
    "path": "app/views/shared/admin/_flash_messages.html.slim",
    "content": "- flash.each do |key, value|\n  = content_tag :div, class: flash_class(key) do\n    button.close[type=\"button\" data-dismiss=\"alert\"]\n      | ×\n    = value\n"
  },
  {
    "path": "app/views/shared/admin/_header.html.slim",
    "content": "nav class=\"app-header navbar navbar-expand bg-body\"\n  /! Left navbar links\n  ul.navbar-nav\n    li.nav-item\n      a.nav-link data-lte-toggle='sidebar' data-widget=\"pushmenu\" href=\"javascript:void(0)\"\n        i.fas.fa-bars\n  /! SEARCH FORM\n  - if content_for?(:search)\n    = yield(:search)\n  /! Right navbar links\n  ul.navbar-nav.ml-auto\n    /! Messages Dropdown Menu\n    li.nav-item.dropdown.show\n      a.nav-link.dropdown-toggle href=\"javascript:void(0)\" data-toggle=\"dropdown\"\n        span\n          = current_admin.name\n      ul.dropdown-menu.dropdown-menu-right\n        li.user-header\n          = link_to 'Account Setting', edit_admin_account_path, class: 'dropdown-item'\n          .dropdown-divider\n          = link_to 'Logout', admin_logout_path, method: :delete, class: 'dropdown-item'\n"
  },
  {
    "path": "app/views/shared/admin/_sidebar.html.slim",
    "content": "/! Main Sidebar Container\naside.app-sidebar.bg-body-secondary.shadow data-bs-theme=\"dark\"\n  .sidebar-brand\n    /! Brand Logo\n    a.brand-link href=admin_root_path\n      = image_tag 'logo.png', class: 'brand-image img-circle elevation-3'\n      span.brand-text.fw-light Dashboard\n    /! Sidebar\n  .sidebar-wrapper data-overlayscrollbars=\"host\"\n    .os-size-observer\n      .os-size-observer-listener\n    /! Sidebar Menu\n    nav.mt-2\n      ul.nav.sidebar-menu.flex-column data-lte-toggle=\"treeview\" data-accordion=\"false\"\n        li.nav-header 数据\n        li.nav-item\n          = link_to admin_root_path, class: \"nav-link #{admin_active_for(admin_root_path, current_path)}\" do\n            i.nav-icon.fas.fa-tachometer-alt\n            span 数据中心\n        li.nav-header 博客\n        li.nav-item\n          = link_to admin_posts_path, class: \"nav-link #{admin_active_for(admin_posts_path, current_path)}\" do\n            i.nav-icon.fas.fa-pen-square\n            span 博客管理\n        li.nav-item\n          = link_to admin_all_comments_path, class: \"nav-link #{admin_active_for(admin_all_comments_path, current_path)}\" do\n            i.nav-icon.fas.fa-comment\n            span 评论管理\n        li.nav-header 标签\n        li.nav-item\n          = link_to admin_labels_path, class: \"nav-link #{admin_active_for(admin_labels_path, current_path)}\" do\n            i.nav-icon.fas.fa-list\n            span 标签管理\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = function(api) {\n  var validEnv = ['development', 'test', 'production']\n  var currentEnv = api.env()\n  var isDevelopmentEnv = api.env('development')\n  var isProductionEnv = api.env('production')\n  var isTestEnv = api.env('test')\n\n  if (!validEnv.includes(currentEnv)) {\n    throw new Error(\n      'Please specify a valid `NODE_ENV` or ' +\n        '`BABEL_ENV` environment variables. Valid values are \"development\", ' +\n        '\"test\", and \"production\". Instead, received: ' +\n        JSON.stringify(currentEnv) +\n        '.'\n    )\n  }\n\n  return {\n    presets: [\n      isTestEnv && [\n        '@babel/preset-env',\n        {\n          targets: {\n            node: 'current'\n          }\n        }\n      ],\n      (isProductionEnv || isDevelopmentEnv) && [\n        '@babel/preset-env',\n        {\n          forceAllTransforms: true,\n          useBuiltIns: 'entry',\n          corejs: 3,\n          modules: false,\n          exclude: ['transform-typeof-symbol']\n        }\n      ]\n    ].filter(Boolean),\n    plugins: [\n      'babel-plugin-macros',\n      '@babel/plugin-syntax-dynamic-import',\n      isTestEnv && 'babel-plugin-dynamic-import-node',\n      '@babel/plugin-transform-destructuring',\n      [\n        '@babel/plugin-proposal-class-properties',\n        {\n          loose: true\n        }\n      ],\n      [\n        '@babel/plugin-proposal-object-rest-spread',\n        {\n          useBuiltIns: true\n        }\n      ],\n      [\n        '@babel/plugin-transform-runtime',\n        {\n          helpers: false\n        }\n      ],\n      [\n        '@babel/plugin-transform-regenerator',\n        {\n          async: false\n        }\n      ]\n    ].filter(Boolean)\n  }\n}\n"
  },
  {
    "path": "bin/bundle",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n#\n# This file was generated by Bundler.\n#\n# The application 'bundle' is installed as part of a gem, and\n# this file is here to facilitate running it.\n#\n\nrequire \"rubygems\"\n\nm = Module.new do\n  module_function\n\n  def invoked_as_script?\n    File.expand_path($0) == File.expand_path(__FILE__)\n  end\n\n  def env_var_version\n    ENV[\"BUNDLER_VERSION\"]\n  end\n\n  def cli_arg_version\n    return unless invoked_as_script? # don't want to hijack other binstubs\n    return unless \"update\".start_with?(ARGV.first || \" \") # must be running `bundle update`\n    bundler_version = nil\n    update_index = nil\n    ARGV.each_with_index do |a, i|\n      if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN\n        bundler_version = a\n      end\n      next unless a =~ /\\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\\z/\n      bundler_version = $1\n      update_index = i\n    end\n    bundler_version\n  end\n\n  def gemfile\n    gemfile = ENV[\"BUNDLE_GEMFILE\"]\n    return gemfile if gemfile && !gemfile.empty?\n\n    File.expand_path(\"../../Gemfile\", __FILE__)\n  end\n\n  def lockfile\n    lockfile =\n      case File.basename(gemfile)\n      when \"gems.rb\" then gemfile.sub(/\\.rb$/, gemfile)\n      else \"#{gemfile}.lock\"\n      end\n    File.expand_path(lockfile)\n  end\n\n  def lockfile_version\n    return unless File.file?(lockfile)\n    lockfile_contents = File.read(lockfile)\n    return unless lockfile_contents =~ /\\n\\nBUNDLED WITH\\n\\s{2,}(#{Gem::Version::VERSION_PATTERN})\\n/\n    Regexp.last_match(1)\n  end\n\n  def bundler_version\n    @bundler_version ||=\n      env_var_version || cli_arg_version ||\n        lockfile_version\n  end\n\n  def bundler_requirement\n    return \"#{Gem::Requirement.default}.a\" unless bundler_version\n\n    bundler_gem_version = Gem::Version.new(bundler_version)\n\n    requirement = bundler_gem_version.approximate_recommendation\n\n    return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new(\"2.7.0\")\n\n    requirement += \".a\" if bundler_gem_version.prerelease?\n\n    requirement\n  end\n\n  def load_bundler!\n    ENV[\"BUNDLE_GEMFILE\"] ||= gemfile\n\n    activate_bundler\n  end\n\n  def activate_bundler\n    gem_error = activation_error_handling do\n      gem \"bundler\", bundler_requirement\n    end\n    return if gem_error.nil?\n    require_error = activation_error_handling do\n      require \"bundler/version\"\n    end\n    return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))\n    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}'`\"\n    exit 42\n  end\n\n  def activation_error_handling\n    yield\n    nil\n  rescue StandardError, LoadError => e\n    e\n  end\nend\n\nm.load_bundler!\n\nif m.invoked_as_script?\n  load Gem.bin_path(\"bundler\", \"bundle\")\nend\n"
  },
  {
    "path": "bin/dev",
    "content": "#!/usr/bin/env sh\n\nif gem list --no-installed --exact --silent foreman; then\n  echo \"Installing foreman...\"\n  gem install foreman\nfi\n\n# Default to port 3000 if not specified\nexport PORT=\"${PORT:-3000}\"\n\nexec foreman start -f Procfile.dev --env /dev/null \"$@\"\n"
  },
  {
    "path": "bin/rails",
    "content": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequire \"rails/commands\"\n"
  },
  {
    "path": "bin/rake",
    "content": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
  },
  {
    "path": "bin/setup",
    "content": "#!/usr/bin/env ruby\nrequire 'fileutils'\n\n# path to your application root.\nAPP_ROOT = File.expand_path('..', __dir__)\n\ndef system!(*args)\n  system(*args) || abort(\"\\n== Command #{args} failed ==\")\nend\n\nFileUtils.chdir APP_ROOT do\n  # This script is a way to setup or update your development environment automatically.\n  # This script is idempotent, so that you can run it at anytime and get an expectable outcome.\n  # Add necessary setup steps to this file.\n\n  puts '== Installing dependencies =='\n  system! 'gem install bundler --conservative'\n  system('bundle check') || system!('bundle install')\n\n  # Install JavaScript dependencies\n  system('bin/yarn')\n\n  # puts \"\\n== Copying sample files ==\"\n  unless File.exist?('config/database.yml')\n    FileUtils.cp 'config/database.yml.example', 'config/database.yml'\n  end\n\n  unless File.exist?('config/application.yml')\n    FileUtils.cp 'config/application.yml.example', 'config/application.yml'\n  end\n\n  puts \"\\n== Preparing database ==\"\n  system! 'bin/rails db:prepare'\n  puts \"\\n== Preparing db:seed ==\"\n  system! 'bin/rails db:seed'\n\n  puts \"\\n== Removing old logs and tempfiles ==\"\n  system! 'bin/rails log:clear tmp:clear'\n\n  puts \"\\n== Restarting application server ==\"\n  system! 'bin/rails restart'\nend\n"
  },
  {
    "path": "bin/spring",
    "content": "#!/usr/bin/env ruby\nif !defined?(Spring) && [nil, \"development\", \"test\"].include?(ENV[\"RAILS_ENV\"])\n  gem \"bundler\"\n  require \"bundler\"\n\n  # Load Spring without loading other gems in the Gemfile, for speed.\n  Bundler.locked_gems&.specs&.find { |spec| spec.name == \"spring\" }&.tap do |spring|\n    Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path\n    gem \"spring\", spring.version\n    require \"spring/binstub\"\n  rescue Gem::LoadError\n    # Ignore when Spring is not installed.\n  end\nend\n"
  },
  {
    "path": "bin/yarn",
    "content": "#!/usr/bin/env ruby\nAPP_ROOT = File.expand_path('..', __dir__)\nDir.chdir(APP_ROOT) do\n  yarn = ENV[\"PATH\"].split(File::PATH_SEPARATOR).\n    select { |dir| File.expand_path(dir) != __dir__ }.\n    product([\"yarn\", \"yarn.cmd\", \"yarn.ps1\"]).\n    map { |dir, file| File.expand_path(file, dir) }.\n    find { |file| File.executable?(file) }\n\n  if yarn\n    exec yarn, *ARGV\n  else\n    $stderr.puts \"Yarn executable was not detected in the system.\"\n    $stderr.puts \"Download Yarn at https://yarnpkg.com/en/docs/install\"\n    exit 1\n  end\nend\n"
  },
  {
    "path": "config/application.rb",
    "content": "require_relative \"boot\"\n\nrequire \"rails/all\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nmodule Wblog\n  class Application < Rails::Application\n\n    config.generators do |g|\n      g.test_framework :rspec,\n        fixtures: true,\n        view_specs: false,\n        helper_specs: false,\n        routing_specs: false,\n        controller_specs: false,\n        request_specs: false\n      g.fixture_replacement :factory_bot, dir: \"spec/factories\"\n    end\n\n    config.generators.assets = false\n    config.generators.helper = false\n\n    config.time_zone = 'Beijing'\n    config.i18n.available_locales = [:en, :'zh-CN']\n    config.i18n.default_locale = :'zh-CN'\n    # Initialize configuration defaults for originally generated Rails version.\n    config.load_defaults 7.0\n\n    # Please, add to the `ignore` list any other `lib` subdirectories that do\n    # not contain `.rb` files, or that should not be reloaded or eager loaded.\n    # Common ones are `templates`, `generators`, or `middleware`, for example.\n    config.autoload_lib(ignore: %w(assets tasks))\n\n    # Configuration for the application, engines, and railties goes here.\n    #\n    # These settings can be overridden in specific environments using the files\n    # in config/environments, which are processed later.\n    #\n    # config.time_zone = \"Central Time (US & Canada)\"\n    # config.eager_load_paths << Rails.root.join(\"extras\")\n  end\nend\n"
  },
  {
    "path": "config/application.yml.example",
    "content": "# This section MUST be configured\nSITE_NAME: \"WinDy's Blog\"\n\n# domain name used by action cable\nDOMAIN: ''\nPROTOCOL: http\n\n# sidekiq db\nSIDEKIQ_DB: '1'\n\n# google anlytics\nGA: ''\n\n# Rails secret token, use `rake secret` to generate new one here.\nSECRET_KEY_BASE: 'e4122773d4324fce978c52cde790d84d14f7194f377aea41b7b8302d1d10150e6076a3b7e5e0c1f24ca330cf0a058482c95ea37908bba1722d0761ba5d4e566a'\n\n#locale: en or zh-CN, zh-CN is default value\nLOCALE: 'zh-CN'\n\n# sidekiq redis namespace, if you configure two blog in on VPS, you should change it.\n# default is wblog\nREDIS_NAMESPACE: 'wblog'\n\n# META description for SEO\nINTRODUCE: '这是李亚飞的博客, 李亚飞是暂住在深圳的一名 Ruby 程序员, 这里是关于技术, 创业, 生活的思考'\n\n#Website Address for footer display without http\nSITE_ADDRESS: 'yafeilee.com'\n#Website Year for footer display\nSITE_YEAR: '© 2012 - 2019'\n#Website beian for footer display\nSITE_BEIAN: '粤ICP备19030132号-3'\n\n# optional\n# google analytics, blank it if you don't need\nGOOGLE: ''\n\n# CDN ( optional )\nCDN: ''\n\n# Email Setting, see more: /config/environments/production.rb\nMAIL_SERVER: ''\nDOMAIN_NAME: ''\nMAIL_USERNAME: ''\nMAIL_PASSWORD: ''\n"
  },
  {
    "path": "config/backup.rb.example",
    "content": "# encoding: utf-8\n## backup gem example\n## Howto:\n## $ gem install backup\n## $ backup generate:model --trigger wblog --archives --storages='local' --compressor='gzip'\n## $ cp config/backup.rb.example ~/Backup/models/wblog.rb\n## $ backup perform --trigger wblog\n\nModel.new(:wblog, 'Description for wblog') do\n\n  database PostgreSQL do |db|\n    db.name               = \"wblog_production\"\n    db.username           = \"postgres\"\n    db.password           = \"postgres\"\n    db.host               = \"localhost\"\n    db.port               = 5432\n  end\n\n  archive :rails_config do |archive|\n    archive.add \"/data/www/wblog/shared/config/\"\n    archive.add \"/etc/nginx/conf.d/\"\n  end\n\n  store_with Local do |local|\n    local.path       = \"/data/www/backups/\"\n    local.keep       = 5\n  end\n\n  # Use FTP instead of Local in production environments\n  #store_with FTP do |server|\n    #server.username     = \"\"\n    #server.password     = \"\"\n    #server.ip           = \"\"\n    #server.port         = 21\n    #server.path         = \"~/backups/\"\n    #server.keep         = 5\n    ## server.keep         = Time.now - 2592000 # Remove all backups older than 1 month.\n    #server.passive_mode = false\n  #end\n\n  compress_with Gzip\nend\n"
  },
  {
    "path": "config/boot.rb",
    "content": "ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)\n\nrequire \"bundler/setup\" # Set up gems listed in the Gemfile.\nrequire \"bootsnap/setup\" # Speed up boot time by caching expensive operations.\n"
  },
  {
    "path": "config/cable.yml",
    "content": "development:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: redis\n  url: <%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>\n  channel_prefix: wblog_production\n"
  },
  {
    "path": "config/credentials.yml.enc",
    "content": "lNIHR59bC+oahS9eflbwKRqAEUPxasN91LCSNg875kdit4+EUfInV7wQEZ8bsnI+aZxxEF+Rbx9reUR42p5yCSJQCRv8BB6W0bwX65lPypdeivt+puy1SmcLdWWkBcLAyFUAQiVw1Qk+/XrZWqT/P1pIVX1fMBJ/cAIddTZvooIl2PXMEJYmGFUq0sjCYpfsZqrGB1R2WT/7Ks8YAiOG2nyzfaY1OrhRd7UkaCk37SolIYoyrf8gTgZ56mcEcBn9801YZ6hNAmdwUb66TeUMwq+HyJJtwPPDCnqOxlUjbEQVnlqJ27qTvatQj3z2w+NK6UzgYjoZ+m1TQlg0pXqnnemsf2PdWCpmlutUyNxnFZX/Zshdi9gLfXDvUtvtHsUDuWMX7HUQ5bFGNoLiKdRMhxkirxG9qDepDwuo--yAFRd2ZelQPqJupj--VZZY7ftgfjWkvrItf9JYig=="
  },
  {
    "path": "config/database.yml.example",
    "content": "development:\n  adapter:  postgresql\n  host:     localhost\n  encoding: unicode\n  database: wblog_development\n  pool:     5\n  username: postgres\n  password: postgres\n  template: template0\n\ntest:\n  adapter:  postgresql\n  host:     localhost\n  encoding: unicode\n  database: wblog_test\n  pool:     5\n  username: postgres\n  password: postgres\n  template: template0\n\nproduction:\n  adapter:  postgresql\n  host:     localhost\n  encoding: unicode\n  database: wblog_production\n  pool:     16\n  username: postgres\n  password: postgres\n  template: template0\n"
  },
  {
    "path": "config/deploy/production.rb",
    "content": "set :domain, 'yafeilee.com'\nset :deploy_to, '/data/www/wblog'\nset :repository,  'git@github.com:windy/wblog.git'\nset :branch, 'master'\nset :user, 'ruby'\n"
  },
  {
    "path": "config/deploy.rb",
    "content": "STDOUT.sync = true\nset :stages, %w(production)\nset :default_stage, 'production'\n\nrequire 'mina/bundler'\nrequire 'mina/rails'\nrequire 'mina/git'\nrequire 'mina/rbenv'\nrequire 'mina/puma'\nrequire \"mina_sidekiq/tasks\"\nrequire 'mina/logs'\nrequire 'mina/multistage'\n\nset :asset_dirs, fetch(:asset_dirs, []).push('app/javascript')\nset :shared_dirs, fetch(:shared_dirs, []).push('log', 'public/uploads', 'public/packs', 'node_modules', 'storage')\nset :shared_files, fetch(:shared_files, []).push('config/database.yml', 'config/application.yml')\n\nset :puma_config, ->{ \"#{fetch(:current_path)}/config/puma.rb\" }\nset :sidekiq_pid, ->{ \"#{fetch(:shared_path)}/tmp/pids/sidekiq.pid\" }\n\ntask :remote_environment do\n  invoke :'rbenv:load'\nend\n\ntask :setup do\n  command %[mkdir -p \"#{fetch(:shared_path)}/tmp/sockets\"]\n  command %[chmod g+rx,u+rwx \"#{fetch(:shared_path)}/tmp/sockets\"]\n\n  command %[mkdir -p \"#{fetch(:shared_path)}/tmp/pids\"]\n  command %[chmod g+rx,u+rwx \"#{fetch(:shared_path)}/tmp/pids\"]\n\n  command %[mkdir -p \"#{fetch(:shared_path)}/log\"]\n  command %[chmod g+rx,u+rwx \"#{fetch(:shared_path)}/log\"]\n\n  command %[mkdir -p \"#{fetch(:shared_path)}/public/uploads\"]\n  command %[chmod g+rx,u+rwx \"#{fetch(:shared_path)}/public/uploads\"]\n\n  command %[mkdir -p \"#{fetch(:shared_path)}/public/packs\"]\n  command %[chmod g+rx,u+rwx \"#{fetch(:shared_path)}/public/packs\"]\n\n  command %[mkdir -p \"#{fetch(:shared_path)}/node_modules\"]\n  command %[chmod g+rx,u+rwx \"#{fetch(:shared_path)}/node_modules\"]\n\n  command %[mkdir -p \"#{fetch(:shared_path)}/storage\"]\n  command %[chmod g+rx,u+rwx \"#{fetch(:shared_path)}/storage\"]\n\n  command %[mkdir -p \"#{fetch(:shared_path)}/config\"]\n  command %[chmod g+rx,u+rwx \"#{fetch(:shared_path)}/config\"]\n\n  command %[touch \"#{fetch(:shared_path)}/config/application.yml\"]\n  command %[echo \"-----> Be sure to edit '#{fetch(:shared_path)}/config/application.yml'\"]\n\n  command %[touch \"#{fetch(:shared_path)}/config/database.yml\"]\n  command %[echo \"-----> Be sure to edit '#{fetch(:shared_path)}/config/database.yml'\"]\nend\n\ndesc \"Clear bootsnap cache\"\ntask :clear_bootsnap do\n  command %[echo \"Clear bootsnap cache...\"]\n  command %[rm -rf \"#{fetch(:shared_path)}/tmp/bootsnap-*\"]\nend\n\ndesc \"Deploys the current version to the server.\"\ntask :deploy do\n  command %[echo \"-----> Server: #{fetch(:domain)}\"]\n  command %[echo \"-----> Path: #{fetch(:deploy_to)}\"]\n  command %[echo \"-----> Branch: #{fetch(:branch)}\"]\n\n  deploy do\n    invoke :'git:clone'\n    invoke :'deploy:link_shared_paths'\n    invoke :'bundle:install'\n    invoke :'rails:db_migrate'\n    invoke :clear_bootsnap\n    invoke :'rails:assets_precompile'\n    invoke :'deploy:cleanup'\n\n    on :launch do\n      invoke :'rbenv:load'\n      invoke :'sidekiq:quiet'\n      invoke :'puma:smart_restart'\n      invoke :'sidekiq:restart'\n    end\n  end\nend\n\ndesc \"Prepare the first deploy on server.\"\ntask :first_deploy do\n  command %[echo \"-----> Server: #{fetch(:domain)}\"]\n  command %[echo \"-----> Path: #{fetch(:deploy_to)}\"]\n  command %[echo \"-----> Branch: #{fetch(:branch)}\"]\n\n  deploy do\n    invoke :'git:clone'\n    invoke :'deploy:link_shared_paths'\n    invoke :'bundle:install'\n    invoke :'rails:assets_precompile'\n    invoke :'deploy:cleanup'\n\n    on :launch do\n      invoke :'rbenv:load'\n      invoke :'rails:db_create'\n      invoke :'rails', 'db:migrate'\n      invoke :'rails', 'db:seed'\n    end\n  end\nend\n"
  },
  {
    "path": "config/environment.rb",
    "content": "# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.initialize!\n"
  },
  {
    "path": "config/environments/development.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # In the development environment your application's code is reloaded any time\n  # it changes. This slows down response time but is perfect for development\n  # since you don't have to restart the web server when you make code changes.\n  config.enable_reloading = true\n\n  config.file_watcher = ActiveSupport::EventedFileUpdateChecker\n\n  # Do not eager load code on boot.\n  config.eager_load = false\n\n  # Show full error reports.\n  config.consider_all_requests_local = true\n\n  # Enable server timing\n  config.server_timing = true\n\n  # Enable/disable caching. By default caching is disabled.\n  # Run rails dev:cache to toggle caching.\n  if Rails.root.join(\"tmp/caching-dev.txt\").exist?\n    config.action_controller.perform_caching = true\n    config.action_controller.enable_fragment_cache_logging = true\n\n    config.cache_store = :memory_store\n    config.public_file_server.headers = {\n      \"Cache-Control\" => \"public, max-age=#{2.days.to_i}\"\n    }\n  else\n    config.action_controller.perform_caching = false\n\n    config.cache_store = :null_store\n  end\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Don't care if the mailer can't send.\n  config.action_mailer.raise_delivery_errors = false\n\n  config.action_mailer.perform_caching = false\n\n  # Print deprecation notices to the Rails logger.\n  config.active_support.deprecation = :log\n\n  # Raise exceptions for disallowed deprecations.\n  config.active_support.disallowed_deprecation = :raise\n\n  # Tell Active Support which deprecation messages to disallow.\n  config.active_support.disallowed_deprecation_warnings = []\n\n  # Raise an error on page load if there are pending migrations.\n  config.active_record.migration_error = :page_load\n\n  # Highlight code that triggered database queries in logs.\n  config.active_record.verbose_query_logs = true\n\n  # Highlight code that enqueued background job in logs.\n  config.active_job.verbose_enqueue_logs = true\n\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  # config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Uncomment if you wish to allow Action Cable access from any origin.\n  # config.action_cable.disable_request_forgery_protection = true\n\n  # Raise error when a before_action's only/except options reference missing actions\n  config.action_controller.raise_on_missing_callback_actions = true\n\n  # Allow Clacky domains to access the application\n  config.hosts << /.*/\nend"
  },
  {
    "path": "config/environments/production.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.enable_reloading = false\n\n  # Eager load code on boot. This eager loads most of Rails and\n  # your application in memory, allowing both threaded web servers\n  # and those relying on copy on write to perform better.\n  # Rake tasks automatically ignore this option for performance.\n  config.eager_load = true\n\n  # Full error reports are disabled and caching is turned on.\n  config.consider_all_requests_local = false\n  config.action_controller.perform_caching = true\n\n  # Ensures that a master key has been made available in ENV[\"RAILS_MASTER_KEY\"], config/master.key, or an environment\n  # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files).\n  # config.require_master_key = true\n\n  # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead.\n  # config.public_file_server.enabled = false\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = \"http://assets.example.com\"\n\n  # Specifies the header that your server uses for sending files.\n  # config.action_dispatch.x_sendfile_header = \"X-Sendfile\" # for Apache\n  # config.action_dispatch.x_sendfile_header = \"X-Accel-Redirect\" # for NGINX\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Mount Action Cable outside main process or domain.\n  # config.action_cable.mount_path = nil\n  # config.action_cable.url = \"wss://example.com/cable\"\n  # config.action_cable.allowed_request_origins = [ \"http://example.com\", /http:\\/\\/example.*/ ]\n  config.action_cable.allowed_request_origins = [ \"#{ENV['PROTOCOL']}://#{ENV['DOMAIN']}\" ]\n\n  # Assume all access to the app is happening through a SSL-terminating reverse proxy.\n  # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.\n  # config.assume_ssl = true\n\n  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.\n  # config.force_ssl = true\n\n  # Log to STDOUT by default\n  config.logger = ActiveSupport::Logger.new(STDOUT)\n    .tap  { |logger| logger.formatter = ::Logger::Formatter.new }\n    .then { |logger| ActiveSupport::TaggedLogging.new(logger) }\n\n  # Prepend all log lines with the following tags.\n  config.log_tags = [ :request_id ]\n\n  # \"info\" includes generic and useful information about system operation, but avoids logging too much\n  # information to avoid inadvertent exposure of personally identifiable information (PII). If you\n  # want to log everything, set the level to \"debug\".\n  config.log_level = ENV.fetch(\"RAILS_LOG_LEVEL\", \"info\")\n\n  # Use a different cache store in production.\n  # config.cache_store = :mem_cache_store\n\n  # Use a real queuing backend for Active Job (and separate queues per environment).\n  # config.active_job.queue_adapter = :resque\n  # config.active_job.queue_name_prefix = \"wblog_production\"\n\n  config.action_mailer.perform_caching = false\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Don't log any deprecations.\n  config.active_support.report_deprecations = false\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Enable DNS rebinding protection and other `Host` header attacks.\n  # config.hosts = [\n  #   \"example.com\",     # Allow requests from example.com\n  #   /.*\\.example\\.com/ # Allow requests from subdomains like `www.example.com`\n  # ]\n  # Skip DNS rebinding protection for the default health check endpoint.\n  # config.host_authorization = { exclude: ->(request) { request.path == \"/up\" } }\nend\n"
  },
  {
    "path": "config/environments/test.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\n# The test environment is used exclusively to run your application's\n# test suite. You never need to work with it otherwise. Remember that\n# your test database is \"scratch space\" for the test suite and is wiped\n# and recreated between test runs. Don't rely on the data there!\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # While tests run files are not watched, reloading is not necessary.\n  config.enable_reloading = false\n\n  # Eager loading loads your entire application. When running a single test locally,\n  # this is usually not necessary, and can slow down your test suite. However, it's\n  # recommended that you enable it in continuous integration systems to ensure eager\n  # loading is working properly before deploying your code.\n  config.eager_load = ENV[\"CI\"].present?\n\n  # Configure public file server for tests with Cache-Control for performance.\n  config.public_file_server.enabled = true\n  config.public_file_server.headers = {\n    \"Cache-Control\" => \"public, max-age=#{1.hour.to_i}\"\n  }\n\n  # Show full error reports and disable caching.\n  config.consider_all_requests_local = true\n  config.action_controller.perform_caching = false\n  config.cache_store = :null_store\n\n  # Render exception templates for rescuable exceptions and raise for other exceptions.\n  config.action_dispatch.show_exceptions = :rescuable\n\n  # Disable request forgery protection in test environment.\n  config.action_controller.allow_forgery_protection = false\n\n  # Store uploaded files on the local file system in a temporary directory.\n  config.active_storage.service = :test\n\n  config.action_mailer.perform_caching = false\n\n  # Tell Action Mailer not to deliver emails to the real world.\n  # The :test delivery method accumulates sent emails in the\n  # ActionMailer::Base.deliveries array.\n  config.action_mailer.delivery_method = :test\n\n  # Print deprecation notices to the stderr.\n  config.active_support.deprecation = :stderr\n\n  # Raise exceptions for disallowed deprecations.\n  config.active_support.disallowed_deprecation = :raise\n\n  # Tell Active Support which deprecation messages to disallow.\n  config.active_support.disallowed_deprecation_warnings = []\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  # config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Raise error when a before_action's only/except options reference missing actions\n  config.action_controller.raise_on_missing_callback_actions = true\nend\n"
  },
  {
    "path": "config/initializers/application_controller_renderer.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# ActiveSupport::Reloader.to_prepare do\n#   ApplicationController.renderer.defaults.merge!(\n#     http_host: 'example.org',\n#     https: false\n#   )\n# end\n"
  },
  {
    "path": "config/initializers/assets.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire all your assets.\nRails.application.config.assets.version = '1.0'\n\n# Add additional assets to the asset load path.\n# Rails.application.config.assets.paths << Emoji.images_path\n# Add Yarn node_modules folder to the asset load path.\nRails.application.config.assets.paths << Rails.root.join('node_modules')\n\nRails.application.config.assets.paths << Rails.root.join('node_modules/@fortawesome/fontawesome-free/webfonts')\n\n# Precompile additional assets.\n# application.js, application.css, and all non-JS/CSS in the app/assets\n# folder are already added.\nRails.application.config.assets.precompile += %w( admin.js admin.css )\n"
  },
  {
    "path": "config/initializers/backtrace_silencers.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.\n# Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }\n\n# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code\n# by setting BACKTRACE=1 before calling your invocation, like \"BACKTRACE=1 ./bin/rails runner 'MyClass.perform'\".\nRails.backtrace_cleaner.remove_silencers! if ENV[\"BACKTRACE\"]\n"
  },
  {
    "path": "config/initializers/browser_warrior.rb",
    "content": "# BrowserWarrior.detect do |browser|\n  # next true if Rails.env.test?\n  # next true if browser.bot?\n\n  # next true unless browser.known?\n\n  # # Allow weixin callback\n  # next true if browser.platform.other?\n\n  # # See https://github.com/fnando/browser#usage for more usage\n  # next true if browser.wechat?\n  # next true if browser.weibo?\n  # next true if browser.facebook?\n\n  # # Block known non-modern browser\n  # next false if browser.chrome?(\"<= 65\")\n  # next false if browser.safari?(\"< 10\")\n  # next false if browser.firefox?(\"< 52\")\n  # next false if browser.ie?(\"< 11\")\n  # next false if browser.edge?(\"< 15\")\n  # next false if browser.opera?(\"< 50\")\n\n  # # Allow by default\n  # next true\n# end\n"
  },
  {
    "path": "config/initializers/content_security_policy.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy.\n# See the Securing Rails Applications Guide for more information:\n# https://guides.rubyonrails.org/security.html#content-security-policy-header\n\n# Rails.application.configure do\n#   config.content_security_policy do |policy|\n#     policy.default_src :self, :https\n#     policy.font_src    :self, :https, :data\n#     policy.img_src     :self, :https, :data\n#     policy.object_src  :none\n#     policy.script_src  :self, :https\n#     policy.style_src   :self, :https\n#     # Specify URI for violation reports\n#     # policy.report_uri \"/csp-violation-report-endpoint\"\n#   end\n#\n#   # Generate session nonces for permitted importmap, inline scripts, and inline styles.\n#   config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }\n#   config.content_security_policy_nonce_directives = %w(script-src style-src)\n#\n#   # Report violations without enforcing the policy.\n#   # config.content_security_policy_report_only = true\n# end\n"
  },
  {
    "path": "config/initializers/cookies_serializer.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Specify a serializer for the signed and encrypted cookie jars.\n# Valid options are :json, :marshal, and :hybrid.\nRails.application.config.action_dispatch.cookies_serializer = :json\n"
  },
  {
    "path": "config/initializers/filter_parameter_logging.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.\n# Use this to limit dissemination of sensitive information.\n# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.\nRails.application.config.filter_parameters += [\n  :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn\n]\n"
  },
  {
    "path": "config/initializers/inflections.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Inflections\n# are locale specific, and you may define rules for as many different\n# locales as you wish. All of these examples are active by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.plural /^(ox)$/i, \"\\\\1en\"\n#   inflect.singular /^(ox)en/i, \"\\\\1\"\n#   inflect.irregular \"person\", \"people\"\n#   inflect.uncountable %w( fish sheep )\n# end\n\n# These inflection rules are supported but not enabled by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.acronym \"RESTful\"\n# end\n"
  },
  {
    "path": "config/initializers/kaminari_config.rb",
    "content": "# frozen_string_literal: true\n\nKaminari.configure do |config|\n  # config.default_per_page = 25\n  # config.max_per_page = nil\n  # config.window = 4\n  # config.outer_window = 0\n  # config.left = 0\n  # config.right = 0\n  # config.page_method_name = :page\n  # config.param_name = :page\n  # config.max_pages = nil\n  # config.params_on_first_page = false\nend\n"
  },
  {
    "path": "config/initializers/mime_types.rb",
    "content": "# 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::Type.register \"text/richtext\", :rtf\n"
  },
  {
    "path": "config/initializers/permissions_policy.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide HTTP permissions policy. For further\n# information see: https://developers.google.com/web/updates/2018/06/feature-policy\n\n# Rails.application.config.permissions_policy do |policy|\n#   policy.camera      :none\n#   policy.gyroscope   :none\n#   policy.microphone  :none\n#   policy.usb         :none\n#   policy.fullscreen  :self\n#   policy.payment     :self, \"https://secure.example.com\"\n# end\n"
  },
  {
    "path": "config/initializers/sidekiq.rb",
    "content": "Sidekiq.configure_server do |config|\n  config.redis = { db: ENV['SIDEKIQ_DB'].presence || '1' }\nend\n\nSidekiq.configure_client do |config|\n  config.redis = { db: ENV['SIDEKIQ_DB'].presence || '1' }\nend\n"
  },
  {
    "path": "config/initializers/simple_form.rb",
    "content": "# frozen_string_literal: true\n#\n# Uncomment this and change the path if necessary to include your own\n# components.\n# See https://github.com/plataformatec/simple_form#custom-components to know\n# more about custom components.\n# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }\n#\n# Use this setup block to configure all options available in SimpleForm.\nSimpleForm.setup do |config|\n  # Wrappers are used by the form builder to generate a\n  # complete input. You can remove any component from the\n  # wrapper, change the order or even add your own to the\n  # stack. The options given below are used to wrap the\n  # whole input.\n  config.wrappers :default, class: :input,\n    hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b|\n    ## Extensions enabled by default\n    # Any of these extensions can be disabled for a\n    # given input by passing: `f.input EXTENSION_NAME => false`.\n    # You can make any of these extensions optional by\n    # renaming `b.use` to `b.optional`.\n\n    # Determines whether to use HTML5 (:email, :url, ...)\n    # and required attributes\n    b.use :html5\n\n    # Calculates placeholders automatically from I18n\n    # You can also pass a string as f.input placeholder: \"Placeholder\"\n    b.use :placeholder\n\n    ## Optional extensions\n    # They are disabled unless you pass `f.input EXTENSION_NAME => true`\n    # to the input. If so, they will retrieve the values from the model\n    # if any exists. If you want to enable any of those\n    # extensions by default, you can change `b.optional` to `b.use`.\n\n    # Calculates maxlength from length validations for string inputs\n    # and/or database column lengths\n    b.optional :maxlength\n\n    # Calculate minlength from length validations for string inputs\n    b.optional :minlength\n\n    # Calculates pattern from format validations for string inputs\n    b.optional :pattern\n\n    # Calculates min and max from length validations for numeric inputs\n    b.optional :min_max\n\n    # Calculates readonly automatically from readonly attributes\n    b.optional :readonly\n\n    ## Inputs\n    # b.use :input, class: 'input', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :label_input\n    b.use :hint,  wrap_with: { tag: :span, class: :hint }\n    b.use :error, wrap_with: { tag: :span, class: :error }\n\n    ## full_messages_for\n    # If you want to display the full error message for the attribute, you can\n    # use the component :full_error, like:\n    #\n    # b.use :full_error, wrap_with: { tag: :span, class: :error }\n  end\n\n  # The default wrapper to be used by the FormBuilder.\n  config.default_wrapper = :default\n\n  # Define the way to render check boxes / radio buttons with labels.\n  # Defaults to :nested for bootstrap config.\n  #   inline: input + label\n  #   nested: label > input\n  config.boolean_style = :nested\n\n  # Default class for buttons\n  config.button_class = 'btn'\n\n  # Method used to tidy up errors. Specify any Rails Array method.\n  # :first lists the first message for each field.\n  # Use :to_sentence to list all errors for each field.\n  # config.error_method = :first\n\n  # Default tag used for error notification helper.\n  config.error_notification_tag = :div\n\n  # CSS class to add for error notification helper.\n  config.error_notification_class = 'error_notification'\n\n  # Series of attempts to detect a default label method for collection.\n  # config.collection_label_methods = [ :to_label, :name, :title, :to_s ]\n\n  # Series of attempts to detect a default value method for collection.\n  # config.collection_value_methods = [ :id, :to_s ]\n\n  # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.\n  # config.collection_wrapper_tag = nil\n\n  # You can define the class to use on all collection wrappers. Defaulting to none.\n  # config.collection_wrapper_class = nil\n\n  # You can wrap each item in a collection of radio/check boxes with a tag,\n  # defaulting to :span.\n  # config.item_wrapper_tag = :span\n\n  # You can define a class to use in all item wrappers. Defaulting to none.\n  # config.item_wrapper_class = nil\n\n  # How the label text should be generated altogether with the required text.\n  # config.label_text = lambda { |label, required, explicit_label| \"#{required} #{label}\" }\n\n  # You can define the class to use on all labels. Default is nil.\n  # config.label_class = nil\n\n  # You can define the default class to be used on forms. Can be overriden\n  # with `html: { :class }`. Defaulting to none.\n  # config.default_form_class = nil\n\n  # You can define which elements should obtain additional classes\n  # config.generate_additional_classes_for = [:wrapper, :label, :input]\n\n  # Whether attributes are required by default (or not). Default is true.\n  # config.required_by_default = true\n\n  # Tell browsers whether to use the native HTML5 validations (novalidate form option).\n  # These validations are enabled in SimpleForm's internal config but disabled by default\n  # in this configuration, which is recommended due to some quirks from different browsers.\n  # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations,\n  # change this configuration to true.\n  config.browser_validations = false\n\n  # Collection of methods to detect if a file type was given.\n  # config.file_methods = [ :mounted_as, :file?, :public_filename, :attached? ]\n\n  # Custom mappings for input types. This should be a hash containing a regexp\n  # to match as key, and the input type that will be used when the field name\n  # matches the regexp as value.\n  # config.input_mappings = { /count/ => :integer }\n\n  # Custom wrappers for input types. This should be a hash containing an input\n  # type as key and the wrapper that will be used for all inputs with specified type.\n  # config.wrapper_mappings = { string: :prepend }\n\n  # Namespaces where SimpleForm should look for custom input classes that\n  # override default inputs.\n  # config.custom_inputs_namespaces << \"CustomInputs\"\n\n  # Default priority for time_zone inputs.\n  # config.time_zone_priority = nil\n\n  # Default priority for country inputs.\n  # config.country_priority = nil\n\n  # When false, do not use translations for labels.\n  # config.translate_labels = true\n\n  # Automatically discover new inputs in Rails' autoload path.\n  # config.inputs_discovery = true\n\n  # Cache SimpleForm inputs discovery\n  # config.cache_discovery = !Rails.env.development?\n\n  # Default class for inputs\n  # config.input_class = nil\n\n  # Define the default class of the input wrapper of the boolean input.\n  config.boolean_label_class = 'checkbox'\n\n  # Defines if the default input wrapper class should be included in radio\n  # collection wrappers.\n  # config.include_default_input_wrapper_class = true\n\n  # Defines which i18n scope will be used in Simple Form.\n  # config.i18n_scope = 'simple_form'\n\n  # Defines validation classes to the input_field. By default it's nil.\n  # config.input_field_valid_class = 'is-valid'\n  # config.input_field_error_class = 'is-invalid'\nend\n"
  },
  {
    "path": "config/initializers/simple_form_bootstrap.rb",
    "content": "# frozen_string_literal: true\n\n# Please do not make direct changes to this file!\n# This generator is maintained by the community around simple_form-bootstrap:\n# https://github.com/rafaelfranca/simple_form-bootstrap\n# All future development, tests, and organization should happen there.\n# Background history: https://github.com/plataformatec/simple_form/issues/1561\n\n# Uncomment this and change the path if necessary to include your own\n# components.\n# See https://github.com/plataformatec/simple_form#custom-components\n# to know more about custom components.\n# Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }\n\n# Use this setup block to configure all options available in SimpleForm.\nSimpleForm.setup do |config|\n  # Default class for buttons\n  config.button_class = 'btn btn-primary'\n\n  # Define the default class of the input wrapper of the boolean input.\n  config.boolean_label_class = 'form-check-label'\n\n  # How the label text should be generated altogether with the required text.\n  config.label_text = lambda { |label, required, explicit_label| \"#{label} #{required}\" }\n\n  # Define the way to render check boxes / radio buttons with labels.\n  config.boolean_style = :inline\n\n  # You can wrap each item in a collection of radio/check boxes with a tag\n  config.item_wrapper_tag = :div\n\n  # Defines if the default input wrapper class should be included in radio\n  # collection wrappers.\n  config.include_default_input_wrapper_class = false\n\n  # CSS class to add for error notification helper.\n  config.error_notification_class = 'alert alert-danger'\n\n  # Method used to tidy up errors. Specify any Rails Array method.\n  # :first lists the first message for each field.\n  # :to_sentence to list all errors for each field.\n  config.error_method = :to_sentence\n\n  # add validation classes to `input_field`\n  config.input_field_error_class = 'is-invalid'\n  config.input_field_valid_class = 'is-valid'\n\n\n  # vertical forms\n  #\n  # vertical default_wrapper\n  config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.use :placeholder\n    b.optional :maxlength\n    b.optional :minlength\n    b.optional :pattern\n    b.optional :min_max\n    b.optional :readonly\n    b.use :label, class: 'form-control-label'\n    b.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n  # vertical input for boolean\n  config.wrappers :vertical_boolean, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.wrapper :form_check_wrapper, tag: 'div', class: 'form-check' do |bb|\n      bb.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'\n      bb.use :label, class: 'form-check-label'\n      bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }\n      bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n    end\n  end\n\n  # vertical input for radio buttons and check boxes\n  config.wrappers :vertical_collection, item_wrapper_class: 'form-check', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba|\n      ba.use :label_text\n    end\n    b.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n  # vertical input for inline radio buttons and check boxes\n  config.wrappers :vertical_collection_inline, item_wrapper_class: 'form-check form-check-inline', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba|\n      ba.use :label_text\n    end\n    b.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n  # vertical file input\n  config.wrappers :vertical_file, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.use :placeholder\n    b.optional :maxlength\n    b.optional :minlength\n    b.optional :readonly\n    b.use :label\n    b.use :input, class: 'form-control-file', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n  # vertical multi select\n  config.wrappers :vertical_multi_select, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.use :label, class: 'form-control-label'\n    b.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |ba|\n      ba.use :input, class: 'form-control mx-1', error_class: 'is-invalid', valid_class: 'is-valid'\n    end\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n  # vertical range input\n  config.wrappers :vertical_range, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.use :placeholder\n    b.optional :readonly\n    b.optional :step\n    b.use :label\n    b.use :input, class: 'form-control-range', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n\n  # horizontal forms\n  #\n  # horizontal default_wrapper\n  config.wrappers :horizontal_form, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.use :placeholder\n    b.optional :maxlength\n    b.optional :minlength\n    b.optional :pattern\n    b.optional :min_max\n    b.optional :readonly\n    b.use :label, class: 'col-sm-3 col-form-label'\n    b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|\n      ba.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'\n      ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }\n      ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n    end\n  end\n\n  # horizontal input for boolean\n  config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.wrapper tag: 'label', class: 'col-sm-3' do |ba|\n      ba.use :label_text\n    end\n    b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |wr|\n      wr.wrapper :form_check_wrapper, tag: 'div', class: 'form-check' do |bb|\n        bb.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'\n        bb.use :label, class: 'form-check-label'\n        bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n        bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n      end\n    end\n  end\n\n  # horizontal input for radio buttons and check boxes\n  config.wrappers :horizontal_collection, item_wrapper_class: 'form-check', tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.use :label, class: 'col-sm-3 form-control-label'\n    b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|\n      ba.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'\n      ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n      ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n    end\n  end\n\n  # horizontal input for inline radio buttons and check boxes\n  config.wrappers :horizontal_collection_inline, item_wrapper_class: 'form-check form-check-inline', tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.use :label, class: 'col-sm-3 form-control-label'\n    b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|\n      ba.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'\n      ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n      ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n    end\n  end\n\n  # horizontal file input\n  config.wrappers :horizontal_file, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.use :placeholder\n    b.optional :maxlength\n    b.optional :minlength\n    b.optional :readonly\n    b.use :label, class: 'col-sm-3 form-control-label'\n    b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|\n      ba.use :input, error_class: 'is-invalid', valid_class: 'is-valid'\n      ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n      ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n    end\n  end\n\n  # horizontal multi select\n  config.wrappers :horizontal_multi_select, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.use :label, class: 'col-sm-3 control-label'\n    b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|\n      ba.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |bb|\n        bb.use :input, class: 'form-control mx-1', error_class: 'is-invalid', valid_class: 'is-valid'\n      end\n      ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n      ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n    end\n  end\n\n  # horizontal range input\n  config.wrappers :horizontal_range, tag: 'div', class: 'form-group row', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.use :placeholder\n    b.optional :readonly\n    b.optional :step\n    b.use :label, class: 'col-sm-3 form-control-label'\n    b.wrapper :grid_wrapper, tag: 'div', class: 'col-sm-9' do |ba|\n      ba.use :input, class: 'form-control-range', error_class: 'is-invalid', valid_class: 'is-valid'\n      ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n      ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n    end\n  end\n\n\n  # inline forms\n  #\n  # inline default_wrapper\n  config.wrappers :inline_form, tag: 'span', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.use :placeholder\n    b.optional :maxlength\n    b.optional :minlength\n    b.optional :pattern\n    b.optional :min_max\n    b.optional :readonly\n    b.use :label, class: 'sr-only'\n\n    b.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :error, wrap_with: { tag: 'div', class: 'invalid-feedback' }\n    b.optional :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n  # inline input for boolean\n  config.wrappers :inline_boolean, tag: 'span', class: 'form-check flex-wrap justify-content-start mr-sm-2', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.use :input, class: 'form-check-input', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :label, class: 'form-check-label'\n    b.use :error, wrap_with: { tag: 'div', class: 'invalid-feedback' }\n    b.optional :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n\n  # bootstrap custom forms\n  #\n  # custom input for boolean\n  config.wrappers :custom_boolean, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.wrapper :form_check_wrapper, tag: 'div', class: 'custom-control custom-checkbox' do |bb|\n      bb.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid'\n      bb.use :label, class: 'custom-control-label'\n      bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }\n      bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n    end\n  end\n\n  config.wrappers :custom_boolean_switch, tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.wrapper :form_check_wrapper, tag: 'div', class: 'custom-control custom-checkbox-switch' do |bb|\n      bb.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid'\n      bb.use :label, class: 'custom-control-label'\n      bb.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }\n      bb.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n    end\n  end\n\n  # custom input for radio buttons and check boxes\n  config.wrappers :custom_collection, item_wrapper_class: 'custom-control', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba|\n      ba.use :label_text\n    end\n    b.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n  # custom input for inline radio buttons and check boxes\n  config.wrappers :custom_collection_inline, item_wrapper_class: 'custom-control custom-control-inline', tag: 'fieldset', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.wrapper :legend_tag, tag: 'legend', class: 'col-form-label pt-0' do |ba|\n      ba.use :label_text\n    end\n    b.use :input, class: 'custom-control-input', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n  # custom file input\n  config.wrappers :custom_file, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.use :placeholder\n    b.optional :maxlength\n    b.optional :minlength\n    b.optional :readonly\n    b.use :label, class: 'form-control-label'\n    b.wrapper :custom_file_wrapper, tag: 'div', class: 'custom-file' do |ba|\n      ba.use :input, class: 'custom-file-input', error_class: 'is-invalid', valid_class: 'is-valid'\n      ba.use :label, class: 'custom-file-label'\n      ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }\n    end\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n  # custom multi select\n  config.wrappers :custom_multi_select, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.use :label, class: 'form-control-label'\n    b.wrapper tag: 'div', class: 'd-flex flex-row justify-content-between align-items-center' do |ba|\n      ba.use :input, class: 'custom-select mx-1', error_class: 'is-invalid', valid_class: 'is-valid'\n    end\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n  # custom range input\n  config.wrappers :custom_range, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.use :placeholder\n    b.optional :readonly\n    b.optional :step\n    b.use :label, class: 'form-control-label'\n    b.use :input, class: 'custom-range', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n\n  # Input Group - custom component\n  # see example app and config at https://github.com/rafaelfranca/simple_form-bootstrap\n  # config.wrappers :input_group, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n  #   b.use :html5\n  #   b.use :placeholder\n  #   b.optional :maxlength\n  #   b.optional :minlength\n  #   b.optional :pattern\n  #   b.optional :min_max\n  #   b.optional :readonly\n  #   b.use :label, class: 'form-control-label'\n  #   b.wrapper :input_group_tag, tag: 'div', class: 'input-group' do |ba|\n  #     ba.optional :prepend\n  #     ba.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'\n  #     ba.optional :append\n  #   end\n  #   b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback d-block' }\n  #   b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  # end\n\n\n  # Floating Labels form\n  #\n  # floating labels default_wrapper\n  config.wrappers :floating_labels_form, tag: 'div', class: 'form-label-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.use :placeholder\n    b.optional :maxlength\n    b.optional :minlength\n    b.optional :pattern\n    b.optional :min_max\n    b.optional :readonly\n    b.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :label, class: 'form-control-label'\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n  # custom multi select\n  config.wrappers :floating_labels_select, tag: 'div', class: 'form-label-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|\n    b.use :html5\n    b.optional :readonly\n    b.use :input, class: 'custom-select custom-select-lg', error_class: 'is-invalid', valid_class: 'is-valid'\n    b.use :label, class: 'form-control-label'\n    b.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }\n    b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }\n  end\n\n\n  # The default wrapper to be used by the FormBuilder.\n  config.default_wrapper = :vertical_form\n\n  # Custom wrappers for input types. This should be a hash containing an input\n  # type as key and the wrapper that will be used for all inputs with specified type.\n  config.wrapper_mappings = {\n    boolean:       :vertical_boolean,\n    check_boxes:   :vertical_collection,\n    date:          :vertical_multi_select,\n    datetime:      :vertical_multi_select,\n    file:          :vertical_file,\n    radio_buttons: :vertical_collection,\n    range:         :vertical_range,\n    time:          :vertical_multi_select\n  }\n\n  # enable custom form wrappers\n  # config.wrapper_mappings = {\n  #   boolean:       :custom_boolean,\n  #   check_boxes:   :custom_collection,\n  #   date:          :custom_multi_select,\n  #   datetime:      :custom_multi_select,\n  #   file:          :custom_file,\n  #   radio_buttons: :custom_collection,\n  #   range:         :custom_range,\n  #   time:          :custom_multi_select\n  # }\nend\n"
  },
  {
    "path": "config/initializers/wrap_parameters.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# This file contains settings for ActionController::ParamsWrapper which\n# is enabled by default.\n\n# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.\nActiveSupport.on_load(:action_controller) do\n  wrap_parameters format: [:json]\nend\n\n# To enable root element in JSON for ActiveRecord objects.\n# ActiveSupport.on_load(:active_record) do\n#   self.include_root_in_json = true\n# end\n"
  },
  {
    "path": "config/locales/en.yml",
    "content": "en:\n  comment: \"Comment\"\n  edit: 'Edit'\n  destroy: 'Destroy'\n  announce_at: \"Published at \"\n  qr_code: 'QR Code'\n  visited_count: \"Viewed \"\n  nocontent: \"No More Content Here\"\n  prev: '← Prev Post'\n  next: 'Next Post →'\n  none: 'None'\n  qrcodetips: 'Continue reading with mobile phone or share it to Wechat Puppy'\n\n  views:\n    pagination:\n      previous: 'Prev'\n      next: 'Next'\n\n  home:\n    no_blog_here: 'There is no post here'\n    read: 'Read More >>'\n    created_at: 'Posted At'\n    recent: 'RECENT'\n\n  subscribes:\n    title: 'SUBSCRIBE'\n    email: 'Email Subscribe'\n    wechat: 'Wechat Scan QR Code'\n    rss: 'RSS Subscribe'\n    submit: 'Submit'\n    submit_success: 'Submit Successfully'\n\n\n  head:\n    home: 'Home'\n    tech: 'Tech'\n    life: 'Life'\n    creator: 'Startup'\n    timeline: 'Timeline'\n    about: 'About'\n\n  title:\n    home: 'Home'\n    timeline: 'Timeline'\n    about: 'About Me'\n\n  comment_attributes:\n    email: 'Format Validation Failed'\n  comment_placeholder:\n    content: 'Write your viewpoint...'\n    name: 'Your Name'\n    email: 'Your Email'\n    submit: 'Submit'\n    submitting: 'Submitting'\n    publish_success: 'Publish Successfully'\n    publish_fail: 'Publish Failed'\n\n  archive:\n    search: 'Search'\n\n  admin:\n    new_post: 'New Post'\n    posts: 'Manage Posts'\n    posts_attributes:\n      title: 'Title'\n      type: 'Type'\n      labels: 'Labels'\n      already_labels: 'Labels Haven: '\n      content: 'Content'\n      preview: 'Preview'\n      upload_photo: 'Upload Photo'\n      submit: 'Submit'\n    posts_head:\n      title: 'Title'\n      summary: 'Summary'\n      operation: 'Operation'\n\n    comments: 'Manage Comment'\n    comments_head:\n      name: 'Name'\n      email: 'Email'\n      content: 'Content'\n      created_at: 'Created at'\n      operation: 'Operation'\n      reply: 'Reply'\n      destroy: 'Destroy'\n\n    subscribes: 'Manage Subscribes'\n    subscribes_head:\n      email: 'Email'\n      enable: 'Enable Flag'\n      created_at: 'Created at'\n      operation: 'Operation'\n      op:\n        enable: 'Enable'\n        disable: 'Disable'\n\n    back: 'Back to Home'\n    logout: 'Logout'\n    dashboard:\n      head: 'System Information'\n      info: 'Info'\n      data: 'Data'\n      posts_count: 'Posts Count'\n      comments_count: 'Comments Count'\n      visited_count: 'Visited Count'\n      subscribes_count: 'Subscribes Count'\n    session:\n      title: 'Login dashboard'\n      username: 'Username'\n      username_placeholder: 'Admin username'\n      password: 'Password'\n      password_placeholder: 'Admin password'\n      login_button: 'Login now'\n      login_success: 'Login successfully'\n      no_configuration: 'There is no admin user, login failed'\n      username_error: 'Admin username error'\n      password_error: 'Admin password error'\n"
  },
  {
    "path": "config/locales/simple_form.en.yml",
    "content": "en:\n  simple_form:\n    \"yes\": 'Yes'\n    \"no\": 'No'\n    required:\n      text: 'required'\n      mark: '*'\n      # You can uncomment the line below if you need to overwrite the whole required html.\n      # When using html, text and mark won't be used.\n      # html: '<abbr title=\"required\">*</abbr>'\n    error_notification:\n      default_message: \"Some errors were found, please take a look:\"\n    # Labels and hints examples\n    # labels:\n    #   password: 'Password'\n    #   user:\n    #     new:\n    #       email: 'E-mail para efetuar o sign in.'\n    #     edit:\n    #       email: 'E-mail.'\n    # hints:\n    #   username: 'User name to sign in.'\n    #   password: 'No special characters, please.'\n\n"
  },
  {
    "path": "config/locales/simple_form.zh-CN.yml",
    "content": "en:\n  simple_form:\n    \"yes\": '是'\n    \"no\": '否'\n    required:\n      text: '必须的'\n      mark: '*'\n      # You can uncomment the line below if you need to overwrite the whole required html.\n      # When using html, text and mark won't be used.\n      # html: '<abbr title=\"required\">*</abbr>'\n    error_notification:\n      default_message: \"请检查以下问题:\"\n    # Examples\n    # labels:\n    #   defaults:\n    #     password: 'Password'\n    #   user:\n    #     new:\n    #       email: 'E-mail to sign in.'\n    #     edit:\n    #       email: 'E-mail.'\n    # hints:\n    #   defaults:\n    #     username: 'User name to sign in.'\n    #     password: 'No special characters, please.'\n    # include_blanks:\n    #   defaults:\n    #     age: 'Rather not say'\n    # prompts:\n    #   defaults:\n    #     age: 'Select your age'\n"
  },
  {
    "path": "config/locales/zh-CN.yml",
    "content": "zh-CN:\n  comment: \"评论\"\n  edit: '编辑'\n  destroy: '删除'\n  announce_at: \"发表于 \"\n  qr_code: '二维码'\n  visited_count: \"浏览数 \"\n  nocontent: \"没有新的内容\"\n  prev: '← 上一篇'\n  next: '下一篇 →'\n  none: '无'\n  qrcodetips: '扫一扫继续阅读或者帮我分享至朋友圈'\n\n  views:\n    pagination:\n      previous: '上一页'\n      next: '下一页'\n\n  home:\n    no_blog_here: '这里没有博客'\n    read: '阅读全文 >>'\n    created_at: '发表于 '\n    recent: '近期博客'\n\n  subscribes:\n    title: '订阅我'\n    email: '邮件订阅'\n    wechat: '微信扫一扫'\n    rss: 'RSS 订阅'\n    submit: '提交'\n    submit_success: '订阅成功'\n\n\n  head:\n    home: '首页'\n    tech: '技术'\n    life: '生活'\n    creator: '创业'\n    timeline: '时间线'\n    about: '关于我'\n\n  title:\n    home: '首页'\n    timeline: '时间线'\n    about: '关于我'\n\n\n  activerecord:\n    errors:\n      models:\n        comment:\n          attributes:\n            email:\n              blank: '邮件地址错误'\n              invalid: '邮件地址错误'\n            name:\n              blank: '请输入你的名字'\n              invalid: '请输入你的名字'\n            content:\n              blank: '请输入有价值的评论'\n              invalid: '请输入有价值的评论'\n              too_short: '请输入有价值的评论'\n  comment_placeholder:\n    content: '发表你的评论...'\n    name: '名字'\n    email: '邮箱, your@example.com'\n    submit: '提交'\n    submitting: '提交中'\n    publish_success: '发表成功'\n    publish_fail: '发表失败'\n\n  archive:\n    search: '搜索博客标题'\n\n  admin:\n    new_post: '新建博客'\n    posts: '博客管理'\n    posts_attributes:\n      title: '标题'\n      type: '分类'\n      labels: '标签'\n      already_labels: '已有标签: '\n      content: '正文'\n      preview: '预览'\n      upload_photo: '上传图片'\n      submit: '提交'\n    posts_head:\n      title: '标题'\n      summary: '概要'\n      operation: '操作'\n\n    comments: '评论管理'\n    comments_head:\n      name: '名字'\n      email: '邮箱'\n      content: '内容'\n      created_at: '创建时间'\n      operation: '操作'\n      reply: '回复'\n      destroy: '删除'\n\n    subscribes: '订阅管理'\n    subscribes_head:\n      email: '邮箱'\n      enable: '是否启用'\n      created_at: '创建时间'\n      operation: '操作'\n      op:\n        enable: '启用'\n        disable: '禁用'\n\n    back: '返回首页'\n    logout: '退出'\n    dashboard:\n      head: '系统信息'\n      info: '信息'\n      data: '数据'\n      posts_count: '文章数'\n      comments_count: '评论数'\n      visited_count: '浏览数'\n      subscribes_count: '订阅数'\n    session:\n      title: '登录后台'\n      username: '用户名'\n      username_placeholder: '管理员账号'\n      password: '密码'\n      password_placeholder: '管理员密码'\n      login_button: '登录'\n      login_success: '登录成功'\n      no_configuration: '系统未配置管理员, 无法登录'\n      username_error: '用户名错误'\n      password_error: '密码错误'\n"
  },
  {
    "path": "config/logrotate.conf.example",
    "content": "# truncate your rails log every day\n# Usage:\n# `cp logrotate.conf.example /etc/logrotate.d/wblog`\n/data/www/wblog/current/log/*.log {\n  daily\n  missingok\n  rotate 7\n  compress\n  delaycompress\n  notifempty\n  copytruncate\n  su ruby ruby\n}\n"
  },
  {
    "path": "config/monit.conf.example",
    "content": "# Watch your rails app & sidekiq process and restart it automatically\n# Usage:\n# `sudo apt-get install monit -y`\n# `cp monit.conf.example /etc/monit/conf.d/`\n# `service monit restart`\ncheck process wblog_puma\n  with pidfile /data/www/wblog/shared/tmp/pids/puma.pid\n  start program = \"/bin/sh -c 'cd /data/www/wblog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV=\"production\" bundle exec puma -q -d -e production -C config/puma.rb'\" as uid ruby and gid ruby with timeout 90 seconds\n  stop program = \"/bin/sh -c 'cd /data/www/wblog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV=\"production\" bundle exec pumactl -F /data/www/wblog/current/config/puma.rb stop'\" as uid ruby and gid ruby with timeout 90 seconds\n  group wblog\n\ncheck process wblog_sidekiq\n  with pidfile /data/www/wblog/shared/tmp/pids/sidekiq.pid\n  start program = \"/bin/sh -c 'cd /data/www/wblog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV=\"production\" bundle exec sidekiq -d -e production  -C /data/www/wblog/current/config/sidekiq.yml -i 0 -P /data/www/wblog/shared/tmp/pids/sidekiq.pid -L /data/www/wblog/current/log/sidekiq.log'\" as uid ruby and gid ruby with timeout 90 seconds\n  stop program = \"/bin/sh -c 'cd /data/www/wblog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV=\"production\" bundle exec sidekiqctl stop /data/www/wblog/shared/tmp/pids/sidekiq.pid 11'\" as uid ruby and gid ruby with timeout 90 seconds\n  group wblog\n\n#check process wblog_clockwork\n#  with pidfile /data/www/wblog/shared/tmp/pids/clockworkd.clock.pid\n#  start program = \"/bin/sh -c 'cd /data/www/wblog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV=production bundle exec clockworkd -c /data/www/wblog/current/config/clock.rb -i clock -d /data/www/wblog/current --pid-dir /data/www/wblog/shared/tmp/pids --log --log-dir /data/www/wblog/shared/log start'\" as uid ruby and gid ruby with timeout 90 seconds\n#  stop program = \"/bin/sh -c 'cd /data/www/wblog/current; PATH=bin:/home/ruby/.rbenv/shims:/home/ruby/.rbenv/bin:$PATH LC_ALL=en_US.UTF-8 RAILS_ENV=production bundle exec clockworkd -c /data/www/wblog/current/config/clock.rb -i clock -d /data/www/wblog/current --pid-dir /data/www/wblog/shared/tmp/pids --log --log-dir /data/www/wblog/shared/log stop'\" as uid ruby and gid ruby with timeout 90 seconds\n#  group wblog\n"
  },
  {
    "path": "config/nginx.conf.example",
    "content": "upstream wblog {\n  server unix:///data/www/wblog/shared/tmp/sockets/puma.sock fail_timeout=0;\n}\n\nserver {\n  listen 80;\n  server_name example.com;\n  root /data/www/wblog/current/public;\n\n  location ^~ /packs/ {\n    gzip_static on;\n    expires max;\n    add_header Cache-Control public;\n  }\n\n  location /cable {\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"Upgrade\";\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_pass http://wblog;\n  }\n\n  location ~ ^/(uploads)/  {\n    expires max;\n    break;\n  }\n\n\n  try_files $uri/index.html $uri @wblog;\n  location @wblog {\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header Host $http_host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_redirect off;\n    proxy_pass http://wblog;\n  }\n\n  error_page 500 502 503 504 /500.html;\n  client_max_body_size 20M;\n  keepalive_timeout 10;\n}\n"
  },
  {
    "path": "config/nginx.ssl.conf.example",
    "content": "upstream wblog {\n  server unix:///data/www/wblog/shared/tmp/sockets/puma.sock fail_timeout=0;\n}\n\nserver {\n  listen 80;\n  # FIXME: update your domain here\n  server_name example.com;\n  return 301 https://$server_name$request_uri;\n}\n\nserver {\n  listen 443 ssl;\n  # FIXME: update your domain here\n  server_name example.com;\n  root /data/www/wblog/current/public;\n\n  # FIXME: update your domain here\n  ssl_certificate  /etc/nginx/sslkeys/yourdomain.com.key.pem;\n  ssl_certificate_key /etc/nginx/sslkeys/yourdomain.com.key;\n  ssl_dhparam  /etc/nginx/sslkeys/dhparam.pem;\n\n  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n\n  # ssl optimizations\n  ssl_session_cache shared:SSL:30m;\n  ssl_session_timeout 30m;\n  add_header Strict-Transport-Security \"max-age=31536000\";\n\n  ssl_prefer_server_ciphers on;\n  ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';\n\n  location ^~ /packs/ {\n    gzip_static on;\n    expires max;\n    add_header Cache-Control public;\n  }\n\n  location ~ ^/(uploads)/  {\n    expires max;\n    break;\n  }\n\n  location /cable {\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"Upgrade\";\n    proxy_set_header X-Forwarded-Proto $scheme;\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_pass http://wblog;\n  }\n\n  try_files $uri/index.html $uri @wblog;\n  location @wblog {\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header Host $http_host;\n    proxy_set_header X-Forwarded-Proto $scheme;\n    proxy_redirect off;\n    proxy_pass http://wblog;\n  }\n\n  error_page 500 502 503 504 /500;\n  error_page 404 /404;\n  client_max_body_size 20M;\n  keepalive_timeout 10;\n}\n"
  },
  {
    "path": "config/puma.rb",
    "content": "# require 'puma/daemon'\n\nif ENV['RAILS_ENV'] == 'production'\n  app_root = '/data/www/wblog/shared'\n  pidfile \"#{app_root}/tmp/pids/puma.pid\"\n  state_path \"#{app_root}/tmp/pids/puma.state\"\n  bind \"unix://#{app_root}/tmp/sockets/puma.sock\"\n  activate_control_app \"unix://#{app_root}/tmp/sockets/pumactl.sock\"\n  daemonize true\n  workers 2\n  threads 8, 16\n  prune_bundler\n\n  stdout_redirect \"#{app_root}/log/puma_access.log\", \"#{app_root}/log/puma_error.log\", true\nelse\n  plugin :tmp_restart\nend\n"
  },
  {
    "path": "config/routes.rb",
    "content": "require 'sidekiq/web'\nSidekiq::Web.set :session_secret, Rails.application.secrets[:secret_key_base]\n\nclass AdminConstraint\n  def matches?(request)\n    return false unless request.session[:current_admin_id].present?\n    admin = Administrator.find_by(id: request.session[:current_admin_id])\n    admin.present?\n  end\nend\n\nRails.application.routes.draw do\n\n  resources :blogs, only: [:show, :edit] do\n    resources :likes, only: [:index, :create, :destroy]\n    resources :comments, only: [:index, :create] do\n      collection do\n        get :refresh\n      end\n    end\n  end\n\n  resources :archives, only: [:index]\n  resources :photos, only: [:create]\n\n  get '/about', to: 'home#about'\n\n  namespace :admin do\n    get 'login', to: 'sessions#new', as: :login\n    post 'login', to: 'sessions#create'\n    delete 'logout', to: 'sessions#destroy', as: :logout\n    resource :account, only: [:edit, :update]\n\n    resources :posts, only: [:index, :new, :edit, :create, :update, :destroy] do\n      collection do\n        post :preview\n      end\n      resources :comments, only: [:index, :destroy]\n    end\n\n    resources :all_comments, only: [:index, :destroy]\n    resources :labels\n\n\n    root to: 'dashboard#index'\n  end\n\n  mount Sidekiq::Web => '/sidekiq', constraints: AdminConstraint.new\n  #mount ActionCable.server => '/cable'\n  root to: 'home#index'\nend\n"
  },
  {
    "path": "config/secret.yml",
    "content": "development:\n  secret_key_base: df19bc95da417d0dc49e659249d64706168116e77fb348533c32d817cb0a36fe5b6e88303ad680448ddbea65f418a5b8a7293b8c9cbc1bd0f4c4469284e80af7\n\ntest:\n  secret_key_base: 0d0438783b9226801805416e41998780841716fab8794206f17742a7724301dbc67c629a15037afd4df58782c02fe0a91912cbdc7e48453b3aa1fbb6eeacd3bb\n\n# Do not keep production secrets in the repository,\n# instead read values from the environment.\nproduction:\n  secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>\n"
  },
  {
    "path": "config/sidekiq.yml",
    "content": ":concurrency: 5\n:queues:\n  - critical\n  - default\n  - low\nproduction:\n  :concurrency: 25\n"
  },
  {
    "path": "config/spring.rb",
    "content": "%w(\n  .ruby-version\n  .rbenv-vars\n  tmp/restart.txt\n  tmp/caching-dev.txt\n  config/application.yml\n).each { |path| Spring.watch(path) }\n"
  },
  {
    "path": "config/storage.yml",
    "content": "test:\n  service: Disk\n  root: <%= Rails.root.join(\"tmp/storage\") %>\n\nlocal:\n  service: Disk\n  root: <%= Rails.root.join(\"storage\") %>\n\n# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)\n# amazon:\n#   service: S3\n#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>\n#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>\n#   region: us-east-1\n#   bucket: your_own_bucket\n\n# Remember not to checkin your GCS keyfile to a repository\n# google:\n#   service: GCS\n#   project: your_project\n#   credentials: <%= Rails.root.join(\"path/to/gcs.keyfile\") %>\n#   bucket: your_own_bucket\n\n# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)\n# microsoft:\n#   service: AzureStorage\n#   storage_account_name: your_account_name\n#   storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>\n#   container: your_container_name\n\n# mirror:\n#   service: Mirror\n#   primary: local\n#   mirrors: [ amazon, google, microsoft ]\n"
  },
  {
    "path": "config.ru",
    "content": "# This file is used by Rack-based servers to start the application.\n\nrequire_relative \"config/environment\"\n\nrun Rails.application\nRails.application.load_server\n"
  },
  {
    "path": "db/migrate/20160420082319_create_posts.rb",
    "content": "class CreatePosts < ActiveRecord::Migration[6.1]\n  def change\n    create_table :posts do |t|\n      t.string :title\n      t.text :content\n      t.integer :visited_count, default: 0\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20160420082536_create_comments.rb",
    "content": "class CreateComments < ActiveRecord::Migration[6.1]\n  def change\n    create_table :comments do |t|\n      t.string :name\n      t.string :email\n      t.text :content\n      t.references :post\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20160420082629_create_labels.rb",
    "content": "class CreateLabels < ActiveRecord::Migration[6.1]\n  def change\n    create_table :labels do |t|\n      t.string :name\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20160420082734_create_likes.rb",
    "content": "class CreateLikes < ActiveRecord::Migration[6.1]\n  def change\n    create_table :likes do |t|\n      t.references :post\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20160420082811_create_photos.rb",
    "content": "class CreatePhotos < ActiveRecord::Migration[6.1]\n  def change\n    create_table :photos do |t|\n      t.string :image\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20160421035040_create_join_table_post_label.rb",
    "content": "class CreateJoinTablePostLabel < ActiveRecord::Migration[6.1]\n  def change\n    create_join_table :posts, :labels do |t|\n      t.index [:post_id, :label_id]\n      t.index [:label_id, :post_id]\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210614151036_create_active_storage_tables.active_storage.rb",
    "content": "# This migration comes from active_storage (originally 20170806125915)\nclass CreateActiveStorageTables < ActiveRecord::Migration[5.2]\n  def change\n    create_table :active_storage_blobs do |t|\n      t.string   :key,          null: false\n      t.string   :filename,     null: false\n      t.string   :content_type\n      t.text     :metadata\n      t.string   :service_name, null: false\n      t.bigint   :byte_size,    null: false\n      t.string   :checksum,     null: false\n      t.datetime :created_at,   null: false\n\n      t.index [ :key ], unique: true\n    end\n\n    create_table :active_storage_attachments do |t|\n      t.string     :name,     null: false\n      t.references :record,   null: false, polymorphic: true, index: false\n      t.references :blob,     null: false\n\n      t.datetime :created_at, null: false\n\n      t.index [ :record_type, :record_id, :name, :blob_id ], name: \"index_active_storage_attachments_uniqueness\", unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n\n    create_table :active_storage_variant_records do |t|\n      t.belongs_to :blob, null: false, index: false\n      t.string :variation_digest, null: false\n\n      t.index %i[ blob_id variation_digest ], name: \"index_active_storage_variant_records_uniqueness\", unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n  end\nend\n"
  },
  {
    "path": "db/migrate/20210614151102_create_administrators.rb",
    "content": "class CreateAdministrators < ActiveRecord::Migration[6.1]\n  def change\n    create_table :administrators do |t|\n      t.string :name\n      t.string :password_digest\n\n      t.timestamps\n    end\n    add_index :administrators, :name, unique: true\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250120142353_add_service_name_to_active_storage_blobs.active_storage.rb",
    "content": "# This migration comes from active_storage (originally 20190112182829)\nclass AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]\n  def up\n    return unless table_exists?(:active_storage_blobs)\n\n    unless column_exists?(:active_storage_blobs, :service_name)\n      add_column :active_storage_blobs, :service_name, :string\n\n      if configured_service = ActiveStorage::Blob.service.name\n        ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)\n      end\n\n      change_column :active_storage_blobs, :service_name, :string, null: false\n    end\n  end\n\n  def down\n    return unless table_exists?(:active_storage_blobs)\n\n    remove_column :active_storage_blobs, :service_name\n  end\nend\n"
  },
  {
    "path": "db/migrate/20250120142354_create_active_storage_variant_records.active_storage.rb",
    "content": "# This migration comes from active_storage (originally 20191206030411)\nclass CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]\n  def change\n    return unless table_exists?(:active_storage_blobs)\n\n    # Use Active Record's configured type for primary key\n    create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t|\n      t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type\n      t.string :variation_digest, null: false\n\n      t.index %i[ blob_id variation_digest ], name: \"index_active_storage_variant_records_uniqueness\", unique: true\n      t.foreign_key :active_storage_blobs, column: :blob_id\n    end\n  end\n\n  private\n    def primary_key_type\n      config = Rails.configuration.generators\n      config.options[config.orm][:primary_key_type] || :primary_key\n    end\n\n    def blobs_primary_key_type\n      pkey_name = connection.primary_key(:active_storage_blobs)\n      pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name }\n      pkey_column.bigint? ? :bigint : pkey_column.type\n    end\nend\n"
  },
  {
    "path": "db/migrate/20250120142355_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb",
    "content": "# This migration comes from active_storage (originally 20211119233751)\nclass RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0]\n  def change\n    return unless table_exists?(:active_storage_blobs)\n\n    change_column_null(:active_storage_blobs, :checksum, true)\n  end\nend\n"
  },
  {
    "path": "db/schema.rb",
    "content": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema[7.1].define(version: 2025_01_20_142355) do\n  # These are extensions that must be enabled in order to support this database\n  enable_extension \"plpgsql\"\n\n  create_table \"active_storage_attachments\", force: :cascade do |t|\n    t.string \"name\", null: false\n    t.string \"record_type\", null: false\n    t.bigint \"record_id\", null: false\n    t.bigint \"blob_id\", null: false\n    t.datetime \"created_at\", precision: nil, null: false\n    t.index [\"blob_id\"], name: \"index_active_storage_attachments_on_blob_id\"\n    t.index [\"record_type\", \"record_id\", \"name\", \"blob_id\"], name: \"index_active_storage_attachments_uniqueness\", unique: true\n  end\n\n  create_table \"active_storage_blobs\", force: :cascade do |t|\n    t.string \"key\", null: false\n    t.string \"filename\", null: false\n    t.string \"content_type\"\n    t.text \"metadata\"\n    t.string \"service_name\", null: false\n    t.bigint \"byte_size\", null: false\n    t.string \"checksum\"\n    t.datetime \"created_at\", precision: nil, null: false\n    t.index [\"key\"], name: \"index_active_storage_blobs_on_key\", unique: true\n  end\n\n  create_table \"active_storage_variant_records\", force: :cascade do |t|\n    t.bigint \"blob_id\", null: false\n    t.string \"variation_digest\", null: false\n    t.index [\"blob_id\", \"variation_digest\"], name: \"index_active_storage_variant_records_uniqueness\", unique: true\n  end\n\n  create_table \"administrators\", force: :cascade do |t|\n    t.string \"name\"\n    t.string \"password_digest\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"name\"], name: \"index_administrators_on_name\", unique: true\n  end\n\n  create_table \"comments\", force: :cascade do |t|\n    t.string \"name\"\n    t.string \"email\"\n    t.text \"content\"\n    t.bigint \"post_id\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"post_id\"], name: \"index_comments_on_post_id\"\n  end\n\n  create_table \"labels\", force: :cascade do |t|\n    t.string \"name\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"labels_posts\", id: false, force: :cascade do |t|\n    t.bigint \"post_id\", null: false\n    t.bigint \"label_id\", null: false\n    t.index [\"label_id\", \"post_id\"], name: \"index_labels_posts_on_label_id_and_post_id\"\n    t.index [\"post_id\", \"label_id\"], name: \"index_labels_posts_on_post_id_and_label_id\"\n  end\n\n  create_table \"likes\", force: :cascade do |t|\n    t.bigint \"post_id\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"post_id\"], name: \"index_likes_on_post_id\"\n  end\n\n  create_table \"photos\", force: :cascade do |t|\n    t.string \"image\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"posts\", force: :cascade do |t|\n    t.string \"title\"\n    t.text \"content\"\n    t.integer \"visited_count\", default: 0\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  add_foreign_key \"active_storage_attachments\", \"active_storage_blobs\", column: \"blob_id\"\n  add_foreign_key \"active_storage_variant_records\", \"active_storage_blobs\", column: \"blob_id\"\nend\n"
  },
  {
    "path": "db/seeds.rb",
    "content": "# This file should contain all the record creation needed to seed the database with default values.\n# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).\n\nrequire 'faker'\n\n# Clear existing data\nputs \"Clearing existing data...\"\nLike.delete_all\nComment.delete_all\nPost.all.each { |post| post.labels.clear } # Remove associations\nPost.delete_all\nLabel.delete_all\nAdministrator.delete_all\n\nputs 'Creating admin user...'\nAdministrator.create!(\n  name: 'admin',\n  password: 'admin'\n)\n\nputs 'Creating labels...'\nlabels = [\n  'Technology',\n  'Programming',\n  'Ruby on Rails',\n  'Web Development',\n  'Software Engineering'\n].map do |name|\n  Label.create!(name: name)\nend\n\nputs 'Creating blog posts...'\nposts = 10.times.map do |i|\n  post = Post.create!(\n    title: Faker::Lorem.unique.sentence(word_count: 5),\n    content: \"## #{Faker::Lorem.sentence}\\n\\n\" +\n             Faker::Markdown.random +\n             \"\\n\\n### #{Faker::Lorem.sentence}\\n\\n\" +\n             Faker::Lorem.paragraphs(number: 3).join(\"\\n\\n\") +\n             \"\\n\\n```ruby\\n\" +\n             \"class Example\\n  def test\\n    puts 'Hello World'\\n  end\\nend\\n\" +\n             \"```\\n\\n\" +\n             Faker::Lorem.paragraphs(number: 2).join(\"\\n\\n\"),\n    created_at: Faker::Date.between(from: 1.year.ago, to: Date.today),\n    visited_count: 0\n  )\n  \n  # Associate 1-3 random labels with each post\n  post.labels << labels.sample(rand(1..3))\n  post\nend\n\nputs 'Creating comments...'\n15.times do\n  post = posts.sample\n  Comment.create!(\n    post: post,\n    name: Faker::Name.name,\n    email: Faker::Internet.email,\n    content: Faker::Lorem.paragraph(sentence_count: 2, supplemental: true, random_sentences_to_add: 3),\n    created_at: Faker::Time.between(from: post.created_at, to: Date.today)\n  )\nend\n\nputs 'Creating likes...'\n20.times do\n  Like.create!(\n    post: posts.sample\n  )\nend\n\n\nputs 'Seed data created successfully!'"
  },
  {
    "path": "doc/.gitkeep",
    "content": "\n"
  },
  {
    "path": "lib/assets/.keep",
    "content": ""
  },
  {
    "path": "lib/markdown.rb",
    "content": "require 'rouge'\nrequire 'rouge/plugins/redcarpet'\n\nclass CodeHTML < Redcarpet::Render::HTML\n  include Rouge::Plugins::Redcarpet\n\n  def initialize(extensions = {})\n    super extensions.merge(link_attributes: { target: \"_blank\" })\n  end\nend\n"
  },
  {
    "path": "lib/tasks/.keep",
    "content": ""
  },
  {
    "path": "lib/templates/slim/scaffold/_form.html.slim",
    "content": "= simple_form_for(@<%= singular_table_name %>) do |f|\n  = f.error_notification\n  = f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present?\n\n  .form-inputs\n<%- attributes.each do |attribute| -%>\n    = f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %>\n<%- end -%>\n\n  .form-actions\n    = f.button :submit\n"
  },
  {
    "path": "log/.keep",
    "content": ""
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"wblog\",\n  \"private\": true,\n  \"dependencies\": {\n    \"@fortawesome/fontawesome-free\": \"^5.15.4\",\n    \"@hotwired/stimulus\": \"^3.2.2\",\n    \"@hotwired/turbo-rails\": \"^8.0.12\",\n    \"@rails/actioncable\": \"^6.0.0\",\n    \"@rails/activestorage\": \"^6.0.0\",\n    \"@rails/ujs\": \"^6.0.0\",\n    \"@ttskch/select2-bootstrap4-theme\": \"^1.5.2\",\n    \"admin-lte\": \"^4.0.0-beta3\",\n    \"bootstrap\": \"^4.3.1\",\n    \"daterangepicker\": \"^3.0.5\",\n    \"jquery\": \"^3.3.1\",\n    \"js-cookie\": \"^2.2.1\",\n    \"moment\": \"^2.29.2\",\n    \"moment-timezone\": \"^0.5.33\",\n    \"popper.js\": \"^1.14.7\",\n    \"sass\": \"^1.83.1\",\n    \"select2\": \"^4.1.0-rc.0\",\n    \"tempusdominus-core\": \"^5.19.0\"\n  },\n  \"version\": \"0.1.0\",\n  \"devDependencies\": {\n    \"esbuild\": \"^0.24.2\"\n  },\n  \"scripts\": {\n    \"build:css\": \"sass ./app/assets/stylesheets/application.scss:./app/assets/builds/application.css ./app/assets/stylesheets/admin.scss:./app/assets/builds/admin.css --no-source-map --load-path=node_modules --silence-deprecation=import --quiet-deps\",\n    \"build\": \"esbuild app/javascript/*.js --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: [\n    require('postcss-import'),\n    require('postcss-flexbugs-fixes'),\n    require('postcss-preset-env')({\n      autoprefixer: {\n        flexbox: 'no-2009'\n      },\n      stage: 3\n    })\n  ]\n}\n"
  },
  {
    "path": "public/404.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The page you were looking for doesn't exist (404)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/404.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>The page you were looking for doesn't exist.</h1>\n      <p>You may have mistyped the address or the page may have moved.</p>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/422.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The change you wanted was rejected (422)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/422.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>The change you wanted was rejected.</h1>\n      <p>Maybe you tried to change something you didn't have access to.</p>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/500.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>We're sorry, but something went wrong (500)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/500.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>We're sorry, but something went wrong.</h1>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "public/robots.txt",
    "content": "# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file\n"
  },
  {
    "path": "spec/controllers/admin/comments_controller_spec.rb",
    "content": "require 'rails_helper'\n\nRSpec.describe Admin::CommentsController, type: :controller do\nend\n"
  },
  {
    "path": "spec/controllers/admin/dashboard_controller_spec.rb",
    "content": "require 'rails_helper'\n\nRSpec.describe Admin::DashboardController, type: :controller do\n\n  before do\n    session[:login] = true\n  end\n  describe \"GET 'index'\" do\n    it \"returns http success\" do\n      get 'index'\n      expect(response).to be_successful\n    end\n  end\n\nend\n"
  },
  {
    "path": "spec/controllers/admin/posts_controller_spec.rb",
    "content": "require 'rails_helper'\n\nRSpec.describe Admin::PostsController, type: :controller do\n\n  before do\n    session[:login] = true\n  end\n  it \"preview should return ok\" do\n    post :preview\n    expect(response.body).to eq(\"\")\n    post :preview, params: { content: '123' }\n    expect(response.body).to eq(\"<p>123</p>\\n\")\n  end\n\n  it \"update\" do\n    post = create(:post)\n\n    patch 'update', params: { id: post.id, labels: 'think, go ' }\n    expect(post.reload.labels.size).to eq(2)\n  end\n\n\n  it \"destroy\" do\n    post = create(:post)\n    label = create(:label)\n    post.labels << label\n    post.save!\n    expect(label.posts.size).to eq(1)\n\n    delete 'destroy', params: { id: post.id }\n    expect( Post.all.size ).to eq(0)\n    expect( label.reload.posts.size ).to eq(0)\n  end\n\n  it \"create\" do\n    post_params = attributes_for(:post)\n    post 'create', params: post_params.merge( labels: 'think, go ' )\n\n    post = Post.first\n    expect( post.labels.size ).to eq(2)\n  end\n\n  it \"create fail and see labels_content\" do\n    post 'create', params: { labels: 'think, go ' }\n    expect( assigns(:post).labels_content ).to eq('think, go')\n  end\nend\n"
  },
  {
    "path": "spec/controllers/admin/sessions_controller_spec.rb",
    "content": "require 'rails_helper'\n\nRSpec.describe Admin::SessionsController, type: :controller do\n\n  before do\n    session[:login] = true\n  end\n  describe \"GET 'new'\" do\n    it \"returns http success\" do\n      get 'new'\n      expect(response).to be_successful\n    end\n  end\n\nend\n"
  },
  {
    "path": "spec/controllers/archives_controller_spec.rb",
    "content": "require 'rails_helper'\n\nRSpec.describe ArchivesController, type: :controller do\n\n  it \"get index\" do\n    create_list(:post_list, 3)\n    get :index\n    expect(response.status).to eq(200)\n  end\n\nend\n"
  },
  {
    "path": "spec/controllers/blogs_controller_spec.rb",
    "content": "require 'rails_helper'\n\nRSpec.describe BlogsController, type: :controller do\n\n  describe 'get INDEX' do\n    it 'index should get by order desc' do\n      create_list(:post_list, 5)\n\n      first = Post.first\n      first.update(title: 'first')\n\n      second = Post.order(created_at: :desc)[1]\n      get :index\n      expect(assigns(:newest)).to eq(first)\n      expect(assigns(:recent)[0]).to eq(second)\n    end\n  end\n\n  describe \"get SHOW\" do\n\n    it 'get POST' do\n      post = create(:post)\n      comment1 = build(:comment_no_post)\n      comment1.post = post\n      comment1.save!\n      comment2 = build(:comment_no_post)\n      comment2.post = post\n      comment2.save!\n\n      get :show, params: { id: post.id }\n      expect(assigns(:comments)[0]).to eq(comment2)\n      expect(assigns(:comments)[1]).to eq(comment1)\n    end\n\n    it \"#prev, #next\" do\n      posts = create_list(:post_list, 3)\n      posts = Post.order(created_at: :asc)\n      selected = posts[1]\n      s_prev = posts[0]\n      s_next = posts[2]\n      get :show, params: { id: selected.id }\n      expect(assigns(:prev)).to eq(s_prev)\n      expect(assigns(:next)).to eq(s_next)\n\n      # 下界\n      selected = posts[0]\n      get :show, params: { id: selected.id }\n      expect(assigns(:prev)).to be_nil\n      expect(assigns(:next)).to eq(posts[1])\n\n      # 测试上界\n      selected = posts[2]\n      get :show, params: { id: selected.id }\n      expect(assigns(:prev)).to eq(posts[1])\n      expect(assigns(:next)).to be_nil\n\n      # 测试未来时间\n      create(:post, created_at: Time.now + 100)\n      selected = posts[1]\n      get :show, params: { id: selected.id }\n      expect(assigns(:prev)).to eq(posts[0])\n      expect(assigns(:next)).to eq(posts[2])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/controllers/home_controller_spec.rb",
    "content": "require 'rails_helper'\n\nRSpec.describe HomeController, type: :controller do\n\n  describe \"GET 'index'\" do\n    it \"returns http success\" do\n      get 'index'\n      expect(response).to be_successful\n    end\n  end\n\nend\n"
  },
  {
    "path": "spec/controllers/likes_controller_spec.rb",
    "content": "require 'rails_helper'\n\nRSpec.describe LikesController, type: :controller do\n\n  it \"get index\" do\n    a = Post.create!(title: 'one', content: '1'*31)\n    get 'index', params: { blog_id: a.id }\n    expect(JSON.parse(response.body)['count']).to eq(0)\n    a.likes << Like.new\n    a.save!\n    get 'index', params: { blog_id: a.id }\n    expect(JSON.parse(response.body)['count']).to eq(1)\n  end\n\n  it \"post create\" do\n    a = Post.create!(title: 'one', content: '1'*31)\n    post 'create', params: { blog_id: a.id }\n    expect(a.likes.size).to eq(1)\n  end\n\n  it \"DELETE destroy\" do\n    a = Post.create!(title: 'one', content: '1'*31)\n    like = Like.new\n    a.likes << like\n    a.save!\n    delete 'destroy', params: { blog_id: a.id, id: like.id }\n    expect(a.reload.likes.size).to eq(0)\n  end\nend\n"
  },
  {
    "path": "spec/factories/comments.rb",
    "content": "FactoryBot.define do\n  factory :comment do\n    content { 'content' * 10 }\n    name { 'commentor' }\n    email { 'tester@xx.com' }\n    association :post\n  end\n\n  factory :comment_no_post, class: Comment do\n    content { 'content' * 10 }\n    name { 'commentor' }\n    email { 'tester@xx.com' }\n  end\nend\n"
  },
  {
    "path": "spec/factories/labels.rb",
    "content": "FactoryBot.define do\n  factory :label do\n    name { 'label' }\n  end\nend\n"
  },
  {
    "path": "spec/factories/posts.rb",
    "content": "FactoryBot.define do\n  factory :post do\n    title { 'this is a post title' }\n    content { 'content' * 10 }\n  end\n\n  factory :post_list, class: Post do\n    sequence(:title) { |n| \"#{n}: post title\" }\n    content { 'content' * 10 }\n    sequence(:created_at) { |n| n.days.ago }\n  end\nend\n"
  },
  {
    "path": "spec/factories/subscribes.rb",
    "content": "# Read about factories at https://github.com/thoughtbot/factory_girl\n\nFactoryBot.define do\n  factory :subscribe do\n    email { \"tester@mail.com\" }\n    enable { false }\n  end\nend\n"
  },
  {
    "path": "spec/models/like_spec.rb",
    "content": "require 'rails_helper'\n\nRSpec.describe Like, type: :model  do\n  it \"add like\" do\n    a = Post.create!(title: 'one', content: '1'*31)\n    like = Like.new\n    like.post = a\n    expect(like.save).to eq(true)\n  end\nend\n"
  },
  {
    "path": "spec/models/post_spec.rb",
    "content": "require 'rails_helper'\n\nRSpec.describe Post, type: :model do\n  it \"validates should be ok\" do\n    expect(create(:post)).to be_truthy\n  end\nend\n"
  },
  {
    "path": "spec/rails_helper.rb",
    "content": "# This file is copied to spec/ when you run 'rails generate rspec:install'\nrequire 'spec_helper'\nENV['RAILS_ENV'] ||= 'test'\nrequire File.expand_path('../config/environment', __dir__)\n# Prevent database truncation if the environment is production\nabort(\"The Rails environment is running in production mode!\") if Rails.env.production?\nrequire 'rspec/rails'\n# Add additional requires below this line. Rails is not loaded until this point!\n\n# Requires supporting ruby files with custom matchers and macros, etc, in\n# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are\n# run as spec files by default. This means that files in spec/support that end\n# in _spec.rb will both be required and run as specs, causing the specs to be\n# run twice. It is recommended that you do not name files matching this glob to\n# end with _spec.rb. You can configure this pattern with the --pattern\n# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.\n#\n# The following line is provided for convenience purposes. It has the downside\n# of increasing the boot-up time by auto-requiring all files in the support\n# directory. Alternatively, in the individual `*_spec.rb` files, manually\n# require only the support files necessary.\n#\nDir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }\n\n# Checks for pending migrations and applies them before tests are run.\n# If you are not using ActiveRecord, you can remove these lines.\nbegin\n  ActiveRecord::Migration.maintain_test_schema!\nrescue ActiveRecord::PendingMigrationError => e\n  puts e.to_s.strip\n  exit 1\nend\nRSpec.configure do |config|\n  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures\n  config.fixture_path = \"#{::Rails.root}/spec/fixtures\"\n\n  # If you're not using ActiveRecord, or you'd prefer not to run each of your\n  # examples within a transaction, remove the following line or assign false\n  # instead of true.\n  config.use_transactional_fixtures = false\n\n  # You can uncomment this line to turn off ActiveRecord support entirely.\n  # config.use_active_record = false\n\n  # RSpec Rails can automatically mix in different behaviours to your tests\n  # based on their file location, for example enabling you to call `get` and\n  # `post` in specs under `spec/controllers`.\n  #\n  # You can disable this behaviour by removing the line below, and instead\n  # explicitly tag your specs with their type, e.g.:\n  #\n  #     RSpec.describe UsersController, type: :controller do\n  #       # ...\n  #     end\n  #\n  # The different available types are documented in the features, such as in\n  # https://relishapp.com/rspec/rspec-rails/docs\n  config.infer_spec_type_from_file_location!\n\n  # Filter lines from Rails gems in backtraces.\n  config.filter_rails_from_backtrace!\n  # arbitrary gems may also be filtered via:\n  # config.filter_gems_from_backtrace(\"gem name\")\nend\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "# This file was generated by the `rails generate rspec:install` command. Conventionally, all\n# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.\n# The generated `.rspec` file contains `--require spec_helper` which will cause\n# this file to always be loaded, without a need to explicitly require it in any\n# files.\n#\n# Given that it is always loaded, you are encouraged to keep this file as\n# light-weight as possible. Requiring heavyweight dependencies from this file\n# will add to the boot time of your test suite on EVERY test run, even for an\n# individual file that may not need all of that loaded. Instead, consider making\n# a separate helper file that requires the additional dependencies and performs\n# the additional setup, and require it from the spec files that actually need\n# it.\n#\n# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration\nRSpec.configure do |config|\n  # rspec-expectations config goes here. You can use an alternate\n  # assertion/expectation library such as wrong or the stdlib/minitest\n  # assertions if you prefer.\n  config.expect_with :rspec do |expectations|\n    # This option will default to `true` in RSpec 4. It makes the `description`\n    # and `failure_message` of custom matchers include text for helper methods\n    # defined using `chain`, e.g.:\n    #     be_bigger_than(2).and_smaller_than(4).description\n    #     # => \"be bigger than 2 and smaller than 4\"\n    # ...rather than:\n    #     # => \"be bigger than 2\"\n    expectations.include_chain_clauses_in_custom_matcher_descriptions = true\n  end\n\n  # rspec-mocks config goes here. You can use an alternate test double\n  # library (such as bogus or mocha) by changing the `mock_with` option here.\n  config.mock_with :rspec do |mocks|\n    # Prevents you from mocking or stubbing a method that does not exist on\n    # a real object. This is generally recommended, and will default to\n    # `true` in RSpec 4.\n    mocks.verify_partial_doubles = true\n  end\n\n  # This option will default to `:apply_to_host_groups` in RSpec 4 (and will\n  # have no way to turn it off -- the option exists only for backwards\n  # compatibility in RSpec 3). It causes shared context metadata to be\n  # inherited by the metadata hash of host groups and examples, rather than\n  # triggering implicit auto-inclusion in groups with matching metadata.\n  config.shared_context_metadata_behavior = :apply_to_host_groups\n\n# The settings below are suggested to provide a good initial experience\n# with RSpec, but feel free to customize to your heart's content.\n=begin\n  # This allows you to limit a spec run to individual examples or groups\n  # you care about by tagging them with `:focus` metadata. When nothing\n  # is tagged with `:focus`, all examples get run. RSpec also provides\n  # aliases for `it`, `describe`, and `context` that include `:focus`\n  # metadata: `fit`, `fdescribe` and `fcontext`, respectively.\n  config.filter_run_when_matching :focus\n\n  # Allows RSpec to persist some state between runs in order to support\n  # the `--only-failures` and `--next-failure` CLI options. We recommend\n  # you configure your source control system to ignore this file.\n  config.example_status_persistence_file_path = \"spec/examples.txt\"\n\n  # Limits the available syntax to the non-monkey patched syntax that is\n  # recommended. For more details, see:\n  #   - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/\n  #   - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/\n  #   - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode\n  config.disable_monkey_patching!\n\n  # Many RSpec users commonly either run the entire suite or an individual\n  # file, and it's useful to allow more verbose output when running an\n  # individual spec file.\n  if config.files_to_run.one?\n    # Use the documentation formatter for detailed output,\n    # unless a formatter has already been configured\n    # (e.g. via a command-line flag).\n    config.default_formatter = \"doc\"\n  end\n\n  # Print the 10 slowest examples and example groups at the\n  # end of the spec run, to help surface which specs are running\n  # particularly slow.\n  config.profile_examples = 10\n\n  # Run specs in random order to surface order dependencies. If you find an\n  # order dependency and want to debug it, you can fix the order by providing\n  # the seed, which is printed after each run.\n  #     --seed 1234\n  config.order = :random\n\n  # Seed global randomization in this process using the `--seed` CLI option.\n  # Setting this allows you to use `--seed` to deterministically reproduce\n  # test failures related to randomization by passing the same `--seed` value\n  # as the one that triggered the failure.\n  Kernel.srand config.seed\n=end\nend\n"
  },
  {
    "path": "spec/support/capybara.rb",
    "content": "Capybara.asset_host = 'http://localhost:3000'\n"
  },
  {
    "path": "spec/support/database_cleaner.rb",
    "content": "RSpec.configure do |config|\n  config.before(:suite) do\n    DatabaseCleaner.clean_with(:truncation)\n  end\n\n  config.before(:each) do\n    DatabaseCleaner.strategy = :transaction\n  end\n\n  config.before(:each, :js => true) do\n    DatabaseCleaner.strategy = :truncation\n  end\n\n  config.before(:each) do\n    DatabaseCleaner.start\n  end\n\n  config.append_after(:each) do\n    DatabaseCleaner.clean\n  end\nend\n"
  },
  {
    "path": "spec/support/factory_bot.rb",
    "content": "RSpec.configure do |config|\n  config.include FactoryBot::Syntax::Methods\nend\n"
  },
  {
    "path": "storage/.keep",
    "content": ""
  },
  {
    "path": "test/application_system_test_case.rb",
    "content": "require \"test_helper\"\n\nclass ApplicationSystemTestCase < ActionDispatch::SystemTestCase\n  driven_by :selenium, using: :chrome, screen_size: [1400, 1400]\nend\n"
  },
  {
    "path": "test/channels/application_cable/connection_test.rb",
    "content": "require \"test_helper\"\n\nclass ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase\n  # test \"connects with cookies\" do\n  #   cookies.signed[:user_id] = 42\n  #\n  #   connect\n  #\n  #   assert_equal connection.user_id, \"42\"\n  # end\nend\n"
  },
  {
    "path": "test/controllers/.keep",
    "content": ""
  },
  {
    "path": "test/fixtures/files/.keep",
    "content": ""
  },
  {
    "path": "test/helpers/.keep",
    "content": ""
  },
  {
    "path": "test/integration/.keep",
    "content": ""
  },
  {
    "path": "test/mailers/.keep",
    "content": ""
  },
  {
    "path": "test/models/.keep",
    "content": ""
  },
  {
    "path": "test/system/.keep",
    "content": ""
  },
  {
    "path": "test/test_helper.rb",
    "content": "ENV['RAILS_ENV'] ||= 'test'\nrequire_relative \"../config/environment\"\nrequire \"rails/test_help\"\n\nclass ActiveSupport::TestCase\n  # Run tests in parallel with specified workers\n  parallelize(workers: :number_of_processors)\n\n  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.\n  fixtures :all\n\n  # Add more helper methods to be used by all tests here...\nend\n"
  },
  {
    "path": "tmp/.keep",
    "content": ""
  },
  {
    "path": "vendor/.keep",
    "content": ""
  }
]