itismyskillmarket 1.3.21 → 1.3.23
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 +30 -4
- package/gui/app.js +72 -0
- package/gui/index.html +9 -0
- package/gui/style.css +48 -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,40 @@ 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
|
+
const start = (page - 1) * limit;
|
|
2155
|
+
const pagedSkills = skills.slice(start, start + limit);
|
|
2156
|
+
jsonResponse(res, 200, { skills: pagedSkills, page, totalPages, total: filteredTotal, fetchErrors });
|
|
2131
2157
|
} catch (err) {
|
|
2132
2158
|
jsonResponse(res, 500, {
|
|
2133
2159
|
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;
|
|
@@ -777,6 +832,12 @@ function renderPagination(currentPage, totalPages) {
|
|
|
777
832
|
let html = `
|
|
778
833
|
<button ${currentPage <= 1 ? 'disabled' : ''} onclick="changePage(${currentPage - 1})">${t('pagination.prev')}</button>
|
|
779
834
|
<span class="page-info">${t('pagination.pageInfo', { page: currentPage, totalPages: totalPages })}</span>
|
|
835
|
+
<span class="page-jump">
|
|
836
|
+
<label>Go to</label>
|
|
837
|
+
<input type="number" id="page-jump-input" min="1" max="${totalPages}" value="${currentPage}"
|
|
838
|
+
onkeydown="if(event.key==='Enter')jumpToPage()">
|
|
839
|
+
<button onclick="jumpToPage()">Go</button>
|
|
840
|
+
</span>
|
|
780
841
|
<button ${currentPage >= totalPages ? 'disabled' : ''} onclick="changePage(${currentPage + 1})">${t('pagination.next')}</button>
|
|
781
842
|
`;
|
|
782
843
|
|
|
@@ -789,6 +850,17 @@ function changePage(page) {
|
|
|
789
850
|
loadSkills();
|
|
790
851
|
}
|
|
791
852
|
|
|
853
|
+
function jumpToPage() {
|
|
854
|
+
const input = document.getElementById('page-jump-input');
|
|
855
|
+
if (!input) return;
|
|
856
|
+
const page = parseInt(input.value);
|
|
857
|
+
if (isNaN(page) || page < 1 || page > state.totalPages) {
|
|
858
|
+
input.value = state.currentPage;
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
changePage(page);
|
|
862
|
+
}
|
|
863
|
+
|
|
792
864
|
// -----------------------------------------------------------------------------
|
|
793
865
|
// Platforms
|
|
794
866
|
// -----------------------------------------------------------------------------
|
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>
|
package/gui/style.css
CHANGED
|
@@ -366,6 +366,54 @@ body {
|
|
|
366
366
|
font-size: 0.85rem;
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
.pagination .page-jump {
|
|
370
|
+
display: flex;
|
|
371
|
+
align-items: center;
|
|
372
|
+
gap: 4px;
|
|
373
|
+
color: var(--text-muted);
|
|
374
|
+
font-size: 0.85rem;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.pagination .page-jump input {
|
|
378
|
+
width: 50px;
|
|
379
|
+
padding: 5px 6px;
|
|
380
|
+
background: var(--bg-secondary);
|
|
381
|
+
border: 1px solid var(--border-color);
|
|
382
|
+
color: var(--text-secondary);
|
|
383
|
+
border-radius: 4px;
|
|
384
|
+
font-size: 0.85rem;
|
|
385
|
+
text-align: center;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.pagination .page-jump input:focus {
|
|
389
|
+
outline: none;
|
|
390
|
+
border-color: var(--accent);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.pagination .page-jump button {
|
|
394
|
+
padding: 5px 10px;
|
|
395
|
+
background: var(--bg-card);
|
|
396
|
+
border: 1px solid var(--border-color);
|
|
397
|
+
color: var(--text-secondary);
|
|
398
|
+
border-radius: 4px;
|
|
399
|
+
cursor: pointer;
|
|
400
|
+
font-size: 0.8rem;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.pagination .page-jump button:hover {
|
|
404
|
+
background: var(--bg-hover);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* Hide spinner for number input */
|
|
408
|
+
.pagination .page-jump input::-webkit-outer-spin-button,
|
|
409
|
+
.pagination .page-jump input::-webkit-inner-spin-button {
|
|
410
|
+
-webkit-appearance: none;
|
|
411
|
+
margin: 0;
|
|
412
|
+
}
|
|
413
|
+
.pagination .page-jump input[type=number] {
|
|
414
|
+
-moz-appearance: textfield;
|
|
415
|
+
}
|
|
416
|
+
|
|
369
417
|
/* -----------------------------------------------------------------------------
|
|
370
418
|
Skill 详情视图
|
|
371
419
|
----------------------------------------------------------------------------- */
|