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_PUBLIC_KEY='123456abcdef...'
# NOSTR_RELAY_URL='wss://nostr.kosmos.org'
# NOSTR_BLOSSOM_SERVER_URL='https://blossom.kosmos.org'
# RS_STORAGE_URL='https://storage.kosmos.org'
# 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:
- name: Release Drafter
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-test
/node_modules
/yarn-error.log
yarn-debug.log*
.yarn-integrity
bun.lock
# Ignore local dotenv config file
.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
FROM ruby:3.3.12
FROM ruby:3.3.4
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
RUN curl -fsSL https://bun.sh/install | bash -s -- bun-v1.3.14
ENV PATH="/root/.bun/bin:$PATH"
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get update && apt-get install -y nodejs
WORKDIR /akkounts
COPY ["Gemfile", "Gemfile.lock", "package.json", "bun.lock", "./"]
COPY ["Gemfile", "Gemfile.lock", "package.json", "./"]
RUN bundle install
RUN gem install foreman
RUN bun install
RUN npm install -g yarn
RUN yarn install
ENTRYPOINT ["/usr/bin/tini", "--"]
EXPOSE 3000
+5 -6
View File
@@ -2,9 +2,9 @@ source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 8.1'
gem 'rails', '~> 8.0'
# Use Puma as the app server
gem 'puma', '~> 8.0'
gem 'puma', '~> 6.6'
# View components
gem "view_component"
# Asset bundler
@@ -18,7 +18,7 @@ gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
# 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
gem 'bcrypt', '~> 3.1'
@@ -27,7 +27,7 @@ gem 'dotenv-rails'
# Authentication
gem 'warden'
gem 'devise', '~> 5.0'
gem 'devise', '~> 4.9.0'
gem 'devise_ldap_authenticatable'
gem 'net-ldap'
@@ -42,7 +42,6 @@ gem 'flipper-active_record'
gem 'flipper-ui'
gem 'gpgme', '~> 2.0.24'
gem 'zbase32', '~> 0.1.1'
gem 'kramdown'
# HTTP requests
gem 'faraday'
@@ -73,7 +72,7 @@ end
group :development do
# 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 'letter_opener'
gem 'letter_opener_web'
+140 -150
View File
@@ -3,31 +3,29 @@ GEM
specs:
aasm (5.5.0)
concurrent-ruby (~> 1.0)
action_text-trix (2.1.19)
railties
actioncable (8.1.3.1)
actionpack (= 8.1.3.1)
activesupport (= 8.1.3.1)
actioncable (8.0.2)
actionpack (= 8.0.2)
activesupport (= 8.0.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (8.1.3.1)
actionpack (= 8.1.3.1)
activejob (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
actionmailbox (8.0.2)
actionpack (= 8.0.2)
activejob (= 8.0.2)
activerecord (= 8.0.2)
activestorage (= 8.0.2)
activesupport (= 8.0.2)
mail (>= 2.8.0)
actionmailer (8.1.3.1)
actionpack (= 8.1.3.1)
actionview (= 8.1.3.1)
activejob (= 8.1.3.1)
activesupport (= 8.1.3.1)
actionmailer (8.0.2)
actionpack (= 8.0.2)
actionview (= 8.0.2)
activejob (= 8.0.2)
activesupport (= 8.0.2)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (8.1.3.1)
actionview (= 8.1.3.1)
activesupport (= 8.1.3.1)
actionpack (8.0.2)
actionview (= 8.0.2)
activesupport (= 8.0.2)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
@@ -35,43 +33,42 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (8.1.3.1)
action_text-trix (~> 2.1.15)
actionpack (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
actiontext (8.0.2)
actionpack (= 8.0.2)
activerecord (= 8.0.2)
activestorage (= 8.0.2)
activesupport (= 8.0.2)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (8.1.3.1)
activesupport (= 8.1.3.1)
actionview (8.0.2)
activesupport (= 8.0.2)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (8.1.3.1)
activesupport (= 8.1.3.1)
activejob (8.0.2)
activesupport (= 8.0.2)
globalid (>= 0.3.6)
activemodel (8.1.3.1)
activesupport (= 8.1.3.1)
activerecord (8.1.3.1)
activemodel (= 8.1.3.1)
activesupport (= 8.1.3.1)
activemodel (8.0.2)
activesupport (= 8.0.2)
activerecord (8.0.2)
activemodel (= 8.0.2)
activesupport (= 8.0.2)
timeout (>= 0.4.0)
activestorage (8.1.3.1)
actionpack (= 8.1.3.1)
activejob (= 8.1.3.1)
activerecord (= 8.1.3.1)
activesupport (= 8.1.3.1)
activestorage (8.0.2)
actionpack (= 8.0.2)
activejob (= 8.0.2)
activerecord (= 8.0.2)
activesupport (= 8.0.2)
marcel (~> 1.0)
activesupport (8.1.3.1)
activesupport (8.0.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
json
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
@@ -99,12 +96,12 @@ GEM
aws-sigv4 (1.11.0)
aws-eventstream (~> 1, >= 1.0.2)
backport (1.2.0)
base64 (0.3.0)
bcrypt (3.1.22)
base64 (0.2.0)
bcrypt (3.1.20)
bech32 (1.5.0)
thor (>= 1.1.0)
benchmark (0.5.0)
bigdecimal (4.1.2)
benchmark (0.4.0)
bigdecimal (3.1.9)
bindex (0.8.1)
bip-schnorr (0.7.0)
ecdsa_ext (~> 0.5.0)
@@ -121,12 +118,12 @@ GEM
childprocess (5.1.0)
logger (~> 1.5)
chunky_png (1.4.0)
concurrent-ruby (1.3.8)
connection_pool (3.0.2)
concurrent-ruby (1.3.4)
connection_pool (2.5.2)
crack (1.0.0)
bigdecimal
rexml
crass (1.0.7)
crass (1.0.6)
cssbundling-rails (1.4.3)
railties (>= 6.0.0)
database_cleaner (2.1.0)
@@ -135,17 +132,17 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.5.1)
devise (5.0.4)
date (3.4.1)
devise (4.9.4)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 7.0)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise_ldap_authenticatable (0.8.7)
devise (>= 3.4.1)
net-ldap (>= 0.16.0)
diff-lcs (1.6.2)
diff-lcs (1.6.1)
discourse_api (2.0.1)
faraday (~> 2.7)
faraday-follow_redirects
@@ -157,13 +154,12 @@ GEM
railties (>= 6.1)
down (5.4.2)
addressable (~> 2.8)
drb (2.2.3)
drb (2.2.1)
ecdsa (1.2.0)
ecdsa_ext (0.5.1)
ecdsa (~> 1.2.0)
erb (6.0.7)
erubi (1.13.1)
et-orbi (1.4.1)
et-orbi (1.2.11)
tzinfo
event_emitter (0.2.6)
eventmachine (1.2.7)
@@ -200,35 +196,34 @@ GEM
rack-protection (>= 1.5.3, < 5.0.0)
rack-session (>= 1.0.2, < 3.0.0)
sanitize (< 8)
fugit (1.13.0)
et-orbi (~> 1.4)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
globalid (1.4.0)
globalid (1.2.1)
activesupport (>= 6.1)
gpgme (2.0.24)
mini_portile2 (~> 2.7)
hashdiff (1.1.2)
i18n (1.15.2)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
importmap-rails (2.2.3)
importmap-rails (2.1.0)
actionpack (>= 6.0.0)
activesupport (>= 6.0.0)
railties (>= 6.0.0)
io-console (0.9.2)
irb (1.18.0)
io-console (0.8.0)
irb (1.15.2)
pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jaro_winkler (1.6.0)
jbuilder (2.15.1)
actionview (>= 7.0.0)
activesupport (>= 7.0.0)
jbuilder (2.13.0)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
jmespath (1.6.2)
json (2.21.2)
json (2.11.3)
kramdown (2.5.1)
rexml (>= 3.3.9)
kramdown-parser-gfm (1.1.0)
@@ -252,11 +247,10 @@ GEM
lnurl (1.1.1)
bech32 (~> 1.1)
logger (1.7.0)
loofah (2.25.2)
loofah (2.24.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.9.1)
logger
mail (2.8.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
@@ -265,15 +259,14 @@ GEM
faraday (~> 2.9.0)
faraday-follow_redirects (= 0.3.0)
nokogiri (~> 1.16.0)
marcel (1.2.1)
marcel (1.0.4)
matrix (0.4.2)
method_source (1.1.0)
mini_magick (4.13.2)
mini_mime (1.1.5)
mini_portile2 (2.8.9)
minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
mission_control-jobs (1.1.0)
mini_portile2 (2.8.8)
minitest (5.25.5)
mission_control-jobs (1.0.2)
actioncable (>= 7.1)
actionpack (>= 7.1)
activejob (>= 7.1)
@@ -286,7 +279,7 @@ GEM
multipart-post (2.4.1)
net-http (0.6.0)
uri
net-imap (0.6.6)
net-imap (0.5.7)
date
net-protocol
net-ldap (0.19.0)
@@ -296,7 +289,7 @@ GEM
timeout
net-smtp (0.5.1)
net-protocol
nio4r (2.7.5)
nio4r (2.7.4)
nokogiri (1.16.8)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
@@ -320,23 +313,24 @@ GEM
ast (~> 2.4.1)
racc
pg (1.5.9)
pp (0.6.4)
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
prism (1.9.0)
propshaft (1.3.2)
prism (1.4.0)
propshaft (1.1.0)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
rack
psych (5.4.0)
railties (>= 7.0.0)
psych (5.2.3)
date
stringio
public_suffix (6.0.1)
puma (8.0.2)
puma (6.6.0)
nio4r (~> 2.0)
raabro (1.5.0)
raabro (1.4.0)
racc (1.8.1)
rack (2.2.23)
rack (2.2.13)
rack-protection (3.2.0)
base64 (>= 0.1.0)
rack (~> 2.2, >= 2.2.4)
@@ -347,64 +341,61 @@ GEM
rackup (1.0.1)
rack (< 3)
webrick
rails (8.1.3.1)
actioncable (= 8.1.3.1)
actionmailbox (= 8.1.3.1)
actionmailer (= 8.1.3.1)
actionpack (= 8.1.3.1)
actiontext (= 8.1.3.1)
actionview (= 8.1.3.1)
activejob (= 8.1.3.1)
activemodel (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
rails (8.0.2)
actioncable (= 8.0.2)
actionmailbox (= 8.0.2)
actionmailer (= 8.0.2)
actionpack (= 8.0.2)
actiontext (= 8.0.2)
actionview (= 8.0.2)
activejob (= 8.0.2)
activemodel (= 8.0.2)
activerecord (= 8.0.2)
activestorage (= 8.0.2)
activesupport (= 8.0.2)
bundler (>= 1.15.0)
railties (= 8.1.3.1)
railties (= 8.0.2)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 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)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.7.1)
loofah (~> 2.25, >= 2.25.2)
rails-html-sanitizer (1.6.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)
rails-settings-cached (2.8.3)
activerecord (>= 5.0.0)
railties (>= 5.0.0)
railties (8.1.3.1)
actionpack (= 8.1.3.1)
activesupport (= 8.1.3.1)
railties (8.0.2)
actionpack (= 8.0.2)
activesupport (= 8.0.2)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
tsort (>= 0.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.4.2)
rake (13.2.1)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rbs (3.9.2)
logger
rdoc (7.2.0)
erb
rdoc (6.13.1)
psych (>= 4.0.0)
tsort
redis (5.4.0)
redis-client (>= 0.22.0)
redis-client (0.24.0)
connection_pool
regexp_parser (2.10.0)
reline (0.7.0)
reline (0.6.1)
io-console (~> 0.5)
responders (3.2.0)
actionpack (>= 7.0)
railties (>= 7.0)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
reverse_markdown (3.0.0)
nokogiri
rexml (3.4.1)
@@ -412,23 +403,23 @@ GEM
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.2.0)
rspec-core (3.13.6)
rspec-core (3.13.3)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
rspec-expectations (3.13.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.8)
rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (8.0.4)
actionpack (>= 7.2)
activesupport (>= 7.2)
railties (>= 7.2)
rspec-core (>= 3.13.0, < 5.0.0)
rspec-expectations (>= 3.13.0, < 5.0.0)
rspec-mocks (>= 3.13.0, < 5.0.0)
rspec-support (>= 3.13.0, < 5.0.0)
rspec-support (3.13.7)
rspec-rails (7.1.1)
actionpack (>= 7.0)
activesupport (>= 7.0)
railties (>= 7.0)
rspec-core (~> 3.13)
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.2)
rubocop (1.75.3)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
@@ -476,25 +467,24 @@ GEM
tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24)
yard-solargraph (~> 0.1)
solid_queue (1.6.0)
solid_queue (1.1.5)
activejob (>= 7.1)
activerecord (>= 7.1)
concurrent-ruby (>= 1.3.1)
fugit (~> 1.11)
fugit (~> 1.11.0)
railties (>= 7.1)
thor (>= 1.3.1)
thor (~> 1.3.1)
sqlite3 (2.6.0)
mini_portile2 (~> 2.8.0)
sqlite3 (2.6.0-arm64-darwin)
sqlite3 (2.6.0-x86_64-linux-gnu)
stimulus-rails (1.3.4)
railties (>= 6.0.0)
stringio (3.2.0)
thor (1.5.0)
stringio (3.1.7)
thor (1.3.2)
tilt (2.6.0)
timeout (0.6.1)
tsort (0.2.0)
turbo-rails (2.0.23)
timeout (0.4.3)
turbo-rails (2.0.13)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
tzinfo (2.0.6)
@@ -502,24 +492,25 @@ GEM
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
uri (1.1.1)
uri (1.0.3)
useragent (0.16.11)
view_component (4.12.0)
actionview (>= 7.1.0)
activesupport (>= 7.1.0)
concurrent-ruby (~> 1)
view_component (3.22.0)
activesupport (>= 5.2.0, < 8.1)
concurrent-ruby (= 1.3.4)
method_source (~> 1.0)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.3.0)
actionview (>= 8.0.0)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 8.0.0)
railties (>= 6.0.0)
webmock (3.25.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.9.2)
websocket-driver (0.8.2)
webrick (1.9.1)
websocket-driver (0.7.7)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@@ -529,7 +520,7 @@ GEM
yard-solargraph (0.1.0)
yard (~> 0.9)
zbase32 (0.1.1)
zeitwerk (2.8.3)
zeitwerk (2.7.2)
PLATFORMS
arm64-darwin-22
@@ -543,7 +534,7 @@ DEPENDENCIES
capybara
cssbundling-rails
database_cleaner
devise (~> 5.0)
devise (~> 4.9.0)
devise_ldap_authenticatable
discourse_api
dotenv-rails
@@ -557,8 +548,7 @@ DEPENDENCIES
gpgme (~> 2.0.24)
image_processing (~> 1.12.2)
importmap-rails
jbuilder (~> 2.15)
kramdown
jbuilder (~> 2.7)
letter_opener
letter_opener_web
listen (~> 3.2)
@@ -570,8 +560,8 @@ DEPENDENCIES
pagy (~> 6.0, >= 6.0.2)
pg (~> 1.5)
propshaft
puma (~> 8.0)
rails (~> 8.1)
puma (~> 6.6)
rails (~> 8.0)
rails-controller-testing
rails-settings-cached (~> 2.8.3)
redis (~> 5.4)
@@ -587,7 +577,7 @@ DEPENDENCIES
tzinfo-data
view_component
warden
web-console (~> 4.3)
web-console (~> 4.2)
webmock
zbase32 (~> 0.1.1)
+1 -1
View File
@@ -1,2 +1,2 @@
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
@@ -36,7 +36,7 @@ run web`._
Installing dependencies:
bundle install
bun install
yarn install
Migrating the local database (after schema changes):
@@ -7,7 +7,7 @@
@import "components/buttons";
@import "components/dashboard_services";
@import "components/forms";
@import "components/links";
@import "components/notifications";
@import "components/pagination";
@import "components/rich_text";
@import "components/tables";
@@ -6,7 +6,6 @@
body {
@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');
color: black;
}
body#admin {
@@ -33,10 +32,6 @@
@apply pt-8 sm:pt-12;
}
main section h3:not(:first-child) {
@apply mt-8;
}
main section:first-of-type {
@apply pt-0;
}
@@ -60,11 +55,4 @@
main ul li {
@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 {
.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 {
@apply btn-text-dark;
@apply inline-block font-semibold rounded-md leading-none cursor-pointer text-center
transition-colors duration-75 focus:outline-none focus:ring-4;
}
@@ -38,20 +28,17 @@
}
.btn-blue {
@apply btn-text-light;
@apply bg-blue-500 hover:bg-blue-600
@apply bg-blue-500 hover:bg-blue-600 text-white
focus:ring-blue-400 focus:ring-opacity-75;
}
.btn-emerald {
@apply btn-text-light;
@apply bg-emerald-500 hover:bg-emerald-600
@apply bg-emerald-500 hover:bg-emerald-600 text-white
focus:ring-emerald-400 focus:ring-opacity-75;
}
.btn-red {
@apply btn-text-light;
@apply bg-red-600 hover:bg-red-700
@apply bg-red-600 hover:bg-red-700 text-white
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 {
@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
no-underline focus:z-20;
focus:z-20;
}
.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">
<%= content %>
</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">
<%= content %>
</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="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">
@@ -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="px-6 sm:px-12 pt-2 sm:pt-4">
<%= 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
@sustainers = ldap.search_users(:memberStatus, :sustainer, :cn) if @show_sustainers
@admins = ldap.search_users(:admin, true, :cn)
@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)
@pagy, @users = pagy(User.where(ou: ou).order(cn: :asc))
@stats = {
users_confirmed: User.where(ou: ou).confirmed.count,
+1 -1
View File
@@ -39,7 +39,7 @@ class ApplicationController < ActionController::Base
end
def after_sign_in_path_for(user)
session.delete(:user_return_to) || root_path
session[:user_return_to] || root_path
end
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
def after_resetting_password_path_for(resource)
session.delete(:user_return_to)
if Devise.sign_in_after_reset_password
root_path
else
new_session_path(resource_name)
end
Devise.sign_in_after_reset_password ? after_sign_in_path_for(resource) : new_session_path(resource_name)
end
# 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
prepend_before_action :assert_redirect_uri, only: [:new, :create]
before_action :require_signed_in_with_username, only: :new
before_action :authenticate_user!, only: :create
@@ -17,6 +16,8 @@ class Rs::OauthController < ApplicationController
["In 1 month", 1.month.from_now],
["In 1 day", 1.day.from_now]]
http_status :bad_request and return unless @redirect_uri.present?
unless current_user == @user
sign_out :user
@@ -63,6 +64,8 @@ class Rs::OauthController < ApplicationController
state = params[:state].presence
expire_at = params[:expire_at].presence
http_status :bad_request and return unless redirect_uri.present?
if permissions.empty?
redirect_to(url_with_state("#{redirect_uri}#error=invalid_scope", state),
allow_other_host: true) and return
@@ -94,10 +97,6 @@ class Rs::OauthController < ApplicationController
private
def assert_redirect_uri
http_status :bad_request unless params[:redirect_uri].present?
end
def require_signed_in_with_username
unless user_signed_in?
session[:user_return_to] = request.url
+2 -58
View File
@@ -4,13 +4,8 @@ require "bcrypt"
class SettingsController < ApplicationController
before_action :authenticate_user!
before_action :set_main_nav_section
before_action :set_settings_section, only: [
:show, :update, :update_email, :reset_email_password
]
before_action :set_user, only: [
:show, :update, :update_email, :reset_email_password,
:fetch_nostr_metadata
]
before_action :set_settings_section, only: [:show, :update, :update_email, :reset_email_password]
before_action :set_user, only: [:show, :update, :update_email, :reset_email_password]
def index
redirect_to setting_path(:profile)
@@ -148,63 +143,12 @@ class SettingsController < ApplicationController
}
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
def set_main_nav_section
@current_section = :settings
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
@settings_section = params[:section]
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?
raise error
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
redirect_to navigation_location
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"
end
def markdown_to_html(string)
raw Kramdown::Document.new(string, {
input: "GFM"
}).to_html
end
def image_url_for(attachment)
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,
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,
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
-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"
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
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
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
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
+5 -5
View File
@@ -131,11 +131,11 @@ class User < ApplicationRecord
end
def is_admin?
@admin ||= if admin = Devise::LDAP::Adapter.get_ldap_param(self.cn, :admin)
!!admin.first
else
false
end
admin ||= if admin = Devise::LDAP::Adapter.get_ldap_param(self.cn, :admin)
!!admin.first
else
false
end
end
def address
+3 -3
View File
@@ -11,7 +11,7 @@ module EjabberdManager
def call
unless @overwrite
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?
end
@@ -25,8 +25,9 @@ module EjabberdManager
raise res.inspect if res.status != 200
end
end
end
private
private
def process_avatar
@user.avatar.blob.open do |file|
@@ -76,5 +77,4 @@ module EjabberdManager
""".strip,
]
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
client.on :connect do
Rails.logger.debug "#{log_prefix} Publishing #{event.id}..."
puts "#{log_prefix} Publishing #{event.id}..."
client.publish event
end
client.on :error do |e|
Rails.logger.debug "#{log_prefix} Error: #{e}"
Rails.logger.debug "#{log_prefix} Closing thread..."
puts "#{log_prefix} Error: #{e}"
puts "#{log_prefix} Closing thread..."
thread.exit
end
client.on :message do |m|
Rails.logger.debug "#{log_prefix} Message: #{m}"
puts "#{log_prefix} Message: #{m}"
msg = JSON.parse(m) rescue []
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
Rails.logger.debug "#{log_prefix} Unexpected message from relay. Closing thread..."
puts "#{log_prefix} Unexpected message from relay. Closing thread..."
end
thread.exit
end
Rails.logger.debug "#{log_prefix} Connecting to #{relay.url}..."
puts "#{log_prefix} Connecting to #{relay.url}..."
client.connect relay
end
-5
View File
@@ -3,7 +3,6 @@ require "nostr"
class NostrManagerService < ApplicationService
def parse_tags(tags)
out = {}
# TODO support more than 1 item for each tag type
tags.each do |tag|
out[tag[0].to_sym] = tag[1, tag.length]
end
@@ -20,8 +19,4 @@ class NostrManagerService < ApplicationService
def site_user
Nostr::User.new(keypair: site_keypair)
end
def new_relay(url)
Nostr::Relay.new(url: url, name: URI.parse(url).host)
end
end
@@ -38,7 +38,8 @@
<tr>
<td><%= web_app.name %></td>
<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">
<span title="<%= web_app.created_at %>" class="cursor-help">
+1 -1
View File
@@ -12,7 +12,7 @@
<tbody>
<% donations.each do |donation| %>
<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.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>
+1 -1
View File
@@ -6,7 +6,7 @@
<tbody>
<tr>
<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>
<th>Donation Method</th>
+2 -2
View File
@@ -44,8 +44,8 @@
<tr>
<td class="overflow-ellipsis font-mono"><%= invitation.token %></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.invitee.cn, admin_user_path(invitation.invitee.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), class: "ks-text-link" %></td>
</tr>
<% end %>
</tbody>
+1 -1
View File
@@ -36,7 +36,7 @@
</td>
<td>
<% 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 %>
</td>
<td><%= number_with_delimiter account.balance.to_i.to_s %></td>
@@ -29,35 +29,15 @@
title: "Relay URL",
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>
</section>
<section>
<h3>Zaps</h3>
<ul role="list">
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :nostr_zaps_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>
</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 %>
+1 -6
View File
@@ -30,11 +30,6 @@
<% end %>
</section>
<section>
<%= render partial: "admin/username_search_form",
locals: { path: admin_users_path } %>
</section>
<section>
<table class="divided mb-8">
<thead>
@@ -47,7 +42,7 @@
<tbody>
<% @users.each do |user| %>
<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>
<%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %>
<% if @show_contributors %><%= @contributors.include?(user.cn) ? badge("contributor", :green) : "" %><% end %>
+5 -5
View File
@@ -47,7 +47,7 @@
<th>Donations</th>
<td>
<% 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
<%= number_with_delimiter @user.donations.completed.sum("amount_sats") %> sats
<% end %>
@@ -60,7 +60,7 @@
<th>Invited by</th>
<td>
<% 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 %>
</td>
</tr>
@@ -102,10 +102,10 @@
<% if @invitees.any? %>
<ul class="mb-0">
<% @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 %>
<% 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 %>
</ul>
<% else %>&mdash;<% end %>
@@ -168,7 +168,7 @@
<span class="font-mono" title="<%= @user.pgp_fpr %>">
<% if @user.pgp_pubkey_contains_user_address? %>
<%= 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]}" %>
<% end %>
<% else %>
@@ -3,7 +3,7 @@
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
<section>
<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.
</p>
</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-[url(/img/logos/icon_xmpp.svg)]">
<%= 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>
<p class="text-gray-600">
Federated chat rooms and instant messaging
@@ -20,8 +20,7 @@
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:88%] bg-[center_top_-40px] bg-no-repeat
bg-[url(/img/logos/icon_mastodon.svg)]">
<%= link_to services_mastodon_path,
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<%= link_to services_mastodon_path, class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">Mastodon</h3>
<p class="text-gray-600">
Your account on the Open Social Web
@@ -34,8 +33,7 @@
<div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:90%] bg-[center_top_-160px] bg-no-repeat
bg-[url(/img/logos/icon_mail.svg)]">
<%= link_to services_email_path,
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<%= link_to services_email_path, class: "block h-full px-6 py-6 rounded-md" do %>
<h3 class="mb-3.5">E-Mail</h3>
<p class="text-gray-600">
A no-bullshit email account
@@ -49,7 +47,7 @@
bg-[length:80%] bg-[center_top_-156px] bg-no-repeat
bg-[url(/img/logos/icon_remotestorage.svg)]">
<%= 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>
<p class="text-gray-600">
Sync your data between apps and devices
@@ -62,7 +60,7 @@
bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat
bg-[url(/img/logos/icon_lightning.svg)]">
<%= 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>
<p class="text-gray-600">
Send and receive sats over the Bitcoin Lightning Network
@@ -75,7 +73,7 @@
bg-[length:80%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_discourse.svg)]">
<%= 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>
<p class="text-gray-600">
Community forums and support/help site
@@ -88,7 +86,7 @@
bg-[length:92%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_gitea.png)]">
<%= 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>
<p class="text-gray-600">
Code hosting and collaboration for software projects
@@ -101,7 +99,7 @@
bg-[length:86%] bg-[center_top_-60px] bg-no-repeat
bg-[url(/img/logos/icon_droneci.svg)]">
<%= 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>
<p class="text-gray-600">
Continuous integration for software projects on Gitea
@@ -114,10 +112,10 @@
bg-cover bg-[center_top_-20px] bg-no-repeat
bg-[url(/img/logos/icon_mediawiki.svg)]">
<%= 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>
<p class="text-gray-600">
Documentation and knowledge base
Kosmos documentation and knowledge base
</p>
<% end %>
</div>
+1 -1
View File
@@ -47,7 +47,7 @@
data: { action: "click->settings--toggle#toggleSwitch" } %>
<p class="grow text-sm text-right">
<%= 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>
<% end %>
@@ -1,5 +1,5 @@
<% if resource.errors.any? %>
<div id="error_explanation" class="mb-6">
<div id="error_explanation">
<ul>
<% resource.errors.full_messages.each do |message| %>
<li class="text-red-600"><%= message %></li>
+4 -4
View File
@@ -2,28 +2,28 @@
<%- if controller_name != 'sessions' %>
<p class="mb-2">
<%= link_to "Log in", new_session_path(resource_name),
class: "text-gray-500 visited:text-gray-500 underline" %>
class: "text-gray-500 underline" %>
</p>
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<p class="mb-2">
<%= 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>
<% end %>
<%- if devise_mapping.confirmable? && !controller_name.match(/^(confirmations|sessions)$/) %>
<p class="mb-2">
<%= 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>
<% end %>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<p class="mb-2">
<%= 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>
<% end %>
</div>
+1 -1
View File
@@ -4,7 +4,7 @@
<section>
<% if @invitations_unused.any? %>
<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>
<ul class="md:w-3/4">
<% @invitations_unused.each do |invitation| %>
+2 -20
View File
@@ -10,7 +10,7 @@
<%= javascript_importmap_tags %>
</head>
<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">
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
<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' %>
</span>
</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 %>
</div>
<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" />
</svg>
</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 %>
</div>
</div>
@@ -105,16 +96,7 @@
</div>
</div>
<div class="flex-grow">
<%= 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>
<%= yield %>
</div>
</body>
</html>
@@ -1,6 +1,6 @@
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 %>
@@ -1,6 +1,6 @@
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| %>
* <%= 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">
<p class="mb-8">
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:
</p>
+10 -5
View File
@@ -62,30 +62,35 @@
</select>
<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">
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</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>
<div id="apps-android" class="hidden grid grid-cols-1 gap-6"
+5 -5
View File
@@ -34,7 +34,7 @@
<p>
Your email password is different from your main account password. You can
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>
</section>
<section>
@@ -56,22 +56,22 @@
</select>
<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">
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</li>
+7 -7
View File
@@ -8,8 +8,8 @@
<section>
<h3>Lightning Address</h3>
<p class="mb-6">
Your user address is also a
<a href="https://lightningaddress.com/" target="_blank">Lightning Address</a>!
Your Kosmos user address is also 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:
</p>
<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">
<h3>Wallet Apps</h3>
<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
<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
code/URL:
</p>
@@ -57,7 +57,7 @@
<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">
<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">
<%= image_tag("/img/logos/alby.svg", class: 'h-16') %>
</a>
@@ -70,7 +70,7 @@
URL in the "LNDHub Export URI" field.
</p>
<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">
<%= image_tag("/img/logos/bluewallet.svg", class: 'h-16') %>
</a>
@@ -83,7 +83,7 @@
then scan the setup QR code.
</p>
<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">
<%= image_tag("/img/logos/zeus.svg", class: 'h-16') %>
</a>
+8 -8
View File
@@ -3,7 +3,7 @@
<%= render MainSimpleComponent.new do %>
<section>
<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>
</section>
<section data-controller="modal" data-action="keydown.esc->modal#close">
@@ -37,7 +37,7 @@
<p>
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
<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>
</section>
<section>
@@ -61,32 +61,32 @@
</select>
<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">
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</li>
+11 -9
View File
@@ -37,15 +37,17 @@
<h3>Compatible Apps</h3>
<p>
Your Storage account is based on a new open standard called
<img src="/img/logos/icon_remotestorage.svg" class="h-4 w-4 inline">
<strong>remoteStorage</strong>, which is not yet widely supported. Look
<a href="https://remotestorage.io" target="_blank">
<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.
</p>
<p>
If you want your favorite apps to support syncing data with your own
Storage account, let the developers know! All relevant information is
available on the
<a href="https://remotestorage.io" target="_blank">remoteStorage website</a>.
available on the <a href="https://remotestorage.io"
target="_blank" class="ks-text-link">remoteStorage website</a>.
</p>
</section>
@@ -65,27 +67,27 @@
</select>
<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">
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</li>
<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
</a>
</li>
+36 -48
View File
@@ -1,46 +1,47 @@
<div data-controller="settings--nostr-pubkey"
data-settings--nostr-pubkey-user-address-value="<%= current_user.address %>"
data-settings--nostr-pubkey-site-value="<%= Setting.accounts_domain %>"
data-settings--nostr-pubkey-shared-secret-value="<%= session[:shared_secret] %>"
data-settings--nostr-pubkey-pubkey-hex-value="<%= current_user.nostr_pubkey %>">
<section class="mb-8 sm:mb-12">
<h3>Nostr</h3>
<h4 class="mb-0">
Public Key
</h4>
<p class="<%= current_user.nostr_pubkey.present? ? '' : 'hidden' %> mt-2 flex gap-x-1">
<section>
<h3>Nostr</h3>
<h4 class="mb-0">Public Key</h4>
<div data-controller="settings--nostr-pubkey"
data-settings--nostr-pubkey-user-address-value="<%= current_user.address %>"
data-settings--nostr-pubkey-site-value="<%= Setting.accounts_domain %>"
data-settings--nostr-pubkey-shared-secret-value="<%= session[:shared_secret] %>"
data-settings--nostr-pubkey-pubkey-hex-value="<%= current_user.nostr_pubkey %>">
<p class="<%= current_user.nostr_pubkey.present? ? '' : 'hidden' %> mt-2 flex gap-1">
<input type="text" value="<%= current_user.nostr_pubkey_bech32 %>" disabled
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,
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 %>
Remove
<% end %>
</p>
<% if current_user.nostr_pubkey.present? %>
<!-- <div> -->
<!-- Pubkey present -->
<!-- </div> -->
<div class="rounded-md bg-blue-50 p-4">
<div class="flex">
<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 %>
<p class="my-4">
Verify your Nostr public key with us in order to enable Nostr-specific
features for your account:
If you use any apps on the Nostr network, you can verify your public key
with us in order to enable Nostr-specific features for your account.
</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 %>
<div data-settings--nostr-pubkey-target="noExtension"
@@ -57,8 +58,8 @@
</h3>
<div class="mt-2 mb-0 text-sm text-blue-800">
<p>
We recommend Alby, which you can also use a wallet for your
Lightning account.
We recommend Alby, which you can also use for your Lightning
Wallet.
</p>
</div>
<div class="mt-4">
@@ -85,18 +86,5 @@
</button>
</p>
<% end %>
</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>
</div>
</section>
@@ -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">
<p class="font-bold mb-1">Avatar</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? %>
<p class="flex-none">
<%= image_tag image_url_for(@user.avatar), class: "h-24 w-24 rounded-lg" %>
@@ -24,7 +24,7 @@
name: "E-Mail",
path: admin_settings_service_path("email"),
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(
level: 2,
+6 -6
View File
@@ -19,6 +19,12 @@
active: @settings_section.to_s == "email"
) %>
<% 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? &&
Flipper.enabled?(:remotestorage, current_user) %>
<%= render SidenavLinkComponent.new(
@@ -26,12 +32,6 @@
active: @settings_section.to_s == "remotestorage"
) %>
<% 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 %>
<%= render SidenavLinkComponent.new(
name: "Nostr", path: setting_path(:nostr), icon: "nostrich-head",
@@ -1,16 +1,12 @@
<div class="border-b border-gray-200">
<nav class="-mb-px flex" aria-label="Tabs">
<%= render TabnavLinkComponent.new(
name: "Donations",
path: contributions_donations_path,
name: "Donations", path: contributions_donations_path,
active: current_page?(contributions_donations_path)
) %>
<%= render TabnavLinkComponent.new(
name: render(EditableContentComponent.new(
context: "contributions/other", key: "title", default: "Other"
)),
path: contributions_other_path,
active: current_page?(contributions_other_path)
name: "Projects", path: contributions_projects_path,
active: current_page?(contributions_projects_path)
) %>
</nav>
</div>
@@ -1,3 +0,0 @@
<div>
<%= profile.inspect %>
</div>
+1 -1
View File
@@ -2,7 +2,7 @@
<%= render MainCompactComponent.new do %>
<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>.
</p>
<p>
-1
View File
@@ -14,7 +14,6 @@ FileUtils.chdir APP_ROOT do
puts "== Installing dependencies =="
system("bundle check") || system!("bundle install")
system!("bun install")
# puts "\n== Copying sample files =="
# 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
FROM ruby:3.3.12
FROM ruby:3.3.0
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
# 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 apt-get update && apt-get install -y nodejs
# 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"
RUN npm install -g yarn

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