Author SHA1 Message Date
raucao 8cf138b035 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2025-05-28 14:51:31 +04:00
raucao d48f1caa28 Merge branch 'master' into live
continuous-integration/drone/push Build is failing
2025-05-28 14:29:09 +04:00
raucao 6a2ad475be Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2025-05-28 13:26:51 +04:00
raucao 942ae25f09 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2025-05-23 14:10:16 +04:00
raucao b6968f1742 Merge branch 'feature/mastodon_api' into live
continuous-integration/drone/push Build is passing
2025-05-18 14:56:52 +04:00
raucao 4f4e086518 Merge branch 'feature/mastodon_api' into live
continuous-integration/drone/push Build is passing
2025-05-18 14:46:33 +04:00
raucao a04a1479f8 Merge branch 'feature/mastodon_api' into live
continuous-integration/drone/push Build is passing
2025-05-18 14:38:00 +04:00
raucao 613090d38a Merge branch 'feature/mastodon_api' into live
continuous-integration/drone/push Build is passing
2025-05-17 18:57:10 +04:00
raucao f1c540537a Merge branch 'feature/mastodon_api' into live
continuous-integration/drone/push Build is passing
2025-05-17 18:22:38 +04:00
raucao 1dcdc2b032 Allow syncing a single Mastodon profile
continuous-integration/drone/push Build is passing
2025-05-17 18:22:11 +04:00
raucao 4545c3ba19 Merge branch 'feature/mastodon_api' into live
continuous-integration/drone/push Build is passing
2025-05-17 18:12:57 +04:00
raucao bfa1514181 Update doc
continuous-integration/drone/push Build is passing
2025-05-17 18:11:17 +04:00
raucao f0846308da Only update other avatars in one place
continuous-integration/drone/push Build is passing
Prevent future mistakes
2025-05-17 18:02:13 +04:00
raucao b3b7fe6359 Don't queue job when service isn't enabled 2025-05-17 18:02:13 +04:00
raucao f0b541ee50 Add avatar to admin user page 2025-05-17 18:02:13 +04:00
raucao df9077e3c1 Sync Mastodon IDs/profiles to local accounts
Add a new service to import some data from Mastodon accounts:

* Find users by username, store Mastodon account ID in local db when
  found
