open source is social v-it.org
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(explore): differentiate cap and skill list rendering

+184 -27
+184 -27
explore/public/index.html
··· 217 217 text-decoration: underline; 218 218 } 219 219 220 + .cap-title-row { 221 + display: flex; 222 + align-items: baseline; 223 + gap: 4px; 224 + } 225 + 226 + .cap-title-row .cap-title { 227 + flex: 1; 228 + min-width: 0; 229 + margin-bottom: 0; 230 + } 231 + 232 + .cap-title-row .cap-title a { 233 + color: inherit; 234 + text-decoration: none; 235 + } 236 + 237 + .cap-title-row .cap-title a:hover { 238 + text-decoration: underline; 239 + } 240 + 241 + .cap-time { 242 + font-size: 0.8rem; 243 + color: #9ca3af; 244 + white-space: nowrap; 245 + margin-left: auto; 246 + } 247 + 220 248 .skill-item { 221 249 padding: 12px 0; 222 250 border-bottom: 1px solid #e5e7eb; ··· 231 259 margin-bottom: 4px; 232 260 } 233 261 234 - .skill-meta { 262 + .skill-name-row { 263 + display: flex; 264 + align-items: baseline; 265 + gap: 4px; 266 + } 267 + 268 + .skill-name-row .skill-title { 269 + flex: 1; 270 + min-width: 0; 271 + margin-bottom: 0; 272 + } 273 + 274 + .skill-name-row .skill-title a { 275 + color: inherit; 276 + text-decoration: none; 277 + } 278 + 279 + .skill-name-row .skill-title a:hover { 280 + text-decoration: underline; 281 + } 282 + 283 + .skill-version { 284 + font-size: 0.8rem; 285 + color: #9ca3af; 286 + white-space: nowrap; 287 + } 288 + 289 + .skill-desc-truncated { 235 290 font-size: 0.85rem; 236 291 color: #6b7280; 292 + white-space: nowrap; 293 + overflow: hidden; 294 + text-overflow: ellipsis; 295 + margin: 2px 0; 237 296 } 238 297 239 - .skill-tag { 240 - display: inline-block; 241 - background: #e5e7eb; 242 - color: #374151; 243 - font-size: 0.75rem; 244 - padding: 1px 6px; 245 - border-radius: 3px; 246 - margin-right: 4px; 298 + .skill-footer { 299 + display: flex; 300 + align-items: center; 301 + gap: 8px; 302 + margin-top: 4px; 303 + } 304 + 305 + .skill-footer .tag-pills { 306 + flex: 1; 307 + min-width: 0; 308 + } 309 + 310 + .skill-action { 311 + font-size: 0.8rem; 312 + color: var(--vit-green-deep); 313 + text-decoration: none; 314 + white-space: nowrap; 315 + margin-left: auto; 316 + } 317 + 318 + .skill-action:hover { 319 + text-decoration: underline; 247 320 } 248 321 249 322 .beacon-item { ··· 300 373 margin-top: 4px; 301 374 } 302 375 376 + .activity-item { 377 + padding: 12px 0; 378 + border-bottom: 1px solid #e5e7eb; 379 + } 380 + 381 + .activity-item:last-child { 382 + border-bottom: none; 383 + } 384 + 385 + .activity-title { 386 + font-weight: 600; 387 + margin-bottom: 4px; 388 + } 389 + 390 + .activity-meta { 391 + font-size: 0.85rem; 392 + color: #6b7280; 393 + } 394 + 303 395 .empty-state { 304 396 color: #6b7280; 305 397 padding: 32px 0; ··· 390 482 grid-template-columns: 1fr; 391 483 } 392 484 485 + .cap-title-row { 486 + flex-wrap: wrap; 487 + } 488 + 489 + .cap-time { 490 + font-size: 0.75rem; 491 + } 492 + 493 + .skill-name-row { 494 + flex-wrap: wrap; 495 + } 496 + 497 + .skill-footer { 498 + flex-wrap: wrap; 499 + } 500 + 393 501 .beacon-item { 394 502 flex-direction: column; 395 503 gap: 4px; ··· 509 617 .kind-badge.kind-security { 510 618 background-color: #fee2e2; 511 619 color: #991b1b; 620 + } 621 + 622 + .cap-title-row .kind-badge { 623 + margin-left: 0; 624 + margin-right: 4px; 625 + text-transform: lowercase; 512 626 } 513 627 514 628 .command-row { ··· 662 776 return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); 663 777 } 664 778 779 + function kindClassSuffix(kind) { 780 + if (!kind) return ''; 781 + return String(kind) 782 + .toLowerCase() 783 + .replace(/[^a-z0-9_-]+/g, '-') 784 + .replace(/^-+|-+$/g, ''); 785 + } 786 + 665 787 async function api(path) { 666 788 const res = await fetch('/api/' + path); 667 789 return res.json(); ··· 706 828 } 707 829 708 830 function renderCapItem(cap) { 709 - const title = esc(cap.title) || 'untitled'; 710 - const name = esc(displayName(cap)); 711 - const ref = cap.ref ? '<code>' + esc(cap.ref) + '</code>' : ''; 712 - const beacon = cap.beacon ? '<a href="#beacon/' + encodeURIComponent(cap.beacon) + '">' + esc(cap.beacon) + '</a>' : ''; 713 - const time = timeAgo(cap.created_at); 714 - const metaParts = [name, beacon, ref, time].filter(Boolean); 715 - const titleHtml = cap.ref 831 + var kind = null; 832 + if (cap.record_json) { 833 + try { kind = JSON.parse(cap.record_json).kind; } catch(e) {} 834 + } 835 + var kindClass = kindClassSuffix(kind); 836 + var kindBadge = kind ? '<span class="kind-badge' + (kindClass ? ' kind-' + kindClass : '') + '">' + esc(kind) + '</span>' : ''; 837 + var title = esc(cap.title) || 'untitled'; 838 + var titleHtml = cap.ref 716 839 ? '<a href="#cap/' + encodeURIComponent(cap.ref) + '">' + title + '</a>' 717 840 : title; 718 - return '<div class="cap-item"><div class="cap-title">' + titleHtml + '</div><div class="cap-meta">' + metaParts.join(' · ') + '</div></div>'; 841 + var time = '<span class="cap-time">' + timeAgo(cap.created_at) + '</span>'; 842 + var beacon = cap.beacon ? '<a href="#beacon/' + encodeURIComponent(cap.beacon) + '">' + esc(cap.beacon) + '</a>' : ''; 843 + var name = '@' + esc(displayName(cap)); 844 + var metaParts = [beacon, name].filter(Boolean); 845 + return '<div class="cap-item">' + 846 + '<div class="cap-title-row">' + kindBadge + '<div class="cap-title">' + titleHtml + '</div>' + time + '</div>' + 847 + '<div class="cap-meta">' + metaParts.join(' · ') + '</div>' + 848 + '</div>'; 719 849 } 720 850 721 851 function renderSkillItem(skill) { 722 - const name = esc(skill.name) || 'unnamed'; 723 - const desc = skill.description ? '<div>' + esc(skill.description) + '</div>' : ''; 724 - const author = esc(displayName(skill)); 725 - const version = skill.version ? 'v' + esc(skill.version) : ''; 726 - const tags = skill.tags ? skill.tags.split(',').map(function(t) { return '<span class="skill-tag">' + esc(t.trim()) + '</span>'; }).join('') : ''; 727 - const time = timeAgo(skill.created_at); 728 - const metaParts = [author, version, time].filter(Boolean); 729 - return '<div class="skill-item"><div class="skill-title"><a href="#skill/' + encodeURIComponent(skill.name) + '">' + name + '</a></div>' + desc + '<div class="skill-meta">' + metaParts.join(' · ') + (tags ? ' · ' + tags : '') + '</div></div>'; 852 + var name = esc(skill.name) || 'unnamed'; 853 + var version = skill.version ? '<span class="skill-version">v' + esc(skill.version) + '</span>' : ''; 854 + var nameLink = '<a href="#skill/' + encodeURIComponent(skill.name) + '">/' + name + '</a>'; 855 + 856 + var desc = skill.description 857 + ? '<div class="skill-desc-truncated">' + esc(skill.description) + '</div>' 858 + : ''; 859 + 860 + var record = null; 861 + if (skill.record_json) { 862 + try { record = JSON.parse(skill.record_json); } catch(e) {} 863 + } 864 + var tags = []; 865 + if (record && record.tags && Array.isArray(record.tags)) { 866 + tags = record.tags; 867 + } else if (skill.tags) { 868 + tags = skill.tags.split(','); 869 + } 870 + 871 + var tagHtml = ''; 872 + if (tags.length > 0) { 873 + tagHtml = '<div class="tag-pills">' + tags.map(function(t) { 874 + var tag = t.trim ? t.trim() : t; 875 + return tag ? '<a href="#skills?tag=' + encodeURIComponent(tag) + '" class="tag-pill">' + esc(tag) + '</a>' : ''; 876 + }).filter(Boolean).join('') + '</div>'; 877 + } 878 + 879 + var learnLink = '<a href="#skill/' + encodeURIComponent(skill.name) + '" class="skill-action">learn →</a>'; 880 + 881 + return '<div class="skill-item">' + 882 + '<div class="skill-name-row"><div class="skill-title">' + nameLink + '</div>' + version + '</div>' + 883 + desc + 884 + '<div class="skill-footer">' + tagHtml + learnLink + '</div>' + 885 + '</div>'; 730 886 } 731 887 732 888 function appendLoadMore(el) { ··· 807 963 808 964 var kindBadge = ''; 809 965 if (kind) { 810 - kindBadge = '<span class="kind-badge kind-' + esc(kind) + '">' + esc(kind) + '</span>'; 966 + var kindClass = kindClassSuffix(kind); 967 + kindBadge = '<span class="kind-badge' + (kindClass ? ' kind-' + kindClass : '') + '">' + esc(kind) + '</span>'; 811 968 } 812 969 813 970 var title = esc(cap.title) || 'untitled'; ··· 1033 1190 if (events.length > 0) { 1034 1191 activity = '<div class="recent-activity"><h3>recent activity</h3>' + 1035 1192 events.map(function(e) { 1036 - return '<div class="cap-item"><div class="cap-title">' + esc(e.label) + '</div><div class="cap-meta">' + esc(e.type) + ' · ' + esc(e.author) + ' · ' + timeAgo(e.time) + '</div></div>'; 1193 + return '<div class="activity-item"><div class="activity-title">' + esc(e.label) + '</div><div class="activity-meta">' + esc(e.type) + ' · ' + esc(e.author) + ' · ' + timeAgo(e.time) + '</div></div>'; 1037 1194 }).join('') + '</div>'; 1038 1195 } 1039 1196