From 6a2df7b64b2b2474e24fa620ccd1d0017c857c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Colombaro?= Date: Sun, 22 Sep 2024 16:49:48 +0200 Subject: [PATCH] Add sponsors integration (#251) --- .github/workflows/publish.yml | 4 + backers.json | 1 + package-lock.json | 252 ++++++++++++++++++++++ package.json | 4 +- scripts/fetchSponsors.mjs | 69 ++++++ src/components/Sponsors/index.js | 73 +++++++ src/components/Sponsors/styles.module.css | 45 ++++ src/pages/index.js | 2 + src/pages/sponsor.md | 2 +- 9 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 backers.json create mode 100644 scripts/fetchSponsors.mjs create mode 100644 src/components/Sponsors/index.js create mode 100644 src/components/Sponsors/styles.module.css diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e63dcf9..71ec699 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,8 @@ name: Publish on: push: + schedule: + - cron: '30 15 * * *' permissions: deployments: write @@ -27,6 +29,8 @@ jobs: - name: Install NPM packages run: npm ci + - name: Fetch sponsors + run: npm run fetch-sponsors --if-present - name: Run build run: npm run build --if-present diff --git a/backers.json b/backers.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/backers.json @@ -0,0 +1 @@ +[] diff --git a/package-lock.json b/package-lock.json index e55d751..7b7ba39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "devDependencies": { "@docusaurus/module-type-aliases": "3.5.2", "@docusaurus/types": "3.5.2", + "graphql-request": "^7.1.0", "prettier": "^3.3.3" } }, @@ -2993,6 +2994,16 @@ "node": ">=18.0" } }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -3153,6 +3164,91 @@ "react": ">=16" } }, + "node_modules/@molt/command": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@molt/command/-/command-0.9.0.tgz", + "integrity": "sha512-1JI8dAlpqlZoXyKWVQggX7geFNPxBpocHIXQCsnxDjKy+3WX4SGyZVJXuLlqRRrX7FmQCuuMAfx642ovXmPA9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@molt/types": "0.2.0", + "alge": "0.8.1", + "chalk": "^5.3.0", + "lodash.camelcase": "^4.3.0", + "lodash.snakecase": "^4.1.1", + "readline-sync": "^1.4.10", + "string-length": "^6.0.0", + "strip-ansi": "^7.1.0", + "ts-toolbelt": "^9.6.0", + "type-fest": "^4.3.1", + "zod": "^3.22.2" + } + }, + "node_modules/@molt/command/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@molt/command/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@molt/command/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@molt/command/node_modules/type-fest": { + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", + "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@molt/types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@molt/types/-/types-0.2.0.tgz", + "integrity": "sha512-p6ChnEZDGjg9PYPec9BK6Yp5/DdSrYQvXTBAtgrnqX6N36cZy37ql1c8Tc5LclfIYBNG7EZp8NBcRTYJwyi84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ts-toolbelt": "^9.6.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4244,6 +4340,19 @@ "ajv": "^8.8.2" } }, + "node_modules/alge": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/alge/-/alge-0.8.1.tgz", + "integrity": "sha512-kiV9nTt+XIauAXsowVygDxMZLplZxDWt0W8plE/nB32/V2ziM/P/TxDbSVK7FYIUt2Xo16h3/htDh199LNPCKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.ismatch": "^4.4.0", + "remeda": "^1.0.0", + "ts-toolbelt": "^9.6.0", + "zod": "^3.17.3" + } + }, "node_modules/algoliasearch": { "version": "4.24.0", "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", @@ -7439,6 +7548,49 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/graphql": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-request": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-7.1.0.tgz", + "integrity": "sha512-Ouu/lYVFhARS1aXeZoVJWnGT6grFJXTLwXJuK4mUGGRo0EUk1JkyYp43mdGmRgUVezpRm6V5Sq3t8jBDQcajng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.2.0", + "@molt/command": "^0.9.0", + "zod": "^3.23.8" + }, + "bin": { + "graffle": "build/cli/generate.js" + }, + "peerDependencies": { + "@dprint/formatter": "^0.3.0", + "@dprint/typescript": "^0.91.1", + "dprint": "^0.46.2", + "graphql": "14 - 16" + }, + "peerDependenciesMeta": { + "@dprint/formatter": { + "optional": true + }, + "@dprint/typescript": { + "optional": true + }, + "dprint": { + "optional": true + } + } + }, "node_modules/gray-matter": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", @@ -8838,18 +8990,39 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, + "node_modules/lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "license": "MIT" }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -13157,6 +13330,16 @@ "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==", "license": "MIT" }, + "node_modules/readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -13429,6 +13612,13 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remeda": { + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-1.61.0.tgz", + "integrity": "sha512-caKfSz9rDeSKBQQnlJnVW3mbVdFgxgGWQKq1XlFokqjf+hQD5gxutLGTTY2A/x24UxVyJe9gH5fAkFI63ULw4A==", + "dev": true, + "license": "MIT" + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -14321,6 +14511,51 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-length": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-6.0.0.tgz", + "integrity": "sha512-1U361pxZHEQ+FeSjzqRpV+cu2vTzYeWeafXFLykiFlv4Vc0n3njgU8HrMbyik5uwm77naWMuVG8fhEF+Ovb1Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -14752,6 +14987,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", @@ -15892,6 +16134,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index bfb8fa2..b237975 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", "lint": "prettier --check .", - "format": "prettier --write ." + "format": "prettier --write .", + "fetch-sponsors": "node scripts/fetchSponsors.mjs" }, "dependencies": { "@docusaurus/core": "3.5.2", @@ -25,6 +26,7 @@ "devDependencies": { "@docusaurus/module-type-aliases": "3.5.2", "@docusaurus/types": "3.5.2", + "graphql-request": "^7.1.0", "prettier": "^3.3.3" } } diff --git a/scripts/fetchSponsors.mjs b/scripts/fetchSponsors.mjs new file mode 100644 index 0000000..53e873c --- /dev/null +++ b/scripts/fetchSponsors.mjs @@ -0,0 +1,69 @@ +#!/usr/bin/env node + +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * https://github.com/jestjs/jest/blob/bd1c6db7c15c23788ca3e09c919138e48dd3b28a/website/fetchSupporters.js + */ + +import fs from 'fs' +import path from 'path' +import { promisify } from 'util' +import { gql, request } from 'graphql-request' + +// These sponsors will be featured on the homepage. +// These backers donate >100 USD per month, and are +// reviewed by the Jest team to confirm they are not +// donating just to juice their SEO. +const FEATURED_SPONSORS = new Set(['route4me']) +const graphqlQuery = gql` + { + account(slug: "yourls") { + orders(status: ACTIVE, limit: 1000) { + nodes { + tier { + slug + } + fromAccount { + name + slug + website + imageUrl + } + totalDonations { + value + } + } + } + } + } +` + +const writeFile = promisify(fs.writeFile) + +request('https://api.opencollective.com/graphql/v2', graphqlQuery) + .then((data) => { + const backers = data.account.orders.nodes + + const backersWithFeatured = backers.map((backer) => { + if (FEATURED_SPONSORS.has(backer.fromAccount.slug)) { + backer.featured = true + } + return backer + }) + + return writeFile( + path.resolve(path.dirname(''), 'backers.json'), + JSON.stringify(backersWithFeatured), + ) + }) + .then(() => { + console.log('Fetched 1 file: backers.json') + }) + .catch((error) => { + console.error('Failed to write backers file:', error) + process.exitCode = 1 + }) diff --git a/src/components/Sponsors/index.js b/src/components/Sponsors/index.js new file mode 100644 index 0000000..d44eaf5 --- /dev/null +++ b/src/components/Sponsors/index.js @@ -0,0 +1,73 @@ +import React from 'react' +import Link from '@docusaurus/Link' + +import styles from './styles.module.css' +import backers from '@site/backers.json' + +function Sponsor({ + fromAccount: { name, slug, website, imageUrl }, + totalDonations, +}) { + return ( + + { + {name + } + + ) +} + +export default function Sponsors() { + return ( +
+
+

Featured Sponsors

+
+ {backers + .filter((b) => b.featured) + .sort((a, b) => b.totalDonations.value - a.totalDonations.value) + .map(Sponsor)} +
+

+ YOURLS is free and open source, made possible by wonderful sponsors. +
+ + Join {backers.length}+ donors + {' '} + who sponsor YOURLS for $1 or more per month on{' '} + + OpenCollective + + . +

+
+ + Sponsor YOURLS + + + Read more on sponsoring + +
+
+
+ ) +} diff --git a/src/components/Sponsors/styles.module.css b/src/components/Sponsors/styles.module.css new file mode 100644 index 0000000..73596b6 --- /dev/null +++ b/src/components/Sponsors/styles.module.css @@ -0,0 +1,45 @@ +/** + * CSS files with the .module.css suffix will be treated as CSS modules + * and scoped locally. + */ + +.sponsors { + background-color: var(--ifm-hero-background-color); + text-align: center; + display: flex; + align-items: center; + padding: 2rem 0; + width: 100%; +} + +.sponsorsButtons { + display: flex; + margin-top: 48px; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 2rem; +} + +.sponsorsAvatars { + padding: 1rem 0; + border-radius: 50%; +} + +.sponsorItem { + margin: 2rem; +} + +.sponsorItem img { + background: white; +} + +.sponsorAvatar { + display: inline-block; + width: 75px; + height: 75px; + border-radius: 50%; + border: 1px solid white; + overflow: hidden; + object-fit: contain; +} diff --git a/src/pages/index.js b/src/pages/index.js index 479f395..25273bb 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -4,6 +4,7 @@ import Link from '@docusaurus/Link' import useDocusaurusContext from '@docusaurus/useDocusaurusContext' import Layout from '@theme/Layout' import HomepageFeatures from '@site/src/components/HomepageFeatures' +import Sponsors from '@site/src/components/Sponsors' import styles from './index.module.css' @@ -51,6 +52,7 @@ export default function Home() {
+
) diff --git a/src/pages/sponsor.md b/src/pages/sponsor.md index ce42277..e40a8ce 100644 --- a/src/pages/sponsor.md +++ b/src/pages/sponsor.md @@ -20,7 +20,7 @@ Help the project, become a sponsor and get your logo on our README on Github wit Donations and sponsorships can be done via the following methods: - [GitHub Sponsors](https://github.com/sponsors/YOURLS) -- [OpenCollective](https://opencollective.com/YOURLS) +- [OpenCollective](https://opencollective.com/yourls) Both monthly-recurring sponsorships and one-time donations are accepted, using various payment methods (credit cards, Paypal, even crypto currencies). Invoices can be obtained.