* Import display name (don't overwrite existing)
* Import avatar (don't overwrite existing)
2025-05-17 18:02:07 +04:00
raucao 74666130c0 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2025-05-16 17:30:30 +04:00
raucao 7c43a4d919 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2025-05-16 16:01:31 +04:00
raucao 520552ec70 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2025-05-16 15:38:20 +04:00
raucao 307095bfd0 WTF
continuous-integration/drone/push Build is passing
2025-05-15 22:21:33 +04:00
raucao ae7291d4f1 Merge branch 'feature/ejabberd_pep' into live
continuous-integration/drone/push Build is failing
2025-05-15 22:07:26 +04:00
raucao 322ca98da9 Merge branch 'feature/ejabberd_pep' into live
continuous-integration/drone/push Build is passing
2025-05-15 20:05:30 +04:00
raucao 66f3950b83 Add job for setting avatar via XMPP 2025-05-15 20:04:55 +04:00
raucao 4e115eb514 Merge branch 'feature/ejabberd_pep' into live
continuous-integration/drone/push Build is failing
2025-05-15 19:50:30 +04:00
raucao fc9b471a10 Add job for setting avatar via XMPP 2025-05-15 19:50:05 +04:00
raucao b33e47e60f Merge branch 'feature/ejabberd_pep' into live
continuous-integration/drone/push Build is passing
2025-05-15 12:54:25 +04:00
raucao 9878e4a3e8 Merge branch 'feature/ejabberd_pep' into live
continuous-integration/drone/push Build is passing
2025-05-15 12:38:58 +04:00
raucao 9c35323bcd Return response for ejabberd API calls 2025-05-15 12:38:40 +04:00
raucao e2716d94c0 Merge branch 'feature/ejabberd_pep' into live
continuous-integration/drone/push Build is passing
2025-05-15 12:22:29 +04:00
raucao 9394f649a6 Merge branch 'feature/ejabberd_pep' into live
continuous-integration/drone/push Build is passing
2025-05-15 12:02:05 +04:00
raucao 41b9cb722b Merge branch 'feature/ejabberd_pep' into live
continuous-integration/drone/push Build is passing
2025-05-15 11:48:10 +04:00
raucao f1c13d7bd9 Add private_get to ejabberd service
continuous-integration/drone/push Build is passing
2025-05-15 11:47:23 +04:00
raucao c6cb9caa6d Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2025-05-14 18:58:38 +04:00
raucao 208977177a Merge branch 'feature/user_avatars' into live
continuous-integration/drone/push Build is passing
2025-05-14 15:40:47 +04:00
raucao 9a406b8381 Merge branch 'feature/user_avatars' into live
continuous-integration/drone/push Build is passing
2025-05-14 14:44:14 +04:00
raucao 4c972bfe7a Merge branch 'feature/user_avatars' into live
continuous-integration/drone/push Build is passing
2025-05-12 16:40:44 +04:00
raucao 0191d248f8 Merge branch 'feature/user_avatars' into live
continuous-integration/drone/push Build is passing
2025-05-12 15:11:39 +04:00
raucao 710afb6c78 Merge branch 'chore/215-configs' into live
continuous-integration/drone/push Build is passing
2025-05-06 20:14:42 +04:00
raucao 69e9662133 Merge branch 'chore/215-configs' into live
continuous-integration/drone/push Build is failing
2025-05-06 20:01:15 +04:00
raucao a73d0f29bd Merge branch 'chore/215-configs' into live
continuous-integration/drone/push Build is passing
2025-05-06 19:53:52 +04:00
raucao 7836805570 Merge branch 'chore/215-configs' into live
continuous-integration/drone/push Build is passing
2025-05-06 19:04:03 +04:00
raucao 1fc434cb3a Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2025-05-05 17:39:06 +04:00
raucao dad7d5195e Merge branch 'chore/db_configs' into live
continuous-integration/drone/push Build is passing
2025-05-05 15:28:16 +04:00
raucao 596cad34da Merge branch 'chore/upgrade_rails' into live 2025-04-29 17:25:25 +04:00
raucao aaee5cb4ed Merge branch 'chore/upgrade_rails' into live
continuous-integration/drone/push Build is passing
2025-04-28 17:56:57 +04:00
raucao ebaca5ba65 Merge branch 'chore/upgrade_rails' into live 2025-04-28 15:58:17 +04:00
raucao b6bcfa2ee3 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2025-04-18 14:54:01 +04:00
raucao 9082ee45d8 Merge branch 'master' into live 2025-01-02 08:32:15 -05:00
raucao 29264aad98 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-10-16 13:33:35 +02:00
raucao 259b51a95e Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-09-24 21:40:25 +02:00
raucao fb369530e3 Merge branch 'master' into live
continuous-integration/drone/push Build is failing
2024-09-14 17:18:09 +02:00
raucao 5dc10a4d33 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-09-14 16:46:27 +02:00
raucao 2297c68046 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-09-14 16:41:01 +02:00
raucao b82ab45c99 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-09-14 14:57:35 +02:00
raucao d12c63db26 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-09-10 16:07:24 +02:00
raucao e6a9ef84ce Merge branch 'master' into live
continuous-integration/drone/push Build is failing
2024-08-19 15:13:46 +02:00
raucao b7e91344a0 Merge branch 'master' into live
continuous-integration/drone/push Build is failing
2024-08-19 14:48:35 +02:00
raucao 0f07e32781 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-08-17 14:49:29 +02:00
raucao 1311b5ed6a Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-08-17 14:46:00 +02:00
raucao 12f82061e8 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-08-17 14:42:32 +02:00
raucao a07b4369ab Hide njump link for users without pubkey
continuous-integration/drone/push Build is passing
2024-06-23 17:33:34 +02:00
raucao 2605c06807 Merge branch 'feature/admin_pages' into live
continuous-integration/drone/push Build is passing
2024-06-23 17:30:22 +02:00
raucao 1db768fb15 Merge branch 'feature/admin_pages' into live
continuous-integration/drone/push Build is passing
2024-06-23 17:27:03 +02:00
raucao 8a7403df32 Merge branch 'feature/own_relay' into live
continuous-integration/drone/push Build is passing
2024-06-20 15:52:03 +02:00
raucao f0295fef7a Merge branch 'feature/own_relay' into live
continuous-integration/drone/push Build is passing
2024-06-20 15:28:55 +02:00
raucao 090affd304 Merge branch 'feature/own_relay' into live
continuous-integration/drone/push Build is passing
2024-06-20 14:51:20 +02:00
raucao bafddd436b Merge branch 'feature/own_relay' into live
continuous-integration/drone/push Build is passing
2024-06-20 13:59:59 +02:00
raucao 560f193c4b Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-06-20 13:56:11 +02:00
raucao 8aabbad5bb Merge branch 'feature/strfry_zap_receipts' into live 2024-06-20 13:56:00 +02:00
raucao ba8d21eb7a Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-06-09 13:29:57 +02:00
raucao 53df455d53 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-06-09 13:17:51 +02:00
raucao 9f1af3a9aa Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-06-07 14:13:53 +02:00
raucao 1d09008ce2 Merge branch 'master' into live
continuous-integration/drone/push Build is passing
2024-06-04 17:45:13 +02:00
raucao 57c5317c38 Merge branch 'feature/170-nostr_zaps' into live
continuous-integration/drone/push Build is passing
2024-05-21 18:29:13 +02:00
raucao 41bd920060 Merge branch 'feature/170-nostr_zaps' into live
continuous-integration/drone/push Build is passing
2024-05-21 18:09:09 +02:00
raucao 0815fa6040 Merge branch 'feature/170-nostr_zaps' into live
continuous-integration/drone/push Build is passing
2024-05-19 17:07:58 +02:00
raucao af0e99aa50 Merge branch 'feature/170-nostr_zaps' into live
continuous-integration/drone/push Build is passing
2024-05-19 16:55:10 +02:00
raucao f05eec5255 Merge branch 'feature/170-nostr_zaps' into live
continuous-integration/drone/push Build is passing
2024-05-19 16:48:28 +02:00
raucao 66ca2dc6b0 Merge branch 'feature/173-nostr_ldap' into live
continuous-integration/drone/push Build is passing
2024-05-19 13:44:24 +02:00
raucao 800183e9da Increase sidekiq concurrency in prod
continuous-integration/drone/push Build is passing
2024-05-19 13:44:01 +02:00
121 changed files with 1528 additions and 2112 deletions
+61
View File
@@ -0,0 +1,61 @@
---
kind: pipeline
type: docker
name: CI build
steps:
- name: restore-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
restore: true
mount:
- ./vendor/cache
when:
branch:
- master
- name: rspec
image: gitea.kosmos.org/kosmos/akkounts-ci:0.9.1
environment:
RAILS_ENV: test
REDIS_URL: redis://redis:6379/0
RS_REDIS_URL: redis://redis:6379/1
commands:
- bundle config unset deployment
- bundle config set cache_all 'true'
- bundle config set cache_path 'vendor/cache'
- bundle config set with 'development test'
- bundle install --jobs=3 --retry=3
- bundle exec rails db:create
- bundle exec rails db:migrate
- yarn install
- rake css:build
- bundle exec rspec
- name: rebuild-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
rebuild: true
mount:
- ./vendor/cache
when:
branch:
- master
services:
- name: redis
image: redis
volumes:
- name: cache
host:
path: /var/lib/drone/tmp
---
kind: signature
hmac: f9a8cf97f6596625721365f6238f6f298aa5a7a4de10c3fb61c57202ae9d1ee1
...
-1
View File
@@ -87,7 +87,6 @@
# NOSTR_PRIVATE_KEY='123456abcdef...' # NOSTR_PRIVATE_KEY='123456abcdef...'
# NOSTR_PUBLIC_KEY='123456abcdef...' # NOSTR_PUBLIC_KEY='123456abcdef...'
# NOSTR_RELAY_URL='wss://nostr.kosmos.org' # NOSTR_RELAY_URL='wss://nostr.kosmos.org'
# NOSTR_BLOSSOM_SERVER_URL='https://blossom.kosmos.org'
# RS_STORAGE_URL='https://storage.kosmos.org' # RS_STORAGE_URL='https://storage.kosmos.org'
# RS_REDIS_URL='redis://localhost:6379/2' # RS_REDIS_URL='redis://localhost:6379/2'
-59
View File
@@ -1,59 +0,0 @@
name: CI
on:
push:
branches: [master]
pull_request:
jobs:
test:
name: Test
runs-on: ubuntu-latest
container: gitea.kosmos.org/kosmos/akkounts-ci:0.11.1
services:
redis:
image: redis
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
RAILS_ENV: test
REDIS_URL: redis://redis:6379/0
RS_REDIS_URL: redis://redis:6379/1
steps:
- uses: actions/checkout@v4
- name: Restore bundle cache
uses: actions/cache@v4
with:
path: vendor/bundle
key: ${{ runner.os }}-ruby-3.3.12-${{ hashFiles('Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-ruby-3.3.12-
- name: Install Ruby dependencies
run: |
bundle config unset deployment
bundle config set cache_all 'true'
bundle config set cache_path 'vendor/cache'
bundle config set path 'vendor/bundle'
bundle config set with 'development test'
bundle install --jobs=3 --retry=3
- name: Prepare database
run: |
bundle exec rails db:create
bundle exec rails db:migrate
- name: Install JS dependencies
run: bun install
- name: Build CSS
run: |
bundle exec rake css:build
test -s app/assets/builds/application.css
- name: Run tests
run: bundle exec rspec
-2
View File
@@ -9,5 +9,3 @@ jobs:
steps: steps:
- name: Release Drafter - name: Release Drafter
uses: https://github.com/raucao/gitea-release-drafter@dev uses: https://github.com/raucao/gitea-release-drafter@dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+4
View File
@@ -34,6 +34,10 @@
/public/packs /public/packs
/public/packs-test /public/packs-test
/node_modules /node_modules
/yarn-error.log
yarn-debug.log*
.yarn-integrity
bun.lock
# Ignore local dotenv config file # Ignore local dotenv config file
.env .env
+1 -1
View File
@@ -1 +1 @@
3.3.12 3.3.0
-73
View File
@@ -1,73 +0,0 @@
# AGENTS.md
Akkounts is a Rails 8 monolith for managing Kosmos/LDAP user accounts.
It authenticates against an LDAP directory via Devise and integrates with
ejabberd, Discourse, Mastodon, remoteStorage, Nostr, LNDHub, and BTCPay.
## Development environment
Development runs in Docker Compose — run all commands against the `web` container.
Start services: `docker compose up` (web, ldap, redis, minio, liquor-cabinet, strfry).
The `web` service runs `bin/dev` (foreman: Puma + Tailwind CSS watcher) and embeds
Solid Queue workers (`SOLID_QUEUE_IN_PUMA=true`).
First-time LDAP setup (after creating the 389ds backend once):
`docker compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be_name="dev"`
then `docker compose run web bin/rails ldap:setup`.
## Common commands (prefix with `docker compose exec web`)
- `bin/rspec` — run the test suite (CI runs `bundle exec rspec`)
- `bin/rails db:prepare` — create/migrate DB (also `db:setup`, `db:migrate`)
- `bin/rails ldap:setup` — reset LDAP dir and seed dev entries
- `bin/rails css:build` — build Tailwind CSS (auto-watched in dev)
- `bin/rails lndhub:generate_wallets`, `invitations:generate_for_all_users[N]`
- `bin/jobs` — Solid Queue CLI
Tests use the test env explicitly: `docker compose exec -e RAILS_ENV=test web bin/rspec`.
There is no enforced linter — a `bin/rubocop` binstub exists but no `.rubocop.yml`
(rubocop is a transitive gem, not in the Gemfile); CI does not lint.
## Stack & conventions
- **Assets/views:** ERB + ViewComponent (`app/components/`), Tailwind built via Bun +
importmap (`config/importmap.rb`); pin JS deps with `bin/importmap pin <pkg> --download`.
- **Auth:** Devise + `devise_ldap_authenticatable`; `authentication_keys = [:cn]`,
custom SSHA512 password builder, admin bind. Admin-only mounts (`/jobs` MissionControl,
`/flipper` Flipper UI) are gated by `authenticate :user, ->(u) { u.is_admin? }`.
- **Services:** `app/services/` uses `ApplicationService.call(**args)` with namespaced
`*ManagerService` bases (e.g. `UserManagerService`) and verb-named operations
(`UserManager::CreateAccount`, `NostrManager::PublishEvent`, `BtcpayManager::*`).
- **Models:** `User` is central; `Setting` uses rails-settings-cached with one concern per
service in `app/models/concerns/settings/`. LNDHub models connect to a separate DB
(`LndhubBase` `establish_connection :lndhub`). LDAP is accessed via `LdapService`/
`LdapManagerService`, not ActiveRecord.
- **Jobs:** `app/jobs/` inherit `ApplicationJob < ActiveJob::Base`; backend is **Solid Queue**
(`config.active_job.queue_adapter = :solid_queue`, separate `queue` DB in prod).
`config/recurring.yml` has no active scheduled jobs. Ignore `config/sidekiq.yml` — it's
stale (sidekiq is not in the Gemfile; the README mention is outdated).
- **Config:** env-var driven (`.env.example`/`.env.development`/`.env.test`); `SERVICES`
constant is loaded from `config/services.yml` in `config/initializers/service_details.rb`.
Custom LDAP schema attributes are applied from `schemas/ldap/*.ldif` by `lib/tasks/ldap.rake`.
## Testing conventions (`spec/`)
`spec/rails_helper.rb` includes FactoryBot, Devise `ControllerHelpers` (controller specs),
Warden `Test::Helpers`, DatabaseCleaner (transactional fixtures **off**), ViewComponent
`TestHelpers` + Capybara matchers, and `ActiveJob::TestHelper` for `type: :job`.
- **Feature specs** (`spec/features/`, `type: :feature`): Capybara **rack-test** driver (no JS).
Log in with Warden: `login_as user, scope: :user`; stub admin with
`allow(Devise::LDAP::Adapter).to receive(:get_ldap_param).with(user.cn, :admin).and_return(["true"])`.
- **Services** (`spec/services/`) are tagged `type: :model`; exercise `#call`/`service.send(:private)`
and assert on DB rows / `enqueued_jobs`.
- **Request/component/mailer/job/model/helper** specs in matching `spec/` subdirs.
Fixtures live in `spec/fixtures/`; factories in `spec/factories/`.
## Notes & gotchas
- Generators (`config/application.rb`): ERB templates, RSpec, no stylesheets, factory_bot
with `_factory` suffix dir `spec/factories`.
- `gitno/` and `extras/strfry/` are scratch/infra (Deno Nostr relay policies) — not app code; ignore.
- Don't add code comments unless asked (matches repo style); `frozen_string_literal` is inconsistent.
- Default dev login: username `admin` / password `admin is admin`.
+7 -6
View File
@@ -1,21 +1,22 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM ruby:3.3.12 FROM ruby:3.3.4
SHELL ["/bin/bash", "-o", "pipefail", "-c"] SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl unzip \ RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
ldap-utils tini libvips ldap-utils tini libvips
RUN curl -fsSL https://bun.sh/install | bash -s -- bun-v1.3.14 RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
ENV PATH="/root/.bun/bin:$PATH" RUN apt-get update && apt-get install -y nodejs
WORKDIR /akkounts WORKDIR /akkounts
COPY ["Gemfile", "Gemfile.lock", "package.json", "bun.lock", "./"] COPY ["Gemfile", "Gemfile.lock", "package.json", "./"]
RUN bundle install RUN bundle install
RUN gem install foreman RUN gem install foreman
RUN bun install RUN npm install -g yarn
RUN yarn install
ENTRYPOINT ["/usr/bin/tini", "--"] ENTRYPOINT ["/usr/bin/tini", "--"]
EXPOSE 3000 EXPOSE 3000
+5 -6
View File
@@ -2,9 +2,9 @@ source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" } git_source(:github) { |repo| "https://github.com/#{repo}.git" }
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 8.1' gem 'rails', '~> 8.0'
# Use Puma as the app server # Use Puma as the app server
gem 'puma', '~> 8.0' gem 'puma', '~> 6.6'
# View components # View components
gem "view_component" gem "view_component"
# Asset bundler # Asset bundler
@@ -18,7 +18,7 @@ gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev] # Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails" gem "stimulus-rails"
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.15' gem 'jbuilder', '~> 2.7'
# Use Active Model has_secure_password # Use Active Model has_secure_password
gem 'bcrypt', '~> 3.1' gem 'bcrypt', '~> 3.1'
@@ -27,7 +27,7 @@ gem 'dotenv-rails'
# Authentication # Authentication
gem 'warden' gem 'warden'
gem 'devise', '~> 5.0' gem 'devise', '~> 4.9.0'
gem 'devise_ldap_authenticatable' gem 'devise_ldap_authenticatable'
gem 'net-ldap' gem 'net-ldap'
@@ -42,7 +42,6 @@ gem 'flipper-active_record'
gem 'flipper-ui' gem 'flipper-ui'
gem 'gpgme', '~> 2.0.24' gem 'gpgme', '~> 2.0.24'
gem 'zbase32', '~> 0.1.1' gem 'zbase32', '~> 0.1.1'
gem 'kramdown'
# HTTP requests # HTTP requests
gem 'faraday' gem 'faraday'
@@ -73,7 +72,7 @@ end
group :development do group :development do
# Access an interactive console on exception pages or by calling 'console' anywhere in the code. # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
gem 'web-console', '~> 4.3' gem 'web-console', '~> 4.2'
gem 'listen', '~> 3.2' gem 'listen', '~> 3.2'
gem 'letter_opener' gem 'letter_opener'
gem 'letter_opener_web' gem 'letter_opener_web'
+140 -150
View File
@@ -3,31 +3,29 @@ GEM
specs: specs:
aasm (5.5.0) aasm (5.5.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
action_text-trix (2.1.19) actioncable (8.0.2)
railties actionpack (= 8.0.2)
actioncable (8.1.3.1) activesupport (= 8.0.2)
actionpack (= 8.1.3.1)
activesupport (= 8.1.3.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (8.1.3.1) actionmailbox (8.0.2)
actionpack (= 8.1.3.1) actionpack (= 8.0.2)
activejob (= 8.1.3.1) activejob (= 8.0.2)
activerecord (= 8.1.3.1) activerecord (= 8.0.2)
activestorage (= 8.1.3.1) activestorage (= 8.0.2)
activesupport (= 8.1.3.1) activesupport (= 8.0.2)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.1.3.1) actionmailer (8.0.2)
actionpack (= 8.1.3.1) actionpack (= 8.0.2)
actionview (= 8.1.3.1) actionview (= 8.0.2)
activejob (= 8.1.3.1) activejob (= 8.0.2)
activesupport (= 8.1.3.1) activesupport (= 8.0.2)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (8.1.3.1) actionpack (8.0.2)
actionview (= 8.1.3.1) actionview (= 8.0.2)
activesupport (= 8.1.3.1) activesupport (= 8.0.2)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
@@ -35,43 +33,42 @@ GEM
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) useragent (~> 0.16)
actiontext (8.1.3.1) actiontext (8.0.2)
action_text-trix (~> 2.1.15) actionpack (= 8.0.2)
actionpack (= 8.1.3.1) activerecord (= 8.0.2)
activerecord (= 8.1.3.1) activestorage (= 8.0.2)
activestorage (= 8.1.3.1) activesupport (= 8.0.2)
activesupport (= 8.1.3.1)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.1.3.1) actionview (8.0.2)
activesupport (= 8.1.3.1) activesupport (= 8.0.2)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
activejob (8.1.3.1) activejob (8.0.2)
activesupport (= 8.1.3.1) activesupport (= 8.0.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.1.3.1) activemodel (8.0.2)
activesupport (= 8.1.3.1) activesupport (= 8.0.2)
activerecord (8.1.3.1) activerecord (8.0.2)
activemodel (= 8.1.3.1) activemodel (= 8.0.2)
activesupport (= 8.1.3.1) activesupport (= 8.0.2)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.1.3.1) activestorage (8.0.2)
actionpack (= 8.1.3.1) actionpack (= 8.0.2)
activejob (= 8.1.3.1) activejob (= 8.0.2)
activerecord (= 8.1.3.1) activerecord (= 8.0.2)
activesupport (= 8.1.3.1) activesupport (= 8.0.2)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.1.3.1) activesupport (8.0.2)
base64 base64
benchmark (>= 0.3)
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1) concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5) connection_pool (>= 2.2.5)
drb drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
json
logger (>= 1.4.2) logger (>= 1.4.2)
minitest (>= 5.1) minitest (>= 5.1)
securerandom (>= 0.3) securerandom (>= 0.3)
@@ -99,12 +96,12 @@ GEM
aws-sigv4 (1.11.0) aws-sigv4 (1.11.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
backport (1.2.0) backport (1.2.0)
base64 (0.3.0) base64 (0.2.0)
bcrypt (3.1.22) bcrypt (3.1.20)
bech32 (1.5.0) bech32 (1.5.0)
thor (>= 1.1.0) thor (>= 1.1.0)
benchmark (0.5.0) benchmark (0.4.0)
bigdecimal (4.1.2) bigdecimal (3.1.9)
bindex (0.8.1) bindex (0.8.1)
bip-schnorr (0.7.0) bip-schnorr (0.7.0)
ecdsa_ext (~> 0.5.0) ecdsa_ext (~> 0.5.0)
@@ -121,12 +118,12 @@ GEM
childprocess (5.1.0) childprocess (5.1.0)
logger (~> 1.5) logger (~> 1.5)
chunky_png (1.4.0) chunky_png (1.4.0)
concurrent-ruby (1.3.8) concurrent-ruby (1.3.4)
connection_pool (3.0.2) connection_pool (2.5.2)
crack (1.0.0) crack (1.0.0)
bigdecimal bigdecimal
rexml rexml
crass (1.0.7) crass (1.0.6)
cssbundling-rails (1.4.3) cssbundling-rails (1.4.3)
railties (>= 6.0.0) railties (>= 6.0.0)
database_cleaner (2.1.0) database_cleaner (2.1.0)
@@ -135,17 +132,17 @@ GEM
activerecord (>= 5.a) activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0) database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1) database_cleaner-core (2.0.1)
date (3.5.1) date (3.4.1)
devise (5.0.4) devise (4.9.4)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 7.0) railties (>= 4.1.0)
responders responders
warden (~> 1.2.3) warden (~> 1.2.3)
devise_ldap_authenticatable (0.8.7) devise_ldap_authenticatable (0.8.7)
devise (>= 3.4.1) devise (>= 3.4.1)
net-ldap (>= 0.16.0) net-ldap (>= 0.16.0)
diff-lcs (1.6.2) diff-lcs (1.6.1)
discourse_api (2.0.1) discourse_api (2.0.1)
faraday (~> 2.7) faraday (~> 2.7)
faraday-follow_redirects faraday-follow_redirects
@@ -157,13 +154,12 @@ GEM
railties (>= 6.1) railties (>= 6.1)
down (5.4.2) down (5.4.2)
addressable (~> 2.8) addressable (~> 2.8)
drb (2.2.3) drb (2.2.1)
ecdsa (1.2.0) ecdsa (1.2.0)
ecdsa_ext (0.5.1) ecdsa_ext (0.5.1)
ecdsa (~> 1.2.0) ecdsa (~> 1.2.0)
erb (6.0.7)
erubi (1.13.1) erubi (1.13.1)
et-orbi (1.4.1) et-orbi (1.2.11)
tzinfo tzinfo
event_emitter (0.2.6) event_emitter (0.2.6)
eventmachine (1.2.7) eventmachine (1.2.7)
@@ -200,35 +196,34 @@ GEM
rack-protection (>= 1.5.3, < 5.0.0) rack-protection (>= 1.5.3, < 5.0.0)
rack-session (>= 1.0.2, < 3.0.0) rack-session (>= 1.0.2, < 3.0.0)
sanitize (< 8) sanitize (< 8)
fugit (1.13.0) fugit (1.11.1)
et-orbi (~> 1.4) et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.4.0) globalid (1.2.1)
activesupport (>= 6.1) activesupport (>= 6.1)
gpgme (2.0.24) gpgme (2.0.24)
mini_portile2 (~> 2.7) mini_portile2 (~> 2.7)
hashdiff (1.1.2) hashdiff (1.1.2)
i18n (1.15.2) i18n (1.14.7)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
image_processing (1.12.2) image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5) mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3) ruby-vips (>= 2.0.17, < 3)
importmap-rails (2.2.3) importmap-rails (2.1.0)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activesupport (>= 6.0.0) activesupport (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
io-console (0.9.2) io-console (0.8.0)
irb (1.18.0) irb (1.15.2)
pp (>= 0.6.0) pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0) rdoc (>= 4.0.0)
reline (>= 0.4.2) reline (>= 0.4.2)
jaro_winkler (1.6.0) jaro_winkler (1.6.0)
jbuilder (2.15.1) jbuilder (2.13.0)
actionview (>= 7.0.0) actionview (>= 5.0.0)
activesupport (>= 7.0.0) activesupport (>= 5.0.0)
jmespath (1.6.2) jmespath (1.6.2)
json (2.21.2) json (2.11.3)
kramdown (2.5.1) kramdown (2.5.1)
rexml (>= 3.3.9) rexml (>= 3.3.9)
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
@@ -252,11 +247,10 @@ GEM
lnurl (1.1.1) lnurl (1.1.1)
bech32 (~> 1.1) bech32 (~> 1.1)
logger (1.7.0) logger (1.7.0)
loofah (2.25.2) loofah (2.24.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
mail (2.9.1) mail (2.8.1)
logger
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
net-imap net-imap
net-pop net-pop
@@ -265,15 +259,14 @@ GEM
faraday (~> 2.9.0) faraday (~> 2.9.0)
faraday-follow_redirects (= 0.3.0) faraday-follow_redirects (= 0.3.0)
nokogiri (~> 1.16.0) nokogiri (~> 1.16.0)
marcel (1.2.1) marcel (1.0.4)
matrix (0.4.2) matrix (0.4.2)
method_source (1.1.0)
mini_magick (4.13.2) mini_magick (4.13.2)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.9) mini_portile2 (2.8.8)
minitest (6.0.6) minitest (5.25.5)
drb (~> 2.0) mission_control-jobs (1.0.2)
prism (~> 1.5)
mission_control-jobs (1.1.0)
actioncable (>= 7.1) actioncable (>= 7.1)
actionpack (>= 7.1) actionpack (>= 7.1)
activejob (>= 7.1) activejob (>= 7.1)
@@ -286,7 +279,7 @@ GEM
multipart-post (2.4.1) multipart-post (2.4.1)
net-http (0.6.0) net-http (0.6.0)
uri uri
net-imap (0.6.6) net-imap (0.5.7)
date date
net-protocol net-protocol
net-ldap (0.19.0) net-ldap (0.19.0)
@@ -296,7 +289,7 @@ GEM
timeout timeout
net-smtp (0.5.1) net-smtp (0.5.1)
net-protocol net-protocol
nio4r (2.7.5) nio4r (2.7.4)
nokogiri (1.16.8) nokogiri (1.16.8)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
@@ -320,23 +313,24 @@ GEM
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.5.9) pg (1.5.9)
pp (0.6.4) pp (0.6.2)
prettyprint prettyprint
prettyprint (0.2.0) prettyprint (0.2.0)
prism (1.9.0) prism (1.4.0)
propshaft (1.3.2) propshaft (1.1.0)
actionpack (>= 7.0.0) actionpack (>= 7.0.0)
activesupport (>= 7.0.0) activesupport (>= 7.0.0)
rack rack
psych (5.4.0) railties (>= 7.0.0)
psych (5.2.3)
date date
stringio stringio
public_suffix (6.0.1) public_suffix (6.0.1)
puma (8.0.2) puma (6.6.0)
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.5.0) raabro (1.4.0)
racc (1.8.1) racc (1.8.1)
rack (2.2.23) rack (2.2.13)
rack-protection (3.2.0) rack-protection (3.2.0)
base64 (>= 0.1.0) base64 (>= 0.1.0)
rack (~> 2.2, >= 2.2.4) rack (~> 2.2, >= 2.2.4)
@@ -347,64 +341,61 @@ GEM
rackup (1.0.1) rackup (1.0.1)
rack (< 3) rack (< 3)
webrick webrick
rails (8.1.3.1) rails (8.0.2)
actioncable (= 8.1.3.1) actioncable (= 8.0.2)
actionmailbox (= 8.1.3.1) actionmailbox (= 8.0.2)
actionmailer (= 8.1.3.1) actionmailer (= 8.0.2)
actionpack (= 8.1.3.1) actionpack (= 8.0.2)
actiontext (= 8.1.3.1) actiontext (= 8.0.2)
actionview (= 8.1.3.1) actionview (= 8.0.2)
activejob (= 8.1.3.1) activejob (= 8.0.2)
activemodel (= 8.1.3.1) activemodel (= 8.0.2)
activerecord (= 8.1.3.1) activerecord (= 8.0.2)
activestorage (= 8.1.3.1) activestorage (= 8.0.2)
activesupport (= 8.1.3.1) activesupport (= 8.0.2)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.1.3.1) railties (= 8.0.2)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1) activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.3.0) rails-dom-testing (2.2.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.7.1) rails-html-sanitizer (1.6.2)
loofah (~> 2.25, >= 2.25.2) loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails-settings-cached (2.8.3) rails-settings-cached (2.8.3)
activerecord (>= 5.0.0) activerecord (>= 5.0.0)
railties (>= 5.0.0) railties (>= 5.0.0)
railties (8.1.3.1) railties (8.0.2)
actionpack (= 8.1.3.1) actionpack (= 8.0.2)
activesupport (= 8.1.3.1) activesupport (= 8.0.2)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0, >= 1.2.2) thor (~> 1.0, >= 1.2.2)
tsort (>= 0.2)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.4.2) rake (13.2.1)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.11.1) rb-inotify (0.11.1)
ffi (~> 1.0) ffi (~> 1.0)
rbs (3.9.2) rbs (3.9.2)
logger logger
rdoc (7.2.0) rdoc (6.13.1)
erb
psych (>= 4.0.0) psych (>= 4.0.0)
tsort
redis (5.4.0) redis (5.4.0)
redis-client (>= 0.22.0) redis-client (>= 0.22.0)
redis-client (0.24.0) redis-client (0.24.0)
connection_pool connection_pool
regexp_parser (2.10.0) regexp_parser (2.10.0)
reline (0.7.0) reline (0.6.1)
io-console (~> 0.5) io-console (~> 0.5)
responders (3.2.0) responders (3.1.1)
actionpack (>= 7.0) actionpack (>= 5.2)
railties (>= 7.0) railties (>= 5.2)
reverse_markdown (3.0.0) reverse_markdown (3.0.0)
nokogiri nokogiri
rexml (3.4.1) rexml (3.4.1)
@@ -412,23 +403,23 @@ GEM
chunky_png (~> 1.0) chunky_png (~> 1.0)
rqrcode_core (~> 1.0) rqrcode_core (~> 1.0)
rqrcode_core (1.2.0) rqrcode_core (1.2.0)
rspec-core (3.13.6) rspec-core (3.13.3)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-expectations (3.13.5) rspec-expectations (3.13.3)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-mocks (3.13.8) rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-rails (8.0.4) rspec-rails (7.1.1)
actionpack (>= 7.2) actionpack (>= 7.0)
activesupport (>= 7.2) activesupport (>= 7.0)
railties (>= 7.2) railties (>= 7.0)
rspec-core (>= 3.13.0, < 5.0.0) rspec-core (~> 3.13)
rspec-expectations (>= 3.13.0, < 5.0.0) rspec-expectations (~> 3.13)
rspec-mocks (>= 3.13.0, < 5.0.0) rspec-mocks (~> 3.13)
rspec-support (>= 3.13.0, < 5.0.0) rspec-support (~> 3.13)
rspec-support (3.13.7) rspec-support (3.13.2)
rubocop (1.75.3) rubocop (1.75.3)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
@@ -476,25 +467,24 @@ GEM
tilt (~> 2.0) tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24) yard (~> 0.9, >= 0.9.24)
yard-solargraph (~> 0.1) yard-solargraph (~> 0.1)
solid_queue (1.6.0) solid_queue (1.1.5)
activejob (>= 7.1) activejob (>= 7.1)
activerecord (>= 7.1) activerecord (>= 7.1)
concurrent-ruby (>= 1.3.1) concurrent-ruby (>= 1.3.1)
fugit (~> 1.11) fugit (~> 1.11.0)
railties (>= 7.1) railties (>= 7.1)
thor (>= 1.3.1) thor (~> 1.3.1)
sqlite3 (2.6.0) sqlite3 (2.6.0)
mini_portile2 (~> 2.8.0) mini_portile2 (~> 2.8.0)
sqlite3 (2.6.0-arm64-darwin) sqlite3 (2.6.0-arm64-darwin)
sqlite3 (2.6.0-x86_64-linux-gnu) sqlite3 (2.6.0-x86_64-linux-gnu)
stimulus-rails (1.3.4) stimulus-rails (1.3.4)
railties (>= 6.0.0) railties (>= 6.0.0)
stringio (3.2.0) stringio (3.1.7)
thor (1.5.0) thor (1.3.2)
tilt (2.6.0) tilt (2.6.0)
timeout (0.6.1) timeout (0.4.3)
tsort (0.2.0) turbo-rails (2.0.13)
turbo-rails (2.0.23)
actionpack (>= 7.1.0) actionpack (>= 7.1.0)
railties (>= 7.1.0) railties (>= 7.1.0)
tzinfo (2.0.6) tzinfo (2.0.6)
@@ -502,24 +492,25 @@ GEM
unicode-display_width (3.1.4) unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4) unicode-emoji (4.0.4)
uri (1.1.1) uri (1.0.3)
useragent (0.16.11) useragent (0.16.11)
view_component (4.12.0) view_component (3.22.0)
actionview (>= 7.1.0) activesupport (>= 5.2.0, < 8.1)
activesupport (>= 7.1.0) concurrent-ruby (= 1.3.4)
concurrent-ruby (~> 1) method_source (~> 1.0)
warden (1.2.9) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
web-console (4.3.0) web-console (4.2.1)
actionview (>= 8.0.0) actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 8.0.0) railties (>= 6.0.0)
webmock (3.25.1) webmock (3.25.1)
addressable (>= 2.8.0) addressable (>= 2.8.0)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.9.2) webrick (1.9.1)
websocket-driver (0.8.2) websocket-driver (0.7.7)
base64 base64
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
@@ -529,7 +520,7 @@ GEM
yard-solargraph (0.1.0) yard-solargraph (0.1.0)
yard (~> 0.9) yard (~> 0.9)
zbase32 (0.1.1) zbase32 (0.1.1)
zeitwerk (2.8.3) zeitwerk (2.7.2)
PLATFORMS PLATFORMS
arm64-darwin-22 arm64-darwin-22
@@ -543,7 +534,7 @@ DEPENDENCIES
capybara capybara
cssbundling-rails cssbundling-rails
database_cleaner database_cleaner
devise (~> 5.0) devise (~> 4.9.0)
devise_ldap_authenticatable devise_ldap_authenticatable
discourse_api discourse_api
dotenv-rails dotenv-rails
@@ -557,8 +548,7 @@ DEPENDENCIES
gpgme (~> 2.0.24) gpgme (~> 2.0.24)
image_processing (~> 1.12.2) image_processing (~> 1.12.2)
importmap-rails importmap-rails
jbuilder (~> 2.15) jbuilder (~> 2.7)
kramdown
letter_opener letter_opener
letter_opener_web letter_opener_web
listen (~> 3.2) listen (~> 3.2)
@@ -570,8 +560,8 @@ DEPENDENCIES
pagy (~> 6.0, >= 6.0.2) pagy (~> 6.0, >= 6.0.2)
pg (~> 1.5) pg (~> 1.5)
propshaft propshaft
puma (~> 8.0) puma (~> 6.6)
rails (~> 8.1) rails (~> 8.0)
rails-controller-testing rails-controller-testing
rails-settings-cached (~> 2.8.3) rails-settings-cached (~> 2.8.3)
redis (~> 5.4) redis (~> 5.4)
@@ -587,7 +577,7 @@ DEPENDENCIES
tzinfo-data tzinfo-data
view_component view_component
warden warden
web-console (~> 4.3) web-console (~> 4.2)
webmock webmock
zbase32 (~> 0.1.1) zbase32 (~> 0.1.1)
+1 -1
View File
@@ -1,2 +1,2 @@
web: bin/rails server -b 0.0.0.0 -p 3000 web: bin/rails server -b 0.0.0.0 -p 3000
css: bun run build:css:tailwind --watch css: yarn build:css --watch
+2 -2
View File
@@ -1,4 +1,4 @@
[![Build Status](https://gitea.kosmos.org/kosmos/akkounts/actions/workflows/ci.yml/badge.svg)](https://gitea.kosmos.org/kosmos/akkounts/actions) [![Build Status](https://drone.kosmos.org/api/badges/kosmos/akkounts/status.svg)](https://drone.kosmos.org/kosmos/akkounts)
# Akkounts # Akkounts
@@ -36,7 +36,7 @@ run web`._
Installing dependencies: Installing dependencies:
bundle install bundle install
bun install yarn install
Migrating the local database (after schema changes): Migrating the local database (after schema changes):
@@ -7,7 +7,7 @@
@import "components/buttons"; @import "components/buttons";
@import "components/dashboard_services"; @import "components/dashboard_services";
@import "components/forms"; @import "components/forms";
@import "components/links";
@import "components/notifications"; @import "components/notifications";
@import "components/pagination"; @import "components/pagination";
@import "components/rich_text";
@import "components/tables"; @import "components/tables";
@@ -6,7 +6,6 @@
body { body {
@apply leading-none bg-cover bg-fixed; @apply leading-none bg-cover bg-fixed;
background-image: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%), url('/img/bg-1.jpg'); background-image: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%), url('/img/bg-1.jpg');
color: black;
} }
body#admin { body#admin {
@@ -33,10 +32,6 @@
@apply pt-8 sm:pt-12; @apply pt-8 sm:pt-12;
} }
main section h3:not(:first-child) {
@apply mt-8;
}
main section:first-of-type { main section:first-of-type {
@apply pt-0; @apply pt-0;
} }
@@ -60,11 +55,4 @@
main ul li { main ul li {
@apply leading-6; @apply leading-6;
} }
main a:not(nav > *) {
@apply text-blue-600;
&:hover { @apply underline; }
&:visited { @apply text-indigo-600; }
&:active { @apply text-red-600; }
}
} }
+3 -16
View File
@@ -1,15 +1,5 @@
@layer components { @layer components {
.btn-text-dark { @apply text-black; }
.btn-text-dark:hover { @apply text-black no-underline; }
.btn-text-dark:visited { @apply text-black; }
.btn-text-dark:active { @apply text-black; }
.btn-text-light { @apply text-white; }
.btn-text-light:hover { @apply text-white no-underline; }
.btn-text-light:visited { @apply text-white; }
.btn-text-light:active { @apply text-white; }
.btn { .btn {
@apply btn-text-dark;
@apply inline-block font-semibold rounded-md leading-none cursor-pointer text-center @apply inline-block font-semibold rounded-md leading-none cursor-pointer text-center
transition-colors duration-75 focus:outline-none focus:ring-4; transition-colors duration-75 focus:outline-none focus:ring-4;
} }
@@ -38,20 +28,17 @@
} }
.btn-blue { .btn-blue {
@apply btn-text-light; @apply bg-blue-500 hover:bg-blue-600 text-white
@apply bg-blue-500 hover:bg-blue-600
focus:ring-blue-400 focus:ring-opacity-75; focus:ring-blue-400 focus:ring-opacity-75;
} }
.btn-emerald { .btn-emerald {
@apply btn-text-light; @apply bg-emerald-500 hover:bg-emerald-600 text-white
@apply bg-emerald-500 hover:bg-emerald-600
focus:ring-emerald-400 focus:ring-opacity-75; focus:ring-emerald-400 focus:ring-opacity-75;
} }
.btn-red { .btn-red {
@apply btn-text-light; @apply bg-red-600 hover:bg-red-700 text-white
@apply bg-red-600 hover:bg-red-700
focus:ring-red-500 focus:ring-opacity-75; focus:ring-red-500 focus:ring-opacity-75;
} }
@@ -0,0 +1,8 @@
@layer components {
.ks-text-link {
@apply text-blue-600;
&:hover { @apply underline; }
&:visited { @apply text-indigo-600; }
&:active { @apply text-red-600; }
}
}
@@ -34,7 +34,7 @@
.pagy-nav .page a, .page.gap { .pagy-nav .page a, .page.gap {
@apply bg-white border-gray-300 text-gray-500 hover:bg-gray-100 relative @apply bg-white border-gray-300 text-gray-500 hover:bg-gray-100 relative
inline-flex items-center border px-4 py-2 text-sm font-medium inline-flex items-center border px-4 py-2 text-sm font-medium
no-underline focus:z-20; focus:z-20;
} }
.pagy-nav .page.active { .pagy-nav .page.active {
@@ -1,30 +0,0 @@
@layer base {
main {
.rich-text {
ul {
@apply list-disc list-inside;
li {
@apply my-2;
}
}
ol {
@apply list-decimal list-inside;
li {
@apply my-2;
}
}
li > ul,
li > ol {
@apply ps-6 my-1;
}
hr {
@apply my-8;
}
}
}
}
@@ -1,30 +0,0 @@
<div class="inline-block text-left" data-controller="modal" data-action="keydown.esc->modal#close">
<button class="btn-md btn-outline text-red-600" data-action="click->modal#open" title="Edit">
<%= content || "Edit" %>
</button>
<%= render ModalComponent.new(show_close_button: false) do %>
<%= form_with model: [:admin, @editable_content],
html: { autocomplete: "off" } do |form| %>
<%= form.hidden_field :redirect_to, value: @redirect_to %>
<p class="mb-2">
<%= form.label :content, @editable_content.key.capitalize, class: 'font-bold' %>
</p>
<% if @editable_content.rich_text %>
<p>
<%= form.textarea :content, class: "md:w-[56rem] md:h-[28rem]" %>
</p>
<p class="text-right">
<%= form.submit "Save", class: "ml-2 btn-md btn-blue" %>
</p>
<% else %>
<p class="">
<%= form.text_field :content, class: "w-80" %>
</p>
<p>
<%= form.submit "Save", class: "btn-md btn-blue w-full" %>
</p>
<% end %>
<% end %>
<% end %>
</div>
@@ -1,6 +0,0 @@
class EditContentButtonComponent < ViewComponent::Base
def initialize(context:, key:, rich_text: false, redirect_to: nil)
@editable_content = EditableContent.find_or_create_by(context:, key:, rich_text:)
@redirect_to = redirect_to
end
end
@@ -1,11 +0,0 @@
<% if @editable_content.has_content? %>
<% if @editable_content.rich_text %>
<div class="rich-text">
<%= helpers.markdown_to_html @editable_content.content %>
</div>
<% else %>
<%= @editable_content.content %>
<% end %>
<% else %>
<%= @default %>
<% end %>
@@ -1,6 +0,0 @@
class EditableContentComponent < ViewComponent::Base
def initialize(context:, key:, rich_text: false, default: nil)
@editable_content = EditableContent.find_or_create_by(context:, key:, rich_text:)
@default = default
end
end
@@ -1,4 +1,4 @@
<main class="w-full max-w-xl mx-auto px-4 sm:px-6 lg:px-8"> <main class="w-full max-w-xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
<div class="bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12"> <div class="bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
<%= content %> <%= content %>
</div> </div>
@@ -1,4 +1,4 @@
<main class="w-full max-w-6xl mx-auto px-4 md:px-6 lg:px-8"> <main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
<div class="md:min-h-[50vh] bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12"> <div class="md:min-h-[50vh] bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
<%= content %> <%= content %>
</div> </div>
@@ -1,4 +1,4 @@
<main class="w-full max-w-6xl mx-auto px-4 md:px-6 lg:px-8"> <main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
<div class="bg-white rounded-lg shadow"> <div class="bg-white rounded-lg shadow">
<div class="md:min-h-[50vh] divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x"> <div class="md:min-h-[50vh] divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
<aside class="py-6 sm:py-8 lg:col-span-3"> <aside class="py-6 sm:py-8 lg:col-span-3">
@@ -1,4 +1,4 @@
<main class="w-full max-w-6xl mx-auto px-4 md:px-6 lg:px-8"> <main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
<div class="md:min-h-[50vh] bg-white rounded-lg shadow"> <div class="md:min-h-[50vh] bg-white rounded-lg shadow">
<div class="px-6 sm:px-12 pt-2 sm:pt-4"> <div class="px-6 sm:px-12 pt-2 sm:pt-4">
<%= render partial: @tabnav_partial %> <%= render partial: @tabnav_partial %>
@@ -1,44 +0,0 @@
<div class="w-[72vw] md:w-[500px]">
<header class="absolute z-10 h-36 sm:h-44 inset-x-1 top-1 rounded-t
bg-cover bg-center bg-gray-50"
style="background-image: url('<%= @profile["banner"]%>');">
<div class="inline-block z-20 size-28 sm:size-32 ml-4 mt-16 sm:mt-20">
<% if @profile["picture"].present? %>
<img src="<%= @profile["picture"] %>"
class="inline-block size:28 sm:size-32 rounded-full border-2 border-white" />
<% else %>
<span class="inline-block size:28 sm:size-32 overflow-hidden rounded-full border-2 border-white bg-gray-100">
<svg class="size-full text-gray-300" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</span>
<% end %>
</div>
</header>
<main class="mt-44 sm:mt-52">
<%= form_for(@user, url: setting_path(:nostr), html: { :method => :put }) do |f| %>
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Display name") do %>
<%= f.text_field :display_name, value: @display_name, class: "w-full sm:w-3/5" %>
<% if @validation_errors.present? && @validation_errors[:display_name].present? %>
<p class="error-msg mt-2"><%= @validation_errors[:display_name].first %></p>
<% end %>
<% end %>
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Nostr address (NIP-05)") do %>
<%= f.text_field :nip05_address, value: @profile["nip05"], class: "w-full sm:w-3/5" %>
<% if @validation_errors.present? && @validation_errors[:nip05_address].present? %>
<p class="error-msg mt-2"><%= @validation_errors[:nip05_address].first %></p>
<% end %>
<% end %>
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Ligtning address for Zaps") do %>
<%= f.text_field :lud16_address, value: @profile["lud16"], class: "w-full sm:w-3/5" %>
<% if @validation_errors.present? && @validation_errors[:lud16_address].present? %>
<p class="error-msg mt-2"><%= @validation_errors[:lud16_address].first %></p>
<% end %>
<% end %>
<% end %>
</main>
<footer>
<%# <%= @profile.inspect %>
<%# <%= @profile_event.inspect %>
</footer>
</div>
@@ -1,17 +0,0 @@
# frozen_string_literal: true
module Settings
class NostrEditProfileComponent < ViewComponent::Base
def initialize(user:, profile_event:)
@user = user
@profile_event = profile_event
@profile = {}
@display_name = nil
if profile_event.present? && profile_event["content"].present?
@profile = JSON.parse(profile_event["content"]) rescue {}
@display_name = @profile["display_name"] || @profile["displayName"]
end
end
end
end
@@ -1,45 +0,0 @@
<div class="w-[72vw] md:w-[500px]">
<h3 class="mb-6">Edit relays</h3>
<%= form_with url: "#", local: true,
html: { data: { controller: "relay-list-editor" } } do |f| %>
<ul data-relay-list-editor-target="list" class="mb-4 space-y-2">
<% @relays.each do |relay| %>
<li class="flex gap-x-2 items-center">
<input type="text" name="relay_urls[]" value="<%= relay[:url] %>" class="grow" />
<select name="relay_markers[]" class="shrink-0 w-32">
<option value=""<%= %( selected="selected") if relay[:marker].nil? %>>Read & write</option>
<option value="read"<%= %( selected="selected") if relay[:marker] == "read" %>>Read only</option>
<option value="write"<%= %( selected="selected") if relay[:marker] == "write" %>>Write only</option>
</select>
<button type="button" class="btn-md btn-icon btn-outline text-red-700 shrink-0"
data-action="click->relay-list-editor#remove" title="Remove relay">
<%= render partial: "icons/trash-2", locals: { custom_class: "h-4 w-4" } %>
</button>
</li>
<% end %>
</ul>
<template data-relay-list-editor-target="template">
<li class="flex gap-x-2 items-center">
<input type="text" name="relay_urls[]" value="" class="grow" />
<select name="relay_markers[]" class="shrink-0 w-32">
<option value="">Read & write</option>
<option value="read">Read only</option>
<option value="write">Write only</option>
</select>
<button type="button" class="btn-md btn-icon btn-outline text-red-700 shrink-0"
data-action="click->relay-list-editor#remove" title="Remove relay">
<%= render partial: "icons/trash-2", locals: { custom_class: "h-4 w-4" } %>
</button>
</li>
</template>
<button type="button" class="btn-md btn-gray w-full mb-6"
data-action="click->relay-list-editor#add">
<%= render partial: "icons/plus", locals: { custom_class: "h-4 w-4 inline mr-1" } %>
Add relay
</button>
<%= f.submit "Save relay list", class: "btn-md btn-blue w-full" %>
<% end %>
</div>
@@ -1,31 +0,0 @@
# frozen_string_literal: true
module Settings
class NostrEditRelaysComponent < ViewComponent::Base
def initialize(nip65_event: nil)
@nip65_event = nip65_event
@relays = build_relays
ensure_site_relay
end
private
def build_relays
if @nip65_event.present?
@nip65_event["tags"]
.select { |t| t[0] == "r" }
.map { |t| { url: t[1], marker: t[2] } }
else
Setting.nostr_discovery_relays.map { |url| { url: url, marker: nil } }
end
end
def ensure_site_relay
site_relay_url = Setting.nostr_relay_url
return if site_relay_url.blank?
@relays.reject! { |r| r[:url] == site_relay_url }
@relays.unshift({ url: site_relay_url, marker: nil })
end
end
end
@@ -1,21 +0,0 @@
<% @statuses.each do |status| %>
<%= render StatusTextComponent.new(
text: status[:text],
icon_name: status[:icon_name],
icon_color: status[:icon_color]
) %>
<% end %>
<% if @status == 1 %>
<p class="mt-8">
<button class="btn-md btn-blue">
Edit my profile
</button>
</p>
<% elsif @status == 2 %>
<p class="mt-8">
<button class="btn-md btn-blue">
Create my profile
</button>
</p>
<% end %>
@@ -1,53 +0,0 @@
# frozen_string_literal: true
module Settings
class NostrProfileStatusComponent < ViewComponent::Base
def initialize(profile_event:, user_address:)
@statuses = []
if profile_event.present?
profile = JSON.parse(profile_event["content"])
@statuses.push({
text: "You have a public Nostr profile",
icon_name: "check-circle",
icon_color: "emerald-500"
})
if profile["nip05"].present? && profile["nip05"] == user_address
@statuses.push({
text: "Your profile's Nostr address is set to <strong>#{ user_address }</strong>",
icon_name: "check-circle",
icon_color: "emerald-500"
})
else
@statuses.push({
text: "Your profile's Nostr address is not set to <strong>#{ user_address }</strong> yet",
icon_name: "alert-octagon",
icon_color: "amber-500"
})
end
if profile["lud16"].present? && profile["lud16"] == user_address
@statuses.push({
text: "Your profile's Lightning address is set to <strong>#{ user_address }</strong>",
icon_name: "check-circle",
icon_color: "emerald-500"
})
else
@statuses.push({
text: "Your profile's Lightning address is not set to <strong>#{ user_address }</strong> yet",
icon_name: "alert-octagon",
icon_color: "amber-500"
})
end
else
@statuses.push({
text: "We could not find a profile for your public key",
icon_name: "alert-octagon",
icon_color: "amber-500"
})
end
end
end
end
@@ -1,18 +0,0 @@
<% @statuses.each do |status| %>
<%= render StatusTextComponent.new(
text: status[:text],
icon_name: status[:icon_name],
icon_color: status[:icon_color]
) %>
<% end %>
<div class="mt-8" data-controller="modal" data-action="keydown.esc->modal#close">
<button data-action="click->modal#open" class="btn-md btn-blue w-full sm:w-auto">
<%= @button_text %>
</button>
<%= render ModalComponent.new(show_close_button: false) do %>
<%= render Settings::NostrEditRelaysComponent.new(
nip65_event: @nip65_event
) %>
<% end %>
</div>
@@ -1,48 +0,0 @@
# frozen_string_literal: true
module Settings
class NostrRelayStatusComponent < ViewComponent::Base
def initialize(nip65_event:, relay_url: Setting.nostr_relay_url)
@nip65_event = nip65_event
@relay_url = relay_url
@relay_host = relay_url.present? ? URI.parse(relay_url).host : nil
@statuses = []
if nip65_event.present?
@statuses.push({
text: "You have a relay list configured",
icon_name: "check-circle",
icon_color: "emerald-500"
})
if relay_urls(nip65_event).include?(@relay_url)
@statuses.push({
text: "The <strong>#{@relay_host}</strong> relay is part of your relay list",
icon_name: "check-circle",
icon_color: "emerald-500"
})
else
@statuses.push({
text: "The <strong>#{@relay_host}</strong> relay is missing from your relay list",
icon_name: "alert-octagon",
icon_color: "amber-500"
})
end
else
@statuses.push({
text: "We could not find a relay list for your public key",
icon_name: "alert-octagon",
icon_color: "amber-500"
})
end
@button_text = (nip65_event.present? ? "Edit relays" : "Set up relays")
end
private
def relay_urls(nip65_event)
nip65_event["tags"].select { |t| t[0] == "r" }.map { |t| t[1] }
end
end
end
@@ -1,8 +0,0 @@
<p class="flex gap-x-4 items-center">
<span class="inline-block h-6 w-6 grow-0 text-<%= @icon_color %>">
<%= render "icons/#{@icon_name}" %>
</span>
<span>
<%= raw @text %>
</span>
</p>
-7
View File
@@ -1,7 +0,0 @@
class StatusTextComponent < ViewComponent::Base
def initialize(text:, icon_name:, icon_color:)
@text = text
@icon_name = icon_name
@icon_color = icon_color
end
end
@@ -1,45 +0,0 @@
class Admin::EditableContentsController < Admin::BaseController
before_action :set_content, only: [:show, :edit, :update]
before_action :set_current_section, only: [:index, :show, :edit]
def index
@path = params[:path].presence
scope = EditableContent.order(path: :asc)
scope = scope.where(path: @path) if @path
@pagy, @contents = pagy(scope)
end
def show
end
def edit
end
def update
return_to = params[:editable_content][:redirect_to].presence
if @editable_content.update(content_params)
if return_to
redirect_to return_to
else
render status: :ok
end
else
render :edit, status: :unprocessable_entity
end
end
private
def set_content
@editable_content = EditableContent.find(params[:id])
end
def content_params
params.require(:editable_content).permit(:path, :key, :lang, :content, :rich_text)
end
def set_current_section
@current_section = :content
end
end
+1 -5
View File
@@ -12,11 +12,7 @@ class Admin::UsersController < Admin::BaseController
@contributors = ldap.search_users(:memberStatus, :contributor, :cn) if @show_contributors @contributors = ldap.search_users(:memberStatus, :contributor, :cn) if @show_contributors
@sustainers = ldap.search_users(:memberStatus, :sustainer, :cn) if @show_sustainers @sustainers = ldap.search_users(:memberStatus, :sustainer, :cn) if @show_sustainers
@admins = ldap.search_users(:admin, true, :cn) @admins = ldap.search_users(:admin, true, :cn)
@pagy, @users = pagy(User.where(ou: ou).order(cn: :asc))
@username = params[:username].presence
users_scope = User.where(ou: ou).order(cn: :asc)
users_scope = users_scope.where("cn LIKE ?", "%#{User.sanitize_sql_like(@username.downcase)}%") if @username
@pagy, @users = pagy(users_scope)
@stats = { @stats = {
users_confirmed: User.where(ou: ou).confirmed.count, users_confirmed: User.where(ou: ou).confirmed.count,
+1 -1
View File
@@ -39,7 +39,7 @@ class ApplicationController < ActionController::Base
end end
def after_sign_in_path_for(user) def after_sign_in_path_for(user)
session.delete(:user_return_to) || root_path session[:user_return_to] || root_path
end end
def lndhub_authenticate(options={}) def lndhub_authenticate(options={})
@@ -1,16 +0,0 @@
class Contributions::OtherController < ApplicationController
before_action :authenticate_user!
before_action :set_content_editing
# GET /contributions/other
def index
@current_section = :contributions
end
private
def set_content_editing
return unless params[:edit] && current_user.is_admin?
@edit_content = true
end
end
@@ -0,0 +1,8 @@
class Contributions::ProjectsController < ApplicationController
before_action :authenticate_user!
# GET /contributions
def index
@current_section = :contributions
end
end
@@ -55,12 +55,7 @@ class Devise::PasswordsController < DeviseController
protected protected
def after_resetting_password_path_for(resource) def after_resetting_password_path_for(resource)
session.delete(:user_return_to) Devise.sign_in_after_reset_password ? after_sign_in_path_for(resource) : new_session_path(resource_name)
if Devise.sign_in_after_reset_password
root_path
else
new_session_path(resource_name)
end
end end
# The path used after sending reset password instructions # The path used after sending reset password instructions
-9
View File
@@ -1,9 +0,0 @@
class PagesController < ApplicationController
def privacy
@current_section = :privacy
end
def tos
@current_section = :tos
end
end
+4 -5
View File
@@ -1,5 +1,4 @@
class Rs::OauthController < ApplicationController class Rs::OauthController < ApplicationController
prepend_before_action :assert_redirect_uri, only: [:new, :create]
before_action :require_signed_in_with_username, only: :new before_action :require_signed_in_with_username, only: :new
before_action :authenticate_user!, only: :create before_action :authenticate_user!, only: :create
@@ -17,6 +16,8 @@ class Rs::OauthController < ApplicationController
["In 1 month", 1.month.from_now], ["In 1 month", 1.month.from_now],
["In 1 day", 1.day.from_now]] ["In 1 day", 1.day.from_now]]
http_status :bad_request and return unless @redirect_uri.present?
unless current_user == @user unless current_user == @user
sign_out :user sign_out :user
@@ -63,6 +64,8 @@ class Rs::OauthController < ApplicationController
state = params[:state].presence state = params[:state].presence
expire_at = params[:expire_at].presence expire_at = params[:expire_at].presence
http_status :bad_request and return unless redirect_uri.present?
if permissions.empty? if permissions.empty?
redirect_to(url_with_state("#{redirect_uri}#error=invalid_scope", state), redirect_to(url_with_state("#{redirect_uri}#error=invalid_scope", state),
allow_other_host: true) and return allow_other_host: true) and return
@@ -94,10 +97,6 @@ class Rs::OauthController < ApplicationController
private private
def assert_redirect_uri
http_status :bad_request unless params[:redirect_uri].present?
end
def require_signed_in_with_username def require_signed_in_with_username
unless user_signed_in? unless user_signed_in?
session[:user_return_to] = request.url session[:user_return_to] = request.url
+2 -58
View File
@@ -4,13 +4,8 @@ require "bcrypt"
class SettingsController < ApplicationController class SettingsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_main_nav_section before_action :set_main_nav_section
before_action :set_settings_section, only: [ before_action :set_settings_section, only: [:show, :update, :update_email, :reset_email_password]
:show, :update, :update_email, :reset_email_password before_action :set_user, only: [:show, :update, :update_email, :reset_email_password]
]
before_action :set_user, only: [
:show, :update, :update_email, :reset_email_password,
:fetch_nostr_metadata
]
def index def index
redirect_to setting_path(:profile) redirect_to setting_path(:profile)
@@ -148,63 +143,12 @@ class SettingsController < ApplicationController
} }
end end
def fetch_nostr_metadata
return render plain: "no pubkey", status: :bad_request if @user.nostr_pubkey.blank?
cache_key = "nostr_metadata:#{@user.id}"
case params[:step]
when "relays"
nip65_event = NostrManager::DiscoverUserRelays.call(pubkey: @user.nostr_pubkey)
write_relays = nip65_event ? extract_write_relays(nip65_event) : []
if nip65_event && write_relays.any? && stale?(nip65_event)
auth_event = NostrManager::DiscoverUserRelays.call(
pubkey: @user.nostr_pubkey, relays: write_relays
)
nip65_event = auth_event if auth_event
write_relays = extract_write_relays(nip65_event) if auth_event
end
Rails.cache.write("#{cache_key}:nip65", nip65_event, expires_in: 5.minutes)
Rails.cache.write("#{cache_key}:write_relays", write_relays, expires_in: 5.minutes)
render json: { next: "profile" }
when "profile"
write_relays = Rails.cache.read("#{cache_key}:write_relays") || []
relays = write_relays.any? ? write_relays : nil
profile = NostrManager::DiscoverUserProfile.call(
pubkey: @user.nostr_pubkey, relays: relays
)
Rails.cache.write("#{cache_key}:profile", profile, expires_in: 5.minutes)
render json: { next: "render" }
when "render", nil
@nip65_event = Rails.cache.read("#{cache_key}:nip65")
@profile = Rails.cache.read("#{cache_key}:profile")
render partial: "nostr_metadata", formats: :html
else
render plain: "invalid step", status: :bad_request
end
end
private private
def set_main_nav_section def set_main_nav_section
@current_section = :settings @current_section = :settings
end end
def extract_write_relays(nip65_event)
nip65_event["tags"]
.select { |t| t[0] == "r" && t[2] != "read" }
.map { |t| t[1] }
end
def stale?(event, threshold = 30.minutes)
Time.at(event["created_at"]) < threshold.ago
end
def set_settings_section def set_settings_section
@settings_section = params[:section] @settings_section = params[:section]
allowed_sections = [ allowed_sections = [
+18
View File
@@ -0,0 +1,18 @@
class TurboController < ApplicationController
class Responder < ActionController::Responder
def to_turbo_stream
controller.render(options.merge(formats: :html))
rescue ActionView::MissingTemplate => error
if get?
raise error
elsif has_errors? && default_action
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
else
redirect_to navigation_location
end
end
end
self.responder = Responder
respond_to :html, :turbo_stream
end
+1 -1
View File
@@ -6,7 +6,7 @@ class Users::DeviseController < ApplicationController
if get? if get?
raise error raise error
elsif has_errors? && default_action elsif has_errors? && default_action
render error_rendering_options.merge(formats: :html, status: :unprocessable_entity) render rendering_options.merge(formats: :html, status: :unprocessable_entity)
else else
redirect_to navigation_location redirect_to navigation_location
end end
-6
View File
@@ -15,12 +15,6 @@ module ApplicationHelper
tag.span text, class: "inline-flex items-center rounded-full bg-#{color}-100 px-2.5 py-0.5 text-xs font-medium text-#{color}-800" tag.span text, class: "inline-flex items-center rounded-full bg-#{color}-100 px-2.5 py-0.5 text-xs font-medium text-#{color}-800"
end end
def markdown_to_html(string)
raw Kramdown::Document.new(string, {
input: "GFM"
}).to_html
end
def image_url_for(attachment) def image_url_for(attachment)
return s3_image_url(attachment) if Setting.s3_enabled? return s3_image_url(attachment) if Setting.s3_enabled?
@@ -1,50 +0,0 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "status", "loading", "content" ]
static values = { url: String }
connect () {
this.runStep("relays")
}
async runStep (step) {
const messages = {
relays: "Looking up your relay list…",
profile: "Fetching your profile…",
blossom: "Fetching your media server list…",
render: null
}
if (messages[step]) {
this.statusTarget.textContent = messages[step]
}
try {
const headers = (step === "render")
? { "Accept": "text/html" }
: { "Accept": "application/json" }
const res = await fetch(`${this.urlValue}?step=${step}`, { headers })
if (!res.ok) {
throw new Error(`HTTP ${res.status}`)
}
if (step === "render") {
const html = await res.text()
this.loadingTarget.classList.add("hidden")
this.contentTarget.classList.remove("hidden")
this.contentTarget.innerHTML = html
} else {
const data = await res.json()
if (data.next) {
this.runStep(data.next)
}
}
} catch (error) {
console.warn("Nostr metadata fetch failed:", error.message)
this.statusTarget.textContent = "Something went wrong. Please reload."
}
}
}
@@ -1,17 +0,0 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "list", "template" ]
add (event) {
event.preventDefault()
const row = this.templateTarget.content.cloneNode(true)
this.listTarget.appendChild(row)
}
remove (event) {
event.preventDefault()
const row = event.target.closest("li")
row.remove()
}
}
@@ -18,23 +18,8 @@ module Settings
field :nostr_relay_url, type: :string, field :nostr_relay_url, type: :string,
default: ENV["NOSTR_RELAY_URL"].presence default: ENV["NOSTR_RELAY_URL"].presence
field :nostr_blossom_server_url, type: :string,
default: ENV["NOSTR_BLOSSOM_SERVER_URL"].presence
field :nostr_zaps_relay_limit, type: :integer, field :nostr_zaps_relay_limit, type: :integer,
default: 12 default: 12
field :nostr_discovery_relays, type: :array, default: %w[
wss://nostr.kosmos.org
wss://relay.nostr.band
wss://njump.me
wss://relay.damus.io
]
def self.nostr_relay_url_http
self.nostr_relay_url.gsub(/^ws:/, "http:")
.gsub(/^wss:/, "https:")
end
end end
end end
end end
-12
View File
@@ -1,12 +0,0 @@
class EditableContent < ApplicationRecord
validates :key, presence: true,
uniqueness: { scope: :context }
def has_content?
content.present?
end
def is_empty?
content.blank?
end
end
+4 -4
View File
@@ -10,18 +10,18 @@ class LndhubUser < LndhubBase
foreign_key: "login" foreign_key: "login"
def balance def balance
accounts.current.order(:id).first.ledgers.sum("account_ledgers.amount").to_i.abs accounts.current.first.ledgers.sum("account_ledgers.amount").to_i.abs
end end
def sum_outgoing def sum_outgoing
accounts.outgoing.order(:id).first.ledgers.sum("account_ledgers.amount").to_i.abs accounts.outgoing.first.ledgers.sum("account_ledgers.amount").to_i.abs
end end
def sum_incoming def sum_incoming
accounts.incoming.order(:id).first.ledgers.sum("account_ledgers.amount").to_i.abs accounts.incoming.first.ledgers.sum("account_ledgers.amount").to_i.abs
end end
def sum_fees def sum_fees
accounts.fees.order(:id).first.ledgers.sum("account_ledgers.amount").to_i.abs accounts.fees.first.ledgers.sum("account_ledgers.amount").to_i.abs
end end
end end
+5 -5
View File
@@ -131,11 +131,11 @@ class User < ApplicationRecord
end end
def is_admin? def is_admin?
@admin ||= if admin = Devise::LDAP::Adapter.get_ldap_param(self.cn, :admin) admin ||= if admin = Devise::LDAP::Adapter.get_ldap_param(self.cn, :admin)
!!admin.first !!admin.first
else else
false false
end end
end end
def address def address
+3 -3
View File
@@ -11,7 +11,7 @@ module EjabberdManager
def call def call
unless @overwrite unless @overwrite
current_avatar = EjabberdManager::GetAvatar.call(user: @user) current_avatar = EjabberdManager::GetAvatar.call(user: @user)
Rails.logger.info { "User #{@user.cn} already has an avatar set" } Rails.logger.info { "User #{user.cn} already has an avatar set" }
return if current_avatar.present? return if current_avatar.present?
end end
@@ -25,8 +25,9 @@ module EjabberdManager
raise res.inspect if res.status != 200 raise res.inspect if res.status != 200
end end
end end
end
private private
def process_avatar def process_avatar
@user.avatar.blob.open do |file| @user.avatar.blob.open do |file|
@@ -76,5 +77,4 @@ module EjabberdManager
""".strip, """.strip,
] ]
end end
end
end end
@@ -1,21 +0,0 @@
module NostrManager
class DiscoverUserProfile < NostrManagerService
def initialize(pubkey:, relays: nil)
@pubkey = pubkey
@relays = relays.present? ? relays : Setting.nostr_discovery_relays
end
def call
filter = Nostr::Filter.new(
authors: [@pubkey],
kinds: [0],
limit: 1,
)
NostrManager::FetchLatestEvent.call(
relays: @relays,
filter: filter
)
end
end
end
@@ -1,21 +0,0 @@
module NostrManager
class DiscoverUserRelays < NostrManagerService
def initialize(pubkey:, relays: nil)
@pubkey = pubkey
@relays = relays.present? ? relays : Setting.nostr_discovery_relays
end
def call
filter = Nostr::Filter.new(
authors: [@pubkey],
kinds: [10002],
limit: 1,
)
NostrManager::FetchLatestEvent.call(
relays: @relays,
filter: filter
)
end
end
end
-61
View File
@@ -1,61 +0,0 @@
module NostrManager
class FetchEvent < NostrManagerService
TIMEOUT = 10
def initialize(filter:, relay_url:)
@filter = filter
@relay = new_relay(relay_url)
@client = Nostr::Client.new
end
def call
filter, client, relay = @filter, @client, @relay
event = nil
mutex = Mutex.new
received_event = ConditionVariable.new
log_prefix = "[nostr][#{@relay.name}]"
thread = Thread.new do
client.on :connect do
client.subscribe(filter: filter)
end
client.on :error do |e|
Rails.logger.info "#{log_prefix} Error: #{e}"
Thread.current.exit
end
client.on :message do |m|
msg = JSON.parse(m) rescue nil
if msg && msg[0] == "EVENT" && msg[2]
Rails.logger.debug "#{log_prefix} Event received: #{msg[2]["id"]}"
mutex.synchronize do
event = msg[2]
received_event.signal
end
elsif msg && msg[0] == "EOSE"
mutex.synchronize do
received_event.signal
end
end
end
client.connect relay
end
begin
Timeout.timeout(TIMEOUT) do
mutex.synchronize do
received_event.wait(mutex) if event.nil?
end
end
rescue Timeout::Error
Rails.logger.debug "#{log_prefix} Timeout: No event received within #{TIMEOUT} seconds"
ensure
thread.exit
end
event
end
end
end
@@ -1,32 +0,0 @@
module NostrManager
class FetchLatestEvent < NostrManagerService
TIMEOUT = 20
def initialize(relays:, filter:, max_events: 2)
@relays = relays
@filter = filter
@max_events = max_events
end
def call
events = []
begin
Timeout.timeout(TIMEOUT) do
@relays.each do |url|
event = NostrManager::FetchEvent.call(filter: @filter, relay_url: url)
if event.present? && events.none? { |e| e["id"] == event["id"] }
events << event
break if events.size >= @max_events
end
end
end
rescue Timeout::Error
Rails.logger.debug "[nostr] Timeout after #{TIMEOUT}s, found #{events.size} events"
end
events.max_by { |e| e["created_at"] }
end
end
end
+7 -7
View File
@@ -19,28 +19,28 @@ module NostrManager
thread = Thread.new do thread = Thread.new do
client.on :connect do client.on :connect do
Rails.logger.debug "#{log_prefix} Publishing #{event.id}..." puts "#{log_prefix} Publishing #{event.id}..."
client.publish event client.publish event
end end
client.on :error do |e| client.on :error do |e|
Rails.logger.debug "#{log_prefix} Error: #{e}" puts "#{log_prefix} Error: #{e}"
Rails.logger.debug "#{log_prefix} Closing thread..." puts "#{log_prefix} Closing thread..."
thread.exit thread.exit
end end
client.on :message do |m| client.on :message do |m|
Rails.logger.debug "#{log_prefix} Message: #{m}" puts "#{log_prefix} Message: #{m}"
msg = JSON.parse(m) rescue [] msg = JSON.parse(m) rescue []
if msg[0] == "OK" && msg[1] == event.id && msg[2] if msg[0] == "OK" && msg[1] == event.id && msg[2]
Rails.logger.debug "#{log_prefix} Event published. Closing thread..." puts "#{log_prefix} Event published. Closing thread..."
else else
Rails.logger.debug "#{log_prefix} Unexpected message from relay. Closing thread..." puts "#{log_prefix} Unexpected message from relay. Closing thread..."
end end
thread.exit thread.exit
end end
Rails.logger.debug "#{log_prefix} Connecting to #{relay.url}..." puts "#{log_prefix} Connecting to #{relay.url}..."
client.connect relay client.connect relay
end end
-5
View File
@@ -3,7 +3,6 @@ require "nostr"
class NostrManagerService < ApplicationService class NostrManagerService < ApplicationService
def parse_tags(tags) def parse_tags(tags)
out = {} out = {}
# TODO support more than 1 item for each tag type
tags.each do |tag| tags.each do |tag|
out[tag[0].to_sym] = tag[1, tag.length] out[tag[0].to_sym] = tag[1, tag.length]
end end
@@ -20,8 +19,4 @@ class NostrManagerService < ApplicationService
def site_user def site_user
Nostr::User.new(keypair: site_keypair) Nostr::User.new(keypair: site_keypair)
end end
def new_relay(url)
Nostr::Relay.new(url: url, name: URI.parse(url).host)
end
end end
@@ -38,7 +38,8 @@
<tr> <tr>
<td><%= web_app.name %></td> <td><%= web_app.name %></td>
<td><%= link_to web_app.url, web_app.url, <td><%= link_to web_app.url, web_app.url,
target: "_blank", rel: "nofollow noopener" %></td> target: "_blank", rel: "nofollow noopener",
class: "ks-text-link" %></td>
<td class="hidden md:table-cell"><%= web_app.remote_storage_authorizations.count %></td> <td class="hidden md:table-cell"><%= web_app.remote_storage_authorizations.count %></td>
<td class="hidden md:table-cell"> <td class="hidden md:table-cell">
<span title="<%= web_app.created_at %>" class="cursor-help"> <span title="<%= web_app.created_at %>" class="cursor-help">
+1 -1
View File
@@ -12,7 +12,7 @@
<tbody> <tbody>
<% donations.each do |donation| %> <% donations.each do |donation| %>
<tr> <tr>
<td><%= link_to donation.user.cn, admin_user_path(donation.user.cn) %></td> <td><%= link_to donation.user.cn, admin_user_path(donation.user.cn), class: 'ks-text-link' %></td>
<td class="text-right"><% if donation.amount_sats.present? %><%= number_with_delimiter donation.amount_sats %><% end %></td> <td class="text-right"><% if donation.amount_sats.present? %><%= number_with_delimiter donation.amount_sats %><% end %></td>
<td class="text-right"><% if donation.fiat_amount.present? %><%= number_to_currency donation.fiat_amount.to_f / 100, unit: "" %> <%= donation.fiat_currency %><% end %></td> <td class="text-right"><% if donation.fiat_amount.present? %><%= number_to_currency donation.fiat_amount.to_f / 100, unit: "" %> <%= donation.fiat_currency %><% end %></td>
<td class="pl-2"><%= donation.public_name %></td> <td class="pl-2"><%= donation.public_name %></td>
+1 -1
View File
@@ -6,7 +6,7 @@
<tbody> <tbody>
<tr> <tr>
<th>User</th> <th>User</th>
<td><%= link_to @donation.user.cn, admin_user_path(@donation.user.cn) %></td> <td><%= link_to @donation.user.cn, admin_user_path(@donation.user.cn), class: 'ks-text-link' %></td>
</tr> </tr>
<tr> <tr>
<th>Donation Method</th> <th>Donation Method</th>
+2 -2
View File
@@ -44,8 +44,8 @@
<tr> <tr>
<td class="overflow-ellipsis font-mono"><%= invitation.token %></td> <td class="overflow-ellipsis font-mono"><%= invitation.token %></td>
<td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td> <td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
<td><%= link_to invitation.user.cn, admin_user_path(invitation.user.cn) %></td> <td><%= link_to invitation.user.cn, admin_user_path(invitation.user.cn), class: "ks-text-link" %></td>
<td><%= link_to invitation.invitee.cn, admin_user_path(invitation.invitee.cn) %></td> <td><%= link_to invitation.invitee.cn, admin_user_path(invitation.invitee.cn), class: "ks-text-link" %></td>
</tr> </tr>
<% end %> <% end %>
</tbody> </tbody>
+1 -1
View File
@@ -36,7 +36,7 @@
</td> </td>
<td> <td>
<% if user = @users.find{ |u| u[2] == account.login } %> <% if user = @users.find{ |u| u[2] == account.login } %>
<%= link_to user[0], admin_user_path(user[0]) %> <%= link_to user[0], admin_user_path(user[0]), class: "ks-text-link" %>
<% end %> <% end %>
</td> </td>
<td><%= number_with_delimiter account.balance.to_i.to_s %></td> <td><%= number_with_delimiter account.balance.to_i.to_s %></td>
@@ -29,35 +29,15 @@
title: "Relay URL", title: "Relay URL",
description: "Websockets URL of a relay associated with #{Setting.primary_domain}" description: "Websockets URL of a relay associated with #{Setting.primary_domain}"
) %> ) %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :nostr_blossom_server_url,
title: "Blossom server URL",
description: "HTTP URL of the Blossom media server to add to users' server lists during onboarding"
) %>
</ul> </ul>
</section> </section>
<section> <section>
<h3>Zaps</h3> <h3>Zaps</h3>
<ul role="list"> <ul role="list">
<%= render FormElements::FieldsetResettableSettingComponent.new( <%= render FormElements::FieldsetResettableSettingComponent.new(
key: :nostr_zaps_relay_limit, key: :nostr_zaps_relay_limit,
title: "Relay limit", title: "Relay limit",
description: "The maximum number of sender-defined relays to try to publish zap receipts to" description: "The maximum number of relays to publish zap receipts to"
) %> ) %>
</ul> </ul>
</section>
<section>
<h3>Onboarding</h3>
<ul role="list">
<%= render FormElements::FieldsetComponent.new(
title: "Discovery relays",
description: "Used to discover a user's published relay list and/or profile"
) do %>
<%= f.text_area :nostr_discovery_relays,
value: Setting.nostr_discovery_relays.join("\n"),
class: "h-44 w-80" %>
<% end %>
</ul>
<% end %> <% end %>
+1 -6
View File
@@ -30,11 +30,6 @@
<% end %> <% end %>
</section> </section>
<section>
<%= render partial: "admin/username_search_form",
locals: { path: admin_users_path } %>
</section>
<section> <section>
<table class="divided mb-8"> <table class="divided mb-8">
<thead> <thead>
@@ -47,7 +42,7 @@
<tbody> <tbody>
<% @users.each do |user| %> <% @users.each do |user| %>
<tr> <tr>
<td><%= link_to(user.cn, admin_user_path(user.cn)) %></td> <td><%= link_to(user.cn, admin_user_path(user.cn), class: 'ks-text-link') %></td>
<td> <td>
<%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %> <%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %>
<% if @show_contributors %><%= @contributors.include?(user.cn) ? badge("contributor", :green) : "" %><% end %> <% if @show_contributors %><%= @contributors.include?(user.cn) ? badge("contributor", :green) : "" %><% end %>
+5 -5
View File
@@ -47,7 +47,7 @@
<th>Donations</th> <th>Donations</th>
<td> <td>
<% if @user.donations.any? %> <% if @user.donations.any? %>
<%= link_to admin_donations_path(username: @user.cn) do %> <%= link_to admin_donations_path(username: @user.cn), class: "ks-text-link" do %>
<%= @user.donations.completed.count %> for <%= @user.donations.completed.count %> for
<%= number_with_delimiter @user.donations.completed.sum("amount_sats") %> sats <%= number_with_delimiter @user.donations.completed.sum("amount_sats") %> sats
<% end %> <% end %>
@@ -60,7 +60,7 @@
<th>Invited by</th> <th>Invited by</th>
<td> <td>
<% if @user.inviter %> <% if @user.inviter %>
<%= link_to @user.inviter.cn, admin_user_path(@user.inviter.cn) %> <%= link_to @user.inviter.cn, admin_user_path(@user.inviter.cn), class: 'ks-text-link' %>
<% else %>&mdash;<% end %> <% else %>&mdash;<% end %>
</td> </td>
</tr> </tr>
@@ -102,10 +102,10 @@
<% if @invitees.any? %> <% if @invitees.any? %>
<ul class="mb-0"> <ul class="mb-0">
<% @recent_invitees.each do |invitee| %> <% @recent_invitees.each do |invitee| %>
<li class="leading-none mb-2 last:mb-0"><%= link_to invitee.cn, admin_user_path(invitee.cn) %></li> <li class="leading-none mb-2 last:mb-0"><%= link_to invitee.cn, admin_user_path(invitee.cn), class: "ks-text-link" %></li>
<% end %> <% end %>
<% if @more_invitees > 0 %> <% if @more_invitees > 0 %>
<li>and <%= link_to "#{@more_invitees} more", admin_invitations_path(username: @user.cn) %></li> <li>and <%= link_to "#{@more_invitees} more", admin_invitations_path(username: @user.cn), class: "ks-text-link" %></li>
<% end %> <% end %>
</ul> </ul>
<% else %>&mdash;<% end %> <% else %>&mdash;<% end %>
@@ -168,7 +168,7 @@
<span class="font-mono" title="<%= @user.pgp_fpr %>"> <span class="font-mono" title="<%= @user.pgp_fpr %>">
<% if @user.pgp_pubkey_contains_user_address? %> <% if @user.pgp_pubkey_contains_user_address? %>
<%= link_to wkd_key_url(hashed_username: @user.wkd_hash, l: @user.cn, format: :txt), <%= link_to wkd_key_url(hashed_username: @user.wkd_hash, l: @user.cn, format: :txt),
target: "_blank" do %> class: "ks-text-link", target: "_blank" do %>
<%= "#{@user.pgp_fpr[0, 8]}#{@user.pgp_fpr[-8..-1]}" %> <%= "#{@user.pgp_fpr[0, 8]}#{@user.pgp_fpr[-8..-1]}" %>
<% end %> <% end %>
<% else %> <% else %>
@@ -3,7 +3,7 @@
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %> <%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
<section> <section>
<p class="mb-12"> <p class="mb-12">
Your financial contributions to the development and upkeep of our Your financial contributions to the development and upkeep of Kosmos
software and services. software and services.
</p> </p>
</section> </section>
@@ -1,20 +0,0 @@
<%= render HeaderComponent.new(title: "Contributions") %>
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
<section>
<%= render EditableContentComponent.new(
context: "contributions/other", key: "body", rich_text: true,
default: "No content yet") %>
<% if current_user.is_admin? %>
<div class="mt-8 pt-6 border-t border-gray-200 text-right">
<%= render EditContentButtonComponent.new(
context: "contributions/other", key: "title",
redirect_to: request.path) do %>Edit title<% end %>
<%= render EditContentButtonComponent.new(
context: "contributions/other", key: "body", rich_text: true,
redirect_to: request.path) do %>Edit content<% end %>
</div>
<% end %>
</section>
<% end %>
@@ -0,0 +1,49 @@
<%= render HeaderComponent.new(title: "Contributions") %>
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
<section>
<p class="mb-8">
Project contributions are how we develop and run all Kosmos software and
services. Everything we create and provide is free and open-source
software, even the page you're looking at right now!
</p>
<h3>Start contributing</h3>
<p>
Check out our
<a href="https://kosmos.org/projects/" target="_blank" class="ks-text-link">projects page</a>
for some (but not all) potential places that can use your help.
</p>
<p>
There's something to do for everyone, especially non-programmers! For
example, we need more help with graphics, UI/UX design, and
content/copywriting. Also, testing any of our software and reporting
issues you encounter along the way is very valuable.
</p>
<p>
A good way to get started is to join one of our
<a href="https://wiki.kosmos.org/Main_Page#Chat" target="_blank" class="ks-text-link">chat rooms</a>
and introduce yourself. Alternatively, you can also ping us on any other
medium, or even just grab an open issue on our
<a href="https://gitea.kosmos.org/kosmos/" target="_blank" class="ks-text-link">Gitea</a>
or on
<a href="https://github.com/67P/" target="_blank" class="ks-text-link">GitHub</a>
and dive right in.
</p>
<p class="mb-8">
Last but not least, if you want to help by proposing new features or
services, or by giving feedback on existing ones, head over to the
<a href="https://community.kosmos.org/" target="_blank" class="ks-text-link">community forums</a>,
where you can do just that.
</p>
<h3>Open Source Grants</h3>
<p>
Money coming in from financial contributions is first used to pay for our
bills. Additional funds are being paid out directly to our contributors,
including you, according to their rough share of contributions.
</p>
<p>
We have run two 6-month trials so far, with the next trial period
starting sometime soon. Watch your email for notifications about it!
</p>
</section>
<% end %>
+10 -12
View File
@@ -8,7 +8,7 @@
bg-[length:86%] bg-[center_top_-40px] bg-no-repeat bg-[length:86%] bg-[center_top_-40px] bg-no-repeat
bg-[url(/img/logos/icon_xmpp.svg)]"> bg-[url(/img/logos/icon_xmpp.svg)]">
<%= link_to services_chat_path, <%= link_to services_chat_path,
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %> class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Chat</h3> <h3 class="mb-3.5">Chat</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Federated chat rooms and instant messaging Federated chat rooms and instant messaging
@@ -20,8 +20,7 @@
<div class="border border-gray-300 rounded-md hover:border-gray-400 <div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:88%] bg-[center_top_-40px] bg-no-repeat bg-[length:88%] bg-[center_top_-40px] bg-no-repeat
bg-[url(/img/logos/icon_mastodon.svg)]"> bg-[url(/img/logos/icon_mastodon.svg)]">
<%= link_to services_mastodon_path, <%= link_to services_mastodon_path, class: "block h-full px-6 py-6 rounded-md" do %>
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<h3 class="mb-3.5">Mastodon</h3> <h3 class="mb-3.5">Mastodon</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Your account on the Open Social Web Your account on the Open Social Web
@@ -34,8 +33,7 @@
<div class="border border-gray-300 rounded-md hover:border-gray-400 <div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:90%] bg-[center_top_-160px] bg-no-repeat bg-[length:90%] bg-[center_top_-160px] bg-no-repeat
bg-[url(/img/logos/icon_mail.svg)]"> bg-[url(/img/logos/icon_mail.svg)]">
<%= link_to services_email_path, <%= link_to services_email_path, class: "block h-full px-6 py-6 rounded-md" do %>
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<h3 class="mb-3.5">E-Mail</h3> <h3 class="mb-3.5">E-Mail</h3>
<p class="text-gray-600"> <p class="text-gray-600">
A no-bullshit email account A no-bullshit email account
@@ -49,7 +47,7 @@
bg-[length:80%] bg-[center_top_-156px] bg-no-repeat bg-[length:80%] bg-[center_top_-156px] bg-no-repeat
bg-[url(/img/logos/icon_remotestorage.svg)]"> bg-[url(/img/logos/icon_remotestorage.svg)]">
<%= link_to services_storage_path, <%= link_to services_storage_path,
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %> class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Storage</h3> <h3 class="mb-3.5">Storage</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Sync your data between apps and devices Sync your data between apps and devices
@@ -62,7 +60,7 @@
bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat
bg-[url(/img/logos/icon_lightning.svg)]"> bg-[url(/img/logos/icon_lightning.svg)]">
<%= link_to services_lightning_index_path, <%= link_to services_lightning_index_path,
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %> class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Lightning Network</h3> <h3 class="mb-3.5">Lightning Network</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Send and receive sats over the Bitcoin Lightning Network Send and receive sats over the Bitcoin Lightning Network
@@ -75,7 +73,7 @@
bg-[length:80%] bg-center bg-no-repeat bg-[length:80%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_discourse.svg)]"> bg-[url(/img/logos/icon_discourse.svg)]">
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/", <%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %> class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Discourse</h3> <h3 class="mb-3.5">Discourse</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Community forums and support/help site Community forums and support/help site
@@ -88,7 +86,7 @@
bg-[length:92%] bg-center bg-no-repeat bg-[length:92%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_gitea.png)]"> bg-[url(/img/logos/icon_gitea.png)]">
<%= link_to Setting.gitea_public_url, <%= link_to Setting.gitea_public_url,
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %> class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Gitea</h3> <h3 class="mb-3.5">Gitea</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Code hosting and collaboration for software projects Code hosting and collaboration for software projects
@@ -101,7 +99,7 @@
bg-[length:86%] bg-[center_top_-60px] bg-no-repeat bg-[length:86%] bg-[center_top_-60px] bg-no-repeat
bg-[url(/img/logos/icon_droneci.svg)]"> bg-[url(/img/logos/icon_droneci.svg)]">
<%= link_to Setting.droneci_public_url, <%= link_to Setting.droneci_public_url,
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %> class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Drone CI</h3> <h3 class="mb-3.5">Drone CI</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Continuous integration for software projects on Gitea Continuous integration for software projects on Gitea
@@ -114,10 +112,10 @@
bg-cover bg-[center_top_-20px] bg-no-repeat bg-cover bg-[center_top_-20px] bg-no-repeat
bg-[url(/img/logos/icon_mediawiki.svg)]"> bg-[url(/img/logos/icon_mediawiki.svg)]">
<%= link_to Setting.mediawiki_public_url, <%= link_to Setting.mediawiki_public_url,
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %> class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Wiki</h3> <h3 class="mb-3.5">Wiki</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Documentation and knowledge base Kosmos documentation and knowledge base
</p> </p>
<% end %> <% end %>
</div> </div>
+1 -1
View File
@@ -47,7 +47,7 @@
data: { action: "click->settings--toggle#toggleSwitch" } %> data: { action: "click->settings--toggle#toggleSwitch" } %>
<p class="grow text-sm text-right"> <p class="grow text-sm text-right">
<%= link_to "Forgot your password?", new_password_path(resource_name), <%= link_to "Forgot your password?", new_password_path(resource_name),
class: "text-gray-500 visited:text-gray-500 underline" %><br /> class: "text-gray-500 underline" %><br />
</p> </p>
<% end %> <% end %>
@@ -1,5 +1,5 @@
<% if resource.errors.any? %> <% if resource.errors.any? %>
<div id="error_explanation" class="mb-6"> <div id="error_explanation">
<ul> <ul>
<% resource.errors.full_messages.each do |message| %> <% resource.errors.full_messages.each do |message| %>
<li class="text-red-600"><%= message %></li> <li class="text-red-600"><%= message %></li>
+4 -4
View File
@@ -2,28 +2,28 @@
<%- if controller_name != 'sessions' %> <%- if controller_name != 'sessions' %>
<p class="mb-2"> <p class="mb-2">
<%= link_to "Log in", new_session_path(resource_name), <%= link_to "Log in", new_session_path(resource_name),
class: "text-gray-500 visited:text-gray-500 underline" %> class: "text-gray-500 underline" %>
</p> </p>
<% end %> <% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<p class="mb-2"> <p class="mb-2">
<%= link_to "Forgot your password?", new_password_path(resource_name), <%= link_to "Forgot your password?", new_password_path(resource_name),
class: "text-gray-500 visited:text-gray-500 underline" %> class: "text-gray-500 underline" %>
</p> </p>
<% end %> <% end %>
<%- if devise_mapping.confirmable? && !controller_name.match(/^(confirmations|sessions)$/) %> <%- if devise_mapping.confirmable? && !controller_name.match(/^(confirmations|sessions)$/) %>
<p class="mb-2"> <p class="mb-2">
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name),
class: "text-gray-500 visited:text-gray-500 underline" %> class: "text-gray-500 underline" %>
</p> </p>
<% end %> <% end %>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<p class="mb-2"> <p class="mb-2">
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name), <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name),
class: "text-gray-500 visited:text-gray-500 underline" %> class: "text-gray-500 underline" %>
</p> </p>
<% end %> <% end %>
</div> </div>
+1 -1
View File
@@ -4,7 +4,7 @@
<section> <section>
<% if @invitations_unused.any? %> <% if @invitations_unused.any? %>
<p class="mb-8"> <p class="mb-8">
Invite your friends to register an account by sharing an invitation URL with them: Invite your friends to a Kosmos account by sharing an invitation URL with them:
</p> </p>
<ul class="md:w-3/4"> <ul class="md:w-3/4">
<% @invitations_unused.each do |invitation| %> <% @invitations_unused.each do |invitation| %>
+2 -20
View File
@@ -10,7 +10,7 @@
<%= javascript_importmap_tags %> <%= javascript_importmap_tags %>
</head> </head>
<body <%= @context.present? ? "id=#{@context}" : "" %> class="h-full <%= @context == :admin ? "bg-red-500" : "bg-sky-900" %>"> <body <%= @context.present? ? "id=#{@context}" : "" %> class="h-full <%= @context == :admin ? "bg-red-500" : "bg-sky-900" %>">
<div class="min-h-full flex flex-col"> <div class="min-h-full">
<nav data-controller="topbar"> <nav data-controller="topbar">
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
<div class="border-b border-gray-200/10"> <div class="border-b border-gray-200/10">
@@ -41,10 +41,6 @@
<%= link_to "Log out", destroy_user_session_path, class: 'underline hover:text-white' %> <%= link_to "Log out", destroy_user_session_path, class: 'underline hover:text-white' %>
</span> </span>
</div> </div>
<% elsif !user_signed_in? && controller_name != 'sessions' %>
<div class="flex items-baseline text-gray-200/80 text-sm font-medium">
<%= link_to "Log in", new_user_session_path, class: 'underline hover:text-white' %>
</div>
<% end %> <% end %>
</div> </div>
<div class="-mr-2 flex md:hidden"> <div class="-mr-2 flex md:hidden">
@@ -64,11 +60,6 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
</button> </button>
<% elsif !user_signed_in? && controller_name != 'sessions' %>
<div class="mt-3 px-2 space-y-1">
<%= link_to "Log in", new_user_session_path,
class: 'block px-3 py-2 rounded-md text-base font-medium text-gray-300 hover:text-white hover:bg-gray-900/30 active:text-white active:bg-gray-900/30' %>
</div>
<% end %> <% end %>
</div> </div>
</div> </div>
@@ -105,16 +96,7 @@
</div> </div>
</div> </div>
<div class="flex-grow"> <%= yield %>
<%= yield %>
</div>
<nav id="footer" class="max-w-6xl mx-auto pt-4 sm:px-6 lg:px-8">
<div class="flex justify-end items-center space-x-4 h-16">
<%= link_to "Terms", tos_page_path, class: main_nav_class(@current_section, :tos) %>
<%= link_to "Privacy", privacy_page_path, class: main_nav_class(@current_section, :privacy) %>
</div>
</nav>
</div> </div>
</body> </body>
</html> </html>
@@ -1,6 +1,6 @@
Hi <%= @user.display_name.presence || @user.cn %>, Hi <%= @user.display_name.presence || @user.cn %>,
New invitations have just been added to your account, so you can invite more people to our cooperative services: New invitations have just been added to your Kosmos account, so you can invite more people to our cooperative services:
<%= invitations_url %> <%= invitations_url %>
@@ -1,6 +1,6 @@
Hi <%= @user.display_name.presence || @user.cn %>, Hi <%= @user.display_name.presence || @user.cn %>,
You have just granted '<%= @auth.client_id %>' access to your remote storage, with the following permissions: You have just granted '<%= @auth.client_id %>' access to your Kosmos Storage, with the following permissions:
<% @permissions.each do |p| %> <% @permissions.each do |p| %>
* <%= p %> * <%= p %>
-17
View File
@@ -1,17 +0,0 @@
<%= render HeaderComponent.new(title: "Privacy Policy") %>
<%= render MainSimpleComponent.new do %>
<section>
<%= render EditableContentComponent.new(
context: "privacy", key: "body", rich_text: true,
default: "No content yet. Please add a privacy policy.") %>
<% if user_signed_in? && current_user.is_admin? %>
<div class="mt-8 pt-6 border-t border-gray-200 text-right">
<%= render EditContentButtonComponent.new(
context: "privacy", key: "body", rich_text: true,
redirect_to: request.path) do %>Edit content<% end %>
</div>
<% end %>
</section>
<% end %>
-17
View File
@@ -1,17 +0,0 @@
<%= render HeaderComponent.new(title: "Terms of Service") %>
<%= render MainSimpleComponent.new do %>
<section>
<%= render EditableContentComponent.new(
context: "tos", key: "body", rich_text: true,
default: "No content yet. Please add your terms of service.") %>
<% if user_signed_in? && current_user.is_admin? %>
<div class="mt-8 pt-6 border-t border-gray-200 text-right">
<%= render EditContentButtonComponent.new(
context: "tos", key: "body", rich_text: true,
redirect_to: request.path) do %>Edit content<% end %>
</div>
<% end %>
</section>
<% end %>
+1 -1
View File
@@ -4,7 +4,7 @@
<section class="permissions"> <section class="permissions">
<p class="mb-8"> <p class="mb-8">
The app on The app on
<%= link_to @client_id, "https://#{@client_id}" %> <%= link_to @client_id, "https://#{@client_id}", class: "ks-text-link" %>
is asking for access to these folders: is asking for access to these folders:
</p> </p>
+10 -5
View File
@@ -62,30 +62,35 @@
</select> </select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b"> <ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Android Android
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
iOS iOS
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Linux Linux
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Windows Windows
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
macOS macOS
</a> </a>
</li> </li>
<!-- <li class="mr&#45;2" data&#45;tabs&#45;target="tab" data&#45;action="click&#45;>tabs#change"> -->
<!-- <a href="#" class="bg&#45;white inline&#45;block py&#45;2 px&#45;4 font&#45;semibold no&#45;underline"> -->
<!-- Web -->
<!-- </a> -->
<!-- </li> -->
</ul> </ul>
<div id="apps-android" class="hidden grid grid-cols-1 gap-6" <div id="apps-android" class="hidden grid grid-cols-1 gap-6"
+5 -5
View File
@@ -34,7 +34,7 @@
<p> <p>
Your email password is different from your main account password. You can Your email password is different from your main account password. You can
reset your email password in the reset your email password in the
<%= link_to "email settings", setting_path(:email) %>. <%= link_to "email settings", setting_path(:email), class: "ks-text-link" %>.
</p> </p>
</section> </section>
<section> <section>
@@ -56,22 +56,22 @@
</select> </select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b"> <ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Android Android
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Linux Linux
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Windows Windows
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
macOS macOS
</a> </a>
</li> </li>
+7 -7
View File
@@ -8,8 +8,8 @@
<section> <section>
<h3>Lightning Address</h3> <h3>Lightning Address</h3>
<p class="mb-6"> <p class="mb-6">
Your user address is also a Your Kosmos user address is also a
<a href="https://lightningaddress.com/" target="_blank">Lightning Address</a>! <a class="ks-text-link" href="https://lightningaddress.com/" target="_blank">Lightning Address</a>!
The easiest way to receive sats is by just giving out your address: The easiest way to receive sats is by just giving out your address:
</p> </p>
<p data-controller="clipboard" class="flex gap-1 sm:w-2/5"> <p data-controller="clipboard" class="flex gap-1 sm:w-2/5">
@@ -32,9 +32,9 @@
<section data-controller="modal" data-action="keydown.esc->modal#close"> <section data-controller="modal" data-action="keydown.esc->modal#close">
<h3>Wallet Apps</h3> <h3>Wallet Apps</h3>
<p> <p>
You can connect various wallet apps to your account. This allows You can connect various wallet apps to your Kosmos account. This allows
you to both receive and send sats. Any wallet that supports you to both receive and send sats. Any wallet that supports
<a href="https://bluewallet.io/lndhub/" target="_blank">LNDHub</a> <a href="https://bluewallet.io/lndhub/" class="ks-text-link" target="_blank">LNDHub</a>
accounts should be able to add/import your account using our setup accounts should be able to add/import your account using our setup
code/URL: code/URL:
</p> </p>
@@ -57,7 +57,7 @@
<div class="w-full grid grid-cols-1 gap-y-4 md:grid-cols-12 <div class="w-full grid grid-cols-1 gap-y-4 md:grid-cols-12
md:gap-y-6 md:gap-x-4 md:items-center"> md:gap-y-6 md:gap-x-4 md:items-center">
<h4 class="md:col-span-3"> <h4 class="md:col-span-3">
<a href="https://getalby.com/" class="text-xl" <a href="https://getalby.com/" class="ks-text-link text-xl"
title="Alby" target="_blank"> title="Alby" target="_blank">
<%= image_tag("/img/logos/alby.svg", class: 'h-16') %> <%= image_tag("/img/logos/alby.svg", class: 'h-16') %>
</a> </a>
@@ -70,7 +70,7 @@
URL in the "LNDHub Export URI" field. URL in the "LNDHub Export URI" field.
</p> </p>
<h4 class="md:col-span-3 mt-4 mb:mt-0"> <h4 class="md:col-span-3 mt-4 mb:mt-0">
<a href="https://bluewallet.io" class="text-xl" <a href="https://bluewallet.io" class="ks-text-link text-xl"
title="Blue Wallet" target="_blank"> title="Blue Wallet" target="_blank">
<%= image_tag("/img/logos/bluewallet.svg", class: 'h-16') %> <%= image_tag("/img/logos/bluewallet.svg", class: 'h-16') %>
</a> </a>
@@ -83,7 +83,7 @@
then scan the setup QR code. then scan the setup QR code.
</p> </p>
<h4 class="md:col-span-3 mt-4 mb:mt-0"> <h4 class="md:col-span-3 mt-4 mb:mt-0">
<a href="https://zeusln.app" class="text-xl" <a href="https://zeusln.app" class="ks-text-link text-xl"
title="Zeus" target="_blank"> title="Zeus" target="_blank">
<%= image_tag("/img/logos/zeus.svg", class: 'h-16') %> <%= image_tag("/img/logos/zeus.svg", class: 'h-16') %>
</a> </a>
+8 -8
View File
@@ -3,7 +3,7 @@
<%= render MainSimpleComponent.new do %> <%= render MainSimpleComponent.new do %>
<section> <section>
<p class="mb-6"> <p class="mb-6">
Follow and interact with anyone on the open social web from your Mastodon account. Follow and interact with anyone on the open social web, from your Kosmos Mastodon account.
</p> </p>
</section> </section>
<section data-controller="modal" data-action="keydown.esc->modal#close"> <section data-controller="modal" data-action="keydown.esc->modal#close">
@@ -37,7 +37,7 @@
<p> <p>
Use your Mastodon account with many different apps, and on any devices Use your Mastodon account with many different apps, and on any devices
you wish! When adding your account to an app, you will log in via you wish! When adding your account to an app, you will log in via
<a href="https://kosmos.social" target="_blank">kosmos.social</a>. <a href="https://kosmos.social" target="_blank" class="ks-text-link">kosmos.social</a>.
</p> </p>
</section> </section>
<section> <section>
@@ -61,32 +61,32 @@
</select> </select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b"> <ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Web Web
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-5 font-semibold no-underline">
Android Android
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
iOS iOS
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Linux Linux
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Windows Windows
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
macOS macOS
</a> </a>
</li> </li>
+11 -9
View File
@@ -37,15 +37,17 @@
<h3>Compatible Apps</h3> <h3>Compatible Apps</h3>
<p> <p>
Your Storage account is based on a new open standard called Your Storage account is based on a new open standard called
<img src="/img/logos/icon_remotestorage.svg" class="h-4 w-4 inline"> <a href="https://remotestorage.io" target="_blank">
<strong>remoteStorage</strong>, which is not yet widely supported. Look <img src="/img/logos/icon_remotestorage.svg" class="h-4 w-4 inline">
<strong>remoteStorage</strong>
</a>, which is not yet widely supported. Look
for the remoteStorage icon, or check the Sync settings in apps. for the remoteStorage icon, or check the Sync settings in apps.
</p> </p>
<p> <p>
If you want your favorite apps to support syncing data with your own If you want your favorite apps to support syncing data with your own
Storage account, let the developers know! All relevant information is Storage account, let the developers know! All relevant information is
available on the available on the <a href="https://remotestorage.io"
<a href="https://remotestorage.io" target="_blank">remoteStorage website</a>. target="_blank" class="ks-text-link">remoteStorage website</a>.
</p> </p>
</section> </section>
@@ -65,27 +67,27 @@
</select> </select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b"> <ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b">
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Productivity Productivity
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Bookmarks Bookmarks
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Reading Reading
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
File sharing File sharing
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline">
Learning Learning
</a> </a>
</li> </li>
+36 -48
View File
@@ -1,46 +1,47 @@
<div data-controller="settings--nostr-pubkey" <section>
data-settings--nostr-pubkey-user-address-value="<%= current_user.address %>" <h3>Nostr</h3>
data-settings--nostr-pubkey-site-value="<%= Setting.accounts_domain %>" <h4 class="mb-0">Public Key</h4>
data-settings--nostr-pubkey-shared-secret-value="<%= session[:shared_secret] %>" <div data-controller="settings--nostr-pubkey"
data-settings--nostr-pubkey-pubkey-hex-value="<%= current_user.nostr_pubkey %>"> data-settings--nostr-pubkey-user-address-value="<%= current_user.address %>"
<section class="mb-8 sm:mb-12"> data-settings--nostr-pubkey-site-value="<%= Setting.accounts_domain %>"
<h3>Nostr</h3> data-settings--nostr-pubkey-shared-secret-value="<%= session[:shared_secret] %>"
<h4 class="mb-0"> data-settings--nostr-pubkey-pubkey-hex-value="<%= current_user.nostr_pubkey %>">
Public Key
</h4> <p class="<%= current_user.nostr_pubkey.present? ? '' : 'hidden' %> mt-2 flex gap-1">
<p class="<%= current_user.nostr_pubkey.present? ? '' : 'hidden' %> mt-2 flex gap-x-1">
<input type="text" value="<%= current_user.nostr_pubkey_bech32 %>" disabled <input type="text" value="<%= current_user.nostr_pubkey_bech32 %>" disabled
data-settings--nostr-pubkey-target="pubkeyBech32Input" data-settings--nostr-pubkey-target="pubkeyBech32Input"
name="nostr_public_key" class="w-full" /> name="nostr_public_key" class="relative grow" />
<%= link_to nostr_pubkey_settings_path, <%= link_to nostr_pubkey_settings_path,
class: 'btn-md btn-outline relative grow-0 shrink-0 text-red-700', class: 'btn-md btn-outline text-red-700 relative shrink-0',
data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } do %> data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } do %>
Remove Remove
<% end %> <% end %>
</p> </p>
<% if current_user.nostr_pubkey.present? %> <% if current_user.nostr_pubkey.present? %>
<!-- <div> --> <div class="rounded-md bg-blue-50 p-4">
<!-- Pubkey present --> <div class="flex">
<!-- </div> --> <div class="flex-shrink-0">
<svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3 flex-1">
<p class="text-sm text-blue-800">
Your user address <strong><%= current_user.address %></strong> is
also a Nostr address now. Use your favorite Nostr app, or for
example <a href="http://metadata.nostr.com" target="_blank"
class="underline">metadata.nostr.com</a>, to add this
<strong>NIP-05</strong> address to your public profile.
</p>
</div>
</div>
</div>
<% else %> <% else %>
<p class="my-4"> <p class="my-4">
Verify your Nostr public key with us in order to enable Nostr-specific If you use any apps on the Nostr network, you can verify your public key
features for your account: with us in order to enable Nostr-specific features for your account.
</p> </p>
<ul class="list-disc list-inside">
<li>Log in with Nostr (no password needed)</li>
<li>Verified Nostr address</li>
<% if Setting.lndhub_enabled? %>
<li>Receive zaps in your Lightning account</li>
<% end %>
<% if Setting.nostr_relay_url.present? %>
<li>Publish notes and other stuff on our Nostr relay</li>
<% end %>
<% if Setting.nostr_blossom_server_url.present? %>
<li>Publish media files on our Nostr media server</li>
<% end %>
</ul>
<% end %> <% end %>
<div data-settings--nostr-pubkey-target="noExtension" <div data-settings--nostr-pubkey-target="noExtension"
@@ -57,8 +58,8 @@
</h3> </h3>
<div class="mt-2 mb-0 text-sm text-blue-800"> <div class="mt-2 mb-0 text-sm text-blue-800">
<p> <p>
We recommend Alby, which you can also use a wallet for your We recommend Alby, which you can also use for your Lightning
Lightning account. Wallet.
</p> </p>
</div> </div>
<div class="mt-4"> <div class="mt-4">
@@ -85,18 +86,5 @@
</button> </button>
</p> </p>
<% end %> <% end %>
</section> </div>
</section>
<% if current_user.nostr_pubkey.present? %>
<div data-controller="nostr-metadata"
data-nostr-metadata-url-value="<%= nostr_metadata_settings_path %>">
<div data-nostr-metadata-target="loading"
class="flex flex-col items-center justify-center py-16">
<div class="mb-4 h-12 w-12"></div>
<p data-nostr-metadata-target="status"
class="text-gray-500 animate-pulse">Loading…</p>
</div>
<div data-nostr-metadata-target="content" class="hidden"></div>
</div>
<% end %>
</div>
@@ -1,25 +0,0 @@
<section class="">
<h3>Profile</h3>
<%= render Settings::NostrProfileStatusComponent.new(
profile_event: @profile,
user_address: current_user.address
) %>
<div class="mt-8" data-controller="modal" data-action="keydown.esc->modal#close">
<button data-action="click->modal#open" class="btn-md btn-blue w-full sm:w-auto">
Edit profile
</button>
<%= render ModalComponent.new(show_close_button: false) do %>
<%= render Settings::NostrEditProfileComponent.new(
user: current_user,
profile_event: @profile
) %>
<% end %>
</div>
</section>
<section class="mb-8 sm:mb-12">
<h3>Relays</h3>
<%= render Settings::NostrRelayStatusComponent.new(
nip65_event: @nip65_event
) %>
</section>
+1 -1
View File
@@ -34,7 +34,7 @@
<label class="block"> <label class="block">
<p class="font-bold mb-1">Avatar</p> <p class="font-bold mb-1">Avatar</p>
<p class="text-gray-500">Default profile picture</p> <p class="text-gray-500">Default profile picture</p>
<div class="flex flex-col sm:flex-row items-center gap-6"> <div class="flex items-center gap-6">
<% if @user.avatar.attached? %> <% if @user.avatar.attached? %>
<p class="flex-none"> <p class="flex-none">
<%= image_tag image_url_for(@user.avatar), class: "h-24 w-24 rounded-lg" %> <%= image_tag image_url_for(@user.avatar), class: "h-24 w-24 rounded-lg" %>
@@ -24,7 +24,7 @@
name: "E-Mail", name: "E-Mail",
path: admin_settings_service_path("email"), path: admin_settings_service_path("email"),
text_icon: Setting.email_enabled? ? "◉" : "○", text_icon: Setting.email_enabled? ? "◉" : "○",
active: current_page?(admin_settings_service_path("email")), active: current_page?(admin_settings_services_path(params: { s: "email" })),
) %> ) %>
<%= render SidenavLinkComponent.new( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
+6 -6
View File
@@ -19,6 +19,12 @@
active: @settings_section.to_s == "email" active: @settings_section.to_s == "email"
) %> ) %>
<% end %> <% end %>
<% if Setting.lndhub_enabled %>
<%= render SidenavLinkComponent.new(
name: "Lightning", path: setting_path(:lightning), icon: "zap",
active: @settings_section.to_s == "lightning"
) %>
<% end %>
<% if Setting.remotestorage_enabled? && <% if Setting.remotestorage_enabled? &&
Flipper.enabled?(:remotestorage, current_user) %> Flipper.enabled?(:remotestorage, current_user) %>
<%= render SidenavLinkComponent.new( <%= render SidenavLinkComponent.new(
@@ -26,12 +32,6 @@
active: @settings_section.to_s == "remotestorage" active: @settings_section.to_s == "remotestorage"
) %> ) %>
<% end %> <% end %>
<% if Setting.lndhub_enabled %>
<%= render SidenavLinkComponent.new(
name: "Lightning", path: setting_path(:lightning), icon: "zap",
active: @settings_section.to_s == "lightning"
) %>
<% end %>
<% if Setting.nostr_enabled %> <% if Setting.nostr_enabled %>
<%= render SidenavLinkComponent.new( <%= render SidenavLinkComponent.new(
name: "Nostr", path: setting_path(:nostr), icon: "nostrich-head", name: "Nostr", path: setting_path(:nostr), icon: "nostrich-head",
@@ -1,16 +1,12 @@
<div class="border-b border-gray-200"> <div class="border-b border-gray-200">
<nav class="-mb-px flex" aria-label="Tabs"> <nav class="-mb-px flex" aria-label="Tabs">
<%= render TabnavLinkComponent.new( <%= render TabnavLinkComponent.new(
name: "Donations", name: "Donations", path: contributions_donations_path,
path: contributions_donations_path,
active: current_page?(contributions_donations_path) active: current_page?(contributions_donations_path)
) %> ) %>
<%= render TabnavLinkComponent.new( <%= render TabnavLinkComponent.new(
name: render(EditableContentComponent.new( name: "Projects", path: contributions_projects_path,
context: "contributions/other", key: "title", default: "Other" active: current_page?(contributions_projects_path)
)),
path: contributions_other_path,
active: current_page?(contributions_other_path)
) %> ) %>
</nav> </nav>
</div> </div>
@@ -1,3 +0,0 @@
<div>
<%= profile.inspect %>
</div>
+1 -1
View File
@@ -2,7 +2,7 @@
<%= render MainCompactComponent.new do %> <%= render MainCompactComponent.new do %>
<p> <p>
Hey there! You were invited to sign up for an account by Hey there! You were invited to sign up for a Kosmos account by
<strong><%= @invited_by_name %></strong>. <strong><%= @invited_by_name %></strong>.
</p> </p>
<p> <p>
-1
View File
@@ -14,7 +14,6 @@ FileUtils.chdir APP_ROOT do
puts "== Installing dependencies ==" puts "== Installing dependencies =="
system("bundle check") || system!("bundle install") system("bundle check") || system!("bundle install")
system!("bun install")
# puts "\n== Copying sample files ==" # puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml") # unless File.exist?("config/database.yml")
Executable
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env ruby
APP_ROOT = File.expand_path('..', __dir__)
Dir.chdir(APP_ROOT) do
begin
exec "yarnpkg", *ARGV
rescue Errno::ENOENT
$stderr.puts "Yarn executable was not detected in the system."
$stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
exit 1
end
end
-288
View File
@@ -1,288 +0,0 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "akkounts",
"dependencies": {
"@tailwindcss/forms": "^0.5.3",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.19",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-import": "^15.0.1",
"postcss-nested": "^6.0.0",
"postcss-preset-env": "^7.8.3",
"tailwindcss": "^3.4.0",
},
},
},
"packages": {
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@csstools/postcss-cascade-layers": ["@csstools/postcss-cascade-layers@1.1.1", "", { "dependencies": { "@csstools/selector-specificity": "^2.0.2", "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA=="],
"@csstools/postcss-color-function": ["@csstools/postcss-color-function@1.1.1", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^1.1.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw=="],
"@csstools/postcss-font-format-keywords": ["@csstools/postcss-font-format-keywords@1.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg=="],
"@csstools/postcss-hwb-function": ["@csstools/postcss-hwb-function@1.0.2", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w=="],
"@csstools/postcss-ic-unit": ["@csstools/postcss-ic-unit@1.0.1", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^1.1.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw=="],
"@csstools/postcss-is-pseudo-class": ["@csstools/postcss-is-pseudo-class@2.0.7", "", { "dependencies": { "@csstools/selector-specificity": "^2.0.0", "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA=="],
"@csstools/postcss-nested-calc": ["@csstools/postcss-nested-calc@1.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ=="],
"@csstools/postcss-normalize-display-values": ["@csstools/postcss-normalize-display-values@1.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw=="],
"@csstools/postcss-oklab-function": ["@csstools/postcss-oklab-function@1.1.1", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^1.1.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA=="],
"@csstools/postcss-progressive-custom-properties": ["@csstools/postcss-progressive-custom-properties@1.3.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.3" } }, "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA=="],
"@csstools/postcss-stepped-value-functions": ["@csstools/postcss-stepped-value-functions@1.0.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ=="],
"@csstools/postcss-text-decoration-shorthand": ["@csstools/postcss-text-decoration-shorthand@1.0.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw=="],
"@csstools/postcss-trigonometric-functions": ["@csstools/postcss-trigonometric-functions@1.0.2", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og=="],
"@csstools/postcss-unset-value": ["@csstools/postcss-unset-value@1.0.2", "", { "peerDependencies": { "postcss": "^8.2" } }, "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g=="],
"@csstools/selector-specificity": ["@csstools/selector-specificity@2.2.0", "", { "peerDependencies": { "postcss-selector-parser": "^6.0.10" } }, "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@tailwindcss/forms": ["@tailwindcss/forms@0.5.11", "", { "dependencies": { "mini-svg-data-uri": "^1.2.3" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" } }, "sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA=="],
"any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
"autoprefixer": ["autoprefixer@10.5.4", "", { "dependencies": { "browserslist": "^4.28.6", "caniuse-lite": "^1.0.30001806", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.11.13", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="],
"camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
"caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="],
"chokidar": ["chokidar@3.6.0", "", { "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" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
"css-blank-pseudo": ["css-blank-pseudo@3.0.3", "", { "dependencies": { "postcss-selector-parser": "^6.0.9" }, "peerDependencies": { "postcss": "^8.4" }, "bin": { "css-blank-pseudo": "dist/cli.cjs" } }, "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ=="],
"css-has-pseudo": ["css-has-pseudo@3.0.4", "", { "dependencies": { "postcss-selector-parser": "^6.0.9" }, "peerDependencies": { "postcss": "^8.4" }, "bin": { "css-has-pseudo": "dist/cli.cjs" } }, "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw=="],
"css-prefers-color-scheme": ["css-prefers-color-scheme@6.0.3", "", { "peerDependencies": { "postcss": "^8.4" }, "bin": { "css-prefers-color-scheme": "dist/cli.cjs" } }, "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA=="],
"cssdb": ["cssdb@7.11.2", "", {}, "sha512-lhQ32TFkc1X4eTefGfYPvgovRSzIMofHkigfH8nWtyRL4XJLsRhJFreRvEgKzept7x1rjBuy3J/MurXLaFxW/A=="],
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
"electron-to-chromium": ["electron-to-chromium@1.5.403", "", {}, "sha512-MQsYmdaLzvaCX5j+ZZBr5Fm6uCCnPQcRtlvmvRlWqrXy+BH2O4ffXIAScF+JQznQWB9brWp4lSD9Z4yNmaf2BA=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
"is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mini-svg-data-uri": ["mini-svg-data-uri@1.4.4", "", { "bin": { "mini-svg-data-uri": "cli.js" } }, "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="],
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
"nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
"node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
"postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="],
"postcss-attribute-case-insensitive": ["postcss-attribute-case-insensitive@5.0.2", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ=="],
"postcss-clamp": ["postcss-clamp@4.1.0", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.6" } }, "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow=="],
"postcss-color-functional-notation": ["postcss-color-functional-notation@4.2.4", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg=="],
"postcss-color-hex-alpha": ["postcss-color-hex-alpha@8.0.4", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ=="],
"postcss-color-rebeccapurple": ["postcss-color-rebeccapurple@7.1.1", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg=="],
"postcss-custom-media": ["postcss-custom-media@8.0.2", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.3" } }, "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg=="],
"postcss-custom-properties": ["postcss-custom-properties@12.1.11", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ=="],
"postcss-custom-selectors": ["postcss-custom-selectors@6.0.3", "", { "dependencies": { "postcss-selector-parser": "^6.0.4" }, "peerDependencies": { "postcss": "^8.3" } }, "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg=="],
"postcss-dir-pseudo-class": ["postcss-dir-pseudo-class@6.0.5", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA=="],
"postcss-double-position-gradients": ["postcss-double-position-gradients@3.1.2", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^1.1.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ=="],
"postcss-env-function": ["postcss-env-function@4.0.6", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA=="],
"postcss-flexbugs-fixes": ["postcss-flexbugs-fixes@5.0.2", "", { "peerDependencies": { "postcss": "^8.1.4" } }, "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ=="],
"postcss-focus-visible": ["postcss-focus-visible@6.0.4", "", { "dependencies": { "postcss-selector-parser": "^6.0.9" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw=="],
"postcss-focus-within": ["postcss-focus-within@5.0.4", "", { "dependencies": { "postcss-selector-parser": "^6.0.9" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ=="],
"postcss-font-variant": ["postcss-font-variant@5.0.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA=="],
"postcss-gap-properties": ["postcss-gap-properties@3.0.5", "", { "peerDependencies": { "postcss": "^8.2" } }, "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg=="],
"postcss-image-set-function": ["postcss-image-set-function@4.0.7", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw=="],
"postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="],
"postcss-initial": ["postcss-initial@4.0.1", "", { "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ=="],
"postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="],
"postcss-lab-function": ["postcss-lab-function@4.2.1", "", { "dependencies": { "@csstools/postcss-progressive-custom-properties": "^1.1.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w=="],
"postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="],
"postcss-logical": ["postcss-logical@5.0.4", "", { "peerDependencies": { "postcss": "^8.4" } }, "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g=="],
"postcss-media-minmax": ["postcss-media-minmax@5.0.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ=="],
"postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="],
"postcss-nesting": ["postcss-nesting@10.2.0", "", { "dependencies": { "@csstools/selector-specificity": "^2.0.0", "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA=="],
"postcss-opacity-percentage": ["postcss-opacity-percentage@1.1.3", "", { "peerDependencies": { "postcss": "^8.2" } }, "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A=="],
"postcss-overflow-shorthand": ["postcss-overflow-shorthand@3.0.4", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A=="],
"postcss-page-break": ["postcss-page-break@3.0.4", "", { "peerDependencies": { "postcss": "^8" } }, "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ=="],
"postcss-place": ["postcss-place@7.0.5", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g=="],
"postcss-preset-env": ["postcss-preset-env@7.8.3", "", { "dependencies": { "@csstools/postcss-cascade-layers": "^1.1.1", "@csstools/postcss-color-function": "^1.1.1", "@csstools/postcss-font-format-keywords": "^1.0.1", "@csstools/postcss-hwb-function": "^1.0.2", "@csstools/postcss-ic-unit": "^1.0.1", "@csstools/postcss-is-pseudo-class": "^2.0.7", "@csstools/postcss-nested-calc": "^1.0.0", "@csstools/postcss-normalize-display-values": "^1.0.1", "@csstools/postcss-oklab-function": "^1.1.1", "@csstools/postcss-progressive-custom-properties": "^1.3.0", "@csstools/postcss-stepped-value-functions": "^1.0.1", "@csstools/postcss-text-decoration-shorthand": "^1.0.0", "@csstools/postcss-trigonometric-functions": "^1.0.2", "@csstools/postcss-unset-value": "^1.0.2", "autoprefixer": "^10.4.13", "browserslist": "^4.21.4", "css-blank-pseudo": "^3.0.3", "css-has-pseudo": "^3.0.4", "css-prefers-color-scheme": "^6.0.3", "cssdb": "^7.1.0", "postcss-attribute-case-insensitive": "^5.0.2", "postcss-clamp": "^4.1.0", "postcss-color-functional-notation": "^4.2.4", "postcss-color-hex-alpha": "^8.0.4", "postcss-color-rebeccapurple": "^7.1.1", "postcss-custom-media": "^8.0.2", "postcss-custom-properties": "^12.1.10", "postcss-custom-selectors": "^6.0.3", "postcss-dir-pseudo-class": "^6.0.5", "postcss-double-position-gradients": "^3.1.2", "postcss-env-function": "^4.0.6", "postcss-focus-visible": "^6.0.4", "postcss-focus-within": "^5.0.4", "postcss-font-variant": "^5.0.0", "postcss-gap-properties": "^3.0.5", "postcss-image-set-function": "^4.0.7", "postcss-initial": "^4.0.1", "postcss-lab-function": "^4.2.1", "postcss-logical": "^5.0.4", "postcss-media-minmax": "^5.0.0", "postcss-nesting": "^10.2.0", "postcss-opacity-percentage": "^1.1.2", "postcss-overflow-shorthand": "^3.0.4", "postcss-page-break": "^3.0.4", "postcss-place": "^7.0.5", "postcss-pseudo-class-any-link": "^7.1.6", "postcss-replace-overflow-wrap": "^4.0.0", "postcss-selector-not": "^6.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag=="],
"postcss-pseudo-class-any-link": ["postcss-pseudo-class-any-link@7.1.6", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w=="],
"postcss-replace-overflow-wrap": ["postcss-replace-overflow-wrap@4.0.0", "", { "peerDependencies": { "postcss": "^8.0.3" } }, "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw=="],
"postcss-selector-not": ["postcss-selector-not@6.0.1", "", { "dependencies": { "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ=="],
"postcss-selector-parser": ["postcss-selector-parser@6.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ=="],
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="],
"thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
"update-browserslist-db": ["update-browserslist-db@1.3.0", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"tinyglobby/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
}
}
+3 -9
View File
@@ -1,16 +1,10 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM ruby:3.3.12 FROM ruby:3.3.0
SHELL ["/bin/bash", "-o", "pipefail", "-c"] SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl unzip \ RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
ldap-utils tini libvips ldap-utils tini libvips
# Node.js — required by GitHub/Gitea Actions runtime for JS-based actions
# (actions/checkout, actions/cache, etc.)
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get update && apt-get install -y nodejs RUN apt-get update && apt-get install -y nodejs
RUN npm install -g yarn
# Bun — used by the app for CSS builds
RUN curl -fsSL https://bun.sh/install | bash -s -- bun-v1.3.14
ENV PATH="/root/.bun/bin:$PATH"

Some files were not shown because too many files have changed in this diff Show More