diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..b5c7ede3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.css] +indent_style = tab +indent_size = 2 + +[*.js] +indent_style = tab +indent_size = 4 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..52df463f --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# editorconfig +30db58ead8d80749c1ff86bacc3ef89899dae62f diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5b75d4dd..7db92657 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,6 +2,7 @@ ## Contributor checklist -- [ ] My PR is related to \ +- [ ] My PR is related to \ - [ ] I have read and understood the [CONTRIBUTING guide](https://github.com/flora-pm/flora-server/blob/development/CONTRIBUTING.md) - [ ] I have inserted my change and a link to this PR in the [CHANGELOG](https://github.com/flora-pm/flora-server/blob/development/CHANGELOG.md) +- [ ] I have updated documentation in `./docs/docs` if a public feature has a behaviour change diff --git a/.github/mergify.yml b/.github/mergify.yml index 6e8b56c9..45c3b4ca 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -1,33 +1,28 @@ + pull_request_rules: # rebase+merge strategy - - actions: + - name: Put pull requests in the rebase+merge queue + Put pull requests in the squash+merge + queue + conditions: [] + actions: queue: - name: default - # Merge into master with a merge commit - method: merge - # Update the pr branch with rebase, so the history is clean - update_method: rebase - name: Put pull requests in the rebase+merge queue - conditions: +queue_rules: + - name: duplicated default from Put pull requests in the squash+merge queue + queue_conditions: - label=merge me - 'check-success=Frontend_tests' - 'check-success~=.*Backend_tests.*' - # - '#approved-reviews-by>=1' - # merge+squash strategy - - actions: - queue: - name: default - method: squash - # both update methods get absorbed by the squash, so we use the most - # reliable - update_method: merge - name: Put pull requests in the squash+merge queue - conditions: - label=squash+merge me - 'check-success=Frontend_tests' - 'check-success=Backend_tests' - # - '#approved-reviews-by>=1' - -queue_rules: + merge_conditions: [] + update_method: merge + merge_method: squash - name: default - conditions: [] + queue_conditions: + - label=merge me + - 'check-success=Frontend_tests' + - 'check-success~=.*Backend_tests.*' + merge_conditions: [] + update_method: rebase + merge_method: merge diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 5bd2295f..df29128e 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -12,18 +12,17 @@ concurrency: jobs: generateMatrix: name: "Generate matrix from cabal" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - - name: Checkout base repo - uses: actions/checkout@v4 - name: Extract the tested GHC versions id: set-matrix - run: | - wget https://github.com/Kleidukos/get-tested/releases/download/v0.1.4.0/get-tested-0.1.4.0-linux-amd64 -O get-tested - chmod +x get-tested - ./get-tested --ubuntu *.cabal >> $GITHUB_OUTPUT + uses: kleidukos/get-tested@v0.1.7.1 + with: + cabal-file: flora.cabal + ubuntu-version: "22.04" + version: 0.1.7.1 Backend_tests: needs: generateMatrix @@ -57,12 +56,6 @@ jobs: ghc-version: "${{ matrix.ghc }}" cabal-version: "latest" - - uses: actions/setup-node@v4 - with: - node-version: "18" - cache: "yarn" - cache-dependency-path: assets/yarn.lock - - name: Configure environment run: | ./.github/workflows/setup.sh @@ -78,22 +71,28 @@ jobs: echo "${FLORA_DB_HOST}:${FLORA_DB_PORT}:${FLORA_DB_DATABASE}:${FLORA_DB_USER}:${FLORA_DB_PASSWORD}" > .pgpass cat ~/.pgpass cabal update - cabal freeze + mkdir -p ~/.local/share + git clone https://github.com/haskell/security-advisories.git ~/.local/share/security-advisories + cd ~/.local/share/security-advisories + git checkout df64e86a39668c057031fe7e2c679b1003090e03 + cd - + - name: "Create freeze file" + run: | + cabal freeze --enable-tests - name: Cache - uses: actions/cache@v4.0.0 + uses: actions/cache@v4 with: path: ${{ steps.setup-haskell.outputs.cabal-store }} - key: ${{ runner.os }}-ghc-${{ matrix.ghc }}-cabal-${{ hashFiles('**/plan.json') }} + key: ${{ runner.os }}-ghc-${{ matrix.ghc }}-cabal-${{ hashFiles('./dist-newstyle/cache/plan.json') }} restore-keys: ${{ runner.os }}-ghc-${{ matrix.ghc }}- - name: Build run: | + cabal install postgresql-migration make soufflé - make assets-deps - make build-assets make build - cabal install postgresql-migration + - name: Test run: | set -x diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml new file mode 100644 index 00000000..d6cfb10b --- /dev/null +++ b/.github/workflows/changelog.yaml @@ -0,0 +1,19 @@ +name: "Lint the changelog entries" +on: + pull_request: + types: + - synchronize +jobs: + lint-changelog: + runs-on: ubuntu-22.04 + steps: + - name: Checkout code + uses: "actions/checkout@v4" + - name: Install changelog-d + run: | + mkdir -p ~/.local/bin + wget https://codeberg.org/fgaz/changelog-d/releases/download/v1.0/changelog-d-v1.0-x86_64-linux -O ~/.local/bin/changelog-d + chmod +x ~/.local/bin/changelog-d + echo "~/.local/bin" >> $GITHUB_PATH + - name: Run changelog-d check + run: changelog-d changelog.d diff --git a/.github/workflows/check-duplicate-indexes.sh b/.github/workflows/check-duplicate-indexes.sh new file mode 100755 index 00000000..c6c6eeb3 --- /dev/null +++ b/.github/workflows/check-duplicate-indexes.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -euo pipefail + +output="$(psql "$FLORA_DB_CONNSTRING" -f scripts/duplicate-indexes.sql)" + +if [[ "$output" == *"(0 rows)"* ]] +then + exit 0 +else + echo "Duplicate indexes! Run \`psql \"\$FLORA_DB_CONNSTRING\" -f scripts/duplicate-indexes.sql\` and remove them" + echo $output + exit 1 +fi diff --git a/.github/workflows/check-flora-tickets-in-prs.yaml b/.github/workflows/check-flora-tickets-in-prs.yaml deleted file mode 100644 index 1b2e7062..00000000 --- a/.github/workflows/check-flora-tickets-in-prs.yaml +++ /dev/null @@ -1,23 +0,0 @@ -jobs: - make-sure-titles-contain-flora-issue: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: "actions/checkout@v4" - with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - - name: Make sure there is one proper commit or PR title - env: - PULL_REQUEST_TITLE: "${{ github.event.pull_request.title }}" - GITHUB_TOKEN: "${{ github.token }}" - PULL_REQUEST_COMMITS_URL: "${{ github.event.pull_request.commits_url }}" - PULL_REQUEST_AUTHOR: "${{ github.event.pull_request.user.login }}" - run: | - ./.github/workflows/check-ticket-in-prs.sh -name: Make sure PR has FLORA or NO-ISSUE issue in title or has one commit with FLORA/NO-ISSUE issue -on: - pull_request: - types: - - synchronize - - edited diff --git a/.github/workflows/check-missing-fk-indexes.sh b/.github/workflows/check-missing-fk-indexes.sh new file mode 100755 index 00000000..6fa0d55e --- /dev/null +++ b/.github/workflows/check-missing-fk-indexes.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -euo pipefail + +output="$(psql "$FLORA_DB_CONNSTRING" -f scripts/missing-fk-indexes.sql 2>&1 > /dev/null)" + +if [[ "$output" == *"Missing FK indexes"* ]] +then + echo "Missing FK index! Run \`psql \"\$FLORA_DB_CONNSTRING\" -f scripts/missing-fk-indexes.sql\` and apply them" + echo $output + exit 1 +else + exit 0 +fi diff --git a/.github/workflows/check-ticket-in-prs.sh b/.github/workflows/check-ticket-in-prs.sh deleted file mode 100755 index 89ad212e..00000000 --- a/.github/workflows/check-ticket-in-prs.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -COMMIT_MESSAGE_PATTERN="\[((FLORA)-[0-9]+)|NO-ISSUE\]" - -case "$PULL_REQUEST_AUTHOR" in - "dependabot[bot]" | "dependabot" ) - echo "Pull request by dependabot - not checking" - exit 0 - ;; - - *) - if ! echo "$PULL_REQUEST_TITLE" | grep -E "${COMMIT_MESSAGE_PATTERN}"; then - echo "Pull request title doesn't start with '[FLORA-ISSUE]' or '[NO-ISSUE]'" > /dev/stderr - exit 1 - fi - ;; -esac diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 0f0eb1b5..55ce1524 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,16 +1,16 @@ name: Publish Docker Image -on: +on: push: branches: ["development"] paths: - - Dockerfile + - Dockerfile - docker-compose.yml - scripts/.zshrc jobs: publish-hello-docker-image: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Login to GitHub Container Registry @@ -19,7 +19,7 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build the hello-docker Docker image + - name: Build the Docker image run: | docker build . --tag ghcr.io/flora-pm/flora-server:latest docker run ghcr.io/flora-pm/flora-server:latest diff --git a/.github/workflows/duplicate-indexes.yaml b/.github/workflows/duplicate-indexes.yaml new file mode 100644 index 00000000..ac7973dc --- /dev/null +++ b/.github/workflows/duplicate-indexes.yaml @@ -0,0 +1,97 @@ +name: Duplicate Indexes + +on: + pull_request: + push: + branches: ["main", "development"] + +concurrency: + group: duplicate-indexes-${{ github.ref_name }} + cancel-in-progress: true + +jobs: + generateMatrix: + name: "Generate matrix from cabal" + runs-on: ubuntu-22.04 + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Extract the tested GHC versions + id: set-matrix + uses: kleidukos/get-tested@v0.1.7.1 + with: + cabal-file: flora.cabal + ubuntu-version: "22.04" + version: 0.1.7.1 + + duplicate-index-check: + needs: generateMatrix + runs-on: ${{ matrix.os }} + strategy: + matrix: ${{ fromJSON(needs.generateMatrix.outputs.matrix) }} + # Service containers to run with `container-job` + services: + # Label used to access the service container + postgres: + # Docker Hub image + image: postgres + # Provide the password for postgres + env: + POSTGRES_PASSWORD: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + steps: + - uses: actions/checkout@v4 + + - name: Set up Haskell + id: setup-haskell + uses: haskell-actions/setup@v2 + with: + ghc-version: "${{ matrix.ghc }}" + cabal-version: "latest" + + - name: Configure environment + run: | + ./.github/workflows/setup.sh + echo "/usr/lib/postgresql/14/bin/" >> $GITHUB_PATH + echo "$HOME/.ghcup/bin" >> $GITHUB_PATH + echo "$HOME/.cabal/bin" >> $GITHUB_PATH + echo "$HOME/.local/bin" >> $GITHUB_PATH + echo "$HOME/node_modules/.bin" >> $GITHUB_PATH + sudo apt install libsodium-dev + source ./environment.ci.sh + touch ~/.pgpass + chmod 0600 ~/.pgpass + echo "${FLORA_DB_HOST}:${FLORA_DB_PORT}:${FLORA_DB_DATABASE}:${FLORA_DB_USER}:${FLORA_DB_PASSWORD}" > .pgpass + cat ~/.pgpass + cabal update + + - name: Cache + uses: actions/cache@v4.2.0 + with: + path: ${{ steps.setup-haskell.outputs.cabal-store }} + key: ${{ runner.os }}-ghc-${{ matrix.ghc }}-cabal-${{ hashFiles('./.plan.json') }} + restore-keys: ${{ runner.os }}-ghc-${{ matrix.ghc }}- + + + - name: Migrate + run: | + cabal install postgresql-migration + set -x + source ./environment.ci.sh + createdb -h "${FLORA_DB_HOST}" -p "${FLORA_DB_PORT}" -U "${FLORA_DB_USER}" -w "${FLORA_DB_DATABASE}" + migrate init "${FLORA_DB_CONNSTRING}" + migrate migrate "${FLORA_DB_CONNSTRING}" migrations + env: + PGPASSWORD: "postgres" + + - name: Check + run: | + source ./environment.ci.sh + .github/workflows/check-duplicate-indexes.sh diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 84f08c53..f8a706c0 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -7,7 +7,7 @@ on: jobs: Frontend_tests: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -22,12 +22,7 @@ jobs: echo "$HOME/node_modules/.bin" >> $GITHUB_PATH source ./environment.ci.sh - - uses: actionsx/prettier@v3 - with: - # prettier CLI arguments. - args: --check ./assets/css - - - name: Stylelint + - name: Stylelint run: | cd assets yarn diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 28aa6657..8c730a0e 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -7,11 +7,11 @@ on: jobs: fourmolu: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - - uses: haskell-actions/run-fourmolu@v10 + - uses: haskell-actions/run-fourmolu@v11 with: version: "0.14.1.0" pattern: | @@ -20,17 +20,17 @@ jobs: app/**/*.hs hlint: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: 'Set up HLint' - uses: rwe/actions-hlint-setup@v1 + uses: haskell-actions/hlint-setup@v2 with: version: '3.8' - name: 'Run HLint' - uses: rwe/actions-hlint-run@v2 + uses: haskell-actions/hlint-run@v2 with: - path: '["src/", "app/", "test/"]' + path: '["src/", "test/", "app/"]' fail-on: warning diff --git a/.github/workflows/missing-fk-indexes.yml b/.github/workflows/missing-fk-indexes.yml new file mode 100644 index 00000000..f185da40 --- /dev/null +++ b/.github/workflows/missing-fk-indexes.yml @@ -0,0 +1,97 @@ +name: Missing FK Indexes + +on: + pull_request: + push: + branches: ["main", "development"] + +concurrency: + group: missing-kf-indexes-${{ github.ref_name }} + cancel-in-progress: true + +jobs: + generateMatrix: + name: "Generate matrix from cabal" + runs-on: ubuntu-22.04 + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Extract the tested GHC versions + id: set-matrix + uses: kleidukos/get-tested@v0.1.7.1 + with: + cabal-file: flora.cabal + ubuntu-version: "22.04" + version: 0.1.7.1 + + missing-fk-index-check: + needs: generateMatrix + runs-on: ${{ matrix.os }} + strategy: + matrix: ${{ fromJSON(needs.generateMatrix.outputs.matrix) }} + # Service containers to run with `container-job` + services: + # Label used to access the service container + postgres: + # Docker Hub image + image: postgres + # Provide the password for postgres + env: + POSTGRES_PASSWORD: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + steps: + - uses: actions/checkout@v4 + + - name: Set up Haskell + id: setup-haskell + uses: haskell-actions/setup@v2 + with: + ghc-version: "${{ matrix.ghc }}" + cabal-version: "latest" + + - name: Configure environment + run: | + ./.github/workflows/setup.sh + echo "/usr/lib/postgresql/14/bin/" >> $GITHUB_PATH + echo "$HOME/.ghcup/bin" >> $GITHUB_PATH + echo "$HOME/.cabal/bin" >> $GITHUB_PATH + echo "$HOME/.local/bin" >> $GITHUB_PATH + echo "$HOME/node_modules/.bin" >> $GITHUB_PATH + sudo apt install libsodium-dev + source ./environment.ci.sh + touch ~/.pgpass + chmod 0600 ~/.pgpass + echo "${FLORA_DB_HOST}:${FLORA_DB_PORT}:${FLORA_DB_DATABASE}:${FLORA_DB_USER}:${FLORA_DB_PASSWORD}" > .pgpass + cat ~/.pgpass + cabal update + + - name: Cache + uses: actions/cache@v4.2.0 + with: + path: ${{ steps.setup-haskell.outputs.cabal-store }} + key: ${{ runner.os }}-ghc-${{ matrix.ghc }}-cabal-${{ hashFiles('./.plan.json') }} + restore-keys: ${{ runner.os }}-ghc-${{ matrix.ghc }}- + + + - name: Migrate + run: | + cabal install postgresql-migration + set -x + source ./environment.ci.sh + createdb -h "${FLORA_DB_HOST}" -p "${FLORA_DB_PORT}" -U "${FLORA_DB_USER}" -w "${FLORA_DB_DATABASE}" + migrate init "${FLORA_DB_CONNSTRING}" + migrate migrate "${FLORA_DB_CONNSTRING}" migrations + env: + PGPASSWORD: "postgres" + + - name: Check + run: | + source ./environment.ci.sh + .github/workflows/check-missing-fk-indexes.sh diff --git a/.github/workflows/nix-check.yml b/.github/workflows/nix-check.yaml.backup similarity index 77% rename from .github/workflows/nix-check.yml rename to .github/workflows/nix-check.yaml.backup index 9eb169ac..f1c2854a 100644 --- a/.github/workflows/nix-check.yml +++ b/.github/workflows/nix-check.yaml.backup @@ -1,14 +1,10 @@ name: "Flora nix check" -on: - pull_request: - push: - branches: ["main", "development"] jobs: tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v25 + - uses: cachix/install-nix-action@v26 with: github_access_token: ${{ secrets.GITHUB_TOKEN }} - uses: cachix/cachix-action@v14 diff --git a/.github/workflows/setup.sh b/.github/workflows/setup.sh index b355c28b..bd4d676e 100755 --- a/.github/workflows/setup.sh +++ b/.github/workflows/setup.sh @@ -3,7 +3,7 @@ sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - sudo apt -y update -sudo apt -y install postgresql-14 g++ mcpp libffi7 llvm +sudo apt -y install postgresql-16 g++ mcpp libffi7 llvm zlib1g-dev -wget https://github.com/souffle-lang/souffle/releases/download/2.3/x86_64-ubuntu-2004-souffle-2.3-Linux.deb -sudo dpkg -i ./x86_64-ubuntu-2004-souffle-2.3-Linux.deb +wget https://github.com/souffle-lang/souffle/releases/download/2.2/x86_64-ubuntu-2004-souffle-2.2-Linux.deb +sudo dpkg -i ./x86_64-ubuntu-2004-souffle-2.2-Linux.deb diff --git a/.github/workflows/test-docker-image.yml b/.github/workflows/test-docker-image.yml index a92eb35c..06ba5aa3 100644 --- a/.github/workflows/test-docker-image.yml +++ b/.github/workflows/test-docker-image.yml @@ -1,17 +1,17 @@ name: Build Docker Image -on: +on: pull_request: branches: ["main", "development"] paths: - - Dockerfile + - Dockerfile - docker-compose.yml - scripts/.zshrc jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - - name: Build the hello-docker Docker image + - name: Build the Docker image run: make docker-build diff --git a/.gitignore b/.gitignore index e628620e..a9814963 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ design/node_modules *.dump-timings _build/cabal-store +storage/ +cabal.project.release.freeze +prlog diff --git a/CHANGELOG.md b/CHANGELOG.md index fbcca093..9f46db68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,82 @@ # CHANGELOG +## 1.0.24 -- 2025-01-12 + +- Do not wrongly include "Nothing" as a query param to dependents listing pages' URL [#817](https://github.com/flora-pm/flora-server/pull/817) +- Include prismJS to highlight code in the READMEs [#819](https://github.com/flora-pm/flora-server/pull/819) + +## 1.0.23 -- 2025-01-02 + +- Record more route duration metrics with prometheus [#810](https://github.com/flora-pm/flora-server/pull/810) +- Add prometheus counter for package imports [#811](https://github.com/flora-pm/flora-server/pull/811) +- Add new GHC versions [#813](https://github.com/flora-pm/flora-server/pull/813) + + The following versions have been added: + * 9.12.1 + * 9.10.1 + * 9.8.4 + * 9.8.3 + * 9.6.7 + * 9.6.6 + +## 1.0.22 -- 2024-12-27 + +### Significant changes + +- Start the data model for security advisories [#762](https://github.com/flora-pm/flora-server/pull/762) +- Search in security advisories with the `hsec:` qualifier [#805](https://github.com/flora-pm/flora-server/pull/805) +- Display the advisories linked to a package in their `/security` sub-page [#790](https://github.com/flora-pm/flora-server/pull/790) + +### Other changes + +- Membership data model for packages [#556](https://github.com/flora-pm/flora-server/issue/556) [#785](https://github.com/flora-pm/flora-server/pull/785) + + Migration for `create_package_groups` & `create_package_group_packages` + +- Render the HTML of READMEs and Changelogs [#781](https://github.com/flora-pm/flora-server/pull/781) + + There was a regression, likely due to the switch to lucid2, that escaped the HTML from READMEs and Changelogs + +- Re-enable prometheus metrics for http & process resources [#802](https://github.com/flora-pm/flora-server/pull/802) + +## 1.0.21 -- 2024-11-01 + +- Add htmx polling for page reload [#579](https://github.com/flora-pm/flora-server/pull/579) +- Show last upload or revision date in packsge listings [#580](https://github.com/flora-pm/flora-server/pull/580) +- Add CI check for missing FK indexes [#605](https://github.com/flora-pm/flora-server/pull/605) +- Add more logging statements when importing packages [#778](https://github.com/flora-pm/flora-server/pull/778) + +## 1.0.20 Hotfix release -- 2024-07-24 + +- Sort and limit the amount of releases in the DB instead of Flora [#567](https://github.com/flora-pm/flora-server/pull/567) + +## 1.0.19 -- 2024-07-23 + +- Log and re-import packages with zero dependencies [#553](https://github.com/flora-pm/flora-server/pull/553) +- Have explicit version ARGS in docker for tools [#557](https://github.com/flora-pm/flora-server/pull/557) +- Remove the enqueueImportJob function [#558](https://github.com/flora-pm/flora-server/pull/558) +- Store archive hashes [#560](https://github.com/flora-pm/flora-server/pull/560) +- Implement tracing with zipkin [#564](https://github.com/flora-pm/flora-server/pull/564) +- Parametrise tracing options [#566](https://github.com/flora-pm/flora-server/pull/566) + +## 1.0.18 -- 2024-05-18 + +* Add `@horizon` namespace ([#498](https://github.com/flora-pm/flora-server/issues/498)) +* Signal deprecations and revision dates in version listing page ([#548](https://github.com/flora-pm/flora-server/pull/548)) +* Introduce [changelog-d](https://codeberg.org/fgaz/changelog-d) in the release process. +* Remove the last @apply from tailwind ([#550](https://github.com/flora-pm/flora-server/pulls/550)) +* Use GHC 9.6.5 and Souffle Datalog 2.2 for development ([#552](https://github.com/flora-pm/flora-server/pull/552)) + +## 1.0.17 -- 2024-03-26 + +* Add `exe:` search modifier to look up packages by executable name ([#529](https://github.com/flora-pm/flora-server/pull/529)) +* Add a link to the search documentation under the main search bar ([#532](https://github.com/flora-pm/flora-server/pull/532)) +* Add a link to the general documentation in the navbar ([#537](https://github.com/flora-pm/flora-server/pull/537)) + +## 1.0.16 -- 2024-02-26 + +* Add badge component for custom build type ([#517](https://github.com/flora-pm/flora-server/pull/517)) + ## 1.0.15 -- 2024-02-16 * Show 3 digits in version if using 0x scheme. ([#490](https://github.com/flora-pm/flora-server/pull/490)) * Deduplicate dependencies listed in package overview ([#492](https://github.com/flora-pm/flora-server/pull/492)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d30b15e..f0c11af2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,12 +18,13 @@ The following Haskell command-line tools will have to be installed: (Some of the above packages have incompatible dependencies, so don't try to install them all at once with `cabal install`) -* [Soufflé datalog engine v2.3](https://github.com/souffle-lang/souffle/releases/tag/2.3): The datalog engine for package classification +* [Soufflé datalog engine v2.2](https://github.com/souffle-lang/souffle/releases/tag/2.2): The datalog engine for package classification * `libsodium-1.0.18`: The system library that powers most of the cryptography happening in flora * `yarn`: The tool that handles the JavaScript code bases * `esbuild`: The tool that handles asset bundling +* `changelog-d` v1.0: https://codeberg.org/fgaz/changelog-d/releases/tag/v1.0 -### Questions +### Questions Open a thread in the [Questions][Questions board] discussion board. You'll get help from everyone in the community. @@ -45,6 +46,22 @@ PR title and commit message: [NO-ISSUE] Update dependencies for Storybook.js ``` +* Insert a changelog entry in the `changelog.d` directory, based on this template: + +```cabal +synopsis: Add feature xyz +prs: #102 +issues: #100 #101 +significance: significant -- Only if this is important enough to be put at the top of the changelog. + +description: { + +- Detail number 1 +- Detail number 2 + +} +``` + ### Feature request Open a thread in the [Feature Request][Feature Request board] discussion board. @@ -56,7 +73,10 @@ Here is the procedure to follow when making a release: 1. Create a PR to prepare the release of the next version targeting `development`. It must include: * Bump the version in the flora.cabal file + * Generate the changelog with ./scripts/generate-changelog.sh * Write down the date in the CHANGELOG + * Remove the changelog.d entries + 2. Once the PR is merged into `development`, merge `development` into `main` 3. Create a [new release](https://github.com/flora-pm/flora-server/releases/new). @@ -154,7 +174,7 @@ postgres=# alter role postgres with password 'postgres'; And you are good to go. -#### Setup project +#### Setup project To create the database and apply the migrations, type: @@ -170,17 +190,23 @@ A docker-based workflow is provided. The idea is to develop from within a contai and communicates with another container for the PostgreSQL database. ```bash -# You need to build the container first. It's gonna take around 13 minutes the first time you build -$ make docker-build +# Start the containers for the database and the server +$ make docker-up # Once the containers are running, you can enter the development environment and start hacking $ make docker-enter -$ source environment.docker.sh -# You'll be in the docker container. Environment variables are automatically set +# You'll be in the docker container. Environment variables are automatically set # so you should be able to start Flora (docker)$ make start-tmux # You'll be in a tmux session, everything should be launched # Visit localhost:8084 from your web browser to see if it all works. ``` + +If you need to rebuild the container, run the following command: + +```bash +$ make docker-build +``` + ### Provisioning the database After everything is set up, (locally or via Docker), you can start populating the database: @@ -189,7 +215,7 @@ After everything is set up, (locally or via Docker), you can start populating th $ make db-setup $ make db-provision $ cabal run -- flora-cli create-user --admin --can-login --username "admin" \ - --email "admin@localhost" --password "password123" + --email "admin@localhost" --password "password123" $ make db-provision-test-packages ``` @@ -243,7 +269,7 @@ Direnv can drastically reduce development cycles by reducing the amount of times this repository, which is a drastic improvement, especially with `IFD` (which this repo uses due to `callCabal2nix`). Devshell startup times will be instant if you didn't change anything in the configuration and as long as usual if you -need to re-evaluate the `nix`-expressions (i.e. on cabal config changes or `nix` changes). +need to re-evaluate the `nix`-expressions (i.e. on cabal config changes or `nix` changes). Find out how to install `direnv` on your machine by visiting [their github](https://github.com/direnv/direnv/).o After installing, add a `.envrc` file to the root of the project containing: @@ -324,12 +350,12 @@ nix run .#server Contributions to our `nix` infrastructures are always appreciated, however, there are a couple of guidelines - don't forget to run formatting and linting (see above for `pre-commit-hooks`) - prefer cached derivations, that means: - - prefer upstream haskell packages over custom versions- + - prefer upstream haskell packages over custom versions- - prefer frameworks that have reliable and trusted binary caches - prefer versions with less IFD: - prefer realized `nix` derivations over `callHackage` over `callCabal2nix` - don't use custom packages if not absolutely necessary -- locking happens in the `nix` flake +- locking happens in the `nix` flake - `nix` provides a native locking mechanism with flakes, we only use that mechanism - if we need a source of a package, we add it as a flake input with `flake = false;` - we don't use any fetcher, if not absolutely needed (e.g. if you need a tarball which is diff --git a/Dockerfile b/Dockerfile index 6aa985d6..2fa9ed94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,8 +5,15 @@ FROM ubuntu@sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6 ARG GID=1000 ARG UID=1000 -ARG ghc_version=9.4.5 -ARG cabal_version=3.10.2.0 +ARG GHC_VERSION=9.6.6 +ARG CABAL_VERSION=3.10.3.0 +ARG FOURMOLU_VERSION=0.14.1.0 +ARG HLINT_VERSION=3.8 +ARG APPLY_REFACT_VERSION=0.14.0.0 +ARG CABAL_FMT_VERSION=0.1.12 +ARG GHCID_VERSION=0.8.9 +ARG GHC_TAGS_VERSION=1.9 +ARG POSTGRESQL_MIGRATION_VERSION=0.2.1.8 # generate a working directory USER "root" @@ -23,34 +30,34 @@ RUN chown -R $USER:$USER /home/$USER/.cabal WORKDIR /flora-server RUN apt update && \ - apt install -y build-essential curl libffi-dev libffi8 libgmp-dev libgmp10 libncurses-dev libncurses5 libtinfo5 git + apt install -y build-essential curl libffi-dev libffi8 libgmp-dev libgmp10 libncurses-dev libncurses5 libtinfo5 git libsodium-dev pkg-config # install dependencies (pg_config, postgresql-client, yarn) ENV BOOTSTRAP_HASKELL_NONINTERACTIVE="YES" -ENV BOOTSTRAP_HASKELL_GHC_VERSION="$ghc_version" -ENV BOOTSTRAP_HASKELL_CABAL_VERSION="$cabal_version" ENV BOOTSTRAP_HASKELL_INSTALL_NO_STACK="YES" ENV BOOTSTRAP_HASKELL_INSTALL_NO_STACK_HOOK="YES" -ENV BOOTSTRAP_HASKELL_INSTALL_HLS="YES" +ENV PATH="$PATH:/home/$USER/.ghcup/bin" RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt install -y nodejs libpq-dev mcpp wget zsh tmux postgresql-client RUN corepack enable -RUN chmod ugo+x /home/$USER/.cabal - USER ${USER} +RUN chmod ugo+x /home/$USER/.cabal RUN git config --global --add safe.directory "*" RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh +RUN ghcup install hls $HLS_VERSION \ + && ghcup install ghc $GHC_VERSION \ + && ghcup set ghc $GHC_VERSION \ + && ghcup install cabal $CABAL_VERSION USER ${USER} -ENV PATH="$PATH:/home/$USER/.ghcup/bin" # install soufflé USER "root" -RUN wget --content-disposition https://github.com/souffle-lang/souffle/releases/download/2.3/x86_64-ubuntu-2004-souffle-2.3-Linux.deb -RUN apt install -f -y ./x86_64-ubuntu-2004-souffle-2.3-Linux.deb +RUN wget --content-disposition https://github.com/souffle-lang/souffle/releases/download/2.2/x86_64-ubuntu-2004-souffle-2.2-Linux.deb +RUN apt install -f -y ./x86_64-ubuntu-2004-souffle-2.2-Linux.deb USER ${USER} RUN echo $PATH @@ -59,12 +66,13 @@ RUN echo $PATH # to run `cabal update` as separate step, as cabal doesn't delete # package versions) RUN cabal update -RUN cabal install -j postgresql-migration -RUN cabal install -j hlint apply-refact -RUN cabal install -j fourmolu-0.14.1.0 -RUN cabal install -j cabal-fmt -RUN cabal install -j ghcid -RUN cabal install -j ghc-tags +RUN cabal install -j postgresql-migration-$POSTGRESQL_MIGRATION_VERSION +RUN cabal install -j hlint-$HLINT_VERSION +RUN cabal install -j apply-refact-$APPLY_REFACT_VERSION +RUN cabal install -j fourmolu-$FOURMOLU_VERSION +RUN cabal install -j cabal-fmt-$CABAL_FMT_VERSION +RUN cabal install -j ghcid-$GHCID_VERSION +RUN cabal install -j ghc-tags-$GHC_TAGS_VERSION # configure the shell RUN sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" @@ -72,7 +80,7 @@ COPY --chown=${USER} scripts/shell-welcome.txt /etc/motd COPY --chown=${USER} scripts/.zshrc /home/$USER/.zshrc # build Haskell dependencies -COPY --chown=${USER} cabal.project flora.cabal ./ +COPY --chown=${USER} cabal.project flora.cabal cabal.project.freeze ./ RUN cabal build --only-dependencies -j # compile Souffle source files diff --git a/Makefile b/Makefile index 99b6dc14..2537efbd 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,12 @@ init: ## Set up git hooks properly - needs calling once when cloning the repo start: ## Start flora-server @cabal run exe:flora-server -build: soufflé ## Build the backend - @cabal build -j -O1 +build: soufflé ## Build the server + @cabal build + +build-release: soufflé ## Build the server for production + @cabal freeze --project-file cabal.project.release + @cabal build --project-file cabal.project.release clean: ## Remove the cabal build artifacts @rm cbits/*.cpp @@ -26,16 +30,16 @@ clean-assets: ## Remove JS artifacts @cd assets/ && rm -R node_modules @cd docs/ && rm -R node_modules +db-setup: db-create db-init db-migrate ## Setup the dev database + db-create: ## Create the database @createdb -h $(FLORA_DB_HOST) -p $(FLORA_DB_PORT) -U $(FLORA_DB_USER) $(FLORA_DB_DATABASE) db-drop: ## Drop the database @dropdb -f --if-exists -h $(FLORA_DB_HOST) -p $(FLORA_DB_PORT) -U $(FLORA_DB_USER) $(FLORA_DB_DATABASE) -db-setup: db-create db-init db-migrate ## Setup the dev database - db-init: ## Create the database schema - @migrate init "$(FLORA_DB_CONNSTRING)" + @migrate init "$(FLORA_DB_CONNSTRING)" db-migrate: ## Apply database migrations @migrate migrate "$(FLORA_DB_CONNSTRING)" migrations @@ -47,13 +51,43 @@ db-provision: ## Create categories and repositories @cabal run -- flora-cli provision categories @cabal run -- flora-cli provision-repository --name "hackage" --url https://hackage.haskell.org \ --description "Central package repository" - @cabal run -- flora-cli provision-repository --name "cardano" --url https://input-output-hk.github.io/cardano-haskell-packages \ + @cabal run -- flora-cli provision-repository --name "cardano" --url https://chap.intersectmbo.org \ --description "Packages of the Cardano project" + @cabal run -- flora-cli provision-repository --name "horizon" --url https://packages.horizon-haskell.net \ + --description "Packages of the Horizon project" -db-provision-test-packages: ## Load development data in the database +db-provision-advisories: ## Load HSEC advisories in the database + @cabal run -- flora-cli provision advisories + +db-provision-packages: ## Load development data in the dev database @cabal run -- flora-cli provision test-packages --repository "hackage" @cabal run -- flora-cli provision test-packages --repository "cardano" +db-test-create: ## Create the test database + ./scripts/run-with-test-config.sh db-create + +db-test-setup: db-test-create db-test-init db-test-migrate ## Setup the dev database + +db-test-drop: ## Drop the test database + ./scripts/run-with-test-config.sh db-drop + +db-test-init: ## Create the test database schema + ./scripts/run-with-test-config.sh db-init + +db-test-migrate: ## Apply test database migrations + ./scripts/run-with-test-config.sh db-migrate + +db-test-reset: db-test-drop db-test-setup db-test-provision ## Reset the test database + +db-test-provision: ## Create categories and repositories + ./scripts/run-with-test-config.sh db-provision + +db-test-provision-advisories: ## Load HSEC advisories in the test database + ./scripts/run-with-test-config.sh db-provision-advisories + +db-test-provision-packages: ## Load development data in the database + ./scripts/run-with-test-config.sh db-provision-packages + import-from-hackage: ## Imports every cabal file from the ./index-01 directory @cabal run -- flora-cli import-packages ./01-index @@ -77,17 +111,16 @@ watch-server: soufflé ## Start flora-server in ghcid lint-hs: ## Run the code linter (HLint) @find app test src -name "*.hs" | xargs -P $(PROCS) -I {} hlint --refactor-options="-i" --refactor {} -style-hs-quick: ## Run the haskell code formatters (fourmolu, cabal-fmt) +style-hs-quick: ## Run the haskell code formatters (fourmolu, cabal-fmt) @cabal-fmt -i flora.cabal @git diff origin --name-only src test/**/*.hs app | xargs -P $(PROCS) -I {} fourmolu -q -i {} -style-hs: ## Run the haskell code formatters (fourmolu, cabal-fmt) +style-hs: ## Run the haskell code formatters (fourmolu, cabal-fmt) @cabal-fmt -i flora.cabal @find app test src -name '*.hs' | xargs -P $(PROCS) -I {} fourmolu -q -i {} -style-css: ## Run the CSS code formatters (prettier, stylelint) - @cd assets ; yarn prettier --write css - @cd assets ; yarn stylelint --fix css +style-css: ## Run the CSS code formatter (stylelint) + @cd assets ; yarn stylelint --fix css --ignore-path .stylelintignore style: style-hs style-css ## Run all the code formatters @@ -100,7 +133,10 @@ docker-build: ## Build and start the container cluster docker-up: ## Start the container cluster @docker compose up -d -docker-down: ## Start the container cluster +docker-stop: ## Stop the container cluster without removing the containers + @docker compose stop + +docker-down: ## Stop and remove the container cluster @docker compose down docker-enter: ## Enter the docker environment @@ -117,9 +153,20 @@ tags: ## Generate ctags for the project with `ghc-tags` design-system: ## Generate the HTML components used by the design system @cabal run -- flora-cli gen-design-system + start-design-sysytem: ## Start storybook.js @cd design; yarn storybook +migration: ## Generate timestamped database migration boilerplate files + @if test -z "$$name"; then \ + echo "Usage: make migration name=some-name"; \ + else \ + migName="`date -u '+%Y%m%d%H%M%S'`_$$name"; \ + fname="migrations/$$migName.sql"; \ + touch "$$fname"; \ + echo "Touched $$fname";\ + fi + help: @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.* ?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @@ -136,14 +183,3 @@ endif .PHONY: all $(MAKECMDGOALS) .DEFAULT_GOAL := help - -.PHONY: migration -migration: ## Generate timestamped database migration boilerplate files - @if test -z "$$name"; then \ - echo "Usage: make migration name=some-name"; \ - else \ - migName="`date -u '+%Y%m%d%H%M%S'`_$$name"; \ - fname="migrations/$$migName.sql"; \ - touch "$$fname"; \ - echo "Touched $$fname";\ - fi diff --git a/README.md b/README.md index e2c6cf0d..253ae23e 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ * 🌓 Dark and light modes * 📱 Mobile user interface -## 📖 Guides +## 📖 Guides Visit https://flora.pm/documentation for explanations on what Flora can do. @@ -63,3 +63,7 @@ To setup a local installation, see [CONTRIBUTING.md#project-setup](https://githu * [Code of Conduct](./CODE_OF_CONDUCT.md) * [Development Wiki](https://github.com/flora-pm/flora-server/wiki) + +## 🫶 Special Collaborations + +We would like to thank our dear friends at Guérilla.Studio ([www](https://guerilla.studio/), [GitHub](https://github.com/GuerillaStudio)) for help with accessibility and CSS integration. diff --git a/app/cli/DesignSystem.hs b/app/cli/DesignSystem.hs index c1c80f39..304f88c6 100644 --- a/app/cli/DesignSystem.hs +++ b/app/cli/DesignSystem.hs @@ -5,10 +5,14 @@ module DesignSystem where import Control.Monad.Trans.Reader (runReaderT) import Data.ByteString.Lazy (ByteString) import Data.ByteString.Lazy qualified as ByteString +import Data.Either.Extra import Data.Foldable (forM_) import Data.Functor.Identity (runIdentity) +import Data.Maybe (fromJust) import Data.Text (Text) import Data.Text.Lazy qualified as TL +import Data.Time.Calendar.OrdinalDate as Time +import Data.Time.Clock (UTCTime (..)) import Data.UUID qualified as UUID import Data.Vector (Vector) import Data.Vector qualified as Vector @@ -18,13 +22,17 @@ import Effectful.Fail import Env import Lucid import PyF (fmt) +import Security.Advisories.Core.HsecId qualified as HsecId +import Security.CVSS +import Advisories.Model.Affected.Types import Distribution.SPDX import Flora.Environment.Config import Flora.Model.Category import Flora.Model.Category qualified as Category import Flora.Model.Package import Flora.Search +import FloraWeb.Components.AdvisoryListItem qualified as Component import FloraWeb.Components.Alert qualified as Component import FloraWeb.Components.CategoryCard qualified as Component import FloraWeb.Components.PackageListItem qualified as Component @@ -71,6 +79,7 @@ components = , ("category-card", ComponentTitle "Category", ComponentName "CategoryCard", categoryCardExample) , ("pagination-area", ComponentTitle "Pagination Area", ComponentName "Pagination", paginationExample) , ("alerts", ComponentTitle "Alerts", ComponentName "Alert", alertsExample) + , ("advisory-preview", ComponentTitle "Advisories", ComponentName "AdvisoryPreviews", packageAdvisoriesExample) ] ----------------------- @@ -100,6 +109,8 @@ packageListItemExample = , "Basic libraries" , mkVersion [4, 16, 0, 0] , License (simpleLicenseExpression BSD_3_Clause) + , Just $ UTCTime (Time.fromMondayStartWeek 2024 32 1) 0 + , Just $ UTCTime (Time.fromMondayStartWeek 2024 60 15) 0 ) categoryCardExample :: FloraHTML @@ -129,9 +140,60 @@ paginationExample = div_ $ do Component.paginationNav 32 1 (SearchPackages "text") alertsExample :: FloraHTML -alertsExample = div_ $ do - div_ $ do - h4_ "Info alert" - Component.info "Info alert" - h4_ "Error alert" - Component.exception "Error alert!" +alertsExample = div_ $ div_ $ do + h4_ "Info alert" + Component.info "Info alert" + h4_ "Error alert" + Component.exception "Error alert!" + +packageAdvisoriesExample :: FloraHTML +packageAdvisoriesExample = do + let advisoryPreviews = + Vector.fromList + [ PackageAdvisoryPreview + { hsecId = fromJust $ HsecId.parseHsecId "HSEC-2023-0009" + , namespace = Namespace "hackage" + , packageName = PackageName "git-annex" + , summary = "git-annex command injection via malicious SSH hostname" + , fixed = True + , published = read "2023-07-25 13:25:42 UTC" + , cvss = fromRight' $ parseCVSS "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H" + } + , PackageAdvisoryPreview + { hsecId = fromJust $ HsecId.parseHsecId "HSEC-2023-0010" + , namespace = Namespace "hackage" + , packageName = PackageName "git-annex" + , summary = "git-annex private data exfiltration to compromised remote" + , fixed = True + , published = read "2023-07-25 13:25:42 UTC" + , cvss = fromRight' $ parseCVSS "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N" + } + , PackageAdvisoryPreview + { hsecId = fromJust $ HsecId.parseHsecId "HSEC-2023-0012" + , namespace = Namespace "hackage" + , packageName = PackageName "git-annex" + , summary = "git-annex checksum exposure to encrypted special remotes" + , fixed = True + , published = read "2023-07-25 13:25:42 UTC" + , cvss = fromRight' $ parseCVSS "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N" + } + , PackageAdvisoryPreview + { hsecId = fromJust $ HsecId.parseHsecId "HSEC-2023-0013" + , namespace = Namespace "hackage" + , packageName = PackageName "git-annex" + , summary = "git-annex plaintext storage of embedded credentials on encrypted remotes" + , fixed = True + , published = read "2023-07-25 13:25:42 UTC" + , cvss = fromRight' $ parseCVSS "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H" + } + , PackageAdvisoryPreview + { hsecId = fromJust $ HsecId.parseHsecId "HSEC-2023-0011" + , namespace = Namespace "hackage" + , packageName = PackageName "git-annex" + , summary = "git-annex GPG decryption attack via compromised remote" + , fixed = True + , published = read "2023-07-25 13:25:42 UTC" + , cvss = fromRight' $ parseCVSS "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N" + } + ] + ul_ [class_ "advisory-list"] $ Vector.forM_ advisoryPreviews (\preview -> Component.advisoryListRow True preview) diff --git a/app/cli/Main.hs b/app/cli/Main.hs index e00c3f82..711510b1 100644 --- a/app/cli/Main.hs +++ b/app/cli/Main.hs @@ -1,40 +1,59 @@ module Main where import Codec.Compression.GZip qualified as GZip +import Control.Monad.Extra (unlessM) import Data.ByteString.Lazy.Char8 qualified as BS +import Data.List.NonEmpty (NonEmpty) import Data.Maybe +import Data.Poolboy (poolboySettingsWith) +import Data.Set (Set) import Data.Text (Text) import Data.Text qualified as Text import Data.Text.Display (display) import DesignSystem (generateComponents) import Distribution.Version (Version) import Effectful +import Effectful.Error.Static (Error, runErrorNoCallStack) import Effectful.Fail -import Effectful.Log +import Effectful.FileSystem +import Effectful.Log (Log, runLog) +import Effectful.Poolboy import Effectful.PostgreSQL.Transact.Effect -import Effectful.Reader.Static (Reader, runReader) +import Effectful.Reader.Static (Reader) +import Effectful.Reader.Static qualified as Reader +import Effectful.State.Static.Shared (State) +import Effectful.State.Static.Shared qualified as State import Effectful.Time (Time, runTime) +import Effectful.Trace (Trace) +import Effectful.Trace qualified as Trace +import GHC.Conc import GHC.Generics (Generic) +import GHC.Records import Log qualified import Log.Backend.StandardOutput qualified as Log +import Monitor.Tracing.Zipkin (Zipkin (..)) import Optics.Core import Options.Applicative import Sel.Hashing.Password qualified as Sel import System.FilePath (()) -import Flora.Environment -import Flora.Environment.Config (PoolConfig (..)) +import Advisories.Import (importAdvisories) +import Advisories.Import.Error (AdvisoryImportError) +import Flora.Environment (getFloraEnv) +import Flora.Environment.Env import Flora.Import.Categories (importCategories) import Flora.Import.Package.Bulk (importAllFilesInRelativeDirectory, importFromIndex) import Flora.Model.BlobIndex.Update qualified as Update import Flora.Model.BlobStore.API -import Flora.Model.Package (PackageName) +import Flora.Model.Package (Namespace, PackageName) import Flora.Model.PackageIndex.Query qualified as Query import Flora.Model.PackageIndex.Types import Flora.Model.PackageIndex.Update qualified as Update import Flora.Model.User import Flora.Model.User.Query qualified as Query import Flora.Model.User.Update +import Flora.Tracing qualified as Tracing +import System.Exit (exitFailure) data Options = Options { cliCommand :: Command @@ -54,6 +73,7 @@ data Command data ProvisionTarget = Categories | TestPackages Text + | Advisories deriving stock (Show, Eq) data UserCreationOptions = UserCreationOptions @@ -66,19 +86,38 @@ data UserCreationOptions = UserCreationOptions deriving stock (Generic, Show, Eq) main :: IO () -main = do - result <- execParser (parseOptions `withInfo` "CLI tool for flora-server") +main = Log.withStdOutLogger $ \logger -> do + cliArgs <- execParser (parseOptions `withInfo` "CLI tool for flora-server") + capabilities <- getNumCapabilities env <- getFloraEnv & runFailIO & runEff - runEff - . runReader env.dbConfig - . runDB env.pool - . runFailIO - . runTime - . ( case env.features.blobStoreImpl of - Just (BlobStoreFS fp) -> runBlobStoreFS fp - _ -> runBlobStorePure - ) - $ runOptions result + runTrace <- + if env.environment == Production + then do + zipkin <- liftIO $ Tracing.newZipkin env.mltp.zipkinHost "flora-cli" + pure $ Trace.runTrace zipkin.zipkinTracer + else pure Trace.runNoTrace + result <- + runEff + . runTrace + . runErrorNoCallStack + . State.evalState (mempty @(Set (Namespace, PackageName, Version))) + . withUnliftStrategy (ConcUnlift Ephemeral Unlimited) + . runDB env.pool + . runFailIO + . runTime + . runPoolboy (poolboySettingsWith capabilities) + . ( case env.features.blobStoreImpl of + Just (BlobStoreFS fp) -> runBlobStoreFS fp + _ -> runBlobStorePure + ) + . runFileSystem + . runLog "flora-cli" logger Log.LogTrace + . Reader.runReader env + $ runOptions cliArgs + case result of + Right _ -> pure () + Left errors -> + error $ show errors parseOptions :: Parser Options parseOptions = @@ -104,6 +143,7 @@ parseProvision = subparser $ command "categories" (pure (Provision Categories) `withInfo` "Load the canonical categories in the system") <> command "test-packages" (parseProvisionTestPackages `withInfo` "Load the test packages in the database") + <> command "advisories" (pure (Provision Advisories) `withInfo` "Load the security advisories database") parseProvisionTestPackages :: Parser Command parseProvisionTestPackages = @@ -150,33 +190,52 @@ parseImportPackageTarball = <*> argument str (metavar "PATH") runOptions - :: ( Reader PoolConfig :> es + :: ( Log :> es + , FileSystem :> es , DB :> es , Time :> es , Fail :> es , IOE :> es , BlobStoreAPI :> es + , State (Set (Namespace, PackageName, Version)) :> es + , Poolboy :> es + , Error (NonEmpty AdvisoryImportError) :> es + , Trace :> es + , HasField "metrics" r Metrics + , HasField "mltp" r MLTP + , Reader r :> es ) => Options -> Eff es () runOptions (Options (Provision Categories)) = importCategories +runOptions (Options (Provision Advisories)) = do + dataDir <- getXdgDirectory XdgData "" + let advisoriesDirectory = dataDir "security-advisories" + unlessM (doesDirectoryExist advisoriesDirectory) $ do + Log.logAttention_ $ Text.pack $ "Could not find " <> advisoriesDirectory <> ". Clone https://github.com/haskell/security-advisories.git at this location." + liftIO exitFailure + importAdvisories advisoriesDirectory runOptions (Options (Provision (TestPackages repository))) = importFolderOfCabalFiles "./test/fixtures/Cabal/" repository runOptions (Options (CreateUser opts)) = do let username = opts ^. #username email = opts ^. #email canLogin = opts ^. #canLogin - password <- liftIO $ Sel.hashText opts.password - if opts ^. #isAdmin - then - addAdmin AdminCreationForm{..} - >>= \admin -> - if canLogin - then pure () - else lockAccount (admin ^. #userId) - else do - templateUser <- mkUser UserCreationForm{..} - let user = if canLogin then templateUser else templateUser & #userFlags % #canLogin .~ False - insertUser user + mUser <- Query.getUserByEmail email + case mUser of + Just _ -> pure () + Nothing -> do + password <- liftIO $ Sel.hashText opts.password + if opts ^. #isAdmin + then + addAdmin AdminCreationForm{..} + >>= \admin -> + if canLogin + then pure () + else lockAccount admin.userId + else do + templateUser <- mkUser UserCreationForm{..} + let user = if canLogin then templateUser else templateUser & #userFlags % #canLogin .~ False + insertUser user runOptions (Options GenDesignSystemComponents) = generateComponents runOptions (Options (ImportPackages path repository)) = importFolderOfCabalFiles path repository runOptions (Options (ImportIndex path repository)) = importIndex path repository @@ -184,39 +243,69 @@ runOptions (Options (ProvisionRepository name url description)) = provisionRepos runOptions (Options (ImportPackageTarball pname version path)) = importPackageTarball pname version path provisionRepository :: (DB :> es, IOE :> es) => Text -> Text -> Text -> Eff es () -provisionRepository name url description = Update.createPackageIndex name url description Nothing +provisionRepository name url description = Update.upsertPackageIndex name url description Nothing -importFolderOfCabalFiles :: (Reader PoolConfig :> es, DB :> es, IOE :> es) => FilePath -> Text -> Eff es () -importFolderOfCabalFiles path repository = Log.withStdOutLogger $ \appLogger -> do +importFolderOfCabalFiles + :: ( FileSystem :> es + , Time :> es + , Log :> es + , Poolboy :> es + , DB :> es + , IOE :> es + , State (Set (Namespace, PackageName, Version)) :> es + , HasField "metrics" r Metrics + , HasField "mltp" r MLTP + , Reader r :> es + ) + => FilePath + -> Text + -> Eff es () +importFolderOfCabalFiles path repository = do user <- fromJust <$> Query.getUserByUsername "hackage-user" mPackageIndex <- Query.getPackageIndexByName repository case mPackageIndex of Nothing -> error $ Text.unpack $ "Package index " <> repository <> " not found in the database!" Just packageIndex -> - importAllFilesInRelativeDirectory appLogger (user ^. #userId) (repository, packageIndex.url) (path Text.unpack repository) True + importAllFilesInRelativeDirectory (user ^. #userId) (repository, packageIndex.url) (path Text.unpack repository) -importIndex :: (Reader PoolConfig :> es, DB :> es, IOE :> es) => FilePath -> Text -> Eff es () -importIndex path repository = Log.withStdOutLogger $ \logger -> do +importIndex + :: ( Time :> es + , Log :> es + , Poolboy :> es + , DB :> es + , IOE :> es + , State (Set (Namespace, PackageName, Version)) :> es + , HasField "metrics" r Metrics + , HasField "mltp" r MLTP + , Reader r :> es + ) + => FilePath + -> Text + -> Eff es () +importIndex path repository = do user <- fromJust <$> Query.getUserByUsername "hackage-user" mPackageIndex <- Query.getPackageIndexByName repository case mPackageIndex of Nothing -> error $ Text.unpack $ "Package index " <> repository <> " not found in the database!" - Just packageIndex -> - importFromIndex logger (user ^. #userId) (repository, packageIndex.url) path True + Just _ -> + importFromIndex (user ^. #userId) repository path importPackageTarball - :: (BlobStoreAPI :> es, Time :> es, IOE :> es, DB :> es) + :: ( Log :> es + , BlobStoreAPI :> es + , IOE :> es + , DB :> es + ) => PackageName -> Version -> FilePath -> Eff es () -importPackageTarball pname version path = Log.withStdOutLogger $ \logger -> do +importPackageTarball pname version path = do contents <- liftIO $ GZip.decompress <$> BS.readFile path - runLog "flora-cli" logger Log.LogTrace $ do - res <- Update.insertTar pname version contents - case res of - Right hash -> Log.logInfo_ $ "Insert tarball with root hash: " <> display hash - Left err -> Log.logAttention_ $ display err + res <- Update.insertTar pname version contents + case res of + Right hash -> Log.logInfo_ $ "Insert tarball with root hash: " <> display hash + Left err -> Log.logAttention_ $ display err withInfo :: Parser a -> String -> ParserInfo a withInfo opts desc = info (helper <*> opts) $ progDesc desc diff --git a/app/server/Main.hs b/app/server/Main.hs index 8398c49e..04297315 100644 --- a/app/server/Main.hs +++ b/app/server/Main.hs @@ -1,9 +1,81 @@ +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} + module Main where -import FloraWeb.Server +import Control.Monad (forM_, unless) +import Data.Function ((&)) +import Data.List qualified as List +import Data.Set qualified as Set +import Data.Text (Text) +import Data.Text qualified as Text +import Data.Vector (Vector) +import Data.Vector qualified as Vector +import Database.PostgreSQL.Entity +import Database.PostgreSQL.Entity.DBT +import Database.PostgreSQL.Entity.Types (field) +import Database.PostgreSQL.Simple (Only (..)) +import Effectful +import Effectful.Fail (runFailIO) +import Effectful.Log (Log, runLog) +import Effectful.PostgreSQL.Transact.Effect (DB, dbtToEff, runDB) +import Log qualified +import System.Exit import System.IO +import Flora.Environment (getFloraEnv) +import Flora.Environment.Env (FloraEnv (..), MLTP (..)) +import Flora.Logging qualified as Logging +import Flora.Model.PackageIndex.Types +import FloraJobs.Scheduler (checkIfIndexRefreshJobIsPlanned) +import FloraWeb.Server + main :: IO () main = do hSetBuffering stdout LineBuffering + env <- getFloraEnv & runFailIO & runEff + runEff $ do + let withLogger = Logging.makeLogger env.mltp.logger + withLogger $ \appLogger -> + runDB env.pool + . withUnliftStrategy (ConcUnlift Ephemeral Unlimited) + $ runLog + "flora-server" + appLogger + Log.LogTrace + $ do + checkRepositoriesAreConfigured + checkIfIndexRefreshJobIsPlanned env.pool runFlora + +checkRepositoriesAreConfigured :: (DB :> es, Log :> es, IOE :> es) => Eff es () +checkRepositoriesAreConfigured = do + let expectedRepositories = Set.fromList ["hackage", "cardano", "horizon"] + (result :: (Vector (Only Text))) <- + dbtToEff $ + query_ + Select + (_selectWithFields @PackageIndex [[field| repository |]]) + let actualRepositories = Set.fromList $ Vector.toList $ Vector.map fromOnly result + let missingExpectedIndexes = Set.difference expectedRepositories actualRepositories + let unexpectedIndexes = Set.difference actualRepositories expectedRepositories + let (messages :: Vector Text) = + let missingIndexMessage = + if not $ null missingExpectedIndexes + then + "Database validation failed: Expected package indexes: " + <> mconcat (List.intersperse ", " (Set.toList missingExpectedIndexes)) + <> "." + else "" + unexpectedIndexMessage = + if not $ null unexpectedIndexes + then + Text.pack "Database validation failed: Unexpected package indexes: " + <> mconcat (List.intersperse ", " (Set.toList unexpectedIndexes)) + <> "." + else "" + in Vector.fromList $ filter (/= "") [missingIndexMessage, unexpectedIndexMessage] + unless (null messages) $ do + forM_ messages Log.logAttention_ + liftIO exitFailure diff --git a/assets/.prettierignore b/assets/.prettierignore deleted file mode 100644 index 51e3286f..00000000 --- a/assets/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -# Ignore artifacts: -node_modules diff --git a/assets/.prettierrc.json b/assets/.prettierrc.json deleted file mode 100644 index 0967ef42..00000000 --- a/assets/.prettierrc.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/assets/.stylelintignore b/assets/.stylelintignore new file mode 100644 index 00000000..ba7672d5 --- /dev/null +++ b/assets/.stylelintignore @@ -0,0 +1 @@ +css/prism.css diff --git a/assets/.stylelintrc.json b/assets/.stylelintrc.json index 26cba3a9..38e0a7d4 100644 --- a/assets/.stylelintrc.json +++ b/assets/.stylelintrc.json @@ -1,6 +1,18 @@ { - "extends": ["stylelint-config-standard", "stylelint-config-prettier"], + "extends": ["stylelint-config-standard"], "rules": { - "hue-degree-notation": "number" + "hue-degree-notation": "number", + "media-feature-range-notation": "prefix", + "import-notation": "string", + "selector-class-pattern": null, + "at-rule-no-unknown": [ + true, + { + "ignoreAtRules": [ + "/^design-token-utils/", + "/^custom-media/" + ] + } + ] } } diff --git a/assets/css/1-core/1-core.css b/assets/css/1-core/1-core.css deleted file mode 100644 index 3f90d982..00000000 --- a/assets/css/1-core/1-core.css +++ /dev/null @@ -1,42 +0,0 @@ -/* stylelint-disable selector-class-pattern */ -/* stylelint-disable declaration-block-no-redundant-longhand-properties */ - -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; - -body { - background-color: var(--background-color); - color: var(--text-color); -} - -.container { - width: auto; - max-width: 1300px; - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} - -.container-small { - margin-right: auto; - margin-left: auto; - padding-right: 1rem; - padding-left: 1rem; - max-width: 28rem; - width: calc(100% - 2rem); -} - -a { - text-decoration: none; - color: var(--link-color); -} - -.larger-container { - margin-left: auto; - margin-right: auto; - max-width: 80rem; - padding-left: 0.5rem; - padding-right: 0.5rem; -} diff --git a/assets/css/1-core/1-reset.css b/assets/css/1-core/1-reset.css new file mode 100644 index 00000000..10760ffc --- /dev/null +++ b/assets/css/1-core/1-reset.css @@ -0,0 +1,348 @@ +/* Reset inherited from TailwindCSS/base package */ + +/* + TODO: See what's really necessary, the goal is not to micromanage the browser (cf. https://andy-bell.co.uk/be-the-browsers-mentor-not-its-micromanager/). + That could be a good inspiration: https://piccalil.li/blog/a-more-modern-css-reset/ could be an inspiration) +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ +*, +::before, +::after { + box-sizing: border-box; /* 1 */ + border-width: 0; /* 2 */ + border-style: solid; /* 2 */ + border-color: currentcolor; /* 2 */ +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +*/ +html, +:host { + line-height: 1.5; /* 1 */ + /* stylelint-disable-next-line property-no-vendor-prefix */ + -webkit-text-size-adjust: 100%; /* 2 */ + text-size-adjust: 100%; /* 2 */ + tab-size: 4; /* 3 */ + font-feature-settings: normal; + font-variation-settings: normal; + -webkit-tap-highlight-color: transparent; +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ +body { + margin: 0; /* 1 */ + line-height: inherit; /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ +hr { + height: 0; /* 1 */ + color: inherit; /* 2 */ + border-top-width: 1px; /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ +abbr[title] { + /* stylelint-disable-next-line property-no-vendor-prefix */ + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ +b, +strong { + font-weight: bolder; +} + +/* +1. Correct the odd `em` font sizing in all browsers. +*/ +code, +kbd, +samp, +pre { + font-family: var(--font-monospace); + font-feature-settings: normal; + font-variation-settings: normal; + font-size: 1em; /* 1 */ +} + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ +table { + text-indent: 0; /* 1 */ + border-color: inherit; /* 2 */ + border-collapse: collapse; /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ +button, +input, +optgroup, +select, +textarea { + font-feature-settings: inherit; + font-variation-settings: inherit; + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: inherit; /* 1 */ + color: inherit; /* 1 */ + margin: 0; /* 2 */ + padding: 0; /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ +button, +[type="button"], +[type="reset"], +[type="submit"] { + appearance: button; + background-color: transparent; + background-image: none; +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ +[type="search"] { + /* stylelint-disable-next-line property-no-vendor-prefix */ + -webkit-appearance: textfield; + appearance: textfield; + outline-offset: -2px; +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ +::-webkit-search-decoration { + appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ +::-webkit-file-upload-button { + appearance: button; + font: inherit; +} + +/* +Add the correct display in Chrome and Safari. +*/ +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ +input::placeholder, +textarea::placeholder { + opacity: 1; /* 1 */ + color: #9ca3af; /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; /* 1 */ + vertical-align: middle; /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ +img, +video { + max-width: 100%; + height: auto; +} + +/* +Ensure the default browser behavior of the `hidden` attribute. +*/ +[hidden] { + display: none; +} diff --git a/assets/css/1-core/2-core.css b/assets/css/1-core/2-core.css new file mode 100644 index 00000000..78917d36 --- /dev/null +++ b/assets/css/1-core/2-core.css @@ -0,0 +1,53 @@ +/* stylelint-disable selector-class-pattern */ +/* stylelint-disable declaration-block-no-redundant-longhand-properties */ + +html { + font-family: var(--font-sans); +} + +body { + background-color: var(--background-color); + color: var(--text-color); +} + +.container { + width: calc(100% - 2rem); + max-width: 1300px; + padding-right: 1rem; + padding-left: 1rem; + margin-right: auto; + margin-left: auto; +} + +@media (--viewport-sm) { + .container { + max-width: 640px; + } +} + +@media (--viewport-md) { + .container { + max-width: 768px; + } +} + +@media (--viewport-lg) { + .container { + max-width: 1024px; + } +} + +@media (--viewport-xl) { + .container { + max-width: 1280px; + } +} + +.container--small { + max-width: 28rem; +} + +a { + text-decoration: none; + color: var(--link-color); +} diff --git a/assets/css/1-core/2-variables.css b/assets/css/1-core/2-variables.css deleted file mode 100644 index 3473979f..00000000 --- a/assets/css/1-core/2-variables.css +++ /dev/null @@ -1,121 +0,0 @@ -/* stylelint-disable selector-class-pattern */ -/* stylelint-disable declaration-block-no-redundant-longhand-properties */ - -:root { - /* Primary Blue */ - --blue-15: hsl(221 30% 15%); - --blue-20: hsl(221 30% 20%); - --blue-30: hsl(221 30% 30%); - --blue-40: hsl(221 30% 40%); - --blue-60: hsl(221 30% 60%); - --blue-70: hsl(221 30% 70%); - --blue-80: hsl(221 30% 80%); - - /* Secondary Purple */ - --purple-20: hsl(294 40% 20%); - --purple-30: hsl(294 40% 30%); - --purple-40: hsl(294 40% 40%); - --purple-60: hsl(294 40% 60%); - --purple-70: hsl(294 40% 70%); - --purple-80: hsl(294 40% 80%); - - /* Neutral Grey */ - --gray-10: hsl(221 9% 10%); - --gray-20: hsl(221 9% 20%); - --gray-30: hsl(221 9% 30%); - --gray-80: hsl(221 9% 80%); - --gray-90: hsl(221 9% 90%); - --gray-100: hsl(221 9% 100%); - - /* Version */ - --green-30: hsl(140 100% 30%); - --green-40: hsl(140 100% 40%); - --red-60: hsl(358 80% 60%); - - /* Light backgrounds */ - --light-blue-background: hsl(210 100% 96%); - --light-red-background: hsl(355 73% 97%); - --light-green: hsl(206 41% 97%); - - /* Dark foregrounds to go with backgrounds */ - --dark-blue: hsl(220 64% 33%); - --dark-red: hsl(0 69% 36%); - --background-color: var(--gray-100); - --brand-border: var(--gray-100); - --category-card-name-color: var(--gray-30); - --category-card-synopsis-color: var(--blue-20); - --category-card-background-hover: var(--gray-90); - --error-button: var(--purple-20); - --error-code: var(--purple-20); - --link-color: var(--blue-40); - --link-hover-color: hsl(220 13% 91%); - --navbar-background-color: var(--purple-20); - --navbar-border-color: var(--gray-90); - --navbar-search-color: var(--gray-10); - --navbar-search-background-color: var(--gray-100); - --navbar-search-border-color: var(--gray-100); - --package-list-item-background-hover-color: var(--gray-90); - --package-list-item-name-color: var(--blue-20); - --package-list-item-synopsis-color: black; - --package-list-item-metadata-color: black; - --package-list-item-version-color: var(--green-30); - --package-list-item-component-color: var(--gray-80); - --search-bar-color: hsl(221 39% 11%); - --search-bar-background-color: var(--gray-100); - --search-bar-background-hover-color: white; - --search-bar-background-focus-color: white; - --search-bar-focus-border-color: var(--purple-30); - --readme-pre-background-color: var(--gray-90); - --readme-code-background-color: var(--gray-90); - --changelog-pre-background-color: var(--gray-90); - --changelog-code-background-color: var(--gray-90); - - /* --install-string-border: hsl(215 28% 17%); */ - --compiler-badge-background: hsl(218 12% 84%); - --text-color: var(--blue-20); - --main-page-button-background: var(--gray-100); - --main-page-button-focus-border-color: var(--purple-30); - --main-page-button-divider: var(--purple-20); - --deprecated-version: var(--red-60); - --breadcrumb-color: var(--gray-30); -} - -html[data-theme="dark"] { - --background-color: var(--blue-20); - --brand-border: var(--purple-30); - --category-card-name-color: var(--purple-60); - --category-card-synopsis-color: var(--gray-90); - --category-card-background-hover: var(--blue-15); - --error-button: var(--purple-30); - --error-code: var(--gray-90); - --link-color: var(--purple-60); - --link-hover-color: var(--blue-15); - --navbar-background-color: var(--blue-15); - --navbar-border-color: transparent; - --navbar-search-color: var(--gray-90); - --navbar-search-background-color: var(--blue-20); - --navbar-search-border-color: var(--blue-20); - --package-list-item-background-hover-color: var(--blue-15); - --package-list-item-name-color: var(--purple-60); - --package-list-item-metadata-color: var(--gray-90); - --package-list-item-synopsis-color: var(--gray-90); - --package-list-item-version-color: var(--green-40); - --search-bar-color: var(--gray-90); - --search-bar-background-color: var(--blue-20); - --search-bar-background-hover-color: var(--blue-20); - --search-bar-background-focus-color: var(--blue-20); - --search-bar-focus-border-color: var(--purple-30); - --readme-pre-background-color: var(--blue-30); - --readme-code-background-color: var(--blue-30); - --changelog-pre-background-color: var(--blue-30); - --changelog-code-background-color: var(--blue-30); - - /* --install-string-border: hsl(215 28% 17%); */ - --compiler-badge-background: var(--blue-30); - --text-color: var(--gray-80); - --main-page-button-background: var(--blue-20); - --main-page-button-focus-border-color: var(--purple-30); - --main-page-button-divider: var(--blue-15); - --deprecated-version: var(--red-60); - --breadcrumb-color: hsl(221 9% 60%); -} diff --git a/assets/css/1-core/3-variables.css b/assets/css/1-core/3-variables.css new file mode 100644 index 00000000..a34d9c90 --- /dev/null +++ b/assets/css/1-core/3-variables.css @@ -0,0 +1,152 @@ +/* stylelint-disable declaration-block-no-redundant-longhand-properties, selector-class-pattern */ + +:root { + @design-token-utils (custom-properties); + + + /* Responsive breaking points */ + @custom-media --viewport-sm (min-width: 640px); + @custom-media --viewport-md (min-width: 768px); + @custom-media --viewport-lg (min-width: 1024px); + @custom-media --viewport-xl (min-width: 1280px); + @custom-media --viewport-2xl (min-width: 1536px); + + /* Primary Blue */ + --blue-15: hsl(221 30% 15%); + --blue-20: hsl(221 30% 20%); + --blue-30: hsl(221 30% 30%); + --blue-40: hsl(221 30% 40%); + --blue-60: hsl(221 30% 60%); + --blue-70: hsl(221 30% 70%); + --blue-80: hsl(221 30% 80%); + + /* Secondary Purple */ + --purple-20: hsl(294 40% 20%); + --purple-30: hsl(294 40% 30%); + --purple-40: hsl(294 40% 40%); + --purple-60: hsl(294 40% 60%); + --purple-70: hsl(294 40% 70%); + --purple-80: hsl(294 40% 80%); + + /* Neutral Grey */ + --gray-10: hsl(221 9% 10%); + --gray-20: hsl(221 9% 20%); + --gray-30: hsl(221 9% 30%); + --gray-80: hsl(221 9% 80%); + --gray-90: hsl(221 9% 90%); + --gray-100: hsl(221 9% 100%); + + /* Version */ + --green-30: hsl(140 100% 30%); + --green-40: hsl(140 100% 40%); + --red-60: hsl(358 80% 60%); + + /* Light backgrounds */ + --light-blue-background: hsl(210 100% 96%); + --light-red-background: hsl(355 73% 97%); + --light-green: hsl(206 41% 97%); + --pale-blue: hsl(184 55% 77%); + --pale-green: hsl(120 93% 79%); + + /* Dark foregrounds to go with backgrounds */ + --dark-blue: hsl(220 64% 33%); + --dark-red: hsl(0 69% 36%); + --background-color: var(--gray-100); + --brand-border: var(--gray-100); + --category-card-name-color: var(--gray-30); + --category-card-synopsis-color: var(--blue-20); + --category-card-background-hover: var(--gray-90); + --error-button: var(--purple-20); + --error-code: var(--purple-20); + --link-color: var(--blue-40); + --link-hover-color: hsl(220 13% 91%); + --navbar-background-color: var(--purple-20); + --navbar-border-color: var(--gray-90); + --navbar-search-color: var(--gray-10); + --navbar-search-background-color: var(--gray-100); + --navbar-search-border-color: var(--gray-100); + --package-list-item-background-hover-color: var(--gray-90); + --package-list-item-name-color: var(--blue-20); + --package-list-item-synopsis-color: black; + --package-list-item-metadata-color: black; + --package-list-item-version-color: var(--green-30); + --package-list-item-component-color: var(--gray-80); + --search-bar-color: hsl(221 39% 11%); + --search-bar-background-color: var(--gray-100); + --search-bar-background-hover-color: white; + --search-bar-background-focus-color: white; + --search-bar-focus-border-color: var(--purple-30); + --readme-pre-background-color: var(--gray-90); + --readme-code-background-color: var(--gray-90); + --changelog-pre-background-color: var(--gray-90); + --changelog-code-background-color: var(--gray-90); + + /* --install-string-border: hsl(215 28% 17%); */ + --compiler-badge-background: hsl(218 12% 84%); + --text-color: var(--blue-20); + --main-page-button-background: var(--gray-100); + --main-page-button-focus-border-color: var(--purple-30); + --main-page-button-divider: var(--purple-20); + --deprecated-version: var(--red-60); + --breadcrumb-color: var(--gray-30); + --severity-critical: var(--red-60); + --severity-high: orange; + --severity-medium: gold; + --severity-low: var(--pale-blue); + --severity-none: hsl(156 100% 99%); + --severity-rating-colour: black; + + /* Focus ring (aka outline) */ + --outline-offset-shadow: 0 0 #0000; + --outline-shadow: 0 0 #0000; + + /* Shadows */ + --shadow-simple: 0 1px 3px 0 rgb(0 0 0 / 10%), 0 1px 2px -1px rgb(0 0 0 / 10%); + --shadow-colored: 0 1px 3px 0 var(--purple-30), 0 1px 2px -1px var(--purple-30); +} + +html[data-theme="dark"] { + --background-color: var(--blue-20); + --brand-border: var(--purple-30); + --category-card-name-color: var(--purple-60); + --category-card-synopsis-color: var(--gray-90); + --category-card-background-hover: var(--blue-15); + --error-button: var(--purple-30); + --error-code: var(--gray-90); + --link-color: var(--purple-60); + --link-hover-color: var(--blue-15); + --navbar-background-color: var(--blue-15); + --navbar-border-color: transparent; + --navbar-search-color: var(--gray-90); + --navbar-search-background-color: var(--blue-20); + --navbar-search-border-color: var(--blue-20); + --package-list-item-background-hover-color: var(--blue-15); + --package-list-item-name-color: var(--purple-60); + --package-list-item-metadata-color: var(--gray-90); + --package-list-item-synopsis-color: var(--gray-90); + --package-list-item-version-color: var(--green-40); + --search-bar-color: var(--gray-90); + --search-bar-background-color: var(--blue-20); + --search-bar-background-hover-color: var(--blue-20); + --search-bar-background-focus-color: var(--blue-20); + --search-bar-focus-border-color: var(--purple-30); + --readme-pre-background-color: var(--blue-30); + --readme-code-background-color: var(--blue-30); + --changelog-pre-background-color: var(--blue-30); + --changelog-code-background-color: var(--blue-30); + + /* --install-string-border: hsl(215 28% 17%); */ + --compiler-badge-background: var(--blue-30); + --text-color: var(--gray-80); + --main-page-button-background: var(--blue-20); + --main-page-button-focus-border-color: var(--purple-30); + --main-page-button-divider: var(--blue-15); + --deprecated-version: var(--red-60); + --breadcrumb-color: hsl(221 9% 60%); + --severity-critical: var(--red-60); + --severity-high: orange; + --severity-medium: gold; + --severity-low: var(--pale-blue); + --severity-none: hsl(156 100% 99%); + --severity-rating-colour: black; +} diff --git a/assets/css/2-components/1-navbar.css b/assets/css/2-components/1-navbar.css index 96c44b73..e09dcdb7 100644 --- a/assets/css/2-components/1-navbar.css +++ b/assets/css/2-components/1-navbar.css @@ -2,135 +2,145 @@ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ .top-navbar { - background-color: var(--navbar-background-color); - border-bottom-width: 1px; - border-color: var(--navbar-border-color); - margin-bottom: 0.75rem; - z-index: 10; - top: 0; - left: 0; - position: sticky; - - .navbar-content { - padding-left: 2rem; - padding-right: 2rem; - margin-left: auto; - margin-right: auto; - justify-content: space-between; - height: 4rem; - display: flex; - - .navbar-dropdown { - display: none; - } - - .navbar-left { - display: flex; - align-items: center; - height: 4rem; - - form { - max-width: 24rem; - width: 100%; - display: inline-flex; - margin-left: 1.25rem; - } - } - - .brand { - display: flex; - flex-shrink: 0; - border-bottom-width: 4px; - padding-bottom: 0.5rem; /* 8px */ - border-color: var(--brand-border); - - a { - color: rgb(255 255 255); - font-weight: 700; - text-decoration: none; - } - } - } - - .navbar-right { - align-items: center; - background-color: var(--navbar-background-color); - display: flex; - flex-direction: row; - left: 0; - position: relative; - top: 0; - width: auto; - - .navbar-menu-button { - display: none; - } - } - - .navbar-link { - display: inline-flex; - color: rgb(255 255 255); - font-weight: 700; - padding-top: 0.75rem; - padding-bottom: 0.75rem; - align-items: center; - margin-left: 1rem; - margin-right: 1rem; - } - - .navbar-search { - @apply rounded-full w-full mr-3 pl-3 py-1 px-1; - @apply leading-tight focus:outline-none border border-2; - - border-color: var(--navbar-search-border-color); - color: var(--navbar-search-color); - background-color: var(--navbar-search-background-color); - } - - .navbar-search:focus { - border-color: var(--search-bar-focus-border-color); - } - - #navbar-search-package-name { - padding-left: 20px; - } + background-color: var(--navbar-background-color); + border-bottom-width: 1px; + border-color: var(--navbar-border-color); + margin-bottom: 0.75rem; + z-index: 10; + top: 0; + left: 0; + position: sticky; + + .navbar-content { + padding-left: 2rem; + padding-right: 2rem; + margin-left: auto; + margin-right: auto; + justify-content: space-between; + height: 4rem; + display: flex; + + .navbar-dropdown { + display: none; + } + + .navbar-left { + display: flex; + align-items: center; + height: 4rem; + + form { + max-width: 24rem; + width: 100%; + display: inline-flex; + margin-left: 1.25rem; + } + } + + .brand { + display: flex; + flex-shrink: 0; + border-bottom-width: 4px; + padding-bottom: 0.5rem; /* 8px */ + border-color: var(--brand-border); + + a { + color: rgb(255 255 255); + font-weight: 700; + text-decoration: none; + } + } + } + + .navbar-right { + align-items: center; + background-color: var(--navbar-background-color); + display: flex; + flex-direction: row; + left: 0; + position: relative; + top: 0; + width: auto; + + .navbar-menu-button { + display: none; + } + } + + .navbar-link { + display: inline-flex; + color: rgb(255 255 255); + font-weight: 700; + padding-top: 0.75rem; + padding-bottom: 0.75rem; + align-items: center; + margin-left: 1rem; + margin-right: 1rem; + } + + .navbar-search { + background-color: var(--navbar-search-background-color); + border-color: var(--navbar-search-border-color); + border-radius: 9999px; + border-width: 2px; + color: var(--navbar-search-color); + line-height: 1.25; + margin-right: 0.75rem; + padding-bottom: 0.25rem; + padding-left: 0.75rem; + padding-right: 0.25rem; + padding-top: 0.25rem; + width: 100%; + } + + .navbar-search:focus { + border-color: var(--search-bar-focus-border-color); + } + + #navbar-search-package-name { + padding-left: 20px; + } + + .navbar-themeBtn { + flex-shrink: 0; + } } @media (max-width: 48rem) { - .top-navbar { - .navbar-content { - display: flex; - padding-left: 1rem; - padding-right: 1rem; - - .navbar-left { - display: none; - } - - .navbar-right { - display: none; - } - - .navbar-dropdown { - align-items: center; - display: flex; - - .navbar-dropdown__button { - color: rgb(255 255 255); - font-weight: 700; - text-decoration: none; - } - - .navbar-dropdown__menu { - background-color: var(--navbar-background-color); - display: flex; - flex-direction: column; - width: 100%; - left: 0; - top: 100%; - position: absolute; - } - } - } - } + .top-navbar { + .navbar-content { + display: flex; + padding-left: 1rem; + padding-right: 1rem; + + .navbar-left { + display: none; + } + + .navbar-right { + display: none; + } + + .navbar-dropdown { + align-items: center; + display: flex; + + .navbar-dropdown__button { + color: rgb(255 255 255); + font-weight: 700; + text-decoration: none; + } + + .navbar-dropdown__menu { + background-color: var(--navbar-background-color); + display: flex; + flex-direction: column; + width: 100%; + left: 0; + top: 100%; + position: absolute; + } + } + } + } } diff --git a/assets/css/2-components/10-error.css b/assets/css/2-components/10-error.css new file mode 100644 index 00000000..176a71fd --- /dev/null +++ b/assets/css/2-components/10-error.css @@ -0,0 +1,45 @@ +.error { + margin-left: auto; + margin-right: auto; + max-width: max-content; + padding-top: 6rem; + text-wrap: nowrap; +} + +p.error-zone { + display: flex; + flex-flow: row wrap; + color: var(--error-code); + + > * { + font-size: 3rem; + font-weight: 800; + line-height: 1; + letter-spacing: -0.025rem; + } +} + +.error-code { + color: var(--error-code); + font-size: 3rem; + line-height: 1; + font-weight: 800; + border-color: rgb(229 231 235 / 100%); + border-right-width: 1px; + margin-right: 1.5rem; + padding-right: 1.5rem; +} + +.error-message { + color: var(--error-code); + font-size: 3rem; + font-weight: 800; + letter-spacing: -0.025rem; + line-height: 1; +} + +.error-page-button { + background-color: var(--error-button); + border-color: transparent; + margin-top: 2.5rem; +} diff --git a/assets/css/2-components/11-advisory-list-item.css b/assets/css/2-components/11-advisory-list-item.css new file mode 100644 index 00000000..70095f2d --- /dev/null +++ b/assets/css/2-components/11-advisory-list-item.css @@ -0,0 +1,105 @@ +.package-advisory-list-item { + display: grid; + align-items: center; + padding: 1rem; +} + +.package-advisory-list-item__inline-published { + margin-left: 1rem; + display: inline; +} + +.package-advisory-list-item__package { + font-weight: bolder; + order: 1; +} + +.package-advisory-list-item__hsec-id { + order: 2; +} + +.package-advisory-list-item__published { + order: 3; + display: none; +} + +.package-advisory-list-item__attributes { + order: 4; +} + +.package-advisory-list-item__summary { + font-weight: bolder; + order: 5; +} + +.advisory-list-item__severity-pill { + border-radius: 0.5rem; + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.advisory-list-item__severity-none { + background-color: var(--severity-none); + color: var(--severity-rating-colour); +} + +.advisory-list-item__severity-low { + background-color: var(--severity-low); + color: var(--severity-rating-colour); + padding-right: 0.5rem; +} + +.advisory-list-item__severity-medium { + background-color: var(--severity-medium); + color: var(--severity-rating-colour); +} + +.advisory-list-item__severity-high { + background-color: var(--severity-high); + color: var(--severity-rating-colour); +} + +.advisory-list-item__severity-critical { + background-color: var(--severity-critical); + color: var(--severity-rating-colour); +} + +.advisory-list-item__fix-available { + background-color: var(--pale-green); + color: var(--severity-rating-colour); + margin-left: 0.5rem; + +} + +@media only screen and (--viewport-md) { + .package-advisory-list-item { + display: table-row; + font-size: 1.10rem; + line-height: 2rem; + } + + .package-advisory-list-item__hsec-id { + order: initial; + display: table-cell; + + .package-advisory-list-item__inline-published { + display: none; + } + } + + .package-advisory-list-item__summary { + order: initial; + display: table-cell; + } + + .package-advisory-list-item__published { + order: initial; + display: table-cell; + } + + .package-advisory-list-item__attributes { + order: initial; + display: table-cell; + font-size: 0.90em; + } +} diff --git a/assets/css/2-components/12-headline.css b/assets/css/2-components/12-headline.css new file mode 100644 index 00000000..100f1861 --- /dev/null +++ b/assets/css/2-components/12-headline.css @@ -0,0 +1,4 @@ +/* stylelint-disable declaration-block-no-redundant-longhand-properties, selector-class-pattern */ +.headline { + word-break: keep-all; +} diff --git a/assets/css/2-components/2-package-component.css b/assets/css/2-components/2-package-component.css index 230795e9..b5786731 100644 --- a/assets/css/2-components/2-package-component.css +++ b/assets/css/2-components/2-package-component.css @@ -1,19 +1,19 @@ summary.package-component { - cursor: pointer; - font-size: 1.25rem; - line-height: 1.25rem; - margin-top: 1rem; - margin-bottom: 1rem; - padding-left: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; + cursor: pointer; + font-size: 1.25rem; + line-height: 1.25rem; + margin-top: 1rem; + margin-bottom: 1rem; + padding-left: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; } summary.package-component > * { - display: inline; + display: inline; } summary.package-component:hover { - background-color: var(--package-list-item-background-hover-color); - border-radius: 6px; + background-color: var(--package-list-item-background-hover-color); + border-radius: 6px; } diff --git a/assets/css/2-components/3-breadcrumb.css b/assets/css/2-components/3-breadcrumb.css index 9df6cc42..dcb32a54 100644 --- a/assets/css/2-components/3-breadcrumb.css +++ b/assets/css/2-components/3-breadcrumb.css @@ -1,8 +1,8 @@ /* stylelint-disable selector-class-pattern */ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ .breadcrumb { - height: 1.5ex; - width: auto; - display: inline; - color: var(--breadcrumb-color); + height: 1.5ex; + width: auto; + display: inline; + color: var(--breadcrumb-color); } diff --git a/assets/css/2-components/4-license.css b/assets/css/2-components/4-license.css index a8a87f48..473e40aa 100644 --- a/assets/css/2-components/4-license.css +++ b/assets/css/2-components/4-license.css @@ -2,8 +2,8 @@ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ .license-icon { - height: 1.25rem; - width: 1.25rem; - display: inline; - margin-top: -0.25rem; + height: 1.25rem; + width: 1.25rem; + display: inline; + margin-top: -0.25rem; } diff --git a/assets/css/2-components/5-primary-search.css b/assets/css/2-components/5-primary-search.css index 46ad7145..ca2d797c 100644 --- a/assets/css/2-components/5-primary-search.css +++ b/assets/css/2-components/5-primary-search.css @@ -2,60 +2,60 @@ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ .main-search { - background-color: var(--search-bar-background-color); - border-radius: 0.75rem; - border-width: 2px; - display: flex; - font-size: 1.5rem; - justify-content: center; - line-height: 2rem; - max-width: 28rem; - outline-offset: -2px; - overflow: hidden; - padding: 0.5rem; - - .search-bar { - background-color: var(--search-bar-background-color); - color: var(--search-bar-color); - display: block; - margin-left: 0.5rem; - font-size: 1.5rem; - line-height: 2rem; - padding: 0.5rem; - flex-grow: 1; - min-width: 0; - } - - .search-bar:hover { - background-color: var(--search-bar-background-hover-color); - } - - .search-bar:focus { - background-color: var(--search-bar-background-focus-color); - outline: 2px solid transparent; - outline-offset: 2px; - } + background-color: var(--search-bar-background-color); + border-radius: 0.75rem; + border-width: 2px; + display: flex; + font-size: 1.5rem; + justify-content: center; + line-height: 2rem; + max-width: 28rem; + outline-offset: -2px; + overflow: hidden; + padding: 0.5rem; + + .search-bar { + background-color: var(--search-bar-background-color); + color: var(--search-bar-color); + display: block; + margin-left: 0.5rem; + font-size: 1.5rem; + line-height: 2rem; + padding: 0.5rem; + flex-grow: 1; + min-width: 0; + } + + .search-bar:hover { + background-color: var(--search-bar-background-hover-color); + } + + .search-bar:focus { + background-color: var(--search-bar-background-focus-color); + outline: 2px solid transparent; + outline-offset: 2px; + } } .main-search:focus-within { - border-color: var(--search-bar-focus-border-color); - transition-property: box-shadow; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 400ms; + border-color: var(--search-bar-focus-border-color); + transition-property: box-shadow; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 400ms; - /* offset-x | offset-y | blur-radius | spread-radius | color */ - box-shadow: 5px 5px 5px 2px var(--search-bar-focus-border-color); + /* offset-x | offset-y | blur-radius | spread-radius | color */ + box-shadow: 5px 5px 5px 2px var(--search-bar-focus-border-color); } .main-search button { - margin-bottom: 1.25rem; - margin-right: 1rem; - margin-top: 1.25rem; - - svg { - width: 1.5rem; - height: 1.5rem; - margin-top: auto; - margin-bottom: auto; - } + margin-bottom: 1.25rem; + margin-right: 1rem; + margin-top: 1.25rem; + + svg { + width: 1.5rem; + height: 1.5rem; + margin-top: auto; + margin-bottom: auto; + } } diff --git a/assets/css/2-components/6-secondary-search.css b/assets/css/2-components/6-secondary-search.css index 6bef98a1..40246763 100644 --- a/assets/css/2-components/6-secondary-search.css +++ b/assets/css/2-components/6-secondary-search.css @@ -2,59 +2,59 @@ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ .secondary-search { - background-color: var(--search-bar-background-color); - border-radius: 0.75rem; - border-width: 2px; - display: flex; - font-size: 1rem; - justify-content: center; - line-height: 2rem; - max-width: 20rem; - outline-offset: -2px; - overflow: hidden; - padding: 0.3rem; - - .search-bar { - background-color: var(--search-bar-background-color); - color: var(--search-bar-color); - display: block; - font-size: 1.5rem; - line-height: 2rem; - padding: 0.5rem; - flex-grow: 1; - min-width: 0; - } - - .search-bar:hover { - background-color: var(--search-bar-background-hover-color); - } - - .search-bar:focus { - background-color: var(--search-bar-background-focus-color); - outline: transparent solid 2px; - outline-offset: 2px; - } + background-color: var(--search-bar-background-color); + border-radius: 0.75rem; + border-width: 2px; + display: flex; + font-size: 1rem; + justify-content: center; + line-height: 2rem; + max-width: 20rem; + outline-offset: -2px; + overflow: hidden; + padding: 0.3rem; + + .search-bar { + background-color: var(--search-bar-background-color); + color: var(--search-bar-color); + display: block; + font-size: 1.5rem; + line-height: 2rem; + padding: 0.5rem; + flex-grow: 1; + min-width: 0; + } + + .search-bar:hover { + background-color: var(--search-bar-background-hover-color); + } + + .search-bar:focus { + background-color: var(--search-bar-background-focus-color); + outline: transparent solid 2px; + outline-offset: 2px; + } } .secondary-search:focus-within { - border-color: var(--search-bar-focus-border-color); - transition-property: box-shadow; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 300ms; + border-color: var(--search-bar-focus-border-color); + transition-property: box-shadow; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 300ms; - /* offset-x | offset-y | blur-radius | spread-radius | color */ - box-shadow: 4px 4px 4px 1px var(--search-bar-focus-border-color); + /* offset-x | offset-y | blur-radius | spread-radius | color */ + box-shadow: 4px 4px 4px 1px var(--search-bar-focus-border-color); } .secondary-search button { - margin-bottom: 1.25rem; - margin-right: 1rem; - margin-top: 1.25rem; - - svg { - width: 1em; - height: 1em; - margin-top: auto; - margin-bottom: auto; - } + margin-bottom: 1.25rem; + margin-right: 1rem; + margin-top: 1.25rem; + + svg { + width: 1em; + height: 1em; + margin-top: auto; + margin-bottom: auto; + } } diff --git a/assets/css/2-components/7-button.css b/assets/css/2-components/7-button.css index 3cd25020..ffd41184 100644 --- a/assets/css/2-components/7-button.css +++ b/assets/css/2-components/7-button.css @@ -2,31 +2,31 @@ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ .button { - background-color: var(--main-page-button-background); - border-radius: 50rem; - border-width: 1px; - color: var(--text-color); - font-weight: bolder; - padding-bottom: 1rem; - padding-left: 2rem; - padding-right: 2rem; - padding-top: 1rem; + background-color: var(--main-page-button-background); + border-radius: 50rem; + border-width: 1px; + color: var(--text-color); + font-weight: bolder; + padding-bottom: 1rem; + padding-left: 2rem; + padding-right: 2rem; + padding-top: 1rem; } .button:hover { - border-color: var(--main-page-button-focus-border-color); - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 200ms; + border-color: var(--main-page-button-focus-border-color); + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; - /* offset-x | offset-y | blur-radius | spread-radius | color */ - box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color); + /* offset-x | offset-y | blur-radius | spread-radius | color */ + box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color); } .button:active { - border-color: var(--main-page-button-focus-border-color); - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 200ms; + border-color: var(--main-page-button-focus-border-color); + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; - /* offset-x | offset-y | blur-radius | spread-radius | color */ - box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color); + /* offset-x | offset-y | blur-radius | spread-radius | color */ + box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color); } diff --git a/assets/css/2-components/8-alert.css b/assets/css/2-components/8-alert.css index 63dec2d5..e6c46708 100644 --- a/assets/css/2-components/8-alert.css +++ b/assets/css/2-components/8-alert.css @@ -1,27 +1,27 @@ .alert { - padding: 1rem; - border-radius: 0.5rem; - align-items: center; - display: flex; - margin-bottom: 1rem; + padding: 1rem; + border-radius: 0.5rem; + align-items: center; + display: flex; + margin-bottom: 1rem; } .alert-info { - color: var(--dark-blue); - background-color: var(--light-blue-background); + color: var(--dark-blue); + background-color: var(--light-blue-background); } .alert-error { - color: var(--dark-red); - background-color: var(--light-red-background); + color: var(--dark-red); + background-color: var(--light-red-background); } svg.alert-icon { - flex-shrink: 0; - width: 1em; - height: 1em; + flex-shrink: 0; + width: 1em; + height: 1em; } .alert-message { - margin-inline-start: 0.75rem; + margin-inline-start: 0.75rem; } diff --git a/assets/css/2-components/9-terminal-icon.css b/assets/css/2-components/9-terminal-icon.css new file mode 100644 index 00000000..f1fb37e7 --- /dev/null +++ b/assets/css/2-components/9-terminal-icon.css @@ -0,0 +1,9 @@ +/* stylelint-disable selector-class-pattern */ +/* stylelint-disable declaration-block-no-redundant-longhand-properties */ + +.terminal-icon { + height: 1.25rem; + width: 1.25rem; + display: inline; + margin-top: -0.25rem; +} diff --git a/assets/css/3-screens/1-package/1-package.css b/assets/css/3-screens/1-package/1-package.css index 0bda37af..f4b675b5 100644 --- a/assets/css/3-screens/1-package/1-package.css +++ b/assets/css/3-screens/1-package/1-package.css @@ -1,277 +1,304 @@ /* stylelint-disable selector-class-pattern */ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ + +#package-install-section { + p { + display: inline-block; + } + + .package-build-type-custom { + background-color: #ffc107; + border-radius: 0.25rem; + color: white; + font-size: 50%; + font-weight: 700; + padding-left: 0.5rem; + padding-right: 0.5rem; + margin-left: 0.5rem; + } +} + .package-install-string { - background-color: transparent; - border-color: var(--install-string-border); - border-radius: 6px; - display: block; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; - font-size: 1rem; - font-weight: 500; - line-height: 1.5rem; - width: 100%; + background-color: transparent; + border-color: var(--install-string-border); + border-radius: 6px; + display: block; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + font-size: 1rem; + font-weight: 500; + line-height: 1.5rem; + width: 100%; } .package-alternatives { - list-style-type: disc; - list-style-position: inside; + list-style-type: disc; + list-style-position: inside; } .synopsis { - text-align: center; - font-size: 1.25rem; - line-height: 1.75rem; + text-align: center; + font-size: 1.25rem; + line-height: 1.75rem; } .release-deprecated { - color: var(--deprecated-version); + color: var(--deprecated-version); } .package-body { - justify-content: center; - display: grid; - grid-template-columns: 25% 1fr 25%; - grid-gap: 10px; - - a { - font-weight: 500; - } - - .maintainer-info { - font-weight: 500; - flex-wrap: wrap; - } - - .package-body-section { - font-size: 1.5rem; - line-height: 2rem; - margin-bottom: 0.75rem; - } - - .package-right-column { - order: 3; - } - - .package-right-rows { - position: sticky; - top: 7rem; - } - - .package-right-rows > * { - margin-bottom: 1.25rem; - } - - .package-left-column { - order: 1; - } - - .package-left-rows { - position: sticky; - top: 7rem; - } - - .package-left-rows > * { - margin-bottom: 1.25rem; - } - - .package-flags-section { - display: inline; - } - - .package-flags { - .package-flag-description { - padding-left: 0.5rem; - margin-bottom: 0.5em; - } - - .package-flag-name { - display: inline; - } - } - - .package-flags > * { - pre { - margin: 0; - font-size: 85%; - background-color: var(--readme-code-background-color); - border-radius: 6px; - padding-top: 0.2em; - padding-right: 0.4em; - padding-bottom: 0.2em; - padding-left: 0.4em; - } - - code { - margin: 0; - font-size: 85%; - background-color: var(--readme-code-background-color); - border-radius: 6px; - padding-top: 0.2em; - padding-right: 0.4em; - padding-bottom: 0.2em; - padding-left: 0.4em; - } - } - - .package-body-section__license { - font-weight: 500; - } + --package-layout-gap: 10px; + + justify-content: center; + display: grid; + grid-template-columns: + 25% minmax(calc(50% - (var(--package-layout-gap) * 2)), 1fr) + 25%; + gap: var(--package-layout-gap); + + > * { + grid-row-start: 1; + } + + .package-left-column { + grid-column: 1 / 2; + } + + .release-readme-column { + grid-column: 2 / 3; + } + + .package-right-column { + grid-column: 3 / 4; + } + + .package-right-rows { + position: sticky; + top: 7rem; + } + + .package-right-rows > * { + margin-bottom: 1.25rem; + } + + .package-left-rows { + position: sticky; + top: 7rem; + } + + .package-left-rows > * { + margin-bottom: 1.25rem; + } + + a { + font-weight: 500; + } + + .maintainer-info { + font-weight: 500; + flex-wrap: wrap; + } + + .package-body-section { + font-size: 1.5rem; + line-height: 2rem; + margin-bottom: 0.5rem; + } + + .package-flags-section { + display: inline; + } + + .package-flags { + .package-flag-description { + padding-left: 0.5rem; + margin-bottom: 0.5em; + } + + .package-flag-name { + display: inline; + } + } + + .package-flags > * { + pre { + margin: 0; + font-size: 85%; + background-color: var(--readme-code-background-color); + border-radius: 6px; + padding-top: 0.2em; + padding-right: 0.4em; + padding-bottom: 0.2em; + padding-left: 0.4em; + } + + code { + margin: 0; + font-size: 85%; + background-color: var(--readme-code-background-color); + border-radius: 6px; + padding-top: 0.2em; + padding-right: 0.4em; + padding-bottom: 0.2em; + padding-left: 0.4em; + } + } + + .package-body-section__license { + font-weight: 500; + } } .release { - a:hover { - text-decoration: underline; - } + a:hover { + text-decoration: underline; + } } span.revised-date::before { - content: attr(data-text); /* here's the magic */ - position: absolute; - font-size: 0.85em; - - /* vertically center */ - top: 50%; - transform: translateY(-50%); - - /* move to right */ - left: 100%; - - /* basic styles */ - background: #000; - border-radius: 10px; - box-shadow: 0 1px 8px rgb(0 0 0 / 50%); - color: #fff; - padding: 5px; - text-align: center; - width: 200px; - display: none; /* hide by default */ + content: attr(data-text); /* here's the magic */ + position: absolute; + font-size: 0.85em; + + /* vertically center */ + top: 50%; + transform: translateY(-50%); + + /* move to right */ + left: 100%; + + /* basic styles */ + background: #000; + border-radius: 10px; + box-shadow: 0 1px 8px rgb(0 0 0 / 50%); + color: #fff; + padding: 5px; + text-align: center; + display: none; /* hide by default */ } span.revised-date:hover::before { - display: block; + display: block; + width: 200px; } .revised-date { - svg { - display: inline; - width: 1rem; - height: 1rem; - } - - position: relative; /* making the .tooltip span a container for the tooltip text */ - border-bottom: 1px dashed #000; /* little indicater to indicate it's hoverable */ + svg { + display: inline; + width: 1rem; + height: 1rem; + } + + position: relative; /* making the .tooltip span a container for the tooltip text */ + border-bottom: 1px dashed #000; /* little indicater to indicate it's hoverable */ } .instruction-tooltip { - svg { - display: inline; - width: 1rem; - height: 1rem; - } - - position: relative; /* making the .tooltip span a container for the tooltip text */ - border-bottom: 1px dashed #000; /* little indicater to indicate it's hoverable */ + svg { + display: inline; + width: 1rem; + height: 1rem; + } + + position: relative; /* making the .tooltip span a container for the tooltip text */ + border-bottom: 1px dashed #000; /* little indicater to indicate it's hoverable */ } .instruction-tooltip::before { - content: attr(data-text); /* here's the magic */ - position: absolute; - - /* vertically center */ - top: 50%; - transform: translateY(-50%); - - /* move to right */ - left: 100%; - - /* basic styles */ - background: #000; - border-radius: 10px; - box-shadow: 0 1px 8px rgb(0 0 0 / 50%); - color: #fff; - padding: 10px; - text-align: center; - width: 250px; - display: none; /* hide by default */ + content: attr(data-text); /* here's the magic */ + position: absolute; + + /* vertically center */ + top: 50%; + transform: translateY(-50%); + + /* move to right */ + left: 100%; + + /* basic styles */ + background: #000; + border-radius: 10px; + box-shadow: 0 1px 8px rgb(0 0 0 / 50%); + color: #fff; + padding: 10px; + text-align: center; + width: 250px; + display: none; /* hide by default */ } .instruction-tooltip:hover::before { - display: block; + display: block; } .pagination-area { - display: flex; - padding-bottom: 40px; + display: flex; + padding-bottom: 40px; } .pagination-footer { - margin-left: auto; - margin-right: auto; - display: flex; - align-items: center; - - .pagination-footer__page { - padding-bottom: 0.5rem; - padding-top: 0.5rem; - padding-left: 0.75rem; - padding-right: 0.75rem; - border-width: 1px; - border-collapse: collapse; - height: 40px; - } - - .pagination-footer__previous { - border-bottom-left-radius: 0.5rem; - border-top-left-radius: 0.5rem; - } - - .pagination-footer__next { - border-bottom-right-radius: 0.5rem; - border-top-right-radius: 0.5rem; - } + margin-left: auto; + margin-right: auto; + display: flex; + align-items: center; + + .pagination-footer__page { + padding-bottom: 0.5rem; + padding-top: 0.5rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + border-width: 1px; + border-collapse: collapse; + height: 40px; + } + + .pagination-footer__previous { + border-bottom-left-radius: 0.5rem; + border-top-left-radius: 0.5rem; + } + + .pagination-footer__next { + border-bottom-right-radius: 0.5rem; + border-top-right-radius: 0.5rem; + } } .package-title { - .version { - font-size: medium; - line-height: 1.3rem; - padding-left: 4px; - } - - a:hover { - text-decoration: underline; - } + .version { + font-size: medium; + line-height: 1.3rem; + padding-left: 4px; + } + + a:hover { + text-decoration: underline; + } } a.breadcrumb-segment { - color: var(--breadcrumb-color); + color: var(--breadcrumb-color); } a.breadcrumb-segment:hover { - text-decoration: underline; + text-decoration: underline; } @media (max-width: 47rem) { - .package-body { - grid-template-columns: repeat(1, 1fr); - - .package-left-column { - order: 1; - } - - .package-right-column { - order: 2; - - .package-right-rows .dependents { - margin-bottom: 2rem; - } - } - - .release-readme-column { - order: 3; - grid-column: 1; - overflow: auto; - } - } + .package-body { + grid-template-columns: 1fr; + + > *:nth-child(n) { + grid-row-start: auto; + grid-column: 1 / 2; + } + + .package-right-column { + .package-right-rows .dependents { + margin-bottom: 2rem; + } + } + + .release-readme-column { + overflow: auto; + } + } } diff --git a/assets/css/3-screens/1-package/2-release-changelog.css b/assets/css/3-screens/1-package/2-release-changelog.css index c8e38272..d8edd1ff 100644 --- a/assets/css/3-screens/1-package/2-release-changelog.css +++ b/assets/css/3-screens/1-package/2-release-changelog.css @@ -1,109 +1,109 @@ .release-changelog { - overflow-wrap: break-word; - box-sizing: border-box; - - h1 { - border-bottom: 1px solid #354561; - padding-bottom: 0.3em; - margin-bottom: 16px; - font-size: 2em; - - a { - display: inline-block; - } - } - - img { - max-width: 100%; - box-sizing: content-box; - display: inline-block; - } - - h2 { - border-bottom: 1px solid #354561; - margin-bottom: 16px; - margin-top: 16px; - padding-bottom: 0.3em; - font-size: 1.5em; - } - - h3 { - border-bottom: 1px solid #354561; - margin-bottom: 16px; - margin-top: 16px; - padding-bottom: 0.3em; - font-size: 1.25em; - } - - h4 { - border-bottom: 1px solid #354561; - margin-bottom: 16px; - margin-top: 16px; - padding-bottom: 0.3em; - font-size: 1em; - } - - p { - overflow-wrap: break-word; - margin-bottom: 16px; - box-sizing: border-box; - - a { - display: inline-block; - } - } - - ol { - margin-bottom: 16px; - padding-left: 1em; - - li { - overflow-wrap: break-word; - list-style-type: decimal; - list-style-position: outside; - - p { - display: inline; - } - } - } - - ul { - margin-bottom: 16px; - padding-left: 1em; - - li { - overflow-wrap: break-word; - list-style-type: disc; - list-style-position: outside; - margin-bottom: 10px; - - p { - display: inline; - } - } - - li + li { - margin-top: 0.25em; - } - } - - pre { - line-height: 1.45; - padding: 16px; - font-family: monospace; - border-radius: 6px; - overflow: auto; - font-size: 90%; - background-color: var(--changelog-pre-background-color); - margin-bottom: 16px; - } - - code { - margin: 0; - font-size: 85%; - background-color: var(--changelog-code-background-color); - border-radius: 6px; - padding: 0.2em 0.4em; - } + overflow-wrap: break-word; + box-sizing: border-box; + + h1 { + border-bottom: 1px solid #354561; + padding-bottom: 0.3em; + margin-bottom: 16px; + font-size: 2em; + + a { + display: inline-block; + } + } + + img { + max-width: 100%; + box-sizing: content-box; + display: inline-block; + } + + h2 { + border-bottom: 1px solid #354561; + margin-bottom: 16px; + margin-top: 16px; + padding-bottom: 0.3em; + font-size: 1.5em; + } + + h3 { + border-bottom: 1px solid #354561; + margin-bottom: 16px; + margin-top: 16px; + padding-bottom: 0.3em; + font-size: 1.25em; + } + + h4 { + border-bottom: 1px solid #354561; + margin-bottom: 16px; + margin-top: 16px; + padding-bottom: 0.3em; + font-size: 1em; + } + + p { + overflow-wrap: break-word; + margin-bottom: 16px; + box-sizing: border-box; + + a { + display: inline-block; + } + } + + ol { + margin-bottom: 16px; + padding-left: 1em; + + li { + overflow-wrap: break-word; + list-style-type: decimal; + list-style-position: outside; + + p { + display: inline; + } + } + } + + ul { + margin-bottom: 16px; + padding-left: 1em; + + li { + overflow-wrap: break-word; + list-style-type: disc; + list-style-position: outside; + margin-bottom: 10px; + + p { + display: inline; + } + } + + li + li { + margin-top: 0.25em; + } + } + + pre { + line-height: 1.45; + padding: 16px; + font-family: monospace; + border-radius: 6px; + overflow: auto; + font-size: 90%; + background-color: var(--changelog-pre-background-color); + margin-bottom: 16px; + } + + code { + margin: 0; + font-size: 85%; + background-color: var(--changelog-code-background-color); + border-radius: 6px; + padding: 0.2em 0.4em; + } } diff --git a/assets/css/3-screens/1-package/3-release-readme.css b/assets/css/3-screens/1-package/3-release-readme.css index 23986bfc..49704380 100644 --- a/assets/css/3-screens/1-package/3-release-readme.css +++ b/assets/css/3-screens/1-package/3-release-readme.css @@ -1,132 +1,125 @@ -.release-readme-column { - margin: 0 2em; - flex-basis: 40rem; - order: 2; - min-width: 75%; -} - .release-readme { - overflow-wrap: break-word; - box-sizing: border-box; - - h1 { - border-bottom: 1px solid #354561; - padding-bottom: 0.3em; - margin-bottom: 16px; - font-size: 2em; - - a { - display: inline-block; - } - } - - img { - max-width: 100%; - box-sizing: content-box; - display: inline-block; - } - - h2 { - border-bottom: 1px solid #354561; - margin-bottom: 16px; - margin-top: 16px; - padding-bottom: 0.3em; - font-size: 1.5em; - } - - h3 { - border-bottom: 1px solid #354561; - margin-bottom: 16px; - margin-top: 16px; - padding-bottom: 0.3em; - font-size: 1.25em; - } - - h4 { - border-bottom: 1px solid #354561; - margin-bottom: 16px; - margin-top: 16px; - padding-bottom: 0.3em; - font-size: 1em; - } - - p { - overflow-wrap: break-word; - margin-bottom: 16px; - box-sizing: border-box; - - a { - display: inline-block; - } - - a:hover { - text-decoration: underline; - } - } - - ol { - margin-bottom: 16px; - padding-left: 1em; - - li { - margin-top: 1rem; - overflow-wrap: break-word; - list-style-type: decimal; - list-style-position: outside; - - p { - display: inline; - } - } - } - - ul { - margin-bottom: 16px; - padding-left: 1em; - - li { - margin-top: 1rem; - overflow-wrap: break-word; - list-style-type: disc; - list-style-position: outside; - margin-bottom: 10px; - - p { - display: inline; - } - } - - li + li { - margin-top: 0.25em; - } - } - - code { - margin: 0; - font-size: 85%; - background-color: var(--readme-code-background-color); - border-radius: 6px; - padding: 0.2em 0.4em; - } - - pre { - line-height: 1.45; - padding: 16px; - font-family: monospace; - border-radius: 6px; - overflow: auto; - font-size: 90%; - background-color: var(--readme-pre-background-color); - margin-bottom: 16px; - - code { - margin: 0; - padding: 0; - background-color: transparent; - } - } - - tr { - border-bottom-width: 1px; - } + overflow-wrap: break-word; + box-sizing: border-box; + + h1 { + border-bottom: 1px solid #354561; + padding-bottom: 0.3em; + margin-bottom: 16px; + font-size: 2em; + + a { + display: inline-block; + } + } + + img { + max-width: 100%; + box-sizing: content-box; + display: inline-block; + } + + h2 { + border-bottom: 1px solid #354561; + margin-bottom: 16px; + margin-top: 16px; + padding-bottom: 0.3em; + font-size: 1.5em; + } + + h3 { + border-bottom: 1px solid #354561; + margin-bottom: 16px; + margin-top: 16px; + padding-bottom: 0.3em; + font-size: 1.25em; + } + + h4 { + border-bottom: 1px solid #354561; + margin-bottom: 16px; + margin-top: 16px; + padding-bottom: 0.3em; + font-size: 1em; + } + + p { + overflow-wrap: break-word; + margin-bottom: 16px; + box-sizing: border-box; + + a { + display: inline-block; + } + + a:hover { + text-decoration: underline; + } + } + + ol { + margin-bottom: 16px; + padding-left: 1em; + + li { + margin-top: 1rem; + overflow-wrap: break-word; + list-style-type: decimal; + list-style-position: outside; + + p { + display: inline; + } + } + } + + ul { + margin-bottom: 16px; + padding-left: 1em; + + li { + margin-top: 1rem; + overflow-wrap: break-word; + list-style-type: disc; + list-style-position: outside; + margin-bottom: 10px; + + p { + display: inline; + } + } + + li + li { + margin-top: 0.25em; + } + } + + code { + margin: 0; + font-size: 85%; + background-color: var(--readme-code-background-color); + border-radius: 6px; + padding: 0.2em 0.4em; + } + + pre { + line-height: 1.45; + padding: 16px; + font-family: monospace; + border-radius: 6px; + overflow: auto; + font-size: 90%; + background-color: var(--readme-pre-background-color); + margin-bottom: 16px; + + code { + margin: 0; + padding: 0; + background-color: transparent; + } + } + + tr { + border-bottom-width: 1px; + } } diff --git a/assets/css/3-screens/1-package/4-compiler-badge.css b/assets/css/3-screens/1-package/4-compiler-badge.css index 39619ecb..54a5bc0d 100644 --- a/assets/css/3-screens/1-package/4-compiler-badge.css +++ b/assets/css/3-screens/1-package/4-compiler-badge.css @@ -1,24 +1,27 @@ ul.compiler-badges { - list-style: none; - display: grid; - grid-template-columns: repeat(3, 80px); + list-style: none; + display: grid; + grid-template-columns: repeat( + auto-fit, + minmax(min(calc(1.2em + 6ch), 80px), 1fr) + ); /* FIXME: Kinda magic numbers soup. Should be improved */ - li { - margin-left: 4px; - margin-right: 4px; - margin-bottom: 7px; - border-radius: 50rem; - max-width: 100%; - background-color: var(--compiler-badge-background); - text-align: center; - } + li { + margin-left: 4px; + margin-right: 4px; + margin-bottom: 7px; + border-radius: 50rem; + max-width: 100%; + background-color: var(--compiler-badge-background); + text-align: center; + } } a.compiler-badge { - box-sizing: border-box; - color: var(--text-color); - display: inline-block; - font-size: 0.9rem; - padding: 0.3em 0.6em; - text-decoration: none; + box-sizing: border-box; + color: var(--text-color); + display: inline-block; + font-size: 0.9rem; + padding: 0.3em 0.6em; + text-decoration: none; } diff --git a/assets/css/3-screens/1-package/5-security.css b/assets/css/3-screens/1-package/5-security.css new file mode 100644 index 00000000..2f63ce70 --- /dev/null +++ b/assets/css/3-screens/1-package/5-security.css @@ -0,0 +1,26 @@ +.advisory-list__head { + display: none; +} + +@media only screen and (--viewport-md) { + .advisory-list { + display: table; + border-collapse: separate; + border-spacing: 12px; + } + + .advisory-list__head { + display: table-header-group; + border-inline: solid; + font-size: 1.25rem; + } + + .advisory-list__body { + display: table-row-group; + } + + .advisory-list__header { + display: table-cell; + } +} + diff --git a/assets/css/3-screens/2-categories.css b/assets/css/3-screens/2-categories.css index 44670b8b..60e661f7 100644 --- a/assets/css/3-screens/2-categories.css +++ b/assets/css/3-screens/2-categories.css @@ -2,56 +2,54 @@ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ .categories-title { - padding-top: 1rem; - padding-left: 2rem; - padding-right: 2rem; - - h1 { - font-size: 3rem; - line-height: 1; - text-align: center; - overflow-wrap: anywhere; - } + padding-top: 1rem; + padding-left: 2rem; + padding-right: 2rem; + + h1 { + font-size: 3rem; + line-height: 1; + text-align: center; + overflow-wrap: anywhere; + } } .categories-body { - display: grid; - margin-bottom: 3rem; - margin-left: auto; - margin-right: auto; - margin-top: 3rem; - max-width: 80rem; - padding-left: 0.5rem; - padding-right: 0.5rem; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 2rem; - - .category-card__name { - color: var(--category-card-name-color); - } - - .category-card__synopsis { - color: var(--category-card-synopsis-color); - overflow-wrap: break-word; - } - - .category-card { - display: block; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - padding-left: 2rem; - padding-right: 2rem; - width: 400px; - height: 100px; - } - - .category-card:hover { - background-color: var(--category-card-background-hover); - } + display: grid; + margin-bottom: 3rem; + margin-left: auto; + margin-right: auto; + margin-top: 3rem; + max-width: 80rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 2rem; + + .category-card__name { + color: var(--category-card-name-color); + } + + .category-card__synopsis { + color: var(--category-card-synopsis-color); + overflow-wrap: break-word; + } + + .category-card { + display: block; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 2rem; + padding-right: 2rem; + } + + .category-card:hover { + background-color: var(--category-card-background-hover); + } } @media (max-width: 48rem) { - .categories-body { - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - } + .categories-body { + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + } } diff --git a/assets/css/3-screens/3-about.css b/assets/css/3-screens/3-about.css index c6128bf3..6885c1f8 100644 --- a/assets/css/3-screens/3-about.css +++ b/assets/css/3-screens/3-about.css @@ -2,116 +2,107 @@ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ .about-page { - margin-left: auto; - margin-right: auto; - max-width: 1000px; - padding-bottom: 4rem; - padding-left: 2rem; - padding-right: 2rem; - padding-top: 6rem; - width: auto; + h3 { + font-weight: 700; + font-size: 1.25rem; + line-height: 1.75rem; + margin-top: 2.5rem; + } - h3 { - font-weight: 700; - font-size: 1.25rem; - line-height: 1.75rem; - margin-top: 2.5rem; - } + div h3 { + font-weight: 700; + font-size: 1.25rem; + line-height: 1.75rem; + margin-bottom: 2.5rem; + } - div h3 { - font-weight: 700; - font-size: 1.25rem; - line-height: 1.75rem; - margin-bottom: 2.5rem; - } - - ul { - margin-top: 1rem; - margin-bottom: 1rem; - } + ul { + margin-top: 1rem; + margin-bottom: 1rem; + } } .about-page__banner { - margin-top: 0.25rem; - text-align: center; - margin-bottom: 4rem; + margin-top: 0.25rem; + text-align: center; + margin-bottom: 4rem; } .about-page__title { - letter-spacing: -0.025rem; - font-weight: 800; - font-size: 3.75rem; - line-height: 1; + letter-spacing: -0.025rem; + font-weight: 800; + font-size: 3.75rem; + line-height: 1; } .about-page__subtitle { - font-size: 1.25rem; - line-height: 1.75rem; - max-width: 36rem; - margin-top: 1.25rem; - margin-left: auto; - margin-right: auto; + font-size: 1.25rem; + line-height: 1.75rem; + max-width: 36rem; + margin-top: 1.25rem; + margin-left: auto; + margin-right: auto; } .admin-page { - padding-top: 1.25rem; - padding-bottom: 1.25rem; + padding-top: 1.25rem; + padding-bottom: 1.25rem; - .admin-title { - font-size: 3rem; - line-height: 1; - letter-spacing: -0.025em; - text-align: center; - } + .admin-title { + font-size: 3rem; + line-height: 1; + letter-spacing: -0.025em; + text-align: center; + } - .admin-cards { - grid-template-columns: repeat(3, minmax(0, 1fr)); - padding-top: 2.5rem; - padding-bottom: 2.5rem; - gap: 1.25rem; - display: grid; - margin-top: 1.25rem; - } + .admin-cards { + grid-template-columns: repeat(3, minmax(0, 1fr)); + padding-top: 2.5rem; + padding-bottom: 2.5rem; + gap: 1.25rem; + display: grid; + margin-top: 1.25rem; + } - .admin-card { - padding: 1.5rem; - border-radius: 0.5rem; - overflow: hidden; + .admin-card { + padding: 1.5rem; + border-radius: 0.5rem; + overflow: hidden; - dt { - font-weight: 500; - font-size: 0.875rem; - line-height: 1.25rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } + dt { + font-weight: 500; + font-size: 0.875rem; + line-height: 1.25rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } - dd { - font-weight: 600; - font-size: 1.875rem; - line-height: 2.25rem; - margin-top: 0.25rem; - } - } + dd { + font-weight: 600; + font-size: 1.875rem; + line-height: 2.25rem; + margin-top: 0.25rem; + } + } } @media (max-width: 48rem) { - .about-page { - margin-left: auto; - margin-right: auto; - max-width: 1000px; - padding-bottom: 4rem; - padding-left: 1rem; - padding-right: 1rem; - padding-top: 4rem; - width: auto; - } + .about-page { + margin-left: auto; + margin-right: auto; + max-width: 1000px; + padding-bottom: 4rem; + padding-left: 1rem; + padding-right: 1rem; + padding-top: 4rem; + width: auto; + } - .about-page__subtitle { - font-size: 1.25rem; - line-height: 1.75rem; - max-width: 36rem; - margin-top: 1.25rem; - } + .about-page__subtitle { + font-size: 1.25rem; + line-height: 1.75rem; + max-width: 36rem; + margin-top: 1.25rem; + } } diff --git a/assets/css/3-screens/4-front-page.css b/assets/css/3-screens/4-front-page.css index 1cc00908..6c615b34 100644 --- a/assets/css/3-screens/4-front-page.css +++ b/assets/css/3-screens/4-front-page.css @@ -2,60 +2,71 @@ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ .main-title { - color: var(--text-color); - font-size: 2.25rem; - font-weight: 800; - letter-spacing: -0.025rem; - line-height: 2.5rem; - margin-bottom: 5px; - padding-top: 4rem; - padding-bottom: 4rem; - text-align: center; + color: var(--text-color); + font-size: 2.25rem; + font-weight: 800; + letter-spacing: -0.025rem; + line-height: 2.5rem; + margin-bottom: 5px; + padding-top: 4rem; + padding-bottom: 4rem; + text-align: center; +} + +.search-hint { + margin-top: 1rem; + text-align: center; + + .lightbulb-icon { + display: inline; + height: 1.25rem; + width: 1.25rem; + } } section#main-page-buttons { - border-top: 1px solid var(--text-color); - border-color: var(--navbar-background-color); - display: flex; - flex-wrap: wrap; - gap: 16px; - justify-content: center; - padding-top: 3rem; - margin-top: 3rem; - - .button { - background-color: var(--main-page-button-background); - border-radius: 50rem; - border-width: 1px; - color: var(--text-color); - font-weight: bolder; - padding-bottom: 1rem; - padding-left: 2rem; - padding-right: 2rem; - padding-top: 1rem; - } - - .button:hover { - border-color: var(--main-page-button-focus-border-color); - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 200ms; - - /* offset-x | offset-y | blur-radius | spread-radius | color */ - box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color); - } - - .button:active { - border-color: var(--main-page-button-focus-border-color); - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 200ms; - - /* offset-x | offset-y | blur-radius | spread-radius | color */ - box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color); - } + border-top: 1px solid var(--text-color); + border-color: var(--navbar-background-color); + display: flex; + flex-wrap: wrap; + gap: 16px; + justify-content: center; + padding-top: 3rem; + margin-top: 3rem; + + .button { + background-color: var(--main-page-button-background); + border-radius: 50rem; + border-width: 1px; + color: var(--text-color); + font-weight: bolder; + padding-bottom: 1rem; + padding-left: 2rem; + padding-right: 2rem; + padding-top: 1rem; + } + + .button:hover { + border-color: var(--main-page-button-focus-border-color); + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; + + /* offset-x | offset-y | blur-radius | spread-radius | color */ + box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color); + } + + .button:active { + border-color: var(--main-page-button-focus-border-color); + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; + + /* offset-x | offset-y | blur-radius | spread-radius | color */ + box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color); + } } @media (max-width: 48rem) { - .main-search { - align-items: center; - } + .main-search { + align-items: center; + } } diff --git a/assets/css/3-screens/5-package-version.css b/assets/css/3-screens/5-settings/1-dashboard.css similarity index 77% rename from assets/css/3-screens/5-package-version.css rename to assets/css/3-screens/5-settings/1-dashboard.css index 07e42822..b8c4cdd1 100644 --- a/assets/css/3-screens/5-package-version.css +++ b/assets/css/3-screens/5-settings/1-dashboard.css @@ -1,2 +1,6 @@ /* stylelint-disable selector-class-pattern */ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ + +#subheader { + text-align: right; +} diff --git a/assets/css/4-helpers.css b/assets/css/4-helpers.css deleted file mode 100644 index c9363bde..00000000 --- a/assets/css/4-helpers.css +++ /dev/null @@ -1,22 +0,0 @@ -/* stylelint-disable selector-class-pattern */ -/* stylelint-disable declaration-block-no-redundant-longhand-properties */ - -.hidden { - display: none; -} - -.font-light { - font-weight: 300; -} - -.divider { - padding-bottom: 1rem; - margin-bottom: 1rem; - border-bottom: 1px solid hsl(0 0% 80%); -} - -.divider-with-margin { - padding-bottom: 1rem; - margin-bottom: 4rem; - border-bottom: 1px solid hsl(0 0% 80%); -} diff --git a/assets/css/4-helpers/1-globals.css b/assets/css/4-helpers/1-globals.css new file mode 100644 index 00000000..eb7227fd --- /dev/null +++ b/assets/css/4-helpers/1-globals.css @@ -0,0 +1,165 @@ +/* stylelint-disable selector-class-pattern */ +/* stylelint-disable declaration-block-no-redundant-longhand-properties */ + +/* Position */ +.static { + position: static; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +@media (--viewport-sm) { + .md\:flex { + display: flex; + } + + .md\:items-center { + align-items: center; + } + + .md\:justify-between { + justify-content: space-between; + } + + .md\:order-1 { + order: 1; + } + + .md\:order-2 { + order: 2; + } +} + +/* Sizes */ + +.h-5 { + height: 1.25rem; +} + +.h-6 { + height: 1.5rem; +} + +.w-5 { + width: 1.25rem; +} + +.w-6 { + width: 1.5rem; +} + +.min-w-full { + min-width: 100%; +} + +.max-w-7xl { + max-width: 80rem; +} + +/* Overflow */ + +.overflow-hidden { + overflow: hidden; +} + +.overflow-x-auto { + overflow-x: auto; +} + +/* Borders */ + +.rounded-md { + border-radius: var(--space-md); +} + +.border-b { + border-bottom-width: 1px; +} + +.border-gray-200 { + border-color: rgb(229 231 235 / 100%); +} + +.bg-slate-200 { + background-color: rgb(226 232 240 / 100%); +} + +@media (--viewport-sm) { + .sm\:rounded-lg { + border-radius: var(--space-lg); + } + + .sm\:border-l { + border-left-width: 1px; + } + + .sm\:border-transparent { + border-color: transparent; + } +} + +/* Content */ + +.divider { + padding-bottom: 1rem; + margin-bottom: 1rem; + border-bottom: 1px solid hsl(0 0% 80%); +} + +.divider-with-margin { + padding-bottom: 1rem; + margin-bottom: 4rem; + border-bottom: 1px solid hsl(0 0% 80%); +} + +.divide-y > :not([hidden]) ~ :not([hidden]) { + border-top-width: 1px; + border-bottom-width: 0; +} + +.divide-gray-200 > :not([hidden]) ~ :not([hidden]) { + border-color: rgb(229 231 235 / 100%); +} + +/* Effects */ + +.shadow { + box-shadow: var(--outline-offset-shadow, 0 0 #0000), + var(--outline-shadow, 0 0 #0000), var(--shadow-simple); +} + +.invert { filter: invert(100%); } diff --git a/assets/css/4-helpers/2-colors.css b/assets/css/4-helpers/2-colors.css new file mode 100644 index 00000000..87fa6df5 --- /dev/null +++ b/assets/css/4-helpers/2-colors.css @@ -0,0 +1,13 @@ +/* stylelint-disable selector-class-pattern */ + +/* Background */ +.bg-slate-200 { background-color: rgb(226 232 240 / 100%); } + +/* Text */ + +.text-black { color: rgb(0 0 0 / 100%); } +.text-white { color: rgb(255 255 255 / 100%); } +.text-gray-400 { color: rgb(156 163 175 / 100%); } + +:is(.dark .dark\:text-gray-100) { color: rgb(243 244 246 / 100%); } +:is(.dark .dark\:text-gray-400) { color: rgb(156 163 175 / 100%); } diff --git a/assets/css/4-helpers/3-spacing.css b/assets/css/4-helpers/3-spacing.css new file mode 100644 index 00000000..0d573cd0 --- /dev/null +++ b/assets/css/4-helpers/3-spacing.css @@ -0,0 +1,42 @@ +/* stylelint-disable selector-class-pattern */ + +.inset-x-0 { inset-inline: var(--space-0); } + +.bottom-0 { bottom: var(--space-0); } + +.m-4 { margin: var(--space-4); } +.mx-auto { margin-inline: auto; } +.mx-4 { margin-inline: var(--space-4); } +.-my-2 { margin-block: calc(var(--space-2) * -1); } +.mt-10 { margin-top: var(--space-10); } +.mt-2 { margin-top: var(--space-2); } +.mt-8 { margin-top: 2rem; } + +.p-2 { padding: var(--space-2); } +.px-4 { padding-inline: var(--space-4); } +.px-6 { padding-inline: var(--space-6); } +.py-2 { padding-block: var(--space-2); } +.py-3 { padding-block: var(--space-3); } +.py-4 { padding-block: var(--space-4); } +.py-5 { padding-block: var(--space-5); } +.pt-12 { padding-top: var(--space-12); } + +.space-x-3 > :not([hidden]) ~ :not([hidden]) { margin-inline: var(--space-3) 0; } +.space-x-6 > :not([hidden]) ~ :not([hidden]) { margin-inline: var(--space-6) 0; } + + +@media (--viewport-sm) { + .sm\:-mx-6 { margin-inline: calc((var(--space-6) * -1)); } + .sm\:px-6 { padding-inline: var(--space-6); } + .sm\:pl-6 { padding-left: var(--space-6); } +} + +@media (--viewport-md) { + .md\:m-0 { margin: var(--space-0); } + .md\:mt-0 { margin-top: var(--space-0); } +} + +@media (--viewport-lg) { + .lg\:-mx-8 { margin-inline: calc(var(--space-8) * -1); } + .lg\:px-8 { padding-inline: var(--space-8); } +} diff --git a/assets/css/4-helpers/4-typography.css b/assets/css/4-helpers/4-typography.css new file mode 100644 index 00000000..5991c79e --- /dev/null +++ b/assets/css/4-helpers/4-typography.css @@ -0,0 +1,67 @@ +/* stylelint-disable selector-class-pattern */ +.whitespace-nowrap { + white-space: nowrap; +} + +.text-left { text-align: left; } +.text-center { text-align: center; } +.text-right { text-align: right; } +.text-justify { text-align: justify; } + +/* Text sizes */ + +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + + +@media (--viewport-sm) { + .sm\:text-2xl { + font-size: 1.5rem; + line-height: 2rem; + } +} + +@media (--viewport-smmd) { + .lg\:text-xl { + font-size: 1.25rem; + line-height: 1.75rem; + } + + .lg\:text-5xl { + font-size: 3rem; + line-height: 1; + } +} + + +.align-top { vertical-align: top; } +.align-middle { vertical-align: middle; } +.align-baseline { vertical-align: baseline; } +.align-bottom { vertical-align: bottom; } + +.font-bold { font-weight: 700; } +.font-light { font-weight: 300; } +.font-medium { font-weight: 500; } +.font-semibold { font-weight: 600; } + +.uppercase { text-transform: uppercase; } + +.tracking-tight { letter-spacing: var(--space-relative-tight);} +.tracking-wider { letter-spacing: var(--space-relative-wider); } diff --git a/assets/css/4-helpers/5-state.css b/assets/css/4-helpers/5-state.css new file mode 100644 index 00000000..ab22d3e9 --- /dev/null +++ b/assets/css/4-helpers/5-state.css @@ -0,0 +1,13 @@ +.hidden { display: none; } + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} diff --git a/assets/css/prism.css b/assets/css/prism.css new file mode 100644 index 00000000..c5e8b315 --- /dev/null +++ b/assets/css/prism.css @@ -0,0 +1,144 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apex+apl+applescript+aql+arduino+arff+armasm+arturo+asciidoc+aspnet+asm6502+asmatmel+autohotkey+autoit+avisynth+avro-idl+awk+bash+basic+batch+bbcode+bbj+bicep+birb+bison+bnf+bqn+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cfscript+chaiscript+cil+cilkc+cilkcpp+clojure+cmake+cobol+coffeescript+concurnas+csp+cooklang+coq+crystal+css-extras+csv+cue+cypher+d+dart+dataweave+dax+dhall+diff+django+dns-zone-file+docker+dot+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+false+firestore-security-rules+flow+fortran+ftl+gml+gap+gcode+gdscript+gedcom+gettext+gherkin+git+glsl+gn+linker-script+go+go-module+gradle+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+hoon+http+hpkp+hsts+ichigojam+icon+icu-message-format+idris+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jexl+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keepalived+keyman+kotlin+kumir+kusto+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+log+lolcode+lua+magma+makefile+markdown+markup-templating+mata+matlab+maxscript+mel+mermaid+metafont+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nevod+nginx+nim+nix+nsis+objectivec+ocaml+odin+opencl+openqasm+oz+parigp+parser+pascal+pascaligo+psl+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plant-uml+plsql+powerquery+powershell+processing+prolog+promql+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+qsharp+q+qml+qore+r+racket+cshtml+jsx+tsx+reason+regex+rego+renpy+rescript+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+squirrel+stan+stata+iecst+stylus+supercollider+swift+systemd+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+tremor+turtle+twig+typescript+typoscript+unrealscript+uorazor+uri+v+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+web-idl+wgsl+wiki+wolfram+wren+xeora+xml-doc+xojo+xquery+yaml+yang+zig */ + +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + tab-size: 4; + tab-size: 4; + tab-size: 4; + hyphens: none; + hyphens: none; + hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.token.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + + /* This background color was intended by the author of this theme. */ + background: hsl(0 0% 100% / 50%); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + diff --git a/assets/css/styles.css b/assets/css/styles.css index 631dfa54..9bf86c0e 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -1,9 +1,12 @@ /* stylelint-disable selector-class-pattern */ /* stylelint-disable declaration-block-no-redundant-longhand-properties */ -@import "1-core/1-core.css"; -@import "1-core/2-variables.css"; +/* Core (careful before touching the foundations) */ +@import "1-core/1-reset.css"; +@import "1-core/2-core.css"; +@import "1-core/3-variables.css"; +/* Atomic (small) reusable components */ @import "2-components/1-navbar.css"; @import "2-components/2-package-component.css"; @import "2-components/3-breadcrumb.css"; @@ -12,270 +15,264 @@ @import "2-components/6-secondary-search.css"; @import "2-components/7-button.css"; @import "2-components/8-alert.css"; +@import "2-components/9-terminal-icon.css"; +@import "2-components/10-error.css"; +@import "2-components/11-advisory-list-item.css"; +@import "2-components/12-headline.css"; +/* Screens/Pages specific style */ @import "3-screens/1-package/1-package.css"; @import "3-screens/1-package/2-release-changelog.css"; @import "3-screens/1-package/3-release-readme.css"; @import "3-screens/1-package/4-compiler-badge.css"; +@import "3-screens/1-package/5-security.css"; @import "3-screens/2-categories.css"; @import "3-screens/3-about.css"; @import "3-screens/4-front-page.css"; +@import "3-screens/5-settings/1-dashboard.css"; -@import "4-helpers.css"; +/* Helpers/Utility classes */ +@import "4-helpers/1-globals.css"; +@import "4-helpers/2-colors.css"; +@import "4-helpers/3-spacing.css"; +@import "4-helpers/4-typography.css"; +@import "4-helpers/5-state.css"; +@import "prism.css"; /* Used for the sessions page */ .login-form { - margin-left: auto; - margin-right: auto; - margin-top: 3rem; - max-width: 28rem; - padding-left: 0.5rem; - padding-right: 0.5rem; - - h2 { - color: var(--text-color); - font-weight: 800; - font-size: 1.875rem; - line-height: 2.25rem; - text-align: center; - margin-bottom: 0.75rem; - } - - button { - color: var(--text-color); - } - - .form-input { - border-color: rgb(107 114 128); - border-radius: 6px; - border-width: 2px; - color: rgb(17 24 39); - display: block; - margin-bottom: 0.25rem; - margin-top: 0.25rem; - padding: 0.5rem; - width: 100%; - } - - .password { - margin-bottom: 1rem; - } - - .totp-zone { - display: none; - } - - input[type="checkbox"]:checked + div.totp-zone { - display: block; - } - - .login-button { - display: flex; - flex-wrap: wrap; - justify-content: center; - margin-top: 1rem; - } - - div.login-button button { - background-color: var(--main-page-button-background); - border-radius: 50rem; - border-width: 1px; - color: var(--text-color); - font-weight: bolder; - padding-bottom: 1rem; - padding-left: 2rem; - padding-right: 2rem; - padding-top: 1rem; - } - - div.login-button button:hover { - border-color: var(--main-page-button-focus-border-color); - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 200ms; - - /* offset-x | offset-y | blur-radius | spread-radius | color */ - box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color); - } + margin-left: auto; + margin-right: auto; + margin-top: 3rem; + max-width: 28rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + + h2 { + color: var(--text-color); + font-weight: 800; + font-size: 1.875rem; + line-height: 2.25rem; + text-align: center; + margin-bottom: 0.75rem; + } + + button { + color: var(--text-color); + } + + .form-input { + border-color: rgb(107 114 128); + border-radius: 6px; + border-width: 2px; + color: rgb(17 24 39); + display: block; + margin-bottom: 0.25rem; + margin-top: 0.25rem; + padding: 0.5rem; + width: 100%; + } + + .password { + margin-bottom: 1rem; + } + + .totp-zone { + display: none; + } + + input[type="checkbox"]:checked + div.totp-zone { + display: block; + } + + .login-button { + display: flex; + flex-wrap: wrap; + justify-content: center; + margin-top: 1rem; + } + + div.login-button button { + background-color: var(--main-page-button-background); + border-radius: 50rem; + border-width: 1px; + color: var(--text-color); + font-weight: bolder; + padding-bottom: 1rem; + padding-left: 2rem; + padding-right: 2rem; + padding-top: 1rem; + } + + div.login-button button:hover { + border-color: var(--main-page-button-focus-border-color); + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; + + /* offset-x | offset-y | blur-radius | spread-radius | color */ + box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color); + } } .version-list-item { - a { - display: block; - margin-top: 1rem; - margin-bottom: 1rem; - } - - a:hover h4.version-list-item__name { - text-decoration: underline; - } - - .version-list-item__name { - margin-right: 0.5rem; - display: inline; - color: var(--link-color); - font-weight: bolder; - font-size: 1.25rem; - line-height: 1.75rem; - } + a { + display: block; + margin-top: 1rem; + margin-bottom: 1rem; + } + + a:hover h4.version-list-item__name { + text-decoration: underline; + } + + .version-list-item__name { + margin-right: 0.5rem; + display: inline; + color: var(--link-color); + font-weight: bolder; + font-size: 1.25rem; + line-height: 1.75rem; + } } .package-list-item { - list-style: none; - - a { - display: block; - font-size: 1.25rem; - line-height: 1.75rem; - margin-top: 1rem; - margin-bottom: 1rem; - padding-left: 1rem; - padding-top: 0.25rem; - padding-bottom: 0.25rem; - } - - a:hover { - background-color: var(--package-list-item-background-hover-color); - border-radius: 6px; - } - - a:hover h4.package-list-item__name { - text-decoration: underline; - } - - .package-list-item__name { - display: inline; - color: var(--package-list-item-name-color); - } - - .package-list-item__synopsis { - display: inline; - margin-left: 10px; - color: var(--package-list-item-synopsis-color); - } - - .package-list-item__metadata { - color: var(--package-list-item-metadata-color); - - .package-list-item__license { - color: var(--package-list-item-metadata-color); - } - - .package-list-item__version { - color: var(--package-list-item-version-color); - } - } - - .package-list-item__metadata > * { - margin-top: 0.5rem; - margin-right: 10px; - font-size: 0.875rem; - line-height: 1.25rem; - } - - .package-list-item__component { - display: inline; - color: var(--package-list-item-component-color); - } + list-style: none; + + a { + display: block; + font-size: 1.25rem; + line-height: 1.75rem; + margin-top: 1rem; + margin-bottom: 1rem; + padding-left: 1rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + } + + a:hover { + background-color: var(--package-list-item-background-hover-color); + border-radius: 6px; + } + + a:hover h4.package-list-item__name { + text-decoration: underline; + } + + .package-list-item__name { + display: inline; + color: var(--package-list-item-name-color); + } + + .package-list-item__synopsis { + display: inline; + margin-left: 10px; + color: var(--package-list-item-synopsis-color); + } + + .package-list-item__metadata { + color: var(--package-list-item-metadata-color); + + .package-list-item__license { + color: var(--package-list-item-metadata-color); + } + + .package-list-item__version { + color: var(--package-list-item-version-color); + } + } + + .package-list-item__metadata > * { + margin-top: 0.5rem; + margin-right: 10px; + font-size: 0.875rem; + line-height: 1.25rem; + } + + .package-list-item__component { + display: inline; + color: var(--package-list-item-component-color); + } } .category a:hover { - text-decoration: underline; + text-decoration: underline; } .package-link a:hover { - text-decoration: underline; + text-decoration: underline; } .version-link a:hover { - text-decoration: underline; + text-decoration: underline; } .dependency { - white-space: nowrap; + white-space: nowrap; - a:hover { - text-decoration: underline; - } + a:hover { + text-decoration: underline; + } } .theme-button--light { - display: none; + display: none; } [data-theme="dark"] .theme-button--light { - display: inline-flex; - background-color: hsl(294 40% 30%); + display: inline-flex; + background-color: hsl(294 40% 30%); } .theme-button--dark { - display: inline-flex; + display: inline-flex; } [data-theme="dark"] .theme-button--dark { - display: none; -} - -.error-code { - color: var(--error-code); - font-size: 3rem; - line-height: 1; - font-weight: 800; -} - -.error-message { - letter-spacing: -0.025rem; - font-weight: 800; - font-size: 3rem; - line-height: 1; - color: var(--error-code); -} - -.error-page-button { - background-color: var(--error-button); + display: none; } /* Dark mode-specific rules */ .dark { - .social-button:hover { - color: hsl(254.3 95% 76.7%); - } + .social-button:hover { + color: hsl(254.3 95% 76.7%); + } } .page-title { - padding-top: 1rem; - padding-bottom: 1rem; - padding-left: 2rem; - padding-right: 2rem; - - h1 { - font-size: 3rem; - line-height: 1; - letter-spacing: -0.025rem; - text-align: center; - overflow-wrap: anywhere; - } - - .version { - font-size: 0.5em; - line-height: 1.3rem; - padding-left: 4px; - } + padding-top: 1rem; + padding-bottom: 1rem; + padding-left: 2rem; + padding-right: 2rem; + + h1 { + font-size: 3rem; + line-height: 1; + letter-spacing: -0.025rem; + text-align: center; + overflow-wrap: anywhere; + } + + .version { + font-size: 0.5em; + line-height: 1.3rem; + padding-left: 4px; + } } .dependents a.dependent:hover { - text-decoration: underline; + text-decoration: underline; } /* offset-x | offset-y | blur-radius | spread-radius | color */ .exact-match { - box-shadow: 0 5px 10px 0 rgb(0 0 0 / 50%); - border-radius: 6px; + box-shadow: 0 5px 10px 0 rgb(0 0 0 / 50%); + border-radius: 6px; } .package-count { - text-align: center; - font-size: 1.25rem; - line-height: 1.75rem; + text-align: center; + font-size: 1.25rem; + line-height: 1.75rem; } /* Media Query Breakpoints */ @@ -284,9 +281,9 @@ /* Medium rules */ @media (max-width: 48rem) { - .page-title { - h1 { - font-size: 2rem; - } - } + .page-title { + h1 { + font-size: 2rem; + } + } } diff --git a/assets/esbuild.config.js b/assets/esbuild.config.js index f22c9814..579e0a66 100644 --- a/assets/esbuild.config.js +++ b/assets/esbuild.config.js @@ -6,19 +6,22 @@ const chokidar = require('chokidar'); const path = require("path"); // PostCSS plugins -const postcssImport = require("postcss-import"); -const tailwindNesting = require("@tailwindcss/nesting"); -const tailwind = require("tailwindcss"); -const autoprefixer = require("autoprefixer"); -const postcssCopy = require("postcss-copy")({ - dest: "../assets/fonts", +const postcssImport = require("postcss-import"); +const postcssNesting = require("postcss-nesting"); +const postcssCustomMedia = require('postcss-custom-media'); +const autoprefixer = require("autoprefixer"); +const postcssCopy = require("postcss-copy")({ + dest: "../assets/fonts", }); +const postcssDesignTokenUtils = require("postcss-design-token-utils"); +const designTokensConfig = require("./style-tokens/tokens.js"); + let minify = false; let sourcemap = true; let entryNames = "[name]"; -const watchDirectories = [ "./css", "./js"]; +const watchDirectories = [ "./css", "./js", "./style-tokens"]; const mkProdPlugins = () => { return [ @@ -29,7 +32,8 @@ const mkProdPlugins = () => { console.log(assets); const orderAssets = { "app.js": assets.app.js, - "styles.css": assets[''].css[0] + "styles.css": assets[''].css[0], + "prism.js": assets.prism.js, } return JSON.stringify(orderAssets, null, " "); } @@ -40,16 +44,19 @@ const mkProdPlugins = () => { const pluginsList = () => { let plugins = [ postcssPlugin({ - plugins: [ - postcssImport, - tailwindNesting, - tailwind, - autoprefixer, - postcssCopy, + plugins: [ + postcssDesignTokenUtils({ + tokens: designTokensConfig, + }), + postcssImport, + postcssNesting, + postcssCustomMedia, + autoprefixer, + postcssCopy, ], })]; let prodPlugins = process.env.NODE_ENV === "prod" ? mkProdPlugins() : []; - return plugins.concat(prodPlugins); + return plugins.concat(prodPlugins); } if (process.env.NODE_ENV === "prod") { @@ -64,6 +71,7 @@ const config = { entryPoints: { "app": "./js/app.js", "styles": "./css/styles.css", + "prism": "./js/prism.js", }, outdir: "../static", bundle: true, diff --git a/assets/js/app.js b/assets/js/app.js index 001220e9..555218c3 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,3 +1,6 @@ +import './htmx.js'; +import 'htmx-ext-sse'; + import Alpine from "alpinejs"; window.Alpine = Alpine; Alpine.start(); diff --git a/assets/js/autoreload.js b/assets/js/autoreload.js deleted file mode 100644 index 8f507657..00000000 --- a/assets/js/autoreload.js +++ /dev/null @@ -1,42 +0,0 @@ -// const initialReconnectDelay = 1000; -// const maxReconnectDelay = 16000; -// var currentReconnectDelay = initialReconnectDelay; - -// var wasConnectionLost = false; - -// const connectToServer = () => { -// sessionStorage.setItem("connId", null); -// console.log("[+] Connecting to the autoreload websocket…"); -// const autoreloadSocket = new WebSocket( -// "ws://localhost:8083/sockets/autoreload" -// ); -// autoreloadSocket.addEventListener("close", onWebsocketClose); -// autoreloadSocket.addEventListener("error", onWebsocketClose); -// autoreloadSocket.addEventListener("open", (event) => -// onWebsocketOpen(autoreloadSocket, event) -// ); -// }; - -// const onWebsocketOpen = (socket, event) => { -// console.log("[+] Connection to server established!"); -// if (wasConnectionLost) { -// location.reload(true); -// } -// }; - -// const onWebsocketClose = () => { -// console.log("[!] Server disconnected, attempting to reconnect…"); -// wasConnectionLost = true; -// setTimeout(() => { -// reconnectToWebsocket(); -// }, currentReconnectDelay); -// }; - -// const reconnectToWebsocket = () => { -// if (currentReconnectDelay < maxReconnectDelay) { -// currentReconnectDelay *= 2; -// } -// connectToServer(); -// }; - -// connectToServer(); diff --git a/assets/js/htmx.js b/assets/js/htmx.js new file mode 100644 index 00000000..2cd43d0d --- /dev/null +++ b/assets/js/htmx.js @@ -0,0 +1,2 @@ +import htmx from "htmx.org"; +window.htmx = htmx; diff --git a/assets/js/prism.js b/assets/js/prism.js new file mode 100644 index 00000000..ba63c2cf --- /dev/null +++ b/assets/js/prism.js @@ -0,0 +1,21514 @@ +/* PrismJS 1.29.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apex+apl+applescript+aql+arduino+arff+armasm+arturo+asciidoc+aspnet+asm6502+asmatmel+autohotkey+autoit+avisynth+avro-idl+awk+bash+basic+batch+bbcode+bbj+bicep+birb+bison+bnf+bqn+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cfscript+chaiscript+cil+cilkc+cilkcpp+clojure+cmake+cobol+coffeescript+concurnas+csp+cooklang+coq+crystal+css-extras+csv+cue+cypher+d+dart+dataweave+dax+dhall+diff+django+dns-zone-file+docker+dot+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+false+firestore-security-rules+flow+fortran+ftl+gml+gap+gcode+gdscript+gedcom+gettext+gherkin+git+glsl+gn+linker-script+go+go-module+gradle+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+hoon+http+hpkp+hsts+ichigojam+icon+icu-message-format+idris+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jexl+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keepalived+keyman+kotlin+kumir+kusto+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+log+lolcode+lua+magma+makefile+markdown+markup-templating+mata+matlab+maxscript+mel+mermaid+metafont+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nevod+nginx+nim+nix+nsis+objectivec+ocaml+odin+opencl+openqasm+oz+parigp+parser+pascal+pascaligo+psl+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plant-uml+plsql+powerquery+powershell+processing+prolog+promql+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+qsharp+q+qml+qore+r+racket+cshtml+jsx+tsx+reason+regex+rego+renpy+rescript+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+squirrel+stan+stata+iecst+stylus+supercollider+swift+systemd+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+tremor+turtle+twig+typescript+typoscript+unrealscript+uorazor+uri+v+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+web-idl+wgsl+wiki+wolfram+wren+xeora+xml-doc+xojo+xquery+yaml+yang+zig */ +/// + +var _self = (typeof window !== 'undefined') + ? window // if in browser + : ( + (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) + ? self // if in worker + : {} // if in node js + ); + +/** + * Prism: Lightweight, robust, elegant syntax highlighting + * + * @license MIT + * @author Lea Verou + * @namespace + * @public + */ +var Prism = (function (_self) { + + // Private helper vars + var lang = /(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i; + var uniqueId = 0; + + // The grammar object for plaintext + var plainTextGrammar = {}; + + + var _ = { + /** + * By default, Prism will attempt to highlight all code elements (by calling {@link Prism.highlightAll}) on the + * current page after the page finished loading. This might be a problem if e.g. you wanted to asynchronously load + * additional languages or plugins yourself. + * + * By setting this value to `true`, Prism will not automatically highlight all code elements on the page. + * + * You obviously have to change this value before the automatic highlighting started. To do this, you can add an + * empty Prism object into the global scope before loading the Prism script like this: + * + * ```js + * window.Prism = window.Prism || {}; + * Prism.manual = true; + * // add a new