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 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
- const skills = skillDetails.filter(Boolean);
2129
- const totalPages = Math.ceil(total / limit) || 1;
2130
- jsonResponse(res, 200, { skills, page, totalPages, total, fetchErrors });
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
  ----------------------------------------------------------------------------- */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itismyskillmarket",
3
- "version": "1.3.21",
3
+ "version": "1.3.23",
4
4
  "description": "Cross-platform skill manager for AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {