mirror of
https://github.com/tauri-apps/tauri-docs.git
synced 2026-01-31 00:35:16 +01:00
stuff script, ci, component, data
This commit is contained in:
50
.github/workflows/syncCommunityResources.yml
vendored
Normal file
50
.github/workflows/syncCommunityResources.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: 'Sync Community Resources'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# weekly on Mondays at 00:00 UTC
|
||||
- cron: '0 0 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync-community-resources:
|
||||
name: Sync Community Resources
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm i
|
||||
|
||||
- name: sync-community-resources
|
||||
run: pnpm build:community-resources
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- run: pnpm prettier src/data -w
|
||||
|
||||
# tauri-docs PR
|
||||
- name: Git config
|
||||
run: |
|
||||
git config --global user.name "tauri-bot"
|
||||
git config --global user.email "tauri-bot@tauri.app"
|
||||
|
||||
- name: Create pull request for updated docs
|
||||
id: cpr
|
||||
# soft fork of https://github.com/peter-evans/create-pull-request for security purposes
|
||||
uses: tauri-apps/create-pull-request@v3.4.1
|
||||
if: github.event_name != 'pull_request' && github.event_name != 'push'
|
||||
with:
|
||||
token: ${{ secrets.ORG_TAURI_BOT_PAT }}
|
||||
commit-message: 'chore(docs): Update Community Resources'
|
||||
branch: ci/v2/update-community-resources
|
||||
title: Update Community Resources
|
||||
labels: 'bot'
|
||||
@@ -14,6 +14,7 @@
|
||||
"dev": "astro dev",
|
||||
"format": "prettier -w --cache --plugin prettier-plugin-astro .",
|
||||
"format:check": "prettier -c --cache --plugin prettier-plugin-astro .",
|
||||
"build:community-resources": "pnpm --filter community-resources run build",
|
||||
"build:compatibility-table": "pnpm --filter compatibility-table run build",
|
||||
"build:references": "pnpm --filter js-api-generator run build",
|
||||
"build:releases": "pnpm --filter releases-generator run build",
|
||||
|
||||
22
packages/community-resources/.prettierrc
Normal file
22
packages/community-resources/.prettierrc
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"useTabs": false,
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.json", "*.md", "*.toml", "*.yml"],
|
||||
"options": {
|
||||
"useTabs": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.md", "*.mdx"],
|
||||
"options": {
|
||||
"printWidth": 80
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
218
packages/community-resources/build.ts
Normal file
218
packages/community-resources/build.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const OUTPUT_FILE = path.resolve(__dirname, '../../src/data/communityResources.json');
|
||||
|
||||
const GITHUB_TOKEN = process.env.GITHUB_TOKEN || null;
|
||||
const query = 'tauri-plugin-';
|
||||
const npmBaseUrl = 'https://registry.npmjs.org';
|
||||
const cratesBaseUrl = 'https://crates.io/';
|
||||
const githubBaseUrl = 'https://api.github.com/repos';
|
||||
|
||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
function cleanRepoUrl(url: string) {
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
// hmm
|
||||
return url.replace(/^git\+/, '').replace(/\.git$/, '');
|
||||
}
|
||||
|
||||
async function fetchJson(url: string, headers?: Headers) {
|
||||
if (!headers) {
|
||||
headers = new Headers();
|
||||
}
|
||||
if (!headers.has('User-Agent')) {
|
||||
headers.set(
|
||||
'User-Agent',
|
||||
'tauri-docs-plugins-discover (https://github.com/tauri-apps/tauri-docs - @vasfvitor)'
|
||||
);
|
||||
}
|
||||
|
||||
const res = await fetch(url, { headers });
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed ${url}: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function fetchCrates() {
|
||||
const results = [];
|
||||
let page = 1;
|
||||
const per_page = 100;
|
||||
while (true) {
|
||||
const url = `https://crates.io/api/v1/crates?page=${page}&per_page=${per_page}&q=${query}`;
|
||||
const j = await fetchJson(url);
|
||||
if (!j.crates || j.crates.length === 0) {
|
||||
break;
|
||||
}
|
||||
for (const c of j.crates) {
|
||||
if (!c.name || !c.name.startsWith(query)) {
|
||||
continue;
|
||||
}
|
||||
results.push({
|
||||
source: 'crates',
|
||||
name: c.name,
|
||||
description: c.description || '',
|
||||
version: c.max_version || c.newest_version || '',
|
||||
downloads: c.downloads || 0,
|
||||
repository: cleanRepoUrl(c.repository || c.homepage || ''),
|
||||
license: c.license || '',
|
||||
homepage: c.homepage || '',
|
||||
crates_io: `https://crates.io/crates/${c.name}`,
|
||||
});
|
||||
}
|
||||
if (j.meta && j.meta.total <= page * per_page) break;
|
||||
page++;
|
||||
await sleep(1001);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
interface ResultsItem {
|
||||
source: string;
|
||||
name: string;
|
||||
description: string;
|
||||
version: string;
|
||||
version_npm?: string;
|
||||
date?: string;
|
||||
repository: string | null;
|
||||
npm?: string;
|
||||
crates_io?: string;
|
||||
downloads?: number;
|
||||
github_stars?: number | null;
|
||||
}
|
||||
|
||||
async function fetchNpm() {
|
||||
const results: ResultsItem[] = [];
|
||||
const size = 250;
|
||||
const url = `https://registry.npmjs.org/-/v1/search?text=tauri-plugin-&size=${size}`;
|
||||
const j = await fetchJson(url);
|
||||
if (!j.objects) return results;
|
||||
for (const obj of j.objects) {
|
||||
const p = obj.package;
|
||||
const name = p.name;
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
if (!/(^|\/)tauri-plugin-/.test(name)) {
|
||||
continue;
|
||||
}
|
||||
const repo = p.links && p.links.repository ? p.links.repository : p.repository;
|
||||
results.push({
|
||||
source: 'npm',
|
||||
name,
|
||||
description: p.description || '',
|
||||
version: p.version || '',
|
||||
date: p.date || '',
|
||||
repository: cleanRepoUrl(repo || p.links?.homepage || ''),
|
||||
npm: `https://www.npmjs.com/package/${encodeURIComponent(name)}`,
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function extractGithubRepo(url: string) {
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const u = new URL(url);
|
||||
if (u.hostname !== 'github.com') {
|
||||
return null;
|
||||
}
|
||||
const parts = u.pathname.replace(/^\//, '').split('/');
|
||||
if (parts.length < 2) {
|
||||
return null;
|
||||
}
|
||||
return `${parts[0]}/${parts[1]}`;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchGithubStars(ownerRepo: string) {
|
||||
if (!ownerRepo) {
|
||||
return null;
|
||||
}
|
||||
const url = `https://api.github.com/repos/${ownerRepo}`;
|
||||
const headers = new Headers();
|
||||
headers.append('Accept', 'application/vnd.github+json');
|
||||
if (GITHUB_TOKEN) {
|
||||
headers.append('Authorization', `token ${GITHUB_TOKEN}`);
|
||||
}
|
||||
try {
|
||||
const j = await fetchJson(url, headers);
|
||||
return j.stargazers_count ?? null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
console.log('Fetching crates.io packages...');
|
||||
const crates = await fetchCrates();
|
||||
console.log(`Found ${crates.length} crates matching prefix.`);
|
||||
|
||||
console.log('Fetching npm packages...');
|
||||
const npm = await fetchNpm();
|
||||
console.log(`Found ${npm.length} npm packages matching prefix.`);
|
||||
|
||||
const map = new Map();
|
||||
|
||||
for (const c of crates) {
|
||||
map.set(c.name, { ...c });
|
||||
}
|
||||
for (const n of npm) {
|
||||
const existing = map.get(n.name);
|
||||
if (existing) {
|
||||
existing.npm = n.npm;
|
||||
existing.version_npm = n.version;
|
||||
existing.description = existing.description || n.description;
|
||||
existing.repository = existing.repository || n.repository;
|
||||
} else {
|
||||
map.set(n.name, { ...n });
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: fetch GitHub stars
|
||||
let count = 0;
|
||||
for (const [name, item] of map) {
|
||||
const ownerRepo = extractGithubRepo(item.repository);
|
||||
if (ownerRepo) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
item.github_stars = await fetchGithubStars(ownerRepo);
|
||||
count++;
|
||||
// Rate limit for GitHub API (especially without token): ~60 req/hour unauthenticated
|
||||
// Add small delay to avoid hitting rate limits
|
||||
if (!GITHUB_TOKEN && count % 10 === 0) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await sleep(1000);
|
||||
}
|
||||
} else {
|
||||
item.github_stars = null;
|
||||
}
|
||||
}
|
||||
|
||||
const items = Array.from(map.values()).sort(
|
||||
(a, b) => (b.github_stars || 0) - (a.github_stars || 0)
|
||||
);
|
||||
|
||||
const outputData = {
|
||||
generated: new Date().toISOString(),
|
||||
count: items.length,
|
||||
resources: items,
|
||||
};
|
||||
|
||||
await fs.mkdir(path.dirname(OUTPUT_FILE), { recursive: true });
|
||||
await fs.writeFile(OUTPUT_FILE, JSON.stringify(outputData, null, 2), 'utf8');
|
||||
console.log(`Wrote ${items.length} resources to ${OUTPUT_FILE}`);
|
||||
}
|
||||
|
||||
run().catch((e) => {
|
||||
console.error('Error generating resources:', e);
|
||||
process.exit(1);
|
||||
});
|
||||
19
packages/community-resources/package.json
Normal file
19
packages/community-resources/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "community-resources",
|
||||
"version": "1.0.0",
|
||||
"private": "true",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsm ./build.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"tsm": "^2.3.0",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
10
packages/community-resources/tsconfig.json
Normal file
10
packages/community-resources/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"strict": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -87,6 +87,18 @@ importers:
|
||||
specifier: ^5.3.3
|
||||
version: 5.9.3
|
||||
|
||||
packages/community-resources:
|
||||
dependencies:
|
||||
'@types/node':
|
||||
specifier: ^22.0.0
|
||||
version: 22.15.21
|
||||
tsm:
|
||||
specifier: ^2.3.0
|
||||
version: 2.3.0
|
||||
typescript:
|
||||
specifier: ^5.3.3
|
||||
version: 5.9.3
|
||||
|
||||
packages/compatibility-table:
|
||||
dependencies:
|
||||
'@iarna/toml':
|
||||
|
||||
@@ -6,6 +6,7 @@ packages:
|
||||
- packages/releases-generator
|
||||
- packages/compatibility-table
|
||||
- packages/fetch-sponsors
|
||||
- packages/community-resources
|
||||
onlyBuiltDependencies:
|
||||
- '@parcel/watcher'
|
||||
- esbuild
|
||||
|
||||
372
src/components/CommunityResources.astro
Normal file
372
src/components/CommunityResources.astro
Normal file
@@ -0,0 +1,372 @@
|
||||
---
|
||||
import communityResourcesData from '../data/communityResources.json';
|
||||
|
||||
const { resources, count, generated } = communityResourcesData;
|
||||
---
|
||||
|
||||
<community-resources>
|
||||
<div class="search-container">
|
||||
<input
|
||||
type="text"
|
||||
id="resource-search"
|
||||
placeholder="Search plugins by name or description..."
|
||||
class="search-input"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<div class="filters">
|
||||
<label>
|
||||
<span>Source:</span>
|
||||
<select id="filter-source" class="filter-select">
|
||||
<option value="">All</option>
|
||||
<option value="both">Both (npm & crates)</option>
|
||||
<option value="npm">npm only</option>
|
||||
<option value="crates">crates only</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Sort by:</span>
|
||||
<select id="sort-by" class="filter-select">
|
||||
<option value="stars">GitHub Stars</option>
|
||||
<option value="name">Name</option>
|
||||
<option value="downloads">Crate Downloads</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="summary" id="resource-summary">
|
||||
Showing <strong id="visible-count">{count}</strong> of <strong>{count}</strong> community plugins
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table class="resources-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name & Links</th>
|
||||
<th>Description</th>
|
||||
<th class="text-right">Crate Version</th>
|
||||
<th class="text-right">Crate Downloads</th>
|
||||
<th class="text-right">NPM Version</th>
|
||||
<th class="text-right">GitHub Stars</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="resources-tbody">
|
||||
{
|
||||
resources.map((item) => (
|
||||
<tr
|
||||
class="resource-row"
|
||||
data-name={item.name}
|
||||
data-description={item.description}
|
||||
data-has-npm={item.npm ? 'true' : 'false'}
|
||||
data-has-crates={item.crates_io ? 'true' : 'false'}
|
||||
data-stars={item.github_stars || 0}
|
||||
data-downloads={item.downloads || 0}
|
||||
>
|
||||
<td class="name-cell">
|
||||
<div class="name-with-links">
|
||||
<code>{item.name}</code>
|
||||
<div class="links">
|
||||
{item.repository && (
|
||||
<a
|
||||
href={item.repository}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Repository"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
|
||||
</svg>
|
||||
</a>
|
||||
)}
|
||||
{item.crates_io && (
|
||||
<a
|
||||
href={item.crates_io}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="crates.io"
|
||||
>
|
||||
<span class="link-text">crates</span>
|
||||
</a>
|
||||
)}
|
||||
{item.npm && (
|
||||
<a href={item.npm} target="_blank" rel="noopener noreferrer" title="npm">
|
||||
<span class="link-text">npm</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="description-cell">{item.description}</td>
|
||||
<td class="text-right">{item.version || '-'}</td>
|
||||
<td class="text-right">{item.downloads ? item.downloads.toLocaleString() : '-'}</td>
|
||||
<td class="text-right">{item.version_npm || '-'}</td>
|
||||
<td class="text-right">{item.github_stars ?? '-'}</td>
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="no-results hidden" id="no-results">No plugins match your search criteria.</div>
|
||||
</community-resources>
|
||||
|
||||
<style>
|
||||
.search-container {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 1rem;
|
||||
border: 1px solid var(--sl-color-gray-5);
|
||||
border-radius: 0.5rem;
|
||||
background: var(--sl-color-black);
|
||||
color: var(--sl-color-white);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: 2px solid var(--sl-color-text-accent);
|
||||
outline-offset: 0;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.filters label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--sl-color-gray-5);
|
||||
border-radius: 0.375rem;
|
||||
background: var(--sl-color-black);
|
||||
color: var(--sl-color-white);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.summary {
|
||||
font-size: 0.9rem;
|
||||
color: var(--sl-color-gray-2);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--sl-color-gray-5);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.resources-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.resources-table th,
|
||||
.resources-table td {
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--sl-color-gray-6);
|
||||
}
|
||||
|
||||
.resources-table thead {
|
||||
background: var(--sl-color-gray-6);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.resources-table th {
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.resources-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.resources-table tbody tr:hover {
|
||||
background: var(--sl-color-gray-7);
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.name-cell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.name-with-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.name-cell code {
|
||||
font-size: 0.85rem;
|
||||
color: var(--sl-color-text-accent);
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.description-cell {
|
||||
max-width: 400px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.links a {
|
||||
color: var(--sl-color-text-accent);
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.links a:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.link-text {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
padding: 3rem;
|
||||
text-align: center;
|
||||
color: var(--sl-color-gray-3);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.resources-table {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.resources-table th,
|
||||
.resources-table td {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.description-cell {
|
||||
max-width: 200px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
class CommunityResources extends HTMLElement {
|
||||
searchInput: HTMLInputElement;
|
||||
sourceFilter: HTMLSelectElement;
|
||||
sortSelect: HTMLSelectElement;
|
||||
tbody: HTMLElement;
|
||||
summaryEl: HTMLElement;
|
||||
visibleCountEl: HTMLElement;
|
||||
noResultsEl: HTMLElement;
|
||||
rows: HTMLTableRowElement[];
|
||||
totalCount: number;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.searchInput = this.querySelector('#resource-search') as HTMLInputElement;
|
||||
this.sourceFilter = this.querySelector('#filter-source') as HTMLSelectElement;
|
||||
this.sortSelect = this.querySelector('#sort-by') as HTMLSelectElement;
|
||||
this.tbody = this.querySelector('#resources-tbody') as HTMLElement;
|
||||
this.summaryEl = this.querySelector('#resource-summary') as HTMLElement;
|
||||
this.visibleCountEl = this.querySelector('#visible-count') as HTMLElement;
|
||||
this.noResultsEl = this.querySelector('#no-results') as HTMLElement;
|
||||
this.rows = Array.from(this.tbody.querySelectorAll('.resource-row'));
|
||||
this.totalCount = this.rows.length;
|
||||
|
||||
this.searchInput.addEventListener('input', () => this.filterAndSort());
|
||||
this.sourceFilter.addEventListener('change', () => this.filterAndSort());
|
||||
this.sortSelect.addEventListener('change', () => this.filterAndSort());
|
||||
}
|
||||
|
||||
filterAndSort() {
|
||||
const query = this.searchInput.value.toLowerCase();
|
||||
const sourceFilter = this.sourceFilter.value;
|
||||
|
||||
let visibleRows = this.rows.filter((row) => {
|
||||
const name = row.dataset.name?.toLowerCase() || '';
|
||||
const description = row.dataset.description?.toLowerCase() || '';
|
||||
const hasNpm = row.dataset.hasNpm === 'true';
|
||||
const hasCrates = row.dataset.hasCrates === 'true';
|
||||
|
||||
const matchesQuery = !query || name.includes(query) || description.includes(query);
|
||||
|
||||
let matchesSource = true;
|
||||
if (sourceFilter === 'both') {
|
||||
matchesSource = hasNpm && hasCrates;
|
||||
} else if (sourceFilter === 'npm') {
|
||||
matchesSource = hasNpm;
|
||||
} else if (sourceFilter === 'crates') {
|
||||
matchesSource = hasCrates;
|
||||
}
|
||||
|
||||
return matchesQuery && matchesSource;
|
||||
});
|
||||
|
||||
const sortBy = this.sortSelect.value;
|
||||
visibleRows.sort((a, b) => {
|
||||
if (sortBy === 'stars') {
|
||||
return (parseInt(b.dataset.stars || '0') || 0) - (parseInt(a.dataset.stars || '0') || 0);
|
||||
} else if (sortBy === 'name') {
|
||||
return (a.dataset.name || '').localeCompare(b.dataset.name || '');
|
||||
} else if (sortBy === 'downloads') {
|
||||
return (
|
||||
(parseInt(b.dataset.downloads || '0') || 0) -
|
||||
(parseInt(a.dataset.downloads || '0') || 0)
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.rows.forEach((row) => row.classList.add('hidden'));
|
||||
visibleRows.forEach((row) => {
|
||||
row.classList.remove('hidden');
|
||||
|
||||
this.tbody.appendChild(row);
|
||||
});
|
||||
|
||||
const visibleCount = visibleRows.length;
|
||||
this.visibleCountEl.textContent = visibleCount.toString();
|
||||
|
||||
if (visibleCount === 0) {
|
||||
this.noResultsEl.classList.remove('hidden');
|
||||
this.querySelector('.table-wrapper')?.classList.add('hidden');
|
||||
} else {
|
||||
this.noResultsEl.classList.add('hidden');
|
||||
this.querySelector('.table-wrapper')?.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('community-resources', CommunityResources);
|
||||
</script>
|
||||
12
src/content/docs/plugin/index-external-resources.mdx
Normal file
12
src/content/docs/plugin/index-external-resources.mdx
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Community Plugins
|
||||
i18nReady: true
|
||||
sidebar:
|
||||
label: Overview
|
||||
tableOfContents: false
|
||||
template: splash
|
||||
---
|
||||
|
||||
import CommunityResources from '@components/CommunityResources.astro';
|
||||
|
||||
<CommunityResources />
|
||||
@@ -12,6 +12,7 @@ import CommunityList from '@components/list/Community.astro';
|
||||
import Search from '@components/CardGridSearch.astro';
|
||||
import AwesomeTauri from '@components/AwesomeTauri.astro';
|
||||
import TableCompatibility from '@components/plugins/TableCompatibility.astro';
|
||||
import CommunityResources from '@components/CommunityResources.astro';
|
||||
import { platformOptions } from 'src/types';
|
||||
import { platformFilters } from 'src/api/search.ts';
|
||||
|
||||
@@ -21,6 +22,8 @@ Tauri comes with extensibility in mind. On this page you'll find:
|
||||
- **[Community Resources](#community-plugins)**: More plugins and recipes built by the Tauri community. You can also contribute your own on [Awesome Tauri](https://github.com/tauri-apps/awesome-tauri)
|
||||
- **[Support Table](#support-table)**: A compatibility table showing which platforms are supported by each official plugin
|
||||
|
||||
Also we have a dedicated page for listing discovered community tauri-plugin-* [Crates and NPM packages](/plugin/index-external-resources/)
|
||||
|
||||
<br />
|
||||
|
||||
**Use the search and filter functionality to find features or community resources:**
|
||||
@@ -34,6 +37,8 @@ Tauri comes with extensibility in mind. On this page you'll find:
|
||||
<AwesomeTauri section="integrations" />
|
||||
</Search>
|
||||
|
||||
|
||||
|
||||
## Support Table
|
||||
|
||||
Hover "\*" to see notes. For more details visit the plugin page
|
||||
|
||||
3984
src/data/communityResources.json
Normal file
3984
src/data/communityResources.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user