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
3 changes: 3 additions & 0 deletions crates/desktop/src/lib/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,9 @@ export default {
searchToFindSkills: "Search to find skills from skills.sh",
poweredByVercel: "Powered by Vercel",
dataFromSkillsSh: "Data from skills.sh",
copySkillsShLink: "Copy skills.sh link",
skillsShLinkCopied: "skills.sh link copied",
skillsShLinkCopyFailed: "Failed to copy skills.sh link",

// MCP Detail
connection: "Connection",
Expand Down
3 changes: 3 additions & 0 deletions crates/desktop/src/lib/locales/zh-Hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,9 @@ export default {
searchToFindSkills: "搜索以从 skills.sh 查找技能",
poweredByVercel: "由 Vercel 提供支持",
dataFromSkillsSh: "数据来自 skills.sh",
copySkillsShLink: "复制 skills.sh 链接",
skillsShLinkCopied: "skills.sh 链接已复制",
skillsShLinkCopyFailed: "复制 skills.sh 链接失败",

// Projects
addProject: "添加项目",
Expand Down
3 changes: 3 additions & 0 deletions crates/desktop/src/lib/locales/zh-Hant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,9 @@ export default {
searchToFindSkills: "搜尋以從 skills.sh 尋找技能",
poweredByVercel: "由 Vercel 提供支援",
dataFromSkillsSh: "資料來自 skills.sh",
copySkillsShLink: "複製 skills.sh 連結",
skillsShLinkCopied: "skills.sh 連結已複製",
skillsShLinkCopyFailed: "複製 skills.sh 連結失敗",

// Projects
addProject: "新增專案",
Expand Down
140 changes: 107 additions & 33 deletions crates/desktop/src/pages/skills-sh/search.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { MagnifyingGlassIcon } from "@heroicons/react/24/solid";
import { Button, Spinner } from "@heroui/react";
import { Button, Spinner, toast } from "@heroui/react";
import { useInfiniteQuery } from "@tanstack/react-query";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { useQueryState } from "nuqs";
import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
Expand All @@ -23,6 +24,21 @@
const BATCH_SIZE = 20;
const FETCH_SIZE = 100;
const ROW_HEIGHT = 48;
const SKILLS_SH_BASE_URL = "https://www.skills.sh";

function splitUrlPath(path: string) {
return path.split("/").filter(Boolean);
}

function normalizeSkillsShPathParts(parts: string[]) {
return parts[0] === "github" ? parts.slice(1) : parts;
}

function formatSkillsShUrl(parts: string[]) {
const urlSegments = parts.map((part) => encodeURIComponent(part));

return `${SKILLS_SH_BASE_URL}/${urlSegments.join("/")}`;
}

const tableComponents: TableComponents<MarketSkill> = {
Table: ({ style, ...props }) => (
Expand All @@ -45,6 +61,38 @@
),
};

function buildSkillsShUrl(skill: MarketSkill) {
const slugParts = normalizeSkillsShPathParts(splitUrlPath(skill.slug));
if (slugParts.length > 1) {
return formatSkillsShUrl(slugParts);
}

const sourceParts = splitUrlPath(skill.source);
const pathParts = (() => {
if (sourceParts.length === 0) {
return [];
}

if (sourceParts[0] === "github") {
return sourceParts.slice(1);
}

if (sourceParts[0] === "site") {
return sourceParts;
}

if (sourceParts.length === 1 && sourceParts[0].includes(".")) {
return ["site", ...sourceParts];
}

return sourceParts;
})();
const skillParts =
slugParts.length > 0 ? slugParts : splitUrlPath(skill.name);

return formatSkillsShUrl([...pathParts, ...skillParts]);
}

export default function SkillsSearchPage() {
const { t, i18n } = useTranslation();
const api = useApi();
Expand Down Expand Up @@ -103,6 +151,21 @@

const hasMore = visibleCount < searchResults.length;

const handleCopySkillsShUrl = useCallback(
async (skill: MarketSkill) => {
const skillsShUrl = buildSkillsShUrl(skill);

try {
await writeText(skillsShUrl);
toast.success(t("skillsShLinkCopied"));
} catch (error) {
console.error("Failed to copy skills.sh URL:", error);
toast.danger(t("skillsShLinkCopyFailed"));
}
},
[t],
);

const handleEndReached = useCallback(() => {
if (hasMore && !isFetching) {
setVisibleCount((c) =>
Expand Down Expand Up @@ -170,38 +233,49 @@
fixedItemHeight={ROW_HEIGHT}
style={{ height: "100%" }}
components={tableComponents}
itemContent={(_index, skill) => (
<>
<td className="p-2 align-middle">
<span className="font-medium">
{skill.name}
</span>
</td>
<td className="p-2 align-middle">
<span className="text-muted">
{compactFormatter.format(
skill.installs,
)}
</span>
</td>
<td className="p-2 align-middle">
<span className="text-muted text-sm">
{skill.source}
</span>
</td>
<td className="p-2 align-middle">
<Button
size="sm"
variant="tertiary"
onPress={() =>
handleInstallClick(skill)
}
>
{t("install")}
</Button>
</td>
</>
)}
itemContent={(_index, skill) => {
return (
<>
<td className="p-2 align-middle">
<span className="font-medium">
{skill.name}
</span>
</td>
<td className="p-2 align-middle">
<span className="text-muted">
{compactFormatter.format(
skill.installs,
)}
</span>
</td>
<td className="p-2 align-middle">
<button
type="button"
className="block max-w-full truncate text-left text-sm text-muted underline-offset-4 hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"

Check failure on line 254 in crates/desktop/src/pages/skills-sh/search.tsx

View workflow job for this annotation

GitHub Actions / Check (windows-latest)

Unknown class detected: focus-visible:outline-primary

Check failure on line 254 in crates/desktop/src/pages/skills-sh/search.tsx

View workflow job for this annotation

GitHub Actions / Check (windows-latest)

Conflicting class detected: "focus-visible:outline-2" and "focus-visible:outline" apply the same CSS properties: ""outline-style", "outline-width""

Check failure on line 254 in crates/desktop/src/pages/skills-sh/search.tsx

View workflow job for this annotation

GitHub Actions / Check (windows-latest)

Conflicting class detected: "focus-visible:outline" and "focus-visible:outline-2" apply the same CSS properties: ""outline-style", "outline-width""

Check failure on line 254 in crates/desktop/src/pages/skills-sh/search.tsx

View workflow job for this annotation

GitHub Actions / Check (macos-latest)

Unknown class detected: focus-visible:outline-primary

Check failure on line 254 in crates/desktop/src/pages/skills-sh/search.tsx

View workflow job for this annotation

GitHub Actions / Check (macos-latest)

Conflicting class detected: "focus-visible:outline-2" and "focus-visible:outline" apply the same CSS properties: ""outline-style", "outline-width""

Check failure on line 254 in crates/desktop/src/pages/skills-sh/search.tsx

View workflow job for this annotation

GitHub Actions / Check (macos-latest)

Conflicting class detected: "focus-visible:outline" and "focus-visible:outline-2" apply the same CSS properties: ""outline-style", "outline-width""

Check failure on line 254 in crates/desktop/src/pages/skills-sh/search.tsx

View workflow job for this annotation

GitHub Actions / Check (ubuntu-latest)

Unknown class detected: focus-visible:outline-primary

Check failure on line 254 in crates/desktop/src/pages/skills-sh/search.tsx

View workflow job for this annotation

GitHub Actions / Check (ubuntu-latest)

Conflicting class detected: "focus-visible:outline-2" and "focus-visible:outline" apply the same CSS properties: ""outline-style", "outline-width""

Check failure on line 254 in crates/desktop/src/pages/skills-sh/search.tsx

View workflow job for this annotation

GitHub Actions / Check (ubuntu-latest)

Conflicting class detected: "focus-visible:outline" and "focus-visible:outline-2" apply the same CSS properties: ""outline-style", "outline-width""
aria-label={t("copySkillsShLink")}
onClick={() => {
void handleCopySkillsShUrl(
skill,
);
}}
>
{skill.source}
</button>
</td>
<td className="p-2 align-middle">
<Button
size="sm"
variant="tertiary"
onPress={() =>
handleInstallClick(skill)
}
>
{t("install")}
</Button>
</td>
</>
);
}}
>
<thead>
<tr>
Expand Down
Loading