diff --git a/source/app/mocks/api/github/graphql/sponsors.default.mjs b/source/app/mocks/api/github/graphql/sponsors.default.mjs new file mode 100644 index 00000000..3f886807 --- /dev/null +++ b/source/app/mocks/api/github/graphql/sponsors.default.mjs @@ -0,0 +1,28 @@ +/**Mocked data */ +export default function({faker, query, login = faker.internet.userName()}) { + console.debug("metrics/compute/mocks > mocking graphql api result > sponsors/default") + return ({ + user:{ + sponsorsListing:{ + fullDescription:faker.lorem.sentences(), + activeGoal:{ + percentComplete:faker.datatype.number(100), + title:faker.lorem.sentence(), + description:faker.lorem.sentence(), + } + }, + sponsorshipsAsMaintainer:{ + totalCount:faker.datatype.number(100), + nodes:new Array(10).fill(null).map(_ => ({ + sponsorEntity:{ + login:faker.internet.userName(), + avatarUrl:null, + }, + tier:{ + monthlyPriceInDollars:faker.datatype.number(10), + } + })) + } + }, + }) +} \ No newline at end of file diff --git a/source/app/web/statics/app.placeholder.js b/source/app/web/statics/app.placeholder.js index 44f60d21..2afa7419 100644 --- a/source/app/web/statics/app.placeholder.js +++ b/source/app/web/statics/app.placeholder.js @@ -353,6 +353,26 @@ } }) : null), + //Sponsors + ...(set.plugins.enabled.sponsors + ? ({ + sponsors: { + sections: options["sponsors.sections"].split(",").map(x => x.trim()), + about: "A new way to contribute to open source", + list: new Array(Number(faker.datatype.number(40))).fill(null).map(_ => ({ + login: faker.internet.userName(), + amount: faker.datatype.number(10), + avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==", + })), + count: faker.datatype.number(100), + goal: { + progress: faker.datatype.number(100), + title: `$${faker.datatype.number(100)*10} per month`, + description: "Invest in the software that powers your world" + } + } + }) + : null), //Languages ...(set.plugins.enabled.languages ? ({ diff --git a/source/plugins/sponsors/README.md b/source/plugins/sponsors/README.md new file mode 100644 index 00000000..4be4ad84 --- /dev/null +++ b/source/plugins/sponsors/README.md @@ -0,0 +1,25 @@ +### 💕 GitHub Sponsors + +The *sponsors* plugin lets you display your sponsors and introduction text from [GitHub sponsors](https://github.com/sponsors/). + + + +
+ +
With GitHub sponsors introduction + +
+ +
+ +#### â„šī¸ Examples workflows + +[âžĄī¸ Available options for this plugin](metadata.yml) + +```yaml +- uses: lowlighter/metrics@latest + with: + # ... other options + plugin_sponsors: yes + plugin_sponsors_sections: goal, about # Display goal and about sections +``` \ No newline at end of file diff --git a/source/plugins/sponsors/index.mjs b/source/plugins/sponsors/index.mjs new file mode 100644 index 00000000..2f467de0 --- /dev/null +++ b/source/plugins/sponsors/index.mjs @@ -0,0 +1,26 @@ +//Setup +export default async function({login, q, imports, data, graphql, queries, account}, {enabled = false} = {}) { + //Plugin execution + try { + //Check if plugin is enabled and requirements are met + if ((!enabled)||(!q.sponsors)) + return null + + //Load inputs + const {sections} = await imports.metadata.plugins.sponsors.inputs({data, account, q}) + + //Query sponsors and goal + const {[account]:{sponsorsListing:{fullDescription, activeGoal}, sponsorshipsAsMaintainer:{nodes, totalCount:count}}} = await graphql(queries.sponsors({login, account})) + const about = await imports.markdown(fullDescription, {mode:"multiline"}) + const goal = activeGoal ? {progress:activeGoal.percentComplete, title:activeGoal.title, description:await imports.markdown(activeGoal.description)} : null + const list = nodes.map(({sponsorEntity:{login, avatarUrl}, tier:{monthlyPriceInDollars:amount}}) => ({login, avatarUrl, amount})) + await Promise.all(list.map(async user => user.avatar = await imports.imgb64(user.avatarUrl))) + + //Results + return {sections, about, list, count, goal} + } + //Handle errors + catch (error) { + throw {error:{message:"An error occured", instance:error}} + } +} diff --git a/source/plugins/sponsors/metadata.yml b/source/plugins/sponsors/metadata.yml new file mode 100644 index 00000000..81966e64 --- /dev/null +++ b/source/plugins/sponsors/metadata.yml @@ -0,0 +1,25 @@ +name: "💕 GitHub Sponsors" +cost: 1 GraphQL request +category: github +index: 23 +supports: + - user + - organization +inputs: + + # Enable or disable plugin + plugin_sponsors: + description: Display GitHub sponsors + type: boolean + default: no + + # Sections to display + plugin_sponsors_sections: + description: Sections to display + type: array + format: comma-separated + default: goal, about + example: goal, about + values: + - goal # Display your GitHub active goal + - about # Display your GitHub sponsors introduction \ No newline at end of file diff --git a/source/plugins/sponsors/queries/sponsors.graphql b/source/plugins/sponsors/queries/sponsors.graphql new file mode 100644 index 00000000..18eb1cc3 --- /dev/null +++ b/source/plugins/sponsors/queries/sponsors.graphql @@ -0,0 +1,26 @@ +query SponsorsDefault { + $account(login: "$login") { + sponsorsListing { + fullDescription + activeGoal { + percentComplete + title + description + } + } + sponsorshipsAsMaintainer(first: 100) { + totalCount + nodes { + sponsorEntity { + ... on User { + login + avatarUrl(size: 36) + } + } + tier { + monthlyPriceInDollars + } + } + } + } +} diff --git a/source/plugins/sponsors/tests.yml b/source/plugins/sponsors/tests.yml new file mode 100644 index 00000000..716c1d28 --- /dev/null +++ b/source/plugins/sponsors/tests.yml @@ -0,0 +1,5 @@ +- name: Sponsors plugin (default) + uses: lowlighter/metrics@latest + with: + token: MOCKED_TOKEN + plugin_sponsors: yes \ No newline at end of file diff --git a/source/templates/classic/partials/_.json b/source/templates/classic/partials/_.json index c02068e3..96e20eed 100644 --- a/source/templates/classic/partials/_.json +++ b/source/templates/classic/partials/_.json @@ -32,5 +32,6 @@ "stock", "achievements", "screenshot", - "code" + "code", + "sponsors" ] diff --git a/source/templates/classic/partials/sponsors.ejs b/source/templates/classic/partials/sponsors.ejs new file mode 100644 index 00000000..415fa791 --- /dev/null +++ b/source/templates/classic/partials/sponsors.ejs @@ -0,0 +1,60 @@ +<% if (plugins.sponsors) { %> +
+

