From 0c8278820620b93424fa351e2a8ce98a194b6b5f Mon Sep 17 00:00:00 2001 From: Whiletruedoend Date: Sun, 30 Jun 2024 12:47:10 +0300 Subject: [PATCH 01/47] Docker update && captcha fix && gems --- Dockerfile | 34 +- Gemfile | 9 +- Gemfile.lock | 533 ------------------------- README-RU.md | 8 +- README.md | 8 +- app/views/layouts/application.html.erb | 2 +- docker-compose.yml | 31 +- vendor/gems/easy_captcha | 1 + 8 files changed, 58 insertions(+), 568 deletions(-) delete mode 100644 Gemfile.lock create mode 160000 vendor/gems/easy_captcha diff --git a/Dockerfile b/Dockerfile index c8b365c..8bde16d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,45 +25,55 @@ RUN apt-get update; apt-get install -y --no-install-recommends \ # This stage will be responsible for installing gems and npm packages FROM base AS dependencies -COPY Gemfile Gemfile.lock ./ +COPY Gemfile ./ + +# For EasyCaptcha install +#RUN git clone https://github.com/4point/easy_captcha /vendor/gems/easy_captcha +COPY ./vendor/gems/easy_captcha /vendor/gems/easy_captcha # Install gems (excluding test dependencies) -RUN bundle config set without "test" && \ - bundle install --jobs=3 --retry=3 +RUN gem install bundler -v 2.2.25 + +RUN bundle install --jobs=3 --retry=3 COPY package.json yarn.lock ./ # NodeJS & yarn install -RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash -RUN . ~/.nvm/nvm.sh && nvm install node && \ - yarn install --frozen-lockfile +RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash +RUN . ~/.nvm/nvm.sh && nvm install 22 && \ + yarn install # We're back at the base stage FROM base # Create a non-root user to run the app and own app-specific files -RUN adduser app # Switch to this user -USER app # We'll install the app in this directory -WORKDIR /home/app # Copy over gems from the dependencies stage COPY --from=dependencies /usr/local/bundle/ /usr/local/bundle/ # Copy over npm packages from the dependencies stage # Note that we have to use `--chown` here -COPY --chown=app --from=dependencies /node_modules/ node_modules/ + +COPY --from=dependencies /node_modules/ node_modules/ # Finally, copy over the code # This is where the .dockerignore file comes into play # Note that we have to use `--chown` here -COPY --chown=app . ./ +RUN mkdir -p /root/app +COPY . /root/app/ + +# For EasyCaptcha install +COPY --from=dependencies /vendor/gems/ /root/app/vendor/gems/ + + +WORKDIR /root/app # Install assets -# RUN cd /home/app && bundle exec rake webpacker:compile && bundle exec rake assets:precompile +RUN cd /root/app && bundle exec rake webpacker:compile && bundle exec rake assets:precompile # Listen port EXPOSE ${PORT} diff --git a/Gemfile b/Gemfile index 91820fc..bb776fa 100644 --- a/Gemfile +++ b/Gemfile @@ -7,11 +7,11 @@ ruby '2.7.7' gem 'activerecord', '>= 6.1.7.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '~> 7.0.4.2' +gem 'rails', '~> 7.0.8.1' # Use sqlite3 as the database for Active Record gem 'sqlite3', '>= 1.4' # Use Puma as the app server -gem 'puma', '>= 5.6.4' +gem 'puma', '>= 6.4.2' # Use SCSS for stylesheets gem 'sass-rails', '>= 6' # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacke @@ -82,7 +82,6 @@ gem 'addressable', '>= 2.8.0' gem 'betterlorem' gem 'devise' gem 'dry-initializer-rails' -gem 'easy_captcha', github: 'kopylovvlad/easy_captcha' gem 'enumerize' gem 'grape' gem 'image_processing', '>= 1.12.2' @@ -92,12 +91,14 @@ gem 'open-uri' gem 'pg' gem 'redcarpet' gem 'reverse_markdown' -gem 'rmagick', '~> 4.1.1' +gem 'rmagick' +gem 'easy_captcha', path: 'vendor/gems/easy_captcha' gem 'rollbar' gem 'rubyzip' gem 'simple_command' gem 'socksify', require: false # TCP through a SOCKS5 proxy gem 'telegram-bot' +gem 'rake', '~> 13.2.1' gem 'sidekiq' diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index a2df0ec..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,533 +0,0 @@ -GIT - remote: https://github.com/kopylovvlad/easy_captcha.git - revision: 43d99136b50f45a77beb027e8ac124d7b4f2aed2 - specs: - easy_captcha (0.6.5) - bundler (>= 1.1.0) - rails (>= 3.0.0) - rmagick (>= 2.13.1) - rspec-rails (>= 2.8.1) - simplecov (>= 0.3.8) - yard (>= 0.7.0) - -GEM - remote: https://rubygems.org/ - specs: - action_policy (0.6.4) - ruby-next-core (>= 0.14.0) - actioncable (7.0.4.2) - actionpack (= 7.0.4.2) - activesupport (= 7.0.4.2) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailbox (7.0.4.2) - actionpack (= 7.0.4.2) - activejob (= 7.0.4.2) - activerecord (= 7.0.4.2) - activestorage (= 7.0.4.2) - activesupport (= 7.0.4.2) - mail (>= 2.7.1) - net-imap - net-pop - net-smtp - actionmailer (7.0.4.2) - actionpack (= 7.0.4.2) - actionview (= 7.0.4.2) - activejob (= 7.0.4.2) - activesupport (= 7.0.4.2) - mail (~> 2.5, >= 2.5.4) - net-imap - net-pop - net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.0.4.2) - actionview (= 7.0.4.2) - activesupport (= 7.0.4.2) - rack (~> 2.0, >= 2.2.0) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.4.2) - actionpack (= 7.0.4.2) - activerecord (= 7.0.4.2) - activestorage (= 7.0.4.2) - activesupport (= 7.0.4.2) - globalid (>= 0.6.0) - nokogiri (>= 1.8.5) - actionview (7.0.4.2) - activesupport (= 7.0.4.2) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - active_storage_validations (1.0.3) - activejob (>= 5.2.0) - activemodel (>= 5.2.0) - activestorage (>= 5.2.0) - activesupport (>= 5.2.0) - activejob (7.0.4.2) - activesupport (= 7.0.4.2) - globalid (>= 0.3.6) - activemodel (7.0.4.2) - activesupport (= 7.0.4.2) - activerecord (7.0.4.2) - activemodel (= 7.0.4.2) - activesupport (= 7.0.4.2) - activestorage (7.0.4.2) - actionpack (= 7.0.4.2) - activejob (= 7.0.4.2) - activerecord (= 7.0.4.2) - activesupport (= 7.0.4.2) - marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (7.0.4.2) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - ast (2.4.2) - autoprefixer-rails (10.4.7.0) - execjs (~> 2) - bcrypt (3.1.18) - betterlorem (0.1.2) - bindex (0.8.1) - bootsnap (1.16.0) - msgpack (~> 1.2) - bootstrap-sass (3.4.1) - autoprefixer-rails (>= 5.2.1) - sassc (>= 2.0.0) - bootstrap-will_paginate (0.0.11) - will_paginate - brakeman (5.4.0) - builder (3.2.4) - byebug (11.1.3) - capybara (3.38.0) - addressable - matrix - mini_mime (>= 0.1.3) - nokogiri (~> 1.8) - rack (>= 1.6.0) - rack-test (>= 0.6.3) - regexp_parser (>= 1.5, < 3.0) - xpath (~> 3.2) - code-scanning-rubocop (0.6.1) - rubocop (~> 1.0) - colorize (0.8.1) - concurrent-ruby (1.2.0) - connection_pool (2.3.0) - crass (1.0.6) - date (3.3.3) - devise (4.8.1) - bcrypt (~> 3.0) - orm_adapter (~> 0.1) - railties (>= 4.1.0) - responders - warden (~> 1.2.3) - diff-lcs (1.5.0) - docile (1.4.0) - dotenv (2.8.1) - dotenv-rails (2.8.1) - dotenv (= 2.8.1) - railties (>= 3.2) - dry-core (1.0.0) - concurrent-ruby (~> 1.0) - zeitwerk (~> 2.6) - dry-inflector (1.0.0) - dry-initializer (3.1.1) - dry-initializer-rails (3.1.1) - dry-initializer (>= 2.4, < 4) - rails (> 3.0) - dry-logic (1.5.0) - concurrent-ruby (~> 1.0) - dry-core (~> 1.0, < 2) - zeitwerk (~> 2.6) - dry-types (1.7.0) - concurrent-ruby (~> 1.0) - dry-core (~> 1.0, < 2) - dry-inflector (~> 1.0, < 2) - dry-logic (>= 1.4, < 2) - zeitwerk (~> 2.6) - enumerize (2.5.0) - activesupport (>= 3.2) - erubi (1.12.0) - execjs (2.8.1) - factory_bot (6.2.1) - activesupport (>= 5.0.0) - factory_bot_rails (6.2.0) - factory_bot (~> 6.2.0) - railties (>= 5.0.0) - faker (3.1.1) - i18n (>= 1.8.11, < 2) - ffi (1.15.5) - font-awesome-rails (4.7.0.8) - railties (>= 3.2, < 8.0) - font-awesome-sass (5.15.1) - sassc (>= 1.11) - globalid (1.1.0) - activesupport (>= 5.0) - grape (1.7.0) - activesupport - builder - dry-types (>= 1.1) - mustermann-grape (~> 1.0.0) - rack (>= 1.3.0) - rack-accept - httpclient (2.8.3) - i18n (1.12.0) - concurrent-ruby (~> 1.0) - image_processing (1.12.2) - mini_magick (>= 4.9.5, < 5) - ruby-vips (>= 2.0.17, < 3) - importmap-rails (1.1.5) - actionpack (>= 6.0.0) - railties (>= 6.0.0) - jbuilder (2.11.5) - actionview (>= 5.0.0) - activesupport (>= 5.0.0) - jquery-rails (4.5.1) - rails-dom-testing (>= 1, < 3) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) - jquery-ui-rails (6.0.1) - railties (>= 3.2.16) - json (2.6.3) - libv8 (3.16.14.19) - libv8 (3.16.14.19-x86_64-linux) - listen (3.8.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.19.1) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.8.1) - mini_mime (>= 0.1.1) - net-imap - net-pop - net-smtp - marcel (1.0.2) - matrix (0.4.2) - method_source (1.0.0) - mini_magick (4.12.0) - mini_mime (1.1.2) - mini_portile2 (2.8.1) - minitest (5.17.0) - msgpack (1.6.0) - mustermann (3.0.0) - ruby2_keywords (~> 0.0.1) - mustermann-grape (1.0.2) - mustermann (>= 1.0.0) - net-http (0.3.2) - uri - net-imap (0.3.4) - date - net-protocol - net-pop (0.1.2) - net-protocol - net-protocol (0.2.1) - timeout - net-smtp (0.3.3) - net-protocol - nio4r (2.5.8) - nokogiri (1.14.1) - mini_portile2 (~> 2.8.0) - racc (~> 1.4) - nokogiri (1.14.1-x86_64-linux) - racc (~> 1.4) - open-uri (0.3.0) - stringio - time - uri - orm_adapter (0.5.0) - panolint (0.1.6) - brakeman (>= 4.8, < 6.0) - rubocop (>= 0.83, < 2.0) - rubocop-performance (~> 1.5) - rubocop-rails (~> 2.5) - rubocop-rake (~> 0.5) - rubocop-rspec (~> 2.0) - parallel (1.22.1) - parser (3.2.1.0) - ast (~> 2.4.1) - pg (1.4.5) - public_suffix (5.0.1) - puma (6.1.0) - nio4r (~> 2.0) - racc (1.6.2) - rack (2.2.6.2) - rack-accept (0.4.5) - rack (>= 0.4) - rack-mini-profiler (2.3.4) - rack (>= 1.2.0) - rack-proxy (0.7.6) - rack - rack-test (2.0.2) - rack (>= 1.3) - rails (7.0.4.2) - actioncable (= 7.0.4.2) - actionmailbox (= 7.0.4.2) - actionmailer (= 7.0.4.2) - actionpack (= 7.0.4.2) - actiontext (= 7.0.4.2) - actionview (= 7.0.4.2) - activejob (= 7.0.4.2) - activemodel (= 7.0.4.2) - activerecord (= 7.0.4.2) - activestorage (= 7.0.4.2) - activesupport (= 7.0.4.2) - bundler (>= 1.15.0) - railties (= 7.0.4.2) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.5.0) - loofah (~> 2.19, >= 2.19.1) - rails_sortable (1.5.0) - railties (7.0.4.2) - actionpack (= 7.0.4.2) - activesupport (= 7.0.4.2) - method_source - rake (>= 12.2) - thor (~> 1.0) - zeitwerk (~> 2.5) - rainbow (3.1.1) - rake (13.0.6) - rb-fsevent (0.11.2) - rb-inotify (0.10.1) - ffi (~> 1.0) - redcarpet (3.6.0) - redis (4.8.1) - redis-client (0.12.1) - connection_pool - ref (2.0.0) - regexp_parser (2.7.0) - responders (3.1.0) - actionpack (>= 5.2) - railties (>= 5.2) - reverse_markdown (2.1.1) - nokogiri - rexml (3.2.5) - rmagick (4.1.2) - rollbar (3.4.0) - rspec (3.12.0) - rspec-core (~> 3.12.0) - rspec-expectations (~> 3.12.0) - rspec-mocks (~> 3.12.0) - rspec-core (3.12.1) - rspec-support (~> 3.12.0) - rspec-expectations (3.12.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-mocks (3.12.3) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-rails (6.0.1) - actionpack (>= 6.1) - activesupport (>= 6.1) - railties (>= 6.1) - rspec-core (~> 3.11) - rspec-expectations (~> 3.11) - rspec-mocks (~> 3.11) - rspec-support (~> 3.11) - rspec-support (3.12.0) - rubocop (1.45.1) - json (~> 2.3) - parallel (~> 1.10) - parser (>= 3.2.0.0) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.24.1, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.26.0) - parser (>= 3.2.1.0) - rubocop-capybara (2.17.0) - rubocop (~> 1.41) - rubocop-performance (1.16.0) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) - rubocop-rails (2.17.4) - activesupport (>= 4.2.0) - rack (>= 1.1) - rubocop (>= 1.33.0, < 2.0) - rubocop-rake (0.6.0) - rubocop (~> 1.0) - rubocop-rspec (2.18.1) - rubocop (~> 1.33) - rubocop-capybara (~> 2.17) - ruby-next-core (0.15.3) - ruby-progressbar (1.11.0) - ruby-vips (2.1.4) - ffi (~> 1.12) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - sass-rails (6.0.0) - sassc-rails (~> 2.1, >= 2.1.1) - sassc (2.4.0) - ffi (~> 1.9) - sassc-rails (2.1.2) - railties (>= 4.0.0) - sassc (>= 2.0) - sprockets (> 3.0) - sprockets-rails - tilt - selenium-webdriver (4.8.0) - rexml (~> 3.2, >= 3.2.5) - rubyzip (>= 1.2.2, < 3.0) - websocket (~> 1.0) - semantic_range (3.0.0) - sidekiq (7.0.3) - concurrent-ruby (< 2) - connection_pool (>= 2.3.0) - rack (>= 2.2.4) - redis-client (>= 0.11.0) - simple_command (1.0.1) - simplecov (0.22.0) - docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) - simplecov-html (0.12.3) - simplecov_json_formatter (0.1.4) - socksify (1.7.1) - spring (4.1.1) - sprockets (4.2.0) - concurrent-ruby (~> 1.0) - rack (>= 2.2.4, < 4) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) - sprockets (>= 3.0.0) - sqlite3 (1.6.0) - mini_portile2 (~> 2.8.0) - sqlite3 (1.6.0-x86_64-linux) - stringio (3.0.5) - telegram-bot (0.15.7) - actionpack (>= 4.0, < 7.1) - activesupport (>= 4.0, < 7.1) - httpclient (~> 2.7) - therubyracer (0.12.3) - libv8 (~> 3.16.14.15) - ref - thor (1.2.1) - tilt (2.0.11) - time (0.2.1) - date - timeout (0.3.1) - turbo-rails (1.3.3) - actionpack (>= 6.0.0) - activejob (>= 6.0.0) - railties (>= 6.0.0) - turbolinks (5.2.1) - turbolinks-source (~> 5.2) - turbolinks-source (5.2.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (2.4.2) - uri (0.12.0) - warden (1.2.9) - rack (>= 2.0.9) - web-console (4.2.0) - actionview (>= 6.0.0) - activemodel (>= 6.0.0) - bindex (>= 0.4.0) - railties (>= 6.0.0) - webdrivers (5.2.0) - nokogiri (~> 1.6) - rubyzip (>= 1.3.0) - selenium-webdriver (~> 4.0) - webpacker (5.4.4) - activesupport (>= 5.2) - rack-proxy (>= 0.6.1) - railties (>= 5.2) - semantic_range (>= 2.3.0) - webrick (1.7.0) - websocket (1.2.9) - websocket-driver (0.7.5) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - will_paginate (3.3.1) - xpath (3.2.0) - nokogiri (~> 1.8) - yard (0.9.28) - webrick (~> 1.7.0) - zeitwerk (2.6.7) - -PLATFORMS - ruby - x86_64-linux - -DEPENDENCIES - action_policy - actionpack (>= 6.1.4.1) - active_storage_validations - activerecord (>= 6.1.7.1) - addressable (>= 2.8.0) - betterlorem - bootsnap (>= 1.4.4) - bootstrap-sass - bootstrap-will_paginate (~> 0.0.10) - byebug - capybara (>= 3.26) - code-scanning-rubocop - colorize - devise - dotenv-rails - dry-initializer-rails - easy_captcha! - enumerize - factory_bot_rails - faker - font-awesome-rails - font-awesome-sass (~> 5.15.1) - grape - image_processing (>= 1.12.2) - importmap-rails - jbuilder (>= 2.7) - jquery-rails - jquery-ui-rails - listen (~> 3.3) - mini_magick - net-http - nokogiri (>= 1.11.0.rc4) - open-uri - panolint - pg - puma (>= 5.6.4) - rack-mini-profiler (~> 2.0) - rails (~> 7.0.4.2) - rails_sortable - redcarpet - redis (~> 4.0) - reverse_markdown - rmagick (~> 4.1.1) - rollbar - rspec - rspec-rails - rubocop - rubocop-rails - rubocop-rspec - rubyzip - sass-rails (>= 6) - selenium-webdriver - sidekiq - simple_command - socksify - spring - sqlite3 (>= 1.4) - telegram-bot - therubyracer - turbo-rails - turbolinks (>= 5) - tzinfo-data - web-console (>= 4.1.0) - webdrivers - webpacker (>= 5.4.2) - will_paginate (~> 3.3.1) - -RUBY VERSION - ruby 2.7.7p221 - -BUNDLED WITH - 2.2.25 diff --git a/README-RU.md b/README-RU.md index ece1ddc..8cb79b6 100644 --- a/README-RU.md +++ b/README-RU.md @@ -93,19 +93,19 @@ P.S. Список последних изменений можно посмот * Настроить config/credentials.yml * Выполнить команду: ``` - docker-compose build web + docker build -t twilight . ``` * После успешной сборки образа, выполнить: ``` - docker-compose up web + docker-compose up twilight ``` * (Если нужно выполнить миграции, то): ``` - docker-compose run --rm web bin/rails db:migrate + docker-compose run --rm twilight bin/rails db:migrate ``` * Чтобы сделать себя админом и в контейнере изменять credentials.yml и database.yml, нужно войти в контейнер командой: ``` - docker exec -it twilight-web-1 /bin/bash + docker exec -it twilight /bin/bash ``` Теперь сайт будет доступен по адресу: `http://localhost:3080` diff --git a/README.md b/README.md index f1cd9ed..35d7b3a 100644 --- a/README.md +++ b/README.md @@ -93,19 +93,19 @@ Analyzing various blog sites and the platforms adjacent to them (where the repos * Configure config/credentials.yml * Run: ``` - docker-compose build web + docker build -t twilight . ``` * After a successful build, run: ``` - docker-compose up web + docker-compose up twilight ``` * (If you need make migrations, use): ``` - docker-compose run --rm web bin/rails db:migrate + docker-compose run --rm twilight bin/rails db:migrate ``` * To make yourself an admin and configure (credentials.yml and database.yml) server, you need to enter the container with the command: ``` - docker exec -it twilight-web-1 /bin/bash + docker exec -it twilight /bin/bash ``` The site will now be available at: `http://localhost:3080` diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 979e0a7..4d80124 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -17,7 +17,7 @@ diff --git a/docker-compose.yml b/docker-compose.yml index 03fc4e3..574a3bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,15 +19,11 @@ services: volumes: - .db-data:/var/lib/postgresql/pgdata - web: - build: . - #command: > - # /bin/bash -c - # "rm -f /tmp/server.pid - # && bin/rails db:create - # && rails db:migrate - # && bundle exec rake assets:precompile - # && bundle exec rails server -e ${ENV} -b 0.0.0.0 -p ${PORT} -P /tmp/server.pid" + twilight: + #build: . + image: twilight:latest + container_name: twilight + restart: on-failure ports: - "${PORT}:${PORT}" env_file: .env @@ -39,13 +35,28 @@ services: - POSTGRES_DB=${POSTGRES_DB} - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + # Traefik example +# labels: +# - "traefik.enable=true" +# - "traefik.port=${PORT}" + +# - "traefik.http.services.twilight.loadbalancer.server.port=${PORT}" +# - "traefik.http.routers.twilight.rule=Host(`twilight.example.com`)" # change this +# - "traefik.http.routers.twilight.entrypoints=http" +# - "traefik.http.routers.twilight.middlewares=twilight-secure-redirect" + +# - "traefik.http.middlewares.twilight-secure-redirect.redirectscheme.scheme=https" + +# - "traefik.http.routers.twilight-secure.rule=Host(`twilight.example.com`)" # change this +# - "traefik.http.routers.twilight-secure.entrypoints=https" +# - "traefik.http.routers.twilight-secure.tls=true" depends_on: - db # - redis links: - db:db volumes: - - .:/home/app + - .:/root/app/ tty: true stdin_open: true diff --git a/vendor/gems/easy_captcha b/vendor/gems/easy_captcha new file mode 160000 index 0000000..929c49d --- /dev/null +++ b/vendor/gems/easy_captcha @@ -0,0 +1 @@ +Subproject commit 929c49d371665a3fb0746db75c84725b3c24e267 From c8ec74f98358e4cd321eea008ed8b2f9645cd463 Mon Sep 17 00:00:00 2001 From: Whiletruedoend Date: Fri, 5 Jul 2024 00:29:43 +0300 Subject: [PATCH 02/47] Docker update && themes for feed --- .env | 2 +- .gitignore | 3 +- Dockerfile | 4 +- app/assets/stylesheets/base.scss | 49 +++++++- .../default (Dark feed)_theme.scss | 108 ++++++++++++++++++ app/assets/stylesheets/default_theme.scss | 16 ++- app/assets/stylesheets/gruvbox_theme.scss | 40 ++++--- app/assets/stylesheets/ruby_theme.scss | 36 ++++-- app/assets/stylesheets/twilight_theme.scss | 44 ++++--- docker-compose.yml | 26 +++-- 10 files changed, 270 insertions(+), 58 deletions(-) create mode 100644 app/assets/stylesheets/default (Dark feed)_theme.scss diff --git a/.env b/.env index 980bf6c..f200841 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ RAILS_ENV=development PORT=3080 -POSTGRES_HOST="db" +POSTGRES_HOST="twilight_db" POSTGRES_PORT=5432 POSTGRES_DB=twilight_development POSTGRES_USER=postgres diff --git a/.gitignore b/.gitignore index 63f43b2..36afee1 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,5 @@ yarn-debug.log* .yarn-integrity config/database.yml Gemfile.lock -.env \ No newline at end of file +.env +gemset.nix diff --git a/Dockerfile b/Dockerfile index 8bde16d..469d897 100644 --- a/Dockerfile +++ b/Dockerfile @@ -58,12 +58,12 @@ COPY --from=dependencies /usr/local/bundle/ /usr/local/bundle/ # Copy over npm packages from the dependencies stage # Note that we have to use `--chown` here -COPY --from=dependencies /node_modules/ node_modules/ +COPY --from=dependencies /node_modules/ /root/app/node_modules/ # Finally, copy over the code # This is where the .dockerignore file comes into play # Note that we have to use `--chown` here -RUN mkdir -p /root/app +#RUN mkdir -p /root/app COPY . /root/app/ # For EasyCaptcha install diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 1e4f45a..1f55a0a 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -85,7 +85,10 @@ $color-black: #000000; $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, $color-feed-menu-active, $color-feed-tags-list, $color-feed-tags-list-item, $color-feed-tags-border,$color-feed-link, - $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text){ + $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, + $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, + $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, + $color-feed-filters-badge-font, $color-feed-badge-bg){ @include dropzone($color-border, $color-form-textarea-bg); code{ /* #cc241d */ @@ -223,7 +226,14 @@ $color-black: #000000; color: $font; } - .nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs li a:hover, .nav-tabs > li.active > a:focus{ color: $color-link-light; background-color: $color-nav-focus-bg; } + .nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs li a:hover, .nav-tabs > li.active > a:focus{ + color: $color-link-light; + background-color: $color-nav-focus-bg; + border-left-color: $font; + border-top-color: $font; + border-right-color: $font; + } + .nav-tabs { border-bottom: 1px solid $font;} .tab-content{ font-family: 'Roboto', sans-serif; @@ -552,7 +562,11 @@ $color-black: #000000; transition: transform .3s cubic-bezier(.2,0,.2,1) !important; padding-right: 5px; } - + + .dropdown-menu{ + color: $color-feed-font; + background-color: $color-feed-bg; + } /* WTF */ .tooltip { @@ -966,10 +980,22 @@ $color-black: #000000; overflow: scroll; overflow-x: hidden; -webkit-overflow-scrolling: touch; + + a:hover { + color: $color-feed-tags-list-item; + background-color: $color-feed-tags-list; } + } .list-group-item{ margin-top: .5rem; + color: $color-feed-tags-list-item; + background-color: $color-feed-filters-bg; + border-color: $color-feed-filters-border; + .badge{ + color: $color-feed-filters-badge-font; + background-color: $color-feed-badge-bg; + } } .filters-group{ @@ -982,6 +1008,13 @@ $color-black: #000000; font-size: 0; white-space: nowrap; vertical-align: middle; + .btn{ + color: $color-feed-filters-search-font; + background-color: $color-feed-filters-search-bg; + } + .btn-primary{ + border-color: $color-feed-filters-search-border; + } } } .btn-danger{ @@ -995,6 +1028,16 @@ $color-black: #000000; display: none; } } + .form-control{ + color: $color-feed-filters-font; + background-color: $color-feed-filters-bg; + border-color: $color-feed-filters-border; + } + .input-group-addon{ + color: $color-feed-filters-icon; + background-color: $color-feed-filters-bg2; + border-color: $color-feed-filters-border; + } } /* feeds/right-bar */ diff --git a/app/assets/stylesheets/default (Dark feed)_theme.scss b/app/assets/stylesheets/default (Dark feed)_theme.scss new file mode 100644 index 0000000..c2e47b6 --- /dev/null +++ b/app/assets/stylesheets/default (Dark feed)_theme.scss @@ -0,0 +1,108 @@ +@import "base"; + +$bg: #181a1b; + +$color-link-light: #22c6c6; +$color-link: #10A0A0; +$color-link-dark: #0c6a6a; + +$color-avatar-bg: #3E5A68; +$color-avatar-border: #FBFBFB; + +$color-privacy-registered-p: #1375b2; +$color-privacy-registered: #5cb85c; +$color-privacy-me-p: #df4655; +$color-privacy-me: #4e518b; + +$color-wrapper: #515151; + +$color-border: #555555; +$color-nav-focus-bg: #3939398c; +$color-gray: #2E2F30; +$color-form-textarea-bg: #222426; + +$color-container-bg: #121414; + +$color-scrollbar-light: #454a4d; +$color-scrollbar-dark: #202324; + +$color-darkpurple: #31303b; +$color-button-bg: #306593; +$color-button-text: #ffffff; + +$color-checkbox-bg: #1f2123; +$color-checkbox-border: #454646; +$color-checkbox-checked-bg: #1d2021; +$color-checkbox-checked-border: #bdae93; + +$color-checkbox-channels-bg: #5cb85c; + +$color-checkbox-text-checked: #6af68a; +$color-checkbox-process_checking: #a1bfa1; +$color-checkbox-unchecked-bg: #631119; +$color-checkbox-text-unchecked: #df4655; +$color-checkbox-focus: #1b1b1c; + +$color-feed-bg: #181a1b; +$color-feed-bg2: #222426; +$color-feed-font: #f0f0f0; +$color-feed-icon: #3E5A68; /* #e0e0e0; */ +$color-feed-time: #909399; +$color-feed-date: #e6e6e6; + +$color-feed-link: #b3b3b3; + +$color-feed-menu-active: #0c6a6a; + +$color-feed-tags-list: #1d2021; +$color-feed-tags-list-item: #b3b3b3; +$color-feed-tags-border: #454646; + +$color-feed-filters-search-bg: #3E5A68; +$color-feed-filters-search-border: #3E5A68; +$color-feed-filters-search-font: #f0f0f0; +$color-feed-filters-icon: #f0f0f0; +$color-feed-filters-bg: #181a1b; +$color-feed-filters-bg2: #1d2021; +$color-feed-filters-font: #f0f0f0; +$color-feed-filters-border: #1d2021; +$color-feed-filters-badge-font: #f0f0f0; +$color-feed-badge-bg: #454646; + +$color-alert-danger: #222426; +$color-alert-danger-text: #df4655; +$color-alert-success: #222426; +$color-alert-success-text: #a1bfa1; + +$color-table-th: #22c6c6; +$color-table-th-border: #10A0A0; +$color-table-td: #10A0A0; +$color-table-tr-hover: #FBFBFB; + +$color-category-default: #10A0A0; + +$color-code-bg: #222426; + +$font: #f0f0f0; +$font-link: #b3b3b3; + +$color-white: #ffffff; +$color-black: #000000; + +@include base($bg, $color-darkpurple, $color-code-bg, + $font, $font-link, $color-white, $color-black, + $color-link-light,$color-link,$color-link-dark,$color-avatar-bg,$color-avatar-border, + $color-checkbox-channels-bg, $color-category-default, + $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, + $color-checkbox-focus, $color-container-bg, $color-scrollbar-light, $color-scrollbar-dark, + $color-checkbox-bg, $color-checkbox-border, $color-checkbox-checked-bg, $color-checkbox-checked-border, + $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, + $color-checkbox-text-checked, $color-checkbox-process_checking, $color-checkbox-unchecked-bg, $color-checkbox-text-unchecked, + $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, + $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, + $color-feed-menu-active, $color-feed-tags-list, $color-feed-tags-list-item, $color-feed-tags-border, $color-feed-link, + $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, + $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, + $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, + $color-feed-filters-badge-font, $color-feed-badge-bg +); \ No newline at end of file diff --git a/app/assets/stylesheets/default_theme.scss b/app/assets/stylesheets/default_theme.scss index b60bd57..87579a4 100644 --- a/app/assets/stylesheets/default_theme.scss +++ b/app/assets/stylesheets/default_theme.scss @@ -58,6 +58,17 @@ $color-feed-tags-list: #ecf5ff; $color-feed-tags-list-item: #909399; $color-feed-tags-border: #e6e6e6; +$color-feed-filters-search-bg: #66b1ff; +$color-feed-filters-search-border: #66b1ff; +$color-feed-filters-search-font: #fff; +$color-feed-filters-icon: #555555; +$color-feed-filters-bg: #fff; +$color-feed-filters-bg2: #eeeeee; +$color-feed-filters-font: #555555; +$color-feed-filters-border: #ccc; +$color-feed-filters-badge-font: #fff; +$color-feed-badge-bg: #777777; + $color-alert-danger: #222426; $color-alert-danger-text: #df4655; $color-alert-success: #222426; @@ -90,5 +101,8 @@ $color-black: #000000; $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, $color-feed-menu-active, $color-feed-tags-list, $color-feed-tags-list-item, $color-feed-tags-border, $color-feed-link, - $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text + $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, + $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, + $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, + $color-feed-filters-badge-font, $color-feed-badge-bg ); \ No newline at end of file diff --git a/app/assets/stylesheets/gruvbox_theme.scss b/app/assets/stylesheets/gruvbox_theme.scss index 5a98d47..3054e93 100644 --- a/app/assets/stylesheets/gruvbox_theme.scss +++ b/app/assets/stylesheets/gruvbox_theme.scss @@ -24,7 +24,7 @@ $color-form-textarea-bg: #504945; $color-container-bg: #3c3836; $color-scrollbar-light: #3c3836; -$color-scrollbar-dark: #d65d0e; +$color-scrollbar-dark: #282828; $color-darkpurple: #31303b; $color-button-bg: #458588; @@ -43,20 +43,31 @@ $color-checkbox-unchecked-bg: #cc241d; $color-checkbox-text-unchecked: #fb4934; $color-checkbox-focus: #1d2021; -$color-feed-bg: #ffffff; -$color-feed-bg2: #eeeeee; -$color-feed-font: #000000; -$color-feed-icon: #9cccfd; /* #e0e0e0; */ -$color-feed-time: #bbb; -$color-feed-date: #66b1ff; +$color-feed-bg: #282828; +$color-feed-bg2: #504945; +$color-feed-font: #ebdbb2; +$color-feed-icon: #689d6a; /* #e0e0e0; */ +$color-feed-time: #fbf1c7; +$color-feed-date: #fbf1c7; -$color-feed-link: #337ab7; +$color-feed-link: #a89984; -$color-feed-menu-active: #409eff; +$color-feed-menu-active: #d65d0e; -$color-feed-tags-list: #ecf5ff; -$color-feed-tags-list-item: #909399; -$color-feed-tags-border: #e6e6e6; +$color-feed-tags-list: #1d2021; +$color-feed-tags-list-item: #928374; +$color-feed-tags-border: #454646; + +$color-feed-filters-search-bg: #689d6a; +$color-feed-filters-search-border: #689d6a; +$color-feed-filters-search-font: #fbf1c7; +$color-feed-filters-icon: #fbf1c7; +$color-feed-filters-bg: #282828; +$color-feed-filters-bg2: #1d2021; +$color-feed-filters-font: #fbf1c7; +$color-feed-filters-border: #1d2021; +$color-feed-filters-badge-font: #fbf1c7; +$color-feed-badge-bg: #928374; $color-alert-danger: #ad3b14; $color-alert-danger-text: #fcf4cd; @@ -90,5 +101,8 @@ $color-black: #000000; $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, $color-feed-menu-active, $color-feed-tags-list, $color-feed-tags-list-item, $color-feed-tags-border, $color-feed-link, - $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text + $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, + $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, + $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, + $color-feed-filters-badge-font, $color-feed-badge-bg ); \ No newline at end of file diff --git a/app/assets/stylesheets/ruby_theme.scss b/app/assets/stylesheets/ruby_theme.scss index 5c6f526..6369449 100644 --- a/app/assets/stylesheets/ruby_theme.scss +++ b/app/assets/stylesheets/ruby_theme.scss @@ -1,9 +1,9 @@ @import "base"; -$bg: #f2f2f4; +$bg: #eeeeee; $color-link-light: #bd6d76; -$color-link: #df4655; +$color-link: #d72134; $color-link-dark: #631119; $color-avatar-bg: #bd6d76; @@ -11,17 +11,17 @@ $color-avatar-border: #1b1b1c; $color-privacy-registered-p: #1375b2; $color-privacy-registered: #5cb85c; -$color-privacy-me-p: #df4655; +$color-privacy-me-p: #d72134; $color-privacy-me: #5659e0; $color-wrapper: #515151; $color-border: #555555; -$color-nav-focus-bg: #fafbfc; +$color-nav-focus-bg: #d3d3d3; $color-gray: #2E2F30; $color-form-textarea-bg: #f6f8fa; -$color-container-bg: #f1f8ff; +$color-container-bg: #d3d3d3; $color-scrollbar-light: #bbc1c7; $color-scrollbar-dark: #93989e; @@ -30,17 +30,17 @@ $color-darkpurple: #31303b; $color-button-bg: #306593; $color-button-text: #f0f0f0; -$color-checkbox-bg: #fafbfc; +$color-checkbox-bg: #dfdfdf; $color-checkbox-border: #000000; -$color-checkbox-checked-bg: #fafbfc; +$color-checkbox-checked-bg: #dfdfdf; $color-checkbox-checked-border: #000000; $color-checkbox-channels-bg: #5cb85c; -$color-checkbox-text-checked: #6af68a; +$color-checkbox-text-checked: #5cb85c; $color-checkbox-process_checking: #a1bfa1; -$color-checkbox-unchecked-bg: #df4655; -$color-checkbox-text-unchecked: #df4655; +$color-checkbox-unchecked-bg: #d72134; +$color-checkbox-text-unchecked: #d72134; $color-checkbox-focus: #1b1b1c; $color-feed-bg: #ffffff; @@ -58,6 +58,17 @@ $color-feed-tags-list: #ecf5ff; $color-feed-tags-list-item: #909399; $color-feed-tags-border: #e6e6e6; +$color-feed-filters-search-bg: #66b1ff; +$color-feed-filters-search-border: #66b1ff; +$color-feed-filters-search-font: #fff; +$color-feed-filters-icon: #555555; +$color-feed-filters-bg: #fff; +$color-feed-filters-bg2: #eeeeee; +$color-feed-filters-font: #555555; +$color-feed-filters-border: #ccc; +$color-feed-filters-badge-font: #fff; +$color-feed-badge-bg: #777777; + $color-alert-danger: #f2dede; $color-alert-danger-text: #a94442; $color-alert-success: #a1bfa1; @@ -90,5 +101,8 @@ $color-black: #000000; $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, $color-feed-menu-active, $color-feed-tags-list, $color-feed-tags-list-item, $color-feed-tags-border, $color-feed-link, - $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text + $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, + $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, + $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, + $color-feed-filters-badge-font, $color-feed-badge-bg ); \ No newline at end of file diff --git a/app/assets/stylesheets/twilight_theme.scss b/app/assets/stylesheets/twilight_theme.scss index 6ce51e0..577a5d0 100644 --- a/app/assets/stylesheets/twilight_theme.scss +++ b/app/assets/stylesheets/twilight_theme.scss @@ -43,20 +43,31 @@ $color-checkbox-unchecked-bg: #631119; $color-checkbox-text-unchecked: #df4655; $color-checkbox-focus: #1b1b1c; -$color-feed-bg: #ffffff; -$color-feed-bg2: #eeeeee; -$color-feed-font: #000000; -$color-feed-icon: #9cccfd; /* #e0e0e0; */ -$color-feed-time: #bbb; -$color-feed-date: #66b1ff; - -$color-feed-link: #337ab7; - -$color-feed-menu-active: #409eff; - -$color-feed-tags-list: #ecf5ff; -$color-feed-tags-list-item: #909399; -$color-feed-tags-border: #e6e6e6; +$color-feed-bg: #0d1117; +$color-feed-bg2: #161b22; +$color-feed-font: #f0f0f0; +$color-feed-icon: #3E5A68; /* #e0e0e0; */ +$color-feed-time: #f0f0f0; +$color-feed-date: #4683c8; + +$color-feed-link: #b3b3b3; + +$color-feed-menu-active: #2e6aaf; + +$color-feed-tags-list: #1d2021; +$color-feed-tags-list-item: #bdae93; +$color-feed-tags-border: #454646; + +$color-feed-filters-search-bg: #3E5A68; +$color-feed-filters-search-border: #3E5A68; +$color-feed-filters-search-font: #f0f0f0; +$color-feed-filters-icon: #b3b3b3; +$color-feed-filters-bg: #0d1117; +$color-feed-filters-bg2: #1b1b1c; +$color-feed-filters-font: #f0f0f0; +$color-feed-filters-border: #b3b3b3; +$color-feed-filters-badge-font: #f0f0f0; +$color-feed-badge-bg: #bdae93; $color-alert-danger: #13171d; $color-alert-danger-text: #df4655; @@ -90,5 +101,8 @@ $color-black: #000000; $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, $color-feed-menu-active, $color-feed-tags-list, $color-feed-tags-list-item, $color-feed-tags-border, $color-feed-link, - $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text + $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, + $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, + $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, + $color-feed-filters-badge-font, $color-feed-badge-bg ); \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 574a3bb..cdf962e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,23 +1,22 @@ -version: '3.4' +version: '3.8' services: # redis: # image: "redis:7-alpine" -# ports: -# - 6379 # volumes: # - ./tmp/redis_data:/var/lib/redis/data +# networks: +# - twilightnet - db: + twilight_db: image: postgres - ports: - - ${POSTGRES_PORT}:${POSTGRES_PORT} - expose: - - ${POSTGRES_PORT} + container_name: twilight_db env_file: - .env volumes: - .db-data:/var/lib/postgresql/pgdata + networks: + - twilightnet twilight: #build: . @@ -51,15 +50,20 @@ services: # - "traefik.http.routers.twilight-secure.entrypoints=https" # - "traefik.http.routers.twilight-secure.tls=true" depends_on: - - db + - twilight_db # - redis links: - - db:db + - twilight_db:twilight_db volumes: - - .:/root/app/ + - ./config:/root/app/config tty: true stdin_open: true + networks: + - twilightnet +networks: + twilightnet: + external: false volumes: db-data: external: false \ No newline at end of file From 0eaf053e981e0aed89e34ac105de76589cba4d79 Mon Sep 17 00:00:00 2001 From: Whiletruedoend Date: Fri, 5 Jul 2024 21:50:09 +0300 Subject: [PATCH 03/47] OpenGraph support && fixes --- Dockerfile | 2 +- Gemfile | 6 +- .../default (Dark feed)_theme.scss | 65 +------------- app/controllers/application_controller.rb | 7 ++ app/controllers/channels_controller.rb | 6 ++ app/controllers/posts_controller.rb | 90 +++++++++++++++++-- .../users/registrations_controller.rb | 12 +++ app/controllers/users/sessions_controller.rb | 6 ++ app/helpers/posts_helper.rb | 6 +- app/models/category.rb | 2 +- app/models/tag.rb | 2 +- app/services/platform/send_post_to_matrix.rb | 8 +- .../platform/send_post_to_telegram.rb | 5 +- app/services/platform/update_matrix_posts.rb | 5 +- .../platform/update_telegram_posts.rb | 5 +- app/services/send_post_to_platforms.rb | 8 +- app/services/update_post_messages.rb | 7 +- app/views/layouts/application.html.erb | 2 +- app/views/layouts/clear.html.erb | 2 +- app/views/posts/rss.builder | 2 +- config/credentials.yml | 4 +- config/initializers/meta_tags.rb | 46 ++++++++++ docker-compose.yml | 15 ++-- .../platform/send_post_to_telegram_spec.rb | 2 +- spec/services/send_post_to_platforms_spec.rb | 2 +- update_log.md | 11 +++ 26 files changed, 218 insertions(+), 110 deletions(-) create mode 100644 config/initializers/meta_tags.rb diff --git a/Dockerfile b/Dockerfile index 469d897..96f337d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list RUN apt-get update; apt-get install -y --no-install-recommends \ - libpng-dev libjpeg-dev libtiff-dev \ + libpng-dev libjpeg-dev libtiff-dev ffmpeg \ tzdata libv8-dev imagemagick libmagickwand-dev \ libpq-dev libffi-dev \ postgresql-client sqlite3 \ diff --git a/Gemfile b/Gemfile index bb776fa..0d6c6cd 100644 --- a/Gemfile +++ b/Gemfile @@ -82,6 +82,7 @@ gem 'addressable', '>= 2.8.0' gem 'betterlorem' gem 'devise' gem 'dry-initializer-rails' +gem 'easy_captcha', path: 'vendor/gems/easy_captcha' gem 'enumerize' gem 'grape' gem 'image_processing', '>= 1.12.2' @@ -89,16 +90,16 @@ gem 'mini_magick' gem 'nokogiri', '>= 1.11.0.rc4' gem 'open-uri' gem 'pg' +gem 'rake', '~> 13.2.1' gem 'redcarpet' gem 'reverse_markdown' gem 'rmagick' -gem 'easy_captcha', path: 'vendor/gems/easy_captcha' gem 'rollbar' gem 'rubyzip' +gem 'ruby-vips' gem 'simple_command' gem 'socksify', require: false # TCP through a SOCKS5 proxy gem 'telegram-bot' -gem 'rake', '~> 13.2.1' gem 'sidekiq' @@ -108,6 +109,7 @@ gem 'font-awesome-rails' gem 'font-awesome-sass', '~> 5.15.1' gem 'jquery-rails' gem 'jquery-ui-rails' +gem 'meta-tags' gem 'rails_sortable' gem 'therubyracer', platforms: :ruby gem 'will_paginate', '~> 3.3.1' diff --git a/app/assets/stylesheets/default (Dark feed)_theme.scss b/app/assets/stylesheets/default (Dark feed)_theme.scss index c2e47b6..f3627bc 100644 --- a/app/assets/stylesheets/default (Dark feed)_theme.scss +++ b/app/assets/stylesheets/default (Dark feed)_theme.scss @@ -1,47 +1,4 @@ -@import "base"; - -$bg: #181a1b; - -$color-link-light: #22c6c6; -$color-link: #10A0A0; -$color-link-dark: #0c6a6a; - -$color-avatar-bg: #3E5A68; -$color-avatar-border: #FBFBFB; - -$color-privacy-registered-p: #1375b2; -$color-privacy-registered: #5cb85c; -$color-privacy-me-p: #df4655; -$color-privacy-me: #4e518b; - -$color-wrapper: #515151; - -$color-border: #555555; -$color-nav-focus-bg: #3939398c; -$color-gray: #2E2F30; -$color-form-textarea-bg: #222426; - -$color-container-bg: #121414; - -$color-scrollbar-light: #454a4d; -$color-scrollbar-dark: #202324; - -$color-darkpurple: #31303b; -$color-button-bg: #306593; -$color-button-text: #ffffff; - -$color-checkbox-bg: #1f2123; -$color-checkbox-border: #454646; -$color-checkbox-checked-bg: #1d2021; -$color-checkbox-checked-border: #bdae93; - -$color-checkbox-channels-bg: #5cb85c; - -$color-checkbox-text-checked: #6af68a; -$color-checkbox-process_checking: #a1bfa1; -$color-checkbox-unchecked-bg: #631119; -$color-checkbox-text-unchecked: #df4655; -$color-checkbox-focus: #1b1b1c; +@import "default_theme"; $color-feed-bg: #181a1b; $color-feed-bg2: #222426; @@ -69,26 +26,6 @@ $color-feed-filters-border: #1d2021; $color-feed-filters-badge-font: #f0f0f0; $color-feed-badge-bg: #454646; -$color-alert-danger: #222426; -$color-alert-danger-text: #df4655; -$color-alert-success: #222426; -$color-alert-success-text: #a1bfa1; - -$color-table-th: #22c6c6; -$color-table-th-border: #10A0A0; -$color-table-td: #10A0A0; -$color-table-tr-hover: #FBFBFB; - -$color-category-default: #10A0A0; - -$color-code-bg: #222426; - -$font: #f0f0f0; -$font-link: #b3b3b3; - -$color-white: #ffffff; -$color-black: #000000; - @include base($bg, $color-darkpurple, $color-code-bg, $font, $font-link, $color-white, $color-black, $color-link-light,$color-link,$color-link-dark,$color-avatar-bg,$color-avatar-border, diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 27841a3..dc62752 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,6 +4,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :set_locale + before_action :set_tags before_action :configure_permitted_parameters, if: :devise_controller? # reset captcha code after each request for security after_action :reset_last_captcha_code! @@ -16,6 +17,12 @@ class ApplicationController < ActionController::Base protected + def set_tags + set_meta_tags(title: 'Notes', + description: 'My useful notes', + keywords: 'Twilight, Notes') + end + def current_post @current_post ||= Post.find(params[:id]) end diff --git a/app/controllers/channels_controller.rb b/app/controllers/channels_controller.rb index c255b7c..10fd9b8 100644 --- a/app/controllers/channels_controller.rb +++ b/app/controllers/channels_controller.rb @@ -3,6 +3,12 @@ class ChannelsController < ApplicationController before_action :authenticate_user! + def set_tags + set_meta_tags(title: 'Channels', + description: 'Manage your channels', + keywords: 'Twilight, Notes, channels') + end + def edit authorize! current_channel, to: :update? end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index be4bb8e..0fc4deb 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -10,6 +10,59 @@ class PostsController < ApplicationController before_action :authenticate_user!, except: except_pages + def set_tags + if params['action'].nil? || params['action'] == 'index' + set_meta_tags(title: 'Posts', + description: 'Browse all posts', + keywords: 'Twilight, Notes, posts') + elsif params['action'] == 'feed' + set_meta_tags(title: 'Feed', + description: 'Twitter-style posts', + keywords: 'Twilight, Notes, feed') + elsif params['action'] == 'new' + set_meta_tags(title: 'Create post', + description: 'Create your posts', + keywords: 'Twilight, Notes, posts') + end + end + + def set_tags_post(current_post) + img_preview = current_post.content_attachments&.find{ |a| a.image? } + img_preview_url = img_preview.present? ? "#{request.base_url}#{rails_blob_path(img_preview, only_path: true)}" : "" + + video_preview = current_post.content_attachments&.find{ |a| a.video? } + video_preview_url = video_preview.present? ? "#{request.base_url}#{rails_blob_path(video_preview, only_path: true)}" : "" + + title = current_post.title.present? ? current_post.title : "#{current_post.id} | #{Rails.configuration.credentials[:title]}" + category = Rails.configuration.credentials[:enable_categories] ? current_post.category&.name || "" : "" + + author = current_post.user.name.present? ? current_post.user.name : current_post.user.login + + if img_preview_url.present? + set_meta_tags(og: {image: img_preview_url}) + end + if video_preview_url.present? # Todo: add YouTube support + set_meta_tags(og: {video: video_preview_url}) + end + + set_meta_tags(title: title, + description: current_post.text, + keywords: current_post.active_tags.map { |t| t.tag.name }.join(", "), + og: { + title: title, + description: current_post.text, + type: "website", + url: request.original_url + }, + article: { + published_time: current_post.created_at, + modified_time: current_post.updated_at, + section: category + }, + author: author + ) + end + def index user_post = Post.get_posts(params, current_user) if user_post.present? && params.key?(:tags) @@ -23,6 +76,8 @@ def index def show authorize! current_post + + set_tags_post(current_post) end def edit @@ -32,7 +87,7 @@ def edit def update authorize! current_post - if current_post.update(privacy: (posts_params[:post][:privacy] || 2)) + if current_post.update(privacy: posts_params[:post][:privacy] || 2) tags = (params[:tags].present? ? params[:tags].to_unsafe_h : {}) @@ -53,9 +108,9 @@ def update if posts_params[:post][:category_name].present? cat = current_user.categories.find_by(name: posts_params[:post][:category_name]) - current_post.update(category: (cat.presence || Category.create!(user: current_user, - name: posts_params[:post][:category_name], - color: posts_params[:post][:category_color]))) + current_post.update(category: cat.presence || Category.create!(user: current_user, + name: posts_params[:post][:category_name], + color: posts_params[:post][:category_color])) # We need category owned by user checking? elsif posts_params[:post][:category_name].blank? && posts_params[:post][:category].present? if current_post.category_id != posts_params[:post][:category] @@ -65,19 +120,21 @@ def update current_post.update(category_id: nil) unless current_post.category_id.nil? end + base_url = request.base_url + channels_p = params['channels']&.to_unsafe_h if channels_p.present? channels_p.each do |k, v| if v.to_i == 1 # TODO: make it? Need2fix duplicate content when creating! # params["platforms"] = { k=>(v ? 1 : 0).to_s } - # SendPostToPlatforms.call(@post, params) + # SendPostToPlatforms.call(@post, base_url, params) else DeletePostMessages.call(current_post, k) end end end - UpdatePostMessages.call(current_post, posts_params) + UpdatePostMessages.call(current_post, base_url, posts_params) current_post.update(title: posts_params[:post][:title]) # ? redirect_to current_post @@ -90,6 +147,9 @@ def new authorize! current_user, to: :create_posts? @post = Post.new + set_meta_tags(title: 'Posts', + description: 'Create & share your posts', + keywords: 'Twilight, Notes, posts') end def create @@ -98,6 +158,7 @@ def create @post = Post.new(title: posts_params[:post][:title]) @post.user = current_user @post.privacy = posts_params.dig(:post, :privacy) || 2 + base_url = request.base_url if posts_params[:post][:category_name].present? cat = current_user.categories.find_by(name: posts_params[:post][:category_name]) @@ -129,7 +190,7 @@ def create tags.merge!(new_tags) if new_tags.any? tags.each { |tag| ItemTag.create!(item: @post, tag_id: tag[0], enabled: tag[1].to_i) } if tags.any? - SendPostToPlatforms.call(@post, posts_params) + SendPostToPlatforms.call(@post, base_url, posts_params) redirect_to @post else @@ -184,6 +245,11 @@ def feed Rails.logger.error("Failed bypass token from #{ip} at #{Time.now.utc.iso8601}") end + if params.dig("id").present? + post = @posts.find_by(id: params["id"]) + set_tags_post(post) if post.present? + end + respond_to do |format| format.html format.js @@ -205,6 +271,16 @@ def export def raw authorize! current_post, to: :show? + set_meta_tags(title: current_post.title, + description: current_post.text, + keywords: current_post.active_tags.map { |t| t.tag.name }.join(", "), + og: { + title: current_post.title, + description: current_post.text, + type: "website", + url: request.original_url + }) + @markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, no_intra_emphasis: false, fenced_code_blocks: false, disable_indented_code_blocks: true, autolink: false, tables: false, underline: false, highlight: false) diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index 6313b1b..4d02eb0 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -11,6 +11,18 @@ class RegistrationsController < Devise::RegistrationsController # super # end + def set_tags + if params['action'].nil? || params['action'] == 'new' + set_meta_tags(title: 'Sign up', + description: 'Sign up to view posts and manage preferences', + keywords: 'Twilight, Notes, signup') + elsif params['action'] == 'edit' + set_meta_tags(title: 'Profile', + description: 'Manage your profile', + keywords: 'Twilight, Notes, profile') + end + end + def after_sign_in_path_for(_resource) edit_user_path end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index d75139e..26b7751 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -6,6 +6,12 @@ class SessionsController < Devise::SessionsController # GET /resource/sign_in + def set_tags + set_meta_tags(title: 'Sign in', + description: 'Sign in to view posts and manage preferences', + keywords: 'Twilight, Notes, signin') + end + # POST /resource/sign_in def create if valid_captcha?(sessions_params[:user][:captcha]) diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index 9fc05ed..2adde55 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -1,10 +1,6 @@ # frozen_string_literal: true module PostsHelper - def host_link - "http://#{Rails.configuration.credentials[:host]}:#{Rails.configuration.credentials[:port]}" - end - def get_post_tags(post) tags = post.active_tags_names.join(' ') tags.presence || 'no tags' @@ -27,7 +23,7 @@ def post_link(post) end def get_full_attachment_link(att) - "#{host_link}#{url_for(att)}" + "#{request.base_url}#{url_for(att)}" end def display_attachments(post) diff --git a/app/models/category.rb b/app/models/category.rb index 715bbcd..e7cca1c 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -2,7 +2,7 @@ class Category < ApplicationRecord include RailsSortable::Model - set_sortable :sort # Indicate a sort column + set_sortable :sort # Indicate a sort column belongs_to :user has_many :posts, dependent: :nullify diff --git a/app/models/tag.rb b/app/models/tag.rb index a1a15b2..7b3a3c5 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -2,7 +2,7 @@ class Tag < ApplicationRecord include RailsSortable::Model - set_sortable :sort # Indicate a sort column + set_sortable :sort # Indicate a sort column has_many :item_tags diff --git a/app/services/platform/send_post_to_matrix.rb b/app/services/platform/send_post_to_matrix.rb index f580549..7b8bcaa 100644 --- a/app/services/platform/send_post_to_matrix.rb +++ b/app/services/platform/send_post_to_matrix.rb @@ -5,9 +5,11 @@ class Platform::SendPostToMatrix attr_accessor :post, :params, :channel_ids - def initialize(post, params, channel_ids) + def initialize(post, base_url, params, channel_ids) @params = params @post = post + @base_url = base_url + @channels = Channel.where(id: channel_ids).map do |channel| { id: channel.id, room: channel.room, matrix_token: channel.token, server: channel.options['server'] } @@ -77,7 +79,7 @@ def send_mx_content(content, text) options = channel_options(channel) if options[:onlylink] - post_link = "http://#{Rails.configuration.credentials[:host]}:#{Rails.configuration.credentials[:port]}/posts/#{@post.id}" + post_link = "#{@base_url}/posts/#{@post.id}" full_post_link = "#{post_link}" text = @post.title.present? ? "#{@post.title}

#{full_post_link}" : full_post_link.to_s end @@ -187,7 +189,7 @@ def channel_options(channel) end def send_mx_onlylink_post(channel, options) - post_link = "http://#{Rails.configuration.credentials[:host]}:#{Rails.configuration.credentials[:port]}/posts/#{@post.id}" + post_link = "#{@base_url}/posts/#{@post.id}" full_post_link = "#{post_link}" text = @post.title.present? ? "#{@post.title}
#{full_post_link}" : full_post_link.to_s diff --git a/app/services/platform/send_post_to_telegram.rb b/app/services/platform/send_post_to_telegram.rb index b5af4ba..53aeb9f 100644 --- a/app/services/platform/send_post_to_telegram.rb +++ b/app/services/platform/send_post_to_telegram.rb @@ -5,9 +5,10 @@ class Platform::SendPostToTelegram attr_accessor :post, :params, :channel_ids - def initialize(post, params, channel_ids) + def initialize(post, base_url, params, channel_ids) @params = params @post = post + @base_url = base_url @platform = Platform.find_by(title: 'telegram') @@ -212,7 +213,7 @@ def get_tg_bot(channel) def send_tg_onlylink_post(channel, options) bot = get_tg_bot(channel) - post_link = "http://#{Rails.configuration.credentials[:host]}:#{Rails.configuration.credentials[:port]}/posts/#{@post.id}" + post_link = "#{@base_url}/posts/#{@post.id}" full_post_link = "#{post_link}" text = @post.title.present? ? "#{@post.title}\n\n#{full_post_link}" : full_post_link diff --git a/app/services/platform/update_matrix_posts.rb b/app/services/platform/update_matrix_posts.rb index 3eaf273..e493e60 100644 --- a/app/services/platform/update_matrix_posts.rb +++ b/app/services/platform/update_matrix_posts.rb @@ -5,9 +5,10 @@ class Platform::UpdateMatrixPosts attr_accessor :post, :params - def initialize(post, params) + def initialize(post, base_url, params) @params = params @post = post + @base_url = base_url @title = post.title @content = params[:post][:content] @@ -35,7 +36,7 @@ def call server = platform_post.channel.options['server'] deleted_indexes = [] - del_att.each do |k, _v| + del_att.each_key do |k| attachment = platform_post[:identifier].select { |att| att['blob_signed_id'] == k } i = platform_post[:identifier].index { |x| attachment.include?(x) } method = "rooms/#{platform_post[:identifier][i]['room_id']}/redact/#{platform_post[:identifier][i]['event_id']}" diff --git a/app/services/platform/update_telegram_posts.rb b/app/services/platform/update_telegram_posts.rb index e661fae..bc98556 100644 --- a/app/services/platform/update_telegram_posts.rb +++ b/app/services/platform/update_telegram_posts.rb @@ -5,9 +5,10 @@ class Platform::UpdateTelegramPosts attr_accessor :post, :params - def initialize(post, params) + def initialize(post, base_url, params) @params = params @post = post + @base_url = base_url @platform = Platform.find_by(title: 'telegram') @@ -169,7 +170,7 @@ def check_onlylink end def update_onlylink(bot, platform_post) - post_link = "http://#{Rails.configuration.credentials[:host]}:#{Rails.configuration.credentials[:port]}/posts/#{@post.id}" + post_link = "#{@base_url}/posts/#{@post.id}" full_post_link = "#{post_link}" onlylink_text = @new_title.present? ? "#{@new_title}\n\n#{full_post_link}" : full_post_link diff --git a/app/services/send_post_to_platforms.rb b/app/services/send_post_to_platforms.rb index 4cf6efa..8e5dc1f 100644 --- a/app/services/send_post_to_platforms.rb +++ b/app/services/send_post_to_platforms.rb @@ -5,9 +5,11 @@ class SendPostToPlatforms attr_accessor :post, :params - def initialize(post, params) + def initialize(post, base_url, params) @params = params @post = post + @base_url = base_url + @attachments = @params[:post][:attachments].reverse if @params[:post][:attachments].present? @options = @params[:options] @@ -70,9 +72,9 @@ def call def check_platforms(platform, channel_ids) case platform when 'telegram' - Platform::SendPostToTelegram.call(@post, params, channel_ids) + Platform::SendPostToTelegram.call(@post, @base_url, params, channel_ids) when 'matrix' - Platform::SendPostToMatrix.call(@post, params, channel_ids) + Platform::SendPostToMatrix.call(@post, @base_url, params, channel_ids) end end end diff --git a/app/services/update_post_messages.rb b/app/services/update_post_messages.rb index 6f3264a..f6f5cd1 100644 --- a/app/services/update_post_messages.rb +++ b/app/services/update_post_messages.rb @@ -5,9 +5,10 @@ class UpdatePostMessages attr_accessor :post, :params - def initialize(post, params) + def initialize(post, base_url, params) @params = params @post = post + @base_url = base_url @attachments = @params[:post][:attachments] @deleted_attachments = @params[:deleted_attachments] @@ -38,8 +39,8 @@ def call execution_context = Rails.application.executor.run! posted_platforms = @post.platforms - Platform::UpdateTelegramPosts.call(@post, params) if posted_platforms['telegram'] - Platform::UpdateMatrixPosts.call(@post, params) if posted_platforms['matrix'] + Platform::UpdateTelegramPosts.call(@post, @base_url, params) if posted_platforms['telegram'] + Platform::UpdateMatrixPosts.call(@post, @base_url, params) if posted_platforms['matrix'] ensure execution_context&.complete! end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 4d80124..719835a 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,7 +1,7 @@ - <%=Rails.configuration.credentials[:title]%> + <%= display_meta_tags site: Rails.configuration.credentials[:title] %> <%= csrf_meta_tags %> <%= csp_meta_tag %> diff --git a/app/views/layouts/clear.html.erb b/app/views/layouts/clear.html.erb index eee1b9c..7347005 100644 --- a/app/views/layouts/clear.html.erb +++ b/app/views/layouts/clear.html.erb @@ -1,7 +1,7 @@ - <%=Rails.configuration.credentials[:title]%> + <%= display_meta_tags site: Rails.configuration.credentials[:title] %> <%= csrf_meta_tags %> <%= csp_meta_tag %> diff --git a/app/views/posts/rss.builder b/app/views/posts/rss.builder index 9f4964f..e8913a4 100644 --- a/app/views/posts/rss.builder +++ b/app/views/posts/rss.builder @@ -8,7 +8,7 @@ xml.rss version: '2.0', 'xmlns:atom' => 'http://www.w3.org/2005/Atom' do xml.link root_url xml.language 'ru' xml.tag! 'atom:link', rel: 'self', type: 'application/rss+xml', - href: "#{host_link}/rss?rss_token=#{params.key?(:rss_token) && User.find_by(rss_token: params[:rss_token].to_s).present? ? params[:rss_token] : (current_user&.rss_token || 'none')}" + href: "#{request.base_url}/rss?rss_token=#{params.key?(:rss_token) && User.find_by(rss_token: params[:rss_token].to_s).present? ? params[:rss_token] : (current_user&.rss_token || 'none')}" xml.ttl '60' @posts.each do |post| diff --git a/config/credentials.yml b/config/credentials.yml index b3f1852..053c5b2 100644 --- a/config/credentials.yml +++ b/config/credentials.yml @@ -17,7 +17,7 @@ development: # set 'false' if you already have working redis server, or want # launch him manually with command: redis-server autostart: true - url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + url: <%= ENV.fetch("REDIS_URL") { "redis://redis:6379/1" } %> rollbar: enabled: false auth_token: "" @@ -57,7 +57,7 @@ production: # set 'false' if you already have working redis server, or want # launch him manually with command: redis-server autostart: true - url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + url: <%= ENV.fetch("REDIS_URL") { "redis://redis:6379/1" } %> rollbar: enabled: false auth_token: "" diff --git a/config/initializers/meta_tags.rb b/config/initializers/meta_tags.rb new file mode 100644 index 0000000..d9f2f84 --- /dev/null +++ b/config/initializers/meta_tags.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# Use this setup block to configure all options available in MetaTags. +MetaTags.configure do |config| + # How many characters should the title meta tag have at most. Default is 70. + # Set to nil or 0 to remove limits. + # config.title_limit = 70 + + # When true, site title will be truncated instead of title. Default is false. + # config.truncate_site_title_first = false + + # Add HTML attributes to the HTML tag. Default is {}. + # config.title_tag_attributes = {} + + # Add HTML attributes to the <title> HTML tag. Default is {}. + # config.title_tag_attributes = {} + + # Maximum length of the page description. Default is 300. + # Set to nil or 0 to remove limits. + # config.description_limit = 300 + + # Maximum length of the keywords meta tag. Default is 255. + # config.keywords_limit = 255 + + # Default separator for keywords meta tag (used when an Array passed with + # the list of keywords). Default is ", ". + # config.keywords_separator = ', ' + + # When true, keywords will be converted to lowercase, otherwise they will + # appear on the page as is. Default is true. + # config.keywords_lowercase = true + + # When true, the output will not include new line characters between meta tags. + # Default is false. + # config.minify_output = false + + # When false, generated meta tags will be self-closing (<meta ... />) instead + # of open (`<meta ...>`). Default is true. + # config.open_meta_tags = true + + # List of additional meta tags that should use "property" attribute instead + # of "name" attribute in <meta> tags. + # config.property_tags.push( + # 'x-hearthstone:deck', + # ) +end diff --git a/docker-compose.yml b/docker-compose.yml index cdf962e..e048ef8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,12 @@ version: '3.8' services: -# redis: -# image: "redis:7-alpine" -# volumes: -# - ./tmp/redis_data:/var/lib/redis/data -# networks: -# - twilightnet + redis: + image: "redis:7-alpine" + volumes: + - ./tmp/redis_data:/var/lib/redis/data + networks: + - twilightnet twilight_db: image: postgres @@ -51,11 +51,12 @@ services: # - "traefik.http.routers.twilight-secure.tls=true" depends_on: - twilight_db -# - redis + - redis links: - twilight_db:twilight_db volumes: - ./config:/root/app/config + - .env:/root/app/.env tty: true stdin_open: true networks: diff --git a/spec/services/platform/send_post_to_telegram_spec.rb b/spec/services/platform/send_post_to_telegram_spec.rb index 61c3e90..b4b66d1 100644 --- a/spec/services/platform/send_post_to_telegram_spec.rb +++ b/spec/services/platform/send_post_to_telegram_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe Platform::SendPostToTelegram, type: :service do - subject(:service) { described_class.new(post, params, channel_ids) } + subject(:service) { described_class.new(post, "http://localhost:3080", params, channel_ids) } let(:platform) { create(:platform, title: 'Telegram') } let(:post) { create(:post, title: 'Post title') } diff --git a/spec/services/send_post_to_platforms_spec.rb b/spec/services/send_post_to_platforms_spec.rb index e6439c9..ffec171 100644 --- a/spec/services/send_post_to_platforms_spec.rb +++ b/spec/services/send_post_to_platforms_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe SendPostToPlatforms, type: :service do - subject(:service) { described_class.new(post, params) } + subject(:service) { described_class.new(post, "http://localhost:3080", params) } let(:post) { create(:post, title: 'Post title') } let(:params) do diff --git a/update_log.md b/update_log.md index 0c8bc18..f132d81 100644 --- a/update_log.md +++ b/update_log.md @@ -1,5 +1,16 @@ ### Format: Y-M-D +### 2024-0x-xx => 1.0.2 + +* Поддержка OpenGraph для предпросмотра постов; +* Обновлены темы: + * Для тем Gruvbox, Twilight добавлена тёмная тема для feed; + * Для темы по-умолчанию добавлена отдельно тёмная подтема для feed; + * Изменена тема 'Ruby'; +* Обновлены docker-контейнеры; +* Исправлен баг с плохо видимой каптчей; +* Мелкие фиксы; + ### 2023-02-01 => 1.0 * Интеграция telegram-poller в проект: From a206157ec100622226158caf2da0351958c10e48 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <Whiletruedoend@protonmail.com> Date: Mon, 8 Jul 2024 23:48:49 +0300 Subject: [PATCH 04/47] Add view counter for post --- .env | 3 +- Gemfile | 1 + app/assets/stylesheets/base.scss | 26 +++++++- app/controllers/posts_controller.rb | 21 +++++- app/models/ahoy/event.rb | 8 +++ app/models/ahoy/visit.rb | 6 ++ app/models/post.rb | 5 ++ app/models/user.rb | 1 + app/views/posts/_post_header.html.erb | 4 +- app/views/posts/feed/_feed.html.erb | 9 ++- config/initializers/ahoy.rb | 10 +++ ...707184104_create_ahoy_visits_and_events.rb | 66 +++++++++++++++++++ 12 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 app/models/ahoy/event.rb create mode 100644 app/models/ahoy/visit.rb create mode 100644 config/initializers/ahoy.rb create mode 100644 db/migrate/20240707184104_create_ahoy_visits_and_events.rb diff --git a/.env b/.env index f200841..5fa5628 100644 --- a/.env +++ b/.env @@ -4,4 +4,5 @@ POSTGRES_HOST="twilight_db" POSTGRES_PORT=5432 POSTGRES_DB=twilight_development POSTGRES_USER=postgres -POSTGRES_PASSWORD=pass \ No newline at end of file +POSTGRES_PASSWORD=pass +REDIS_URL="redis://redis:6379/1" \ No newline at end of file diff --git a/Gemfile b/Gemfile index 0d6c6cd..3134c73 100644 --- a/Gemfile +++ b/Gemfile @@ -79,6 +79,7 @@ gem 'rubocop-rails' gem 'action_policy' gem 'active_storage_validations' gem 'addressable', '>= 2.8.0' +gem "ahoy_matey" gem 'betterlorem' gem 'devise' gem 'dry-initializer-rails' diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 1f55a0a..3d0fc3e 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -390,8 +390,8 @@ $color-black: #000000; font-size: 65%; } - .comments{ - margin-left: 1%; + h1 .right-small:not(:first-child) { + margin-right: 0.5%; } .tags{ @@ -1108,12 +1108,17 @@ $color-black: #000000; .createdAt{ color: $color-feed-time; margin-right: 5px; + margin-left: 5px; a, a:hover{ color: $color-feed-time; background-color: $color-feed-bg; } } + .views{ + cursor: pointer; + color: $color-feed-time; + } } .readmore{ @@ -1160,5 +1165,22 @@ $color-black: #000000; object-position: top; } } + .footer{ + margin-top: 1.5%; + .item-right{ + margin-right: 5px; + float: right; + } + .item-center{ + text-align: center; + } + .comments-container{ + background: none; + background-color: none; + } + .comments-width{ + max-width: 100%; + } + } } } \ No newline at end of file diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 0fc4deb..af867fc 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -9,6 +9,23 @@ class PostsController < ApplicationController end before_action :authenticate_user!, except: except_pages + after_action :track_action, only: %i[index show raw feed] + + def track_action + post = Post.find_by(id: params.dig("id")) + request_params = request.path_parameters + if post.present? + post_id = post.id.to_s + if not Ahoy::Event.where(name: "Post_#{post_id}", visit_id: current_visit.id).where_properties(id: post_id).exists? + ahoy.track("Post_#{post_id}", request_params) + end + else + request_properties = { action: request_params[:action], controller: request_params[:controller] } + if not Ahoy::Event.where(name: "Posts", visit_id: current_visit.id).where_properties(request_properties).exists? + ahoy.track("Posts", request_params) + end + end + end def set_tags if params['action'].nil? || params['action'] == 'index' @@ -246,8 +263,8 @@ def feed end if params.dig("id").present? - post = @posts.find_by(id: params["id"]) - set_tags_post(post) if post.present? + @current_post = @posts.find_by(id: params["id"]) + set_tags_post(@current_post) if @current_post.present? end respond_to do |format| diff --git a/app/models/ahoy/event.rb b/app/models/ahoy/event.rb new file mode 100644 index 0000000..4c3125b --- /dev/null +++ b/app/models/ahoy/event.rb @@ -0,0 +1,8 @@ +class Ahoy::Event < ApplicationRecord + include Ahoy::QueryMethods + + self.table_name = "ahoy_events" + + belongs_to :visit + belongs_to :user, optional: true +end diff --git a/app/models/ahoy/visit.rb b/app/models/ahoy/visit.rb new file mode 100644 index 0000000..66f89e5 --- /dev/null +++ b/app/models/ahoy/visit.rb @@ -0,0 +1,6 @@ +class Ahoy::Visit < ApplicationRecord + self.table_name = "ahoy_visits" + + has_many :events, class_name: "Ahoy::Event" + belongs_to :user, optional: true +end diff --git a/app/models/post.rb b/app/models/post.rb index 05eb78b..c85b190 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -63,4 +63,9 @@ def destroy DeletePostMessages.call(self) super end + + # Todo: make turbo for views? + def views + Ahoy::Event.where(name: "Post_#{self.id}").distinct.count(:visit_id) + end end diff --git a/app/models/user.rb b/app/models/user.rb index 9cb82fd..4794278 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -14,6 +14,7 @@ class User < ApplicationRecord has_and_belongs_to_many :tags, class_name: 'Tag', join_table: 'item_tags', as: :item, dependent: :delete_all # Not working deletion with SQLite! has_many :active_tags, -> { active('User') }, class_name: 'ItemTag', foreign_key: 'item_id' + has_many :visits, class_name: "Ahoy::Visit" has_one_attached :avatar diff --git a/app/views/posts/_post_header.html.erb b/app/views/posts/_post_header.html.erb index b13c4a1..0517c0d 100644 --- a/app/views/posts/_post_header.html.erb +++ b/app/views/posts/_post_header.html.erb @@ -1,6 +1,8 @@ <header> <h1> - <i class="fa fa-commenting-o right-small comments"> <%= post.comments.count %></i> + <!-- <i class="fa fa-bookmark right-small"></i> --> + <i class="fa fa-light fa-eye right-small"> <%= post.views %></i> + <i class="fa fa-commenting-o right-small"> <%= post.comments.count %></i> <% if Rails.configuration.credentials[:enable_categories] %> <div class="category" style="<%="border-left: 5px solid #{post.category.color};" if post.category.present? %>"> <a href="/posts/<%= post.id %>"><%= render_title(post) %> </a> diff --git a/app/views/posts/feed/_feed.html.erb b/app/views/posts/feed/_feed.html.erb index b37e005..7689571 100644 --- a/app/views/posts/feed/_feed.html.erb +++ b/app/views/posts/feed/_feed.html.erb @@ -13,6 +13,7 @@ <%end%> </div> <div class="name"><a href="?user=<%=post.user.id%>"><%=display_name(post.user)%></a></div> + <div class="views"><i class="fa fa-light fa-eye"></i> <%= post.views %> |</div> <div class="createdAt"><a href="?id=<%=post.id%>"><%=post.created_at.strftime("%H:%M:%S")%></a></div> <%= render "privacy-icons", object: post %> </div> @@ -25,6 +26,12 @@ <%if post.content_attachments&.any?%> <div class="media-list"> <%=display_feed_attachments(post)%> - <div> + </div> <%end%> + <div class="footer"> + <a class="item-right" href="?id=<%=post.id%>"><i class="fa fa-commenting-o"> <%= I18n.t("comments.comment_header") %> (<%= post.comments.count %>)</i></a> + <%if params[:id].present?%> + <%= render "posts/comments" %> + <%end%> + </div> </article> \ No newline at end of file diff --git a/config/initializers/ahoy.rb b/config/initializers/ahoy.rb new file mode 100644 index 0000000..fad5c2c --- /dev/null +++ b/config/initializers/ahoy.rb @@ -0,0 +1,10 @@ +class Ahoy::Store < Ahoy::DatabaseStore +end + +# set to true for JavaScript tracking +Ahoy.api = false + +# set to true for geocoding (and add the geocoder gem to your Gemfile) +# we recommend configuring local geocoding as well +# see https://github.com/ankane/ahoy#geocoding +Ahoy.geocode = false diff --git a/db/migrate/20240707184104_create_ahoy_visits_and_events.rb b/db/migrate/20240707184104_create_ahoy_visits_and_events.rb new file mode 100644 index 0000000..652c00d --- /dev/null +++ b/db/migrate/20240707184104_create_ahoy_visits_and_events.rb @@ -0,0 +1,66 @@ +class CreateAhoyVisitsAndEvents < ActiveRecord::Migration[7.0] + def up + create_table :ahoy_visits do |t| + t.string :visit_token + t.string :visitor_token + + # the rest are recommended but optional + # simply remove any you don't want + + # user + t.references :user + + # standard + t.string :ip + t.text :user_agent + t.text :referrer + t.string :referring_domain + t.text :landing_page + + # technology + t.string :browser + t.string :os + t.string :device_type + + # location + t.string :country + t.string :region + t.string :city + t.float :latitude + t.float :longitude + + # utm parameters + t.string :utm_source + t.string :utm_medium + t.string :utm_term + t.string :utm_content + t.string :utm_campaign + + # native apps + t.string :app_version + t.string :os_version + t.string :platform + + t.datetime :started_at + end + + add_index :ahoy_visits, :visit_token, unique: true + + create_table :ahoy_events do |t| + t.references :visit + t.references :user + + t.string :name + t.jsonb :properties + t.datetime :time + end + + add_index :ahoy_events, [:name, :time] + add_index :ahoy_events, :properties, using: :gin, opclass: :jsonb_path_ops + end + def down + drop_table :ahoy_events + drop_table :ahoy_visits + end + +end From 33e2111a687348cdcee1185f5bd01f53d7995730 Mon Sep 17 00:00:00 2001 From: wtde <wtde@waifu.club> Date: Wed, 10 Jul 2024 22:06:19 +0300 Subject: [PATCH 05/47] Ruby 3.3.2 && devbox for NixOS --- .gitignore | 6 ++++++ .ruby-version | 2 +- Dockerfile | 7 +++---- Gemfile | 30 ++++++++++++++++-------------- README-RU.md | 10 ++-------- README.md | 12 +++--------- config/application.rb | 2 ++ config/database.yml | 9 +++------ devbox.json | 28 ++++++++++++++++++++++++++++ update_log.md | 7 ++++++- 10 files changed, 70 insertions(+), 43 deletions(-) create mode 100644 devbox.json diff --git a/.gitignore b/.gitignore index 36afee1..e09d7ee 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .vscode .idea .db-data +.devbox .cache /config/credentials.yml /db/schema.rb @@ -50,3 +51,8 @@ config/database.yml Gemfile.lock .env gemset.nix +flake.lock +flake.nix +devbox.d +devbox.lock +dump.rdb \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index d0a7ee9..ff57f4b 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-2.7.7 \ No newline at end of file +ruby-3.3.2 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 96f337d..4e9c57a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Start from a small, trusted base image with the version pinned down -FROM ruby:2.7.7-slim AS base +FROM ruby:3.3.2-slim AS base # Required Libraries #RUN apt-get update; apt-get install -y --no-install-recommends \ @@ -18,8 +18,7 @@ RUN apt-get update; apt-get install -y --no-install-recommends \ libpng-dev libjpeg-dev libtiff-dev ffmpeg \ tzdata libv8-dev imagemagick libmagickwand-dev \ libpq-dev libffi-dev \ - postgresql-client sqlite3 \ - #postgresql postgresql-contrib sqlite3 \ + postgresql-client \ nodejs redis yarn # This stage will be responsible for installing gems and npm packages @@ -32,7 +31,7 @@ COPY Gemfile ./ COPY ./vendor/gems/easy_captcha /vendor/gems/easy_captcha # Install gems (excluding test dependencies) -RUN gem install bundler -v 2.2.25 +RUN gem install bundler -v 2.5.9 RUN bundle install --jobs=3 --retry=3 diff --git a/Gemfile b/Gemfile index 3134c73..ca3086c 100644 --- a/Gemfile +++ b/Gemfile @@ -3,13 +3,13 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '2.7.7' +ruby '3.3.2' gem 'activerecord', '>= 6.1.7.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 7.0.8.1' # Use sqlite3 as the database for Active Record -gem 'sqlite3', '>= 1.4' +#gem 'sqlite3', '>= 1.4' # Use Puma as the app server gem 'puma', '>= 6.4.2' # Use SCSS for stylesheets @@ -35,6 +35,17 @@ gem 'actionpack', '>= 6.1.4.1' # Reduces boot times through caching; required in config/boot.b gem 'bootsnap', '>= 1.4.4', require: false +group :development do + # Access an interactive console on exception pages or by calling 'console' anywhere in the code + gem 'web-console', '>= 4.1.0' + # Display performance information such as SQL time and flame graphs for each request in your browser. + # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md + gem 'listen' + gem 'rack-mini-profiler' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' +end + group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: %i[mri mingw x64_mingw] @@ -45,17 +56,6 @@ group :development, :test do gem 'rubocop-rspec', require: false end -group :development do - # Access an interactive console on exception pages or by calling 'console' anywhere in the code - gem 'web-console', '>= 4.1.0' - # Display performance information such as SQL time and flame graphs for each request in your browser. - # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md - gem 'listen', '~> 3.3' - gem 'rack-mini-profiler', '~> 2.0' - # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring - gem 'spring' -end - group :test do # Adds support for Capybara system testing and selenium driver gem 'capybara', '>= 3.26' @@ -91,7 +91,7 @@ gem 'mini_magick' gem 'nokogiri', '>= 1.11.0.rc4' gem 'open-uri' gem 'pg' -gem 'rake', '~> 13.2.1' +gem 'rake', '>= 13.2.1' gem 'redcarpet' gem 'reverse_markdown' gem 'rmagick' @@ -103,6 +103,8 @@ gem 'socksify', require: false # TCP through a SOCKS5 proxy gem 'telegram-bot' gem 'sidekiq' +gem 'base64' +gem 'mutex_m' gem 'bootstrap-sass' gem 'bootstrap-will_paginate', '~> 0.0.10' diff --git a/README-RU.md b/README-RU.md index 8cb79b6..7419312 100644 --- a/README-RU.md +++ b/README-RU.md @@ -84,12 +84,11 @@ P.S. Список последних изменений можно посмот ### Docker - * Загрузить проект: ``` git clone https://github.com/Whiletruedoend/Twilight cd Twilight/ ``` - * (Не обязательно) Настроить файл .env для подключения к существующей бд postgres, или изменить тип бд на sqlite3 в config/database.yml + * (Не обязательно) Настроить файл .env для подключения к существующей бд postgres * Настроить config/credentials.yml * Выполнить команду: ``` @@ -232,13 +231,8 @@ ER-диаграмма: <img src="https://i.imgur.com/3QStroz.png"></img> Конкретная статья: <img src="https://i.imgur.com/9F0W2Nr.png"></img> -## Contribution - 1) Сделать fork проекта; - 2) Сделать изменения в этом проекте; - 3) На странице этого репозитория, нажать Pull Requests и сделать Pull Request, выбрав свой fork в правом списке; - ## Связь -Если у вас есть какие-то идеи или собственные наработки, или же просто вопросы по поводу работоспособности кода, то вы всегда можете обратиться ко мне по следующим адресам: +Если у Вас есть вопросы, идеи или собственные наработки, то всегда можете обратиться ко мне по следующему адресу: - [Matrix](https://matrix.to/#/@whiletruedoend:matrix.org) \ No newline at end of file diff --git a/README.md b/README.md index 35d7b3a..825ae0f 100644 --- a/README.md +++ b/README.md @@ -84,12 +84,11 @@ Analyzing various blog sites and the platforms adjacent to them (where the repos ### Docker - * Download project: ``` git clone https://github.com/Whiletruedoend/Twilight cd Twilight/ ``` - * (Optional) Configure .env for existing postgres database, or change to sqlite3 in config/database.yml + * (Optional) Configure .env for existing postgres database * Configure config/credentials.yml * Run: ``` @@ -212,7 +211,7 @@ Only the information from the first token is identical to the information from t Therefore, this does not seem to carry a serious threat. But just in case, he warned that there were no questions. ## Schemas and screenshots -ER-diagram: +ER-diagram(Ver. 1.0.1): <img src="https://i.imgur.com/RQQCRpa.jpeg"></img> Main page (configurating): * Version 0 (standalone page): @@ -233,13 +232,8 @@ Article creation (Default theme): <img src="https://i.imgur.com/3QStroz.png"></img> Specific article: <img src="https://i.imgur.com/9F0W2Nr.png"></img> -## Contribution - 1) Fork tis project; - 2) Make changes to the forked project; - 3) On the page of this repository, poke Pull Requests and make a Pull Request by selecting your fork in the right list; - ## Contact -If you have any ideas or your own developments, or just questions about the performance of the code, then you can always contact me at the following addresses: +If you have any ideas or your own developments, then you can always contact me at the following addresses: - [Matrix](https://matrix.to/#/@whiletruedoend:matrix.org) \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index f3607a8..80ecda8 100644 --- a/config/application.rb +++ b/config/application.rb @@ -18,6 +18,8 @@ class Application < Rails::Application config.time_zone = config.credentials[:time_zone] config.i18n.default_locale = config.credentials[:locale] + config.active_storage.variant_processor = :mini_magick + require 'ext/string' require 'ext/matrix' require 'ext/zip_file_generator' diff --git a/config/database.yml b/config/database.yml index accafcd..1428008 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,6 +1,6 @@ # config/database.yml default: &default - adapter: postgresql # sqlite3 + adapter: postgresql encoding: unicode host: <%= ENV['POSTGRES_HOST'] { localhost } %> port: <%= ENV['POSTGRES_PORT'] { 5432 } %> @@ -11,14 +11,11 @@ default: &default development: <<: *default database: <%= ENV['POSTGRES_DB'] { twilight_development } %> - # database: db/development.sqlite3 # For sqlite3 production: <<: *default database: <%= ENV['POSTGRES_DB'] { twilight_production } %> - # database: db/production.sqlite3 # For sqlite3 test: - adapter: sqlite3 - encoding: unicode - database: db/twilight_test.sqtile3 \ No newline at end of file + <<: *default + database: twilight_test \ No newline at end of file diff --git a/devbox.json b/devbox.json new file mode 100644 index 0000000..2a23f92 --- /dev/null +++ b/devbox.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.10.7/.schema/devbox.schema.json", + "packages": { + "imagemagick": "7.1.1-34", + "pkg-config": "latest", + "postgresql": "latest", + "clang": "latest", + "gcc": { + "version": "latest" + }, + "ruby": { + "version": "3.3.2", + "patch_glibc": true + }, + "rubyPackages_3_2.ruby-vips": "latest", + "redis": "latest" + }, + "shell": { + "init_hook": [ + "echo 'Welcome to devbox!' > /dev/null" + ], + "scripts": { + "test": [ + "echo \"Error: no test specified\" && exit 1" + ] + } + } +} diff --git a/update_log.md b/update_log.md index f132d81..196ca55 100644 --- a/update_log.md +++ b/update_log.md @@ -2,13 +2,18 @@ ### 2024-0x-xx => 1.0.2 +* Обновлён Ruby 2.7.7->3.3.2; * Поддержка OpenGraph для предпросмотра постов; +* Добавлен счётчик просмотров поста; * Обновлены темы: * Для тем Gruvbox, Twilight добавлена тёмная тема для feed; * Для темы по-умолчанию добавлена отдельно тёмная подтема для feed; * Изменена тема 'Ruby'; -* Обновлены docker-контейнеры; + * Для feed теперь также есть счётчик просмотров и комментарии; * Исправлен баг с плохо видимой каптчей; +* Обновлены docker-контейнеры; +* Добавлен файл devbox для NixOS; +* Удалена поддержка sqlite3; * Мелкие фиксы; ### 2023-02-01 => 1.0 From 71d11ac23f60c0f9a3b28ad497d83ac1b0387987 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Wed, 10 Jul 2024 23:18:05 +0300 Subject: [PATCH 06/47] Minor fixes --- app/controllers/posts_controller.rb | 18 ++++++++-------- app/controllers/users/sessions_controller.rb | 22 +++++++++++--------- app/searches/posts_search.rb | 2 +- config/locales/en.yml | 1 + config/locales/ru.yml | 1 + 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index af867fc..309bf32 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -225,9 +225,9 @@ def rss tags = user&.active_tags&.any? ? user&.active_tags&.map { |i| i.tag.id } : Tag.all.ids limit = user&.options&.dig('visible_posts_count') || Rails.configuration.credentials[:rss_default_visible_posts] - post_params = { current_user: user, limit: limit, tags: tags, title: params[:title] } + @posts = PostsSearch.new(current_user: user, limit: limit, tags: tags, title: params[:title]).call(Post.all) - @posts = PostsSearch.new(post_params).call(Post.all).order(created_at: (params[:sort] == 'asc' ? 'asc' : 'desc')) + @posts = @posts.order(created_at: (params[:sort] == 'asc' ? 'asc' : 'desc')) @markdown = Redcarpet::Markdown.new(CustomRender.new({ hard_wrap: true, no_intra_emphasis: true, @@ -246,14 +246,14 @@ def feed (User.find_by(rss_token: params[:rss_token]) if params.key?(:rss_token)) end - post_params = { current_user: user, - id: params[:id], - user_id: params[:user], - strict_tags: params[:tag], - text: params[:text], - date: params[:to] } + @posts = PostsSearch.new( current_user: user, + id: params[:id], + user_id: params[:user], + strict_tags: params[:tag], + text: params[:text], + date: params[:to] + ).call(Post.all) - @posts = PostsSearch.new(post_params).call(Post.all) @posts = @posts.paginate(page: params[:page], per_page: 15).order(created_at: (params[:sort] == 'asc' ? 'asc' : 'desc')) @last_date = nil diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 26b7751..6cd39d1 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -3,6 +3,7 @@ module Users class SessionsController < Devise::SessionsController # before_action :configure_sign_in_params, only: [:create] + prepend_before_action :captcha_valid, only: [:create] # GET /resource/sign_in @@ -12,25 +13,26 @@ def set_tags keywords: 'Twilight, Notes, signin') end - # POST /resource/sign_in - def create + protected + + def captcha_valid if valid_captcha?(sessions_params[:user][:captcha]) - super + true else + set_flash_message :alert, :wrong_captcha redirect_to sign_in_url end end - protected - def sessions_params params.permit(:authenticity_token, :commit, - user: %i[login - password - code - captcha - remember_me]) + user: [:login, + :password, + :code, + :captcha, + :remember_me, + ]) end # DELETE /resource/sign_out diff --git a/app/searches/posts_search.rb b/app/searches/posts_search.rb index 58f9746..23029fd 100644 --- a/app/searches/posts_search.rb +++ b/app/searches/posts_search.rb @@ -3,7 +3,7 @@ require 'dry-initializer' class PostsSearch < ApplicationSearch - option :current_user + option :current_user, optional: true # option :user_tags, optional: true option :id, optional: true option :user_id, optional: true diff --git a/config/locales/en.yml b/config/locales/en.yml index e073948..ce4e2d0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -208,6 +208,7 @@ en: signed_in: You have successfully logged in! signed_up: You have successfully registered! signed_out: You have successfully signed out! + wrong_captcha: Wrong captcha failure: user: unauthenticated: Authorization required diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 9a60d52..76abaf4 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -208,6 +208,7 @@ ru: signed_in: Вы успешно авторизовались! signed_up: Вы успешно зарегистрировались! signed_out: Вы успешно вышли из аккаунта! + wrong_captcha: Неверный проверочный код failure: user: unauthenticated: Необходима авторизация From 96841e3e740bd25472c15a95d3ed95d5ddc96a05 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 11 Jul 2024 22:23:47 +0300 Subject: [PATCH 07/47] New attachments view --- app/assets/stylesheets/base.scss | 53 +++++++++++++---- ...me.scss => default_-Dark feed-_theme.scss} | 5 +- app/assets/stylesheets/default_theme.scss | 4 +- app/assets/stylesheets/gruvbox_theme.scss | 4 +- app/assets/stylesheets/ruby_theme.scss | 4 +- app/assets/stylesheets/twilight_theme.scss | 4 +- app/helpers/posts_helper.rb | 59 +++++++++++++------ app/views/posts/feed/_feed.html.erb | 4 +- app/views/posts/raw.html.erb | 2 +- 9 files changed, 101 insertions(+), 38 deletions(-) rename app/assets/stylesheets/{default (Dark feed)_theme.scss => default_-Dark feed-_theme.scss} (93%) diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 3d0fc3e..69321d6 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -88,7 +88,7 @@ $color-black: #000000; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg){ + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon){ @include dropzone($color-border, $color-form-textarea-bg); code{ /* #cc241d */ @@ -381,13 +381,16 @@ $color-black: #000000; margin-bottom: 0; } + /* article img{ padding-right: 10px; } + */ .right-small{ float: right; font-size: 65%; + margin-right: 0.5%; } h1 .right-small:not(:first-child) { @@ -421,6 +424,44 @@ $color-black: #000000; .twilight-icon{ color: $color-privacy-me; } .red-icon { color: $color-privacy-me-p; } + /* attachments */ + + .attachments{ + display: grid; + /* grid-template-columns: repeat(3, 1fr); */ + grid-gap: 1%; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + + img{ + width: 100%; + object-fit: cover; + object-position: top; + } + .audio-cover{ + background-color: $color-feed-music-bg; + min-width: 200px; + min-height: 200px; + max-width: 220px; + max-height: 220px; + background-size: cover; + position: relative; + display: flex; + justify-content: center; + align-items: center; + + .fa-music::before{ + font-size: 50px; + color: $color-feed-music-icon; + } + + audio{ + position: absolute; + bottom: 0; + width: 100%; + } + } + } + /* channels */ .channels-container{ @@ -1155,16 +1196,6 @@ $color-black: #000000; margin: revert; } - .media-list{ - display: grid; - grid-template-columns: repeat(3, 1fr); - - img{ - width: 100%; - object-fit: cover; - object-position: top; - } - } .footer{ margin-top: 1.5%; .item-right{ diff --git a/app/assets/stylesheets/default (Dark feed)_theme.scss b/app/assets/stylesheets/default_-Dark feed-_theme.scss similarity index 93% rename from app/assets/stylesheets/default (Dark feed)_theme.scss rename to app/assets/stylesheets/default_-Dark feed-_theme.scss index f3627bc..608197e 100644 --- a/app/assets/stylesheets/default (Dark feed)_theme.scss +++ b/app/assets/stylesheets/default_-Dark feed-_theme.scss @@ -25,6 +25,9 @@ $color-feed-filters-font: #f0f0f0; $color-feed-filters-border: #1d2021; $color-feed-filters-badge-font: #f0f0f0; $color-feed-badge-bg: #454646; +$color-feed-music-bg: black; +$color-feed-music-icon: white; + @include base($bg, $color-darkpurple, $color-code-bg, $font, $font-link, $color-white, $color-black, @@ -41,5 +44,5 @@ $color-feed-badge-bg: #454646; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon ); \ No newline at end of file diff --git a/app/assets/stylesheets/default_theme.scss b/app/assets/stylesheets/default_theme.scss index 87579a4..2d087a6 100644 --- a/app/assets/stylesheets/default_theme.scss +++ b/app/assets/stylesheets/default_theme.scss @@ -68,6 +68,8 @@ $color-feed-filters-font: #555555; $color-feed-filters-border: #ccc; $color-feed-filters-badge-font: #fff; $color-feed-badge-bg: #777777; +$color-feed-music-bg: black; +$color-feed-music-icon: white; $color-alert-danger: #222426; $color-alert-danger-text: #df4655; @@ -104,5 +106,5 @@ $color-black: #000000; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon ); \ No newline at end of file diff --git a/app/assets/stylesheets/gruvbox_theme.scss b/app/assets/stylesheets/gruvbox_theme.scss index 3054e93..613edd4 100644 --- a/app/assets/stylesheets/gruvbox_theme.scss +++ b/app/assets/stylesheets/gruvbox_theme.scss @@ -68,6 +68,8 @@ $color-feed-filters-font: #fbf1c7; $color-feed-filters-border: #1d2021; $color-feed-filters-badge-font: #fbf1c7; $color-feed-badge-bg: #928374; +$color-feed-music-bg: #1d2021; +$color-feed-music-icon: #fbf1c7; $color-alert-danger: #ad3b14; $color-alert-danger-text: #fcf4cd; @@ -104,5 +106,5 @@ $color-black: #000000; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon ); \ No newline at end of file diff --git a/app/assets/stylesheets/ruby_theme.scss b/app/assets/stylesheets/ruby_theme.scss index 6369449..5f943c7 100644 --- a/app/assets/stylesheets/ruby_theme.scss +++ b/app/assets/stylesheets/ruby_theme.scss @@ -68,6 +68,8 @@ $color-feed-filters-font: #555555; $color-feed-filters-border: #ccc; $color-feed-filters-badge-font: #fff; $color-feed-badge-bg: #777777; +$color-feed-music-bg: #eeeeee; +$color-feed-music-icon: #000000; $color-alert-danger: #f2dede; $color-alert-danger-text: #a94442; @@ -104,5 +106,5 @@ $color-black: #000000; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon ); \ No newline at end of file diff --git a/app/assets/stylesheets/twilight_theme.scss b/app/assets/stylesheets/twilight_theme.scss index 577a5d0..36bb4d4 100644 --- a/app/assets/stylesheets/twilight_theme.scss +++ b/app/assets/stylesheets/twilight_theme.scss @@ -68,6 +68,8 @@ $color-feed-filters-font: #f0f0f0; $color-feed-filters-border: #b3b3b3; $color-feed-filters-badge-font: #f0f0f0; $color-feed-badge-bg: #bdae93; +$color-feed-music-bg: black; +$color-feed-music-icon: white; $color-alert-danger: #13171d; $color-alert-danger-text: #df4655; @@ -104,5 +106,5 @@ $color-black: #000000; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon ); \ No newline at end of file diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index 2adde55..1880f2a 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -28,20 +28,6 @@ def get_full_attachment_link(att) def display_attachments(post) content = '' - attachments_count = post.content_attachments&.count || 0 - size = - case attachments_count - when 1 - :thumb300 - when 2 - :thumb250 - when 3 - :thumb200 - when 4, 5 - :thumb150 - else - :thumb100 - end documents = post.content_attachments.select { |b| !b.image? && !b.video? && !b.audio? } if documents.any? @@ -52,16 +38,49 @@ def display_attachments(post) end content += '<br><br>' if documents.any? + content += "<div class=\"attachments\">" post.content_attachments&.each do |att| if att.image? - content += link_to image_tag(url_for(att.variant(size).processed)), url_for(att), target: '_blank'.to_s + content += link_to image_tag(url_for(att)), url_for(att), target: '_blank'.to_s elsif att.video? content += video_tag(url_for(att), controls: true, preload: 'none', poster: url_for(att.preview(resize_to_limit: [200, 200]).processed)).to_s elsif att.audio? - content += link_to audio_tag(url_for(att), autoplay: false, controls: true), url_for(att), target: '_blank'.to_s + content += "<div class=\"audio-cover\">" + content += "<i class=\"fa fa-light fa-music\"></i>" + content += "#{audio_tag(url_for(att), autoplay: false, controls: true)}" + content += '</div>' end end + content += '</div>' + content.html_safe + end + + # TODO + def display_raw_attachments(post) + content = '' + documents = post.content_attachments.select { |b| !b.image? && !b.video? && !b.audio? } + if documents.any? + documents.each do |att| + content += "<br><a target=\"_blank\" href=\"#{get_full_attachment_link(att)}\"> + #{I18n.t('posts.download')} #{truncate(att.filename.to_s, length: 100)} </a>" + end + end + content += '<br><br>' if documents.any? + + post.content_attachments&.each do |att| + if att.image? + content += link_to image_tag(url_for(att)), url_for(att), target: '_blank'.to_s + content += '<br>' + elsif att.video? + content += link_to url_for(att).to_s + content += '<br>' + elsif att.audio? + content += link_to url_for(att).to_s + content += '<br>' + end + end + content += '<br>' if post.content_attachments&.any? content.html_safe end @@ -77,6 +96,7 @@ def display_feed_attachments(post) end end + content += "<div class=\"attachments\">" post.content_attachments&.each do |att| if att.image? content += image_tag url_for(att), id: 'zoom-bg'.to_s @@ -88,10 +108,13 @@ def display_feed_attachments(post) # content += "<a target=\"_blank\" href=\"#{get_full_attachment_link(att)}\"> # #{image_tag url_for(att.preview(resize_to_limit: [50, 50]).processed)}</a>" elsif att.audio? - content += "<a target=\"_blank\" href=\"#{get_full_attachment_link(att)}\"> - #{audio_tag(url_for(att), autoplay: false, controls: true)}</a>" + content += "<div class=\"audio-cover\">" + content += "<i class=\"fa fa-light fa-music\"></i>" + content += "#{audio_tag(url_for(att), autoplay: false, controls: true)}" + content += '</div>' end end + content += '</div>' content.html_safe end diff --git a/app/views/posts/feed/_feed.html.erb b/app/views/posts/feed/_feed.html.erb index 7689571..8be1192 100644 --- a/app/views/posts/feed/_feed.html.erb +++ b/app/views/posts/feed/_feed.html.erb @@ -24,9 +24,7 @@ </div> <%if post.content_attachments&.any?%> - <div class="media-list"> - <%=display_feed_attachments(post)%> - </div> + <%=display_feed_attachments(post)%> <%end%> <div class="footer"> <a class="item-right" href="?id=<%=post.id%>"><i class="fa fa-commenting-o"> <%= I18n.t("comments.comment_header") %> (<%= post.comments.count %>)</i></a> diff --git a/app/views/posts/raw.html.erb b/app/views/posts/raw.html.erb index e75b1d6..82fafd5 100644 --- a/app/views/posts/raw.html.erb +++ b/app/views/posts/raw.html.erb @@ -3,7 +3,7 @@ <%= @current_post.title %> <% if @current_post.content_attachments.present? %> -<% att = display_attachments(@current_post) %> +<% att = display_raw_attachments(@current_post) %> <%= ReverseMarkdown.convert(@markdown.render(att), unknown_tags: :pass_through) %> <% end %> <%= ReverseMarkdown.convert(@markdown.render(@current_post.text), unknown_tags: :pass_through) %> From 29dc7a1644783541ee4306c3f39a1e9a0bd4b70d Mon Sep 17 00:00:00 2001 From: Whiletruedoend <Whiletruedoend@protonmail.com> Date: Sat, 13 Jul 2024 20:29:25 +0300 Subject: [PATCH 08/47] Categories always enabled --- .github/workflows/rspec.yml | 2 +- .github/workflows/rubocop-analysis.yml | 2 +- .rubocop.yml | 2 +- README-RU.md | 12 +++++-- README.md | 12 +++++-- app/controllers/posts_controller.rb | 2 +- app/views/posts/_post_header.html.erb | 22 ++++-------- app/views/posts/edit.html.erb | 34 +++++++++---------- app/views/posts/new.html.erb | 26 +++++++------- .../users/registrations/_manage_tab.html.erb | 4 +-- config/credentials.yml | 5 +-- 11 files changed, 58 insertions(+), 65 deletions(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index f498a4d..1bcad71 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7.7 + ruby-version: 3.3.2 - name: Install libvips run: sudo apt-get install -y libvips - name: Install gems diff --git a/.github/workflows/rubocop-analysis.yml b/.github/workflows/rubocop-analysis.yml index 61b158a..0cd05bd 100644 --- a/.github/workflows/rubocop-analysis.yml +++ b/.github/workflows/rubocop-analysis.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7.7 + ruby-version: 3.3.2 - name: Rubocop run run: | diff --git a/.rubocop.yml b/.rubocop.yml index 1f8e4e0..1758acf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,7 +4,7 @@ require: AllCops: NewCops: enable - TargetRubyVersion: 2.7.7 + TargetRubyVersion: 3.3.2 Exclude: - 'bin/*' - 'config/**/*' diff --git a/README-RU.md b/README-RU.md index 7419312..b127b7c 100644 --- a/README-RU.md +++ b/README-RU.md @@ -56,14 +56,14 @@ P.S. Список последних изменений можно посмот ### Обычный - * Установить ruby (2.7.7): + * Установить ruby (3.3.2): * Для [rvm](https://rvm.io/): ```ssh - rvm install ruby-2.7.7 + rvm install ruby-3.3.2 ``` * Для [rbenv](https://github.com/rbenv/rbenv): ```ssh - rbenv install 2.7.7 + rbenv install 3.3.2 ``` * Установить yarn: [Windows](https://github.com/yarnpkg/yarn/releases/download/v1.22.19/yarn-1.22.19.msi) | [Linux](https://www.ubuntupit.com/how-to-install-and-configure-yarn-on-linux-distributions/); * Установить redis: [Windows](https://github.com/tporadowski/redis/releases) | [Linux](https://redis.io/docs/getting-started/); @@ -82,6 +82,12 @@ P.S. Список последних изменений можно посмот * Настроить: `config/credentials.yml` * Запустить сервер командой: `rails s` + **Windows установка проблемных гемов**: + ``` +gem install pg -- --with-pg-dir="C:\Program Files\PostgreSQL\15" (вставьте ваш путь) +gem install wdm -- --with-cflags=-Wno-implicit-function-declaration + ``` + ### Docker ``` diff --git a/README.md b/README.md index 825ae0f..ef5a8ed 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,14 @@ Analyzing various blog sites and the platforms adjacent to them (where the repos ### Manual - * Install ruby (2.7.7): + * Install ruby (3.3.2): * For [rvm](https://rvm.io/): ```ssh - rvm install ruby-2.7.7 + rvm install ruby-3.3.2 ``` * For [rbenv](https://github.com/rbenv/rbenv): ```ssh - rbenv install 2.7.7 + rbenv install 3.3.2 ``` * Install yarn: [Windows](https://github.com/yarnpkg/yarn/releases/download/v1.22.19/yarn-1.22.19.msi) | [Linux](https://www.ubuntupit.com/how-to-install-and-configure-yarn-on-linux-distributions/); * Install redis: [Windows](https://github.com/tporadowski/redis/releases) | [Linux](https://redis.io/docs/getting-started/); @@ -82,6 +82,12 @@ Analyzing various blog sites and the platforms adjacent to them (where the repos * Setting up: `config/credentials.yml` * Run server with command: `rails s` + **Windows gem install fixes**: + ``` +gem install pg -- --with-pg-dir="C:\Program Files\PostgreSQL\15" (insert your path) +gem install wdm -- --with-cflags=-Wno-implicit-function-declaration + ``` + ### Docker ``` diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 309bf32..3d8305e 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -51,7 +51,7 @@ def set_tags_post(current_post) video_preview_url = video_preview.present? ? "#{request.base_url}#{rails_blob_path(video_preview, only_path: true)}" : "" title = current_post.title.present? ? current_post.title : "#{current_post.id} | #{Rails.configuration.credentials[:title]}" - category = Rails.configuration.credentials[:enable_categories] ? current_post.category&.name || "" : "" + category = current_post.category&.name || "" author = current_post.user.name.present? ? current_post.user.name : current_post.user.login diff --git a/app/views/posts/_post_header.html.erb b/app/views/posts/_post_header.html.erb index 0517c0d..6e4a4d4 100644 --- a/app/views/posts/_post_header.html.erb +++ b/app/views/posts/_post_header.html.erb @@ -3,21 +3,13 @@ <!-- <i class="fa fa-bookmark right-small"></i> --> <i class="fa fa-light fa-eye right-small"> <%= post.views %></i> <i class="fa fa-commenting-o right-small"> <%= post.comments.count %></i> - <% if Rails.configuration.credentials[:enable_categories] %> - <div class="category" style="<%="border-left: 5px solid #{post.category.color};" if post.category.present? %>"> - <a href="/posts/<%= post.id %>"><%= render_title(post) %> </a> - <%= render "privacy-icons", object: post %> - <% if post.content_attachments.present? && post.content_attachments.select{ |b| !b.image? && !b.video? && !b.audio? }.any? %> - <a><i class="fa fa-file right-small"></i></a> - <% end %> - </div> - <% else %> + <div class="category" style="<%="border-left: 5px solid #{post.category.color};" if post.category.present? %>"> <a href="/posts/<%= post.id %>"><%= render_title(post) %> </a> <%= render "privacy-icons", object: post %> <% if post.content_attachments.present? && post.content_attachments.select{ |b| !b.image? && !b.video? && !b.audio? }.any? %> <a><i class="fa fa-file right-small"></i></a> <% end %> - <% end %> + </div> <h1 class="dashed-line"></h1> </h1> <p class="metadata"><i class="fa fa-clock-o"> <%= I18n.t("posts.publish_date") %>: <%= post.created_at.strftime("%Y.%m.%d %H:%M") %></i></p> @@ -38,12 +30,10 @@ </p> <% if params[:action] == "show" %> - <% if Rails.configuration.credentials[:enable_categories] %> - <% if post.category&.name.present? %> - <p class="metadata"><i class="fa fa-folder-open-o"> <%= I18n.t("posts.category") %>: <a href="/posts?category=<%=post.category.id%>"><%= post.category.name %></a></i></p> - <% else %> - <p class="metadata"><i class="fa fa-folder-open-o"> <%= I18n.t("posts.category") %>: <%= I18n.t("posts.no_category") %></i></p> - <% end %> + <% if post.category&.name.present? %> + <p class="metadata"><i class="fa fa-folder-open-o"> <%= I18n.t("posts.category") %>: <a href="/posts?category=<%=post.category.id%>"><%= post.category.name %></a></i></p> + <% else %> + <p class="metadata"><i class="fa fa-folder-open-o"> <%= I18n.t("posts.category") %>: <%= I18n.t("posts.no_category") %></i></p> <% end %> <% end %> </header> \ No newline at end of file diff --git a/app/views/posts/edit.html.erb b/app/views/posts/edit.html.erb index 91fa11d..37afb17 100644 --- a/app/views/posts/edit.html.erb +++ b/app/views/posts/edit.html.erb @@ -70,26 +70,24 @@ <p><%= I18n.t("posts.channels_not_found") %></p> <% end %> - <% if Rails.configuration.credentials[:enable_categories] %> - <div class="form-group"> - <h3><%= I18n.t("posts.category") %></h3> - <% if @current_user.categories.any? %> - <% categories = @current_user.categories.collect{ |cat| [cat.name, cat.id] } %> - <% categories.unshift(["-- #{I18n.t("posts.no_category")} --", nil])%> - <% if @current_post.category.present? %> - <%= f.select(:category, categories, :selected=>[@current_post.category.name, @current_post.category.id]) %> - <% else %> - <%= f.select(:category, categories) %> - <% end %> - <% end %><br><br> - <div class="form-inline"> - <div class="form-group"> - <%= f.text_field :category_name, size: 20, class: "form-control", autofocus: true, placeholder: I18n.t("posts.new_category") %> - <%= f.color_field :category_color, class: "color-picker" %> - </div> + <div class="form-group"> + <h3><%= I18n.t("posts.category") %></h3> + <% if @current_user.categories.any? %> + <% categories = @current_user.categories.collect{ |cat| [cat.name, cat.id] } %> + <% categories.unshift(["-- #{I18n.t("posts.no_category")} --", nil])%> + <% if @current_post.category.present? %> + <%= f.select(:category, categories, :selected=>[@current_post.category.name, @current_post.category.id]) %> + <% else %> + <%= f.select(:category, categories) %> + <% end %> + <% end %><br><br> + <div class="form-inline"> + <div class="form-group"> + <%= f.text_field :category_name, size: 20, class: "form-control", autofocus: true, placeholder: I18n.t("posts.new_category") %> + <%= f.color_field :category_color, class: "color-picker" %> </div> </div> - <% end %> + </div> <div class="form-group"> <h3><%= I18n.t("posts.privacy") %></h3> diff --git a/app/views/posts/new.html.erb b/app/views/posts/new.html.erb index 337f39f..cd732a6 100644 --- a/app/views/posts/new.html.erb +++ b/app/views/posts/new.html.erb @@ -63,22 +63,20 @@ <p><%= I18n.t("posts.channels_not_found") %></p> <% end %> - <% if Rails.configuration.credentials[:enable_categories] %> - <div class="form-group"> - <h3><%= I18n.t("posts.category") %></h3> - <% if @current_user.categories.any? %> - <% categories = @current_user.categories.order(:sort).collect{ |cat| [cat.name, cat.id] } %> - <% categories.unshift(["-- #{I18n.t("posts.no_category")} --", nil])%> - <%= f.select(:category, categories) %> - <% end %><br><br> - <div class="form-inline"> - <div class="form-group"> - <%= f.text_field :category_name, size: 20, class: "form-control", autofocus: true, placeholder: I18n.t("posts.new_category") %> - <%= f.color_field :category_color, class: "color-picker" %> - </div> + <div class="form-group"> + <h3><%= I18n.t("posts.category") %></h3> + <% if @current_user.categories.any? %> + <% categories = @current_user.categories.order(:sort).collect{ |cat| [cat.name, cat.id] } %> + <% categories.unshift(["-- #{I18n.t("posts.no_category")} --", nil])%> + <%= f.select(:category, categories) %> + <% end %><br><br> + <div class="form-inline"> + <div class="form-group"> + <%= f.text_field :category_name, size: 20, class: "form-control", autofocus: true, placeholder: I18n.t("posts.new_category") %> + <%= f.color_field :category_color, class: "color-picker" %> </div> </div> - <% end %> + </div> <div class="form-group"> <h3><%= I18n.t("posts.privacy") %></h3> diff --git a/app/views/users/registrations/_manage_tab.html.erb b/app/views/users/registrations/_manage_tab.html.erb index 6d82e32..6c3082d 100644 --- a/app/views/users/registrations/_manage_tab.html.erb +++ b/app/views/users/registrations/_manage_tab.html.erb @@ -2,9 +2,7 @@ <div class="comments-container"><center><%=I18n.t("manage.manage_admins_only")%></center></div> <% else %> <div class="comments-container"> - <% if Rails.configuration.credentials[:enable_categories] %> - <%= render "users/registrations/manage/categories" %><br> - <% end %> + <%= render "users/registrations/manage/categories" %><br> <%= render "users/registrations/manage/invite_codes_gen" %><br> <%= render "users/registrations/manage/invite_codes", object: InviteCode.first(10) %><br> <%= (button_to I18n.t("manage.all_invite_codes_list"), full_invite_codes_list_path, class: "actions btn btn-primary", method: :get) if InviteCode.any? %><br> diff --git a/config/credentials.yml b/config/credentials.yml index 053c5b2..f6c3be4 100644 --- a/config/credentials.yml +++ b/config/credentials.yml @@ -12,7 +12,6 @@ development: max_upload_files: 10 max_file_size: 10 invite_codes_register_only: false - enable_categories: true redis: # set 'false' if you already have working redis server, or want # launch him manually with command: redis-server @@ -52,7 +51,6 @@ production: max_upload_files: 10 max_file_size: 10 invite_codes_register_only: false - enable_categories: true redis: # set 'false' if you already have working redis server, or want # launch him manually with command: redis-server @@ -86,5 +84,4 @@ test: rss_max_visible_posts: 50 max_upload_files: 10 max_file_size: 10 - invite_codes_register_only: false - enable_categories: true \ No newline at end of file + invite_codes_register_only: false \ No newline at end of file From 9f274aa715498caebd4961edcbc7c713de7f4ef4 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <Whiletruedoend@protonmail.com> Date: Sat, 13 Jul 2024 22:49:04 +0300 Subject: [PATCH 09/47] Another commit --- app/assets/stylesheets/base.scss | 14 ++++++++++++++ ...ed-_theme.scss => default_Dark_feed_theme.scss} | 0 app/controllers/posts_controller.rb | 6 +++--- app/helpers/application_helper.rb | 9 +++++++++ app/helpers/posts_helper.rb | 2 ++ app/views/pages/main.html.erb | 2 +- app/views/posts/rss.builder | 2 +- .../users/registrations/_edit_profile.html.erb | 1 + app/views/users/registrations/edit.html.erb | 9 ++++++--- config/locales/en.yml | 1 + config/locales/ru.yml | 1 + 11 files changed, 39 insertions(+), 8 deletions(-) rename app/assets/stylesheets/{default_-Dark feed-_theme.scss => default_Dark_feed_theme.scss} (100%) diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 69321d6..7c57be9 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -454,12 +454,26 @@ $color-black: #000000; color: $color-feed-music-icon; } + .audio-title{ + position: absolute; + /* min-height: 95%; + max-width: 95%; */ + margin-bottom: 70%; + display: flex; + justify-content: center; + color: $color-feed-music-icon; + font-size: 12px; + } + audio{ position: absolute; bottom: 0; width: 100%; } } + .vsc-controller{ /* wtf */ + display: none; + } } /* channels */ diff --git a/app/assets/stylesheets/default_-Dark feed-_theme.scss b/app/assets/stylesheets/default_Dark_feed_theme.scss similarity index 100% rename from app/assets/stylesheets/default_-Dark feed-_theme.scss rename to app/assets/stylesheets/default_Dark_feed_theme.scss diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 3d8305e..d1c208b 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -220,7 +220,7 @@ def rss if current_user.present? current_user else - (User.find_by(rss_token: params[:rss_token]) if params.key?(:rss_token)) + (User.find_by(rss_token: params[:token]) if params.key?(:token)) end tags = user&.active_tags&.any? ? user&.active_tags&.map { |i| i.tag.id } : Tag.all.ids @@ -243,7 +243,7 @@ def feed if current_user.present? current_user else - (User.find_by(rss_token: params[:rss_token]) if params.key?(:rss_token)) + (User.find_by(rss_token: params[:token]) if params.key?(:token)) end @posts = PostsSearch.new( current_user: user, @@ -257,7 +257,7 @@ def feed @posts = @posts.paginate(page: params[:page], per_page: 15).order(created_at: (params[:sort] == 'asc' ? 'asc' : 'desc')) @last_date = nil - if Rails.configuration.credentials[:fail2ban][:enabled] && user.nil? && params.key?(:rss_token) + if Rails.configuration.credentials[:fail2ban][:enabled] && user.nil? && params.key?(:token) ip = request.env['action_dispatch.remote_ip'] || request.env['REMOTE_ADDR'] Rails.logger.error("Failed bypass token from #{ip} at #{Time.now.utc.iso8601}") end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9ddbb6f..50f0c39 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -14,7 +14,16 @@ def markdown(text) redcarpet.render(text).html_safe end + def asset_exist?(path) + if Rails.configuration.assets.compile + Rails.application.precompiled_assets.include? path + else + Rails.application.assets_manifest.assets[path].present? + end + end + def current_theme + return "#{params[:theme]}_theme" if params.dig('theme').present? && asset_exist?("#{params[:theme]}_theme.css") current_user&.options&.dig('theme') || 'default_theme' end end diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index 1880f2a..32df14e 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -47,6 +47,7 @@ def display_attachments(post) poster: url_for(att.preview(resize_to_limit: [200, 200]).processed)).to_s elsif att.audio? content += "<div class=\"audio-cover\">" + content += "<div class=\"audio-title\">#{att.filename.to_s[0..64]}</div>" content += "<i class=\"fa fa-light fa-music\"></i>" content += "#{audio_tag(url_for(att), autoplay: false, controls: true)}" content += '</div>' @@ -109,6 +110,7 @@ def display_feed_attachments(post) # #{image_tag url_for(att.preview(resize_to_limit: [50, 50]).processed)}</a>" elsif att.audio? content += "<div class=\"audio-cover\">" + content += "<div class=\"audio-title\">#{att.filename.to_s[0..64]}</div>" content += "<i class=\"fa fa-light fa-music\"></i>" content += "#{audio_tag(url_for(att), autoplay: false, controls: true)}" content += '</div>' diff --git a/app/views/pages/main.html.erb b/app/views/pages/main.html.erb index 751d365..63bbb3e 100644 --- a/app/views/pages/main.html.erb +++ b/app/views/pages/main.html.erb @@ -73,7 +73,7 @@ </tr> <tr> <td><i class="fa fa-rss"></i></td> - <td><a target="_blank" href=<%=current_user.present? ? "/rss?rss_token=#{current_user[:rss_token]}" : "/rss" %>>RSS</a></td> + <td><a target="_blank" href=<%=current_user.present? ? "/rss?token=#{current_user[:token]}" : "/rss" %>>RSS</a></td> </tr> <tr> <th class="icon"></th> diff --git a/app/views/posts/rss.builder b/app/views/posts/rss.builder index e8913a4..d4c8440 100644 --- a/app/views/posts/rss.builder +++ b/app/views/posts/rss.builder @@ -8,7 +8,7 @@ xml.rss version: '2.0', 'xmlns:atom' => 'http://www.w3.org/2005/Atom' do xml.link root_url xml.language 'ru' xml.tag! 'atom:link', rel: 'self', type: 'application/rss+xml', - href: "#{request.base_url}/rss?rss_token=#{params.key?(:rss_token) && User.find_by(rss_token: params[:rss_token].to_s).present? ? params[:rss_token] : (current_user&.rss_token || 'none')}" + href: "#{request.base_url}/rss?token=#{params.key?(:token) && User.find_by(rss_token: params[:token].to_s).present? ? params[:token] : (current_user&.rss_token || 'none')}" xml.ttl '60' @posts.each do |post| diff --git a/app/views/users/registrations/_edit_profile.html.erb b/app/views/users/registrations/_edit_profile.html.erb index b13cfdc..df86d9c 100644 --- a/app/views/users/registrations/_edit_profile.html.erb +++ b/app/views/users/registrations/_edit_profile.html.erb @@ -5,6 +5,7 @@ <input type="text" size="28" class="form-control" id="token_field" readonly value="<%= current_user.rss_token || "#{I18n.t("edit.token_not_generated")}" %>"> </div> <button onclick="copy()" type="submit" class="btn btn-primary"><%= I18n.t("edit.copy") %></button> + <button onclick="copy_url()" type="submit" class="btn btn-primary"><%= I18n.t("edit.copy_url") %></button> </div> <br> diff --git a/app/views/users/registrations/edit.html.erb b/app/views/users/registrations/edit.html.erb index c23f548..30e628c 100644 --- a/app/views/users/registrations/edit.html.erb +++ b/app/views/users/registrations/edit.html.erb @@ -38,11 +38,14 @@ <script> function copy() { var copyText = document.getElementById("token_field"); - copyText.select(); copyText.setSelectionRange(0, 99999); /*For mobile devices*/ - - document.execCommand("copy"); + navigator.clipboard.writeText(copyText.value); + } + function copy_url(){ + var copyText = document.getElementById("token_field"); + url = window.location.protocol + '//' + window.location.host + '/rss?token=' + copyText.value; + navigator.clipboard.writeText(url); } </script> diff --git a/config/locales/en.yml b/config/locales/en.yml index ce4e2d0..66b4d75 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -112,6 +112,7 @@ en: name: Display name tags: Tags copy: Copy + copy_url: Copy URL main_information: Main Information personal_tags: Personal Tags avatar: Avatar diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 76abaf4..44bd5b5 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -112,6 +112,7 @@ ru: name: Отображаемое имя tags: Тэги copy: Скопировать + copy_url: Скопировать URL main_information: Основная Информация personal_tags: Персональные Тэги avatar: Аватар From 8fec5e43915134d368641ad5bec96f449875995e Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Tue, 16 Jul 2024 21:46:41 +0300 Subject: [PATCH 10/47] Dockerfile && packages update --- Dockerfile | 2 +- app/controllers/posts_controller.rb | 4 ++-- app/services/check_channel.rb | 3 +++ package.json | 26 +++++++++++++------------- update_log.md | 4 ++++ 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4e9c57a..ab1f10f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,7 +39,7 @@ COPY package.json yarn.lock ./ # NodeJS & yarn install RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash -RUN . ~/.nvm/nvm.sh && nvm install 22 && \ +RUN . ~/.nvm/nvm.sh && nvm install 16 && nvm alias default 16 && \ yarn install # We're back at the base stage diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index d1c208b..52df5c7 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -16,12 +16,12 @@ def track_action request_params = request.path_parameters if post.present? post_id = post.id.to_s - if not Ahoy::Event.where(name: "Post_#{post_id}", visit_id: current_visit.id).where_properties(id: post_id).exists? + if not Ahoy::Event.where(name: "Post_#{post_id}", visit_id: current_visit&.id).where_properties(id: post_id).exists? ahoy.track("Post_#{post_id}", request_params) end else request_properties = { action: request_params[:action], controller: request_params[:controller] } - if not Ahoy::Event.where(name: "Posts", visit_id: current_visit.id).where_properties(request_properties).exists? + if not Ahoy::Event.where(name: "Posts", visit_id: current_visit&.id).where_properties(request_properties).exists? ahoy.track("Posts", request_params) end end diff --git a/app/services/check_channel.rb b/app/services/check_channel.rb index 5ee87e8..d1fdb80 100644 --- a/app/services/check_channel.rb +++ b/app/services/check_channel.rb @@ -179,6 +179,9 @@ def check_matrix options[:avatar_size] = 0 end + #Channel URL (via matrix.to) + options[:url] = "https://matrix.to/#/#{room}" + @channel.options = options end end diff --git a/package.json b/package.json index 8f7cbfa..ce33d91 100644 --- a/package.json +++ b/package.json @@ -2,20 +2,20 @@ "name": "Twilight", "private": true, "dependencies": { - "@hotwired/turbo-rails": "^7.2.4", - "@rails/actioncable": "^6.0.0", - "@rails/activestorage": "^6.0.0", - "@rails/ujs": "^6.0.0", - "@rails/webpacker": "5.4.2", - "bootstrap-datepicker": "^1.9.0", - "dropzone": "^5.9.2", - "elliptic": "6.5.4", - "glob-parent": "^5.1.2", - "is-svg": "^4.2.2", - "medium-zoom": "^1.0.6", + "@hotwired/turbo-rails": "^8.0.4", + "@rails/actioncable": "^7.1.3", + "@rails/activestorage": "^7.1.3", + "@rails/ujs": "^7.1.3", + "@rails/webpacker": "5.4.4", + "bootstrap-datepicker": "^1.10.0", + "dropzone": "^5.9.3", + "elliptic": "6.5.5", + "glob-parent": "^6.0.2", + "is-svg": "^5.0.1", + "medium-zoom": "^1.1.0", "readmore-js": "^2.2.1", - "ssri": "8.0.1", - "stimulus": "^2.0.0", + "ssri": "10.0.6", + "stimulus": "^3.2.2", "turbolinks": "^5.2.0", "webpack": "^4.46.0", "webpack-cli": "^3.3.12" diff --git a/update_log.md b/update_log.md index 196ca55..9fe6e7b 100644 --- a/update_log.md +++ b/update_log.md @@ -10,9 +10,13 @@ * Для темы по-умолчанию добавлена отдельно тёмная подтема для feed; * Изменена тема 'Ruby'; * Для feed теперь также есть счётчик просмотров и комментарии; + * Предпросмотр темы через параметр `?theme=<название>` в URL-адресе; * Исправлен баг с плохо видимой каптчей; * Обновлены docker-контейнеры; * Добавлен файл devbox для NixOS; +* Параметр rss_token в строке `/rss?rss_token=<token>` заменён на: `/rss?token=<token>` +* Кнопка 'Скопировать URL' копирует весь URL-адрес (для rss); +* Новый вид аттачментов; * Удалена поддержка sqlite3; * Мелкие фиксы; From 7b9ed6f8e06c8f1fbaccf0334230dd87c438d3f7 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Tue, 16 Jul 2024 23:11:11 +0300 Subject: [PATCH 11/47] Matrix fixes --- app/helpers/users_helper.rb | 9 +++++++++ app/services/platform/delete_matrix_posts.rb | 3 ++- app/services/platform/send_post_to_matrix.rb | 18 +++++++++++------- .../users/registrations/_channels_tab.html.erb | 2 +- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index e59dc81..bf4b83e 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -8,4 +8,13 @@ def display_name(user) user&.login || 'Guest' end end + def channel_url(channel) + if channel.platform.title == "telegram" && channel.options&.dig("username").present? + link_to(channel.platform.title.capitalize, "https://t.me/#{channel.options&.dig("username")}", target: '_blank' ) + elsif channel.platform.title == "matrix" && channel.options&.dig("url").present? + link_to(channel.platform.title.capitalize, channel.options&.dig("url"), target: '_blank') + else + channel.platform.title.capitalize + end + end end diff --git a/app/services/platform/delete_matrix_posts.rb b/app/services/platform/delete_matrix_posts.rb index 632ae48..9f61671 100644 --- a/app/services/platform/delete_matrix_posts.rb +++ b/app/services/platform/delete_matrix_posts.rb @@ -14,7 +14,8 @@ def call matrix_token = platform_post.channel.token server = platform_post.channel.options['server'] begin - if platform_post.content.has_attachments? + # Matrix onlylink is a Hash, but attachments is an Array. + if platform_post.content.has_attachments? && !platform_post.identifier.is_a?(Hash) platform_post.identifier.each do |att| method = "rooms/#{att['room_id']}/redact/#{att['event_id']}" data = { reason: "Delete post ##{platform_post.post_id}" } diff --git a/app/services/platform/send_post_to_matrix.rb b/app/services/platform/send_post_to_matrix.rb index 7b8bcaa..3f2c7f1 100644 --- a/app/services/platform/send_post_to_matrix.rb +++ b/app/services/platform/send_post_to_matrix.rb @@ -44,11 +44,16 @@ def call unless @post.text.present? || (@post.text.blank? && @post.content_attachments.present?) # Content not created! content_text = params[:post][:content] - attachment_content = Content.create!(user: @post.user, post: @post, has_attachments: true) if @attachments.present? + attachment_content = Content.create!(user: @post.user, post: @post, has_attachments: true) unless @attachments.join(' ').empty? @attachments.each { |att| attachment_content.attachments.attach(att) } if attachment_content.present? Content.create!(user: @post.user, post: @post, text: content_text, has_attachments: false) if content_text.present? end + # TODO: Check compatibility with telegram + if @post.title.present? && @post.contents.where(has_attachments: false).empty? + Content.create!(user: @post.user, post: @post, text: "", has_attachments: false) + end + # No content - no post :\ Content.where(post: @post).order(:id).each do |content| title = @post.title @@ -66,7 +71,7 @@ def call if content.has_attachments? send_mx_attachments(content) - elsif content_text.present? + elsif content_text.present? || (!content_text.present? && title.present?) send_mx_content(content, text) break # If posts exists && content count >2, then for matrix PlatformPost content has first content id end @@ -79,9 +84,8 @@ def send_mx_content(content, text) options = channel_options(channel) if options[:onlylink] - post_link = "#{@base_url}/posts/#{@post.id}" - full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" - text = @post.title.present? ? "<b>#{@post.title}</b><br><br>#{full_post_link}" : full_post_link.to_s + send_mx_onlylink_post(content, channel, options) unless Content.where(post: @post, has_attachments: true).present? + next end method = "rooms/#{channel[:room]}/send/m.room.message" @@ -108,7 +112,7 @@ def send_mx_attachments(content) options = channel_options(channel) if options[:onlylink] - send_mx_onlylink_post(channel, options) + send_mx_onlylink_post(content, channel, options) next end @@ -188,7 +192,7 @@ def channel_options(channel) { enable_notifications: notification, onlylink: onlylink, caption: caption } end - def send_mx_onlylink_post(channel, options) + def send_mx_onlylink_post(content, channel, options) post_link = "#{@base_url}/posts/#{@post.id}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" text = @post.title.present? ? "<b>#{@post.title}</b><br>#{full_post_link}" : full_post_link.to_s diff --git a/app/views/users/registrations/_channels_tab.html.erb b/app/views/users/registrations/_channels_tab.html.erb index d262e1e..358d648 100644 --- a/app/views/users/registrations/_channels_tab.html.erb +++ b/app/views/users/registrations/_channels_tab.html.erb @@ -7,7 +7,7 @@ <div class="comment"> <div class="comment-avatar"><% if channel&.avatar&.present?%><%= image_tag url_for(channel.avatar) %><% end %></div> <% name = channel.options&.dig("title") %> - <div class="comment-metadata"><%= name %> <i class="comment-small"> | <%= channel.options&.dig("username").present? ? link_to(channel.platform.title.capitalize, "https://t.me/#{channel.options&.dig("username")}" ) : channel.platform.title.capitalize %> | <%= link_to I18n.t("channels.update"), edit_channel_url(channel) %> | <a href="/channels/delete/<%=channel.id%>" data-confirm="<%= I18n.t("channels.delete_channel_confirm") %>"><%= I18n.t("channels.delete") %></a></i><i class="comment-small right">ID: <%= channel.room %></i></div> + <div class="comment-metadata"><%= name %> <i class="comment-small"> | <%= channel_url(channel) %> | <%= link_to I18n.t("channels.update"), edit_channel_url(channel) %> | <a href="/channels/delete/<%=channel.id%>" data-confirm="<%= I18n.t("channels.delete_channel_confirm") %>"><%= I18n.t("channels.delete") %></a></i><i class="comment-small right">ID: <%= channel.room %></i></div> <%=I18n.t("channels.status")%>: <%= channel.enabled ? I18n.t("channels.enabled") : I18n.t("channels.disabled") %><br> <%=I18n.t("channels.comments")%>: <%= channel.options["comments_enabled"] ? I18n.t("channels.c_enabled") : I18n.t("channels.c_disabled") %><br> Posts count: <%= channel.platform_posts.group(:post).count.count %> From 3d35690e487094285b606b37389870a952574a54 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Wed, 17 Jul 2024 22:50:50 +0300 Subject: [PATCH 12/47] Matrix update fixes --- app/services/platform/update_matrix_posts.rb | 51 ++++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/app/services/platform/update_matrix_posts.rb b/app/services/platform/update_matrix_posts.rb index e493e60..af900c8 100644 --- a/app/services/platform/update_matrix_posts.rb +++ b/app/services/platform/update_matrix_posts.rb @@ -24,13 +24,23 @@ def call platform_posts = @post.platform_posts.where(platform: Platform.where(title: 'matrix')) need_delete_attachments = false + platform_posts.joins(:content).where(contents: { has_attachments: true }).each do |platform_post| + if platform_post.identifier.is_a?(Hash) && platform_post.identifier.dig('options', 'onlylink') + room_id = platform_post[:identifier]['room_id'] + event_id = platform_post[:identifier]['event_id'] + edit_mx_onlylink_post(platform_post, room_id, event_id) + next + end + end + if @deleted_attachments.present? attachments = @deleted_attachments.to_unsafe_h del_att = attachments.select { |val| attachments[val] == '0' } need_delete_attachments = true if del_att.any? platform_posts.joins(:content).where(contents: { has_attachments: true }).each do |platform_post| - next if platform_post.identifier[0].dig('options', 'onlylink') # array + next if platform_post.identifier.is_a?(Array) && platform_post.identifier[0].dig('options', 'onlylink') + next if platform_post.identifier.is_a?(Hash) && platform_post.identifier.dig('options', 'onlylink') matrix_token = platform_post.channel.token server = platform_post.channel.options['server'] @@ -76,11 +86,17 @@ def call text = text.replace_html_to_mx_markdown if text.present? platform_posts.joins(:content).where(contents: { has_attachments: false }).each do |platform_post| - next if platform_post.identifier.dig('options', 'onlylink') # not array + room_id = platform_post[:identifier]['room_id'] + event_id = platform_post[:identifier]['event_id'] + + if platform_post.identifier.dig('options', 'onlylink') # not array + edit_mx_onlylink_post(platform_post, room_id, event_id) + next + end matrix_token = platform_post.channel.token server = platform_post.channel.options['server'] - method = "rooms/#{platform_post[:identifier]['room_id']}/send/m.room.message" + method = "rooms/#{room_id}/send/m.room.message" data = { msgtype: 'm.text', format: 'org.matrix.custom.html', @@ -93,7 +109,7 @@ def call formatted_body: text }, 'm.relates_to': { - event_id: platform_post[:identifier]['event_id'], + event_id: event_id, rel_type: 'm.replace' } } @@ -140,4 +156,31 @@ def call @post.content_attachments.find_by(blob_id: blob_id).purge if blob_id.present? && attachment[1] == '0' end end + + def edit_mx_onlylink_post(platform_post, room_id, event_id) + post_link = "#{@base_url}/posts/#{@post.id}" + full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" + text = @post.title.present? ? "<b>#{@post.title}</b><br>#{full_post_link}" : full_post_link.to_s + + matrix_token = platform_post.channel.token + server = platform_post.channel.options['server'] + method = "rooms/#{room_id}/send/m.room.message" + data = { + msgtype: 'm.text', + format: 'org.matrix.custom.html', + body: text, + formatted_body: text, + 'm.new_content': { + msgtype: 'm.text', + format: 'org.matrix.custom.html', + body: text, + formatted_body: text + }, + 'm.relates_to': { + event_id: event_id, + rel_type: 'm.replace' + } + } + Matrix.post(server, matrix_token, method, data) + end end From 27f2ceae762d9cbbe58236692d26e96374ff4797 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 18 Jul 2024 12:49:23 +0300 Subject: [PATCH 13/47] Links to platform posts in post header --- README-RU.md | 11 - README.md | 11 - app/helpers/posts_helper.rb | 32 +- app/models/platform_post.rb | 17 + app/models/post.rb | 4 + app/views/posts/_post_header.html.erb | 10 + update_log.md | 6 +- vendor/gems.tar.gz | Bin 0 -> 262806 bytes yarn.lock | 7290 ------------------------- 9 files changed, 51 insertions(+), 7330 deletions(-) create mode 100644 vendor/gems.tar.gz delete mode 100644 yarn.lock diff --git a/README-RU.md b/README-RU.md index b127b7c..7d782fc 100644 --- a/README-RU.md +++ b/README-RU.md @@ -14,9 +14,7 @@ Also available EN version => [README.md](https://github.com/Whiletruedoend/Twili + [Продакшн](#Продакшн) * [Текущие возможности](#Текущие-возможности) * [Баги и некоторые особенности](#Баги-и-некоторые-особенности) -* [Вопрос к безопасности](#Вопрос-к-безопасности) * [Схемы и скриншоты](#Схемы-и-скриншоты) -* [Contribution](#Contribution) * [Связь](#Связь) <img src="https://i.imgur.com/j6FCqsv.png"></img> @@ -205,15 +203,6 @@ gem install wdm -- --with-cflags=-Wno-implicit-function-declaration Мне лень их фиксить, кто захочет (было бы очень круто), то с радостью приму Pull Request; -### Вопрос к безопасности - -Сейчас получается так, что, если у пользователя есть несколько каналов, то даже имея несколько токенов авторизации, для предзагрузки картинок (аттачментов) используется какой-то один (самый первый указанный). - -Но допустим такую ситуацию: у пользователя есть 2 канала (2 тонена соответственно), и есть второй человек, который знает первый токен, но не знает второй. Тогда исходя из логики, что все аттачменты загружаются во временный канал от первого токена, он может просто перехватить информацию которая предназначалась для второго токена. - -Только вот информация с первого токена идентична информации со второго токена (ведь контент заливается на разные каналы один и тот же!), поэтому даже перехватив эту информацию условный злоумышленник в результате получит то же самое. - -Поэтому серъёной угрозы это вроде не несёт. Но на всякий случай предупредил, чтобы не было вопросов. ## Схемы и скриншоты ER-диаграмма: <img src="https://i.imgur.com/RQQCRpa.jpeg"></img> diff --git a/README.md b/README.md index ef5a8ed..88ccb80 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,7 @@ + [Production](#Production) * [Current features](#Current-features) * [Bugs and some features](#Bugs-and-some-features) -* [Security question](#Security-question) * [Schemas and screenshots](#Schemas-and-screenshots) -* [Contribution](#Contribution) * [Contact](#Contact) <img src="https://i.imgur.com/j6FCqsv.png"></img> @@ -207,15 +205,6 @@ Bugs: I'm too lazy to fix them, whoever wants (it would be very cool), then I will gladly accept the Pull Request; -### Security question - -Now it turns out that if a user has several channels, then even having several authorization tokens, only one (the very first specified) is used to preload pictures (attachments). - -But let's say this situation: the user has 2 channels (2 tones, respectively), and there is a second person who knows the first token, but does not know the second. Then, proceeding from the logic that all attachments are loaded into a temporary channel from the first token, he can simply intercept the information that was intended for the second token. - -Only the information from the first token is identical to the information from the second token (after all, the content is uploaded to different channels the same!), So even by intercepting this information, a conditional attacker will receive the same result. - -Therefore, this does not seem to carry a serious threat. But just in case, he warned that there were no questions. ## Schemas and screenshots ER-diagram(Ver. 1.0.1): <img src="https://i.imgur.com/RQQCRpa.jpeg"></img> diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index 32df14e..16aa81c 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -6,22 +6,6 @@ def get_post_tags(post) tags.presence || 'no tags' end - def get_published_platforms(post) - post.platform_posts.map { |p| { name: p.platform.title, link: post_link(p) } } - end - - def post_link(post) - case p.platform.title - when 'telegram' - chat = Telegram.bot.get_chat(chat_id: post.identifier['chat_id']) - if chat['result']['username'].present? && (chat['result']['type'] != 'private') - "https://t.me/#{chat['result']['username']}/#{post.identifier['message_id']}" - end - # else # TODO: moare platform support! - # nil - end - end - def get_full_attachment_link(att) "#{request.base_url}#{url_for(att)}" end @@ -161,6 +145,22 @@ def render_title(post) post.title.present? ? link_markdown(post.title).html_safe : "##{post.id}" end + def render_published_platforms(post) + content = '' + post.published_platforms.each_with_index do |(k,v), i| + vv = v.reject{ |vv| vv[:channel_name].nil?}.uniq{ |u| u[:channel_name] } + next if vv.empty? + content += "#{k.capitalize}: " + content += ' | ' if i != 0 + vv.each_with_index do |vvv, ii| + content += vvv[:url].present? ? link_to(vvv[:channel_name], vvv[:url]) : "#{vvv[:channel_name]} (Private)" + content += ', ' if ii != vv.size - 1 + end + end + # content += '-' if content.empty? # Post present, but channel was deleted + content.html_safe + end + def link_markdown(title) title.gsub(/\[(.*?)\]\((.*?)\)/) { |_m| "<a href=\"#{Regexp.last_match(2)}\">#{Regexp.last_match(1)}</a>" } end diff --git a/app/models/platform_post.rb b/app/models/platform_post.rb index 28882d0..1a33c46 100644 --- a/app/models/platform_post.rb +++ b/app/models/platform_post.rb @@ -6,4 +6,21 @@ class PlatformPost < ApplicationRecord belongs_to :content belongs_to :channel belongs_to :platform + + def post_link + case platform.title + when 'telegram' + # TODO: WTF?? Make local + chat = Telegram.bot.get_chat(chat_id: identifier['chat_id']) + if chat['result']['username'].present? && (chat['result']['type'] != 'private') + "https://t.me/#{chat['result']['username']}/#{identifier['message_id']}" + end + when 'matrix' + return "" if channel.nil? + event_id = identifier.is_a?(Hash) ? identifier["event_id"] : identifier[0]["event_id"] + server = channel.options.dig("server") + url = (server == "https://matrix.org/_matrix/") ? "https://app.element.io/#/room/#{channel.room}" : channel.options.dig("url") + "#{url}/#{event_id}" + end + end end diff --git a/app/models/post.rb b/app/models/post.rb index c85b190..8eac363 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -31,6 +31,10 @@ def self.get_posts(params, current_user) posts end + def published_platforms + platform_posts.map { |p| { name: p.platform.title, url: p.post_link, channel_name: p.channel&.options&.dig("title") } }.group_by{ |g| g[:name] } + end + def active_tags_names active_tags.map { |s| s.tag.name } end diff --git a/app/views/posts/_post_header.html.erb b/app/views/posts/_post_header.html.erb index 6e4a4d4..a61e7a4 100644 --- a/app/views/posts/_post_header.html.erb +++ b/app/views/posts/_post_header.html.erb @@ -28,6 +28,16 @@ <% end %> </i> </p> + <% if post.platform_posts.any? %> + <% r = render_published_platforms(post) %> + <% unless r.empty? %> + <p class="metadata"> + <i class="fa fa-tasks"> + <%= r %> + </i> + </p> + <% end %> + <% end %> <% if params[:action] == "show" %> <% if post.category&.name.present? %> diff --git a/update_log.md b/update_log.md index 9fe6e7b..7cbb2a2 100644 --- a/update_log.md +++ b/update_log.md @@ -1,6 +1,6 @@ ### Format: Y-M-D -### 2024-0x-xx => 1.0.2 +### 2024-0x-xx => 1.1 * Обновлён Ruby 2.7.7->3.3.2; * Поддержка OpenGraph для предпросмотра постов; @@ -11,10 +11,12 @@ * Изменена тема 'Ruby'; * Для feed теперь также есть счётчик просмотров и комментарии; * Предпросмотр темы через параметр `?theme=<название>` в URL-адресе; +* Добавлены ссылки на каналы платформ в шапке поста; * Исправлен баг с плохо видимой каптчей; +* Исправлен баги постинга в Matrix; * Обновлены docker-контейнеры; * Добавлен файл devbox для NixOS; -* Параметр rss_token в строке `/rss?rss_token=<token>` заменён на: `/rss?token=<token>` +* Параметр rss_token в строке `/rss?rss_token=<token>` заменён на: `/rss?token=<token>`; * Кнопка 'Скопировать URL' копирует весь URL-адрес (для rss); * Новый вид аттачментов; * Удалена поддержка sqlite3; diff --git a/vendor/gems.tar.gz b/vendor/gems.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..fe897446eb67cdbea389b8dd7e5244f5076e2c20 GIT binary patch literal 262806 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 F#=z$ZaCJw)UEbWB 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 gp-G59Z2VW=e&ceaPKAzU2;IR^?ekCls@I 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 z%0D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 TUz=d>H!<Rm{|AT=6v+PrKqahE literal 0 HcmV?d00001 diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 9c9169e..0000000 --- a/yarn.lock +++ /dev/null @@ -1,7290 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.1.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== - dependencies: - "@jridgewell/gen-mapping" "^0.1.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5": - version "7.20.14" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.14.tgz#4106fc8b755f3e3ee0a0a7c27dde5de1d2b2baf8" - integrity sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw== - -"@babel/core@^7.15.0": - version "7.20.12" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" - integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.7" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helpers" "^7.20.7" - "@babel/parser" "^7.20.7" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.12" - "@babel/types" "^7.20.7" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.2" - semver "^6.3.0" - -"@babel/generator@^7.20.7": - version "7.20.14" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.14.tgz#9fa772c9f86a46c6ac9b321039400712b96f64ce" - integrity sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg== - dependencies: - "@babel/types" "^7.20.7" - "@jridgewell/gen-mapping" "^0.3.2" - jsesc "^2.5.1" - -"@babel/helper-annotate-as-pure@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" - integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" - integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.18.6" - "@babel/types" "^7.18.9" - -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0", "@babel/helper-compilation-targets@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" - integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== - dependencies: - "@babel/compat-data" "^7.20.5" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.21.3" - lru-cache "^5.1.1" - semver "^6.3.0" - -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.5", "@babel/helper-create-class-features-plugin@^7.20.7": - version "7.20.12" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz#4349b928e79be05ed2d1643b20b99bb87c503819" - integrity sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.20.7" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.20.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/helper-split-export-declaration" "^7.18.6" - -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz#5ea79b59962a09ec2acf20a963a01ab4d076ccca" - integrity sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.2.1" - -"@babel/helper-define-polyfill-provider@^0.3.3": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" - integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== - dependencies: - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - semver "^6.1.2" - -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-explode-assignable-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" - integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== - dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" - -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-member-expression-to-functions@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz#a6f26e919582275a93c3aa6594756d71b0bb7f05" - integrity sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw== - dependencies: - "@babel/types" "^7.20.7" - -"@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz#df4c7af713c557938c50ea3ad0117a7944b2f1b0" - integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.20.2" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.10" - "@babel/types" "^7.20.7" - -"@babel/helper-optimise-call-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" - integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" - integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== - -"@babel/helper-remap-async-to-generator@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" - integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-wrap-function" "^7.18.9" - "@babel/types" "^7.18.9" - -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" - integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.20.7" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.7" - "@babel/types" "^7.20.7" - -"@babel/helper-simple-access@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" - integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== - dependencies: - "@babel/types" "^7.20.2" - -"@babel/helper-skip-transparent-expression-wrappers@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684" - integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== - dependencies: - "@babel/types" "^7.20.0" - -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== - -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== - -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== - -"@babel/helper-wrap-function@^7.18.9": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3" - integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q== - dependencies: - "@babel/helper-function-name" "^7.19.0" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.5" - "@babel/types" "^7.20.5" - -"@babel/helpers@^7.20.7": - version "7.20.13" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.13.tgz#e3cb731fb70dc5337134cadc24cbbad31cc87ad2" - integrity sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg== - dependencies: - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.13" - "@babel/types" "^7.20.7" - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.20.13", "@babel/parser@^7.20.7": - version "7.20.15" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.15.tgz#eec9f36d8eaf0948bb88c87a46784b5ee9fd0c89" - integrity sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg== - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" - integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz#d9c85589258539a22a901033853101a6198d4ef1" - integrity sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/plugin-proposal-optional-chaining" "^7.20.7" - -"@babel/plugin-proposal-async-generator-functions@^7.20.1": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326" - integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-remap-async-to-generator" "^7.18.9" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-proposal-class-properties@^7.14.5", "@babel/plugin-proposal-class-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" - integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-class-static-block@^7.18.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.20.7.tgz#92592e9029b13b15be0f7ce6a7aedc2879ca45a7" - integrity sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.20.7" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - -"@babel/plugin-proposal-dynamic-import@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94" - integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - -"@babel/plugin-proposal-export-namespace-from@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" - integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-proposal-json-strings@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" - integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83" - integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" - integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-proposal-numeric-separator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" - integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-proposal-object-rest-spread@^7.14.7", "@babel/plugin-proposal-object-rest-spread@^7.20.2": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" - integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== - dependencies: - "@babel/compat-data" "^7.20.5" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.20.7" - -"@babel/plugin-proposal-optional-catch-binding@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" - integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - -"@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz#49f2b372519ab31728cc14115bb0998b15bfda55" - integrity sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -"@babel/plugin-proposal-private-methods@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" - integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-private-property-in-object@^7.18.6": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz#309c7668f2263f1c711aa399b5a9a6291eef6135" - integrity sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.20.5" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - -"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" - integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" - integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" - integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-import-assertions@^7.20.0": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4" - integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" - integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-transform-arrow-functions@^7.18.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz#bea332b0e8b2dab3dafe55a163d8227531ab0551" - integrity sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-async-to-generator@^7.18.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" - integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== - dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-remap-async-to-generator" "^7.18.9" - -"@babel/plugin-transform-block-scoped-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" - integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-block-scoping@^7.20.2": - version "7.20.15" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.15.tgz#3e1b2aa9cbbe1eb8d644c823141a9c5c2a22392d" - integrity sha512-Vv4DMZ6MiNOhu/LdaZsT/bsLRxgL94d269Mv4R/9sp6+Mp++X/JqypZYypJXLlM4mlL352/Egzbzr98iABH1CA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-classes@^7.20.2": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz#f438216f094f6bb31dc266ebfab8ff05aecad073" - integrity sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-replace-supers" "^7.20.7" - "@babel/helper-split-export-declaration" "^7.18.6" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.18.9": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz#704cc2fd155d1c996551db8276d55b9d46e4d0aa" - integrity sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/template" "^7.20.7" - -"@babel/plugin-transform-destructuring@^7.14.7", "@babel/plugin-transform-destructuring@^7.20.2": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz#8bda578f71620c7de7c93af590154ba331415454" - integrity sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" - integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-duplicate-keys@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" - integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-exponentiation-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" - integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-for-of@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" - integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" - integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== - dependencies: - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" - integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-member-expression-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" - integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-modules-amd@^7.19.6": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a" - integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g== - dependencies: - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-modules-commonjs@^7.19.6": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz#8cb23010869bf7669fd4b3098598b6b2be6dc607" - integrity sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw== - dependencies: - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-simple-access" "^7.20.2" - -"@babel/plugin-transform-modules-systemjs@^7.19.6": - version "7.20.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz#467ec6bba6b6a50634eea61c9c232654d8a4696e" - integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw== - dependencies: - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.20.11" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-validator-identifier" "^7.19.1" - -"@babel/plugin-transform-modules-umd@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" - integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== - dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8" - integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.20.5" - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-new-target@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" - integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-object-super@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" - integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.6" - -"@babel/plugin-transform-parameters@^7.20.1", "@babel/plugin-transform-parameters@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz#0ee349e9d1bc96e78e3b37a7af423a4078a7083f" - integrity sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - -"@babel/plugin-transform-property-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" - integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-regenerator@^7.14.5", "@babel/plugin-transform-regenerator@^7.18.6": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d" - integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - regenerator-transform "^0.15.1" - -"@babel/plugin-transform-reserved-words@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" - integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-runtime@^7.15.0": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz#9d2a9dbf4e12644d6f46e5e75bfbf02b5d6e9194" - integrity sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw== - dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" - babel-plugin-polyfill-corejs2 "^0.3.3" - babel-plugin-polyfill-corejs3 "^0.6.0" - babel-plugin-polyfill-regenerator "^0.4.1" - semver "^6.3.0" - -"@babel/plugin-transform-shorthand-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" - integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-spread@^7.19.0": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e" - integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw== - dependencies: - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" - -"@babel/plugin-transform-sticky-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" - integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-template-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" - integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-typeof-symbol@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" - integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" - integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - -"@babel/plugin-transform-unicode-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" - integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/preset-env@^7.15.0": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.20.2.tgz#9b1642aa47bb9f43a86f9630011780dab7f86506" - integrity sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg== - dependencies: - "@babel/compat-data" "^7.20.1" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.20.1" - "@babel/plugin-proposal-class-properties" "^7.18.6" - "@babel/plugin-proposal-class-static-block" "^7.18.6" - "@babel/plugin-proposal-dynamic-import" "^7.18.6" - "@babel/plugin-proposal-export-namespace-from" "^7.18.9" - "@babel/plugin-proposal-json-strings" "^7.18.6" - "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" - "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.20.2" - "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-private-methods" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object" "^7.18.6" - "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.20.0" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.18.6" - "@babel/plugin-transform-async-to-generator" "^7.18.6" - "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.20.2" - "@babel/plugin-transform-classes" "^7.20.2" - "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.20.2" - "@babel/plugin-transform-dotall-regex" "^7.18.6" - "@babel/plugin-transform-duplicate-keys" "^7.18.9" - "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-for-of" "^7.18.8" - "@babel/plugin-transform-function-name" "^7.18.9" - "@babel/plugin-transform-literals" "^7.18.9" - "@babel/plugin-transform-member-expression-literals" "^7.18.6" - "@babel/plugin-transform-modules-amd" "^7.19.6" - "@babel/plugin-transform-modules-commonjs" "^7.19.6" - "@babel/plugin-transform-modules-systemjs" "^7.19.6" - "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" - "@babel/plugin-transform-new-target" "^7.18.6" - "@babel/plugin-transform-object-super" "^7.18.6" - "@babel/plugin-transform-parameters" "^7.20.1" - "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.18.6" - "@babel/plugin-transform-reserved-words" "^7.18.6" - "@babel/plugin-transform-shorthand-properties" "^7.18.6" - "@babel/plugin-transform-spread" "^7.19.0" - "@babel/plugin-transform-sticky-regex" "^7.18.6" - "@babel/plugin-transform-template-literals" "^7.18.9" - "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.18.10" - "@babel/plugin-transform-unicode-regex" "^7.18.6" - "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.20.2" - babel-plugin-polyfill-corejs2 "^0.3.3" - babel-plugin-polyfill-corejs3 "^0.6.0" - babel-plugin-polyfill-regenerator "^0.4.1" - core-js-compat "^3.25.1" - semver "^6.3.0" - -"@babel/preset-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" - integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/regjsgen@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" - integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== - -"@babel/runtime@^7.15.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": - version "7.20.13" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b" - integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA== - dependencies: - regenerator-runtime "^0.13.11" - -"@babel/template@^7.18.10", "@babel/template@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" - integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - -"@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7": - version "7.20.13" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.13.tgz#817c1ba13d11accca89478bd5481b2d168d07473" - integrity sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.7" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.13" - "@babel/types" "^7.20.7" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.4.4": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" - integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@csstools/convert-colors@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" - integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== - -"@gar/promisify@^1.0.1": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" - integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== - -"@hotwired/turbo-rails@^7.2.4": - version "7.2.5" - resolved "https://registry.yarnpkg.com/@hotwired/turbo-rails/-/turbo-rails-7.2.5.tgz#74fc3395a29a76df2bb8835aa88c86885cffde4c" - integrity sha512-F8ztmARxd/XBdevRa//HoJGZ7u+Unb0J7cQUeUP+pBvt9Ta2TJJ7a2TORAOhjC8Zgxx+LKwm/1UUHqN3ojjiGw== - dependencies: - "@hotwired/turbo" "^7.2.5" - "@rails/actioncable" "^7.0" - -"@hotwired/turbo@^7.2.5": - version "7.2.5" - resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-7.2.5.tgz#2d9d6bde8a9549c3aea8970445ade16ffd56719a" - integrity sha512-o5PByC/mWkmTe4pWnKrixhPECJUxIT/NHtxKqjq7n9Fj6JlNza1pgxdTCJVIq+PI0j95U+7mA3N4n4A/QYZtZQ== - -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== - dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== - -"@npmcli/fs@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" - integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== - dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" - -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - -"@rails/actioncable@^6.0.0": - version "6.1.7" - resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.7.tgz#8b4506925d3f7146a70941e4db7ce9dda99f99ae" - integrity sha512-F6S74NGpxhbbDRFsQFGYqefRfZPgYvePNtz9hHKYOqLturrsqrDoG+UcrxEGHsvqDUorMYfx4Wl3K8smmk/u2g== - -"@rails/actioncable@^7.0": - version "7.0.4" - resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.0.4.tgz#70a3ca56809f7aaabb80af2f9c01ae51e1a8ed41" - integrity sha512-tz4oM+Zn9CYsvtyicsa/AwzKZKL+ITHWkhiu7x+xF77clh2b4Rm+s6xnOgY/sGDWoFWZmtKsE95hxBPkgQQNnQ== - -"@rails/activestorage@^6.0.0": - version "6.1.7" - resolved "https://registry.yarnpkg.com/@rails/activestorage/-/activestorage-6.1.7.tgz#5aaae9f4d10800fdb4fd6fe26fd8b4218579c6e3" - integrity sha512-h++k8LBLns4O8AqzdaFp1TsCLP9VSc2hgWI37bjzJ+4D995X7Rd8kdkRmXRaNAUlHDJgy6RpnbhBJ5oiIgWTDw== - dependencies: - spark-md5 "^3.0.0" - -"@rails/ujs@^6.0.0": - version "6.1.7" - resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.7.tgz#b09dc5b2105dd267e8374c47e4490240451dc7f6" - integrity sha512-0e7WQ4LE/+LEfW2zfAw9ppsB6A8RmxbdAUPAF++UT80epY+7emuQDkKXmaK0a9lp6An50RvzezI0cIQjp1A58w== - -"@rails/webpacker@5.4.2": - version "5.4.2" - resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.4.2.tgz#efc94f7ff396529411bb02a9da964e269ff2edb0" - integrity sha512-35qQWK2HCHGx2TT0UH0vuo5PyXQaahB2KJeGWOVl0jDPfwbvC4yvTq3+0/lhD5uOJx2eraGLRaIN4umwFCsksw== - dependencies: - "@babel/core" "^7.15.0" - "@babel/plugin-proposal-class-properties" "^7.14.5" - "@babel/plugin-proposal-object-rest-spread" "^7.14.7" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-destructuring" "^7.14.7" - "@babel/plugin-transform-regenerator" "^7.14.5" - "@babel/plugin-transform-runtime" "^7.15.0" - "@babel/preset-env" "^7.15.0" - "@babel/runtime" "^7.15.3" - babel-loader "^8.2.2" - babel-plugin-dynamic-import-node "^2.3.3" - babel-plugin-macros "^2.8.0" - case-sensitive-paths-webpack-plugin "^2.4.0" - compression-webpack-plugin "^4.0.1" - core-js "^3.16.2" - css-loader "^3.6.0" - file-loader "^6.2.0" - flatted "^3.2.2" - glob "^7.1.7" - js-yaml "^3.14.1" - mini-css-extract-plugin "^0.9.0" - optimize-css-assets-webpack-plugin "^5.0.8" - path-complete-extname "^1.0.0" - pnp-webpack-plugin "^1.7.0" - postcss-flexbugs-fixes "^4.2.1" - postcss-import "^12.0.1" - postcss-loader "^3.0.0" - postcss-preset-env "^6.7.0" - postcss-safe-parser "^4.0.2" - regenerator-runtime "^0.13.9" - sass "^1.38.0" - sass-loader "10.1.1" - style-loader "^1.3.0" - terser-webpack-plugin "^4.2.3" - webpack "^4.46.0" - webpack-assets-manifest "^3.1.1" - webpack-cli "^3.3.12" - webpack-sources "^1.4.3" - -"@stimulus/core@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@stimulus/core/-/core-2.0.0.tgz#140c85318d6a8a8210c0faf182223b8459348877" - integrity sha512-ff70GafKtzc8zQ1/cG+UvL06GcifPWovf2wBEdjLMh9xO2GOYURO3y2RYgzIGYUIBefQwyfX2CLfJdZFJrEPTw== - dependencies: - "@stimulus/mutation-observers" "^2.0.0" - -"@stimulus/multimap@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@stimulus/multimap/-/multimap-2.0.0.tgz#420cfa096ed6538df4a91dbd2b2842c1779952b2" - integrity sha512-pMBCewkZCFVB3e5mEMoyO9+9aKzHDITmf3OnPun51YWxlcPdHcwbjqm1ylK63fsoduIE+RowBpFwFqd3poEz4w== - -"@stimulus/mutation-observers@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@stimulus/mutation-observers/-/mutation-observers-2.0.0.tgz#3dbe37453bda47a6c795a90204ee8d77a799fb87" - integrity sha512-kx4VAJdPhIGBQKGIoUDC2tupEKorG3A+ckc2b1UiwInKTMAC1axOHU8ebcwhaJIxRqIrs8//4SJo9YAAOx6FEg== - dependencies: - "@stimulus/multimap" "^2.0.0" - -"@stimulus/webpack-helpers@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@stimulus/webpack-helpers/-/webpack-helpers-2.0.0.tgz#54296d2a2dffd4f962d2e802d99a3fdd84b8845f" - integrity sha512-D6tJWsAC024MwGEIKlUVYU8Ln87mlrmiwHvYAjipg+s8H4eLxUMQ3PZkWyPevfipH+oR3leuHsjYsK1gN5ViQA== - -"@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bonjour@^3.5.9": - version "3.5.10" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" - integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== - dependencies: - "@types/node" "*" - -"@types/connect-history-api-fallback@^1.3.5": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" - integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.17.33" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" - integrity sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express@*", "@types/express@^4.17.13": - version "4.17.17" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" - integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/http-proxy@^1.17.8": - version "1.17.9" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" - integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw== - dependencies: - "@types/node" "*" - -"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== - -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - -"@types/node@*": - version "18.13.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.13.0.tgz#0400d1e6ce87e9d3032c19eb6c58205b0d3f7850" - integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg== - -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/q@^1.5.1": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" - integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ== - -"@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - -"@types/serve-index@^1.9.1": - version "1.9.1" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" - integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== - dependencies: - "@types/express" "*" - -"@types/serve-static@*", "@types/serve-static@^1.13.10": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" - integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== - dependencies: - "@types/mime" "*" - "@types/node" "*" - -"@types/sockjs@^0.3.33": - version "0.3.33" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" - integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== - dependencies: - "@types/node" "*" - -"@types/ws@^8.5.1": - version "8.5.4" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" - integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== - dependencies: - "@types/node" "*" - -"@webassemblyjs/ast@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" - integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== - dependencies: - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - -"@webassemblyjs/floating-point-hex-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" - integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== - -"@webassemblyjs/helper-api-error@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" - integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== - -"@webassemblyjs/helper-buffer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" - integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== - -"@webassemblyjs/helper-code-frame@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" - integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== - dependencies: - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/helper-fsm@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" - integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== - -"@webassemblyjs/helper-module-context@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" - integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== - dependencies: - "@webassemblyjs/ast" "1.9.0" - -"@webassemblyjs/helper-wasm-bytecode@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" - integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== - -"@webassemblyjs/helper-wasm-section@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" - integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - -"@webassemblyjs/ieee754@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" - integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" - integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" - integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== - -"@webassemblyjs/wasm-edit@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" - integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/helper-wasm-section" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-opt" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - "@webassemblyjs/wast-printer" "1.9.0" - -"@webassemblyjs/wasm-gen@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" - integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wasm-opt@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" - integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-buffer" "1.9.0" - "@webassemblyjs/wasm-gen" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - -"@webassemblyjs/wasm-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" - integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-wasm-bytecode" "1.9.0" - "@webassemblyjs/ieee754" "1.9.0" - "@webassemblyjs/leb128" "1.9.0" - "@webassemblyjs/utf8" "1.9.0" - -"@webassemblyjs/wast-parser@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" - integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/floating-point-hex-parser" "1.9.0" - "@webassemblyjs/helper-api-error" "1.9.0" - "@webassemblyjs/helper-code-frame" "1.9.0" - "@webassemblyjs/helper-fsm" "1.9.0" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/wast-printer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" - integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - "@xtuc/long" "4.2.2" - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn@^6.4.1: - version "6.4.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" - integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== - -acorn@^8.5.0: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" - -ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.0, ajv@^8.8.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -alphanum-sort@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ== - -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - -ansi-regex@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" - integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-flatten@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== - -array.prototype.reduce@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz#6b20b0daa9d9734dd6bc7ea66b5bbce395471eac" - integrity sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - es-array-method-boxes-properly "^1.0.0" - is-string "^1.0.7" - -asn1.js@^5.2.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" - integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - safer-buffer "^2.1.0" - -assert@^1.1.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== - dependencies: - object-assign "^4.1.1" - util "0.10.3" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== - -async-each@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.6.tgz#52f1d9403818c179b7561e11a5d1b77eb2160e77" - integrity sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg== - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -autoprefixer@^9.6.1: - version "9.8.8" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a" - integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA== - dependencies: - browserslist "^4.12.0" - caniuse-lite "^1.0.30001109" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - picocolors "^0.2.1" - postcss "^7.0.32" - postcss-value-parser "^4.1.0" - -available-typed-arrays@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" - integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== - -babel-loader@^8.2.2: - version "8.3.0" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" - integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== - dependencies: - find-cache-dir "^3.3.1" - loader-utils "^2.0.0" - make-dir "^3.1.0" - schema-utils "^2.6.5" - -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== - dependencies: - object.assign "^4.1.0" - -babel-plugin-macros@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" - integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== - dependencies: - "@babel/runtime" "^7.7.2" - cosmiconfig "^6.0.0" - resolve "^1.12.0" - -babel-plugin-polyfill-corejs2@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" - integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== - dependencies: - "@babel/compat-data" "^7.17.7" - "@babel/helper-define-polyfill-provider" "^0.3.3" - semver "^6.1.1" - -babel-plugin-polyfill-corejs3@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" - integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.3" - core-js-compat "^3.25.1" - -babel-plugin-polyfill-regenerator@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" - integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.3" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.0.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -bluebird@^3.5.5: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -bn.js@^5.0.0, bn.js@^5.1.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" - integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== - -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -bonjour-service@^1.0.11: - version "1.1.0" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.1.0.tgz#424170268d68af26ff83a5c640b95def01803a13" - integrity sha512-LVRinRB3k1/K0XzZ2p58COnWvkQknIY6sf0zF2rpErvcJXpMBttEPQSxK+HEXSS9VmpZlDoDnQWv8ftJT20B0Q== - dependencies: - array-flatten "^2.1.2" - dns-equal "^1.0.0" - fast-deep-equal "^3.1.3" - multicast-dns "^7.2.5" - -boolbase@^1.0.0, boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -bootstrap-datepicker@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/bootstrap-datepicker/-/bootstrap-datepicker-1.9.0.tgz#e4bfce3fcce1967876b21dc6833ec5994aaed090" - integrity sha512-9rYYbaVOheGYxjOr/+bJCmRPihfy+LkLSg4fIFMT9Od8WwWB/MB50w0JO1eBgKUMbb7PFHQD5uAfI3ArAxZRXA== - dependencies: - jquery ">=1.7.1 <4.0.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -brorand@^1.0.1, brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" - integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== - dependencies: - bn.js "^5.0.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" - integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== - dependencies: - bn.js "^5.1.1" - browserify-rsa "^4.0.1" - create-hash "^1.2.0" - create-hmac "^1.1.7" - elliptic "^6.5.3" - inherits "^2.0.4" - parse-asn1 "^5.1.5" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== - dependencies: - pako "~1.0.5" - -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.6.4: - version "4.21.5" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" - integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== - dependencies: - caniuse-lite "^1.0.30001449" - electron-to-chromium "^1.4.284" - node-releases "^2.0.8" - update-browserslist-db "^1.0.10" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== - -buffer@^4.3.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" - integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== - -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -cacache@^12.0.2: - version "12.0.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" - integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - -cacache@^15.0.5: - version "15.3.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" - integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== - dependencies: - "@npmcli/fs" "^1.0.0" - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ== - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A== - dependencies: - caller-callsite "^2.0.0" - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ== - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001449: - version "1.0.30001451" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz#2e197c698fc1373d63e1406d6607ea4617c613f1" - integrity sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w== - -case-sensitive-paths-webpack-plugin@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" - integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== - -chalk@^2.0, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1, chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0, color-convert@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.0.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -colorette@^2.0.10: - version "2.0.19" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -compressible@~2.0.16: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression-webpack-plugin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-4.0.1.tgz#33eda97f1170dd38c5556771de10f34245aa0274" - integrity sha512-0mg6PgwTsUe5LEcUrOu3ob32vraDx2VdbMGAT1PARcOV+UJWDYZFdkSo6RbHoGQ061mmmkC7XpRKOlvwm/gzJQ== - dependencies: - cacache "^15.0.5" - find-cache-dir "^3.3.1" - schema-utils "^2.7.0" - serialize-javascript "^4.0.0" - webpack-sources "^1.4.3" - -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== - dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" - debug "2.6.9" - on-headers "~1.0.2" - safe-buffer "5.1.2" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -concat-stream@^1.5.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -connect-history-api-fallback@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" - integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== - -console-browserify@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" - integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -convert-source-map@^1.7.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" - integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== - -core-js-compat@^3.25.1: - version "3.27.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.27.2.tgz#607c50ad6db8fd8326af0b2883ebb987be3786da" - integrity sha512-welaYuF7ZtbYKGrIy7y3eb40d37rG1FvzEOfe7hSLd2iD6duMDqUhRfSvCGyC46HhR6Y8JXXdZ2lnRUMkPBpvg== - dependencies: - browserslist "^4.21.4" - -core-js@^3.16.2: - version "3.27.2" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.27.2.tgz#85b35453a424abdcacb97474797815f4d62ebbf7" - integrity sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" - integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== - dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" - -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.7.2" - -create-ecdh@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" - integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== - dependencies: - bn.js "^4.1.0" - elliptic "^6.5.3" - -create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -css-blank-pseudo@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" - integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== - dependencies: - postcss "^7.0.5" - -css-color-names@0.0.4, css-color-names@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" - integrity sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q== - -css-declaration-sorter@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" - integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== - dependencies: - postcss "^7.0.1" - timsort "^0.3.0" - -css-has-pseudo@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" - integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^5.0.0-rc.4" - -css-loader@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" - integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ== - dependencies: - camelcase "^5.3.1" - cssesc "^3.0.0" - icss-utils "^4.1.1" - loader-utils "^1.2.3" - normalize-path "^3.0.0" - postcss "^7.0.32" - postcss-modules-extract-imports "^2.0.0" - postcss-modules-local-by-default "^3.0.2" - postcss-modules-scope "^2.2.0" - postcss-modules-values "^3.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^2.7.0" - semver "^6.3.0" - -css-prefers-color-scheme@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" - integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== - dependencies: - postcss "^7.0.5" - -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== - -css-select@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" - integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== - dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" - -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== - dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" - -css-tree@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -css-what@^3.2.1: - version "3.4.2" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" - integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== - -cssdb@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" - integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== - -cssesc@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" - integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-default@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz#920622b1fc1e95a34e8838203f1397a504f2d3ff" - integrity sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ== - dependencies: - css-declaration-sorter "^4.0.1" - cssnano-util-raw-cache "^4.0.1" - postcss "^7.0.0" - postcss-calc "^7.0.1" - postcss-colormin "^4.0.3" - postcss-convert-values "^4.0.1" - postcss-discard-comments "^4.0.2" - postcss-discard-duplicates "^4.0.2" - postcss-discard-empty "^4.0.1" - postcss-discard-overridden "^4.0.1" - postcss-merge-longhand "^4.0.11" - postcss-merge-rules "^4.0.3" - postcss-minify-font-values "^4.0.2" - postcss-minify-gradients "^4.0.2" - postcss-minify-params "^4.0.2" - postcss-minify-selectors "^4.0.2" - postcss-normalize-charset "^4.0.1" - postcss-normalize-display-values "^4.0.2" - postcss-normalize-positions "^4.0.2" - postcss-normalize-repeat-style "^4.0.2" - postcss-normalize-string "^4.0.2" - postcss-normalize-timing-functions "^4.0.2" - postcss-normalize-unicode "^4.0.1" - postcss-normalize-url "^4.0.1" - postcss-normalize-whitespace "^4.0.2" - postcss-ordered-values "^4.1.2" - postcss-reduce-initial "^4.0.3" - postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.3" - postcss-unique-selectors "^4.0.1" - -cssnano-util-get-arguments@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" - integrity sha512-6RIcwmV3/cBMG8Aj5gucQRsJb4vv4I4rn6YjPbVWd5+Pn/fuG+YseGvXGk00XLkoZkaj31QOD7vMUpNPC4FIuw== - -cssnano-util-get-match@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" - integrity sha512-JPMZ1TSMRUPVIqEalIBNoBtAYbi8okvcFns4O0YIhcdGebeYZK7dMyHJiQ6GqNBA9kE0Hym4Aqym5rPdsV/4Cw== - -cssnano-util-raw-cache@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" - integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== - dependencies: - postcss "^7.0.0" - -cssnano-util-same-parent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" - integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== - -cssnano@^4.1.10: - version "4.1.11" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.11.tgz#c7b5f5b81da269cb1fd982cb960c1200910c9a99" - integrity sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g== - dependencies: - cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.8" - is-resolvable "^1.0.0" - postcss "^7.0.0" - -csso@^4.0.2: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - -cyclist@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" - integrity sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A== - -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^4.1.0, debug@^4.1.1: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -decode-uri-component@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== - dependencies: - execa "^5.0.0" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -define-properties@^1.1.3, define-properties@^1.1.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" - integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== - dependencies: - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -des.js@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" - integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== - -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -dns-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== - -dns-packet@^5.2.2: - version "5.4.0" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b" - integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== - -domelementtype@1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - -dropzone@^5.9.2: - version "5.9.3" - resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-5.9.3.tgz#b3070ae090fa48cbc04c17535635537ca72d70d6" - integrity sha512-Azk8kD/2/nJIuVPK+zQ9sjKMRIpRvNyqn9XwbBHNq+iNuSccbJS6hwm1Woy0pMST0erSo0u4j+KJaodndDk4vA== - -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.4.284: - version "1.4.295" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.295.tgz#911d5df67542bf7554336142eb302c5ec90bba66" - integrity sha512-lEO94zqf1bDA3aepxwnWoHUjA8sZ+2owgcSZjYQy0+uOSEclJX0VieZC+r+wLpSxUHRd6gG32znTWmr+5iGzFw== - -elliptic@6.5.4, elliptic@^6.5.3: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enhanced-resolve@^4.1.1, enhanced-resolve@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" - integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - -errno@^0.1.3, errno@~0.1.7: - version "0.1.8" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" - integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== - dependencies: - prr "~1.0.1" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.17.2, es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.21.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.1.tgz#e6105a099967c08377830a0c9cb589d570dd86c6" - integrity sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-set-tostringtag "^2.0.1" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.1.3" - get-symbol-description "^1.0.0" - globalthis "^1.0.3" - gopd "^1.0.1" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" - has-symbols "^1.0.3" - internal-slot "^1.0.4" - is-array-buffer "^3.0.1" - is-callable "^1.2.7" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-typed-array "^1.1.10" - is-weakref "^1.0.2" - object-inspect "^1.12.2" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" - safe-regex-test "^1.0.0" - string.prototype.trimend "^1.0.6" - string.prototype.trimstart "^1.0.6" - typed-array-length "^1.0.4" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.9" - -es-array-method-boxes-properly@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== - -es-set-tostringtag@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" - integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== - dependencies: - get-intrinsic "^1.1.3" - has "^1.0.3" - has-tostringtag "^1.0.0" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -eslint-scope@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esrecurse@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== - dependencies: - homedir-polyfill "^1.0.1" - -express@^4.17.3: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.1" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-xml-parser@^3.19.0: - version "3.21.1" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz#152a1d51d445380f7046b304672dd55d15c9e736" - integrity sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg== - dependencies: - strnum "^1.0.4" - -faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - -figgy-pudding@^3.5.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" - integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== - -file-loader@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-cache-dir@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" - integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - -flatted@^3.2.2: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== - -flatten@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" - integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== - -flush-write-stream@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" - integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== - dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - -follow-redirects@^1.0.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== - -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== - dependencies: - map-cache "^0.2.2" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs-monkey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" - integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== - -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - integrity sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA== - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@^1.2.7: - version "1.2.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" - -functions-have-names@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" - integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA== - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@^7.1.3, glob@^7.1.4, glob@^7.1.7: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-modules@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globalthis@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" - integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== - dependencies: - define-properties "^1.1.3" - -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.6: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" - integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== - -has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has@^1.0.0, has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hex-color-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" - integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -hsl-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" - integrity sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A== - -hsla-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" - integrity sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA== - -html-entities@^2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" - integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-parser-js@>=0.5.1: - version "0.5.8" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" - integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== - -http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -icss-utils@^4.0.0, icss-utils@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" - integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== - dependencies: - postcss "^7.0.14" - -ieee754@^1.1.4: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - integrity sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA== - -immutable@^4.0.0: - version "4.2.4" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.2.4.tgz#83260d50889526b4b531a5e293709a77f7c55a2a" - integrity sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w== - -import-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" - integrity sha512-Ew5AZzJQFqrOV5BTW3EIoHAnoie1LojZLXKcCQ/yTRyVZosBhK1x1ViYjHGf5pAFOq8ZyChZp6m/fSN7pJyZtg== - dependencies: - import-from "^2.1.0" - -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg== - dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" - -import-fresh@^3.1.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-from@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" - integrity sha512-0vdnLL2wSGnhlRmzHJAg5JHjt1l2vYhzJ7tNLGbeVg0fse56tpGaH0uzH+r9Slej+BSXXEHvBKDEnVSLLE9/+w== - dependencies: - resolve-from "^3.0.0" - -import-local@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" - integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== - dependencies: - pkg-dir "^3.0.0" - resolve-cwd "^2.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA== - -infer-owner@^1.0.3, infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA== - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -ini@^1.3.4, ini@^1.3.5: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -internal-slot@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" - integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== - dependencies: - get-intrinsic "^1.2.0" - has "^1.0.3" - side-channel "^1.0.4" - -interpret@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" - integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== - -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - integrity sha512-vOx7VprsKyllwjSkLV79NIhpyLfr3jAp7VaTCMXOJHu4m0Ew1CZ2fcjASwmV1jI3BWuWHB013M48eyeldk9gYg== - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-array-buffer@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.1.tgz#deb1db4fcae48308d54ef2442706c0393997052a" - integrity sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - is-typed-array "^1.1.10" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q== - dependencies: - binary-extensions "^1.0.0" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-color-stop@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" - integrity sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA== - dependencies: - css-color-names "^0.0.4" - hex-color-regex "^1.1.0" - hsl-regex "^1.0.0" - hsla-regex "^1.0.0" - rgb-regex "^1.0.1" - rgba-regex "^1.0.0" - -is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== - dependencies: - has "^1.0.3" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw== - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw== - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== - dependencies: - kind-of "^3.0.2" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-plain-obj@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== - -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== - dependencies: - call-bind "^1.0.2" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-svg@^4.2.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-4.3.2.tgz#a119e9932e1af53f6be1969d1790d6cc5fd947d3" - integrity sha512-mM90duy00JGMyjqIVHu9gNTjywdZV+8qNasX8cm/EEYZ53PHDgajvbBwNVvty5dwSAxLUD3p3bdo+7sR/UMrpw== - dependencies: - fast-xml-parser "^3.19.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typed-array@^1.1.10, is-typed-array@^1.1.9: - version "1.1.10" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" - integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-windows@^1.0.1, is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jest-worker@^26.5.0: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jquery@>2.1.4, "jquery@>=1.7.1 <4.0.0": - version "3.6.3" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.3.tgz#23ed2ffed8a19e048814f13391a19afcdba160e6" - integrity sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1, js-yaml@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== - -json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json5@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== - dependencies: - minimist "^1.2.0" - -json5@^2.1.2, json5@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -klona@^2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" - integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== - -last-call-webpack-plugin@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" - integrity sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w== - dependencies: - lodash "^4.17.5" - webpack-sources "^1.1.0" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -loader-runner@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" - integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== - -loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: - version "1.4.2" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" - integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -loader-utils@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" - integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - -lodash.get@^4.0: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== - -lodash.has@^4.0: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" - integrity sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g== - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== - -lodash@^4.17.5: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - -make-dir@^3.0.2, make-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== - dependencies: - object-visit "^1.0.0" - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -mdn-data@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" - integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -medium-zoom@^1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/medium-zoom/-/medium-zoom-1.0.8.tgz#2bd1fbcf2961fa7b0e318fe284462aa9b8608ed2" - integrity sha512-CjFVuFq/IfrdqesAXfg+hzlDKu6A2n80ZIq0Kl9kWjoHh9j1N9Uvk5X0/MmN0hOfm5F9YBswlClhcwnmtwz7gA== - -memfs@^3.4.3: - version "3.4.13" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.13.tgz#248a8bd239b3c240175cd5ec548de5227fc4f345" - integrity sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg== - dependencies: - fs-monkey "^1.0.3" - -memory-fs@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - integrity sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ== - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -memory-fs@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" - integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.2: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mini-css-extract-plugin@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e" - integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A== - dependencies: - loader-utils "^1.1.0" - normalize-url "1.9.1" - schema-utils "^1.0.0" - webpack-sources "^1.1.0" - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== - -minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2: - version "1.2.4" - resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" - integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== - dependencies: - minipass "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.1: - version "3.3.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== - dependencies: - yallist "^4.0.0" - -minipass@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.0.3.tgz#00bfbaf1e16e35e804f4aa31a7c1f6b8d9f0ee72" - integrity sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw== - -minizlib@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@^0.5, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -move-concurrently@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - integrity sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ== - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multicast-dns@^7.2.5: - version "7.2.5" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" - integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== - dependencies: - dns-packet "^5.2.2" - thunky "^1.0.2" - -nan@^2.12.1: - version "2.17.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" - integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -node-forge@^1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== - -node-libs-browser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-releases@^2.0.8: - version "2.0.10" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" - integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== - -normalize-url@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" - integrity sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ== - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" - -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -nth-check@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg== - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-inspect@^1.12.2, object-inspect@^1.9.0: - version "1.12.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" - integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.0, object.assign@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.getownpropertydescriptors@^2.1.0: - version "2.1.5" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz#db5a9002489b64eef903df81d6623c07e5b4b4d3" - integrity sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw== - dependencies: - array.prototype.reduce "^1.0.5" - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== - dependencies: - isobject "^3.0.1" - -object.values@^1.1.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" - integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -open@^8.0.9: - version "8.4.1" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.1.tgz#2ab3754c07f5d1f99a7a8d6a82737c95e3101cff" - integrity sha512-/4b7qZNhv6Uhd7jjnREh1NjnPxlTq+XNWPG88Ydkj5AILcA5m3ajvcg57pB24EQjKv0dK62XnDqk9c/hkIG5Kg== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -optimize-css-assets-webpack-plugin@^5.0.8: - version "5.0.8" - resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.8.tgz#cbccdcf5a6ef61d4f8cc78cf083a67446e5f402a" - integrity sha512-mgFS1JdOtEGzD8l+EuISqL57cKO+We9GcoiQEmdCWRqqck+FGNmYJtx9qfAPzEz+lRrlThWMuGDaRkI/yWNx/Q== - dependencies: - cssnano "^4.1.10" - last-call-webpack-plugin "^3.0.0" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== - -p-limit@^2.0.0, p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-retry@^4.5.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -pako@~1.0.5: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -parallel-transform@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" - integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== - dependencies: - cyclist "^1.0.1" - inherits "^2.0.3" - readable-stream "^2.1.5" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-asn1@^5.0.0, parse-asn1@^5.1.5: - version "5.1.6" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" - integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== - dependencies: - asn1.js "^5.2.0" - browserify-aes "^1.0.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== - -path-complete-extname@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/path-complete-extname/-/path-complete-extname-1.0.0.tgz#f889985dc91000c815515c0bfed06c5acda0752b" - integrity sha512-CVjiWcMRdGU8ubs08YQVzhutOR5DEfO97ipRIlOGMK5Bek5nQySknBpuxVAVJ36hseTNs+vdIcv57ZrWxH7zvg== - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q== - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pbkdf2@^3.0.3: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" - integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - -pkg-dir@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -pnp-webpack-plugin@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz#65741384f6d8056f36e2255a8d67ffc20866f5c9" - integrity sha512-2Rb3vm+EXble/sMXNSu6eoBx8e79gKqhNq9F5ZWW6ERNCTE/Q0wQNne5541tE5vKjfM8hpNCYL+LGc1YTfI0dg== - dependencies: - ts-pnp "^1.1.6" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== - -postcss-attribute-case-insensitive@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" - integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^6.0.2" - -postcss-calc@^7.0.1: - version "7.0.5" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" - integrity sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg== - dependencies: - postcss "^7.0.27" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" - -postcss-color-functional-notation@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" - integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-color-gray@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" - integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.5" - postcss-values-parser "^2.0.0" - -postcss-color-hex-alpha@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" - integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== - dependencies: - postcss "^7.0.14" - postcss-values-parser "^2.0.1" - -postcss-color-mod-function@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" - integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-color-rebeccapurple@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" - integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-colormin@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" - integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== - dependencies: - browserslist "^4.0.0" - color "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-convert-values@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" - integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-custom-media@^7.0.8: - version "7.0.8" - resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" - integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== - dependencies: - postcss "^7.0.14" - -postcss-custom-properties@^8.0.11: - version "8.0.11" - resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" - integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== - dependencies: - postcss "^7.0.17" - postcss-values-parser "^2.0.1" - -postcss-custom-selectors@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" - integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-dir-pseudo-class@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" - integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-discard-comments@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" - integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== - dependencies: - postcss "^7.0.0" - -postcss-discard-duplicates@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" - integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== - dependencies: - postcss "^7.0.0" - -postcss-discard-empty@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" - integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== - dependencies: - postcss "^7.0.0" - -postcss-discard-overridden@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" - integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== - dependencies: - postcss "^7.0.0" - -postcss-double-position-gradients@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" - integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== - dependencies: - postcss "^7.0.5" - postcss-values-parser "^2.0.0" - -postcss-env-function@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" - integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-flexbugs-fixes@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz#9218a65249f30897deab1033aced8578562a6690" - integrity sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ== - dependencies: - postcss "^7.0.26" - -postcss-focus-visible@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" - integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== - dependencies: - postcss "^7.0.2" - -postcss-focus-within@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" - integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== - dependencies: - postcss "^7.0.2" - -postcss-font-variant@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz#42d4c0ab30894f60f98b17561eb5c0321f502641" - integrity sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA== - dependencies: - postcss "^7.0.2" - -postcss-gap-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" - integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== - dependencies: - postcss "^7.0.2" - -postcss-image-set-function@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" - integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-import@^12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-12.0.1.tgz#cf8c7ab0b5ccab5649024536e565f841928b7153" - integrity sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw== - dependencies: - postcss "^7.0.1" - postcss-value-parser "^3.2.3" - read-cache "^1.0.0" - resolve "^1.1.7" - -postcss-initial@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.4.tgz#9d32069a10531fe2ecafa0b6ac750ee0bc7efc53" - integrity sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg== - dependencies: - postcss "^7.0.2" - -postcss-lab-function@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" - integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== - dependencies: - "@csstools/convert-colors" "^1.4.0" - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-load-config@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a" - integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw== - dependencies: - cosmiconfig "^5.0.0" - import-cwd "^2.0.0" - -postcss-loader@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" - integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== - dependencies: - loader-utils "^1.1.0" - postcss "^7.0.0" - postcss-load-config "^2.0.0" - schema-utils "^1.0.0" - -postcss-logical@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" - integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== - dependencies: - postcss "^7.0.2" - -postcss-media-minmax@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" - integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== - dependencies: - postcss "^7.0.2" - -postcss-merge-longhand@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" - integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== - dependencies: - css-color-names "0.0.4" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - stylehacks "^4.0.0" - -postcss-merge-rules@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" - integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - cssnano-util-same-parent "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - vendors "^1.0.0" - -postcss-minify-font-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" - integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-gradients@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" - integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - is-color-stop "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-minify-params@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" - integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== - dependencies: - alphanum-sort "^1.0.0" - browserslist "^4.0.0" - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - uniqs "^2.0.0" - -postcss-minify-selectors@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" - integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== - dependencies: - alphanum-sort "^1.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -postcss-modules-extract-imports@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" - integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== - dependencies: - postcss "^7.0.5" - -postcss-modules-local-by-default@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" - integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== - dependencies: - icss-utils "^4.1.1" - postcss "^7.0.32" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" - integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== - dependencies: - postcss "^7.0.6" - postcss-selector-parser "^6.0.0" - -postcss-modules-values@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" - integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== - dependencies: - icss-utils "^4.0.0" - postcss "^7.0.6" - -postcss-nesting@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" - integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== - dependencies: - postcss "^7.0.2" - -postcss-normalize-charset@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" - integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== - dependencies: - postcss "^7.0.0" - -postcss-normalize-display-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" - integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-positions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" - integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== - dependencies: - cssnano-util-get-arguments "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-repeat-style@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" - integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== - dependencies: - cssnano-util-get-arguments "^4.0.0" - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-string@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" - integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== - dependencies: - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-timing-functions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" - integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== - dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-unicode@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" - integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-url@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" - integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== - dependencies: - is-absolute-url "^2.0.0" - normalize-url "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-normalize-whitespace@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" - integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-ordered-values@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" - integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== - dependencies: - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-overflow-shorthand@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" - integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== - dependencies: - postcss "^7.0.2" - -postcss-page-break@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" - integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== - dependencies: - postcss "^7.0.2" - -postcss-place@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" - integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== - dependencies: - postcss "^7.0.2" - postcss-values-parser "^2.0.0" - -postcss-preset-env@^6.7.0: - version "6.7.1" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.1.tgz#26563d2e9395d626a45a836450844540694bfcef" - integrity sha512-rlRkgX9t0v2On33n7TK8pnkcYOATGQSv48J2RS8GsXhqtg+xk6AummHP88Y5mJo0TLJelBjePvSjScTNkj3+qw== - dependencies: - autoprefixer "^9.6.1" - browserslist "^4.6.4" - caniuse-lite "^1.0.30000981" - css-blank-pseudo "^0.1.4" - css-has-pseudo "^0.10.0" - css-prefers-color-scheme "^3.1.1" - cssdb "^4.4.0" - postcss "^7.0.17" - postcss-attribute-case-insensitive "^4.0.1" - postcss-color-functional-notation "^2.0.1" - postcss-color-gray "^5.0.0" - postcss-color-hex-alpha "^5.0.3" - postcss-color-mod-function "^3.0.3" - postcss-color-rebeccapurple "^4.0.1" - postcss-custom-media "^7.0.8" - postcss-custom-properties "^8.0.11" - postcss-custom-selectors "^5.1.2" - postcss-dir-pseudo-class "^5.0.0" - postcss-double-position-gradients "^1.0.0" - postcss-env-function "^2.0.2" - postcss-focus-visible "^4.0.0" - postcss-focus-within "^3.0.0" - postcss-font-variant "^4.0.0" - postcss-gap-properties "^2.0.0" - postcss-image-set-function "^3.0.1" - postcss-initial "^3.0.0" - postcss-lab-function "^2.0.1" - postcss-logical "^3.0.0" - postcss-media-minmax "^4.0.0" - postcss-nesting "^7.0.0" - postcss-overflow-shorthand "^2.0.0" - postcss-page-break "^2.0.0" - postcss-place "^4.0.1" - postcss-pseudo-class-any-link "^6.0.0" - postcss-replace-overflow-wrap "^3.0.0" - postcss-selector-matches "^4.0.0" - postcss-selector-not "^4.0.0" - -postcss-pseudo-class-any-link@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" - integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== - dependencies: - postcss "^7.0.2" - postcss-selector-parser "^5.0.0-rc.3" - -postcss-reduce-initial@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" - integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== - dependencies: - browserslist "^4.0.0" - caniuse-api "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - -postcss-reduce-transforms@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" - integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== - dependencies: - cssnano-util-get-match "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-replace-overflow-wrap@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" - integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== - dependencies: - postcss "^7.0.2" - -postcss-safe-parser@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz#a6d4e48f0f37d9f7c11b2a581bf00f8ba4870b96" - integrity sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g== - dependencies: - postcss "^7.0.26" - -postcss-selector-matches@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" - integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== - dependencies: - balanced-match "^1.0.0" - postcss "^7.0.2" - -postcss-selector-not@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz#263016eef1cf219e0ade9a913780fc1f48204cbf" - integrity sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ== - dependencies: - balanced-match "^1.0.0" - postcss "^7.0.2" - -postcss-selector-parser@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" - integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== - dependencies: - dot-prop "^5.2.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" - integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== - dependencies: - cssesc "^2.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: - version "6.0.11" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" - integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-svgo@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e" - integrity sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw== - dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - svgo "^1.0.0" - -postcss-unique-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" - integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== - dependencies: - alphanum-sort "^1.0.0" - postcss "^7.0.0" - uniqs "^2.0.0" - -postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3: - version "3.3.1" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" - integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== - -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" - integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== - dependencies: - flatten "^1.0.2" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.39" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" - integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== - dependencies: - picocolors "^0.2.1" - source-map "^0.6.1" - -prepend-http@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -prr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== - -public-encrypt@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" - -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== - -punycode@^1.2.4: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== - -punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== - -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== - -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - integrity sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q== - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== - dependencies: - pify "^2.3.0" - -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -readmore-js@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readmore-js/-/readmore-js-2.2.1.tgz#ab75036c8a47b6ffa2d3d14cd527c7f479edc117" - integrity sha512-hbPP0nQpYYkAywCEZ8ozHivvhWyHic37KJ2IXrHES4qzjp0+nmw8R33MeyMAtXBZfXX4Es8cpd5JBVf9qj47+Q== - dependencies: - jquery ">2.1.4" - -regenerate-unicode-properties@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" - integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.9: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - -regenerator-transform@^0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" - integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== - dependencies: - "@babel/runtime" "^7.8.4" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" - -regexpu-core@^5.2.1: - version "5.3.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.0.tgz#4d0d044b76fedbad6238703ae84bfdedee2cf074" - integrity sha512-ZdhUQlng0RoscyW7jADnUZ25F5eVtHdMyXSb2PiwafvteRAOJUjFoUPEYZSIfP99fBIs3maLIRfpEddT78wAAQ== - dependencies: - "@babel/regjsgen" "^0.8.0" - regenerate "^1.4.2" - regenerate-unicode-properties "^10.1.0" - regjsparser "^0.9.1" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" - -regjsparser@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" - integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== - dependencies: - jsesc "~0.5.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg== - dependencies: - resolve-from "^3.0.0" - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== - -resolve@^1.1.7, resolve@^1.12.0, resolve@^1.14.2: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - -rgb-regex@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" - integrity sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w== - -rgba-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" - integrity sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg== - -rimraf@^2.5.4, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -run-queue@^1.0.0, run-queue@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" - integrity sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg== - dependencies: - aproba "^1.1.1" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - is-regex "^1.1.4" - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== - dependencies: - ret "~0.1.10" - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sass-loader@10.1.1: - version "10.1.1" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.1.1.tgz#4ddd5a3d7638e7949065dd6e9c7c04037f7e663d" - integrity sha512-W6gVDXAd5hR/WHsPicvZdjAWHBcEJ44UahgxcIE196fW2ong0ZHMPO1kZuI5q0VlvMQZh32gpv69PLWQm70qrw== - dependencies: - klona "^2.0.4" - loader-utils "^2.0.0" - neo-async "^2.6.2" - schema-utils "^3.0.0" - semver "^7.3.2" - -sass@^1.38.0: - version "1.58.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.58.0.tgz#ee8aea3ad5ea5c485c26b3096e2df6087d0bb1cc" - integrity sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg== - dependencies: - chokidar ">=3.0.0 <4.0.0" - immutable "^4.0.0" - source-map-js ">=0.6.2 <2.0.0" - -sax@~1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - -schema-utils@^2.6.5, schema-utils@^2.7.0: - version "2.7.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" - integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== - dependencies: - "@types/json-schema" "^7.0.5" - ajv "^6.12.4" - ajv-keywords "^3.5.2" - -schema-utils@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" - integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.8.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.0.0" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selfsigned@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" - integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== - dependencies: - node-forge "^1" - -semver@^5.5.0, semver@^5.6.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.3.2, semver@^7.3.5: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== - dependencies: - lru-cache "^6.0.0" - -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-javascript@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" - integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== - dependencies: - randombytes "^2.1.0" - -serialize-javascript@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== - dependencies: - randombytes "^2.1.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== - dependencies: - shebang-regex "^1.0.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sockjs@^0.3.24: - version "0.3.24" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - integrity sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg== - dependencies: - is-plain-obj "^1.0.0" - -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -"source-map-js@>=0.6.2 <2.0.0": - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@~0.5.12, source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spark-md5@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc" - integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - -ssri@8.0.1, ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== - dependencies: - minipass "^3.1.1" - -ssri@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" - integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== - dependencies: - figgy-pudding "^3.5.1" - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -stimulus@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/stimulus/-/stimulus-2.0.0.tgz#713c8b91a72ef90914b90955f0e705f004403047" - integrity sha512-xipy7BS5TVpg4fX6S8LhrYZp7cmHGjmk09WSAiVx1gF5S5g43IWsuetfUhIk8HfHUG+4MQ9nY0FQz4dRFLs/8w== - dependencies: - "@stimulus/core" "^2.0.0" - "@stimulus/webpack-helpers" "^2.0.0" - -stream-browserify@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" - integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-each@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" - integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== - dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== - -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string.prototype.trimend@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" - integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strnum@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" - integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== - -style-loader@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e" - integrity sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q== - dependencies: - loader-utils "^2.0.0" - schema-utils "^2.7.0" - -stylehacks@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" - integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== - dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -svgo@^1.0.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" - integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== - dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" - -tapable@^1.0.0, tapable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" - integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== - -tar@^6.0.2: - version "6.1.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" - integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^4.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -terser-webpack-plugin@^1.4.3: - version "1.4.5" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b" - integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^4.0.0" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser-webpack-plugin@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a" - integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ== - dependencies: - cacache "^15.0.5" - find-cache-dir "^3.3.1" - jest-worker "^26.5.0" - p-limit "^3.0.2" - schema-utils "^3.0.0" - serialize-javascript "^5.0.1" - source-map "^0.6.1" - terser "^5.3.4" - webpack-sources "^1.4.3" - -terser@^4.1.2: - version "4.8.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f" - integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -terser@^5.3.4: - version "5.16.3" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.3.tgz#3266017a9b682edfe019b8ecddd2abaae7b39c6b" - integrity sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q== - dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -timers-browserify@^2.0.4: - version "2.0.12" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" - integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== - dependencies: - setimmediate "^1.0.4" - -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A== - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - integrity sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -ts-pnp@^1.1.6: - version "1.2.0" - resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" - integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== - -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - integrity sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw== - -turbolinks@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/turbolinks/-/turbolinks-5.2.0.tgz#e6877a55ea5c1cb3bb225f0a4ae303d6d32ff77c" - integrity sha512-pMiez3tyBo6uRHFNNZoYMmrES/IaGgMhQQM+VFF36keryjb5ms0XkVpmKHkfW/4Vy96qiGW3K9bz0tF5sK9bBw== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typed-array-length@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" - integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== - dependencies: - call-bind "^1.0.2" - for-each "^0.3.3" - is-typed-array "^1.1.9" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" - integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" - integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA== - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha512-mZdDpf3vBV5Efh29kMw5tXoup/buMgxLzOt/XKFKcVmi+15ManNQWr6HfZ2aiZTYlYixbdNJ0KFmIZIv52tHSQ== - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" - -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== - dependencies: - imurmurhash "^0.1.4" - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg== - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== - -update-browserslist-db@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== - -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ== - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -util.promisify@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ== - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== - dependencies: - inherits "2.0.3" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8-compile-cache@^2.1.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -vendors@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== - -vm-browserify@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" - integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== - -watchpack-chokidar2@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" - integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww== - dependencies: - chokidar "^2.1.8" - -watchpack@^1.7.4: - version "1.7.5" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" - integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== - dependencies: - graceful-fs "^4.1.2" - neo-async "^2.5.0" - optionalDependencies: - chokidar "^3.4.1" - watchpack-chokidar2 "^2.0.1" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -webpack-assets-manifest@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/webpack-assets-manifest/-/webpack-assets-manifest-3.1.1.tgz#39bbc3bf2ee57fcd8ba07cda51c9ba4a3c6ae1de" - integrity sha512-JV9V2QKc5wEWQptdIjvXDUL1ucbPLH2f27toAY3SNdGZp+xSaStAgpoMcvMZmqtFrBc9a5pTS1058vxyMPOzRQ== - dependencies: - chalk "^2.0" - lodash.get "^4.0" - lodash.has "^4.0" - mkdirp "^0.5" - schema-utils "^1.0.0" - tapable "^1.0.0" - webpack-sources "^1.0.0" - -webpack-cli@^3.3.12: - version "3.3.12" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.12.tgz#94e9ada081453cd0aa609c99e500012fd3ad2d4a" - integrity sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag== - dependencies: - chalk "^2.4.2" - cross-spawn "^6.0.5" - enhanced-resolve "^4.1.1" - findup-sync "^3.0.0" - global-modules "^2.0.0" - import-local "^2.0.0" - interpret "^1.4.0" - loader-utils "^1.4.0" - supports-color "^6.1.0" - v8-compile-cache "^2.1.1" - yargs "^13.3.2" - -webpack-dev-middleware@^5.3.1: - version "5.3.3" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" - integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== - dependencies: - colorette "^2.0.10" - memfs "^3.4.3" - mime-types "^2.1.31" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-dev-server@^4.2.0: - version "4.11.1" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz#ae07f0d71ca0438cf88446f09029b92ce81380b5" - integrity sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw== - dependencies: - "@types/bonjour" "^3.5.9" - "@types/connect-history-api-fallback" "^1.3.5" - "@types/express" "^4.17.13" - "@types/serve-index" "^1.9.1" - "@types/serve-static" "^1.13.10" - "@types/sockjs" "^0.3.33" - "@types/ws" "^8.5.1" - ansi-html-community "^0.0.8" - bonjour-service "^1.0.11" - chokidar "^3.5.3" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^2.0.0" - default-gateway "^6.0.3" - express "^4.17.3" - graceful-fs "^4.2.6" - html-entities "^2.3.2" - http-proxy-middleware "^2.0.3" - ipaddr.js "^2.0.1" - open "^8.0.9" - p-retry "^4.5.0" - rimraf "^3.0.2" - schema-utils "^4.0.0" - selfsigned "^2.1.1" - serve-index "^1.9.1" - sockjs "^0.3.24" - spdy "^4.0.2" - webpack-dev-middleware "^5.3.1" - ws "^8.4.2" - -webpack-sources@^1.0.0, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack@^4.46.0: - version "4.46.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" - integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.4.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.5.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.3" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.7.4" - webpack-sources "^1.4.1" - -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== - -which-typed-array@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" - integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - is-typed-array "^1.1.10" - -which@^1.2.14, which@^1.2.9, which@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== - dependencies: - errno "~0.1.7" - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -ws@^8.4.2: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.0.tgz#485074cc392689da78e1828a9ff23585e06cddd8" - integrity sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig== - -xtend@^4.0.0, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@^1.7.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs@^13.3.2: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 26a9cfbf7fb3620cc2c50347f592610df4565490 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 18 Jul 2024 16:10:31 +0300 Subject: [PATCH 14/47] Links to platform posts in feed --- app/assets/stylesheets/base.scss | 34 ++++++++++++++++++- .../stylesheets/default_Dark_feed_theme.scss | 6 +++- app/assets/stylesheets/default_theme.scss | 6 +++- app/assets/stylesheets/gruvbox_theme.scss | 6 +++- app/assets/stylesheets/ruby_theme.scss | 6 +++- app/assets/stylesheets/twilight_theme.scss | 6 +++- app/helpers/posts_helper.rb | 14 ++++++++ app/views/posts/feed/_feed.html.erb | 14 ++++++++ 8 files changed, 86 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 7c57be9..b18aecc 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -88,7 +88,8 @@ $color-black: #000000; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon){ + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, + $color-feed-platform-tg, $color-feed-platform-mx){ @include dropzone($color-border, $color-form-textarea-bg); code{ /* #cc241d */ @@ -1131,6 +1132,13 @@ $color-black: #000000; padding: 1rem; margin-top: 15px; + .category{ + height: 5px; + /* background-color: $color-category-default; */ + margin-bottom: 1%; + opacity: 70%; + } + .user{ display: flex; align-items: center; @@ -1212,6 +1220,30 @@ $color-black: #000000; .footer{ margin-top: 1.5%; + + .platform-tg{ + border: 2px solid $color-feed-platform-tg; + border-radius: 8px; + margin: 3px 5px; + padding: 5px; + font-size: 13px; + line-height: 1.2; + } + + .platform-mx{ + border: 2px solid $color-feed-platform-mx; + border-radius: 8px; + margin: 3px 5px; + padding: 5px; + font-size: 13px; + line-height: 1.2; + } + + .platforms{ + display: flex; + flex-wrap: wrap; + } + .item-right{ margin-right: 5px; float: right; diff --git a/app/assets/stylesheets/default_Dark_feed_theme.scss b/app/assets/stylesheets/default_Dark_feed_theme.scss index 608197e..f85ad43 100644 --- a/app/assets/stylesheets/default_Dark_feed_theme.scss +++ b/app/assets/stylesheets/default_Dark_feed_theme.scss @@ -28,6 +28,9 @@ $color-feed-badge-bg: #454646; $color-feed-music-bg: black; $color-feed-music-icon: white; +$color-feed-platform-tg: #3E6A80; +$color-feed-platform-mx: #439143; + @include base($bg, $color-darkpurple, $color-code-bg, $font, $font-link, $color-white, $color-black, @@ -44,5 +47,6 @@ $color-feed-music-icon: white; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, + $color-feed-platform-tg, $color-feed-platform-mx ); \ No newline at end of file diff --git a/app/assets/stylesheets/default_theme.scss b/app/assets/stylesheets/default_theme.scss index 2d087a6..d05f11c 100644 --- a/app/assets/stylesheets/default_theme.scss +++ b/app/assets/stylesheets/default_theme.scss @@ -71,6 +71,9 @@ $color-feed-badge-bg: #777777; $color-feed-music-bg: black; $color-feed-music-icon: white; +$color-feed-platform-tg: #66b1ff; +$color-feed-platform-mx: #5cb85c; + $color-alert-danger: #222426; $color-alert-danger-text: #df4655; $color-alert-success: #222426; @@ -106,5 +109,6 @@ $color-black: #000000; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, + $color-feed-platform-tg, $color-feed-platform-mx ); \ No newline at end of file diff --git a/app/assets/stylesheets/gruvbox_theme.scss b/app/assets/stylesheets/gruvbox_theme.scss index 613edd4..dade1e0 100644 --- a/app/assets/stylesheets/gruvbox_theme.scss +++ b/app/assets/stylesheets/gruvbox_theme.scss @@ -71,6 +71,9 @@ $color-feed-badge-bg: #928374; $color-feed-music-bg: #1d2021; $color-feed-music-icon: #fbf1c7; +$color-feed-platform-tg: #458588; +$color-feed-platform-mx: #689d6a; + $color-alert-danger: #ad3b14; $color-alert-danger-text: #fcf4cd; $color-alert-success: #689d6a; @@ -106,5 +109,6 @@ $color-black: #000000; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, + $color-feed-platform-tg, $color-feed-platform-mx ); \ No newline at end of file diff --git a/app/assets/stylesheets/ruby_theme.scss b/app/assets/stylesheets/ruby_theme.scss index 5f943c7..8f8dd6a 100644 --- a/app/assets/stylesheets/ruby_theme.scss +++ b/app/assets/stylesheets/ruby_theme.scss @@ -71,6 +71,9 @@ $color-feed-badge-bg: #777777; $color-feed-music-bg: #eeeeee; $color-feed-music-icon: #000000; +$color-feed-platform-tg: #66b1ff; +$color-feed-platform-mx: #5cb85c; + $color-alert-danger: #f2dede; $color-alert-danger-text: #a94442; $color-alert-success: #a1bfa1; @@ -106,5 +109,6 @@ $color-black: #000000; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, + $color-feed-platform-tg, $color-feed-platform-mx ); \ No newline at end of file diff --git a/app/assets/stylesheets/twilight_theme.scss b/app/assets/stylesheets/twilight_theme.scss index 36bb4d4..6074c4f 100644 --- a/app/assets/stylesheets/twilight_theme.scss +++ b/app/assets/stylesheets/twilight_theme.scss @@ -71,6 +71,9 @@ $color-feed-badge-bg: #bdae93; $color-feed-music-bg: black; $color-feed-music-icon: white; +$color-feed-platform-tg: #458588; +$color-feed-platform-mx: #689d6a; + $color-alert-danger: #13171d; $color-alert-danger-text: #df4655; $color-alert-success: #13171d; @@ -106,5 +109,6 @@ $color-black: #000000; $color-alert-danger, $color-alert-danger-text, $color-alert-success, $color-alert-success-text, $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, - $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon + $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, + $color-feed-platform-tg, $color-feed-platform-mx ); \ No newline at end of file diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index 16aa81c..113cb27 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -161,6 +161,20 @@ def render_published_platforms(post) content.html_safe end + def render_feed_published_flatforms(post) + content = '' + post.published_platforms.each_with_index do |(k,v), i| + vv = v.reject{ |vv| vv[:channel_name].nil?}.uniq{ |u| u[:channel_name] } + p_class = "platform-tg" if k == "telegram" + p_class = "platform-mx" if k == "matrix" + next if vv.empty? + vv.each_with_index do |vvv, ii| + content += vvv[:url].present? ? link_to(vvv[:channel_name], vvv[:url], class: p_class) : "#{vvv[:channel_name]} (Private)" + end + end + content.html_safe + end + def link_markdown(title) title.gsub(/\[(.*?)\]\((.*?)\)/) { |_m| "<a href=\"#{Regexp.last_match(2)}\">#{Regexp.last_match(1)}</a>" } end diff --git a/app/views/posts/feed/_feed.html.erb b/app/views/posts/feed/_feed.html.erb index 8be1192..12eeba2 100644 --- a/app/views/posts/feed/_feed.html.erb +++ b/app/views/posts/feed/_feed.html.erb @@ -6,6 +6,11 @@ <%end%> <article id="<%=post.id%>" class="feed"> + <!-- + <% if post.category.present? %> + <div class="category" style="<%="border-left: none; background-color: #{post.category.color};"%>"></div> + <% end %> + --> <div class="user"> <div class="user-avatar feed-icon"> <%if post.user.avatar.present?%> @@ -27,6 +32,15 @@ <%=display_feed_attachments(post)%> <%end%> <div class="footer"> + <% if post.platform_posts.any? %> + <% r = render_feed_published_flatforms(post) %> + <% unless r.empty? %> + <div class="platforms"> + <%= r %> + <!-- <div class="platform-tg">Telegram debug></div><div class="platform-mx">Matrix debug></div><div class="platform-mx">Matrix debug></div><div class="platform-mx">Matrix debug></div><div class="platform-mx">Matrix debug></div><div class="platform-tg">Telegram debug></div><div class="platform-tg">Telegram debug></div><div class="platform-tg">Telegram debug></div><div class="platform-tg">Telegram debug></div><div class="platform-tg">Telegram debug></div><div class="platform-tg">Telegram debug></div><div class="platform-tg">Telegram debug></div> --> + </div> + <% end %> + <% end %> <a class="item-right" href="?id=<%=post.id%>"><i class="fa fa-commenting-o"> <%= I18n.t("comments.comment_header") %> (<%= post.comments.count %>)</i></a> <%if params[:id].present?%> <%= render "posts/comments" %> From 82482d6b3b1970f52f3c9d714abf5996a1e884b9 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 18 Jul 2024 18:33:25 +0300 Subject: [PATCH 15/47] Redirect comments fix --- app/controllers/comments_controller.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 7d756aa..d9d8d1a 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -6,11 +6,16 @@ def create current_post = Post.find(params[:comment][:post]) authorize! current_post, to: :create_comments? + ref_url = request.referrer current_comment = Comment.create!(text: params[:comment][:content], user: current_user, post: current_post) if current_comment.save + if ref_url.include?("feed") + redirect_to ref_url + else redirect_to post_path(current_comment.post) + end else render :new end @@ -22,9 +27,14 @@ def edit def update authorize! current_comment + ref_url = request.referrer if current_comment.update(text: params[:comment][:content], is_edited: true) - redirect_to post_path(current_comment.post) + if ref_url.include?("feed") + redirect_to ref_url + else + redirect_to post_path(current_comment.post) + end else render :edit end From deee1dd400928b60bcb768f2066c656fc2c60ea0 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 18 Jul 2024 21:53:53 +0300 Subject: [PATCH 16/47] Replace post id to uuid --- app/controllers/application_controller.rb | 2 +- app/controllers/posts_controller.rb | 16 +++++++-------- app/helpers/posts_helper.rb | 2 +- app/models/post.rb | 20 +++++++++++++++++++ app/searches/posts_search.rb | 6 ++++++ app/services/export_files.rb | 10 +++++----- app/services/platform/send_post_to_matrix.rb | 2 +- .../platform/send_post_to_telegram.rb | 2 +- app/services/platform/update_matrix_posts.rb | 2 +- .../platform/update_telegram_posts.rb | 2 +- app/views/comments/_add.html.erb | 2 +- app/views/posts/_links_header.html.erb | 2 +- app/views/posts/_post_header.html.erb | 10 +++++----- app/views/posts/feed/_feed.html.erb | 10 +++++----- app/views/posts/index.html.erb | 2 +- app/views/posts/rss.builder | 2 +- config/routes.rb | 10 ++++------ .../20240718151737_add_is_hide_to_posts.rb | 10 ++++++++++ 18 files changed, 73 insertions(+), 39 deletions(-) create mode 100644 db/migrate/20240718151737_add_is_hide_to_posts.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index dc62752..410466e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -24,7 +24,7 @@ def set_tags end def current_post - @current_post ||= Post.find(params[:id]) + @current_post ||= Post.find(params[:uuid]) end def current_comment diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 52df5c7..db61992 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -12,12 +12,12 @@ class PostsController < ApplicationController after_action :track_action, only: %i[index show raw feed] def track_action - post = Post.find_by(id: params.dig("id")) + post = Post.find_by(uuid: params.dig("uuid")) request_params = request.path_parameters if post.present? - post_id = post.id.to_s - if not Ahoy::Event.where(name: "Post_#{post_id}", visit_id: current_visit&.id).where_properties(id: post_id).exists? - ahoy.track("Post_#{post_id}", request_params) + post_uuid = post.uuid.to_s + if not Ahoy::Event.where(name: "Post_#{post_uuid}", visit_id: current_visit&.id).where_properties(id: post_uuid).exists? + ahoy.track("Post_#{post_uuid}", request_params) end else request_properties = { action: request_params[:action], controller: request_params[:controller] } @@ -50,7 +50,7 @@ def set_tags_post(current_post) video_preview = current_post.content_attachments&.find{ |a| a.video? } video_preview_url = video_preview.present? ? "#{request.base_url}#{rails_blob_path(video_preview, only_path: true)}" : "" - title = current_post.title.present? ? current_post.title : "#{current_post.id} | #{Rails.configuration.credentials[:title]}" + title = current_post.title.present? ? current_post.title : "#{current_post.uuid} | #{Rails.configuration.credentials[:title]}" category = current_post.category&.name || "" author = current_post.user.name.present? ? current_post.user.name : current_post.user.login @@ -247,7 +247,7 @@ def feed end @posts = PostsSearch.new( current_user: user, - id: params[:id], + uuid: params[:uuid], user_id: params[:user], strict_tags: params[:tag], text: params[:text], @@ -262,8 +262,8 @@ def feed Rails.logger.error("Failed bypass token from #{ip} at #{Time.now.utc.iso8601}") end - if params.dig("id").present? - @current_post = @posts.find_by(id: params["id"]) + if params.dig("uuid").present? + @current_post = @posts.find_by(uuid: params["uuid"]) set_tags_post(@current_post) if @current_post.present? end diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index 113cb27..8c55a16 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -142,7 +142,7 @@ def tags_with_count_list(tags) end def render_title(post) - post.title.present? ? link_markdown(post.title).html_safe : "##{post.id}" + post.title.present? ? link_markdown(post.title).html_safe : "##{post.uuid}" end def render_published_platforms(post) diff --git a/app/models/post.rb b/app/models/post.rb index 8eac363..1c42bb1 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -11,9 +11,15 @@ class Post < ApplicationRecord has_many :item_tags, class_name: 'ItemTag', foreign_key: 'item_id', dependent: :delete_all has_many :active_tags, -> { active('Post') }, class_name: 'ItemTag', foreign_key: 'item_id' + before_create -> { self.uuid = SecureRandom.uuid } + + extend FriendlyId + friendly_id :slug_candidates, use: [:slugged, :finders] + # scope :with_active_tags, ->(tag_id) { select { |post| post.active_tags.include?(tag_id) } } scope :without_active_tags, -> { select { |post| post.active_tags.empty? } } + #self.implicit_order_column = :created_at self.per_page = 15 def self.get_posts(params, current_user) @@ -72,4 +78,18 @@ def destroy def views Ahoy::Event.where(name: "Post_#{self.id}").distinct.count(:visit_id) end + + def to_param + uuid + end + + def self.find(id) + find_by! uuid: id + end + + private + + def slug_candidates + [:title, [:title, :uuid]] + end end diff --git a/app/searches/posts_search.rb b/app/searches/posts_search.rb index 23029fd..d4a094b 100644 --- a/app/searches/posts_search.rb +++ b/app/searches/posts_search.rb @@ -6,6 +6,7 @@ class PostsSearch < ApplicationSearch option :current_user, optional: true # option :user_tags, optional: true option :id, optional: true + option :uuid, optional: true option :user_id, optional: true option :limit, optional: true option :strict_tags, optional: true @@ -18,6 +19,7 @@ def call(posts) @query = posts @query = reduce_by_privacy @query = reduce_by_id if id.present? + @query = reduce_by_uuid if uuid.present? @query = reduce_by_user if user_id.present? # @query = reduce_by_user_tags #if user_tags @query = reduce_by_strict_tags if strict_tags.present? @@ -57,6 +59,10 @@ def reduce_by_id @query.where('posts.id=?', id) end + def reduce_by_uuid + @query.where('posts.uuid=?', uuid) + end + def reduce_by_user @query.where('posts.user_id=?', user_id) end diff --git a/app/services/export_files.rb b/app/services/export_files.rb index fae81fb..9ab425d 100644 --- a/app/services/export_files.rb +++ b/app/services/export_files.rb @@ -20,7 +20,7 @@ def call # ![p](...%20=..x..) => ![p](... =..x..) text = text.gsub(/!\[[^>]+\]\([^>]+(%20)=[0-9]+x[0-9]+\)/) { |m| m.gsub!('%20', ' ') } - post_dir = "tmp/export/#{@post.id}" + post_dir = "tmp/export/#{@post.uuid}" metadata = <<~TW_METADATA \n\n<TW_METADATA> @@ -35,7 +35,7 @@ def call FileUtils.mkdir_p(post_dir) unless File.directory?(post_dir) - File.open("#{post_dir}/#{@post.id}.md", 'w') do |f| + File.open("#{post_dir}/#{@post.uuid}.md", 'w') do |f| f.truncate(0) # delete old content if exists f.write(text) end @@ -48,12 +48,12 @@ def call end if @post.content_attachments.present? - output_file = "tmp/export/#{@post.id}.zip" + output_file = "tmp/export/#{@post.uuid}.zip" zf = ZipFileGenerator.new(post_dir, output_file) zf.write - { path: output_file, filename: "#{@post.id}.zip", type: 'application/zip' } + { path: output_file, filename: "#{@post.uuid}.zip", type: 'application/zip' } else - { path: "#{post_dir}/#{@post.id}.md", filename: "#{@post.id}.md", type: 'text/markdown' } + { path: "#{post_dir}/#{@post.uuid}.md", filename: "#{@post.uuid}.md", type: 'text/markdown' } end end end diff --git a/app/services/platform/send_post_to_matrix.rb b/app/services/platform/send_post_to_matrix.rb index 3f2c7f1..5f620e9 100644 --- a/app/services/platform/send_post_to_matrix.rb +++ b/app/services/platform/send_post_to_matrix.rb @@ -193,7 +193,7 @@ def channel_options(channel) end def send_mx_onlylink_post(content, channel, options) - post_link = "#{@base_url}/posts/#{@post.id}" + post_link = "#{@base_url}/posts/#{@post.uuid}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" text = @post.title.present? ? "<b>#{@post.title}</b><br>#{full_post_link}" : full_post_link.to_s diff --git a/app/services/platform/send_post_to_telegram.rb b/app/services/platform/send_post_to_telegram.rb index 53aeb9f..17070be 100644 --- a/app/services/platform/send_post_to_telegram.rb +++ b/app/services/platform/send_post_to_telegram.rb @@ -213,7 +213,7 @@ def get_tg_bot(channel) def send_tg_onlylink_post(channel, options) bot = get_tg_bot(channel) - post_link = "#{@base_url}/posts/#{@post.id}" + post_link = "#{@base_url}/posts/#{@post.uuid}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" text = @post.title.present? ? "<b>#{@post.title}</b>\n\n#{full_post_link}" : full_post_link diff --git a/app/services/platform/update_matrix_posts.rb b/app/services/platform/update_matrix_posts.rb index af900c8..2091cf2 100644 --- a/app/services/platform/update_matrix_posts.rb +++ b/app/services/platform/update_matrix_posts.rb @@ -158,7 +158,7 @@ def call end def edit_mx_onlylink_post(platform_post, room_id, event_id) - post_link = "#{@base_url}/posts/#{@post.id}" + post_link = "#{@base_url}/posts/#{@post.uuid}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" text = @post.title.present? ? "<b>#{@post.title}</b><br>#{full_post_link}" : full_post_link.to_s diff --git a/app/services/platform/update_telegram_posts.rb b/app/services/platform/update_telegram_posts.rb index bc98556..6914c9d 100644 --- a/app/services/platform/update_telegram_posts.rb +++ b/app/services/platform/update_telegram_posts.rb @@ -170,7 +170,7 @@ def check_onlylink end def update_onlylink(bot, platform_post) - post_link = "#{@base_url}/posts/#{@post.id}" + post_link = "#{@base_url}/posts/#{@post.uuid}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" onlylink_text = @new_title.present? ? "<b>#{@new_title}</b>\n\n#{full_post_link}" : full_post_link diff --git a/app/views/comments/_add.html.erb b/app/views/comments/_add.html.erb index b92dbdf..f9dceba 100644 --- a/app/views/comments/_add.html.erb +++ b/app/views/comments/_add.html.erb @@ -8,7 +8,7 @@ <div id="add" class="collapse" role="tabpanel" aria-labelledby="headingTwo"> <div class="card-block"> <%= form_with(model: Comment, :method => 'put', :remote => true) do |f| %> - <%= hidden_field(:comment, :post, { value: @current_post.id }) %> + <%= hidden_field(:comment, :post, { value: @current_post.uuid }) %> <div id="markdown" class="tab-pane in active title"> <div class="comment-editor"> <%= f.text_area :content, { id: "comments_content", placeholder: "#{I18n.t("posts.text_placeholder")}" } %> diff --git a/app/views/posts/_links_header.html.erb b/app/views/posts/_links_header.html.erb index d4d28a2..f5c6d22 100644 --- a/app/views/posts/_links_header.html.erb +++ b/app/views/posts/_links_header.html.erb @@ -2,7 +2,7 @@ <%= link_to current_user&.present? ? "#{I18n.t("posts.profile")}" : "#{I18n.t("auth.sign_in_header")}", edit_user_path %> <%= link_to " | #{I18n.t("auth.sign_up_header")}", sign_up_path if !current_user&.present? %> <%= link_to "| #{I18n.t("posts.main_page")}", root_path if Rails.configuration.credentials[:root_page] != 1%> - <%= link_to "| #{I18n.t("posts.all_posts")}", posts_url if post.present? && post_path(post.id)%> + <%= link_to "| #{I18n.t("posts.all_posts")}", posts_url if post.present? && post_path(post.uuid)%> <%= link_to "| #{I18n.t("posts.add_post")}", posts_new_path if current_user&.is_admin?%> <%= link_to "| #{I18n.t("posts.import")}", import_post_path if current_user&.is_admin?%> <%= link_to "| #{I18n.t("posts.reset_filters")}", posts_path if params.has_key?(:tags) || params.has_key?(:user) || params.has_key?(:search) || params.has_key?(:category)%> diff --git a/app/views/posts/_post_header.html.erb b/app/views/posts/_post_header.html.erb index a61e7a4..d5ade66 100644 --- a/app/views/posts/_post_header.html.erb +++ b/app/views/posts/_post_header.html.erb @@ -4,7 +4,7 @@ <i class="fa fa-light fa-eye right-small"> <%= post.views %></i> <i class="fa fa-commenting-o right-small"> <%= post.comments.count %></i> <div class="category" style="<%="border-left: 5px solid #{post.category.color};" if post.category.present? %>"> - <a href="/posts/<%= post.id %>"><%= render_title(post) %> </a> + <a href="/posts/<%= post.uuid %>"><%= render_title(post) %> </a> <%= render "privacy-icons", object: post %> <% if post.content_attachments.present? && post.content_attachments.select{ |b| !b.image? && !b.video? && !b.audio? }.any? %> <a><i class="fa fa-file right-small"></i></a> @@ -16,15 +16,15 @@ <p class="metadata"> <i class="fa fa-user-circle"> <%= I18n.t("posts.author") %>: <a href="posts?user=<%=post.user.id%>"><%= display_name(post.user) %></a> <% if allowed_to?(:update?, post) %> - | <a href="/posts/<%=post.id%>/edit"><%= I18n.t("posts.edit_post") %></a> - | <a href="/posts/delete/<%=post.id%>" data-confirm="<%= I18n.t("posts.delete_post_confirm") %>"><%= I18n.t("posts.delete_post") %></a> + | <a href="/posts/<%=post.uuid%>/edit"><%= I18n.t("posts.edit_post") %></a> + | <a href="/posts/delete/<%=post.uuid%>" data-confirm="<%= I18n.t("posts.delete_post_confirm") %>"><%= I18n.t("posts.delete_post") %></a> <% end %> </i> </p> <p class="metadata"> - <i class="fa fa-gear"> <%= I18n.t("posts.options") %>: <a href="/posts/raw/<%=post.id%>"><%= I18n.t("posts.raw") %></a> + <i class="fa fa-gear"> <%= I18n.t("posts.options") %>: <a href="/posts/raw/<%=post.uuid%>"><%= I18n.t("posts.raw") %></a> <% if allowed_to?(:export?, post) %> - | <a href="/posts/export/<%=post.id%>"><%= I18n.t("posts.export") %></a> + | <a href="/posts/export/<%=post.uuid%>"><%= I18n.t("posts.export") %></a> <% end %> </i> </p> diff --git a/app/views/posts/feed/_feed.html.erb b/app/views/posts/feed/_feed.html.erb index 12eeba2..361f824 100644 --- a/app/views/posts/feed/_feed.html.erb +++ b/app/views/posts/feed/_feed.html.erb @@ -5,7 +5,7 @@ <% @last_date = date %> <%end%> -<article id="<%=post.id%>" class="feed"> +<article id="<%=post.uuid%>" class="feed"> <!-- <% if post.category.present? %> <div class="category" style="<%="border-left: none; background-color: #{post.category.color};"%>"></div> @@ -19,11 +19,11 @@ </div> <div class="name"><a href="?user=<%=post.user.id%>"><%=display_name(post.user)%></a></div> <div class="views"><i class="fa fa-light fa-eye"></i> <%= post.views %> |</div> - <div class="createdAt"><a href="?id=<%=post.id%>"><%=post.created_at.strftime("%H:%M:%S")%></a></div> + <div class="createdAt"><a href="?uuid=<%=post.uuid%>"><%=post.created_at.strftime("%H:%M:%S")%></a></div> <%= render "privacy-icons", object: post %> </div> - <div id="<%=post.id%>" class="text"> + <div id="<%=post.uuid%>" class="text"> <%= markdown(post.title) if post.title.present?%> <%= markdown(post.text) %> </div> @@ -41,8 +41,8 @@ </div> <% end %> <% end %> - <a class="item-right" href="?id=<%=post.id%>"><i class="fa fa-commenting-o"> <%= I18n.t("comments.comment_header") %> (<%= post.comments.count %>)</i></a> - <%if params[:id].present?%> + <a class="item-right" href="?uuid=<%=post.uuid%>"><i class="fa fa-commenting-o"> <%= I18n.t("comments.comment_header") %> (<%= post.comments.count %>)</i></a> + <%if params[:uuid].present?%> <%= render "posts/comments" %> <%end%> </div> diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb index f18b466..4802f23 100644 --- a/app/views/posts/index.html.erb +++ b/app/views/posts/index.html.erb @@ -18,7 +18,7 @@ <%= display_attachments(post) if post.content_attachments.present? %> <div class="posts-content"><% if post.text.length > 512 %> <%= markdown(truncate(post.text, length: 512)) %> - <%= link_to "...#{I18n.t("posts.read_more")}", "posts/#{post.id}", class: "read-more-#{post.id}" %> + <%= link_to "...#{I18n.t("posts.read_more")}", "posts/#{post.uuid}", class: "read-more-#{post.uuid}" %> <% else %> <%= markdown(post.text) %> <% end %> diff --git a/app/views/posts/rss.builder b/app/views/posts/rss.builder index d4c8440..05178dd 100644 --- a/app/views/posts/rss.builder +++ b/app/views/posts/rss.builder @@ -16,7 +16,7 @@ xml.rss version: '2.0', 'xmlns:atom' => 'http://www.w3.org/2005/Atom' do if post.title.present? xml.title @markdown.render(post.title) else - xml.title "##{post.id}" + xml.title "##{post.uuid}" end text = post.text diff --git a/config/routes.rb b/config/routes.rb index a776e66..5772459 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,7 +6,7 @@ get 'posts/import', to: 'posts#import', as: :import_post post 'posts/import', to: 'posts#import' - resources :posts, except: [:destroy] + resources :posts, except: [:destroy], param: :uuid resources :users, except: [:edit] post '/edit', to: 'users#edit', as: :edit_user @@ -22,9 +22,9 @@ get '/rss', to: 'posts#rss', as: :rss, format: 'rss' post '/posts/new', to: 'posts#new' - get 'posts/delete/:id', to: 'posts#destroy', as: :post_path - - get 'posts/export/:id', to: 'posts#export', as: :export_post_path + get 'posts/delete/:uuid', to: 'posts#destroy', as: :post_path + get 'posts/export/:uuid', to: 'posts#export', as: :export_post_path + get 'posts/raw/:uuid', to: 'posts#raw', as: :raw_post_path resources :channels, except: %i[index show destroy] get 'channels/delete/:id', to: 'channels#destroy', as: :channel_path @@ -40,8 +40,6 @@ get '/stats/full_users_list', to: 'pages#full_users_list', as: :full_users_list get '/manage/full_invite_codes_list', to: 'pages#full_invite_codes_list', as: :full_invite_codes_list - get 'posts/raw/:id', to: 'posts#raw', as: :raw_post_path - case Rails.configuration.credentials[:root_page] when 1 root to: 'posts#index' diff --git a/db/migrate/20240718151737_add_is_hide_to_posts.rb b/db/migrate/20240718151737_add_is_hide_to_posts.rb new file mode 100644 index 0000000..b8446e7 --- /dev/null +++ b/db/migrate/20240718151737_add_is_hide_to_posts.rb @@ -0,0 +1,10 @@ +class AddIsHideToPosts < ActiveRecord::Migration[7.0] + def up + add_column :posts, :is_hide, :boolean, default: false + add_column :posts, :uuid, :uuid, null: false, unique: true + end + def down + remove_column :posts, :is_hide, :boolean + remove_column :posts, :uuid, :uuid + end +end From fc26326c39a95c932aa89c70859b59fb0772eb0e Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 18 Jul 2024 22:22:33 +0300 Subject: [PATCH 17/47] Slug support --- Gemfile | 1 + app/controllers/application_controller.rb | 2 +- app/models/post.rb | 8 ++ app/views/posts/_post_header.html.erb | 10 +- app/views/posts/index.html.erb | 2 +- config/initializers/friendly_id.rb | 107 ++++++++++++++++++ .../20240718154521_add_slug_to_posts.rb | 6 + ...20240718163935_create_friendly_id_slugs.rb | 21 ++++ 8 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 config/initializers/friendly_id.rb create mode 100644 db/migrate/20240718154521_add_slug_to_posts.rb create mode 100644 db/migrate/20240718163935_create_friendly_id_slugs.rb diff --git a/Gemfile b/Gemfile index ca3086c..d5a540c 100644 --- a/Gemfile +++ b/Gemfile @@ -85,6 +85,7 @@ gem 'devise' gem 'dry-initializer-rails' gem 'easy_captcha', path: 'vendor/gems/easy_captcha' gem 'enumerize' +gem 'friendly_id', '~> 5.5.0' gem 'grape' gem 'image_processing', '>= 1.12.2' gem 'mini_magick' diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 410466e..23bb8b9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -24,7 +24,7 @@ def set_tags end def current_post - @current_post ||= Post.find(params[:uuid]) + @current_post ||= Post.find_with_slug(params[:uuid]) end def current_comment diff --git a/app/models/post.rb b/app/models/post.rb index 1c42bb1..406c62c 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -87,6 +87,14 @@ def self.find(id) find_by! uuid: id end + def self.find_with_slug(s) + friendly.find_by(slug: s) || find(s) + end + + def slug_url + slug.present? ? slug : uuid + end + private def slug_candidates diff --git a/app/views/posts/_post_header.html.erb b/app/views/posts/_post_header.html.erb index d5ade66..0af69ef 100644 --- a/app/views/posts/_post_header.html.erb +++ b/app/views/posts/_post_header.html.erb @@ -4,7 +4,7 @@ <i class="fa fa-light fa-eye right-small"> <%= post.views %></i> <i class="fa fa-commenting-o right-small"> <%= post.comments.count %></i> <div class="category" style="<%="border-left: 5px solid #{post.category.color};" if post.category.present? %>"> - <a href="/posts/<%= post.uuid %>"><%= render_title(post) %> </a> + <a href="/posts/<%= post.slug_url %>"><%= render_title(post) %> </a> <%= render "privacy-icons", object: post %> <% if post.content_attachments.present? && post.content_attachments.select{ |b| !b.image? && !b.video? && !b.audio? }.any? %> <a><i class="fa fa-file right-small"></i></a> @@ -16,15 +16,15 @@ <p class="metadata"> <i class="fa fa-user-circle"> <%= I18n.t("posts.author") %>: <a href="posts?user=<%=post.user.id%>"><%= display_name(post.user) %></a> <% if allowed_to?(:update?, post) %> - | <a href="/posts/<%=post.uuid%>/edit"><%= I18n.t("posts.edit_post") %></a> - | <a href="/posts/delete/<%=post.uuid%>" data-confirm="<%= I18n.t("posts.delete_post_confirm") %>"><%= I18n.t("posts.delete_post") %></a> + | <a href="/posts/<%=post.slug_url%>/edit"><%= I18n.t("posts.edit_post") %></a> + | <a href="/posts/delete/<%=post.slug_url%>" data-confirm="<%= I18n.t("posts.delete_post_confirm") %>"><%= I18n.t("posts.delete_post") %></a> <% end %> </i> </p> <p class="metadata"> - <i class="fa fa-gear"> <%= I18n.t("posts.options") %>: <a href="/posts/raw/<%=post.uuid%>"><%= I18n.t("posts.raw") %></a> + <i class="fa fa-gear"> <%= I18n.t("posts.options") %>: <a href="/posts/raw/<%=post.slug_url%>"><%= I18n.t("posts.raw") %></a> <% if allowed_to?(:export?, post) %> - | <a href="/posts/export/<%=post.uuid%>"><%= I18n.t("posts.export") %></a> + | <a href="/posts/export/<%=post.slug_url%>"><%= I18n.t("posts.export") %></a> <% end %> </i> </p> diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb index 4802f23..68823c9 100644 --- a/app/views/posts/index.html.erb +++ b/app/views/posts/index.html.erb @@ -18,7 +18,7 @@ <%= display_attachments(post) if post.content_attachments.present? %> <div class="posts-content"><% if post.text.length > 512 %> <%= markdown(truncate(post.text, length: 512)) %> - <%= link_to "...#{I18n.t("posts.read_more")}", "posts/#{post.uuid}", class: "read-more-#{post.uuid}" %> + <%= link_to "...#{I18n.t("posts.read_more")}", "posts/#{post.slug_url}", class: "read-more-#{post.uuid}" %> <% else %> <%= markdown(post.text) %> <% end %> diff --git a/config/initializers/friendly_id.rb b/config/initializers/friendly_id.rb new file mode 100644 index 0000000..5852b58 --- /dev/null +++ b/config/initializers/friendly_id.rb @@ -0,0 +1,107 @@ +# FriendlyId Global Configuration +# +# Use this to set up shared configuration options for your entire application. +# Any of the configuration options shown here can also be applied to single +# models by passing arguments to the `friendly_id` class method or defining +# methods in your model. +# +# To learn more, check out the guide: +# +# http://norman.github.io/friendly_id/file.Guide.html + +FriendlyId.defaults do |config| + # ## Reserved Words + # + # Some words could conflict with Rails's routes when used as slugs, or are + # undesirable to allow as slugs. Edit this list as needed for your app. + config.use :reserved + + config.reserved_words = %w[new edit index session login logout users admin + stylesheets assets javascripts images] + + # This adds an option to treat reserved words as conflicts rather than exceptions. + # When there is no good candidate, a UUID will be appended, matching the existing + # conflict behavior. + + # config.treat_reserved_as_conflict = true + + # ## Friendly Finders + # + # Uncomment this to use friendly finders in all models. By default, if + # you wish to find a record by its friendly id, you must do: + # + # MyModel.friendly.find('foo') + # + # If you uncomment this, you can do: + # + # MyModel.find('foo') + # + # This is significantly more convenient but may not be appropriate for + # all applications, so you must explicitly opt-in to this behavior. You can + # always also configure it on a per-model basis if you prefer. + # + # Something else to consider is that using the :finders addon boosts + # performance because it will avoid Rails-internal code that makes runtime + # calls to `Module.extend`. + # + # config.use :finders + # + # ## Slugs + # + # Most applications will use the :slugged module everywhere. If you wish + # to do so, uncomment the following line. + # + # config.use :slugged + # + # By default, FriendlyId's :slugged addon expects the slug column to be named + # 'slug', but you can change it if you wish. + # + # config.slug_column = 'slug' + # + # By default, slug has no size limit, but you can change it if you wish. + # + # config.slug_limit = 255 + # + # When FriendlyId can not generate a unique ID from your base method, it appends + # a UUID, separated by a single dash. You can configure the character used as the + # separator. If you're upgrading from FriendlyId 4, you may wish to replace this + # with two dashes. + # + # config.sequence_separator = '-' + # + # Note that you must use the :slugged addon **prior** to the line which + # configures the sequence separator, or else FriendlyId will raise an undefined + # method error. + # + # ## Tips and Tricks + # + # ### Controlling when slugs are generated + # + # As of FriendlyId 5.0, new slugs are generated only when the slug field is + # nil, but if you're using a column as your base method can change this + # behavior by overriding the `should_generate_new_friendly_id?` method that + # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave + # more like 4.0. + # Note: Use(include) Slugged module in the config if using the anonymous module. + # If you have `friendly_id :name, use: slugged` in the model, Slugged module + # is included after the anonymous module defined in the initializer, so it + # overrides the `should_generate_new_friendly_id?` method from the anonymous module. + # + # config.use :slugged + # config.use Module.new { + # def should_generate_new_friendly_id? + # slug.blank? || <your_column_name_here>_changed? + # end + # } + # + # FriendlyId uses Rails's `parameterize` method to generate slugs, but for + # languages that don't use the Roman alphabet, that's not usually sufficient. + # Here we use the Babosa library to transliterate Russian Cyrillic slugs to + # ASCII. If you use this, don't forget to add "babosa" to your Gemfile. + # + # config.use Module.new { + # def normalize_friendly_id(text) + # text.to_slug.normalize! :transliterations => [:russian, :latin] + # end + # } +end diff --git a/db/migrate/20240718154521_add_slug_to_posts.rb b/db/migrate/20240718154521_add_slug_to_posts.rb new file mode 100644 index 0000000..24272ee --- /dev/null +++ b/db/migrate/20240718154521_add_slug_to_posts.rb @@ -0,0 +1,6 @@ +class AddSlugToPosts < ActiveRecord::Migration[7.0] + def change + add_column :posts, :slug, :string + add_index :posts, :slug, unique: true + end +end diff --git a/db/migrate/20240718163935_create_friendly_id_slugs.rb b/db/migrate/20240718163935_create_friendly_id_slugs.rb new file mode 100644 index 0000000..a09491d --- /dev/null +++ b/db/migrate/20240718163935_create_friendly_id_slugs.rb @@ -0,0 +1,21 @@ +MIGRATION_CLASS = + if ActiveRecord::VERSION::MAJOR >= 5 + ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"] + else + ActiveRecord::Migration + end + +class CreateFriendlyIdSlugs < MIGRATION_CLASS + def change + create_table :friendly_id_slugs do |t| + t.string :slug, null: false + t.integer :sluggable_id, null: false + t.string :sluggable_type, limit: 50 + t.string :scope + t.datetime :created_at + end + add_index :friendly_id_slugs, [:sluggable_type, :sluggable_id] + add_index :friendly_id_slugs, [:slug, :sluggable_type], length: {slug: 140, sluggable_type: 50} + add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], length: {slug: 70, sluggable_type: 50, scope: 70}, unique: true + end +end From f5aa10c4067907652c03dda05458622dd71c07ee Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 18 Jul 2024 23:24:14 +0300 Subject: [PATCH 18/47] Post is hidden --- app/controllers/posts_controller.rb | 4 +++- app/helpers/posts_helper.rb | 2 +- app/models/post.rb | 11 ++++++++--- app/searches/posts_search.rb | 6 ++++++ app/views/posts/_post_header.html.erb | 2 +- app/views/posts/edit.html.erb | 4 ++++ db/migrate/20240718151737_add_is_hidden_to_posts.rb | 10 ++++++++++ db/migrate/20240718151737_add_is_hide_to_posts.rb | 10 ---------- update_log.md | 3 +++ 9 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 db/migrate/20240718151737_add_is_hidden_to_posts.rb delete mode 100644 db/migrate/20240718151737_add_is_hide_to_posts.rb diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index db61992..a4c17d6 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -152,7 +152,7 @@ def update end UpdatePostMessages.call(current_post, base_url, posts_params) - current_post.update(title: posts_params[:post][:title]) # ? + current_post.update(title: posts_params[:post][:title], is_hidden: params[:post][:is_hidden]) # ? redirect_to current_post else @@ -329,6 +329,7 @@ def destroy def posts_params params.permit(:_method, :id, + :uuid, :authenticity_token, :commit, channels: {}, @@ -342,6 +343,7 @@ def posts_params :privacy, :new_tags_name, :new_tags_enabled_by_default, + :is_hidden, { attachments: [] }]) end end diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index 8c55a16..2734d85 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -132,7 +132,7 @@ def tags_with_count_list(tags) privacy = [0] end - item = Post.where(privacy: privacy) + item = Post.where(privacy: privacy, is_hidden: false) item += users_item if users_item.present? tags.map do |tag| diff --git a/app/models/post.rb b/app/models/post.rb index 406c62c..bc11d11 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -25,12 +25,17 @@ class Post < ApplicationRecord def self.get_posts(params, current_user) if params.key?(:user) current_privacy = current_user.present? ? [0, 1] : [0] - privacy = (current_user.present? && (User.find_by(id: params[:user]) == current_user) ? [0, 1, 2] : current_privacy) - posts = Post.where(privacy: privacy, user: User.find_by(id: params[:user])) + is_same_user = (User.find_by(id: params[:user]) == current_user) + privacy = (current_user.present? && is_same_user ? [0, 1, 2] : current_privacy) + if is_same_user + posts = Post.where(privacy: privacy, user: User.find_by(id: params[:user])) + else + posts = Post.where(privacy: privacy, user: User.find_by(id: params[:user]), is_hidden: false) + end else my_posts = current_user.present? ? Post.where(user: current_user).ids : [] current_privacy = current_user.present? ? [0, 1] : [0] - not_my_posts = Post.where.not(user: current_user).where(privacy: current_privacy).ids + not_my_posts = Post.where.not(user: current_user).where(privacy: current_privacy, is_hidden: false).ids posts = Post.where(id: my_posts + not_my_posts) end posts = posts.where('lower(title) LIKE ?', "%#{params[:search].downcase}%") if params.key?(:search) diff --git a/app/searches/posts_search.rb b/app/searches/posts_search.rb index d4a094b..ed741d5 100644 --- a/app/searches/posts_search.rb +++ b/app/searches/posts_search.rb @@ -21,6 +21,7 @@ def call(posts) @query = reduce_by_id if id.present? @query = reduce_by_uuid if uuid.present? @query = reduce_by_user if user_id.present? + @query = reduce_by_hidden # @query = reduce_by_user_tags #if user_tags @query = reduce_by_strict_tags if strict_tags.present? @query = reduce_by_tags if tags @@ -67,6 +68,11 @@ def reduce_by_user @query.where('posts.user_id=?', user_id) end + # Todo: display for feed? + def reduce_by_hidden + @query.where('posts.uuid=? OR posts.is_hidden=false', uuid) + end + def reduce_by_title @query.where('title LIKE :like', like: "%#{title}%") end diff --git a/app/views/posts/_post_header.html.erb b/app/views/posts/_post_header.html.erb index 0af69ef..e53cf6e 100644 --- a/app/views/posts/_post_header.html.erb +++ b/app/views/posts/_post_header.html.erb @@ -14,7 +14,7 @@ </h1> <p class="metadata"><i class="fa fa-clock-o"> <%= I18n.t("posts.publish_date") %>: <%= post.created_at.strftime("%Y.%m.%d %H:%M") %></i></p> <p class="metadata"> - <i class="fa fa-user-circle"> <%= I18n.t("posts.author") %>: <a href="posts?user=<%=post.user.id%>"><%= display_name(post.user) %></a> + <i class="fa fa-user-circle"> <%= I18n.t("posts.author") %>: <a href="?user=<%=post.user.id%>"><%= display_name(post.user) %></a> <% if allowed_to?(:update?, post) %> | <a href="/posts/<%=post.slug_url%>/edit"><%= I18n.t("posts.edit_post") %></a> | <a href="/posts/delete/<%=post.slug_url%>" data-confirm="<%= I18n.t("posts.delete_post_confirm") %>"><%= I18n.t("posts.delete_post") %></a> diff --git a/app/views/posts/edit.html.erb b/app/views/posts/edit.html.erb index 37afb17..31a9ff3 100644 --- a/app/views/posts/edit.html.erb +++ b/app/views/posts/edit.html.erb @@ -93,6 +93,10 @@ <h3><%= I18n.t("posts.privacy") %></h3> <%= f.select(:privacy, [[I18n.t("posts.privacy_public"), 0], [I18n.t("posts.privacy_registered"), 1], [I18n.t("posts.privacy_onlyme"), 2]], required: true) %> </div> + <div class="colorful-checkboxes" style="display: inherit"> + <%= f.check_box(:is_hidden, { :class=>"colorful-checkbox", :checked => @current_post.is_hidden }) %> + <%= f.label :is_hidden, I18n.t("posts.is_hidden") %> + </div> <div class="tags"> <h3><%= I18n.t("posts.tags") %></h3> <% tags = ItemTag.where(item: @current_post) %> diff --git a/db/migrate/20240718151737_add_is_hidden_to_posts.rb b/db/migrate/20240718151737_add_is_hidden_to_posts.rb new file mode 100644 index 0000000..d71ebca --- /dev/null +++ b/db/migrate/20240718151737_add_is_hidden_to_posts.rb @@ -0,0 +1,10 @@ +class AddIsHiddenToPosts < ActiveRecord::Migration[7.0] + def up + add_column :posts, :is_hidden, :boolean, default: false + add_column :posts, :uuid, :uuid, null: false, default: "gen_random_uuid()", unique: true + end + def down + remove_column :posts, :is_hidden, :boolean + remove_column :posts, :uuid, :uuid + end +end diff --git a/db/migrate/20240718151737_add_is_hide_to_posts.rb b/db/migrate/20240718151737_add_is_hide_to_posts.rb deleted file mode 100644 index b8446e7..0000000 --- a/db/migrate/20240718151737_add_is_hide_to_posts.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AddIsHideToPosts < ActiveRecord::Migration[7.0] - def up - add_column :posts, :is_hide, :boolean, default: false - add_column :posts, :uuid, :uuid, null: false, unique: true - end - def down - remove_column :posts, :is_hide, :boolean - remove_column :posts, :uuid, :uuid - end -end diff --git a/update_log.md b/update_log.md index 7cbb2a2..df85fd1 100644 --- a/update_log.md +++ b/update_log.md @@ -5,6 +5,9 @@ * Обновлён Ruby 2.7.7->3.3.2; * Поддержка OpenGraph для предпросмотра постов; * Добавлен счётчик просмотров поста; +* Теперь посты имеют UUID вместо ID; +* Добавлена генерация slug на основе названия поста; +* Добавлена опция скрытия поста из ленты (их видит только автор); * Обновлены темы: * Для тем Gruvbox, Twilight добавлена тёмная тема для feed; * Для темы по-умолчанию добавлена отдельно тёмная подтема для feed; From 5af3082ad966fec6f640b2ba4c29bd605fa3580c Mon Sep 17 00:00:00 2001 From: Whiletruedoend <Whiletruedoend@protonmail.com> Date: Sat, 20 Jul 2024 00:00:56 +0300 Subject: [PATCH 19/47] Slug url && hidden icon --- app/assets/stylesheets/base.scss | 4 +++- .../stylesheets/default_Dark_feed_theme.scss | 2 +- app/assets/stylesheets/default_theme.scss | 4 +++- app/assets/stylesheets/gruvbox_theme.scss | 4 +++- app/assets/stylesheets/ruby_theme.scss | 4 +++- app/assets/stylesheets/twilight_theme.scss | 4 +++- app/controllers/channels_controller.rb | 3 ++- app/controllers/comments_controller.rb | 7 ++++++- app/controllers/posts_controller.rb | 2 +- app/helpers/posts_helper.rb | 2 +- app/models/post.rb | 19 ++++++++++++------- app/services/platform/send_post_to_matrix.rb | 2 +- .../platform/send_post_to_telegram.rb | 2 +- app/services/platform/update_matrix_posts.rb | 2 +- .../platform/update_telegram_posts.rb | 2 +- app/views/posts/_post_header.html.erb | 5 ++++- config/locales/en.yml | 1 + config/locales/ru.yml | 1 + .../20240718151737_add_is_hidden_to_posts.rb | 3 ++- 19 files changed, 50 insertions(+), 23 deletions(-) diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index b18aecc..41eb02c 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -80,7 +80,7 @@ $color-black: #000000; $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, $color-checkbox-focus, $color-container-bg, $color-scrollbar-light, $color-scrollbar-dark, $color-checkbox-bg, $color-checkbox-border, $color-checkbox-checked-bg, $color-checkbox-checked-border, - $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, + $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, $color-hidden, $color-checkbox-text-checked, $color-checkbox-process_checking, $color-checkbox-unchecked-bg, $color-checkbox-text-unchecked, $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, @@ -425,6 +425,8 @@ $color-black: #000000; .twilight-icon{ color: $color-privacy-me; } .red-icon { color: $color-privacy-me-p; } + .hidden-icon{ color: $color-hidden; } + /* attachments */ .attachments{ diff --git a/app/assets/stylesheets/default_Dark_feed_theme.scss b/app/assets/stylesheets/default_Dark_feed_theme.scss index f85ad43..9f77092 100644 --- a/app/assets/stylesheets/default_Dark_feed_theme.scss +++ b/app/assets/stylesheets/default_Dark_feed_theme.scss @@ -39,7 +39,7 @@ $color-feed-platform-mx: #439143; $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, $color-checkbox-focus, $color-container-bg, $color-scrollbar-light, $color-scrollbar-dark, $color-checkbox-bg, $color-checkbox-border, $color-checkbox-checked-bg, $color-checkbox-checked-border, - $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, + $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, $color-hidden, $color-checkbox-text-checked, $color-checkbox-process_checking, $color-checkbox-unchecked-bg, $color-checkbox-text-unchecked, $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, diff --git a/app/assets/stylesheets/default_theme.scss b/app/assets/stylesheets/default_theme.scss index d05f11c..938a699 100644 --- a/app/assets/stylesheets/default_theme.scss +++ b/app/assets/stylesheets/default_theme.scss @@ -14,6 +14,8 @@ $color-privacy-registered: #5cb85c; $color-privacy-me-p: #df4655; $color-privacy-me: #4e518b; +$color-hidden: #8ba6db; + $color-wrapper: #515151; $color-border: #555555; @@ -101,7 +103,7 @@ $color-black: #000000; $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, $color-checkbox-focus, $color-container-bg, $color-scrollbar-light, $color-scrollbar-dark, $color-checkbox-bg, $color-checkbox-border, $color-checkbox-checked-bg, $color-checkbox-checked-border, - $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, + $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, $color-hidden, $color-checkbox-text-checked, $color-checkbox-process_checking, $color-checkbox-unchecked-bg, $color-checkbox-text-unchecked, $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, diff --git a/app/assets/stylesheets/gruvbox_theme.scss b/app/assets/stylesheets/gruvbox_theme.scss index dade1e0..2b27fa2 100644 --- a/app/assets/stylesheets/gruvbox_theme.scss +++ b/app/assets/stylesheets/gruvbox_theme.scss @@ -14,6 +14,8 @@ $color-privacy-registered: #689d6a; $color-privacy-me-p: #fb4934; $color-privacy-me: #b16286; +$color-hidden: #83a598; + $color-wrapper: #515151; $color-border: #555555; @@ -101,7 +103,7 @@ $color-black: #000000; $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, $color-checkbox-focus, $color-container-bg, $color-scrollbar-light, $color-scrollbar-dark, $color-checkbox-bg, $color-checkbox-border, $color-checkbox-checked-bg, $color-checkbox-checked-border, - $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, + $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, $color-hidden, $color-checkbox-text-checked, $color-checkbox-process_checking, $color-checkbox-unchecked-bg, $color-checkbox-text-unchecked, $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, diff --git a/app/assets/stylesheets/ruby_theme.scss b/app/assets/stylesheets/ruby_theme.scss index 8f8dd6a..ac3fde5 100644 --- a/app/assets/stylesheets/ruby_theme.scss +++ b/app/assets/stylesheets/ruby_theme.scss @@ -14,6 +14,8 @@ $color-privacy-registered: #5cb85c; $color-privacy-me-p: #d72134; $color-privacy-me: #5659e0; +$color-hidden: #67799d; + $color-wrapper: #515151; $color-border: #555555; @@ -101,7 +103,7 @@ $color-black: #000000; $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, $color-checkbox-focus, $color-container-bg, $color-scrollbar-light, $color-scrollbar-dark, $color-checkbox-bg, $color-checkbox-border, $color-checkbox-checked-bg, $color-checkbox-checked-border, - $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, + $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, $color-hidden, $color-checkbox-text-checked, $color-checkbox-process_checking, $color-checkbox-unchecked-bg, $color-checkbox-text-unchecked, $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, diff --git a/app/assets/stylesheets/twilight_theme.scss b/app/assets/stylesheets/twilight_theme.scss index 6074c4f..e9b6630 100644 --- a/app/assets/stylesheets/twilight_theme.scss +++ b/app/assets/stylesheets/twilight_theme.scss @@ -14,6 +14,8 @@ $color-privacy-registered: #5cb85c; $color-privacy-me-p: #df4655; $color-privacy-me: #4e518b; +$color-hidden: #526ea6; + $color-wrapper: #515151; $color-border: #555555; @@ -101,7 +103,7 @@ $color-black: #000000; $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, $color-checkbox-focus, $color-container-bg, $color-scrollbar-light, $color-scrollbar-dark, $color-checkbox-bg, $color-checkbox-border, $color-checkbox-checked-bg, $color-checkbox-checked-border, - $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, + $color-privacy-registered-p, $color-privacy-registered, $color-privacy-me-p, $color-privacy-me, $color-hidden, $color-checkbox-text-checked, $color-checkbox-process_checking, $color-checkbox-unchecked-bg, $color-checkbox-text-unchecked, $color-button-bg, $color-button-text, $color-table-th, $color-table-th-border, $color-table-td, $color-table-tr-hover, $color-feed-bg, $color-feed-bg2, $color-feed-font, $color-feed-icon, $color-feed-time, $color-feed-date, diff --git a/app/controllers/channels_controller.rb b/app/controllers/channels_controller.rb index 10fd9b8..0846d3c 100644 --- a/app/controllers/channels_controller.rb +++ b/app/controllers/channels_controller.rb @@ -69,7 +69,8 @@ def destroy token = current_channel.token bot = Twilight::Application::CURRENT_TG_BOTS&.dig(token.to_s, :client) Platform::ManageTelegramPollers.call(bot, 'delete') if bot.present? - current_channel.delete + current_channel.platform_posts.destroy_all + current_channel.destroy redirect_to edit_user_path end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index d9d8d1a..2af1283 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -42,9 +42,14 @@ def update def destroy authorize! current_comment + ref_url = request.referrer post = current_comment.post current_comment.delete - redirect_to post_path(post) + if ref_url.include?("feed") + redirect_to ref_url + else + redirect_to post_path(post) + end end end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index a4c17d6..02fc0b8 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -16,7 +16,7 @@ def track_action request_params = request.path_parameters if post.present? post_uuid = post.uuid.to_s - if not Ahoy::Event.where(name: "Post_#{post_uuid}", visit_id: current_visit&.id).where_properties(id: post_uuid).exists? + if not Ahoy::Event.where(name: "Post_#{post_uuid}", visit_id: current_visit&.id).where_properties(uuid: post_uuid).exists? ahoy.track("Post_#{post_uuid}", request_params) end else diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index 2734d85..75339d6 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -142,7 +142,7 @@ def tags_with_count_list(tags) end def render_title(post) - post.title.present? ? link_markdown(post.title).html_safe : "##{post.uuid}" + post.title.present? ? link_markdown(post.title).html_safe : "##{post.id}" end def render_published_platforms(post) diff --git a/app/models/post.rb b/app/models/post.rb index bc11d11..0b7c2ea 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -11,10 +11,10 @@ class Post < ApplicationRecord has_many :item_tags, class_name: 'ItemTag', foreign_key: 'item_id', dependent: :delete_all has_many :active_tags, -> { active('Post') }, class_name: 'ItemTag', foreign_key: 'item_id' - before_create -> { self.uuid = SecureRandom.uuid } + before_create :gen_uuid extend FriendlyId - friendly_id :slug_candidates, use: [:slugged, :finders] + friendly_id :title, use: [:slugged, :finders] # scope :with_active_tags, ->(tag_id) { select { |post| post.active_tags.include?(tag_id) } } scope :without_active_tags, -> { select { |post| post.active_tags.empty? } } @@ -81,7 +81,12 @@ def destroy # Todo: make turbo for views? def views - Ahoy::Event.where(name: "Post_#{self.id}").distinct.count(:visit_id) + Ahoy::Event.where(name: "Post_#{self.uuid}").distinct.count(:visit_id) + end + + def gen_uuid + self.uuid = SecureRandom.uuid + gen_uuid if Post.find_by(uuid: uuid).present? end def to_param @@ -100,9 +105,9 @@ def slug_url slug.present? ? slug : uuid end - private + # private - def slug_candidates - [:title, [:title, :uuid]] - end + # def slug_candidates + # [:title, [:title, :uuid]] + # end end diff --git a/app/services/platform/send_post_to_matrix.rb b/app/services/platform/send_post_to_matrix.rb index 5f620e9..092e243 100644 --- a/app/services/platform/send_post_to_matrix.rb +++ b/app/services/platform/send_post_to_matrix.rb @@ -193,7 +193,7 @@ def channel_options(channel) end def send_mx_onlylink_post(content, channel, options) - post_link = "#{@base_url}/posts/#{@post.uuid}" + post_link = "#{@base_url}/posts/#{@post.slug_url}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" text = @post.title.present? ? "<b>#{@post.title}</b><br>#{full_post_link}" : full_post_link.to_s diff --git a/app/services/platform/send_post_to_telegram.rb b/app/services/platform/send_post_to_telegram.rb index 17070be..037e41a 100644 --- a/app/services/platform/send_post_to_telegram.rb +++ b/app/services/platform/send_post_to_telegram.rb @@ -213,7 +213,7 @@ def get_tg_bot(channel) def send_tg_onlylink_post(channel, options) bot = get_tg_bot(channel) - post_link = "#{@base_url}/posts/#{@post.uuid}" + post_link = "#{@base_url}/posts/#{@post.slug_url}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" text = @post.title.present? ? "<b>#{@post.title}</b>\n\n#{full_post_link}" : full_post_link diff --git a/app/services/platform/update_matrix_posts.rb b/app/services/platform/update_matrix_posts.rb index 2091cf2..68861f9 100644 --- a/app/services/platform/update_matrix_posts.rb +++ b/app/services/platform/update_matrix_posts.rb @@ -158,7 +158,7 @@ def call end def edit_mx_onlylink_post(platform_post, room_id, event_id) - post_link = "#{@base_url}/posts/#{@post.uuid}" + post_link = "#{@base_url}/posts/#{@post.slug_url}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" text = @post.title.present? ? "<b>#{@post.title}</b><br>#{full_post_link}" : full_post_link.to_s diff --git a/app/services/platform/update_telegram_posts.rb b/app/services/platform/update_telegram_posts.rb index 6914c9d..8c89bb3 100644 --- a/app/services/platform/update_telegram_posts.rb +++ b/app/services/platform/update_telegram_posts.rb @@ -170,7 +170,7 @@ def check_onlylink end def update_onlylink(bot, platform_post) - post_link = "#{@base_url}/posts/#{@post.uuid}" + post_link = "#{@base_url}/posts/#{@post.slug_url}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" onlylink_text = @new_title.present? ? "<b>#{@new_title}</b>\n\n#{full_post_link}" : full_post_link diff --git a/app/views/posts/_post_header.html.erb b/app/views/posts/_post_header.html.erb index e53cf6e..6fefab4 100644 --- a/app/views/posts/_post_header.html.erb +++ b/app/views/posts/_post_header.html.erb @@ -6,6 +6,9 @@ <div class="category" style="<%="border-left: 5px solid #{post.category.color};" if post.category.present? %>"> <a href="/posts/<%= post.slug_url %>"><%= render_title(post) %> </a> <%= render "privacy-icons", object: post %> + <% if post.is_hidden %> + <div class="fa fa-low-vision hidden-icon"></div> + <% end %> <% if post.content_attachments.present? && post.content_attachments.select{ |b| !b.image? && !b.video? && !b.audio? }.any? %> <a><i class="fa fa-file right-small"></i></a> <% end %> @@ -14,7 +17,7 @@ </h1> <p class="metadata"><i class="fa fa-clock-o"> <%= I18n.t("posts.publish_date") %>: <%= post.created_at.strftime("%Y.%m.%d %H:%M") %></i></p> <p class="metadata"> - <i class="fa fa-user-circle"> <%= I18n.t("posts.author") %>: <a href="?user=<%=post.user.id%>"><%= display_name(post.user) %></a> + <i class="fa fa-user-circle"> <%= I18n.t("posts.author") %>: <a href="/posts/?user=<%=post.user.id%>"><%= display_name(post.user) %></a> <% if allowed_to?(:update?, post) %> | <a href="/posts/<%=post.slug_url%>/edit"><%= I18n.t("posts.edit_post") %></a> | <a href="/posts/delete/<%=post.slug_url%>" data-confirm="<%= I18n.t("posts.delete_post_confirm") %>"><%= I18n.t("posts.delete_post") %></a> diff --git a/config/locales/en.yml b/config/locales/en.yml index 66b4d75..3cc139f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -65,6 +65,7 @@ en: no_category: No category new_category: "*Optional* New category" new_tags: "*Optional* tag1,tag2,tag3" + is_hidden: "Post is hidden (from posts list)?" tags: manage_tags: Manage tags tags_header: New tags diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 44bd5b5..c2430d5 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -65,6 +65,7 @@ ru: no_category: Без категории new_category: "*Не обязательно* Название" new_tags: "*Не обязательно* тэг1,тэг2,тэг3" + is_hidden: "Статья скрыта? (в ленте)" tags: manage_tags: Управление тэгами tags_header: Новые тэги diff --git a/db/migrate/20240718151737_add_is_hidden_to_posts.rb b/db/migrate/20240718151737_add_is_hidden_to_posts.rb index d71ebca..d42a5ae 100644 --- a/db/migrate/20240718151737_add_is_hidden_to_posts.rb +++ b/db/migrate/20240718151737_add_is_hidden_to_posts.rb @@ -1,7 +1,8 @@ class AddIsHiddenToPosts < ActiveRecord::Migration[7.0] def up add_column :posts, :is_hidden, :boolean, default: false - add_column :posts, :uuid, :uuid, null: false, default: "gen_random_uuid()", unique: true + add_column :posts, :uuid, :uuid + Post.update_all(uuid: SecureRandom.uuid) end def down remove_column :posts, :is_hidden, :boolean From 38e3c4bfcc126b5d40989250ca9ca51451fac986 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sat, 20 Jul 2024 20:13:19 +0300 Subject: [PATCH 20/47] Feed page color --- app/assets/stylesheets/base.scss | 27 +++++++++++++++++-- .../stylesheets/default_Dark_feed_theme.scss | 8 +++++- app/assets/stylesheets/default_theme.scss | 8 +++++- app/assets/stylesheets/gruvbox_theme.scss | 8 +++++- app/assets/stylesheets/ruby_theme.scss | 8 +++++- app/assets/stylesheets/twilight_theme.scss | 10 +++++-- 6 files changed, 61 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 41eb02c..f57dc3c 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -89,7 +89,8 @@ $color-black: #000000; $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, - $color-feed-platform-tg, $color-feed-platform-mx){ + $color-feed-platform-tg, $color-feed-platform-mx, $color-feed-pages-bg, $color-feed-pages-font, $color-feed-pages-active-bg, + $color-feed-pages-active-font){ @include dropzone($color-border, $color-form-textarea-bg); code{ /* #cc241d */ @@ -1055,7 +1056,7 @@ $color-black: #000000; background-color: $color-feed-badge-bg; } } - + .filters-group{ margin-bottom: 10px; display: table; @@ -1096,6 +1097,28 @@ $color-black: #000000; background-color: $color-feed-filters-bg2; border-color: $color-feed-filters-border; } + + /* feeds pages */ + .pagination{ + li > a, span { + color: $color-feed-pages-font; + background-color: $color-feed-pages-bg; + border: 1px solid $color-feed-pages-active-bg; + } + .active > { + a { + color: $color-feed-pages-active-font; + background-color: $color-feed-pages-active-bg; + text-decoration: underline; + + &:hover, &:focus { + color: $color-feed-date; + background-color: $color-feed-pages-active-bg; + text-decoration: underline; + } + } + } + } } /* feeds/right-bar */ diff --git a/app/assets/stylesheets/default_Dark_feed_theme.scss b/app/assets/stylesheets/default_Dark_feed_theme.scss index 9f77092..f63621c 100644 --- a/app/assets/stylesheets/default_Dark_feed_theme.scss +++ b/app/assets/stylesheets/default_Dark_feed_theme.scss @@ -31,6 +31,11 @@ $color-feed-music-icon: white; $color-feed-platform-tg: #3E6A80; $color-feed-platform-mx: #439143; +$color-feed-pages-bg: #1d2021; +$color-feed-pages-font: #f0f0f0; +$color-feed-pages-active-bg: #3E5A68; +$color-feed-pages-active-font: white; + @include base($bg, $color-darkpurple, $color-code-bg, $font, $font-link, $color-white, $color-black, @@ -48,5 +53,6 @@ $color-feed-platform-mx: #439143; $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, - $color-feed-platform-tg, $color-feed-platform-mx + $color-feed-platform-tg, $color-feed-platform-mx, $color-feed-pages-bg, $color-feed-pages-font, $color-feed-pages-active-bg, + $color-feed-pages-active-font ); \ No newline at end of file diff --git a/app/assets/stylesheets/default_theme.scss b/app/assets/stylesheets/default_theme.scss index 938a699..d80be64 100644 --- a/app/assets/stylesheets/default_theme.scss +++ b/app/assets/stylesheets/default_theme.scss @@ -76,6 +76,11 @@ $color-feed-music-icon: white; $color-feed-platform-tg: #66b1ff; $color-feed-platform-mx: #5cb85c; +$color-feed-pages-bg: #ffffff; +$color-feed-pages-font: #367cb8; +$color-feed-pages-active-bg: #367cb8; +$color-feed-pages-active-font: #ffffff; + $color-alert-danger: #222426; $color-alert-danger-text: #df4655; $color-alert-success: #222426; @@ -112,5 +117,6 @@ $color-black: #000000; $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, - $color-feed-platform-tg, $color-feed-platform-mx + $color-feed-platform-tg, $color-feed-platform-mx, $color-feed-pages-bg, $color-feed-pages-font, $color-feed-pages-active-bg, + $color-feed-pages-active-font ); \ No newline at end of file diff --git a/app/assets/stylesheets/gruvbox_theme.scss b/app/assets/stylesheets/gruvbox_theme.scss index 2b27fa2..114e364 100644 --- a/app/assets/stylesheets/gruvbox_theme.scss +++ b/app/assets/stylesheets/gruvbox_theme.scss @@ -76,6 +76,11 @@ $color-feed-music-icon: #fbf1c7; $color-feed-platform-tg: #458588; $color-feed-platform-mx: #689d6a; +$color-feed-pages-bg: #1d2021; +$color-feed-pages-font: #fbf1c7; +$color-feed-pages-active-bg: #928374; +$color-feed-pages-active-font: #fbf1c7; + $color-alert-danger: #ad3b14; $color-alert-danger-text: #fcf4cd; $color-alert-success: #689d6a; @@ -112,5 +117,6 @@ $color-black: #000000; $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, - $color-feed-platform-tg, $color-feed-platform-mx + $color-feed-platform-tg, $color-feed-platform-mx, $color-feed-pages-bg, $color-feed-pages-font, $color-feed-pages-active-bg, + $color-feed-pages-active-font ); \ No newline at end of file diff --git a/app/assets/stylesheets/ruby_theme.scss b/app/assets/stylesheets/ruby_theme.scss index ac3fde5..34e6346 100644 --- a/app/assets/stylesheets/ruby_theme.scss +++ b/app/assets/stylesheets/ruby_theme.scss @@ -76,6 +76,11 @@ $color-feed-music-icon: #000000; $color-feed-platform-tg: #66b1ff; $color-feed-platform-mx: #5cb85c; +$color-feed-pages-bg: #ffffff; +$color-feed-pages-font: #367cb8; +$color-feed-pages-active-bg: #367cb8; +$color-feed-pages-active-font: #ffffff; + $color-alert-danger: #f2dede; $color-alert-danger-text: #a94442; $color-alert-success: #a1bfa1; @@ -112,5 +117,6 @@ $color-black: #000000; $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, - $color-feed-platform-tg, $color-feed-platform-mx + $color-feed-platform-tg, $color-feed-platform-mx, $color-feed-pages-bg, $color-feed-pages-font, $color-feed-pages-active-bg, + $color-feed-pages-active-font ); \ No newline at end of file diff --git a/app/assets/stylesheets/twilight_theme.scss b/app/assets/stylesheets/twilight_theme.scss index e9b6630..ed368be 100644 --- a/app/assets/stylesheets/twilight_theme.scss +++ b/app/assets/stylesheets/twilight_theme.scss @@ -69,13 +69,18 @@ $color-feed-filters-bg2: #1b1b1c; $color-feed-filters-font: #f0f0f0; $color-feed-filters-border: #b3b3b3; $color-feed-filters-badge-font: #f0f0f0; -$color-feed-badge-bg: #bdae93; +$color-feed-badge-bg: #82745b; $color-feed-music-bg: black; $color-feed-music-icon: white; $color-feed-platform-tg: #458588; $color-feed-platform-mx: #689d6a; +$color-feed-pages-bg: #0d1117; +$color-feed-pages-font: #f0f0f0; +$color-feed-pages-active-bg: #3E5A68; +$color-feed-pages-active-font: white; + $color-alert-danger: #13171d; $color-alert-danger-text: #df4655; $color-alert-success: #13171d; @@ -112,5 +117,6 @@ $color-black: #000000; $color-feed-filters-search-bg, $color-feed-filters-search-font, $color-feed-filters-search-border, $color-feed-filters-icon, $color-feed-filters-bg, $color-feed-filters-bg2, $color-feed-filters-font, $color-feed-filters-border, $color-feed-filters-badge-font, $color-feed-badge-bg, $color-feed-music-bg, $color-feed-music-icon, - $color-feed-platform-tg, $color-feed-platform-mx + $color-feed-platform-tg, $color-feed-platform-mx, $color-feed-pages-bg, $color-feed-pages-font, $color-feed-pages-active-bg, + $color-feed-pages-active-font ); \ No newline at end of file From 765ba6eafb9d6af4c53318ef5b5fa933bb514c41 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 25 Jul 2024 19:01:57 +0300 Subject: [PATCH 21/47] Fixes & improvements --- app/controllers/posts_controller.rb | 2 +- app/helpers/posts_helper.rb | 13 +++++++------ app/models/content.rb | 16 +++++++--------- app/models/platform_post.rb | 9 +++++---- app/models/user.rb | 18 ++++++++++++++---- app/services/check_channel.rb | 1 + app/services/platform/send_post_to_telegram.rb | 1 + app/services/platform/update_matrix_posts.rb | 1 + app/services/platform/update_telegram_posts.rb | 1 + app/services/send_post_to_platforms.rb | 1 + app/views/posts/new.html.erb | 4 ++++ 11 files changed, 43 insertions(+), 24 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 02fc0b8..1e3369c 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -172,7 +172,7 @@ def new def create authorize! current_user, to: :create_posts? - @post = Post.new(title: posts_params[:post][:title]) + @post = Post.new(title: posts_params[:post][:title], is_hidden: params[:post][:is_hidden]) @post.user = current_user @post.privacy = posts_params.dig(:post, :privacy) || 2 base_url = request.base_url diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index 75339d6..9db7d28 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -54,14 +54,15 @@ def display_raw_attachments(post) content += '<br><br>' if documents.any? post.content_attachments&.each do |att| + full_att_link = get_full_attachment_link(att) if att.image? - content += link_to image_tag(url_for(att)), url_for(att), target: '_blank'.to_s + content += link_to image_tag(full_att_link), full_att_link, target: '_blank'.to_s content += '<br>' elsif att.video? - content += link_to url_for(att).to_s + content += link_to full_att_link.to_s content += '<br>' elsif att.audio? - content += link_to url_for(att).to_s + content += link_to full_att_link.to_s content += '<br>' end end @@ -150,10 +151,10 @@ def render_published_platforms(post) post.published_platforms.each_with_index do |(k,v), i| vv = v.reject{ |vv| vv[:channel_name].nil?}.uniq{ |u| u[:channel_name] } next if vv.empty? - content += "#{k.capitalize}: " content += ' | ' if i != 0 + content += "#{k.capitalize}: " vv.each_with_index do |vvv, ii| - content += vvv[:url].present? ? link_to(vvv[:channel_name], vvv[:url]) : "#{vvv[:channel_name]} (Private)" + content += vvv[:url].present? ? link_to(vvv[:channel_name], vvv[:url]) : link_to("#{vvv[:channel_name]} (Private)", "#") content += ', ' if ii != vv.size - 1 end end @@ -169,7 +170,7 @@ def render_feed_published_flatforms(post) p_class = "platform-mx" if k == "matrix" next if vv.empty? vv.each_with_index do |vvv, ii| - content += vvv[:url].present? ? link_to(vvv[:channel_name], vvv[:url], class: p_class) : "#{vvv[:channel_name]} (Private)" + content += vvv[:url].present? ? link_to(vvv[:channel_name], vvv[:url], class: p_class) : link_to("#{vvv[:channel_name]} (Private)", "#", class: p_class) end end content.html_safe diff --git a/app/models/content.rb b/app/models/content.rb index b69d519..b8bbaa5 100644 --- a/app/models/content.rb +++ b/app/models/content.rb @@ -6,15 +6,9 @@ class Content < ApplicationRecord belongs_to :post # TODO: Если было изменено содержимое контента, то оно не всегда почему-то хочет отображать изменения. - after_create_commit do - broadcast_update_to [post], partial: 'posts/post', locals: { post: post }, target: "post_#{post.id}" - end - after_update_commit do - broadcast_update_to [post], partial: 'posts/post', locals: { post: post }, target: "post_#{post.id}" - end - after_destroy_commit do - broadcast_update_to [post], partial: 'posts/post', locals: { post: post }, target: "post_#{post.id}" - end + after_create_commit do upd_post end + after_update_commit do upd_post end + after_destroy_commit do upd_post end has_many_attached :attachments do |attachable| attachable.variant :thumb100, resize_to_limit: [100, 100] @@ -28,4 +22,8 @@ def destroy attachments.purge super end + + def upd_post + broadcast_update_to [self.post], partial: 'posts/post', locals: { post: self.post }, target: "post_#{self.post.id}" + end end diff --git a/app/models/platform_post.rb b/app/models/platform_post.rb index 1a33c46..8a124ac 100644 --- a/app/models/platform_post.rb +++ b/app/models/platform_post.rb @@ -10,10 +10,11 @@ class PlatformPost < ApplicationRecord def post_link case platform.title when 'telegram' - # TODO: WTF?? Make local - chat = Telegram.bot.get_chat(chat_id: identifier['chat_id']) - if chat['result']['username'].present? && (chat['result']['type'] != 'private') - "https://t.me/#{chat['result']['username']}/#{identifier['message_id']}" + return "" if channel.nil? + if channel.options.dig("username").present? #&& (chat['result']['type'] != 'private') + "https://t.me/#{channel.options.dig("username")}/#{identifier['message_id']}" + else + "" end when 'matrix' return "" if channel.nil? diff --git a/app/models/user.rb b/app/models/user.rb index 4794278..4ecd437 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -11,8 +11,7 @@ class User < ApplicationRecord has_many :comments, dependent: :destroy has_many :invite_codes, dependent: :delete_all has_many :categories, dependent: :delete_all - has_and_belongs_to_many :tags, class_name: 'Tag', join_table: 'item_tags', as: :item, - dependent: :delete_all # Not working deletion with SQLite! + has_and_belongs_to_many :tags, class_name: 'Tag', join_table: 'item_tags', as: :item has_many :active_tags, -> { active('User') }, class_name: 'ItemTag', foreign_key: 'item_id' has_many :visits, class_name: "Ahoy::Visit" @@ -110,7 +109,18 @@ def verify_password_and_update(params) def destroy avatar.purge - ItemTag.where(item: self).delete_all - super + ItemTag.where(item_type: "User", item: self).delete_all + ids = ItemTag.select { |i| i.item_type == "Post" && i.item.present? && i.item.user == self } + ItemTag.where(id: ids).delete_all if ids.present? + InviteCode.where(user: self).delete_all + Comment.where(user: self).destroy_all + Post.where(user: self).destroy_all + Content.where(user: self).destroy_all + Channel.where(user: self).destroy_all + Category.where(user: self).destroy_all + Ahoy::Event.where(user: self).update(user: nil) + Ahoy::Visit.where(user: self).update(user: nil) + self.delete + #super # TODO: Fix it end end diff --git a/app/services/check_channel.rb b/app/services/check_channel.rb index d1fdb80..2be6529 100644 --- a/app/services/check_channel.rb +++ b/app/services/check_channel.rb @@ -97,6 +97,7 @@ def check_telegram options[:title] = chat['result']['title'] options[:username] = chat['result']['username'] + options[:invite_link] = chat['result']['invite_link'] avatar = get_chat_avatar(bot, params[:channel][:room]) if avatar.present? diff --git a/app/services/platform/send_post_to_telegram.rb b/app/services/platform/send_post_to_telegram.rb index 037e41a..b9b7c17 100644 --- a/app/services/platform/send_post_to_telegram.rb +++ b/app/services/platform/send_post_to_telegram.rb @@ -61,6 +61,7 @@ def call if @attachments.present? # Create first attachment post attachment_content = Content.create!(user: @post.user, post: @post, has_attachments: true) @attachments.each { |att| attachment_content.attachments.attach(att) } if @attachments.present? + attachment_content.upd_post if @attachments.present? end post_text_blocks.each { |t| Content.create!(user: @post.user, post: @post, text: t) } if post_text_blocks.present? diff --git a/app/services/platform/update_matrix_posts.rb b/app/services/platform/update_matrix_posts.rb index 68861f9..15abc5e 100644 --- a/app/services/platform/update_matrix_posts.rb +++ b/app/services/platform/update_matrix_posts.rb @@ -155,6 +155,7 @@ def call blob_id = ActiveStorage::Blob.find_signed(attachment[0])&.id # may deleted in tg update @post.content_attachments.find_by(blob_id: blob_id).purge if blob_id.present? && attachment[1] == '0' end + @post.contents.first&.upd_post if @deleted_attachments.present? end def edit_mx_onlylink_post(platform_post, room_id, event_id) diff --git a/app/services/platform/update_telegram_posts.rb b/app/services/platform/update_telegram_posts.rb index 8c89bb3..78ffc92 100644 --- a/app/services/platform/update_telegram_posts.rb +++ b/app/services/platform/update_telegram_posts.rb @@ -296,6 +296,7 @@ def delete_attachments @deleted_attachments.each do |attachment| @post.content_attachments.find_by(blob_id: ActiveStorage::Blob.find_signed!(attachment).id).purge end + @post.contents.first&.upd_post if @deleted_attachments.present? end def move_caption(bot, platform_post, deleted_indexes) diff --git a/app/services/send_post_to_platforms.rb b/app/services/send_post_to_platforms.rb index 8e5dc1f..55eac9f 100644 --- a/app/services/send_post_to_platforms.rb +++ b/app/services/send_post_to_platforms.rb @@ -26,6 +26,7 @@ def create_only_site_post content = Content.create!(user: @post.user, post: @post, text: params[:post][:content], has_attachments: @attachments.present?) @attachments.each { |att| content.attachments.attach(att) } if @attachments.present? + content.upd_post if @attachments.present? end def call diff --git a/app/views/posts/new.html.erb b/app/views/posts/new.html.erb index cd732a6..9c7be69 100644 --- a/app/views/posts/new.html.erb +++ b/app/views/posts/new.html.erb @@ -82,6 +82,10 @@ <h3><%= I18n.t("posts.privacy") %></h3> <%= f.select(:privacy, [[I18n.t("posts.privacy_public"), 0], [I18n.t("posts.privacy_registered"), 1], [I18n.t("posts.privacy_onlyme"), 2]], required: true) %> </div> + <div class="colorful-checkboxes" style="display: inherit"> + <%= f.check_box(:is_hidden, { :class=>"colorful-checkbox", :checked => false }) %> + <%= f.label :is_hidden, I18n.t("posts.is_hidden") %> + </div> <div class="tags"> <h3><%= I18n.t("posts.tags") %></h3> <% if Tag.any? %> From cc7ef0c649b4a83fc2c73559cd03fdcc85c9a9d1 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sun, 28 Jul 2024 18:05:09 +0300 Subject: [PATCH 22/47] Fixes --- app/controllers/posts_controller.rb | 1 + app/services/export_files.rb | 1 + app/services/import_files.rb | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 1e3369c..8b71eab 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -17,6 +17,7 @@ def track_action if post.present? post_uuid = post.uuid.to_s if not Ahoy::Event.where(name: "Post_#{post_uuid}", visit_id: current_visit&.id).where_properties(uuid: post_uuid).exists? + request_params["uuid"] = post_uuid ahoy.track("Post_#{post_uuid}", request_params) end else diff --git a/app/services/export_files.rb b/app/services/export_files.rb index 9ab425d..0c23cd3 100644 --- a/app/services/export_files.rb +++ b/app/services/export_files.rb @@ -26,6 +26,7 @@ def call \n\n<TW_METADATA> <DATE>#{@post.updated_at}</DATE> <PRIVACY>#{@post.privacy}</PRIVACY> + <IS_HIDDEN>#{@post.is_hidden ? 1 : 0}</IS_HIDDEN> <CATEGORY>#{@post.category_id}</CATEGORY> <TAGS>#{ItemTag.where(enabled: true, item: @post).map { |item_tag| item_tag.tag.name.to_s }.join(',')}</TAGS> </TW_METADATA> diff --git a/app/services/import_files.rb b/app/services/import_files.rb index 303c62d..3e40bbf 100644 --- a/app/services/import_files.rb +++ b/app/services/import_files.rb @@ -50,6 +50,16 @@ def call privacy = 2 end + # Parse post privacy + is_hidden = text.match('<IS_HIDDEN>([^\\D>]+)<\\/IS_HIDDEN>') + is_hidden = is_hidden.captures.first if is_hidden.present? + + begin + is_hidden = !is_hidden.to_i.zero? if is_hidden.present? + rescue StandardError + is_hidden = false + end + # Parse post category category = text.match('<CATEGORY>([^\\D>]+)<\\/CATEGORY>') category = category.captures.first if category.present? @@ -57,8 +67,8 @@ def call category = nil if @current_user.categories.find_by(id: category).blank? # Create post, lol - post = Post.create!(user: @current_user, title: content_title, privacy: privacy, category_id: category, - created_at: date) + post = Post.create!(user: @current_user, title: content_title, privacy: privacy, is_hidden: is_hidden, + category_id: category, created_at: date) # Parse post tags tags = text.match('<TAGS>([^\\>]+)<\\/TAGS>') From 724526ba334f928e16ce434b90cec539ff65e765 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Wed, 31 Jul 2024 18:10:09 +0300 Subject: [PATCH 23/47] Comments to tg --- app/assets/stylesheets/base.scss | 6 ++ app/controllers/comments_controller.rb | 16 +++- app/controllers/telegram_controller.rb | 67 +++++++++----- app/models/channel.rb | 1 + app/models/comment.rb | 1 + app/services/check_channel.rb | 2 +- .../platform/send_comment_to_telegram.rb | 89 +++++++++++++++++++ app/services/send_comment_to_platforms.rb | 59 ++++++++++++ app/views/comments/_add.html.erb | 12 ++- ...240731125054_add_channel_id_to_comments.rb | 9 ++ 10 files changed, 235 insertions(+), 27 deletions(-) create mode 100644 app/services/platform/send_comment_to_telegram.rb create mode 100644 app/services/send_comment_to_platforms.rb create mode 100644 db/migrate/20240731125054_add_channel_id_to_comments.rb diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index f57dc3c..1a6d12b 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -920,6 +920,12 @@ $color-black: #000000; tr:hover td { color: $color-table-tr-hover; } + + /* Platforms in comments */ + .colorful-checkboxes{ + text-align: left; + margin: 1% 1% auto 1%; + } } .user-avatar{ diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 2af1283..7cb6100 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -8,16 +8,22 @@ def create authorize! current_post, to: :create_comments? ref_url = request.referrer - current_comment = Comment.create!(text: params[:comment][:content], user: current_user, post: current_post) + channels = params['channels']&.to_unsafe_h.select{ |k, v| v.to_i == 1 } if params['channels'].present? + if params['channels'].present? && channels.any? + SendCommentToPlatforms.call(params, channels, current_post, current_user) + else + current_comment = Comment.create!(text: params[:comment][:content], user: current_user, post: current_post) + end - if current_comment.save + if current_comment.present? && current_comment.save if ref_url.include?("feed") redirect_to ref_url else redirect_to post_path(current_comment.post) end else - render :new + redirect_to ref_url + #render :new end end @@ -42,7 +48,9 @@ def update def destroy authorize! current_comment - ref_url = request.referrer + ref_url = request. + + # TODO: Delete from platforms post = current_comment.post current_comment.delete diff --git a/app/controllers/telegram_controller.rb b/app/controllers/telegram_controller.rb index b78983f..15dfaa9 100644 --- a/app/controllers/telegram_controller.rb +++ b/app/controllers/telegram_controller.rb @@ -5,7 +5,7 @@ class TelegramController < Telegram::Bot::UpdatesController include TelegramShared def initialize(bot = nil, update = nil) - @channel_ids = Channel.where(enabled: true).map(&:room) + @channel_ids = Channel.where(enabled: true).map{ |ch| [ch.id, ch.room]}.to_h @telegram_platform = Platform.find_by(title: 'telegram') super end @@ -26,24 +26,48 @@ def edited_message(message) def check_message(message) comment_channel = message.dig('reply_to_message', 'sender_chat', 'id') + from_channel = message.dig('sender_chat', 'id') - return check_platform_comment(message) if comment_channel.present? && @channel_ids.include?(comment_channel.to_s) + if from_channel.present? && @channel_ids.values.include?(from_channel.to_s) + channel = Channel.find_by(room: from_channel) + check_linked_group(message, channel) + check_platform_message(message, channel) + end + return check_platform_comment(message) if comment_channel.present? && @channel_ids.values.include?(comment_channel.to_s) reply_message = message.dig('reply_to_message', 'message_id') # Check reply on chat comment check_reply_comment(message, reply_message) if reply_message.present? end + def check_linked_group(message, channel) + linked_chat_id = channel.options.dig('linked_chat_id') + return if linked_chat_id.present? + options = channel.options + options["linked_chat_id"] = message["chat"]["id"] + options["comments_enabled"] = true + channel.update!(options: options) + end + + def check_platform_message(message, channel) + chat_id = message.dig('sender_chat', 'id') + message_id = message.dig('forward_from_message_id') + platform_post = PlatformPost.where(channel: channel).find{ |pp| pp.identifier["chat_id"] == chat_id && pp.identifier["message_id"] == message_id } + identifier = platform_post.identifier + identifier["linked_chat_message_id"] = message.dig('message_id') + platform_post.update!(identifier: identifier) + end + def check_edit_message(message) comment_channel = message.dig('reply_to_message', 'sender_chat', 'id') - return check_edit_platform_comment(message) if comment_channel.present? && @channel_ids.include?(comment_channel.to_s) + return check_edit_platform_comment(message) if comment_channel.present? && @channel_ids.values.include?(comment_channel.to_s) reply_message = message.dig('reply_to_message', 'message_id') # Check reply on chat comment check_edit_reply_comment(message, reply_message) if reply_message.present? end def check_platform_comment(message) - Rails.logger.debug('TG: ADDING COMMENT...'.red) if Rails.env.development? + Rails.logger.debug('TG: ADDING COMMENT...'.green) if Rails.env.development? post_message_id = message['reply_to_message']['forward_from_message_id'] platform_post = nil @@ -62,7 +86,7 @@ def check_platform_comment(message) end def check_edit_platform_comment(message) - Rails.logger.debug('TG: CHECK EDITING COMMENT...'.red) if Rails.env.development? + Rails.logger.debug('TG: CHECK EDITING COMMENT...'.green) if Rails.env.development? post_message_id = message['message_id'] comment = nil Comment.all.each do |p| @@ -77,7 +101,7 @@ def check_edit_platform_comment(message) end def check_reply_comment(message, reply_message) - Rails.logger.debug('TG: CHECK REPLY COMMMENT...'.red) if Rails.env.development? + Rails.logger.debug('TG: CHECK REPLY COMMMENT...'.green) if Rails.env.development? platform_post = nil @@ -95,7 +119,7 @@ def check_reply_comment(message, reply_message) end def check_edit_reply_comment(message, reply_message) - Rails.logger.debug('TG: CHECK REPLY EDITING COMMMENT...'.red) if Rails.env.development? + Rails.logger.debug('TG: CHECK REPLY EDITING COMMMENT...'.green) if Rails.env.development? prev_comment = nil current_comment = nil @@ -119,13 +143,14 @@ def create_comment(message, platform_post) user = check_user(message) attachment = check_attachments(message) + channel_id = @channel_ids.find { |k,v| v == message.dig('reply_to_message', 'sender_chat', 'id').to_s }.first if attachment.present? identifier = { message_id: message['message_id'], chat_id: message['chat']['id'], file_id: attachment[:file_id], file_size: attachment[:file_size] } if attachment[:media_group_id].present? - Rails.logger.debug("MEDIA GROUP ID PRESENT... #{attachment[:media_group_id]}".red) if Rails.env.development? + Rails.logger.debug("MEDIA GROUP ID PRESENT... #{attachment[:media_group_id]}".green) if Rails.env.development? # puts(attachment) # Find comment by media_group_id and att attachment if found media_comment = [] @@ -138,7 +163,7 @@ def create_comment(message, platform_post) media_comment = comm end end - Rails.logger.debug('MEDIA COMMENT CHECK...'.red) if Rails.env.development? + Rails.logger.debug('MEDIA COMMENT CHECK...'.green) if Rails.env.development? if media_comment.present? # Already exists, update comment... media_array = [] # P.S. Only first array element contains media_group_id @@ -159,15 +184,15 @@ def create_comment(message, platform_post) end end comment = Comment.create!(identifier: identifier, text: attachment[:caption], post: platform_post.post, - platform_user: user, has_attachments: true) + platform_user: user, has_attachments: true, channel_id: channel_id) file = URI.parse(attachment[:link]).open comment.attachments.attach(io: file, filename: attachment[:file_name], content_type: file.content_type) - Rails.logger.debug('TG: COMMENT WITH ATTACHMENT ADDED!'.red) if Rails.env.development? + Rails.logger.debug('TG: COMMENT WITH ATTACHMENT ADDED!'.green) if Rails.env.development? else comment_text = message['text'] identifier = { message_id: message['message_id'], chat_id: message['chat']['id'] } - Comment.create!(identifier: identifier, text: comment_text, post: platform_post.post, platform_user: user) - Rails.logger.debug('TG: COMMENT ADDED!'.red) if Rails.env.development? + Comment.create!(identifier: identifier, text: comment_text, post: platform_post.post, platform_user: user, channel_id: channel_id) + Rails.logger.debug('TG: COMMENT ADDED!'.green) if Rails.env.development? end end @@ -186,7 +211,7 @@ def edit_comment(message, comment) c.each_with_index do |e, j| next unless (e['message_id'] == message['message_id']) && (c['file_size'] != attachment[:file_size]) - Rails.logger.debug('FOUND YA IN ARRAY!'.red) if Rails.env.development? + Rails.logger.debug('FOUND YA IN ARRAY!'.green) if Rails.env.development? # delete(e) # Sync attachment deletion # c.append(identifier) e.clear @@ -194,7 +219,7 @@ def edit_comment(message, comment) att_id = j end elsif (c['message_id'] == message['message_id']) && (c['file_size'] != attachment[:file_size]) - Rails.logger.debug('FOUND YA IN HASH!'.red) if Rails.env.development? + Rails.logger.debug('FOUND YA IN HASH!'.green) if Rails.env.development? c.clear c.merge!(identifier) att_id = i @@ -209,11 +234,11 @@ def edit_comment(message, comment) end comment_text = message['caption'] comment.update!(text: comment_text, is_edited: true) - Rails.logger.debug('TG: COMMENT WITH ATTACHMENTS UPDATED!'.red) if Rails.env.development? + Rails.logger.debug('TG: COMMENT WITH ATTACHMENTS UPDATED!'.green) if Rails.env.development? else comment_text = message['text'] comment.update!(text: comment_text, is_edited: true) - Rails.logger.debug('TG: COMMENT UPDATED!'.red) if Rails.env.development? + Rails.logger.debug('TG: COMMENT UPDATED!'.green) if Rails.env.development? end end @@ -239,7 +264,7 @@ def check_user(message) end if user.present? - Rails.logger.debug('TG: USER FOUND'.red) if Rails.env.development? + Rails.logger.debug('TG: USER FOUND'.green) if Rails.env.development? old_identifier = user.identifier identifier = { id: tg_user, fname: tg_fname, lname: tg_lname, username: tg_username } identifier[:avatar_size] = avatar[:file_size] if avatar.present? @@ -252,7 +277,7 @@ def check_user(message) user.update!(identifier: identifier) unless old_identifier == identifier # Update user info else - Rails.logger.debug('TG: USER NOT FOUND, CREATING...'.red) if Rails.env.development? + Rails.logger.debug('TG: USER NOT FOUND, CREATING...'.green) if Rails.env.development? identifier = { id: tg_user, fname: tg_fname, lname: tg_lname, username: tg_username }.compact user = PlatformUser.create!(identifier: identifier, platform: @telegram_platform) @@ -282,7 +307,7 @@ def get_avatar(tg_user) end def check_attachments(message) - Rails.logger.debug('CHECKING ATTACHMENTS...'.red) if Rails.env.development? + Rails.logger.debug('CHECKING ATTACHMENTS...'.green) if Rails.env.development? if message.key?('photo') file_id = message['photo'].last['file_id'] elsif message.key?('video') @@ -295,7 +320,7 @@ def check_attachments(message) return nil if file_id.nil? - Rails.logger.debug('TG: ATTACHMENT FOUND!'.red) if Rails.env.development? + Rails.logger.debug('TG: ATTACHMENT FOUND!'.green) if Rails.env.development? msg = bot.get_file(file_id: file_id) { link: "https://api.telegram.org/file/bot#{bot.token}/#{msg['result']['file_path']}", diff --git a/app/models/channel.rb b/app/models/channel.rb index 2dc163e..95967e1 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -4,6 +4,7 @@ class Channel < ApplicationRecord belongs_to :platform belongs_to :user has_many :platform_posts + has_many :comments has_one_attached :avatar diff --git a/app/models/comment.rb b/app/models/comment.rb index 997a0c3..8ab90e8 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -3,6 +3,7 @@ class Comment < ApplicationRecord belongs_to :post belongs_to :user, optional: true # Site comment + belongs_to :channel, optional: true belongs_to :platform_user, optional: true # Platform comment has_many_attached :attachments diff --git a/app/services/check_channel.rb b/app/services/check_channel.rb index 2be6529..5237136 100644 --- a/app/services/check_channel.rb +++ b/app/services/check_channel.rb @@ -92,7 +92,7 @@ def check_telegram return errors.add(:base, errs) if errs.any? end - options[:room_comments] = comment_chat_id + options[:linked_chat_id] = comment_chat_id end options[:title] = chat['result']['title'] diff --git a/app/services/platform/send_comment_to_telegram.rb b/app/services/platform/send_comment_to_telegram.rb new file mode 100644 index 0000000..be8b6ff --- /dev/null +++ b/app/services/platform/send_comment_to_telegram.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +class Platform::SendCommentToTelegram + prepend SimpleCommand + + attr_accessor :params, :channel_ids, :current_post, :current_user + + def initialize(params, channel_ids, current_post, current_user) + @params = params + @channel_ids = channel_ids + @current_post = current_post + @current_user = current_user + + @platform = Platform.find_by(title: 'telegram') + + @channels = + Channel.where(id: channel_ids).map do |channel| + { id: channel.id, + room: channel.room, + token: channel.token, + room_attachments: channel.options['room_attachments'], + linked_chat_id: channel.options.dig('linked_chat_id')} + end + + @markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, no_intra_emphasis: false, fenced_code_blocks: false, + disable_indented_code_blocks: true, autolink: false, + tables: false, underline: false, highlight: false) + end + + def call + return if @channels.empty? + + @channels.each do |channel| + send_telegram_comment(channel) + end + end + + def send_telegram_comment(channel) + bot = get_tg_bot(channel) + + text = @params[:comment][:content] + + has_attachments = @params.dig(:comment, :attachments) + + platform_post = @current_post.platform_posts.find{ |pp| pp.channel_id == channel[:id] } + chat_id = platform_post.identifier["chat_id"] + message_id = platform_post.identifier["linked_chat_message_id"] + linked_chat_id = channel[:linked_chat_id] # || get_linked_chat(bot, chat_id) + if linked_chat_id.nil? || message_id.nil? + return Rails.logger.error("Can't get linked group chat_id or linked message_id for channel #{channel[:id]} at #{Time.now.utc.iso8601}!") + end + + if has_attachments.present? && !has_attachments.empty? + send_telegram_attachments(bot, channel) + else + @msg = bot.send_message({ chat_id: linked_chat_id, + text: text, + reply_to_message_id: message_id, + parse_mode: 'html'}) + + res = { chat_id: @msg['result']['chat']['id'], + message_id: @msg['result']['message_id'], + #date: @msg['result']['date'], + platform: @platform + } + # TODO: make platform user detecteble? + Comment.create!(text: text, identifier: res, post: @current_post, user: @current_user, platform_user_id: nil, has_attachments: has_attachments, channel_id: channel[:id]) + end + rescue StandardError => e + Rails.logger.error("Failed create telegram comment for chat #{channel[:id]} at #{Time.now.utc.iso8601}:\n#{e}") + end + + def send_telegram_attachments(bot, channel) + print("NOT IMPLEMENTED YET!") + end + +# def get_linked_chat(bot, chat_id) +# begin + # select only one platform post (make comments for all platform posts is a bad idea) +# bot.get_chat(chat_id: chat_id).dig('result', 'linked_chat_id') +# rescue Telegram::Bot::Error +# errs << 'Channel not available! (Not found or bot access problems?)' +# end +# end + + def get_tg_bot(channel) + Twilight::Application::CURRENT_TG_BOTS.dig((channel[:token]).to_s, :client) + end +end diff --git a/app/services/send_comment_to_platforms.rb b/app/services/send_comment_to_platforms.rb new file mode 100644 index 0000000..5acfa5d --- /dev/null +++ b/app/services/send_comment_to_platforms.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +class SendCommentToPlatforms + prepend SimpleCommand + + attr_accessor :params, :channel_params, :current_post, :current_user + + def initialize(params, channel_params, current_post, current_user) + @params = params + @channel_params = channel_params + @current_post = current_post + @current_user = current_user + end + + def call + channel_ids = channel_params.map { |k, _v| k } + + channels = + Channel.where(id: channel_ids).map do |channel| + { + channel.platform.title => channel.id + } + end + + merged = + channels.inject do |h1, h2| + h1.merge(h2) do |_k, v1, v2| + if v1 == v2 + v1 + elsif v1.is_a?(Hash) && v2.is_a?(Hash) + v1.merge(v2) + else + [*v1, *v2] + end + end + end + + channels = merged.sort_by { |k, _v| k }.reverse.to_h # { "telegram"=>[1, 2], "matrix"=>3 } + + # Только так, иначе всё сломается! + Thread.new do + execution_context = Rails.application.executor.run! + channels.each do |k, v| + check_platforms(k, v) + ensure + execution_context&.complete! + end + end + end + + def check_platforms(platform, channel_ids) + case platform + when 'telegram' + Platform::SendCommentToTelegram.call(@params, channel_ids, @current_post, @current_user) + when 'matrix' + print("NOT IMPLEMENTED YET!") + end + end +end diff --git a/app/views/comments/_add.html.erb b/app/views/comments/_add.html.erb index f9dceba..e2a3b33 100644 --- a/app/views/comments/_add.html.erb +++ b/app/views/comments/_add.html.erb @@ -13,7 +13,17 @@ <div class="comment-editor"> <%= f.text_area :content, { id: "comments_content", placeholder: "#{I18n.t("posts.text_placeholder")}" } %> </div> - <p><br><%= f.submit I18n.t("channels.add")%><br></p> + <%telegram_channels = @current_post.published_channels.where(platform: Platform.find_by(title: "telegram"))%> + <% if telegram_channels.any? %> + <div class="colorful-checkboxes"> + <% telegram_channels.each do |channel| %> + <% title = channel.options&.dig("title") %> + <%=check_box("channels", channel.id, { :class=>"colorful-checkbox", :checked => false })%> + <%= label_tag "channels_#{channel.id}", title&.capitalize %> + <% end %> + </div> + <% end %> + <p><%= f.submit I18n.t("channels.add")%><br></p> </div> <% end %> </div> diff --git a/db/migrate/20240731125054_add_channel_id_to_comments.rb b/db/migrate/20240731125054_add_channel_id_to_comments.rb new file mode 100644 index 0000000..3711e31 --- /dev/null +++ b/db/migrate/20240731125054_add_channel_id_to_comments.rb @@ -0,0 +1,9 @@ +class AddChannelIdToComments < ActiveRecord::Migration[7.0] + def up + add_belongs_to :comments, :channel + end + + def down + remove_belongs_to :comments, :channel + end +end From f2433692a0404347713e65297c6c26a903683368 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Wed, 31 Jul 2024 22:12:51 +0300 Subject: [PATCH 24/47] Turbo for comments --- app/controllers/comments_controller.rb | 17 +----- app/controllers/telegram_controller.rb | 4 +- app/helpers/users_helper.rb | 11 ++++ app/models/comment.rb | 10 ++++ app/models/content.rb | 1 - .../platform/send_comment_to_telegram.rb | 2 +- app/views/comments/_comment_list.erb | 33 +++++++++++ app/views/posts/_comments.erb | 56 +++---------------- 8 files changed, 68 insertions(+), 66 deletions(-) create mode 100644 app/views/comments/_comment_list.erb diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 7cb6100..3049efe 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -12,18 +12,7 @@ def create if params['channels'].present? && channels.any? SendCommentToPlatforms.call(params, channels, current_post, current_user) else - current_comment = Comment.create!(text: params[:comment][:content], user: current_user, post: current_post) - end - - if current_comment.present? && current_comment.save - if ref_url.include?("feed") - redirect_to ref_url - else - redirect_to post_path(current_comment.post) - end - else - redirect_to ref_url - #render :new + current_comment = Comment.create!(text: params[:comment][:content], user: current_user, post: current_post, current_user: current_user) end end @@ -35,7 +24,7 @@ def update authorize! current_comment ref_url = request.referrer - if current_comment.update(text: params[:comment][:content], is_edited: true) + if current_comment.update(text: params[:comment][:content], is_edited: true, current_user: current_user) if ref_url.include?("feed") redirect_to ref_url else @@ -48,7 +37,7 @@ def update def destroy authorize! current_comment - ref_url = request. + ref_url = request.referrer # TODO: Delete from platforms diff --git a/app/controllers/telegram_controller.rb b/app/controllers/telegram_controller.rb index 15dfaa9..c8b5866 100644 --- a/app/controllers/telegram_controller.rb +++ b/app/controllers/telegram_controller.rb @@ -184,14 +184,14 @@ def create_comment(message, platform_post) end end comment = Comment.create!(identifier: identifier, text: attachment[:caption], post: platform_post.post, - platform_user: user, has_attachments: true, channel_id: channel_id) + platform_user: user, has_attachments: true, channel_id: channel_id, current_user: 0) file = URI.parse(attachment[:link]).open comment.attachments.attach(io: file, filename: attachment[:file_name], content_type: file.content_type) Rails.logger.debug('TG: COMMENT WITH ATTACHMENT ADDED!'.green) if Rails.env.development? else comment_text = message['text'] identifier = { message_id: message['message_id'], chat_id: message['chat']['id'] } - Comment.create!(identifier: identifier, text: comment_text, post: platform_post.post, platform_user: user, channel_id: channel_id) + Comment.create!(identifier: identifier, text: comment_text, post: platform_post.post, platform_user: user, channel_id: channel_id, current_user: 0) Rails.logger.debug('TG: COMMENT ADDED!'.green) if Rails.env.development? end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index bf4b83e..d1853a0 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -8,6 +8,17 @@ def display_name(user) user&.login || 'Guest' end end + + def display_comments_name(user) + return display_name(user) if user.is_a?(User) + # PlatformUser + username = user.identifier.dig("username") + fname = user.identifier.dig("fname") + lname = user.identifier.dig("lname") + name = "#{fname} #{lname}" + username.present? ? "#{name} (#{username})" : name + end + def channel_url(channel) if channel.platform.title == "telegram" && channel.options&.dig("username").present? link_to(channel.platform.title.capitalize, "https://t.me/#{channel.options&.dig("username")}", target: '_blank' ) diff --git a/app/models/comment.rb b/app/models/comment.rb index 8ab90e8..9f800d0 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -9,6 +9,12 @@ class Comment < ApplicationRecord validate :text_or_attachments + thread_mattr_accessor :current_user + + after_create_commit do upd_comment end + after_update_commit do upd_comment end + after_destroy_commit do upd_comment end + def text_or_attachments return unless text.empty? && !has_attachments @@ -30,4 +36,8 @@ def destroy attachments.purge super end + + def upd_comment + broadcast_update_to [self.post], partial: 'comments/comment_list', locals: { post: self.post, current_user: self.current_user }, target: "comments_#{self.post.id}" + end end diff --git a/app/models/content.rb b/app/models/content.rb index b8bbaa5..8c5baa3 100644 --- a/app/models/content.rb +++ b/app/models/content.rb @@ -5,7 +5,6 @@ class Content < ApplicationRecord belongs_to :user belongs_to :post - # TODO: Если было изменено содержимое контента, то оно не всегда почему-то хочет отображать изменения. after_create_commit do upd_post end after_update_commit do upd_post end after_destroy_commit do upd_post end diff --git a/app/services/platform/send_comment_to_telegram.rb b/app/services/platform/send_comment_to_telegram.rb index be8b6ff..18463ab 100644 --- a/app/services/platform/send_comment_to_telegram.rb +++ b/app/services/platform/send_comment_to_telegram.rb @@ -64,7 +64,7 @@ def send_telegram_comment(channel) platform: @platform } # TODO: make platform user detecteble? - Comment.create!(text: text, identifier: res, post: @current_post, user: @current_user, platform_user_id: nil, has_attachments: has_attachments, channel_id: channel[:id]) + Comment.create!(text: text, identifier: res, post: @current_post, user: @current_user, platform_user_id: nil, has_attachments: has_attachments, channel_id: channel[:id], current_user: @current_user) end rescue StandardError => e Rails.logger.error("Failed create telegram comment for chat #{channel[:id]} at #{Time.now.utc.iso8601}:\n#{e}") diff --git a/app/views/comments/_comment_list.erb b/app/views/comments/_comment_list.erb new file mode 100644 index 0000000..b6d9b7b --- /dev/null +++ b/app/views/comments/_comment_list.erb @@ -0,0 +1,33 @@ +<% if post.comments.empty? %> + <center><%= I18n.t("comments.comments_not_found") %></center> +<% else %> + <%post.comments.order("created_at ASC").group_by(&:channel).each do |ch, comms|%> + <%= ch.present? ? "#{ch.options["title"]} (#{ch.platform.title})" : "Blog" %>: <br> + <%comms.each do |comm|%> + <% user = comm.platform_user || comm.user %> + <div class="comment"> + <div class="comment-avatar"><% if user.avatar.present?%><%= image_tag url_for(user.avatar) %><% end %></div> + <% name = display_comments_name(user) %> + <div class="comment-metadata"> + <%= name %> + <i class="comment-small">- <%= comm.created_at.strftime("%Y-%m-%d %H:%M") %> + <% if comm.is_edited %> + <i class="fa fa-pencil-square-o "></i> + <% end %> + <% if current_user.present? && current_user.is_a?(User) && comm.user.present? && (current_user == comm.user)%> + | <a href="/comments/<%=comm.id%>/edit"><%= I18n.t("comments.edit") %></a> + <% end %> + </i> + <% if current_user.present? && current_user.is_a?(User) && (current_user.is_admin? || (current_user.present? && current_user.is_a?(User) && comm.user.present? && (current_user == comm.user))) %> + <i class="comment-small right"><a href = "/comments/delete/<%=comm.id%>"><%= I18n.t("comments.delete") %></a></i> + <% end %> + </div> + <%= markdown(comm.text) %> + <% if comm.has_attachments %> + <hr style="border-top: dashed 2px;" /> + <%= display_comment_attachments(comm) if comm.attachments.any? %> + <% end%> + </div> + <%end%> + <%end%> +<% end %> diff --git a/app/views/posts/_comments.erb b/app/views/posts/_comments.erb index 44a6738..4aa564c 100644 --- a/app/views/posts/_comments.erb +++ b/app/views/posts/_comments.erb @@ -1,49 +1,9 @@ <br> -<% if @current_post.comments.any? %> - <div class="comments-container comments-width"> - <center><%= render "comments/add" if current_user.present? %></center> - <div class="comment-header"><%= I18n.t("comments.comment_header") %>:</div> - <% comments = @current_post.comments.order("created_at DESC").group_by{ |item| item.send("user").present? } %> - <% if comments[true].present? %> - <%= I18n.t("comments.blog") %>: <br> - <% comments[true].each do |comment|%> - <% user = comment.user %> - <div class="comment"> - <div class="comment-avatar"><% if user.avatar.present?%><%= image_tag url_for(user.avatar) %><% end %></div> - <% name = display_name(user) %> - <div class="comment-metadata"><%= name %> <i class="comment-small">- <%= comment.created_at.strftime("%Y-%m-%d %H:%M") %> <% if comment.is_edited %> <i class="fa fa-pencil-square-o "></i> <% end %><% if current_user.present? && (current_user == user)%> | <a href="/comments/<%=comment.id%>/edit"><%= I18n.t("comments.edit") %></a><% end %></i><% if current_user.present? && (current_user.is_admin? || (current_user == user)) %><i class="comment-small right"><a href = "/comments/delete/<%=comment.id%>"><%= I18n.t("comments.delete") %></a></i><% end %></div> - <%= markdown(comment.text) %> - <% if comment.has_attachments %> - <hr style="border-top: dashed 2px;" /> - <%= display_comment_attachments(comment) if comment.attachments.any? %> - <% end%> - </div> - <br> <!-- May delete (reply?) --> - <% end %> - <% end %> - <% if comments[false].present? %> - <%= I18n.t("comments.telegram") %>: - <% comments[false].each do |comment|%> - <% user = comment.platform_user %> - <div class="comment"> - <div class="comment-avatar"><% if user.avatar.present?%><%= image_tag url_for(user.avatar) %><% end %></div> - <% name = comment.username %> - <div class="comment-metadata"><%= name[:name] %> <i class="comment-small">- <%= comment.created_at.strftime("%Y-%m-%d %H:%M") %> <% if comment.is_edited %> <i class="fa fa-pencil-square-o"></i> <% end %>| <%= user["identifier"].dig("username").present? ? link_to(user.platform.title.capitalize, "https://t.me/#{user["identifier"].dig("username")}" ) : user.platform.title.capitalize %></i><i class="comment-small right">ID: <%= comment.platform_user.identifier["id"] %></i></div> - <%= comment.text %> - <% if comment.has_attachments %> - <hr style="border-top: dashed 2px;" /> - <%= display_comment_attachments(comment) if comment.attachments.any? %> - <% end %> - </div> - <br> <!-- May delete (reply?) --> - <% end %> - <% end %> - </div> -<% else %> - <div class="comments-container comments-width"> - <center> - <%= render "comments/add" if current_user.present? %><br> - <%= I18n.t("comments.comments_not_found") %> - </center> - </div> -<% end %> \ No newline at end of file +<div class="comments-container comments-width"> + <center><%= render "comments/add" if current_user.present? %></center> + <div class="comment-header"><%= I18n.t("comments.comment_header") %>:</div> + <%= turbo_stream_from @current_post %> + <div id="<%="comments_#{@current_post.id}"%>"> + <%= render partial: "comments/comment_list", locals: { post: @current_post, current_user: current_user } %> + </div> +</div> \ No newline at end of file From 53ee581157b74cf31d086a6875fffd87adc1f74e Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 1 Aug 2024 09:00:27 +0300 Subject: [PATCH 25/47] Comments for tg fix --- app/assets/stylesheets/base.scss | 6 +++- app/controllers/comments_controller.rb | 14 ++++---- app/controllers/telegram_controller.rb | 32 +++++++++++-------- app/models/comment.rb | 3 +- app/policies/post_policy.rb | 4 +++ .../platform/send_comment_to_telegram.rb | 9 +++--- app/views/comments/_add.html.erb | 26 ++++++++------- app/views/comments/_comment_list.erb | 4 ++- app/views/posts/_comments.erb | 1 - ...240731125054_add_channel_id_to_comments.rb | 2 ++ 10 files changed, 62 insertions(+), 39 deletions(-) diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 1a6d12b..5e3f2f2 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -851,9 +851,13 @@ $color-black: #000000; font-size: 25px; } + .comment-header-medium{ + font-size: 20px; + } + .comment{ position: relative; - margin-top: 1%; + margin-top: 0.5%; margin-bottom: 2%; .wrapper { diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 3049efe..d55727f 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -10,9 +10,11 @@ def create channels = params['channels']&.to_unsafe_h.select{ |k, v| v.to_i == 1 } if params['channels'].present? if params['channels'].present? && channels.any? + return set_flash_message :alert, "Not allowed!" unless allowed_to?(:create_platform_comments?, current_post) SendCommentToPlatforms.call(params, channels, current_post, current_user) else - current_comment = Comment.create!(text: params[:comment][:content], user: current_user, post: current_post, current_user: current_user) + current_comment = Comment.create!(text: params[:comment][:content], + user: current_user, post: current_post, current_user: current_user) end end @@ -42,11 +44,9 @@ def destroy # TODO: Delete from platforms post = current_comment.post - current_comment.delete - if ref_url.include?("feed") - redirect_to ref_url - else - redirect_to post_path(post) - end + current_comment.current_user = current_user + current_comment.destroy! + + redirect_to ref_url end end diff --git a/app/controllers/telegram_controller.rb b/app/controllers/telegram_controller.rb index c8b5866..a70f47f 100644 --- a/app/controllers/telegram_controller.rb +++ b/app/controllers/telegram_controller.rb @@ -5,8 +5,9 @@ class TelegramController < Telegram::Bot::UpdatesController include TelegramShared def initialize(bot = nil, update = nil) - @channel_ids = Channel.where(enabled: true).map{ |ch| [ch.id, ch.room]}.to_h @telegram_platform = Platform.find_by(title: 'telegram') + @channel_ids = Channel.where(enabled: true, platform: @telegram_platform).map{ |ch| [ch.id, ch.room]}.to_h + @linked_group_channels_ids = Channel.where(id: @channel_ids.keys).map{ |ch| [ch.id, ch.options.dig("linked_chat_id")] }.to_h.compact_blank super end @@ -72,12 +73,12 @@ def check_platform_comment(message) platform_post = nil - PlatformPost.all.each do |p_post| + PlatformPost.where(platform: @telegram_platform).each do |p_post| if p_post[:identifier].is_a?(Array) # Post has attachments p_post[:identifier].each do |p| platform_post = p_post if p['message_id'] == post_message_id && p_post.platform_id == @telegram_platform.id end - elsif p_post[:identifier]['message_id'] == post_message_id && p_post.platform_id == @telegram_platform.id + elsif p_post[:identifier]['message_id'] == post_message_id platform_post = p_post end end @@ -89,7 +90,7 @@ def check_edit_platform_comment(message) Rails.logger.debug('TG: CHECK EDITING COMMENT...'.green) if Rails.env.development? post_message_id = message['message_id'] comment = nil - Comment.all.each do |p| + Comment.where(platform: @telegram_platform).each do |p| if p[:identifier].is_a?(Array) # Comment has attachments p[:identifier].each { |c| comment = p if c['message_id'] == post_message_id } elsif p[:identifier]['message_id'] == post_message_id @@ -104,13 +105,15 @@ def check_reply_comment(message, reply_message) Rails.logger.debug('TG: CHECK REPLY COMMMENT...'.green) if Rails.env.development? platform_post = nil - - Comment.all.each do |p_post| + + Comment.where(platform: @telegram_platform).each do |p_post| + next if p_post[:identifier].nil? # Blog comment if p_post[:identifier].is_a?(Array) # Post has attachments p_post[:identifier].each do |p| - platform_post = p_post if p['message_id'] == reply_message && p_post.platform_user.platform_id == @telegram_platform.id + platform_post = p_post if p['message_id'] == reply_message #&& p_post.platform_user.platform_id == @telegram_platform.id end - elsif p_post[:identifier]['message_id'] == reply_message && p_post.platform_user.platform_id == @telegram_platform.id + # Todo: Add platform id to comment + elsif p_post[:identifier]['message_id'] == reply_message #&& p_post.platform_user.platform_id == @telegram_platform.id platform_post = p_post end end @@ -124,7 +127,7 @@ def check_edit_reply_comment(message, reply_message) prev_comment = nil current_comment = nil - Comment.all.each do |p| + Comment.where(platform: @telegram_platform).each do |p| if p[:identifier].is_a?(Array) # Comment has attachments p[:identifier].each do |c| prev_comment = p if c['message_id'] == reply_message @@ -143,7 +146,8 @@ def create_comment(message, platform_post) user = check_user(message) attachment = check_attachments(message) - channel_id = @channel_ids.find { |k,v| v == message.dig('reply_to_message', 'sender_chat', 'id').to_s }.first + channel_id = @channel_ids.find { |k,v| v == message.dig('reply_to_message', 'sender_chat', 'id').to_s }&.first + channel_id = @linked_group_channels_ids.find { |k,v| v == message.dig('reply_to_message', 'sender_chat', 'id') }&.first if channel_id.nil? if attachment.present? identifier = { message_id: message['message_id'], chat_id: message['chat']['id'], @@ -154,7 +158,7 @@ def create_comment(message, platform_post) # puts(attachment) # Find comment by media_group_id and att attachment if found media_comment = [] - Comment.all.each do |comm| + Comment.where(platform: @telegram_platform).each do |comm| if comm[:identifier].is_a?(Array) # Comment has attachments comm[:identifier].each do |c| media_comment = comm if c['media_group_id'] == attachment[:media_group_id].to_s @@ -184,14 +188,16 @@ def create_comment(message, platform_post) end end comment = Comment.create!(identifier: identifier, text: attachment[:caption], post: platform_post.post, - platform_user: user, has_attachments: true, channel_id: channel_id, current_user: 0) + platform_user: user, has_attachments: true, channel_id: channel_id, current_user: 0, + platform: @telegram_platform) file = URI.parse(attachment[:link]).open comment.attachments.attach(io: file, filename: attachment[:file_name], content_type: file.content_type) Rails.logger.debug('TG: COMMENT WITH ATTACHMENT ADDED!'.green) if Rails.env.development? else comment_text = message['text'] identifier = { message_id: message['message_id'], chat_id: message['chat']['id'] } - Comment.create!(identifier: identifier, text: comment_text, post: platform_post.post, platform_user: user, channel_id: channel_id, current_user: 0) + Comment.create!(identifier: identifier, text: comment_text, post: platform_post.post, platform_user: user, + channel_id: channel_id, current_user: 0, platform: @telegram_platform) Rails.logger.debug('TG: COMMENT ADDED!'.green) if Rails.env.development? end end diff --git a/app/models/comment.rb b/app/models/comment.rb index 9f800d0..caa55dc 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -3,8 +3,9 @@ class Comment < ApplicationRecord belongs_to :post belongs_to :user, optional: true # Site comment - belongs_to :channel, optional: true + belongs_to :channel, optional: true # Optional if used linked channel belongs_to :platform_user, optional: true # Platform comment + belongs_to :platform, optional: true has_many_attached :attachments validate :text_or_attachments diff --git a/app/policies/post_policy.rb b/app/policies/post_policy.rb index 161a858..7f3bc23 100644 --- a/app/policies/post_policy.rb +++ b/app/policies/post_policy.rb @@ -35,4 +35,8 @@ def create? def create_comments? allowed_to?(:privacy?, record) || record&.is_admin? end + + def create_platform_comments? + (allowed_to?(:privacy?, record) && record&.user == user) || user&.is_admin? + end end diff --git a/app/services/platform/send_comment_to_telegram.rb b/app/services/platform/send_comment_to_telegram.rb index 18463ab..1152b93 100644 --- a/app/services/platform/send_comment_to_telegram.rb +++ b/app/services/platform/send_comment_to_telegram.rb @@ -19,7 +19,8 @@ def initialize(params, channel_ids, current_post, current_user) room: channel.room, token: channel.token, room_attachments: channel.options['room_attachments'], - linked_chat_id: channel.options.dig('linked_chat_id')} + linked_chat_id: channel.options.dig('linked_chat_id'), + } end @markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, no_intra_emphasis: false, fenced_code_blocks: false, @@ -59,12 +60,12 @@ def send_telegram_comment(channel) parse_mode: 'html'}) res = { chat_id: @msg['result']['chat']['id'], - message_id: @msg['result']['message_id'], + message_id: @msg['result']['message_id'] #date: @msg['result']['date'], - platform: @platform } # TODO: make platform user detecteble? - Comment.create!(text: text, identifier: res, post: @current_post, user: @current_user, platform_user_id: nil, has_attachments: has_attachments, channel_id: channel[:id], current_user: @current_user) + Comment.create!(text: text, identifier: res, post: @current_post, user: @current_user, has_attachments: has_attachments, + channel_id: channel[:id], current_user: @current_user, platform: @platform) end rescue StandardError => e Rails.logger.error("Failed create telegram comment for chat #{channel[:id]} at #{Time.now.utc.iso8601}:\n#{e}") diff --git a/app/views/comments/_add.html.erb b/app/views/comments/_add.html.erb index e2a3b33..dddf673 100644 --- a/app/views/comments/_add.html.erb +++ b/app/views/comments/_add.html.erb @@ -13,19 +13,23 @@ <div class="comment-editor"> <%= f.text_area :content, { id: "comments_content", placeholder: "#{I18n.t("posts.text_placeholder")}" } %> </div> - <%telegram_channels = @current_post.published_channels.where(platform: Platform.find_by(title: "telegram"))%> - <% if telegram_channels.any? %> - <div class="colorful-checkboxes"> - <% telegram_channels.each do |channel| %> - <% title = channel.options&.dig("title") %> - <%=check_box("channels", channel.id, { :class=>"colorful-checkbox", :checked => false })%> - <%= label_tag "channels_#{channel.id}", title&.capitalize %> - <% end %> - </div> - <% end %> + <%if allowed_to?(:create_platform_comments?, @current_post)%> + <%telegram_channels = @current_post.published_channels.where(platform: Platform.find_by(title: "telegram"))%> + <%if telegram_channels.any?%> + <div class="colorful-checkboxes"> + <%telegram_channels.each do |channel|%> + <%title = channel.options&.dig("title")%> + <%=check_box("channels", channel.id, { :class=>"colorful-checkbox", :checked => false })%> + <%= label_tag "channels_#{channel.id}", title&.capitalize %> + <%end%> + </div> + <%end%> + <%else%> + <br> + <%end%> <p><%= f.submit I18n.t("channels.add")%><br></p> </div> - <% end %> + <%end%> </div> </div> </div> diff --git a/app/views/comments/_comment_list.erb b/app/views/comments/_comment_list.erb index b6d9b7b..d3c735a 100644 --- a/app/views/comments/_comment_list.erb +++ b/app/views/comments/_comment_list.erb @@ -1,8 +1,10 @@ <% if post.comments.empty? %> <center><%= I18n.t("comments.comments_not_found") %></center> <% else %> + <div class="comment-header"><%= I18n.t("comments.comment_header") %>:</div> <%post.comments.order("created_at ASC").group_by(&:channel).each do |ch, comms|%> - <%= ch.present? ? "#{ch.options["title"]} (#{ch.platform.title})" : "Blog" %>: <br> + <%next if ch.present? && !ch.enabled%> + <div class="comment-header-medium"><%= ch.present? ? "#{ch.options["title"]} (#{ch.platform.title.capitalize})" : "Blog" %>:</div> <%comms.each do |comm|%> <% user = comm.platform_user || comm.user %> <div class="comment"> diff --git a/app/views/posts/_comments.erb b/app/views/posts/_comments.erb index 4aa564c..0394339 100644 --- a/app/views/posts/_comments.erb +++ b/app/views/posts/_comments.erb @@ -1,7 +1,6 @@ <br> <div class="comments-container comments-width"> <center><%= render "comments/add" if current_user.present? %></center> - <div class="comment-header"><%= I18n.t("comments.comment_header") %>:</div> <%= turbo_stream_from @current_post %> <div id="<%="comments_#{@current_post.id}"%>"> <%= render partial: "comments/comment_list", locals: { post: @current_post, current_user: current_user } %> diff --git a/db/migrate/20240731125054_add_channel_id_to_comments.rb b/db/migrate/20240731125054_add_channel_id_to_comments.rb index 3711e31..7f0520f 100644 --- a/db/migrate/20240731125054_add_channel_id_to_comments.rb +++ b/db/migrate/20240731125054_add_channel_id_to_comments.rb @@ -1,9 +1,11 @@ class AddChannelIdToComments < ActiveRecord::Migration[7.0] def up add_belongs_to :comments, :channel + add_belongs_to :comments, :platform end def down remove_belongs_to :comments, :channel + remove_belongs_to :comments, :platform end end From e9ef8419bbb6ee9ab6a856a0dc160534edf1dea1 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sat, 3 Aug 2024 12:04:19 +0300 Subject: [PATCH 26/47] Delete comments from tg --- app/controllers/comments_controller.rb | 4 +- app/controllers/telegram_controller.rb | 98 ++++++++++--------- app/models/comment.rb | 3 + app/services/delete_platform_comments.rb | 34 +++++++ .../platform/delete_matrix_comments.rb | 34 +++++++ .../platform/delete_telegram_comments.rb | 36 +++++++ .../platform/send_comment_to_telegram.rb | 1 - app/views/comments/_add.html.erb | 2 + ...20240802095320_add_reply_id_to_comments.rb | 8 ++ 9 files changed, 171 insertions(+), 49 deletions(-) create mode 100644 app/services/delete_platform_comments.rb create mode 100644 app/services/platform/delete_matrix_comments.rb create mode 100644 app/services/platform/delete_telegram_comments.rb create mode 100644 db/migrate/20240802095320_add_reply_id_to_comments.rb diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index d55727f..d13cb73 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -42,9 +42,11 @@ def destroy ref_url = request.referrer # TODO: Delete from platforms - post = current_comment.post current_comment.current_user = current_user + + DeletePlatformComments.call([current_comment]) + current_comment.destroy! redirect_to ref_url diff --git a/app/controllers/telegram_controller.rb b/app/controllers/telegram_controller.rb index a70f47f..6c46deb 100644 --- a/app/controllers/telegram_controller.rb +++ b/app/controllers/telegram_controller.rb @@ -73,7 +73,7 @@ def check_platform_comment(message) platform_post = nil - PlatformPost.where(platform: @telegram_platform).each do |p_post| + PlatformPost.where(channel: @channel_ids.keys).each do |p_post| if p_post[:identifier].is_a?(Array) # Post has attachments p_post[:identifier].each do |p| platform_post = p_post if p['message_id'] == post_message_id && p_post.platform_id == @telegram_platform.id @@ -112,7 +112,6 @@ def check_reply_comment(message, reply_message) p_post[:identifier].each do |p| platform_post = p_post if p['message_id'] == reply_message #&& p_post.platform_user.platform_id == @telegram_platform.id end - # Todo: Add platform id to comment elsif p_post[:identifier]['message_id'] == reply_message #&& p_post.platform_user.platform_id == @telegram_platform.id platform_post = p_post end @@ -149,59 +148,64 @@ def create_comment(message, platform_post) channel_id = @channel_ids.find { |k,v| v == message.dig('reply_to_message', 'sender_chat', 'id').to_s }&.first channel_id = @linked_group_channels_ids.find { |k,v| v == message.dig('reply_to_message', 'sender_chat', 'id') }&.first if channel_id.nil? if attachment.present? - identifier = { message_id: message['message_id'], - chat_id: message['chat']['id'], - file_id: attachment[:file_id], - file_size: attachment[:file_size] } - if attachment[:media_group_id].present? - Rails.logger.debug("MEDIA GROUP ID PRESENT... #{attachment[:media_group_id]}".green) if Rails.env.development? - # puts(attachment) - # Find comment by media_group_id and att attachment if found - media_comment = [] - Comment.where(platform: @telegram_platform).each do |comm| - if comm[:identifier].is_a?(Array) # Comment has attachments - comm[:identifier].each do |c| - media_comment = comm if c['media_group_id'] == attachment[:media_group_id].to_s - end - elsif comm[:identifier]['media_group_id'] == attachment[:media_group_id].to_s - media_comment = comm - end - end - Rails.logger.debug('MEDIA COMMENT CHECK...'.green) if Rails.env.development? - if media_comment.present? # Already exists, update comment... - media_array = [] - # P.S. Only first array element contains media_group_id - if media_comment.identifier.is_a?(Array) - media_comment.identifier.each { |media| media_array.append(media) } - else - media_array.append(media_comment.identifier) - end - media_array.append(identifier) - media_comment.identifier = media_array - media_comment.text = attachment[:caption] if attachment[:caption] != media_comment.text && attachment[:caption].present? - file = URI.parse(attachment[:link]).open - media_comment.attachments.attach(io: file, filename: attachment[:file_name], content_type: file.content_type) - media_comment.save! - return - else - identifier[:media_group_id] = attachment[:media_group_id].to_s - end - end - comment = Comment.create!(identifier: identifier, text: attachment[:caption], post: platform_post.post, - platform_user: user, has_attachments: true, channel_id: channel_id, current_user: 0, - platform: @telegram_platform) - file = URI.parse(attachment[:link]).open - comment.attachments.attach(io: file, filename: attachment[:file_name], content_type: file.content_type) - Rails.logger.debug('TG: COMMENT WITH ATTACHMENT ADDED!'.green) if Rails.env.development? + create_attachment_comment(message, attachment, user, platform_post, channel_id) else comment_text = message['text'] identifier = { message_id: message['message_id'], chat_id: message['chat']['id'] } Comment.create!(identifier: identifier, text: comment_text, post: platform_post.post, platform_user: user, - channel_id: channel_id, current_user: 0, platform: @telegram_platform) + channel_id: channel_id, current_user: 0, platform: @telegram_platform, + reply: (platform_post.is_a?(Comment) ? platform_post : nil)) Rails.logger.debug('TG: COMMENT ADDED!'.green) if Rails.env.development? end end + def create_attachment_comment(message, attachment, user, platform_post, channel_id) + identifier = { message_id: message['message_id'], + chat_id: message['chat']['id'], + file_id: attachment[:file_id], + file_size: attachment[:file_size] } + if attachment[:media_group_id].present? + Rails.logger.debug("MEDIA GROUP ID PRESENT... #{attachment[:media_group_id]}".green) if Rails.env.development? + # puts(attachment) + # Find comment by media_group_id and att attachment if found + media_comment = [] + Comment.where(platform: @telegram_platform).each do |comm| + if comm[:identifier].is_a?(Array) # Comment has attachments + comm[:identifier].each do |c| + media_comment = comm if c['media_group_id'] == attachment[:media_group_id].to_s + end + elsif comm[:identifier]['media_group_id'] == attachment[:media_group_id].to_s + media_comment = comm + end + end + Rails.logger.debug('MEDIA COMMENT CHECK...'.green) if Rails.env.development? + if media_comment.present? # Already exists, update comment... + media_array = [] + # P.S. Only first array element contains media_group_id + if media_comment.identifier.is_a?(Array) + media_comment.identifier.each { |media| media_array.append(media) } + else + media_array.append(media_comment.identifier) + end + media_array.append(identifier) + media_comment.identifier = media_array + media_comment.text = attachment[:caption] if attachment[:caption] != media_comment.text && attachment[:caption].present? + file = URI.parse(attachment[:link]).open + media_comment.attachments.attach(io: file, filename: attachment[:file_name], content_type: file.content_type) + media_comment.save! + return + else + identifier[:media_group_id] = attachment[:media_group_id].to_s + end + end + comment = Comment.create!(identifier: identifier, text: attachment[:caption], post: platform_post.post, + platform_user: user, has_attachments: true, channel_id: channel_id, current_user: 0, + platform: @telegram_platform, reply: (platform_post.is_a?(Comment) ? platform_post : nil)) + file = URI.parse(attachment[:link]).open + comment.attachments.attach(io: file, filename: attachment[:file_name], content_type: file.content_type) + Rails.logger.debug('TG: COMMENT WITH ATTACHMENT ADDED!'.green) if Rails.env.development? + end + def edit_comment(message, comment) check_user(message) attachment = check_attachments(message) diff --git a/app/models/comment.rb b/app/models/comment.rb index caa55dc..0a9c6f4 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -8,6 +8,9 @@ class Comment < ApplicationRecord belongs_to :platform, optional: true has_many_attached :attachments + has_many :replies, class_name: "Comment", foreign_key: "reply_id" + belongs_to :reply, class_name: "Comment", optional: true + validate :text_or_attachments thread_mattr_accessor :current_user diff --git a/app/services/delete_platform_comments.rb b/app/services/delete_platform_comments.rb new file mode 100644 index 0000000..c50d55c --- /dev/null +++ b/app/services/delete_platform_comments.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class DeletePlatformComments + prepend SimpleCommand + + attr_accessor :comments + + def initialize(comments) + @comments = comments + end + + def call + comments.group_by(&:platform).each do |pl, comms| + if pl.present? && pl.title == "telegram" + delete_comments("telegram", comms) + elsif pl.present? && pl.title == "matrix" + delete_comments("matrix", comms) + end + end + end + + def delete_comments(platform_title, comms) + Thread.new do + execution_context = Rails.application.executor.run! + if platform_title == "telegram" + Platform::DeleteTelegramComments.call(comms) + elsif platform_title == "matrix" + Platform::DeleteMatrixComments.call(comms) + end + ensure + execution_context&.complete! + end + end +end diff --git a/app/services/platform/delete_matrix_comments.rb b/app/services/platform/delete_matrix_comments.rb new file mode 100644 index 0000000..34d9830 --- /dev/null +++ b/app/services/platform/delete_matrix_comments.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class Platform::DeleteMatrixComments + prepend SimpleCommand + + attr_accessor :comms + + def initialize(comms) + @comms = comms + end + + def call + @comms.each do |comm| + matrix_token = comm.channel.token + server = comm.channel.options['server'] + begin + # Matrix onlylink is a Hash, but attachments is an Array. + if comm.has_attachments? && comm.identifier.is_a?(Hash) + comm.identifier.each do |att| + method = "rooms/#{att['room_id']}/redact/#{att['event_id']}" + data = { reason: "Delete comment ##{comm.id}" } + Matrix.post(server, matrix_token, method, data) + end + else + method = "rooms/#{comm.identifier['room_id']}/redact/#{comm.identifier['event_id']}" + data = { reason: "Delete comment ##{comm.id}" } + Matrix.post(server, matrix_token, method, data) + end + rescue StandardError + Rails.logger.error("Failed to delete matrix comments at #{Time.now.utc.iso8601}") + end + end + end +end diff --git a/app/services/platform/delete_telegram_comments.rb b/app/services/platform/delete_telegram_comments.rb new file mode 100644 index 0000000..4ef3f33 --- /dev/null +++ b/app/services/platform/delete_telegram_comments.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class Platform::DeleteTelegramComments + prepend SimpleCommand + + attr_accessor :comms + + def initialize(comms) + @comms = comms + end + + def call + @comms.each do |comm| + if comm.channel&.nil? # Comment from linked group + linked_channel_ids = comm.post.published_channels.map{ |ch| [ch.id, ch.options.dig("linked_chat_id")] }.to_h.compact_blank + channel_id = linked_channel_ids.find { |k,v| v == comm[:identifier].dig('reply_to_message', 'sender_chat', 'id') }&.first + channel = Channel.find(channel_id) + token = channel.token&.to_s + else + channel = comm.channel + token = channel.token&.to_s + end + bot = Twilight::Application::CURRENT_TG_BOTS&.dig(token, :client) + if comm.has_attachments? + comm.identifier.each do |att| + bot.delete_message({ chat_id: att['chat_id'], message_id: att['message_id'] }) + end + else + bot.delete_message({ chat_id: comm[:identifier]['chat_id'], + message_id: comm[:identifier]['message_id'] }) + end + rescue StandardError # Message don't delete (if bot don't have access to message) + Rails.logger.error("Failed to delete telegram comments at #{Time.now.utc.iso8601}") + end + end +end diff --git a/app/services/platform/send_comment_to_telegram.rb b/app/services/platform/send_comment_to_telegram.rb index 1152b93..1821601 100644 --- a/app/services/platform/send_comment_to_telegram.rb +++ b/app/services/platform/send_comment_to_telegram.rb @@ -63,7 +63,6 @@ def send_telegram_comment(channel) message_id: @msg['result']['message_id'] #date: @msg['result']['date'], } - # TODO: make platform user detecteble? Comment.create!(text: text, identifier: res, post: @current_post, user: @current_user, has_attachments: has_attachments, channel_id: channel[:id], current_user: @current_user, platform: @platform) end diff --git a/app/views/comments/_add.html.erb b/app/views/comments/_add.html.erb index dddf673..d3d61e3 100644 --- a/app/views/comments/_add.html.erb +++ b/app/views/comments/_add.html.erb @@ -23,6 +23,8 @@ <%= label_tag "channels_#{channel.id}", title&.capitalize %> <%end%> </div> + <%else%> + <br> <%end%> <%else%> <br> diff --git a/db/migrate/20240802095320_add_reply_id_to_comments.rb b/db/migrate/20240802095320_add_reply_id_to_comments.rb new file mode 100644 index 0000000..1d73e58 --- /dev/null +++ b/db/migrate/20240802095320_add_reply_id_to_comments.rb @@ -0,0 +1,8 @@ +class AddReplyIdToComments < ActiveRecord::Migration[7.0] + def up + add_reference :comments, :reply, index: true + end + def down + remove_reference :comments, :reply, index: true + end +end From 08ff1115778933c834b5d6e8f84aab9bc0970824 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sat, 3 Aug 2024 23:42:26 +0300 Subject: [PATCH 27/47] Comment tg update fixes --- app/assets/stylesheets/base.scss | 23 +++++++++++++ app/controllers/telegram_controller.rb | 34 +++++++++---------- app/models/comment.rb | 8 ++++- app/views/comments/_comment.erb | 24 +++++++++++++ app/views/comments/_comment_list.erb | 25 +------------- .../registrations/_channels_tab.html.erb | 2 +- 6 files changed, 73 insertions(+), 43 deletions(-) create mode 100644 app/views/comments/_comment.erb diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 5e3f2f2..e15295a 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -872,6 +872,29 @@ $color-black: #000000; justify-content: center; align-items: center; + float:left; /* Выравнивание по левому краю */ + margin: 0px 7px 0 0; /* Отступы вокруг картинки */ + position: static; + width: 75px; + height: 75px; + + background: $color-avatar-bg; + border: 1px solid $color-avatar-border; + box-sizing: border-box; + border-radius: 90px; + + img{ + height: 100%; + width: auto; + } + } + + .channel-avatar{ + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; + float:left; /* Выравнивание по левому краю */ margin: 7px 7px 0 0; /* Отступы вокруг картинки */ position: static; diff --git a/app/controllers/telegram_controller.rb b/app/controllers/telegram_controller.rb index 6c46deb..575666a 100644 --- a/app/controllers/telegram_controller.rb +++ b/app/controllers/telegram_controller.rb @@ -198,7 +198,8 @@ def create_attachment_comment(message, attachment, user, platform_post, channel_ identifier[:media_group_id] = attachment[:media_group_id].to_s end end - comment = Comment.create!(identifier: identifier, text: attachment[:caption], post: platform_post.post, + text = attachment[:caption].present? ? attachment[:caption] : "" + comment = Comment.create!(identifier: identifier, text: text, post: platform_post.post, platform_user: user, has_attachments: true, channel_id: channel_id, current_user: 0, platform: @telegram_platform, reply: (platform_post.is_a?(Comment) ? platform_post : nil)) file = URI.parse(attachment[:link]).open @@ -216,22 +217,21 @@ def edit_comment(message, comment) file_size: attachment[:file_size] } att_id = nil - comment[:identifier].each_with_index do |c, i| - if c.is_a?(Array) - c.each_with_index do |e, j| - next unless (e['message_id'] == message['message_id']) && (c['file_size'] != attachment[:file_size]) - - Rails.logger.debug('FOUND YA IN ARRAY!'.green) if Rails.env.development? - # delete(e) # Sync attachment deletion - # c.append(identifier) - e.clear - e.merge!(identifier) - att_id = j - end - elsif (c['message_id'] == message['message_id']) && (c['file_size'] != attachment[:file_size]) - Rails.logger.debug('FOUND YA IN HASH!'.green) if Rails.env.development? + if comment[:identifier].is_a?(Array) + comment[:identifier].each_with_index do |c, i| + next unless (c['message_id'] == message['message_id']) && (c['file_size'] != attachment[:file_size]) + Rails.logger.debug('FOUND YA IN ARRAY!'.green) if Rails.env.development? + # delete(e) # Sync attachment deletion + # c.append(identifier) c.clear c.merge!(identifier) + att_id = j + end + else + if (comment[:identifier]['message_id'] == message['message_id']) && (comment[:identifier]['file_size'] != attachment[:file_size]) + Rails.logger.debug('FOUND YA IN HASH!'.green) if Rails.env.development? + comment[:identifier].clear + comment[:identifier].merge!(identifier) att_id = i end end @@ -243,11 +243,11 @@ def edit_comment(message, comment) content_type: file.content_type) end comment_text = message['caption'] - comment.update!(text: comment_text, is_edited: true) + comment.update!(text: comment_text, is_edited: true, current_user: 0) Rails.logger.debug('TG: COMMENT WITH ATTACHMENTS UPDATED!'.green) if Rails.env.development? else comment_text = message['text'] - comment.update!(text: comment_text, is_edited: true) + comment.update!(text: comment_text, is_edited: true, current_user: 0) Rails.logger.debug('TG: COMMENT UPDATED!'.green) if Rails.env.development? end end diff --git a/app/models/comment.rb b/app/models/comment.rb index 0a9c6f4..5f34adc 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -6,7 +6,13 @@ class Comment < ApplicationRecord belongs_to :channel, optional: true # Optional if used linked channel belongs_to :platform_user, optional: true # Platform comment belongs_to :platform, optional: true - has_many_attached :attachments + has_many_attached :attachments do |attachable| + attachable.variant :thumb100, resize_to_limit: [100, 100] + attachable.variant :thumb150, resize_to_limit: [150, 150] + attachable.variant :thumb200, resize_to_limit: [200, 200] + attachable.variant :thumb250, resize_to_limit: [250, 250] + attachable.variant :thumb300, resize_to_limit: [300, 300] + end has_many :replies, class_name: "Comment", foreign_key: "reply_id" belongs_to :reply, class_name: "Comment", optional: true diff --git a/app/views/comments/_comment.erb b/app/views/comments/_comment.erb new file mode 100644 index 0000000..df2844b --- /dev/null +++ b/app/views/comments/_comment.erb @@ -0,0 +1,24 @@ +<% user = comm.platform_user || comm.user %> +<div class="comment"> + <div class="comment-avatar"><% if user.avatar.present?%><%= image_tag url_for(user.avatar) %><% end %></div> + <% name = display_comments_name(user) %> + <div class="comment-metadata"> + <%= name %> + <i class="comment-small">- <%= comm.created_at.strftime("%Y-%m-%d %H:%M") %> + <% if comm.is_edited %> + <i class="fa fa-pencil-square-o "></i> + <% end %> + <% if current_user.present? && current_user.is_a?(User) && comm.user.present? && (current_user == comm.user)%> + | <a href="/comments/<%=comm.id%>/edit"><%= I18n.t("comments.edit") %></a> + <% end %> + </i> + <% if current_user.present? && current_user.is_a?(User) && (current_user.is_admin? || (current_user.present? && current_user.is_a?(User) && comm.user.present? && (current_user == comm.user))) %> + <i class="comment-small right"><a href = "/comments/delete/<%=comm.id%>"><%= I18n.t("comments.delete") %></a></i> + <% end %> + </div> + <%= markdown(comm.text) %> + <% if comm.has_attachments %> + <hr style="border-top: dashed 2px;" /> + <%= display_comment_attachments(comm) if comm.attachments.any? %> + <% end%> +</div> \ No newline at end of file diff --git a/app/views/comments/_comment_list.erb b/app/views/comments/_comment_list.erb index d3c735a..0d5841f 100644 --- a/app/views/comments/_comment_list.erb +++ b/app/views/comments/_comment_list.erb @@ -6,30 +6,7 @@ <%next if ch.present? && !ch.enabled%> <div class="comment-header-medium"><%= ch.present? ? "#{ch.options["title"]} (#{ch.platform.title.capitalize})" : "Blog" %>:</div> <%comms.each do |comm|%> - <% user = comm.platform_user || comm.user %> - <div class="comment"> - <div class="comment-avatar"><% if user.avatar.present?%><%= image_tag url_for(user.avatar) %><% end %></div> - <% name = display_comments_name(user) %> - <div class="comment-metadata"> - <%= name %> - <i class="comment-small">- <%= comm.created_at.strftime("%Y-%m-%d %H:%M") %> - <% if comm.is_edited %> - <i class="fa fa-pencil-square-o "></i> - <% end %> - <% if current_user.present? && current_user.is_a?(User) && comm.user.present? && (current_user == comm.user)%> - | <a href="/comments/<%=comm.id%>/edit"><%= I18n.t("comments.edit") %></a> - <% end %> - </i> - <% if current_user.present? && current_user.is_a?(User) && (current_user.is_admin? || (current_user.present? && current_user.is_a?(User) && comm.user.present? && (current_user == comm.user))) %> - <i class="comment-small right"><a href = "/comments/delete/<%=comm.id%>"><%= I18n.t("comments.delete") %></a></i> - <% end %> - </div> - <%= markdown(comm.text) %> - <% if comm.has_attachments %> - <hr style="border-top: dashed 2px;" /> - <%= display_comment_attachments(comm) if comm.attachments.any? %> - <% end%> - </div> + <%= render partial: "comments/comment", locals: { comm: comm, current_user: current_user } %> <%end%> <%end%> <% end %> diff --git a/app/views/users/registrations/_channels_tab.html.erb b/app/views/users/registrations/_channels_tab.html.erb index 358d648..1d97184 100644 --- a/app/views/users/registrations/_channels_tab.html.erb +++ b/app/views/users/registrations/_channels_tab.html.erb @@ -5,7 +5,7 @@ <div class="comment-header"><%=I18n.t("channels.channels")%>:</div> <% current_user.channels.each do |channel| %> <div class="comment"> - <div class="comment-avatar"><% if channel&.avatar&.present?%><%= image_tag url_for(channel.avatar) %><% end %></div> + <div class="channel-avatar"><% if channel&.avatar&.present?%><%= image_tag url_for(channel.avatar) %><% end %></div> <% name = channel.options&.dig("title") %> <div class="comment-metadata"><%= name %> <i class="comment-small"> | <%= channel_url(channel) %> | <%= link_to I18n.t("channels.update"), edit_channel_url(channel) %> | <a href="/channels/delete/<%=channel.id%>" data-confirm="<%= I18n.t("channels.delete_channel_confirm") %>"><%= I18n.t("channels.delete") %></a></i><i class="comment-small right">ID: <%= channel.room %></i></div> <%=I18n.t("channels.status")%>: <%= channel.enabled ? I18n.t("channels.enabled") : I18n.t("channels.disabled") %><br> From 5bb422c4bb99b57d636c77ec6cf07ce55b60985c Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sun, 11 Aug 2024 18:12:08 +0300 Subject: [PATCH 28/47] Nested comments reply --- Gemfile | 1 + app/assets/stylesheets/base.scss | 9 ++++ app/controllers/comments_controller.rb | 46 +++++++++++++++---- app/controllers/telegram_controller.rb | 12 ++--- app/helpers/comments_helper.rb | 10 ++++ app/models/comment.rb | 7 +-- .../platform/send_comment_to_telegram.rb | 14 ++++-- app/views/comments/_add.html.erb | 24 +++++++--- app/views/comments/_comment.erb | 27 ++++++----- app/views/comments/_comment_list.erb | 6 +-- app/views/posts/_comments.erb | 2 +- config/locales/en.yml | 1 + config/locales/ru.yml | 1 + config/routes.rb | 1 + ...0240802095320_add_parent_id_to_comments.rb | 5 ++ ...20240802095320_add_reply_id_to_comments.rb | 8 ---- ...240804072619_create_comment_hierarchies.rb | 17 +++++++ 17 files changed, 138 insertions(+), 53 deletions(-) create mode 100644 app/helpers/comments_helper.rb create mode 100644 db/migrate/20240802095320_add_parent_id_to_comments.rb delete mode 100644 db/migrate/20240802095320_add_reply_id_to_comments.rb create mode 100644 db/migrate/20240804072619_create_comment_hierarchies.rb diff --git a/Gemfile b/Gemfile index d5a540c..b6e08c1 100644 --- a/Gemfile +++ b/Gemfile @@ -81,6 +81,7 @@ gem 'active_storage_validations' gem 'addressable', '>= 2.8.0' gem "ahoy_matey" gem 'betterlorem' +gem 'closure_tree' gem 'devise' gem 'dry-initializer-rails' gem 'easy_captcha', path: 'vendor/gems/easy_captcha' diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index e15295a..db6ac49 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -626,6 +626,10 @@ $color-black: #000000; color: $color-feed-font; background-color: $color-feed-bg; } + + .center{ + text-align: center; + } /* WTF */ .tooltip { @@ -955,6 +959,11 @@ $color-black: #000000; } } + /* comment replies */ + .replies { margin-left: 75px; } /* avatar size */ + /* 7 levels nesting */ + .replies .replies .replies .replies .replies .replies .replies {margin-left: 0;} + .user-avatar{ overflow: hidden; display: flex; diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index d13cb73..4cea894 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -1,20 +1,45 @@ # frozen_string_literal: true class CommentsController < ApplicationController + def new + @comment = Comment.new(parent_id: params[:parent_id]) + end + def create return redirect_to sign_in_path if current_user.nil? # No anonymous comments, sorry! - current_post = Post.find(params[:comment][:post]) + current_post = Post.find(params[:post][:uuid]) authorize! current_post, to: :create_comments? + ref_url = request.referrer channels = params['channels']&.to_unsafe_h.select{ |k, v| v.to_i == 1 } if params['channels'].present? - if params['channels'].present? && channels.any? - return set_flash_message :alert, "Not allowed!" unless allowed_to?(:create_platform_comments?, current_post) - SendCommentToPlatforms.call(params, channels, current_post, current_user) + + # Nested comments + if params[:comment][:parent_id].to_i > 0 + parent = current_post.comments.find_by_id(params[:comment][:parent_id]) + @comment = parent.children.build(comment_params) + if parent.channel.present? + channels = {"#{parent.channel.id}" => "1"} + SendCommentToPlatforms.call(params, channels, current_post, current_user) + else + @comment.parent = parent + @comment.post = current_post + @comment.user = current_user + @comment.save! + end else - current_comment = Comment.create!(text: params[:comment][:content], - user: current_user, post: current_post, current_user: current_user) + @comment = Comment.new(comment_params) + + if params['channels'].present? && channels.any? + return set_flash_message :alert, "Not allowed!" unless allowed_to?(:create_platform_comments?, current_post) + SendCommentToPlatforms.call(params, channels, current_post, current_user) + else + @comment.post = current_post + @comment.user = current_user + @comment.save! + end + end end @@ -26,7 +51,7 @@ def update authorize! current_comment ref_url = request.referrer - if current_comment.update(text: params[:comment][:content], is_edited: true, current_user: current_user) + if current_comment.update(text: params[:comment][:content], is_edited: true) if ref_url.include?("feed") redirect_to ref_url else @@ -43,7 +68,6 @@ def destroy # TODO: Delete from platforms post = current_comment.post - current_comment.current_user = current_user DeletePlatformComments.call([current_comment]) @@ -51,4 +75,10 @@ def destroy redirect_to ref_url end + + private + + def comment_params + params.require(:comment).permit(:text, :parent_id) + end end diff --git a/app/controllers/telegram_controller.rb b/app/controllers/telegram_controller.rb index 575666a..cde19a5 100644 --- a/app/controllers/telegram_controller.rb +++ b/app/controllers/telegram_controller.rb @@ -153,8 +153,8 @@ def create_comment(message, platform_post) comment_text = message['text'] identifier = { message_id: message['message_id'], chat_id: message['chat']['id'] } Comment.create!(identifier: identifier, text: comment_text, post: platform_post.post, platform_user: user, - channel_id: channel_id, current_user: 0, platform: @telegram_platform, - reply: (platform_post.is_a?(Comment) ? platform_post : nil)) + channel_id: channel_id, platform: @telegram_platform, + parent_id: (platform_post.is_a?(Comment) ? platform_post.id : nil)) Rails.logger.debug('TG: COMMENT ADDED!'.green) if Rails.env.development? end end @@ -200,8 +200,8 @@ def create_attachment_comment(message, attachment, user, platform_post, channel_ end text = attachment[:caption].present? ? attachment[:caption] : "" comment = Comment.create!(identifier: identifier, text: text, post: platform_post.post, - platform_user: user, has_attachments: true, channel_id: channel_id, current_user: 0, - platform: @telegram_platform, reply: (platform_post.is_a?(Comment) ? platform_post : nil)) + platform_user: user, has_attachments: true, channel_id: channel_id, + platform: @telegram_platform, parent_id: (platform_post.is_a?(Comment) ? platform_post.id : nil)) file = URI.parse(attachment[:link]).open comment.attachments.attach(io: file, filename: attachment[:file_name], content_type: file.content_type) Rails.logger.debug('TG: COMMENT WITH ATTACHMENT ADDED!'.green) if Rails.env.development? @@ -243,11 +243,11 @@ def edit_comment(message, comment) content_type: file.content_type) end comment_text = message['caption'] - comment.update!(text: comment_text, is_edited: true, current_user: 0) + comment.update!(text: comment_text, is_edited: true) Rails.logger.debug('TG: COMMENT WITH ATTACHMENTS UPDATED!'.green) if Rails.env.development? else comment_text = message['text'] - comment.update!(text: comment_text, is_edited: true, current_user: 0) + comment.update!(text: comment_text, is_edited: true) Rails.logger.debug('TG: COMMENT UPDATED!'.green) if Rails.env.development? end end diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb new file mode 100644 index 0000000..3cb1c67 --- /dev/null +++ b/app/helpers/comments_helper.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module CommentsHelper + def comments_tree_for(comments) + comments.map do |comment, nested_comments| + render(comment) + + (nested_comments.size > 0 ? content_tag(:div, comments_tree_for(nested_comments), class: "replies") : nil) + end.join.html_safe + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb index 5f34adc..8ae43c4 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -14,12 +14,9 @@ class Comment < ApplicationRecord attachable.variant :thumb300, resize_to_limit: [300, 300] end - has_many :replies, class_name: "Comment", foreign_key: "reply_id" - belongs_to :reply, class_name: "Comment", optional: true - validate :text_or_attachments - thread_mattr_accessor :current_user + acts_as_tree order: 'created_at ASC' after_create_commit do upd_comment end after_update_commit do upd_comment end @@ -48,6 +45,6 @@ def destroy end def upd_comment - broadcast_update_to [self.post], partial: 'comments/comment_list', locals: { post: self.post, current_user: self.current_user }, target: "comments_#{self.post.id}" + broadcast_update_to [self.post], partial: 'comments/comment_list', locals: { post: self.post }, target: "comments_#{self.post.id}" end end diff --git a/app/services/platform/send_comment_to_telegram.rb b/app/services/platform/send_comment_to_telegram.rb index 1821601..8c89a4e 100644 --- a/app/services/platform/send_comment_to_telegram.rb +++ b/app/services/platform/send_comment_to_telegram.rb @@ -39,7 +39,7 @@ def call def send_telegram_comment(channel) bot = get_tg_bot(channel) - text = @params[:comment][:content] + text = @params[:comment][:text] has_attachments = @params.dig(:comment, :attachments) @@ -51,20 +51,28 @@ def send_telegram_comment(channel) return Rails.logger.error("Can't get linked group chat_id or linked message_id for channel #{channel[:id]} at #{Time.now.utc.iso8601}!") end + if @params[:comment][:parent_id].to_i > 0 + parent = current_post.comments.find_by_id(@params[:comment].delete(:parent_id)) + linked_chat_id = parent.identifier["chat_id"] + message_id = parent.identifier["message_id"] + parent_id = parent.id + end + if has_attachments.present? && !has_attachments.empty? send_telegram_attachments(bot, channel) else @msg = bot.send_message({ chat_id: linked_chat_id, text: text, reply_to_message_id: message_id, - parse_mode: 'html'}) + parse_mode: 'html', + parent_id: parent_id.present? ? parent_id : nil}) res = { chat_id: @msg['result']['chat']['id'], message_id: @msg['result']['message_id'] #date: @msg['result']['date'], } Comment.create!(text: text, identifier: res, post: @current_post, user: @current_user, has_attachments: has_attachments, - channel_id: channel[:id], current_user: @current_user, platform: @platform) + channel_id: channel[:id], platform: @platform) end rescue StandardError => e Rails.logger.error("Failed create telegram comment for chat #{channel[:id]} at #{Time.now.utc.iso8601}:\n#{e}") diff --git a/app/views/comments/_add.html.erb b/app/views/comments/_add.html.erb index d3d61e3..6e8d717 100644 --- a/app/views/comments/_add.html.erb +++ b/app/views/comments/_add.html.erb @@ -1,20 +1,30 @@ +<%if parent_id.present?%> + <%number = parent_id%> +<%else%> + <%number = 0%> +<%end%> <div id="accordion" role="tablist" aria-multiselectable="true"> <div class="card"> + <%unless parent_id.present?%> <div class="card-header" role="tab" id="headingTwo"> - <a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#add" aria-expanded="false" aria-controls="add"> + <a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#add-<%=number%>" aria-expanded="false" aria-controls="add"> <%=I18n.t("comments.add")%> </a> </div> - <div id="add" class="collapse" role="tabpanel" aria-labelledby="headingTwo"> + <%end%> + <div id="add-<%=number%>" class="collapse" role="tabpanel" aria-labelledby="headingTwo"> <div class="card-block"> <%= form_with(model: Comment, :method => 'put', :remote => true) do |f| %> - <%= hidden_field(:comment, :post, { value: @current_post.uuid }) %> + <%if parent_id.present?%> + <%= hidden_field(:comment, :parent_id, { value: parent_id }) %> + <%end%> + <%= hidden_field(:post, :uuid, { value: post.uuid }) %> <div id="markdown" class="tab-pane in active title"> <div class="comment-editor"> - <%= f.text_area :content, { id: "comments_content", placeholder: "#{I18n.t("posts.text_placeholder")}" } %> + <%= f.text_area :text, { id: "comments_content", placeholder: "#{I18n.t("posts.text_placeholder")}" } %> </div> - <%if allowed_to?(:create_platform_comments?, @current_post)%> - <%telegram_channels = @current_post.published_channels.where(platform: Platform.find_by(title: "telegram"))%> + <%if parent_id.nil? && allowed_to?(:create_platform_comments?, post)%> + <%telegram_channels = post.published_channels.where(platform: Platform.find_by(title: "telegram"))%> <%if telegram_channels.any?%> <div class="colorful-checkboxes"> <%telegram_channels.each do |channel|%> @@ -29,7 +39,7 @@ <%else%> <br> <%end%> - <p><%= f.submit I18n.t("channels.add")%><br></p> + <p class="center"><%= f.submit I18n.t("channels.add")%><br></p> </div> <%end%> </div> diff --git a/app/views/comments/_comment.erb b/app/views/comments/_comment.erb index df2844b..20c2bff 100644 --- a/app/views/comments/_comment.erb +++ b/app/views/comments/_comment.erb @@ -1,24 +1,29 @@ -<% user = comm.platform_user || comm.user %> +<% user = comment.platform_user || comment.user %> <div class="comment"> <div class="comment-avatar"><% if user.avatar.present?%><%= image_tag url_for(user.avatar) %><% end %></div> <% name = display_comments_name(user) %> <div class="comment-metadata"> <%= name %> - <i class="comment-small">- <%= comm.created_at.strftime("%Y-%m-%d %H:%M") %> - <% if comm.is_edited %> + <i class="comment-small">- <%= comment.created_at.strftime("%Y-%m-%d %H:%M") %> + <% if comment.is_edited %> <i class="fa fa-pencil-square-o "></i> <% end %> - <% if current_user.present? && current_user.is_a?(User) && comm.user.present? && (current_user == comm.user)%> - | <a href="/comments/<%=comm.id%>/edit"><%= I18n.t("comments.edit") %></a> + <% if current_user.present? && comment.user.present? && (current_user == comment.user)%> + | <a href="/comments/<%=comment.id%>/edit"><%= I18n.t("comments.edit") %></a> <% end %> </i> - <% if current_user.present? && current_user.is_a?(User) && (current_user.is_admin? || (current_user.present? && current_user.is_a?(User) && comm.user.present? && (current_user == comm.user))) %> - <i class="comment-small right"><a href = "/comments/delete/<%=comm.id%>"><%= I18n.t("comments.delete") %></a></i> + <% if current_user.present? && (current_user.is_admin? || (current_user.present? && comment.user.present? && (current_user == comment.user))) %> + <i class="comment-small right"><a href = "/comments/delete/<%=comment.id%>"><%= I18n.t("comments.delete") %></a></i> <% end %> + <% if current_user.present? %> + <a class="comment-small right collapsed" data-toggle="collapse" data-parent="#accordion" href="#add-<%=comment.id%>" aria-expanded="false" aria-controls="add"><%=I18n.t("comments.reply")%></a> + <% end%> </div> - <%= markdown(comm.text) %> - <% if comm.has_attachments %> + <%= markdown(comment.text) %> + <% if comment.has_attachments %> <hr style="border-top: dashed 2px;" /> - <%= display_comment_attachments(comm) if comm.attachments.any? %> + <%= display_comment_attachments(comment) if comment.attachments.any? %> <% end%> -</div> \ No newline at end of file +</div> + +<p><%= render partial: 'comments/add', locals: { post: comment.post, parent_id: comment.id } %></p> \ No newline at end of file diff --git a/app/views/comments/_comment_list.erb b/app/views/comments/_comment_list.erb index 0d5841f..f9a0281 100644 --- a/app/views/comments/_comment_list.erb +++ b/app/views/comments/_comment_list.erb @@ -2,11 +2,9 @@ <center><%= I18n.t("comments.comments_not_found") %></center> <% else %> <div class="comment-header"><%= I18n.t("comments.comment_header") %>:</div> - <%post.comments.order("created_at ASC").group_by(&:channel).each do |ch, comms|%> + <%post.comments.group_by(&:channel).each do |ch, _comms|%> <%next if ch.present? && !ch.enabled%> <div class="comment-header-medium"><%= ch.present? ? "#{ch.options["title"]} (#{ch.platform.title.capitalize})" : "Blog" %>:</div> - <%comms.each do |comm|%> - <%= render partial: "comments/comment", locals: { comm: comm, current_user: current_user } %> - <%end%> + <%= comments_tree_for post.comments.where(channel: ch).order("created_at ASC").hash_tree %> <%end%> <% end %> diff --git a/app/views/posts/_comments.erb b/app/views/posts/_comments.erb index 0394339..597b100 100644 --- a/app/views/posts/_comments.erb +++ b/app/views/posts/_comments.erb @@ -1,8 +1,8 @@ <br> <div class="comments-container comments-width"> - <center><%= render "comments/add" if current_user.present? %></center> <%= turbo_stream_from @current_post %> <div id="<%="comments_#{@current_post.id}"%>"> <%= render partial: "comments/comment_list", locals: { post: @current_post, current_user: current_user } %> </div> + <center><%= render partial: 'comments/add', locals: { post: @current_post, parent_id: nil } if current_user.present? %></center> </div> \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 3cc139f..2b49865 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -194,6 +194,7 @@ en: blog: Blog telegram: Telegram add: Add new comment + reply: Reply edit: Edit delete: Delete comment feeds: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index c2430d5..6ab8ffd 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -194,6 +194,7 @@ ru: blog: Блог telegram: Telegram add: Добавить комментарий + reply: Ответить edit: Редактировать delete: Удалить feeds: diff --git a/config/routes.rb b/config/routes.rb index 5772459..a21b7a7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -31,6 +31,7 @@ put '/comments', to: 'comments#create', as: :create_comments_path resources :comments, only: %i[create edit update] + post '/comments/new/(:parent_id)', to: 'comments#new', as: :new_comment get 'comments/delete/:id', to: 'comments#destroy', as: :comment_path resources :invite_codes, only: [:create] diff --git a/db/migrate/20240802095320_add_parent_id_to_comments.rb b/db/migrate/20240802095320_add_parent_id_to_comments.rb new file mode 100644 index 0000000..9e37d4d --- /dev/null +++ b/db/migrate/20240802095320_add_parent_id_to_comments.rb @@ -0,0 +1,5 @@ +class AddParentIdToComments < ActiveRecord::Migration[7.0] + def change + add_column :comments, :parent_id, :bigint + end +end diff --git a/db/migrate/20240802095320_add_reply_id_to_comments.rb b/db/migrate/20240802095320_add_reply_id_to_comments.rb deleted file mode 100644 index 1d73e58..0000000 --- a/db/migrate/20240802095320_add_reply_id_to_comments.rb +++ /dev/null @@ -1,8 +0,0 @@ -class AddReplyIdToComments < ActiveRecord::Migration[7.0] - def up - add_reference :comments, :reply, index: true - end - def down - remove_reference :comments, :reply, index: true - end -end diff --git a/db/migrate/20240804072619_create_comment_hierarchies.rb b/db/migrate/20240804072619_create_comment_hierarchies.rb new file mode 100644 index 0000000..8217310 --- /dev/null +++ b/db/migrate/20240804072619_create_comment_hierarchies.rb @@ -0,0 +1,17 @@ +class CreateCommentHierarchies < ActiveRecord::Migration[7.0] + def change + create_table :comment_hierarchies, :id => false do |t| + t.integer :ancestor_id, :null => false # ID of the parent/grandparent/great-grandparent/... comments + t.integer :descendant_id, :null => false # ID of the target comment + t.integer :generations, :null => false # Number of generations between the ancestor and the descendant. Parent/child = 1, for example. + end + + # For "all progeny of…" and leaf selects: + add_index :comment_hierarchies, [:ancestor_id, :descendant_id, :generations], + :unique => true, :name => "comment_anc_desc_udx" + + # For "all ancestors of…" selects, + add_index :comment_hierarchies, [:descendant_id], + :name => "comment_desc_idx" + end +end From e6f108481f373c946a29070fc6556815f00bdbb8 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sun, 11 Aug 2024 21:51:17 +0300 Subject: [PATCH 29/47] Comments fixes --- app/controllers/comments_controller.rb | 36 ++++++++++++++++--- app/controllers/telegram_controller.rb | 5 +-- app/helpers/comments_helper.rb | 6 ++-- app/models/comment.rb | 8 ----- app/policies/comment_policy.rb | 8 +++-- .../platform/send_comment_to_telegram.rb | 7 ++-- app/views/comments/_add.html.erb | 8 ++--- app/views/comments/_comment.erb | 10 +++--- app/views/comments/_comment_list.erb | 2 +- app/views/posts/_comments.erb | 2 +- 10 files changed, 56 insertions(+), 36 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 4cea894..4cbf510 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -6,7 +6,10 @@ def new end def create - return redirect_to sign_in_path if current_user.nil? # No anonymous comments, sorry! + if current_user.nil? # No anonymous comments, sorry! + set_flash_message :alert, "Not allowed!" + return + end current_post = Post.find(params[:post][:uuid]) authorize! current_post, to: :create_comments? @@ -41,6 +44,16 @@ def create end end + + respond_to do |format| + if @comment.save + @comment.broadcast_update_to @comment.post, partial: 'comments/comment_list', target: "comments_#{@comment.post.uuid}", locals: {post: current_post, current_user: current_user} + format.turbo_stream + else # Create comments for platforms + redirect_to ref_url + # format.html { head :unprocessable_entity } + end + end end def edit @@ -66,12 +79,25 @@ def destroy authorize! current_comment ref_url = request.referrer - # TODO: Delete from platforms - post = current_comment.post + @comment = current_comment + post = @comment.post + + DeletePlatformComments.call([@comment]) - DeletePlatformComments.call([current_comment]) + if @comment.children.any? && @comment.channel.present? + @comment.update!(text: "<#DELETED>", identifier: {"is_deleted": true}) + else + @comment.destroy! + end - current_comment.destroy! + #respond_to do |format| + # if @comment.destroyed? + # @comment.broadcast_replace_to @comment.post, partial: 'comments/comment_list', target: "comments_#{@comment.post.uuid}", locals: {post: current_post, current_user: current_user} + # format.turbo_stream + # else + # format.html { head :unprocessable_entity } + # end + #end redirect_to ref_url end diff --git a/app/controllers/telegram_controller.rb b/app/controllers/telegram_controller.rb index cde19a5..4d631a3 100644 --- a/app/controllers/telegram_controller.rb +++ b/app/controllers/telegram_controller.rb @@ -145,8 +145,9 @@ def create_comment(message, platform_post) user = check_user(message) attachment = check_attachments(message) - channel_id = @channel_ids.find { |k,v| v == message.dig('reply_to_message', 'sender_chat', 'id').to_s }&.first - channel_id = @linked_group_channels_ids.find { |k,v| v == message.dig('reply_to_message', 'sender_chat', 'id') }&.first if channel_id.nil? + channel_id = @channel_ids.find { |k,v| v == message.dig('reply_to_message', 'chat', 'id').to_s }&.first # old: sender_chat + channel_id = @linked_group_channels_ids.find { |k,v| v == message.dig('reply_to_message', 'chat', 'id') }&.first if channel_id.nil? # old: sender_chat + if attachment.present? create_attachment_comment(message, attachment, user, platform_post, channel_id) else diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index 3cb1c67..6d5eb0d 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module CommentsHelper - def comments_tree_for(comments) + def comments_tree_for(comments, current_user) comments.map do |comment, nested_comments| - render(comment) + - (nested_comments.size > 0 ? content_tag(:div, comments_tree_for(nested_comments), class: "replies") : nil) + render(partial: "comments/comment", locals: {comment: comment, current_user: current_user}) + + (nested_comments.size > 0 ? content_tag(:div, comments_tree_for(nested_comments, current_user), class: "replies") : nil) end.join.html_safe end end diff --git a/app/models/comment.rb b/app/models/comment.rb index 8ae43c4..338d51b 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -18,10 +18,6 @@ class Comment < ApplicationRecord acts_as_tree order: 'created_at ASC' - after_create_commit do upd_comment end - after_update_commit do upd_comment end - after_destroy_commit do upd_comment end - def text_or_attachments return unless text.empty? && !has_attachments @@ -43,8 +39,4 @@ def destroy attachments.purge super end - - def upd_comment - broadcast_update_to [self.post], partial: 'comments/comment_list', locals: { post: self.post }, target: "comments_#{self.post.id}" - end end diff --git a/app/policies/comment_policy.rb b/app/policies/comment_policy.rb index 715a4a2..6b4f7aa 100644 --- a/app/policies/comment_policy.rb +++ b/app/policies/comment_policy.rb @@ -2,10 +2,14 @@ class CommentPolicy < ApplicationPolicy def update? - (record.user == user) || user&.is_admin? + !record.identifier&.has_key?("is_deleted") && ((record.user == user) || user&.is_admin?) end def destroy? - (record.user == user) || user&.is_admin? + !record.identifier&.has_key?("is_deleted") && ((record.user == user) || user&.is_admin?) + end + + def comment? + user&.present? && !record.identifier&.has_key?("is_deleted") end end diff --git a/app/services/platform/send_comment_to_telegram.rb b/app/services/platform/send_comment_to_telegram.rb index 8c89a4e..5deffc1 100644 --- a/app/services/platform/send_comment_to_telegram.rb +++ b/app/services/platform/send_comment_to_telegram.rb @@ -64,15 +64,14 @@ def send_telegram_comment(channel) @msg = bot.send_message({ chat_id: linked_chat_id, text: text, reply_to_message_id: message_id, - parse_mode: 'html', - parent_id: parent_id.present? ? parent_id : nil}) + parse_mode: 'html'}) res = { chat_id: @msg['result']['chat']['id'], message_id: @msg['result']['message_id'] #date: @msg['result']['date'], } - Comment.create!(text: text, identifier: res, post: @current_post, user: @current_user, has_attachments: has_attachments, - channel_id: channel[:id], platform: @platform) + + Comment.create!(text: text, identifier: res, post: @current_post, user: @current_user, has_attachments: has_attachments, channel_id: channel[:id], platform: @platform, parent_id: parent_id.present? ? parent_id : nil) end rescue StandardError => e Rails.logger.error("Failed create telegram comment for chat #{channel[:id]} at #{Time.now.utc.iso8601}:\n#{e}") diff --git a/app/views/comments/_add.html.erb b/app/views/comments/_add.html.erb index 6e8d717..fc4ce06 100644 --- a/app/views/comments/_add.html.erb +++ b/app/views/comments/_add.html.erb @@ -1,11 +1,7 @@ -<%if parent_id.present?%> - <%number = parent_id%> -<%else%> - <%number = 0%> -<%end%> +<%number = parent_id.present? ? parent_id : 0%> <div id="accordion" role="tablist" aria-multiselectable="true"> <div class="card"> - <%unless parent_id.present?%> + <%if !parent_id.present? && current_user.present? %> <div class="card-header" role="tab" id="headingTwo"> <a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#add-<%=number%>" aria-expanded="false" aria-controls="add"> <%=I18n.t("comments.add")%> diff --git a/app/views/comments/_comment.erb b/app/views/comments/_comment.erb index 20c2bff..7a3d710 100644 --- a/app/views/comments/_comment.erb +++ b/app/views/comments/_comment.erb @@ -8,14 +8,14 @@ <% if comment.is_edited %> <i class="fa fa-pencil-square-o "></i> <% end %> - <% if current_user.present? && comment.user.present? && (current_user == comment.user)%> + <% if allowed_to?(:update?, comment)%> | <a href="/comments/<%=comment.id%>/edit"><%= I18n.t("comments.edit") %></a> <% end %> </i> - <% if current_user.present? && (current_user.is_admin? || (current_user.present? && comment.user.present? && (current_user == comment.user))) %> + <% if allowed_to?(:destroy?, comment) %> <i class="comment-small right"><a href = "/comments/delete/<%=comment.id%>"><%= I18n.t("comments.delete") %></a></i> <% end %> - <% if current_user.present? %> + <% if allowed_to?(:comment?, comment) %> <a class="comment-small right collapsed" data-toggle="collapse" data-parent="#accordion" href="#add-<%=comment.id%>" aria-expanded="false" aria-controls="add"><%=I18n.t("comments.reply")%></a> <% end%> </div> @@ -26,4 +26,6 @@ <% end%> </div> -<p><%= render partial: 'comments/add', locals: { post: comment.post, parent_id: comment.id } %></p> \ No newline at end of file +<% if allowed_to?(:comment?, comment) %> + <p><%= render partial: 'comments/add', locals: { post: comment.post, parent_id: comment.id } %></p> +<% end%> \ No newline at end of file diff --git a/app/views/comments/_comment_list.erb b/app/views/comments/_comment_list.erb index f9a0281..891de7e 100644 --- a/app/views/comments/_comment_list.erb +++ b/app/views/comments/_comment_list.erb @@ -5,6 +5,6 @@ <%post.comments.group_by(&:channel).each do |ch, _comms|%> <%next if ch.present? && !ch.enabled%> <div class="comment-header-medium"><%= ch.present? ? "#{ch.options["title"]} (#{ch.platform.title.capitalize})" : "Blog" %>:</div> - <%= comments_tree_for post.comments.where(channel: ch).order("created_at ASC").hash_tree %> + <%= comments_tree_for(post.comments.where(channel: ch).order("created_at ASC").hash_tree, current_user) %> <%end%> <% end %> diff --git a/app/views/posts/_comments.erb b/app/views/posts/_comments.erb index 597b100..ab1a6be 100644 --- a/app/views/posts/_comments.erb +++ b/app/views/posts/_comments.erb @@ -1,7 +1,7 @@ <br> <div class="comments-container comments-width"> <%= turbo_stream_from @current_post %> - <div id="<%="comments_#{@current_post.id}"%>"> + <div id="<%="comments_#{@current_post.uuid}"%>"> <%= render partial: "comments/comment_list", locals: { post: @current_post, current_user: current_user } %> </div> <center><%= render partial: 'comments/add', locals: { post: @current_post, parent_id: nil } if current_user.present? %></center> From f3661cb2033bbda7e32c073a8b2be6f681dee7a1 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sun, 11 Aug 2024 23:27:19 +0300 Subject: [PATCH 30/47] Stylesheet improvements --- app/assets/stylesheets/base.scss | 46 +++++++++++++++++++++++++++++--- app/helpers/comments_helper.rb | 21 +++++++++++++++ app/views/comments/_add.html.erb | 12 ++++----- app/views/comments/_comment.erb | 21 ++++++++------- 4 files changed, 81 insertions(+), 19 deletions(-) diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index db6ac49..fe02366 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -5,6 +5,9 @@ @import "dropzone"; @import "fonts"; +$small: 400px; +$medium: 1000px; + /* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. @@ -833,6 +836,10 @@ $color-black: #000000; font-style: normal; font-weight: normal; + @media screen and (max-width: $medium) { + font-size: 14px; + } + .comment-editor{ padding-top: 20px; display: flex; @@ -853,16 +860,22 @@ $color-black: #000000; .comment-header{ font-size: 25px; + @media screen and (max-width: $medium) { + font-size: 21px; + } } .comment-header-medium{ font-size: 20px; + @media screen and (max-width: $medium) { + font-size: 17px; + } } .comment{ position: relative; margin-top: 0.5%; - margin-bottom: 2%; + margin-bottom: 0.5%; .wrapper { width: 300px; @@ -891,6 +904,11 @@ $color-black: #000000; height: 100%; width: auto; } + + @media screen and (max-width: $medium) { + width: 35px; + height: 35px; + } } .channel-avatar{ @@ -922,12 +940,28 @@ $color-black: #000000; font-size: 20px; margin-bottom: 10px; - .comment-small{ font-size: 16px; } + @media screen and (max-width: $medium) { + font-size: 17px; + } + + .comment-small{ + font-size: 16px; + @media screen and (max-width: $medium) { + font-size: 13px; + } + } .right{ float: right; margin-right: 1%; } } + .comment-text p{ + font-size: 16px; + @media screen and (max-width: $medium) { + font-size: 14px; + margin: 12px 0px 0px 12px; /* WTF bypass */ + } + } } .width-table { @@ -960,7 +994,13 @@ $color-black: #000000; } /* comment replies */ - .replies { margin-left: 75px; } /* avatar size */ + .replies { + display: grid; + margin-left: 75px; /* avatar size */ + @media screen and (max-width: $medium) { + margin-left: 35px; /* avatar size */ + } + } /* 7 levels nesting */ .replies .replies .replies .replies .replies .replies .replies {margin-left: 0;} diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index 6d5eb0d..54e1175 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -7,4 +7,25 @@ def comments_tree_for(comments, current_user) (nested_comments.size > 0 ? content_tag(:div, comments_tree_for(nested_comments, current_user), class: "replies") : nil) end.join.html_safe end + + def allow_update(comment, current_user) + return false if !current_user.present? || comment.identifier&.has_key?("is_deleted") + return true if current_user.is_admin + + comment.user.present? && (comment.user == current_user) + end + + def allow_destroy(comment, current_user) + return false if !current_user.present? || comment.identifier&.has_key?("is_deleted") + return true if current_user.is_admin + + comment.user.present? && (comment.user == current_user) + end + + def allow_comment(comment, current_user) + return false if !current_user.present? || comment.identifier&.has_key?("is_deleted") + return true if current_user.is_admin + + true + end end diff --git a/app/views/comments/_add.html.erb b/app/views/comments/_add.html.erb index fc4ce06..a1209b0 100644 --- a/app/views/comments/_add.html.erb +++ b/app/views/comments/_add.html.erb @@ -1,12 +1,12 @@ <%number = parent_id.present? ? parent_id : 0%> -<div id="accordion" role="tablist" aria-multiselectable="true"> +<div id="accordion-<%=number%>" role="tablist" aria-multiselectable="true"> <div class="card"> <%if !parent_id.present? && current_user.present? %> - <div class="card-header" role="tab" id="headingTwo"> - <a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#add-<%=number%>" aria-expanded="false" aria-controls="add"> - <%=I18n.t("comments.add")%> - </a> - </div> + <div class="card-header" role="tab" id="headingTwo"> + <a class="collapsed" data-toggle="collapse" data-parent="#accordion-<%=number%>" href="#add-<%=number%>" aria-expanded="false" aria-controls="add"> + <%=I18n.t("comments.add")%> + </a> + </div> <%end%> <div id="add-<%=number%>" class="collapse" role="tabpanel" aria-labelledby="headingTwo"> <div class="card-block"> diff --git a/app/views/comments/_comment.erb b/app/views/comments/_comment.erb index 7a3d710..eb01bfc 100644 --- a/app/views/comments/_comment.erb +++ b/app/views/comments/_comment.erb @@ -1,6 +1,6 @@ <% user = comment.platform_user || comment.user %> <div class="comment"> - <div class="comment-avatar"><% if user.avatar.present?%><%= image_tag url_for(user.avatar) %><% end %></div> + <div class="comment-avatar"><% if user.avatar.present?%><%= image_tag url_for(user.avatar) %><% end %></div> <% name = display_comments_name(user) %> <div class="comment-metadata"> <%= name %> @@ -8,24 +8,25 @@ <% if comment.is_edited %> <i class="fa fa-pencil-square-o "></i> <% end %> - <% if allowed_to?(:update?, comment)%> + <% # https://github.com/palkan/action_policy/discussions/254 # %> + <% if current_user.present? && allow_update(comment, current_user)%> | <a href="/comments/<%=comment.id%>/edit"><%= I18n.t("comments.edit") %></a> <% end %> </i> - <% if allowed_to?(:destroy?, comment) %> - <i class="comment-small right"><a href = "/comments/delete/<%=comment.id%>"><%= I18n.t("comments.delete") %></a></i> + <% if current_user.present? && allow_destroy(comment, current_user) %> + <i class="comment-small right"><a href = "/comments/delete/<%=comment.id%>"><%= I18n.t("comments.delete") %></a></i> <% end %> - <% if allowed_to?(:comment?, comment) %> - <a class="comment-small right collapsed" data-toggle="collapse" data-parent="#accordion" href="#add-<%=comment.id%>" aria-expanded="false" aria-controls="add"><%=I18n.t("comments.reply")%></a> + <% if current_user.present? && allow_comment(comment, current_user) %> + <a class="comment-small right collapsed" data-toggle="collapse" data-parent="#accordion" href="#add-<%=comment.id%>" aria-expanded="false" aria-controls="add"><%=I18n.t("comments.reply")%></a> <% end%> </div> - <%= markdown(comment.text) %> + <div class="comment-text"><%= markdown(comment.text) %></div> <% if comment.has_attachments %> <hr style="border-top: dashed 2px;" /> <%= display_comment_attachments(comment) if comment.attachments.any? %> <% end%> </div> -<% if allowed_to?(:comment?, comment) %> - <p><%= render partial: 'comments/add', locals: { post: comment.post, parent_id: comment.id } %></p> -<% end%> \ No newline at end of file +<% if current_user.present? && allow_comment(comment, current_user) %> + <%= render partial: 'comments/add', locals: { post: comment.post, parent_id: comment.id } if current_user.present? %><br> +<% end%> From 0d4c348fa2c4ff1d174e5450549b8bc95ec0e449 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 15 Aug 2024 09:50:26 +0300 Subject: [PATCH 31/47] Fixes && improvements --- app/assets/stylesheets/base.scss | 2 +- app/controllers/comments_controller.rb | 4 ++++ app/models/user.rb | 9 +++++++++ app/services/check_channel.rb | 9 ++++++--- app/services/platform/send_comment_to_telegram.rb | 8 +++++++- app/views/channels/edit.html.erb | 4 ---- app/views/channels/new.html.erb | 4 ---- app/views/comments/_comment_list.erb | 2 +- app/views/users/registrations/_channels_tab.html.erb | 2 +- config/locales/en.yml | 1 + config/locales/ru.yml | 1 + 11 files changed, 31 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index fe02366..97d5257 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -825,7 +825,7 @@ $color-black: #000000; .comments-container{ font-size: 16px; background-color: $color-container-bg; - color: $font; + color: $color-feed-font; box-sizing: border-box; /* max-width: 60%; */ margin: 2% auto; diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 4cbf510..0d5c0c3 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -45,6 +45,10 @@ def create end + if ref_url.include?("feed") + return redirect_to ref_url + end + respond_to do |format| if @comment.save @comment.broadcast_update_to @comment.post, partial: 'comments/comment_list', target: "comments_#{@comment.post.uuid}", locals: {post: current_post, current_user: current_user} diff --git a/app/models/user.rb b/app/models/user.rb index 4ecd437..a735e94 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -107,6 +107,15 @@ def verify_password_and_update(params) result end + def displayed_name + if name.present? + name + else + login + end + end + + def destroy avatar.purge ItemTag.where(item_type: "User", item: self).delete_all diff --git a/app/services/check_channel.rb b/app/services/check_channel.rb index 5237136..a52e995 100644 --- a/app/services/check_channel.rb +++ b/app/services/check_channel.rb @@ -70,10 +70,11 @@ def check_telegram options[:notifications_enabled] = (params[:channel][:enable_notifications] == '1') - comments_enabled = (params[:channel][:enable_comments] == '1') - options[:comments_enabled] = comments_enabled + # Comments - if comments_enabled + comment_chat_id = chat.dig('result', 'linked_chat_id') + options[:comments_enabled] = comment_chat_id.present? + if comment_chat_id.present? comment_chat_id = chat.dig('result', 'linked_chat_id') begin @@ -95,6 +96,8 @@ def check_telegram options[:linked_chat_id] = comment_chat_id end + # Other + options[:title] = chat['result']['title'] options[:username] = chat['result']['username'] options[:invite_link] = chat['result']['invite_link'] diff --git a/app/services/platform/send_comment_to_telegram.rb b/app/services/platform/send_comment_to_telegram.rb index 5deffc1..fa615d8 100644 --- a/app/services/platform/send_comment_to_telegram.rb +++ b/app/services/platform/send_comment_to_telegram.rb @@ -18,6 +18,7 @@ def initialize(params, channel_ids, current_post, current_user) { id: channel.id, room: channel.room, token: channel.token, + user_id: channel.user_id, room_attachments: channel.options['room_attachments'], linked_chat_id: channel.options.dig('linked_chat_id'), } @@ -40,6 +41,11 @@ def send_telegram_comment(channel) bot = get_tg_bot(channel) text = @params[:comment][:text] + send_text = text + + if @current_user&.id != channel[:user_id] + send_text = "#{@current_user.displayed_name}:\n#{text}" + end has_attachments = @params.dig(:comment, :attachments) @@ -62,7 +68,7 @@ def send_telegram_comment(channel) send_telegram_attachments(bot, channel) else @msg = bot.send_message({ chat_id: linked_chat_id, - text: text, + text: send_text, reply_to_message_id: message_id, parse_mode: 'html'}) diff --git a/app/views/channels/edit.html.erb b/app/views/channels/edit.html.erb index 6d9ee55..8742052 100644 --- a/app/views/channels/edit.html.erb +++ b/app/views/channels/edit.html.erb @@ -36,10 +36,6 @@ <%= f.label I18n.t("channels.author_identifier") %> <%= f.text_field :author, { class: "form-control", placeholder: "1234567890", value: @current_channel.options["author"] } %> </div> - <div class="col-xs-8 col-sm-4 col-md-9 field"><br> - <%= f.check_box :enable_comments, { checked: @current_channel.options["comments_enabled"] } %> - <%= f.label I18n.t("channels.enable_comments") %> - </div> </div> <h3><%= I18n.t("channels.options") %></h3> <div class="row"> diff --git a/app/views/channels/new.html.erb b/app/views/channels/new.html.erb index f8097d2..64127ff 100644 --- a/app/views/channels/new.html.erb +++ b/app/views/channels/new.html.erb @@ -31,10 +31,6 @@ <div class="col-xs-8 col-sm-4 col-md-9"> <%= f.label I18n.t("channels.author_identifier") %> <%= f.text_field :author, { class: "form-control", placeholder: "1234567890" } %> - </div> - <div class="col-xs-8 col-sm-4 col-md-9 field"> - <%= f.check_box :enable_comments %> - <%= f.label I18n.t("channels.enable_comments") %> </div> <center><%= f.submit I18n.t("channels.add"), class: "col-md-3 btn btn-primary col-md-offset-3" %></center> </div> diff --git a/app/views/comments/_comment_list.erb b/app/views/comments/_comment_list.erb index 891de7e..b0d623d 100644 --- a/app/views/comments/_comment_list.erb +++ b/app/views/comments/_comment_list.erb @@ -4,7 +4,7 @@ <div class="comment-header"><%= I18n.t("comments.comment_header") %>:</div> <%post.comments.group_by(&:channel).each do |ch, _comms|%> <%next if ch.present? && !ch.enabled%> - <div class="comment-header-medium"><%= ch.present? ? "#{ch.options["title"]} (#{ch.platform.title.capitalize})" : "Blog" %>:</div> + <div class="comment-header-medium"><%= ch.present? ? "#{ch.options["title"]} (#{ch.platform.title.capitalize})" : I18n.t("comments.blog") %>:</div> <%= comments_tree_for(post.comments.where(channel: ch).order("created_at ASC").hash_tree, current_user) %> <%end%> <% end %> diff --git a/app/views/users/registrations/_channels_tab.html.erb b/app/views/users/registrations/_channels_tab.html.erb index 1d97184..6026355 100644 --- a/app/views/users/registrations/_channels_tab.html.erb +++ b/app/views/users/registrations/_channels_tab.html.erb @@ -10,7 +10,7 @@ <div class="comment-metadata"><%= name %> <i class="comment-small"> | <%= channel_url(channel) %> | <%= link_to I18n.t("channels.update"), edit_channel_url(channel) %> | <a href="/channels/delete/<%=channel.id%>" data-confirm="<%= I18n.t("channels.delete_channel_confirm") %>"><%= I18n.t("channels.delete") %></a></i><i class="comment-small right">ID: <%= channel.room %></i></div> <%=I18n.t("channels.status")%>: <%= channel.enabled ? I18n.t("channels.enabled") : I18n.t("channels.disabled") %><br> <%=I18n.t("channels.comments")%>: <%= channel.options["comments_enabled"] ? I18n.t("channels.c_enabled") : I18n.t("channels.c_disabled") %><br> - Posts count: <%= channel.platform_posts.group(:post).count.count %> + <%=I18n.t("channels.posts_count")%>: <%= channel.platform_posts.group(:post).count.count %> </div> <br> <!-- May delete (reply?) --> <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 2b49865..fc9a5f2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -150,6 +150,7 @@ en: local: Local global: Global option_enable_notifications: Enable notifications? + posts_count: Number of posts manage: manage_admins_only: You don't have permissions to manage options! all_invite_codes_list: All invite codes list diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 6ab8ffd..a883239 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -150,6 +150,7 @@ ru: local: Локальные global: Глобальные option_enable_notifications: Включить уведомления? + posts_count: Кол-во постов manage: manage_admins_only: У вас нет прав для управления настройками! all_invite_codes_list: Список всех инвайт кодов From c31fd4e6aed0dbe55bbab17dff2075d95f6b74f5 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 15 Aug 2024 13:41:53 +0300 Subject: [PATCH 32/47] Import from tg feature --- app/assets/stylesheets/base.scss | 2 +- app/controllers/telegram_controller.rb | 96 ++++++++++++++++++- app/services/check_channel.rb | 1 + .../platform/send_comment_to_telegram.rb | 4 +- .../platform/send_post_to_telegram.rb | 3 + .../platform/update_telegram_posts.rb | 1 + app/views/channels/edit.html.erb | 4 + app/views/channels/new.html.erb | 4 + app/views/comments/_add.html.erb | 2 +- config/locales/en.yml | 1 + config/locales/ru.yml | 1 + 11 files changed, 113 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 97d5257..c8b6b7b 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -689,7 +689,7 @@ $color-black: #000000; } .colorful-checkbox { - position: absolute; + position: fixed; z-index: -1; opacity: 0; } diff --git a/app/controllers/telegram_controller.rb b/app/controllers/telegram_controller.rb index 4d631a3..e22f55f 100644 --- a/app/controllers/telegram_controller.rb +++ b/app/controllers/telegram_controller.rb @@ -40,6 +40,95 @@ def check_message(message) check_reply_comment(message, reply_message) if reply_message.present? end + # Autocall when post + def channel_post(message) + from_channel = message.dig('sender_chat', 'id') + return if !from_channel.present? && !@channel_ids.values.include?(from_channel.to_s) + channel = Channel.find_by(room: from_channel) + + if message.dig('new_chat_photo').present? + return new_chat_photo(message, channel, from_channel) + end + if message.dig('delete_chat_photo').present? && message.dig('delete_chat_photo') + return delete_chat_photo(channel) + end + + import_option = channel.options.dig("import_from_tg") + import_from_tg(message, channel, from_channel) if import_option.present? && import_option + end + + def import_from_tg(message, channel, from_channel) + Rails.logger.debug('TG: NEW POST DETECTED!'.green) if Rails.env.development? + attachment = check_attachments(message) + + if attachment.present? + import_from_tg_withatt(message, channel, from_channel) + else + import_from_tg_noatt(message, channel, from_channel) + end + end + + # TODO + def import_from_tg_withatt(message, channel, from_channel) + print("ATTACHMENT: #{attachment}\n".red) + end + + def import_from_tg_noatt(message, channel, from_channel) + title = message.dig("entities").present? ? get_post_title(message) : nil + + text = message.dig("text") + text = text[title.length..text.length] if title.present? + + date = message.dig('date') + existing_post = PlatformPost.where(channel: channel).find{ |pp| pp.identifier&.dig('date') == date } + if existing_post.present? + post = existing_post.post + else + post = Post.create!(title: title, user: channel.user, privacy: 0) + text = text.lstrip + end + + content = Content.create!(text: text, post: post, user: channel.user, has_attachments: false) + options = { "enable_notifications": true, "onlylink": false, "caption": false } + + message_id = message.dig('message_id') + identifier = { chat_id: from_channel, message_id: message_id, options: options, date: date } + platform_post = PlatformPost.create!(identifier: identifier, platform: channel.platform, + post: post, content: content, channel_id: channel.id) + end + + def get_post_title(message) + bold_entry = message.dig("entities").find{ |e| e["type"] == "bold" } + return nil if bold_entry.nil? || bold_entry["offset"] != 0 + Rails.logger.debug('TG: POST TITLE FOUND!'.green) if Rails.env.development? + message.dig("text")[bold_entry["offset"]..bold_entry["length"]] + end + + def new_chat_photo(message, channel, from_channel) + Rails.logger.debug('TG: NEW CHANNEL PHOTO...'.green) if Rails.env.development? + new_photo = message.dig('new_chat_photo').last + options = channel.options + + if new_photo["avatar_size"] != options["avatar_size"] + options["avatar_size"] = new_photo["file_size"] + avatar = get_chat_avatar(bot, from_channel) + + channel.avatar.purge if channel.avatar.present? && avatar.present? + file = URI.parse(avatar[:link]).open if avatar.present? + + channel.avatar.attach(io: file, filename: 'avatar.jpg', content_type: file.content_type) if avatar.present? + channel.update!(options: options) + end + end + + def delete_chat_photo(channel) + Rails.logger.debug('TG: CHANNEL PHOTO DELETE...'.green) if Rails.env.development? + channel.avatar.purge if channel.avatar.present? + options = channel.options + options["avatar_size"] = 0 + channel.update!(options: options) + end + def check_linked_group(message, channel) linked_chat_id = channel.options.dig('linked_chat_id') return if linked_chat_id.present? @@ -50,9 +139,12 @@ def check_linked_group(message, channel) end def check_platform_message(message, channel) + Rails.logger.debug('TG: CHECKING POST FOR LINKED CHAT...'.green) if Rails.env.development? chat_id = message.dig('sender_chat', 'id') message_id = message.dig('forward_from_message_id') - platform_post = PlatformPost.where(channel: channel).find{ |pp| pp.identifier["chat_id"] == chat_id && pp.identifier["message_id"] == message_id } + platform_post = PlatformPost.where(channel: channel).find{ |pp| pp.identifier&.dig("chat_id") == chat_id && pp.identifier&.dig("message_id") == message_id } + return if platform_post.nil? + Rails.logger.debug('TG: LINKED CHAT FOR POST FOUND!'.green) if Rails.env.development? identifier = platform_post.identifier identifier["linked_chat_message_id"] = message.dig('message_id') platform_post.update!(identifier: identifier) @@ -152,7 +244,7 @@ def create_comment(message, platform_post) create_attachment_comment(message, attachment, user, platform_post, channel_id) else comment_text = message['text'] - identifier = { message_id: message['message_id'], chat_id: message['chat']['id'] } + identifier = { message_id: message['message_id'], chat_id: message['chat']['id'], date: message['date'] } Comment.create!(identifier: identifier, text: comment_text, post: platform_post.post, platform_user: user, channel_id: channel_id, platform: @telegram_platform, parent_id: (platform_post.is_a?(Comment) ? platform_post.id : nil)) diff --git a/app/services/check_channel.rb b/app/services/check_channel.rb index a52e995..0ca4fea 100644 --- a/app/services/check_channel.rb +++ b/app/services/check_channel.rb @@ -69,6 +69,7 @@ def check_telegram options[:author] = author if author.present? options[:notifications_enabled] = (params[:channel][:enable_notifications] == '1') + options[:import_from_tg] = (params[:channel][:import_from_tg] == '1') # Comments diff --git a/app/services/platform/send_comment_to_telegram.rb b/app/services/platform/send_comment_to_telegram.rb index fa615d8..13488a9 100644 --- a/app/services/platform/send_comment_to_telegram.rb +++ b/app/services/platform/send_comment_to_telegram.rb @@ -73,8 +73,8 @@ def send_telegram_comment(channel) parse_mode: 'html'}) res = { chat_id: @msg['result']['chat']['id'], - message_id: @msg['result']['message_id'] - #date: @msg['result']['date'], + message_id: @msg['result']['message_id'], + date: @msg['result']['date'] } Comment.create!(text: text, identifier: res, post: @current_post, user: @current_user, has_attachments: has_attachments, channel_id: channel[:id], platform: @platform, parent_id: parent_id.present? ? parent_id : nil) diff --git a/app/services/platform/send_post_to_telegram.rb b/app/services/platform/send_post_to_telegram.rb index b9b7c17..054cdaa 100644 --- a/app/services/platform/send_post_to_telegram.rb +++ b/app/services/platform/send_post_to_telegram.rb @@ -117,6 +117,7 @@ def send_telegram_content(channel, options) PlatformPost.create!(identifier: { chat_id: @msg['result']['chat']['id'], message_id: @msg['result']['message_id'], + date: @msg['result']['date'], options: options }, platform: @platform, post: @post, content: content, channel_id: channel[:id]) end @@ -163,6 +164,7 @@ def send_telegram_attachments(bot, channel, options, content, has_caption) options = options.merge(caption: media[i].key?(:caption)) msg_ids.append({ chat_id: msg['result'][0]['chat']['id'], message_id: msg['result'][0]['message_id'] + i, + #date: msg['result']['date'], file_id: media[i][:media], type: media[i][:type], blob_signed_id: media[i][:blob_signed_id], @@ -226,6 +228,7 @@ def send_tg_onlylink_post(channel, options) PlatformPost.create!( identifier: { chat_id: msg['result']['chat']['id'], message_id: msg['result']['message_id'], + date: msg['result']['date'], options: options }, platform: @platform, post: @post, diff --git a/app/services/platform/update_telegram_posts.rb b/app/services/platform/update_telegram_posts.rb index 78ffc92..6b7c02c 100644 --- a/app/services/platform/update_telegram_posts.rb +++ b/app/services/platform/update_telegram_posts.rb @@ -120,6 +120,7 @@ def add_content(new_block, index) PlatformPost.create!( identifier: { chat_id: @msg['result']['chat']['id'], message_id: @msg['result']['message_id'], + date: @msg['result']['date'], options: options }, platform: @platform, post: @post, diff --git a/app/views/channels/edit.html.erb b/app/views/channels/edit.html.erb index 8742052..44ee31d 100644 --- a/app/views/channels/edit.html.erb +++ b/app/views/channels/edit.html.erb @@ -36,6 +36,10 @@ <%= f.label I18n.t("channels.author_identifier") %> <%= f.text_field :author, { class: "form-control", placeholder: "1234567890", value: @current_channel.options["author"] } %> </div> + <div class="col-xs-8 col-sm-4 col-md-9 field"><br> + <%= f.check_box :import_from_tg, { checked: @current_channel.options["import_from_tg"] } %> + <%= f.label I18n.t("channels.import_from_tg") %> + </div> </div> <h3><%= I18n.t("channels.options") %></h3> <div class="row"> diff --git a/app/views/channels/new.html.erb b/app/views/channels/new.html.erb index 64127ff..49822a9 100644 --- a/app/views/channels/new.html.erb +++ b/app/views/channels/new.html.erb @@ -50,6 +50,10 @@ <%= f.label I18n.t("channels.api_server_address") %> <%= f.text_field :server, { class: "form-control", placeholder: "https://matrix.org/_matrix/", value: "https://matrix.org/_matrix/" } %> </div> + <div class="col-xs-8 col-sm-4 col-md-9 field"> + <%= f.check_box :import_from_tg %> + <%= f.label I18n.t("channels.import_from_tg") %> + </div> <center><%= f.submit I18n.t("channels.add"), class: "col-md-3 btn btn-primary col-md-offset-3" %></center> </div> </div> diff --git a/app/views/comments/_add.html.erb b/app/views/comments/_add.html.erb index a1209b0..0016074 100644 --- a/app/views/comments/_add.html.erb +++ b/app/views/comments/_add.html.erb @@ -44,5 +44,5 @@ </div> <%= javascript_include_tag 'application.js', "data-turbo-track": "reload" %> -<%= javascript_include_tag 'showdown.min.js' %> +<%= javascript_include_tag 'showdown.min.js', "data-turbo-track": "reload" %> <%= javascript_include_tag 'run_showdown.js' %> \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index fc9a5f2..c76bc19 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -151,6 +151,7 @@ en: global: Global option_enable_notifications: Enable notifications? posts_count: Number of posts + import_from_tg: Automatically add posts sent via Telegram? manage: manage_admins_only: You don't have permissions to manage options! all_invite_codes_list: All invite codes list diff --git a/config/locales/ru.yml b/config/locales/ru.yml index a883239..1a7b9f8 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -151,6 +151,7 @@ ru: global: Глобальные option_enable_notifications: Включить уведомления? posts_count: Кол-во постов + import_from_tg: Автоматически добавлять посты отправленные через Telegram? manage: manage_admins_only: У вас нет прав для управления настройками! all_invite_codes_list: Список всех инвайт кодов From a67a75f64d3af01225ad75e1b5755031172bb98d Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Thu, 15 Aug 2024 17:21:18 +0300 Subject: [PATCH 33/47] Posting attachments from tg --- app/controllers/telegram_controller.rb | 152 +++++++++++++++--- .../platform/send_post_to_telegram.rb | 3 +- config/locales/en.yml | 4 +- config/locales/ru.yml | 9 +- 4 files changed, 141 insertions(+), 27 deletions(-) diff --git a/app/controllers/telegram_controller.rb b/app/controllers/telegram_controller.rb index e22f55f..3d384aa 100644 --- a/app/controllers/telegram_controller.rb +++ b/app/controllers/telegram_controller.rb @@ -8,6 +8,17 @@ def initialize(bot = nil, update = nil) @telegram_platform = Platform.find_by(title: 'telegram') @channel_ids = Channel.where(enabled: true, platform: @telegram_platform).map{ |ch| [ch.id, ch.room]}.to_h @linked_group_channels_ids = Channel.where(id: @channel_ids.keys).map{ |ch| [ch.id, ch.options.dig("linked_chat_id")] }.to_h.compact_blank + + @images = ['image/gif', 'image/jpeg', 'image/pjpeg', 'image/png', 'image/webp', 'image/svg+xml'] + @videos = ['video/mp4', 'video/mpeg', 'video/webm', 'video/ogg'] + @audios = ['audio/mp4', + 'audio/aac', + 'audio/mpeg', + 'audio/ogg', + 'audio/vorbis', + 'audio/webm', + 'audio/vnd.wave', + 'audio/basic'] super end @@ -40,6 +51,8 @@ def check_message(message) check_reply_comment(message, reply_message) if reply_message.present? end + ######### Import info from channel ######### + # Autocall when post def channel_post(message) from_channel = message.dig('sender_chat', 'id') @@ -58,34 +71,85 @@ def channel_post(message) end def import_from_tg(message, channel, from_channel) - Rails.logger.debug('TG: NEW POST DETECTED!'.green) if Rails.env.development? + Rails.logger.debug('TG: POSTING FROM TG DETECTED!'.green) if Rails.env.development? attachment = check_attachments(message) if attachment.present? - import_from_tg_withatt(message, channel, from_channel) + import_from_tg_withatt(message, channel, from_channel, attachment) else import_from_tg_noatt(message, channel, from_channel) end end - # TODO - def import_from_tg_withatt(message, channel, from_channel) - print("ATTACHMENT: #{attachment}\n".red) + def import_from_tg_withatt(message, channel, from_channel, attachment) + caption = message.dig('caption') + caption_entities = attachment.dig(:caption_entities) + title = caption_entities.present? ? get_post_title(caption_entities, caption) : nil + + caption = caption[title.length..caption.length] if title.present? + + date = message.dig('date') + existing_pp = get_existing_pp(channel, date) + if existing_pp.present? + Rails.logger.debug('TG: EXISTING POST WITH ATTACHMENTS FOUND!'.green) if Rails.env.development? + post = existing_pp.post + content = post.contents.find{ |c| c.has_attachments == true } + if content.nil? + content = Content.create!(text: caption, post: post, user: channel.user, has_attachments: true) + end + else + Rails.logger.debug('TG: NEW POST WITH ATTACHMENTS'.green) if Rails.env.development? + post = Post.create!(title: title, user: channel.user, privacy: 0) + caption = caption.lstrip if caption.present? + + content = Content.create!(text: caption, post: post, user: channel.user, has_attachments: true) + end + + options = { "enable_notifications": true, "onlylink": false, "caption": caption.present? } + file = URI.parse(attachment[:link]).open + content.attachments.attach(io: file, filename: attachment[:file_name], content_type: file.content_type) + att = content.attachments.find{ |att| att.byte_size == attachment[:file_size] } + blob_signed_id = att.signed_id + + identifier = { chat_id: message['chat']['id'], + message_id: message['message_id'], + file_id: attachment[:file_id], + type: get_attachment_type(att), + blob_signed_id: blob_signed_id, + date: message['date'], + options: options + } + + if existing_pp.present? + existing_pp_identifier = existing_pp.identifier + if existing_pp_identifier.is_a?(Array) + existing_pp_identifier.append(identifier) + existing_pp.update!(identifier: existing_pp_identifier) + #elsif existing_pp_identifier.is_a?(Hash) + # print("EXISTING PP ID: #{existing_pp.id}\n".red) + end + else + platform_post = PlatformPost.create!(identifier: [identifier], platform: channel.platform, + post: post, content: content, channel_id: channel.id) + end end def import_from_tg_noatt(message, channel, from_channel) - title = message.dig("entities").present? ? get_post_title(message) : nil - text = message.dig("text") + entities = message.dig("entities") + title = entities.present? ? get_post_title(entities, text) : nil + text = text[title.length..text.length] if title.present? date = message.dig('date') - existing_post = PlatformPost.where(channel: channel).find{ |pp| pp.identifier&.dig('date') == date } - if existing_post.present? - post = existing_post.post + existing_pp = get_existing_pp(channel, date) + if existing_pp.present? + Rails.logger.debug('TG: EXISTING POST FOUND!'.green) if Rails.env.development? + post = existing_pp.post else + Rails.logger.debug('TG: NEW POST'.green) if Rails.env.development? post = Post.create!(title: title, user: channel.user, privacy: 0) - text = text.lstrip + text = text.lstrip if text.present? end content = Content.create!(text: text, post: post, user: channel.user, has_attachments: false) @@ -97,11 +161,31 @@ def import_from_tg_noatt(message, channel, from_channel) post: post, content: content, channel_id: channel.id) end - def get_post_title(message) - bold_entry = message.dig("entities").find{ |e| e["type"] == "bold" } + def get_existing_pp(channel, date) + PlatformPost.where(channel: channel).find do |pp| + if pp.identifier&.is_a?(Array) + pp.identifier&.find{ |ppp| ppp["date"] == date } + elsif pp.identifier&.is_a?(Hash) + pp.identifier&.dig('date') == date + end + end + end + + def get_existing_pp_by_msg(channel, chat_id, message_id) + PlatformPost.where(channel: channel).find do |pp| + if pp.identifier&.is_a?(Array) + pp.identifier&.find{ |ppp| ppp["chat_id"] == chat_id && ppp["message_id"] == message_id } + elsif pp.identifier&.is_a?(Hash) + pp.identifier&.dig('chat_id') == chat_id && pp.identifier&.dig('message_id') == message_id + end + end + end + + def get_post_title(entities, text) + bold_entry = entities.find{ |e| e["type"] == "bold" } return nil if bold_entry.nil? || bold_entry["offset"] != 0 Rails.logger.debug('TG: POST TITLE FOUND!'.green) if Rails.env.development? - message.dig("text")[bold_entry["offset"]..bold_entry["length"]] + text[bold_entry["offset"]..bold_entry["length"]] end def new_chat_photo(message, channel, from_channel) @@ -129,6 +213,8 @@ def delete_chat_photo(channel) channel.update!(options: options) end + ######### Group linking ######### + def check_linked_group(message, channel) linked_chat_id = channel.options.dig('linked_chat_id') return if linked_chat_id.present? @@ -142,14 +228,20 @@ def check_platform_message(message, channel) Rails.logger.debug('TG: CHECKING POST FOR LINKED CHAT...'.green) if Rails.env.development? chat_id = message.dig('sender_chat', 'id') message_id = message.dig('forward_from_message_id') - platform_post = PlatformPost.where(channel: channel).find{ |pp| pp.identifier&.dig("chat_id") == chat_id && pp.identifier&.dig("message_id") == message_id } - return if platform_post.nil? + existing_pp = get_existing_pp_by_msg(channel, chat_id, message_id) + return if existing_pp.nil? Rails.logger.debug('TG: LINKED CHAT FOR POST FOUND!'.green) if Rails.env.development? - identifier = platform_post.identifier - identifier["linked_chat_message_id"] = message.dig('message_id') - platform_post.update!(identifier: identifier) + identifier = existing_pp.identifier + if identifier.is_a?(Array) + identifier = identifier.each { |ii| ii.merge!("linked_chat_message_id" => message.dig('message_id')) } + elsif identifier.is_a?(Hash) + identifier["linked_chat_message_id"] = message.dig('message_id') + end + existing_pp.update!(identifier: identifier) end + ######### Comments ######### + def check_edit_message(message) comment_channel = message.dig('reply_to_message', 'sender_chat', 'id') @@ -256,7 +348,8 @@ def create_attachment_comment(message, attachment, user, platform_post, channel_ identifier = { message_id: message['message_id'], chat_id: message['chat']['id'], file_id: attachment[:file_id], - file_size: attachment[:file_size] } + file_size: attachment[:file_size], + date: message['date'] } if attachment[:media_group_id].present? Rails.logger.debug("MEDIA GROUP ID PRESENT... #{attachment[:media_group_id]}".green) if Rails.env.development? # puts(attachment) @@ -306,6 +399,7 @@ def edit_comment(message, comment) if attachment.present? identifier = { message_id: message['message_id'], chat_id: message['chat']['id'], + date: message['date'], file_id: attachment[:file_id], file_size: attachment[:file_size] } att_id = nil @@ -345,6 +439,8 @@ def edit_comment(message, comment) end end + ######### PlatformUser && Other ######### + def check_user(message) tg_user = message['from']['id'] @@ -428,9 +524,23 @@ def check_attachments(message) { link: "https://api.telegram.org/file/bot#{bot.token}/#{msg['result']['file_path']}", caption: message['caption'], + caption_entities: message.dig('caption_entities'), file_id: file_id, file_size: msg['result']['file_size'], file_name: msg['result']['file_path'].split('/').last, - media_group_id: message['media_group_id'] } + media_group_id: message['media_group_id'], + date: message['date'] } + end + + def get_attachment_type(att) + if @images.include?(att.content_type) + 'photo' + elsif @videos.include?(att.content_type) + 'video' + elsif @audios.include?(att.content_type) + 'audio' + else + 'document' + end end end diff --git a/app/services/platform/send_post_to_telegram.rb b/app/services/platform/send_post_to_telegram.rb index 054cdaa..c108a1c 100644 --- a/app/services/platform/send_post_to_telegram.rb +++ b/app/services/platform/send_post_to_telegram.rb @@ -149,6 +149,7 @@ def send_telegram_attachments(bot, channel, options, content, has_caption) options = options.merge(caption: v[i].key?(:caption)) msg_ids.append({ chat_id: msg['result'][0]['chat']['id'], message_id: msg['result'][0]['message_id'] + i, + date: msg['result'][0]['date'], file_id: v[i][:media], type: v[i][:type], blob_signed_id: v[i][:blob_signed_id], @@ -164,7 +165,7 @@ def send_telegram_attachments(bot, channel, options, content, has_caption) options = options.merge(caption: media[i].key?(:caption)) msg_ids.append({ chat_id: msg['result'][0]['chat']['id'], message_id: msg['result'][0]['message_id'] + i, - #date: msg['result']['date'], + date: msg['result'][0]['date'], file_id: media[i][:media], type: media[i][:type], blob_signed_id: media[i][:blob_signed_id], diff --git a/config/locales/en.yml b/config/locales/en.yml index c76bc19..5578cd4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -31,7 +31,9 @@ en: author: Author download: Download edit_post: Edit - delete_post: Delete post + delete_post: Delete + delete_post: Delete + delete_post_local: Delete (Local) delete_post_confirm: Are you sure? read_more: Read more new_header: New post diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 1a7b9f8..9366da2 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -31,8 +31,9 @@ ru: author: Автор download: Загрузить edit_post: Редактировать - delete_post: Удалить статью - delete_post_confirm: Вы серьёзно? + delete_post: Удалить + delete_post_local: Удалить (Локально) + delete_post_confirm: Вы уверены? read_more: Читать далее new_header: Новый пост edit_header: Редактировать пост @@ -51,7 +52,7 @@ ru: tags: Тэги tags_not_found: Тэги не найдены! back: Вернуться назад - back_confirm: Вы серьёзно? Текущий прогресс будет потерян! + back_confirm: Вы уверены? Текущий прогресс будет потерян! privacy: Видимость privacy_public: Для всех privacy_registered: Только участники @@ -135,7 +136,7 @@ ru: update: Обновить edit: Изменить delete: Удалить - delete_channel_confirm: Вы серьёзно? + delete_channel_confirm: Вы уверены? add_new_channel: Добавить новый канал channels_not_found: Каналы не найдены! need_more_channels: Нужно больше каналов? From 806ee71fc901f9453d2ada03a75668e5976f1e0e Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Fri, 23 Aug 2024 17:30:45 +0300 Subject: [PATCH 34/47] New content existence logic --- README-RU.md | 4 +- README.md | 4 +- app/controllers/posts_controller.rb | 6 +- app/helpers/posts_helper.rb | 14 +- app/models/content.rb | 28 +-- app/models/platform.rb | 1 + app/models/post.rb | 16 +- app/services/check_channel.rb | 13 +- app/services/delete_post_messages.rb | 12 +- app/services/export_files.rb | 6 +- .../platform/delete_matrix_comments.rb | 2 +- app/services/platform/delete_matrix_posts.rb | 2 +- .../platform/delete_telegram_comments.rb | 2 +- .../platform/delete_telegram_posts.rb | 2 +- .../platform/send_comment_to_telegram.rb | 4 +- app/services/platform/send_post_to_matrix.rb | 106 ++++++------ .../platform/send_post_to_telegram.rb | 159 ++++++++++++------ app/services/platform/update_matrix_posts.rb | 139 +++++---------- .../platform/update_telegram_posts.rb | 6 +- app/services/send_post_to_platforms.rb | 23 ++- app/services/update_post_messages.rb | 32 +++- app/views/channels/edit.html.erb | 8 +- app/views/channels/new.html.erb | 8 +- app/views/posts/_post.html.erb | 2 +- app/views/posts/_post_header.html.erb | 2 +- app/views/posts/edit.html.erb | 5 +- app/views/posts/feed/_feed.html.erb | 2 +- app/views/posts/index.html.erb | 2 +- app/views/posts/raw.html.erb | 2 +- app/views/posts/rss.builder | 2 +- config.ru | 1 + config/locales/en.yml | 6 +- config/locales/ru.yml | 6 +- ...40822061838_add_platform_id_to_contents.rb | 9 + spec/models/post_spec.rb | 22 +-- spec/services/send_post_to_platforms_spec.rb | 2 +- update_log.md | 6 + 37 files changed, 365 insertions(+), 301 deletions(-) create mode 100644 db/migrate/20240822061838_add_platform_id_to_contents.rb diff --git a/README-RU.md b/README-RU.md index 7d782fc..44efc59 100644 --- a/README-RU.md +++ b/README-RU.md @@ -190,12 +190,10 @@ gem install wdm -- --with-cflags=-Wno-implicit-function-declaration ## Баги и некоторые особенности Особенности: -* [TG] Если есть заголовок, но отсутствует текст поста, то заголовок не отправляется; -* [TG] При удалении поста с какого-либо канала, удаляются все комментарии, в т.ч. и привязанные к другим постам. Это связано с тем, что комментарии привязаны к посту, а не к платформе, иначе при просмотре поста пришлось бы показывать разные версии текста (для каждого канала) с разными комментариями. А подразумевается, что пост один (одинаковый), просто на нескольких каналах; +* [ANY] Если удалить канал, а затем удалить пост, то пост из платформ не удалится (нет токенов - нет удаления, вроде логично); * [TG] Если у поста в telegram был текст и аттачменты, и при редактировании убрать все аттачменты, то пост полностью удалится. Это особенность телеги, я не могу превратить подпись в текст, нужно создавать новый пост; * [TG] Если вы отправляете несколько аттачментов разного типа и используете подпись, то аттачменты будут сгруппированы по группам, первой группой будет такого же типа как и первый аттачмент, и подпись будет прикреплена именно к ней; * [TG] Если был создан пост с <= 4096 символами и при обновлении поста его длина будет превышать 4096 символов, то будет создано новое сообщение, которое может быть на дальнем расстоянии от первого (например, если были ещё посты то оно будет идти после них). Переместить сообщение вверх я не могу, поэтому советую использовать опцию onlylink в таких случаях; -* [ANY] Если удалить канал, а затем удалить пост, то пост из платформ не удалится (нет токенов - нет удаления, вроде логично); Баги: diff --git a/README.md b/README.md index 88ccb80..abb271b 100644 --- a/README.md +++ b/README.md @@ -192,12 +192,10 @@ For the production, do not forget to recompile the assets: ## Bugs and some features Features: -* [TG] If there is a title, but there is no post text, then the title is not sent; -* [TG] When you delete a post from any channel, all comments are deleted, incl. and tied to other posts. This is due to the fact that comments are tied to the post, not to the platform, otherwise, when viewing the post, you would have to show different versions of the text (for each channel) with different comments. And it is understood that the post is one (the same), just on several channels; +* [ANY] If you delete a channel and then delete a post, then the post will not be deleted from the platforms (no tokens - no deletion, it seems logical); * [TG] If a post in telegram had text and attachments, and when editing, remove all attachments, then the post will be completely deleted. This is a feature of the cart, I cannot turn the capture into text, I need to create a new post; * [TG] If you send several attachments of different types and use a caption, the attachments will be grouped into groups, the first group will be of the same type as the first attachment, and the caption will be attached to it; * [TG] If a post was created with <= 4096 characters and when the post is updated its length will exceed 4096 characters, then a new message will be created, which may be at a far distance from the first one (for example, if there were more posts, it will go after them). I cannot move the message up, so I advise you to use the onlylink option in such cases; -* [ANY] If you delete a channel and then delete a post, then the post will not be deleted from the platforms (no tokens - no deletion, it seems logical); Bugs: diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 8b71eab..eac7224 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -45,10 +45,10 @@ def set_tags end def set_tags_post(current_post) - img_preview = current_post.content_attachments&.find{ |a| a.image? } + img_preview = current_post.attachments&.find{ |a| a.image? } img_preview_url = img_preview.present? ? "#{request.base_url}#{rails_blob_path(img_preview, only_path: true)}" : "" - video_preview = current_post.content_attachments&.find{ |a| a.video? } + video_preview = current_post.attachments&.find{ |a| a.video? } video_preview_url = video_preview.present? ? "#{request.base_url}#{rails_blob_path(video_preview, only_path: true)}" : "" title = current_post.title.present? ? current_post.title : "#{current_post.uuid} | #{Rails.configuration.credentials[:title]}" @@ -260,7 +260,7 @@ def feed if Rails.configuration.credentials[:fail2ban][:enabled] && user.nil? && params.key?(:token) ip = request.env['action_dispatch.remote_ip'] || request.env['REMOTE_ADDR'] - Rails.logger.error("Failed bypass token from #{ip} at #{Time.now.utc.iso8601}") + Rails.logger.error("Failed bypass token from #{ip} at #{Time.now.utc.iso8601}".red) end if params.dig("uuid").present? diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index 9db7d28..ddb3499 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -13,7 +13,7 @@ def get_full_attachment_link(att) def display_attachments(post) content = '' - documents = post.content_attachments.select { |b| !b.image? && !b.video? && !b.audio? } + documents = post.attachments&.select { |b| !b.image? && !b.video? && !b.audio? } if documents.any? documents.each do |att| content += "<br><a target=\"_blank\" href=\"#{get_full_attachment_link(att)}\"> @@ -23,7 +23,7 @@ def display_attachments(post) content += '<br><br>' if documents.any? content += "<div class=\"attachments\">" - post.content_attachments&.each do |att| + post.attachments.each do |att| if att.image? content += link_to image_tag(url_for(att)), url_for(att), target: '_blank'.to_s elsif att.video? @@ -44,7 +44,7 @@ def display_attachments(post) # TODO def display_raw_attachments(post) content = '' - documents = post.content_attachments.select { |b| !b.image? && !b.video? && !b.audio? } + documents = post.attachments&.select { |b| !b.image? && !b.video? && !b.audio? } if documents.any? documents.each do |att| content += "<br><a target=\"_blank\" href=\"#{get_full_attachment_link(att)}\"> @@ -53,7 +53,7 @@ def display_raw_attachments(post) end content += '<br><br>' if documents.any? - post.content_attachments&.each do |att| + post.attachments.each do |att| full_att_link = get_full_attachment_link(att) if att.image? content += link_to image_tag(full_att_link), full_att_link, target: '_blank'.to_s @@ -66,7 +66,7 @@ def display_raw_attachments(post) content += '<br>' end end - content += '<br>' if post.content_attachments&.any? + content += '<br>' if post.attachments.present? content.html_safe end @@ -74,7 +74,7 @@ def display_raw_attachments(post) def display_feed_attachments(post) content = '' - documents = post.content_attachments.select { |b| !b.image? && !b.video? && !b.audio? } + documents = post.attachments&.select { |b| !b.image? && !b.video? && !b.audio? } if documents.any? documents.each do |att| content += "<a target=\"_blank\" href=\"#{get_full_attachment_link(att)}\"> @@ -83,7 +83,7 @@ def display_feed_attachments(post) end content += "<div class=\"attachments\">" - post.content_attachments&.each do |att| + post.attachments.each do |att| if att.image? content += image_tag url_for(att), id: 'zoom-bg'.to_s elsif att.video? diff --git a/app/models/content.rb b/app/models/content.rb index 8c5baa3..eba8785 100644 --- a/app/models/content.rb +++ b/app/models/content.rb @@ -4,25 +4,27 @@ class Content < ApplicationRecord # validates :text, presence: true belongs_to :user belongs_to :post + belongs_to :platform + + has_many :platform_posts + + #before_validation :check_platform_presence, on: %i[craete update] after_create_commit do upd_post end after_update_commit do upd_post end after_destroy_commit do upd_post end - has_many_attached :attachments do |attachable| - attachable.variant :thumb100, resize_to_limit: [100, 100] - attachable.variant :thumb150, resize_to_limit: [150, 150] - attachable.variant :thumb200, resize_to_limit: [200, 200] - attachable.variant :thumb250, resize_to_limit: [250, 250] - attachable.variant :thumb300, resize_to_limit: [300, 300] - end - - def destroy - attachments.purge - super - end - def upd_post broadcast_update_to [self.post], partial: 'posts/post', locals: { post: self.post }, target: "post_#{self.post.id}" end + + #private + + #def check_platform_presence + # set_blog_platform if platform.nil? + #end + + #def set_blog_platform + # self.platform = Platform.find_by(title: 'blog') + #end end diff --git a/app/models/platform.rb b/app/models/platform.rb index 658aabb..4ea33f7 100644 --- a/app/models/platform.rb +++ b/app/models/platform.rb @@ -5,4 +5,5 @@ class Platform < ApplicationRecord has_many :platform_posts has_many :platform_users has_many :channels + has_many :contents end diff --git a/app/models/post.rb b/app/models/post.rb index 0b7c2ea..e8eb906 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -13,6 +13,14 @@ class Post < ApplicationRecord before_create :gen_uuid + has_many_attached :attachments do |attachable| + attachable.variant :thumb100, resize_to_limit: [100, 100] + attachable.variant :thumb150, resize_to_limit: [150, 150] + attachable.variant :thumb200, resize_to_limit: [200, 200] + attachable.variant :thumb250, resize_to_limit: [250, 250] + attachable.variant :thumb300, resize_to_limit: [300, 300] + end + extend FriendlyId friendly_id :title, use: [:slugged, :finders] @@ -63,19 +71,17 @@ def published_channels end def text + platform = Platform.find_by(title: 'blog') text = '' - contents.order(:id).each do |msg| + contents.where(platform: platform).order(:id).each do |msg| text += msg[:text] if msg[:text].present? end text end - def content_attachments - Content.where(post: self).select { |c| c.attachments.any? }.map(&:attachments)[0] - end - def destroy DeletePostMessages.call(self) + attachments.purge super end diff --git a/app/services/check_channel.rb b/app/services/check_channel.rb index 0ca4fea..07f8b19 100644 --- a/app/services/check_channel.rb +++ b/app/services/check_channel.rb @@ -52,13 +52,14 @@ def check_telegram errs << 'Channel not available! (Not found or bot access problems?)' end - begin - bot.get_chat(chat_id: params[:channel][:room_attachments]) - rescue Telegram::Bot::Error - errs << 'Attachments channel not available! (Not found or bot access problems?)' - end - + if params[:channel][:room_attachments].present? && !params[:channel][:room_attachments].empty? + begin + bot.get_chat(chat_id: params[:channel][:room_attachments]) + rescue Telegram::Bot::Error + errs << 'Attachments channel not available! (Not found or bot access problems?)' + end errs << 'Channel ID == Attachment Channel ID' if params[:channel][:room] == params[:channel][:room_attachments] + end return errors.add(:base, errs) if errs.any? diff --git a/app/services/delete_post_messages.rb b/app/services/delete_post_messages.rb index fa9db09..1d4bca5 100644 --- a/app/services/delete_post_messages.rb +++ b/app/services/delete_post_messages.rb @@ -16,6 +16,9 @@ def call matrix_posts = post.platform_posts.joins(:platform).where(platforms: { title: 'matrix' }) Platform::DeleteTelegramPosts.call(telegram_posts) if telegram_posts.any? Platform::DeleteMatrixPosts.call(matrix_posts) if matrix_posts.any? + comment_ids = Comment.where(post: post).ids + ActiveStorage::Attachment.where(record_type: 'Comment', record: comment_ids).destroy_all + Comment.where(id: comment_ids).destroy_all else telegram_posts = post.platform_posts.joins(:platform).where(platforms: { title: 'telegram' }, channel_id: channel_id) @@ -29,9 +32,14 @@ def call end platform = Platform.find_by(title: title) PlatformPost.where(platform: platform, post: post, channel_id: channel_id).destroy_all - comment_ids = Comment.where(post: post).ids # Сделать привязку коммента к платформ посту + comment_ids = Comment.where(post: post, channel_id: channel_id).ids ActiveStorage::Attachment.where(record_type: 'Comment', record: comment_ids).destroy_all - Comment.where(post: post).destroy_all + Comment.where(id: comment_ids).destroy_all end + + # Delete content without platform posts + blog_platform = Platform.find_by(title: 'blog') + content_nopp = @post.contents.includes(:platform_posts).where(platform_posts: {content_id: nil}).where.not(platform: blog_platform) + content_nopp.destroy_all if content_nopp.any? end end diff --git a/app/services/export_files.rb b/app/services/export_files.rb index 0c23cd3..75694c1 100644 --- a/app/services/export_files.rb +++ b/app/services/export_files.rb @@ -41,14 +41,14 @@ def call f.write(text) end - if @post.content_attachments.present? - @post.content_attachments.each do |attachment| + if @post.attachments.present? + @post.attachments.each do |attachment| filename = attachment.blob.filename.to_s FileUtils.cp(ActiveStorage::Blob.service.send(:path_for, attachment.blob.key), "#{post_dir}/#{filename}") end end - if @post.content_attachments.present? + if @post.attachments.present? output_file = "tmp/export/#{@post.uuid}.zip" zf = ZipFileGenerator.new(post_dir, output_file) zf.write diff --git a/app/services/platform/delete_matrix_comments.rb b/app/services/platform/delete_matrix_comments.rb index 34d9830..1bd10a3 100644 --- a/app/services/platform/delete_matrix_comments.rb +++ b/app/services/platform/delete_matrix_comments.rb @@ -27,7 +27,7 @@ def call Matrix.post(server, matrix_token, method, data) end rescue StandardError - Rails.logger.error("Failed to delete matrix comments at #{Time.now.utc.iso8601}") + Rails.logger.error("Failed to delete matrix comments at #{Time.now.utc.iso8601}".red) end end end diff --git a/app/services/platform/delete_matrix_posts.rb b/app/services/platform/delete_matrix_posts.rb index 9f61671..1f281a5 100644 --- a/app/services/platform/delete_matrix_posts.rb +++ b/app/services/platform/delete_matrix_posts.rb @@ -27,7 +27,7 @@ def call Matrix.post(server, matrix_token, method, data) end rescue StandardError - Rails.logger.error("Failed delete matrix messages at #{Time.now.utc.iso8601}") + Rails.logger.error("Failed delete matrix messages at #{Time.now.utc.iso8601}".red) end end end diff --git a/app/services/platform/delete_telegram_comments.rb b/app/services/platform/delete_telegram_comments.rb index 4ef3f33..5154f8c 100644 --- a/app/services/platform/delete_telegram_comments.rb +++ b/app/services/platform/delete_telegram_comments.rb @@ -30,7 +30,7 @@ def call message_id: comm[:identifier]['message_id'] }) end rescue StandardError # Message don't delete (if bot don't have access to message) - Rails.logger.error("Failed to delete telegram comments at #{Time.now.utc.iso8601}") + Rails.logger.error("Failed to delete telegram comments at #{Time.now.utc.iso8601}".red) end end end diff --git a/app/services/platform/delete_telegram_posts.rb b/app/services/platform/delete_telegram_posts.rb index 68c1a65..4ae0ef2 100644 --- a/app/services/platform/delete_telegram_posts.rb +++ b/app/services/platform/delete_telegram_posts.rb @@ -21,7 +21,7 @@ def call message_id: platform_post[:identifier]['message_id'] }) end rescue StandardError # Message don't delete (if bot don't have access to message) - Rails.logger.error("Failed delete telegram messages at #{Time.now.utc.iso8601}") + Rails.logger.error("Failed delete telegram messages at #{Time.now.utc.iso8601}".red) end end end diff --git a/app/services/platform/send_comment_to_telegram.rb b/app/services/platform/send_comment_to_telegram.rb index 13488a9..6590c42 100644 --- a/app/services/platform/send_comment_to_telegram.rb +++ b/app/services/platform/send_comment_to_telegram.rb @@ -54,7 +54,7 @@ def send_telegram_comment(channel) message_id = platform_post.identifier["linked_chat_message_id"] linked_chat_id = channel[:linked_chat_id] # || get_linked_chat(bot, chat_id) if linked_chat_id.nil? || message_id.nil? - return Rails.logger.error("Can't get linked group chat_id or linked message_id for channel #{channel[:id]} at #{Time.now.utc.iso8601}!") + return Rails.logger.error("Can't get linked group chat_id or linked message_id for channel #{channel[:id]} at #{Time.now.utc.iso8601}!".red) end if @params[:comment][:parent_id].to_i > 0 @@ -80,7 +80,7 @@ def send_telegram_comment(channel) Comment.create!(text: text, identifier: res, post: @current_post, user: @current_user, has_attachments: has_attachments, channel_id: channel[:id], platform: @platform, parent_id: parent_id.present? ? parent_id : nil) end rescue StandardError => e - Rails.logger.error("Failed create telegram comment for chat #{channel[:id]} at #{Time.now.utc.iso8601}:\n#{e}") + Rails.logger.error("Failed create telegram comment for chat #{channel[:id]} at #{Time.now.utc.iso8601}:\n#{e}".red) end def send_telegram_attachments(bot, channel) diff --git a/app/services/platform/send_post_to_matrix.rb b/app/services/platform/send_post_to_matrix.rb index 092e243..31bf113 100644 --- a/app/services/platform/send_post_to_matrix.rb +++ b/app/services/platform/send_post_to_matrix.rb @@ -9,12 +9,13 @@ def initialize(post, base_url, params, channel_ids) @params = params @post = post @base_url = base_url - + @platform = Platform.find_by(title: 'matrix') + @channels = Channel.where(id: channel_ids).map do |channel| { id: channel.id, room: channel.room, matrix_token: channel.token, server: channel.options['server'] } end - @attachments = @params[:post][:attachments].reverse if @params[:post][:attachments].present? + #@attachments = @params[:post][:attachments].reverse if @params[:post][:attachments].present? @options = @params[:options] if @options.present? @options = @@ -41,42 +42,21 @@ def initialize(post, base_url, params, channel_ids) end def call - unless @post.text.present? || (@post.text.blank? && @post.content_attachments.present?) # Content not created! - content_text = params[:post][:content] + title = @post.title - attachment_content = Content.create!(user: @post.user, post: @post, has_attachments: true) unless @attachments.join(' ').empty? - @attachments.each { |att| attachment_content.attachments.attach(att) } if attachment_content.present? - Content.create!(user: @post.user, post: @post, text: content_text, has_attachments: false) if content_text.present? - end + text = @post.text.present? ? @post.text : '' + text = @markdown.render(text) + text = text.replace_html_to_mx_markdown + text.delete_suffix!("<br>") + send_text = title.present? ? "<b>#{title}</b><br><br>#{text}" : text.to_s - # TODO: Check compatibility with telegram - if @post.title.present? && @post.contents.where(has_attachments: false).empty? - Content.create!(user: @post.user, post: @post, text: "", has_attachments: false) - end + attachment_content = Content.create!(post: @post, user: @post.user, + platform: @platform, has_attachments: true) if @post.attachments.present? + content = Content.create!(post: @post, user: @post.user, platform: @platform, + text: text, has_attachments: false) - # No content - no post :\ - Content.where(post: @post).order(:id).each do |content| - title = @post.title - - if content.text.present? - content_text = - if Content.where(post: @post, has_attachments: false).count >= 2 # Already upload in tg, also check down below - @markdown.render(@post.text) - else # Tg has 1 content or no contents - @markdown.render(content.text) - end - content_text = content_text.replace_html_to_mx_markdown - end - text = title.present? ? "<b>#{title}</b><br><br>#{content_text}" : content_text.to_s - - if content.has_attachments? - send_mx_attachments(content) - elsif content_text.present? || (!content_text.present? && title.present?) - send_mx_content(content, text) - break # If posts exists && content count >2, then for matrix PlatformPost content has first content id - end - next if Content.where(post: @post, has_attachments: false).count >= 2 - end + send_mx_attachments(attachment_content) if attachment_content.present? + send_mx_content(content, send_text) #unless send_text.empty? # Reserve empty text block for future text edit end def send_mx_content(content, text) @@ -84,7 +64,7 @@ def send_mx_content(content, text) options = channel_options(channel) if options[:onlylink] - send_mx_onlylink_post(content, channel, options) unless Content.where(post: @post, has_attachments: true).present? + send_mx_onlylink_post(content, channel, options) #unless Content.where(post: @post, platform: @platform, has_attachments: true).present? next end @@ -97,22 +77,23 @@ def send_mx_content(content, text) } msg = Matrix.post(channel[:server], channel[:matrix_token], method, data) identifier = { event_id: JSON.parse(msg)['event_id'], room_id: channel[:room], options: options } - PlatformPost.create!(identifier: identifier, platform: Platform.find_by(title: 'matrix'), post: @post, + PlatformPost.create!(identifier: identifier, platform: @platform, post: @post, content: content, channel_id: channel[:id]) end end def send_mx_attachments(content) - atts = upload_to_matrix(content) + # atts = upload_to_matrix_onechannel # Not used # TODO: ENCRYPTED FILE UPLOAD SUPPORT @channels.each do |channel| + atts = upload_to_matrix(channel) uploaded_atts = [] # Only link publish (for 'attachments' method lol) options = channel_options(channel) if options[:onlylink] - send_mx_onlylink_post(content, channel, options) + # send_mx_onlylink_post(content, channel, options) # because we always have 1 reserve text content next end @@ -147,24 +128,46 @@ def send_mx_attachments(content) type: type, blob_signed_id: uploaded_attachment[:blob_signed_id] }) end - PlatformPost.create!(identifier: uploaded_atts, platform: Platform.find_by(title: 'matrix'), post: @post, + PlatformPost.create!(identifier: uploaded_atts, platform: @platform, post: @post, content: content, channel_id: channel[:id]) end end - def upload_to_matrix(content) + def upload_to_matrix(channel) + atts = [] + # Upload attachment to matrix servers + @post.attachments.each do |attachment| + filename = attachment.blob.filename.to_s + content_type = attachment.blob.content_type + data = File.read(ActiveStorage::Blob.service.send(:path_for, attachment.blob.key)) + msg = Matrix.upload(channel[:server], channel[:matrix_token], filename, content_type, + data) + content_uri = JSON.parse(msg)['content_uri'] + width = attachment.blob[:metadata][:width].to_i + height = attachment.blob[:metadata][:height].to_i + blob_signed_id = attachment.blob.signed_id + size = attachment.blob.byte_size + next if msg.blank? + + atts.append({ content_uri: content_uri, + filename: filename, + size: size, + content_type: content_type, + width: width, + height: height, + blob_signed_id: blob_signed_id }) + end + atts + end + + # Not used. Faster than upload to each channel + def upload_to_matrix_onechannel atts = [] # Upload attachment to matrix servers - content.attachments.each do |attachment| + @post.attachments.each do |attachment| filename = attachment.blob.filename.to_s content_type = attachment.blob.content_type data = File.read(ActiveStorage::Blob.service.send(:path_for, attachment.blob.key)) - # КОСТЫЛЬ! Ну даже если разные токены, какая разница куда предварительно загружать? - # Главное чтобы серверы одианковые были, или даже просто связь между ними - # Ну ладно это угроза безопасности, но если у человека и так есть 2 токена, то не всё ли равно? - # Разве что что-то заапложенное обнаружит человек у которого есть один токен, то нет второго. - # Ну не знаю, это специфический случай. Возможно когда-нибудь сделаю его фикс. Когда-нибудь. - # Возможно. msg = Matrix.upload(@channels.first[:server], @channels.first[:matrix_token], filename, content_type, data) content_uri = JSON.parse(msg)['content_uri'] @@ -195,7 +198,8 @@ def channel_options(channel) def send_mx_onlylink_post(content, channel, options) post_link = "#{@base_url}/posts/#{@post.slug_url}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" - text = @post.title.present? ? "<b>#{@post.title}</b><br>#{full_post_link}" : full_post_link.to_s + text = @post.title.present? ? "<b>#{@post.title}</b><br><br>#{full_post_link}" : full_post_link.to_s + text.delete_suffix!("<br>") method = "rooms/#{channel[:room]}/send/m.room.message" data = { @@ -206,9 +210,9 @@ def send_mx_onlylink_post(content, channel, options) } msg = Matrix.post(channel[:server], channel[:matrix_token], method, data) identifier = { event_id: JSON.parse(msg)['event_id'], room_id: channel[:room], options: options } - PlatformPost.create!(identifier: identifier, platform: Platform.find_by(title: 'matrix'), post: @post, + PlatformPost.create!(identifier: identifier, platform: @platform, post: @post, content: content, channel_id: channel[:id]) rescue StandardError - Rails.logger.error("Failed create matrix message for chat #{channel[:id]} at #{Time.now.utc.iso8601}") + Rails.logger.error("Failed create matrix message for chat #{channel[:id]} at #{Time.now.utc.iso8601}".red) end end diff --git a/app/services/platform/send_post_to_telegram.rb b/app/services/platform/send_post_to_telegram.rb index c108a1c..babfe6e 100644 --- a/app/services/platform/send_post_to_telegram.rb +++ b/app/services/platform/send_post_to_telegram.rb @@ -58,23 +58,59 @@ def call post_text_blocks = text_blocks(text, max_first_post_length) - if @attachments.present? # Create first attachment post - attachment_content = Content.create!(user: @post.user, post: @post, has_attachments: true) - @attachments.each { |att| attachment_content.attachments.attach(att) } if @attachments.present? - attachment_content.upd_post if @attachments.present? + if post_text_blocks.present? + text_contents = post_text_blocks.map { |t| Content.create!(user: @post.user, post: @post, + text: t, platform: @platform, has_attachments: false) + } + else # Only title without text or attachments case + text_contents = [Content.create!(user: @post.user, post: @post, + text: "", platform: @platform, has_attachments: false) ] end - - post_text_blocks.each { |t| Content.create!(user: @post.user, post: @post, text: t) } if post_text_blocks.present? - + + use_attachment_channel = (1 == 1) # TMP!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + if use_attachment_channel + already_uploaded_media = [] + channel_with_roomatt = @channels.find{ |ch| ch[:room_attachments].present? && !ch[:room_attachments].empty? } + if channel_with_roomatt.present? + if already_uploaded_media.empty? + media = upload_to_attachment_channel(channel_with_roomatt) + already_uploaded_media = media + else + media = already_uploaded_media + end + else + # We select 'Upload to attachment channel', but channels with att room not found. So, we do each-channel upload + use_attachment_channel = false + end + end + @channels.each do |channel| options = channel_options(channel) + bot = get_tg_bot(channel) + if options[:onlylink] - send_tg_onlylink_post(channel, options) + send_tg_onlylink_post(bot, channel, options) next end - send_telegram_content(channel, options) if @post.contents.any? + if @post.attachments.present? + if !use_attachment_channel + media = prepare_media_to_upload() + end + + full_text = @post.title.present? ? "<b>#{@post.title}</b>\n\n#{@post.text}" : @post.text.to_s + has_caption = ((full_text.length < 1024) && !full_text.empty?) && @options["caption_#{channel[:id]}"] + + send_telegram_attachments(bot, channel, options, has_caption, media) + next if has_caption + end + + next unless text_contents.present? + text_contents.each_with_index do |text_content, index| + next if !title.present? && (!text_content.text.present? || text_content.text.empty?) + send_telegram_content(bot, channel, text_content, options, index) + end end end @@ -90,47 +126,43 @@ def text_blocks(text, length) end end - def send_telegram_content(channel, options) - bot = get_tg_bot(channel) - - full_text = @post.title.present? ? "<b>#{@post.title}</b>\n\n#{@post.text}" : @post.text.to_s - has_caption = ((full_text.length < 1024) && !full_text.empty?) && @post.contents.order(:id)[0][:has_attachments] && - @options["caption_#{channel[:id]}"] - - @post.contents.order(:id).each_with_index do |content, index| - has_attachments = content[:has_attachments] - first_message = @post.contents.order(:id)[0][:has_attachments] ? (index == 1) : (index == 0) - - text = @markdown.render(content[:text]) if content[:text].present? - text = text.html_to_tg_markdown if content[:text].present? - text = "<b>#{@post.title}</b>\n\n#{text}" if first_message && @post.title.present? && text.present? - - if has_attachments - send_telegram_attachments(bot, channel, options, content, has_caption) + def send_telegram_content(bot, channel, text_content, options, index) + first_message = text_content[:has_attachments] ? (index == 1) : (index == 0) + text = @markdown.render(text_content.text) + text = text.html_to_tg_markdown + if first_message && @post.title.present? + if text.present? + text = "<b>#{@post.title}</b>\n\n#{text}" else - next if has_caption - - @msg = bot.send_message({ chat_id: channel[:room], - text: text, - parse_mode: 'html', - disable_notification: !options[:enable_notifications] }) - - PlatformPost.create!(identifier: { chat_id: @msg['result']['chat']['id'], - message_id: @msg['result']['message_id'], - date: @msg['result']['date'], - options: options }, platform: @platform, - post: @post, content: content, channel_id: channel[:id]) + text = "#{@post.title}" end end + + @msg = bot.send_message({ chat_id: channel[:room], + text: text, + parse_mode: 'html', + disable_notification: !options[:enable_notifications] }) + + PlatformPost.create!(identifier: { chat_id: @msg['result']['chat']['id'], + message_id: @msg['result']['message_id'], + date: @msg['result']['date'], + options: options }, platform: @platform, + post: @post, content: text_content, channel_id: channel[:id] + ) rescue StandardError => e - Rails.logger.error("Failed create telegram message for chat #{channel[:id]} at #{Time.now.utc.iso8601}:\n#{e}") + Rails.logger.error("Failed create telegram message for chat #{channel[:id]} at #{Time.now.utc.iso8601}:\n#{e}".red) end - def send_telegram_attachments(bot, channel, options, content, has_caption) + def send_telegram_attachments(bot, channel, options, has_caption, media) full_text = @post.title.present? ? "<b>#{@post.title}</b>\n\n#{@post.text}" : @post.text.to_s text = full_text if has_caption - media = upload_to_telegram(bot, channel[:room_attachments], content) + attachment_content = @post.contents.where(platform: @platform).find{ |c| c.has_attachments } + unless attachment_content.present? + attachment_content = Content.create!(post: @post, user: @post.user, + platform: @platform, has_attachments: true) + end + msg_ids = [] # Видео и картиночки могут стакаться, остальное - нет begin @@ -173,15 +205,33 @@ def send_telegram_attachments(bot, channel, options, content, has_caption) end end PlatformPost.create!(identifier: msg_ids, platform: @platform, post: @post, - content: content, channel_id: channel[:id]) + content: attachment_content, channel_id: channel[:id]) rescue StandardError => e - Rails.logger.error("Failed create tg message (attachment) for chat #{channel[:room]} at #{Time.now.utc.iso8601}:\n#{e}") + Rails.logger.error("Failed create tg message (attachment) for chat #{channel[:room]} at #{Time.now.utc.iso8601}:\n#{e}".red) end end - def upload_to_telegram(bot, room_attachments, content) - attachment_channel = room_attachments - content.attachments.order('created_at ASC').map do |att| + def prepare_media_to_upload + @post.attachments.order('created_at ASC').map do |att| + blob_signed_id = att.blob.signed_id + file = File.open(ActiveStorage::Blob.service.send(:path_for, att.blob.key)) + if @images.include?(att.content_type) + { type: 'photo', media: file, blob_signed_id: blob_signed_id } + elsif @videos.include?(att.content_type) + { type: 'video', media: file, blob_signed_id: blob_signed_id } + elsif @audios.include?(att.content_type) + { type: 'audio', media: file, blob_signed_id: blob_signed_id } + else + { type: 'document', media: file, blob_signed_id: blob_signed_id } + end + end + end + + # Faster than upload to each channel, but requires attachment channel + def upload_to_attachment_channel(channel) + bot = get_tg_bot(channel) + attachment_channel = channel[:room_attachments] + @post.attachments.order('created_at ASC').map do |att| blob_signed_id = att.blob.signed_id file = File.open(ActiveStorage::Blob.service.send(:path_for, att.blob.key)) if @images.include?(att.content_type) @@ -198,7 +248,7 @@ def upload_to_telegram(bot, room_attachments, content) { type: 'document', media: msg['result']['document']['file_id'], blob_signed_id: blob_signed_id } end rescue StandardError => e - Rails.logger.error("Failed upload telegram message at #{Time.now.utc.iso8601}:\n#{e}") + Rails.logger.error("Failed upload telegram message at #{Time.now.utc.iso8601}:\n#{e}".red) end end @@ -214,9 +264,7 @@ def get_tg_bot(channel) Twilight::Application::CURRENT_TG_BOTS.dig((channel[:token]).to_s, :client) end - def send_tg_onlylink_post(channel, options) - bot = get_tg_bot(channel) - + def send_tg_onlylink_post(bot, channel, options) post_link = "#{@base_url}/posts/#{@post.slug_url}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" text = @post.title.present? ? "<b>#{@post.title}</b>\n\n#{full_post_link}" : full_post_link @@ -226,6 +274,13 @@ def send_tg_onlylink_post(channel, options) parse_mode: 'html', disable_notification: !options[:enable_notifications] }) + content = @post.contents.where(platform: @platform, has_attachments: false)&.first + content = @post.contents.where(platform: @platform, has_attachments: true)&.first if content.nil? + if content.nil? # Send 'link only' to platforms where post is empty (no text or attachments). Ok... + content = Content.create!(user: @post.user, post: @post, + text: text, platform: @platform, has_attachments: false) + end + PlatformPost.create!( identifier: { chat_id: msg['result']['chat']['id'], message_id: msg['result']['message_id'], @@ -233,9 +288,9 @@ def send_tg_onlylink_post(channel, options) options: options }, platform: @platform, post: @post, - content: @post.contents.first, channel_id: channel[:id] + content: content, channel_id: channel[:id] ) rescue StandardError => e - Rails.logger.error("Failed create telegram message for chat #{channel[:id]} at #{Time.current.utc.iso8601}:\n#{e}") + Rails.logger.error("Failed create telegram message for chat #{channel[:id]} at #{Time.current.utc.iso8601}:\n#{e}".red) end end diff --git a/app/services/platform/update_matrix_posts.rb b/app/services/platform/update_matrix_posts.rb index 15abc5e..e797052 100644 --- a/app/services/platform/update_matrix_posts.rb +++ b/app/services/platform/update_matrix_posts.rb @@ -11,9 +11,11 @@ def initialize(post, base_url, params) @base_url = base_url @title = post.title - @content = params[:post][:content] + @text = params[:post][:content] @deleted_attachments = @params[:deleted_attachments] - @attachments_count = @post.content_attachments&.count || 0 + @attachments_count = @post.attachments.count || 0 + + @platform = Platform.find_by(title: 'matrix') @markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, no_intra_emphasis: false, fenced_code_blocks: false, disable_indented_code_blocks: true, autolink: false, @@ -22,7 +24,6 @@ def initialize(post, base_url, params) def call platform_posts = @post.platform_posts.where(platform: Platform.where(title: 'matrix')) - need_delete_attachments = false platform_posts.joins(:content).where(contents: { has_attachments: true }).each do |platform_post| if platform_post.identifier.is_a?(Hash) && platform_post.identifier.dig('options', 'onlylink') @@ -33,57 +34,20 @@ def call end end - if @deleted_attachments.present? - attachments = @deleted_attachments.to_unsafe_h - del_att = attachments.select { |val| attachments[val] == '0' } - need_delete_attachments = true if del_att.any? - - platform_posts.joins(:content).where(contents: { has_attachments: true }).each do |platform_post| - next if platform_post.identifier.is_a?(Array) && platform_post.identifier[0].dig('options', 'onlylink') - next if platform_post.identifier.is_a?(Hash) && platform_post.identifier.dig('options', 'onlylink') - - matrix_token = platform_post.channel.token - server = platform_post.channel.options['server'] - - deleted_indexes = [] - del_att.each_key do |k| - attachment = platform_post[:identifier].select { |att| att['blob_signed_id'] == k } - i = platform_post[:identifier].index { |x| attachment.include?(x) } - method = "rooms/#{platform_post[:identifier][i]['room_id']}/redact/#{platform_post[:identifier][i]['event_id']}" - data = { reason: "Delete post ##{platform_post.post_id}" } - Matrix.post(server, matrix_token, method, data) - deleted_indexes.append(i) - end - if deleted_indexes.any? - new_params = platform_post[:identifier].reject.with_index { |_e, i| deleted_indexes.include? i } - new_params.present? ? platform_post.update!(identifier: new_params) : platform_post.delete - end - end - - end - - # Если есть изменения и не обновился в предыдущих платформах, то обновляем тут - if @content.length != @post.text.length - # отсутствие контента - это content? - content = @post.contents.where(has_attachments: false).first # first тому что matrix только 1 контент - - if content.present? - content.update(text: @content) - elsif content.nil? && !@content.empty? - Content.create!(user: @post.user, post: @post, text: @content) - end - end + delete_attachments(platform_posts) if @deleted_attachments.present? - text = - if @title.present? && @content.present? - "<b>#{@title}</b><br><br>#{@content}" - elsif @title.present? && @content.empty? - @post.title - else - @content - end - text = @markdown.render(text) + text = @markdown.render(@post.text) text = text.replace_html_to_mx_markdown if text.present? + text.delete_suffix!("<br>") + send_text = @post.title.present? ? "<b>#{@post.title}</b><br><br>#{text}" : text.to_s + + text_content = @post.contents.find{ |c| c.platform == @platform && !c.has_attachments } + if text_content.nil? + Content.create!(user: @post.user, post: @post, text: text, + has_attachments: true, platform: @platform) + else + text_content.update!(text: text) + end platform_posts.joins(:content).where(contents: { has_attachments: false }).each do |platform_post| room_id = platform_post[:identifier]['room_id'] @@ -100,13 +64,13 @@ def call data = { msgtype: 'm.text', format: 'org.matrix.custom.html', - body: text, - formatted_body: text, + body: send_text, + formatted_body: send_text, 'm.new_content': { msgtype: 'm.text', format: 'org.matrix.custom.html', - body: text, - formatted_body: text + body: send_text, + formatted_body: send_text }, 'm.relates_to': { event_id: event_id, @@ -115,53 +79,40 @@ def call } Matrix.post(server, matrix_token, method, data) end + end - # Fix if has telegram post && attachments has caption && update from nil to text, send msg - fix_content = platform_posts.joins(:content).where(contents: { has_attachments: false }).none? - - if fix_content # Постим недостающее сообщение с текстом - channel_ids = - @post.user.channels.where(platform: Platform.find_by(title: 'matrix')).map do |channel| - { id: channel.id, room: channel.room, token: channel.token, server: channel.options['server'] } - end - channel_ids.each do |channel| - method = "rooms/#{channel[:room]}/send/m.room.message" - data = { - msgtype: 'm.text', - format: 'org.matrix.custom.html', - body: text, - formatted_body: text - } - msg = Matrix.post(channel[:server], channel[:token], method, data) - identifier = { event_id: JSON.parse(msg)['event_id'], room_id: channel[:room] } - PlatformPost.create!(identifier: identifier, platform: Platform.find_by(title: 'matrix'), post: @post, - content: @post.contents.where(has_attachments: false).first, channel_id: channel[:id]) - end - # elsif fix_content && @title.empty? && @content.empty? # Delete if text not present - # platform_posts.joins(:content).where(contents: { has_attachments: false }).each do |platform_post| - # method = "rooms/#{platform_post[:identifier]["room_id"]}/redact/#{platform_post[:identifier]["event_id"]}" - # data = { reason: "Delete post ##{platform_post.post_id}" } - # Matrix.post(matrix_token, method, data) - # platform_post.delete - # end - end + def delete_attachments(platform_posts) + attachments = @deleted_attachments.to_unsafe_h + del_att = attachments.select { |val| attachments[val] == '0' } - unless need_delete_attachments && @deleted_attachments.present? && - @attachments_count == (@post.content_attachments&.count || 0) - return - end + platform_posts.joins(:content).where(contents: { has_attachments: true }).each do |platform_post| + next if platform_post.identifier.is_a?(Array) && platform_post.identifier[0].dig('options', 'onlylink') + next if platform_post.identifier.is_a?(Hash) && platform_post.identifier.dig('options', 'onlylink') + + matrix_token = platform_post.channel.token + server = platform_post.channel.options['server'] - @deleted_attachments.each do |attachment| - blob_id = ActiveStorage::Blob.find_signed(attachment[0])&.id # may deleted in tg update - @post.content_attachments.find_by(blob_id: blob_id).purge if blob_id.present? && attachment[1] == '0' + deleted_indexes = [] + del_att.each_key do |k| + attachment = platform_post[:identifier].select { |att| att['blob_signed_id'] == k } + i = platform_post[:identifier].index { |x| attachment.include?(x) } + method = "rooms/#{platform_post[:identifier][i]['room_id']}/redact/#{platform_post[:identifier][i]['event_id']}" + data = { reason: "Delete post ##{platform_post.post_id}" } + Matrix.post(server, matrix_token, method, data) + deleted_indexes.append(i) + end + if deleted_indexes.any? + new_params = platform_post[:identifier].reject.with_index { |_e, i| deleted_indexes.include? i } + new_params.present? ? platform_post.update!(identifier: new_params) : platform_post.delete + end end - @post.contents.first&.upd_post if @deleted_attachments.present? end def edit_mx_onlylink_post(platform_post, room_id, event_id) post_link = "#{@base_url}/posts/#{@post.slug_url}" full_post_link = "<a href=\"#{post_link}\">#{post_link}</a>" - text = @post.title.present? ? "<b>#{@post.title}</b><br>#{full_post_link}" : full_post_link.to_s + text = @post.title.present? ? "<b>#{@post.title}</b><br><br>#{full_post_link}" : full_post_link.to_s + text.delete_suffix!("<br>") matrix_token = platform_post.channel.token server = platform_post.channel.options['server'] diff --git a/app/services/platform/update_telegram_posts.rb b/app/services/platform/update_telegram_posts.rb index 6b7c02c..094f1ce 100644 --- a/app/services/platform/update_telegram_posts.rb +++ b/app/services/platform/update_telegram_posts.rb @@ -17,7 +17,7 @@ def initialize(post, base_url, params) @deleted_attachments = @params[:deleted_attachments]&.to_unsafe_h&.select { |_k, v| v == '0' }&.keys # need sort @deleted_attachments in the order of their posting platforms (grouping of pictures) - @attachments = @post.content_attachments&.map { |att| att.blob.signed_id } + @attachments = @post.attachments.map { |att| att.blob.signed_id } @deleted_attachments = @attachments&.select { |att| att.in?(@deleted_attachments) } @markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, no_intra_emphasis: false, fenced_code_blocks: false, @@ -295,7 +295,7 @@ def delete_attachments return if @deleted_attachments.blank? @deleted_attachments.each do |attachment| - @post.content_attachments.find_by(blob_id: ActiveStorage::Blob.find_signed!(attachment).id).purge + @post.attachments.find_by(blob_id: ActiveStorage::Blob.find_signed!(attachment).id).purge end @post.contents.first&.upd_post if @deleted_attachments.present? end @@ -348,7 +348,7 @@ def edit_media_caption(bot, identifier, text) # In an amicable way, if there are no attachments, you need to convert the media message to text, but this cannot be done # so the caption is removed if there are no attachments rescue StandardError - Rails.logger.error("Failed edit caption for telegram message at #{Time.now.utc.iso8601}") + Rails.logger.error("Failed edit caption for telegram message at #{Time.now.utc.iso8601}".red) end def post_options(post) diff --git a/app/services/send_post_to_platforms.rb b/app/services/send_post_to_platforms.rb index 55eac9f..20e4b63 100644 --- a/app/services/send_post_to_platforms.rb +++ b/app/services/send_post_to_platforms.rb @@ -22,15 +22,26 @@ def initialize(post, base_url, params) end end - def create_only_site_post - content = Content.create!(user: @post.user, post: @post, text: params[:post][:content], - has_attachments: @attachments.present?) - @attachments.each { |att| content.attachments.attach(att) } if @attachments.present? - content.upd_post if @attachments.present? + def create_blog_post + platform = Platform.find_by(title: 'blog') + + if @attachments.present? + attachments_content = Content.create!(user: @post.user, post: @post, text: nil, + has_attachments: true, platform: platform) + @attachments.each { |att| @post.attachments.attach(att) } + attachments_content.upd_post + end + + if params[:post][:content].present? && !params[:post][:content].empty? + content = Content.create!(user: @post.user, post: @post, text: params[:post][:content], + has_attachments: false, platform: platform) + content.upd_post + end end def call - return create_only_site_post if params[:channels].nil? || params[:channels].values.exclude?('1') + create_blog_post + return if params[:channels].nil? || params[:channels].values.exclude?('1') channel_ids = [] params[:channels].to_unsafe_h.select { |_k, v| v == '1' }.each do |k, _v| diff --git a/app/services/update_post_messages.rb b/app/services/update_post_messages.rb index f6f5cd1..8158baa 100644 --- a/app/services/update_post_messages.rb +++ b/app/services/update_post_messages.rb @@ -14,26 +14,40 @@ def initialize(post, base_url, params) @deleted_attachments = @params[:deleted_attachments] end - def update_only_site_posts + def update_blog_posts + platform = Platform.find_by(title: 'blog') + att_content = @post.contents.find{ |c| c.platform == platform && c.has_attachments } + if @attachments.present? - content = @post.contents.first # first content contains images - @attachments.each { |image| content.attachments.attach(image) } - content.update(has_attachments: true) unless content.has_attachments + if att_content.nil? + att_content = Content.create!(user: @post.user, post: @post, text: nil, + has_attachments: true, platform: platform) + end + @attachments.each { |image| @post.attachments.attach(image) } + att_content.upd_post end + if @deleted_attachments.present? @deleted_attachments.each do |attachment| if attachment[1] == '0' - @post.content_attachments.find_by(blob_id: ActiveStorage::Blob.find_signed!(attachment[0]).id).purge + @post.attachments.find_by(blob_id: ActiveStorage::Blob.find_signed!(attachment[0]).id).purge end end + att_content.upd_post + end + + text_content = @post.contents.find{ |c| c.platform == platform && !c.has_attachments } + if text_content.nil? + Content.create!(user: @post.user, post: @post, text: params[:post][:content], + has_attachments: false, platform: platform) + elsif text_content.present? && (text_content.text != params[:post][:content]) + text_content.update!(text: params[:post][:content]) end - # fix if platform was deleted, but content still exist - @post.contents.last(@post.contents.count - 1).each(&:delete) if @post.contents.count > 1 - @post.contents.update(text: params[:post][:content], has_attachments: @post.content_attachments.present?) end def call - return update_only_site_posts if @post.platform_posts.empty? + update_blog_posts + return if @post.platform_posts.empty? Thread.new do execution_context = Rails.application.executor.run! diff --git a/app/views/channels/edit.html.erb b/app/views/channels/edit.html.erb index 44ee31d..6428820 100644 --- a/app/views/channels/edit.html.erb +++ b/app/views/channels/edit.html.erb @@ -28,14 +28,14 @@ <%= f.label I18n.t("channels.channel_identifier") %> <%= f.text_field :room, { class: "form-control", required: true, placeholder: "-0123456789000" } %> </div> - <div class="col-xs-8 col-sm-4 col-md-9"> - <%= f.label I18n.t("channels.channel_identifier_for_att") %> - <%= f.text_field :room_attachments, { class: "form-control", placeholder: "-0123456789000", value: @current_channel.options["room_attachments"] } %> - </div> <div class="col-xs-8 col-sm-4 col-md-9"> <%= f.label I18n.t("channels.author_identifier") %> <%= f.text_field :author, { class: "form-control", placeholder: "1234567890", value: @current_channel.options["author"] } %> </div> + <div class="col-xs-8 col-sm-4 col-md-9"> + <%= f.label I18n.t("channels.channel_identifier_for_att") %> + <%= f.text_field :room_attachments, { class: "form-control", placeholder: "-0123456789000", value: @current_channel.options["room_attachments"] } %> + </div> <div class="col-xs-8 col-sm-4 col-md-9 field"><br> <%= f.check_box :import_from_tg, { checked: @current_channel.options["import_from_tg"] } %> <%= f.label I18n.t("channels.import_from_tg") %> diff --git a/app/views/channels/new.html.erb b/app/views/channels/new.html.erb index 49822a9..85c7719 100644 --- a/app/views/channels/new.html.erb +++ b/app/views/channels/new.html.erb @@ -24,13 +24,13 @@ <%= f.label I18n.t("channels.channel_identifier") %> <%= f.text_field :room, { class: "form-control", required: true, placeholder: "-0123456789000" } %> </div> - <div class="col-xs-8 col-sm-4 col-md-9"> - <%= f.label I18n.t("channels.channel_identifier_for_att") %> - <%= f.text_field :room_attachments, { class: "form-control", placeholder: "-0123456789000" } %> - </div> <div class="col-xs-8 col-sm-4 col-md-9"> <%= f.label I18n.t("channels.author_identifier") %> <%= f.text_field :author, { class: "form-control", placeholder: "1234567890" } %> + </div> + <div class="col-xs-8 col-sm-4 col-md-9"> + <%= f.label I18n.t("channels.channel_identifier_for_att") %> + <%= f.text_field :room_attachments, { class: "form-control", placeholder: "-0123456789000" } %> </div> <center><%= f.submit I18n.t("channels.add"), class: "col-md-3 btn btn-primary col-md-offset-3" %></center> </div> diff --git a/app/views/posts/_post.html.erb b/app/views/posts/_post.html.erb index e49569b..e93763f 100644 --- a/app/views/posts/_post.html.erb +++ b/app/views/posts/_post.html.erb @@ -1,4 +1,4 @@ -<%= display_attachments(post) if post.content_attachments.present? %> +<%= display_attachments(post) if post.attachments.present? %> <div class="posts-content"> <%= markdown(post.text) %> diff --git a/app/views/posts/_post_header.html.erb b/app/views/posts/_post_header.html.erb index 6fefab4..924d71b 100644 --- a/app/views/posts/_post_header.html.erb +++ b/app/views/posts/_post_header.html.erb @@ -9,7 +9,7 @@ <% if post.is_hidden %> <div class="fa fa-low-vision hidden-icon"></div> <% end %> - <% if post.content_attachments.present? && post.content_attachments.select{ |b| !b.image? && !b.video? && !b.audio? }.any? %> + <% if post.attachments.present? && post.attachments.select{ |b| !b.image? && !b.video? && !b.audio? }.any? %> <a><i class="fa fa-file right-small"></i></a> <% end %> </div> diff --git a/app/views/posts/edit.html.erb b/app/views/posts/edit.html.erb index 31a9ff3..dfcd005 100644 --- a/app/views/posts/edit.html.erb +++ b/app/views/posts/edit.html.erb @@ -10,7 +10,7 @@ <%= form_with(model: @current_post, local: true) do |f| %> <% platforms = @current_post.platforms %> - <% channels = Channel.joins(:platform_posts).where(platform_posts: { post: @current_post }) %> + <% channels = Channel.joins(:platform_posts).where(platform_posts: { post: @current_post }).uniq %> <div class="tab-content"> <div id="markdown" class="tab-pane in active title"> <%= f.text_field(:title, { class: 'form-control', placeholder: "#{I18n.t("posts.title_placeholder")}", value: @current_post.title }) %> @@ -31,7 +31,7 @@ </div> </div> <% end %> - <% attachments = @current_post.content_attachments %> + <% attachments = @current_post.attachments %> <% if attachments.present? %> <div class="content-attachments"> <% attachments.each do |att| %> @@ -60,7 +60,6 @@ <% if channels.any? %> <div class="colorful-checkboxes"> <% channels.each_with_index do |channel, index| %> - <% next if channels[index+1].present? && (channels[index+1].id == channel.id) # avoid matrix duplicates %> <% title = channel.options&.dig("title") %> <%=check_box("channels", channel.id, { :class=>"colorful-checkbox", :checked => true })%> <%= label_tag "channels_#{channel.id}", title&.capitalize %> diff --git a/app/views/posts/feed/_feed.html.erb b/app/views/posts/feed/_feed.html.erb index 361f824..90480b8 100644 --- a/app/views/posts/feed/_feed.html.erb +++ b/app/views/posts/feed/_feed.html.erb @@ -28,7 +28,7 @@ <%= markdown(post.text) %> </div> - <%if post.content_attachments&.any?%> + <%if post.attachments.any?%> <%=display_feed_attachments(post)%> <%end%> <div class="footer"> diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb index 68823c9..d01d217 100644 --- a/app/views/posts/index.html.erb +++ b/app/views/posts/index.html.erb @@ -15,7 +15,7 @@ <% @posts.each do |post| %> <article> <%= render partial: "posts/post_header", locals: { post: post } %> - <%= display_attachments(post) if post.content_attachments.present? %> + <%= display_attachments(post) if post.attachments.present? %> <div class="posts-content"><% if post.text.length > 512 %> <%= markdown(truncate(post.text, length: 512)) %> <%= link_to "...#{I18n.t("posts.read_more")}", "posts/#{post.slug_url}", class: "read-more-#{post.uuid}" %> diff --git a/app/views/posts/raw.html.erb b/app/views/posts/raw.html.erb index 82fafd5..649c7d2 100644 --- a/app/views/posts/raw.html.erb +++ b/app/views/posts/raw.html.erb @@ -2,7 +2,7 @@ <pre> <%= @current_post.title %> -<% if @current_post.content_attachments.present? %> +<% if @current_post.attachments.present? %> <% att = display_raw_attachments(@current_post) %> <%= ReverseMarkdown.convert(@markdown.render(att), unknown_tags: :pass_through) %> <% end %> diff --git a/app/views/posts/rss.builder b/app/views/posts/rss.builder index 05178dd..8b80107 100644 --- a/app/views/posts/rss.builder +++ b/app/views/posts/rss.builder @@ -21,7 +21,7 @@ xml.rss version: '2.0', 'xmlns:atom' => 'http://www.w3.org/2005/Atom' do text = post.text - attachments = post.content_attachments + attachments = post.attachments if attachments.present? attachments = attachments.map do |attachment| if attachment.image? diff --git a/config.ru b/config.ru index 3a58a56..3a33c16 100644 --- a/config.ru +++ b/config.ru @@ -10,6 +10,7 @@ begin if ActiveRecord::Base.connection.data_source_exists?('platforms') && ActiveRecord::Base.connection.data_source_exists?('channels') + Platform.find_or_initialize_by(title: 'blog').save Platform.find_or_initialize_by(title: 'telegram').save Platform.find_or_initialize_by(title: 'matrix').save diff --git a/config/locales/en.yml b/config/locales/en.yml index 5578cd4..efc48f6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -128,9 +128,9 @@ en: select_platform: Please, select platform bot_token: Bot token access_token: Access token - channel_identifier: Channel identifier - channel_identifier_for_att: Channel identifier for attachments - author_identifier: Author identifier (not bot) + channel_identifier: Channel ID + channel_identifier_for_att: Channel ID for preload attachments (Optional) + author_identifier: Author ID (not bot) api_server_address: API server address enable_comments: Enable comments? add: Add diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 9366da2..cd50bc6 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -127,9 +127,9 @@ ru: select_platform: Пожалуйста, выберите платформу bot_token: Токен бота access_token: Токен доступа - channel_identifier: Идентификатор канала - channel_identifier_for_att: Идентификатор канала для аттачментов - author_identifier: Идентификатор автора (не бота) + channel_identifier: ID канала + channel_identifier_for_att: ID канала для предзагрузки вложений (Не обязательно) + author_identifier: ID автора (не бота) api_server_address: Адрес сервера API enable_comments: Включить комментарии? add: Добавить diff --git a/db/migrate/20240822061838_add_platform_id_to_contents.rb b/db/migrate/20240822061838_add_platform_id_to_contents.rb new file mode 100644 index 0000000..539f636 --- /dev/null +++ b/db/migrate/20240822061838_add_platform_id_to_contents.rb @@ -0,0 +1,9 @@ +class AddPlatformIdToContents < ActiveRecord::Migration[7.0] + def up + add_reference :contents, :platform + end + + def down + remove_reference :contents, :platform + end +end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 0a6e51c..575a1f1 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -45,17 +45,17 @@ end end - describe '#content_attachments' do - context 'when content has no attachments' do - let!(:content) { create(:content, post: post) } - it { expect(subject.content_attachments).to eq(nil) } - end - - context 'when content has attachments' do - let!(:content_with_att) { create(:content, :with_attachment, post: post) } - it { expect(subject.content_attachments.attachments.last).to eq(content_with_att.attachments.last) } # wtf - end - end + #describe '#content_attachments' do + # context 'when content has no attachments' do + # let!(:content) { create(:content, post: post) } + # it { expect(subject.content_attachments).to eq(nil) } + # end + + # context 'when content has attachments' do + # let!(:content_with_att) { create(:content, :with_attachment, post: post) } + # it { expect(subject.content_attachments.attachments.last).to eq(content_with_att.attachments.last) } # wtf + # end + #end # describe '#get_posts' do # end diff --git a/spec/services/send_post_to_platforms_spec.rb b/spec/services/send_post_to_platforms_spec.rb index ffec171..48ace4f 100644 --- a/spec/services/send_post_to_platforms_spec.rb +++ b/spec/services/send_post_to_platforms_spec.rb @@ -42,7 +42,7 @@ it 'post has no attachments' do subject - expect(post.reload.content_attachments).to eq(nil) + expect(post.reload.attachments).to eq(nil) end it 'post has no platform posts' do diff --git a/update_log.md b/update_log.md index df85fd1..3929fb4 100644 --- a/update_log.md +++ b/update_log.md @@ -4,10 +4,16 @@ * Обновлён Ruby 2.7.7->3.3.2; * Поддержка OpenGraph для предпросмотра постов; +* Добавлен импорт постов при постинге из ТГ; * Добавлен счётчик просмотров поста; * Теперь посты имеют UUID вместо ID; * Добавлена генерация slug на основе названия поста; * Добавлена опция скрытия поста из ленты (их видит только автор); +* Измемены комментарии: + * Теперь комментарии имеют иерархию (на основе ответов); + * Возможность отвечать в комментариях: + * При создании комментария можно выбрать каналы (Пока доступно только для ТГ); + * При ответе на комментарии ответ автоматически постится в нужную платформу; * Обновлены темы: * Для тем Gruvbox, Twilight добавлена тёмная тема для feed; * Для темы по-умолчанию добавлена отдельно тёмная подтема для feed; From a5b6803e036e450c10113d31154214e8314f7bc2 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Fri, 30 Aug 2024 14:16:02 +0300 Subject: [PATCH 35/47] Tg update fix && improvements --- Gemfile | 1 + app/assets/stylesheets/base.scss | 9 +- .../stylesheets/default_Dark_feed_theme.scss | 2 +- app/assets/stylesheets/default_theme.scss | 4 +- app/assets/stylesheets/gruvbox_theme.scss | 3 +- app/assets/stylesheets/ruby_theme.scss | 5 +- app/assets/stylesheets/twilight_theme.scss | 3 +- app/controllers/posts_controller.rb | 23 +-- app/controllers/telegram_controller.rb | 21 ++- app/models/content.rb | 3 +- app/models/post.rb | 3 +- .../platform/send_comment_to_telegram.rb | 13 +- .../platform/update_telegram_posts.rb | 156 ++++++++++++------ app/services/send_post_to_platforms.rb | 2 + app/services/update_post_messages.rb | 3 +- app/views/posts/edit.html.erb | 31 +++- config/application.rb | 2 + db/migrate/20240825104720_create_versions.rb | 38 +++++ update_log.md | 3 + 19 files changed, 244 insertions(+), 81 deletions(-) create mode 100644 db/migrate/20240825104720_create_versions.rb diff --git a/Gemfile b/Gemfile index b6e08c1..d68e5f5 100644 --- a/Gemfile +++ b/Gemfile @@ -92,6 +92,7 @@ gem 'image_processing', '>= 1.12.2' gem 'mini_magick' gem 'nokogiri', '>= 1.11.0.rc4' gem 'open-uri' +gem 'paper_trail' gem 'pg' gem 'rake', '>= 13.2.1' gem 'redcarpet' diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index c8b6b7b..62a4c33 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -77,7 +77,7 @@ $color-white: #ffffff; $color-black: #000000; */ @mixin base($bg, $color-darkpurple, $color-code-bg, - $font, $font-link, $color-white, $color-black, + $font, $font-link, $comments-font, $color-white, $color-black, $color-link-light,$color-link,$color-link-dark,$color-avatar-bg,$color-avatar-border, $color-checkbox-channels-bg, $color-category-default, $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, @@ -825,7 +825,7 @@ $color-black: #000000; .comments-container{ font-size: 16px; background-color: $color-container-bg; - color: $color-feed-font; + color: $comments-font; box-sizing: border-box; /* max-width: 60%; */ margin: 2% auto; @@ -1359,8 +1359,13 @@ $color-black: #000000; text-align: center; } .comments-container{ + color: $color-feed-font; background: none; background-color: none; + + .comment-avatar{ + background: $color-feed-icon; + } } .comments-width{ max-width: 100%; diff --git a/app/assets/stylesheets/default_Dark_feed_theme.scss b/app/assets/stylesheets/default_Dark_feed_theme.scss index f63621c..285f72a 100644 --- a/app/assets/stylesheets/default_Dark_feed_theme.scss +++ b/app/assets/stylesheets/default_Dark_feed_theme.scss @@ -38,7 +38,7 @@ $color-feed-pages-active-font: white; @include base($bg, $color-darkpurple, $color-code-bg, - $font, $font-link, $color-white, $color-black, + $font, $font-link, $comments-font, $color-white, $color-black, $color-link-light,$color-link,$color-link-dark,$color-avatar-bg,$color-avatar-border, $color-checkbox-channels-bg, $color-category-default, $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, diff --git a/app/assets/stylesheets/default_theme.scss b/app/assets/stylesheets/default_theme.scss index d80be64..e5f7f98 100644 --- a/app/assets/stylesheets/default_theme.scss +++ b/app/assets/stylesheets/default_theme.scss @@ -45,6 +45,7 @@ $color-checkbox-unchecked-bg: #631119; $color-checkbox-text-unchecked: #df4655; $color-checkbox-focus: #1b1b1c; + $color-feed-bg: #ffffff; $color-feed-bg2: #eeeeee; $color-feed-font: #000000; @@ -97,12 +98,13 @@ $color-code-bg: #222426; $font: #f0f0f0; $font-link: #b3b3b3; +$comments-font: #f0f0f0; $color-white: #ffffff; $color-black: #000000; @include base($bg, $color-darkpurple, $color-code-bg, - $font, $font-link, $color-white, $color-black, + $font, $font-link, $comments-font, $color-white, $color-black, $color-link-light,$color-link,$color-link-dark,$color-avatar-bg,$color-avatar-border, $color-checkbox-channels-bg, $color-category-default, $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, diff --git a/app/assets/stylesheets/gruvbox_theme.scss b/app/assets/stylesheets/gruvbox_theme.scss index 114e364..2616af6 100644 --- a/app/assets/stylesheets/gruvbox_theme.scss +++ b/app/assets/stylesheets/gruvbox_theme.scss @@ -97,12 +97,13 @@ $color-code-bg: #32302f; $font: #ebdbb2; $font-link: #a89984; +$comments-font: #ebdbb2; $color-white: #fbf1c7; $color-black: #000000; @include base($bg, $color-darkpurple, $color-code-bg, - $font, $font-link, $color-white, $color-black, + $font, $font-link, $comments-font, $color-white, $color-black, $color-link-light,$color-link,$color-link-dark,$color-avatar-bg,$color-avatar-border, $color-checkbox-channels-bg, $color-category-default, $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, diff --git a/app/assets/stylesheets/ruby_theme.scss b/app/assets/stylesheets/ruby_theme.scss index 34e6346..4eeb8bc 100644 --- a/app/assets/stylesheets/ruby_theme.scss +++ b/app/assets/stylesheets/ruby_theme.scss @@ -48,7 +48,7 @@ $color-checkbox-focus: #1b1b1c; $color-feed-bg: #ffffff; $color-feed-bg2: #eeeeee; $color-feed-font: #000000; -$color-feed-icon: #9cccfd; /* #e0e0e0; */ +$color-feed-icon: #bd6d76; $color-feed-time: #bbb; $color-feed-date: #66b1ff; @@ -97,12 +97,13 @@ $color-code-bg: #222426; $font: #000000; $font-link: #b3b3b3; +$comments-font: #000000; $color-white: #fcfcff; $color-black: #000000; @include base($bg, $color-darkpurple, $color-code-bg, - $font, $font-link, $color-white, $color-black, + $font, $font-link, $comments-font, $color-white, $color-black, $color-link-light,$color-link,$color-link-dark,$color-avatar-bg,$color-avatar-border, $color-checkbox-channels-bg, $color-category-default, $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, diff --git a/app/assets/stylesheets/twilight_theme.scss b/app/assets/stylesheets/twilight_theme.scss index ed368be..6b3e00a 100644 --- a/app/assets/stylesheets/twilight_theme.scss +++ b/app/assets/stylesheets/twilight_theme.scss @@ -97,12 +97,13 @@ $color-code-bg: #222426; $font: #f0f0f0; $font-link: #b3b3b3; +$comments-font: #f0f0f0; $color-white: #ffffff; $color-black: #000000; @include base($bg, $color-darkpurple, $color-code-bg, - $font, $font-link, $color-white, $color-black, + $font, $font-link, $comments-font, $color-white, $color-black, $color-link-light,$color-link,$color-link-dark,$color-avatar-bg,$color-avatar-border, $color-checkbox-channels-bg, $color-category-default, $color-wrapper, $color-border, $color-nav-focus-bg,$color-gray, $color-form-textarea-bg, diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index eac7224..7957805 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -141,19 +141,22 @@ def update base_url = request.base_url channels_p = params['channels']&.to_unsafe_h - if channels_p.present? - channels_p.each do |k, v| - if v.to_i == 1 # TODO: make it? Need2fix duplicate content when creating! - # params["platforms"] = { k=>(v ? 1 : 0).to_s } - # SendPostToPlatforms.call(@post, base_url, params) - else - DeletePostMessages.call(current_post, k) - end - end + published_channels_list = {} + + published_channels = current_post.published_channels.each{ |ch| published_channels_list.merge!("#{ch.id}" => "1") } + new_channels_list = channels_p.reject{|k,v| (v == "0") || (published_channels_list[k] == v) } + deleted_channels_list = channels_p.reject{ |k,v| (v == "1") || (published_channels_list[k] == v) } + + deleted_channels_list.each do |k, v| + DeletePostMessages.call(current_post, k) + end + + if new_channels_list.any? + SendPostToPlatforms.call(current_post, base_url, params) end UpdatePostMessages.call(current_post, base_url, posts_params) - current_post.update(title: posts_params[:post][:title], is_hidden: params[:post][:is_hidden]) # ? + current_post.update(title: posts_params[:post][:title], is_hidden: params[:post][:is_hidden]) redirect_to current_post else diff --git a/app/controllers/telegram_controller.rb b/app/controllers/telegram_controller.rb index 3d384aa..9cfefd1 100644 --- a/app/controllers/telegram_controller.rb +++ b/app/controllers/telegram_controller.rb @@ -82,6 +82,7 @@ def import_from_tg(message, channel, from_channel) end def import_from_tg_withatt(message, channel, from_channel, attachment) + blog_platform = Platform.find_by(title: 'blog') caption = message.dig('caption') caption_entities = attachment.dig(:caption_entities) title = caption_entities.present? ? get_post_title(caption_entities, caption) : nil @@ -95,20 +96,26 @@ def import_from_tg_withatt(message, channel, from_channel, attachment) post = existing_pp.post content = post.contents.find{ |c| c.has_attachments == true } if content.nil? - content = Content.create!(text: caption, post: post, user: channel.user, has_attachments: true) + # For blog platform + Content.create!(text: caption, post: post, user: channel.user, has_attachments: true, platform: blog_platform) + # For tg + content = Content.create!(text: caption, post: post, user: channel.user, has_attachments: true, platform: channel.platform) end else Rails.logger.debug('TG: NEW POST WITH ATTACHMENTS'.green) if Rails.env.development? post = Post.create!(title: title, user: channel.user, privacy: 0) caption = caption.lstrip if caption.present? - content = Content.create!(text: caption, post: post, user: channel.user, has_attachments: true) + # For blog platform + Content.create!(text: caption, post: post, user: channel.user, has_attachments: true, platform: blog_platform) + # For tg + content = Content.create!(text: caption, post: post, user: channel.user, has_attachments: true, platform: channel.platform) end options = { "enable_notifications": true, "onlylink": false, "caption": caption.present? } file = URI.parse(attachment[:link]).open - content.attachments.attach(io: file, filename: attachment[:file_name], content_type: file.content_type) - att = content.attachments.find{ |att| att.byte_size == attachment[:file_size] } + post.attachments.attach(io: file, filename: attachment[:file_name], content_type: file.content_type) + att = post.attachments.find{ |att| att.byte_size == attachment[:file_size] } blob_signed_id = att.signed_id identifier = { chat_id: message['chat']['id'], @@ -135,6 +142,7 @@ def import_from_tg_withatt(message, channel, from_channel, attachment) end def import_from_tg_noatt(message, channel, from_channel) + blog_platform = Platform.find_by(title: 'blog') text = message.dig("text") entities = message.dig("entities") title = entities.present? ? get_post_title(entities, text) : nil @@ -152,7 +160,10 @@ def import_from_tg_noatt(message, channel, from_channel) text = text.lstrip if text.present? end - content = Content.create!(text: text, post: post, user: channel.user, has_attachments: false) + # For blog platform + Content.create!(text: text, post: post, user: channel.user, has_attachments: false, platform: blog_platform) + # For tg + content = Content.create!(text: text, post: post, user: channel.user, has_attachments: false, platform: channel.platform) options = { "enable_notifications": true, "onlylink": false, "caption": false } message_id = message.dig('message_id') diff --git a/app/models/content.rb b/app/models/content.rb index eba8785..112f417 100644 --- a/app/models/content.rb +++ b/app/models/content.rb @@ -1,13 +1,14 @@ # frozen_string_literal: true class Content < ApplicationRecord + has_paper_trail # validates :text, presence: true belongs_to :user belongs_to :post belongs_to :platform has_many :platform_posts - + #before_validation :check_platform_presence, on: %i[craete update] after_create_commit do upd_post end diff --git a/app/models/post.rb b/app/models/post.rb index e8eb906..d07dc3e 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Post < ApplicationRecord + has_paper_trail # validates :title, presence: true belongs_to :user belongs_to :category, optional: true @@ -60,7 +61,7 @@ def active_tags_names def platforms platforms = {} - Platform.all.find_each do |platform| + Platform.where.not(title: "blog").find_each do |platform| platforms.merge!(platform.title => PlatformPost.where(platform_id: platform.id, post: self).any?) end platforms diff --git a/app/services/platform/send_comment_to_telegram.rb b/app/services/platform/send_comment_to_telegram.rb index 6590c42..c4b1e82 100644 --- a/app/services/platform/send_comment_to_telegram.rb +++ b/app/services/platform/send_comment_to_telegram.rb @@ -50,15 +50,22 @@ def send_telegram_comment(channel) has_attachments = @params.dig(:comment, :attachments) platform_post = @current_post.platform_posts.find{ |pp| pp.channel_id == channel[:id] } - chat_id = platform_post.identifier["chat_id"] - message_id = platform_post.identifier["linked_chat_message_id"] + + if platform_post.identifier.is_a?(Array) + chat_id = platform_post.identifier[0]["chat_id"] + message_id = platform_post.identifier[0]["linked_chat_message_id"] + elsif platform_post.identifier.is_a?(Hash) + chat_id = platform_post.identifier["chat_id"] + message_id = platform_post.identifier["linked_chat_message_id"] + end + linked_chat_id = channel[:linked_chat_id] # || get_linked_chat(bot, chat_id) if linked_chat_id.nil? || message_id.nil? return Rails.logger.error("Can't get linked group chat_id or linked message_id for channel #{channel[:id]} at #{Time.now.utc.iso8601}!".red) end if @params[:comment][:parent_id].to_i > 0 - parent = current_post.comments.find_by_id(@params[:comment].delete(:parent_id)) + parent = @current_post.comments.find_by_id(@params[:comment].delete(:parent_id)) linked_chat_id = parent.identifier["chat_id"] message_id = parent.identifier["message_id"] parent_id = parent.id diff --git a/app/services/platform/update_telegram_posts.rb b/app/services/platform/update_telegram_posts.rb index 094f1ce..d4ebde0 100644 --- a/app/services/platform/update_telegram_posts.rb +++ b/app/services/platform/update_telegram_posts.rb @@ -3,22 +3,24 @@ class Platform::UpdateTelegramPosts prepend SimpleCommand - attr_accessor :post, :params + attr_accessor :post, :params, :old_title - def initialize(post, base_url, params) + def initialize(post, base_url, params, old_title) @params = params @post = post @base_url = base_url @platform = Platform.find_by(title: 'telegram') + #@old_title = @post.versions.any? ? (@post.versions.last.reify&.title).to_s : @post.title + @old_title = old_title @new_title = params[:post][:title] @new_text = params[:post][:content] @deleted_attachments = @params[:deleted_attachments]&.to_unsafe_h&.select { |_k, v| v == '0' }&.keys # need sort @deleted_attachments in the order of their posting platforms (grouping of pictures) - @attachments = @post.attachments.map { |att| att.blob.signed_id } - @deleted_attachments = @attachments&.select { |att| att.in?(@deleted_attachments) } + #@attachments = @post.attachments.map { |att| att.blob.signed_id } + #@deleted_attachments = @attachments&.select { |att| att.in?(@deleted_attachments) } @markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, no_intra_emphasis: false, fenced_code_blocks: false, disable_indented_code_blocks: true, autolink: false, @@ -28,9 +30,9 @@ def initialize(post, base_url, params) def call delete_attachments if @deleted_attachments.present? - old_text = @post.text.to_s + old_text = telegram_text().to_s - old_max_first_post_length = @post.title.present? ? (4096 - "<b>#{@post.title}</b>\n\n".length) : 4096 + old_max_first_post_length = @old_title.present? ? (4096 - "<b>#{@old_title}</b>\n\n".length) : 4096 new_max_first_post_length = @new_title.present? ? (4096 - "<b>#{@new_title}</b>\n\n".length) : 4096 old_post_text_blocks = text_blocks(old_text, old_max_first_post_length) @@ -40,23 +42,18 @@ def call return if new_post_text_blocks.nil? && old_post_text_blocks.nil? # Something changed - if ((new_post_text_blocks != old_post_text_blocks) || (@new_title.length != @post.title.length)) && check_caption + if ((new_post_text_blocks != old_post_text_blocks) || (@new_title.length != @old_title.length)) && check_caption return # false if no caption or new length > 1024 (to create contents) end # If, during caption check the new text length exceeded 1024 characters, then we deleted the content # So the old text also needs to be updated; - if old_text != @post.text.to_s - old_text = @post.text.to_s + if old_text != telegram_text().to_s + old_text = telegram_text().to_s old_post_text_blocks = text_blocks(old_text, old_max_first_post_length) end if new_post_text_blocks.nil? - options = post_options(@post) - if options[:onlylink] - @post.contents.where(has_attachments: false).destroy_all - return - end degree_index = 0 old_post_text_blocks.each_with_index do |_old_block, i| remove_content(i - degree_index) @@ -66,7 +63,7 @@ def call new_post_text_blocks.each_with_index { |new_block, i| add_content(new_block, i) if new_block.present? } elsif new_post_text_blocks.count >= old_post_text_blocks.count new_post_text_blocks.each_with_index do |new_block, i| - next if (new_block == old_post_text_blocks[i]) && (@new_title == @post.title) + next if (new_block == old_post_text_blocks[i]) && (@new_title == @old_title) update_content(new_block, i) if old_post_text_blocks[i].present? add_content(new_block, i) if old_post_text_blocks[i].nil? @@ -75,7 +72,7 @@ def call degree_index = 0 old_post_text_blocks.each_with_index do |old_block, i| degree_index += 1 - next if (old_block == new_post_text_blocks[i]) && (@new_title == @post.title) + next if (old_block == new_post_text_blocks[i]) && (@new_title == @old_title) degree_index -= i if degree_index == i @@ -83,6 +80,7 @@ def call remove_content(i - degree_index) if new_post_text_blocks[i].nil? end end + check_empty_content() end def text_blocks(text, length) @@ -98,19 +96,27 @@ def text_blocks(text, length) end def add_content(new_block, index) - content = Content.create!(user: @post.user, post: @post, text: new_block) - - options = post_options(@post) - return if options[:onlylink] + content = Content.create!(user: @post.user, post: @post, text: new_block, platform: @platform, has_attachments: false) @post.published_channels.where(platform: @platform).each do |channel| + options = post_options(@post, channel) + next if options[:onlylink] + bot = get_tg_bot(channel) first_message = (index == 0) new_block = @markdown.render(new_block) if new_block.present? new_block = new_block.html_to_tg_markdown if new_block.present? - new_block = "<b>#{@new_title}</b>\n\n#{new_block}" if first_message && @new_title.present? && new_block.present? + + if first_message && @new_title.present? + if new_block.present? + new_block = "<b>#{@new_title}</b>\n\n#{new_block}" + else + new_block = "#{@new_title}" + end + end + #new_block = "<b>#{@new_title}</b>\n\n#{new_block}" if first_message && @new_title.present? && new_block.present? @msg = bot.send_message({ chat_id: channel[:room], text: new_block, @@ -130,13 +136,11 @@ def add_content(new_block, index) end def update_content(new_text, index) - content = @post.contents.where(has_attachments: false).order(:id)[index] + content = @post.contents.where(platform: @platform, has_attachments: false).order(:id)[index] content.update!(text: new_text) - options = post_options(@post) - return if options[:onlylink] - @post.platform_posts.where(content: content, platform: @platform).each do |platform_post| + next if pp_onlylink(platform_post) bot = get_tg_bot(platform_post) first_message = (index == 0) @@ -144,7 +148,14 @@ def update_content(new_text, index) new_text = @markdown.render(new_text) if new_text.present? new_text = new_text.html_to_tg_markdown if new_text.present? text = new_text.to_s - text = "<b>#{@new_title}</b>\n\n#{new_text}" if first_message && @new_title.present? && new_text.present? + + if first_message && @new_title.present? + if text.present? + text = "<b>#{@new_title}</b>\n\n#{text}" + else + text = "#{@new_title}" + end + end bot.edit_message_text({ chat_id: platform_post.identifier['chat_id'], message_id: platform_post.identifier['message_id'], @@ -154,20 +165,15 @@ def update_content(new_text, index) end def check_onlylink - options = post_options(@post) - return false unless options[:onlylink] - - # It is assumed that the onlylink option will not have 2+ posts or 4096+ characters - - # and there will be no extra post platforms, respectively; - if @new_title != @post.title + if @new_title != @old_title platform_posts = PlatformPost.where(post: @post, platform: @platform) platform_posts.each do |platform_post| - bot = get_tg_bot(platform_post) - update_onlylink(bot, platform_post) + if pp_onlylink(platform_post) + bot = get_tg_bot(platform_post) + update_onlylink(bot, platform_post) + end end end - - options[:onlylink] end def update_onlylink(bot, platform_post) @@ -182,7 +188,7 @@ def update_onlylink(bot, platform_post) end def check_caption - content_with_att = @post.contents.select(&:has_attachments?)&.first + content_with_att = @post.contents.where(platform: @platform).select(&:has_attachments?)&.first has_platform_post_with_caption = false if content_with_att.present? @@ -222,7 +228,7 @@ def check_caption end # Remove all contents where is no platform posts (in caption case its all contents where is no attachments). # It eliminates next content creation errors. Not perfect solution but works. - @post.contents.where(has_attachments: false).destroy_all + @post.contents.where(platform: @platform, has_attachments: false).destroy_all # Make contents, just run further false # Length looks good @@ -232,7 +238,14 @@ def check_caption text = @markdown.render(@new_text) if @new_text.present? text = text.html_to_tg_markdown if text.present? - text = "<b>#{@new_title}</b>\n\n#{text}" if @new_title.present? && text.present? + + if @new_title.present? + if text.present? + text = "<b>#{@new_title}</b>\n\n#{text}" + else + text = "#{@new_title}" + end + end @post.platform_posts.where(content: content_with_att, platform: @platform).each do |platform_post| bot = get_tg_bot(platform_post) @@ -245,9 +258,10 @@ def check_caption end def remove_content(index) - content = @post.contents.where(has_attachments: false).order(:id)[index] + content = @post.contents.where(platform: @platform, has_attachments: false).order(:id)[index] - platform_posts = @post.platform_posts.where(content: content, platform: @platform) + onlylink_pps = @post.platform_posts.select{ |pp| pp_onlylink(pp) }.map(&:id) + platform_posts = @post.platform_posts.where(content: content, platform: @platform).where.not(id: onlylink_pps) Platform::DeleteTelegramPosts.call(platform_posts) PlatformPost.where(platform: platform_posts, post: @post).destroy_all @@ -292,12 +306,14 @@ def delete_attachments new_identifier.empty? ? platform_post.delete : platform_post.update!(identifier: new_identifier) end - return if @deleted_attachments.blank? + #return if @deleted_attachments.blank? - @deleted_attachments.each do |attachment| - @post.attachments.find_by(blob_id: ActiveStorage::Blob.find_signed!(attachment).id).purge - end - @post.contents.first&.upd_post if @deleted_attachments.present? + #@deleted_attachments.each do |attachment| + #@post.attachments.find_by(blob_id: ActiveStorage::Blob.find_signed!(attachment).id).purge + #end + #@post.contents.first&.upd_post if @deleted_attachments.present? + rescue StandardError + Rails.logger.error("Failed to delete telegram attachments message at #{Time.now.utc.iso8601}".red) end def move_caption(bot, platform_post, deleted_indexes) @@ -332,7 +348,7 @@ def move_caption(bot, platform_post, deleted_indexes) end future_identifier = platform_post.identifier[future_identifier_index] - text = @post.title.present? ? "<b>#{@post.title}</b>\n\n#{@post.text}" : @post.text.to_s + text = @new_title.present? ? "<b>#{@new_title}</b>\n\n#{@post.text}" : @post.text.to_s markdown_text = @markdown.render(text) markdown_text = markdown_text.html_to_tg_markdown @@ -340,8 +356,27 @@ def move_caption(bot, platform_post, deleted_indexes) future_identifier end + def check_empty_content + if @deleted_attachments.present? + att_content = @post.contents.find{ |c| c.platform == @platform && c.has_attachments } + if att_content.present? && att_content.platform_posts.count.zero? && @post.attachments.count.zero? + att_content.destroy + end + end + #contents = @post.contents.select{ |c| c.platform == @platform && !c.has_attachments } + # contents.each do |content| + # if content.platform_posts.count.zero? + # content.destroy + # end + #end + end + def edit_media_caption(bot, identifier, text) - media = { type: identifier['type'], media: identifier['file_id'], caption: text, parse_mode: 'html' } # photo type ??? + media = { type: identifier['type'], media: identifier['file_id'], parse_mode: 'html' } # photo type ??? + if text.present? && !text.empty? + media.merge!(caption: text) + end + bot.edit_message_media({ chat_id: identifier['chat_id'], message_id: identifier['message_id'], media: media }) @@ -351,20 +386,39 @@ def edit_media_caption(bot, identifier, text) Rails.logger.error("Failed edit caption for telegram message at #{Time.now.utc.iso8601}".red) end - def post_options(post) + def post_options(post, channel) # Get last platform post options if exists - platform_post = post.platform_posts.select { |p| p.platform == @platform }&.last + platform_post = post.platform_posts.select { |p| p.platform == @platform && p.channel == channel }&.last platform_identifier = platform_post&.identifier.is_a?(Array) ? platform_post&.identifier&.first : platform_post&.identifier platform_options = platform_identifier&.dig('options') notification = platform_options&.dig('enable_notifications') || false - onlylink = platform_options&.dig('onlylink') || false + onlylink = pp_onlylink(platform_post) #platform_options&.dig('onlylink') || false { enable_notifications: notification, onlylink: onlylink, caption: false } end + def pp_onlylink(platform_post) + is_onlylink = false + ident = platform_post.identifier + if ident.is_a?(Array) + is_onlylink = true if ident.find{ |i| i.dig('options', 'onlylink') }.present? + elsif ident.is_a?(Hash) + is_onlylink = true if ident.dig('options', 'onlylink') + end + is_onlylink + end + # PlatformPost or Channel support def get_tg_bot(object) object = object.channel if object.is_a?(PlatformPost) Twilight::Application::CURRENT_TG_BOTS&.dig(object.token.to_s, :client) end + + def telegram_text + text = '' + @post.contents.where(platform: @platform).order(:id).each do |msg| + text += msg[:text] if msg[:text].present? + end + text + end end diff --git a/app/services/send_post_to_platforms.rb b/app/services/send_post_to_platforms.rb index 20e4b63..97dac3f 100644 --- a/app/services/send_post_to_platforms.rb +++ b/app/services/send_post_to_platforms.rb @@ -24,6 +24,8 @@ def initialize(post, base_url, params) def create_blog_post platform = Platform.find_by(title: 'blog') + # return when update post && create for new channels + return if @post.contents.find{|c| c.platform == platform }.present? if @attachments.present? attachments_content = Content.create!(user: @post.user, post: @post, text: nil, diff --git a/app/services/update_post_messages.rb b/app/services/update_post_messages.rb index 8158baa..b112b33 100644 --- a/app/services/update_post_messages.rb +++ b/app/services/update_post_messages.rb @@ -46,6 +46,7 @@ def update_blog_posts end def call + old_title = @post.title update_blog_posts return if @post.platform_posts.empty? @@ -53,7 +54,7 @@ def call execution_context = Rails.application.executor.run! posted_platforms = @post.platforms - Platform::UpdateTelegramPosts.call(@post, @base_url, params) if posted_platforms['telegram'] + Platform::UpdateTelegramPosts.call(@post, @base_url, params, old_title) if posted_platforms['telegram'] Platform::UpdateMatrixPosts.call(@post, @base_url, params) if posted_platforms['matrix'] ensure execution_context&.complete! diff --git a/app/views/posts/edit.html.erb b/app/views/posts/edit.html.erb index dfcd005..64d1787 100644 --- a/app/views/posts/edit.html.erb +++ b/app/views/posts/edit.html.erb @@ -66,7 +66,36 @@ <% end %> </div> <% else %> - <p><%= I18n.t("posts.channels_not_found") %></p> + <% if (platforms.empty? || platforms.values.exclude?(true)) && @current_user.channels.where(enabled: true).any? %> + <div class="colorful-checkboxes"> + <% @current_user.channels.where(enabled: true).each do |channel| %> + <% title = channel.options&.dig("title") %> + <% if channel.platform.title == "telegram" %> + <%=check_box("channels", channel.id, { "data-toggle" => "collapse", "data-target"=>"#options-#{channel.id}", :class=>"colorful-checkbox", :checked => false })%> + <%= label_tag "channels_#{channel.id}", title&.capitalize %> + <div id="options-<%=channel.id%>" class="collapse"> + * <%=check_box("options", "enable_notifications_#{channel.id}", { :id=> "enable_notifications_#{channel.id}", :class=>"colorful-checkbox", :checked => channel.options["notifications_enabled"] })%> + <%= label_tag "enable_notifications_#{channel.id}", "Enable notifications?" %> + + * <%=check_box("options", "onlylink_#{channel.id}", { :id=> "onlylink_#{channel.id}", :class=>"colorful-checkbox", :checked => false })%> + <%= label_tag "onlylink_#{channel.id}", "Publish only link?" %> + + * <%=check_box("options", "caption_#{channel.id}", { :id=> "caption_#{channel.id}", :class=>"colorful-checkbox", :checked => false })%> + <%= label_tag "caption_#{channel.id}", "Text is caption (if available?)" %> + </div> + <% else %> + <%=check_box("channels", channel.id, { "data-toggle" => "collapse", "data-target"=>"#options-#{channel.id}", :class=>"colorful-checkbox", :checked => false })%> + <%= label_tag "channels_#{channel.id}", title&.capitalize %> + <div id="options-<%=channel.id%>" class="collapse"> + * <%=check_box("options", "onlylink_#{channel.id}", { :id=> "onlylink_#{channel.id}", :class=>"colorful-checkbox", :checked => false })%> + <%= label_tag "onlylink_#{channel.id}", "Publish only link?" %> + </div> + <% end %> + <br> + <% end %> + </div> + <% end %> + <!-- <p><%= I18n.t("posts.channels_not_found") %></p> --> <% end %> <div class="form-group"> diff --git a/config/application.rb b/config/application.rb index 80ecda8..39871d1 100644 --- a/config/application.rb +++ b/config/application.rb @@ -20,6 +20,8 @@ class Application < Rails::Application config.active_storage.variant_processor = :mini_magick + config.active_record.yaml_column_permitted_classes = [Time] # PaperTrail fix + require 'ext/string' require 'ext/matrix' require 'ext/zip_file_generator' diff --git a/db/migrate/20240825104720_create_versions.rb b/db/migrate/20240825104720_create_versions.rb new file mode 100644 index 0000000..df8b0a3 --- /dev/null +++ b/db/migrate/20240825104720_create_versions.rb @@ -0,0 +1,38 @@ +# This migration creates the `versions` table, the only schema PT requires. +# All other migrations PT provides are optional. +class CreateVersions < ActiveRecord::Migration[7.0] + + # The largest text column available in all supported RDBMS is + # 1024^3 - 1 bytes, roughly one gibibyte. We specify a size + # so that MySQL will use `longtext` instead of `text`. Otherwise, + # when serializing very large objects, `text` might not be big enough. + TEXT_BYTES = 1_073_741_823 + + def change + create_table :versions do |t| + t.string :item_type, null: false + t.bigint :item_id, null: false + t.string :event, null: false + t.string :whodunnit + t.text :object, limit: TEXT_BYTES + + # Known issue in MySQL: fractional second precision + # ------------------------------------------------- + # + # MySQL timestamp columns do not support fractional seconds unless + # defined with "fractional seconds precision". MySQL users should manually + # add fractional seconds precision to this migration, specifically, to + # the `created_at` column. + # (https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html) + # + # MySQL users should also upgrade to at least rails 4.2, which is the first + # version of ActiveRecord with support for fractional seconds in MySQL. + # (https://github.com/rails/rails/pull/14359) + # + # MySQL users should use the following line for `created_at` + # t.datetime :created_at, limit: 6 + t.datetime :created_at + end + add_index :versions, %i[item_type item_id] + end +end diff --git a/update_log.md b/update_log.md index 3929fb4..59cec86 100644 --- a/update_log.md +++ b/update_log.md @@ -2,6 +2,8 @@ ### 2024-0x-xx => 1.1 +**Крупное обновление! Из-за изменения логики работы нет обратной совместимости, необходимо сбросить старые данные!** + * Обновлён Ruby 2.7.7->3.3.2; * Поддержка OpenGraph для предпросмотра постов; * Добавлен импорт постов при постинге из ТГ; @@ -20,6 +22,7 @@ * Изменена тема 'Ruby'; * Для feed теперь также есть счётчик просмотров и комментарии; * Предпросмотр темы через параметр `?theme=<название>` в URL-адресе; +* Возможность постить в платформы, если нет существующих постов в платформах. * Добавлены ссылки на каналы платформ в шапке поста; * Исправлен баг с плохо видимой каптчей; * Исправлен баги постинга в Matrix; From 4cd39b628d52e4356b11f52000ff5c7081e17c25 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sun, 1 Sep 2024 11:37:03 +0300 Subject: [PATCH 36/47] Hide imported posts option by default --- app/controllers/telegram_controller.rb | 10 +++++++--- app/services/check_channel.rb | 5 ++++- app/views/channels/edit.html.erb | 8 ++++++-- app/views/channels/new.html.erb | 12 ++++++++---- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/app/controllers/telegram_controller.rb b/app/controllers/telegram_controller.rb index 9cfefd1..c4b7c15 100644 --- a/app/controllers/telegram_controller.rb +++ b/app/controllers/telegram_controller.rb @@ -66,7 +66,7 @@ def channel_post(message) return delete_chat_photo(channel) end - import_option = channel.options.dig("import_from_tg") + import_option = channel.options.dig("import_from_tg", "enabled") import_from_tg(message, channel, from_channel) if import_option.present? && import_option end @@ -103,7 +103,9 @@ def import_from_tg_withatt(message, channel, from_channel, attachment) end else Rails.logger.debug('TG: NEW POST WITH ATTACHMENTS'.green) if Rails.env.development? - post = Post.create!(title: title, user: channel.user, privacy: 0) + is_hidden = channel.options.dig("import_from_tg", "hide_by_default") + is_hidden = is_hidden.present? && is_hidden + post = Post.create!(title: title, user: channel.user, privacy: 0, is_hidden: is_hidden) caption = caption.lstrip if caption.present? # For blog platform @@ -156,7 +158,9 @@ def import_from_tg_noatt(message, channel, from_channel) post = existing_pp.post else Rails.logger.debug('TG: NEW POST'.green) if Rails.env.development? - post = Post.create!(title: title, user: channel.user, privacy: 0) + is_hidden = channel.options.dig("import_from_tg", "hide_by_default") + is_hidden = is_hidden.present? && is_hidden + post = Post.create!(title: title, user: channel.user, privacy: 0, is_hidden: is_hidden) text = text.lstrip if text.present? end diff --git a/app/services/check_channel.rb b/app/services/check_channel.rb index 07f8b19..094dc9f 100644 --- a/app/services/check_channel.rb +++ b/app/services/check_channel.rb @@ -70,7 +70,10 @@ def check_telegram options[:author] = author if author.present? options[:notifications_enabled] = (params[:channel][:enable_notifications] == '1') - options[:import_from_tg] = (params[:channel][:import_from_tg] == '1') + + options[:import_from_tg] = {} + options[:import_from_tg][:enabled] = (params[:channel][:import_from_tg] == '1') + options[:import_from_tg][:hide_by_default] = (params[:channel][:hide_by_default] == '1') # Comments diff --git a/app/views/channels/edit.html.erb b/app/views/channels/edit.html.erb index 6428820..0bc4a54 100644 --- a/app/views/channels/edit.html.erb +++ b/app/views/channels/edit.html.erb @@ -36,9 +36,13 @@ <%= f.label I18n.t("channels.channel_identifier_for_att") %> <%= f.text_field :room_attachments, { class: "form-control", placeholder: "-0123456789000", value: @current_channel.options["room_attachments"] } %> </div> - <div class="col-xs-8 col-sm-4 col-md-9 field"><br> - <%= f.check_box :import_from_tg, { checked: @current_channel.options["import_from_tg"] } %> + <div class="col-xs-8 col-sm-4 col-md-9 field"> + <%= f.check_box("import_from_tg", { "data-toggle" => "collapse", "data-target"=>"#import_from_tg", :class=>"colorful-checkbox", :checked => @current_channel.options.dig("import_from_tg","enabled") })%> <%= f.label I18n.t("channels.import_from_tg") %> + <div id="import_from_tg" class="collapse"> + * <%= f.check_box("hide_by_default", { :id=> "hide_by_default", :class=>"colorful-checkbox", :checked => @current_channel.options.dig("import_from_tg","hide_by_default") })%> + <%= f.label I18n.t("channels.hidden_by_default") %> + </div> </div> </div> <h3><%= I18n.t("channels.options") %></h3> diff --git a/app/views/channels/new.html.erb b/app/views/channels/new.html.erb index 85c7719..4f93079 100644 --- a/app/views/channels/new.html.erb +++ b/app/views/channels/new.html.erb @@ -31,6 +31,14 @@ <div class="col-xs-8 col-sm-4 col-md-9"> <%= f.label I18n.t("channels.channel_identifier_for_att") %> <%= f.text_field :room_attachments, { class: "form-control", placeholder: "-0123456789000" } %> + </div> + <div class="col-xs-8 col-sm-4 col-md-9 field"> + <%=check_box("import_from_tg", "enabled", { "data-toggle" => "collapse", "data-target"=>"#import_from_tg", :class=>"colorful-checkbox", :checked => false })%> + <%= label_tag "import_from_tg", I18n.t("channels.import_from_tg") %> + <div id="import_from_tg" class="collapse"> + * <%=check_box("import_from_tg", "hide_by_default", { :id=> "hide_by_default", :class=>"colorful-checkbox", :checked => false })%> + <%= label_tag "hide_by_default", I18n.t("channels.hidden_by_default") %> + </div> </div> <center><%= f.submit I18n.t("channels.add"), class: "col-md-3 btn btn-primary col-md-offset-3" %></center> </div> @@ -50,10 +58,6 @@ <%= f.label I18n.t("channels.api_server_address") %> <%= f.text_field :server, { class: "form-control", placeholder: "https://matrix.org/_matrix/", value: "https://matrix.org/_matrix/" } %> </div> - <div class="col-xs-8 col-sm-4 col-md-9 field"> - <%= f.check_box :import_from_tg %> - <%= f.label I18n.t("channels.import_from_tg") %> - </div> <center><%= f.submit I18n.t("channels.add"), class: "col-md-3 btn btn-primary col-md-offset-3" %></center> </div> </div> From b35400281a7e52c16978005140a8d1315aa5e0d0 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sun, 1 Sep 2024 12:14:41 +0300 Subject: [PATCH 37/47] First run setup --- app/controllers/application_controller.rb | 9 +++++++++ app/controllers/users/registrations_controller.rb | 3 +++ config/credentials.yml | 2 ++ config/locales/en.yml | 2 ++ config/locales/ru.yml | 2 ++ 5 files changed, 18 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 23bb8b9..303df2e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base before_action :set_locale before_action :set_tags before_action :configure_permitted_parameters, if: :devise_controller? + before_action :check_first_run # reset captcha code after each request for security after_action :reset_last_captcha_code! @@ -17,6 +18,14 @@ class ApplicationController < ActionController::Base protected + def check_first_run + return if !Rails.configuration.credentials.dig(:first_run_setup) + return if params["controller"].include?("users") + if User.count.zero? + return redirect_to sign_up_path, notice: I18n.t("auth.first_run_user_creation") + end + end + def set_tags set_meta_tags(title: 'Notes', description: 'My useful notes', diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index 4d02eb0..f6de23a 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -45,6 +45,9 @@ def create options[:invite_code] = users_params[:user][:code] current_user.update!(options: options) end + if Rails.configuration.credentials.dig(:first_run_setup) && (User.count == 1) + current_user.update!(is_admin: true) + end Tag.all.each { |tag| ItemTag.create!(item: current_user, tag: tag, enabled: tag.enabled_by_default) } end diff --git a/config/credentials.yml b/config/credentials.yml index f6c3be4..17f1477 100644 --- a/config/credentials.yml +++ b/config/credentials.yml @@ -12,6 +12,7 @@ development: max_upload_files: 10 max_file_size: 10 invite_codes_register_only: false + first_run_setup: true # Creating admin user if no user exists redis: # set 'false' if you already have working redis server, or want # launch him manually with command: redis-server @@ -51,6 +52,7 @@ production: max_upload_files: 10 max_file_size: 10 invite_codes_register_only: false + first_run_setup: true # Creating admin user if no user exists redis: # set 'false' if you already have working redis server, or want # launch him manually with command: redis-server diff --git a/config/locales/en.yml b/config/locales/en.yml index efc48f6..b4e3012 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -20,6 +20,7 @@ en: sign_up_button: Sign up sign_in_button: Sign in remember_me: Remember me? + first_run_user_creation: Initial admin user creation posts: main_page: Main page add_post: Add post @@ -154,6 +155,7 @@ en: option_enable_notifications: Enable notifications? posts_count: Number of posts import_from_tg: Automatically add posts sent via Telegram? + hidden_by_default: Added posts are hidden by default? manage: manage_admins_only: You don't have permissions to manage options! all_invite_codes_list: All invite codes list diff --git a/config/locales/ru.yml b/config/locales/ru.yml index cd50bc6..90929c5 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -20,6 +20,7 @@ ru: sign_up_button: Зарегистрироваться sign_in_button: Войти remember_me: Запомнить меня? + first_run_user_creation: Начальное создание пользователя-администратора posts: main_page: На главную add_post: Добавить статью @@ -153,6 +154,7 @@ ru: option_enable_notifications: Включить уведомления? posts_count: Кол-во постов import_from_tg: Автоматически добавлять посты отправленные через Telegram? + hidden_by_default: Добавленные посты по-умолчанию скрыты? manage: manage_admins_only: У вас нет прав для управления настройками! all_invite_codes_list: Список всех инвайт кодов From 9c2e44a3576ec3731bd567e8ac57b99eef1c146d Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sun, 1 Sep 2024 14:07:21 +0300 Subject: [PATCH 38/47] Preload room option support --- README-RU.md | 12 +-------- README.md | 10 -------- app/controllers/posts_controller.rb | 1 + app/models/channel.rb | 2 ++ app/services/check_channel.rb | 25 +++++++++++++------ .../platform/send_comment_to_telegram.rb | 1 - .../platform/send_post_to_telegram.rb | 11 ++++---- app/views/channels/edit.html.erb | 9 +++++-- app/views/channels/new.html.erb | 19 +++++++++----- app/views/posts/edit.html.erb | 4 +++ app/views/posts/new.html.erb | 8 ++++-- config/locales/en.yml | 4 ++- config/locales/ru.yml | 4 ++- update_log.md | 7 ++++-- 14 files changed, 68 insertions(+), 49 deletions(-) diff --git a/README-RU.md b/README-RU.md index 44efc59..9047513 100644 --- a/README-RU.md +++ b/README-RU.md @@ -106,10 +106,6 @@ gem install wdm -- --with-cflags=-Wno-implicit-function-declaration ``` docker-compose run --rm twilight bin/rails db:migrate ``` - * Чтобы сделать себя админом и в контейнере изменять credentials.yml и database.yml, нужно войти в контейнер командой: - ``` - docker exec -it twilight /bin/bash - ``` Теперь сайт будет доступен по адресу: `http://localhost:3080` @@ -117,19 +113,13 @@ gem install wdm -- --with-cflags=-Wno-implicit-function-declaration [Production] Не забудьте настроить переменную secret_key_base в файле credentials.tml! -Некоторые настройки делаются через консоль (`rails c`), но большинство работает и так при условии правильно введённых данных; - -Чтобы иметь возможность создавать посты, необходимо получить права администратора: - ```ssh - User.where(login: "ВАШЛОГИН").update(is_admin: true) - ``` ### Комментарии Для поддержки трансляции комментариев из Telegram в пост блога, необходимо: 1. Проверить настройки приватности бота; 2. Добавить бота в чат с комментариями; 3. При добавлении канала поставить галку 'Включить комментарии'; -После чего трансляция комментариев должен заработать. +После чего трансляция комментариев должна заработать. ### Matrix Краткая инструкция по настройке matrix. diff --git a/README.md b/README.md index abb271b..a3f88fc 100644 --- a/README.md +++ b/README.md @@ -106,10 +106,6 @@ gem install wdm -- --with-cflags=-Wno-implicit-function-declaration ``` docker-compose run --rm twilight bin/rails db:migrate ``` - * To make yourself an admin and configure (credentials.yml and database.yml) server, you need to enter the container with the command: - ``` - docker exec -it twilight /bin/bash - ``` The site will now be available at: `http://localhost:3080` @@ -117,12 +113,6 @@ The site will now be available at: `http://localhost:3080` [Production] Don't forget to set variable *secret_key_base* in credentials.yml: -Some settings are done through the console (`rails c`), but most work anyway, provided the data is entered correctly; - -To be able to create posts, you need to get administrator rights: - ```ssh - User.where(login: "YOURLOGIN").update(is_admin: true) - ``` ### Comments To support broadcasting comments from Telegram to a blog post, you need to: diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 7957805..3464715 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -348,6 +348,7 @@ def posts_params :new_tags_name, :new_tags_enabled_by_default, :is_hidden, + :use_preload_room, { attachments: [] }]) end end diff --git a/app/models/channel.rb b/app/models/channel.rb index 95967e1..59c7b60 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -8,6 +8,8 @@ class Channel < ApplicationRecord has_one_attached :avatar + scope :with_enabled_preload_room, -> { select { |ch| (ch.platform == Platform.find_by(title: "telegram")) && ch.options.dig("preload_attachments", "enabled") } } + def platform_posts_for_post(post) platform_posts.where(post: post) end diff --git a/app/services/check_channel.rb b/app/services/check_channel.rb index 094dc9f..61c8880 100644 --- a/app/services/check_channel.rb +++ b/app/services/check_channel.rb @@ -52,21 +52,30 @@ def check_telegram errs << 'Channel not available! (Not found or bot access problems?)' end - if params[:channel][:room_attachments].present? && !params[:channel][:room_attachments].empty? - begin - bot.get_chat(chat_id: params[:channel][:room_attachments]) - rescue Telegram::Bot::Error - errs << 'Attachments channel not available! (Not found or bot access problems?)' + options[:preload_attachments] = {} + + preload_attachments_enabled = (params[:channel][:preload_attachments] == '1') + preload_attachments_room = params[:channel][:preload_room] + options[:preload_attachments][:enabled] = preload_attachments_enabled + options[:preload_attachments][:preload_room] = preload_attachments_room + + if preload_attachments_enabled + if preload_attachments_room.present? && !preload_attachments_room.empty? + begin + bot.get_chat(chat_id: preload_attachments_room) + rescue Telegram::Bot::Error + errs << 'Attachments channel not available! (Not found or bot access problems?)' + end + errs << 'Channel ID == Attachment Channel ID' if params[:channel][:room] == preload_attachments_room + else + errs << 'Attachments channel not specified!' end - errs << 'Channel ID == Attachment Channel ID' if params[:channel][:room] == params[:channel][:room_attachments] end return errors.add(:base, errs) if errs.any? - room_attachments = params[:channel][:room_attachments] author = params[:channel][:author] - options[:room_attachments] = room_attachments if room_attachments.present? options[:author] = author if author.present? options[:notifications_enabled] = (params[:channel][:enable_notifications] == '1') diff --git a/app/services/platform/send_comment_to_telegram.rb b/app/services/platform/send_comment_to_telegram.rb index c4b1e82..5d099cc 100644 --- a/app/services/platform/send_comment_to_telegram.rb +++ b/app/services/platform/send_comment_to_telegram.rb @@ -19,7 +19,6 @@ def initialize(params, channel_ids, current_post, current_user) room: channel.room, token: channel.token, user_id: channel.user_id, - room_attachments: channel.options['room_attachments'], linked_chat_id: channel.options.dig('linked_chat_id'), } end diff --git a/app/services/platform/send_post_to_telegram.rb b/app/services/platform/send_post_to_telegram.rb index babfe6e..25998ab 100644 --- a/app/services/platform/send_post_to_telegram.rb +++ b/app/services/platform/send_post_to_telegram.rb @@ -17,7 +17,7 @@ def initialize(post, base_url, params, channel_ids) { id: channel.id, room: channel.room, token: channel.token, - room_attachments: channel.options['room_attachments'] } + preload_attachments: channel.options['preload_attachments'] } end @attachments = @params[:post][:attachments].reverse if @params[:post][:attachments].present? @options = @params[:options] @@ -66,11 +66,12 @@ def call text_contents = [Content.create!(user: @post.user, post: @post, text: "", platform: @platform, has_attachments: false) ] end - - use_attachment_channel = (1 == 1) # TMP!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + use_attachment_channel = (params[:post]["use_preload_room"] == "1") + if use_attachment_channel already_uploaded_media = [] - channel_with_roomatt = @channels.find{ |ch| ch[:room_attachments].present? && !ch[:room_attachments].empty? } + channel_with_roomatt = @channels.find{ |ch| ch[:preload_attachments]["enabled"] && !ch[:preload_attachments]["preload_room"].empty? } if channel_with_roomatt.present? if already_uploaded_media.empty? media = upload_to_attachment_channel(channel_with_roomatt) @@ -230,7 +231,7 @@ def prepare_media_to_upload # Faster than upload to each channel, but requires attachment channel def upload_to_attachment_channel(channel) bot = get_tg_bot(channel) - attachment_channel = channel[:room_attachments] + attachment_channel = channel[:preload_attachments]["preload_room"] @post.attachments.order('created_at ASC').map do |att| blob_signed_id = att.blob.signed_id file = File.open(ActiveStorage::Blob.service.send(:path_for, att.blob.key)) diff --git a/app/views/channels/edit.html.erb b/app/views/channels/edit.html.erb index 0bc4a54..d89496d 100644 --- a/app/views/channels/edit.html.erb +++ b/app/views/channels/edit.html.erb @@ -33,8 +33,13 @@ <%= f.text_field :author, { class: "form-control", placeholder: "1234567890", value: @current_channel.options["author"] } %> </div> <div class="col-xs-8 col-sm-4 col-md-9"> - <%= f.label I18n.t("channels.channel_identifier_for_att") %> - <%= f.text_field :room_attachments, { class: "form-control", placeholder: "-0123456789000", value: @current_channel.options["room_attachments"] } %> + <%= f.check_box("preload_attachments", { "data-toggle" => "collapse", "data-target"=>"#preload_attachments", :class=>"colorful-checkbox", :checked => @current_channel.options.dig("preload_attachments","enabled") })%> + <%= f.label I18n.t("channels.preload_attachments") %> + <div id="preload_attachments" class="collapse"> + <p><%= I18n.t("channels.preload_attachments_desc") %></p> + <%= f.label I18n.t("channels.channel_identifier_for_att") %> + <%= f.text_field :preload_room, { class: "form-control", placeholder: "-0123456789000", value: @current_channel.options.dig("preload_attachments","preload_room") } %> + </div> </div> <div class="col-xs-8 col-sm-4 col-md-9 field"> <%= f.check_box("import_from_tg", { "data-toggle" => "collapse", "data-target"=>"#import_from_tg", :class=>"colorful-checkbox", :checked => @current_channel.options.dig("import_from_tg","enabled") })%> diff --git a/app/views/channels/new.html.erb b/app/views/channels/new.html.erb index 4f93079..76006e4 100644 --- a/app/views/channels/new.html.erb +++ b/app/views/channels/new.html.erb @@ -28,16 +28,23 @@ <%= f.label I18n.t("channels.author_identifier") %> <%= f.text_field :author, { class: "form-control", placeholder: "1234567890" } %> </div> + <div class="col-xs-8 col-sm-4 col-md-9"> - <%= f.label I18n.t("channels.channel_identifier_for_att") %> - <%= f.text_field :room_attachments, { class: "form-control", placeholder: "-0123456789000" } %> + <%= f.check_box("preload_attachments", { "data-toggle" => "collapse", "data-target"=>"#preload_attachments", :class=>"colorful-checkbox", :checked => false })%> + <%= f.label I18n.t("channels.preload_attachments") %> + <div id="preload_attachments" class="collapse"> + <p><%= I18n.t("channels.preload_attachments_desc") %></p> + <%= f.label I18n.t("channels.channel_identifier_for_att") %> + <%= f.text_field :preload_room, { class: "form-control", placeholder: "-0123456789000" } %> + </div> </div> + <div class="col-xs-8 col-sm-4 col-md-9 field"> - <%=check_box("import_from_tg", "enabled", { "data-toggle" => "collapse", "data-target"=>"#import_from_tg", :class=>"colorful-checkbox", :checked => false })%> - <%= label_tag "import_from_tg", I18n.t("channels.import_from_tg") %> + <%= f.check_box("import_from_tg", { "data-toggle" => "collapse", "data-target"=>"#import_from_tg", :class=>"colorful-checkbox", :checked => false })%> + <%= f.label I18n.t("channels.import_from_tg") %> <div id="import_from_tg" class="collapse"> - * <%=check_box("import_from_tg", "hide_by_default", { :id=> "hide_by_default", :class=>"colorful-checkbox", :checked => false })%> - <%= label_tag "hide_by_default", I18n.t("channels.hidden_by_default") %> + * <%= f.check_box("hide_by_default", { :id=> "hide_by_default", :class=>"colorful-checkbox", :checked => false })%> + <%= f.label I18n.t("channels.hidden_by_default") %> </div> </div> <center><%= f.submit I18n.t("channels.add"), class: "col-md-3 btn btn-primary col-md-offset-3" %></center> diff --git a/app/views/posts/edit.html.erb b/app/views/posts/edit.html.erb index 64d1787..737281b 100644 --- a/app/views/posts/edit.html.erb +++ b/app/views/posts/edit.html.erb @@ -93,6 +93,10 @@ <% end %> <br> <% end %> + <% if @current_user.channels.where(enabled: true).with_enabled_preload_room.any?%> + * <%= f.check_box("use_preload_room", { :id=> "use_preload_room", :class=>"colorful-checkbox", :checked => false }) %> + <%= label_tag "use_preload_room", "Use telegram preload room for attachments (if available?)" %> + <% end %> </div> <% end %> <!-- <p><%= I18n.t("posts.channels_not_found") %></p> --> diff --git a/app/views/posts/new.html.erb b/app/views/posts/new.html.erb index 9c7be69..9628ca4 100644 --- a/app/views/posts/new.html.erb +++ b/app/views/posts/new.html.erb @@ -58,6 +58,10 @@ <% end %> <br> <% end %> + <% if @current_user.channels.where(enabled: true).with_enabled_preload_room.any?%> + * <%= f.check_box("use_preload_room", { :id=> "use_preload_room", :class=>"colorful-checkbox", :checked => false }) %> + <%= label_tag "use_preload_room", "Use telegram preload room for attachments (if available?)" %> + <% end %> </div> <% else %> <p><%= I18n.t("posts.channels_not_found") %></p> @@ -68,8 +72,8 @@ <% if @current_user.categories.any? %> <% categories = @current_user.categories.order(:sort).collect{ |cat| [cat.name, cat.id] } %> <% categories.unshift(["-- #{I18n.t("posts.no_category")} --", nil])%> - <%= f.select(:category, categories) %> - <% end %><br><br> + <%= f.select(:category, categories) %><br><br> + <% end %> <div class="form-inline"> <div class="form-group"> <%= f.text_field :category_name, size: 20, class: "form-control", autofocus: true, placeholder: I18n.t("posts.new_category") %> diff --git a/config/locales/en.yml b/config/locales/en.yml index b4e3012..8fb6442 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -130,7 +130,7 @@ en: bot_token: Bot token access_token: Access token channel_identifier: Channel ID - channel_identifier_for_att: Channel ID for preload attachments (Optional) + channel_identifier_for_att: Channel ID for preload attachments author_identifier: Author ID (not bot) api_server_address: API server address enable_comments: Enable comments? @@ -156,6 +156,8 @@ en: posts_count: Number of posts import_from_tg: Automatically add posts sent via Telegram? hidden_by_default: Added posts are hidden by default? + preload_attachments: Upload attachments in individual channel? + preload_attachments_desc: Preloading attachments to a separate channel saves time if you send a post with attachments to multiple channels. Otherwise, each attachment would be uploaded to each channel, which can be a long process. manage: manage_admins_only: You don't have permissions to manage options! all_invite_codes_list: All invite codes list diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 90929c5..acf302d 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -129,7 +129,7 @@ ru: bot_token: Токен бота access_token: Токен доступа channel_identifier: ID канала - channel_identifier_for_att: ID канала для предзагрузки вложений (Не обязательно) + channel_identifier_for_att: ID канала для предзагрузки вложений author_identifier: ID автора (не бота) api_server_address: Адрес сервера API enable_comments: Включить комментарии? @@ -155,6 +155,8 @@ ru: posts_count: Кол-во постов import_from_tg: Автоматически добавлять посты отправленные через Telegram? hidden_by_default: Добавленные посты по-умолчанию скрыты? + preload_attachments: Загружать вложения в отдельный канал? + preload_attachments_desc: Предзагрузка вложений в отдельный канал позволяет сэкономить время в случае отправки поста с вложениями в несколько каналов. В противном случае, каждое вложение будет загружаться в каждый канал, что может быть долгим процессом. manage: manage_admins_only: У вас нет прав для управления настройками! all_invite_codes_list: Список всех инвайт кодов diff --git a/update_log.md b/update_log.md index 59cec86..3dd4207 100644 --- a/update_log.md +++ b/update_log.md @@ -7,8 +7,9 @@ * Обновлён Ruby 2.7.7->3.3.2; * Поддержка OpenGraph для предпросмотра постов; * Добавлен импорт постов при постинге из ТГ; + * Опция 'делать скрытыми' автоматически добавленные посты; * Добавлен счётчик просмотров поста; -* Теперь посты имеют UUID вместо ID; +* Теперь посты имеют UUID; * Добавлена генерация slug на основе названия поста; * Добавлена опция скрытия поста из ленты (их видит только автор); * Измемены комментарии: @@ -22,7 +23,9 @@ * Изменена тема 'Ruby'; * Для feed теперь также есть счётчик просмотров и комментарии; * Предпросмотр темы через параметр `?theme=<название>` в URL-адресе; -* Возможность постить в платформы, если нет существующих постов в платформах. +* Добавлена страница создания пользователя-администратора (если включена и нет других пользователей); +* Возможность постить в платформы, если нет существующих постов в платформах; +* Возможность выбора предзагрузки вложений в ТГ канал, или напрямую для каждого канала; * Добавлены ссылки на каналы платформ в шапке поста; * Исправлен баг с плохо видимой каптчей; * Исправлен баги постинга в Matrix; From a9aef66b676da7d09f855103ca51806891c1c09c Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sun, 8 Sep 2024 22:37:23 +0300 Subject: [PATCH 39/47] Notifications system --- app/assets/stylesheets/base.scss | 61 +++++++++++++++++++ app/controllers/notifications_controller.rb | 8 +++ app/models/application_record.rb | 2 + app/models/concerns/notificable.rb | 16 +++++ app/models/notification.rb | 38 ++++++++++++ app/models/post.rb | 5 ++ app/models/user.rb | 5 ++ app/views/notifications/_count.html.erb | 3 + .../notifications/_notification.html.erb | 3 + .../notifications/_notifications.html.erb | 7 +++ app/views/notifications/_post.html.erb | 7 +++ app/views/notifications/index.html.erb | 8 +++ app/views/posts/index.html.erb | 51 ++++++++++++++++ config/locales/en.yml | 24 +++++++- config/locales/ru.yml | 23 ++++++- config/routes.rb | 1 + .../20240904090005_create_notifications.rb | 14 +++++ spec/factories.rb | 9 +++ .../platform/send_post_to_telegram_spec.rb | 2 +- 19 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 app/controllers/notifications_controller.rb create mode 100644 app/models/concerns/notificable.rb create mode 100644 app/models/notification.rb create mode 100644 app/views/notifications/_count.html.erb create mode 100644 app/views/notifications/_notification.html.erb create mode 100644 app/views/notifications/_notifications.html.erb create mode 100644 app/views/notifications/_post.html.erb create mode 100644 app/views/notifications/index.html.erb create mode 100644 db/migrate/20240904090005_create_notifications.rb diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 62a4c33..0a359f1 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -1372,4 +1372,65 @@ $color-black: #000000; } } } + + /* */ + + details[open] summary ~ * { + animation: open .5s ease-in-out; + } + + @keyframes sweep { + 0% {opacity: 0; margin-left: -10px} + 100% {opacity: 1; margin-left: 0px} + } + + /* */ + + .notification-window{ + position: absolute; + + left: 81%; + background: $color-container-bg; + width: 18%; + + /*display: flex; + flex-direction: column; */ + font-size: 13px; + + .notification-header{ + height: 20px; + margin-top: 10px; + padding-left: 5%; + border-bottom: 1.5px solid $font; + } + + .notification-list{ + padding: 0; + list-style-type: none; + margin-top: 0; + margin-bottom: 1px; + } + + .notify{ + box-sizing: border-box; + padding: 3% 5% 3% 5%; + background: $color-form-textarea-bg; + border-bottom: 1px dashed $font; + list-style-type: none; + } + + /* + .notify:last-child { + border-bottom: none; + } + + .notification-footer{ + box-sizing: border-box; + border-top: 1.5px solid $font; + text-align: center; + align-content: center; + padding: 3%; + } + */ + } } \ No newline at end of file diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb new file mode 100644 index 0000000..589a727 --- /dev/null +++ b/app/controllers/notifications_controller.rb @@ -0,0 +1,8 @@ +class NotificationsController < ApplicationController + before_action :authenticate_user! + + def index + @notifications = current_user.notifications + @notifications.update(viewed: true) + end +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 71fbba5..012734b 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -2,4 +2,6 @@ class ApplicationRecord < ActiveRecord::Base self.abstract_class = true + + scope :latest, ->{ order("created_at DESC") } end diff --git a/app/models/concerns/notificable.rb b/app/models/concerns/notificable.rb new file mode 100644 index 0000000..1102b48 --- /dev/null +++ b/app/models/concerns/notificable.rb @@ -0,0 +1,16 @@ +module Notificable + extend ActiveSupport::Concern + + included do + has_many :notifications, as: :item, dependent: :destroy + after_create_commit :send_notifications_to_users + end + + def send_notifications_to_users + if self.respond_to? :user_ids + self.user_ids&.each do |user_id| + Notification.create user_id: user_id, item: self + end + end + end +end diff --git a/app/models/notification.rb b/app/models/notification.rb new file mode 100644 index 0000000..0830d69 --- /dev/null +++ b/app/models/notification.rb @@ -0,0 +1,38 @@ +class Notification < ApplicationRecord + extend Enumerize + belongs_to :item, polymorphic: true + belongs_to :user + + scope :unviewed, ->{ where(viewed: false) } + default_scope { latest } + + enumerize :event, in: %w[none create update delete], default: :none, scope: :having_event + enumerize :status, in: %w[notice success warning error], default: :notice, scope: :having_status + validates :event, :status, presence: true + + after_create_commit do + broadcast_prepend_to "broadcast_to_user_#{self.user_id}", + target: :notifications + end + + after_update_commit do + broadcast_update_to "broadcast_to_user_#{self.user_id}", + target: :notifications, + partial: "notifications/notifications", + locals: { noti: Notification.unviewed.where(user_id: self.user_id) } + end + + after_destroy_commit do + broadcast_update_to "broadcast_to_user_#{self.user_id}", + target: :notifications, + partial: "notifications/notifications", + locals: { noti: Notification.unviewed.where(user_id: self.user_id) } + end + + after_commit do + broadcast_replace_to "broadcast_to_user_#{self.user_id}", + target: "notifications_count", + partial: "notifications/count", + locals: { count: self.user.unviewed_notifications_count } + end +end diff --git a/app/models/post.rb b/app/models/post.rb index d07dc3e..87cfa9d 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Post < ApplicationRecord + include Notificable has_paper_trail # validates :title, presence: true belongs_to :user @@ -112,6 +113,10 @@ def slug_url slug.present? ? slug : uuid end + def user_ids + User.where.not(id: self.user_id).ids + end + # private # def slug_candidates diff --git a/app/models/user.rb b/app/models/user.rb index a735e94..59e4826 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -14,6 +14,7 @@ class User < ApplicationRecord has_and_belongs_to_many :tags, class_name: 'Tag', join_table: 'item_tags', as: :item has_many :active_tags, -> { active('User') }, class_name: 'ItemTag', foreign_key: 'item_id' has_many :visits, class_name: "Ahoy::Visit" + has_many :notifications has_one_attached :avatar @@ -64,6 +65,10 @@ def email_changed? false end + def unviewed_notifications_count + self.notifications.unviewed.count + end + # 'Disable' current password validation def update_with_password(params = {}) diff --git a/app/views/notifications/_count.html.erb b/app/views/notifications/_count.html.erb new file mode 100644 index 0000000..6b7c866 --- /dev/null +++ b/app/views/notifications/_count.html.erb @@ -0,0 +1,3 @@ +<%= tag.div id: :notifications_count do %> + <%=I18n.t('main.notifications')%>: (<%= count > 0 ? (count > 9 ? "+9" : count) : 0 %>) +<% end %> \ No newline at end of file diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb new file mode 100644 index 0000000..924e7a4 --- /dev/null +++ b/app/views/notifications/_notification.html.erb @@ -0,0 +1,3 @@ +<li class="py-4 notify" id="<%= dom_id(notification) %>"> + <%= render "notifications/#{notification.item_type.downcase}", notification: notification %> +</li> \ No newline at end of file diff --git a/app/views/notifications/_notifications.html.erb b/app/views/notifications/_notifications.html.erb new file mode 100644 index 0000000..5213b43 --- /dev/null +++ b/app/views/notifications/_notifications.html.erb @@ -0,0 +1,7 @@ +<ul class="notification-list" id="notifications"> + <%noti.unviewed.first(9).each do |notification|%> + <li class="notify py-4" id="<%= dom_id(notification) %>"> + <%= render "notifications/#{notification.item_type.downcase}", notification: notification %> + </li> + <%end%> +</ul> \ No newline at end of file diff --git a/app/views/notifications/_post.html.erb b/app/views/notifications/_post.html.erb new file mode 100644 index 0000000..0568efd --- /dev/null +++ b/app/views/notifications/_post.html.erb @@ -0,0 +1,7 @@ +<%from_time = Time.now%> +<%distance = distance_of_time_in_words(from_time, notification.updated_at)%> +<p> + <%=distance%> ● <%= notification.item.user.login %> posted: + <%= link_to notification.item.title, notification.item, class: "underline font-medium" %> +</p> +<!-- Unsed but may be useful --> \ No newline at end of file diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb new file mode 100644 index 0000000..3ada91e --- /dev/null +++ b/app/views/notifications/index.html.erb @@ -0,0 +1,8 @@ +<div class="space-y-4"> + <p class="text-lg leading-6 font-medium text-gray-900 flex justify-between"> + Notifications + </p> + <ul class="border-t border-b border-gray-200 divide-y divide-gray-200" id="notifications"> + <%= render @notifications %> + </ul> +</div> \ No newline at end of file diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb index d01d217..e0fcd13 100644 --- a/app/views/posts/index.html.erb +++ b/app/views/posts/index.html.erb @@ -1,6 +1,57 @@ <body id="bg"> <div class="posts-container"> +<!-- + <% if user_signed_in? %> + <%= turbo_stream_from dom_id(current_user, :broadcast_to) %> + + <%= link_to notifications_path, class: "bg-gray-800 p-1 rounded-full text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" do %> + <span class="sr-only">View notifications</span> + <%= tag.div id: :notifications_count do %> + <%= render "notifications/count", count: current_user.unviewed_notifications_count %> + <% end %> + <% end %> + <% end %> +--> +<!--- + <div class="notification-window"> + <div class="notification-header">Notifications (0):</div> + <div class="notify">Notify 1: bla bla bla</div> + <div class="notify">Notify 2 lol adas asdas as dsad asdas asdadsd!</div> + <div class="notify">Notify 3: 4314 214125 12 45125 12AS Esso</div> + <div class="notification-footer">Hide</div> + </div> +--> + <% if user_signed_in? %> + <%= turbo_stream_from dom_id(current_user, :broadcast_to) %> + <div class="notification-window"> + <details open class="accordion-item"> + <summary class="accordion-trigger notification-header"> + <span class="accordion-title"> + <%= tag.div id: :notifications_count do %> + <%= render "notifications/count", count: current_user.unviewed_notifications_count %> + <% end %> + </span> + </summary> + <div class="accordion-content"> + <%= render "notifications/notifications", noti: current_user.notifications.unviewed %> + </div> + </details> + </div> + <% end %> +<!-- + <div class="notification-window"> + <div class="accordion-trigger notification-header"><span class="accordion-title">Notifications (0):</span></div> + <details open class="accordion-item"> + <div class="accordion-content"> + <div class="notify">Notify 1: bla bla bla</div> + <div class="notify">Notify 2 lol adas asdas as dsad asdas asdadsd!</div> + <div class="notify">Notify 3: 4314 214125 12 45125 12AS Esso</div> + </div> + <summary class="accordion-trigger notification-footer"><span class="accordion-title"> Hide</span></summary> + </details> + </div> +--> <section> <%= render partial: "posts/links_header", locals: { post: nil } %> <br> diff --git a/config/locales/en.yml b/config/locales/en.yml index 8fb6442..15576f9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -10,6 +10,7 @@ en: new_post: New post all_posts: All posts please_log_in: (please, sign in) + notifications: Notifications auth: sign_up_header: Sign up sign_in_header: Sign in @@ -33,7 +34,6 @@ en: download: Download edit_post: Edit delete_post: Delete - delete_post: Delete delete_post_local: Delete (Local) delete_post_confirm: Are you sure? read_more: Read more @@ -243,4 +243,24 @@ en: current_password: blank: Current password not specified! password: - too_short: (Password too short!) \ No newline at end of file + too_short: (Password too short!) + datetime: + distance_in_words: + less_than_x_minutes: + one: '> %{count} min' + other: '> %{count} min.' + x_seconds: + '%{count} s.' + x_minutes: + '%{count} min.' + x_hours: + '%{count} h.' + x_days: + one: "1 d." + other: "%{count} d." + about_x_months: + one: '~ %{count} mo.' + other: '~ %{count} mo.' + about_x_years: + one: '~ %{count} y.' + other: '~ %{count} y.' \ No newline at end of file diff --git a/config/locales/ru.yml b/config/locales/ru.yml index acf302d..bafb041 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -10,6 +10,7 @@ ru: new_post: Новый пост all_posts: Все посты please_log_in: (пожалуйста, войдите!) + notifications: Notifications # Turbo not work with I18n? auth: sign_up_header: Регистрация sign_in_header: Авторизация @@ -242,4 +243,24 @@ ru: current_password: blank: Не указан текущий пароль! password: - too_short: (Пароль слишком короткий!) \ No newline at end of file + too_short: (Пароль слишком короткий!) + datetime: + distance_in_words: + less_than_x_minutes: # Turbo not work with I18n? + one: '> %{count} min' + other: '> %{count} min.' + x_seconds: + '%{count} s.' + x_minutes: + '%{count} min.' + x_hours: + '%{count} h.' + x_days: + one: "1 d." + other: "%{count} d." + about_x_months: + one: '~ %{count} mo.' + other: '~ %{count} mo.' + about_x_years: + one: '~ %{count} y.' + other: '~ %{count} y.' \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index a21b7a7..a9e7084 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -37,6 +37,7 @@ resources :invite_codes, only: [:create] resources :tags, only: %i[create update] resources :categories, only: %i[create update] + resources :notifications, only: [ :index ] get '/stats/full_users_list', to: 'pages#full_users_list', as: :full_users_list get '/manage/full_invite_codes_list', to: 'pages#full_invite_codes_list', as: :full_invite_codes_list diff --git a/db/migrate/20240904090005_create_notifications.rb b/db/migrate/20240904090005_create_notifications.rb new file mode 100644 index 0000000..e876046 --- /dev/null +++ b/db/migrate/20240904090005_create_notifications.rb @@ -0,0 +1,14 @@ +class CreateNotifications < ActiveRecord::Migration[7.0] + def change + create_table :notifications do |t| + t.references :item, polymorphic: true, null: false + t.references :user, null: false, foreign_key: true + t.boolean :viewed, null: false, default: false + t.string :event + t.string :status + t.string :text + + t.timestamps + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index fed507f..85a2239 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -1,6 +1,15 @@ # frozen_string_literal: true FactoryBot.define do + factory :notification do + item { nil } + user { nil } + viewed { false } + event { "none" } + status { "notice" } + text { nil } + end + factory :category do user name { Faker::Book.genre } diff --git a/spec/services/platform/send_post_to_telegram_spec.rb b/spec/services/platform/send_post_to_telegram_spec.rb index b4b66d1..583ab75 100644 --- a/spec/services/platform/send_post_to_telegram_spec.rb +++ b/spec/services/platform/send_post_to_telegram_spec.rb @@ -25,7 +25,7 @@ create(:channel, platform: platform, enabled: true, token: bot.token, room: '-1001234567890', options: { 'id' => bot.token.split(':')[0], - 'room_attachments' => '123456789', + 'preload_attachments=' => {'enabled' => true, 'preload_room' => '123456789'}, 'author' => '987654321', 'notifications_enabled' => false, 'comments_enabled' => false, From 9db92d3c4c4a2f19dc235197d24d9034dfc718d4 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Sun, 8 Sep 2024 23:54:29 +0300 Subject: [PATCH 40/47] View notification option --- app/assets/stylesheets/base.scss | 26 +++++++++ app/controllers/application_controller.rb | 4 ++ app/controllers/notifications_controller.rb | 16 ++++-- app/policies/notification_policy.rb | 7 +++ .../notifications/_notification.html.erb | 2 +- .../_notification_window.html.erb | 17 ++++++ .../notifications/_notifications.html.erb | 2 +- app/views/posts/edit.html.erb | 1 + app/views/posts/import.html.erb | 1 + app/views/posts/index.html.erb | 53 +------------------ app/views/posts/new.html.erb | 1 + app/views/posts/show.html.erb | 1 + config/locales/en.yml | 6 +++ config/locales/ru.yml | 6 +++ config/routes.rb | 4 +- 15 files changed, 89 insertions(+), 58 deletions(-) create mode 100644 app/policies/notification_policy.rb create mode 100644 app/views/notifications/_notification_window.html.erb diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 0a359f1..2362e8a 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -1395,8 +1395,19 @@ $color-black: #000000; /*display: flex; flex-direction: column; */ + line-height: 1.5rem; font-size: 13px; + margin: 1% auto; + + li p{ + margin: 0; + } + + p{ + margin: 0 0 16px; + } + .notification-header{ height: 20px; margin-top: 10px; @@ -1412,6 +1423,8 @@ $color-black: #000000; } .notify{ + display: block; + position: relative; box-sizing: border-box; padding: 3% 5% 3% 5%; background: $color-form-textarea-bg; @@ -1419,6 +1432,19 @@ $color-black: #000000; list-style-type: none; } + .close { + cursor: pointer; + position: absolute; + top: 50%; + right: 0%; + padding: 12px 12px; + transform: translate(0%, -50%); + font-size: 22px; + color: $font; + } + + /* .close:hover {background: #bbb;} */ + /* .notify:last-child { border-bottom: none; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 303df2e..5dd43e0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -44,6 +44,10 @@ def current_channel @current_channel ||= Channel.find(params[:id]) end + def current_notification + @current_notification ||= current_user.notifications.find(params[:id]) + end + def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up) do |user_params| user_params.permit(:login, :password, :password_confirmation, :captcha) diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 589a727..57319d7 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -1,8 +1,18 @@ class NotificationsController < ApplicationController before_action :authenticate_user! - def index - @notifications = current_user.notifications - @notifications.update(viewed: true) + # Not used. For now. + #def index + # @notifications = current_user.notifications + #@notifications.update(viewed: true) + #end + + def view + authorize! current_notification, to: :view? + + current_notification.update(viewed: true) if current_notification.present? + + ref_url = request.referrer + redirect_to ref_url end end diff --git a/app/policies/notification_policy.rb b/app/policies/notification_policy.rb new file mode 100644 index 0000000..fec15fc --- /dev/null +++ b/app/policies/notification_policy.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class NotificationPolicy < ApplicationPolicy + def view? + record.user_id == user&.id + end +end diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb index 924e7a4..1505360 100644 --- a/app/views/notifications/_notification.html.erb +++ b/app/views/notifications/_notification.html.erb @@ -1,3 +1,3 @@ <li class="py-4 notify" id="<%= dom_id(notification) %>"> - <%= render "notifications/#{notification.item_type.downcase}", notification: notification %> + <%= render "notifications/#{notification.item_type.downcase}", notification: notification %><%=link_to "×".html_safe, view_notification_path(notification), class: "close", method: :put %> </li> \ No newline at end of file diff --git a/app/views/notifications/_notification_window.html.erb b/app/views/notifications/_notification_window.html.erb new file mode 100644 index 0000000..edaf863 --- /dev/null +++ b/app/views/notifications/_notification_window.html.erb @@ -0,0 +1,17 @@ +<% if user_signed_in? %> + <%= turbo_stream_from dom_id(current_user, :broadcast_to) %> + <div class="notification-window"> + <details open class="accordion-item"> + <summary class="accordion-trigger notification-header"> + <span class="accordion-title"> + <%= tag.div id: :notifications_count do %> + <%= render "notifications/count", count: current_user.unviewed_notifications_count %> + <% end %> + </span> + </summary> + <div class="accordion-content"> + <%= render "notifications/notifications", noti: current_user.notifications.unviewed %> + </div> + </details> + </div> +<% end %> \ No newline at end of file diff --git a/app/views/notifications/_notifications.html.erb b/app/views/notifications/_notifications.html.erb index 5213b43..f052745 100644 --- a/app/views/notifications/_notifications.html.erb +++ b/app/views/notifications/_notifications.html.erb @@ -1,7 +1,7 @@ <ul class="notification-list" id="notifications"> <%noti.unviewed.first(9).each do |notification|%> <li class="notify py-4" id="<%= dom_id(notification) %>"> - <%= render "notifications/#{notification.item_type.downcase}", notification: notification %> + <%= render "notifications/#{notification.item_type.downcase}", notification: notification %><%=link_to "×".html_safe, view_notification_path(notification), class: "close", method: :put %> </li> <%end%> </ul> \ No newline at end of file diff --git a/app/views/posts/edit.html.erb b/app/views/posts/edit.html.erb index 737281b..270dfb4 100644 --- a/app/views/posts/edit.html.erb +++ b/app/views/posts/edit.html.erb @@ -1,4 +1,5 @@ <body id="bg"> + <%= render "notifications/notification_window" %> <div class="container"> <h2><%= I18n.t("posts.edit_header") %></h2> diff --git a/app/views/posts/import.html.erb b/app/views/posts/import.html.erb index 7577b26..faadd0f 100644 --- a/app/views/posts/import.html.erb +++ b/app/views/posts/import.html.erb @@ -1,4 +1,5 @@ <body id="bg"> + <%= render "notifications/notification_window" %> <div class="container"> <center><h2><%= I18n.t("posts.import") %></h2></center> diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb index e0fcd13..f70ccbb 100644 --- a/app/views/posts/index.html.erb +++ b/app/views/posts/index.html.erb @@ -1,57 +1,6 @@ <body id="bg"> + <%= render "notifications/notification_window" %> <div class="posts-container"> - -<!-- - <% if user_signed_in? %> - <%= turbo_stream_from dom_id(current_user, :broadcast_to) %> - - <%= link_to notifications_path, class: "bg-gray-800 p-1 rounded-full text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" do %> - <span class="sr-only">View notifications</span> - <%= tag.div id: :notifications_count do %> - <%= render "notifications/count", count: current_user.unviewed_notifications_count %> - <% end %> - <% end %> - <% end %> ---> -<!--- - <div class="notification-window"> - <div class="notification-header">Notifications (0):</div> - <div class="notify">Notify 1: bla bla bla</div> - <div class="notify">Notify 2 lol adas asdas as dsad asdas asdadsd!</div> - <div class="notify">Notify 3: 4314 214125 12 45125 12AS Esso</div> - <div class="notification-footer">Hide</div> - </div> ---> - <% if user_signed_in? %> - <%= turbo_stream_from dom_id(current_user, :broadcast_to) %> - <div class="notification-window"> - <details open class="accordion-item"> - <summary class="accordion-trigger notification-header"> - <span class="accordion-title"> - <%= tag.div id: :notifications_count do %> - <%= render "notifications/count", count: current_user.unviewed_notifications_count %> - <% end %> - </span> - </summary> - <div class="accordion-content"> - <%= render "notifications/notifications", noti: current_user.notifications.unviewed %> - </div> - </details> - </div> - <% end %> -<!-- - <div class="notification-window"> - <div class="accordion-trigger notification-header"><span class="accordion-title">Notifications (0):</span></div> - <details open class="accordion-item"> - <div class="accordion-content"> - <div class="notify">Notify 1: bla bla bla</div> - <div class="notify">Notify 2 lol adas asdas as dsad asdas asdadsd!</div> - <div class="notify">Notify 3: 4314 214125 12 45125 12AS Esso</div> - </div> - <summary class="accordion-trigger notification-footer"><span class="accordion-title"> Hide</span></summary> - </details> - </div> ---> <section> <%= render partial: "posts/links_header", locals: { post: nil } %> <br> diff --git a/app/views/posts/new.html.erb b/app/views/posts/new.html.erb index 9628ca4..cc67a1e 100644 --- a/app/views/posts/new.html.erb +++ b/app/views/posts/new.html.erb @@ -1,4 +1,5 @@ <body id="bg"> + <%= render "notifications/notification_window" %> <div class="container"> <h2><%= I18n.t("posts.new_header") %></h2> diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb index d20a6d3..0ad6cf1 100644 --- a/app/views/posts/show.html.erb +++ b/app/views/posts/show.html.erb @@ -1,4 +1,5 @@ <body id="bg"> + <%= render "notifications/notification_window" %> <div class="posts-container"> <%= render partial: "posts/links_header", locals: { post: @current_post } %> <article> diff --git a/config/locales/en.yml b/config/locales/en.yml index 15576f9..89cbb47 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -258,6 +258,12 @@ en: x_days: one: "1 d." other: "%{count} d." + about_x_hours: + one: '~ %{count} h.' + other: '~ %{count} h.' + about_x_days: + one: '~ %{count} d.' + other: '~ %{count} d.' about_x_months: one: '~ %{count} mo.' other: '~ %{count} mo.' diff --git a/config/locales/ru.yml b/config/locales/ru.yml index bafb041..96cc363 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -258,6 +258,12 @@ ru: x_days: one: "1 d." other: "%{count} d." + about_x_hours: + one: '~ %{count} h.' + other: '~ %{count} h.' + about_x_days: + one: '~ %{count} d.' + other: '~ %{count} d.' about_x_months: one: '~ %{count} mo.' other: '~ %{count} mo.' diff --git a/config/routes.rb b/config/routes.rb index a9e7084..9c7e2bd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -37,7 +37,9 @@ resources :invite_codes, only: [:create] resources :tags, only: %i[create update] resources :categories, only: %i[create update] - resources :notifications, only: [ :index ] + + #resources :notifications, only: [ :index ] + put '/notifications/view/:id', to: 'notifications#view', as: :view_notification get '/stats/full_users_list', to: 'pages#full_users_list', as: :full_users_list get '/manage/full_invite_codes_list', to: 'pages#full_invite_codes_list', as: :full_invite_codes_list From e524756545c7af9a45aec44ac708ab9ef5ffd83c Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Tue, 10 Sep 2024 17:17:53 +0300 Subject: [PATCH 41/47] Redis async jobs && notifications --- README-RU.md | 2 +- README.md | 2 +- app/assets/stylesheets/base.scss | 2 +- app/controllers/posts_controller.rb | 4 +- app/jobs/delete_matrix_posts.rb | 11 +++ app/jobs/delete_telegram_posts.rb | 11 +++ app/jobs/send_post_to_matrix.rb | 10 +++ app/jobs/send_post_to_telegram.rb | 10 +++ app/jobs/update_matrix_posts.rb | 10 +++ app/jobs/update_telegram_posts.rb | 10 +++ app/models/comment.rb | 51 ++++++++++++- app/models/notification.rb | 4 +- app/models/platform_post.rb | 39 ++++++++++ app/models/post.rb | 16 +++- app/services/delete_post_messages.rb | 13 ++-- app/services/platform/delete_matrix_posts.rb | 14 ++-- .../platform/delete_telegram_posts.rb | 13 ++-- app/services/platform/send_post_to_matrix.rb | 16 +++- .../platform/send_post_to_telegram.rb | 30 +++++--- app/services/platform/update_matrix_posts.rb | 16 +++- .../platform/update_telegram_posts.rb | 28 ++++++- app/services/send_post_to_platforms.rb | 20 ++--- app/services/update_post_messages.rb | 13 +--- app/views/notifications/_comment.html.erb | 75 +++++++++++++++++++ .../notifications/_platformpost.html.erb | 75 +++++++++++++++++++ app/views/notifications/_post.html.erb | 63 ++++++++++++++-- app/views/posts/edit.html.erb | 1 - app/views/posts/new.html.erb | 1 - config/locales/en.yml | 38 ++++++++++ config/locales/ru.yml | 38 ++++++++++ .../20240904090005_create_notifications.rb | 2 +- update_log.md | 3 + 32 files changed, 566 insertions(+), 75 deletions(-) create mode 100644 app/jobs/delete_matrix_posts.rb create mode 100644 app/jobs/delete_telegram_posts.rb create mode 100644 app/jobs/send_post_to_matrix.rb create mode 100644 app/jobs/send_post_to_telegram.rb create mode 100644 app/jobs/update_matrix_posts.rb create mode 100644 app/jobs/update_telegram_posts.rb create mode 100644 app/views/notifications/_comment.html.erb create mode 100644 app/views/notifications/_platformpost.html.erb diff --git a/README-RU.md b/README-RU.md index 9047513..15f8d0e 100644 --- a/README-RU.md +++ b/README-RU.md @@ -189,7 +189,7 @@ gem install wdm -- --with-cflags=-Wno-implicit-function-declaration * [TG] При редактировании аттачментов в комментариях (добавление нового и удаление старого) сбивается порядок и при повторном редактировании удаляется не та картинка; -Мне лень их фиксить, кто захочет (было бы очень круто), то с радостью приму Pull Request; +Если решите их пофиксить, то с радостью приму Pull Request; ## Схемы и скриншоты ER-диаграмма: diff --git a/README.md b/README.md index a3f88fc..ef24426 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ Bugs: * [TG] When editing attachments in comments (adding a new one and deleting an old one), the order gets lost and when you edit it again, the wrong picture is deleted; -I'm too lazy to fix them, whoever wants (it would be very cool), then I will gladly accept the Pull Request; +If you decide to fix them, I'll gladly accept the Pull Request; ## Schemas and screenshots ER-diagram(Ver. 1.0.1): diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 2362e8a..ed09fd2 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -1401,7 +1401,7 @@ $color-black: #000000; margin: 1% auto; li p{ - margin: 0; + margin: 0px 8px 0px 0px; } p{ diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 3464715..a6a79b3 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -148,7 +148,7 @@ def update deleted_channels_list = channels_p.reject{ |k,v| (v == "1") || (published_channels_list[k] == v) } deleted_channels_list.each do |k, v| - DeletePostMessages.call(current_post, k) + DeletePostMessages.call(current_post, current_user, k) end if new_channels_list.any? @@ -213,7 +213,7 @@ def create SendPostToPlatforms.call(@post, base_url, posts_params) - redirect_to @post + redirect_to post_path(@post) else render :new end diff --git a/app/jobs/delete_matrix_posts.rb b/app/jobs/delete_matrix_posts.rb new file mode 100644 index 0000000..4a90d01 --- /dev/null +++ b/app/jobs/delete_matrix_posts.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class DeleteMatrixPosts < ApplicationJob + queue_as :default + + def perform(matrix_posts_ids, user_id) + matrix_posts = PlatformPost.where(id: matrix_posts_ids) + user = User.find(user_id) + Platform::DeleteMatrixPosts.call(matrix_posts, user) + end +end diff --git a/app/jobs/delete_telegram_posts.rb b/app/jobs/delete_telegram_posts.rb new file mode 100644 index 0000000..d8bb4a5 --- /dev/null +++ b/app/jobs/delete_telegram_posts.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class DeleteTelegramPosts < ApplicationJob + queue_as :default + + def perform(telegram_posts_ids, user_id) + telegram_posts = PlatformPost.where(id: telegram_posts_ids) + user = User.find(user_id) + Platform::DeleteTelegramPosts.call(telegram_posts, user) + end +end diff --git a/app/jobs/send_post_to_matrix.rb b/app/jobs/send_post_to_matrix.rb new file mode 100644 index 0000000..fb21c0a --- /dev/null +++ b/app/jobs/send_post_to_matrix.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class SendPostToMatrix < ApplicationJob + queue_as :default + + def perform(post_id, base_url, params, channel_ids) + post = Post.find_by(id: post_id) + Platform::SendPostToMatrix.call(post, base_url, params, channel_ids) + end +end diff --git a/app/jobs/send_post_to_telegram.rb b/app/jobs/send_post_to_telegram.rb new file mode 100644 index 0000000..eaa6c99 --- /dev/null +++ b/app/jobs/send_post_to_telegram.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class SendPostToTelegram < ApplicationJob + queue_as :default + + def perform(post_id, base_url, params, channel_ids) + post = Post.find_by(id: post_id) + Platform::SendPostToTelegram.call(post, base_url, params, channel_ids) + end +end diff --git a/app/jobs/update_matrix_posts.rb b/app/jobs/update_matrix_posts.rb new file mode 100644 index 0000000..9247470 --- /dev/null +++ b/app/jobs/update_matrix_posts.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class UpdateMatrixPosts < ApplicationJob + queue_as :default + + def perform(post_id, base_url, params) + post = Post.find_by(id: post_id) + Platform::UpdateMatrixPosts.call(post, base_url, params) + end +end diff --git a/app/jobs/update_telegram_posts.rb b/app/jobs/update_telegram_posts.rb new file mode 100644 index 0000000..9eb1f6d --- /dev/null +++ b/app/jobs/update_telegram_posts.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class UpdateTelegramPosts < ApplicationJob + queue_as :default + + def perform(post_id, base_url, params, old_title) + post = Post.find_by(id: post_id) + Platform::UpdateTelegramPosts.call(post, base_url, params, old_title) + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb index 338d51b..f93ac79 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -16,6 +16,10 @@ class Comment < ApplicationRecord validate :text_or_attachments + after_create :new_create_notification + after_update :new_update_notification + around_destroy :new_destroy_notification + acts_as_tree order: 'created_at ASC' def text_or_attachments @@ -26,17 +30,62 @@ def text_or_attachments def username if platform_user.present? + id = platform_user_id identifier = platform_user.identifier name = '' name += identifier['fname'] if identifier['fname'].present? name += identifier['lname'] if identifier['lname'].present? username = identifier[:username] + else + id = user.id + name = user.name + username = user.login end - { name: name.presence || '<No name>', username: username.presence || '' } + { id: id, name: name, username: username } end def destroy attachments.purge super end + + private + + def new_create_notification + if self.platform_user_id.present? || (self.user_id.present? && (self.post.user_id != self.user_id)) + Notification.create!(item: self, user_id: self.post.user_id, event: "create", status: "success") + end + end + + def new_update_notification + created_notification = self.post.user.notifications.where(item_type: self.class.name.to_s, event: "create").find{ |n| (n.item&.post_id.present? && (n.item.post_id == self.post_id)) && (n.item&.channel_id.nil? || (n.item&.channel_id.present? && (n.item.channel_id == self.channel_id))) } + + if created_notification.present? && ((Time.now - created_notification.created_at) > 15) + if self.platform_user_id.present? || (self.user_id.present? && (self.post.user_id != self.user_id)) + Notification.create!(item: self, user_id: self.post.user_id, event: "update", status: "success") + end + if self.user_id.present? && (self.post.user_id != self.user_id) + Notification.create!(item: self, user_id: self.user_id, event: "update", status: "warning") + end + end + end + + def new_destroy_notification + comment_user = self.user + comment_user_id = self.user_id + comment_platform_user = self.platform_user + comment_platform_user_id = self.platform_user_id + comment_post_user_id = self.post.user_id + comment_user_name = self.username + post_id = self.post_id + yield + if comment_platform_user_id.present? || (comment_user_id.present? && (comment_post_user_id != comment_user_id)) + name = comment_user_name[:name].present? ? comment_user_name[:name] : comment_user_name[:username] + text = "#{post_id}@@#{name}" + Notification.create!(item_type: self.class.name.to_s, user_id: comment_post_user_id, event: "delete", status: "success", text: text) + end + if comment_user_id.present? && (comment_post_user_id != comment_user_id) + Notification.create!(item_type: self.class.name.to_s, user_id: comment_user_id, event: "delete", status: "warning", text: post_id) + end + end end diff --git a/app/models/notification.rb b/app/models/notification.rb index 0830d69..5a583d4 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -1,13 +1,13 @@ class Notification < ApplicationRecord extend Enumerize - belongs_to :item, polymorphic: true + belongs_to :item, polymorphic: true, optional: true belongs_to :user scope :unviewed, ->{ where(viewed: false) } default_scope { latest } enumerize :event, in: %w[none create update delete], default: :none, scope: :having_event - enumerize :status, in: %w[notice success warning error], default: :notice, scope: :having_status + enumerize :status, in: %w[info success warning error], default: :info, scope: :having_status validates :event, :status, presence: true after_create_commit do diff --git a/app/models/platform_post.rb b/app/models/platform_post.rb index 8a124ac..0761054 100644 --- a/app/models/platform_post.rb +++ b/app/models/platform_post.rb @@ -7,6 +7,12 @@ class PlatformPost < ApplicationRecord belongs_to :channel belongs_to :platform + has_many :notifications, :as=>:item#, dependent: :destroy + + after_create :new_create_notification + after_update :new_update_notification + around_destroy :new_destroy_notification + def post_link case platform.title when 'telegram' @@ -24,4 +30,37 @@ def post_link "#{url}/#{event_id}" end end + + private + + def new_create_notification + notification = self.post.user.notifications.where(item_type: self.class.name.to_s, event: "create").find{ |n| (n.item&.post_id.present? && (n.item.post_id == self.post_id)) && (n.item&.channel_id.present? && (n.item.channel_id == self.channel_id)) } + if !notification.present? + Notification.create!(item: self, user_id: self.post.user.id, event: "create", status: "success") + end + end + + def new_update_notification + created_notification = self.post.user.notifications.where(item_type: self.class.name.to_s, event: "create").find{ |n| (n.item&.post_id.present? && (n.item.post_id == self.post_id)) && (n.item&.channel_id.present? && (n.item.channel_id == self.channel_id)) } + + if created_notification.present? && ((Time.now - created_notification.created_at) > 15) + notification = self.post.user.notifications.where(item_type: self.class.name.to_s, event: "update").find{ |n| (n.item&.post_id.present? && (n.item.post_id == self.post_id)) && (n.item&.channel_id.present? && (n.item.channel_id == self.channel_id)) } + if !notification.present? || ((Time.now - notification.updated_at) > 15) + Notification.create!(item: self, user_id: self.post.user.id, event: "update", status: "success") + end + end + end + + def new_destroy_notification + old_pp_post = self.post + old_pp_user = old_pp_post.user_id + old_pp_channel_id = self.channel_id + title = self.channel.options.dig("title") + yield + notifications = old_pp_post.platform_posts.where(channel_id: old_pp_channel_id) + if notifications.empty? + Notification.create!(item_type: "PlatformPost", user_id: old_pp_user, event: "delete", status: "success", text: "#{title}" ) + end + end + end diff --git a/app/models/post.rb b/app/models/post.rb index 87cfa9d..a94abc9 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -12,8 +12,11 @@ class Post < ApplicationRecord has_many :platform_posts, dependent: :delete_all has_many :item_tags, class_name: 'ItemTag', foreign_key: 'item_id', dependent: :delete_all has_many :active_tags, -> { active('Post') }, class_name: 'ItemTag', foreign_key: 'item_id' + has_many :notifications, :as=>:item, dependent: :destroy + has_many :platform_notifications, class_name: 'PlatformPost', foreign_key: 'post_id', dependent: :delete_all before_create :gen_uuid + around_destroy :new_destroy_notification has_many_attached :attachments do |attachable| attachable.variant :thumb100, resize_to_limit: [100, 100] @@ -82,7 +85,7 @@ def text end def destroy - DeletePostMessages.call(self) + DeletePostMessages.call(self, self.user) attachments.purge super end @@ -117,7 +120,16 @@ def user_ids User.where.not(id: self.user_id).ids end - # private + private + + def new_destroy_notification + old_post = self + old_post_id = self.id + user = self.user + old_post_title = self.title + yield + Notification.create!(item: old_post, user_id: user.id, event: "delete", status: "success", text: "#{old_post_title}" ) + end # def slug_candidates # [:title, [:title, :uuid]] diff --git a/app/services/delete_post_messages.rb b/app/services/delete_post_messages.rb index 1d4bca5..eedab96 100644 --- a/app/services/delete_post_messages.rb +++ b/app/services/delete_post_messages.rb @@ -3,10 +3,11 @@ class DeletePostMessages prepend SimpleCommand - attr_accessor :post, :channel_id + attr_accessor :post, :user, :channel_id - def initialize(post, channel_id = nil) + def initialize(post, user, channel_id = nil) @post = post + @user = user @channel_id = channel_id end @@ -14,8 +15,8 @@ def call if channel_id.nil? telegram_posts = post.platform_posts.joins(:platform).where(platforms: { title: 'telegram' }) matrix_posts = post.platform_posts.joins(:platform).where(platforms: { title: 'matrix' }) - Platform::DeleteTelegramPosts.call(telegram_posts) if telegram_posts.any? - Platform::DeleteMatrixPosts.call(matrix_posts) if matrix_posts.any? + DeleteTelegramPosts.perform_later(telegram_posts.ids, user.id) if telegram_posts.any? + DeleteMatrixPosts.perform_later(matrix_posts.ids, user.id) if matrix_posts.any? comment_ids = Comment.where(post: post).ids ActiveStorage::Attachment.where(record_type: 'Comment', record: comment_ids).destroy_all Comment.where(id: comment_ids).destroy_all @@ -26,9 +27,9 @@ def call title = Channel.find_by(id: channel_id).platform.title case title when 'telegram' - Platform::DeleteTelegramPosts.call(telegram_posts) if telegram_posts.any? + DeleteTelegramPosts.perform_later(telegram_posts.ids, user.id) if telegram_posts.any? when 'matrix' - Platform::DeleteMatrixPosts.call(matrix_posts) if matrix_posts.any? + DeleteMatrixPosts.perform_later(matrix_posts.ids, user.id) if matrix_posts.any? end platform = Platform.find_by(title: title) PlatformPost.where(platform: platform, post: post, channel_id: channel_id).destroy_all diff --git a/app/services/platform/delete_matrix_posts.rb b/app/services/platform/delete_matrix_posts.rb index 1f281a5..c8db680 100644 --- a/app/services/platform/delete_matrix_posts.rb +++ b/app/services/platform/delete_matrix_posts.rb @@ -3,10 +3,11 @@ class Platform::DeleteMatrixPosts prepend SimpleCommand - attr_accessor :platform_posts + attr_accessor :platform_posts, :user - def initialize(platform_posts) + def initialize(platform_posts, user) @platform_posts = platform_posts + @user = user end def call @@ -15,7 +16,8 @@ def call server = platform_post.channel.options['server'] begin # Matrix onlylink is a Hash, but attachments is an Array. - if platform_post.content.has_attachments? && !platform_post.identifier.is_a?(Hash) + #if platform_post.content.has_attachments? && !platform_post.identifier.is_a?(Hash) + if platform_post.identifier.is_a?(Array) platform_post.identifier.each do |att| method = "rooms/#{att['room_id']}/redact/#{att['event_id']}" data = { reason: "Delete post ##{platform_post.post_id}" } @@ -26,9 +28,11 @@ def call data = { reason: "Delete post ##{platform_post.post_id}" } Matrix.post(server, matrix_token, method, data) end - rescue StandardError - Rails.logger.error("Failed delete matrix messages at #{Time.now.utc.iso8601}".red) end end + rescue StandardError => e + Rails.logger.error("Failed delete matrix messages at #{Time.now.utc.iso8601}".red) + error_text = "Matrix (delete message: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: user.id, event: "destroy", status: "error", text: error_text) end end diff --git a/app/services/platform/delete_telegram_posts.rb b/app/services/platform/delete_telegram_posts.rb index 4ae0ef2..f1659d0 100644 --- a/app/services/platform/delete_telegram_posts.rb +++ b/app/services/platform/delete_telegram_posts.rb @@ -3,16 +3,17 @@ class Platform::DeleteTelegramPosts prepend SimpleCommand - attr_accessor :platform_posts + attr_accessor :platform_posts, :user - def initialize(platform_posts) + def initialize(platform_posts, user) @platform_posts = platform_posts + @user = user end def call @platform_posts.each do |platform_post| bot = Twilight::Application::CURRENT_TG_BOTS&.dig(platform_post.channel.token.to_s, :client) - if platform_post.content.has_attachments? + if platform_post.identifier.is_a?(Array) platform_post.identifier.each do |att| bot.delete_message({ chat_id: att['chat_id'], message_id: att['message_id'] }) end @@ -20,8 +21,10 @@ def call bot.delete_message({ chat_id: platform_post[:identifier]['chat_id'], message_id: platform_post[:identifier]['message_id'] }) end - rescue StandardError # Message don't delete (if bot don't have access to message) - Rails.logger.error("Failed delete telegram messages at #{Time.now.utc.iso8601}".red) end + rescue StandardError => e # Message don't delete (if bot don't have access to message) + Rails.logger.error("Failed delete telegram messages at #{Time.now.utc.iso8601}: #{e.message}".red) + error_text = "Telegram (delete message: #{e.message})" + Notification.create!(item_type: "PlatformPost", user_id: user.id, event: "destroy", status: "error", text: error_text) end end diff --git a/app/services/platform/send_post_to_matrix.rb b/app/services/platform/send_post_to_matrix.rb index 31bf113..acf6d24 100644 --- a/app/services/platform/send_post_to_matrix.rb +++ b/app/services/platform/send_post_to_matrix.rb @@ -19,7 +19,7 @@ def initialize(post, base_url, params, channel_ids) @options = @params[:options] if @options.present? @options = - @options&.to_unsafe_h&.inject({}) do |h, (k, v)| + @options&.inject({}) do |h, (k, v)| h[k] = (v.to_i == 1) h end @@ -80,6 +80,10 @@ def send_mx_content(content, text) PlatformPost.create!(identifier: identifier, platform: @platform, post: @post, content: content, channel_id: channel[:id]) end + rescue StandardError => e + Rails.logger.error("Error when send matrix message for chat #{channel[:id]} at #{Time.now.utc.iso8601}".red) + error_text = "Matrix (send message for chat #{channel[:id]}: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end def send_mx_attachments(content) @@ -131,6 +135,10 @@ def send_mx_attachments(content) PlatformPost.create!(identifier: uploaded_atts, platform: @platform, post: @post, content: content, channel_id: channel[:id]) end + rescue StandardError => e + Rails.logger.error("Error when send matrix attachment message for chat #{channel[:id]} at #{Time.now.utc.iso8601}".red) + error_text = "Matrix (send attachment message for chat #{channel[:id]}: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end def upload_to_matrix(channel) @@ -212,7 +220,9 @@ def send_mx_onlylink_post(content, channel, options) identifier = { event_id: JSON.parse(msg)['event_id'], room_id: channel[:room], options: options } PlatformPost.create!(identifier: identifier, platform: @platform, post: @post, content: content, channel_id: channel[:id]) - rescue StandardError - Rails.logger.error("Failed create matrix message for chat #{channel[:id]} at #{Time.now.utc.iso8601}".red) + rescue StandardError => e + Rails.logger.error("Error when send matrix onlylink message for chat #{channel[:id]} at #{Time.now.utc.iso8601}".red) + error_text = "Matrix (send onlylink message for chat #{channel[:id]}: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end end diff --git a/app/services/platform/send_post_to_telegram.rb b/app/services/platform/send_post_to_telegram.rb index 25998ab..1c116e7 100644 --- a/app/services/platform/send_post_to_telegram.rb +++ b/app/services/platform/send_post_to_telegram.rb @@ -23,7 +23,7 @@ def initialize(post, base_url, params, channel_ids) @options = @params[:options] if @options.present? @options = - @options&.to_unsafe_h&.inject({}) do |h, (k, v)| + @options&.inject({}) do |h, (k, v)| h[k] = (v.to_i == 1) h end @@ -143,15 +143,17 @@ def send_telegram_content(bot, channel, text_content, options, index) text: text, parse_mode: 'html', disable_notification: !options[:enable_notifications] }) - - PlatformPost.create!(identifier: { chat_id: @msg['result']['chat']['id'], - message_id: @msg['result']['message_id'], - date: @msg['result']['date'], - options: options }, platform: @platform, - post: @post, content: text_content, channel_id: channel[:id] - ) + + identifier = { chat_id: @msg['result']['chat']['id'], + message_id: @msg['result']['message_id'], + date: @msg['result']['date'], + options: options + } + PlatformPost.create!(identifier: identifier, platform: @platform, post: @post, content: text_content, channel_id: channel[:id]) rescue StandardError => e Rails.logger.error("Failed create telegram message for chat #{channel[:id]} at #{Time.now.utc.iso8601}:\n#{e}".red) + error_text = "Telegram (create message for chat #{channel[:id]}: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end def send_telegram_attachments(bot, channel, options, has_caption, media) @@ -208,7 +210,9 @@ def send_telegram_attachments(bot, channel, options, has_caption, media) PlatformPost.create!(identifier: msg_ids, platform: @platform, post: @post, content: attachment_content, channel_id: channel[:id]) rescue StandardError => e - Rails.logger.error("Failed create tg message (attachment) for chat #{channel[:room]} at #{Time.now.utc.iso8601}:\n#{e}".red) + Rails.logger.error("Failed create telegram attachment message for chat #{channel[:room]} at #{Time.now.utc.iso8601}:\n#{e}".red) + error_text = "Telegram (create attachment message for chat #{channel[:room]}: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end end @@ -249,7 +253,9 @@ def upload_to_attachment_channel(channel) { type: 'document', media: msg['result']['document']['file_id'], blob_signed_id: blob_signed_id } end rescue StandardError => e - Rails.logger.error("Failed upload telegram message at #{Time.now.utc.iso8601}:\n#{e}".red) + Rails.logger.error("Failed upload telegram message in room #{attachment_channel} at #{Time.now.utc.iso8601}:\n#{e}".red) + error_text = "Telegram (upload message in room #{attachment_channel}: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end end @@ -292,6 +298,8 @@ def send_tg_onlylink_post(bot, channel, options) content: content, channel_id: channel[:id] ) rescue StandardError => e - Rails.logger.error("Failed create telegram message for chat #{channel[:id]} at #{Time.current.utc.iso8601}:\n#{e}".red) + Rails.logger.error("Failed create telegram onlylink message for chat #{channel[:id]} at #{Time.current.utc.iso8601}:\n#{e}".red) + error_text = "Telegram (create onlylink message: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end end diff --git a/app/services/platform/update_matrix_posts.rb b/app/services/platform/update_matrix_posts.rb index e797052..7eb8729 100644 --- a/app/services/platform/update_matrix_posts.rb +++ b/app/services/platform/update_matrix_posts.rb @@ -78,11 +78,16 @@ def call } } Matrix.post(server, matrix_token, method, data) + platform_post.update(updated_at: Time.now()) # Only for notifications work! end + rescue StandardError => e + Rails.logger.error("Error when update matrix message at #{Time.now.utc.iso8601}".red) + error_text = "Matrix (update message: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end def delete_attachments(platform_posts) - attachments = @deleted_attachments.to_unsafe_h + attachments = @deleted_attachments del_att = attachments.select { |val| attachments[val] == '0' } platform_posts.joins(:content).where(contents: { has_attachments: true }).each do |platform_post| @@ -106,6 +111,10 @@ def delete_attachments(platform_posts) new_params.present? ? platform_post.update!(identifier: new_params) : platform_post.delete end end + rescue StandardError => e + Rails.logger.error("Error when update matrix message (delete attachments) at #{Time.now.utc.iso8601}".red) + error_text = "Matrix (update message (delete attachments): #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end def edit_mx_onlylink_post(platform_post, room_id, event_id) @@ -134,5 +143,10 @@ def edit_mx_onlylink_post(platform_post, room_id, event_id) } } Matrix.post(server, matrix_token, method, data) + platform_post.update(updated_at: Time.now()) # Only for notifications work! + rescue StandardError => e + Rails.logger.error("Error when update matrix onlylink message at #{Time.now.utc.iso8601}".red) + error_text = "Matrix (update onlylink message: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end end diff --git a/app/services/platform/update_telegram_posts.rb b/app/services/platform/update_telegram_posts.rb index d4ebde0..7efae9f 100644 --- a/app/services/platform/update_telegram_posts.rb +++ b/app/services/platform/update_telegram_posts.rb @@ -17,7 +17,7 @@ def initialize(post, base_url, params, old_title) @new_title = params[:post][:title] @new_text = params[:post][:content] - @deleted_attachments = @params[:deleted_attachments]&.to_unsafe_h&.select { |_k, v| v == '0' }&.keys + @deleted_attachments = @params[:deleted_attachments]&.select { |_k, v| v == '0' }&.keys # need sort @deleted_attachments in the order of their posting platforms (grouping of pictures) #@attachments = @post.attachments.map { |att| att.blob.signed_id } #@deleted_attachments = @attachments&.select { |att| att.in?(@deleted_attachments) } @@ -133,6 +133,10 @@ def add_content(new_block, index) content: content, channel_id: channel[:id] ) end + rescue StandardError => e + Rails.logger.error("Failed to update telegram message at #{Time.now.utc.iso8601}".red) + error_text = "Telegram (update message: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end def update_content(new_text, index) @@ -161,7 +165,13 @@ def update_content(new_text, index) message_id: platform_post.identifier['message_id'], text: text, parse_mode: 'html' }) + + platform_post.update(updated_at: Time.now()) # Only for notifications work! end + rescue StandardError => e + Rails.logger.error("Failed to update telegram message at #{Time.now.utc.iso8601}".red) + error_text = "Telegram (update message: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end def check_onlylink @@ -185,6 +195,12 @@ def update_onlylink(bot, platform_post) message_id: platform_post.identifier['message_id'], text: onlylink_text, parse_mode: 'html' }) + + platform_post.update(updated_at: Time.now()) # Only for notifications work! + rescue StandardError => e + Rails.logger.error("Failed to update telegram onlylink message at #{Time.now.utc.iso8601}".red) + error_text = "Telegram (update onlylink message: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end def check_caption @@ -263,7 +279,7 @@ def remove_content(index) onlylink_pps = @post.platform_posts.select{ |pp| pp_onlylink(pp) }.map(&:id) platform_posts = @post.platform_posts.where(content: content, platform: @platform).where.not(id: onlylink_pps) - Platform::DeleteTelegramPosts.call(platform_posts) + Platform::DeleteTelegramPosts.call(platform_posts, @post.user) PlatformPost.where(platform: platform_posts, post: @post).destroy_all content.destroy @@ -312,8 +328,10 @@ def delete_attachments #@post.attachments.find_by(blob_id: ActiveStorage::Blob.find_signed!(attachment).id).purge #end #@post.contents.first&.upd_post if @deleted_attachments.present? - rescue StandardError + rescue StandardError => e Rails.logger.error("Failed to delete telegram attachments message at #{Time.now.utc.iso8601}".red) + error_text = "Telegram (delete attachments: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end def move_caption(bot, platform_post, deleted_indexes) @@ -382,8 +400,10 @@ def edit_media_caption(bot, identifier, text) media: media }) # In an amicable way, if there are no attachments, you need to convert the media message to text, but this cannot be done # so the caption is removed if there are no attachments - rescue StandardError + rescue StandardError => e Rails.logger.error("Failed edit caption for telegram message at #{Time.now.utc.iso8601}".red) + error_text = "Telegram (edit caption: #{e.message})" + Notification.create!(item_type: PlatformPost, user_id: @post.user.id, event: "create", status: "error", text: error_text) end def post_options(post, channel) diff --git a/app/services/send_post_to_platforms.rb b/app/services/send_post_to_platforms.rb index 97dac3f..8b0a601 100644 --- a/app/services/send_post_to_platforms.rb +++ b/app/services/send_post_to_platforms.rb @@ -6,7 +6,7 @@ class SendPostToPlatforms attr_accessor :post, :params def initialize(post, base_url, params) - @params = params + @params = params.to_unsafe_h @post = post @base_url = base_url @@ -16,7 +16,7 @@ def initialize(post, base_url, params) return unless @options.present? @options = - @options&.to_unsafe_h&.inject({}) do |h, (k, v)| + @options&.inject({}) do |h, (k, v)| h[k] = (v.to_i == 1) h end @@ -46,7 +46,7 @@ def call return if params[:channels].nil? || params[:channels].values.exclude?('1') channel_ids = [] - params[:channels].to_unsafe_h.select { |_k, v| v == '1' }.each do |k, _v| + params[:channels].select { |_k, v| v == '1' }.each do |k, _v| channel_ids.append(k) end @@ -72,23 +72,17 @@ def call channels = merged.sort_by { |k, _v| k }.reverse.to_h # { "telegram"=>[1, 2], "matrix"=>3 } - # Только так, иначе всё сломается! - Thread.new do - execution_context = Rails.application.executor.run! - channels.each do |k, v| - check_platforms(k, v) - ensure - execution_context&.complete! - end + channels.each do |k, v| + check_platforms(k, v) end end def check_platforms(platform, channel_ids) case platform when 'telegram' - Platform::SendPostToTelegram.call(@post, @base_url, params, channel_ids) + SendPostToTelegram.perform_later(@post.id, @base_url, @params, channel_ids) when 'matrix' - Platform::SendPostToMatrix.call(@post, @base_url, params, channel_ids) + SendPostToMatrix.perform_later(@post.id, @base_url, @params, channel_ids) end end end diff --git a/app/services/update_post_messages.rb b/app/services/update_post_messages.rb index b112b33..7660d9c 100644 --- a/app/services/update_post_messages.rb +++ b/app/services/update_post_messages.rb @@ -6,7 +6,7 @@ class UpdatePostMessages attr_accessor :post, :params def initialize(post, base_url, params) - @params = params + @params = params.to_unsafe_h @post = post @base_url = base_url @@ -50,14 +50,9 @@ def call update_blog_posts return if @post.platform_posts.empty? - Thread.new do - execution_context = Rails.application.executor.run! - posted_platforms = @post.platforms + posted_platforms = @post.platforms - Platform::UpdateTelegramPosts.call(@post, @base_url, params, old_title) if posted_platforms['telegram'] - Platform::UpdateMatrixPosts.call(@post, @base_url, params) if posted_platforms['matrix'] - ensure - execution_context&.complete! - end + UpdateTelegramPosts.perform_later(@post.id, @base_url, params, old_title) if posted_platforms['telegram'] + UpdateMatrixPosts.perform_later(@post.id, @base_url, params) if posted_platforms['matrix'] end end diff --git a/app/views/notifications/_comment.html.erb b/app/views/notifications/_comment.html.erb new file mode 100644 index 0000000..92b1d13 --- /dev/null +++ b/app/views/notifications/_comment.html.erb @@ -0,0 +1,75 @@ +<%from_time = Time.now%> +<%distance = distance_of_time_in_words(from_time, notification.updated_at)%> +<%event = notification.event%> +<%status = notification.status%> +<%text = notification.text || "test message"%> +<%link = nil%> + +<%post_url = notification.item&.post%> +<%user = notification.item&.user&.displayed_name || "-"%> +<%post_title = notification.item&.post&.title || "-"%> + +<%case event%> + <%when "none"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% res = "#{distance} ✅ #{text}" %> + <%when "warning"%> + <% res = "#{distance} ⚠️ #{text}" %> + <%when "error"%> + <% res = "#{distance} 🛑 #{text}" %> + <%end%> + <%when "create"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% linkk = post_url.present? ? link_to(post_title, post_url) : link_to(post_title, "#") %> + <% text = "#{I18n.t('notifications.comment.create.success', user: user, post: linkk)}" %> + <% res = "#{distance} ✅ #{text}".html_safe %> + <%when "warning"%> + <% res = "#{distance} ⚠️ #{text}" %> + <%when "error"%> + <% res = "#{distance} 🛑 #{text}" %> + <%end%> + <%when "update"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% linkk = post_url.present? ? link_to(post_title, post_url) : link_to(post_title, "#") %> + <% text = "#{I18n.t('notifications.comment.update.success', user: user, post: linkk)}" %> + <% res = "#{distance} ✅ #{text}".html_safe %> + <%when "warning"%> + <% linkk = post_url.present? ? link_to(post_title, post_url) : link_to(post_title, "#") %> + <% text = "#{I18n.t('notifications.comment.update.warning', post: linkk)}" %> + <% res = "#{distance} ⚠️ #{text}".html_safe %> + <%when "error"%> + <% res = "#{distance} 🛑 #{text}" %> + <%end%> + <%when "delete"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% text = text.split('@@')%> + <% post_url = Post.find_by(id: text[0])%> + <% post_title = post_url&.title || "-"%> + <% linkk = post_url.present? ? link_to(post_title, post_url) : link_to(post_title, "#") %> + <% res = "#{distance} ✅ #{I18n.t('notifications.comment.delete.success', user: text[1], post: linkk)}".html_safe %> + <%when "warning"%> + <% post_url = Post.find_by(id: text)%> + <% post_title = post_url&.title || "-"%> + <% linkk = post_url.present? ? link_to(post_title, post_url) : link_to(post_title, "#") %> + <% res = "#{distance} ⚠️ #{I18n.t('notifications.comment.delete.warning', post: linkk)}".html_safe %> + <%when "error"%> + <% res = "#{distance} 🛑 #{text}" %> + <%end%> +<%end%> +<%if link.present?%> + <p><%=res%>: <%=link.html_safe%></p> +<%else%> + <p><%=res%></p> +<%end%> \ No newline at end of file diff --git a/app/views/notifications/_platformpost.html.erb b/app/views/notifications/_platformpost.html.erb new file mode 100644 index 0000000..26f7bce --- /dev/null +++ b/app/views/notifications/_platformpost.html.erb @@ -0,0 +1,75 @@ +<%from_time = Time.now%> +<%distance = distance_of_time_in_words(from_time, notification.updated_at)%> +<%event = notification.event%> +<%status = notification.status%> +<%text = notification.text || "test message"%> +<%link = nil%> + +<% title = notification.item&.channel&.options&.dig("title") || "-"%> +<% url = notification.item&.channel&.options&.dig("url")%> + +<%case event%> + <%when "none"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% res = "#{distance} ✅ #{text}" %> + <%when "warning"%> + <% res = "#{distance} ⚠️ #{text}" %> + <%when "error"%> + <% res = "#{distance} 🛑 #{text}" %> + <%end%> + <%when "create"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% link = url.present? ? "#{link_to(title, url)}" : "#{link_to("#{title} (Private)", "#")}"%> + <% text = "#{I18n.t('notifications.platform_post.create.success')}" %> + <% res = "#{distance} ✅ #{text}" %> + <%when "warning"%> + <% link = url.present? ? "#{link_to(title, url)}" : "#{link_to("#{title} (Private)", "#")}"%> + <% text = "#{I18n.t('notifications.platform_post.create.warning')}" %> + <% res = "#{distance} ⚠️ #{text}" %> + <%when "error"%> + <% res = "#{distance} 🛑 #{I18n.t('notifications.platform_post.create.error')}: #{text}" %> + <%end%> + <%when "update"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% link = url.present? ? "#{link_to(title, url)}" : "#{link_to("#{title} (Private)", "#")}"%> + <% text = "#{I18n.t('notifications.platform_post.update.success')}" %> + <% res = "#{distance} ✅ #{text}" %> + <%when "warning"%> + <% link = url.present? ? "#{link_to(title, url)}" : "#{link_to("#{title} (Private)", "#")}"%> + <% text = "#{I18n.t('notifications.platform_post.update.warning')}" %> + <% res = "#{distance} ⚠️ #{text}" %> + <%when "error"%> + <% res = "#{distance} 🛑 #{I18n.t('notifications.platform_post.update.error')}: #{text}" %> + <%end%> + <%when "delete"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% title = text %> + <% link = "#{link_to("#{title}", "#")}"%> + <% res = "#{distance} ✅ #{I18n.t('notifications.platform_post.delete.success')}"%> + <%when "warning"%> + <% title = text %> + <% link = "#{link_to("#{title}", "#")}"%> + <% res = "#{distance} ⚠️ #{I18n.t('notifications.platform_post.delete.warning')}"%> + <%when "error"%> + <% title = text %> + <% link = "#{link_to("#{title}", "#")}"%> + <% res = "#{distance} 🛑 #{I18n.t('notifications.platform_post.delete.error')}" %> + <%end%> +<%end%> +<%if link.present?%> + <p><%=res%>: <%=link.html_safe%></p> +<%else%> + <p><%=res%></p> +<%end%> \ No newline at end of file diff --git a/app/views/notifications/_post.html.erb b/app/views/notifications/_post.html.erb index 0568efd..92c4beb 100644 --- a/app/views/notifications/_post.html.erb +++ b/app/views/notifications/_post.html.erb @@ -1,7 +1,60 @@ <%from_time = Time.now%> <%distance = distance_of_time_in_words(from_time, notification.updated_at)%> -<p> - <%=distance%> ● <%= notification.item.user.login %> posted: - <%= link_to notification.item.title, notification.item, class: "underline font-medium" %> -</p> -<!-- Unsed but may be useful --> \ No newline at end of file +<%event = notification.event%> +<%status = notification.status%> +<%text = notification.text || "test message"%> +<%link = nil%> + +<%case event%> + <%when "none"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% res = "#{distance} ✅ #{text}" %> + <%when "warning"%> + <% res = "#{distance} ⚠️ #{text}" %> + <%when "error"%> + <% res = "#{distance} 🛑 #{text}" %> + <%end%> + <%when "create"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% res = "#{distance} ✅ #{text}" %> + <%when "warning"%> + <% res = "#{distance} ⚠️ #{text}" %> + <%when "error"%> + <% res = "#{distance} 🛑 #{text}" %> + <%end%> + <%when "update"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% res = "#{distance} ✅ #{text}" %> + <%when "warning"%> + <% res = "#{distance} ⚠️ #{text}" %> + <%when "error"%> + <% res = "#{distance} 🛑 #{text}" %> + <%end%> + <%when "delete"%> + <%case status%> + <%when "info"%> + <% res = "#{distance} ⓘ #{text}" %> + <%when "success"%> + <% title = text %> + <% linkk = "#{link_to("#{title}", "#")}"%> + <% res = "#{distance} ✅ #{I18n.t('notifications.post.delete.success', post: linkk)}".html_safe%> + <%when "warning"%> + <% res = "#{distance} ⚠️ #{text}" %> + <%when "error"%> + <% res = "#{distance} 🛑 #{text}" %> + <%end%> +<%end%> +<%if link.present?%> + <p><%=res%>: <%=link.html_safe%></p> +<%else%> + <p><%=res%></p> +<%end%> \ No newline at end of file diff --git a/app/views/posts/edit.html.erb b/app/views/posts/edit.html.erb index 270dfb4..737281b 100644 --- a/app/views/posts/edit.html.erb +++ b/app/views/posts/edit.html.erb @@ -1,5 +1,4 @@ <body id="bg"> - <%= render "notifications/notification_window" %> <div class="container"> <h2><%= I18n.t("posts.edit_header") %></h2> diff --git a/app/views/posts/new.html.erb b/app/views/posts/new.html.erb index cc67a1e..9628ca4 100644 --- a/app/views/posts/new.html.erb +++ b/app/views/posts/new.html.erb @@ -1,5 +1,4 @@ <body id="bg"> - <%= render "notifications/notification_window" %> <div class="container"> <h2><%= I18n.t("posts.new_header") %></h2> diff --git a/config/locales/en.yml b/config/locales/en.yml index 89cbb47..1bd829a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -207,6 +207,44 @@ en: delete: Delete comment feeds: no_results: No results found. + notifications: + platform_post: + create: + success: The post has been successfully created on the channel + warning: There was a problem with a post on the channel + error: There was an error when creating a post on the channel + update: + success: The post was successfully updated on the channel + warning: Having trouble updating a post on the channel + error: There was an error when updating the post on the channel + delete: + success: The post was successfully deleted from the channel + warning: There were problems when deleting a post from the channel + error: There was an error when deleting a post from the channel + post: + create: + success: The post %{post} was successfully created + warning: There was a problem with the post + error: There was an error when creating a post + update: + success: The post %{post} has been successfully updated + warning: Having trouble updating the post + error: There was an error when updating the post + delete: + success: The post %{post} was successfully deleted + warning: Having trouble deleting a post + error: There was an error when deleting a post + comment: + create: + success: "%{user} left a comment on post %{post}" + error: An error occurred while creating a comment + update: + success: "%{user} comment in post %{post} has been updated" + warning: Your comment in post %{post} has been edited + error: There was an error when updating a comment + delete: + success: "%{user} comment in post %{post} has been removed" + warning: Your comment in post %{post} has been deleted devise: registrations: user: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 96cc363..3a3f55d 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -207,6 +207,44 @@ ru: delete: Удалить feeds: no_results: Поиск не дал результатов. + notifications: + platform_post: + create: + success: Пост успешно создан на канале + warning: Возникли проблемы с постом на канале + error: Произошла ошибка при создании поста на канале + update: + success: Пост успешно обновлён на канале + warning: Возникли проблемы при обновлении поста на канале + error: Произошла ошибка при обновлении поста на канале + delete: + success: Пост успешно удалён с канала + warning: Возникли проблемы при удалении поста с канала + error: Произошла ошибка при удалении поста с канала + post: + create: + success: Пост %{post} успешно создан + warning: Возникли проблемы с постом + error: Произошла ошибка при создании поста + update: + success: Пост %{post} успешно обновлён + warning: Возникли проблемы при обновлении поста + error: Произошла ошибка при обновлении поста + delete: + success: Пост %{post} успешно удалён + warning: Возникли проблемы при удалении поста + error: Произошла ошибка при удалении поста + comment: + create: + success: "%{user} оставил(а) комментарий к посту %{post}" + error: Произошла ошибка при создании комментария + update: + success: "Комментарий пользователя %{user} в посте %{post} был обновлён" + warning: Ваш комметарий в посте %{post} был изменён + error: Произошла ошибка при обновлении комментария + delete: + success: Комментарий пользователя %{user} в посте %{post} был удалён + warning: Ваш комметарий в посте %{post} был удалён devise: registrations: user: diff --git a/db/migrate/20240904090005_create_notifications.rb b/db/migrate/20240904090005_create_notifications.rb index e876046..85a11ca 100644 --- a/db/migrate/20240904090005_create_notifications.rb +++ b/db/migrate/20240904090005_create_notifications.rb @@ -1,7 +1,7 @@ class CreateNotifications < ActiveRecord::Migration[7.0] def change create_table :notifications do |t| - t.references :item, polymorphic: true, null: false + t.references :item, polymorphic: true t.references :user, null: false, foreign_key: true t.boolean :viewed, null: false, default: false t.string :event diff --git a/update_log.md b/update_log.md index 3dd4207..12c13e4 100644 --- a/update_log.md +++ b/update_log.md @@ -12,6 +12,9 @@ * Теперь посты имеют UUID; * Добавлена генерация slug на основе названия поста; * Добавлена опция скрытия поста из ленты (их видит только автор); +* Добавлена панель уведомлений: + * При создании/изменении/удалении постов в платформах будут приходить уведомления о статусе операции; + * При создании/изменении/удалении комментариев также будут приходить уведомления; * Измемены комментарии: * Теперь комментарии имеют иерархию (на основе ответов); * Возможность отвечать в комментариях: From 2be29c517a8fa98004ee87b8ade4581678c726af Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Tue, 10 Sep 2024 18:53:36 +0300 Subject: [PATCH 42/47] Update platform comments --- app/controllers/comments_controller.rb | 11 ++-- app/jobs/update_matrix_comments.rb | 12 ++++ app/jobs/update_telegram_comments.rb | 11 ++++ app/models/comment.rb | 2 + .../platform/update_matrix_comments.rb | 18 ++++++ .../platform/update_telegram_comments.rb | 62 +++++++++++++++++++ app/services/update_platform_comments.rb | 31 ++++++++++ 7 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 app/jobs/update_matrix_comments.rb create mode 100644 app/jobs/update_telegram_comments.rb create mode 100644 app/services/platform/update_matrix_comments.rb create mode 100644 app/services/platform/update_telegram_comments.rb create mode 100644 app/services/update_platform_comments.rb diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 0d5c0c3..485c54d 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -68,14 +68,11 @@ def update authorize! current_comment ref_url = request.referrer - if current_comment.update(text: params[:comment][:content], is_edited: true) - if ref_url.include?("feed") - redirect_to ref_url - else - redirect_to post_path(current_comment.post) - end + UpdatePlatformComments.call(Comment.where(id: current_comment.id), current_user, params) + if ref_url.include?("feed") + redirect_to ref_url else - render :edit + redirect_to post_path(current_comment.post) end end diff --git a/app/jobs/update_matrix_comments.rb b/app/jobs/update_matrix_comments.rb new file mode 100644 index 0000000..71c86c9 --- /dev/null +++ b/app/jobs/update_matrix_comments.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class UpdateMatrixComments < ApplicationJob + queue_as :default + + def perform(mx_comments_ids, user_id, text) + mx_comments = Comment.where(id: mx_comments_ids) + user = User.find_by(id: user_id) + Platform::UpdateMatrixComments.call(mx_comments, user, text) + end +end +# Not used \ No newline at end of file diff --git a/app/jobs/update_telegram_comments.rb b/app/jobs/update_telegram_comments.rb new file mode 100644 index 0000000..d23c1b4 --- /dev/null +++ b/app/jobs/update_telegram_comments.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class UpdateTelegramComments < ApplicationJob + queue_as :default + + def perform(tg_comments_ids, user_id, text) + tg_comments = Comment.where(id: tg_comments_ids) + user = User.find_by(id: user_id) + Platform::UpdateTelegramComments.call(tg_comments, user, text) + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb index f93ac79..96c949e 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Comment < ApplicationRecord + has_paper_trail + belongs_to :post belongs_to :user, optional: true # Site comment belongs_to :channel, optional: true # Optional if used linked channel diff --git a/app/services/platform/update_matrix_comments.rb b/app/services/platform/update_matrix_comments.rb new file mode 100644 index 0000000..61a7ff1 --- /dev/null +++ b/app/services/platform/update_matrix_comments.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Platform::UpdateMatrixComments + prepend SimpleCommand + + attr_accessor :comms, :user, :text + + def initialize(comms, user, text) + @comms = comms + @user = user + @text = text + end + + def call + print("NOT IMPLEMENTED YET!") + end +end +# Not used \ No newline at end of file diff --git a/app/services/platform/update_telegram_comments.rb b/app/services/platform/update_telegram_comments.rb new file mode 100644 index 0000000..1ce5c4c --- /dev/null +++ b/app/services/platform/update_telegram_comments.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +class Platform::UpdateTelegramComments + prepend SimpleCommand + + attr_accessor :comms, :user, :text + + def initialize(comms, user, text) + @comms = comms + @user = user + @text = text + end + + # TODO: Attachments update support? + def call + @comms.each do |comm| + post_user = comm.post.user + + if @user.id != comm.channel.user_id + if comm.user_id.present? && (@user.id != comm.user_id) + send_text = "#{post_user.displayed_name} (Edited by #{@user.displayed_name}):\n#{@text}" + else + send_text = "#{comm.user.displayed_name}:\n#{@text}" + end + else + if comm.user_id.present? && (@user.id != comm.user_id) + send_text = "#{comm.user.displayed_name} (Edited by #{@user.displayed_name}):\n#{@text}" + else + send_text = "#{@text}" + end + end + begin + bot = get_tg_bot(comm) + if comm.identifier.is_a?(Array) + comm.identifier.each do |ident| + bot.edit_message_text({ chat_id: ident['chat_id'], + message_id: ident['message_id'], + text: send_text, + parse_mode: 'html' + }) + end + comm.update!(text: @text, is_edited: true) + else + bot.edit_message_text({ chat_id: comm.identifier['chat_id'], + message_id: comm.identifier['message_id'], + text: send_text, + parse_mode: 'html' + }) + comm.update!(text: @text, is_edited: true) + end + rescue StandardError => e + Rails.logger.error("Failed update telegram comment at #{Time.now.utc.iso8601}: #{e.message}".red) + error_text = "Telegram (update comment: #{e.message})" + Notification.create!(item: comm, user_id: user.id, event: "update", status: "error", text: error_text) + end + end + end + + def get_tg_bot(comm) + Twilight::Application::CURRENT_TG_BOTS&.dig(comm.channel.token.to_s, :client) + end +end diff --git a/app/services/update_platform_comments.rb b/app/services/update_platform_comments.rb new file mode 100644 index 0000000..1363411 --- /dev/null +++ b/app/services/update_platform_comments.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class UpdatePlatformComments + prepend SimpleCommand + + attr_accessor :comments, :current_user, :params + + def initialize(comments, current_user, params) + @params = params.to_unsafe_h + @text = @params[:comment][:content] + @comments = comments + @current_user = current_user + end + + def call + blog_platform = Platform.find_by(title: 'blog') + @comments.where("platform_id=? or platform_id is NULL", blog_platform.id).update!(text: @text, is_edited: true) + + tg_platform = Platform.find_by(title: 'telegram') + matrix = Platform.find_by(title: 'matrix') + + if tg_platform.present? + tg_comments = @comments.where(platform: tg_platform) + UpdateTelegramComments.perform_later(tg_comments.ids, @current_user.id, @text) if tg_comments.any? + end + if matrix.present? + mx_comments = @comments.where(platform: matrix) + UpdateMatrixComments.perform_later(tg_comments.ids, @current_user.id, @text) if mx_comments.any? + end + end +end From 1fdf219d224ea1d92c4479543c50e5ba6bf06318 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Fri, 13 Sep 2024 10:56:48 +0300 Subject: [PATCH 43/47] Notification window option --- app/models/user.rb | 6 +++++- app/views/notifications/_notification_window.html.erb | 2 +- app/views/users/registrations/_edit_profile.html.erb | 4 ++++ config/locales/en.yml | 1 + config/locales/ru.yml | 1 + 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 59e4826..858d483 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -50,7 +50,7 @@ def generate_rss def default_options self.options = { visible_posts_count: Rails.configuration.credentials[:rss_default_visible_posts].to_s, - theme: 'default_theme' } + theme: 'default_theme', notification_window: { enabled: true } } end def will_save_change_to_email? @@ -119,6 +119,10 @@ def displayed_name login end end + + def is_notification_window_enabled + options.dig('notification_window', 'enabled') || false + end def destroy diff --git a/app/views/notifications/_notification_window.html.erb b/app/views/notifications/_notification_window.html.erb index edaf863..1298569 100644 --- a/app/views/notifications/_notification_window.html.erb +++ b/app/views/notifications/_notification_window.html.erb @@ -1,4 +1,4 @@ -<% if user_signed_in? %> +<% if user_signed_in? && current_user.is_notification_window_enabled%> <%= turbo_stream_from dom_id(current_user, :broadcast_to) %> <div class="notification-window"> <details open class="accordion-item"> diff --git a/app/views/users/registrations/_edit_profile.html.erb b/app/views/users/registrations/_edit_profile.html.erb index df86d9c..f5b1781 100644 --- a/app/views/users/registrations/_edit_profile.html.erb +++ b/app/views/users/registrations/_edit_profile.html.erb @@ -22,6 +22,10 @@ <br><br> <%= field.label "#{I18n.t("edit.locale")}:" %> <%= field.select :locale, [['English', 'en'], ['Russian', 'ru']], :selected => (current_user.options.dig("locale") || "Default locale")%> + <br><br> + <%nw_enabled = current_user.is_notification_window_enabled%> + <%= check_box_tag("user[options][notification_window][enabled]", nw_enabled, nw_enabled, { :class=>"colorful-checkbox", :name =>"user[options][notification_window][enabled]" })%> + <%= label "user[options][notification_window][enabled]", I18n.t("manage.is_notification_window_enabled") %> <% end %> </div> diff --git a/config/locales/en.yml b/config/locales/en.yml index 1bd829a..b71a9e0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -178,6 +178,7 @@ en: list_invite_max_usages: Max usages list_invite_expires_at: Expires at list_invite_created_at: Created at + is_notification_window_enabled: Turn on the notification window? categories: manage_categories: Manage categories no_categories: You have no categories! diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 3a3f55d..a93adfa 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -178,6 +178,7 @@ ru: list_invite_max_usages: Макс. Исп. list_invite_expires_at: Дата просрочки list_invite_created_at: Дата создания + is_notification_window_enabled: Включить панель уведомлений? categories: manage_categories: Управление категориями no_categories: У вас нет категорий! From f74df4cfa3be69abba5eb0e2463a418a5d6d90cd Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Mon, 16 Sep 2024 15:28:28 +0300 Subject: [PATCH 44/47] Readme && Docker update --- Dockerfile | 3 +-- Gemfile | 9 +++++---- README-RU.md | 3 ++- README.md | 3 ++- babel.config.js | 4 +++- package.json | 8 ++++---- 6 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index ab1f10f..8e11d07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,7 +39,7 @@ COPY package.json yarn.lock ./ # NodeJS & yarn install RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash -RUN . ~/.nvm/nvm.sh && nvm install 16 && nvm alias default 16 && \ +RUN . ~/.nvm/nvm.sh && nvm install 20.12.2 && nvm alias default 20.12.2 && \ yarn install # We're back at the base stage @@ -68,7 +68,6 @@ COPY . /root/app/ # For EasyCaptcha install COPY --from=dependencies /vendor/gems/ /root/app/vendor/gems/ - WORKDIR /root/app # Install assets diff --git a/Gemfile b/Gemfile index d68e5f5..c73f4a5 100644 --- a/Gemfile +++ b/Gemfile @@ -19,7 +19,7 @@ gem 'webpacker', '>= 5.4.2' # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] gem 'importmap-rails' # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] -gem 'turbo-rails' +gem 'turbo-rails', '~> 2.0.5' # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks gem 'turbolinks', '>= 5' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder @@ -54,6 +54,7 @@ group :development, :test do gem 'rspec' gem 'rspec-rails' gem 'rubocop-rspec', require: false + gem 'rubocop-performance' end group :test do @@ -81,18 +82,18 @@ gem 'active_storage_validations' gem 'addressable', '>= 2.8.0' gem "ahoy_matey" gem 'betterlorem' -gem 'closure_tree' +gem 'closure_tree', '>= 7.4.0' gem 'devise' gem 'dry-initializer-rails' gem 'easy_captcha', path: 'vendor/gems/easy_captcha' gem 'enumerize' -gem 'friendly_id', '~> 5.5.0' +gem 'friendly_id', '~> 5.5.1' gem 'grape' gem 'image_processing', '>= 1.12.2' gem 'mini_magick' gem 'nokogiri', '>= 1.11.0.rc4' gem 'open-uri' -gem 'paper_trail' +gem 'paper_trail', '>= 15.1.0' gem 'pg' gem 'rake', '>= 13.2.1' gem 'redcarpet' diff --git a/README-RU.md b/README-RU.md index 15f8d0e..48fc275 100644 --- a/README-RU.md +++ b/README-RU.md @@ -38,7 +38,7 @@ P.S. Список последних изменений можно посмот * Telegram: * Отправка в платформы: Да * Редактирование, удаление: Да - * Отправка из платформы: Нет + * Отправка из платформы на сайт: Да * Поддержка комментариев: Да * Поддержка аттачменов: картинки, видео, аудио, файлы @@ -91,6 +91,7 @@ gem install wdm -- --with-cflags=-Wno-implicit-function-declaration ``` git clone https://github.com/Whiletruedoend/Twilight cd Twilight/ + yarn ``` * (Не обязательно) Настроить файл .env для подключения к существующей бд postgres * Настроить config/credentials.yml diff --git a/README.md b/README.md index ef24426..581ddb1 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Analyzing various blog sites and the platforms adjacent to them (where the repos * Telegram: * Send to platforms: Yes * Editing, deleting: Yes - * Send from platform: No + * Send from platform to site: Yes * Comment support: Yes * Support for attachments: pictures, video, audio, files @@ -91,6 +91,7 @@ gem install wdm -- --with-cflags=-Wno-implicit-function-declaration ``` git clone https://github.com/Whiletruedoend/Twilight cd Twilight/ + yarn ``` * (Optional) Configure .env for existing postgres database * Configure config/credentials.yml diff --git a/babel.config.js b/babel.config.js index 4df1949..8a1a390 100644 --- a/babel.config.js +++ b/babel.config.js @@ -32,6 +32,7 @@ module.exports = function(api) { useBuiltIns: 'entry', corejs: 3, modules: false, + loose: true, exclude: ['transform-typeof-symbol'] } ] @@ -64,7 +65,8 @@ module.exports = function(api) { { async: false } - ] + ], + ["@babel/plugin-transform-private-methods", { "loose": true }] ].filter(Boolean) } } diff --git a/package.json b/package.json index ce33d91..8c31db1 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,11 @@ "name": "Twilight", "private": true, "dependencies": { - "@hotwired/turbo-rails": "^8.0.4", + "@hotwired/turbo-rails": "7.2.4", "@rails/actioncable": "^7.1.3", "@rails/activestorage": "^7.1.3", "@rails/ujs": "^7.1.3", - "@rails/webpacker": "5.4.4", + "@rails/webpacker": "5.4.2", "bootstrap-datepicker": "^1.10.0", "dropzone": "^5.9.3", "elliptic": "6.5.5", @@ -17,11 +17,11 @@ "ssri": "10.0.6", "stimulus": "^3.2.2", "turbolinks": "^5.2.0", - "webpack": "^4.46.0", - "webpack-cli": "^3.3.12" + "webpack": "^5.1.4" }, "version": "0.1.0", "devDependencies": { + "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.11.1" } } From 579e0411d751a05ede010fb47e73c0c9e7014e8b Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Mon, 16 Sep 2024 15:51:43 +0300 Subject: [PATCH 45/47] Docker fix --- .env | 4 +++- config/application.rb | 15 +++++++++++++++ docker-compose.yml | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.env b/.env index 5fa5628..0d5319b 100644 --- a/.env +++ b/.env @@ -5,4 +5,6 @@ POSTGRES_PORT=5432 POSTGRES_DB=twilight_development POSTGRES_USER=postgres POSTGRES_PASSWORD=pass -REDIS_URL="redis://redis:6379/1" \ No newline at end of file +REDIS_URL="redis://redis:6379/1" + +HOST_URL="twilight.example.com" # Enter your URL, without http(s). Required for Docker. \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 39871d1..8f80505 100644 --- a/config/application.rb +++ b/config/application.rb @@ -46,5 +46,20 @@ def credentials CURRENT_TG_BOTS = {} end # config.eager_load_paths << Rails.root.join("extras") + + ## Hosts access ## + config.hosts = [ + IPAddr.new("0.0.0.0/0"), + IPAddr.new("::/0"), + "localhost", + "twilight", + ENV['HOST_URL'] + ] + + config.action_cable.allowed_request_origins = ["http://#{ENV['HOST_URL']}", "https://#{ENV['HOST_URL']}"] + + if ENV['DOCKERIZED'] == 'true' + config.web_console.whitelisted_ips = ENV['DOCKER_HOST_IP'] + end end end diff --git a/docker-compose.yml b/docker-compose.yml index e048ef8..6f5ee70 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,6 +27,8 @@ services: - "${PORT}:${PORT}" env_file: .env environment: + - DOCKERIZED=true + - HOST_URL=${HOST_URL} - RAILS_ENV=${RAILS_ENV} - PORT=${PORT} - POSTGRES_HOST=${POSTGRES_HOST} From 0bb60324000a7e489686fa6e4d59f5e1a1cc67b7 Mon Sep 17 00:00:00 2001 From: Whiletruedoend <whiletruedoend@protonmail.com> Date: Mon, 16 Sep 2024 16:13:08 +0300 Subject: [PATCH 46/47] Fix --- config/application.rb | 4 ---- config/environments/development.rb | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/application.rb b/config/application.rb index 8f80505..bfdf0a6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -57,9 +57,5 @@ def credentials ] config.action_cable.allowed_request_origins = ["http://#{ENV['HOST_URL']}", "https://#{ENV['HOST_URL']}"] - - if ENV['DOCKERIZED'] == 'true' - config.web_console.whitelisted_ips = ENV['DOCKER_HOST_IP'] - end end end diff --git a/config/environments/development.rb b/config/environments/development.rb index 3f1e88a..fba268a 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -76,4 +76,8 @@ config.action_mailer.default_url_options = { host: Rails.configuration.credentials[:host], port: Rails.configuration.credentials[:port] } + + if ENV['DOCKERIZED'] == 'true' + config.web_console.whitelisted_ips = ENV['DOCKER_HOST_IP'] + end end From 87e84be7623923315f834997b9cea5efec0e075a Mon Sep 17 00:00:00 2001 From: Whiletruedoend <Whiletruedoend@protonmail.com> Date: Sat, 21 Sep 2024 13:49:20 +0300 Subject: [PATCH 47/47] Update update_log.md --- update_log.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update_log.md b/update_log.md index 12c13e4..37c1f10 100644 --- a/update_log.md +++ b/update_log.md @@ -1,6 +1,6 @@ ### Format: Y-M-D -### 2024-0x-xx => 1.1 +### 2024-09-22 => 1.1 **Крупное обновление! Из-за изменения логики работы нет обратной совместимости, необходимо сбросить старые данные!**