Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 104 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,130 @@

Get dynamically generated StackOverflow stats on your readmes.

```markdown
Use the HTML `img` tag:

```html
<img height="137px"
src="https://stackoverflow-card.vercel.app/?userID=353337&theme=dracula"
src="https://stackoverflow-card-tan.vercel.app/?userID=23095094"
/>
```
Or use the Markdown image syntax:

```md
![StackOverflow Card](https://stackoverflow-card-tan.vercel.app/?userID=23095094)
```

<img height="137px"
src="https://stackoverflow-card-tan.vercel.app/?userID=23095094"
/>

You can optionally provide a site parameter to retrieve data from a specific site in the StackExchange ecosystem.<br>For example:

```html
<img height="137px"
src="https://stackoverflow-card.vercel.app/?userID=353337&theme=dracula"
src="https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow"
/>
```

```md
![StackOverflow Card](https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow)
```
<img height="137px"
src="https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow"
/>

You have to provide a valid `userID`. Apart from that, StackOverflow Card supports
several options (with their default values):
```
```js
showLogo: true
theme: [stackoverflow-dark, stackoverflow-light, dracula, ...]
showBorder: true
showIcons: true
showAnimations: true
site: stackoverflow
```
### Themes
With built-in themes, you can customize the look of the card.

Use `&theme=THEME_NAME` parameter like so:
```md
![StackOverflow Card](https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=dracula)
```
<details>
<summary>Show example</summary>

![StackOverflow Card](https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=dracula)

</details>

#### All built-in themes
StackOverflow Card comes with several built-in themes (E.g. `dracula`, `stackoverflowdark`, `stackoverflowdark`, `gruvboxdark`, `gruvboxlight`, `solarizeddark`, `solarizedlight`, `tomorrownight`, `tomorrow`).

See [here](src/themes.js) for all available themes.

Some card examples can be found [here](themes.md).

#### Use GitHub's theme context tag

You can use [GitHub's theme context](https://github.blog/changelog/2021-11-24-specify-theme-context-for-images-in-markdown/) to make the card match the user's GitHub theme automatically. Just add `#gh-dark-mode-only` or `#gh-light-mode-only` at the end of an image link. This tells GitHub how to show the card to users with a light or dark theme preference.

```md
[![StackOverflow Card Light](https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=tomorrow#gh-light-mode-only)](https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=tomorrow#gh-light-mode-only)
[![StackOverflow Card Dark](https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=dracula#gh-dark-mode-only)](https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=dracula#gh-dark-mode-only)
```
See [here](https://github.com/nschloe/stackoverflow-card/blob/main/src/themes.js) for
all available themes. Some examples
[here](https://github.com/nschloe/stackoverflow-card/blob/main/themes.md).
<details>
<summary>Show example</summary>

[![StackOverflow Card Light](https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=tomorrow#gh-light-mode-only)](https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=tomorrow#gh-light-mode-only)
[![StackOverflow Card Dark](https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=dracula#gh-dark-mode-only)](https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=dracula#gh-dark-mode-only)

</details>

#### Use GitHub's new media feature

You can also use [GitHub's new media feature](https://github.blog/changelog/2022-05-19-specify-theme-context-for-images-in-markdown-beta/) in HTML to control how cards appear in light or dark themes. This is done using the `picture` element in combination with the `prefers-color-scheme` media feature.

```html
<picture>
<source
srcset="https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=tomorrow"
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
/>
<source
srcset="https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=dracula"
media="(prefers-color-scheme: dark)"
/>
<img src="https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=dracula" />
</picture>
```

<details>
<summary>Show example</summary>

<picture>
<source
srcset="https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=tomorrow"
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
/>
<source
srcset="https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=dracula"
media="(prefers-color-scheme: dark)"
/>
<img src="https://stackoverflow-card-tan.vercel.app/?userID=536223&site=ru.stackoverflow&theme=dracula" />
</picture>

</details>

### Development

Start
```
nodemon index.js
```sh
nodemon index.js # or npm run start
```
and point a browser to
```
http://localhost:3000/?userID=353337&theme=stackoverflow-dark
```

http://localhost:3000/?userID=536223&site=ru.stackoverflow&theme=stackoverflow-dark

with the desired options.


Expand Down
19 changes: 14 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ http
}
const userID = searchParams.get("userID");

// Get the site parameter and remove .com if it exists
let site = searchParams.has("site")
? searchParams.get("site")
: "stackoverflow";

if (site.endsWith('.com')) {
site = site.substring(0, site.length - 4);
}

const showLogo = searchParams.has("showLogo")
? stringToBoolean(searchParams.get("showLogo"))
: true;
Expand All @@ -64,8 +73,9 @@ http
? stringToBoolean(searchParams.get("showAnimations"))
: true;

// Using site in API request
const responseArticles = await fetch(
`https://api.stackexchange.com/2.3/users/${userID}?site=stackoverflow`
`https://api.stackexchange.com/2.3/users/${userID}?site=${site}`
);
const json = await responseArticles.json();

Expand All @@ -75,10 +85,10 @@ http
return;
}

// Using site to generate URL ratings
const res2 = await fetch(
`https://stackoverflow.com/users/rank\?userId\=${userID}`
`https://${site}.com/users/rank?userId=${userID}`
);
// get text, trim, and remove tags
const ratingText = (await res2.text()).trim().replace(/(<([^>]+)>)/gi, "");

const result = await StackOverflowCard(
Expand All @@ -98,10 +108,9 @@ http
// res.setHeader("Expires", "-1");
// res.setHeader("Pragma", "no-cache");
res.writeHead(200, { "Content-Type": "image/svg+xml" });

res.write(result);
res.end();
})
.listen(process.env.PORT || 3000, function () {
console.log("server start at port 3000");
});
});
7 changes: 7 additions & 0 deletions src/artwork.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 27 additions & 2 deletions src/stackoverflow-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
reputation,
achievementsSm,
medal,
source,
logo,
} from "./artwork.js";
import { themes } from "./themes.js";
Expand Down Expand Up @@ -43,6 +44,16 @@ const statLine = (icon, iconColor, label, value) => {
`;
};

function getRootUrl(url) {
try {
const parsedUrl = new URL(url.startsWith('http') ? url : `https://${url}`);
return parsedUrl.hostname;
} catch (error) {
console.error('Invalid URL:', error);
return null;
}
}

export const StackOverflowCard = async (
data,
ratingText,
Expand All @@ -64,7 +75,7 @@ export const StackOverflowCard = async (
}

const width = 325;
const height = showLogo ? 150 : 105;
let height = showLogo ? 170 : 125;

let logoSvg;
if (showLogo) {
Expand All @@ -81,6 +92,13 @@ export const StackOverflowCard = async (

const iconSize = 16;

const lineSrc = statLine(
showIcons ? source(iconSize) : null,
colors.icon,
"Source",
getRootUrl(data.link)
);

const lineRep = statLine(
showIcons ? coinsMono(iconSize) : null,
colors.icon,
Expand Down Expand Up @@ -119,7 +137,14 @@ export const StackOverflowCard = async (
40
);

const lines = [lineRep, lineRepYear, lineRating, lineBadges];
let lines
if(getRootUrl(data.link) == "stackoverflow.com"){
lines = [lineRep, lineRepYear, lineRating, lineBadges];
height = showLogo ? 150 : 125;
} else {
lines = [lineSrc, lineRep, lineRepYear, lineRating, lineBadges];
}

let linesStr = ``;
const yOffset = showLogo ? 55 : 0;
for (let i = 0; i < lines.length; i++) {
Expand Down