198 lines
5.1 KiB
Vue
198 lines
5.1 KiB
Vue
<script lang="ts" setup>
|
|
import { computed } from 'vue';
|
|
import useListExtensionDetails, {
|
|
ChromeExtension,
|
|
} from '../composables/useListExtensionDetails';
|
|
|
|
// Add extension IDs here. Order doesn't matter, will be sorted by weekly active users
|
|
// During the transition from chrome.google.com/webstore to
|
|
// chromewebstore.google.com, queue.wxt.dev might return null for your
|
|
// extension. If it does, use "<slug>/<id>" instead of just the ID. The slug
|
|
// can be retrieved from the URL of the item on chromewebstore.google.com
|
|
const chromeExtensionIds = [
|
|
'ocfdgncpifmegplaglcnglhioflaimkd', // GitHub: Better Line Counts
|
|
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
|
|
'bfbnagnphiehemkdgmmficmjfddgfhpl', // UltraWideo
|
|
'elfaihghhjjoknimpccccmkioofjjfkf', // StayFree - Website Blocker & Web Analytics
|
|
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
|
|
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
|
'youtube中文配音/oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
|
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
|
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
|
|
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
|
|
'ceicccfeikoipigeghddpocceifjelph', // Plex Skipper
|
|
'aelkipgppclpfimeamgmlonimflbhlgf', // GitHub Custom Notifier
|
|
'djnlaiohfaaifbibleebjggkghlmcpcj', // Fluent Read
|
|
'nhclljcpfmmaiojbhhnkpjcfmacfcian', // Facebook Video Controls
|
|
'mblkhbaakhbhiimkbcnmeciblfhmafna', // ElemSnap - Quick capture of webpage elements and conversion to images,
|
|
'oajalfneblkfiejoadecnmodfpnaeblh', // MS Edge TTS (Text to Speech)
|
|
'nedcanggplmbbgmlpcjiafgjcpdimpea', // YTBlock - Block any content from YouTube™
|
|
'oadbjpccljkplmhnjekgjamejnbadlne', // demo.fun - Interactive product demos that convert
|
|
'iopdafdcollfgaoffingmahpffckmjni', // SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
|
|
];
|
|
|
|
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
|
const sortedExtensions = computed(() => {
|
|
if (!data.value?.length) return [];
|
|
|
|
return [...data.value]
|
|
.filter((item) => item != null)
|
|
.map((item) => ({
|
|
...item,
|
|
// Sort based on the user count weighted by the rating
|
|
sortKey: ((item.rating ?? 5) / 5) * item.weeklyActiveUsers,
|
|
}))
|
|
.filter((item) => !!item)
|
|
.sort((l, r) => r.sortKey - l.sortKey);
|
|
});
|
|
|
|
function getStoreUrl(extension: ChromeExtension) {
|
|
const url = new URL(extension.storeUrl);
|
|
url.searchParams.set('utm_source', 'wxt.dev');
|
|
return url.href;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<p v-if="isLoading" style="text-align: center; opacity: 50%">Loading...</p>
|
|
<p
|
|
v-else-if="err || sortedExtensions.length === 0"
|
|
style="text-align: center; opacity: 50%"
|
|
>
|
|
Failed to load extension details.
|
|
</p>
|
|
<ul v-else>
|
|
<li
|
|
v-for="extension of sortedExtensions"
|
|
:key="extension.id"
|
|
class="relative"
|
|
>
|
|
<img
|
|
:src="extension.iconUrl"
|
|
:alt="`${extension.name} icon`"
|
|
referrerpolicy="no-referrer"
|
|
/>
|
|
<div class="relative">
|
|
<a
|
|
:href="getStoreUrl(extension)"
|
|
target="_blank"
|
|
:title="extension.name"
|
|
class="extension-name"
|
|
>{{ extension.name }}</a
|
|
>
|
|
<p class="description" :title="extension.shortDescription">
|
|
{{ extension.shortDescription }}
|
|
</p>
|
|
</div>
|
|
<p class="user-count">
|
|
<span>{{ extension.weeklyActiveUsers.toLocaleString() }} users</span
|
|
><template v-if="extension.rating != null"
|
|
>,
|
|
<span>{{ extension.rating }} stars</span>
|
|
</template>
|
|
</p>
|
|
</li>
|
|
</ul>
|
|
<p class="centered pr">
|
|
<a
|
|
href="https://github.com/wxt-dev/wxt/edit/main/docs/.vitepress/components/UsingWxtSection.vue"
|
|
target="_blank"
|
|
>Open a PR</a
|
|
>
|
|
to add your extension to the list!
|
|
</p>
|
|
</template>
|
|
|
|
<style scoped>
|
|
li img {
|
|
width: 116px;
|
|
height: 116px;
|
|
padding: 16px;
|
|
border-radius: 8px;
|
|
background-color: var(--vp-c-default-soft);
|
|
}
|
|
|
|
ul {
|
|
display: grid;
|
|
grid-template-columns: repeat(1, 1fr);
|
|
align-items: stretch;
|
|
gap: 16px;
|
|
list-style: none;
|
|
margin: 16px 0;
|
|
padding: 0;
|
|
}
|
|
@media (min-width: 960px) {
|
|
ul {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
}
|
|
|
|
li {
|
|
margin: 0 !important;
|
|
padding: 16px;
|
|
display: flex;
|
|
background-color: var(--vp-c-bg-soft);
|
|
border-radius: 12px;
|
|
flex: 1;
|
|
gap: 24px;
|
|
align-items: center;
|
|
}
|
|
|
|
.centered {
|
|
text-align: center;
|
|
}
|
|
|
|
li a,
|
|
li .user-count,
|
|
li .description {
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
li .user-count {
|
|
opacity: 70%;
|
|
font-size: small;
|
|
position: absolute;
|
|
bottom: 12px;
|
|
right: 16px;
|
|
}
|
|
|
|
li a {
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 1;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
cursor: pointer;
|
|
padding: 0;
|
|
margin: 0;
|
|
text-decoration: none;
|
|
}
|
|
li a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
li div {
|
|
flex: 1;
|
|
}
|
|
|
|
li .description {
|
|
opacity: 90%;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
li .extension-name {
|
|
font-size: large;
|
|
}
|
|
|
|
.pr {
|
|
opacity: 70%;
|
|
}
|
|
|
|
.relative {
|
|
position: relative;
|
|
}
|
|
</style>
|