diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index cd6d9f9ae..000000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,91 +0,0 @@ -env: - browser: true - es2021: true -parserOptions: - ecmaVersion: 12 - sourceType: module - allowImportExportEverywhere: true -extends: - - "plugin:svelte/recommended" -overrides: - - - files: - - "*.svelte" - parser: svelte-eslint-parser -ignorePatterns: - - /public - - /**/zez-ui -rules: - semi: - - error - - never - quotes: - - error - - double - comma-spacing: - - error - comma-dangle: - - error - - never - indent: - - error - - 2 - - SwitchCase: 1 - no-trailing-spaces: - - error - space-before-function-paren: - - error - - never - no-var: - - error - prefer-const: - - error - arrow-spacing: - - error - - before: true - after: true - template-curly-spacing: - - error - - never - svelte/no-at-html-tags: - - off - no-unused-vars: - - error - - vars: all - args: after-used - ignoreRestSiblings: true - argsIgnorePattern: "^_" - varsIgnorePattern: "^_" - caughtErrorsIgnorePattern: "^_" - no-whitespace-before-property: - - error - block-spacing: - - error - prefer-arrow-callback: - - error - prefer-spread: - - error - no-duplicate-imports: - - error - space-before-blocks: - - error - no-console: - - warn - - allow: - - error - space-infix-ops: - - error - no-multiple-empty-lines: - - warn - - max: 1 - svelte/no-dupe-on-directives: - - error - svelte/no-dupe-use-directives: - - error - svelte/html-self-closing: - - error - - all - svelte/no-spaces-around-equal-signs-in-attribute: - - error - svelte/mustache-spacing: - - error diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 0742590f4..4c1e893ef 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -8,6 +8,7 @@ concurrency: env: BLIND_INDEX_MASTER_KEY: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" LOCKBOX_MASTER_KEY: "0000000000000000000000000000000000000000000000000000000000000000" + DYNOSCALE_URL: "" jobs: linters: @@ -22,16 +23,16 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.0.0 + ruby-version: 3.2.2 bundler-cache: true - uses: actions/setup-node@v3 with: node-version: "18" check-latest: true - - run: yarn install + - run: npm install - name: ESLint run: | - yarn run eslint app/javascript + npx eslint app/javascript test: runs-on: ubuntu-latest steps: @@ -44,55 +45,55 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 3.0.0 + ruby-version: 3.2.2 bundler-cache: true - uses: actions/setup-node@v3 with: node-version: "18" check-latest: true - cache: 'yarn' - cache-dependency-path: './yarn.lock' - - run: yarn install + cache: 'npm' + cache-dependency-path: './package-lock.json' + - run: npm install - name: Precompile assets run: | bundle exec rails vite:build RAILS_ENV=test - - name: Test - run: | - bundle exec rake db:test:prepare - bundle exec rails test - - name: RSpec - run: | - bundle exec rspec + # - name: Test + # run: | + # bundle exec rake db:test:prepare + # bundle exec rails test + # - name: RSpec + # run: | + # bundle exec rspec - name: Vitest run: | - yarn test - features: - name: Cucumber Feature Check - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup System - run: | - sudo apt update - sudo apt-get install libsqlite3-dev - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.0.0 - bundler-cache: true - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version: "18" - check-latest: true - cache: 'yarn' - cache-dependency-path: './yarn.lock' - - run: yarn install - - name: Precompile assets - run: | - bundle exec rails vite:build RAILS_ENV=test - - name: Cucumber - run: | - bundle exec rake db:test:prepare - bundle exec cucumber + npm test + # features: + # name: Cucumber Feature Check + # runs-on: ubuntu-latest + # steps: + # - name: Checkout + # uses: actions/checkout@v3 + # - name: Setup System + # run: | + # sudo apt update + # sudo apt-get install libsqlite3-dev + # - name: Setup Ruby + # uses: ruby/setup-ruby@v1 + # with: + # ruby-version: 3.2.2 + # bundler-cache: true + # - name: Setup Node + # uses: actions/setup-node@v3 + # with: + # node-version: "18" + # check-latest: true + # cache: 'npm' + # cache-dependency-path: './package-lock.json' + # - run: npm install + # - name: Precompile assets + # run: | + # bundle exec rails vite:build RAILS_ENV=test + # - name: Cucumber + # run: | + # bundle exec rake db:test:prepare + # bundle exec cucumber diff --git a/.husky/pre-commit b/.husky/pre-commit index 36af21989..2312dc587 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - npx lint-staged diff --git a/.ruby-version b/.ruby-version index 45418de72..9e79f6c4a 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-3.0.1 +ruby-3.2.2 diff --git a/.tool-versions b/.tool-versions index d27a8a66c..2471af028 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -ruby 3.0.1 +ruby 3.2.2 nodejs 18.12.0 diff --git a/.vscode/launch.json b/.vscode/launch.json index 7d861e8d1..6e87a96d2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,7 +21,7 @@ "name": "Vite Dev Server", "type": "rdbg", "request": "launch", - "script": "./bin/vite dev", + "script": "npx vite", "cwd": "${workspaceFolder}" }, { diff --git a/Gemfile b/Gemfile index bb68031d0..e5447528c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source 'https://rubygems.org' -ruby "~> 3.0.0" +ruby "~> 3.2.2" git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem "active_model_serializers" @@ -10,13 +10,17 @@ gem "aws-sdk-s3", require: false gem "bcrypt", require: "bcrypt" gem "blind_index" gem "breadcrumbs_on_rails" +gem 'bonsai-elasticsearch-rails', github: 'omc/bonsai-elasticsearch-rails', branch: 'master' gem "bootsnap", require: false gem "chart-js-rails" gem "diffy" gem "disco" gem "discord-notifier" -gem "elasticsearch-model", "~> 6" -gem "elasticsearch-rails", "~> 6" +gem "dynoscale_ruby" +gem "elasticsearch-model", "7.1.1" +gem "elasticsearch-rails", "7.1.1" +gem "elasticsearch", "<= 7.10.2" # Limited by Bonsai support +gem "elasticsearch-api", "<= 7.10.2" # Limited by Bonsai support gem "geocoder" gem "image_processing", "~> 1.12" gem "inline_svg" @@ -30,30 +34,33 @@ gem "omniauth-discord" # Mitigate CVE-2015-9284 gem "omniauth-rails_csrf_protection" gem "puma", "~> 5.6" -gem "rails", "~> 6.1.7" +gem "rails", "~> 7.0.5" gem "rails_same_site_cookie" gem "redcarpet" gem "render_async" gem "reverse_markdown" gem "ruby-openai", "~> 5.0" +gem "sanitize", "6.1.3" gem "sass-rails", "~> 5.0" gem "sendgrid-ruby" +gem "sprockets-rails" gem "sucker_punch" gem "turbolinks", "~> 5" +gem "tzinfo-data" gem "uglifier", ">= 1.3.0" gem "vite_rails" group :development, :test do gem "bullet" gem "byebug", platforms: [:mri, :mingw, :x64_mingw] - gem "capybara", "~> 3.35" + gem "capybara", "3.38.0" gem "cucumber-rails", require: false gem "database_cleaner" gem "factory_bot_rails", "~> 6.2" gem "faker" - gem "pry-byebug", "~> 3.9.0" + gem "pry-byebug", "~> 3.10.1" gem "rspec-rails", "~> 4.0.2" - gem "selenium-webdriver", "~> 4.0" + gem "selenium-webdriver", "~> 4.7.1" gem "shoulda-matchers", "~> 4.5" gem "sqlite3", "1.4.2" # To freeze time and allow time travel for time-sensitive tests @@ -66,7 +73,7 @@ group :development do gem "web-console", ">= 3.3.0" gem "win32-security", platforms: [:mingw, :x64_mingw, :mswin] gem "derailed_benchmarks", group: :development - gem "sys-proctable", platforms: [:mingw, :x64_mingw, :mswin] + gem "sys-proctable" end group :production do @@ -76,5 +83,3 @@ group :production do gem "pg" gem "scout_apm" end - -gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] diff --git a/Gemfile.lock b/Gemfile.lock index 216d243ee..0cd0af336 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,126 +1,144 @@ +GIT + remote: https://github.com/omc/bonsai-elasticsearch-rails.git + revision: 942dea4a263e0b86c7dc197528ea9770c88146c8 + branch: master + specs: + bonsai-elasticsearch-rails (0.3.0) + elasticsearch-model (< 99) + elasticsearch-rails (< 99) + GIT remote: https://github.com/technion/ruby-argon2.git - revision: 0822cddc9feab76d15df0ec3a4203359c0b16be9 + revision: 3388d7e05e8b486ea4ba8bd2aeb1e9988f025f13 submodules: true specs: - argon2 (2.1.1) - ffi (~> 1.14) + argon2 (2.3.0) + ffi (~> 1.15) ffi-compiler (~> 1.0) GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7.8) - actionpack (= 6.1.7.8) - activesupport (= 6.1.7.8) + actioncable (7.0.8.6) + actionpack (= 7.0.8.6) + activesupport (= 7.0.8.6) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.8) - actionpack (= 6.1.7.8) - activejob (= 6.1.7.8) - activerecord (= 6.1.7.8) - activestorage (= 6.1.7.8) - activesupport (= 6.1.7.8) + actionmailbox (7.0.8.6) + actionpack (= 7.0.8.6) + activejob (= 7.0.8.6) + activerecord (= 7.0.8.6) + activestorage (= 7.0.8.6) + activesupport (= 7.0.8.6) mail (>= 2.7.1) - actionmailer (6.1.7.8) - actionpack (= 6.1.7.8) - actionview (= 6.1.7.8) - activejob (= 6.1.7.8) - activesupport (= 6.1.7.8) + net-imap + net-pop + net-smtp + actionmailer (7.0.8.6) + actionpack (= 7.0.8.6) + actionview (= 7.0.8.6) + activejob (= 7.0.8.6) + activesupport (= 7.0.8.6) mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp rails-dom-testing (~> 2.0) - actionpack (6.1.7.8) - actionview (= 6.1.7.8) - activesupport (= 6.1.7.8) - rack (~> 2.0, >= 2.0.9) + actionpack (7.0.8.6) + actionview (= 7.0.8.6) + activesupport (= 7.0.8.6) + 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 (6.1.7.8) - actionpack (= 6.1.7.8) - activerecord (= 6.1.7.8) - activestorage (= 6.1.7.8) - activesupport (= 6.1.7.8) + actiontext (7.0.8.6) + actionpack (= 7.0.8.6) + activerecord (= 7.0.8.6) + activestorage (= 7.0.8.6) + activesupport (= 7.0.8.6) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.1.7.8) - activesupport (= 6.1.7.8) + actionview (7.0.8.6) + activesupport (= 7.0.8.6) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - active_model_serializers (0.10.13) - actionpack (>= 4.1, < 7.1) - activemodel (>= 4.1, < 7.1) + active_model_serializers (0.10.14) + actionpack (>= 4.1) + activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - active_record_doctor (1.10.0) + active_record_doctor (1.15.0) activerecord (>= 4.2.0) - active_storage_validations (0.9.8) - activejob (>= 5.2.0) - activemodel (>= 5.2.0) - activestorage (>= 5.2.0) - activesupport (>= 5.2.0) - activejob (6.1.7.8) - activesupport (= 6.1.7.8) + active_storage_validations (1.3.4) + activejob (>= 6.1.4) + activemodel (>= 6.1.4) + activestorage (>= 6.1.4) + activesupport (>= 6.1.4) + marcel (>= 1.0.3) + activejob (7.0.8.6) + activesupport (= 7.0.8.6) globalid (>= 0.3.6) - activemodel (6.1.7.8) - activesupport (= 6.1.7.8) - activerecord (6.1.7.8) - activemodel (= 6.1.7.8) - activesupport (= 6.1.7.8) - activestorage (6.1.7.8) - actionpack (= 6.1.7.8) - activejob (= 6.1.7.8) - activerecord (= 6.1.7.8) - activesupport (= 6.1.7.8) + activemodel (7.0.8.6) + activesupport (= 7.0.8.6) + activerecord (7.0.8.6) + activemodel (= 7.0.8.6) + activesupport (= 7.0.8.6) + activestorage (7.0.8.6) + actionpack (= 7.0.8.6) + activejob (= 7.0.8.6) + activerecord (= 7.0.8.6) + activesupport (= 7.0.8.6) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.8) + activesupport (7.0.8.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - ahoy_matey (4.1.0) - activesupport (>= 5.2) - device_detector - safely_block (>= 0.2.1) - argon2-kdf (0.1.7) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + ahoy_matey (5.2.1) + activesupport (>= 6.1) + device_detector (>= 1) + safely_block (>= 0.4) + argon2-kdf (0.2.0) ast (2.4.2) - autoprefixer-rails (10.4.7.0) + autoprefixer-rails (10.4.19.0) execjs (~> 2) - aws-eventstream (1.2.0) - aws-partitions (1.610.0) - aws-sdk-core (3.131.3) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.525.0) - aws-sigv4 (~> 1.1) + aws-eventstream (1.3.0) + aws-partitions (1.1011.0) + aws-sdk-core (3.213.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.58.0) - aws-sdk-core (~> 3, >= 3.127.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.114.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-kms (1.96.0) + aws-sdk-core (~> 3, >= 3.210.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.172.0) + aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.10.1) aws-eventstream (~> 1, >= 1.0.2) - bcrypt (3.1.18) - benchmark-ips (2.13.0) + base64 (0.2.0) + bcrypt (3.1.20) + benchmark-ips (2.14.0) + bigdecimal (3.1.8) bindex (0.8.1) - blind_index (2.3.0) - activesupport (>= 5.2) - argon2-kdf (>= 0.1.1) - bootsnap (1.12.0) + blind_index (2.6.1) + activesupport (>= 7) + argon2-kdf (>= 0.2) + bootsnap (1.18.4) msgpack (~> 1.2) breadcrumbs_on_rails (4.1.0) railties (>= 5.0) - bugsnag (6.24.2) + bugsnag (6.27.1) concurrent-ruby (~> 1.0) builder (3.3.0) - bullet (7.0.2) + bullet (8.0.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) byebug (11.1.3) @@ -138,98 +156,97 @@ GEM chart-js-rails (0.1.7) railties (> 3.1) coderay (1.1.3) - concurrent-ruby (1.3.3) + concurrent-ruby (1.3.4) crass (1.0.6) - cucumber (7.1.0) - builder (~> 3.2, >= 3.2.4) - cucumber-core (~> 10.1, >= 10.1.0) - cucumber-create-meta (~> 6.0, >= 6.0.1) - cucumber-cucumber-expressions (~> 14.0, >= 14.0.0) - cucumber-gherkin (~> 22.0, >= 22.0.0) - cucumber-html-formatter (~> 17.0, >= 17.0.0) - cucumber-messages (~> 17.1, >= 17.1.1) - cucumber-wire (~> 6.2, >= 6.2.0) - diff-lcs (~> 1.4, >= 1.4.4) - mime-types (~> 3.3, >= 3.3.1) - multi_test (~> 0.1, >= 0.1.2) - sys-uname (~> 1.2, >= 1.2.2) - cucumber-core (10.1.1) - cucumber-gherkin (~> 22.0, >= 22.0.0) - cucumber-messages (~> 17.1, >= 17.1.1) - cucumber-tag-expressions (~> 4.1, >= 4.1.0) - cucumber-create-meta (6.0.4) - cucumber-messages (~> 17.1, >= 17.1.1) - sys-uname (~> 1.2, >= 1.2.2) - cucumber-cucumber-expressions (14.0.0) - cucumber-gherkin (22.0.0) - cucumber-messages (~> 17.1, >= 17.1.1) - cucumber-html-formatter (17.0.0) - cucumber-messages (~> 17.1, >= 17.1.0) - cucumber-messages (17.1.1) - cucumber-rails (2.5.1) - capybara (>= 2.18, < 4) - cucumber (>= 3.2, < 8) - mime-types (~> 3.3) - nokogiri (~> 1.10) - railties (>= 5.0, < 8) - rexml (~> 3.0) - webrick (~> 1.7) - cucumber-tag-expressions (4.1.0) - cucumber-wire (6.2.1) - cucumber-core (~> 10.1, >= 10.1.0) - cucumber-cucumber-expressions (~> 14.0, >= 14.0.0) - database_cleaner (2.0.1) - database_cleaner-active_record (~> 2.0.0) - database_cleaner-active_record (2.0.1) + csv (3.3.0) + cucumber (9.2.0) + builder (~> 3.2) + cucumber-ci-environment (> 9, < 11) + cucumber-core (> 13, < 14) + cucumber-cucumber-expressions (~> 17.0) + cucumber-gherkin (> 24, < 28) + cucumber-html-formatter (> 20.3, < 22) + cucumber-messages (> 19, < 25) + diff-lcs (~> 1.5) + mini_mime (~> 1.1) + multi_test (~> 1.1) + sys-uname (~> 1.2) + cucumber-ci-environment (10.0.1) + cucumber-core (13.0.3) + cucumber-gherkin (>= 27, < 28) + cucumber-messages (>= 20, < 23) + cucumber-tag-expressions (> 5, < 7) + cucumber-cucumber-expressions (17.1.0) + bigdecimal + cucumber-gherkin (27.0.0) + cucumber-messages (>= 19.1.4, < 23) + cucumber-html-formatter (21.7.0) + cucumber-messages (> 19, < 27) + cucumber-messages (22.0.0) + cucumber-rails (3.0.1) + capybara (>= 3.11, < 4) + cucumber (>= 5, < 10) + railties (>= 5.2, < 8) + cucumber-tag-expressions (6.1.1) + database_cleaner (2.1.0) + database_cleaner-active_record (>= 2, < 3) + database_cleaner-active_record (2.2.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.3.4) - dead_end (4.0.0) - derailed_benchmarks (2.1.2) + date (3.4.0) + derailed_benchmarks (2.2.1) + base64 benchmark-ips (~> 2) - dead_end - get_process_mem (~> 0) + bigdecimal + drb + get_process_mem heapy (~> 0) + logger memory_profiler (>= 0, < 2) mini_histogram (>= 0.3.0) + mutex_m + ostruct rack (>= 1) rack-test rake (> 10, < 14) - ruby-statistics (>= 2.1) + ruby-statistics (>= 4.0.1) + ruby2_keywords thor (>= 0.19, < 2) - device_detector (1.0.7) - diff-lcs (1.5.0) - diffy (3.4.2) - disco (0.3.1) - libmf (>= 0.2.0) - numo-narray + device_detector (1.1.3) + diff-lcs (1.5.1) + diffy (3.4.3) + disco (0.5.0) + libmf (>= 0.4) + numo-narray (>= 0.9.2) discord-notifier (1.0.3) - dry-cli (1.0.0) - elasticsearch (6.8.3) - elasticsearch-api (= 6.8.3) - elasticsearch-transport (= 6.8.3) - elasticsearch-api (6.8.3) + drb (2.2.1) + dry-cli (1.2.0) + dynoscale_ruby (1.0.4) + elasticsearch (7.10.1) + elasticsearch-api (= 7.10.1) + elasticsearch-transport (= 7.10.1) + elasticsearch-api (7.10.1) multi_json - elasticsearch-model (6.1.1) + elasticsearch-model (7.1.1) activesupport (> 3) - elasticsearch (~> 6) + elasticsearch (> 1) hashie - elasticsearch-rails (6.1.1) - elasticsearch-transport (6.8.3) + elasticsearch-rails (7.1.1) + elasticsearch-transport (7.10.1) faraday (~> 1) multi_json - errbase (0.2.2) erubi (1.13.0) - execjs (2.8.1) - factory_bot (6.2.1) + event_stream_parser (0.3.0) + execjs (2.10.0) + factory_bot (6.5.0) activesupport (>= 5.0.0) - factory_bot_rails (6.2.0) - factory_bot (~> 6.2.0) + factory_bot_rails (6.4.4) + factory_bot (~> 6.5) railties (>= 5.0.0) - faker (2.21.0) + faker (3.5.1) i18n (>= 1.8.11, < 2) - faraday (1.10.0) + faraday (1.10.4) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -247,40 +264,43 @@ GEM faraday-httpclient (1.0.1) faraday-multipart (1.0.4) multipart-post (~> 2) - faraday-net_http (1.0.1) + faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) - ffi (1.15.5) - ffi (1.15.5-x64-mingw32) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) + ffi (1.17.0) + ffi-compiler (1.3.2) + ffi (>= 1.15.5) rake ffi-win32-extensions (1.0.4) ffi - geocoder (1.8.0) - get_process_mem (0.2.7) + geocoder (1.8.3) + base64 (>= 0.1.0) + csv (>= 3.0.0) + get_process_mem (1.0.0) + bigdecimal (>= 2.0) ffi (~> 1.0) globalid (1.2.1) activesupport (>= 6.1) hashie (5.0.0) heapy (0.2.0) thor - i18n (1.14.5) + i18n (1.14.6) concurrent-ruby (~> 1.0) - image_processing (1.12.2) + image_processing (1.13.0) mini_magick (>= 4.9.5, < 5) ruby-vips (>= 2.0.17, < 3) - inline_svg (1.8.0) + inline_svg (1.10.0) activesupport (>= 3.0) nokogiri (>= 1.6) - jbuilder (2.11.5) + jbuilder (2.13.0) actionview (>= 5.0.0) activesupport (>= 5.0.0) - jmespath (1.6.1) + jmespath (1.6.2) jsonapi-renderer (0.2.2) - jwt (2.4.1) + jwt (2.9.3) + base64 kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -293,10 +313,11 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - libmf (0.3.0) + libmf (0.4.0) ffi - lockbox (1.0.0) - loofah (2.22.0) + lockbox (2.0.0) + logger (1.6.1) + loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -304,48 +325,43 @@ GEM net-imap net-pop net-smtp - marcel (1.0.3) + marcel (1.0.4) matrix (0.4.2) maxminddb (0.1.22) - memory_profiler (1.0.1) - method_source (1.0.0) - mime-types (3.4.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2022.0105) + memory_profiler (1.1.0) + method_source (1.1.0) mini_histogram (0.3.1) - mini_magick (4.11.0) + mini_magick (4.13.2) mini_mime (1.1.5) - mini_portile2 (2.8.7) - minitest (5.23.1) - msgpack (1.5.4) + mini_portile2 (2.8.8) + minitest (5.25.1) + msgpack (1.7.5) multi_json (1.15.0) - multi_test (0.1.2) - multi_xml (0.6.0) - multipart-post (2.2.3) - net-imap (0.4.10) + multi_test (1.1.0) + multi_xml (0.7.1) + bigdecimal (~> 3.1) + multipart-post (2.4.1) + mutex_m (0.3.0) + net-imap (0.5.1) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout - net-smtp (0.4.0.1) + net-smtp (0.5.0) net-protocol - nio4r (2.7.0) - nokogiri (1.16.6) + nio4r (2.7.4) + nokogiri (1.16.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.16.6-x64-mingw32) - racc (~> 1.4) - nokogiri (1.16.6-x86_64-linux) - racc (~> 1.4) - numo-narray (0.9.2.0) - oauth2 (2.0.6) + numo-narray (0.9.2.1) + oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) jwt (>= 1.0, < 3.0) multi_xml (~> 0.5) - rack (>= 1.2, < 3) - rash_alt (>= 0.4, < 1) + rack (>= 1.2, < 4) + snaky_hash (~> 2.0) version_gem (~> 1.1) omniauth (1.9.2) hashie (>= 3.4.6) @@ -353,49 +369,48 @@ GEM omniauth-bnet (2.0.0) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) - omniauth-discord (1.0.0) - omniauth - omniauth-oauth2 + omniauth-discord (1.2.0) + omniauth-oauth2 (~> 1.6) omniauth-oauth2 (1.7.3) oauth2 (>= 1.4, < 3) omniauth (>= 1.9, < 3) omniauth-rails_csrf_protection (0.1.2) actionpack (>= 4.2) omniauth (>= 1.3.1) - parser (3.1.2.0) + ostruct (0.6.1) + parser (3.3.6.0) ast (~> 2.4.1) - pg (1.4.1) - pg (1.4.1-x64-mingw32) - pry (0.13.1) + racc + pg (1.5.9) + pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) - pry-byebug (3.9.0) + pry-byebug (3.10.1) byebug (~> 11.0) - pry (~> 0.13.0) - public_suffix (5.0.1) - puma (5.6.8) + pry (>= 0.13, < 0.15) + public_suffix (6.0.1) + puma (5.6.9) nio4r (~> 2.0) - racc (1.8.0) - rack (2.2.9) - rack-proxy (0.7.2) + racc (1.8.1) + rack (2.2.10) + rack-proxy (0.7.7) rack rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.8) - actioncable (= 6.1.7.8) - actionmailbox (= 6.1.7.8) - actionmailer (= 6.1.7.8) - actionpack (= 6.1.7.8) - actiontext (= 6.1.7.8) - actionview (= 6.1.7.8) - activejob (= 6.1.7.8) - activemodel (= 6.1.7.8) - activerecord (= 6.1.7.8) - activestorage (= 6.1.7.8) - activesupport (= 6.1.7.8) + rails (7.0.8.6) + actioncable (= 7.0.8.6) + actionmailbox (= 7.0.8.6) + actionmailer (= 7.0.8.6) + actionpack (= 7.0.8.6) + actiontext (= 7.0.8.6) + actionview (= 7.0.8.6) + activejob (= 7.0.8.6) + activemodel (= 7.0.8.6) + activerecord (= 7.0.8.6) + activestorage (= 7.0.8.6) + activesupport (= 7.0.8.6) bundler (>= 1.15.0) - railties (= 6.1.7.8) - sprockets-rails (>= 2.0.0) + railties (= 7.0.8.6) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -406,33 +421,31 @@ GEM rails_same_site_cookie (0.1.9) rack (>= 1.5) user_agent_parser (~> 2.6) - railties (6.1.7.8) - actionpack (= 6.1.7.8) - activesupport (= 6.1.7.8) + railties (7.0.8.6) + actionpack (= 7.0.8.6) + activesupport (= 7.0.8.6) method_source rake (>= 12.2) thor (~> 1.0) - rake (13.1.0) - rash_alt (0.4.12) - hashie (>= 3.4) - rb-fsevent (0.11.1) - rb-inotify (0.10.1) + zeitwerk (~> 2.5) + rake (13.2.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) ffi (~> 1.0) - redcarpet (3.5.1) - regexp_parser (2.6.1) + redcarpet (3.6.0) + regexp_parser (2.9.2) render_async (2.1.11) - reverse_markdown (2.1.1) + reverse_markdown (3.0.0) nokogiri - rexml (3.2.8) - strscan (>= 3.0.9) - rspec-core (3.11.0) - rspec-support (~> 3.11.0) - rspec-expectations (3.11.0) + rexml (3.3.9) + rspec-core (3.13.2) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.1) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) + rspec-support (~> 3.13.0) rspec-rails (4.0.2) actionpack (>= 4.2) activesupport (>= 4.2) @@ -441,18 +454,22 @@ GEM rspec-expectations (~> 3.10) rspec-mocks (~> 3.10) rspec-support (~> 3.10) - rspec-support (3.11.0) - ruby-openai (5.1.0) + rspec-support (3.13.1) + ruby-openai (5.2.0) + event_stream_parser (>= 0.3.0, < 1.0.0) faraday (>= 1) faraday-multipart (>= 1) - ruby-statistics (3.0.2) - ruby-vips (2.1.4) + ruby-statistics (4.0.1) + ruby-vips (2.2.2) ffi (~> 1.12) + logger ruby2_keywords (0.0.5) ruby_http_client (3.5.5) rubyzip (2.3.2) - safely_block (0.3.0) - errbase (>= 0.1.1) + safely_block (0.4.1) + sanitize (6.1.3) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) @@ -464,65 +481,69 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - scout_apm (5.2.0) + scout_apm (5.4.0) parser selenium-webdriver (4.7.1) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - sendgrid-ruby (6.6.2) + sendgrid-ruby (6.7.0) ruby_http_client (~> 3.4) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) - sprockets (3.7.2) + snaky_hash (2.0.1) + hashie + version_gem (~> 1.1, >= 1.1.1) + sprockets (3.7.5) + base64 concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) + sprockets-rails (3.5.2) + actionpack (>= 6.1) + activesupport (>= 6.1) sprockets (>= 3.0.0) sqlite3 (1.4.2) - strscan (3.1.0) - sucker_punch (3.0.1) + sucker_punch (3.2.0) concurrent-ruby (~> 1.0) sys-proctable (1.3.0) ffi (~> 1.1) - sys-uname (1.2.2) + sys-uname (1.3.1) ffi (~> 1.1) - thor (1.3.1) - tilt (2.0.11) - timecop (0.9.5) - timeout (0.4.1) + thor (1.3.2) + tilt (2.4.0) + timecop (0.9.10) + timeout (0.4.2) turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2022.1) + tzinfo-data (1.2024.2) tzinfo (>= 1.0.0) - uglifier (4.2.0) + uglifier (4.2.1) execjs (>= 0.3.0, < 3) uniform_notifier (1.16.0) - user_agent_parser (2.11.0) - version_gem (1.1.0) - vite_rails (3.0.17) - railties (>= 5.1, < 8) + user_agent_parser (2.18.0) + version_gem (1.1.4) + vite_rails (3.0.19) + railties (>= 5.1, < 9) vite_ruby (~> 3.0, >= 3.2.2) - vite_ruby (3.5.0) + vite_ruby (3.9.1) dry-cli (>= 0.7, < 2) + logger (~> 1.6) + mutex_m rack-proxy (~> 0.6, >= 0.6.1) zeitwerk (~> 2.2) - web-console (4.2.0) + web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webdrivers (5.2.0) + webdrivers (5.3.1) nokogiri (~> 1.6) rubyzip (>= 1.3.0) - selenium-webdriver (~> 4.0) - webrick (1.7.0) - websocket (1.2.9) + selenium-webdriver (~> 4.0, < 4.11) + websocket (1.2.11) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -531,7 +552,7 @@ GEM ffi-win32-extensions xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.16) + zeitwerk (2.7.1) PLATFORMS ruby @@ -548,12 +569,13 @@ DEPENDENCIES aws-sdk-s3 bcrypt blind_index + bonsai-elasticsearch-rails! bootsnap breadcrumbs_on_rails bugsnag bullet byebug - capybara (~> 3.35) + capybara (= 3.38.0) chart-js-rails cucumber-rails database_cleaner @@ -561,8 +583,11 @@ DEPENDENCIES diffy disco discord-notifier - elasticsearch-model (~> 6) - elasticsearch-rails (~> 6) + dynoscale_ruby + elasticsearch (<= 7.10.2) + elasticsearch-api (<= 7.10.2) + elasticsearch-model (= 7.1.1) + elasticsearch-rails (= 7.1.1) factory_bot_rails (~> 6.2) faker geocoder @@ -577,20 +602,22 @@ DEPENDENCIES omniauth-discord omniauth-rails_csrf_protection pg - pry-byebug (~> 3.9.0) + pry-byebug (~> 3.10.1) puma (~> 5.6) - rails (~> 6.1.7) + rails (~> 7.0.5) rails_same_site_cookie redcarpet render_async reverse_markdown rspec-rails (~> 4.0.2) ruby-openai (~> 5.0) + sanitize (= 6.1.3) sass-rails (~> 5.0) scout_apm - selenium-webdriver (~> 4.0) + selenium-webdriver (~> 4.7.1) sendgrid-ruby shoulda-matchers (~> 4.5) + sprockets-rails sqlite3 (= 1.4.2) sucker_punch sys-proctable @@ -604,7 +631,7 @@ DEPENDENCIES win32-security RUBY VERSION - ruby 3.0.0p0 + ruby 3.2.2p53 BUNDLED WITH 2.3.7 diff --git a/Procfile.dev b/Procfile.dev index 1acf605c3..58a01e924 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,3 +1,3 @@ -vite: bin/vite dev +vite: npx vite web: bin/rails s diff --git a/README.md b/README.md index cd3e9f019..c6765f63a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Images should be losslessly compressed. SVG and WebP should be used wherever pos ## Setup -Follow [this Wiki page](https://github.com/EloHellEsports/workshop.codes/wiki/Local-Development:-Setup) to get started. +Follow [this Wiki page](https://github.com/Mitcheljager/workshop.codes/wiki/Local-Development:-Setup) to get started. ## Environment vars diff --git a/app/assets/images/abilities/128/bonespur.png b/app/assets/images/abilities/128/bonespur.png new file mode 100644 index 000000000..352c99130 Binary files /dev/null and b/app/assets/images/abilities/128/bonespur.png differ diff --git a/app/assets/images/abilities/128/downpour.png b/app/assets/images/abilities/128/downpour.png new file mode 100644 index 000000000..ae279bbd1 Binary files /dev/null and b/app/assets/images/abilities/128/downpour.png differ diff --git a/app/assets/images/abilities/128/glide-boost.png b/app/assets/images/abilities/128/glide-boost.png new file mode 100644 index 000000000..39b22ef46 Binary files /dev/null and b/app/assets/images/abilities/128/glide-boost.png differ diff --git a/app/assets/images/abilities/128/hover-jets.png b/app/assets/images/abilities/128/hover-jets.png index 3901e53cf..d51cfcaf8 100644 Binary files a/app/assets/images/abilities/128/hover-jets.png and b/app/assets/images/abilities/128/hover-jets.png differ diff --git a/app/assets/images/abilities/128/hyper-ring.png b/app/assets/images/abilities/128/hyper-ring.png new file mode 100644 index 000000000..be4a56080 Binary files /dev/null and b/app/assets/images/abilities/128/hyper-ring.png differ diff --git a/app/assets/images/abilities/128/jagged-wall.png b/app/assets/images/abilities/128/jagged-wall.png new file mode 100644 index 000000000..16b7cbf6a Binary files /dev/null and b/app/assets/images/abilities/128/jagged-wall.png differ diff --git a/app/assets/images/abilities/128/jet-dash.png b/app/assets/images/abilities/128/jet-dash.png index 94dd65856..fd0b179f2 100644 Binary files a/app/assets/images/abilities/128/jet-dash.png and b/app/assets/images/abilities/128/jet-dash.png differ diff --git a/app/assets/images/abilities/128/martian-overboots.png b/app/assets/images/abilities/128/martian-overboots.png new file mode 100644 index 000000000..fafb70682 Binary files /dev/null and b/app/assets/images/abilities/128/martian-overboots.png differ diff --git a/app/assets/images/abilities/128/mediblaster.png b/app/assets/images/abilities/128/mediblaster.png new file mode 100644 index 000000000..d325bf828 Binary files /dev/null and b/app/assets/images/abilities/128/mediblaster.png differ diff --git a/app/assets/images/abilities/128/orbital-ray.png b/app/assets/images/abilities/128/orbital-ray.png new file mode 100644 index 000000000..063464fd9 Binary files /dev/null and b/app/assets/images/abilities/128/orbital-ray.png differ diff --git a/app/assets/images/abilities/128/passive.png b/app/assets/images/abilities/128/passive.png new file mode 100644 index 000000000..3901e53cf Binary files /dev/null and b/app/assets/images/abilities/128/passive.png differ diff --git a/app/assets/images/abilities/128/photon-barrier-original.png b/app/assets/images/abilities/128/photon-barrier-original.png new file mode 100644 index 000000000..7be9663b8 Binary files /dev/null and b/app/assets/images/abilities/128/photon-barrier-original.png differ diff --git a/app/assets/images/abilities/128/photon-shield.png b/app/assets/images/abilities/128/photon-shield.png new file mode 100644 index 000000000..7e4a8f384 Binary files /dev/null and b/app/assets/images/abilities/128/photon-shield.png differ diff --git a/app/assets/images/abilities/128/pulsar-torpedoes.png b/app/assets/images/abilities/128/pulsar-torpedoes.png new file mode 100644 index 000000000..997960041 Binary files /dev/null and b/app/assets/images/abilities/128/pulsar-torpedoes.png differ diff --git a/app/assets/images/abilities/128/self-repair.png b/app/assets/images/abilities/128/self-repair.png index f93965c4e..299431688 100644 Binary files a/app/assets/images/abilities/128/self-repair.png and b/app/assets/images/abilities/128/self-repair.png differ diff --git a/app/assets/images/abilities/128/snap-kick.png b/app/assets/images/abilities/128/snap-kick.png index 3901e53cf..6e037d7b3 100644 Binary files a/app/assets/images/abilities/128/snap-kick.png and b/app/assets/images/abilities/128/snap-kick.png differ diff --git a/app/assets/images/abilities/128/solar-rifle-alt.png b/app/assets/images/abilities/128/solar-rifle-alt.png new file mode 100644 index 000000000..f49ccd539 Binary files /dev/null and b/app/assets/images/abilities/128/solar-rifle-alt.png differ diff --git a/app/assets/images/abilities/128/spike-guard.png b/app/assets/images/abilities/128/spike-guard.png new file mode 100644 index 000000000..71f08e9d4 Binary files /dev/null and b/app/assets/images/abilities/128/spike-guard.png differ diff --git a/app/assets/images/abilities/128/sympathetic-recovery.png b/app/assets/images/abilities/128/sympathetic-recovery.png index 3901e53cf..06d4defbb 100644 Binary files a/app/assets/images/abilities/128/sympathetic-recovery.png and b/app/assets/images/abilities/128/sympathetic-recovery.png differ diff --git a/app/assets/images/abilities/128/vault.png b/app/assets/images/abilities/128/vault.png new file mode 100644 index 000000000..618ddeb13 Binary files /dev/null and b/app/assets/images/abilities/128/vault.png differ diff --git a/app/assets/images/abilities/128/violent-leap.png b/app/assets/images/abilities/128/violent-leap.png new file mode 100644 index 000000000..bb8aa0a6d Binary files /dev/null and b/app/assets/images/abilities/128/violent-leap.png differ diff --git a/app/assets/images/abilities/128/wall-climb.png b/app/assets/images/abilities/128/wall-climb.png index c81b619a4..336c2c0f4 100644 Binary files a/app/assets/images/abilities/128/wall-climb.png and b/app/assets/images/abilities/128/wall-climb.png differ diff --git a/app/assets/images/abilities/128/wall-ride.png b/app/assets/images/abilities/128/wall-ride.png index 3901e53cf..4d258af9c 100644 Binary files a/app/assets/images/abilities/128/wall-ride.png and b/app/assets/images/abilities/128/wall-ride.png differ diff --git a/app/assets/images/abilities/50/bonespur.png b/app/assets/images/abilities/50/bonespur.png new file mode 100644 index 000000000..c425056d9 Binary files /dev/null and b/app/assets/images/abilities/50/bonespur.png differ diff --git a/app/assets/images/abilities/50/downpour.png b/app/assets/images/abilities/50/downpour.png new file mode 100644 index 000000000..3d102a510 Binary files /dev/null and b/app/assets/images/abilities/50/downpour.png differ diff --git a/app/assets/images/abilities/50/glide-boost.png b/app/assets/images/abilities/50/glide-boost.png new file mode 100644 index 000000000..1b0127f44 Binary files /dev/null and b/app/assets/images/abilities/50/glide-boost.png differ diff --git a/app/assets/images/abilities/50/hover-jets.png b/app/assets/images/abilities/50/hover-jets.png index 98c854cb6..7f1137a0d 100644 Binary files a/app/assets/images/abilities/50/hover-jets.png and b/app/assets/images/abilities/50/hover-jets.png differ diff --git a/app/assets/images/abilities/50/hyper-ring.png b/app/assets/images/abilities/50/hyper-ring.png new file mode 100644 index 000000000..7dd35eee2 Binary files /dev/null and b/app/assets/images/abilities/50/hyper-ring.png differ diff --git a/app/assets/images/abilities/50/jagged-wall.png b/app/assets/images/abilities/50/jagged-wall.png new file mode 100644 index 000000000..ee3527c64 Binary files /dev/null and b/app/assets/images/abilities/50/jagged-wall.png differ diff --git a/app/assets/images/abilities/50/jet-dash.png b/app/assets/images/abilities/50/jet-dash.png index 02b28197c..465e06310 100644 Binary files a/app/assets/images/abilities/50/jet-dash.png and b/app/assets/images/abilities/50/jet-dash.png differ diff --git a/app/assets/images/abilities/50/martian-overboots.png b/app/assets/images/abilities/50/martian-overboots.png new file mode 100644 index 000000000..e48399b5c Binary files /dev/null and b/app/assets/images/abilities/50/martian-overboots.png differ diff --git a/app/assets/images/abilities/50/mediblaster.png b/app/assets/images/abilities/50/mediblaster.png new file mode 100644 index 000000000..abe2b2044 Binary files /dev/null and b/app/assets/images/abilities/50/mediblaster.png differ diff --git a/app/assets/images/abilities/50/orbital-ray.png b/app/assets/images/abilities/50/orbital-ray.png new file mode 100644 index 000000000..f3978d645 Binary files /dev/null and b/app/assets/images/abilities/50/orbital-ray.png differ diff --git a/app/assets/images/abilities/50/passive.png b/app/assets/images/abilities/50/passive.png new file mode 100644 index 000000000..98c854cb6 Binary files /dev/null and b/app/assets/images/abilities/50/passive.png differ diff --git a/app/assets/images/abilities/50/photon-barrier-original.png b/app/assets/images/abilities/50/photon-barrier-original.png new file mode 100644 index 000000000..f3520315c Binary files /dev/null and b/app/assets/images/abilities/50/photon-barrier-original.png differ diff --git a/app/assets/images/abilities/50/photon-barrier.png b/app/assets/images/abilities/50/photon-barrier.png index 8abf5d13e..c612028a7 100644 Binary files a/app/assets/images/abilities/50/photon-barrier.png and b/app/assets/images/abilities/50/photon-barrier.png differ diff --git a/app/assets/images/abilities/50/photon-shield.png b/app/assets/images/abilities/50/photon-shield.png new file mode 100644 index 000000000..a444884e1 Binary files /dev/null and b/app/assets/images/abilities/50/photon-shield.png differ diff --git a/app/assets/images/abilities/50/pulsar-torpedoes.png b/app/assets/images/abilities/50/pulsar-torpedoes.png new file mode 100644 index 000000000..41d57351d Binary files /dev/null and b/app/assets/images/abilities/50/pulsar-torpedoes.png differ diff --git a/app/assets/images/abilities/50/self-repair.png b/app/assets/images/abilities/50/self-repair.png index 71fc979d7..67339dd5c 100644 Binary files a/app/assets/images/abilities/50/self-repair.png and b/app/assets/images/abilities/50/self-repair.png differ diff --git a/app/assets/images/abilities/50/snap-kick.png b/app/assets/images/abilities/50/snap-kick.png index 98c854cb6..4c3436740 100644 Binary files a/app/assets/images/abilities/50/snap-kick.png and b/app/assets/images/abilities/50/snap-kick.png differ diff --git a/app/assets/images/abilities/50/solar-rifle-alt.png b/app/assets/images/abilities/50/solar-rifle-alt.png new file mode 100644 index 000000000..5761aa2f5 Binary files /dev/null and b/app/assets/images/abilities/50/solar-rifle-alt.png differ diff --git a/app/assets/images/abilities/50/spike-guard.png b/app/assets/images/abilities/50/spike-guard.png new file mode 100644 index 000000000..d41b14633 Binary files /dev/null and b/app/assets/images/abilities/50/spike-guard.png differ diff --git a/app/assets/images/abilities/50/sympathetic-recovery.png b/app/assets/images/abilities/50/sympathetic-recovery.png index 98c854cb6..dd3c2bda2 100644 Binary files a/app/assets/images/abilities/50/sympathetic-recovery.png and b/app/assets/images/abilities/50/sympathetic-recovery.png differ diff --git a/app/assets/images/abilities/50/vault.png b/app/assets/images/abilities/50/vault.png new file mode 100644 index 000000000..22a9a7d82 Binary files /dev/null and b/app/assets/images/abilities/50/vault.png differ diff --git a/app/assets/images/abilities/50/violent-leap.png b/app/assets/images/abilities/50/violent-leap.png new file mode 100644 index 000000000..b41f4ccb0 Binary files /dev/null and b/app/assets/images/abilities/50/violent-leap.png differ diff --git a/app/assets/images/abilities/50/wall-climb.png b/app/assets/images/abilities/50/wall-climb.png index 47821a984..5feed1cbc 100644 Binary files a/app/assets/images/abilities/50/wall-climb.png and b/app/assets/images/abilities/50/wall-climb.png differ diff --git a/app/assets/images/abilities/50/wall-ride.png b/app/assets/images/abilities/50/wall-ride.png index 98c854cb6..806673cc5 100644 Binary files a/app/assets/images/abilities/50/wall-ride.png and b/app/assets/images/abilities/50/wall-ride.png differ diff --git a/app/assets/images/abilities/bonespur.png b/app/assets/images/abilities/bonespur.png new file mode 100644 index 000000000..cc7f4f9c0 Binary files /dev/null and b/app/assets/images/abilities/bonespur.png differ diff --git a/app/assets/images/abilities/downpour.png b/app/assets/images/abilities/downpour.png new file mode 100644 index 000000000..7357a9e10 Binary files /dev/null and b/app/assets/images/abilities/downpour.png differ diff --git a/app/assets/images/abilities/jagged-wall.png b/app/assets/images/abilities/jagged-wall.png new file mode 100644 index 000000000..4f669e7c5 Binary files /dev/null and b/app/assets/images/abilities/jagged-wall.png differ diff --git a/app/assets/images/abilities/spike-guard.png b/app/assets/images/abilities/spike-guard.png new file mode 100644 index 000000000..cb2b4b43c Binary files /dev/null and b/app/assets/images/abilities/spike-guard.png differ diff --git a/app/assets/images/abilities/vault.png b/app/assets/images/abilities/vault.png new file mode 100644 index 000000000..fd2cd20c0 Binary files /dev/null and b/app/assets/images/abilities/vault.png differ diff --git a/app/assets/images/abilities/violent-leap.png b/app/assets/images/abilities/violent-leap.png new file mode 100644 index 000000000..70eb6d0b0 Binary files /dev/null and b/app/assets/images/abilities/violent-leap.png differ diff --git a/app/assets/images/controls/playstation/ability-1.png b/app/assets/images/controls/playstation/ability-1.png index b7564f406..0642e32e6 100644 Binary files a/app/assets/images/controls/playstation/ability-1.png and b/app/assets/images/controls/playstation/ability-1.png differ diff --git a/app/assets/images/controls/playstation/ability-2.png b/app/assets/images/controls/playstation/ability-2.png index 0642e32e6..b7564f406 100644 Binary files a/app/assets/images/controls/playstation/ability-2.png and b/app/assets/images/controls/playstation/ability-2.png differ diff --git a/app/assets/images/controls/switch/ability-1.png b/app/assets/images/controls/switch/ability-1.png index ab1ddebd2..dd6ca66ec 100644 Binary files a/app/assets/images/controls/switch/ability-1.png and b/app/assets/images/controls/switch/ability-1.png differ diff --git a/app/assets/images/controls/switch/ability-2.png b/app/assets/images/controls/switch/ability-2.png index dd6ca66ec..ab1ddebd2 100644 Binary files a/app/assets/images/controls/switch/ability-2.png and b/app/assets/images/controls/switch/ability-2.png differ diff --git a/app/assets/images/controls/xbox/ability-1.png b/app/assets/images/controls/xbox/ability-1.png index 01ad14078..3b18f7b31 100644 Binary files a/app/assets/images/controls/xbox/ability-1.png and b/app/assets/images/controls/xbox/ability-1.png differ diff --git a/app/assets/images/controls/xbox/ability-2.png b/app/assets/images/controls/xbox/ability-2.png index 3b18f7b31..01ad14078 100644 Binary files a/app/assets/images/controls/xbox/ability-2.png and b/app/assets/images/controls/xbox/ability-2.png differ diff --git a/app/assets/images/elo-hell-logo-white.svg b/app/assets/images/elo-hell-logo-white.svg deleted file mode 100644 index 0d2feca4d..000000000 --- a/app/assets/images/elo-hell-logo-white.svg +++ /dev/null @@ -1 +0,0 @@ -Elo Hell Logo_H-M-Dark \ No newline at end of file diff --git a/app/assets/images/favicon/apple-touch-icon.png b/app/assets/images/favicon/apple-touch-icon.png index d57d7df55..1783e63aa 100644 Binary files a/app/assets/images/favicon/apple-touch-icon.png and b/app/assets/images/favicon/apple-touch-icon.png differ diff --git a/app/assets/images/favicon/favicon-16x16.png b/app/assets/images/favicon/favicon-16x16.png index 364e9f94a..7ce435b86 100644 Binary files a/app/assets/images/favicon/favicon-16x16.png and b/app/assets/images/favicon/favicon-16x16.png differ diff --git a/app/assets/images/favicon/favicon-192x192.png b/app/assets/images/favicon/favicon-192x192.png index 6ee243cf4..935d03cbd 100644 Binary files a/app/assets/images/favicon/favicon-192x192.png and b/app/assets/images/favicon/favicon-192x192.png differ diff --git a/app/assets/images/favicon/favicon-32x32.png b/app/assets/images/favicon/favicon-32x32.png index 5e45b8f26..86608a674 100644 Binary files a/app/assets/images/favicon/favicon-32x32.png and b/app/assets/images/favicon/favicon-32x32.png differ diff --git a/app/assets/images/favicon/favicon-512x512.png b/app/assets/images/favicon/favicon-512x512.png index 2e55de235..4eec9e7ad 100644 Binary files a/app/assets/images/favicon/favicon-512x512.png and b/app/assets/images/favicon/favicon-512x512.png differ diff --git a/app/assets/images/heroes/100/hazard.png b/app/assets/images/heroes/100/hazard.png new file mode 100644 index 000000000..05b1dfffe Binary files /dev/null and b/app/assets/images/heroes/100/hazard.png differ diff --git a/app/assets/images/heroes/100/juno.png b/app/assets/images/heroes/100/juno.png new file mode 100644 index 000000000..be8c0f696 Binary files /dev/null and b/app/assets/images/heroes/100/juno.png differ diff --git a/app/assets/images/heroes/256/hazard.png b/app/assets/images/heroes/256/hazard.png new file mode 100644 index 000000000..0520c4716 Binary files /dev/null and b/app/assets/images/heroes/256/hazard.png differ diff --git a/app/assets/images/heroes/256/juno.png b/app/assets/images/heroes/256/juno.png new file mode 100644 index 000000000..266aef8dd Binary files /dev/null and b/app/assets/images/heroes/256/juno.png differ diff --git a/app/assets/images/heroes/50/hazard.png b/app/assets/images/heroes/50/hazard.png new file mode 100644 index 000000000..eb0ce5ab4 Binary files /dev/null and b/app/assets/images/heroes/50/hazard.png differ diff --git a/app/assets/images/heroes/50/juno.png b/app/assets/images/heroes/50/juno.png new file mode 100644 index 000000000..798aacd0c Binary files /dev/null and b/app/assets/images/heroes/50/juno.png differ diff --git a/app/assets/images/icons/icon-copy.svg b/app/assets/images/icons/icon-copy.svg index 188202e46..fa5b12cb6 100644 --- a/app/assets/images/icons/icon-copy.svg +++ b/app/assets/images/icons/icon-copy.svg @@ -1,5 +1,6 @@ - + Copy + diff --git a/app/assets/images/icons/icon-discord.svg b/app/assets/images/icons/icon-discord.svg index 5f21b8651..4127b4946 100644 --- a/app/assets/images/icons/icon-discord.svg +++ b/app/assets/images/icons/icon-discord.svg @@ -1 +1,4 @@ - + + Discord + + diff --git a/app/assets/images/icons/icon-filter.svg b/app/assets/images/icons/icon-filter.svg index 91bddda73..7f20bd459 100644 --- a/app/assets/images/icons/icon-filter.svg +++ b/app/assets/images/icons/icon-filter.svg @@ -1 +1,4 @@ - + + Filter + + diff --git a/app/assets/images/icons/icon-warning.svg b/app/assets/images/icons/icon-warning.svg new file mode 100644 index 000000000..06b9e0118 --- /dev/null +++ b/app/assets/images/icons/icon-warning.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/app/assets/images/layout/carousel-left.svg b/app/assets/images/layout/carousel-left.svg index ff3597a17..19cf723a9 100644 --- a/app/assets/images/layout/carousel-left.svg +++ b/app/assets/images/layout/carousel-left.svg @@ -1,4 +1,5 @@ + Previous + Next Elo Hell Logo_I-M-Light diff --git a/app/assets/images/logo-small.svg b/app/assets/images/logo-small.svg index 1b00c8b87..12ce69b9d 100644 --- a/app/assets/images/logo-small.svg +++ b/app/assets/images/logo-small.svg @@ -1,4 +1,5 @@ + Workshop.codes diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg index 33c0fb479..208521d4e 100644 --- a/app/assets/images/logo.svg +++ b/app/assets/images/logo.svg @@ -1,4 +1,5 @@ + Workshop.codes diff --git a/app/assets/images/maps/large/throne-of-anubis.jpg b/app/assets/images/maps/large/throne-of-anubis.jpg new file mode 100644 index 000000000..d47ea582c Binary files /dev/null and b/app/assets/images/maps/large/throne-of-anubis.jpg differ diff --git a/app/assets/images/maps/large/webp/throne-of-anubis.webp b/app/assets/images/maps/large/webp/throne-of-anubis.webp new file mode 100644 index 000000000..c8db982de Binary files /dev/null and b/app/assets/images/maps/large/webp/throne-of-anubis.webp differ diff --git a/app/assets/images/maps/medium/throne-of-anubis.jpg b/app/assets/images/maps/medium/throne-of-anubis.jpg new file mode 100644 index 000000000..d603804d7 Binary files /dev/null and b/app/assets/images/maps/medium/throne-of-anubis.jpg differ diff --git a/app/assets/images/maps/medium/webp/throne-of-anubis.webp b/app/assets/images/maps/medium/webp/throne-of-anubis.webp new file mode 100644 index 000000000..60ee77b6e Binary files /dev/null and b/app/assets/images/maps/medium/webp/throne-of-anubis.webp differ diff --git a/app/assets/images/maps/small/throne-of-anubis.jpg b/app/assets/images/maps/small/throne-of-anubis.jpg new file mode 100644 index 000000000..23f85e714 Binary files /dev/null and b/app/assets/images/maps/small/throne-of-anubis.jpg differ diff --git a/app/assets/images/maps/small/webp/throne-of-anubis.webp b/app/assets/images/maps/small/webp/throne-of-anubis.webp new file mode 100644 index 000000000..c58d698b3 Binary files /dev/null and b/app/assets/images/maps/small/webp/throne-of-anubis.webp differ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7793e3951..de15a79e4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -9,7 +9,6 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :login_from_cookie before_action :reject_if_banned - before_action :redirect_non_www, if: -> { Rails.env.production? } before_action :expire_oauth_session rescue_from ActionController::InvalidAuthenticityToken, with: :handle_failed_authenticity_token @@ -109,12 +108,6 @@ def expire_oauth_session end end - def redirect_non_www - if /^www/.match(request.host) - redirect_to("#{ request.url }".gsub("www.", ""), status: 301) - end - end - def handle_failed_authenticity_token @message = "Authentication failed. Please refresh the page and try again." render "application/error" diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb index 84eaa537b..6748f076a 100644 --- a/app/controllers/articles_controller.rb +++ b/app/controllers/articles_controller.rb @@ -4,7 +4,7 @@ class ArticlesController < ApplicationController end def show - @article = Article.find_by_slug(params[:slug]) + @article = Article.find_by_slug!(params[:slug]) track_action end diff --git a/app/controllers/collections_controller.rb b/app/controllers/collections_controller.rb index 3999e83f8..c86602643 100644 --- a/app/controllers/collections_controller.rb +++ b/app/controllers/collections_controller.rb @@ -6,7 +6,7 @@ class CollectionsController < ApplicationController after_action :track_action, only: [:show] def index - @collections = Collection.includes(:posts).where("posts_count > ?", 0).order(created_at: :desc).limit(20) + @collections = Collection.includes(:posts).where("posts_count > ?", 0).order(created_at: :desc).page(params[:page]) end def show @@ -29,11 +29,11 @@ def partial def new @collection = Collection.new + @collection.nice_url = SecureRandom.alphanumeric(6).downcase end def create @collection = Collection.new(collection_params) - @collection.nice_url = SecureRandom.alphanumeric(6).downcase @collection.user_id = current_user.id if @collection.save @@ -41,18 +41,18 @@ def create set_collection_id_for_posts(collection_params[:collection_posts]) flash[:notice] = "Collection created" - redirect_to edit_collection_path(@collection.nice_url) + redirect_to edit_collection_path(@collection.id) else render :new end end def edit - @collection = current_user.collections.find_by_nice_url!(params[:nice_url].downcase) + @collection = current_user.collections.find(params[:id]) end def update - @collection = current_user.collections.find_by_nice_url!(params[:nice_url].downcase) + @collection = current_user.collections.find(params[:id]) initial_ids = @collection.posts.pluck(:id) param_ids = (collection_params[:collection_posts] || []).map { |id| id.to_i } @@ -67,17 +67,17 @@ def update end flash[:alert] = "Successfully saved" - redirect_to edit_collection_path(@collection.nice_url) + redirect_to edit_collection_path(@collection.id) else render :edit end end def destroy - @collection = Collection.where(user_id: current_user.id).find_by_nice_url!(params[:nice_url].downcase) + @collection = Collection.where(user_id: current_user.id).find(params[:id]) if @collection.posts.none? && @collection.destroy - redirect_to collections_path + redirect_to account_collections_path else render "application/error" end @@ -99,7 +99,7 @@ def revisions private def collection_params - params.require(:collection).permit(:title, :cover_image, :description, :display_type, { collection_posts: [] }) + params.require(:collection).permit(:title, :nice_url, :cover_image, :description, :display_type, { collection_posts: [] }) end def set_collection_id_for_posts(current_ids = [], initial_ids = []) diff --git a/app/controllers/editor_controller.rb b/app/controllers/editor_controller.rb index 048c206d6..ee5868612 100644 --- a/app/controllers/editor_controller.rb +++ b/app/controllers/editor_controller.rb @@ -7,11 +7,11 @@ def data expires_in 4.hours, public: true response = Rails.cache.fetch("editor_data", expires_in: 1.day) do - events = YAML.load(File.read(Rails.root.join("config/arrays/wiki", "events.yml"))) - actions = YAML.load(File.read(Rails.root.join("config/arrays/wiki", "actions.yml"))) - values = YAML.load(File.read(Rails.root.join("config/arrays/wiki", "values.yml"))) - defaults = YAML.load(File.read(Rails.root.join("config/arrays/wiki", "defaults.yml"))) - constants = YAML.load(File.read(Rails.root.join("config/arrays/wiki", "constants.yml"))) + events = YAML.safe_load(File.read(Rails.root.join("config/arrays/wiki", "events.yml"))) + actions = YAML.safe_load(File.read(Rails.root.join("config/arrays/wiki", "actions.yml"))) + values = YAML.safe_load(File.read(Rails.root.join("config/arrays/wiki", "values.yml"))) + defaults = YAML.safe_load(File.read(Rails.root.join("config/arrays/wiki", "defaults.yml"))) + constants = YAML.safe_load(File.read(Rails.root.join("config/arrays/wiki", "constants.yml"))) response = { events: events, diff --git a/app/controllers/filter_controller.rb b/app/controllers/filter_controller.rb index 0c34cf36e..e10e524e9 100644 --- a/app/controllers/filter_controller.rb +++ b/app/controllers/filter_controller.rb @@ -3,7 +3,7 @@ def index # REFACTOR: Make this set of attributes have a single source of accessible truth filtered_params = params.permit([:category, :code, :hero, :map, :sort, :expired, :author, :players, :search, :page]) respond_to do |format| - format.html { redirect_to filter_path(filtered_params), status: :moved_permanently } + format.html { redirect_to filter_path(filtered_params), status: :moved_permanently, allow_other_host: true } format.js { head :moved_permanently, location: filter_path(params: filtered_params, format: :js) } format.json { head :moved_permanently, location: filter_path(params: filtered_params, format: :json) } end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 5608cd667..216e55c43 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -35,7 +35,7 @@ def latest end def show - @post = Post.includes(:user, :collection, :revisions, :blocks, :derivations).find_by_code(params[:code]) + @post = Post.includes(:user).find_by_code(params[:code]) not_found and return if @post && (@post.private? || @post.draft?) && @post.user != current_user @@ -455,7 +455,7 @@ def notify_discord(type) path = post_url(post.code.upcase) user_path = user_url(post.user.username) image = @ordered_images.present? && @ordered_images.first.present? ? url_for(@ordered_images.first.variant(quality: 95).processed.url) : "" - avatar = @post.user.profile_image.present? ? url_for(@post.user.profile_image.variant(quality: 95, resize_to_fill: [120, 120]).processed.url) : "" + avatar = post.user.profile_image.present? ? url_for(post.user.profile_image.variant(quality: 95, resize_to_fill: [120, 120]).processed.url) : "" content = ActionController::Base.helpers.strip_tags(post.description).truncate(type == "New" ? 500 : 250) embed = Discord::Embed.new do @@ -467,7 +467,7 @@ def notify_discord(type) description content add_field name: "Update notes", value: revision.description.truncate(400) if revision && revision.description.present? add_field name: "Code", value: post.code.upcase - footer text: "Workshop.codes" + footer text: "Workshop.codes", icon_url: "https://workshop.codes/apple-touch-icon.png" end Discord::Notifier.message(embed) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 02b30ee70..14e5d53a2 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -102,7 +102,14 @@ def omniauth_params end def set_return_path - session[:return_to] = request.referrer + return if !request.referrer.present? + + referrer_domain = URI(request.referrer).host + current_domain = request.host + + if referrer_domain == current_domain + session[:return_to] = request.referrer + end end def redirect_to_path(path) diff --git a/app/controllers/wiki/articles_controller.rb b/app/controllers/wiki/articles_controller.rb index 499ac0c36..c87e4aae5 100644 --- a/app/controllers/wiki/articles_controller.rb +++ b/app/controllers/wiki/articles_controller.rb @@ -23,7 +23,21 @@ def show @article = Wiki::Article.where(slug: params[:slug]).last not_found and return unless @article - redirect_to_latest_article + + latest_article = Wiki::Article.where(group_id: @article.group_id).last + + # Redirect to the latest article within the same group if it's a HTML request + # Render the JSON for the latest article if it's a JSON request + if latest_article != @article + respond_to do |format| + format.html { redirect_to wiki_article_path(latest_article.slug) and return } + format.json { + latest_article.readonly! + latest_article.content = sanitized_markdown(latest_article.content) if params[:parse_markdown] + render json: latest_article.to_json(include: :category) and return + } + end + end @initial_article = Wiki::Article.where(group_id: @article.group_id).first @@ -132,14 +146,6 @@ def title_to_slug end end - def redirect_to_latest_article - @latest_article = Wiki::Article.where(group_id: @article.group_id).last - - if @latest_article != @article - redirect_to wiki_article_path(@latest_article.slug) - end - end - def create_wiki_edit(content_type, article_id, notes = "") Wiki::Edit.create( user_id: current_user.id, diff --git a/app/controllers/wiki/dictionary_controller.rb b/app/controllers/wiki/dictionary_controller.rb index 720b707a5..72e71e6b5 100644 --- a/app/controllers/wiki/dictionary_controller.rb +++ b/app/controllers/wiki/dictionary_controller.rb @@ -2,18 +2,18 @@ class Wiki::DictionaryController < Wiki::BaseController add_breadcrumb "Dictionary", :wiki_dictionary_path def index - @actions = YAML.load(File.read(Rails.root.join("config/arrays/wiki", "actions.yml"))) - @values = YAML.load(File.read(Rails.root.join("config/arrays/wiki", "values.yml"))) + @actions = YAML.safe_load(File.read(Rails.root.join("config/arrays/wiki", "actions.yml"))) + @values = YAML.safe_load(File.read(Rails.root.join("config/arrays/wiki", "values.yml"))) - merged_array = @actions.merge(@values) + merged_array = @actions + @values - @dictionary = merged_array.map { |a| a[1]["en-US"] } + dictionary = merged_array.map { |a| a["en-US"] }.compact respond_to do |format| - format.html { render "wiki/dictionary/index.html.erb" } # Automatic path doesn't work, possibly because of no plurals + format.html # Automatic path doesn't work, possibly because of no plurals format.json { set_request_headers - render json: @dictionary.to_json, layout: false + render json: dictionary.to_json, layout: false } end end diff --git a/app/controllers/wiki/edits_controller.rb b/app/controllers/wiki/edits_controller.rb index e0250ad4e..c9d51e04b 100644 --- a/app/controllers/wiki/edits_controller.rb +++ b/app/controllers/wiki/edits_controller.rb @@ -15,8 +15,11 @@ def show @edit = Wiki::Edit.find(params[:id]) @previous_article = Wiki::Article.where(group_id: @edit.article.group_id).where("id < ?", @edit.article.id).last + not_found if @previous_article.nil? + if @edit.content_type == "edited" @title_difference = Diffy::SplitDiff.new(@previous_article.title, @edit.article.title, format: :html, allow_empty_diff: false) + @slug_difference = Diffy::SplitDiff.new(@previous_article.slug, @edit.article.slug, format: :html, allow_empty_diff: false) @content_difference = Diffy::SplitDiff.new(@previous_article.content, @edit.article.content, format: :html, allow_empty_diff: false) @category_difference = Diffy::SplitDiff.new(@previous_article.category.title, @edit.article.category.title, format: :html, allow_empty_diff: false) @tags_difference = Diffy::SplitDiff.new(@previous_article.tags || "", @edit.article.tags || "", format: :html, allow_empty_diff: false) @@ -25,4 +28,10 @@ def show add_breadcrumb @edit.article.group_id, Proc.new { wiki_article_edits_path(@edit.article.group_id) } add_breadcrumb @edit.id, Proc.new { wiki_edit_path(@edit) } end + + private + + def not_found + raise ActionController::RoutingError.new("Not Found") + end end diff --git a/app/controllers/wiki/search_controller.rb b/app/controllers/wiki/search_controller.rb index fb34c14ba..1af0e6f2c 100644 --- a/app/controllers/wiki/search_controller.rb +++ b/app/controllers/wiki/search_controller.rb @@ -2,7 +2,7 @@ class Wiki::SearchController < Wiki::BaseController add_breadcrumb "Search", :wiki_articles_path def query - unless params[:query].empty? + if params[:query].present? respond_to do |format| format.js { redirect_to wiki_search_results_path(params[:query].gsub(".", "")) } format.html { redirect_to wiki_search_results_path(params[:query].gsub(".", "")) } diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 3be4e99e4..9a998c445 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -29,6 +29,10 @@ def is_wiki? controller_path.split('/').first == "wiki" ? true : false end + def is_editor? + controller_path.split('/').first == "editor" ? true : false + end + def is_admin_controller? controller_path.split('/').first == "admin" ? true : false end @@ -46,27 +50,27 @@ def to_range(string) end def maps - YAML.load(File.read(Rails.root.join("config/arrays", "maps.yml"))) + YAML.safe_load(File.read(Rails.root.join("config/arrays", "maps.yml"))) end def heroes - YAML.load(File.read(Rails.root.join("config/arrays", "heroes.yml"))) + YAML.safe_load(File.read(Rails.root.join("config/arrays", "heroes.yml"))) end def categories - YAML.load(File.read(Rails.root.join("config/arrays", "categories.yml"))) + YAML.safe_load(File.read(Rails.root.join("config/arrays", "categories.yml"))) end def quotes - YAML.load(File.read(Rails.root.join("config/arrays", "quotes.yml"))) + YAML.safe_load(File.read(Rails.root.join("config/arrays", "quotes.yml"))) end def badges - YAML.load(File.read(Rails.root.join("config/arrays", "badges.yml"))) + YAML.safe_load(File.read(Rails.root.join("config/arrays", "badges.yml"))) end def abilities - YAML.load(File.read(Rails.root.join("config/arrays", "abilities.yml"))) + YAML.safe_load(File.read(Rails.root.join("config/arrays", "abilities.yml"))) end def user_menu_items diff --git a/app/helpers/assets_helper.rb b/app/helpers/assets_helper.rb deleted file mode 100644 index 0da431587..000000000 --- a/app/helpers/assets_helper.rb +++ /dev/null @@ -1,19 +0,0 @@ -module AssetsHelper - def inline_file(path) - if assets = Rails.application.assets - asset = assets.find_asset(path) - return "" unless asset - asset.source - else - File.read(File.join(Rails.root, "public", asset_path(path))) - end - end - - def inline_js(path) - "".html_safe - end - - def inline_css(path) - "".html_safe - end -end diff --git a/app/helpers/content_helper.rb b/app/helpers/content_helper.rb index 22c38274e..607f659d5 100644 --- a/app/helpers/content_helper.rb +++ b/app/helpers/content_helper.rb @@ -7,7 +7,8 @@ def block_code(code, language) end def image(link, title, alt_text) - image_tag(link, title: title, alt: alt_text, loading: "lazy") + alt_text = "" if alt_text == "Text description" + image_tag(link, title: title, alt: alt_text || "", loading: "lazy") end # loosely based on https://github.com/vmg/redcarpet/blob/3e3f0b522fbe9283ba450334b5cec7a439dc0955/ext/redcarpet/html.c#L297 @@ -45,12 +46,12 @@ def header(title, level) hash = header_anchor_hash(title) if @options[:header_anchors] - "<#{ tag } id='#{ hash }'> + "<#{ tag } id='#{ hash }' aria-level='2'> #{ title } " else - "<#{ tag }>#{ title }" + "<#{ tag } aria-level='2'>#{ title }" end end end @@ -87,10 +88,7 @@ def markdown(text, rendererOptions: {}) def markdown_youtube(text) text.gsub /\[youtube\s+(.*?)\]/ do - video_id = $1 - "
- -
" + youtube_preview_tag($1, true) end end @@ -111,9 +109,9 @@ def markdown_video(text) end def markdown_gallery(text) - text.gsub /\[gallery\s+(.*?)\]/m do + text.gsub /\[gallery\s+([^\]]+)\]/m do begin - images = JSON.parse($1) + images = JSON.parse($1.strip) if action_name == "parse_markdown" render_to_string partial: "markdown_elements/gallery", locals: { images: images } @@ -127,17 +125,19 @@ def markdown_gallery(text) end def markdown_hero_icon(text) - text.gsub /\[hero\s+(.*?)\]/ do + text.gsub /\[hero\s+([\p{L}\p{N}_:.\-\s]+)\]/ do begin - ActionController::Base.helpers.image_tag(hero_name_to_icon_url($1), width: 50, height: 50, loading: "lazy") + hero_name = ERB::Util.html_escape($1.strip) + ActionController::Base.helpers.image_tag(hero_name_to_icon_url(hero_name), width: 50, height: 50, loading: "lazy", alt: $1) rescue; end end end def markdown_ability_icon(text) - text.gsub /\[ability\s+(.*?)\]/ do + text.gsub /\[ability\s+([\p{L}\p{N}_:.\(\)\-\s]+)\]/ do begin - ActionController::Base.helpers.image_tag(ability_name_to_icon_url($1), height: 50, loading: "lazy") + ability_name = ERB::Util.html_escape($1.strip) + ActionController::Base.helpers.image_tag(ability_name_to_icon_url(ability_name), height: 50, loading: "lazy", alt: $1) rescue; end end end @@ -173,11 +173,12 @@ def markdown_update_notes(text) title = data["title"] description = data["description"] abilities = data["abilities"] + icons = data["icons"] || {} if action_name == "parse_markdown" - render_to_string partial: "markdown_elements/update_notes", locals: { hero: hero, title: title, description: description, abilities: abilities } + render_to_string partial: "markdown_elements/update_notes", locals: { hero: hero, title: title, description: description, abilities: abilities, icons: icons } else - render partial: "markdown_elements/update_notes", locals: { hero: hero, title: title, description: description, abilities: abilities } + render partial: "markdown_elements/update_notes", locals: { hero: hero, title: title, description: description, abilities: abilities, icons: icons } end rescue => error "An error was found in the Hero Update markdown" @@ -198,8 +199,8 @@ def ability_name_to_icon_url(ability, size = 50) def sanitized_markdown(text, rendererOptions: {}) ActionController::Base.helpers.sanitize( markdown(text, rendererOptions: rendererOptions), - tags: %w(div span hr style mark dl dd dt img details summary a b iframe audio video source blockquote pre code br p table td tr th thead tbody ul ol li h1 h2 h3 h4 h5 h6 em i strong), - attributes: %w(style href id class src title width height frameborder allow allowfullscreen alt loading data-autoplay data-src data-action data-target data-tab data-hide-on-close data-toggle-content data-modal data-role data-url data-gallery controls playsinline loop muted) + tags: %w(div span hr style mark dl dd dt img details summary a button b iframe audio video source blockquote pre code br p table td tr th thead tbody ul ol li h1 h2 h3 h4 h5 h6 em i strong big), + attributes: %w(style href id class src srcset title width height frameborder allow allowfullscreen alt loading data-autoplay data-src data-action data-target data-tab data-hide-on-close data-toggle-content data-modal data-role data-url data-gallery data-id controls playsinline loop muted aria-level aria-labelledby aria-hidden aria-expanded tabindex role) ) end @@ -241,4 +242,19 @@ def ability_icons def hero_names heroes.map { |hero| hero["name"] }.sort end + + # This uses a string instead of Rails tags because those tags are not available when parsed as JSON + def youtube_preview_tag(video_id, lazy = false) + "
+
+
+ +
+
".gsub("\n","") # For some reason Markdown after this element is ignored when newlines are present + end end diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index bfb4e144e..2807728be 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -54,7 +54,8 @@ def is_active_tab?(tab) def tabs_content_tag(name, alt_url = nil, extra_class = "item__content shadow-block") tag.div class: "#{ extra_class } tabs-content #{ "tabs-content--active" if is_active_tab?(alt_url || name) }", - data: { tab: name, partial: name } do + data: { tab: name, partial: name }, + aria: { hidden: !is_active_tab?(alt_url || name) } do yield end end diff --git a/app/javascript/entrypoints/application.scss b/app/javascript/entrypoints/application.scss index 1c2fe59a7..62f0c452e 100644 --- a/app/javascript/entrypoints/application.scss +++ b/app/javascript/entrypoints/application.scss @@ -4,6 +4,5 @@ // Aliases don't work for glob imports @import "../scss/general/*"; @import "../scss/elements/*"; -@import "../scss/editor/*"; @import "../scss/utilities/*"; @import "../scss/wiki/*" diff --git a/app/javascript/entrypoints/application.js b/app/javascript/entrypoints/application.ts similarity index 91% rename from app/javascript/entrypoints/application.js rename to app/javascript/entrypoints/application.ts index fd84c8230..633f9d385 100644 --- a/app/javascript/entrypoints/application.js +++ b/app/javascript/entrypoints/application.ts @@ -17,6 +17,7 @@ import * as dismissParent from "@src/dismiss-parent" import * as dropdown from "@src/dropdown" import * as dynamicMaxHeight from "@src/dynamic-max-height" import * as filter from "@src/filter" +import * as focusOnLoad from "@src/focus-on-load" import * as gallery from "@src/gallery" import * as getKoFiValue from "@src/get-ko-fi-value" import * as getMoreComments from "@src/comments" @@ -24,7 +25,6 @@ import * as getPartial from "@src/get-partial" import * as getReportsForm from "@src/get-reports-form" import * as getSnippet from "@src/get-snippet" import * as getVerifiedUsers from "@src/get-verified-users" -import * as imagePreview from "@src/image-preview" import * as infiniteScroll from "@src/infinite-scroll" import * as lazyVideo from "@src/lazy-video" import * as microlight from "@src/microlight" @@ -32,15 +32,14 @@ import * as modal from "@src/modal" import * as navigation from "@src/navigation" import * as numPlayersSlider from "@src/num-players-slider" import * as ollieForm from "@src/ollie-form" -import * as revealByCheckbox from "@src/reveal-by-checkbox" import * as revealBySelect from "@src/reveal-by-select" -import * as revealOnDifference from "@src/reveal-on-difference" import * as scrollIndicator from "@src/scroll-indicator" import * as scrollIntoViewOnLoad from "@src/scroll-into-view-on-load" import * as sticky from "@src/sticky" import * as tabs from "@src/tabs" import * as timeago from "@src/timeago" import * as toggleContent from "@src/toggle-content" +import * as youtubePreview from "@src/youtube-preview" import * as wikiSearch from "@src/wiki/search" document.addEventListener("turbolinks:load", () => { @@ -56,19 +55,17 @@ document.addEventListener("turbolinks:load", () => { getReportsForm.bind() getSnippet.bind() getVerifiedUsers.bind() - imagePreview.bind() infiniteScroll.bind() lazyVideo.bind() modal.bind() navigation.bind() ollieForm.bind() - revealByCheckbox.bind() revealBySelect.bind() - revealOnDifference.bind() scrollIndicator.bind() sticky.bind() tabs.bind() toggleContent.bind() + youtubePreview.bind() wikiSearch.bind() microlight.reset() @@ -81,6 +78,7 @@ document.addEventListener("turbolinks:load", () => { timeago.initialize() scrollIntoViewOnLoad.initialize() + focusOnLoad.initialize() }) document.addEventListener("turbolinks:before-cache", () => { diff --git a/app/javascript/entrypoints/editor.scss b/app/javascript/entrypoints/editor.scss new file mode 100644 index 000000000..c05c6fbfa --- /dev/null +++ b/app/javascript/entrypoints/editor.scss @@ -0,0 +1,4 @@ +@import "@scss/variables", + "@scss/mixins"; + +@import "../scss/editor/*"; diff --git a/app/javascript/entrypoints/editor.js b/app/javascript/entrypoints/editor.ts similarity index 100% rename from app/javascript/entrypoints/editor.js rename to app/javascript/entrypoints/editor.ts diff --git a/app/javascript/entrypoints/logged-in-user.js b/app/javascript/entrypoints/logged-in-user.ts similarity index 80% rename from app/javascript/entrypoints/logged-in-user.js rename to app/javascript/entrypoints/logged-in-user.ts index 9201570d7..e9be5e16a 100644 --- a/app/javascript/entrypoints/logged-in-user.js +++ b/app/javascript/entrypoints/logged-in-user.ts @@ -22,9 +22,13 @@ import * as favorite from "@src/favorite" import * as getPostAnalytics from "@src/get-post-analytics" import * as getUserAnalytics from "@src/get-user-analytics" import * as ide from "@src/ide" +import * as imagePreview from "@src/image-preview" import * as inscrybMde from "@src/inscryb-mde" import * as linkedInput from "@src/linked-input" +import * as navigateOnChange from "@src/navigate-on-change" import * as setCssVariable from "@src/set-css-variable" +import * as revealByCheckbox from "@src/reveal-by-checkbox" +import * as revealOnDifference from "@src/reveal-on-difference" document.addEventListener("turbolinks:load", () => { const svelteComponents = { @@ -52,6 +56,10 @@ document.addEventListener("turbolinks:load", () => { getUserAnalytics.bind() setCssVariable.bind() linkedInput.bind() + imagePreview.bind() + navigateOnChange.bind() + revealByCheckbox.bind() + revealOnDifference.bind() chart.render() inscrybMde.render() @@ -60,11 +68,11 @@ document.addEventListener("turbolinks:load", () => { document.addEventListener("turbolinks:before-cache", () => { inscrybMde.destroy() - const svelteComponents = document.querySelectorAll("[data-svelte-component]") - svelteComponents.forEach(element => element.dataset.initialized = null) + const svelteComponents = Array.from(document.querySelectorAll("[data-svelte-component]")) as HTMLElement[] + svelteComponents.forEach(element => element.dataset.initialized = "null") }) document.addEventListener("turbolinks:click", () => { - const svelteComponents = document.querySelectorAll("[data-svelte-component]") - svelteComponents.forEach(element => element.dataset.initialized = null) + const svelteComponents = Array.from(document.querySelectorAll("[data-svelte-component]")) as HTMLElement[] + svelteComponents.forEach(element => element.dataset.initialized = "null") }) diff --git a/app/javascript/fonts/barlow-condensed-cleaned.woff2 b/app/javascript/fonts/barlow-condensed-cleaned.woff2 index c9919f16c..048103c64 100644 Binary files a/app/javascript/fonts/barlow-condensed-cleaned.woff2 and b/app/javascript/fonts/barlow-condensed-cleaned.woff2 differ diff --git a/app/javascript/images/layout/icon-link.svg b/app/javascript/images/icons/icon-link.svg similarity index 100% rename from app/javascript/images/layout/icon-link.svg rename to app/javascript/images/icons/icon-link.svg diff --git a/app/javascript/images/icons/icon-youtube.svg b/app/javascript/images/icons/icon-youtube.svg new file mode 100644 index 000000000..7c48c4998 --- /dev/null +++ b/app/javascript/images/icons/icon-youtube.svg @@ -0,0 +1 @@ + diff --git a/app/javascript/scss/_mixins.scss b/app/javascript/scss/_mixins.scss index d6079bbe5..825ee887c 100644 --- a/app/javascript/scss/_mixins.scss +++ b/app/javascript/scss/_mixins.scss @@ -60,7 +60,7 @@ @mixin inline-input() { -webkit-appearance: none; - appearance: none; + appearance: textfield; flex: 1 1 auto; width: 100%; border: 0; @@ -68,10 +68,6 @@ font-family: $font-stack; font-size: $font-size-base; color: $text-color-lightest; - - &:focus { - outline: none; - } } @mixin motion() { @@ -99,3 +95,27 @@ } } } + +@mixin safari() { + @media not all and (min-resolution: 0.001dpcm) { + @supports (-webkit-appearance: none) { + @content; + } + } + + @supports (hanging-punctuation: first) and (font: -apple-system-body) and (-webkit-appearance: none) { + @content; + } +} + +@mixin high-contrast() { + @media (prefers-contrast: more) { + @content; + } +} + +@mixin light() { + @media (prefers-color-scheme: light) { + @content; + } +} diff --git a/app/javascript/scss/_variables.scss b/app/javascript/scss/_variables.scss index 486531f16..e244876bd 100644 --- a/app/javascript/scss/_variables.scss +++ b/app/javascript/scss/_variables.scss @@ -11,7 +11,7 @@ $media-min-widths: ( $white: #fff; $black: #000; -$gray: #90989c; +$gray: #939b9f; $gray-dark: #6a7277; $red: #b33834; $orange: #e89930; @@ -25,15 +25,15 @@ $border-color: #353b3f; $primary: #f06414; $secondary: lighten($body-bg, 5%); $discord: #5865f2; -$bnet: #148eff; +$bnet: #0074e0; $overwatch-2: #ed6516; $wiki: #00abff; $logo-cog: #565a5d; -$text-color: $gray; -$text-color-light: lighten($gray, 10%); -$text-color-lightest: #dfdfdf; -$text-color-dark: $gray-dark; +$text-color: lighten($gray, 5%); +$text-color-light: lighten($text-color, 10%); +$text-color-lightest: lighten($text-color, 25%); +$text-color-dark: darken($text-color, 11%); $font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; $font-stack-alt: "Barlow Condensed", "Impact", monospace; @@ -67,9 +67,9 @@ $shadow: 0 2px 1px rgba(0, 0, 0, .1), --red: #{ $red }; --overwatch-2: #{ $overwatch-2 }; --wiki: #{ $wiki }; - --text-color: #{ $gray }; + --text-color: #{ $text-color }; --text-color-light: #{ $text-color-light }; - --text-color-dark: #{ $gray-dark }; + --text-color-dark: #{ $text-color-dark }; --bg-body: #{ $body-bg }; --bg-dark: #{ $bg-dark }; --bg-darker: #{ $bg-darker }; diff --git a/app/javascript/scss/editor/_codemirror-6.scss b/app/javascript/scss/editor/_codemirror-6.scss index 64d4a9c0b..0ae498678 100644 --- a/app/javascript/scss/editor/_codemirror-6.scss +++ b/app/javascript/scss/editor/_codemirror-6.scss @@ -24,7 +24,7 @@ .cm-gutters { border-right: 0; - background: var(--editor-background, #{$bg-dark}); + background: transparent; color: $text-color-dark; .cm-activeLineGutter { @@ -42,7 +42,7 @@ } .cm-selectionBackground { - background: lighten($bg-dark, 10%); + background: lighten($bg-dark, 10%) !important; } .cm-selectionMatch { @@ -72,26 +72,58 @@ } .cm-completionIcon { - padding-right: 1rem; - } - .cm-completionIcon-event::after { - content: "∈"; + padding-right: 2.15rem; + font-size: 0.75em; + + $types: ( + value: "Value", + action: "Action", + event: "Event", + constant: "Const", + hero: "Hero", + map: "Map", + variable: "Other", + keyword: "Key", + rule: "Rule", + snippet: "Snippet" + ); + + @each $name, $type in $types { + &-#{$name}::after { + content: "#{$type}"; + } + } } + .cm-tooltip { - background: darken($bg-dark, 5%); + border-radius: $border-radius * 0.5; border: 0; - box-shadow: $shadow; + background: darken($bg-dark, 5%); &.cm-tooltip-hover { - border-radius: $border-radius; - padding: math.div($margin, 16) $margin * 0.125; - box-shadow: none; + max-width: 25em; + max-height: 15em; + overflow-y: auto; + border-radius: $border-radius * 0.5; + border: 1px solid $bg-darker; + padding: $margin * 0.125; color: $text-color; font-size: 0.75rem; - pointer-events: none; + + @include styled-scrollbar; + } + + &.cm-completionInfo { + margin: -2px 0 0 0.5em; + border-radius: $border-radius * 0.5; + border: 1px solid $bg-darker; + box-shadow: $shadow; + background: $bg-dark !important; } > ul { + padding: $margin * 0.125; + @include styled-scrollbar; } @@ -101,6 +133,30 @@ color: $text-color-light; white-space: pre-wrap; } + + .cm-diagnostic { + background: $bg-darker; + border-radius: 0 $border-radius * 0.5 $border-radius * 0.5 0; + color: $text-color-lightest; + font-weight: bold; + font-size: 12px; + } + + hr { + margin: $margin * 0.125 0; + background-color: $bg-darker + } + + em { + color: var(--color-custom-keyword); + font-weight: bold; + font-style: normal; + } + } + + .cm-tooltip-section:not(:first-child) { + border: 0; + margin-top: $margin * 0.125; } .cm-tooltip-arrow { @@ -112,6 +168,16 @@ font-size: 0.85em; } + .cm-tooltip-autocomplete { + border-radius: 0; + border: 1px solid $bg-darker; + box-shadow: $shadow; + } + + .cm-tooltip-autocomplete ul li { + padding: 0.25rem !important; + } + .cm-tooltip-autocomplete ul li[aria-selected] { background: lighten($bg-dark, 5%); } @@ -126,18 +192,33 @@ background: transparent; } - .cm-lint-marker-error::before { - content: "!"; - display: block; - width: 1em; - height: 1em; - padding: 0.1em; - border-radius: 50%; - background: $red; - text-align: center; - color: white; - font-weight: bold; - line-height: 1em; + .cm-lint-marker-warning { + content: ""; + + &::before { + content: "!"; + display: block; + width: 1em; + height: 1em; + padding: 0.1em; + clip-path: polygon(50% 0%, 0% 100%, 100% 100%); + background: $orange; + text-align: center; + color: black; + font-weight: bold; + line-height: 1.25; + } + } + + .cm-lint-marker-error { + @extend .cm-lint-marker-warning; + + &::before { + border-radius: 50%; + background: $red; + line-height: 1; + clip-path: none; + } } .cm-panel.cm-search { @@ -154,6 +235,11 @@ box-shadow: $shadow; animation: fly-in 200ms; + @include safari() { + backdrop-filter: none; + background: rgba(darken($bg-dark, 10%), 0.75); + } + input { width: auto; padding: .25em .5em; @@ -247,6 +333,47 @@ } } + .cm-trailingSpace { + position: relative; + background: transparent; + + &:hover { + &::after { + display: block; + } + } + + &::before { + content: ""; + display: block; + position: absolute; + top: 50%; + left: 2px; + height: 1px; + width: calc(100% - 4px); + background: repeating-linear-gradient(90deg, var(--color-comment), var(--color-comment) 0.25rem, transparent 0.25rem,transparent 0.5rem); + opacity: 0.5; + } + + &::after { + display: none; + content: "Trailing whitespace"; + position: absolute; + top: 0; + left: 0; + transform: translateY(-100%); + padding: 0.25rem 0.5rem; + border-radius: 0 $border-radius * 0.5 $border-radius * 0.5 0; + border-left: 3px solid $text-color; + background: darken($bg-dark, 5%); + white-space: nowrap; + line-height: 1; + text-indent: 0; + font-family: $font-stack; + color: $text-color; + } + } + .code-actions-lightbulb { position: absolute; aspect-ratio: 1; @@ -270,29 +397,36 @@ opacity: 0; } + // From import rainbowBrackets from 'rainbowbrackets' + // Skipping red, despite the classname, red is too error-like. + .rainbow-bracket-red span { color: forestgreen } + .rainbow-bracket-orange span { color: orange } + .rainbow-bracket-yellow span { color: yellow } + .rainbow-bracket-green span { color: turquoise } + .rainbow-bracket-blue span { color: dodgerblue } + .rainbow-bracket-indigo span { color: hotpink } + .rainbow-bracket-violet span { color: plum } } .indented-wrapped-line { - --text-indent-multiplier: -1.5; - margin-left: calc(var(--indented)); - text-indent: calc(var(--text-indent-multiplier) * var(--indented)); - - .is-firefox & { + @supports (-moz-appearance: none) { --text-indent-multiplier: -1; - } + margin-left: var(--indented); + text-indent: calc(var(--text-indent-multiplier) * var(--indented)); - &.cm-activeLine { - box-shadow: calc((-1 * var(--indented)) + 4px) 0 0 rgba($white, 0.025) - } + &.cm-activeLine { + box-shadow: calc((-1 * var(--indented)) + 4px) 0 0 rgba($white, 0.025) + } - &.cm-indent-markers { - &::before { - left: calc(var(--indented) * -1); + &.cm-indent-markers { + &::before { + left: calc(var(--indented) * -1 + 2px); + } } - } - button, - .button { - text-indent: 0; + button, + .button { + text-indent: 0; + } } } diff --git a/app/javascript/scss/editor/_editor-folder.scss b/app/javascript/scss/editor/_editor-folder.scss index 513914362..c652ad36a 100644 --- a/app/javascript/scss/editor/_editor-folder.scss +++ b/app/javascript/scss/editor/_editor-folder.scss @@ -3,6 +3,12 @@ .editor-folder.editor-folder { position: relative; + &.editor-folder--active { + > span > input { + color: $text-color-lightest; + } + } + > span > input { color: $text-color-dark; pointer-events: all; @@ -14,7 +20,7 @@ align-items: center; justify-content: center; position: absolute; - top: 0.1rem; + top: 0.25rem; left: 0; height: 1.5rem; width: 1.5rem; @@ -33,6 +39,17 @@ } } +.editor-folder__name { + display: flex !important; + align-items: center; + + svg { + width: 1.1rem; + height: 1.1rem; + margin: 0 0.125rem; + } +} + .editor-folder__content { display: none; padding-left: math.div($margin, 3); diff --git a/app/javascript/scss/editor/_editor-item.scss b/app/javascript/scss/editor/_editor-item.scss index 6452e64c2..7a36ff86e 100644 --- a/app/javascript/scss/editor/_editor-item.scss +++ b/app/javascript/scss/editor/_editor-item.scss @@ -35,8 +35,8 @@ white-space: nowrap; &:hover { - background: darken($bg-dark, 7.5%); - box-shadow: -100px 0 0 darken($bg-dark, 7.5%); + background: darken($bg-dark, 5%); + box-shadow: -100px 0 0 darken($bg-dark, 5%); input { color: $text-color-lightest; @@ -51,12 +51,12 @@ &--active { &:not(.editor-folder)::before { - color: $text-color-light; + color: var(--primary, $primary); } > span { - background: darken($bg-dark, 10%); - box-shadow: -100px 0 0 darken($bg-dark, 10%); + background: darken($bg-dark, 5%); + box-shadow: -100px 0 0 darken($bg-dark, 5%); input { color: $text-color-lightest; @@ -82,8 +82,10 @@ gap: 0.1rem; position: absolute; right: 0; - top: 0.25rem; - background: darken($bg-dark, 7.5%); + top: 0; + padding: 0.375rem; + border-radius: $border-radius * 0.5 0 0 $border-radius * 0.5; + background: darken($bg-darker, 2.5%); z-index: 1; .editor-item:hover > & { @@ -95,6 +97,10 @@ &:hover { filter: brightness(1.25); + + svg { + fill: $text-color-light; + } } &.inactive { @@ -105,7 +111,7 @@ display: block; height: 1rem; width: 1rem; - fill: currentColor; + fill: $text-color; } } } diff --git a/app/javascript/scss/editor/_editor-settings.scss b/app/javascript/scss/editor/_editor-settings.scss index 1a74eeed9..9b23902f4 100644 --- a/app/javascript/scss/editor/_editor-settings.scss +++ b/app/javascript/scss/editor/_editor-settings.scss @@ -16,4 +16,13 @@ fill: $text-color-lightest; } } + + hr { + margin: $margin * 0.125 $margin * -0.25; + background: $bg-darker; + + &.large { + margin: $margin * 0.25 $margin * -0.25; + } + } } diff --git a/app/javascript/scss/editor/_editor.scss b/app/javascript/scss/editor/_editor.scss index 69f62aee3..d627ff69e 100644 --- a/app/javascript/scss/editor/_editor.scss +++ b/app/javascript/scss/editor/_editor.scss @@ -1,6 +1,7 @@ $sidebar-width: clamp(var(--clamp-min), var(--sidebar-width, 300px), 80vmax); // width = height on mobile $popout-width: clamp(var(--popout-clamp-min, var(--clamp-min)), var(--popout-width, 300px), 80vmax); // width = height on mobile $top-height: 4rem; +$separator-color: darken($bg-darker, 5%); .editor { --clamp-min: 10vmax; @@ -12,7 +13,7 @@ $top-height: 4rem; left: 0; height: 100%; width: 100%; - background: var(--editor-background, #{$bg-dark}); + background: var(--editor-background, #{darken($bg-dark, 2.5%)}); z-index: 500; @include media-min(mbl) { @@ -34,20 +35,30 @@ $top-height: 4rem; .editor__scrollable { height: 100%; max-width: 100%; - padding: $margin * 0.25 0; + padding: $margin * 0.25 0 0; overflow: auto; @include styled-scrollbar; } +.editor__add-item { + position: sticky; + bottom: 0; + padding: 1.5rem 0.5rem 0.5rem; + z-index: 5; + background: linear-gradient(180deg, + transparent 0, + darken($bg-dark, 10%) 1.5rem); +} + .editor__top { grid-area: top; position: relative; display: flex; align-items: center; padding: $margin * 0.25; - background: lighten($bg-dark, 5%); box-shadow: $shadow; + background: lighten($bg-dark, 5%); z-index: 10; &::after { @@ -93,20 +104,36 @@ $top-height: 4rem; .editor__aside { grid-area: aside; position: relative; - background: darken($bg-dark, 5%); + background: darken($bg-dark, 10%); overflow: hidden; + + @include media-min(mbl) { + border-right: 1px solid $separator-color; + } + + .form-input { + background: darken($bg-dark, 15%); + } + + hr { + background: $separator-color; + } } .editor__popout { grid-area: popout; position: relative; padding: $margin * 0.25; - border-top: 1px solid lighten($border-color, 5%); - background: $bg-dark; + border-top: 1px solid $separator-color; + background: darken($bg-dark, 2.5%); @include media-min(mbl) { border-top: 0; - border-left: 1px solid lighten($border-color, 5%); + border-left: 1px solid $separator-color; + } + + .header-anchor { + visibility: hidden; } } @@ -155,7 +182,6 @@ $top-height: 4rem; .editor-hidden-item-indicator__tooltip { $tooltip-bg-color: $bg-darker; $tooltip-arrow-size: 0.5rem; - position: absolute; top: calc(100% + #{$tooltip-arrow-size}); right: 0; diff --git a/app/javascript/scss/editor/_translation-settings.scss b/app/javascript/scss/editor/_translation-settings.scss index 41f890d1e..707bbe5ba 100644 --- a/app/javascript/scss/editor/_translation-settings.scss +++ b/app/javascript/scss/editor/_translation-settings.scss @@ -1,13 +1,24 @@ .translation-settings { display: grid; grid-template-columns: 1fr 3fr; - gap: $margin * 0.5; } .translation-settings__aside { display: flex; flex-direction: column; gap: $margin * 0.125; + padding: $margin * 0.25; + border-top: 2px solid darken($bg-dark, 5%); + border-bottom-left-radius: $border-radius; + background: $bg-darker; +} + +.translation-settings__content { + padding: $margin * 0.25; + + .form-textarea { + width: calc(100% - $margin * 0.25); + } } .translation-settings__item { @@ -33,6 +44,12 @@ } } +.translation-settings__create { + padding: $margin * 0.25; + margin: $margin * 0.125 $margin * -0.25 $margin * -0.25; + border-top: 2px solid darken($bg-dark, 5%); +} + .translation-settings__copy { margin-left: auto; text-transform: lowercase; diff --git a/app/javascript/scss/elements/_background-blur.scss b/app/javascript/scss/elements/_background-blur.scss index ebfcb2bb4..743bef39d 100644 --- a/app/javascript/scss/elements/_background-blur.scss +++ b/app/javascript/scss/elements/_background-blur.scss @@ -8,10 +8,17 @@ aspect-ratio: 16/9; filter: blur(50px); transform: scale(1.1); - transition: opacity 1000ms; opacity: 0; z-index: -1; + @include media-min(sm) { + transition: opacity 1000ms; + } + + @include safari() { + transform: scale(1.1) translate3d(0, 0, 0); + } + &--visible { opacity: .25; } diff --git a/app/javascript/scss/elements/_backgrounds.scss b/app/javascript/scss/elements/_backgrounds.scss index ab85c478d..fd95656e5 100644 --- a/app/javascript/scss/elements/_backgrounds.scss +++ b/app/javascript/scss/elements/_backgrounds.scss @@ -5,7 +5,7 @@ left: 0; height: 160px; width: 100%; - background: lighten($body-bg, 30%); + background: lighten($body-bg, 65%); background-size: cover; clip-path: polygon(0 0, 100% 0, 100% 90px, 0% 100%); z-index: -5; @@ -15,6 +15,10 @@ clip-path: polygon(0 0, 100% 0, 100% 120px, 0% 100%); } + @include high-contrast() { + display: none; + } + &--medium { height: 380px; clip-path: polygon(0 0, 100% 0, 100% 230px, 0% 100%); @@ -80,7 +84,7 @@ height: 120vw; max-height: 800px; max-width: 800px; - background: url("/images/layout/elo-hell-logo.svg"); + background-image: url("/images/layout/overwatch-logo.svg"); background-size: 120vw; background-position: center; transform: translateX(-50%) translateY(-50%); @@ -90,11 +94,9 @@ @media (min-width: 800px) { background-size: contain; } - } - &--overwatch { - &::after { - background-image: url("/images/layout/overwatch-logo.svg"); + @include high-contrast() { + display: none; } } } diff --git a/app/javascript/scss/elements/_buttons.scss b/app/javascript/scss/elements/_buttons.scss index fade0c8bd..926059205 100644 --- a/app/javascript/scss/elements/_buttons.scss +++ b/app/javascript/scss/elements/_buttons.scss @@ -24,7 +24,7 @@ button { border-radius: 0.25em; transform: translateY(100%); font-size: $font-size-small; - color: $text-color-dark; + color: $text-color; white-space: nowrap; z-index: 2; } @@ -33,12 +33,13 @@ button { .button { display: inline-block; - padding: .55em 1.25em; + padding: .5em 1.25em; border: 0; border-radius: 99px; background: var(--primary, $primary); color: $white; - font-size: 18px; + font-size: 19px; + font-weight: bold; font-family: $font-stack-alt; text-transform: uppercase; text-decoration: none; @@ -46,6 +47,10 @@ button { cursor: pointer; transition: box-shadow 100ms; + @include high-contrast() { + border: 2px solid currentColor; + } + &:hover:not([disabled]), &:active:not([disabled]), &:focus-visible:not([disabled]) { @@ -131,7 +136,6 @@ button { box-shadow: none; font-size: $font-size-small; line-height: 1.5rem; - font-weight: medium; } &--bnet { diff --git a/app/javascript/scss/elements/_card.scss b/app/javascript/scss/elements/_card.scss index c1a5a7680..cb3bb5209 100644 --- a/app/javascript/scss/elements/_card.scss +++ b/app/javascript/scss/elements/_card.scss @@ -99,6 +99,10 @@ overflow: hidden; box-shadow: $shadow; + @include high-contrast() { + border: 2px solid currentColor; + } + &[href]:hover, &[type]:hover { background: darken($bg-dark, 2%); @@ -141,14 +145,18 @@ .card__author { display: inline-block; margin-top: $margin * 0.125; - color: var(--text-color-dark); + color: $text-color-dark; a { - color: var(--text-color-dark); + color: $text-color; text-decoration: none; @include hover-stack { color: $text-color-lightest; + + @include high-contrast() { + text-decoration: underline; + } } } } @@ -197,7 +205,15 @@ mask-image: linear-gradient(170deg, rgba(0, 0, 0, 1) 50%, rgba(0, 0, 0, 0) 300px); } + @include safari() { + transform: translate3d(0, 0, 0); + } + @media (prefers-reduced-motion) { display: none; } + + @include high-contrast() { + display: none; + } } diff --git a/app/javascript/scss/elements/_carousel.scss b/app/javascript/scss/elements/_carousel.scss index 392db32b0..4f94f9d36 100644 --- a/app/javascript/scss/elements/_carousel.scss +++ b/app/javascript/scss/elements/_carousel.scss @@ -4,7 +4,7 @@ max-width: 900px; [data-role="carousel"]:not([style]) { - height: 500px; + aspect-ratio: 900/500; overflow: hidden; } } @@ -20,7 +20,7 @@ height: 100%; background: $bg-darker; text-indent: 1000 * 1000px; - aspect-ratio: 16/9; + aspect-ratio: 900/500; } } @@ -60,6 +60,7 @@ border-radius: $border-radius; background: $bg-darker; box-shadow: $shadow; + aspect-ratio: 1/1; @include hover-stack { opacity: 0.75; @@ -81,29 +82,20 @@ img { width: 100%; - height: auto; - aspect-ratio: 1/1; + height: 100%; + object-fit: cover; } } .carousel__navigation-item-video-overlay { - background: rgba($black, .5); - - &::after { - content: "▶"; - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - position: absolute; - color: $text-color-lightest; - font-size: 24px; - background: rgba($black, .5); - } + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba($black, .5) url("/images/icons/icon-youtube.svg") no-repeat center; + background-size: 2.5rem; + filter: saturate(0); } .card-carousel { diff --git a/app/javascript/scss/elements/_code.scss b/app/javascript/scss/elements/_code.scss index fe8be1f47..c2e68c293 100644 --- a/app/javascript/scss/elements/_code.scss +++ b/app/javascript/scss/elements/_code.scss @@ -23,9 +23,18 @@ overflow: hidden; @include hover-stack { + box-shadow: inset 0 0 0 2px $white; filter: brightness(1.15); } + @include high-contrast() { + border: 2px solid currentColor; + } + + &:focus-visible { + box-shadow: none; + } + .item--show & { background: darken($bg-dark, 5%); } diff --git a/app/javascript/scss/elements/_collection.scss b/app/javascript/scss/elements/_collection.scss index e87bc37d2..c892aeb3e 100644 --- a/app/javascript/scss/elements/_collection.scss +++ b/app/javascript/scss/elements/_collection.scss @@ -60,7 +60,7 @@ } .collection__title { - display: block; + display: inline-block; margin-bottom: math.div($margin, 16); color: $text-color-lightest; font-size: 18px; @@ -93,8 +93,8 @@ .collection__posts { display: flex; gap: $margin * 0.125; - margin: 0 $margin * 0.25 * -1; - padding: 0 $margin * 0.25; + margin: -0.5rem $margin * 0.25 * -1; + padding: 0.5rem $margin * 0.25; overflow: auto; scrollbar-width: none; filter: drop-shadow(0 4px 2px rgba(0, 0, 0, .1)) drop-shadow(0 8px 4px rgba(0, 0, 0, 0.1)) drop-shadow(0 32px 16px rgba(0, 0, 0, 0.1)); diff --git a/app/javascript/scss/elements/_derivatives.scss b/app/javascript/scss/elements/_derivatives.scss index bfd4ed8cc..85b7885c6 100644 --- a/app/javascript/scss/elements/_derivatives.scss +++ b/app/javascript/scss/elements/_derivatives.scss @@ -11,9 +11,13 @@ border-radius: $border-radius * 0.5; background: darken($bg-dark, 5%); color: $text-color-lightest; - text-decoration: none; + text-decoration: none !important; overflow: hidden; + @include high-contrast() { + padding: 0; + } + &[href]:hover, &[type]:hover { filter: brightness(1.15); diff --git a/app/javascript/scss/elements/_dropdown.scss b/app/javascript/scss/elements/_dropdown.scss index 81c45746b..867c46b7f 100644 --- a/app/javascript/scss/elements/_dropdown.scss +++ b/app/javascript/scss/elements/_dropdown.scss @@ -42,6 +42,10 @@ @include styled-scrollbar(); + @include high-contrast() { + border: 2px solid currentColor; + } + &--left { left: 0; right: auto; diff --git a/app/javascript/scss/elements/_faq.scss b/app/javascript/scss/elements/_faq.scss index af46b9030..596442d0b 100644 --- a/app/javascript/scss/elements/_faq.scss +++ b/app/javascript/scss/elements/_faq.scss @@ -3,6 +3,10 @@ border-radius: $border-radius; background: $bg-darker; + @include high-contrast() { + border: 2px solid currentColor; + } + *:last-child { margin-bottom: 0; } @@ -10,9 +14,12 @@ .faq__question { display: flex; + width: 100%; align-items: center; justify-content: space-between; padding: 1rem; + border-radius: $border-radius; + color: $text-color; cursor: pointer; @include hover-stack { diff --git a/app/javascript/scss/elements/_filter.scss b/app/javascript/scss/elements/_filter.scss index 8a346c3d5..41a07b528 100644 --- a/app/javascript/scss/elements/_filter.scss +++ b/app/javascript/scss/elements/_filter.scss @@ -38,6 +38,10 @@ transition: box-shadow var(--animation-duration); animation: fade-in-filter-content var(--animation-duration, 0ms) forwards; + @include high-contrast() { + border: 2px solid currentColor; + } + &::after { content: ""; display: block; diff --git a/app/javascript/scss/elements/_footer.scss b/app/javascript/scss/elements/_footer.scss index 6c517511a..320bbe1ce 100644 --- a/app/javascript/scss/elements/_footer.scss +++ b/app/javascript/scss/elements/_footer.scss @@ -11,10 +11,6 @@ justify-content: space-between; } - a { - color: $text-color; - } - .large-fonts & { font-size: 14px; } diff --git a/app/javascript/scss/elements/_gallery.scss b/app/javascript/scss/elements/_gallery.scss index b93c7a048..000e45d72 100644 --- a/app/javascript/scss/elements/_gallery.scss +++ b/app/javascript/scss/elements/_gallery.scss @@ -13,6 +13,7 @@ } .gallery__item { + appearance: none; position: relative; display: flex; flex-direction: column; @@ -20,6 +21,11 @@ border-radius: $border-radius; overflow: hidden; cursor: pointer; + text-align: center; + + @include high-contrast() { + border: 2px solid currentColor; + } &:hover { &::before { diff --git a/app/javascript/scss/elements/_header.scss b/app/javascript/scss/elements/_header.scss index 7baa59017..5009eb831 100644 --- a/app/javascript/scss/elements/_header.scss +++ b/app/javascript/scss/elements/_header.scss @@ -17,6 +17,14 @@ @include media-min(xl) { padding: 0 math.div($margin, 3) 0 0; } + + @include high-contrast() { + --cog-color: #{ $white }; + + @include light() { + --cog-color: #{ $black }; + } + } } @keyframes fade-in-header-content { diff --git a/app/javascript/scss/elements/_ide.scss b/app/javascript/scss/elements/_ide.scss index dd33b25ce..0c5d15657 100644 --- a/app/javascript/scss/elements/_ide.scss +++ b/app/javascript/scss/elements/_ide.scss @@ -25,13 +25,13 @@ -moz-tab-size: 2; -webkit-text-size-adjust: 100%; + @include styled-scrollbar; + @include media-min("lg") { font-size: $font-size-small; line-height: 2em; } - @include styled-scrollbar(); - .ide--fullscreen & { max-height: 100%; border-radius: 0; diff --git a/app/javascript/scss/elements/_items.scss b/app/javascript/scss/elements/_items.scss index 6029c68ab..60ec9299e 100644 --- a/app/javascript/scss/elements/_items.scss +++ b/app/javascript/scss/elements/_items.scss @@ -116,26 +116,12 @@ background: $bg-dark; box-shadow: $shadow; - @include media-min(sm) { - display: block; + @include hover-stack { + filter: brightness(1.15); } - &::before { - content: ""; + @include media-min(sm) { display: block; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-image: url("/images/snowball-face.png"); - background-repeat: no-repeat; - background-position: center; - background-size: 40%; - } - - @include hover-stack { - filter: brightness(1.15); } .item--large & { @@ -214,6 +200,10 @@ @include hover-stack { color: $white; + + @include high-contrast() { + text-decoration: underline; + } } } @@ -275,11 +265,15 @@ } a { - color: $text-color; + color: $text-color-light; text-decoration: none; @include hover-stack { color: $text-color-lightest; + + @include high-contrast() { + text-decoration: underline; + } } } @@ -442,17 +436,26 @@ } } + a { + color: currentColor; + text-decoration: underline; + + &:hover { + color: $white; + } + } + img { max-width: 100%; height: auto; } - h1, - h2 { + h1 { margin: $margin 0 $margin * 0.5; line-height: 1.1em; } + h2, h3, h4, h5, @@ -542,11 +545,12 @@ } pre { - @include styled-scrollbar; width: 100%; overflow-x: auto; tab-size: 2; + @include styled-scrollbar; + code { white-space: pre; } diff --git a/app/javascript/scss/elements/_modal.scss b/app/javascript/scss/elements/_modal.scss index f9e7465ec..4008a3948 100644 --- a/app/javascript/scss/elements/_modal.scss +++ b/app/javascript/scss/elements/_modal.scss @@ -51,6 +51,7 @@ width: 90%; max-width: map-get($media-min-widths, xs); padding: $margin * 0.5; + border-radius: $border-radius; background: $body-bg; &::before { @@ -73,6 +74,13 @@ } } + &--no-shadow { + &::before { + display: none; + box-shadow: none; + } + } + .modal--auto & { width: auto; max-width: 90%; diff --git a/app/javascript/scss/elements/_navigation.scss b/app/javascript/scss/elements/_navigation.scss index 7267da3ee..6452a1b3c 100644 --- a/app/javascript/scss/elements/_navigation.scss +++ b/app/javascript/scss/elements/_navigation.scss @@ -65,6 +65,10 @@ &:hover::before { background: var(--primary, $primary); } + + @include high-contrast() { + border-bottom: 2px solid currentColor; + } } .wiki & { diff --git a/app/javascript/scss/elements/_noui-slider.scss b/app/javascript/scss/elements/_noui-slider.scss index becb50f2b..b05b90f67 100644 --- a/app/javascript/scss/elements/_noui-slider.scss +++ b/app/javascript/scss/elements/_noui-slider.scss @@ -89,7 +89,8 @@ /* Slider size and handle placement; */ -.noUi-horizontal { +.noUi-horizontal, +.noUi-placeholder { height: 0.25rem; margin-bottom: 1.75rem; } diff --git a/app/javascript/scss/elements/_ollie.scss b/app/javascript/scss/elements/_ollie.scss index a7996df8b..51b8558e7 100644 --- a/app/javascript/scss/elements/_ollie.scss +++ b/app/javascript/scss/elements/_ollie.scss @@ -70,6 +70,10 @@ $ollie-login-size: 100px; border-radius: 50%; background: lighten($bg-dark, 5%); overflow: hidden; + + @include high-contrast() { + border: 2px solid currentColor; + } } .ollie-login__images { diff --git a/app/javascript/scss/elements/_profile.scss b/app/javascript/scss/elements/_profile.scss index 4ca9e54f4..19d4eba2c 100644 --- a/app/javascript/scss/elements/_profile.scss +++ b/app/javascript/scss/elements/_profile.scss @@ -203,6 +203,10 @@ z-index: 0; overflow: hidden; + @include safari() { + transform: scale(1.1) translate3d(0, 0, 0); + } + img { display: block; width: 100%; diff --git a/app/javascript/scss/elements/_quick-filter.scss b/app/javascript/scss/elements/_quick-filter.scss index d5d706e3c..0b799f103 100644 --- a/app/javascript/scss/elements/_quick-filter.scss +++ b/app/javascript/scss/elements/_quick-filter.scss @@ -24,6 +24,10 @@ @include hover-stack { filter: brightness(1.15); } + + @include high-contrast() { + border: 2px solid currentColor; + } } .quick-filter__icon { diff --git a/app/javascript/scss/elements/_quick-navigation.scss b/app/javascript/scss/elements/_quick-navigation.scss index b703d0bbb..3de521481 100644 --- a/app/javascript/scss/elements/_quick-navigation.scss +++ b/app/javascript/scss/elements/_quick-navigation.scss @@ -24,7 +24,7 @@ $width: clamp(10rem, 12.5vw, 15rem); &:hover { scrollbar-color: initial; - @include styled-scrollbar(); + @include styled-scrollbar; } } diff --git a/app/javascript/scss/elements/_search-terms.scss b/app/javascript/scss/elements/_search-terms.scss index df77c60c7..bdc58aff4 100644 --- a/app/javascript/scss/elements/_search-terms.scss +++ b/app/javascript/scss/elements/_search-terms.scss @@ -1,14 +1,5 @@ @use "sass:math"; -.search-terms { - padding: math.div($margin, 2.5); - - @include media-min(lg) { - display: flex; - align-items: flex-start; - } -} - .search-terms__title { font-size: 18px; margin: 0 0 $margin * 0.25; diff --git a/app/javascript/scss/elements/_search.scss b/app/javascript/scss/elements/_search.scss index 3be3bcd58..e6b29659c 100644 --- a/app/javascript/scss/elements/_search.scss +++ b/app/javascript/scss/elements/_search.scss @@ -1,3 +1,5 @@ +@use "sass:math"; + .search { position: relative; z-index: 10; @@ -39,7 +41,7 @@ transform: translateY(-50%); font-style: italic; font-size: $font-size-base; - font-weight: light; + font-weight: normal; color: $text-color-lightest; cursor: text; pointer-events: none; @@ -52,15 +54,15 @@ } .search__submit { + display: flex; + align-items: center; position: absolute; top: 0; right: 0; - padding: 0 1.75rem; + padding: 0 1.15rem 0 0.5rem; height: 100%; - background: transparent url("/images/icons/icon-search.svg") no-repeat; - background-size: 20px 20px; - background-position: center; border: 0; + background: transparent; cursor: pointer; z-index: 2; @@ -105,28 +107,47 @@ .search__results { width: 100%; - padding: 0 0 0 1.5rem; - max-height: 175px; + padding: 0; + max-height: 15rem; overflow: auto; @include styled-scrollbar; + + &--empty { + padding: math.div($margin, 4) 2rem; + + @include media-min(sm) { + padding: math.div($margin, 4) 3rem; + } + } } .search__item { display: block; flex: 0 0 100%; width: 100%; - padding: .25rem 1.5rem; - margin: 0 -1.5rem; + padding: .25rem 2rem; color: $text-color-lightest; text-decoration: none; font-size: 18px; + @include media-min(sm) { + padding: .25rem 3rem; + } + &:hover, &:active { color: $white; background: $bg-dark; } + + &:first-child { + margin-top: 1rem; + } + + &:last-child { + margin-bottom: 1rem; + } } .search__item-category { diff --git a/app/javascript/scss/elements/_shadow-block.scss b/app/javascript/scss/elements/_shadow-block.scss index 70126e76c..9e74a9107 100644 --- a/app/javascript/scss/elements/_shadow-block.scss +++ b/app/javascript/scss/elements/_shadow-block.scss @@ -3,4 +3,8 @@ border-radius: $border-radius; box-shadow: $shadow; margin: $margin * 0.25 0; + + @include high-contrast() { + border: 2px solid currentColor; + } } diff --git a/app/javascript/scss/elements/_spinner.scss b/app/javascript/scss/elements/_spinner.scss index 4a0001f9a..c3f963097 100644 --- a/app/javascript/scss/elements/_spinner.scss +++ b/app/javascript/scss/elements/_spinner.scss @@ -16,8 +16,15 @@ height: 2rem; border-radius: 50%; border: .25rem solid $text-color; - border-color: $text-color rgba($white, .1) rgba($white, .1); + border-color: $text-color rgba($white, .1) rgba($white, .1); animation: spinner 750ms linear infinite; + + @include high-contrast() { + content: "Loading..."; + border: 0; + animation: none; + font-weight: bold; + } } &--small { diff --git a/app/javascript/scss/elements/_standout.scss b/app/javascript/scss/elements/_standout.scss index 40a0abda3..c142e103a 100644 --- a/app/javascript/scss/elements/_standout.scss +++ b/app/javascript/scss/elements/_standout.scss @@ -8,6 +8,10 @@ border-radius: $border-radius; box-shadow: $shadow; + @include high-contrast() { + border: 2px solid currentColor; + } + &::before { content: ""; display: block; diff --git a/app/javascript/scss/elements/_syntax-highlight.scss b/app/javascript/scss/elements/_syntax-highlight.scss index a2669b682..07c0c2a11 100644 --- a/app/javascript/scss/elements/_syntax-highlight.scss +++ b/app/javascript/scss/elements/_syntax-highlight.scss @@ -30,3 +30,7 @@ $highlight-types: ( color: var(--color-#{ $type }); } } + +.comment { + font-style: italic; +} diff --git a/app/javascript/scss/elements/_update-notes.scss b/app/javascript/scss/elements/_update-notes.scss index 7aea35fd1..5b0aec6b6 100644 --- a/app/javascript/scss/elements/_update-notes.scss +++ b/app/javascript/scss/elements/_update-notes.scss @@ -20,6 +20,7 @@ margin: 0 !important; font-size: 1.5rem; font-weight: bold; + color: $text-color-lightest; } .update-notes__body { diff --git a/app/javascript/scss/elements/_user-block.scss b/app/javascript/scss/elements/_user-block.scss index 0200f0c47..e29ab6549 100644 --- a/app/javascript/scss/elements/_user-block.scss +++ b/app/javascript/scss/elements/_user-block.scss @@ -26,6 +26,10 @@ text-decoration: none; transition: box-shadow 100ms; + @include high-contrast() { + border: 2px solid currentColor; + } + &:hover, &:active, &:focus { diff --git a/app/javascript/scss/elements/_video.scss b/app/javascript/scss/elements/_video.scss index 46a092f79..f61a62651 100644 --- a/app/javascript/scss/elements/_video.scss +++ b/app/javascript/scss/elements/_video.scss @@ -1,6 +1,8 @@ +@use "sass:math"; + .video { position: relative; - padding-bottom: 56.25%; + padding-bottom: math.div(100%, 900) * 500; height: 0; background: $bg-darker; } @@ -13,6 +15,40 @@ height: 100%; } +.video__preview { + @extend .video__iframe; + cursor: pointer; + + &:focus-visible { + @include focus-visible-outline; + } +} + +.video__thumbnail { + width: 100%; + height: 100% !important; + object-fit: cover; +} + +.video__play-icon { + position: absolute; + left: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%); + width: 6rem; + height: 6rem; + background: url("/images/icons/icon-youtube.svg") no-repeat center; + background-size: contain; + z-index: 2; + filter: saturate(0); + transition: filter 100ms; + + .video__preview:hover &, + .video__preview:focus-visible & { + filter: none; + } +} + video { display: block; width: 100%; diff --git a/app/javascript/scss/elements/_warning.scss b/app/javascript/scss/elements/_warning.scss index b2b0d5715..25f5ab4df 100644 --- a/app/javascript/scss/elements/_warning.scss +++ b/app/javascript/scss/elements/_warning.scss @@ -4,6 +4,10 @@ text-align: center; color: white; + @include high-contrast() { + border: 2px solid currentColor; + } + &--alert { background: $green; } diff --git a/app/javascript/scss/elements/_well.scss b/app/javascript/scss/elements/_well.scss index ec4331c0d..3abdc106f 100644 --- a/app/javascript/scss/elements/_well.scss +++ b/app/javascript/scss/elements/_well.scss @@ -13,6 +13,10 @@ align-items: center; } + @include high-contrast() { + border: 2px solid currentColor; + } + &--simple { box-shadow: none; background: transparent @@ -56,6 +60,12 @@ a { text-decoration: none; + + @include hover-stack { + @include high-contrast() { + text-decoration: underline; + } + } } } diff --git a/app/javascript/scss/general/_form.scss b/app/javascript/scss/general/_form.scss index 64f39d199..219544742 100644 --- a/app/javascript/scss/general/_form.scss +++ b/app/javascript/scss/general/_form.scss @@ -11,6 +11,10 @@ font-size: $font-size-base; color: $text-color-lightest; + @include high-contrast() { + border: 2px solid currentColor; + } + &:focus, &:focus-within { outline: none; @@ -25,7 +29,7 @@ &--large { width: 100%; - height: 80px / 1.25; + height: calc(80px * 0.8); padding: calc(80px - (1.5em * 2)) math.div($margin, 3); font-size: 21px; font-weight: lighter; @@ -148,6 +152,10 @@ border-color: $text-color; } + @include high-contrast() { + border: 2px solid currentColor; + } + &:focus { outline: none; background-color: lighten($bg-dark, 5%); @@ -203,8 +211,13 @@ } &:checked { - background: $primary; + background: var(--primary, $primary); border: 0; + + @include high-contrast() { + background: currentColor; + border: 2px solid currentColor; + } } &:focus { @@ -319,6 +332,7 @@ input[type="file"] { } .hidden-field { + visibility: hidden; position: absolute; opacity: 0; height: 0; @@ -330,11 +344,17 @@ input[type="file"] { .url-input { display: flex; + align-items: center; color: $text-color; + white-space: nowrap; } .inline-input { @include inline-input; + + &:focus { + outline: none; + } } .form-required { @@ -343,9 +363,15 @@ input[type="file"] { } .range { + padding: 0; + margin: 0; + height: 1rem; background: transparent; &::-moz-range-thumb { + height: 1rem; + width: 0.5rem; + border-radius: $border-radius * 0.25; background: $text-color; border: 0; cursor: pointer; @@ -356,11 +382,15 @@ input[type="file"] { } &::-moz-range-track { + height: 0.5rem; + border-radius: 0.5rem; background: $bg-darker; } &::-moz-range-progress { - background: $primary; + height: 0.5rem; + border-radius: 0.5rem 0 0 0.5rem; + background: var(--primary, $primary); } } diff --git a/app/javascript/scss/general/_general.scss b/app/javascript/scss/general/_general.scss index 1a5dd7aeb..245a8e481 100644 --- a/app/javascript/scss/general/_general.scss +++ b/app/javascript/scss/general/_general.scss @@ -20,8 +20,12 @@ body { color: $text-color; overflow-x: hidden; - &.high-contrast { - background: darken($body-bg, 5%); + @include high-contrast() { + background: $black; + + @include light() { + background: $white; + } } } @@ -33,12 +37,26 @@ a { @include hover-stack { color: $text-color-lightest; + + @include high-contrast() { + text-decoration: underline; + } } } mark { background: transparent; color: var(--primary, $primary); + + @include high-contrast() { + color: $black; + background: $white; + + @include light() { + color: $white; + background: $black; + } + } } hr { @@ -67,6 +85,10 @@ pre { border-radius: $border-radius * 0.5; line-height: 2rem; + @include high-contrast() { + border: 2px solid currentColor; + } + code { padding: 0; white-space: pre-wrap; @@ -80,12 +102,20 @@ pre { blockquote { display: inline-block; margin: $margin * 0.5 0; - padding: 1em 2em 1em 2em; - border-left: 3px solid $border-color; - background: rgba($black, .1); + padding: 1em 1.5em 1em 1.5em; + border-left: 3px solid var(--primary); + background: rgba($black, .35); line-height: 1.5em; - border-top-right-radius: $border-radius; - border-bottom-right-radius: $border-radius; + border-top-right-radius: $border-radius * 0.5; + border-bottom-right-radius: $border-radius * 0.5; + + strong { + color: $text-color-lightest; + } + + mark strong { + color: var(--primary); + } } strong { @@ -105,6 +135,16 @@ kbd { letter-spacing: -1px; } +fieldset { + border: 0; + padding: 0; +} + +legend { + margin: 0; + padding: 0; +} + .wrapper { position: relative; width: 100%; diff --git a/app/javascript/scss/general/_table.scss b/app/javascript/scss/general/_table.scss index d47d49d0d..cb5eff597 100644 --- a/app/javascript/scss/general/_table.scss +++ b/app/javascript/scss/general/_table.scss @@ -11,8 +11,8 @@ table { td { padding: .5rem; - tr:nth-child(odd) & { - background: rgba($white, .05); + tr:nth-child(even) & { + background: rgba($black, .1); } a { @@ -27,7 +27,7 @@ td { th { padding: .5rem; - background: rgba($black, .15); + background: rgba($black, .25); text-align: left; font-weight: bolder; color: $text-color-lightest; @@ -36,7 +36,7 @@ th { tr { @include hover-stack { td { - background: rgba($white, .1); + background: rgba($white, .05); } } } diff --git a/app/javascript/scss/general/_titles.scss b/app/javascript/scss/general/_titles.scss index a8e2cbb37..e9323eb8f 100644 --- a/app/javascript/scss/general/_titles.scss +++ b/app/javascript/scss/general/_titles.scss @@ -32,8 +32,9 @@ h1 { } h2 { - @include responsive-font-width(28px, map-get($media-min-widths, lg), 24px); margin-bottom: math.div($margin, 3); + + @include responsive-font-width(28px, map-get($media-min-widths, lg), 24px); } h3 { @@ -57,7 +58,7 @@ h1, h2, h3, h4, h5, h6 { left: -20px; width: 20px; height: 1em; - background: transparent url("/images/icons/icon-link.svg") no-repeat; + background: transparent url("@images/icons/icon-link.svg") no-repeat; background-position: left center; opacity: 0; transition: opacity 150ms linear; diff --git a/app/javascript/scss/logged_in_user/_codemirror-5.scss b/app/javascript/scss/logged_in_user/_codemirror-5.scss index 7cf72e7ca..4fcbdf84c 100644 --- a/app/javascript/scss/logged_in_user/_codemirror-5.scss +++ b/app/javascript/scss/logged_in_user/_codemirror-5.scss @@ -5,6 +5,10 @@ color: $text-color-lightest; min-height: 600px; + @include high-contrast() { + border: 2px solid currentColor; + } + .CodeMirror-selectedtext, .CodeMirror-selected { background: lighten($bg-dark, 20%); diff --git a/app/javascript/scss/logged_in_user/_fixed-box.scss b/app/javascript/scss/logged_in_user/_fixed-box.scss index ba820f10a..c784a5b70 100644 --- a/app/javascript/scss/logged_in_user/_fixed-box.scss +++ b/app/javascript/scss/logged_in_user/_fixed-box.scss @@ -1,5 +1,4 @@ .fixed-box { - @include styled-scrollbar; position: fixed; left: 1rem; bottom: 1rem; @@ -12,6 +11,8 @@ z-index: 10; overflow-y: auto; + @include styled-scrollbar; + &--large { max-width: 400px; } diff --git a/app/javascript/scss/logged_in_user/_form-tags.scss b/app/javascript/scss/logged_in_user/_form-tags.scss index 1ed284b12..8503b3b87 100644 --- a/app/javascript/scss/logged_in_user/_form-tags.scss +++ b/app/javascript/scss/logged_in_user/_form-tags.scss @@ -10,26 +10,39 @@ width: initial; margin-top: .35rem; padding: .35rem; + + &:focus { + outline: none; + } } .form-tags__tag { display: flex; + align-items: center; white-space: nowrap; border-radius: .1rem; margin-right: .35rem; margin-top: .25rem; - padding: .25rem .15rem .25rem .2rem; + padding: .15rem .2rem; border-radius: $border-radius * 0.25; background: lighten($bg-dark, 5%); line-height: 1em; + color: $text-color-light; + font-weight: bold; } .form-tags__remove-tag { - padding: 0 .25rem; + padding: .1rem .2rem .2rem; background: transparent; border: none; color: $text-color-lightest; cursor: pointer; + line-height: 1em; + + &:hover, + &:active { + color: $white; + } } .form-tags-autocomplete-results__anchor { diff --git a/app/javascript/scss/logged_in_user/_images-preview.scss b/app/javascript/scss/logged_in_user/_images-preview.scss index c632b14c0..0a0ae2e94 100644 --- a/app/javascript/scss/logged_in_user/_images-preview.scss +++ b/app/javascript/scss/logged_in_user/_images-preview.scss @@ -14,6 +14,8 @@ background: $body-bg; cursor: grab; color: $body-bg; + border-radius: $border-radius * 0.5; + overflow: hidden; @include hover-stack { filter: brightness(1.1); @@ -24,7 +26,6 @@ cursor: grabbing; } - img, svg { display: block; @@ -33,22 +34,24 @@ } } -.images-preview__action, -.images-preview__label { +.images-preview__action { position: absolute; top: 0; right: 0; bottom: auto; - border-radius: 0 0 0 $border-radius * 0.25; - padding: .25em .5em; - background: $red; - font-size: 12px; + padding: .25em; + background: $bg-dark; color: $white; cursor: pointer; - &:hover, - &:active { - filter: brightness(1.2); + &--close { + background: $red; + border-radius: 0 0 0 $border-radius * 0.5; + + &:hover, + &:active { + background: saturate(lighten($red, 20%), 50%); + } } .sortable-ghost & { diff --git a/app/javascript/scss/logged_in_user/_switch.scss b/app/javascript/scss/logged_in_user/_switch.scss index 56425f86c..d99934870 100644 --- a/app/javascript/scss/logged_in_user/_switch.scss +++ b/app/javascript/scss/logged_in_user/_switch.scss @@ -15,6 +15,10 @@ background: lighten($border-color, 10%); z-index: 0; pointer-events: none; + + @include high-contrast() { + background: currentColor; + } } &::after { @@ -35,6 +39,10 @@ &::before { left: calc(2rem + 6px); background: $text-color-lightest; + + @include high-contrast() { + background: currentColor; + } } &::after { @@ -89,6 +97,10 @@ @include hover-stack { &::before { background: $text-color; + + @include high-contrast() { + background: currentColor; + } } &::after { @@ -107,6 +119,10 @@ .switch-checkbox--active & { left: calc(1rem + 6px); background: $text-color-lightest; + + @include high-contrast() { + background: currentColor; + } } } diff --git a/app/javascript/scss/utilities/_border-radius.scss b/app/javascript/scss/utilities/_border-radius.scss index d79e75956..91209f077 100644 --- a/app/javascript/scss/utilities/_border-radius.scss +++ b/app/javascript/scss/utilities/_border-radius.scss @@ -5,3 +5,7 @@ .br-1\/2 { border-radius: $border-radius * 0.5; } + +.br-top { + border-radius: $border-radius $border-radius 0 0; +} diff --git a/app/javascript/scss/utilities/_colors.scss b/app/javascript/scss/utilities/_colors.scss index c1f253beb..785c2d41b 100644 --- a/app/javascript/scss/utilities/_colors.scss +++ b/app/javascript/scss/utilities/_colors.scss @@ -26,6 +26,12 @@ $text-colors: ( } } +.sm\:text-black { + @include media-min(sm) { + color: $black; + } +} + $bg-colors: ( primary: var(--primary, $primary), dark: $bg-dark, diff --git a/app/javascript/scss/utilities/_cursor.scss b/app/javascript/scss/utilities/_cursor.scss index 92d1f1f0f..70074d600 100644 --- a/app/javascript/scss/utilities/_cursor.scss +++ b/app/javascript/scss/utilities/_cursor.scss @@ -1,7 +1,3 @@ .cursor-pointer { cursor: pointer; } - -.cursor-copy { - cursor: copy; -} diff --git a/app/javascript/scss/utilities/_display.scss b/app/javascript/scss/utilities/_display.scss index f50f5b946..9c9fc1c25 100644 --- a/app/javascript/scss/utilities/_display.scss +++ b/app/javascript/scss/utilities/_display.scss @@ -9,18 +9,6 @@ display: none; } -@each $identifier, $breakpoint in $media-min-widths { - @include media-min(#{$identifier}) { - .hidden-#{$identifier} { - display: none; - } - - .visible-#{$identifier} { - display: initial; - } - } -} - .block { display: block; } @@ -29,8 +17,28 @@ display: inline-block; } +.sm\:hidden { + @include media-min(sm) { + display: none; + } +} + .sm\:inline-block { @include media-min(sm) { display: inline; } } + +@each $identifier, $breakpoint in $media-min-widths { + @include media-min(#{$identifier}) { + .#{$identifier}\:visible { + display: initial; + } + } +} + +.sr-only { + opacity: 0; + position: absolute; + left: -9999px; +} diff --git a/app/javascript/scss/utilities/_offset.scss b/app/javascript/scss/utilities/_offset.scss index 012a700ab..2edb8f042 100644 --- a/app/javascript/scss/utilities/_offset.scss +++ b/app/javascript/scss/utilities/_offset.scss @@ -26,8 +26,10 @@ $directions: ( @each $property, $property-abr in $properties { @each $direction, $direction-abr in $directions { @each $name, $value in $offset-values { - .#{ $property-abr }#{ $direction-abr }-#{ $name } { - #{ $property }#{ $direction }: $value; + @if not ($property == padding and $value == auto) { + .#{ $property-abr }#{ $direction-abr }-#{ $name } { + #{ $property }#{ $direction }: $value; + } } } } @@ -38,8 +40,10 @@ $directions: ( @each $property, $property-abr in $properties { @each $direction, $direction-abr in $directions { @each $name, $value in $offset-values { - .#{ $breakpoint }\:#{ $property-abr }#{ $direction-abr }-#{ $name } { - #{ $property }#{ $direction }: $value; + @if not ($property == padding and $value == auto) { + .#{ $breakpoint }\:#{ $property-abr }#{ $direction-abr }-#{ $name } { + #{ $property }#{ $direction }: $value; + } } } } diff --git a/app/javascript/scss/utilities/_overflow.scss b/app/javascript/scss/utilities/_overflow.scss index 0cb193f4d..e8edfac2b 100644 --- a/app/javascript/scss/utilities/_overflow.scss +++ b/app/javascript/scss/utilities/_overflow.scss @@ -4,6 +4,7 @@ .overflow-auto { overflow: auto; + @include styled-scrollbar; } diff --git a/app/javascript/scss/utilities/_text.scss b/app/javascript/scss/utilities/_text.scss index be6cb2bec..3be3c6fee 100644 --- a/app/javascript/scss/utilities/_text.scss +++ b/app/javascript/scss/utilities/_text.scss @@ -30,6 +30,7 @@ .text-small { font-size: $font-size-small; + line-height: 1.35em; } .text-big { diff --git a/app/javascript/scss/wiki/_article.scss b/app/javascript/scss/wiki/_article.scss index a4aaeea14..2fda62385 100644 --- a/app/javascript/scss/wiki/_article.scss +++ b/app/javascript/scss/wiki/_article.scss @@ -65,11 +65,16 @@ margin-top: 0; } + @include high-contrast() { + border: 2px solid currentColor; + } + a { color: $text-color-lightest; text-decoration: none; @include hover-stack { + text-decoration: underline; color: $white; } } diff --git a/app/javascript/scss/wiki/_breadcrumbs.scss b/app/javascript/scss/wiki/_breadcrumbs.scss index 9ccba7720..73db8f28d 100644 --- a/app/javascript/scss/wiki/_breadcrumbs.scss +++ b/app/javascript/scss/wiki/_breadcrumbs.scss @@ -21,6 +21,11 @@ a { color: $text-color-lightest; text-decoration: none; + + @include hover-stack { + text-decoration: underline; + color: $white; + } } span { diff --git a/app/javascript/scss/wiki/_card.scss b/app/javascript/scss/wiki/_card.scss index a2ddd9248..8d557ded4 100644 --- a/app/javascript/scss/wiki/_card.scss +++ b/app/javascript/scss/wiki/_card.scss @@ -30,5 +30,10 @@ a { color: $text-color-lightest; text-decoration: none; + + @include hover-stack { + text-decoration: underline; + color: $white; + } } } diff --git a/app/javascript/scss/wiki/_header.scss b/app/javascript/scss/wiki/_header.scss index 445d50a28..e669977d0 100644 --- a/app/javascript/scss/wiki/_header.scss +++ b/app/javascript/scss/wiki/_header.scss @@ -7,7 +7,7 @@ left: 0; width: 100%; height: 200px; - background: lighten($body-bg, 5%) url("/images/wiki/bg.webp"); + background: lighten($body-bg, 25%) url("/images/wiki/bg.webp"); background-size: cover; z-index: -1; box-shadow: 5px 0 25px rgba($black, .25); @@ -23,6 +23,10 @@ @supports (background: -webkit-canvas(squares)) { background-image: url("/images/wiki/bg.jpg"); } + + @include high-contrast() { + background-image: none; + } } &--small { diff --git a/app/javascript/scss/wiki/_search.scss b/app/javascript/scss/wiki/_search.scss index 371fe666f..b0ceb6bdd 100644 --- a/app/javascript/scss/wiki/_search.scss +++ b/app/javascript/scss/wiki/_search.scss @@ -63,12 +63,14 @@ } .search__submit { - padding: 0 2.5rem; - background-size: 24px 24px; + padding: 0 2rem 0 1rem; - @include media-min(sm) { - padding: 0 3rem; - background-size: 28px 28px; + svg { + height: 24px; + + @include media-min(sm) { + height: 28px; + } } } } diff --git a/app/javascript/src/apply-custom-css.js b/app/javascript/src/apply-custom-css.ts similarity index 56% rename from app/javascript/src/apply-custom-css.js rename to app/javascript/src/apply-custom-css.ts index 16add9f4a..9c1e5ca5a 100644 --- a/app/javascript/src/apply-custom-css.js +++ b/app/javascript/src/apply-custom-css.ts @@ -1,12 +1,12 @@ -export function bind() { +export function bind(): void { const elements = document.querySelectorAll("[data-action='apply-custom-css']") elements.forEach((element) => element.removeAndAddEventListener("input", applyCustomCSS)) } -function applyCustomCSS() { - const value = this.value +function applyCustomCSS({ currentTarget }: { currentTarget: HTMLFormElement }): void { + const value = currentTarget.value const styleTag = document.querySelector("#custom-css") - styleTag.textContent = value + styleTag!.textContent = value } diff --git a/app/javascript/src/blocks.js b/app/javascript/src/blocks.ts similarity index 52% rename from app/javascript/src/blocks.js rename to app/javascript/src/blocks.ts index ede60afed..4c988e438 100644 --- a/app/javascript/src/blocks.js +++ b/app/javascript/src/blocks.ts @@ -3,8 +3,8 @@ import Sortable from "sortablejs" import LimitedCheckboxes from "@components/form/LimitedCheckboxes.svelte" import FetchRails from "@src/fetch-rails" -export function bind() { - const element = document.querySelector("[data-role~='block-sortable']") +export function bind(): void { + const element = document.querySelector("[data-role~='block-sortable']") as HTMLElement if (element) buildBlockSortable(element) @@ -12,21 +12,21 @@ export function bind() { createBlockElements.forEach(element => element.removeAndAddEventListener("click", createBlock)) } -function createBlock() { - if (this.dataset.disabled == "true") return +function createBlock({ currentTarget }: { currentTarget: HTMLElement }): void { + if (currentTarget.dataset.disabled == "true") return - this.dataset.disabled = true + currentTarget.dataset.disabled = "true" - new FetchRails("/blocks", { block: { content_type: this.dataset.contentType, name: this.dataset.name } }) + new FetchRails("/blocks", { block: { content_type: currentTarget.dataset.contentType, name: currentTarget.dataset.name } }) .post().then(data => { new Promise((resolve) => new Function("resolve", data)(resolve)) renderSvelteComponents() }).finally(() => { - this.dataset.disabled = false + currentTarget.dataset.disabled = "false" }) } -function buildBlockSortable(element) { +function buildBlockSortable(element: HTMLElement): void { Sortable.create(element, { draggable: "[data-sortable-block]", animation: 50, @@ -34,12 +34,13 @@ function buildBlockSortable(element) { }) } -function updateBlockSortable() { - const blocks = document.querySelectorAll(".content-block") +function updateBlockSortable(): void { + const blocks = Array.from(document.querySelectorAll(".content-block")) as HTMLElement[] - const positions = [] + const positions: { id: any[]; position: number }[] = [] blocks.forEach((block, i) => positions.push({ id: [block.dataset.id], position: i })) + // @ts-ignore const progressBar = new Turbolinks.ProgressBar() progressBar.setValue(0) progressBar.show() @@ -51,27 +52,31 @@ function updateBlockSortable() { }) } -function renderSvelteComponents() { +function renderSvelteComponents(): void { initializeSvelteComponent("LimitedCheckboxes", LimitedCheckboxes) } -export function insertBlockTemplate(event) { +export function insertBlockTemplate(event: MouseEvent): void { event.preventDefault() - const template = document.getElementById(`${this.dataset.template}`).content.cloneNode(true) - const targetElement = document.querySelector(`[data-template-target="${this.dataset.target}"]`) + const currentTarget = event.currentTarget as HTMLElement + const templateElement = document.getElementById(`${currentTarget.dataset.template}`) as HTMLFormElement + const template = templateElement.content.cloneNode(true) + const targetElement = document.querySelector(`[data-template-target="${currentTarget.dataset.target}"]`) - targetElement.append(template) + targetElement!.append(template) } -export function removeBlockTemplate(event) { +export function removeBlockTemplate(event: MouseEvent): void { event.preventDefault() - const target = event.target.closest("[data-remove-target]") - target.remove() + const currentTarget = event.currentTarget as HTMLElement + const target = currentTarget.closest("[data-remove-target]") + + target!.remove() } -export function buildInputSortable(element) { +export function buildInputSortable(element: HTMLElement): void { Sortable.create(element, { animation: 50, handle: "[data-role~='sortable-handle']" diff --git a/app/javascript/src/carousel-cards.js b/app/javascript/src/carousel-cards.js deleted file mode 100644 index c5f71fd1b..000000000 --- a/app/javascript/src/carousel-cards.js +++ /dev/null @@ -1,62 +0,0 @@ -import Siema from "siema/dist/siema.min" - -let carouselCards = [] - -export function destroy() { - if (carouselCards.length == 0) return - - const elements = document.querySelectorAll("[data-role='carousel-cards']") - - elements.forEach(element => { element.classList.remove("initialised") }) - carouselCards.forEach(carousel => { carousel.destroy(true) }) - - carouselCards = [] -} - -export function render() { - const elements = document.querySelectorAll("[data-role='carousel-cards']") - - if (elements.length == 0) return - - elements.forEach(element => { - carouselCards = [...carouselCards, new Siema({ - selector: element, - onInit: (() => element.classList.add("initialised")), - onChange: carouselCardsChanged, - perPage: { 450: 2, 768: 3 }, - duration: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? 0 : 200 - })] - - element.dataset.carouselId = carouselCards.length - 1 - }) - - const previousElements = document.querySelectorAll("[data-action='carousel-previous']") - previousElements.forEach((element) => element.removeAndAddEventListener("click", carouselPrevious)) - - const nextElements = document.querySelectorAll("[data-action='carousel-next']") - nextElements.forEach((element) => element.removeAndAddEventListener("click", carouselNext)) -} - -function carouselCardsChanged() { - const parent = this.selector.closest(".card-carousel") - const nextElement = parent.querySelector("[data-action='carousel-next']") - const previousElement = parent.querySelector("[data-action='carousel-previous']") - - previousElement.classList.toggle("card-carousel__control--disabled", this.currentSlide == 0) - nextElement.classList.toggle("card-carousel__control--disabled", this.currentSlide + this.perPage >= parseInt(this.selector.dataset.max)) -} - -function carouselNext() { - const carouselId = getCarouselId(this) - carouselCards[carouselId].next() -} - -function carouselPrevious() { - const carouselId = getCarouselId(this) - carouselCards[carouselId].prev() -} - -function getCarouselId(element) { - const carouselElement = element.closest(".card-carousel").querySelector("[data-role='carousel-cards']") - return carouselElement.dataset.carouselId -} diff --git a/app/javascript/src/carousel-cards.ts b/app/javascript/src/carousel-cards.ts new file mode 100644 index 000000000..45fcc80f9 --- /dev/null +++ b/app/javascript/src/carousel-cards.ts @@ -0,0 +1,68 @@ +import Siema, { type SiemaOptions } from "siema" + +let carouselCards: Siema[] = [] + +export function destroy(): void { + if (carouselCards.length == 0) return + + const elements = document.querySelectorAll("[data-role='carousel-cards']") + + elements.forEach(element => { element.classList.remove("initialised") }) + carouselCards.forEach(carousel => carousel.destroy(true)) + + carouselCards = [] +} + +export function render(): void { + const elements = Array.from(document.querySelectorAll("[data-role='carousel-cards']")) as HTMLElement[] + + if (elements.length == 0) return + + elements.forEach(element => { + carouselCards = [...carouselCards, new Siema({ + selector: element, + onInit: (() => element.classList.add("initialised")), + onChange: carouselCardsChanged, + perPage: { 450: 2, 768: 3 }, + duration: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? 0 : 200 + })] + + element.dataset.carouselId = (carouselCards.length - 1).toString() + }) + + const previousElements = document.querySelectorAll("[data-action='carousel-previous']") + previousElements.forEach((element) => element.removeAndAddEventListener("click", carouselPrevious)) + + const nextElements = document.querySelectorAll("[data-action='carousel-next']") + nextElements.forEach((element) => element.removeAndAddEventListener("click", carouselNext)) +} + +function carouselCardsChanged(this: Siema & SiemaOptions): void { + const selector = this.selector as HTMLElement + const parent = selector.closest(".card-carousel") + const nextElement = parent!.querySelector("[data-action='carousel-next']") as HTMLButtonElement + const previousElement = parent!.querySelector("[data-action='carousel-previous']") as HTMLButtonElement + + const atStart = this.currentSlide == 0 + previousElement!.classList.toggle("card-carousel__control--disabled", atStart) + previousElement!.disabled = atStart + + const atEnd = this.currentSlide + this.perPage >= parseInt(selector?.dataset?.max || "") + nextElement!.classList.toggle("card-carousel__control--disabled", atEnd) + nextElement!.disabled = atEnd +} + +function carouselNext({ currentTarget }: { currentTarget: HTMLElement }): void { + const carouselId = getCarouselId(currentTarget) + carouselCards[carouselId].next() +} + +function carouselPrevious({ currentTarget }: { currentTarget: HTMLElement }): void { + const carouselId = getCarouselId(currentTarget) + carouselCards[carouselId].prev() +} + +function getCarouselId(element: HTMLElement): number { + const carouselElement = element.closest(".card-carousel")?.querySelector("[data-role='carousel-cards']") as HTMLElement + return parseInt(carouselElement.dataset.carouselId || "0") +} diff --git a/app/javascript/src/carousel.js b/app/javascript/src/carousel.js deleted file mode 100644 index 6ea37024e..000000000 --- a/app/javascript/src/carousel.js +++ /dev/null @@ -1,121 +0,0 @@ -import Siema from "siema/dist/siema.min" - -export let carousel - -export function render() { - const blurElement = document.querySelector("[data-use-blur='true']") - if (blurElement && blurElement.dataset.role != "carousel") setBlur(blurElement) - - const element = document.querySelector("[data-role='carousel']") - - if (!element) return - - setCarousel(element) - - const navigationElements = document.querySelectorAll("[data-action='carousel-go-to']") - navigationElements.forEach((element) => element.removeAndAddEventListener("click", carouselGoTo)) -} - -export function setCarousel(element) { - carousel = new Siema({ - selector: element, - onInit: setActiveItem, - onChange: setActiveItem, - duration: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? 0 : 200 - }) - - setResizeHandler() -} - -function carouselGoTo() { - const target = this.dataset.target - - carousel.goTo(target) -} - -async function setActiveItem() { - await new Promise(res => setTimeout(res)) // Wait(0) for carousel to be initiated - - const navigationElements = document.querySelectorAll("[data-action='carousel-go-to']") - const activeElement = document.querySelector(".carousel__navigation-item--is-active") - - if (activeElement) activeElement.classList.remove("carousel__navigation-item--is-active") - if (navigationElements.length) navigationElements[this.currentSlide].classList.add("carousel__navigation-item--is-active") - - setLazyImage(this) - - stopVideo() - - if (carousel?.selector?.dataset.useBlur) setBlur(carousel.innerElements[carousel.currentSlide]) -} - -function setLazyImage(element) { - const slides = [] - slides.push(element.innerElements[element.currentSlide - 1]) - slides.push(element.innerElements[element.currentSlide]) - slides.push(element.innerElements[element.currentSlide + 1]) - - slides.forEach(slide => { - if (!slide) return - - const sources = slide.querySelectorAll("source, img") - - sources.forEach(source => { - if (source.dataset.src) { - source.src = source.dataset.src - } else if (source.dataset.srcset) { - source.srcset = source.dataset.srcset - } - }) - }) -} - -function stopVideo() { - const carousel = document.querySelector("[data-role='carousel']") - const iframe = carousel.querySelector("iframe") - - if (!iframe) return - - iframe.contentWindow.postMessage("{\"event\":\"command\",\"func\":\"pauseVideo\",\"args\":\"\"}", "*") -} - -async function setBlur(element) { - const image = element.querySelector("img") - const blurElements = document.querySelectorAll("[data-role='carousel-blur']") - const blurElement = blurElements[blurElements.length - 1] - const whitePixel = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NobW39DwAFsQKP8FV1WwAAAABJRU5ErkJggg==" - - const newElement = new Image() - if (blurElement.classList.contains("background-blur--visible")) { - newElement.src = image?.src || whitePixel - newElement.dataset.role = "carousel-blur" - newElement.classList.add("background-blur") - - blurElement.insertAdjacentElement("afterend", newElement) - - await new Promise(res => setTimeout(res, 1)) // Await one tick before fading - - newElement.classList.add("background-blur--visible") - blurElement.classList.remove("background-blur--visible") - - await new Promise(res => setTimeout(res, 1200)) // Await transition - blurElement.remove() - } else { - blurElement.src = image?.src || whitePixel - await new Promise(res => setTimeout(res, 1)) // Await one tick before fading - blurElement.classList.add("background-blur--visible") - } -} - -function setResizeHandler() { - // Normally videos can't fullscreened because Siema resizeHandler fires when - // you go into fullscreen. This fixes that by checking if the page is in - // fullscreen before firing resizeHandler. - window.removeEventListener("resize", carousel.resizeHandler) - window.removeEventListener("resize", resizeHandler) - window.addEventListener("resize", resizeHandler) -} - -function resizeHandler() { - if (!document.fullscreenElement) carousel.resizeHandler() -} diff --git a/app/javascript/src/carousel.ts b/app/javascript/src/carousel.ts new file mode 100644 index 000000000..53e0167ec --- /dev/null +++ b/app/javascript/src/carousel.ts @@ -0,0 +1,142 @@ +import Siema, { type SiemaOptions } from "siema" + +interface SiemaExtended extends Siema { + innerElements: HTMLElement[], + resizeHandler: EventListener +} + +export let carousel: SiemaExtended + +export function render(): void { + const blurElement = document.querySelector("[data-use-blur='true']") as HTMLElement + if (blurElement && blurElement.dataset.role != "carousel") setBlur(blurElement) + + const element = document.querySelector("[data-role='carousel']") as HTMLElement + + if (!element) return + + setCarousel(element) + + const navigationElements = document.querySelectorAll("[data-action='carousel-go-to']") + navigationElements.forEach((element) => element.removeAndAddEventListener("click", carouselGoTo)) +} + +export function setCarousel(element: HTMLElement): void { + carousel = new Siema({ + selector: element, + onInit: setActiveItem, + onChange: setActiveItem, + duration: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? 0 : 200 + }) as SiemaExtended + + setResizeHandler() +} + +function carouselGoTo({ currentTarget }: { currentTarget: HTMLElement }): void { + const target = parseInt(currentTarget.dataset.target || "0") + carousel.goTo(target) +} + +async function setActiveItem(this: SiemaExtended & SiemaOptions): Promise { + await new Promise(res => setTimeout(res)) // Wait(0) for carousel to be initiated + + const navigationElements = document.querySelectorAll("[data-action='carousel-go-to']") + const activeElement = document.querySelector(".carousel__navigation-item--is-active") + + if (activeElement) { + activeElement.classList.remove("carousel__navigation-item--is-active") + activeElement.ariaSelected = null + } + + if (navigationElements.length) { + navigationElements[this.currentSlide].classList.add("carousel__navigation-item--is-active") + navigationElements[this.currentSlide].ariaSelected = "true" + } + + setLazyImage(this) + + stopVideo() + + const selector = this.selector as HTMLElement + if (selector.dataset.useBlur) setBlur(carousel.innerElements[carousel.currentSlide]) +} + +function setLazyImage(carousel: SiemaExtended): void { + const slides = [] + slides.push(carousel.innerElements[carousel.currentSlide - 1]) + slides.push(carousel.innerElements[carousel.currentSlide]) + slides.push(carousel.innerElements[carousel.currentSlide + 1]) + + slides.forEach(slide => { + if (!slide) return + + const images = slide.querySelectorAll("img") + + images.forEach(image => { + if (image.dataset.src) image.src = image.dataset.src + if (image.dataset.srcset) image.srcset = image.dataset.srcset + }) + }) +} + +function stopVideo(): void { + const carousel = document.querySelector("[data-role='carousel']") + const iframe = carousel!.querySelector("iframe") + + if (!iframe?.contentWindow) return + + iframe.contentWindow.postMessage("{\"event\":\"command\",\"func\":\"pauseVideo\",\"args\":\"\"}", "*") +} + +async function setBlur(element: HTMLElement): Promise { + const image = element.querySelector("img") + const blurElements = Array.from(document.querySelectorAll("[data-role='carousel-blur']")) as HTMLImageElement[] + const blurElement = blurElements[blurElements.length - 1] + + const newElement = new Image() + if (blurElement.classList.contains("background-blur--visible")) { + copyImageSources(newElement, image) + + newElement.dataset.role = "carousel-blur" + newElement.classList.add("background-blur") + + blurElement.insertAdjacentElement("afterend", newElement) + + await new Promise(res => setTimeout(res, 1)) // Await one tick before fading + + newElement.classList.add("background-blur--visible") + blurElement.classList.remove("background-blur--visible") + + await new Promise(res => setTimeout(res, 1200)) // Await transition + blurElement.remove() + } else { + copyImageSources(blurElement, image) + + await new Promise(res => setTimeout(res, 1)) // Await one tick before fading + blurElement.classList.add("background-blur--visible") + } +} + +function setResizeHandler(): void { + // Normally videos can't fullscreened because Siema resizeHandler fires when + // you go into fullscreen. This fixes that by checking if the page is in + // fullscreen before firing resizeHandler. + window.removeEventListener("resize", carousel.resizeHandler) + window.removeEventListener("resize", resizeHandler) + window.addEventListener("resize", resizeHandler) +} + +function resizeHandler(event: Event): void { + if (!document.fullscreenElement) carousel.resizeHandler(event) +} + +function copyImageSources(newElement: HTMLImageElement, originalElement: HTMLImageElement | null): void { + const fallbackPixel = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NobW39DwAFsQKP8FV1WwAAAABJRU5ErkJggg==" + + if (originalElement) { + newElement.srcset = originalElement.srcset || fallbackPixel + newElement.sizes = originalElement.sizes + } + + newElement.src = originalElement?.src || fallbackPixel +} diff --git a/app/javascript/src/chart.js b/app/javascript/src/chart.ts similarity index 71% rename from app/javascript/src/chart.js rename to app/javascript/src/chart.ts index 9c216ecef..2f9589f76 100644 --- a/app/javascript/src/chart.js +++ b/app/javascript/src/chart.ts @@ -1,13 +1,14 @@ -export function render() { - const charts = document.querySelectorAll("[data-role='chart']") +export function render(): void { + const charts = Array.from(document.querySelectorAll("[data-role='chart']")) as HTMLElement[] charts.forEach(element => { if (element.dataset.data != null) createChart(element, JSON.parse(element.dataset.data)) }) } -export default async function createChart(element, data, dateformat = "%Y-%m-%d") { +export default async function createChart(element: HTMLElement, data: any, dateformat = "%Y-%m-%d"): Promise { const { curveStep } = await import("d3") + // @ts-ignore const MG = await import("metrics-graphics") element.innerHTML = "" @@ -15,7 +16,7 @@ export default async function createChart(element, data, dateformat = "%Y-%m-%d" let processedMarkers = [] if (element.dataset.markers != null) { const markers = JSON.parse(element.dataset.markers) - processedMarkers = markers.map(marker => ({ + processedMarkers = markers.map((marker: string[]): { date: Date; label: string; } => ({ "date": new Date(marker[2]), "label": marker[1] })) diff --git a/app/javascript/src/checkbox-select-all.js b/app/javascript/src/checkbox-select-all.js deleted file mode 100644 index 924a65a9c..000000000 --- a/app/javascript/src/checkbox-select-all.js +++ /dev/null @@ -1,14 +0,0 @@ -export function bind() { - const elements = document.querySelectorAll("[data-action='checkbox-select-all']") - - elements.forEach((element) => element.removeAndAddEventListener("click", toggleCheckboxes)) -} - -function toggleCheckboxes() { - const parent = this.closest("[data-checkbox-group]") - - const checkboxes = parent.querySelectorAll("input[type='checkbox']") - const state = this.checked - - checkboxes.forEach(checkbox => checkbox.checked = state) -} diff --git a/app/javascript/src/checkbox-select-all.ts b/app/javascript/src/checkbox-select-all.ts new file mode 100644 index 000000000..edb073dd9 --- /dev/null +++ b/app/javascript/src/checkbox-select-all.ts @@ -0,0 +1,13 @@ +export function bind(): void { + const elements = document.querySelectorAll("[data-action='checkbox-select-all']") + + elements.forEach((element) => element.removeAndAddEventListener("click", toggleCheckboxes)) +} + +function toggleCheckboxes({ currentTarget }: { currentTarget: HTMLFormElement }): void { + const parent = currentTarget.closest("[data-checkbox-group]") + const checkboxes = Array.from(parent!.querySelectorAll("input[type='checkbox']")) as HTMLFormElement[] + const state = currentTarget.checked + + checkboxes.forEach(checkbox => checkbox.checked = state) +} diff --git a/app/javascript/src/comments.js b/app/javascript/src/comments.js deleted file mode 100644 index 95e1479b9..000000000 --- a/app/javascript/src/comments.js +++ /dev/null @@ -1,35 +0,0 @@ -import FetchRails from "@src/fetch-rails" - -export function bind() { - document.body.removeAndAddEventListener("click", getMoreComments) -} - -function getMoreComments(event) { - let eventTarget = event.target - if (eventTarget.dataset.action != "get-more-comments") eventTarget = event.target.closest("[data-action~='get-more-comments']") - if (!eventTarget) return - - const parent = event.target.closest("[data-role='comments']") - const button = parent.querySelector("[data-action~='get-more-comments']") - const buttonParent = parent.querySelector("[data-role~='load-more']") - const id = parent.dataset.id - const page = parseInt(parent.dataset.page) - const commentsCount = parseInt(parent.dataset.commentsCount) - const initialText = button.innerText - - if (parent.dataset.loading === "true") return - - parent.loading = "true" - button.innerText = "Loading..." - button.disabled = true - - new FetchRails(`/comments/${id}/${page + 1}`).get().then(data => { - buttonParent.insertAdjacentHTML("beforeBegin", data) - - if (parent.querySelectorAll("[data-comment]").length >= commentsCount) buttonParent.remove() - }).finally(() => { - button.innerText = initialText - parent.dataset.page = page + 1 - button.disabled = false - }) -} diff --git a/app/javascript/src/comments.ts b/app/javascript/src/comments.ts new file mode 100644 index 000000000..69af7dfa5 --- /dev/null +++ b/app/javascript/src/comments.ts @@ -0,0 +1,36 @@ +import FetchRails from "@src/fetch-rails" + +export function bind(): void { + document.body.removeAndAddEventListener("click", getMoreComments) +} + +function getMoreComments(event: Event): void { + let eventTarget = event.target as HTMLElement | null | undefined + if (eventTarget?.dataset.action != "get-more-comments") eventTarget = eventTarget?.closest("[data-action~='get-more-comments']") + if (!eventTarget) return + + const parent = eventTarget.closest("[data-role='comments']") as HTMLElement + const button = parent.querySelector("[data-action~='get-more-comments']") as HTMLButtonElement + const buttonParent = parent.querySelector("[data-role~='load-more']") + const id = parent.dataset.id + const page = parseInt(parent.dataset.page || "0") + const commentsCount = parseInt(parent.dataset.commentsCount || "0") + const initialText = button.innerText || "" + + if (parent.dataset.loading === "true") return + + parent.dataset.loading = "true" + button.innerText = "Loading..." + button.disabled = true + + new FetchRails(`/comments/${id}/${page + 1}`).get().then(data => { + if (data) buttonParent!.insertAdjacentHTML("beforebegin", data) + + if (parent.querySelectorAll("[data-comment]").length >= commentsCount) buttonParent!.remove() + }).finally(() => { + button.innerText = initialText + parent.dataset.page = (page + 1).toString() + button.disabled = false + parent.dataset.loading = "false" + }) +} diff --git a/app/javascript/src/components/Notifications.svelte b/app/javascript/src/components/Notifications.svelte index 200d117f7..f27b0c20b 100644 --- a/app/javascript/src/components/Notifications.svelte +++ b/app/javascript/src/components/Notifications.svelte @@ -40,7 +40,7 @@ {#if loading} -
+
{/if} {#each $notifications as notification} diff --git a/app/javascript/src/components/editor/CodeMirror.svelte b/app/javascript/src/components/editor/CodeMirror.svelte index baedadbac..65a09b95d 100644 --- a/app/javascript/src/components/editor/CodeMirror.svelte +++ b/app/javascript/src/components/editor/CodeMirror.svelte @@ -1,13 +1,14 @@ @@ -236,7 +252,7 @@ - {#key $settings["word-wrap"]} + {#key $settings["word-wrap"] + $settings["highlight-trailing-whitespace"] + $settings["tooltip-hover-delay"] + $settings["rainbow-brackets"]} { currentSidebarTab = "wiki" await tick() @@ -276,7 +292,7 @@
{:else if loading}
-
+
{:else} diff --git a/app/javascript/src/components/editor/EditorActions.svelte b/app/javascript/src/components/editor/EditorActions.svelte index f3c4258a7..3dcc27f02 100644 --- a/app/javascript/src/components/editor/EditorActions.svelte +++ b/app/javascript/src/components/editor/EditorActions.svelte @@ -29,21 +29,23 @@ {#if !$isMobile} - - - +
+ - {#if $currentProject?.is_owner} - - {/if} - + {#if $currentProject?.is_owner} + + {/if} + + +
{:else} diff --git a/app/javascript/src/components/editor/EditorWikiSearch.svelte b/app/javascript/src/components/editor/EditorWikiSearch.svelte index 5ceb8c04d..3048a9347 100644 --- a/app/javascript/src/components/editor/EditorWikiSearch.svelte +++ b/app/javascript/src/components/editor/EditorWikiSearch.svelte @@ -76,7 +76,7 @@ {#if loading}
-
+
{/if}
diff --git a/app/javascript/src/components/editor/Empty.svelte b/app/javascript/src/components/editor/Empty.svelte index af049ac9e..5feff3b27 100644 --- a/app/javascript/src/components/editor/Empty.svelte +++ b/app/javascript/src/components/editor/Empty.svelte @@ -51,7 +51,7 @@ Create your first project - Check out a sample project + Check out a sample project {/if}
diff --git a/app/javascript/src/components/editor/ExpandableSnippet.svelte b/app/javascript/src/components/editor/ExpandableSnippet.svelte index c13166f6c..ae9c9f300 100644 --- a/app/javascript/src/components/editor/ExpandableSnippet.svelte +++ b/app/javascript/src/components/editor/ExpandableSnippet.svelte @@ -65,12 +65,13 @@
{#if expanded} -