From 563aa6a0f8544499337f7455c059117c57383c37 Mon Sep 17 00:00:00 2001 From: Neetpone <132411956+Neetpone@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:33:19 -0400 Subject: [PATCH] initial: something resembling a minimum viable product --- .gitattributes | 7 + .gitignore | 28 + .ruby-version | 1 + Gemfile | 41 + Gemfile.lock | 291 ++ LICENSE | 19 + README.md | 2 + Rakefile | 6 + app/assets/config/manifest.js | 6 + app/assets/images/.keep | 0 app/assets/javascripts/application.js | 98 + app/assets/stylesheets/about.css | 151 + app/assets/stylesheets/application.css | 50 + app/assets/stylesheets/fimfetch.css | 3548 +++++++++++++++++ app/channels/application_cable/channel.rb | 4 + app/channels/application_cable/connection.rb | 4 + app/controllers/application_controller.rb | 21 + app/controllers/authors_controller.rb | 5 + app/controllers/chapters_controller.rb | 6 + app/controllers/concerns/.keep | 0 app/controllers/images_controller.rb | 28 + app/controllers/search_controller.rb | 125 + app/controllers/static_pages_controller.rb | 4 + app/controllers/stories_controller.rb | 9 + app/helpers/application_helper.rb | 15 + app/helpers/authors_helper.rb | 2 + app/helpers/chapters_helper.rb | 2 + app/helpers/images_helper.rb | 2 + app/helpers/search_helper.rb | 47 + app/helpers/static_pages_helper.rb | 2 + app/helpers/stories_helper.rb | 2 + app/indexes/story_index.rb | 61 + app/jobs/application_job.rb | 7 + app/jobs/index_update_job.rb | 11 + app/models/application_record.rb | 3 + app/models/author.rb | 3 + app/models/chapter.rb | 3 + app/models/concerns/.keep | 0 app/models/concerns/indexable.rb | 34 + app/models/story.rb | 9 + app/models/story/tagging.rb | 6 + app/models/tag.rb | 2 + app/views/chapters/show.html.slim | 29 + app/views/kaminari/_first_page.html.slim | 8 + app/views/kaminari/_gap.html.slim | 7 + app/views/kaminari/_last_page.html.slim | 8 + app/views/kaminari/_next_page.html.slim | 8 + app/views/kaminari/_page.html.slim | 12 + app/views/kaminari/_paginator.html.slim | 18 + app/views/kaminari/_prev_page.html.slim | 8 + app/views/layouts/_banner.html.slim | 3 + app/views/layouts/application.html.slim | 35 + app/views/search/_advanced.html.slim | 127 + app/views/search/index.html.slim | 15 + app/views/search/search.html.slim | 58 + app/views/static_pages/about.html.slim | 25 + app/views/stories/index.html.slim | 0 app/views/stories/show.html.slim | 52 + bin/bundle | 109 + bin/importmap | 4 + bin/rails | 4 + bin/rake | 4 + bin/setup | 33 + config.ru | 6 + config/application.rb | 34 + config/boot.rb | 3 + config/cable.yml | 11 + config/credentials.yml.enc | 1 + config/database.yml | 86 + config/environment.rb | 5 + config/environments/development.rb | 62 + config/environments/production.rb | 84 + config/environments/test.rb | 50 + config/importmap.rb | 7 + config/initializers/assets.rb | 12 + .../initializers/content_security_policy.rb | 25 + config/initializers/elasticsearch.rb | 17 + .../initializers/filter_parameter_logging.rb | 8 + config/initializers/inflections.rb | 16 + config/initializers/permissions_policy.rb | 11 + config/initializers/redis.rb | 14 + config/locales/en.yml | 33 + config/puma.rb | 43 + config/routes.rb | 12 + db/migrate/20240401101354_initial.rb | 63 + .../20240401121829_temp_nullable_body.rb | 5 + db/migrate/20240402172140_remove_urls.rb | 8 + db/schema.rb | 77 + db/seeds.rb | 7 + lib/assets/.keep | 0 lib/tasks/.keep | 0 log/.keep | 0 public/404.html | 67 + public/422.html | 67 + public/500.html | 66 + public/apple-touch-icon-precomposed.png | 0 public/apple-touch-icon.png | 0 public/cached-images/.placeholder | 0 public/favicon.ico | 0 public/img/banner.png | Bin 0 -> 55351 bytes public/img/logo.png | Bin 0 -> 21429 bytes public/img/logo_small.png | Bin 0 -> 16298 bytes public/robots.txt | 1 + test/application_system_test_case.rb | 5 + .../application_cable/connection_test.rb | 11 + test/controllers/.keep | 0 test/controllers/authors_controller_test.rb | 7 + test/controllers/chapters_controller_test.rb | 7 + test/controllers/images_controller_test.rb | 7 + test/controllers/search_controller_test.rb | 7 + .../static_pages_controller_test.rb | 7 + test/controllers/stories_controller_test.rb | 7 + test/fixtures/authors.yml | 11 + test/fixtures/chapters.yml | 11 + test/fixtures/files/.keep | 0 test/fixtures/stories.yml | 11 + test/fixtures/tags.yml | 11 + test/helpers/.keep | 0 test/integration/.keep | 0 test/models/.keep | 0 test/models/author_test.rb | 7 + test/models/chapter_test.rb | 7 + test/models/story_test.rb | 7 + test/models/tag_test.rb | 7 + test/system/.keep | 0 test/test_helper.rb | 13 + tmp/.keep | 0 tmp/pids/.keep | 0 vendor/.keep | 0 vendor/javascript/.keep | 0 130 files changed, 6276 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .ruby-version create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Rakefile create mode 100644 app/assets/config/manifest.js create mode 100644 app/assets/images/.keep create mode 100644 app/assets/javascripts/application.js create mode 100644 app/assets/stylesheets/about.css create mode 100644 app/assets/stylesheets/application.css create mode 100644 app/assets/stylesheets/fimfetch.css create mode 100644 app/channels/application_cable/channel.rb create mode 100644 app/channels/application_cable/connection.rb create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/authors_controller.rb create mode 100644 app/controllers/chapters_controller.rb create mode 100644 app/controllers/concerns/.keep create mode 100644 app/controllers/images_controller.rb create mode 100644 app/controllers/search_controller.rb create mode 100644 app/controllers/static_pages_controller.rb create mode 100644 app/controllers/stories_controller.rb create mode 100644 app/helpers/application_helper.rb create mode 100644 app/helpers/authors_helper.rb create mode 100644 app/helpers/chapters_helper.rb create mode 100644 app/helpers/images_helper.rb create mode 100644 app/helpers/search_helper.rb create mode 100644 app/helpers/static_pages_helper.rb create mode 100644 app/helpers/stories_helper.rb create mode 100644 app/indexes/story_index.rb create mode 100644 app/jobs/application_job.rb create mode 100644 app/jobs/index_update_job.rb create mode 100644 app/models/application_record.rb create mode 100644 app/models/author.rb create mode 100644 app/models/chapter.rb create mode 100644 app/models/concerns/.keep create mode 100644 app/models/concerns/indexable.rb create mode 100644 app/models/story.rb create mode 100644 app/models/story/tagging.rb create mode 100644 app/models/tag.rb create mode 100644 app/views/chapters/show.html.slim create mode 100644 app/views/kaminari/_first_page.html.slim create mode 100644 app/views/kaminari/_gap.html.slim create mode 100644 app/views/kaminari/_last_page.html.slim create mode 100644 app/views/kaminari/_next_page.html.slim create mode 100644 app/views/kaminari/_page.html.slim create mode 100644 app/views/kaminari/_paginator.html.slim create mode 100644 app/views/kaminari/_prev_page.html.slim create mode 100644 app/views/layouts/_banner.html.slim create mode 100644 app/views/layouts/application.html.slim create mode 100644 app/views/search/_advanced.html.slim create mode 100644 app/views/search/index.html.slim create mode 100644 app/views/search/search.html.slim create mode 100644 app/views/static_pages/about.html.slim create mode 100644 app/views/stories/index.html.slim create mode 100644 app/views/stories/show.html.slim create mode 100755 bin/bundle create mode 100755 bin/importmap create mode 100755 bin/rails create mode 100755 bin/rake create mode 100755 bin/setup create mode 100644 config.ru create mode 100644 config/application.rb create mode 100644 config/boot.rb create mode 100644 config/cable.yml create mode 100644 config/credentials.yml.enc create mode 100644 config/database.yml create mode 100644 config/environment.rb create mode 100644 config/environments/development.rb create mode 100644 config/environments/production.rb create mode 100644 config/environments/test.rb create mode 100644 config/importmap.rb create mode 100644 config/initializers/assets.rb create mode 100644 config/initializers/content_security_policy.rb create mode 100644 config/initializers/elasticsearch.rb create mode 100644 config/initializers/filter_parameter_logging.rb create mode 100644 config/initializers/inflections.rb create mode 100644 config/initializers/permissions_policy.rb create mode 100644 config/initializers/redis.rb create mode 100644 config/locales/en.yml create mode 100644 config/puma.rb create mode 100644 config/routes.rb create mode 100644 db/migrate/20240401101354_initial.rb create mode 100644 db/migrate/20240401121829_temp_nullable_body.rb create mode 100644 db/migrate/20240402172140_remove_urls.rb create mode 100644 db/schema.rb create mode 100644 db/seeds.rb create mode 100644 lib/assets/.keep create mode 100644 lib/tasks/.keep create mode 100644 log/.keep create mode 100644 public/404.html create mode 100644 public/422.html create mode 100644 public/500.html create mode 100644 public/apple-touch-icon-precomposed.png create mode 100644 public/apple-touch-icon.png create mode 100644 public/cached-images/.placeholder create mode 100644 public/favicon.ico create mode 100644 public/img/banner.png create mode 100644 public/img/logo.png create mode 100644 public/img/logo_small.png create mode 100644 public/robots.txt create mode 100644 test/application_system_test_case.rb create mode 100644 test/channels/application_cable/connection_test.rb create mode 100644 test/controllers/.keep create mode 100644 test/controllers/authors_controller_test.rb create mode 100644 test/controllers/chapters_controller_test.rb create mode 100644 test/controllers/images_controller_test.rb create mode 100644 test/controllers/search_controller_test.rb create mode 100644 test/controllers/static_pages_controller_test.rb create mode 100644 test/controllers/stories_controller_test.rb create mode 100644 test/fixtures/authors.yml create mode 100644 test/fixtures/chapters.yml create mode 100644 test/fixtures/files/.keep create mode 100644 test/fixtures/stories.yml create mode 100644 test/fixtures/tags.yml create mode 100644 test/helpers/.keep create mode 100644 test/integration/.keep create mode 100644 test/models/.keep create mode 100644 test/models/author_test.rb create mode 100644 test/models/chapter_test.rb create mode 100644 test/models/story_test.rb create mode 100644 test/models/tag_test.rb create mode 100644 test/system/.keep create mode 100644 test/test_helper.rb create mode 100644 tmp/.keep create mode 100644 tmp/pids/.keep create mode 100644 vendor/.keep create mode 100644 vendor/javascript/.keep diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..31eeee0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd8892c --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key + +public/cached-images + diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..9e79f6c --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +ruby-3.2.2 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..55d20b7 --- /dev/null +++ b/Gemfile @@ -0,0 +1,41 @@ +source "https://rubygems.org" +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +ruby "3.2.2" + +gem "rails", "~> 7.0.8", ">= 7.0.8.1" +gem "sprockets-rails" +gem "pg", "~> 1.1" +gem "puma", "~> 5.0" +gem 'slim-rails' +gem 'kaminari' + +gem 'redis' + +gem 'elasticsearch-model' +gem 'model-msearch' +gem 'fancy_searchable', github: 'Twibooru/fancy_searchable', ref: '40687c9' + +gem 'sidekiq' + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] + +# Use Sass to process CSS +# gem "sassc-rails" + +group :development, :test do + # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem + gem "debug", platforms: %i[ mri mingw x64_mingw ] +end + +group :development do + gem "web-console" + gem 'annotate' +end + +group :test do + # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] + gem "capybara" + gem "selenium-webdriver" +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..a350854 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,291 @@ +GIT + remote: https://github.com/Twibooru/fancy_searchable.git + revision: 40687c924d19a51335a79f5157150aca9d531cc9 + ref: 40687c9 + specs: + fancy_searchable (0.2.5) + activesupport (>= 7.0) + elasticsearch-model (>= 7.2.1) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (7.0.8.1) + actionpack (= 7.0.8.1) + activesupport (= 7.0.8.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailbox (7.0.8.1) + actionpack (= 7.0.8.1) + activejob (= 7.0.8.1) + activerecord (= 7.0.8.1) + activestorage (= 7.0.8.1) + activesupport (= 7.0.8.1) + mail (>= 2.7.1) + net-imap + net-pop + net-smtp + actionmailer (7.0.8.1) + actionpack (= 7.0.8.1) + actionview (= 7.0.8.1) + activejob (= 7.0.8.1) + activesupport (= 7.0.8.1) + mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp + rails-dom-testing (~> 2.0) + actionpack (7.0.8.1) + actionview (= 7.0.8.1) + activesupport (= 7.0.8.1) + rack (~> 2.0, >= 2.2.4) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (7.0.8.1) + actionpack (= 7.0.8.1) + activerecord (= 7.0.8.1) + activestorage (= 7.0.8.1) + activesupport (= 7.0.8.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (7.0.8.1) + activesupport (= 7.0.8.1) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (7.0.8.1) + activesupport (= 7.0.8.1) + globalid (>= 0.3.6) + activemodel (7.0.8.1) + activesupport (= 7.0.8.1) + activerecord (7.0.8.1) + activemodel (= 7.0.8.1) + activesupport (= 7.0.8.1) + activestorage (7.0.8.1) + actionpack (= 7.0.8.1) + activejob (= 7.0.8.1) + activerecord (= 7.0.8.1) + activesupport (= 7.0.8.1) + marcel (~> 1.0) + mini_mime (>= 1.1.0) + activesupport (7.0.8.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) + annotate (3.2.0) + activerecord (>= 3.2, < 8.0) + rake (>= 10.4, < 14.0) + base64 (0.2.0) + bindex (0.8.1) + builder (3.2.4) + capybara (3.40.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.11) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + crass (1.0.6) + date (3.3.4) + debug (1.9.2) + irb (~> 1.10) + reline (>= 0.3.8) + elasticsearch (7.17.10) + elasticsearch-api (= 7.17.10) + elasticsearch-transport (= 7.17.10) + elasticsearch-api (7.17.10) + multi_json + elasticsearch-model (7.2.1) + activesupport (> 3) + elasticsearch (~> 7) + hashie + elasticsearch-transport (7.17.10) + faraday (>= 1, < 3) + multi_json + erubi (1.12.0) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) + faraday-net_http (3.1.0) + net-http + globalid (1.2.1) + activesupport (>= 6.1) + hashie (5.0.0) + i18n (1.14.4) + concurrent-ruby (~> 1.0) + io-console (0.7.2) + irb (1.12.0) + rdoc + reline (>= 0.4.2) + kaminari (1.2.2) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.2.2) + kaminari-activerecord (= 1.2.2) + kaminari-core (= 1.2.2) + kaminari-actionview (1.2.2) + actionview + kaminari-core (= 1.2.2) + kaminari-activerecord (1.2.2) + activerecord + kaminari-core (= 1.2.2) + kaminari-core (1.2.2) + loofah (2.22.0) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + matrix (0.4.2) + method_source (1.0.0) + mini_mime (1.1.5) + minitest (5.22.3) + model-msearch (0.0.2) + elasticsearch-model + multi_json (1.15.0) + net-http (0.4.1) + uri + net-imap (0.4.10) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.0) + net-protocol + nio4r (2.7.1) + nokogiri (1.16.3-x86_64-linux) + racc (~> 1.4) + pg (1.5.6) + psych (5.1.2) + stringio + public_suffix (5.0.4) + puma (5.6.8) + nio4r (~> 2.0) + racc (1.7.3) + rack (2.2.9) + rack-test (2.1.0) + rack (>= 1.3) + rails (7.0.8.1) + actioncable (= 7.0.8.1) + actionmailbox (= 7.0.8.1) + actionmailer (= 7.0.8.1) + actionpack (= 7.0.8.1) + actiontext (= 7.0.8.1) + actionview (= 7.0.8.1) + activejob (= 7.0.8.1) + activemodel (= 7.0.8.1) + activerecord (= 7.0.8.1) + activestorage (= 7.0.8.1) + activesupport (= 7.0.8.1) + bundler (>= 1.15.0) + railties (= 7.0.8.1) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + railties (7.0.8.1) + actionpack (= 7.0.8.1) + activesupport (= 7.0.8.1) + method_source + rake (>= 12.2) + thor (~> 1.0) + zeitwerk (~> 2.5) + rake (13.1.0) + rdoc (6.6.3.1) + psych (>= 4.0.0) + redis (5.1.0) + redis-client (>= 0.17.0) + redis-client (0.21.1) + connection_pool + regexp_parser (2.9.0) + reline (0.5.0) + io-console (~> 0.5) + rexml (3.2.6) + rubyzip (2.3.2) + selenium-webdriver (4.19.0) + base64 (~> 0.2) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) + sidekiq (7.2.2) + concurrent-ruby (< 2) + connection_pool (>= 2.3.0) + rack (>= 2.2.4) + redis-client (>= 0.19.0) + slim (5.2.1) + temple (~> 0.10.0) + tilt (>= 2.1.0) + slim-rails (3.6.3) + actionpack (>= 3.1) + railties (>= 3.1) + slim (>= 3.0, < 6.0, != 5.0.0) + sprockets (4.2.1) + concurrent-ruby (~> 1.0) + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + stringio (3.1.0) + temple (0.10.3) + thor (1.3.1) + tilt (2.3.0) + timeout (0.4.1) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + uri (0.13.0) + web-console (4.2.1) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + websocket (1.2.10) + websocket-driver (0.7.6) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + zeitwerk (2.6.13) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + annotate + capybara + debug + elasticsearch-model + fancy_searchable! + kaminari + model-msearch + pg (~> 1.1) + puma (~> 5.0) + rails (~> 7.0.8, >= 7.0.8.1) + redis + selenium-webdriver + sidekiq + slim-rails + sprockets-rails + tzinfo-data + web-console + +RUBY VERSION + ruby 3.2.2p53 + +BUNDLED WITH + 2.4.10 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4fb18ee --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024 Neetpone + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a969da6 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# FoalFetch +A replacement for FiMFetch. \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..9a5ea73 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000..2261605 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,6 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js + +//= link application.js \ No newline at end of file diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000..302ec77 --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,98 @@ +const makeEl = function(html) { + const template = document.createElement('template'); + + template.innerHTML = html.trim(); + + return template.content.firstChild; +}; + +function addTag(element, tag) { + const existingElements = element.value.split(',').filter(n => n); + + if (existingElements.indexOf(tag) === -1) { + existingElements.push(tag); + element.value = existingElements.join(','); + } +} + +function removeTag(element, tag) { + const existingElements = element.value.split(',').filter(n => n); + const index = existingElements.indexOf(tag); + + if (index !== -1) { + existingElements.splice(index, 1); + element.value = existingElements.join(','); + } +} + +function makeTagElement(hiddenInput, visualInput, tagName, excluded) { + // Set up the tag element + const tagEl = makeEl(`
${tagName}
`); + tagEl.firstChild.addEventListener('click', function(e) { + if (tagEl.classList.contains('excluded')) { + removeTag(hiddenInput, '-' + tagName); + addTag(hiddenInput, tagName); + tagEl.classList.remove('excluded'); + } else { + removeTag(hiddenInput, tagName); + addTag(hiddenInput, '-' + tagName); + tagEl.classList.add('excluded'); + } + }); + + // Set up the child "X" button + const removeEl = makeEl(''); + removeEl.addEventListener('click', function(e) { + removeTag(hiddenInput, tagEl.classList.contains('excluded') ? '-' + tagName : tagName); + visualInput.removeChild(tagEl); + }); + + tagEl.appendChild(removeEl); + + return tagEl; +} + +function setupFancyTagEditor(rootElement) { + rootElement.classList.remove('hidden'); + + const selectDropdown = rootElement.querySelector('select'); + const hiddenInput = rootElement.querySelector('input[type=text]'); + const visualInput = rootElement.querySelector('.selected-tags'); + + if (!selectDropdown || !hiddenInput || !visualInput) { + console.log('Nothing there!'); + return; + } + + hiddenInput.setAttribute('type', 'hidden'); + + // Load existing tags from the input field as visual tag elements + for (const tag of hiddenInput.value.split(',').filter(n => n)) { + let tagElement; + if (tag[0] === '-') { + tagElement = makeTagElement(hiddenInput, visualInput, tag.substring(1), true); + } else { + tagElement = makeTagElement(hiddenInput, visualInput, tag, false); + } + + visualInput.appendChild(tagElement); + } + + selectDropdown.addEventListener('change', function(e) { + const tagName = selectDropdown.value; + addTag(hiddenInput, tagName); + visualInput.appendChild(makeTagElement(hiddenInput, visualInput, tagName, false)); + }); +} + +function whenReady() { + for (const element of document.querySelectorAll('.js-tag-editor')) { + setupFancyTagEditor(element); + } +} + +if (document.readyState !== 'loading') { + whenReady(); +} else { + document.addEventListener('DOMContentLoaded', whenReady); +} diff --git a/app/assets/stylesheets/about.css b/app/assets/stylesheets/about.css new file mode 100644 index 0000000..50b6570 --- /dev/null +++ b/app/assets/stylesheets/about.css @@ -0,0 +1,151 @@ +body.about { + background-color: #fff6ea; +} + +.about article { + display: inline-block; + max-width: 800px; + text-align: left; + padding: 0 5px; + line-height: 20px; + font-size: 18px; + margin: 10px 0 0; +} +.about article h1, .about article h2 { + text-align: center; + font-size: 28px; + margin: 5px; +} +.about article h1 { + font-size: 35px; + margin: 15px 0 25px; +} + +.about article section { + padding: 10px 15px; + background-color: #fdfdfd; + margin: 0 0 15px; + overflow: auto; + box-shadow: 0 0 5px #a5a5a5 , 0 0 8px #797979; +} + +.about p { + margin: 10px 0; +} +.about dl { + line-height: 24px; +} +.about dt { + float: left; + width: 100px; + text-align: right; + font-weight: bold; + font-family: Montserrat, Arial, sans-serif; + color: #187099; + padding: 0 0 10px; +} +.about dd { + margin: 0 0 0 110px; + color: #0F5221; + font-family: Arial, sans-serif; +} +.about dd:after { + content: ""; + display: block; + clear: both; +} +.about code { + white-space: pre-wrap; +} +.about pre { + background-color: #eff0f1; +} +.about img.fb-logo { + margin: 15px 0 0 15px; + max-width: 64px; +} +.about .tile { + width: 150px; + height: 180px; + font-size: 10px; + display: inline-block; + overflow: hidden; + padding: 4px; + /*border: 1px solid #333;*/ +} +.about .tile img { + max-width: 100%; +} +.about .tiles { + text-align: center; +} +.about .tile figure { + margin: 0; + display: table-cell; + height: 180px; + vertical-align: bottom; +} +.about .tile figcaption { + min-height: 45px; +} +.about .tile figcaption span { + display: inline-block; + margin-left: 4px; +} + +.collapsable { + overflow: hidden; + -webkit-transition-delay: 2s; + transition-delay: 2s; + transition: max-height .5s ease-out; +} +.collapsable.full { + transition: max-height .8s ease-in; + max-height:0px; + padding-top: 10px; +} +.about .c { + text-align: center; +} +.about .stars { + display: inline-block; + width: auto; + height: 60px; +} +.about .stars .star { + font-size: 45px; + line-height: 45px; + width: 45px; + height: 45px; + margin: 5px 4px; +} +.about .stars .star:before { + left: 0; + top: 0; + padding: 0; + width: 100%; + height: 100%; +} + +.about article .stat { + white-space: pre; + font-family: monospace; + font-size: 17px; +} + +.donates h3 { + display: block; + text-align: center; + font-weight: bold; +} +.donates ul.names { + list-style: none; +} +.donates ul.names li { + height: 35px; + font-size: 18px; + display: inline-block; + overflow: hidden; + padding: 4px; + margin: 3px 4px; +} diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 0000000..f46140a --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,50 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ +.cols { + display: flex; + flex-direction: row; + flex-wrap: nowrap; +} + +.opts { + text-align: left; + margin: 0.75rem; +} +.opts > input { + display: block; +} + +.opts > label { + display: block; +} + +.fake-link { + text-decoration: underline; + cursor: pointer; +} + +input#show-advanced + div { + display: none; +} + +input#show-advanced:checked + div { + display: block; +} + +.search-s > form { + text-align: left; + padding-left: 12rem; +} diff --git a/app/assets/stylesheets/fimfetch.css b/app/assets/stylesheets/fimfetch.css new file mode 100644 index 0000000..03bb38e --- /dev/null +++ b/app/assets/stylesheets/fimfetch.css @@ -0,0 +1,3548 @@ +/* This entire file is just skidded from FiMFetch */ +.library h1 { + font: bold 34px "Times New Roman", Times, serif; + display: inline-block; + vertical-align: top; + margin: 0 0 0 10px; +} +.library > header > div { + display: inline-block; + padding: 0 40px; +} +.library > header { + margin-top: 10px; +} +.udot { + border-image-source: url(/img/dots.svg); + border-image-slice: 30%; + border-image-repeat: round; + border-color: green; + border-style: dotted; + border-width: 0 0 25px; +} +.library .avatar { + display: inline-block; + border: 1px solid #333; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + max-width: 80px; + max-height: 80px; + margin: 0 10px; + padding: 3px; +} +.library .avatar img { + max-width: 100%; + max-height: 100%; +} +.library ul { + list-style: none; + padding: 0; +} +.library li { + display: inline-block; + min-height: 200px; + width: 300px; + vertical-align: top; + padding: 6px; +} +.library li > section { + border: 1px solid #ccc; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + min-height: 200px; + background-color: #f3f2ed; + position: relative; +} +.spine header { + position: relative; + background-color: #585858; + border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + min-height: 60px; + padding: 3px 0; +} +.spine header h2 { + font-size: 24px; + line-height: 26px; + color: #fff; + text-shadow: 0 0 6px #000, 1px 1px 3px #000, 2px 2px 2px #888; + margin: 0 45px 0 75px; +} +.spine header .fa { + position: absolute; + font-size: 20px; + height: 26px; + color: #e6dede; + display: block; + background-color: #333; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + padding: 3px 6px; +} +.spine header .fa.r { + top: 4px; + right: 4px; +} +.spine header .fa.l { + top: 2px; + left: 2px; + font-size: 50px; + height: auto; + min-width: 55px; +} +.spine header .fa.c { + color: #fff; + text-shadow: 0 0 1px #000, 1px 1px 4px #000, 1px 1px 6px #888; +} +.spine header a:hover { + text-decoration: none; +} +.spine section { + margin-bottom: 25px; + padding: 0 6px; +} +.spine footer { + position: absolute; + bottom: 0; + left: 0; + right: 0; + text-align: right; + padding: 3px; +} +.spine footer * { + z-index: 1; +} +.spine footer > span:first-of-type { + float: left; + padding: 5px 0 0 3px; +} +.spine footer > span:nth-of-type(2) { + float: left; + font-size: 85%; + padding: 7px 0 0 15px; +} +.spine .count { + display: inline-block; + background-color: #fff; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + text-shadow: 0 0 1px #999, 1px 1px 2px #ccc, 0 0 4px #eee; + box-shadow: 0 0 3px #333 inset; + padding: 3px 6px; +} +.spine footer a, +.spine footer a:hover { + color: #111; +} +.newlib { + max-width: 450px; + margin: 0 auto; + padding: 10px 5px; +} +.shelf-menu { + border: 1px solid #333; + border-radius: 12px; + padding: 0 5px 10px; +} +.shelf-menu > header { + border-bottom: 1px solid; + font-size: 24px; + padding-bottom: 4px; + margin: 5px -5px 11px; +} +.shelf-menu .line { + display: block; + font-size: 22px; + margin: 3px; + padding: 2px 4px; +} +.shelf-menu .spine header input[type="text"] { + font-size: 22px; + padding: 2px 4px; +} +.shelf-menu .spine section > label { + display: inline-block; + width: 100%; + overflow: auto; + text-align: left; + margin: 6px 0; +} +.shelf-menu .spine section textarea { + width: 100%; + min-height: 60px; +} +.shelf-menu .toggleable-radio { + font-size: 14px; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 4px; + display: inline-block; + background: #e2dddd; + position: relative; + box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1) inset; + transition: background 0.2s; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.toggleable-radio input { + visibility: hidden; + position: absolute; + top: 0; + left: 0; + margin: 0; +} +.toggleable-radio label:hover { + background: #fff; + text-shadow: 1px 1px rgba(255, 255, 255, 0.2); +} +.toggleable-radio input:checked + label { + color: #111; + text-shadow: -1px -1px rgba(255, 255, 255, 0.15); + font-weight: 600; + z-index: 2; + border-right: none; + border-left: none; + background-color: #9a9a9a; +} +.toggleable-radio label:first-of-type { + border-left: none; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.toggleable-radio label:last-of-type { + border-right: none; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.toggleable-radio label { + width: 110px; + display: inline-block; + position: relative; + text-align: center; + vertical-align: top; + line-height: 32px; + height: 32px; + color: #333; + cursor: pointer; + text-shadow: 1px 1px rgba(255, 255, 255, 0.5); + border-right: 1px solid rgba(0, 0, 0, 0.2); +} +.shelf-menu .iconselect .toggleable-radio, +.shelf-menu .colorselect .toggleable-radio { + background: #f9f9f9; + max-width: 350px; + padding: 2px; +} +.iconselect .toggleable-radio label, +.colorselect .toggleable-radio label { + width: 32px; + height: 32px; + box-shadow: 0 0 1px #000; + border-radius: 3px; + border: 1px solid rgba(60, 60, 60, 0.2); + margin: 3px 2px; +} +.iconselect .toggleable-radio input:checked + label, +.colorselect .toggleable-radio input:checked + label { + border: 3px solid #666; + border-radius: 5px; + box-shadow: 0 0 1px #fff inset; +} +.iconselect .toggleable-radio input:checked + label i { + top: -2px; + position: relative; +} +.iconselect .toggleable-radio { + max-height: 150px; + overflow-y: scroll; + overflow-x: hidden; +} +.iconselect i.fa { + color: #fff; + text-shadow: 0 0 1px #000, 1px 1px 4px #000, 1px 1px 6px #888; +} +.iconselect i.fa.pony { + color: #000; + text-shadow: 1px 1px 4px rgba(208, 208, 208, 0.5); +} +.toggleable-radio::-webkit-scrollbar { + width: 8px; +} +.toggleable-radio::-webkit-scrollbar-thumb { + background-color: #aaa; +} +.toggleable-radio::-webkit-scrollbar-track { + background-color: #eee; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.35) inset; +} +#status.ok, +#status.error { + display: block; + text-align: center; + padding: 14px 0 0; +} +.shelf-menu .ok, +.shelf-menu .error { + display: block; + padding: 2px 0 14px; +} +.shelf-menu .submit2, +.shelf .submit2 { + font-size: 16px; + background: #bfb; + padding: 10px; +} +.shelf-menu .submit2:hover, +.shelf .submit2:hover { + background: #7f7; +} +.shelf-menu .submit2:active, +.shelf .submit2:active { + background: #4f4; +} +a.submit2.cancel { + color: #000; + background: #faa; +} +a.submit2.cancel:hover { + color: #000; + background: #f55; + text-decoration: none; +} +a.submit2.cancel:active { + background: #f33; +} +.delete { + display: inline-block; + font-weight: 700; + line-height: 30px; + color: #b11; + padding: 2px 0 12px; +} +.shelf > header h1 { + display: block; + font: bold 34px "Times New Roman", Times, serif; +} +.shelf > header h2 { + font-size: 20px; + margin: 0 0 15px; +} +.shelf .title i { + display: inline-block; + float: left; + background-color: #333; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + box-shadow: 0 1px 1px 1px #272727; + margin: 5px 10px 0 0; + padding: 3px 6px; +} +.shelf .title > div > div { + display: inline-block; + float: left; + text-align: left; +} +.shelf .title .fa { + color: #fff; + top: 2px; + left: 2px; + font-size: 50px; + height: auto; + min-width: 55px; + text-shadow: 0 0 1px #000, 1px 1px 4px #000, 1px 1px 6px #888; +} +.shelf .stats { + display: inline-block; + text-align: left; +} +.shelf > header { + max-width: 650px; + margin: 10px auto 0; + padding: 0 10px; +} +.shelf .shelfpage .result { + margin-top: 20px; + display: block; +} +.shelfpage .fic-cell { + box-shadow: none; +} +.shelfpage .fic-cell, +.comment-cell { + position: relative; + background: #fff; +} +.shelfpage .fic-cell:before, +.comment-cell:before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: -1; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + box-shadow: 5px 5px #ccc; +} +.comment-cell { + border: 1px solid #ccc; + font-family: Arial, Helvetica, sans-serif; + font-size: 16px; + line-height: 1.3em; + text-align: left; + margin: -14px 0 15px; + padding: 5px 10px; +} +.comment-cell .title { + font-size: 14px; + width: 95%; + border-bottom: 1px solid #ddd; + margin-bottom: 8px; + display: block; + text-align: left; + padding: 0 5px; +} +.review { + display: block; + max-height: 200px; + overflow-y: auto; +} +.comment-cell p { + border: 0; + margin: 0; + padding: 0; +} +.comment-cell p.double { + margin-top: 1.5em; +} +.comment-cell a.submit2 { + font-size: 14px; + margin: 6px 4px 2px; + padding: 2px 10px; +} +.comment-cell textarea { + display: block; + width: 100%; + max-width: 100%; + min-height: 140px; +} +.shelfpage { + display: inline-block; + margin-top: 5px; + padding: 0 15px 0 10px; +} +.shelf .result.user { + position: relative; + padding-bottom: 18px; + margin-top: 14px; +} +.revedit, +.ordedit { + position: absolute; + font-size: 12px; +} +.revedit { + bottom: 5px; + left: 10px; +} +.ordedit { + bottom: 0; + right: 2px; +} +.ordedit input { + max-width: 60px; +} +.ordedit .submit2 { + margin-left: 2px; + font-size: 12px; + padding: 2px 4px 2px 2px; +} +.fic-cell { + text-align: left; + border: 1px solid #ccc; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + box-shadow: 5px 5px #ccc; + font-size: 17px; + line-height: 22px; + position: relative; + width: 100%; + overflow: visible; + margin: 0 0 10px; + padding: 5px 10px; +} +.fic-cell a, +.fic-cell a:visited { + color: #66f; + text-decoration: none; +} +.fic-cell a:hover { + color: #282; + text-decoration: underline; +} +.fic-cell header img { + display: block; + float: left; + max-height: 150px; + max-width: 150px; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.2); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + margin: 0 15px 5px 0; + padding: 1px; +} +.fic-cell h1, +.fic-cell h2 { + font-size: 26px; + line-height: 30px; + display: inline-block; + color: #366; + margin: 0; +} +.fic-cell .details h2 { + display: inline; +} +.fic-cell header a, +.fic-cell header a:visited { + color: #366; + text-decoration: none; +} +.fic-cell header a:hover { + color: #366; + text-decoration: underline; +} +.fic-cell .author { + color: #666; + display: inline-block; + margin: 0 0 0 5px; +} +.fic-cell .popular { + display: block; + float: left; + overflow: hidden; + padding: 5px 10px; +} +.fic-cell .description { + overflow: hidden; + border: solid #adc 1px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + font-family: Arial, Helvetica, sans-serif; + margin: 10px 0 0; + padding: 4px 12px 5px; +} +.fic-cell br { + line-height: 0; +} +.rating, +.characters, +.rating *, +.characters * { + display: inline-block; + vertical-align: top; + margin-bottom: 2px; +} +.frating, +.fstatus, +.ftag { + font-size: 12px; + line-height: 16px; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + color: #000; + font-weight: 400; + font-family: Arial, Helvetica, sans-serif; + cursor: default; + text-align: center; + padding: 3px 8px; +} +.frating { + border: 1px solid rgba(0, 0, 0, 0.2); + text-shadow: 0 0 4px rgba(0, 0, 0, 0.4); +} +.frating.ev { + background: #3a3; + box-shadow: 0 1px 0 #292 inset; +} +.frating.ev:hover { + background: #4b4; +} +.frating.tn { + background: #ca0; + box-shadow: 0 1px 0 #b90 inset; +} +.frating.tn:hover { + background: #db1; +} +.frating.ma { + background: #b57; + box-shadow: 0 1px 0 #a46 inset; +} +.frating.ma:hover { + background: #c68; +} +.frating.ya { + background: #b73; + box-shadow: 0 1px 0 #a62 inset; +} +.frating.ya:hover { + background: #c84; +} +.frating.ad { + background: #88f; + box-shadow: 0 1px 0 #77e inset; +} +.frating.ad:hover { + background: #99f; +} +.frating.cl { + background: #d33; + box-shadow: 0 1px 0 #b11 inset; +} +.frating.cl:hover { + background: #e44; +} +.frating.cl2 { + background: #f66; + box-shadow: 0 1px 0 #d44 inset; +} +.frating.cl2:hover { + background: #f77; +} +.frating.db { + background: #ea7; + box-shadow: 0 1px 0 #d96 inset; +} +.frating.db:hover { + background: #fb8; +} +.frating.old { + font-size: 11px; + opacity: 0.6; + text-decoration: line-through; +} +.fstatus { + text-shadow: 0 0 8px rgba(255, 255, 255, 0.9); + border: 1px solid rgba(0, 0, 0, 0.2); +} +.fstatus:before { + color: #000; + width: 16px; + margin-right: 5px; + font-family: FontAwesome, "Trebuchet MS", Helvetica, sans-serif; +} +.fstatus.sc { + background: #5a5; + box-shadow: 0 1px 0 #494 inset; +} +.fstatus.sc:before { + content: "\f00c"; +} +.fstatus.sc:hover { + background: #6b6; +} +.fstatus.si { + background: #fa1; + box-shadow: 0 1px 0 #fd0 inset; +} +.fstatus.si:before { + content: "\f040"; +} +.fstatus.si:hover { + background: #fc0; +} +.fstatus.sh { + background: #b74; +} +.fstatus.sh:before { + content: "\f04c"; +} +.fstatus.sh:hover { + background: #c85; +} +.fstatus.sn { + background: #b33; +} +.fstatus.sn:before { + content: "\f05e"; +} +.fstatus.sn:hover { + background: #c44; +} +.ftag { + color: #fff; + text-shadow: 0 0 6px rgba(0, 0, 0, 0.9); + letter-spacing: 1px; +} +.ftag.adv { + background-color: #394; + box-shadow: 0 1px 0 #5b6 inset; + text-shadow: 0 0 3px #172; + border: 1px solid #172; +} +.ftag.adv:hover { + background-color: #283; +} +.ftag.rom { + background-color: #94f; + box-shadow: 0 1px 0 #b6f inset; + text-shadow: 0 0 3px #72d; + border: 1px solid #72d; +} +.ftag.rom:hover { + background-color: #83e; +} +.ftag.rnd { + background-color: #37c; + box-shadow: 0 1px 0 #59f inset; + text-shadow: 0 0 3px #15a; + border: 1px solid #15a; +} +.ftag.rnd:hover { + background-color: #26b; +} +.ftag.com { + background-color: #ca2; + box-shadow: 0 1px 0 #ec4 inset; + text-shadow: 0 0 3px #a80; + border: 1px solid #a80; +} +.ftag.com:hover { + background-color: #b91; +} +.ftag.lif { + background-color: #48f; + box-shadow: 0 1px 0 #6af inset; + text-shadow: 0 0 3px #26d; + border: 1px solid #26d; +} +.ftag.lif:hover { + background-color: #37e; +} +.ftag.trg { + background-color: #fb4; + box-shadow: 0 1px 0 #fd6 inset; + text-shadow: 0 0 3px #d92; + border: 1px solid #d92; +} +.ftag.trg:hover { + background-color: #ea3; +} +.ftag.sad { + background-color: #949; + box-shadow: 0 1px 0 #b6b inset; + text-shadow: 0 0 3px #727; + border: 1px solid #727; +} +.ftag.sad:hover { + background-color: #838; +} +.ftag.drk { + background-color: #b33; + box-shadow: 0 1px 0 #d55 inset; + text-shadow: 0 0 3px #911; + border: 1px solid #911; +} +.ftag.drk:hover { + background-color: #a22; +} +.ftag.alt { + background-color: #888; + box-shadow: 0 1px 0 #aaa inset; + text-shadow: 0 0 3px #666; + border: 1px solid #666; +} +.ftag.alt:hover { + background-color: #777; +} +.ftag.crs { + background-color: #3a9; + box-shadow: 0 1px 0 #5cb inset; + text-shadow: 0 0 3px #187; + border: 1px solid #187; +} +.ftag.crs:hover { + background-color: #298; +} +.ftag.hum { + background-color: #b85; + box-shadow: 0 1px 0 #da7 inset; + text-shadow: 0 0 3px #963; + border: 1px solid #963; +} +.ftag.hum:hover { + background-color: #a74; +} +.ftag.ath { + background-color: #b65; + box-shadow: 0 1px 0 #d87 inset; + text-shadow: 0 0 3px #943; + border: 1px solid #943; +} +.ftag.ath:hover { + background-color: #a54; +} +.ftag.gor { + background-color: #722; + box-shadow: 0 1px 0 #944 inset; + text-shadow: 0 0 3px #500; + border: 1px solid #500; +} +.ftag.gor:hover { + background-color: #611; +} +.ftag.sex { + background-color: #c49; + box-shadow: 0 1px 0 #e6b inset; + text-shadow: 0 0 3px #a27; + border: 1px solid #a27; +} +.ftag.sex:hover { + background-color: #b38; +} +.ftag.p2 { + background-color: #48a; + box-shadow: 0 1px 0 #6ac inset; + text-shadow: 0 0 3px #268; + border: 1px solid #268; +} +.ftag.p2:hover { + background-color: #59b; +} +.ftag.thr { + background-color: #c36; + box-shadow: 0 1px 0 #e58 inset; + text-shadow: 0 0 3px #a14; + border: 1px solid #a14; +} +.ftag.thr:hover { + background-color: #b25; +} +.ftag.dra { + background-color: #d5d; + box-shadow: 0 1px 0 #f7f inset; + text-shadow: 0 0 3px #c3b; + border: 1px solid #c3b; +} +.ftag.dra:hover { + background-color: #d4c; +} +.ftag.hor { + background-color: #622; + box-shadow: 0 1px 0 #844 inset; + text-shadow: 0 0 3px #400; + border: 1px solid #400; +} +.ftag.hor:hover { + background-color: #511; +} +.ftag.eqg { + background-color: #538; + box-shadow: 0 1px 0 #75a inset; + text-shadow: 0 0 3px #316; + border: 1px solid #316; +} +.ftag.eqg:hover { + background-color: #427; +} +.ftag.mys { + background-color: #444; + box-shadow: 0 1px 0 #666 inset; + text-shadow: 0 0 3px #222; + border: 1px solid #222; +} +.ftag.mys:hover { + background-color: #333; +} +.ftag.sci { + background-color: #66a; + box-shadow: 0 1px 0 #88c inset; + text-shadow: 0 0 3px #448; + border: 1px solid #448; +} +.ftag.sci:hover { + background-color: #559; +} +.ftag.pro { + background-color: #c62; + box-shadow: 0 1px 0 #e84 inset; + text-shadow: 0 0 3px #a40; + border: 1px solid #a40; +} +.ftag.pro:hover { + background-color: #b51; +} +.ftag.vil { + background-color: #d33; + box-shadow: 0 1px 0 #f55 inset; + text-shadow: 0 0 3px #b11; + border: 1px solid #b11; +} +.ftag.vil:hover { + background-color: #c22; +} +.ftag.dth { + background-color: #333; + box-shadow: 0 1px 0 #555 inset; + text-shadow: 0 0 3px #111; + border: 1px solid #111; +} +.ftag.dth:hover { + background-color: #222; +} +.ftag.hrm { + background-color: #c22; + box-shadow: 0 1px 0 #e44 inset; + text-shadow: 0 0 3px #a00; + border: 1px solid #a00; +} +.ftag.hrm:hover { + background-color: #b11; +} +.character { + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + display: inline-block; + max-height: 24px; +} +.published, +.updated { + color: #888; + font: 10pt "Lucida Sans Unicode", "Lucida Grande", sans-serif; + line-height: 10pt; + white-space: pre-wrap; +} +.ficstats { + display: inline-block; + padding: 5px 0; +} +span.star-rating { + font-size: 13px; + line-height: 13px; + overflow: auto; + margin: 4px 0 0; +} +span.star-rating .stars-sm { + float: left; + margin-right: 4px; +} +.stars-txt { + display: inline-block; + line-height: 23px; +} +.stars { + width: 190px; + display: inline-block; +} +.stars-sm { + display: inline-block; + padding: 1px 0 0 4px; +} +label.star { + float: right; + font-size: 30px; + color: #444; + transition: all 0.2s; + padding: 10px 5px; +} +.stars-sm label.star { + font-size: 20px; + padding: 2px; +} +input.star:checked ~ label.star:before { + content: "\f005"; + color: #fd4; + transition: all 0.25s; +} +input.star-5:checked ~ label.star:before { + color: #fe7; + text-shadow: 0 0 20px #952; +} +.stars-sm input.star-5:checked ~ label.star:before { + text-shadow: 0 0 10px #952; +} +label.star:hover { + transform: rotate(-15deg) scale(1.3); +} +label.star:before { + content: "\f006"; + font-family: FontAwesome; +} +span.star { + float: right; + width: 20px; + height: 20px; + line-height: 20px; + font-size: 20px; + position: relative; + margin: 2px; + padding: 0; +} +span.star:before { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + font-family: FontAwesome; + content: "\f006"; + color: #444; +} +span.star.star-filled:before, +span.star.star-filled ~ span.star:before, +span.star.star-half ~ span.star:before { + content: "\f005"; + color: #fd4; + text-shadow: 1px 1px 1px #220, 0 0 1px #220; +} +span.star.star-half:before { + content: "\f005"; + color: #888; + text-shadow: 1px 1px 1px #220, 0 0 1px #220; +} +span.star.star-half:after { + position: absolute; + font-family: FontAwesome; + content: "\f005"; + color: #fd4; + width: 40%; + overflow: hidden; +} +span.star.star-filled.star-5:before, +span.star.star-filled.star-5 ~ span.star:before { + content: "\f005"; + color: #fe7; + text-shadow: 0 0 20px #952; +} +.stars-sm span.star.star-filled.star-5:before, +.stars-sm span.star.star-filled.star-5 ~ span.star:before { + text-shadow: 1px 1px 1px #220, 0 0 1px #220, 0 0 5px #f86, 0 0 10px #952; +} +.fic-tiles { + display: block; + text-align: center; +} +.fic-tiles .fic-cell { + width: 340px; + display: inline-block; + overflow: hidden; + font-size: 13px; + line-height: 13px; + box-shadow: 2px 2px #ccc; + vertical-align: top; + margin: 3px; + padding: 4px; +} +.fic-tiles .fic-cell h1, +.fic-tiles .fic-cell h2 { + font-size: 22px; + line-height: 22px; + padding: 2px 8px 3px 0; +} +.fic-tiles .fic-cell header img { + max-height: 90px; + max-width: 90px; + margin: 0 8px 5px 0; +} +.fic-tiles .popular .stats { + display: none !important; +} +.fic-tiles .fic-cell .popular { + line-height: 0; + padding: 2px 0; +} +.fic-tiles .fic-cell .star-rating { + padding: 1px 0 0; +} +.fic-tiles span.star { + width: 16px; + height: 16px; + line-height: 16px; + font-size: 16px; +} +.fic-tiles .fic-cell .description { + margin: 2px 0; +} +.fic-tiles .ficsrc, +.fic-tiles .character { + max-height: 20px; +} +.fic-tiles .frating, +.fic-tiles .fstatus, +.fic-tiles .ftag { + font-size: 10px; + line-height: 14px; + color: transparent; + width: 25px; + height: 20px; + overflow: hidden; + padding: 2px 0; +} +.fic-tiles .frating:before, +.fic-tiles .ftag:before, +.fic-tiles .fstatus:before { + color: #000; + width: 16px; + display: inline-block; + text-align: center; + font-weight: 700; + margin: 0; +} +.fic-tiles .frating.ev:before { + content: "E"; +} +.fic-tiles .frating.tn:before { + content: "T"; +} +.fic-tiles .frating.ma:before { + content: "M"; +} +.fic-tiles .frating.ya:before { + content: "YA"; +} +.fic-tiles .frating.ad:before { + content: "AD"; +} +.fic-tiles .frating.cl:before { + content: "X"; +} +.fic-tiles .frating.db:before { + content: "?"; +} +.fic-tiles .ftag.adv:before { + content: "Ad"; +} +.fic-tiles .ftag.rom:before { + content: "Ro"; +} +.fic-tiles .ftag.rnd:before { + content: "Ra"; +} +.fic-tiles .ftag.com:before { + content: "Co"; +} +.fic-tiles .ftag.lif:before { + content: "Li"; +} +.fic-tiles .ftag.trg:before { + content: "Tr"; +} +.fic-tiles .ftag.sad:before { + content: "Sa"; +} +.fic-tiles .ftag.drk:before { + content: "Da"; +} +.fic-tiles .ftag.alt:before { + content: "AU"; +} +.fic-tiles .ftag.crs:before { + content: "Cr"; +} +.fic-tiles .ftag.hum:before { + content: "Hu"; +} +.fic-tiles .ftag.ath:before { + content: "An"; +} +.fic-tiles .ftag.gor:before { + content: "Go"; +} +.fic-tiles .ftag.sex:before { + content: "Se"; +} +.fic-tiles .ftag.p2:before { + content: "2p"; +} +.fic-tiles .ftag.thr:before { + content: "Th"; +} +.fic-tiles .ftag.dra:before { + content: "Dr"; +} +.fic-tiles .ftag.hor:before { + content: "Ho"; +} +.fic-tiles .ftag.eqg:before { + content: "Eq"; +} +.fic-tiles .ftag.mys:before { + content: "My"; +} +.fic-tiles .ftag.sci:before { + content: "Sc"; +} +.fic-tiles .ftag.pro:before { + content: "Pf"; +} +.fic-tiles .ftag.vil:before { + content: "Vi"; +} +.fic-tiles .ftag.dth:before { + content: "De"; +} +.fic-tiles .ftag.hrm:before { + content: "Ha"; +} +.fic-tiles .ficstats { + width: 100%; + color: #616161; + padding: 5px 0 0; +} +.fic-tiles .ficstats .chapters { + float: left; + margin-right: 3px; +} +.fic-tiles .readrev { + position: absolute; + right: 6px; + bottom: 3px; +} +body > footer { + min-height: 165px; + background-color: #e9e9e9; + color: #777; +} +body > footer section { + text-align: left; + display: inline-block; + vertical-align: top; + width: 200px; + min-height: 150px; + position: relative; + padding: 0 10px 10px; +} +body > footer section:nth-child(2) { + width: 300px; +} +body > footer section + section:before { + position: absolute; + display: block; + content: ""; + top: 15px; + left: -3px; + height: 130px; + border-left: 1px solid #bbb; +} +body > footer .stat { + display: block; + font-size: 80%; + margin-bottom: 4px; +} +body > footer h4 { + border-bottom: 1px solid #bbb; + padding-bottom: 10px; + margin-bottom: 14px; +} +body > footer .media-links svg { + max-height: 40px; + max-width: 40px; + display: block; + float: left; + margin: 2px 5px; +} +body > footer .media-links { + height: 40px; +} +body > footer .fb-logo { + background-color: #fff; +} +body > footer input[type="image"] { + margin: 6px 0 0; +} +body.fotd .result { + display: inline-block; + width: 100%; + max-width: 700px; + padding: 0; +} +body.fotd .pagenav { + display: inline-block; + padding: 0; +} +.fotd.sm { + max-width: 575px; + text-align: center; + margin: 90px auto 10px; + padding: 0 15px 0 10px; +} +.fotd.sm .fic-cell .popular.popsmall { + display: block !important; + font-size: 14px; + line-height: 14px; +} +article.fotd { + display: inline-block; + width: 100%; + position: relative; + -webkit-box-shadow: 0 3px 10px rgba(0, 0, 0, 0.7); + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.7); + background-color: #fff; + padding: 8px 10px; +} +article.fotd > header { + font: normal 50px / normal Helvetica, sans-serif; + color: #0d242d; + text-shadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9; + margin: 0; + padding: 0 0 10px; +} +article.fotd > footer { + width: 90%; + border-top: 1px solid #000; + border-right: 20px solid #fff; + border-left: 20px solid #fff; + margin: 10px auto 0; + padding: 10px 0; +} +article.fotd .fic-cell { + border: none; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + box-shadow: none; + min-height: 0; + margin: 0; + padding: 0; +} +.fic-cell .fotd-day { + display: block; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + border-top-right-radius: 4px; + text-align: center; + border-bottom: 1px solid #ccc; + box-shadow: 0 0 4px #777 inset; + font: bold 23px "Palatino Linotype", "Book Antiqua", Palatino, serif; + margin: -5px -10px 10px; + padding: 10px 5px; +} +.loginbox { + position: fixed; + z-index: 1001; + top: 10%; + left: 50%; + width: 360px; + height: auto; + display: block; + background: #fff; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.4); + text-align: center; + border: 1px solid #b4b1b1; + visibility: hidden; + line-height: 1em; + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + -o-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + -webkit-transition: opacity 0.5s, top 0.5s; + -moz-transition: opacity 0.5s, top 0.5s; + -ms-transition: opacity 0.5s, top 0.5s; + -o-transition: opacity 0.5s, top 0.5s; + transition: opacity 0.5s, top 0.5s; + padding: 15px; +} +.overlay:target + .loginbox { + top: 50%; + opacity: 1; + visibility: visible; +} +.overlay:target { + visibility: visible; + opacity: 1; +} +.overlay { + position: fixed; + opacity: 0; + visibility: hidden; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.8); + cursor: default; + z-index: 1000; + -webkit-transition: opacity 0.5s; + -moz-transition: opacity 0.5s; + -ms-transition: opacity 0.5s; + -o-transition: opacity 0.5s; + transition: opacity 0.5s; +} +.loginbox h1 { + font-family: Montserrat, sans-serif; + color: #803271; + line-height: 1.5em; + font-weight: 700; + font-size: 190%; + margin: 0 0 5px; +} +.loginbox .sep { + position: relative; + border-top: 2px solid #888; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + margin: 15px; +} +.loginbox .sep > div { + position: absolute; + top: -8px; + left: 50%; + background: linear-gradient(90deg, rgba(255, 255, 255, 0.1), #fff, #fff, #fff, rgba(255, 255, 255, 0.1)); + width: 180px; + margin-left: -90px; +} +.loginbox .fb-logo { + height: 32px; + width: 32px; + border-radius: 2px; + background: #4c69ba; + margin: 2px 5px; + padding: 4px; +} +.loginbox .fb-logo:hover { + background: #5b7bd5; + cursor: pointer; +} +.loginbox .fblogin > div { + display: inline-block; + vertical-align: middle; +} +.loginbox .fbprompt { + margin-right: 10px; + padding-right: 13px; + border-right: 1px solid #888; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; +} +.loginbox .fflogin { + padding-top: 10px; +} +.loginbox .info { + text-align: right; + width: 250px; + font-size: 13px; + margin: 0 auto; +} +.loginbox .info input { + display: inline-block; + width: 160px; + border: 1px solid #c0c2c7; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + color: #161717; + font-style: none; + margin-bottom: 12px; + background-color: #fff; + -moz-box-shadow: inset 0 1px 3px -1px #b4b1b1; + -webkit-box-shadow: inset 0 1px 3px -1px #b4b1b1; + box-shadow: inset 0 1px 3px -1px #b4b1b1; + padding: 6px; +} +.loginbox .info input:focus { + outline: none; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.2), 0 0 5px 1px #51cbee; +} +.fflogin.loginerror .info input { + box-shadow: 0 0 6px 1px red, inset 0 0 3px 1px red; +} +.lerrbox { + border: 1px solid #000; + width: 90%; + margin: -10px auto 15px; + padding: 10px 5px; +} +.loginbox .loginbuttons { + vertical-align: top; +} +.loginbox input.login { + border: 1px solid; + font-size: 14px; + cursor: pointer; + color: #fff; + background: #09f; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + padding: 6px 28px; +} +.loginbox a.register { + color: #666 !important; + font: 11pt Verdana, Geneva, sans-serif; + display: inline-block; + padding: 6px 8px 0; +} +.loginremember { + height: 25px; + line-height: 25px; + font-family: LatoRegular; + color: #7e7e7e; + font-size: 12px; + margin-bottom: -8px; +} +.alogin a { + position: absolute; + display: block; + top: 0; + right: 0; + z-index: 1; + color: #33f; + text-decoration: none; + font-family: Verdana, Geneva, sans-serif; + padding: 10px 10px 15px 15px; +} +.alogin a:visited { + color: #33f; +} +.alogin a:hover { + color: #00f; + font-weight: 700; +} +@font-face { + font-family: PonyEmoji; + src: url(/fonts/ponyemoji.ttf); + font-weight: 400; + font-style: normal; +} +.fa-pony-rd-happy:before { + font-family: PonyEmoji; + content: "\1f600"; +} +.fa-pony-ab-beam:before { + font-family: PonyEmoji; + content: "\1f601"; +} +.fa-pony-scoots-happy:before { + font-family: PonyEmoji; + content: "\1f602"; +} +.fa-pony-twi-happy:before { + font-family: PonyEmoji; + content: "\1f603"; +} +.fa-pony-rd-grin:before { + font-family: PonyEmoji; + content: "\1f604"; +} +.fa-pony-lyra-unsure:before { + font-family: PonyEmoji; + content: "\1f605"; +} +.fa-pony-flutter-yay:before { + font-family: PonyEmoji; + content: "\1f606"; +} +.fa-pony-sweetie-angel:before { + font-family: PonyEmoji; + content: "\1f607"; +} +.fa-pony-twi-crazy:before { + font-family: PonyEmoji; + content: "\1f608"; +} +.fa-pony-flutter-wink:before { + font-family: PonyEmoji; + content: "\1f609"; +} +.fa-pony-lyra-happy:before { + font-family: PonyEmoji; + content: "\1f60a"; +} +.fa-pony-pinkie-silly:before { + font-family: PonyEmoji; + content: "\1f60b"; +} +.fa-pony-rd-beam:before { + font-family: PonyEmoji; + content: "\1f60c"; +} +.fa-pony-scoots-love:before { + font-family: PonyEmoji; + content: "\1f60d"; +} +.fa-pony-rd-cool:before { + font-family: PonyEmoji; + content: "\1f60e"; +} +.fa-pony-twi-smirk:before { + font-family: PonyEmoji; + content: "\1f60f"; +} +.fa-pony-flutter-stare:before { + font-family: PonyEmoji; + content: "\1f610"; +} +.fa-pony-aj-unsure:before { + font-family: PonyEmoji; + content: "\1f611"; +} +.fa-pony-rd-unamused:before { + font-family: PonyEmoji; + content: "\1f612"; +} +.fa-pony-ab-unsure:before { + font-family: PonyEmoji; + content: "\1f613"; +} +.fa-pony-rd-pensive:before { + font-family: PonyEmoji; + content: "\1f614"; +} +.fa-pony-zecora-confused:before { + font-family: PonyEmoji; + content: "\1f615"; +} +.fa-pony-aj-disgust:before { + font-family: PonyEmoji; + content: "\1f616"; +} +.fa-pony-rd-soawesome:before { + font-family: PonyEmoji; + content: "\1f617"; +} +.fa-pony-aj-jewel-kiss:before { + font-family: PonyEmoji; + content: "\1f618"; +} +.fa-pony-rd-startled:before { + font-family: PonyEmoji; + content: "\1f619"; +} +.fa-pony-aj-pensive:before { + font-family: PonyEmoji; + content: "\1f61a"; +} +.fa-pony-fluffles:before { + font-family: PonyEmoji; + content: "\1f61b"; +} +.fa-pony-flutter-grin:before { + font-family: PonyEmoji; + content: "\1f61c"; +} +.fa-pony-aj-cheer:before { + font-family: PonyEmoji; + content: "\1f61d"; +} +.fa-pony-rd-disappointed:before { + font-family: PonyEmoji; + content: "\1f61e"; +} +.fa-pony-pinkie-worried:before { + font-family: PonyEmoji; + content: "\1f61f"; +} +.fa-pony-rarity-angry:before { + font-family: PonyEmoji; + content: "\1f620"; +} +.fa-pony-aj-angry:before { + font-family: PonyEmoji; + content: "\1f621"; +} +.fa-pony-ab-sad:before { + font-family: PonyEmoji; + content: "\1f622"; +} +.fa-pony-twi-angry:before { + font-family: PonyEmoji; + content: "\1f623"; +} +.fa-pony-pinkie-angry:before { + font-family: PonyEmoji; + content: "\1f624"; +} +.fa-pony-scoots-sad:before { + font-family: PonyEmoji; + content: "\1f625"; +} +.fa-pony-rarity-shocked:before { + font-family: PonyEmoji; + content: "\1f626"; +} +.fa-pony-aj-scared:before { + font-family: PonyEmoji; + content: "\1f627"; +} +.fa-pony-rarity-scared:before { + font-family: PonyEmoji; + content: "\1f628"; +} +.fa-pony-pinkie-cry:before { + font-family: PonyEmoji; + content: "\1f629"; +} +.fa-pony-twi-scared:before { + font-family: PonyEmoji; + content: "\1f62a"; +} +.fa-pony-flutter-angry:before { + font-family: PonyEmoji; + content: "\1f62b"; +} +.fa-pony-aj-shocked:before { + font-family: PonyEmoji; + content: "\1f62c"; +} +.fa-pony-flutter-cry:before { + font-family: PonyEmoji; + content: "\1f62d"; +} +.fa-pony-rd-shocked:before { + font-family: PonyEmoji; + content: "\1f62e"; +} +.fa-pony-twi-unsure:before { + font-family: PonyEmoji; + content: "\1f62f"; +} +.fa-pony-ab-scared:before { + font-family: PonyEmoji; + content: "\1f630"; +} +.fa-pony-rd-scared:before { + font-family: PonyEmoji; + content: "\1f631"; +} +.fa-pony-spike-worried:before { + font-family: PonyEmoji; + content: "\1f632"; +} +.fa-pony-flutter-blush:before { + font-family: PonyEmoji; + content: "\1f633"; +} +.fa-pony-ab-sleep:before { + font-family: PonyEmoji; + content: "\1f634"; +} +.fa-pony-ab-cry:before { + font-family: PonyEmoji; + content: "\1f635"; +} +.fa-pony-mac:before { + font-family: PonyEmoji; + content: "\1f636"; +} +.fa-pony-mask:before { + font-family: PonyEmoji; + content: "\1f637"; +} +.fa-pony-ab-happy:before { + font-family: PonyEmoji; + content: "\1f638"; +} +.fa-pony-twi-laugh:before { + font-family: PonyEmoji; + content: "\1f639"; +} +.fa-pony-rarity-happy:before { + font-family: PonyEmoji; + content: "\1f63a"; +} +.fa-pony-ab-love:before { + font-family: PonyEmoji; + content: "\1f63b"; +} +.fa-pony-flutter-wrygrin:before { + font-family: PonyEmoji; + content: "\1f63c"; +} +.fa-pony-pinkie-duckface:before { + font-family: PonyEmoji; + content: "\1f63d"; +} +.fa-pony-pinkie-glare:before { + font-family: PonyEmoji; + content: "\1f63e"; +} +.fa-pony-sweetie-sad:before { + font-family: PonyEmoji; + content: "\1f63f"; +} +.fa-pony-twi-sad:before { + font-family: PonyEmoji; + content: "\1f640"; +} +.fa-pony-flutter-worried:before { + font-family: PonyEmoji; + content: "\1f641"; +} +.fa-pony-pinkie:before { + font-family: PonyEmoji; + content: "\1f642"; +} +.fa-pony-pinkie-party:before { + font-family: PonyEmoji; + content: "\1f644"; +} +.user { + display: inline-block; + max-width: 1100px; + width: 100%; + text-align: left; + overflow: visible; + margin: 30px 0 10px; + padding: 0 15px; +} +.user .name h1 { + font: bold 34px "Times New Roman", Times, serif; + word-break: break-all; + display: inline-block; + margin: 2px; +} +.user .name img { + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + float: left; + top: 2px; + position: relative; + margin-right: 5px; +} +.user .avatar { + position: relative; + float: left; + border: 1px solid #333; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + overflow: hidden; + margin: 0 15px 5px 0; + padding: 3px; +} +.user .avatar:after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.user .avatar img { + display: block; + background-color: #cacaca; + max-height: 200px; + max-width: 200px; + width: 100%; + height: 100%; +} +.user .avatar img.preview { + max-height: none; + max-width: none; + width: auto !important; + height: auto !important; + z-index: 1; +} +.user .avatar a { + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + background-color: rgba(255, 255, 255, 0.7); + -webkit-transition-delay: 0.5s; + transition-delay: 0.5s; + transition: opacity 0.4s ease-out, padding 0.4s ease-out; + z-index: 99; + padding: 1px 5px; +} +.user .avatar a:hover { + opacity: 1 !important; + text-decoration: none; + padding: 4px 5px 1px 9px; +} +.user .avatar a.change { + position: absolute; + right: 5px; + bottom: 5px; + opacity: 0.5; +} +.user .avatar a.save { + position: absolute; + left: 5px; + bottom: 5px; + visibility: hidden; +} +.user .avatar a.save:hover { + padding: 4px 9px 1px 5px; +} +body.droppable .avatar { + border: 5px dashed #add8e6; + z-index: 9999; +} +.user.patreon .info .name, +.user.admin .info .name { + display: inline-block; + position: relative; +} +.user.patreon .info .name:after { + content: "Patreon Supporter!"; + line-height: 30px; + background: 3px 1px url(/img/patreon-logo.svg) no-repeat; + background-size: 30px; + display: block; + height: 30px; + width: min-content; + white-space: nowrap; + overflow: hidden; + top: 2px; + right: 4px; + z-index: 2; + background-color: #ffe971; + box-shadow: 0 1px 1px 1px #555; + margin: 0 0 5px 3px; + padding: 2px 2px 1px 40px; +} +.user.admin .info .name:after { + content: "Admin"; + line-height: 30px; + background: 3px 1px url(/img/logo-admin.svg) no-repeat; + background-size: 30px; + display: block; + height: 30px; + width: min-content; + white-space: nowrap; + overflow: hidden; + top: 2px; + right: 4px; + z-index: 2; + background-color: #ffd578; + box-shadow: 0 1px 1px 1px #555; + margin: 0 0 5px 3px; + padding: 2px 8px 1px 36px; +} +.avatar a.rot { + position: absolute; + top: 2px; + visibility: hidden; + padding: 5px; +} +.user .avatar a.rot:hover { + padding: 5px; +} +.avatar .rot.rotr { + right: 2px; +} +.avatar .rot.rotl { + left: 2px; + -moz-transform: scaleX(-1); + -o-transform: scaleX(-1); + -webkit-transform: scaleX(-1); + transform: scaleX(-1); + filter: FlipH; + -ms-filter: FlipH; +} +.avatar .rot.rotm { + left: 50%; + margin-left: -30px; +} +#profile-loading { + position: absolute; + top: 0; + visibility: hidden; + z-index: 2; +} +.loading { + background-color: transparent !important; + -webkit-animation: spin 2s linear infinite; + -moz-animation: spin 2s linear infinite; + animation: spin 2s linear infinite; +} +.user header { + display: block; + overflow: auto; +} +.user .bio { + overflow: hidden; + border: solid #adc 1px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + margin: 5px 0; + padding: 4px 12px 5px; +} +.user .info label { + color: #666; +} +.user .online { + color: #2a2; +} +.user .online:before { + width: 16px; + font-size: 16px; + display: inline-block; + text-align: center; + font-weight: 700; + content: "●"; + margin: 0; +} +.comment-cell.review-popup { + margin: -4px 10px 15px 18px; +} +body.main { + text-align: left; +} +.searchbox { + background: #fff; + height: 34px; + box-sizing: border-box; + -webkit-box-sizing: border-box; + border: 1px solid #ddd; + border-top-color: #ccc; + -webkit-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; + margin: 12px 0; +} +.searchbox:hover { + border: 1px solid #999; +} +.searchboxactive { + box-shadow: 0 0 5px #51cbee !important; + border: 1px solid #51cbee !important; +} +.searchbox input { + font-weight: lighter; + font-family: arial, sans-serif; + font-size: 14pt; + width: 98%; + border: none; + outline: none; + background-color: inherit; + margin: 0; + padding: 5px; +} +.search input[type="submit"] { + min-width: 150px; + background-color: #eee; + border: 1px solid #999; + color: #333; + margin: 10px 5px; + padding: 5px; +} +.search input[type="submit"]:hover { + background: #f3f9e7; +} +.search input[type="submit"]:active { + background: #badc74; +} +.search-adv-link { + display: block; + font: 10pt Verdana, Geneva, sans-serif; + margin: 8px 5px; +} +.search-adv-link.active a { + background-color: #ff8; + padding: 0 4px; +} +.search-adv-link.active.expanded a { + background-color: transparent; +} +.search-adv-link a, +.search-adv-link a:visited { + font-size: 9pt; + line-height: 12pt; + color: #33f; +} +.search-adv-link a:hover { + color: #009; +} +.search-adv-link.expanded { + font-size: 12pt; + font-weight: 700; + margin: 8px 5px 2px; +} +.search-adv-link.expanded a, +.search-adv-link.expanded a:visited { + font-size: 12pt; + color: #000; + text-decoration: none; +} +.search-adv { + max-width: 600px; + border: 1px #333 solid; + margin: 0 0 10px; + padding: 5px; +} +.search-adv div.opt { + text-align: left; + float: left; + min-height: 168px; + padding: 2px 4px; +} +.search-adv div.opt2 { + min-height: 90px; +} +.search-adv div.opt > div { + padding-bottom: 2px; + margin-bottom: 10px; + min-height: 0; +} +.search-adv div.opt div:last-of-type { + min-height: 0; + margin-bottom: 0; +} +.search-adv div.opt.full { + float: none; + width: 100%; + clear: both; + min-height: 0; +} +.search-adv div.opt.bottom { + min-height: 0; +} +.search-adv div.opt b { + display: inline-block; + padding-top: 3px; + margin-left: -2px; + text-shadow: 0 0 4px #ccc; + font-variant: small-caps; + font-family: sans-serif; +} +.search-adv input[type="number"], +.search-adv .number { + max-width: 100px; + margin: 0 3px; +} +.search-adv div.opt input[type="text"], +.search-adv div.opt input[type="number"] { + line-height: 18px; + padding: 0 2px; +} +.search-adv a.remove { + position: absolute; + top: 50%; + font-size: 12px; + text-decoration: none; + font-family: FontAwesome; + vertical-align: middle; + width: 20px; + height: 20px; + text-align: center; + border-radius: 3px; + background-clip: padding-box; + color: rgba(0, 0, 0, 0.7); + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.15); + margin: -12px 0 0 5px; + padding: 0; +} +.search-adv a.remove:before { + content: "\f00d"; + line-height: 19px; +} +.search-adv .selected-tags > div { + position: relative; + height: 24px; + border: 1px solid #ccc; + border-radius: 3px; + float: left; + vertical-align: middle; + display: table; + cursor: pointer; + background: #cf9; + margin: 4px 2px; + padding: 2px 30px 2px 2px; +} +.search-adv img.icon { + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + max-height: 24px; + margin-bottom: -3px; + padding: 0 2px; +} +.search-l { + text-align: center; + min-height: 100px; + max-width: 600px; + margin: 150px auto 30px; +} +.search-s { + overflow: auto; + width: 100%; + border-bottom: 1px solid #ddd; + margin: 0 0 20px; + padding: 0 0 5px; +} +.search-s a.logo img { + position: absolute; + top: 5px; + left: 8px; +} +.search-s form { + position: relative; + width: 100%; + box-sizing: border-box; + padding: 0 60px 0 140px; +} +.search-s form, +.search-s .searchbox { + display: inline-block; + margin: 0; +} +.search-s .searchbox { + width: 100%; + max-width: 350px; + margin: 10px 0 0; +} +.search-s .searchbox:after { + content: ""; + clear: both; +} +.search-s .search-adv { + margin: 10px 0 10px -116px; +} +.result { + max-width: 750px; + margin: 10px 0; + padding: 0 20px 0 60px; +} +.result section { + display: inline-block; + width: 100%; +} +.searchtime { + padding: 0 60px 0 0; +} +.searchnav { + max-width: 750px; + text-align: center; + margin: 10px 0; + padding: 0 0 0 65px; +} +.searchnav ul { + display: inline-block; + max-width: 90%; + padding: 0 50px 0 0; +} +body.story #wrap { + padding-left: 10px; + padding-right: 15px; +} +.fic-cell.large { + display: inline-block; + max-width: 950px; + margin-top: 20px; +} +.story .notice { + margin: 15px 0 0; +} +.story404 { + text-align: center; + margin: 15px 0; + padding: 10px; +} +.story404 > div { + background: #500200 0 0 / cover url(/img/destruction_by_adiwan-sm.jpg); + min-height: 400px; + max-width: 800px; + margin: 0 auto; + padding: 0; +} +.story404 h1 { + font-weight: 700; + text-shadow: 1px 1px 2px #ff6c6c, 0 0 9px #000, 0 1px 9px #000; + font-size: 42px; + color: #fafafa; + display: inline-block; + box-shadow: 0 1px 16px red; + background: rgba(255, 255, 255, 0.7); + padding: 10px 20px; +} +.story404 h1:after { + content: ""; + display: block; +} +.story404 p { + display: inline-block; + background: rgba(255, 255, 255, 0.9); + margin: 15px; + padding: 15px 25px; +} +.desc_short { + max-width: 700px; + font-size: 90%; + color: #333; +} +.dl-links { + height: 55px; + padding: 9px 4px; +} +.dl-links > span { + font: normal 17px "Times New Roman", serif; + display: block; + float: left; +} +.dl-links a { + display: block; + float: left; + margin-left: 5px; +} +.description span.spoiler { + background-color: #333; + color: #333; + text-shadow: none; +} +.description span.spoiler:hover { + background-color: inherit; + color: inherit; + text-shadow: inherit; +} +.description.full, +.chapterlist.full { + max-height: none !important; +} +.fic-cell .chapterlist { + overflow: hidden; +} +.chapters h3 { + font-weight: 700; + margin: 0; +} +.chapterlist h3 { + margin: 18px 0 10px; +} +.chapter_title { + font: normal 14pt "Trebuchet MS", Helvetica, sans-serif; + color: #111; +} +.chapterlist a { + font-size: 0.8em; +} +.chapterlist .date { + font-size: 0.7em; +} +.chapterlist .word_count { + font-size: 0.7em; + float: right; + right: 10px; +} +.fa-plus-square { + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + margin-right: 5px; +} +.fa-plus-square:before { + font: bold 14px FontAwesome; + content: "\f0fe"; +} +.fa-minus-square:before { + font: bold 14px FontAwesome; + content: "\f146"; +} +.featured { + margin: 0 0 15px; +} +.featured > span:first-of-type { + font-weight: 700; + font-size: 18px; +} +.featured a { + font: normal 14px Verdana, Geneva, sans-serif; + display: block; + width: 150px; + text-align: center; + border: 1px solid rgba(0, 0, 0, 0.2); + box-shadow: 0 0 8px #ccc; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + margin: 15px 0 0 10px; + padding: 4px 12px 5px; +} +.featured a:hover { + background: #ded; +} +.fic-series h3 { + font: bold 23px "Times New Roman", Times, serif; +} +.fic-series ol { + max-width: 940px; + display: inline-block; + list-style-type: none; + margin: 0 15px 10px; + padding: 0; +} +.fic-series li { + counter-increment: series-counter; + position: relative; + margin-bottom: 10px; + padding: 0 0 0 50px; +} +.fic-series li:before { + content: counter(series-counter); + position: absolute; + display: block; + top: 0; + left: 0; + font-size: 16pt; + width: 40px; + text-align: right; + padding: 10px 0; +} +.fic-series li.current:before { + content: ">"; +} +.fic-series li.current .fic-cell { + box-shadow: 0 0 5px #09a209; +} +.fic-series .fic-cell { + font-size: 11pt; + box-shadow: none; + max-width: 100%; + margin: 0; +} +.fic-series header img { + max-height: 120px; + max-width: 120px; +} +.fic-series .title .name { + font-family: Verdana, Geneva, sans-serif; + font-size: 18pt; +} +.fic-series .tag-adv, +.fic-series .tag-rom, +.fic-series .tag-rnd, +.fic-series .tag-com, +.fic-series .tag-lif, +.fic-series .tag-trg, +.fic-series .tag-sad, +.fic-series .tag-drk, +.fic-series .tag-alt, +.fic-series .tag-crs, +.fic-series .tag-hum, +.fic-series .tag-ath, +.fic-series .tag-gor, +.fic-series .tag-sex, +.fic-series .tag-2p, +.fic-series .tag-thr, +.fic-series .tag-dra, +.fic-series .tag-hor, +.fic-series .tag-eqg, +.fic-series .tag-mys, +.fic-series .tag-sci, +.fic-series .status-c, +.fic-series .status-i, +.fic-series .status-h, +.fic-series .status-n, +.fic-series .rating-ev, +.fic-series .rating-tn, +.fic-series .rating-ma, +.fic-series .rating-ya, +.fic-series .rating-ad, +.fic-series .rating-cl, +.fic-series .rating-db { + font-size: 12px; + line-height: 14px; + margin: 2px 0; + padding: 2px 4px; +} +.fic-series .ficsrc { + padding: 0 1px; +} +.fic-series .fic-cell br { + line-height: 24px; +} +.rev-tags { + overflow: auto; + padding: 2px 2px 8px; +} +.rev-tags > div { + position: relative; + height: 28px; + border: 1px solid #ccc; + border-radius: 3px; + float: left; + vertical-align: middle; + display: table; + background: #cf9; + margin: 4px 2px; + padding: 2px 30px 2px 2px; +} +.rev-tags > div.custom { + background: #ff9; +} +.rev-tags a.remove { + position: absolute; + top: 50%; + font-size: 12px; + text-decoration: none; + font-family: FontAwesome; + width: 20px; + height: 20px; + border-radius: 3px; + background-clip: padding-box; + color: rgba(0, 0, 0, 0.7); + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.15); + margin: -10px 0 0 7px; + padding: 0; +} +.rev-tags a.remove:before { + content: "\f00d"; + position: absolute; + display: inline-block; + top: 0; + right: 0; + margin: -2px 4px 0; +} +#report-tags-status.ok, +#report-tags-status.error { + display: block; + text-align: left; + margin: 4px 0 2px; +} +#tag-new { + max-width: 100px; + margin: 10px 3px 3px; + padding: 3px 4px; +} +.fic-cell .report-link { + font-size: 14px; + position: relative; +} +.fic-cell .report-form { + display: display; + position: absolute; + left: 20px; + background: rgba(255, 255, 255, 0.8); + -webkit-border-radius: 8px; + -moz-border-radius: 8px; + border-radius: 8px; + padding: 0 10px 10px 0; +} +.fic-cell .report-form .rbox { + min-height: 100px; + min-width: 400px; + background: #fff; + -webkit-border-radius: 8px; + -moz-border-radius: 8px; + border-radius: 8px; + border: 1px solid #999; + box-shadow: 0 0 2px #aaa; + text-align: center; + z-index: 5; + position: relative; + padding: 10px; +} +.fic-cell .report-form h3 { + text-align: center; + font-weight: 700; + margin: 2px 5px 5px; +} +.report-form a.submit { + color: #000; + display: inline-block; + background: #fff; + border: 1px solid; + font-size: 16px; + cursor: pointer; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + margin: 8px 5px; + padding: 8px 15px; +} +a.submit2 { + color: #000; + display: inline-block; + background: #fff; + border: 1px solid; + font-size: 12px; + cursor: pointer; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + margin: 0; + padding: 0 5px; +} +.report-form a.submit:hover, +a.submit2:hover { + color: #000; + background: #8f9; + text-decoration: none; +} +.report-form a.submit:active, +a.submit2:active { + background: #3d4; +} +.report-form .cselect { + display: inline-block; + position: relative; + min-width: 160px; + background: #fff; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + border: 1px solid #999; + box-shadow: 0 1px 1px #ddd; + cursor: pointer; + outline: none; + color: #245; + margin: 0 0 4px; + padding: 3px 10px; +} +.report-form .cselect span:first-of-type { + display: block; + padding: 2px; +} +.report-form .cselect .dropdown { + position: absolute; + left: 0; + right: 0; + margin-top: 12px; + background: #fff; + border-radius: inherit; + border: 1px solid #999; + box-shadow: 0 0 5px #eee; + font-weight: 400; + transition: all 0.2s ease-in; + list-style: none; + opacity: 0; + pointer-events: none; + padding: 1px; +} +.cselect.active .dropdown { + opacity: 1; + pointer-events: auto; +} +.report-form .cselect:after { + content: ""; + position: absolute; + right: 15px; + top: 50%; + margin-top: -3px; + border-color: #8aa8bd transparent; + border-style: solid; + border-width: 6px 6px 0; +} +.report-form .dropdown:before { + content: ""; + position: absolute; + bottom: 100%; + right: 13px; + border-color: rgba(0, 0, 0, 0.1) transparent; + border-style: solid; + border-width: 0 8px 8px; +} +.report-form .dropdown:after { + content: ""; + position: absolute; + bottom: 100%; + right: 15px; + border-color: #fff transparent; + border-style: solid; + border-width: 0 6px 6px; +} +.report-form .dropdown div { + float: none; + width: 100%; + margin: 0; +} +.report-form .dropdown li { + border-bottom: 1px solid #bbb; + transition: all 0.3s ease-out; + padding: 5px 7px 2px; +} +.report-form .dropdown li:hover { + background: #f3f8f8; +} +#report-status.ok, +#report-status.error { + display: block; + text-align: left; + margin: 4px 0 2px; +} +.loading.sm { + max-height: 40px; + max-width: 40px; +} +.mystar { + display: inline-block; + float: left; + position: relative; + box-shadow: 0 0 2px #059c5d; + width: 0; + overflow: hidden; + margin: 5px 5px 0; + padding: 0; +} +.mystar > div { + position: relative; + width: 200px; + display: none; + top: 0; +} +.mystar .ok { + color: #0b980b; + font-weight: 700; + text-align: center; + font-size: 30px; + padding: 15px 5px 0; +} +label.popout.star:hover:before, +label.popout.star:hover ~ label.star:before { + content: "\f005"; + color: #fd4; +} +label.popout.star-5:hover, +label.popout.star-5:hover ~ label.star:before { + content: "\f005"; + color: #fe4; + text-shadow: 0 0 10px #952; +} +a.read { + color: #000 !important; + font-size: 15px; + display: inline-block; + float: left; + background-image: linear-gradient(rgba(17, 153, 0, 0.5), rgba(0, 221, 34, 0.3), rgba(0, 221, 34, 0.3), rgba(17, 153, 0, 0.5)), linear-gradient(90deg, #190, #0d2, #0d2, #0d2, #0d2, #190); + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + -webkit-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; + margin: 5px 0 0; + padding: 2px 10px; +} +a.read:hover { + background-image: linear-gradient(rgba(17, 153, 0, 0.4), rgba(0, 221, 34, 0.2), rgba(0, 221, 34, 0.2), rgba(17, 153, 0, 0.4)), linear-gradient(90deg, #3a3, #3e5, #3e5, #3e5, #3e5, #3a3); + text-decoration: none !important; + padding: 2px 14px; +} +a.read.noanimate { + overflow: hidden; + -webkit-transition: none; + -moz-transition: none; + -ms-transition: none; + -o-transition: none; +} +a.read.remove { + margin-left: 10px; + background-image: linear-gradient(rgba(153, 50, 50, 0.3), rgba(221, 50, 50, 0.1), rgba(221, 50, 50, 0.1), rgba(153, 50, 50, 0.3)), linear-gradient(90deg, #d00, #d44, #d44, #d44, #d44, #d00); +} +a.read.remove:hover { + background-image: linear-gradient(rgba(153, 100, 100, 0.5), rgba(221, 100, 100, 0.5), rgba(221, 100, 100, 0.5), rgba(153, 100, 100, 0.5)), linear-gradient(90deg, #f00, #e33, #e33, #e33, #e33, #f00); +} +#starsrem a { + float: none; +} +.ficshelves { + display: block; + float: left; + overflow: hidden; + min-width: 260px; + text-align: center; + margin: 5px 10px; +} +.ficshelves > span:first-of-type { + display: block; + padding-top: 2px; + box-shadow: 0 0 1px 1px #4c6d46; + border-top-left-radius: 15px; + border-top-right-radius: 15px; + border-bottom: 1px solid #adadad; + margin: 3px; +} +.ficshelves .inshelves.none { + font-size: 85%; +} +.ficshelves ul { + list-style: none; + margin: 0; + padding: 0; +} +.ficshelves li { + display: inline-block; + position: relative; + width: 95%; + vertical-align: top; + box-shadow: inset 0 0 4px 1px rgba(35, 35, 35, 0.17), 1px 1px 1px #6b6b6b; + margin: 3px 0; + padding: 1px 5px; +} +.ficshelves li > span:first-of-type { + margin-right: 14px; +} +.ficshelves span a:hover { + color: #224; +} +.ficshelves span a:active { + color: #222; +} +.ficshelves .rem { + position: absolute; + top: 0; + right: 3px; +} +.ficshelves .rem a { + color: #532; +} +.ficshelves select { + font-size: 14px; + margin-top: 5px; +} +.removed.dcma { + border: red solid; + display: block; + text-align: center; + margin: 10px auto; + padding: 35px 20px; +} +.removed { + border: red solid; + display: inline-block; + margin-bottom: 8px; + background: #fff; + color: #c00; + box-shadow: 0 0 8px red inset; + text-shadow: 0 0 9px #fff; + font: bold 12pt arial, sans-serif; + padding: 8px 14px; +} +.fic-data { + position: relative; + font-family: Georgia, serif; + top: 15px; + text-align: left; + overflow-wrap: break-word; + word-wrap: break-word; + -webkit-hyphens: auto; + -ms-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.6); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + max-width: 900px; + margin: 10px auto; + padding: 5px 20px; +} +.fic-data header { + position: relative; + margin-bottom: 30px; +} +.fic-data h1 { + font-size: 32px; + font-weight: 400; + margin: 20px 0; +} +.fic-data h2 { + margin: 20px 0; +} +.fic-data h1:first-of-type, +.fic-data h2:first-of-type { + text-align: center; + color: #333; +} +.fic-data h1 a, +.fic-data h1 a:visited, +.fic-data h2 a, +.fic-data h2 a:visited { + text-decoration: none; + color: inherit; +} +.fic-data h1 a:hover, +.fic-data h2 a:hover { + text-decoration: underline; + color: #009; +} +.fic-data ul { + font-family: "Times New Roman", Times, serif; +} +.fic-data .author { + display: block; + text-align: center; + font-size: 18px; + margin: -14px 0 0; +} +.fic-data header .chapnav { + position: absolute; + bottom: 3px; +} +.fic-data .chapnav.next { + right: 5px; +} +.fic-data .chapnav.prev { + left: 5px; +} +.fic-data .chapnav.bot { + font-size: 26px; + text-align: center; + display: block; + padding: 14px 0 18px; +} +.fic-data .timeremest { + display: block; + text-align: center; + color: #444; + font-family: sans-serif; + font-size: 13px; +} +.fic-data h3 { + margin-left: 20px; + font: bold 24px "Times New Roman", Times, serif; +} +.fic-data ul:first-of-type li { + list-style-type: decimal; + padding: 3px 0; +} +.fic-data p { + -webkit-hyphens: auto; + -moz-hyphens: auto; + -ms-hyphens: auto; + hyphens: auto; +} +.fic-data .double { + margin-top: 1em; +} +.fic-data .i { + font-style: italic; +} +.fic-data img.emoticon { + border: 0; + height: 1em; + margin: 0; + padding: 0; +} +.fic-data hr { + margin-top: 12px; +} +.fic-data blockquote { + border-left: 5px solid #aaa; + background: #eee; + margin: 10px; + padding: 5px 10px; +} +a.back { + display: inline-block; + margin: 5px; + padding: 20px; +} +.woverlay { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + cursor: default; + z-index: 1000; + -webkit-transition: opacity 0.3s; + -moz-transition: opacity 0.3s; + -ms-transition: opacity 0.3s; + -o-transition: opacity 0.3s; + transition: opacity 0.3s; +} +.woverlay > iframe { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; +} +.rwarn { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(146, 146, 146, 0.46); + z-index: 1001; +} +.rwarn > div { + position: absolute; + left: 50%; + margin-left: -180px; + top: 25%; + width: 360px; + height: auto; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.4); + text-align: center; + border: 1px solid #b4b1b1; + background-color: #fffeae; + padding: 10px 8px; +} +.rwarn .title { + font-size: 16px; + color: #666; +} +.rwarn .confirm { + display: inline-block; + background-color: #43ce43; + color: #000; + border-radius: 5px; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4); + border: 1px solid #292; + margin: 5px 0 20px; + padding: 5px 8px; +} +.rwarn .confirm:hover { + background-color: #5d5; + text-decoration: none; +} +* { + box-sizing: border-box; +} +body { + font-family: sans-serif; + background-color: #fff; + text-align: center; + margin: 0; + padding: 0; +} +#wrap { + min-height: 100vh; + margin: 0 0 -165px; + padding: 0 0 220px; +} +h1, +h2, +h3, +h4 { + font-weight: 400; +} +.error { + text-align: center; + color: #d20e0e; + font-weight: 700; +} +a, +a:visited { + color: #33f; + text-decoration: none; +} +a:active, +a:hover { + cursor: pointer; + color: #33c; + text-decoration: underline; +} +a label:hover { + cursor: pointer; +} +input:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px #fff inset; +} +nav.home { + color: #778; + font: 15px "Trebuchet MS", Helvetica, sans-serif; + text-align: center; +} +nav.home a.current { + font-size: 18px; + font-weight: 700; +} +nav.home a { + display: inline-block; + margin: 3px; + padding: 3px 5px; +} +header.main { + text-align: center; + padding: 4px; +} +header.main img { + max-width: 100%; + padding: 0 60px; +} +img.emoticon { + max-height: 27px; +} +.more_span { + display: none; + background-image: url(/img/fo-20.png); + background-repeat: repeat-x; + position: relative; + top: -20px; + margin-bottom: -10px; + padding: 25px 10px 5px; +} +.more_span.less { + background-image: none; +} +.more_button { + display: inline-block; + font: normal 14px Verdana, Geneva, sans-serif; + background: #555; + color: #fff; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + text-decoration: none; + margin: 0; + padding: 4px 12px 5px; +} +a.more_button:hover, +.fic-cell a.more_button:active { + color: #fff; + text-decoration: none; + background: #666; + text-shadow: 0 0 1px #777; + box-shadow: 0 0 3px #000; +} +.notice { + display: inline-block; + background: #dff; + color: #3a1; + border: #185 solid; + box-shadow: 0 0 8px #358 inset; + text-shadow: 0 0 9px #fff; + font: bold 12pt arial, sans-serif; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + margin: 0; + padding: 8px 20px; +} +.notice.error { + box-shadow: 0 0 8px #e88 inset; + border: #d25555 solid; + background: #fdd; + color: #a50303; +} +.pagenav li { + vertical-align: top; + zoom: 1; + display: inline; + padding: 0 10px; +} +.pagenav li a, +.pagenav li a:visited { + color: #333; + font-size: 130%; + font-weight: 700; +} +.pagenav li a:hover { + color: #666; + letter-spacing: 1px; +} +img.loading { + -webkit-animation: spin 1s linear infinite; + -moz-animation: spin 1s linear infinite; + animation: spin 1s linear infinite; +} +.usersection { + position: absolute; + display: block; + top: 0; + right: 0; + text-align: right; + z-index: 10; + padding: 10px 10px 15px 15px; +} +.usermenu { + width: 65px; + height: 65px; + position: relative; + text-align: center; + background: #fff; + -webkit-border-radius: 40px; + -moz-border-radius: 40px; + border-radius: 40px; + border: 1px solid #ddd; + box-shadow: 0 0 40px #6b5 inset; + cursor: pointer; + outline: none; + transition: all 0.3s ease-out; + margin: 0 0 0 auto; + padding: 5px; +} +.usermenu .dropdown { + text-align: left; + position: absolute; + top: 80%; + width: 145px; + left: -81px; + right: 0; + background: #fff; + -webkit-border-radius: 20px 0 20px 20px; + -moz-border-radius: 20px 0 20px 20px; + border-radius: 20px 0 20px 20px; + border: 1px solid rgba(0, 0, 0, 0.8); + border-top: none; + border-bottom: none; + list-style: none; + transition: all 0.3s ease-out; + max-height: 0; + overflow: hidden; + margin: 16px 0 0; + padding: 0; +} +.usermenu .dropdown li { + border-bottom: 1px solid #e6e8ea; + padding: 10px; +} +.usermenu .dropdown li a { + display: block; + text-decoration: none; + color: #333; + transition: all 0.3s ease-out; + margin: -10px; + padding: 10px; +} +.usermenu .dropdown li i { + margin-right: 5px; + color: inherit; + vertical-align: middle; +} +.usermenu .dropdown li:hover a { + color: #57a9d9; +} +.usermenu.active { + border-radius: 30px 30px 0 0; + background: #bdf; + box-shadow: none; + border-bottom: none; +} +.usermenu.active .dropdown { + max-height: 400px; + border: 1px solid rgba(0, 0, 0, 0.8); +} +.spine header .fa.c.pony, +.shelf .title .fa.pony { + color: #000; + text-shadow: 1px 1px 4px rgba(208, 208, 208, 0.5); + font-size: 40px; + overflow: hidden; + padding: 8px 6px 3px; +} +.spine footer a:hover, +.fic-data ul:first-of-type a, +.fic-data .b { + font-weight: 700; +} +.shelf-menu .spine section, +.chapterlist li { + margin-bottom: 10px; +} +.iconselect .toggleable-radio label:first-of-type, +.colorselect .toggleable-radio label:first-of-type, +.iconselect .toggleable-radio label:last-of-type, +.colorselect .toggleable-radio label:last-of-type { + border-radius: 3px; +} +#status.ok, +#report-tags-status.ok, +#report-status.ok { + color: #2ea93f; + font-weight: 700; +} +.shelf > header h1, +.shelf > header h2, +.chapterlist ol, +.usersection form { + margin: 0; +} +.shelf .title, +body > footer > div, +body.fotd, +body.story, +.mystar .prompt, +#starsrem, +.fic-data .c { + text-align: center; +} +.shelf .title > div, +.fic-cell .popular span, +.ficstats span, +.user .info { + display: inline-block; +} +.comment-cell p.indented, +.fic-data .indented { + text-indent: 3em; +} +.fic-cell a.more_button, +.fic-tiles .ftag:before { + color: #fff; +} +.fic-cell header, +.fic-cell .details, +.user .name { + overflow: auto; +} +.fic-cell .description a, +.fic-data .u { + text-decoration: underline; +} +.rating, +.characters, +#opt-tags { + padding: 2px; +} +.characters, +.search-adv input[type="submit"] { + float: right; +} +.published span, +.updated span, +.fic-tiles .frating.cl2, +.ficshelves span a { + color: #000; +} +input.star, +.fic-tiles .published, +.fic-tiles .updated, +.fotd.sm .fic-cell .popular, +.fic-data > header p, +.fic-data > header img { + display: none; +} +input.star-1:checked ~ label.star:before, +span.star.star-filled.star-1, +label.popout.star-1:hover:before { + color: #f62; +} +.fic-tiles .ficstats .words, +.fic-cell .popular .star-rating, +header, +section, +footer, +aside, +nav, +main, +article, +figure { + display: block; +} +.fotd.sm .fic-cell .description, +.fotd.sm .fic-cell .desc_short, +.ficblock { + clear: both; +} +.loginbox a:link, +.loginbox a:visited, +nav.home a:link, +nav.home a:visited { + color: inherit; + text-decoration: none; +} +.loginbox a:hover, +nav.home a:hover { + color: inherit; + text-decoration: underline; +} +.loginbox input.login:hover, +.loginbox input.login:active { + background: #06f; +} +.loginbox a.register:hover, +.loginremember a:hover { + color: #333 !important; +} +.search .buttons, +.fic-cell.large .popular { + padding: 5px 0; +} +#search-adv:after, +.search-adv div.opt.full::after { + content: " "; + display: block; + height: 0; + clear: both; +} +.selected-tags .excluded, +.rev-tags .excluded { + background: #f99 !important; +} +.search-adv .selected-tags > div > span, +.rev-tags > div > span { + display: table-cell; + vertical-align: middle; + padding: 0 3px 0 6px; +} +.search-l img, +.story404 *, +.fic-data img, +.usermenu img { + max-width: 100%; +} +.report-form .rem, +.fic-data .s { + text-decoration: line-through; +} +.report-form .dropdown li:last-of-type, +.usermenu .dropdown li:last-of-type { + border: none; +} +@media (max-width: 600px) { + .fic-cell.large .popular { + display: block; + } + .fic-cell .popular.popsmall { + display: block !important; + font-size: 14px; + line-height: 14px; + } + .fic-cell .details br { + line-height: 20px; + } + article.fotd > header { + font-size: 38px; + } + .user .avatar img { + max-height: 130px; + max-width: 130px; + } + .user .avatar .jwc_frame { + height: 130px !important; + width: 130px !important; + } + .user .avatar.preview .jwc_frame { + height: 200px !important; + width: 200px !important; + } + .user .name h1 { + font-size: 28px; + } + .user .name img { + max-height: 28px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + } + .user .info { + font-size: 14px; + } + .search-l { + margin-top: 80px; + max-width: 90%; + } + .search-s #logo2 { + display: block !important; + } + .search-s form { + padding-left: 100px; + } + .search-s .search-adv { + margin: 10px -50px 10px -90px; + } + .result { + padding: 0 10px 0 5px; + } + .searchnav ul { + padding: 0 10px; + } + .fic-series .fic-cell { + position: relative; + top: 30px; + margin: 0 0 50px; + } + .fic-series li:before { + text-align: left; + padding: 0; + } + nav.home { + padding-right: 45px; + } + .usermenu { + width: 45px; + height: 45px; + } + .usermenu .dropdown { + left: -91px; + } + .fic-cell .description, + .user .bio, + .fic-cell .desc_short { + clear: both; + } + .fic-cell .popular, + .search-s #logo1 { + display: none; + } + .searchnav, + .fic-series li { + padding: 0; + } +} +@media (max-width: 450px) { + .fic-tiles { + padding: 0 10px; + } + .fic-tiles .fic-cell { + width: 100%; + } + .user .avatar img { + max-height: 80px; + max-width: 80px; + } + .user .avatar .jwc_frame { + height: 80px !important; + width: 80px !important; + } + .user .avatar.preview .jwc_frame { + height: 200px !important; + width: 200px !important; + } + .user .name h1 { + font-size: 20px; + } + .user .name img { + max-height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + } + .fic-cell .report-form .rbox { + min-height: 100px; + min-width: 200px; + width: 100%; + font-size: 14px; + } + .fic-cell .report-form a.submit { + font-size: 14px; + } + .fic-cell .report-form { + left: 10px; + right: 0; + } +} +@media (max-width: 800px) { + body > footer section { + width: 300px; + display: block; + min-height: 0; + margin: 0 auto; + padding: 10px; + } + body > footer section:last-of-type { + min-height: 150px; + } + body > footer section:before { + visibility: hidden; + } + body > footer h4 { + margin: 5px 0 10px; + } +} diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 0000000..d672697 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 0000000..0ff5442 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000..841d3da --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,21 @@ +class ApplicationController < ActionController::Base + before_action :start_timer + before_action :setup_pagination_and_tags + + private + + def start_timer + @start_time = Time.zone.now + end + + def setup_pagination_and_tags + @per_page = 15 + per_page = params[:per_page] + if per_page + per_page = per_page.to_i + @per_page = per_page if per_page.between? 1, 50 + end + + @page_num = params[:page].to_i + end +end diff --git a/app/controllers/authors_controller.rb b/app/controllers/authors_controller.rb new file mode 100644 index 0000000..14b2cd8 --- /dev/null +++ b/app/controllers/authors_controller.rb @@ -0,0 +1,5 @@ +class AuthorsController < ApplicationController + def show + @author = Author.find(params[:id]) + end +end diff --git a/app/controllers/chapters_controller.rb b/app/controllers/chapters_controller.rb new file mode 100644 index 0000000..a612712 --- /dev/null +++ b/app/controllers/chapters_controller.rb @@ -0,0 +1,6 @@ +class ChaptersController < ApplicationController + def show + @story = Story.find(params[:story_id]) + @chapter = @story.chapters.find_by(number: params[:id]) + end +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/controllers/images_controller.rb b/app/controllers/images_controller.rb new file mode 100644 index 0000000..d385737 --- /dev/null +++ b/app/controllers/images_controller.rb @@ -0,0 +1,28 @@ +require 'open-uri' + +class ImagesController < ApplicationController + def show + url = params[:url] + parsed = URI.parse(url) + + if parsed.host != 'cdn-img.fimfiction.net' + render nothing: true, status: :bad_request + return + end + + hash = Digest::SHA256.hexdigest(url) + path = Rails.root.join('public', 'cached-images', hash + File.extname(url)) + our_url = '/cached-images/' + hash + File.extname(url) + + if File.exist? path + redirect_to our_url + return + end + + File.open(path, 'wb') do |fp| + fp.write(Net::HTTP.get(parsed)) + end + + redirect_to our_url + end +end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb new file mode 100644 index 0000000..7b55ee9 --- /dev/null +++ b/app/controllers/search_controller.rb @@ -0,0 +1,125 @@ +# This whole class is a giant mess but I coded it fast so give me a break. +class SearchController < ApplicationController + ALLOWED_SORT_DIRS = [:asc, :desc] + ALLOWED_SORT_FIELDS = [:title, :author, :date_published, :date_updated, :num_words, :rel] + + before_action :load_tags + + def index + @search_params = {} + end + + def search + unless setup_scope + return + end + + using_random = @search_params['luck'].present? + + # This was mainly written this way to match the way FiMFetch's query interface looks, without using JS. + # I should do a Derpibooru-esque textual search system sometime. + @search = Story.fancy_search(per_page: @per_page, + page: @page_num) do |s| + s.add_query match: { title: { query: @search_params['q'], operator: 'AND' } } unless @search_params['q'].blank? + s.add_query match: { author: { query: @search_params['author'], operator: 'AND' } } if @search_params['author'].present? + + # ratings -> match stories with any of them + s.add_filter bool: { + should: @search_params['ratings'].keys.map { |k| { term: { content_rating: k } } } + } unless @search_params['ratings'].blank? + + # completeness -> match stories with any of them + s.add_filter bool: { + should: @search_params['state'].keys.map { |k| { term: { completion_status: k } } } + } unless @search_params['state'].blank? + + # tags -> match any of the included tags, exclude any of the excluded taags + tag_musts, tag_must_nots = parse_tag_queries + + s.add_filter terms: { + tags: tag_musts + } if tag_musts.any? + + s.add_filter bool: { + must_not: tag_must_nots.map { |t| { term: { tags: t } } } + } if tag_must_nots.any? + + # sort direction + if using_random + s.add_sort _random: :desc + else + s.add_sort parse_sort + end + end + + if using_random && @search.total_count > 0 + redirect_to story_path(@search.records[0]) + return + end + + @records = @search.records + end + + private + def load_tags + @character_tags = Tag.where(type: 'character').pluck(:name) + @other_tags = Tag.where.not(type: 'character').pluck(:name) + end + + # returns: [included tags, excluded tags] + def parse_tag_queries + tag_searches = (@search_params['tags'] + ',' + @search_params['characters']).split(',').reject &:blank? + + [tag_searches.select { |t| t[0] != '-' }, tag_searches.select { |t| t[0] == '-' }] + end + + def parse_sort + sf = ALLOWED_SORT_FIELDS.detect { |f| @search_params['sf'] == f.to_s } || :date_updated + sd = ALLOWED_SORT_DIRS.detect { |d| @search_params['sd'] == d.to_s } || :desc + + sf = case sf + when :rel then + :_score + else + sf + end + + {sf => sd} + end + + # FIXME: This is some of the worst Ruby code I have ever written. + def setup_scope + @scope_key = Random.hex(16) + scope_valid = false + + # scope passed, try to look it up in redis and use the search params from it + if params[:scope].present? + result = $redis.get("search_scope/#{params[:scope]}") + if result.present? + @search_params = JSON.load(result) + @scope_key = params[:scope] + scope_valid = true + else + redirect_to '/' + return false + end + else + @search_params = params + end + + # you can't JSON.dump a Parameters + if @search_params.is_a? ActionController::Parameters + @search_params = @search_params.permit!.to_h + end + $redis.setex("search_scope/#{@scope_key}", 3600, JSON.dump(@search_params)) + + # if the scope was invalid (no key passed, or invalid key passed), redirect to the results page + # with the new key we just generated for the params we had. + unless scope_valid + redirect_to "/search?scope=#{@scope_key}" + return false + end + + true + end +end diff --git a/app/controllers/static_pages_controller.rb b/app/controllers/static_pages_controller.rb new file mode 100644 index 0000000..6fd914a --- /dev/null +++ b/app/controllers/static_pages_controller.rb @@ -0,0 +1,4 @@ +class StaticPagesController < ApplicationController + def about + end +end diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb new file mode 100644 index 0000000..cce75ed --- /dev/null +++ b/app/controllers/stories_controller.rb @@ -0,0 +1,9 @@ +class StoriesController < ApplicationController + def index + end + + def show + @story = Story.find(params[:id]) + @chapters = @story.chapters.order(number: :asc) + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..9747f8a --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,15 @@ +module ApplicationHelper + # https://infusion.media/content-marketing/how-to-calculate-reading-time/ + def reading_time(words) + minutes = words / 200.0 + + distance_of_time_in_words(minutes * 60.0) + end + + def render_time + diff = ((Time.zone.now - @start_time) * 1000.0).round(2) + diff < 1000 ? "#{diff}ms" : "#{(diff / 1000.0).round(2)}s" + rescue StandardError + 'unknown ms' + end +end diff --git a/app/helpers/authors_helper.rb b/app/helpers/authors_helper.rb new file mode 100644 index 0000000..f22e1f9 --- /dev/null +++ b/app/helpers/authors_helper.rb @@ -0,0 +1,2 @@ +module AuthorsHelper +end diff --git a/app/helpers/chapters_helper.rb b/app/helpers/chapters_helper.rb new file mode 100644 index 0000000..063540b --- /dev/null +++ b/app/helpers/chapters_helper.rb @@ -0,0 +1,2 @@ +module ChaptersHelper +end diff --git a/app/helpers/images_helper.rb b/app/helpers/images_helper.rb new file mode 100644 index 0000000..7b3a8bc --- /dev/null +++ b/app/helpers/images_helper.rb @@ -0,0 +1,2 @@ +module ImagesHelper +end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb new file mode 100644 index 0000000..343cd26 --- /dev/null +++ b/app/helpers/search_helper.rb @@ -0,0 +1,47 @@ +module SearchHelper + def rating_display(rating) + case rating + when 'everyone' then + tag.div class: 'frating ev', title: 'Rated Everyone' do + 'Everyone' + end + when 'teen' then + tag.div class: 'frating', title: 'Rated Teen' do + 'Teen' + end + when 'explicit' then + tag.div class: 'frating', title: 'Rated Explicit' do + 'Explicit' + end + end + end + + def status_display(status) + case status + when 'complete' then + tag.div class: 'fstatus sc', title: 'Complete' do + 'Complete' + end + when 'incomplete' then + tag.div class: 'fstatus si', title: 'Incomplete' do + 'Incomplete' + end + when 'cancelled' then + tag.div class: 'fstatus sn', title: 'Cancelled' do + 'Cancelled' + end + end + end + + def tag_to_html(t) + tag.div class: 'ftag', title: t.name do + t.name + end + end + + def character_tag(t) + tag.div class: 'ftag', title: t.name do + t.name + end + end +end diff --git a/app/helpers/static_pages_helper.rb b/app/helpers/static_pages_helper.rb new file mode 100644 index 0000000..2d63e79 --- /dev/null +++ b/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/app/helpers/stories_helper.rb b/app/helpers/stories_helper.rb new file mode 100644 index 0000000..43e5cd8 --- /dev/null +++ b/app/helpers/stories_helper.rb @@ -0,0 +1,2 @@ +module StoriesHelper +end diff --git a/app/indexes/story_index.rb b/app/indexes/story_index.rb new file mode 100644 index 0000000..ad0f1fa --- /dev/null +++ b/app/indexes/story_index.rb @@ -0,0 +1,61 @@ +module StoryIndex + def self.included(base) + base.settings index: { number_of_shards: 5, max_result_window: 10_000_000 } do + mappings dynamic: false do + indexes :id, type: 'integer' + indexes :author_id, type: 'keyword' + indexes :completion_status, type: 'keyword' + indexes :content_rating, type: 'keyword' + indexes :date_published, type: 'date' + indexes :date_updated, type: 'date' + indexes :date_modified, type: 'date' + indexes :num_comments, type: 'integer' + indexes :num_views, type: 'integer' + indexes :num_words, type: 'integer' + indexes :rating, type: 'integer' + indexes :short_description, type: 'text', analyzer: 'snowball' + indexes :description_html, type: 'text', analyzer: 'snowball' + indexes :title, type: 'text', analyzer: 'snowball' + indexes :author, type: 'text', analyzer: 'snowball' + indexes :tags, type: 'keyword' + end + end + + base.extend ClassMethods + end + + module ClassMethods + def default_sort(_options = {}) + [date_published: :desc] + end + + def allowed_search_fields(access_options = {}) + [:title, :completion_status, :content_rating, :date_published, :date_updated, :date_modified, :num_comments, :num_views, :num_words, :rating, :short_description, :description_html, :title, :tags, :author] + end + end + + def as_json(*) + { + id: id, + author_id: author.id, + completion_status: completion_status, + content_rating: content_rating, + date_published: date_published, + date_updated: date_updated, + date_modified: date_modified, + num_comments: num_comments, + num_views: num_views, + num_words: num_words, + rating: rating, + short_description: short_description, + description_html: description_html, + title: title, + tags: tags.map(&:name), + author: author.name + } + end + + def as_indexed_json(*) + as_json + end +end \ No newline at end of file diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000..d394c3d --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/app/jobs/index_update_job.rb b/app/jobs/index_update_job.rb new file mode 100644 index 0000000..258868d --- /dev/null +++ b/app/jobs/index_update_job.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class IndexUpdateJob < ApplicationJob + queue_as :high + def perform(cls, id) + obj = cls.constantize.find(id) + obj.update_index(defer: false) if obj + rescue StandardError => ex + Rails.logger.error ex.message + end +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 0000000..b63caeb --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/app/models/author.rb b/app/models/author.rb new file mode 100644 index 0000000..3b39b4e --- /dev/null +++ b/app/models/author.rb @@ -0,0 +1,3 @@ +class Author < ApplicationRecord + has_many :stories +end diff --git a/app/models/chapter.rb b/app/models/chapter.rb new file mode 100644 index 0000000..a40e143 --- /dev/null +++ b/app/models/chapter.rb @@ -0,0 +1,3 @@ +class Chapter < ApplicationRecord + belongs_to :story +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/concerns/indexable.rb b/app/models/concerns/indexable.rb new file mode 100644 index 0000000..bd00205 --- /dev/null +++ b/app/models/concerns/indexable.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'elasticsearch/model' + +module Indexable + extend ActiveSupport::Concern + + included do + include Elasticsearch::Model + include "#{name}Index".constantize + + after_commit(on: :create) do + __elasticsearch__.index_document + end + + after_commit(on: :destroy) do + __elasticsearch__.delete_document + end + end + + def update_index(defer: true, priority: :high) + if defer + if priority == :high + IndexUpdateJob.perform_later(self.class.to_s, id) + elsif priority == :rebuild + IndexRebuildJob.perform_later(self.class.to_s, id) + else + raise ArgumentError, 'No such priority known' + end + else + __elasticsearch__.index_document + end + end +end diff --git a/app/models/story.rb b/app/models/story.rb new file mode 100644 index 0000000..136fbd2 --- /dev/null +++ b/app/models/story.rb @@ -0,0 +1,9 @@ +class Story < ApplicationRecord + include FancySearchable::Searchable + include Indexable + + belongs_to :author + has_many :chapters + has_many :taggings, validate: false + has_many :tags, through: :taggings, validate: false +end diff --git a/app/models/story/tagging.rb b/app/models/story/tagging.rb new file mode 100644 index 0000000..2349210 --- /dev/null +++ b/app/models/story/tagging.rb @@ -0,0 +1,6 @@ +class Story::Tagging < ApplicationRecord + belongs_to :story + belongs_to :tag + + validates :tag, uniqueness: { scope: [:story_id] } +end \ No newline at end of file diff --git a/app/models/tag.rb b/app/models/tag.rb new file mode 100644 index 0000000..7b23605 --- /dev/null +++ b/app/models/tag.rb @@ -0,0 +1,2 @@ +class Tag < ApplicationRecord +end diff --git a/app/views/chapters/show.html.slim b/app/views/chapters/show.html.slim new file mode 100644 index 0000000..76945a9 --- /dev/null +++ b/app/views/chapters/show.html.slim @@ -0,0 +1,29 @@ +body.story + #wrap + header.banner.main + = link_to '/' + = image_tag '/img/banner.png' + article#story.fic-data.hyphenate + header + h1 + = link_to @story.title, story_path(@story) + span.author + ' by + = link_to @story.author.name, author_path(@story.author) + hr + h2= @chapter.title + span.chapnav.prev + - if @chapter.number == 1 + = link_to 'Load Full Story', story_chapter_path(@story, 0) + - else + = link_to 'Previous Chapter', story_chapter_path(@story, @chapter.number - 1) + span.chapnav.next + - if @chapter.number < @story.chapters.count + = link_to 'Next Chapter', story_chapter_path(@story, @chapter.number + 1) + hr + == @chapter.body + + span.chapnav.bot + - if @chapter.number < @story.chapters.count + = link_to 'Next Chapter', story_chapter_path(@story, @chapter.number + 1) + diff --git a/app/views/kaminari/_first_page.html.slim b/app/views/kaminari/_first_page.html.slim new file mode 100644 index 0000000..62e158d --- /dev/null +++ b/app/views/kaminari/_first_page.html.slim @@ -0,0 +1,8 @@ +/ Link to the "First" page + - available local variables + url : url to the first page + current_page : a page object for the currently displayed page + total_pages : total number of pages + per_page : number of items to fetch per page + remote : data-remote += link_to_unless current_page.first?, '« First', url, remote: remote diff --git a/app/views/kaminari/_gap.html.slim b/app/views/kaminari/_gap.html.slim new file mode 100644 index 0000000..09f9d83 --- /dev/null +++ b/app/views/kaminari/_gap.html.slim @@ -0,0 +1,7 @@ +/ Non-link tag that stands for skipped pages... + - available local variables + current_page : a page object for the currently displayed page + total_pages : total number of pages + per_page : number of items to fetch per page + remote : data-remote +span.page.gap … diff --git a/app/views/kaminari/_last_page.html.slim b/app/views/kaminari/_last_page.html.slim new file mode 100644 index 0000000..f4501e9 --- /dev/null +++ b/app/views/kaminari/_last_page.html.slim @@ -0,0 +1,8 @@ +/ Link to the "Last" page + - available local variables + url : url to the last page + current_page : a page object for the currently displayed page + total_pages : total number of pages + per_page : number of items to fetch per page + remote : data-remote += link_to_unless current_page.last?, 'Last »', url, remote: remote diff --git a/app/views/kaminari/_next_page.html.slim b/app/views/kaminari/_next_page.html.slim new file mode 100644 index 0000000..f55f6c1 --- /dev/null +++ b/app/views/kaminari/_next_page.html.slim @@ -0,0 +1,8 @@ +/ Link to the "Next" page + - available local variables + url : url to the next page + current_page : a page object for the currently displayed page + total_pages : total number of pages + per_page : number of items to fetch per page + remote : data-remote += link_to_unless current_page.last?, 'Next ›', url, rel: 'next', remote: remote, title: 'Next Page (k)', class: 'js-next' diff --git a/app/views/kaminari/_page.html.slim b/app/views/kaminari/_page.html.slim new file mode 100644 index 0000000..7ffe399 --- /dev/null +++ b/app/views/kaminari/_page.html.slim @@ -0,0 +1,12 @@ +/ Link showing page number + - available local variables + page : a page object for "this" page + url : url to this page + current_page : a page object for the currently displayed page + total_pages : total number of pages + per_page : number of items to fetch per page + remote : data-remote +- if page.current? + span class="page-current" = page +- else + = link_to page, url, remote: remote, rel: page.rel, class: 'page' diff --git a/app/views/kaminari/_paginator.html.slim b/app/views/kaminari/_paginator.html.slim new file mode 100644 index 0000000..007006d --- /dev/null +++ b/app/views/kaminari/_paginator.html.slim @@ -0,0 +1,18 @@ +/ The container tag + - available local variables + current_page : a page object for the currently displayed page + total_pages : total number of pages + per_page : number of items to fetch per page + remote : data-remote + paginator : the paginator that renders the pagination tags inside +== paginator.render do + nav.pagination + ==> first_page_tag unless current_page.first? + ==> prev_page_tag unless current_page.first? + - each_page do |page| + - if page.display_tag? + ==> page_tag page + - elsif !page.was_truncated? + ==> gap_tag + ==> next_page_tag unless current_page.last? + == last_page_tag unless current_page.last? diff --git a/app/views/kaminari/_prev_page.html.slim b/app/views/kaminari/_prev_page.html.slim new file mode 100644 index 0000000..f35c308 --- /dev/null +++ b/app/views/kaminari/_prev_page.html.slim @@ -0,0 +1,8 @@ +/ Link to the "Previous" page + - available local variables + url : url to the previous page + current_page : a page object for the currently displayed page + total_pages : total number of pages + per_page : number of items to fetch per page + remote : data-remote += link_to_unless current_page.first?, '‹ Prev', url, rel: 'prev', remote: remote, title: 'Previous Page (j)', class: 'js-prev' diff --git a/app/views/layouts/_banner.html.slim b/app/views/layouts/_banner.html.slim new file mode 100644 index 0000000..55bc14f --- /dev/null +++ b/app/views/layouts/_banner.html.slim @@ -0,0 +1,3 @@ +header.banner.main + = link_to '/' + = image_tag '/img/banner.png' \ No newline at end of file diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim new file mode 100644 index 0000000..cea3f37 --- /dev/null +++ b/app/views/layouts/application.html.slim @@ -0,0 +1,35 @@ +doctype html +html + head + - title_ext = yield :title + title= title_ext.present? ? "#{title_ext} - FoalFetch" : "FoalFetch - The Uncensored FiM Fiction Archive" + meta charset="utf-8" + meta name="viewport" content="width=device-width,initial-scale=1" + = csrf_meta_tags + = csp_meta_tag + = stylesheet_link_tag 'application' + = javascript_include_tag 'application' + link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css" rel="stylesheet" + body + = yield + footer + div + section + h4 Stats + span.stat Page generated in #{render_time} + / span.stat FIXME registered users + / span.stat FIXME visitors total + section + h4 FoalFetch + span.stat Frontend designed by DataByte, backend coded by Floorb. + span.stat + a href="/about" About FoalFetch + span.stat.disclaimer + ' This site and its contents are not affiliated with Hasbro, Inc or FiMFetch. + | My Little Pony: Friendship is Magic and all its characters are trademarks of Hasbro, Inc. + section + h4 Friends + span.stat + a href="https://twibooru.org" Twibooru + span.stat + a href="https://ponepaste.org" PonePaste diff --git a/app/views/search/_advanced.html.slim b/app/views/search/_advanced.html.slim new file mode 100644 index 0000000..7f30a47 --- /dev/null +++ b/app/views/search/_advanced.html.slim @@ -0,0 +1,127 @@ +label.fake-link.search-adv-link for="show-advanced" + | [Toggle Advanced Options] +input type="checkbox" id="show-advanced" style="display: none;" +div.search-adv + .cols + small More options coming soon! + .cols + .opts + b Rating + br + = label_tag "ratings_everyone" + = check_box_tag "ratings[everyone]", 1, @search_params.dig('ratings', 'everyone').present? + | Everyone + = label_tag "ratings_teen" + = check_box_tag "ratings[teen]", 1, @search_params.dig('ratings', 'teen').present? + | Teen + = label_tag "ratings_mature" + = check_box_tag "ratings[mature]", 1, @search_params.dig('ratings', 'mature').present? + | Mature + .opts + b Story State + br + = label_tag "state_complete" + = check_box_tag "state[complete]", 1, @search_params.dig('state', 'complete').present? + | Complete + = label_tag "state_incomplete" + = check_box_tag "state[incomplete]", 1, @search_params.dig('state', 'incomplete').present? + | Incomplete + = label_tag "state_on_hiatus" + = check_box_tag "state[on_hiatus]", 1, @search_params.dig('state', 'on_hiatus').present? + | On Hiatus + = label_tag "state_cancelled" + = check_box_tag "state[cancelled]", 1, @search_params.dig('state', 'cancelled').present? + | Cancelled + /.opts + b Story Age + br + = label_tag + = radio_button_tag 'ac', 'lt', checked: 'checked' + | Newer Than + = label_tag + = radio_button_tag 'ac', 'gt' + | Older Than + = select_tag :age, options_for_select({ \ + '30 days' => 30, '90 days' => 90, '180 days' => 180, '1 year' => 365, \ + '2 years' => 730, '3 years' => 1095, '4 years' => 1460, '5 years' => 1825, \ + '6 years' => 2190, '7 years' => 2555, '8 years' => 2920, '9 years' => 3285, '10 years' => 3650}) + /.opts + b Removed Stories + br + = select_tag "removed", options_for_select({'Include' => 1, 'Exclude' => 0, 'Only' => 'o'}) + .cols + /.opts + b Likes: + br + = label_tag + = radio_button_tag 'lc', 'gt', checked: 'checked' + | More Than + = label_tag + = radio_button_tag 'lc', 'lt' + | Less Than + = number_field_tag 'likes' + /.opts + b Words: + br + = label_tag + = radio_button_tag 'wc', 'gt', checked: 'checked' + | More Than + = label_tag + = radio_button_tag 'wc', 'lt' + | Less Than + = number_field_tag 'words' + .opts + b Author: + br + = text_field_tag :author, @search_params['author'] + /.cols + .opts + b FiMFiction Rating: + br + = label_tag + = radio_button_tag 'rc', 'gt', checked: 'checked' + | More Than + = label_tag + = radio_button_tag 'rc', 'lt' + | Less Than + div.stars-sm + = radio_button_tag :stars, 5, true, class: 'star star-5' + = label_tag 'star_5', '', class: 'star' + = radio_button_tag :stars, 4, true, class: 'star star-4' + = label_tag 'star_4', '', class: 'star' + = radio_button_tag :stars, 3, true, class: 'star star-3' + = label_tag 'star_3', '', class: 'star' + = radio_button_tag :stars, 2, true, class: 'star star-2' + = label_tag 'star_2', '', class: 'star' + = radio_button_tag :stars, 1, true, class: 'star star-1' + = label_tag 'star_1', '', class: 'star' + .cols + .opts + b Characters + br + noscript + p Please enable JavaScript if you wish to have a more friendly tag searching experience. Otherwise, ignore the dropdown and separate exact tag names by commas in the field. + .js-tag-editor + .selected-tags + = text_field_tag :characters, @search_params['characters'] + = select_tag :fancy_tags, options_for_select(@character_tags) + .cols + .opts + b Tags + br + noscript + p Please enable JavaScript if you wish to have a more friendly tag searching experience. Otherwise, ignore the dropdown and separate exact tag names by commas in the field. + .js-tag-editor + .selected-tags + = text_field_tag :tags, @search_params['tags'] + = select_tag :fancy_characters, options_for_select(@other_tags) + .cols + .opts + b Sort By + br + => select_tag :sf, options_for_select({'Query Relevance' => 'rel', 'Title' => 'title', 'Author' => 'author', \ + 'Publish Date' => 'date_published', 'Updated Date' => 'date_updated', 'Word Count' => 'num_words'}, @search_params['sf']) + = select_tag :sd, options_for_select({'High to Low' => 'desc', 'Low to High' => 'asc'}, @search_params['sd']) + - if show_button + .buttons + = submit_tag 'Go Fetch!', name: 'search' \ No newline at end of file diff --git a/app/views/search/index.html.slim b/app/views/search/index.html.slim new file mode 100644 index 0000000..f237dc7 --- /dev/null +++ b/app/views/search/index.html.slim @@ -0,0 +1,15 @@ +.main + #wrap + nav.home + = link_to "Daily Fictions", "/ficoftheday" + = link_to "News", "/news" + = link_to "About", "/about" + section.search.search-l + img alt="FoalFetch" src="/img/banner.png" + = form_tag "/search", method: :post + .searchbox + = search_field_tag "q", nil, placeholder: 'Search story titles...' + = render partial: 'advanced', locals: { show_button: false } + .buttons + = submit_tag 'Go Fetch!', name: 'search' + = submit_tag 'Pick one for me!', name: 'luck' \ No newline at end of file diff --git a/app/views/search/search.html.slim b/app/views/search/search.html.slim new file mode 100644 index 0000000..37e714e --- /dev/null +++ b/app/views/search/search.html.slim @@ -0,0 +1,58 @@ +.main + #wrap + section.search.search-s + = form_tag '/search', method: :post + a.logo href="/" + = image_tag '/img/logo_small.png' + .searchbox + = search_field_tag 'q', @search_params['q'], placeholder: 'Search story titles...' + = render partial: 'advanced', locals: { show_button: true } + + - @records.each do |rec| + - character_tags = rec.tags.where(type: 'character') + - normal_tags = rec.tags.where.not(type: 'character') + section.result + section.fic-cell + header + - if rec.cover_image + = image_tag '/images?url=' + CGI.escape(rec.cover_image), alt: 'Cover Image' + .details + h2= link_to rec.title, story_path(rec) + span.author + ' by + = link_to rec.author.name, author_path(rec.author) + br + span.popular + span.stats + p.description= rec.short_description + footer + .rating + = rating_display(rec.rating) + = status_display(rec.completion_status) + - normal_tags.each do |t| + = tag_to_html(t) + .characters + - character_tags.each do |t| + = character_display(t) + br + .ficstats + span.chapters + = pluralize(rec.chapters.count, 'Chapter') + ',' + '  + span.words + =<> rec.num_words + ' Words:  + span.time_est + ' Estimated + => reading_time(rec.num_words) + | to read + .published + ' Published + span= rec.date_published + .updated + ' Last Update + span= rec.date_updated + .searchnav + = paginate(@records, params: { scope: @scope_key }) + br + span.searchtime Found #{@search.total_count} results. \ No newline at end of file diff --git a/app/views/static_pages/about.html.slim b/app/views/static_pages/about.html.slim new file mode 100644 index 0000000..6a881e8 --- /dev/null +++ b/app/views/static_pages/about.html.slim @@ -0,0 +1,25 @@ +- content_for :title, 'About' +.about + #wrap + = render partial: 'layouts/banner' + nav.home + = link_to 'Home', '/' + = link_to 'News', '/news' + = link_to 'About', '/about', class: 'current' + article + section + h1 About FoalFetch + p FoalFetch is a uncensored archive of My Little Pony: Friendship is Magic fan fiction works. Born after + DataByte began censoring FiMFetch, the project provides an ever-growing list of features: + ul + li Archive of active and past works of fiction + li Easy-to-use granular search features + li Random daily fictions + li Multiple downloadable formats + + p And some long-term goals: + ul + li Scrape multiple fanfiction platforms for MLP:FiM stories + li An OPDS browsing service for compatible e-Readers + + p This site has been designed from the ground-up in an attempt to provide the simplest and most streamlined access to all the pony you should ever want - even content the site's operators don't agree with. \ No newline at end of file diff --git a/app/views/stories/index.html.slim b/app/views/stories/index.html.slim new file mode 100644 index 0000000..e69de29 diff --git a/app/views/stories/show.html.slim b/app/views/stories/show.html.slim new file mode 100644 index 0000000..4b90e79 --- /dev/null +++ b/app/views/stories/show.html.slim @@ -0,0 +1,52 @@ +- content_for :title, @story.title +div.story + div#wrap + = render partial: 'layouts/banner' + section.fic-cell.large + header + - if @story.cover_image + a + = image_tag '/images?url=' + CGI.escape(@story.cover_image), alt: 'Cover Image' + div.details + h2= @story.title + span.author + ' by + = link_to @story.author.name, author_path(@story.author) + div.desc_short + = @story.short_description + span.popular + / likes, etc + section.description#desc + p + == @story.description_html + section + .rating + => rating_display(@story.content_rating) + => status_display(@story.completion_status) + '  + .ficstats + span.words> #{@story.num_words} words: + span.time_est + ' Estimated + => reading_time(@story.num_words) + | to read + span.cached + .dl-links + span Download: + .chapterlist + h3= pluralize(@chapters.count, 'Chapter') + ':' + ol + - @chapters.each do |c| + li + span.chapter_title + => link_to c.title, story_chapter_path(@story, c.number) + span.date= c.date_published + .word_count= c.num_words + footer + .published + ' Published + span= @story.date_published + .updated + ' Last Update + span= @story.date_modified + diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 0000000..42c7fd7 --- /dev/null +++ b/bin/bundle @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || + cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + bundler_gem_version.approximate_recommendation + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/bin/importmap b/bin/importmap new file mode 100755 index 0000000..36502ab --- /dev/null +++ b/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/bin/rails b/bin/rails new file mode 100755 index 0000000..efc0377 --- /dev/null +++ b/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/bin/rake b/bin/rake new file mode 100755 index 0000000..4fbf10b --- /dev/null +++ b/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..ec47b79 --- /dev/null +++ b/bin/setup @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby +require "fileutils" + +# path to your application root. +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system! "gem install bundler --conservative" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + puts "\n== Restarting application server ==" + system! "bin/rails restart" +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..4a3c09a --- /dev/null +++ b/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000..604ffe7 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,34 @@ +require_relative "boot" + +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +# require "active_storage/engine" +require "action_controller/railtie" +# require "action_mailer/railtie" +# require "action_mailbox/engine" +# require "action_text/engine" +require "action_view/railtie" +require "action_cable/engine" +require "rails/test_unit/railtie" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module Foalfetch + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 7.0 + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000..2820116 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,3 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 0000000..9a968cb --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,11 @@ +development: + adapter: redis + url: redis://localhost:6379/1 + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: foalfetch_production diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc new file mode 100644 index 0000000..c79bad0 --- /dev/null +++ b/config/credentials.yml.enc @@ -0,0 +1 @@ +tL8+fuPHdTfbSgNCZVdXNdQ+wKg+87PpLyl5vgFSiqX1lg0WBNv43li9xM4kUU4Zj7YmjS49yqsyOFMhrW+g31Hh+cLZ3fx97XPVV97tu6/l8Dg9Ornldm6+wxg5zpOCrQyvF66DFouH9wMgw0iaEM3wWsvZjFDtsaa/kpgpPqGNUWhMFF6RNnbLeuGN22sm3d6FKEOFa0Ekl+YnKoxnbywshyG3+mAgpLAMt73u62KjiZsgIOku81k44WKDFLqqIeQdEumrWD8IxGFVIVPPm8xzGYq+K5LnIIIF0cw5coEVHZGU904fVvrVxmb0sMU5ZmozM6e98D86SpAQYj6LLFGbKFcZFBgiLknlLnkXDtwIN6chSUNT9lQmSqN4cL8byDwXI2X9hjugIcV7Qn/nGRJ1b4tOrWV/tWo/--CmilwP4O5Ch/FIvK--JD2c8xj2FnJJsSsRAU8HoQ== \ No newline at end of file diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000..e6b4e29 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,86 @@ +# PostgreSQL. Versions 9.3 and up are supported. +# +# Install the pg driver: +# gem install pg +# On macOS with Homebrew: +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config +# On macOS with MacPorts: +# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config +# On Windows: +# gem install pg +# Choose the win32 build. +# Install PostgreSQL and put its /bin directory on your path. +# +# Configure Using Gemfile +# gem "pg" +# +default: &default + adapter: postgresql + encoding: unicode + # For details on connection pooling, see Rails configuration guide + # https://guides.rubyonrails.org/configuring.html#database-pooling + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + +development: + <<: *default + database: foalfetch_development + + # The specified database role being used to connect to postgres. + # To create additional roles in postgres see `$ createuser --help`. + # When left blank, postgres will use the default role. This is + # the same name as the operating system user running Rails. + #username: foalfetch + + # The password associated with the postgres role (username). + #password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + + # The TCP port the server listens on. Defaults to 5432. + # If your server runs on a different port number, change accordingly. + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # Defaults to warning. + #min_messages: notice + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: foalfetch_test + +# As with config/credentials.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password or a full connection URL as an environment +# variable when you boot the app. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# If the connection URL is provided in the special DATABASE_URL environment +# variable, Rails will automatically merge its configuration values on top of +# the values provided in this file. Alternatively, you can specify a connection +# URL environment variable explicitly: +# +# production: +# url: <%= ENV["MY_APP_DATABASE_URL"] %> +# +# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full overview on how database connection configuration can be specified. +# +production: + <<: *default + database: foalfetch_production + username: foalfetch + password: <%= ENV["FOALFETCH_DATABASE_PASSWORD"] %> diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..cac5315 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000..5ab2549 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,62 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded any time + # it changes. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing + config.server_timing = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join("tmp/caching-dev.txt").exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + "Cache-Control" => "public, max-age=#{2.days.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000..d84da77 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,84 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + + # Compress CSS using a preprocessor. + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache + # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX + + # Mount Action Cable outside main process or domain. + # config.action_cable.mount_path = nil + # config.action_cable.url = "wss://example.com/cable" + # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Include generic and useful information about system operation, but avoid logging too much + # information to avoid inadvertent exposure of personally identifiable information (PII). + config.log_level = :info + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment). + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "foalfetch_production" + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require "syslog/logger" + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000..eb2f171 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,50 @@ +require "active_support/core_ext/integer/time" + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Turn false under Spring and add config.action_view.cache_template_loading = true. + config.cache_classes = true + + # Eager loading loads your whole application. When running a single test locally, + # this probably isn't necessary. It's a good idea to do in a continuous integration + # system, or in some way before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + "Cache-Control" => "public, max-age=#{1.hour.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + config.cache_store = :null_store + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true +end diff --git a/config/importmap.rb b/config/importmap.rb new file mode 100644 index 0000000..909dfc5 --- /dev/null +++ b/config/importmap.rb @@ -0,0 +1,7 @@ +# Pin npm packages by running ./bin/importmap + +pin "application" +pin "@hotwired/turbo-rails", to: "turbo.min.js" +pin "@hotwired/stimulus", to: "stimulus.min.js" +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 0000000..2eeef96 --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,12 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 0000000..54f47cf --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap and inline scripts +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/config/initializers/elasticsearch.rb b/config/initializers/elasticsearch.rb new file mode 100644 index 0000000..3223147 --- /dev/null +++ b/config/initializers/elasticsearch.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true +Elasticsearch::Model.client = Elasticsearch::Client.new( + host: 'https://127.0.0.1:9200', + http: { + user: 'elastic', + password: 'FrLV=CCE56dYsK*87jEo' + }, + request_timeout: 30 +) do |f| + f.ssl[:verify] = false +end + +# Prevents this message from appearing in logs: +# You are setting a key that conflicts with a built-in method Hashie::Mash#key defined in Hash. +# This can cause unexpected behavior when accessing the key via as a property. +# You can still access the key via the #[] method. +Hashie.logger = Logger.new(nil) diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000..adc6568 --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be filtered from the log file. Use this to limit dissemination of +# sensitive information. See the ActiveSupport::ParameterFilter documentation for supported +# notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn +] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000..3860f65 --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/config/initializers/permissions_policy.rb b/config/initializers/permissions_policy.rb new file mode 100644 index 0000000..00f64d7 --- /dev/null +++ b/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/config/initializers/redis.rb b/config/initializers/redis.rb new file mode 100644 index 0000000..df64f5d --- /dev/null +++ b/config/initializers/redis.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +ENV['REDIS_HOST'] ||= 'localhost' + +#require 'hiredis' +require 'redis' + +$redis = Redis.new(host: ENV['REDIS_HOST']) + +if defined?(PhusionPassenger) + PhusionPassenger.on_event(:starting_worker_process) do |forked| + $redis.disconnect! if forked + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000..8ca56fc --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# "true": "foo" +# +# To learn more, please read the Rails Internationalization guide +# available at https://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 0000000..daaf036 --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,43 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +threads min_threads_count, max_threads_count + +# Specifies the `worker_timeout` threshold that Puma will use to wait before +# terminating a worker in development environments. +# +worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked web server processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. +# +# preload_app! + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..8fbba7c --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,12 @@ +Rails.application.routes.draw do + root 'search#index' + post '/search' => 'search#search' + get '/search' => 'search#search' + get '/about' => 'static_pages#about' + get '/images' => 'images#show' + + resources :authors, only: [:show] + resources :stories, only: [:show] do + resources :chapters, only: [:show] + end +end diff --git a/db/migrate/20240401101354_initial.rb b/db/migrate/20240401101354_initial.rb new file mode 100644 index 0000000..eafa3a3 --- /dev/null +++ b/db/migrate/20240401101354_initial.rb @@ -0,0 +1,63 @@ +class Initial < ActiveRecord::Migration[7.0] + def change + #create_enum :story_completion_status, %w[hiatus incomplete complete cancelled] + #create_enum :story_content_rating, %w[teen everyone mature] + + create_table :authors do |t| + t.text :name, null: false + t.integer :num_blog_posts, null: false, default: 0 + t.integer :num_followers, null: false, default: 0 + t.text :avatar, null: true + t.text :bio_html, null: true + t.datetime :date_joined, null: false + t.text :url, null: false + end + + create_table :stories do |t| + t.belongs_to :author, null: false + t.integer :color, null: true + # t.enum :completion_status, enum_type: 'story_completion_status', null: false + # t.enum :content_rating, enum_type: 'story_content_rating', null: false + t.text :completion_status + t.text :content_rating + t.text :cover_image, null: true + t.datetime :date_published, null: false + t.datetime :date_updated, null: true + t.datetime :date_modified, null: true + t.text :description_html, null: true + t.integer :num_comments, null: false, default: 0 + t.integer :num_views, null: false, default: 0 + t.integer :num_words, null: false + t.integer :prequel, null: true + t.integer :rating, null: false + t.text :short_description, null: true + t.text :title, null: false + t.integer :total_num_views, null: false, default: 0 + t.text :url, null: false + end + + create_table :chapters do |t| + t.belongs_to :story, null: false + t.integer :number, null: false, default: 1 + t.datetime :date_published, null: false + t.datetime :date_modified, null: true + t.integer :num_views, null: false, default: 0 + t.integer :num_words, null: false + t.text :title, null: false + t.text :url, null: false + t.text :body, null: false + end + + create_table :tags do |t| + t.text :name, null: false + t.text :old_id, null: true + t.text :url, null: false + t.text :type, null: false + end + + create_table :story_taggings, id: false, primary_key: [:story_id, :tag_id] do |t| + t.belongs_to :story, null: false + t.belongs_to :tag, null: false + end + end +end diff --git a/db/migrate/20240401121829_temp_nullable_body.rb b/db/migrate/20240401121829_temp_nullable_body.rb new file mode 100644 index 0000000..aee79b1 --- /dev/null +++ b/db/migrate/20240401121829_temp_nullable_body.rb @@ -0,0 +1,5 @@ +class TempNullableBody < ActiveRecord::Migration[7.0] + def change + change_column_null :chapters, :body, true + end +end diff --git a/db/migrate/20240402172140_remove_urls.rb b/db/migrate/20240402172140_remove_urls.rb new file mode 100644 index 0000000..1ec40cb --- /dev/null +++ b/db/migrate/20240402172140_remove_urls.rb @@ -0,0 +1,8 @@ +class RemoveUrls < ActiveRecord::Migration[7.0] + def change + remove_column :stories, :url, :text + remove_column :chapters, :url, :text + remove_column :tags, :url, :text + remove_column :authors, :url, :text + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..8dbebf4 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,77 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.0].define(version: 2024_04_02_172140) do + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + # Custom types defined in this database. + # Note that some types may not work with other database engines. Be careful if changing database. + create_enum "story_completion_status", ["hiatus", "incomplete", "complete", "cancelled"] + create_enum "story_content_rating", ["teen", "everyone", "mature"] + + create_table "authors", force: :cascade do |t| + t.text "name", null: false + t.integer "num_blog_posts", default: 0, null: false + t.integer "num_followers", default: 0, null: false + t.text "avatar" + t.text "bio_html" + t.datetime "date_joined", null: false + end + + create_table "chapters", force: :cascade do |t| + t.bigint "story_id", null: false + t.integer "number", default: 1, null: false + t.datetime "date_published", null: false + t.datetime "date_modified" + t.integer "num_views", default: 0, null: false + t.integer "num_words", null: false + t.text "title", null: false + t.text "body" + t.index ["story_id"], name: "index_chapters_on_story_id" + end + + create_table "stories", force: :cascade do |t| + t.bigint "author_id", null: false + t.integer "color" + t.text "completion_status" + t.text "content_rating" + t.text "cover_image" + t.datetime "date_published", null: false + t.datetime "date_updated" + t.datetime "date_modified" + t.text "description_html" + t.integer "num_comments", default: 0, null: false + t.integer "num_views", default: 0, null: false + t.integer "num_words", null: false + t.integer "prequel" + t.integer "rating", null: false + t.text "short_description" + t.text "title", null: false + t.integer "total_num_views", default: 0, null: false + t.index ["author_id"], name: "index_stories_on_author_id" + end + + create_table "story_taggings", id: false, force: :cascade do |t| + t.bigint "story_id", null: false + t.bigint "tag_id", null: false + t.index ["story_id"], name: "index_story_taggings_on_story_id" + t.index ["tag_id"], name: "index_story_taggings_on_tag_id" + end + + create_table "tags", force: :cascade do |t| + t.text "name", null: false + t.text "old_id" + t.text "type", null: false + end + +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000..bc25fce --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) +# Character.create(name: "Luke", movie: movies.first) diff --git a/lib/assets/.keep b/lib/assets/.keep new file mode 100644 index 0000000..e69de29 diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 0000000..e69de29 diff --git a/log/.keep b/log/.keep new file mode 100644 index 0000000..e69de29 diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..2be3af2 --- /dev/null +++ b/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

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

+
+

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

+
+ + diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000..c08eac0 --- /dev/null +++ b/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
+
+

The change you wanted was rejected.

+

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

+
+

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

+
+ + diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..78a030a --- /dev/null +++ b/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
+
+

We're sorry, but something went wrong.

+
+

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

+
+ + diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000..e69de29 diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..e69de29 diff --git a/public/cached-images/.placeholder b/public/cached-images/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/public/img/banner.png b/public/img/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..de885b7af1b8ca98ca33a48d533152aa84e04a4c GIT binary patch literal 55351 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 XNE$h3;TP{N0H2YbnQo=FbL{^BxI+V` literal 0 HcmV?d00001 diff --git a/public/img/logo.png b/public/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..05c73d092ed6464056ff9b2c8c7b2bd5e63ad30e GIT binary patch literal 21429 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%0D+G?n{MH2V(jt0GGZl)3 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 U#OlXs!TZRuTL1t6 literal 0 HcmV?d00001 diff --git a/public/img/logo_small.png b/public/img/logo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..54551d30b6058a529a47c5ccebf8ed732e27f728 GIT binary patch literal 16298 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