+ + Sponsor me! +

+ <% if (plugins.sponsors.error) { %> +
+
+
+ + <%= plugins.sponsors.error.message %> +
+
+
+ <% } else { %> + <% for (const section of plugins.sponsors.sections) { %> + <% if ((section === "goal")&&(plugins.sponsors.goal)) { %> +
+
+
+ <%= plugins.sponsors.goal.description %> +
+ <% { const width = 440 * (1 + large) %> +
+ + + + + + + +
+ <% } %> +
+ + <% if (plugins.sponsors.count) { %> + <%= plugins.sponsors.count %> sponsor<%= plugins.sponsors.count !== 1 ? "s are" : " is" %> funding <%= user.login %>'s work + <% } %> + + <%= plugins.sponsors.goal.title %> +
+
+ <% for (const user of plugins.sponsors.list) { %><% } %> +
+
+
+ <% } else if (section === "about") { %> +
+
+
+ <%- plugins.sponsors.about %> +
+
+
+ <% } %> + <% } %> + <% } %> +
+<% } %> \ No newline at end of file diff --git a/source/templates/classic/style.css b/source/templates/classic/style.css index ba0c7f24..1385d50b 100644 --- a/source/templates/classic/style.css +++ b/source/templates/classic/style.css @@ -931,11 +931,26 @@ margin-right: 0; } -/* Introduction */ - .introduction { +/* Introduction and sponsors */ + .introduction, .sponsors { white-space: normal; margin: 0 13px 2px; } + .sponsors.goal { + padding: 6px 8px; + border-radius: 5px; + background-color: #7777771F; + } + .sponsors .goal-text { + display: flex; + justify-content: space-between; + font-style: italic; + font-size: 10px; + margin-bottom: 4px; + } + .sponsors .avatar { + margin: 2px; + } /* Stackoverflow */ .stackoverflow { @@ -1137,6 +1152,22 @@ display: inline-block; width: 97%; } + .markdown p { + margin: 8px 0; + } + .markdown ul { + padding-left: 24px; + } + .markdown a { + color: #58a6ff; + text-decoration: none; + } + .markdown blockquote { + border-left: 4px solid #7777771F; + color: #777777; + margin: 0; + padding-left: 16px; + } code { background-color: #7777771F; display: inline-block;