itismyskillmarket 1.3.21 → 1.3.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +28 -4
- package/gui/app.js +55 -0
- package/gui/index.html +9 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2081,6 +2081,8 @@ API_ROUTES.GET["/api/skills"] = async (_req, res, url) => {
|
|
|
2081
2081
|
const page = Math.max(1, parseInt(url.searchParams.get("page") || "1"));
|
|
2082
2082
|
const limit = Math.min(100, Math.max(1, parseInt(url.searchParams.get("limit") || "20")));
|
|
2083
2083
|
const search = url.searchParams.get("search") || "";
|
|
2084
|
+
const sort = url.searchParams.get("sort") || "name";
|
|
2085
|
+
const platform = url.searchParams.get("platform") || "";
|
|
2084
2086
|
const cacheKey = `search:${search}:limit:${limit}`;
|
|
2085
2087
|
let searchResult = getCached(cacheKey);
|
|
2086
2088
|
if (!searchResult) {
|
|
@@ -2118,16 +2120,38 @@ API_ROUTES.GET["/api/skills"] = async (_req, res, url) => {
|
|
|
2118
2120
|
platforms: meta?.platforms || [],
|
|
2119
2121
|
author: info.author?.name || pkg?.author?.name || "",
|
|
2120
2122
|
homepage: pkg?.homepage || "",
|
|
2121
|
-
repository: pkg?.repository?.url || ""
|
|
2123
|
+
repository: pkg?.repository?.url || "",
|
|
2124
|
+
updated: info.time?.[latestVersion] || info.time?.modified || ""
|
|
2122
2125
|
};
|
|
2123
2126
|
} catch {
|
|
2124
2127
|
fetchErrors++;
|
|
2125
2128
|
return null;
|
|
2126
2129
|
}
|
|
2127
2130
|
}, 3);
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
+
let skills = skillDetails.filter(Boolean);
|
|
2132
|
+
if (platform) {
|
|
2133
|
+
skills = skills.filter(
|
|
2134
|
+
(s) => Array.isArray(s.platforms) && s.platforms.includes(platform)
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2137
|
+
skills.sort((a, b) => {
|
|
2138
|
+
const nameA = (a.displayName || a.id || "").toLowerCase();
|
|
2139
|
+
const nameB = (b.displayName || b.id || "").toLowerCase();
|
|
2140
|
+
switch (sort) {
|
|
2141
|
+
case "-name":
|
|
2142
|
+
return nameB.localeCompare(nameA);
|
|
2143
|
+
case "updated":
|
|
2144
|
+
return (a.updated || "").localeCompare(b.updated || "");
|
|
2145
|
+
case "-updated":
|
|
2146
|
+
return (b.updated || "").localeCompare(a.updated || "");
|
|
2147
|
+
case "name":
|
|
2148
|
+
default:
|
|
2149
|
+
return nameA.localeCompare(nameB);
|
|
2150
|
+
}
|
|
2151
|
+
});
|
|
2152
|
+
const filteredTotal = skills.length;
|
|
2153
|
+
const totalPages = Math.ceil(filteredTotal / limit) || 1;
|
|
2154
|
+
jsonResponse(res, 200, { skills, page, totalPages, total: filteredTotal, fetchErrors });
|
|
2131
2155
|
} catch (err) {
|
|
2132
2156
|
jsonResponse(res, 500, {
|
|
2133
2157
|
error: String(err),
|
package/gui/app.js
CHANGED
|
@@ -11,6 +11,8 @@ const state = {
|
|
|
11
11
|
currentPage: 1,
|
|
12
12
|
pageSize: 20,
|
|
13
13
|
searchQuery: '',
|
|
14
|
+
sortBy: 'name',
|
|
15
|
+
platformFilter: '',
|
|
14
16
|
totalPages: 1,
|
|
15
17
|
previousView: 'skills',
|
|
16
18
|
};
|
|
@@ -626,6 +628,26 @@ function initializeControls() {
|
|
|
626
628
|
loadSkills();
|
|
627
629
|
});
|
|
628
630
|
|
|
631
|
+
// 排序
|
|
632
|
+
const sortSelect = document.getElementById('sort-select');
|
|
633
|
+
if (sortSelect) {
|
|
634
|
+
sortSelect.addEventListener('change', () => {
|
|
635
|
+
state.sortBy = sortSelect.value;
|
|
636
|
+
state.currentPage = 1;
|
|
637
|
+
loadSkills();
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// 平台分类过滤
|
|
642
|
+
const platformFilter = document.getElementById('platform-filter');
|
|
643
|
+
if (platformFilter) {
|
|
644
|
+
platformFilter.addEventListener('change', () => {
|
|
645
|
+
state.platformFilter = platformFilter.value;
|
|
646
|
+
state.currentPage = 1;
|
|
647
|
+
loadSkills();
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
629
651
|
// 刷新按钮
|
|
630
652
|
document.getElementById('refresh-skills').addEventListener('click', () => loadSkills());
|
|
631
653
|
document.getElementById('update-all').addEventListener('click', updateAllSkills);
|
|
@@ -669,11 +691,15 @@ async function loadSkills() {
|
|
|
669
691
|
const params = new URLSearchParams({
|
|
670
692
|
page: state.currentPage.toString(),
|
|
671
693
|
limit: state.pageSize.toString(),
|
|
694
|
+
sort: state.sortBy,
|
|
672
695
|
});
|
|
673
696
|
|
|
674
697
|
if (state.searchQuery) {
|
|
675
698
|
params.append('search', state.searchQuery);
|
|
676
699
|
}
|
|
700
|
+
if (state.platformFilter) {
|
|
701
|
+
params.append('platform', state.platformFilter);
|
|
702
|
+
}
|
|
677
703
|
|
|
678
704
|
const response = await fetch(`/api/skills?${params}`);
|
|
679
705
|
const data = await response.json();
|
|
@@ -686,6 +712,7 @@ async function loadSkills() {
|
|
|
686
712
|
renderSkills(data.skills || data, container);
|
|
687
713
|
renderPagination(data.page, data.totalPages || 1);
|
|
688
714
|
renderFetchWarning(data.fetchErrors);
|
|
715
|
+
updatePlatformFilterOptions(data.skills || []);
|
|
689
716
|
} catch (err) {
|
|
690
717
|
container.innerHTML = `<div class="loading">${t('error.generic')}: ${err.message}</div>`;
|
|
691
718
|
}
|
|
@@ -763,6 +790,34 @@ function renderFetchWarning(fetchErrors) {
|
|
|
763
790
|
// 分页
|
|
764
791
|
// -----------------------------------------------------------------------------
|
|
765
792
|
|
|
793
|
+
/** 收集所有可用平台,更新分类过滤下拉框 */
|
|
794
|
+
function updatePlatformFilterOptions(skills) {
|
|
795
|
+
const select = document.getElementById('platform-filter');
|
|
796
|
+
if (!select) return;
|
|
797
|
+
|
|
798
|
+
// 收集所有唯一的平台名
|
|
799
|
+
const allPlatforms = new Set();
|
|
800
|
+
(skills || []).forEach(s => {
|
|
801
|
+
if (Array.isArray(s.platforms)) {
|
|
802
|
+
s.platforms.forEach(p => allPlatforms.add(p));
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
const currentVal = select.value;
|
|
807
|
+
const sortedPlatforms = [...allPlatforms].sort();
|
|
808
|
+
|
|
809
|
+
// 只有当平台列表有变化时才重新渲染
|
|
810
|
+
const currentOptions = Array.from(select.options).slice(1).map(o => o.value).sort().join(',');
|
|
811
|
+
const newOptions = sortedPlatforms.join(',');
|
|
812
|
+
if (currentOptions === newOptions) return;
|
|
813
|
+
|
|
814
|
+
select.innerHTML = `
|
|
815
|
+
<option value="">All Platforms</option>
|
|
816
|
+
${sortedPlatforms.map(p => `<option value="${p}"${currentVal === p ? ' selected' : ''}>${p}</option>`).join('')}
|
|
817
|
+
`;
|
|
818
|
+
select.value = currentVal && allPlatforms.has(currentVal) ? currentVal : '';
|
|
819
|
+
}
|
|
820
|
+
|
|
766
821
|
function renderPagination(currentPage, totalPages) {
|
|
767
822
|
state.currentPage = currentPage;
|
|
768
823
|
state.totalPages = totalPages;
|
package/gui/index.html
CHANGED
|
@@ -37,6 +37,15 @@
|
|
|
37
37
|
<h2>Available Skills</h2>
|
|
38
38
|
<div class="controls">
|
|
39
39
|
<input type="text" id="search-input" placeholder="🔍 Search skills...">
|
|
40
|
+
<select id="sort-select">
|
|
41
|
+
<option value="name">Name A-Z</option>
|
|
42
|
+
<option value="-name">Name Z-A</option>
|
|
43
|
+
<option value="-updated">Recently Updated</option>
|
|
44
|
+
<option value="updated">Least Recently Updated</option>
|
|
45
|
+
</select>
|
|
46
|
+
<select id="platform-filter">
|
|
47
|
+
<option value="">All Platforms</option>
|
|
48
|
+
</select>
|
|
40
49
|
<select id="page-size">
|
|
41
50
|
<option value="10">10 per page</option>
|
|
42
51
|
<option value="20" selected>20 per page</option>
|