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
117 changes: 117 additions & 0 deletions docs/.vitepress/components/AsideSponsors.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<script setup lang="ts">
import { useLocalStorage } from '@vueuse/core';
import type { Sponsor } from 'vitepress/dist/client/theme-default/components/VPSponsorsGrid.vue';
import { VPDocAsideSponsors } from 'vitepress/theme';
import { computed, onMounted } from 'vue';

/**
* @see https://graphql-docs-v2.opencollective.com/types/Member
*/
interface Backer {
account: {
name: string | null;
slug: string;
imageUrl: string | null;
};
since: string;
isActive: boolean;
totalDonations: {
value: number | null;
currency: string | null;
valueInCents: number | null;
};
}

const backersStorage = useLocalStorage<{
lastUpdated: number;
backers: Backer[];
}>('fakerjs-backers', {
lastUpdated: 0,
backers: [],
});

const sponsors = computed<Sponsor[]>(
() =>
backersStorage.value?.backers
.filter((backer) => backer.account.imageUrl)
.map((backer) => ({
name: backer.account.name ?? backer.account.slug,
img: backer.account.imageUrl!,
url: `https://opencollective.com/${backer.account.slug}`,
}))
.slice(0, 10) || []
Comment on lines +37 to +42
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please filter/reduce the data before writing it to local storage.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stored the full data on purpose, so if we need to adjust data or want to display more or less later, we don't have a breaking change but just can access the cached data as is.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't expect the used data to change. But 🤷

);

async function fetchBackers(): Promise<Backer[]> {
const response = await fetch('https://api.opencollective.com/graphql/v2', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
operationName: 'GetFakerJSDonations',
query: `query GetFakerJSDonations {
account(slug: "fakerjs") {
name
slug
members(role: BACKER, limit: 1000) {
totalCount
nodes {
account {
name
slug
imageUrl
}
since
isActive
totalDonations {
value
currency
valueInCents
}
}
}
}
}
`,
variables: {},
}),
}).then((res) => res.json());

return (response.data.account.members.nodes as Backer[]).toSorted(
(a, b) =>
(b.totalDonations.valueInCents ?? 0) -
(a.totalDonations.valueInCents ?? 0)
);
}

onMounted(async () => {
// Refresh every start of the month
const now = new Date();
const lastUpdated = new Date(backersStorage.value.lastUpdated);
if (
backersStorage.value.backers.length === 0 ||
now.getFullYear() !== lastUpdated.getFullYear() ||
now.getMonth() !== lastUpdated.getMonth()
) {
try {
const backers = await fetchBackers();
backersStorage.value = {
lastUpdated: Date.now(),
backers,
};
} catch {
// Silently ignore fetch errors; the section will hide itself when empty.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe cache the failure, to avoid using up all api limits.

}
}
});
</script>

<template>
<VPDocAsideSponsors
v-if="sponsors.length > 0"
tier="Top 10 Sponsors"
size="xmini"
:data="sponsors"
/>
</template>
26 changes: 12 additions & 14 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import DefaultTheme from 'vitepress/theme';
import { defineAsyncComponent, h } from 'vue';
import AsideSponsors from '../components/AsideSponsors.vue';
import './index.css';

export default {
...DefaultTheme,
extends: DefaultTheme,
Layout() {
return h(
DefaultTheme.Layout,
null,
__BANNER__
? {
'layout-top': () =>
h(
defineAsyncComponent(() => import('../components/Banner.vue')),
{ version: __BANNER__ }
),
}
: null
);
return h(DefaultTheme.Layout, null, {
'layout-top': __BANNER__
? () =>
h(
defineAsyncComponent(() => import('../components/Banner.vue')),
{ version: __BANNER__ }
)
: null,
'aside-ads-before': () => h(AsideSponsors),
});
},
};
Loading